UDP Hole Punching mit Introducer
Verfasst: 15.08.2009 15:26
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:
Sender/Empfänger Peers:
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
.
----------------------------------------
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
----------------------------------------
zumindest nicht bei UDP, denn bei UDP kann sich die IP ändern,
weil es keine "echte" Verbindung gibt.
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
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()
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
----------------------------------------
- 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). - 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. - Computer A wartet nun auf eingehende Nachrichten.
- Computer B schickt eine Aufforderung zur Herausgabe des Ports
der Verbindung von Computer A. - Introducer schickt Computer B den Port 4567.
- Computer B schickt nun die Daten direkt an 99.22.11.33:4567.
zumindest nicht bei UDP, denn bei UDP kann sich die IP ändern,
weil es keine "echte" Verbindung gibt.