LWC Posted November 9, 2024 Posted November 9, 2024 (edited) 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: 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). 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) Automated the record sorting (by Priority, Weight, etc.) Detecting the router's address directly (with this) instead of relying on the registry Used a more direct detection of the first integer of the router's IPv4 address Deleted lots and lots of unneeded linebreaks Fixed spelling errors Removed Case 11 since there's no 11 there Defined UDPStartup() just once instead of multiple times Likewise for UDPShutdown() Shortened the code for both $binarydom and BinaryMid (taken from here) 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 expandcollapse popup#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 November 10, 2024 by LWC Fixed link
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now