Jump to content

STUN UDF


j0kky
 Share

Recommended Posts

Hi guysssss,

have you ever heard of STUN?! No?

When you are under a NAT and you try to connect to an online server, you make the request using an internal port and your local IP.

But when your request arrives to the router, it uses its own port list and it enstablishes the connection to the final server using a different port (which has a different port number, but it is associated to your internal port) and the public (external) IP. So, the server responds to your request using the couple external IP\port assigned by the router.

Well, STUN is a protocol that permits you to know your external IP but, more interesting, your external port associated to your internal port.

But, which is the point?

Client/Server connections don't require to know these informations, but what about P2P connection?

STUN UDF is the second step (the first was my winsock UDF, which is used in this script) to UDP Hole Punching technique:

Quote

UDP hole punching establishes connectivity between two hosts communicating across one or more network address translators.

NAT traversal techniques are typically required for client-to-client networking applications on the Internet involving hosts connected in private networks, especially in peer-to-peer, Direct Client-to-Client (DCC) and Voice over Internet Protocol (VoIP) deployments.

Here you are the function:

; #FUNCTION# ========================================================================================================================
; Name...........: _STUN
; Description ...: Makes a STUN request and processes the response
; Syntax.........: _STUN($iLocalPort, $iStunServer = "", $iStunPort = "")
; Parameters ....: $iLocalPort - The local port to be tested
;                  |1 : 65535
;                  $iStunServer - name of the STUN server [Default = ""]
;                  $iStunPort - STUN server port, used only if $iStunServer <> ""
;                  |1 : 65535 - [Default = 3478]
; Return values .: On success it returns an array:
;                  |[0] - The external IP address
;                  |[1] - The external port
;                  On failure it returns -1 and sets @error to non zero:
;                  |-1 - internal error
;                  |-2 - missing DLL (Ws2_32.dll)
;                  |-3 - Unable to connect to internet
;                  |-4 - $iLocalPort is not a valid port
;                  |-5 - can't bind to $iLocalPort, try a different port
;                  |-6 - problem with servers
; Remarks .......: This function is used by a client to know which external port/IP correspond to the indicated internal port.
;                  By default it is used a STUN server list composed by:
;                  stun.l.google.com:19302, stun1.l.google.com:19302, stun.ekiga.net:3478, stun.ideasip.com:3478, stun.schlund.de:3478
;                  If $iStunServer is indicated, the first element of the list is replaced by $iStunServer:$iStunPort
; Author ........: j0kky
; Modified ......: 1.0.0
; Links .........: STUN RFC 5389:   https://tools.ietf.org/html/rfc5389
; ====================================================================================================================================
#include "winsock.au3"
Func _STUN($iLocalPort, $iStunServer = "", $iStunPort = "")
    $iLocalPort = Int($iLocalPort)
    If ($iLocalPort < 1) Or ($iLocalPort > 65535) Then Return SetError(-4, 0, -1)

    $iExtended = 0
    If $iStunServer = Default Then $iStunServer = ""
    $iStunServer = String($iStunServer)
    If $iStunServer Then
        $iStunServer = StringRegExpReplace($iStunServer, "^http.?://", "")
        If StringRight($iStunServer, 1) = "/" Then $iStunServer = StringTrimRight($iStunServer, 1)
        If Not StringRegExp($iStunServer, ".+\..{2,}") Then
            $iStunServer = "" ; invalid parameter
            $iExtended = -1
        EndIf
    EndIf

    $hWs2 = DllOpen("Ws2_32.dll")
    If @error Then Return SetError(-2, $iExtended, -1)

    $tHeader = DllStructCreate("ushort MessageType; ushort MessageLenght; ulong MagicCookie; byte TransactionID[12]")
    $aRet = DllCall($hWs2, "ushort", "htons", "ushort", 0x0001)
    If @error Then
        DllClose($hWs2)
        Return SetError(-1, $iExtended, -1)
    Else
        DllStructSetData($tHeader, "MessageType", $aRet[0]) ;MessageType
    EndIf
    DllStructSetData($tHeader, "MessageLenght", 0x0000) ;MessageLenght
    $aRet = DllCall($hWs2, "ulong", "htonl", "ulong", 0x2112A442)
    If @error Then
        DllClose($hWs2)
        Return SetError(-1, $iExtended, -1)
    Else
        DllStructSetData($tHeader, "MagicCookie", $aRet[0]) ;MagicCookie
    EndIf
    For $i = 1 To 12
        DllStructSetData($tHeader, "TransactionID", Random(0x00, 0xff, 1), $i) ;TransactionID
    Next

    _UDPStartup()
    If @error Then
        DllClose($hWs2)
        Return SetError(-1, $iExtended, -1)
    EndIf

    $sLocalIP = _GetIPs()
    If @error Then
        _UDPShutdown()
        DllClose($hWs2)
        Return SetError(-3, $iExtended, -1)
    EndIf
    $sLocalIP = $sLocalIP[0]

    If $iStunServer Then
        $sFirstServer = $iStunServer
        If $iStunPort Then
            $nFirstPort = $iStunPort
        Else
            $nFirstPort = 3478
        EndIf
    Else
        $sFirstServer = "stun.l.google.com"
        $nFirstPort = 19302
    EndIf
    Local $aStunServer[5][2] = [[$sFirstServer, $nFirstPort], ["stun1.l.google.com", 19302], ["stun.ekiga.net", 3478], ["stun.ideasip.com", 3478], ["stun.schlund.de", 3478]]

    $nBindSocket = _UDPBind($sLocalIP, $iLocalPort)
    If @error Then Return SetError(-5, $iExtended, -1)

    For $j = 0 To 4
        $iError = 0
        $sServerName = $aStunServer[$j][0]
        $sServerIP = _TCPNameToIP($sServerName)
        If @error Then $iError = -6

        If Not $iError Then
            ;RFC indicates that $nRcMaxValue should be equal to 7 and $nRm should be equal to 16,
            ;but, following those rules, if the server doesn't respond quickly, the script is blocked for more than 1 minute
            $tByteHeader = DllStructCreate("byte[" & DllStructGetSize($tHeader) & "]", DllStructGetPtr($tHeader))
            Local $nRTO = 500, $hTime = TimerInit(), $nRTOTotal = 0, $nRc = 0, $nRcMaxValue = 2, $aRecv = "", $nRm = 2
            Do
                If ($nRc < $nRcMaxValue) And (TimerDiff($hTime) >= $nRTOTotal) Then
                    _UDPSendTo($sServerIP, $aStunServer[$j][1], DllStructGetData($tByteHeader, 1), $nBindSocket)
                    If @error Then $iError = -1
                    $nRTOTotal += $nRTO * (2 ^ $nRc)
                    $nRc += 1
                    If $nRc = $nRcMaxValue Then $hTime = TimerInit()
                EndIf

                $aRecv = _UDPRecvFrom($nBindSocket, 2048, 1)
                If @error Then $iError = -1
                Sleep(100)
            Until ($aRecv <> -1) Or (($nRc = $nRcMaxValue) And (TimerDiff($hTime) >= $nRTO * $nRm))
            If Not IsArray($aRecv) Then $iError = -1
        EndIf

        If Not $iError Then
            $dResponse = $aRecv[0]
            $aRet = DllCall($hWs2, "ushort", "ntohs", "ushort", BinaryMid($dResponse, 3, 2)) ;MessageLenght
            If @error Then
                $iError = -1
            Else
                $nMessageLenght = $aRet[0]
            EndIf
            If Not ($nMessageLenght > 0) Then $iError = -6
        EndIf

        If Not $iError Then
            $dResponse = BinaryMid($dResponse, 1, $nMessageLenght + 20) ;message lenght plus header size
            $aRet = DllCall($hWs2, "ushort", "ntohs", "ushort", BinaryMid($dResponse, 1, 2)) ;MessageType
            If @error Then
                $iError = -1
            Else
                $nMessageType = $aRet[0]
                If Not (($nMessageType = 0x0101) Or ($nMessageType = 0x0111)) Then $iError = -6
            EndIf
        EndIf

        If Not $iError Then
            $nByteNumber = 1
            If $nMessageType = 0x0101 Then ;Binding success response
                Do
                    $iError = 0
                    $aRet = DllCall($hWs2, "ushort", "ntohs", "ushort", BinaryMid($dResponse, 20 + $nByteNumber, 2)) ;Attribute Type
                    If @error Then
                        $iError = -1
                    Else
                        $nAttributeType = $aRet[0]
                    EndIf

                    If Not $iError And (($nAttributeType = 0x0001) Or ($nAttributeType = 0x0020)) Then ;MAPPED-ADDRESS or XOR-MAPPED-ADDRESS
                        $aRet = DllCall($hWs2, "ushort", "ntohs", "ushort", BinaryMid($dResponse, 24 + $nByteNumber, 2)) ;Family
                        If @error Then
                            $iError = -1
                        Else
                            $nFamily = $aRet[0]
                        EndIf
                    EndIf

                    If Not $iError Then
                        Local $aResult[2]
                        If $nAttributeType = 0x0020 Then ;XOR-MAPPED-ADDRESS
                            $aRet = DllCall($hWs2, "ushort", "ntohs", "ushort", BinaryMid($dResponse, 26 + $nByteNumber, 2)) ;Port
                            If @error Then
                                $iError = -1
                            Else
                                $aResult[1] = BitXOR($aRet[0], 0x2112) ;Xored with the most significant 16 bit of Magic Cookie
                            EndIf

                            If Not $iError Then
                                If $nFamily = 0x0001 Then ;IPv4
                                    $dNBOIP = BinaryMid($dResponse, 28 + $nByteNumber, 4)
                                    $dNBOCookie = Binary("0x" & Hex(0x2112A442))
                                    $tNBOXoredIP = DllStructCreate("byte[4]")
                                    For $i = 1 To 4
                                        DllStructSetData($tNBOXoredIP, 1, BitXOR(BinaryMid($dNBOIP, $i, 1), BinaryMid($dNBOCookie, $i, 1)), $i)
                                    Next

                                    $aRet = DllCall($hWs2, "ptr", "inet_ntoa", "ulong", Int(DllStructGetData($tNBOXoredIP, 1)))
                                    If @error Then
                                        $iError = -1
                                    ElseIf $aRet[0] = Null Then
                                        $iError = -6
                                    Else
                                        $aResult[0] = DllStructGetData(DllStructCreate("char[15]", $aRet[0]), 1) ;IP address xored with Magic Cookie
                                    EndIf
                                Else ;IPv6
                                    $dNBOIP = BinaryMid($dResponse, 28 + $nByteNumber, 16)
                                    $dNBOConcatenation = BinaryMid($dResponse, 5, 16)

                                    $tNBOXoredIP = DllStructCreate("byte[16]")
                                    For $i = 1 To 16
                                        DllStructSetData($tNBOXoredIP, 1, BitXOR(BinaryMid($dNBOIP, $i, 1), BinaryMid($dNBOConcatenation, $i, 1)), $i)
                                    Next

                                    $tIP = DllStructCreate("char[46]")
                                    $aRet = DllCall($hWs2, "ptr", "inet_ntop", _
                                            "int", 23, "ptr", DllStructGetPtr($tNBOXoredIP), "ptr", DllStructGetPtr($tIP), "ULONG_PTR", DllStructGetSize($tIP))
                                    If @error Then
                                        $iError = -1
                                    ElseIf $aRet[0] = Null Then
                                        $iError = -6
                                    Else
                                        $aResult[0] = DllStructGetData($tIP, 1) ;IP address
                                    EndIf
                                EndIf
                            EndIf
                            ExitLoop 2
                        ElseIf $nAttributeType = 0x0001 Then ;MAPPED-ADDRESS
                            $aRet = DllCall($hWs2, "ushort", "ntohs", "ushort", BinaryMid($dResponse, 26 + $nByteNumber, 2)) ;Port
                            If @error Then
                                $iError = -1
                            Else
                                $aResult[1] = $aRet[0]
                            EndIf
                            If Not $iError Then
                                If $nFamily = 0x0001 Then ;IPv4
                                    $aRet = DllCall($hWs2, "ptr", "inet_ntoa", "ulong", BinaryMid($dResponse, 28 + $nByteNumber, 4))
                                    If @error Then
                                        $iError = -1
                                    ElseIf $aRet[0] = Null Then
                                        $iError = -6
                                    Else
                                        $aResult[0] = DllStructGetData(DllStructCreate("char[15]", $aRet[0]), 1) ;IP address
                                    EndIf
                                Else ;IPv6
                                    $tNBOIP = DllStructCreate("byte[16]")
                                    DllStructSetData($tNBOIP, 1, BinaryMid($dResponse, 28 + $nByteNumber, 16))
                                    $tIP = DllStructCreate("char[46]")
                                    $aRet = DllCall($hWs2, "ptr", "inet_ntop", _
                                            "int", 23, "ptr", DllStructGetPtr($tNBOIP), "ptr", DllStructGetPtr($tIP), "ULONG_PTR", DllStructGetSize($tIP))
                                    If @error Then
                                        $iError = -1
                                    ElseIf $aRet[0] = Null Then
                                        $iError = -6
                                    Else
                                        $aResult[0] = DllStructGetData($tIP, 1) ;IP address
                                    EndIf
                                EndIf
                            EndIf
                            ExitLoop 2
                        Else
                            $aRet = DllCall($hWs2, "ushort", "ntohs", "ushort", BinaryMid($dResponse, 22 + $nByteNumber, 2)) ;Attribute Lenght
                            If @error Then
                                $iError = -1
                            Else
                                $nAttributeLenght = $aRet[0]
                                If Not (Mod($nAttributeLenght, 4) = 0) Then $nAttributeLenght += 4 - Mod($nAttributeLenght, 4) ;padding
                                $nByteNumber += 4 + $nAttributeLenght ;TLV
                            EndIf
                        EndIf
                    EndIf
                Until $nByteNumber > $nMessageLenght
            Else ;0x0111 = Binding error response
                $iError = -6
            EndIf
        EndIf
    Next

    _UDPCloseSocket($nBindSocket)
    _UDPShutdown()
    DllClose($hWs2)
    If $iError Then $aResult = -1
    If $iStunServer And ($iStunServer <> $sServerName) Then $iExtended = -2
    Return SetError($iError, $iExtended, $aResult)
EndFunc   ;==>_STUN

 

Please test it (especially if you have an IPv6 interface) and report every bug you can try :)

 

STUN.zip

Edited by j0kky
Link to comment
Share on other sites

Haven't tested yet, but it looks like a solid piece of work, potentially quite useful, thanks.:)

RE. your winsock.au3, these functions may help make your WSA error reporting a bit more accessible (just a thought).

Edited by RTFC
Link to comment
Share on other sites

17 minutes ago, RTFC said:

Haven't tested yet, but it looks like a solid piece of work, potentially quite useful, thanks.:)

RE. your winsock.au3, these functions may help make your WSA error reporting a bit more accessible (just a thought).

Thank you for the feedback! 

Talking about WSA error reporting: winsock UDF is planned to be used in simple "If @error then" statement, so it needs to be outputted just an error code number. Anyway who use winsock UDF can freely associate that error code to an explanation using your functions ;)

Link to comment
Share on other sites

Isn' it essentially the same STUN protocol UDF found here :

 

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

4 hours ago, jchd said:

Isn' it essentially the same STUN protocol UDF found here :

 

Short story: No! That script just uses STUN protocol to retrieve public IPv4 IP.

Long story: thanks to the implementation of winsock UDF I can retrieve the external port too, which is necessary to UDP Hole Punching; furthermore my UDF can parse XOR-MAPPED-ADDRESS attribute (used by newest STUN servers) and it can retrieve also IPv6 address (trancexx script needs some other code lines to parse IPv6). Other minor advantages are the possibility of using a customized STUN server name and a strongest error handling.

Edited by j0kky
Link to comment
Share on other sites

Ah I see. Thanks for detail information.

This wonderful site allows debugging and testing regular expressions (many flavors available). An absolute must have in your bookmarks.
Another excellent RegExp tutorial. Don't forget downloading your copy of up-to-date pcretest.exe and pcregrep.exe here
RegExp tutorial: enough to get started
PCRE v8.33 regexp documentation latest available release and currently implemented in AutoIt beta.

SQLitespeed is another feature-rich premier SQLite manager (includes import/export). Well worth a try.
SQLite Expert (freeware Personal Edition or payware Pro version) is a very useful SQLite database manager.
An excellent eBook covering almost every aspect of SQLite3: a must-read for anyone doing serious work.
SQL tutorial (covers "generic" SQL, but most of it applies to SQLite as well)
A work-in-progress SQLite3 tutorial. Don't miss other LxyzTHW pages!
SQLite official website with full documentation (may be newer than the SQLite library that comes standard with AutoIt)

Link to comment
Share on other sites

  • 3 months later...
  • 1 year later...

Signature beginning:
Please remember: "AutoIt"..... *  Wondering who uses AutoIt and what it can be used for ? * Forum Rules *
ADO.au3 UDF * POP3.au3 UDF * XML.au3 UDF * IE on Windows 11 * How to ask ChatGPT for AutoIt Codefor other useful stuff click the following button:

Spoiler

Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind. 

My contribution (my own projects): * Debenu Quick PDF Library - UDF * Debenu PDF Viewer SDK - UDF * Acrobat Reader - ActiveX Viewer * UDF for PDFCreator v1.x.x * XZip - UDF * AppCompatFlags UDF * CrowdinAPI UDF * _WinMergeCompare2Files() * _JavaExceptionAdd() * _IsBeta() * Writing DPI Awareness App - workaround * _AutoIt_RequiredVersion() * Chilkatsoft.au3 UDF * TeamViewer.au3 UDF * JavaManagement UDF * VIES over SOAP * WinSCP UDF * GHAPI UDF - modest begining - comunication with GitHub REST APIErrorLog.au3 UDF - A logging Library * Include Dependency Tree (Tool for analyzing script relations) * Show_Macro_Values.au3 *

 

My contribution to others projects or UDF based on  others projects: * _sql.au3 UDF  * POP3.au3 UDF *  RTF Printer - UDF * XML.au3 UDF * ADO.au3 UDF SMTP Mailer UDF * Dual Monitor resolution detection * * 2GUI on Dual Monitor System * _SciLexer.au3 UDF * SciTE - Lexer for console pane

Useful links: * Forum Rules * Forum etiquette *  Forum Information and FAQs * How to post code on the forum * AutoIt Online Documentation * AutoIt Online Beta Documentation * SciTE4AutoIt3 getting started * Convert text blocks to AutoIt code * Games made in Autoit * Programming related sites * Polish AutoIt Tutorial * DllCall Code Generator * 

Wiki: Expand your knowledge - AutoIt Wiki * Collection of User Defined Functions * How to use HelpFile * Good coding practices in AutoIt * 

OpenOffice/LibreOffice/XLS Related: WriterDemo.au3 * XLS/MDB from scratch with ADOX

IE Related:  * How to use IE.au3  UDF with  AutoIt v3.3.14.x * Why isn't Autoit able to click a Javascript Dialog? * Clicking javascript button with no ID * IE document >> save as MHT file * IETab Switcher (by LarsJ ) * HTML Entities * _IEquerySelectorAll() (by uncommon) * IE in TaskSchedulerIE Embedded Control Versioning (use IE9+ and HTML5 in a GUI) * PDF Related:How to get reference to PDF object embeded in IE * IE on Windows 11

I encourage you to read: * Global Vars * Best Coding Practices * Please explain code used in Help file for several File functions * OOP-like approach in AutoIt * UDF-Spec Questions *  EXAMPLE: How To Catch ConsoleWrite() output to a file or to CMD *

I also encourage you to check awesome @trancexx code:  * Create COM objects from modules without any demand on user to register anything. * Another COM object registering stuffOnHungApp handlerAvoid "AutoIt Error" message box in unknown errors  * HTML editor

winhttp.au3 related : * https://www.autoitscript.com/forum/topic/206771-winhttpau3-download-problem-youre-speaking-plain-http-to-an-ssl-enabled-server-port/

"Homo sum; humani nil a me alienum puto" - Publius Terentius Afer
"Program are meant to be read by humans and only incidentally for computers and execute" - Donald Knuth, "The Art of Computer Programming"
:naughty:  :ranting:, be  :) and       \\//_.

Anticipating Errors :  "Any program that accepts data from a user must include code to validate that data before sending it to the data store. You cannot rely on the data store, ...., or even your programming language to notify you of problems. You must check every byte entered by your users, making sure that data is the correct type for its field and that required fields are not empty."

Signature last update: 2023-04-24

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

×
×
  • Create New...