记一次nat的udp打洞验证

什么是nat,有什么作用

nat是网络地址转换协议,直观上,把一个公网ip,可以变成多个内网ip。
常用的家里的路由器,运营商只给一个ip,nat可以使家里的多台电脑共用这个ip。

nat的类型

  • 完全圆锥型NAT(Full cone NAT)
  • 受限圆锥型NAT(Address-Restricted cone NAT)
  • 端口受限圆锥型NAT(Port-Restricted cone NAT)
  • 对称NAT(Symmetric NAT)

依次对映射和转发条件变严

如何判断nat的类型

python提供工具pystun

要解决什么问题

现有两台电脑A和B都位于nat网络下,让这两台电脑进行通讯。建立peertopeer的连接。可以传文件什么的。

实验条件

  • 家里的电脑A,位于完全圆锥型NAT下。
  • 公司的电脑B,位于受限圆锥型NAT下。
  • 有公网服务器s

实验方案

  • A -> S ,S把ip_a:port_a存起来为addra
  • B -> S ,S把ip_b:port_b存起来为addrb
  • 同时 S 把addra发给B
  • B,按addra发消息给A,再等待收消息
  • A在第一步中发消息给S后,就进入等收数据状态
  • 因为A所在的是完全圆锥型NAT,路由器在只要是port_b的消息,都会转发给A,所以A就收到B的消息且
  • A再用收到的消息来源ip和port向b发消息
  • B因为是受限圆锥型NAT,之前跟A的ip,发过消息,这个时间来自Aip的消息就会转发给B,于是B就能收消息
  • 然后就两者可以愉快的通讯

实验代码

updclientA.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from ast import literal_eval as make_tuple
import socket
address = ('<serverip>',8888)
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.settimeout(5)

msg = "Amsg"
print "send ",msg
s.sendto(msg,address)
data,addr = s.recvfrom(2048)
print "recv from server:",data
s.sendto(msg,make_tuple(data))
data,addr2 = s.recvfrom(2048)
print "recv from peer:",data," addr:",addr2
s.sendto(msg,addr2)
print "send msg peer:",msg
for i in range(3):
datat,addrt = s.recvfrom(2048)
print "data:",datat,"addr:",addrt
s.sendto(str(i),addr2)
s.close()

udpclientB.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import socket
from ast import literal_eval as make_tuple

address = ('<serverip>',8888)
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.settimeout(5)
msg = "Bmsg"
print "send ",msg
s.sendto(msg,address)
data,addr = s.recvfrom(2048)
print "recv from server:",data

s.sendto(msg,make_tuple(data))
data,addr2 = s.recvfrom(2048)
print "recv from peer:",data,"addr:",addr2

for i in range(3):
s.sendto(str(i),addr2)
datat,addrt = s.recvfrom(2048)
print "data:",datat,"addr:",addrt
s.close()

udpserver.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import socket
address = ('0.0.0.0',8888)
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.bind(address)
print "start ... linster at 8888"
flag=True
addra=''
addrb=''
while True:
data,addr = s.recvfrom(2048)
if not data:
print "client has exist"
break
print "received:",data,"from",addr
if flag :
flag=False
addra = addr
s.sendto(str(addrb),addr)
else :
flag=True
addrb = addr
s.sendto(str(addra),addr)
# s.sendto(str(addr),addr)

s.close()

参考

https://zh.wikipedia.org/wiki/%E7%BD%91%E7%BB%9C%E5%9C%B0%E5%9D%80%E8%BD%AC%E6%8D%A2
https://github.com/laike9m/PyPunchP2P
https://github.com/jtriley/pystun
http://blog.sina.com.cn/s/blog_4d61a7570101grwi.html
https://zh.wikipedia.org/wiki/UDP%E6%89%93%E6%B4%9E
https://www.usenix.org/legacy/event/usenix05/tech/general/full_papers/ford/ford_html/