[SOLVED]IP resolve mDNS/Bonjour/zeroconf

Just starting out? Need help? Post your questions and find answers here.
Daveth
New User
New User
Posts: 4
Joined: Tue Jul 01, 2025 1:05 pm

[SOLVED]IP resolve mDNS/Bonjour/zeroconf

Post by Daveth »

Some time ago, I found a script in AutoIt that resolves IP addresses, and it works perfectly. However, I’ve recently switched to PureBasic and would like to implement the same functionality, but I can’t seem to get it working.

Below, I’ve included the working AutoIt script, as well as a PureBasic version that was translated from AutoIt using ChatGPT.
I believe I’m very close to a working implementation, but something still isn’t right. Could someone help me figure out what I’m missing?

AutoIT

Code: Select all

ConsoleWrite(_mDNS("p1meter-232322.local") & @CRLF)

Func _mDNS($domain)
	Local $return_ip, $domain_convert, $mDNS_msg

	$domain = StringReplace($domain , "http://", "")
    Local $domain_array = StringSplit($domain, ".")
    For $i = 1 To $domain_array[0]
        $domain_convert = $domain_convert & Hex(BinaryLen($domain_array[$i]), 2) & StringTrimLeft(StringToBinary($domain_array[$i]), 2)
	Next

    UDPStartup()

    Local $mDNS_sock = UDPOpen("224.0.0.251", 5353)
    If @error Then
        UDPShutdown()
        SetError(1)
        Return ""
    EndIf

    Local $query_time = TimerInit()

    UDPSend($mDNS_sock, "0x5A8901000001000000000000" & $domain_convert & "0000010001")
	ConsoleWrite("0x5A8901000001000000000000" & $domain_convert & "0000010001" & @CRLF)

    Do
        $mDNS_msg = UDPRecv($mDNS_sock, 512, 1)
    Until $mDNS_msg <> "" Or TimerDiff($query_time) > 1000

	UDPShutdown()

	For $i = 3 To 0 Step -1
		$return_ip = $return_ip & Dec(StringMid(BinaryMid($mDNS_msg, BinaryLen($mDNS_msg) - $i, 1), 3))
		If $i <> 0 Then
			$return_ip = $return_ip & "."
		EndIf
	Next

	If $return_ip <> "0.0.0.0" Then
		Return $return_ip
	Else
		SetError(2)
	EndIf
EndFunc
Purebasic

Code: Select all

EnableExplicit

Procedure.s SendMDNSQuery(domain$)
  Define i, j, sock, result$, sendBuf$, domainBin$, part$, segmentCount
  Define queryData$, recvBuf$, ipParts.s = "", ip$

  domain$ = ReplaceString(LCase(domain$), "http://", "")
  If Right(domain$, 6) <> ".local"
    domain$ + ".local"
  EndIf
  Debug "[DEBUG] Domain: " + domain$

  segmentCount = CountString(domain$, ".") + 1
  For i = 1 To segmentCount
    part$ = StringField(domain$, i, ".")
    domainBin$ + RSet(Hex(Len(part$), #PB_Byte), 2, "0")
    For j = 1 To Len(part$)
      domainBin$ + RSet(Hex(Asc(Mid(part$, j, 1))), 2, "0")
    Next
  Next
  ;domainBin$ + "00" ; NULL terminator

  queryData$ = "5A8901000001000000000000" + domainBin$ + "0000010001"
  
  Debug "[DEBUG] mDNS-query hexstring: " + queryData$

  ; -- Verbind naar mDNS multicast adres --
  sock = OpenNetworkConnection("224.0.0.251", 5353, #PB_Network_UDP)
  If Not sock
    Debug "[ERROR] Could't open Socket to mDNS-server."
    ProcedureReturn ""
  EndIf

  ; -- Send mDNS-query --
  For i = 0 To Len(queryData$) - 1 Step 2
    sendBuf$ + Chr(Val("$" + Mid(queryData$, i + 1, 2)))
  Next
  
  SendNetworkData(sock, @sendBuf$, Len(sendBuf$))
  Debug sendBuf$
  Debug "[DEBUG] Query send to 224.0.0.251:5353"

  ; -- Recieve anwser --
  Define start = ElapsedMilliseconds()
  While ElapsedMilliseconds() - start < 2000
    If NetworkClientEvent(sock) = #PB_NetworkEvent_Data
     
      recvBuf$ = Space(512)
      ReceiveNetworkData(sock, @recvBuf$, 512)
      Debug "[DEBUG] Recieve anwser (lengte = " + Str(Len(recvBuf$)) + ")"

      ; -- search last 4 bytes for IP --
      If Len(recvBuf$) >= 4
        For i = 3 To 0 Step -1
          ipParts + Str(PeekB(@recvBuf$ + Len(recvBuf$) - i - 1))
          If i <> 0
            ipParts + "."
          EndIf
        Next
        ip$ = ipParts
        Break
      EndIf
    EndIf
    Delay(10)
  Wend

  CloseNetworkConnection(sock)

  If ip$ = "" Or ip$ = "0.0.0.0"
    Debug "[DEBUG] No IP adres"
    ProcedureReturn ""
  EndIf

  ProcedureReturn ip$
EndProcedure


; =========[ Main ]=========
Define targetName$, resolvedIP$

resolvedIP$ = SendMDNSQuery("p1meter-232322.local")

If resolvedIP$ <> ""
  Debug "✅ IPadres " + targetName$ + " is: " + resolvedIP$
Else
  Debug "❌ No anwser from mDNS."
EndIf
Last edited by Daveth on Thu Jul 03, 2025 5:48 pm, edited 1 time in total.
User avatar
JHPJHP
Addict
Addict
Posts: 2257
Joined: Sat Oct 09, 2010 3:47 am

Re: IP resolve mDNS/Bonjour/zeroconf

Post by JHPJHP »

Hi Daveth,

While I appreciate AI and the contributions to programming, I normally wouldn't ask it to write an entire program (even a small one).

I pretty much started the same way you did, pasting the AutoIT code into ChartGPT and asking for a conversion to PureBasic. But I spent the next hour working with ChatGPT to craft the included code.

Note, most of my contributions were prompt related, steering the "coding ship" in the right direction, fixing obvious mistakes, and removing redundant code like already existing Structures.

My intent was to keep the code as close as possible to the original AI generated version. I'm not sure if this is the best solution or if it even works as I have no local devices to test.

Give it a go and post your results. Another AI experiment.

Code: Select all

EnableExplicit

;-----------------------------------------
; Constants and Macros
;-----------------------------------------
#AF_INET            = 2
#SOCK_DGRAM         = 2
#SOL_SOCKET         = $FFFF
#SO_REUSEADDR       = 4
#IPPROTO_IP         = 0
#IP_ADD_MEMBERSHIP  = 12
#INVALID_SOCKET     = -1

Macro PeekW_BE(ptr)
  ((PeekB(ptr) << 8) | PeekB(ptr + 1))
EndMacro

Macro FD_ZERO(fdset)
  fdset\fd_count = 0
EndMacro

Macro FD_SET(s, fdset)
  fdset\fd_array[0] = s
  fdset\fd_count = 1
EndMacro

;-----------------------------------------
; Imports from Ws2_32.lib
;-----------------------------------------
Import "Ws2_32.lib"
  WSAStartup(wVersionRequired.w, *lpWSAData)
  WSACleanup()
  socket(af.l, type.l, protocol.l)
  closesocket(s.l)
  setsockopt(s.l, level.l, optname.l, *optval, optlen.l)
  bind(s.l, *name, namelen.l)
  htons(hostshort.w)
  inet_addr(cp.p-utf8)
  sendto(s.l, *buf, len.l, flags.l, *to, tolen.l)

  CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
    select_(nfds.l, *readfds, *writefds, *exceptfds, *timeout) As "_select@20"
  CompilerElse
    select_(nfds.l, *readfds, *writefds, *exceptfds, *timeout) As "select"
  CompilerEndIf

  recvfrom(s.l, *buf, len.l, flags.l, *from, *fromlen)
EndImport

;-----------------------------------------
; Utility: Convert string to hex representation
;-----------------------------------------
Procedure.s StringToHex(text.s)
  Protected result.s = ""
  Protected i

  For i = 1 To Len(text)
    result + RSet(Hex(Asc(Mid(text, i, 1))), 2, "0")
  Next

  ProcedureReturn result
EndProcedure

;-----------------------------------------
; Send mDNS query and process response
;-----------------------------------------
Procedure.s mDNS(domain.s)
  Protected ws.WSAData, sock.l, addr.sockaddr_in, rcv_addr.sockaddr_in
  Protected addrlen.l = SizeOf(sockaddr_in)
  Protected queryHex.s, domain_convert.s, return_ip.s
  Protected i, byteVal, len, queryLength
  Protected *queryBinary, *buffer
  Protected BufferSize = 512
  Protected labelCount, opt.l
  Protected tv.timeval
  Protected fd.fd_set

  If WSAStartup($0202, @ws)
    Debug "WSAStartup failed"
    ProcedureReturn ""
  EndIf

  If Left(domain, 7) = "http://"
    domain = Mid(domain, 8)
  EndIf

  If Right(domain, 6) <> ".local"
    domain + ".local"
  EndIf

  labelCount = CountString(domain, ".") + 1
  Dim labels.s(labelCount - 1)

  For i = 0 To labelCount - 1
    labels(i) = StringField(domain, i + 1, ".")
    domain_convert + RSet(Hex(Len(labels(i))), 2, "0")
    domain_convert + StringToHex(labels(i))
  Next

  ; Keep same transaction ID (5A89) since it worked
  queryHex = "5A8901000001000000000000" + domain_convert + "0000010001"

  queryLength = Len(queryHex) / 2
  *queryBinary = AllocateMemory(queryLength)
  For i = 0 To queryLength - 1
    byteVal = Val("$" + Mid(queryHex, i * 2 + 1, 2))
    PokeB(*queryBinary + i, byteVal)
  Next

  sock = socket(#AF_INET, #SOCK_DGRAM, 0)
  If sock = #INVALID_SOCKET
    Debug "socket() failed"
    ProcedureReturn ""
  EndIf

  opt = 1
  setsockopt(sock, #SOL_SOCKET, #SO_REUSEADDR, @opt, SizeOf(opt))

  ; Bind to ephemeral port (0), not port 5353
  addr\sin_family = #AF_INET
  addr\sin_port = htons(0) ; ephemeral port
  addr\sin_addr = 0
  If bind(sock, @addr, SizeOf(addr)) <> 0
    Debug "bind() failed"
    closesocket(sock)
    ProcedureReturn ""
  EndIf

  ; Do NOT join multicast group
  ; mreq\imr_multiaddr = inet_addr("224.0.0.251")
  ; mreq\imr_interface = 0
  ; setsockopt(sock, #IPPROTO_IP, #IP_ADD_MEMBERSHIP, @mreq, SizeOf(mreq))

  addr\sin_addr = inet_addr("224.0.0.251")
  addr\sin_port = htons(5353)
  sendto(sock, *queryBinary, queryLength, 0, @addr, SizeOf(addr))
  Debug "Sent query to 224.0.0.251:5353"

  tv\tv_sec = 2
  tv\tv_usec = 0
  FD_ZERO(fd)
  FD_SET(sock, fd)

  *buffer = AllocateMemory(BufferSize)

  If select_(sock + 1, @fd, 0, 0, @tv) > 0
    len = recvfrom(sock, *buffer, BufferSize, 0, @rcv_addr, @addrlen)
    If len > 0
      Debug "Received response length: " + Str(len)

      ; Simple extraction: last 4 bytes
      If len >= 4
        return_ip = Str(PeekA(*buffer + len - 4)) + "." +
                    Str(PeekA(*buffer + len - 3)) + "." +
                    Str(PeekA(*buffer + len - 2)) + "." +
                    Str(PeekA(*buffer + len - 1))
        Debug "Quick parsed IP: " + return_ip
      EndIf
    EndIf
  Else
    Debug "No response."
  EndIf

  FreeMemory(*buffer)
  FreeMemory(*queryBinary)
  closesocket(sock)
  WSACleanup()

  If return_ip = "" Or return_ip = "0.0.0.0"
    Debug "No valid IP received."
    ProcedureReturn ""
  EndIf

  ProcedureReturn return_ip
EndProcedure

;-----------------------------------------
; Example usage
;-----------------------------------------
Define resolvedIP$

resolvedIP$ = mDNS("p1meter-232322.local")

If resolvedIP$ <> ""
  Debug "IP Address: " + resolvedIP$
Else
  Debug "No response from mDNS."
EndIf
Last edited by JHPJHP on Thu Jul 03, 2025 3:41 pm, edited 3 times in total.

If you're not investing in yourself, you're falling behind.

My PureBasic StuffFREE STUFF, Scripts & Programs.
My PureBasic Forum ➤ Questions, Requests & Comments.
Daveth
New User
New User
Posts: 4
Joined: Tue Jul 01, 2025 1:05 pm

Re: IP resolve mDNS/Bonjour/zeroconf

Post by Daveth »

Thank you, JHPjHP, for taking the time to look at my code!
I was hoping ChatGPT would come up with something solid, since I haven’t been able to find any example code myself—not even something using UDP in Purebasic.
I tested your code and this is what I got back in the debug window:

Sent: 5A89010000010000000000000E70316D657465722D323332333232056C6F63616C0000010001
DNS header counts:
qdCount=1
anCount=0
nsCount=0
arCount=0
User avatar
JHPJHP
Addict
Addict
Posts: 2257
Joined: Sat Oct 09, 2010 3:47 am

Re: IP resolve mDNS/Bonjour/zeroconf

Post by JHPJHP »

Hi Daveth,

Definitely one of the downfalls of AI, while it can save time when everything goes right, it can also be a real time killer trying to debug if it doesn't.

I went a few more rounds with ChatGPT, the updated script can be found in my previous post.

Goodluck and welcome to the forum.

If you're not investing in yourself, you're falling behind.

My PureBasic StuffFREE STUFF, Scripts & Programs.
My PureBasic Forum ➤ Questions, Requests & Comments.
Daveth
New User
New User
Posts: 4
Joined: Tue Jul 01, 2025 1:05 pm

Re: IP resolve mDNS/Bonjour/zeroconf

Post by Daveth »

@JHPJHP
This version works better; I now receive information back, but it's incorrect.
The response length matches what it should be, but I don’t get a valid IP address in return.
This is what the debug screen shows:

Sent query to 224.0.0.251:5353
Received response length: 45
Quick parsed IP: -64.-88.-78.20
IP Address: -64.-88.-78.20

This is the IP address it should be returning: 192.168.178.20
Do you have any idea what might be wrong?

EDIT, I've got it to work

Code: Select all

EnableExplicit

;-----------------------------------------
; Constants and Macros
;-----------------------------------------
#AF_INET            = 2
#SOCK_DGRAM         = 2
#SOL_SOCKET         = $FFFF
#SO_REUSEADDR       = 4
#IPPROTO_IP         = 0
#IP_ADD_MEMBERSHIP  = 12
#INVALID_SOCKET     = -1

Macro PeekW_BE(ptr)
  ((PeekB(ptr) << 8) | PeekB(ptr + 1))
EndMacro

Macro FD_ZERO(fdset)
  fdset\fd_count = 0
EndMacro

Macro FD_SET(s, fdset)
  fdset\fd_array[0] = s
  fdset\fd_count = 1
EndMacro

;-----------------------------------------
; Imports from Ws2_32.lib
;-----------------------------------------
Import "Ws2_32.lib"
  WSAStartup(wVersionRequired.w, *lpWSAData)
  WSACleanup()
  socket(af.l, type.l, protocol.l)
  closesocket(s.l)
  setsockopt(s.l, level.l, optname.l, *optval, optlen.l)
  bind(s.l, *name, namelen.l)
  htons(hostshort.w)
  inet_addr(cp.p-utf8)
  sendto(s.l, *buf, len.l, flags.l, *to, tolen.l)

  CompilerIf #PB_Compiler_Processor = #PB_Processor_x86
    select_(nfds.l, *readfds, *writefds, *exceptfds, *timeout) As "_select@20"
  CompilerElse
    select_(nfds.l, *readfds, *writefds, *exceptfds, *timeout) As "select"
  CompilerEndIf

  recvfrom(s.l, *buf, len.l, flags.l, *from, *fromlen)
EndImport

;-----------------------------------------
; Utility: Convert string to hex representation
;-----------------------------------------
Procedure.s StringToHex(text.s)
  Protected result.s = ""
  Protected i

  For i = 1 To Len(text)
    result + RSet(Hex(Asc(Mid(text, i, 1))), 2, "0")
  Next

  ProcedureReturn result
EndProcedure

;-----------------------------------------
; Send mDNS query and process response
;-----------------------------------------
Procedure.s mDNS(domain.s)
  Protected ws.WSAData, sock.l, addr.sockaddr_in, rcv_addr.sockaddr_in
  Protected addrlen.l = SizeOf(sockaddr_in)
  Protected queryHex.s, domain_convert.s, return_ip.s
  Protected i, byteVal, len, queryLength
  Protected *queryBinary, *buffer
  Protected BufferSize = 512
  Protected labelCount, opt.l
  Protected tv.timeval
  Protected fd.fd_set

  If WSAStartup($0202, @ws)
    Debug "WSAStartup failed"
    ProcedureReturn ""
  EndIf

  If Left(domain, 7) = "http://"
    domain = Mid(domain, 8)
  EndIf

  If Right(domain, 6) <> ".local"
    domain + ".local"
  EndIf

  labelCount = CountString(domain, ".") + 1
  Dim labels.s(labelCount - 1)

  For i = 0 To labelCount - 1
    labels(i) = StringField(domain, i + 1, ".")
    domain_convert + RSet(Hex(Len(labels(i))), 2, "0")
    domain_convert + StringToHex(labels(i))
  Next

  ; Keep same transaction ID (5A89) since it worked
  queryHex = "5A8901000001000000000000" + domain_convert + "0000010001"

  queryLength = Len(queryHex) / 2
  *queryBinary = AllocateMemory(queryLength)
  For i = 0 To queryLength - 1
    byteVal = Val("$" + Mid(queryHex, i * 2 + 1, 2))
    PokeB(*queryBinary + i, byteVal)
  Next

  sock = socket(#AF_INET, #SOCK_DGRAM, 0)
  If sock = #INVALID_SOCKET
    Debug "socket() failed"
    ProcedureReturn ""
  EndIf

  opt = 1
  setsockopt(sock, #SOL_SOCKET, #SO_REUSEADDR, @opt, SizeOf(opt))

  ; Bind to ephemeral port (0), not port 5353
  addr\sin_family = #AF_INET
  addr\sin_port = htons(0) ; ephemeral port
  addr\sin_addr = 0
  If bind(sock, @addr, SizeOf(addr)) <> 0
    Debug "bind() failed"
    closesocket(sock)
    ProcedureReturn ""
  EndIf

  ; Do NOT join multicast group
  ; mreq\imr_multiaddr = inet_addr("224.0.0.251")
  ; mreq\imr_interface = 0
  ; setsockopt(sock, #IPPROTO_IP, #IP_ADD_MEMBERSHIP, @mreq, SizeOf(mreq))

  addr\sin_addr = inet_addr("224.0.0.251")
  addr\sin_port = htons(5353)
  sendto(sock, *queryBinary, queryLength, 0, @addr, SizeOf(addr))
  Debug "Sent query to 224.0.0.251:5353"

  tv\tv_sec = 2
  tv\tv_usec = 0
  FD_ZERO(fd)
  FD_SET(sock, fd)

  *buffer = AllocateMemory(BufferSize)

  If select_(sock + 1, @fd, 0, 0, @tv) > 0
    len = recvfrom(sock, *buffer, BufferSize, 0, @rcv_addr, @addrlen)
    If len > 0
      Debug "Received response length: " + Str(len)

      ; Simple extraction: last 4 bytes
      If len >= 4
        return_ip = Str(PeekB(*buffer + len - 4) & $FF) + "." +
            Str(PeekB(*buffer + len - 3) & $FF) + "." +
            Str(PeekB(*buffer + len - 2) & $FF) + "." +
            Str(PeekB(*buffer + len - 1) & $FF)
        Debug "Quick parsed IP: " + return_ip
      EndIf
    EndIf
  Else
    Debug "No response."
  EndIf

  FreeMemory(*buffer)
  FreeMemory(*queryBinary)
  closesocket(sock)
  WSACleanup()

  If return_ip = "" Or return_ip = "0.0.0.0"
    Debug "No valid IP received."
    ProcedureReturn ""
  EndIf

  ProcedureReturn return_ip
EndProcedure

;-----------------------------------------
; Example usage
;-----------------------------------------
Define resolvedIP$

resolvedIP$ = mDNS("envoy.local")

If resolvedIP$ <> ""
  Debug "IP Address: " + resolvedIP$
Else
  Debug "No response from mDNS."
EndIf
Last edited by Daveth on Thu Jul 03, 2025 3:45 pm, edited 1 time in total.
User avatar
JHPJHP
Addict
Addict
Posts: 2257
Joined: Sat Oct 09, 2010 3:47 am

Re: IP resolve mDNS/Bonjour/zeroconf

Post by JHPJHP »

Hi Daveth,

EDIT: Beat you to it :) See below, cleaner fix.

Replace:

Code: Select all

return_ip = Str(PeekB(*buffer + len - 4)) + "." +
            Str(PeekB(*buffer + len - 3)) + "." +
            Str(PeekB(*buffer + len - 2)) + "." +
            Str(PeekB(*buffer + len - 1))
With:

Code: Select all

return_ip = Str(PeekA(*buffer + len - 4)) + "." +
            Str(PeekA(*buffer + len - 3)) + "." +
            Str(PeekA(*buffer + len - 2)) + "." +
            Str(PeekA(*buffer + len - 1))
NOTE: My first post has been updated.

If you're not investing in yourself, you're falling behind.

My PureBasic StuffFREE STUFF, Scripts & Programs.
My PureBasic Forum ➤ Questions, Requests & Comments.
Daveth
New User
New User
Posts: 4
Joined: Tue Jul 01, 2025 1:05 pm

Re: IP resolve mDNS/Bonjour/zeroconf

Post by Daveth »

This gave the right IPadres back
return_ip = Str(PeekB(*buffer + offset) & $FF) + "." +
Str(PeekB(*buffer + offset + 1) & $FF) + "." +
Str(PeekB(*buffer + offset + 2) & $FF) + "." +
Str(PeekB(*buffer + offset + 3) & $FF)
This solution with PeekA won't give teh right IPadres.

Your helped me a lot with this and I finaly got it to work, thanks
User avatar
JHPJHP
Addict
Addict
Posts: 2257
Joined: Sat Oct 09, 2010 3:47 am

Re: IP resolve mDNS/Bonjour/zeroconf

Post by JHPJHP »

Hi Daveth,

I'm glad I could provide you with a solution...
Daveth wrote:Sent query to 224.0.0.251:5353
Received response length: 45
Quick parsed IP: -64.-88.-78.20
IP Address: -64.-88.-78.20

This is the IP address it should be returning: 192.168.178.20
Daveth wrote:This solution with PeekA won't give teh right IPadres.
... But I don't understand how PeekA won't return the correct values for you:

Code: Select all

*a = AllocateMemory(1)
*b = AllocateMemory(1)
*c = AllocateMemory(1)
*d = AllocateMemory(1)
PokeB(*a, -64)
PokeB(*b, -88)
PokeB(*c, -78)
PokeB(*d, 20)
Debug Str(PeekB(*a) & $FF) + "." +
      Str(PeekB(*b) & $FF) + "." +
      Str(PeekB(*c) & $FF) + "." +
      Str(PeekB(*d) & $FF)
Debug Str(PeekA(*a)) + "." +
      Str(PeekA(*b)) + "." +
      Str(PeekA(*c)) + "." +
      Str(PeekA(*d))
FreeMemory(*a)
FreeMemory(*b)
FreeMemory(*c)
FreeMemory(*d)
Anyway, it works, and the rest is merely a matter of semantics.

If you're not investing in yourself, you're falling behind.

My PureBasic StuffFREE STUFF, Scripts & Programs.
My PureBasic Forum ➤ Questions, Requests & Comments.
Post Reply