Hilfe mit UDP Broadcast

  • Hallo,
    ich habe eine Software, die verschiedene Modbus/TCP-Server im Netzwerk finden kann. Diese Funktionalität hätte ich gerne in AutoIt nachgebaut, nur eben bisher ohne Erfolg.
    Ich habe Wireshark benutzt um zu sehen wie das Programm das macht. Und zwar wird via UDP-Broadcast eine 8-Byte Meldung an 255.255.255.255 geschickt. Danach melden sich die einzelnen Geräte nacheinander und schicken ihre Konfiguration (IP, Subnet, Gateway) und ihre Produktnummer zurück. Der PC hat den Port 17009, das Modbus-Gerät 17008.
    Wenn ich nun das ganze mit AutoIt versuche, dann ist der ausgehende Port immer ein anderer, deswegen vermute ich, dass ich nichts von den Geräten zurückerhalte. Kann ich irgendwie den ausgehenden Port bestimmen? Mit UDPBind scheint es nicht zu funktionieren.

    Hier noch eines meiner Test-Versuchsskripte:

    Spoiler anzeigen
    [autoit]

    UDPStartup()

    [/autoit] [autoit][/autoit] [autoit]

    Global $ThisPCIP = @IPAddress1

    [/autoit] [autoit][/autoit] [autoit]

    Global $aSocket2 = UDPBind($ThisPCIP, 17009)
    If @error <> 0 Then
    ConsoleWrite("error opening $aSocket2, @error = " & @error & @CRLF)
    Exit
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    Global $ThatPCIP = "255.255.255.255" ;broadcast

    [/autoit] [autoit][/autoit] [autoit]

    Global $aSocket = UDPOpen($ThatPCIP, 17008, 1)
    If @error <> 0 Then
    ConsoleWrite("error opening $socket" & @CRLF)
    Exit
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    If UDPSend($aSocket, Binary("0x3101FFFFFFFFFFFF")) = 0 Then
    MsgBox(0, "ERROR", "Error while sending UDP message: " & @error)
    Exit
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    $data = UDPRecv($aSocket2, 512)
    If $data <> "" Then
    MsgBox(0, "UDP DATA", $data, 1)
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    UDPCloseSocket($aSocket)
    UDPCloseSocket($aSocket2)
    UDPShutdown()

    [/autoit]

    Hat irgendwer eine Idee, oder zumindest mehr Erfahrung damit? Muss ich die Funktionen aus Ws2_32.dll wrappen, um zum Ziel zu gelangen?

    Danke für eure Hilfe!

  • Nun diesen Port zu ändern dürfte nicht allzu einfach sein. Soweit ich weis werden die ausgehenden Ports direkt vom TCP/IP-Stack gewählt und der sitzt doch ziemlich tief im OS. So wie du es jetzt gemacht hast müsste es aber eigentlich auch gehen. Ich denke der Fehler liegt woanders. Wo kann ich dir aber auch nicht sagen. Von dieser Methode habe ich noch nie was gehört.

    Gruss Shadowigor

  • Hallo funkey,
    das Problem kenne ich nur zu gut. Hättest du allerdings die Suche benutzt, dann hättest du schon die Lösung gehabt ;)

    [ gelöst ] UDP-Brodcast - Problem Source-Port
    -> Achtung: Den Code von Post 8 verwenden!!!

    Wenn es Probleme gibt oder es funktioniert, lass es uns wissen.

    Gruß
    Homer J. S.

    ...wenn die Donuts auch nur halb so gut schmecken wie sie aussehen, dann sehen sie doppelt so gut aus wie sie schmecken...

  • Hallo, danke für eure Antworten!
    @m-obi: Das werde ich später noch testen, aber dafür braucht man eben WinPcap, und das wollte ich nicht extra installieren.

    Homer J. S.: Die Suche habe ich benutzt, aber dieser Thread ist mir entgangen. Leider hilft mir das auch nicht, weil sich die Geräte anscheinend nur zurückmelden, wenn der Destination-Port der richtige ist. Also ich bekomme immer noch keine Antwort von den Geräten, auf keinem Port. Schade.

  • funkey,
    das ist natürlich nicht schön, das die Geräte nur auf einen
    gewissen Port antworten, der vorher auch mit versendet wird. Eine Lösung für
    dein Problem würde mich auch sehr interessieren. Also wenn du eine Lösung hast,
    dann poste diese bitte.

    Gruß
    Homer J. S.

    PS: Deine alternative Lösung für den Port auszulesen gefällt
    mir sehr gut - Danke!!

    ...wenn die Donuts auch nur halb so gut schmecken wie sie aussehen, dann sehen sie doppelt so gut aus wie sie schmecken...

  • funkey
    nur mal theoretisch, wenn so du so oft eine UDP-Verbindung herstellst bist diese deinem Port 17009 entspricht, dann kannst du dein Skrip auf Richtigkeit überprüfen. Den der Port erhöht sich immer um eins bei jeder neuen Verbindung. Das ist zwar keine Dauerlösung, aber ein Ansatz. Wenn dieses Funktioniert, müsste man dem TCP-Stack "nur" dazu bringen der ID von Autoit einen festen Port zuzuordnen.


    Gruß
    Homer J. S.

    ...wenn die Donuts auch nur halb so gut schmecken wie sie aussehen, dann sehen sie doppelt so gut aus wie sie schmecken...

  • Hey, danke! Die Idee war sehr gut! Das ganze läuft dank _Ws2_GetLocalPort() innerhalb von wenigen Sekunden durch.
    Aber leider bekomme ich nur auf Win7 nur Ports von 49153 bis 65534 und auf WinXP von 1026 bis 5000!? Auch mit #RequireAdmin. Schade.

    Eine Lösung könnte sein, die Daten direkt RAW ins Netzwerk zu schicken. Also in meinem Fall alle 50 Bytes, nur 8 davon sind ja im Datenpaket. nur leider weiß ich noch nicht wie.

  • funkey

    Zitat

    Aber leider bekomme ich nur auf Win7 nur Ports von 49153 bis 65534 und auf WinXP von 1026 bis 5000!? Auch mit #RequireAdmin. Schade.

    Jetzt wo du es schreibst, fâllt mir auch ein, das ich das auch schonmal gelesen habe.
    Aber es kann doch nicht so schwer sein, einen festen Zielport mit AutoIt zu übergeben.

    Wenn mir noch was einfällt melde ich mich.

    Gruß
    Homer J. S.

    PS: Die Funktion _Ws2_GetLocalPort() geht deutlich schneller wie meine Variante. Diese werde ich fest in mein UDP-Broadcast Skript für xPort/wiPort aufnehmen. Danke!

    ...wenn die Donuts auch nur halb so gut schmecken wie sie aussehen, dann sehen sie doppelt so gut aus wie sie schmecken...

  • Hallo!
    Aufgrund des Threads [ offen ] Ausgehende IP erzwingen / ForceBindIP habe ich mich wieder mit einem alten Thema von mir beschäftigt und damit auch gelöst.
    Anbei der Source-Code um alle Siemens Sentron Geräte im Netzwerk über UDP-Broadcast zu ermitteln.

    Spoiler anzeigen
    [autoit]

    #region ;**** Directives created by AutoIt3Wrapper_GUI ****
    #AutoIt3Wrapper_Icon=PAC.ico
    #AutoIt3Wrapper_Res_Comment=Siemens Sentron-Geräte auflisten
    #AutoIt3Wrapper_Res_Description=Siemens Sentron-Geräte auflisten
    #AutoIt3Wrapper_Res_Fileversion=0.9
    #AutoIt3Wrapper_Res_LegalCopyright=funkey 2013
    #AutoIt3Wrapper_Res_Language=3079
    #AutoIt3Wrapper_Run_Obfuscator=y
    #Obfuscator_Parameters=/striponly
    #endregion ;**** Directives created by AutoIt3Wrapper_GUI ****
    #include <Array.au3>
    #include "socket_UDF.au3"

    [/autoit] [autoit][/autoit] [autoit]

    TCPStartup() ; WSAStartup
    Global $iSock = _socket($AF_INET, $SOCK_DGRAM, $IPPROTO_UDP)
    ;~ ConsoleWrite("Socket: " & $iSock & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    Global $tBroadCast = DllStructCreate("int broadcast")
    DllStructSetData($tBroadCast, "broadcast", 1)

    [/autoit] [autoit][/autoit] [autoit]

    Global $iSockError = _setsockopt($iSock, $SOL_SOCKET, $SO_BROADCAST, $tBroadCast) ; Broadcast erlauben
    If $iSockError Then
    ConsoleWrite("SetSockOpt error setting broadcast!. Windows Sockets Error Codes: " & _WSAGetLastError() & @CRLF)
    _closesocket($iSock)
    TCPShutdown() ; WSACleanup
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    Global $tTimeVal = DllStructCreate("DWORD timeout")
    DllStructSetData($tTimeVal, "timeout", 200) ; Timeout von 200ms

    [/autoit] [autoit][/autoit] [autoit]

    $iSockError = _setsockopt($iSock, $SOL_SOCKET, $SO_RCVTIMEO, $tTimeVal) ; Timeout für recv einstellen
    If $iSockError Then
    ConsoleWrite("SetSockOpt error setting recv timeout!. Windows Sockets Error Codes: " & _WSAGetLastError() & @CRLF)
    _closesocket($iSock)
    TCPShutdown() ; WSACleanup
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    Global $iBind = _bind($iSock, @IPAddress1, 17009) ; Lokale IP-Adresse und Port zu verwenden
    If $iBind Then
    ConsoleWrite("Bind error: " & $iBind & @CRLF) ; 0 ist OK
    _closesocket($iSock)
    TCPShutdown() ; WSACleanup
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    ;~ ConsoleWrite("Lokaler Port: " & _Ws2_GetLocalPort($iSock) & @CRLF)

    [/autoit] [autoit][/autoit] [autoit]

    Global $iConnect = _connect($iSock, "255.255.255.255", 17008) ;Broadcast auf Port 17008
    If $iConnect Then
    ConsoleWrite("Connect Error! Windows Sockets Error Codes: " & _WSAGetLastError() & @CRLF)
    _closesocket($iSock)
    TCPShutdown() ; WSACleanup
    EndIf

    [/autoit] [autoit][/autoit] [autoit]

    Global $bToSend = Binary("0x3101FFFFFFFFFFFF") ; Sentron-Geräte meldet euch!! ;)
    Global $tSendBuffer = DllStructCreate("byte[" & BinaryLen($bToSend) & "]")
    DllStructSetData($tSendBuffer, 1, $bToSend)

    [/autoit] [autoit][/autoit] [autoit]

    Global $iSend = _send($iSock, $tSendBuffer)

    [/autoit] [autoit][/autoit] [autoit]

    Global $tRecvBuffer = DllStructCreate("byte[512]")
    Global $iRecv
    Global $aData[1][6] = [["MAC", "IP", "Subnetmask", "Gateway", "Bestellnummer", "Infotext"]]
    Global $iDevices = 0

    [/autoit] [autoit][/autoit] [autoit]

    While 1
    $iRecv = _recv($iSock, $tRecvBuffer)
    If $iRecv = -1 Then ExitLoop ; Timeout

    [/autoit] [autoit][/autoit] [autoit]

    $iDevices += 1
    ReDim $aData[$iDevices + 1][6]

    [/autoit] [autoit][/autoit] [autoit]

    $aData[$iDevices][5] = DllStructGetData($tRecvBuffer, 1)
    $aData[$iDevices][0] = _BytesRecvToMAC($aData[$iDevices][5])
    $aData[$iDevices][1] = _BytesRecvToIP($aData[$iDevices][5], 9)
    $aData[$iDevices][2] = _BytesRecvToIP($aData[$iDevices][5], 13)
    $aData[$iDevices][3] = _BytesRecvToIP($aData[$iDevices][5], 17)
    $aData[$iDevices][4] = _BytesRecvToString($aData[$iDevices][5], 21, 18)
    $aData[$iDevices][5] = _BytesRecvToString($aData[$iDevices][5], 41, $iRecv - 41)
    WEnd

    [/autoit] [autoit][/autoit] [autoit]

    _closesocket($iSock)
    TCPShutdown() ; WSACleanup

    [/autoit] [autoit][/autoit] [autoit]

    _ArraySort($aData, 0, 1, 0, 1) ; Anhand der IP-Adresse sortieren
    _ArrayDisplay($aData, "Gefundene Geräte")

    [/autoit] [autoit][/autoit] [autoit]

    Func _BytesRecvToIP($bBytes, $iOffset)
    Return StringFormat("%i.%i.%i.%i", BinaryMid($bBytes, $iOffset, 1), BinaryMid($bBytes, $iOffset + 1, 1), BinaryMid($bBytes, $iOffset + 2, 1), BinaryMid($bBytes, $iOffset + 3, 1))
    EndFunc ;==>_BytesRecvToIP

    [/autoit] [autoit][/autoit] [autoit]

    Func _BytesRecvToMAC($bBytes)
    Return StringFormat("%02X-%02X-%02X-%02X-%02X-%02X", BinaryMid($bBytes, 3, 1), BinaryMid($bBytes, 4, 1), BinaryMid($bBytes, 5, 1), BinaryMid($bBytes, 6, 1), BinaryMid($bBytes, 7, 1), BinaryMid($bBytes, 8, 1))
    EndFunc ;==>_BytesRecvToMAC

    [/autoit] [autoit][/autoit] [autoit]

    Func _BytesRecvToString($bBytes, $iStart, $iLen)
    Return BinaryToString(BinaryMid($bBytes, $iStart, $iLen))
    EndFunc ;==>_BytesRecvToString

    [/autoit]