Pages

Friday, 18 September 2015

udpip: 用UDP封装IP数据包建立VPN(我成功在linux桌面系统下,用来翻墙)

from http://xiaoxia.org/2012/02/21/udpip-vpn/,https://github.com/78/udptun
(相关帖子:http://briteming.blogspot.com/2015/10/python_27.html)
原理
使用Linux内核提供的tun设备建立可以在脚本读写的虚拟网卡,然后通过UDP将两个网卡的数据连接。
此方法能够使用以下特殊环境下:
1、客户端所在网络的路由不支持ppp,或者网络受到限制
2、TCP数据包被劫持或者受到限制
3、服务器是OpenVZ等不支持建立pptp,像我的burst的VPS就是这样子。
openvz虚拟技术的LINUX VPS,必须要服务商启用tun/tap)
使用
服务器:
# python udptun.py -s 86 -l 10.0.0.1/24
Configuring interface t0 with ip 10.0.0.1/24
客户端:
# python udptun.py -c linux_vps_ip,86 -l 10.0.0.2/24
Configuring interface t0 with ip 10.0.0.2/24
Setting up new gateway …
Do login …
Logged in server succefully!
(客户端机器必须为linux桌面系统。如果你在mac下运行python udptun.py -c linux_vps_ip,86 -l 10.0.0.2/24 ,会遇到提示 No such file or directory: '/dev/tun',可见mac系统默认是没有tun/tap虚拟网卡的驱动程序的,可参看此文mac上,安装tun/tap虚拟网卡的驱动程序
如果运行 python udptun.py -c linux_vps_ip,86 -l 10.0.0.2/24遇到提示:operation not permitted,则用sudo运行:
sudo python udptun.py -c linux_vps_ip,86 -l 10.0.0.2/24)
脚本代码
udptun.py:
  1. #!/usr/bin/python  
  2.   
  3. ''''' 
  4.     UDP Tunnel VPN 
  5.     Xiaoxia (xiaoxia@xiaoxia.org) 
  6.     Updated: 2012-2-21 
  7. '''  
  8.   
  9. import os, sys  
  10. import hashlib  
  11. import getopt  
  12. import fcntl  
  13. import time  
  14. import struct  
  15. import socket, select  
  16. import traceback  
  17. import signal  
  18. import ctypes  
  19. import binascii  
  20.   
  21. SHARED_PASSWORD = hashlib.sha1("xiaoxia").digest()  
  22. TUNSETIFF = 0x400454ca  
  23. IFF_TUN   = 0x0001  
  24.   
  25. BUFFER_SIZE = 8192  
  26. MODE = 0  
  27. DEBUG = 0  
  28. PORT = 0  
  29. IFACE_IP = "10.0.0.1/24"  
  30. MTU = 1500  
  31. TIMEOUT = 60*10 # seconds  
  32.   
  33. class Tunnel():  
  34.     def create(self):  
  35.         try:  
  36.             self.tfd = os.open("/dev/net/tun", os.O_RDWR)  
  37.         except:  
  38.             self.tfd = os.open("/dev/tun", os.O_RDWR)  
  39.         ifs = fcntl.ioctl(self.tfd, TUNSETIFF, struct.pack("16sH""t%d", IFF_TUN))  
  40.         self.tname = ifs[:16].strip("\x00")  
  41.       
  42.     def close(self):  
  43.         os.close(self.tfd)  
  44.           
  45.     def config(self, ip):  
  46.         print "Configuring interface %s with ip %s" % (self.tname, ip)  
  47.         os.system("ip link set %s up" % (self.tname))  
  48.         os.system("ip link set %s mtu 1000" % (self.tname))  
  49.         os.system("ip addr add %s dev %s" % (ip, self.tname))  
  50.           
  51.     def config_routes(self):  
  52.         if MODE == 1# Server  
  53.             pass  
  54.         else# Client  
  55.             print "Setting up new gateway ..."  
  56.             # Look for default route  
  57.             routes = os.popen("ip route show").readlines()  
  58.             defaults = [x.rstrip() for x in routes if x.startswith("default")]  
  59.             if not defaults:  
  60.                 raise Exception("Default route not found, maybe not connected!")  
  61.             self.prev_gateway = defaults[0]  
  62.             self.prev_gateway_metric = self.prev_gateway + " metric 2"  
  63.             self.new_gateway = "default dev %s metric 1" % (self.tname)  
  64.             self.tun_gateway = self.prev_gateway.replace("default", IP)  
  65.             self.old_dns = file("/etc/resolv.conf""rb").read()  
  66.             # Remove default gateway  
  67.             os.system("ip route del " + self.prev_gateway)  
  68.             # Add default gateway with metric  
  69.             os.system("ip route add " + self.prev_gateway_metric)  
  70.             # Add exception for server  
  71.             os.system("ip route add " + self.tun_gateway)  
  72.             # Add new default gateway  
  73.             os.system("ip route add " + self.new_gateway)  
  74.             # Set new DNS to 8.8.8.8  
  75.             file("/etc/resolv.conf""wb").write("nameserver 8.8.8.8")  
  76.               
  77.     def restore_routes(self):  
  78.         if MODE == 1# Server  
  79.             pass  
  80.         else# Client  
  81.             print "Restoring previous gateway ..."  
  82.             os.system("ip route del " + self.new_gateway)  
  83.             os.system("ip route del " + self.prev_gateway_metric)  
  84.             os.system("ip route del " + self.tun_gateway)  
  85.             os.system("ip route add " + self.prev_gateway)  
  86.             file("/etc/resolv.conf""wb").write(self.old_dns)  
  87.       
  88.     def run(self):  
  89.         global PORT  
  90.         self.udpfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  
  91.         if MODE == 1:  
  92.             self.udpfd.bind(("", PORT))  
  93.         else:  
  94.             self.udpfd.bind(("", 0))  
  95.               
  96.         self.clients = {}  
  97.         self.logged = False  
  98.         self.try_logins = 5  
  99.         self.log_time = 0  
  100.               
  101.         while True:  
  102.             if MODE == 2 and not self.logged and time.time() - self.log_time > 2.:  
  103.                 print "Do login ..."  
  104.                 self.udpfd.sendto("LOGIN:" + SHARED_PASSWORD + ":" +   
  105.                     IFACE_IP.split("/")[0], (IP, PORT))  
  106.                 self.try_logins -= 1  
  107.                 if self.try_logins == 0:  
  108.                     raise Exception("Failed to log in server.")  
  109.                 self.log_time = time.time()  
  110.                   
  111.             rset = select.select([self.udpfd, self.tfd], [], [], 1)[0]  
  112.             for r in rset:  
  113.                 if r == self.tfd:  
  114.                     if DEBUG: os.write(1">")  
  115.                     data = os.read(self.tfd, MTU)  
  116.                     if MODE == 1# Server  
  117.                         src, dst = data[16:20], data[20:24]  
  118.                         for key in self.clients:  
  119.                             if dst == self.clients[key]["localIPn"]:  
  120.                                 self.udpfd.sendto(data, key)  
  121.                         # Remove timeout clients  
  122.                         curTime = time.time()  
  123.                         for key in self.clients.keys():  
  124.                             if curTime - self.clients[key]["aliveTime"] > TIMEOUT:  
  125.                                 print "Remove timeout client", key  
  126.                                 del self.clients[key]  
  127.                     else# Client  
  128.                         self.udpfd.sendto(data, (IP, PORT))  
  129.                 elif r == self.udpfd:  
  130.                     if DEBUG: os.write(1"<")  
  131.                     data, src = self.udpfd.recvfrom(BUFFER_SIZE)  
  132.                     if MODE == 1# Server  
  133.                         key = src  
  134.                         if key not in self.clients:  
  135.                             # New client comes  
  136.                             try:  
  137.                                 if data.startswith("LOGIN:"and data.split(":")[1]==SHARED_PASSWORD:  
  138.                                     localIP = data.split(":")[2]  
  139.                                     self.clients[key] = {"aliveTime": time.time(),   
  140.                                                         "localIPn": socket.inet_aton(localIP)}  
  141.                                     print "New Client from", src, "request IP", localIP  
  142.                                     self.udpfd.sendto("LOGIN:SUCCESS", src)  
  143.                             except:  
  144.                                 print "Need valid password from", src  
  145.                                 self.udpfd.sendto("LOGIN:PASSWORD", src)  
  146.                         else:  
  147.                             # Simply write the packet to local or forward them to other clients ???  
  148.                             os.write(self.tfd, data)  
  149.                             self.clients[key]["aliveTime"] = time.time()  
  150.                     else# Client  
  151.                         if data.startswith("LOGIN"):  
  152.                             if data.endswith("PASSWORD"):  
  153.                                 self.logged = False  
  154.                                 print "Need password to login!"  
  155.                             elif data.endswith("SUCCESS"):  
  156.                                 self.logged = True  
  157.                                 self.try_logins = 5  
  158.                                 print "Logged in server succefully!"  
  159.                         else:  
  160.                             os.write(self.tfd, data)  
  161.   
  162.           
  163. def usage(status = 0):  
  164.     print "Usage: %s [-s port|-c serverip] [-hd] [-l localip]" % (sys.argv[0])  
  165.     sys.exit(status)  
  166.   
  167. def on_exit(no, info):  
  168.     raise Exception("TERM signal caught!")  
  169.   
  170. if __name__=="__main__":  
  171.     opts = getopt.getopt(sys.argv[1:],"s:c:l:hd")  
  172.     for opt,optarg in opts[0]:  
  173.         if opt == "-h":  
  174.             usage()  
  175.         elif opt == "-d":  
  176.             DEBUG += 1  
  177.         elif opt == "-s":  
  178.             MODE = 1  
  179.             PORT = int(optarg)  
  180.         elif opt == "-c":  
  181.             MODE = 2  
  182.             IP, PORT = optarg.split(",")  
  183.             IP = socket.gethostbyname(IP)  
  184.             PORT = int(PORT)  
  185.         elif opt == "-l":  
  186.             IFACE_IP = optarg  
  187.       
  188.     if MODE == 0 or PORT == 0:  
  189.         usage(1)  
  190.       
  191.     tun = Tunnel()  
  192.     tun.create()  
  193.     tun.config(IFACE_IP)  
  194.     signal.signal(signal.SIGTERM, on_exit)  
  195.     tun.config_routes()  
  196.     try:  
  197.         tun.run()  
  198.     except KeyboardInterrupt:  
  199.         pass  
  200.     except:  
  201.         print traceback.format_exc()  
  202.     finally:  
  203.         tun.restore_routes()  
  204.         tun.close() 


  1. hi,看了你之前的ICMP Tunnel,在vps上运行脚本后都提示
    tun.create()
    File “udptun.py”, line 38, in create
    self.tfd = os.open(“/dev/tun”, os.O_RDWR)
    OSError: [Errno 2] No such file or directory: ‘/dev/tun’
    我谁否缺了什么东西?
    回复 
    1. Xiaoxia文章作者
      你的vps是openvz的吗?
      如果是的话,需要有tun设备的权限的。如果是burst的vps,需要在VPS的管理面板的选项“Enable Tun/Tap”打开tun。
      这个问题网上应该有很多解决方案。
      回复 
    1. Xiaoxia文章作者
      可以呀,使用长密钥的XOR,可以保证数据的安全性 :)
      我想,以后增强这个VPN脚本的时候,会加入这个简单的加密功能。
      目前G。F。W还不会窥探UDP的数据,除了DNS数据包。
      1. Xiaoxia文章作者
        嗯,暂时还没有这个需求。还有没啥不见的光的 :)
        墙也不会嗅探UDP。
        fish
        hi.怎么我把你的代码保存为py文件后,在本地windows机器下,双击该py文件,弹出一个dos窗口,闪了一下就消失了?我的windows机器装了python-2.7.3
        回复 
        1. Xiaoxia文章作者
          你可以先打开控制台,然后在控制台里输入py文件的路径来执行脚本,出错信息会显示出来。
          回复 
          1. fish
            hi.我运行了c:\python27\python.exe,然后在该dos窗口中输入i:\udptun.py后,回车,显示:
            File “”,line 1
            SyntaxError: invalid syntax
            难道你的代码哪里写错了?
            回复 
              1. hi.
                我操作成功,terminal里的提示跟你文章一样,不过建立连接后,还是翻不了墙阿,怎么回事?

因为DNS没改过来。建议楼上多通过实验来判断问题所在。
Q: xiaoxia你好,请教一个问题,我想把以上的客户端做成支持多客户端,
只建立一个tun interface可以同时连接多客户端么?
还是每个客户端都需要建立一个tun 接口。
麻烦了,等待你的回复。

A: 不需要多个tun的,你可能搞错了。负责连接多客户端的,应该是程序。程序使用的虚拟网卡,只要一个就够。不然,这些客户端怎么互相通信呢。

Q: 虾哥,现在我倒是能理解一个tun设备就能做到多客户端连接,
但是我运行你的这个程序,可以稳定上网大约半小时(稳定多长时间也不固定)
之后客户端就收不到udp包,终止客户端重新运行客户端又会正常一小会儿。之后又收不到回包。
从调试上看,只发包,没有回包,server 上发包,回包都有,应该正常。
我用tcpdump看了下,也是客户端没有回包。和调试信息反应的一样
我家长城宽带连着无线路由上网,vps使用的亚马逊ec2的免费一年那个。
只连接了一个客户端(一台debian)。
虾哥能简单分析下原因么,如何提高稳定性,先谢谢了。

A:  会不会是因为MTU的问题呢?尝试把MTU改小一点?? 

Q: 今天将mtu改成1400和1000,调小了,出现收不到回包的次数少了,但是还是会出现收不到回包的现象。
原因可能不是程序的问题。 

Q: 程序可在Xen Linux执行,不过路由的设定与恢复貌似把路由顺序搞反了。
max@max-K43SV:.bin$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 eth0
169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 eth0
192.168.1.0 0.0.0.0 255.255.255.0 U 1 0 0 eth0
max@max-K43SV:.bin$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 0.0.0.0 0.0.0.0 U 1 0 0 t0
0.0.0.0 192.168.1.1 0.0.0.0 UG 2 0 0 eth0
10.0.0.0 0.0.0.0 255.255.255.0 U 0 0 0 t0
169.254.0.0 0.0.0.0 255.255.0.0 U 1000 0 0 eth0
xxx.xxx.xx.xx 192.168.1.1 255.255.255.255 UGH 0 0 0 eth0
192.168.1.0 0.0.0.0 255.255.255.0 U 1 0 0 eth0
不好意思啊,看错了,原来Ubuntu的路由表顺序和CentOS是相反的,学linux的时候是用CentOS虚拟机,平时用是用ubuntu … 囧。

Q: 在windows 上测试您的程序,fcntl import不了,我换了 tornado的 win32_support.py ,但提示没有/dev/tun或/dev/net/tun,当然因为windows这个器件是linux的。请问在windows上可以怎么改呢?

A: windows需要tap32驱动,这个代码是只能在linux上工作的。
----------------------------------------------------

UDP隧道原理剖析与实现


那我就来说说其中的原理吧,也认真的总结一下相关的知识。当然提到隧道,并不是我们通常意义上火车穿过的隧道,这就要看你如何理解网络中协议二字,理解不同协议之间的层次关系,才是理解隧道二字真正的含义,简而言之,就是以一种协议封装另一种协议,在发送和接受的时候,真确地封装和解封即可。

/dev/net/tun设备

tun在用户空间提供了接受和发送数据包的介质,相当于物理设备中的网卡,读写IP数据包。如果在你的系统中打开tun设备,然后配置路由,那么你系统产生的ip数据包,将通过tun设备,这样你就可以读取每一个ip包了,甚至可以修改,多棒啊!另外还有一个tap,它可以读取以太网的帧。

建立tun隧道

左边的计算机如何通过右边的计算机上网呢,假设我们在二台电脑上已经打开了tun设备,并且配置了路由,此时二台电脑的ip包将分别通过tun虚拟网卡。

在此时,假设左边的计算机产生一个ip数据包,它通过tun设备,然后读取该ip数据包,通过socket发送给右边的计算机,右边的计算机在socket里读取该ip数据包,然后将ip数据包写入自己的tun设备,结果该数据包就发送到了Internet。

相反,从Internet过来的数据包呢,首先右边的计算机从tun读取ip数据包,然后根据ip头的目的地址,可以分析得知是从那一台电脑上过来的,然后通过socket将数据包发送到相应的计算机,当另一台计算机从socket读取数据后,然后就直接写入该电脑的tun即可。

结果就是在IP层又封装了IP数据包,传递给另一个计算机,然后将外层的IP头去掉,就得到了原始的IP数据包。

这样就可以实现通过另外一个台计算机上外网了,大家都懂的.

资料:
http://www.kernel.org/doc/Documentation/networking/tuntap.txt
http://wangcong.org/blog/archives/1679

FROM http://www.ibaiyang.org/2012/08/28/udp-tunnel-analysis/