Seite 1 von 1

UDP Hole Punching mit Introducer

Verfasst: 15.08.2009 15:26
von DarkDragon
Hallo,

Ich hab heut schonwieder das Wort Hole-Punching hier im Forum gelesen. Desshalb hab ich hier mal die einfachste Methode implementiert, ganz ohne API (getestet mit PB 4.31):

Introducer:

Code: Alles auswählen

; Thanks to ZeHa and RSBasic who played the sender when I was testing the code
; ---------------------
; http://www.bradan.eu/
; DarkDragon

InitNetwork()

#INTRODUCER_HOST    = "85.214.72.24"
#INTRODUCER_PORT    = 18123
#INTRODUCER_TIMEOUT = 1000

; Version information constants
#HOLE_VERSION = 1

; Packetmodes
Enumeration
  #HOLE_SAVE_PORT
  #HOLE_REMOVE_PORT
  #HOLE_GET_PORT
EndEnumeration

; Errorcodes
Enumeration
  #HOLE_ERROR_NONE
  #HOLE_ERROR_NOCONNECTION
  #HOLE_ERROR_TIMEOUT
  #HOLE_ERROR_VERSION
  #HOLE_ERROR_NOTREGISTERED
EndEnumeration

Structure SHOLEPACKET
  Version.b
  
  StructureUnion
    ErrorCode.b
    PacketMode.b
  EndStructureUnion
  
  StructureUnion
    IP.l
    Port.l
  EndStructureUnion
EndStructure

Procedure Error(Text.s)
  PrintN("ERROR: " + Text)
EndProcedure

Procedure Out(Text.s)
  PrintN("# " + Text)
EndProcedure

Structure SPORT
  IP.l
  Port.l
EndStructure

NewList Ports.SPORT()

Define SendPacket.SHOLEPACKET
Define RecvPacket.SHOLEPACKET
Define ClientIP, ClientPort
Define Round

OpenConsole()

If CreateNetworkServer(0, #INTRODUCER_PORT, #PB_Network_UDP)
  
  Out("Listening on port: " + Str(#INTRODUCER_PORT))
  Out("(press escape to quit)")
  
  SendPacket\Version    = #HOLE_VERSION
  
  Repeat
    
    Round + 1
    If Round % 10000 = 0
      Out("Feedback: running.")
    EndIf
    
    If NetworkServerEvent() = #PB_NetworkEvent_Data
      
      SendPacket\ErrorCode  = #HOLE_ERROR_NONE
      
      Client     = EventClient()
      ClientIP   = GetClientIP(Client)
      ClientPort = GetClientPort(Client)
      
      ReceiveNetworkData(Client, @RecvPacket, SizeOf(SHOLEPACKET))
      
      If RecvPacket\Version = #HOLE_VERSION
        
        Select RecvPacket\PacketMode
          Case #HOLE_SAVE_PORT
            
            Out("Client saved port: " + IPString(ClientIP) + ":" + Str(ClientPort))
            ForEach Ports()
              If Ports()\IP = ClientIP
                ClientIP = 0
                Ports()\Port = ClientPort
                Break
              EndIf
            Next
            
            If ClientIP <> 0
              AddElement(Ports())
              Ports()\IP    = ClientIP
              Ports()\Port  = ClientPort
            EndIf
            
          Case #HOLE_REMOVE_PORT
            
            Out("Client removed port: " + IPString(ClientIP) + ":" + Str(ClientPort))
            ForEach Ports()
              If Ports()\IP = ClientIP
                DeleteElement(Ports())
                Break
              EndIf
            Next
            
          Case #HOLE_GET_PORT
            
            Out("Client wanted port of " + IPString(RecvPacket\IP) + ": " + IPString(ClientIP) + ":" + Str(ClientPort))
            
            If RecvPacket\IP = 0
              RecvPacket\IP = ClientIP
            EndIf
            
            SendPacket\ErrorCode = #HOLE_ERROR_NOTREGISTERED
            ForEach Ports()
              If Ports()\IP = RecvPacket\IP
                SendPacket\ErrorCode  = #HOLE_ERROR_NONE
                SendPacket\Port       = Ports()\Port
                Break
              EndIf
            Next
            
            SendNetworkData(Client, @SendPacket, SizeOf(SHOLEPACKET))
            
        EndSelect
        
      Else
        SendPacket\ErrorCode  = #HOLE_ERROR_VERSION
      EndIf
      
      Delay(1)
      
    Else
      
      Delay(2)
      
    EndIf
  Until Inkey() = Chr(27)
  CloseNetworkServer(0)
  
Else
  
  Error("Couldn't listen on port: " + Str(#INTRODUCER_PORT))
  
EndIf
Sender/Empfänger Peers:

Code: Alles auswählen

; Thanks to ZeHa and RSBasic who played the sender when I was testing the code
; ---------------------
; http://www.bradan.eu/
; DarkDragon

InitNetwork()

#INTRODUCER_HOST    = "85.214.72.24"
#INTRODUCER_PORT    = 18123
#INTRODUCER_TIMEOUT = 1000

; Version information constants
#HOLE_VERSION = 1

; Packetmodes
Enumeration
  #HOLE_SAVE_PORT
  #HOLE_REMOVE_PORT
  #HOLE_GET_PORT
EndEnumeration

; Errorcodes
Enumeration
  #HOLE_ERROR_NONE
  #HOLE_ERROR_NOCONNECTION
  #HOLE_ERROR_TIMEOUT
  #HOLE_ERROR_VERSION
  #HOLE_ERROR_NOTREGISTERED
EndEnumeration

Structure SHOLEPACKET
  Version.b

  StructureUnion
    ErrorCode.b
    PacketMode.b
  EndStructureUnion

  StructureUnion
    IP.l
    Port.l
  EndStructureUnion
EndStructure

; General introducer functions
Procedure ConnectIntroducer()
  ProcedureReturn OpenNetworkConnection(#INTRODUCER_HOST, #INTRODUCER_PORT, #PB_Network_UDP)
EndProcedure

Procedure DisconnectIntroducer(Connection)
  CloseNetworkConnection(Connection)
EndProcedure

Procedure GetExternalPortOf(Connection, IP.s, *Port.LONG)

  Protected SendPacket.SHOLEPACKET
  Protected RecvPacket.SHOLEPACKET
  Protected StartTime

  SendPacket\Version    = #HOLE_VERSION
  SendPacket\PacketMode = #HOLE_GET_PORT
  SendPacket\IP         = MakeIPAddress(Val(StringField(IP, 1, ".")), Val(StringField(IP, 2, ".")), Val(StringField(IP, 3, ".")), Val(StringField(IP, 4, ".")))

  If Connection

    SendNetworkData(Connection, @SendPacket, SizeOf(SHOLEPACKET))

    StartTime = ElapsedMilliseconds()
    While ElapsedMilliseconds() - StartTime < #INTRODUCER_TIMEOUT
      If NetworkClientEvent(Connection) = #PB_NetworkEvent_Data
        ReceiveNetworkData(Connection, @RecvPacket, SizeOf(SHOLEPACKET))
        Break
      EndIf
      Delay(1)
    Wend

  Else

    RecvPacket\ErrorCode = #HOLE_ERROR_NOCONNECTION

  EndIf

  If RecvPacket\Version = 0 And RecvPacket\Port = 0
    RecvPacket\ErrorCode = #HOLE_ERROR_TIMEOUT
  EndIf

  *Port\l = RecvPacket\Port

  ProcedureReturn RecvPacket\ErrorCode

EndProcedure

Procedure RegisterMe(Connection)

  Protected Packet.SHOLEPACKET

  Packet\Version    = #HOLE_VERSION
  Packet\PacketMode = #HOLE_SAVE_PORT

  If Connection

    SendNetworkData(Connection, @Packet, SizeOf(SHOLEPACKET))

  EndIf

EndProcedure

Procedure UnRegisterMe(Connection)

  Protected Packet.SHOLEPACKET

  Packet\Version    = #HOLE_VERSION
  Packet\PacketMode = #HOLE_REMOVE_PORT

  If Connection

    SendNetworkData(Connection, @Packet, SizeOf(SHOLEPACKET))

  EndIf

EndProcedure

; Example code

#PEER_SENDING = 1 ; Switch this to 1 to start the sender

Define PeerConnection
Define Connection
Define IP.s
Define Port
Define Text.s
Define k

OpenConsole()

Connection = ConnectIntroducer()

If Connection


  CompilerIf #PEER_SENDING = 0

    RegisterMe(Connection)

    Delay(100)

    PrintN("My own open port now (I am the peer you need to specify in the other side):")
    PrintN("Errorcode: " + Str(GetExternalPortOf(Connection, "", @Port)))
    PrintN("Port: " + Str(Port))
    PrintN("")
    PrintN("Waiting for some data which I will receive (press escape to quit).")

    Repeat
      If NetworkClientEvent(Connection) = #PB_NetworkEvent_Data
        Text = Space(32)
        ReceiveNetworkData(Connection, @Text, Len(Text))
        PrintN(Text)
      EndIf
      Delay(1)
    Until Inkey() = Chr(27)

    Delay(100)

    UnRegisterMe(Connection)

  CompilerElse

    Delay(100)

    PrintN("Give me the IP of the peer:")
    IP = Input()

    If GetExternalPortOf(Connection, IP, @Port) = 0

      PeerConnection = OpenNetworkConnection(IP, Port, #PB_Network_UDP)

      If PeerConnection
        
        PrintN("Sending data to the peer which is receiving.")
        
        For k = 1 To 5
          SendNetworkString(PeerConnection, "Hallo, Welt!")
          Delay(10)
        Next k

        CloseNetworkConnection(PeerConnection)

      Else

        PrintN("Couldn't connect to the peer.")

      EndIf

    Else

        PrintN("Couldn't get the port of the peer through the introducer.")

    EndIf

    Delay(100)

  CompilerEndIf


  DisconnectIntroducer(Connection)

Else
  PrintN("Couldn't connect to the introducer.")
EndIf

PrintN("Finished.")

Input()
Zu Testzwecken habe ich heute mal einen Introducer auf meinem Server (Die Werte sind bereits im Code) laufen. Wenn ihr also testen wollt, dann startet zunächst einen Receiver Peer (einfach #PEER_SENDING = 0 stellen und starten) und danach den Sender Peer (einfach #PEER_SENDING = 1 stellen und starten). Die IP Adresse, die ihr eingeben sollt ist die IP Adresse außerhalb der NAT-Box, welche ihr auf der Receiver-Seite durch einen Aufruf von http://www.whatismyip.com/ o.ä. bekommt.

Vielen Dank an ZeHa und RSBasic, die den Sender beim Testen gespielt haben ;-) .

Code: Alles auswählen

              Introducer
            /            \
           /              \
NAT Box of A             Computer B
    |
Computer A
----------------------------------------

Beispielwerte für die Adressen:
Comp. A: 10.0.0.100
NAT Box intern: 10.0.0.1
NAT Box extern: 99.22.11.33
Comp. B: 88.33.22.11

----------------------------------------
  1. Computer A will eine Nachricht an den Introducer schicken.
    Computer A findet die IP des Introducers nicht im eigenen
    Subnetz, also schickt er die Nachricht an das Gateway
    (hier die NAT Box).
  2. Die NAT-Box merkt sich die IP von A an dem Quellport
    (z.B. 1234) in der Network Address Translation Table und
    schickt die Nachricht an den Introducer mit einem anderen
    Quellport (z.B. 4567). Der Introducer weiß nun also, wie
    er A erreichen kann, nämlich durch 99.22.11.33:4567. Jede
    Nachricht, die dort ankommt geht direkt an 10.0.0.100:1234.
  3. Computer A wartet nun auf eingehende Nachrichten.
  4. Computer B schickt eine Aufforderung zur Herausgabe des Ports
    der Verbindung von Computer A.
  5. Introducer schickt Computer B den Port 4567.
  6. Computer B schickt nun die Daten direkt an 99.22.11.33:4567.
Die NAT Box sollte eigentlich die Ziel IP nie speichern,
zumindest nicht bei UDP, denn bei UDP kann sich die IP ändern,
weil es keine "echte" Verbindung gibt.

Re: UDP Hole Punching mit Introducer

Verfasst: 15.08.2009 15:34
von ZeHa
DarkDragon hat geschrieben:Vielen Dank an ZeHa, der den Sender beim Testen gespielt hat ;-)
Wo bleibt das Paket mit den 5 Ritter-Sport-Tafeln!!

Verfasst: 15.08.2009 15:38
von cxAlex
Funzt nicht.

Mit einem Peer verbinde ich (dein Server), Errorcode 0, Port xxxx.

Anderer Peer: Senden, Senden Ok, Finished.

Aber: Daten kommen nicht an.

Ich steh hier hinter 2 Routern. PortForwarding hab ich testweise auch schon aktiviert.

Re: UDP Hole Punching mit Introducer

Verfasst: 15.08.2009 15:39
von DarkDragon
Hab den Code oben nochmal schnell aktualisiert, der ist beim Nachbearbeiten etwas beschädigt worden.
ZeHa hat geschrieben:
DarkDragon hat geschrieben:Vielen Dank an ZeHa, der den Sender beim Testen gespielt hat ;-)
Wo bleibt das Paket mit den 5 Ritter-Sport-Tafeln!!
:lol:

@cxAlex: Ich hab den Code oben nochmal aktualisiert, vielleicht funktioniert es jetzt. Jedenfalls ist mir auch schon aufgefallen, dass nicht immer alle Pakete ankommen, sondern nur eines oder 2 von den 5. Jedenfalls ist es so wenn ich von einem PC aus nach außen und wieder rein sende. Aber als ich es mit ZeHa getestet hab kam es vollständig an.

[EDIT]

Vielleicht geht das bei dir eher (Receiver Code etwas geändert):

Code: Alles auswählen

; Thanks to ZeHa and RSBasic who played the sender when I was testing the code
; ---------------------
; http://www.bradan.eu/
; DarkDragon

InitNetwork()

#INTRODUCER_HOST    = "85.214.72.24"
#INTRODUCER_PORT    = 18123
#INTRODUCER_TIMEOUT = 1000

; Version information constants
#HOLE_VERSION = 1

; Packetmodes
Enumeration
  #HOLE_SAVE_PORT
  #HOLE_REMOVE_PORT
  #HOLE_GET_PORT
EndEnumeration

; Errorcodes
Enumeration
  #HOLE_ERROR_NONE
  #HOLE_ERROR_NOCONNECTION
  #HOLE_ERROR_TIMEOUT
  #HOLE_ERROR_VERSION
  #HOLE_ERROR_NOTREGISTERED
EndEnumeration

Structure SHOLEPACKET
  Version.b

  StructureUnion
    ErrorCode.b
    PacketMode.b
  EndStructureUnion

  StructureUnion
    IP.l
    Port.l
  EndStructureUnion
EndStructure

; General introducer functions
Procedure ConnectIntroducer()
  ProcedureReturn OpenNetworkConnection(#INTRODUCER_HOST, #INTRODUCER_PORT, #PB_Network_UDP)
EndProcedure

Procedure DisconnectIntroducer(Connection)
  CloseNetworkConnection(Connection)
EndProcedure

Procedure GetExternalPortOf(Connection, IP.s, *Port.LONG)

  Protected SendPacket.SHOLEPACKET
  Protected RecvPacket.SHOLEPACKET
  Protected StartTime

  SendPacket\Version    = #HOLE_VERSION
  SendPacket\PacketMode = #HOLE_GET_PORT
  SendPacket\IP         = MakeIPAddress(Val(StringField(IP, 1, ".")), Val(StringField(IP, 2, ".")), Val(StringField(IP, 3, ".")), Val(StringField(IP, 4, ".")))

  If Connection

    SendNetworkData(Connection, @SendPacket, SizeOf(SHOLEPACKET))

    StartTime = ElapsedMilliseconds()
    While ElapsedMilliseconds() - StartTime < #INTRODUCER_TIMEOUT
      If NetworkClientEvent(Connection) = #PB_NetworkEvent_Data
        ReceiveNetworkData(Connection, @RecvPacket, SizeOf(SHOLEPACKET))
        Break
      EndIf
      Delay(1)
    Wend

  Else

    RecvPacket\ErrorCode = #HOLE_ERROR_NOCONNECTION

  EndIf

  If RecvPacket\Version = 0 And RecvPacket\Port = 0
    RecvPacket\ErrorCode = #HOLE_ERROR_TIMEOUT
  EndIf

  *Port\l = RecvPacket\Port

  ProcedureReturn RecvPacket\ErrorCode

EndProcedure

Procedure RegisterMe(Connection)

  Protected Packet.SHOLEPACKET

  Packet\Version    = #HOLE_VERSION
  Packet\PacketMode = #HOLE_SAVE_PORT

  If Connection

    SendNetworkData(Connection, @Packet, SizeOf(SHOLEPACKET))

  EndIf

EndProcedure

Procedure UnRegisterMe(Connection)

  Protected Packet.SHOLEPACKET

  Packet\Version    = #HOLE_VERSION
  Packet\PacketMode = #HOLE_REMOVE_PORT

  If Connection

    SendNetworkData(Connection, @Packet, SizeOf(SHOLEPACKET))

  EndIf

EndProcedure

; Example code

#PEER_SENDING = 1 ; Switch this to 1 to start the sender

Define PeerConnection
Define Connection
Define IP.s
Define Port
Define Text.s
Define k

OpenConsole()

Connection = ConnectIntroducer()

If Connection


  CompilerIf #PEER_SENDING = 0

    RegisterMe(Connection)

    Delay(100)

    PrintN("My own open port now (I am the peer you need to specify in the other side):")
    PrintN("Errorcode: " + Str(GetExternalPortOf(Connection, "", @Port)))
    PrintN("Port: " + Str(Port))
    PrintN("")
    PrintN("Waiting for some data which I will receive (press escape to quit).")
    
    DisconnectIntroducer(Connection)

    If CreateNetworkServer(0, Port, #PB_Network_UDP)

      Repeat
        If NetworkServerEvent() = #PB_NetworkEvent_Data
          Text = Space(32)
          ReceiveNetworkData(EventClient(), @Text, Len(Text))
          PrintN(Text)
        EndIf
        Delay(1)
      Until Inkey() = Chr(27)
      
      CloseNetworkServer(0)

    EndIf

    Delay(100)

    Connection = ConnectIntroducer()

    If Connection
      UnRegisterMe(Connection)
    EndIf

  CompilerElse

    Delay(100)

    PrintN("Give me the IP of the peer:")
    IP = Input()

    If GetExternalPortOf(Connection, IP, @Port) = 0

      PeerConnection = OpenNetworkConnection(IP, Port, #PB_Network_UDP)

      If PeerConnection
        
        Delay(1000)
        
        PrintN("Sending data to the peer which is receiving.")

        For k = 1 To 5
          SendNetworkString(PeerConnection, "Hallo, Welt!")
          Delay(10)
        Next k

        CloseNetworkConnection(PeerConnection)

      Else

        PrintN("Couldn't connect to the peer.")

      EndIf

    Else

      PrintN("Couldn't get the port of the peer through the introducer.")

    EndIf

    Delay(100)

  CompilerEndIf


  DisconnectIntroducer(Connection)

Else
  PrintN("Couldn't connect to the introducer.")
EndIf

PrintN("Finished.")

Input()

Verfasst: 15.08.2009 15:59
von cxAlex
Nein geht immer noch nicht. Dein Code macht ja nichts anderes als das der Client beim Server den Port des Servers des anderen Clients nachzuschlagen und ne Verbindung zu öffnen oder? Da muss man dann doch eventuell Sachen wie Port Forwarding usw. aktivieren?

Gruß, Alex

Verfasst: 15.08.2009 16:05
von DarkDragon
cxAlex hat geschrieben:Nein geht immer noch nicht. Dein Code macht ja nichts anderes als das der Client beim Server den Port des Servers des anderen Clients nachzuschlagen und ne Verbindung zu öffnen oder? Da muss man dann doch eventuell Sachen wie Port Forwarding usw. aktivieren?

Gruß, Alex
Nein. Das ist der Sinn von NAT Punching: Aus ausgehenden Datenströmen werden eingehende.

Eine NAT Box bekommt ja mit, wann ein lokaler Computer nach außen verbindet. Dann speichert die NAT Box die äußere Portnummer und die IP:LokalerPort des lokalen Computers und weiß, dass alle Pakete, die die NAT Box auf dem Port von außen empfängt an den IP:LokalerPort im inneren des Netzwerks gehen.

Und das ist es was hier ausgenutzt wird. Lediglich bei NAT Boxen, die auch die ZielIPs speichern würde es nicht gehen. Aber da NAT nicht wegen der Sicherheit erfunden wurde wird es auch nicht oft so sein, dass die ZielIP mitgespeichert wird (Würde ja Ressourcen kosten).

Jetzt verbindet der Client zu dem Introducer, fragt ab, auf welchem Port der Client spricht und absofort ist der Client als Server auf der ClientIP:ClientPort (Der Port, den der Introducer zurückschickt) zu erreichen.

[EDIT]

In Zusammenarbeit mit RSBasic (Vielen Dank dafür ;-) ) habe ich festgestellt, dass es da anscheinend eine Art Anfangsverzögerung gibt. Diese geht über eine längere Zeit hinweg, danach läufts ohne weitere Verzögerungen und ohne Verluste hier.

Verfasst: 31.08.2009 17:04
von Joel
@DarkDragon: Kannst du deinen Introduce nochmal einschalten? Will das nochmal ausprobieren udn habe gerade nicht 2 Pcs zur Verfügung!

2. Frage: Müssen der PC1 und PC2 in verschiedenen Netzwerken sein? Eigentlich schon oder?

Verfasst: 31.08.2009 17:07
von DarkDragon
Hallo,
Joel hat geschrieben:@DarkDragon: Kannst du deinen Introduce nochmal einschalten? Will das nochmal ausprobieren udn habe gerade nicht 2 Pcs zur Verfügung!

2. Frage: Müssen der PC1 und PC2 in verschiedenen Netzwerken sein? Eigentlich schon oder?
Hab den Introducer angeschalten.

Normalerweise müssen die in verschiedenen Netzwerken sein, vor allem weil manche NAT-Boxen internen externen Zugriff auf sich selbst verbieten (T-Sinus 111 z.B.).