1. 程式人生 > >NAT穿透,UDP打洞程式

NAT穿透,UDP打洞程式

在看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();

執行結果如下: