Jump to content

Selecting a source port for outgoing UDP connections


Go to solution Solved by TheBrainiac,

Recommended Posts

Greetings,

I have been trying to code a DNS server in AutoIt. I have learned the protocol and I am about to complete the implementation.

However, I have hit a road-block:

AutoIt's internal UDP functions do not allow choosing a source port for an outgoing UDP packet.

I'll illustrate the problem with this imaginary scenario where a Client with the IP 1.2.3.4 makes a query to the DNS Server 8.8.8.8:

  • Client's query packet goes from 1.2.3.4:65432 to 8.8.8.8:53
  • Server's response goes from 8.8.8.8:53 to 1.2.3.4:65432.

 

The problem with AutoIt's UDP functions is that to respond to the UDP Packet, I have to first create a UDP "Socket" using UDPOpen, which does not give me the option to keep 53 as the source port for the response packet.

The scenario with my AutoIt server now looks like this:

  • Client's query packet goes from 1.2.3.4:65432 to 8.8.8.8:53
  • Server opens a new UDP Socket to send data to the Client
  • Server's response goes from 8.8.8.8:61234 to 1.2.3.4:65432.

 

It seems that the devices I am using to make DNS requests to my server discard the response packet because of its strange source port.

I have learned that the native functions are unable to solve my problem, so I'd like to know whether there are any workarounds for this.

Thank you

-Brainiac

Edited by TheBrainiac
Link to comment
Share on other sites

check udpbind()

 

Unfortunately, UDPBind() can only be used to wait for incoming packets.

As for the code, I'll just dump the (extremely crude) script I have right now. I'll clean it up a lot once it's functional.

#include <ButtonConstants.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <StaticConstants.au3>
#include <WindowsConstants.au3>

Dim $dnslabels[10]
Dim $dnsentries[10]
Global $banned = ObjCreate("System.Collections.ArrayList")

#Region ### START Koda GUI section ### Form=C:\Users\Dennis\Documents\dns.kxf
$Form1 = GUICreate("DNS Visualizer", 266, 438, 192, 124)
For $i = 0 To 9
    $dnslabels[$i] = GUICtrlCreateLabel("", 8, 8 + 16 * $i, 252, 17)
Next
$Input1 = GUICtrlCreateInput("", 8, 200, 249, 21)
$Button1 = GUICtrlCreateButton("Block", 8, 224, 75, 25)
$Label3 = GUICtrlCreateLabel("Label3", 8, 256, 36, 17)
$Label4 = GUICtrlCreateLabel("Label4", 8, 280, 36, 17)
$Label5 = GUICtrlCreateLabel("Label5", 8, 304, 36, 17)
$Button2 = GUICtrlCreateButton("Unblock", 182, 224, 75, 25)
GUISetState(@SW_SHOW)
#EndRegion ### END Koda GUI section ###

GUIRegisterMsg($WM_COMMAND, "labelclick")
OnAutoItExitRegister("Cleanup")

UDPStartup()
$socket = UDPBind(@IPAddress1, 53)
If @error <> 0 Then Exit

While 1
    $packet = UDPRecv($socket, 512, 3)
    If $packet <> "" Then
        $data = $packet[0]
        ConsoleWrite($packet[1] & ":" & $packet[2] & @CRLF)
        visualize($data)
        $bdomain = BinaryMid($data, 13, BinaryLen($data) - 16)
        $sdomain = extractdomain($bdomain)
        ConsoleWrite("Extracted Domain Name: " & $sdomain & @CRLF)
        ConsoleWrite(@CRLF)
        If $banned.contains($sdomain) Then
            Dim $apacket[4]
            $apacket[0] = BinaryMid($data, 1, 2)
            $apacket[1] = "81800001000100000000"
            $apacket[2] = $bdomain
            $apacket[3] = "00010001C00C000100010000001C00045F735A8B"
            $rpacket = binarymerge($apacket)
            $sock = UDPOpen($packet[1], $packet[2])
            UDPSend($sock, $rpacket)
            ConsoleWrite("->Sent deceiving packet" & @CRLF)
            entry_push("(" & $sdomain & ")")
        Else
            $abc = UDPOpen("8.8.8.8", 53)
            UDPSend($abc, $data)
            While 1
                $dat = UDPRecv($abc, 512, 3)
                If $dat <> "" Then
                    visualize($dat[0])
                    ConsoleWrite(@CRLF & $dat[1] & ":" & $dat[2] & @CRLF)
                    ExitLoop
                EndIf
                Sleep(100)
            WEnd
            $sock = UDPOpen($packet[1], $packet[2])
            If @error Then MsgBox(16, "", "ARGON")
            ConsoleWrite(IsBinary($dat) & @CRLF)
            UDPSend($sock, $dat[0])
            UDPCloseSocket($abc)
            entry_push($sdomain)
        EndIf
    EndIf
    $data = ""
    Sleep(100)
WEnd

Func visualize($binary)
    $tab0 = ""
    $tab1 = ""
    ConsoleWrite("Len: " & BinaryLen($binary) & @CRLF)
    For $i = 1 To BinaryLen($binary)
        $tab0 &= Hex(BinaryMid($binary, $i, 1), 2) & " "
        $symbol = BinaryToString(BinaryMid($binary, $i, 1))
        If StringInStr("abcdefghijklmnopqrstuvwxyz0123456789.-", $symbol) Then
            $tab1 &= $symbol & " "
        Else
            $tab1 &= "? "
        EndIf
        If Mod($i, 8) = 0 Then
            ConsoleWrite($tab0 & @TAB & $tab1 & @CRLF)
            $tab0 = ""
            $tab1 = ""
        EndIf
    Next
    If Mod($i, 8) <> 0 Then
        ConsoleWrite($tab0)
        For $j = 0 To 8 - Mod($i, 8)
            ConsoleWrite("   ")
        Next
        ConsoleWrite(@TAB & $tab1)
    EndIf
    ConsoleWrite(@CRLF)
EndFunc   ;==>visualize

Func Cleanup()
    UDPCloseSocket($socket)
    UDPShutdown()
EndFunc   ;==>Cleanup

Func extractdomain($binary)
    $domain = ""
    For $k = 2 To BinaryLen($binary) - 1
        $symbol = BinaryToString(BinaryMid($binary, $k, 1))
        If StringInStr("abcdefghijklmnopqrstuvwxyz0123456789.-", $symbol) Then
            $domain &= $symbol
        Else
            $domain &= "."
        EndIf
    Next
    Return $domain
EndFunc   ;==>extractdomain

Func entry_push($entry)
    For $i = 0 To 8
        $dnsentries[$i] = $dnsentries[$i + 1]
        GUICtrlSetData($dnslabels[$i], $dnsentries[$i])
    Next
    $dnsentries[9] = $entry
    GUICtrlSetData($dnslabels[9], $entry)
EndFunc   ;==>entry_push

Func labelclick($hwnd, $nmsg, $wparam, $lparam)
    For $i = 0 To 9
        If $wparam = $dnslabels[$i] Then GUICtrlSetData($Input1, $dnsentries[$i])
    Next
    If $wparam = $Button1 Then $banned.add(GUICtrlRead($Input1))
    If $wparam = $Button2 Then $banned.remove(StringTrimLeft(StringTrimRight(GUICtrlRead($Input1), 1), 1))
EndFunc   ;==>labelclick

Func binarymerge($aBinary)
    Local $struct, $struct2
    $ptr = 0
    $len = 0
    For $i = 0 To UBound($aBinary) - 1
        $len += BinaryLen($aBinary[$i])
    Next
    $struct = DllStructCreate("byte[" & $len & "]")
    For $i = 0 To UBound($aBinary) - 1
        $struct2 = DllStructCreate("byte[" & BinaryLen($aBinary[$i]) & "]", DllStructGetPtr($struct) + $ptr)
        DllStructSetData($struct2, 1, $aBinary[$i])
        $ptr += BinaryLen($aBinary[$i])
        $struct2 = 0
    Next
    $binary = DllStructGetData($struct, 1)
    $struct = 0
    Return $binary
EndFunc

It contains a few goodies, such as a function that merges arrays of binary data into one variable and an Object that implements functions of an ArrayList.

The attentive observer will notice that the aim of this project is to sniff DNS requests from mobile devices in order to detect and block potentially unwanted background traffic and ads. The user can block and unblock domains at runtime (blocked domains point to 127.0.0.1).

Everything works except the UDP connections.

Link to comment
Share on other sites

Hi theBrainiac

have a look to this >UDF, maybe it can interest you,
Anyway, I tried to use it to resolve IP addresses in hostname without success  :( can someone test that UDF to see if it works with you?

p.s.
The UDPOpen() function has the "port" parameter to specify the port on which the created socket will be connected. What's wrong with it?

 

image.jpeg.9f1a974c98e9f77d824b358729b089b0.jpeg Chimp

small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Link to comment
Share on other sites

Thank you, I will have a look at that! It seems the program does kind of what I'm doing, except it translates the data back and forth between two different protocols.

 

The UDPOpen() function has the "port" parameter to specify the port on which the created socket will be connected.

 

What you are specifying in UDPOpen() is the remote port, that is the port where the packet is going to. What I have to set is the local port, which is the one where my packet comes from. UDPOpen() chooses a random port for that.

I suppose there isn't a way to build IP packets in Windows, is there? I mean that would be bad news for the internet.

Link to comment
Share on other sites

to reverse lookup you need to query the pointer record in the in-addr.arpa domain 

like this: (note the reversed IP address) 

D:\>nslookup
Default Server:  UnKnown
Address:  1xx.23x.116.158
 
> set q=ptr
> 61.164.198.64.in-addr.arpa
Server:  UnKnown
Address:  1xx.23x.116.158
 
Non-authoritative answer:
61.164.198.64.in-addr.arpa      name = 64-198-164-61.ip.mcleodusa.net
>
Edited by lordofthestrings
Link to comment
Share on other sites

Reverse DNS lookups aren't my problem, although I didn't know that and it might come in handy down the road.. It also explains what my phone has been doing with all the in-arpa queries.

Edit: I'm being an egocentric whackjob, nevermind

Anyway, is there no UDF or exotic DLL that lets me tweak a UDP socket handle's source port? I mean this is a frequently used feature of the UDP protocol, there's got to be something for Windows.

Edited by TheBrainiac
Link to comment
Share on other sites

I think the DNS lookups answer was related to my question in post #4, (thanks lordofthestrings)

TheBrainiac, a question,  isn't the 'port' parameter in UDPBind() the port where you wait for udp data sent by the sender?

sorry, you already say about this in post #3

Edited by PincoPanco

 

image.jpeg.9f1a974c98e9f77d824b358729b089b0.jpeg Chimp

small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Link to comment
Share on other sites

Wouldn't it be easier to just create a VM with Server 2003/2008 or some version of Linux and use that as your DNS server?

If I posted any code, assume that code was written using the latest release version unless stated otherwise. Also, if it doesn't work on XP I can't help with that because I don't have access to XP, and I'm not going to.
Give a programmer the correct code and he can do his work for a day. Teach a programmer to debug and he can do his work for a lifetime - by Chirag Gude
How to ask questions the smart way!

I hereby grant any person the right to use any code I post, that I am the original author of, on the autoitscript.com forums, unless I've specifically stated otherwise in the code or the thread post. If you do use my code all I ask, as a courtesy, is to make note of where you got it from.

Back up and restore Windows user files _Array.au3 - Modified array functions that include support for 2D arrays.  -  ColorChooser - An add-on for SciTE that pops up a color dialog so you can select and paste a color code into a script.  -  Customizable Splashscreen GUI w/Progress Bar - Create a custom "splash screen" GUI with a progress bar and custom label.  -  _FileGetProperty - Retrieve the properties of a file  -  SciTE Toolbar - A toolbar demo for use with the SciTE editor  -  GUIRegisterMsg demo - Demo script to show how to use the Windows messages to interact with controls and your GUI.  -   Latin Square password generator

Link to comment
Share on other sites

  • Solution

Okay, I made it. I used Ws2_32.dll's functions. Here is my code:

UDPStartup()
$hWs2_32 = DllOpen("ws2_32.dll")

$hSocket = _WinsockCreateSocket(2, 2, 17)
_WinsockBind($hSocket, "192.168.178.20", 53); The source address and port for the Socket. The IP must be your LAN IP. I've found that 127.0.0.1 does not work.
_WinsockConnect($hSocket, "192.168.178.22", 65432); The destination IP and port.
_WinsockSend($hSocket, "This UDP Packet came from Port 53!")


Func _WinsockCreateSocket($Address_Family, $Socket_Type, $Protocol)
    $iRet = DllCall($hWs2_32, "int", "socket", "int", $Address_Family, "int", $Socket_Type, "int", $Protocol)
    Return $iRet[0]
EndFunc

Func _WinsockBind($hSocket, $IP, $Port)
    $hAddr = _SocketAddr($IP, $Port)
    $iRet = DllCall($hWs2_32, "int", "bind", "uint", $hSocket, "ptr", DllStructGetPtr($hAddr), "int", DllStructGetSize($hAddr))
    Return $iRet[0]
EndFunc

Func _WinsockConnect($hSocket, $IP, $Port)
    $hAddr = _SocketAddr($IP, $Port)
    $iRet = DllCall($hWs2_32, "int", "connect", "uint", $hSocket, "ptr", DllStructGetPtr($hAddr), "int", DllStructGetSize($hAddr))
EndFunc

Func _WinsockSend($hSocket, $data)
    $hBuf = DllStructCreate("byte[" & BinaryLen($data) & "]")
    DllStructSetData($hBuf, 1, $data)
    $iRet = DllCall($hWs2_32, "int", "send", "uint", $hSocket, "ptr", DllStructGetPtr($hBuf), "int", DllStructGetSize($hBuf), "int", 0)
EndFunc

Func _SocketAddr($IP, $Port, $Address_Family = 2)
    $stAddress = DllStructCreate("short; ushort; uint; char[8]")
    DllStructSetData($stAddress, 1, $Address_Family)
    $iRet = DllCall($hWs2_32, "ushort", "htons", "ushort", $Port)
    DllStructSetData($stAddress, 2, $iRet[0])
    $iRet = DllCall($hWs2_32, "uint", "inet_addr", "str", $IP)
    DllStructSetData($stAddress, 3, $iRet[0])
    Return $stAddress
EndFunc   ;==>_SocketAddr

Func _WSAGetLastError()
    $iRet = DllCall($hWs2_32, "int", "WSAGetLastError")
    Return $iRet[0]
EndFunc   ;==>_WSAGetLastError

On the other computer, there was an AutoIt UDP listener running. It said the source port of the packet received is indeed 53.

 

There is PLENTY of room to expand this and make it into an excellent UDF. I only implemented the functions I need for now, but you probably see where to expand this.

Here's the list of functions that could potentially be adapted in the UDF:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms741394(v=vs.85).aspx

Edited by TheBrainiac
Link to comment
Share on other sites

  • 4 weeks later...

I adapted TheBrainiac code to provide a simple way to make a real udp server. See example below. The example is just echoing to any udp request.

Cheers,

Nicolas

UDPStartup()


Local $socket = UDPBind(@IPAddress1, 53)
If @error <> 0 Then Exit

While 1
    Local $data = UDPRecv($socket, 1024, 2)
    If IsArray($data) Then
        _UDPSendto($socket, $data[0], $data[1], $data[2]) ; answer the same packet to the client...
    EndIf
    Sleep(100)
WEnd


; Send a udp packet to a specific address originating from an existing socket
; parameters : $udpsocket : socket (as returned by UDBbind)
; $data : binary to send
; $IP : destination IP
; $Port : destination Port
; $allowbroadcast : If True, allow sending broadcasts
;
; Returns : number of bytes sent, or check @Error if <0
Func _UDPSendto($udpsocket, $data, $IP, $Port, $allowbroadcast=False)
    If Not IsDeclared("Dll_Ws2_32") Then ; If not already declared, open Dll ws2_32.dll and set up auto close at program exit
        Global $Dll_Ws2_32=DllOpen("ws2_32.dll")
        If $Dll_Ws2_32=-1 Then
            SetError(10101)
            return -1
        EndIf
        OnAutoItExitRegister("_Close_ws2_32")
    EndIf

    If $allowbroadcast Then
        Local $char = DllStructCreate("char")
        DllStructSetData($char, 1, 1)
        DllCall($Dll_Ws2_32, "int", "setsockopt", "uint", $udpsocket[1], "int", 0xffff, "int", 0x0020,  "ptr", DllStructGetPtr($char), "int", 1)
    EndIf

    ; create and populate a sockaddr struct from $IP and $Port
    Local $stAddress = DllStructCreate("short; ushort; uint; char[8]")
    DllStructSetData($stAddress, 1, 2)
    Local $iRet = DllCall($Dll_Ws2_32, "ushort", "htons", "ushort", $Port)
    If Not IsArray($iRet) Then return -1
    DllStructSetData($stAddress, 2, $iRet[0])
    If Not IsArray($iRet) Then return -1
    $iRet = DllCall($Dll_Ws2_32, "uint", "inet_addr", "str", $IP)
    DllStructSetData($stAddress, 3, $iRet[0])

    ; Create buffer with data
    Local $hBuf = DllStructCreate("byte[" & BinaryLen($data) & "]")
    DllStructSetData($hBuf, 1, $data)

    ; Send packet
    $iRet = DllCall($Dll_Ws2_32, "int", "sendto", "uint", $udpsocket[1], "ptr", DllStructGetPtr($hBuf), "int", DllStructGetSize($hBuf), "int", 0, "ptr", DllStructGetPtr($stAddress), "int", DllStructGetSize($stAddress))

    If Not IsArray($iRet) Then return -1

    ; check for error
    If $iRet[0]<0 Then ; Set @error with last error from WSAGetLastError
        Local $errno = DllCall($Dll_Ws2_32, "int", "WSAGetLastError")
        SetError($errno[0])
    EndIf

    return $iRet[0]
EndFunc


Func _Close_ws2_32()
    DllClose($Dll_Ws2_32)
EndFunc
Edited by f1iqf
Link to comment
Share on other sites

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

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...