在看NAT穿透和UDP打洞原理,网上都是讲原理,没有程序,我把程序写出来。
server.py,辅助打洞的服务器。
peer.server.py,被打洞的节点。
peer.client.py,主动打洞的节点。
基本原理是:
1. peer.client向peer.server发个包,把自己的洞打好,这样peer.server可以给peer.client发包。
这个包会被NAT拒绝掉,所以peer.server是收不到这个包的(当然如果没有NAT,譬如在一台机器上,是可以收到的,就不算打洞了)。
2. peer.client向server发个包,server给peer.server发个包,peer.server再给peer.client发个包。
这样peer.client就可以给peer.server发包了。因为peer.client在第1步把自己的洞打好了,所以两者可以互发消息。
3. 打洞完毕,peer.client可以和peer.server通信。
用的三台虚拟机,server为bridge网卡,peer.server和peer.client为NAT网卡。
#!/usr/bin/python2.6
# server.py
import signal;
import sys;
def handler(signum, frame):
print 'usr press ctrl+c, exit';
sys.exit(0)
signal.signal(signal.SIGINT, handler)
##################################################################
import socket;
import json;
import time;
if len(sys.argv) <= 1:
print """Usage: %s <port>
port the [UDP] port to bind.
For example:
%s 2013"""%(sys.argv[0], sys.argv[0]);
sys.exit(1);
port=sys.argv[1];
print "NAT traversal & udp hold-punching"
print "Server-side which used to help the client behind NAT to hold-punching."
max_packet_size = 4096
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);
s.bind(('', int(port)));
print "UDP bind at %s"%(port);
peers = [];
def get_peer(code, required_peer_id):
for peer in peers:
id = peer["id"];
if id != required_peer_id:
continue;
(peer_id, peer_address) = (id, peer["address"]);
return (code, peer_id, peer_address);
return (1, 0, None);
while True:
(data, address) = s.recvfrom(max_packet_size);
#print "get data from %s: %s"%(address, data);
obj = json.loads(data);
action = obj["action"];
code = 0;
if action == "join":
id = obj["id"];
for i in range(0, len(peers)):
if peers[i]["id"] != id:
continue;
del peers[i];
break;
peers.append({"id":id, "address":address});
print "[join] %s %s"%(id, address);
continue;
if action == "find":
(code, peer_server_id, peer_server_address) = get_peer(code, obj["peer_server_id"]);
(code, peer_client_id, peer_client_address) = get_peer(code, obj["peer_client_id"]);
print "[find] %s %s find %s %s"%(peer_client_id, peer_server_id, peer_server_id, peer_server_address);
s.sendto(json.dumps({"code": code, "peer_server_address": peer_server_address, "peer_server_id": peer_server_id, "peer_client_address":peer_client_address}), address);
if action == "hole_punching":
(code, peer_server_id, peer_server_address) = get_peer(code, obj["peer_server_id"]);
(code, peer_client_id, peer_client_address) = get_peer(code, obj["peer_client_id"]);
time.sleep(3);
print "[hole-punching] (%s)%s <==> (%s)%s"%(peer_client_id, peer_client_address, peer_server_id, peer_server_address);
s.sendto(json.dumps({"action": "hole_punching", "peer_server_address":peer_server_address, "peer_client_address": peer_client_address}), tuple(peer_server_address));
pass;
s.close();
#!/usr/bin/python2.6
# peer.server.py
import signal;
import sys;
def handler(signum, frame):
print 'usr press ctrl+c, exit';
sys.exit(0)
signal.signal(signal.SIGINT, handler)
##################################################################
import socket;
import json;
import time;
if len(sys.argv) <= 3:
print """Usage: %s <server> <port> <id>
server the server to connect to.
port the UDP port to connect to.
id the id of peer.
For example:
%s 192.168.20.118 2013 peer.server"""%(sys.argv[0], sys.argv[0]);
sys.exit(1);
(server, port, id) = sys.argv[1:];
port = int(port);
print "NAT traversal & udp hold-punching"
print "Peer-server-side which is behind NAT to hold-punching."
print "peer-server means it wait for peer-client to hole-punching with"
server_endpoint = (server, port)
max_packet_size = 4096
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);
# join server
s.sendto(json.dumps({"action": "join", "id": id}), server_endpoint);
print "[join] %s"%(id)
while True:
# recv request from server.
(data, address) = s.recvfrom(max_packet_size);
#print "get data from %s: %s"%(address, data);
obj = json.loads(data);
action = obj["action"];
if action != "hole_punching":
continue;
if "peer_client_address" not in obj:
continue;
peer_client_address = obj["peer_client_address"];
peer_server_address = obj["peer_server_address"]
print "[open_hole] by %s"%(str(address));
# send a packet to peer.client to open the hole.
s.sendto(json.dumps({"action": "open_hole", "id": id}), tuple(peer_client_address));
data = json.dumps({"action": "video", "video": "xxxxx video xxxxxx"})
while True:
ret = s.sendto(data, tuple(peer_client_address));
print "[success] %s ===> %s: %s"%(peer_server_address, peer_client_address, data);
time.sleep(3);
break;
s.close();
#!/usr/bin/python2.6
# peer.client.py
import signal;
import sys;
def handler(signum, frame):
print 'usr press ctrl+c, exit';
sys.exit(0)
signal.signal(signal.SIGINT, handler)
##################################################################
import socket;
import json;
import time;
if len(sys.argv) <= 4:
print """Usage: %s <server> <port> <id> <peer_server_id>
server the server to connect to.
port the UDP port to connect to.
id the id of peer.
peer_server_id the id of peer to hole-punching to.
For example:
%s 192.168.20.118 2013 peer.client peer.server"""%(sys.argv[0], sys.argv[0]);
sys.exit(1);
(server, port, id, peer_server_id)=sys.argv[1:];
port = int(port);
print "NAT traversal & udp hold-punching"
print "Peer-side which is behind NAT to hold-punching."
server_endpoint = (server, port)
max_packet_size = 4096
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM);
# join server
s.sendto(json.dumps({"action": "join", "id": id}), server_endpoint);
print "[join] %s"%(id)
(peer_client_address, peer_server_address) = (None, None);
while True:
# find the peer to hole-punching
s.sendto(json.dumps({"action": "find", "peer_client_id": id, "peer_server_id": peer_server_id}), server_endpoint);
# discovery result
(data, address) = s.recvfrom(max_packet_size);
#print "get data from %s: %s"%(address, data);
obj = json.loads(data);
code = obj["code"];
if code is not 0:
print "[find] peer %s not found"%(peer_server_id);
time.sleep(1);
continue;
peer_server_address = obj["peer_server_address"];
peer_client_address = obj["peer_client_address"];
break;
# to punching hole.
print "[find] peer(%s) address is %s"%(peer_server_id, peer_server_address);
# step 1: directly send a packet to peer, open self tunnel.
print "[hole-punching] try to punching hole to %s"%(peer_server_address);
s.sendto(json.dumps({"action": "hole_punching", "peer_client_id": id, "peer_client_address":peer_client_address, "peer_server_address":peer_server_address}), tuple(peer_server_address));
# step 2: send a packet to server, open the peer tunnel.
print "[hole-punching] try to use server %s to punching hole"%(str(server_endpoint));
s.sendto(json.dumps({"action": "hole_punching", "peer_client_id": id, "peer_server_id": peer_server_id}), server_endpoint);
while True:
(data, address) = s.recvfrom(max_packet_size);
print "[success] %s ===> %s: %s"%(address, peer_client_address, data);
s.close();
执行结果如下: