UDP穿越NAT的具体设计

首先,Client A登录服务器,NAT
1为这次的Session分配了一个端口60000,那么Server
S收到的Client A的地址是200.0.0.132:60000,这就是ClientA的外网地址了。同样,Client
B登录Server S,NAT
B给此次Session分配的端口是40000,那么Server
S收到的B的地址是200.0.0.133:40000。

此时,Client A与Client
B都可以与ServerS通信了。如果Client
A此时想直接发送信息给ClientB,那么他可以从Server
S那儿获得B的公网地址200.0.0.133:40000,在双方都是FullCone
NAT的情况下,Client A就能够直接往ClientB的公网IP:Port发送数据了。

总结一下这个过程:如果ClientA想向Client
B发送信息,那么双方都需要在Server上通信一次做登记,然后将Client
B的公网地址和端口发送给ClientA,最好Client
A直接连接ClientB的公网地址和端口。呵呵,是不是很绕口,不过没关系,想一想就很清楚了。

注意:以上过程只适合于ConeNAT的情况,如果是Symmetric
NAT,那么当Client A向Client
B在NAT设备上没有形成一个Session,而只有和ClientB 连接Server的Session,所以NAT设备是会对包进行丢弃的。不过如何Symmetric
NAT的端口是按照顺序进行开启的话,那可以通过Server命令Client
B发送数据给ClientA的IP:Port,然后Client
A通过线性端口扫描对端口进行猜测。不过这个方式存在失败率,而且不能明确了解NAT设备开启的端口的规律,所以没有去实现。

四种类型的NAT介绍

NAT的分类:在STUN协议中,根据内部终端的地址(LocalIP:LocalPort)到NAT出口的公网地址(PublicIP:PublicPort)的影射方式,把NAT分为四种类型(英文原文详见rfc3489标准:http://www.ietf.org/rfc/rfc3489.txt):

1.Full Cone

这种NAT内部的机器A连接过外网机器C后,NAT会打开一个端口,然后外网的任何发到这个打开的端口的UDP数据报都可以到达A.不管是不是C发过来的。

2. Restricted Cone

这种NAT内部的机器A连接过外网的机器C后,NAT打开一个端口,然后C可以用任何端口和A通信,其他的外网机器不行。

3. Port Restricted Cone

这种NAT内部的机器A连接过外网的机器C后,NAT打开一个端口,然后C可以用原来的端口和A通信,其他的外网机器不行。

4.Symmetic

对于这种NAT.连接不同的外部目标。原来NAT打开的端口会变化,而Cone
NAT不会,虽然可以用端口猜测,但是成功的概率很小,因此放弃这种NAT的UDP打洞。

不同NAT环境下UDP穿透可行性分析

两侧NAT属于Full
Cone NAT

则无论A侧NAT属于Cone
NAT还是SymmetricNAT,包都能顺利到达B。如果P2P程序设计得好,使得B主动到A的包也能借用A主动发起建立的通道的话,则即使A侧NAT属于Symmetric
NAT,B发出的包也能顺利到达A。

B侧NAT属于Restricted
Cone或PortRestricted Cone

则包不能到达B。再细分两种情况

(1)、A侧NAT属于RestrictedCone或Port
Restricted Cone

虽然先前那个初始包不曾到达B,但该发包过程已经在A侧NAT上留下了足够的记录。如果在这个记录没有超时之前,B也重复和A一样的动作,即向A发包,虽然A侧NAT属于RestrictedCone或Port
Restricted Cone,但先前A侧NAT已经认为A已经向B发过包,故B向A发包能够顺利到达A。同理,此后A到B的包,也能顺利到达。

(2)、A侧NAT属于SymmetricNAT

因为A侧NAT属于Symmetric
NAT,且最初A到Server发包的过程在A侧NAT留下了记录,故A到B发包过程在A侧NAT上留下的记录端口产生了变化。而B向A的发包,只能根据Sever给他的关于A的信息,发往A,因为A端口受限,故此路不通。再来看B侧NAT,由于B也向A发过了包,且B侧NAT属于Restricted
Cone或Port Restricted Cone,故在B侧NAT上留下的记录,此后,如果A还继续向B发包的话(因为同一目标,故仍然使用前面的映射),如果B侧NAT属于Restricted
Cone,则从A(210.21.12.140:8001)来的包能够顺利到达B;如果B侧NAT属于PortRestricted
Cone,则包永远无法到达B。

结论1:只要单侧NAT属于Full
Cone NAT,即可实现双向通信。

结论2:只要两侧NAT都不属于Symmetric
NAT,也可双向通信。换种说法,只要两侧NAT都属于Cone
NAT,即可双向通信。

结论3:一侧NAT属于Symmetric
NAT,另一侧NAT属于Restricted
Cone,也可双向通信。

结论4,两个都是Symmetric
NAT或者一个是SymmetricNAT、另一个是Port
Restricted Cone,则不能双向通信。

常见的NAT实现

由于存在有4种NAT的类型,所以在方案设计中,必须要考虑到用哪种软件或者设备来模拟NAT环境。我在实现过程中尝试过了很多选择,包括Linux下的iptables,海蜘蛛的软路由,使用CISCO的IOS模拟,window2003自带的NAT服务,还有VMWARE自带的NAT网络环境。最终发现window2003和VMWARE是支持full
cone nat的。

iptables

通过前人的试验和我自己的验证,iptables确实是货真价实的Symmetric NAT。不过也有人通过改写在Linux2.4内核下的iptables源码将SymmetricNAT改为了Full
Cone NAT,也可以通过编写规则作弊的方式实现full nat cone,不过我最终没有采取iptables。一是改写源码的方式比较繁琐,需要修改很多个源码文件。而使用编写规则作弊的方式也觉得有点自欺欺人了。

window2003

Window 2003自带有路由和远程访问服务,其中包含有NAT服务。经过测试,可以实现完全的Full Cone NAT。

ciscoIOS模拟

我曾使用c3640-is-mz.122-27版本的IOS,通过Dynamips模拟路由器配置NAT,发现不支持Full
Cone NAT。可能有老版本的IOS会有支持,不过我没有一一测试。

Server端代码:


  1. #!/usr/bin/python

  2. #coding:utf-8

  3. import socket, sys, SocketServer, threading, thread, time

  4. SERVER_PORT = 1234

  5. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

  6. sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

  7. sock.bind((”, SERVER_PORT))

  8. user_list = []

  9. def server_handle():

  10. whileTrue:

  11. cli_date, cli_pub_add = sock.recvfrom(8192)

  12. now_user = []

  13. headder = []

  14. cli_str = {}

  15. headder = cli_date.split(‘\t’)

  16. for one_line in headder:

  17. str = {}

  18. str = one_line

  19. args = str.split(‘:’)

  20. cli_str[args[0]] = args[1]

  21. if cli_str[‘type’] == ‘login’ :

  22. del cli_str[‘type’]

  23. now_user = cli_str

  24. now_user[‘cli_pub_ip’] = cli_pub_add[0]

  25. now_user[‘cli_pub_port’] = cli_pub_add[1]

  26. user_list.append(now_user)

  27. toclient = ‘info#%s login in successful , the info from server’%now_user[‘user_name’]

  28. sock.sendto(toclient,cli_pub_add)

  29. print’-‘*100

  30. print”%s 已经登录,公网IP:%s 端口:%d\n”%(now_user[‘user_name’],now_user[‘cli_pub_ip’],now_user[‘cli_pub_port’])

  31. print”以下是已经登录的用户列表”

  32. for one_user in user_list:

  33. print’用户名:%s 公网ip:%s 公网端口:%s 私网ip:%s 私网端口:%s’%(one_user[‘user_name’],one_user[‘cli_pub_ip’],one_user[‘cli_pub_port’],one_user[‘private_ip’],one_user[‘private_port’])

  34. elif cli_str[‘type’] == ‘alive’:

  35. pass

  36. elif cli_str[‘type’] == ‘logout’ :

  37. pass

  38. elif cli_str[‘type’] == ‘getalluser’ :

  39. print’-‘*100

  40. for one_user in user_list :

  41. toclient = ‘getalluser#username:%s pub_ip:%s pub_port:%s pri_ip:%s pri_port:%s’%(one_user[‘user_name’],one_user[‘cli_pub_ip’],one_user[‘cli_pub_port’],one_user[‘private_ip’],one_user[‘private_port’])

  42. sock.sendto(toclient,cli_pub_add)

  43. if __name__ == ‘__main__’:

  44. thread.start_new_thread(server_handle, ())

  45. print’服务器进程已启动,等待客户连接’

  46. whileTrue:

  47. for one_user in user_list:

  48. toclient = ‘keepconnect#111’

  49. sock.sendto(toclient,(one_user[‘cli_pub_ip’],one_user[‘cli_pub_port’]))

  50. time.sleep(1)

Client端代码:


  1. #!/usr/bin/python

  2. #coding:utf-8

  3. import socket, SocketServer, threading, thread, time

  4. CLIENT_PORT = 4321

  5. SERVER_IP = “200.0.0.128”

  6. SERVER_PORT = 1234

  7. user_list = {}

  8. local_ip = socket.gethostbyname(socket.gethostname())

  9. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

  10. def server_handle():

  11. print’客户端线程已经启动 , 等待其它客户端连接’

  12. whileTrue:

  13. data, addr = sock.recvfrom(8192)

  14. data_str = data.split(‘#’)

  15. data_type = data_str[0]

  16. data_info = data_str[1]

  17. if data_type == ‘info’ :

  18. del data_str[0]

  19. print data_info

  20. if data_type == ‘getalluser’ :

  21. data_sp = data_info.split(’ ‘)

  22. user_name = data_sp[0].split(‘:’)[1]

  23. del data_sp[0]

  24. user_list[user_name] = {}

  25. for one_line in data_sp:

  26. arg = one_line.split(‘:’)

  27. user_list[user_name][arg[0]] = arg[1]

  28. if data_type == ‘echo’ :

  29. print data_info

  30. if data_type == ‘keepconnect’:

  31. messeg = ‘type:alive’

  32. sock.sendto(messeg, addr)

  33. if __name__ == ‘__main__’:

  34. thread.start_new_thread(server_handle, ())

  35. time.sleep(0.1)

  36. cmd = raw_input(‘输入指令>>’)

  37. whileTrue:

  38. args = cmd.split(’ ‘)

  39. if args[0] == ‘login’:

  40. user_name = args[1]

  41. local_uname = args[1]

  42. address = “private_ip:%s private_port:%d” % (local_ip, CLIENT_PORT)

  43. headder = “type:login\tuser_name:%s\tprivate_ip:%s\tprivate_port:%d” % (user_name,local_ip,CLIENT_PORT)

  44. sock.sendto(headder, (SERVER_IP, SERVER_PORT))

  45. elif args[0] == ‘getalluser’:

  46. headder = “type:getalluser\tuser_name:al”

  47. sock.sendto(headder,(SERVER_IP,SERVER_PORT))

  48. print’获取用户列表中。。。’

  49. time.sleep(1)

  50. for one_user in user_list:

  51. print’username:%s pub_ip:%s pub_port:%s pri_ip:%s pri_port:%s’%(one_user,user_list[one_user][‘pub_ip’],user_list[one_user][‘pub_port’],user_list[one_user][‘pri_ip’],user_list[one_user][‘pri_port’])

  52. elif args[0] == ‘connect’:

  53. user_name = args[1]

  54. to_user_ip = user_list[user_name][‘pub_ip’]

  55. to_user_port = int(user_list[user_name][‘pub_port’])

  56. elif args[0] ==’echo’:

  57. m = ’ ‘.join(args[1:])

  58. messeg = ‘echo#from %s:%s’%(local_uname,m)

  59. sock.sendto(messeg, (to_user_ip, to_user_port))

  60. time.sleep(0.1)

  61. cmd = raw_input(‘输入指令>>’)

Python实现简单的udp打洞(P2P)的更多相关文章

  1. UDP打洞、P2P组网方式研究

    catalogue . NAT概念 . P2P概念 . UDP打洞 . P2P DEMO . ZeroNet P2P 1. NAT概念 在STUN协议中,根据内部终端的地址(LocalIP:Local ...

  2. p2p的UDP打洞原理

    >>>>>>>>>>>>>>>>>>>>>>>>> ...

  3. C# p2p UDP穿越NAT,UDP打洞源码

    思路如下(参照源代码): 1. frmServer启动两个网络侦听,主连接侦听,协助打洞的侦听. 2. frmClientA和frmClientB分别与frmServer的主连接保持联系. 3. 当f ...

  4. NAT穿透(UDP打洞)

    1.NAT(Network Address Translator)介绍 NAT有两大类,基本NAT和NAPT. 1.1.基本NAT 静态NAT:一个公网IP对应一个内部IP,一对一转换 动态NAT:N ...

  5. UDP 打洞 原理解释

    终于找到了一份满意的UDP打洞原理解释,附上正文,自己整理了一下源码 3.3. UDP hole punching UDP打洞技术 The third technique, and the one o ...

  6. udp打洞( NAT traversal )的方法介绍

    http://www.cnblogs.com/whyandinside/archive/2010/12/08/1900492.html http://www.gzsec.com/oldversion/ ...

  7. Python基础教程之udp和tcp协议介绍

    Python基础教程之udp和tcp协议介绍 UDP介绍 UDP --- 用户数据报协议,是一个无连接的简单的面向数据报的运输层协议.UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但 ...

  8. Udp打洞原理和源代码。

    所谓udp打洞就是指客户端A通过udp协议向服务器发送数据包,服务器收到后,获取数据包,并且 可获取客户端A地址和端口号.同样在客户端B发送给服务器udp数据包后,服务器同样在收到B发送过来 的数据包 ...

  9. UDP打洞原理及代码

    来源:http://www.fenbi360.net/Content.aspx?id=1021&t=jc UDP"打洞"原理 1.       NAT分类 根据Stun协议 ...

随机推荐

  1. loj#6062. 「2017 山东一轮集训 Day2」Pair hall定理+线段树

    题意:给出一个长度为 n的数列 a和一个长度为 m 的数列 b,求 a有多少个长度为 m的连续子数列能与 b匹配.两个数列可以匹配,当且仅当存在一种方案,使两个数列中的数可以两两配对,两个数可以配对当 ...

  2. MySQL 处理海量数据时一些优化查询速度方法

    1.应尽量避免在where子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描. 2.对查询进行优化,应尽量避免全表扫描,首先应考虑在where及order by设计的列上建立 ...

  3. 非常不错的地区三级联动,js简单易懂。封装起来了

    首先需要引入area.js,然后配置并初始化插件: 例: <!-- 绑定银行卡开始 --> <script src="js/area.js"></sc ...

  4. flask-数据库模型设计2

    3.数据库模型设计 3.1构建蓝图项目目录 1.前后台项目目录分析   2.蓝图构建项目目录 蓝图:一个应用中或跨域应用制作组件和支持通用模式. 蓝图的作用:将不同的功能模块化 构建大型应用 优化项目 ...

  5. docker 系列之 配置阿里云镜像加速器

    1.登录阿里云 2.登录后找到右上角的“管理中心”,点击进入后>点击“镜像加速器”;剩下的安装文档配置就好 问题1:配置完后还是提示:Tag latest not found in reposi ...

  6. python and pycharm and django 环境配置

    python 安装   https://www.python.org/ 我的是win7 32位,下载exe文件傻瓜式安装…… cmd 输入命令 python  则安装成功 如果不能进入,则有可能是环境 ...

  7. CNN autoencoder 先降维再使用kmeans进行图像聚类 是不是也可以降维以后进行iforest处理?

    import keras from keras.datasets import mnist from keras.models import Sequential from keras.layers ...

  8. Android 之常用布局

    LinearLayout 线性布局. android:orientation="horizontal" 制定线性布局的排列方式 水平 horizontal 垂直 vertical ...

  9. 加密解密 AES RSA MD5 SHA

    加密解密: 对称加密:加密和解密相同秘钥.常见算法:AES, XTEA, 3DES. 非对称加密: 公钥加密 私钥加密. 加密和解密秘钥不同.常见算法:RSA OpenSSL> genrsa - ...

  10. Vue + Element UI 实现权限管理系统(更换皮肤主题)

    自定义主题 命令行主题工具 1.安装主题工具 首先安装「主题生成工具」,可以全局安装或者安装在当前项目下,推荐安装在项目里,方便别人 clone 项目时能直接安装依赖并启动. yarn add ele ...