2012年2月22日星期三

[GFW BLOG(功夫网与翻墙)] udpip: 用UDP封装IP数据包建立VPN

原文:http://xiaoxia.org/2012/02/21/udpip-vpn/

原理

使用Linux内核提供的tun设备建立可以在脚本读写的虚拟网卡,然后通过UDP将两个网卡的数据连接。


此方法能够使用以下特殊环境下:

1、客户端所在网络的路由不支持ppp,或者网络受到限制
2、TCP数据包被劫持或者受到限制
3、服务器是OpenVZ等不支持建立pptp,像我的burst的VPS就是这样子。

使用

服务器:

# 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 xiaoxia.org,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!

脚本代码

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() 



--
Posted By GFW BLOG 功夫网与翻墙 to GFW BLOG(功夫网与翻墙) at 2/22/2012 09:22:00 AM

--
1、翻墙利器赛风3下载地址: http://dld.bz/caonima326http://dld.bz/caonima745/
2、我们的订阅地址:http://feeds2.feedburner.com/chinagfwblog
3、停止订阅,请发邮件到
gfw-blog+unsubscribe@googlegroups.com
翻越防火长城,你可以到达世界上的每一个角落。(Across the Great Firewall, you can reach every corner in the world.)

没有评论:

发表评论