{$MODE OBJFPC}
uses
sockets, SysUtils
{$IFDEF WINDOWS}
, winsock
{$ENDIF}
;
{$IFNDEF WINDOWS}
function inet_aton(cp:Pchar; addr:Pin_addr):longint;cdecl;external 'libc' name 'inet_aton';
function inet_ntoa(addr:in_addr):Pchar;cdecl;external 'libc' name 'inet_ntoa';
{$ENDIF}
const SRV_IP = '178.254.3.95';
procedure diep(s:string);
begin
writeln(s);
halt(1);
end;
type
Tclient = record
host : dword;
port : word;
end;
var
si_me, si_other : sockaddr_in;
s, i, f, j, k, slen : dword;
buf, server : Tclient;
peers : array [0..10] of Tclient;
n : longint;
begin
slen:= sizeof(si_other);
s:= fpsocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if s = -1 then diep('socket');
// Our own endpoint data
fillchar(si_me, 0, sizeof(si_me));
si_me.sin_family:= AF_INET;
si_me.sin_port:= htons(9930); // This is not really necessary, we can also use 0 (any port)
si_me.sin_addr.s_addr:= htonl(INADDR_ANY);
// The server's endpoint data
fillchar(si_other, 0, sizeof(si_other));
si_other.sin_family:= AF_INET;
si_other.sin_port:= htons(9930);
{$IFDEF WINDOWS}
si_other.sin_addr.s_addr:=inet_addr(SRV_IP);
{$ELSE}
if inet_aton(SRV_IP, @si_other.sin_addr) = 0 then diep('aton');
{$ENDIF}
// Store the server's endpoint data so we can easily discriminate between server and peer datagrams.
server.host:= si_other.sin_addr.s_addr;
server.port:= si_other.sin_port;
// Send a simple datagram to the server to let it know of our public UDP endpoint.
// Not only the server, but other clients will send their data through this endpoint.
// The datagram payload is irrelevant, but if we wanted to support multiple
// clients behind the same NAT, we'd send our won private UDP endpoint information
// as well.
if fpsendto(s, pchar('hi'), 2, 0, @si_other, slen) = -1 then diep('sendto');
// Right here, our NAT should have a session entry between our host and the server.
// We can only hope our NAT maps the same public endpoint (both host and port) when we
// send datagrams to other clients using our same private endpoint.
while true do begin
// Receive data from the socket. Notice that we use the same socket for server and
// peer communications. We discriminate by using the remote host endpoint data, but
// remember that IP addresses are easily spoofed (actually, that's what the NAT is
// doing), so remember to do some kind of validation in here.
if fprecvfrom(s, @buf, sizeof(buf), 0, @si_other, @slen) = -1 then diep('recvfrom');
writeln('Received packet from ', inet_ntoa(si_other.sin_addr), ':', ntohs(si_other.sin_port));
if (server.host = si_other.sin_addr.s_addr) and (server.port = si_other.sin_port) then begin
// The datagram came from the server. The server code is set to send us a
// datagram for each peer, in which the payload contains the peer's UDP
// endpoint data. We're receiving binary data here, sent using the server's
// byte ordering. We should make sure we agree on the endianness in any
// serious code.
f:= 0;
// Now we just have to add the reported peer into our peer list
for i:= 0 to n do begin
if (peers[i].host = buf.host) and (peers[i].port = buf.port) then begin
f:= 1;
end;
end;
// Only add it if we didn't have it before.
if f = 0 then begin
peers[n].host:= buf.host;
peers[n].port:= buf.port;
inc(n);
end;
si_other.sin_addr.s_addr:= buf.host;
si_other.sin_port:= buf.port;
writeln('Added peer ', inet_ntoa(si_other.sin_addr), ':', ntohs(si_other.sin_port));
writeln('Now we have ', n, ' peers');
// And here is where the actual hole punching happens. We are going to send
// a bunch of datagrams to each peer. Since we're using the same socket we
// previously used to send data to the server, our local endpoint is the same
// as before.
// If the NAT maps our local endpoint to the same public endpoint
// regardless of the remote endpoint, after the first datagram we send, we
// have an open session (the hole punch) between our local endpoint and the
// peer's public endpoint. The first datagram will probably not go through
// the peer's NAT, but since UDP is stateless, there is no way for our NAT
// to know that the datagram we sent got dropped by the peer's NAT (well,
// our NAT may get an ICMP Destination Unreachable, but most NATs are
// configured to simply discard them) but when the peer sends us a datagram,
// it will pass through the hole punch into our local endpoint.
for k:=0 to 10 - 1 do begin
// Send 10 datagrams.
for i:=0 to n - 1 do begin
si_other.sin_addr.s_addr := peers[i].host;
si_other.sin_port := peers[i].port;
// Once again, the payload is irrelevant. Feel free to send your VoIP
// data in here.
writeln('sending to ', inet_ntoa(si_other.sin_addr), ':', ntohs(si_other.sin_port ));
if fpsendto(s, pchar('hi'), 2, 0, @si_other, slen) = -1 then diep('sendto()');
end;
end;
writeln('packages send');
end else begin
// The datagram came from a peer
for i:= 0 to n - 1 do begin
// Identify which peer it came from
//if (peers[i].host = buf.host) and (peers[i].port = buf.port) then begin
// And do something useful with the received payload
writeln('Received from peer ', i);
// break;
// end;
end;
// It is possible to get data from an unregistered peer. These are some reasons
// I quickly came up with, in which this can happen:
// 1. The server's datagram notifying us with the peer's address got lost,
// or it hasn't arrived yet (likely)
// 2. A malicious (or clueless) user is sending you data on this endpoint (maybe
// unlikely)
// 3. The peer's public endpoint changed either because the session timed out,
// or because its NAT did not assign the same public endpoint when sending
// datagrams to different remote endpoints. If this happens, and we're able
// to detect this situation, we could change our peer's endpoint data to
// the correct one. If we manage to pull this off correctly, even if at most
// one client has a NAT that doesn't support hole punching, we can communicate
// directly between both peers.
end;
end;
// Actually, we never reach this point...
closeSocket(s);
end.