from http://xiaoxia.org/2012/02/21/udpip-vpn/,https://github.com/78/udptun
(相关帖子:http://briteming.blogspot.com/2015/10/python_27.html)
(相关帖子: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)
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
客户端:
(客户端机器必须为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
Configuring interface t0 with ip 10.0.0.2/24
Setting up new gateway …
Do login …
Logged in server succefully!
如果运行 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:
- #!/usr/bin/python
- '''''
- UDP Tunnel VPN
- Xiaoxia (xiaoxia@xiaoxia.org)
- Updated: 2012-2-21
- '''
- import os, sys
- import hashlib
- import getopt
- import fcntl
- import time
- import struct
- import socket, select
- import traceback
- import signal
- import ctypes
- import binascii
- SHARED_PASSWORD = hashlib.sha1("xiaoxia").digest()
- TUNSETIFF = 0x400454ca
- IFF_TUN = 0x0001
- BUFFER_SIZE = 8192
- MODE = 0
- DEBUG = 0
- PORT = 0
- IFACE_IP = "10.0.0.1/24"
- MTU = 1500
- TIMEOUT = 60*10 # seconds
- class Tunnel():
- def create(self):
- try:
- self.tfd = os.open("/dev/net/tun", os.O_RDWR)
- except:
- self.tfd = os.open("/dev/tun", os.O_RDWR)
- ifs = fcntl.ioctl(self.tfd, TUNSETIFF, struct.pack("16sH", "t%d", IFF_TUN))
- self.tname = ifs[:16].strip("\x00")
- def close(self):
- os.close(self.tfd)
- def config(self, ip):
- print "Configuring interface %s with ip %s" % (self.tname, ip)
- os.system("ip link set %s up" % (self.tname))
- os.system("ip link set %s mtu 1000" % (self.tname))
- os.system("ip addr add %s dev %s" % (ip, self.tname))
- def config_routes(self):
- if MODE == 1: # Server
- pass
- else: # Client
- print "Setting up new gateway ..."
- # Look for default route
- routes = os.popen("ip route show").readlines()
- defaults = [x.rstrip() for x in routes if x.startswith("default")]
- if not defaults:
- raise Exception("Default route not found, maybe not connected!")
- self.prev_gateway = defaults[0]
- self.prev_gateway_metric = self.prev_gateway + " metric 2"
- self.new_gateway = "default dev %s metric 1" % (self.tname)
- self.tun_gateway = self.prev_gateway.replace("default", IP)
- self.old_dns = file("/etc/resolv.conf", "rb").read()
- # Remove default gateway
- os.system("ip route del " + self.prev_gateway)
- # Add default gateway with metric
- os.system("ip route add " + self.prev_gateway_metric)
- # Add exception for server
- os.system("ip route add " + self.tun_gateway)
- # Add new default gateway
- os.system("ip route add " + self.new_gateway)
- # Set new DNS to 8.8.8.8
- file("/etc/resolv.conf", "wb").write("nameserver 8.8.8.8")
- def restore_routes(self):
- if MODE == 1: # Server
- pass
- else: # Client
- print "Restoring previous gateway ..."
- os.system("ip route del " + self.new_gateway)
- os.system("ip route del " + self.prev_gateway_metric)
- os.system("ip route del " + self.tun_gateway)
- os.system("ip route add " + self.prev_gateway)
- file("/etc/resolv.conf", "wb").write(self.old_dns)
- def run(self):
- global PORT
- self.udpfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- if MODE == 1:
- self.udpfd.bind(("", PORT))
- else:
- self.udpfd.bind(("", 0))
- self.clients = {}
- self.logged = False
- self.try_logins = 5
- self.log_time = 0
- while True:
- if MODE == 2 and not self.logged and time.time() - self.log_time > 2.:
- print "Do login ..."
- self.udpfd.sendto("LOGIN:" + SHARED_PASSWORD + ":" +
- IFACE_IP.split("/")[0], (IP, PORT))
- self.try_logins -= 1
- if self.try_logins == 0:
- raise Exception("Failed to log in server.")
- self.log_time = time.time()
- rset = select.select([self.udpfd, self.tfd], [], [], 1)[0]
- for r in rset:
- if r == self.tfd:
- if DEBUG: os.write(1, ">")
- data = os.read(self.tfd, MTU)
- if MODE == 1: # Server
- src, dst = data[16:20], data[20:24]
- for key in self.clients:
- if dst == self.clients[key]["localIPn"]:
- self.udpfd.sendto(data, key)
- # Remove timeout clients
- curTime = time.time()
- for key in self.clients.keys():
- if curTime - self.clients[key]["aliveTime"] > TIMEOUT:
- print "Remove timeout client", key
- del self.clients[key]
- else: # Client
- self.udpfd.sendto(data, (IP, PORT))
- elif r == self.udpfd:
- if DEBUG: os.write(1, "<")
- data, src = self.udpfd.recvfrom(BUFFER_SIZE)
- if MODE == 1: # Server
- key = src
- if key not in self.clients:
- # New client comes
- try:
- if data.startswith("LOGIN:") and data.split(":")[1]==SHARED_PASSWORD:
- localIP = data.split(":")[2]
- self.clients[key] = {"aliveTime": time.time(),
- "localIPn": socket.inet_aton(localIP)}
- print "New Client from", src, "request IP", localIP
- self.udpfd.sendto("LOGIN:SUCCESS", src)
- except:
- print "Need valid password from", src
- self.udpfd.sendto("LOGIN:PASSWORD", src)
- else:
- # Simply write the packet to local or forward them to other clients ???
- os.write(self.tfd, data)
- self.clients[key]["aliveTime"] = time.time()
- else: # Client
- if data.startswith("LOGIN"):
- if data.endswith("PASSWORD"):
- self.logged = False
- print "Need password to login!"
- elif data.endswith("SUCCESS"):
- self.logged = True
- self.try_logins = 5
- print "Logged in server succefully!"
- else:
- os.write(self.tfd, data)
- def usage(status = 0):
- print "Usage: %s [-s port|-c serverip] [-hd] [-l localip]" % (sys.argv[0])
- sys.exit(status)
- def on_exit(no, info):
- raise Exception("TERM signal caught!")
- if __name__=="__main__":
- opts = getopt.getopt(sys.argv[1:],"s:c:l:hd")
- for opt,optarg in opts[0]:
- if opt == "-h":
- usage()
- elif opt == "-d":
- DEBUG += 1
- elif opt == "-s":
- MODE = 1
- PORT = int(optarg)
- elif opt == "-c":
- MODE = 2
- IP, PORT = optarg.split(",")
- IP = socket.gethostbyname(IP)
- PORT = int(PORT)
- elif opt == "-l":
- IFACE_IP = optarg
- if MODE == 0 or PORT == 0:
- usage(1)
- tun = Tunnel()
- tun.create()
- tun.config(IFACE_IP)
- signal.signal(signal.SIGTERM, on_exit)
- tun.config_routes()
- try:
- tun.run()
- except KeyboardInterrupt:
- pass
- except:
- print traceback.format_exc()
- finally:
- tun.restore_routes()
- tun.close()
只建立一个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上工作的。
----------------------------------------------------
那我就来说说其中的原理吧,也认真的总结一下相关的知识。当然提到隧道,并不是我们通常意义上火车穿过的隧道,这就要看你如何理解网络中协议二字,理解不同协议之间的层次关系,才是理解隧道二字真正的含义,简而言之,就是以一种协议封装另一种协议,在发送和接受的时候,真确地封装和解封即可。
/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/
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/
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’
我谁否缺了什么东西?
如果是的话,需要有tun设备的权限的。如果是burst的vps,需要在VPS的管理面板的选项“Enable Tun/Tap”打开tun。
比如最简单的XOR,复杂一点的AES什么的
我想,以后增强这个VPN脚本的时候,会加入这个简单的加密功能。
目前G。F。W还不会窥探UDP的数据,除了DNS数据包。
墙也不会嗅探UDP。
File “”,line 1
SyntaxError: invalid syntax
hi.
我操作成功,terminal里的提示跟你文章一样,不过建立连接后,还是翻不了墙阿,怎么回事?