Jump to content

DNS records for a specific domain + Compress IPv6 addresses


LWC
 Share

Recommended Posts

Hi, I've found some threads about locating specific records (e.g. MX records), but they all had various issues plus no central way to do it all.
So here's my attempt at a UDF for it. The way it works is you tell it which records to pull and it sends the data to a dedicated function to decipher it.
In other words, if you give me hex values and dedicated functions for more types of records, I will just add them plug and play style.

It's based on finding MX records and its spin-offs finding A records and finding SRV records, but:

  1. You can pass which type of records to pull (currently MX, SRV, A and also AAAA which I've created on my own including an IPv6 Address Compressor function called CompressIPv6).
  2. You can pass fallbacks too (e.g. the default fits SMTP - first MX, if fails then A and lastly AAAA - note RFC doesn't state A comes before AAAA but it seems common sense until IPv6 becomes mainstream)
  3. Automated the record sorting (by Priority, Weight, etc.)
  4. Detecting the router's address directly (with this) instead of relying on the registry
  5. Used a more direct detection of the first integer of the router's IPv4 address
  6. Deleted lots and lots of unneeded linebreaks
  7. Fixed spelling errors
  8. Removed Case 11 since there's no 11 there
  9. Defined UDPStartup() just once instead of multiple times
  10. Likewise for UDPShutdown()
  11. Shortened the code for both $binarydom and BinaryMid (taken from here)
  12. Caught connection @error (taken from same link)

Examples

Local $domain = "gmail.com" ; change it to domain of your interest
;$domain = "_sip._udp.siplogin.de" ; if you want to test SRV

Local $dnsRecords = DNSRecords($domain)
Local $dnsRecords = DNSRecords($domain)

If IsArray($dnsRecords) Then
    _ArrayDisplay($dnsRecords)
Else
    MsgBox(0, "No Records", "No records for " & $domain)
EndIf

Code

#include <Array.au3>

Func DNSRecords($domain, $DNSRecord = "MX-A-AAAA") ; Can be MX, A or SRV but also X-Z, X-Y-Z, etc. (X record, but if fails try Y instead)
    Local $DNSRecords = StringSplit($DNSRecord, "-"), $binary_data
    Local $loc_serv = _GetGateway(), $loc_serv_final = ""
    If IsArray($loc_serv) Then
        if StringSplit($loc_serv[1], ".", 2)[0] <> "192" Then ; this kind of server is not what we want
            $loc_serv_final = $loc_serv[1]
        EndIf
    EndIf
    UDPStartup()
    for $i = 1 to $DNSRecords[0]
        $DNSRecord = $DNSRecords[$i]
        $binary_data = DNSQueryServer($domain, $DNSRecord, $loc_serv_final)
        if $binary_data <> -1 Then
            ExitLoop
        EndIf
    Next
    UDPShutdown()
    If $binary_data == -1 then
        Return -1
    EndIf

    Local $output
    switch $DNSRecord
        case "MX"
            $output = ExtractMXServerData($binary_data)
        case "A"
            $output = _DNS_ExtractAData($binary_data, $DNSRecord)
        case "AAAA"
            $output = _DNS_ExtractAData($binary_data, $DNSRecord)
        case "SRV"
            $output = ExtractSRVServerData($binary_data)
        case Else
            Return -1
    EndSwitch
    If @error Then Return -1
    if IsArray($output) then
        local $lastCol = ubound($output, 2)-1
        if $output[1][$lastCol] <> "" then
            _ArraySort($output, default, 1, default, $lastCol - ($DNSRecord == "SRV" ? 1 : 0))
            if $DNSRecord == "SRV" then
                Local $iStart = -1
                For $iX = 1 To $output[0][0]
                    If $output[$iX][$lastCol-1] = $output[$iX - 1][$lastCol-1] And $iStart = -1 Then
                        $iStart = $iX - 1
                    ElseIf $output[$iX][$lastCol-1] <> $output[$iX - 1][$lastCol-1] And $iStart <> -1 Then
                        _ArraySort($output, 1, $iStart, $iX - 1, $lastCol)
                        $iStart = -1
                    EndIf
                Next
                If $iStart <> -1 Then
                    _ArraySort($output, 1, $iStart, $iX - 1, $lastCol)
                EndIf
            EndIf
        EndIf
    EndIf

    Return $output
EndFunc   ;==>DNSRecords

Func DNSQueryServer($domain, $DNSRecord, $loc_serv)
    Local $domain_array
    $domain_array = StringSplit($domain, ".", 1)

    Local $binarydom
    For $el = 1 To $domain_array[0]
        $binarydom &= Hex(BinaryLen($domain_array[$el]), 2) & Hex(Binary($domain_array[$el]))
    Next
    $binarydom_suffix = "00" ; for example, 'gmail.com' will be '05676D61696C03636F6D00' and 'autoit.com' will be '066175746F697403636F6D00'

    Local $identifier = Hex(Random(0, 1000, 1), 2) ; random hex number serving as a handle for the data that will be received
    Local $server_bin = "0x00" & $identifier & "01000001000000000000" & $binarydom & $binarydom_suffix ; this is our query
    switch $DNSRecord
        case "MX"
            $server_bin &= "000F0001"
        case "A"
            $server_bin &= "00010001"
        case "AAAA"
            $server_bin &= "001C0001"
        case "SRV"
            $server_bin &= "00210001"
        case else
            Return -1
    EndSwitch
    Local $num_time, $data

    For $num_time = 1 To 10
        Local $query_server ; ten(10) DNS servers, we'll start with one that is our's default, if no response or local one switch to public free servers
        Switch $num_time
            Case 1
                $query_server = $loc_serv
            Case 2
                $query_server = "4.2.2.1"
            Case 3
                $query_server = "67.138.54.100"
            Case 4
                $query_server = "208.67.222.222"
            Case 5
                $query_server = "4.2.2.2"
            Case 6
                $query_server = "4.2.2.3"
            Case 7
                $query_server = "208.67.220.220"
            Case 8
                $query_server = "4.2.2.4"
            Case 9
                $query_server = "4.2.2.5"
            Case 10
                $query_server = "4.2.2.6"
        EndSwitch

        If $query_server <> "" Then
            Local $sock
            $sock = UDPOpen($query_server, 53)
            If @error Or $sock = -1 Then ; ok, that happens
                UDPCloseSocket($sock)
                ContinueLoop ; change server and try again
            EndIf

            UDPSend($sock, $server_bin) ; sending query

            Local $tik = 0
            Do
                $data = UDPRecv($sock, 512)
                $tik += 1
                Sleep(100)
            Until $data <> "" Or $tik = 8 ; waiting reasonable time for the response

            If $data And Hex(BinaryMid($data, 2, 1)) = $identifier Then
                Return $data ; if there is data for us, return
            EndIf
        EndIf
    Next

    Return -1
EndFunc   ;==>DNSQueryServer

Func ExtractMXServerData($binary_data)
    Local $num_answ = Dec(StringMid($binary_data, 15, 4)) ; representing number of answers provided by the server
    Local $arr = StringSplit($binary_data, "C00C000F0001", 1) ; splitting input; "C00C000F0001" - translated to human: "this is the answer for your MX query"

    If $num_answ <> $arr[0] - 1 Or $num_answ = 0 Then Return -1 ; dealing with possible options

    Local $pref[$arr[0]] ; preference number(s)
    Local $server[$arr[0]] ; server name(s)
    Local $output[1][2] = [[$arr[0] - 1, "MX"]]
    ; this goes out containing both server names and coresponding preference numbers

    Local $offset = 10 ; initial offset

    For $i = 2 To $arr[0]
        $arr[$i] = "0x" & $arr[$i] ; well, it is binary data
        $pref[$i - 1] = Dec(StringRight(BinaryMid($arr[$i], 7, 2), 4))
        $offset += BinaryLen($arr[$i - 1]) + 6 ; adding length of every past part plus length of that "C00C000F0001" used for splitting
        Local $array = ReadBinary($binary_data, $offset) ; extraction of server names starts here
        While $array[1] = 192 ; dealing with special case
            $array = ReadBinary($binary_data, $array[6] + 2)
        WEnd

        $server[$i - 1] &= $array[2] & "."
        While $array[3] <> 0 ; the end will obviously be at $array[3] = 0
            If $array[3] = 192 Then
                $array = ReadBinary($array[0], $array[4] + 2)
                If $array[3] = 0 Then
                    $server[$i - 1] &= $array[2]
                    ExitLoop
                Else
                    $server[$i - 1] &= $array[2] & "."
                EndIf
            Else
                $array = ReadBinary($array[0], $array[5])
                If $array[3] = 0 Then
                    $server[$i - 1] &= $array[2]
                    ExitLoop
                Else
                    $server[$i - 1] &= $array[2] & "."
                EndIf
            EndIf
        WEnd
        _ArrayAdd($output, $server[$i - 1])
        $output[ubound($output)-1][1] = $pref[$i - 1]
    Next

    Return $output ; two-dimensional array
EndFunc   ;==>ExtractMXServerData

Func _DNS_ExtractAData($bBinary, $DNSRecord)
    Local $aAnswers = StringSplit($bBinary, "C00C" & (($DNSRecord == "A") ? "0001" : "001C") & "0001", 1)
    If UBound($aAnswers) > 1 Then
        Local $ipLen = ($DNSRecord == "A") ? 4 : 16
        Local $bData = BinaryMid($bBinary, 6 + BinaryLen($aAnswers[1]) + 6)
        Local $tARaw = DllStructCreate("byte[" & BinaryLen($bData) & "]")
        DllStructSetData($tARaw, 1, $bData)
        Local $tAData = DllStructCreate("byte DataLength; byte IP[" & $ipLen & "];", DllStructGetPtr($tARaw))
        Local $ip[0]
        For $i = 1 To $ipLen Step ($DNSRecord == "A") ? 1 : 2
            _ArrayAdd($ip, ($DNSRecord == "A") ? DllStructGetData($tAData, "IP", $i) : Hex(DllStructGetData($tAData, "IP", $i) * 256 + DllStructGetData($tAData, "IP", $i + 1), 4))
        Next
        $ip = ($DNSRecord == "A") ? _ArrayToString($ip, ".") : CompressIPv6(_ArrayToString($ip, ":"))
        Local $output[2][2] = [[1, $DNSRecord], [$ip]]
        Return $output
    EndIf
    Return SetError(1, 0, "")
EndFunc   ;==>_DNS_ExtractAData

Func CompressIPv6($ip)
    ; Step 1: Remove leading zeros in each segment; replace '0000' with '0' if necessary
    Local $output = ""
    Local $segments = StringSplit($ip, ":", 2)
    For $i = 0 To UBound($segments) - 1
        $output &= ($i > 0 ? ":" : "") & (StringRegExpReplace($segments[$i], "\b0+", "") ? StringRegExpReplace($segments[$i], "\b0+", "") : "0")
    Next

    ; Step 2: Find all occurrences of continuous '0' segments
    Local $zeros = StringRegExp($output, "\b:?(?:0+:?){2,}", 3)
    Local $max = ""

    ; Step 3: Identify the longest occurrence of consecutive '0' segments
    For $i = 0 To UBound($zeros) - 1
        If StringReplace($zeros[$i], ":", "") > StringReplace($max, ":", "") Then
            $max = $zeros[$i]
        EndIf
    Next

    ; Step 4: Replace the longest sequence of '0' segments with '::' if found
    If $max <> "" Then $output = StringReplace($output, $max, "::", 1)

    ; Step 5: Return the compressed IPv6 address
    Return StringLower($output)
EndFunc

Func ExtractSRVServerData($binary_data)
    Local $num_answ = Dec(StringMid($binary_data, 15, 4)) ; representing number of answers provided by the server
    Local $arr = StringSplit($binary_data, "C00C00210001", 1) ; splitting input; "C00C000F0001" - translated to human: "this is the answer for your MX query"

    If $num_answ <> $arr[0] - 1 Or $num_answ = 0 Then Return -1 ; dealing with possible options

    Local $iPriority[$arr[0]]
    Local $iWeight[$arr[0]]
    Local $iPort[$arr[0]]
    Local $sTarget[$arr[0]] ; server name(s)
    ;Local $output[$arr[0] - 1][4] ; this goes out containing both server names and coresponding priority/weight and port numbers
    Local $output[1][4] = [[$arr[0]-1, "SRV"]] ; this goes out containing both server names and coresponding priority/weight and port numbers
    Local $offset = 14 ; initial offset

    For $i = 2 To $arr[0]

        $arr[$i] = "0x" & $arr[$i] ; well, it is binary data
        $iPriority[$i - 1] = Dec(StringRight(BinaryMid($arr[$i], 7, 2), 4))
        $iWeight[$i - 1] = Dec(StringRight(BinaryMid($arr[$i], 9, 2), 4))
        $iPort[$i - 1] = Dec(StringRight(BinaryMid($arr[$i], 11, 2), 4))
        $offset += BinaryLen($arr[$i - 1]) + 6 ; adding lenght of every past part plus lenght of that "C00C000F0001" used for splitting
        Local $array = ReadBinary($binary_data, $offset) ; extraction of server names starts here
        While $array[1] = 192 ; dealing with special case
            $array = ReadBinary($binary_data, $array[6] + 2)
        WEnd
        $sTarget[$i - 1] &= $array[2] & "."

        While $array[3] <> 0 ; the end will obviously be at $array[3] = 0
            If $array[3] = 192 Then
                $array = ReadBinary($array[0], $array[4] + 2)
                If $array[3] = 0 Then
                    $sTarget[$i - 1] &= $array[2]
                    ExitLoop
                Else
                    $sTarget[$i - 1] &= $array[2] & "."
                EndIf
            Else
                $array = ReadBinary($array[0], $array[5])
                If $array[3] = 0 Then
                    $sTarget[$i - 1] &= $array[2]
                    ExitLoop
                Else
                    $sTarget[$i - 1] &= $array[2] & "."
                EndIf

            EndIf
        WEnd

        local $result[][] = [[$sTarget[$i - 1], $iPort[$i - 1], $iPriority[$i - 1], $iWeight[$i - 1]]]
        _ArrayAdd($output, $result)
    Next

    Return $output ; two-dimensional array
EndFunc   ;==>ExtractSRVServerData

Func ReadBinary($binary_data, $offset)
    Local $len = Dec(StringRight(BinaryMid($binary_data, $offset - 1, 1), 2))
    Local $data_bin = BinaryMid($binary_data, $offset, $len)
    Local $checker = Dec(StringRight(BinaryMid($data_bin, 1, 1), 2))
    Local $data = BinaryToString($data_bin)
    Local $triger = Dec(StringRight(BinaryMid($binary_data, $offset + $len, 1), 2))
    Local $new_offset = Dec(StringRight(BinaryMid($binary_data, $offset + $len + 1, 1), 2))
    Local $another_offset = $offset + $len + 1
    Local $array[7] = [$binary_data, $len, $data, $triger, $new_offset, $another_offset, $checker] ; bit of this and bit of that
    Return $array
EndFunc   ;==>ReadBinary

Func _GetGateway()
    ; Based on:
    ; Rajesh V R
    ; v 1.0 01 June 2009

       ; use the adapter name as seen in the network connections dialog...
    Const $wbemFlagReturnImmediately = 0x10
    Const $wbemFlagForwardOnly = 0x20
    Local $colNICs="", $NIC, $strQuery, $objWMIService

    $strQuery = "SELECT * FROM Win32_NetworkAdapterConfiguration"
    $objWMIService = ObjGet("winmgmts:\\.\root\CIMV2")
    $colNICs = $objWMIService.ExecQuery($strQuery, "WQL", $wbemFlagReturnImmediately + $wbemFlagForwardOnly)

    Local $output[2]

    If IsObj($colNICs) Then
        For $NIC In $colNICs
            if isstring($NIC.DefaultIPGateway(0)) then
                $output[0] = $NIC.IPAddress(0)
                $output[1] = $NIC.DefaultIPGateway(0)
                ExitLoop
            endif
        Next
    Else
        Return SetError(-1, 0, "No WMI Objects Found for class: Win32_NetworkAdapterConfiguration")
    EndIf
    Return $output
EndFunc

 

Edited by LWC
Fixed link
Link to comment
Share on other sites

  • LWC changed the title to DNS records for a specific domain + Compress IPv6 addresses

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...