├── debian ├── compat ├── source │ ├── format │ └── options ├── files ├── rules ├── changelog ├── control └── copyright ├── LICENSE ├── setup.py ├── README.md ├── tests.py └── pytun.py /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/source/options: -------------------------------------------------------------------------------- 1 | extend-diff-ignore="\.egg-info" -------------------------------------------------------------------------------- /debian/files: -------------------------------------------------------------------------------- 1 | python-pytun_1.0.1-1_all.deb python optional 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ --with python2 --buildsystem=python_distutils 5 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | pytun (1.0.1-1) stable; urgency=low 2 | 3 | * Initial package build 4 | 5 | -- Harish Kumar Mon, 04 Jan 2016 11:18:40 +0000 6 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: pytun 2 | Maintainer: Gawen Arab 3 | Section: python 4 | Priority: optional 5 | Build-Depends: python-setuptools (>= 0.6b3), python-all (>= 2.6.6-3), debhelper (>= 7) 6 | Standards-Version: 3.9.1 7 | 8 | Package: python-pytun 9 | Architecture: all 10 | Depends: ${misc:Depends}, ${python:Depends} 11 | Description: Python TUN/TAP tunnel module 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Gawen ARAB 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 Gawen ARAB 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | try: 4 | from setuptools import setup 5 | 6 | except: 7 | from distutils.core import setup 8 | 9 | import os 10 | 11 | import pytun 12 | 13 | setup( 14 | name = "pytun", 15 | description = "Python TUN/TAP tunnel module", 16 | 17 | py_modules = ["pytun"], 18 | test_suite = "tests", 19 | 20 | version = pytun.__version__, 21 | author = pytun.__author__, 22 | author_email = pytun.__email__, 23 | url = "https://github.com/Gawen/pytun", 24 | license = pytun.__license__, 25 | 26 | classifiers = [ 27 | "Development Status :: 4 - Beta", 28 | "Intended Audience :: Developers", 29 | "Intended Audience :: System Administrators", 30 | "Intended Audience :: Telecommunications Industry", 31 | "License :: OSI Approved :: MIT License", 32 | "Operating System :: MacOS", 33 | "Operating System :: POSIX", 34 | "Programming Language :: Python", 35 | "Topic :: Internet", 36 | "Topic :: System :: Networking", 37 | "Topic :: Software Development :: Libraries :: Python Modules", 38 | ], 39 | ) 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pytun 2 | 3 | `pytun` is a Python module to manage tun/tap IP tunnel in the same way you would manipulate a file handler. 4 | 5 | For now, it is only compatible with Linux, probably Unix, maybe MacOsX, and in the future Windows. 6 | 7 | `pytun` is under the MIT license. 8 | 9 | ## How to use 10 | 11 | First of all, clone this repos or use `easy_install` or `pip`. 12 | 13 | pip install pytun 14 | easy_install pytun 15 | 16 | Quite easy. Use the `open()` function. 17 | 18 | import pytun 19 | 20 | tun = pytun.open() # Open a TUN mode tunnel 21 | 22 | For a TAP tunnel, add the `"tap"` argument. 23 | 24 | tun = pytun.open("tap") 25 | 26 | `tun` is the handler for the newly created tunnel and is manipulated like a file. 27 | 28 | To read/write, use the standard methods `recv([size = 1500])` and `send(buf)` 29 | 30 | buf = tun.recv() 31 | tun.send(buf) 32 | 33 | tun is also `select()` compatible to make your network reactor asynchronous. 34 | 35 | import select 36 | 37 | fds = select.select([tun, ...], [...], [...]) 38 | 39 | Finally, you can close the tunnel using the method `close()`. 40 | 41 | tun.close() 42 | 43 | The tunnel automatically closes when the object is not referenced anymore and the garbage collector destroys the handler. 44 | 45 | 46 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import pytun 2 | import logging 3 | import select 4 | 5 | def pprint_buf(buf): 6 | """ Dirty & convenient function to display the hexademical 7 | repr. of a buffer. 8 | """ 9 | 10 | DEFAULT_SIZE = 4 11 | 12 | def hex2(i, l = None): 13 | l = l if l is not None else DEFAULT_SIZE 14 | 15 | h = hex(i).upper()[2:] 16 | 17 | if len(h) != l: 18 | h = "0" * (l - len(h)) + h 19 | 20 | return h 21 | 22 | def displayable_char(c): 23 | if ord(c) < 0x20 or ord(c) >= 0x7f: 24 | c = "." 25 | 26 | return c 27 | 28 | print " " * DEFAULT_SIZE, 29 | for i in range(16): print hex2(i, 2), 30 | print 31 | 32 | raws = [] 33 | for i, c in enumerate(buf): 34 | if i % 16 == 0: 35 | if i: 36 | print "\t" + "".join(raws) 37 | raws = [] 38 | 39 | print hex2(i), 40 | raws.append(displayable_char(c)) 41 | 42 | print hex2(ord(c), 2), 43 | 44 | print " " * (15 - (i % 16)) + "\t" + "".join(raws) 45 | 46 | def main(): 47 | # Configure pytun's logger 48 | pytun.logger.setLevel(logging.DEBUG) 49 | logging.basicConfig() 50 | 51 | # Open the tunnel 52 | try: 53 | tun = pytun.open() 54 | 55 | except pytun.Tunnel.NotPermitted: 56 | print 57 | print "*" * 80 58 | print "You don't have the rights to access the file %s." % (pytun.TUN_KO_PATH, ) 59 | print "Give the access of this file to pytun, or if you trust me," 60 | print "elevate this current script to root level." 61 | print "*" * 80 62 | print 63 | 64 | raise 65 | 66 | print "*" * 80 67 | print 68 | print "OK. The tunnel '%s' had been created." % (tun.name, ) 69 | print 70 | print "If you want to play with it, first configure it." 71 | print 72 | print "1. Set up the network and set an IP" 73 | print " $ ifconfig %s 192.168.42.1" % (tun.name, ) 74 | print 75 | print "2. Add the network route" 76 | print " $ route add -net 192.168.42.0/24 dev %s" % (tun.name, ) 77 | print 78 | print "Then, try to ping some IP in this network ..." 79 | print " $ ping 192.168.42.42" 80 | print 81 | print "Or do some UDP netcat magic." 82 | print " $ nc 192.168.42.42 4242 -u" 83 | print 84 | print "Enjoy !" 85 | print 86 | print "*" * 80 87 | 88 | try: 89 | # Receive loop 90 | while True: 91 | buf = tun.recv() 92 | 93 | pytun.logger.info("Packet received !") 94 | pprint_buf(buf) 95 | print 96 | 97 | except KeyboardInterrupt: 98 | print "Keyboard interrupt. Closing." 99 | 100 | finally: 101 | # Close the tunnel 102 | tun.close() 103 | 104 | 105 | if __name__ == "__main__": 106 | main() 107 | 108 | -------------------------------------------------------------------------------- /pytun.py: -------------------------------------------------------------------------------- 1 | """ pytun 2 | 3 | pytun is a tiny piece of code which gives you the ability to create and 4 | manage tun/tap tunnels on Linux (for now). 5 | 6 | """ 7 | 8 | __author__ = "Gawen Arab" 9 | __copyright__ = "Copyright 2012, Gawen Arab" 10 | __credits__ = ["Gawen Arab", "Ben Lapid"] 11 | __license__ = "MIT" 12 | __version__ = "1.0.1" 13 | __maintainer__ = "Gawen Arab" 14 | __email__ = "g@wenarab.com" 15 | __status__ = "Beta" 16 | 17 | import os 18 | import fcntl 19 | import socket 20 | import struct 21 | import logging 22 | import functools 23 | 24 | TUN_KO_PATH = "/dev/net/tun" 25 | 26 | logger = logging.getLogger("pytun") 27 | 28 | class Tunnel(object): 29 | """ tun/tap handler class """ 30 | 31 | class AlreadyOpened(Exception): 32 | """ Raised when the user try to open a already-opened 33 | tunnel. 34 | """ 35 | pass 36 | 37 | class NotPermitted(Exception): 38 | """ Raised when pytun try to setup a new tunnel without 39 | the good permissions. 40 | """ 41 | pass 42 | 43 | MODES = { 44 | "tun": 0x0001, 45 | "tap": 0x0002, 46 | } 47 | 48 | # No packet information flag 49 | IFF_NO_PI = 0x1000 50 | 51 | # ioctl call 52 | TUNSETIFF = 0x400454ca 53 | SIOCSIFHWADDR = 0x8924 54 | SIOCSIFADDR = 0x8916 55 | SIOCSIFFLAGS = 0x8914 56 | IFF_UP = 0x1 57 | IFF_POINTOPOINT = 0x10 58 | IFF_RUNNING = 0x40 59 | IFF_NOARP = 0x80 60 | IFF_MULTICAST = 0x1000 61 | 62 | def __init__(self, mode = None, pattern = None, auto_open = None, no_pi = False): 63 | """ Create a new tun/tap tunnel. Its type is defined by the 64 | argument 'mode', whose value can be either a string or 65 | the system value. 66 | 67 | The argument 'pattern set the string format used to 68 | generate the name of the future tunnel. By default, for 69 | Linux, it is "tun%d" or "tap%d" depending on the mode. 70 | 71 | If the argument 'auto_open' is true, this constructor 72 | will automatically create the tunnel. 73 | 74 | If the argument 'no_pi' is true, the device will be 75 | be opened with teh IFF_NO_PI flag. Otherwise, 4 extra 76 | bytes are added to the beginning of the packet (2 flag 77 | bytes and 2 protocol bytes). 78 | 79 | """ 80 | 81 | mode = mode if mode is not None else "tun" 82 | pattern = pattern if pattern is not None else "" 83 | auto_open = auto_open if auto_open is not None else True 84 | 85 | super(Tunnel, self).__init__() 86 | 87 | self.pattern = pattern 88 | self.mode = mode 89 | self.no_pi = self.IFF_NO_PI if no_pi else 0x0000 90 | 91 | self.name = None 92 | self.fd = None 93 | 94 | if isinstance(self.mode, basestring): 95 | self.mode = self.MODES.get(self.mode, None) 96 | 97 | assert self.mode is not None, "%r is not a valid tunnel type." % (self.mode, ) 98 | 99 | if auto_open: 100 | self.open() 101 | 102 | def __del__(self): 103 | self.close() 104 | 105 | @property 106 | def mode_name(self): 107 | """ Returns the tunnel mode's name, for printing purpose. """ 108 | 109 | for name, id in self.MODES.iteritems(): 110 | if id == self.mode: 111 | return name 112 | 113 | def fileno(self): 114 | """ Standard function which makes this class 'select()' compatible. """ 115 | return self.fd 116 | 117 | def open(self): 118 | """ Create the tunnel. 119 | If the tunnel is already opened, the function will 120 | raised an AlreadyOpened exception. 121 | """ 122 | 123 | if self.fd is not None: 124 | raise self.AlreadyOpened() 125 | 126 | logger.debug("Opening %s..." % (TUN_KO_PATH, )) 127 | self.fd = os.open(TUN_KO_PATH, os.O_RDWR) 128 | 129 | logger.debug("Opening %s tunnel '%s'..." % (self.mode_name.upper(), self.pattern, )) 130 | try: 131 | ret = fcntl.ioctl(self.fd, self.TUNSETIFF, struct.pack("16sH", self.pattern, self.mode | self.no_pi)) 132 | 133 | except IOError, e: 134 | if e.errno == 1: 135 | logger.error("Cannot open a %s tunnel because the operation is not permitted." % (self.mode_name.upper(), )) 136 | raise self.NotPermitted() 137 | 138 | raise 139 | 140 | self.name = ret[:16].strip("\x00") 141 | 142 | logger.info("Tunnel '%s' opened." % (self.name, )) 143 | 144 | def close(self): 145 | """ Close the tunnel. 146 | If the tunnel is already closed or never opened, 147 | do nothing. 148 | """ 149 | 150 | if self.fd is None: 151 | return 152 | 153 | logger.debug("Closing tunnel '%s'..." % (self.name or "", )) 154 | 155 | # Close tun.ko file 156 | os.close(self.fd) 157 | self.fd = None 158 | 159 | logger.info("Tunnel '%s' closed." % (self.name or "", )) 160 | 161 | def send(self, buf): 162 | """ Send the buffer 'buf'. """ 163 | os.write(self.fd, buf) 164 | 165 | def recv(self, size = None): 166 | """ Receive a buffer. The default size is 1500, the 167 | classical MTU. 168 | """ 169 | 170 | size = size if size is not None else 1500 171 | 172 | return os.read(self.fd, size) 173 | 174 | def set_mac(self, mac): 175 | """ Sets the MAC address of the device to 'mac'. 176 | parameter 'mac' should be a binary representation 177 | of the MAC address 178 | Note: Will fail for TUN devices 179 | """ 180 | mac = map(ord, mac) 181 | ifreq = struct.pack('16sH6B8', self.name, socket.AF_UNIX, *mac) 182 | fcntl.ioctl(self.fileno(), self.SIOCSIFHWADDR, ifreq) 183 | 184 | def set_ipv4(self, ip): 185 | """ Sets the IP address (ifr_addr) of the device 186 | parameter 'ip' should be string representation of IP address 187 | This does the same as ifconfig. 188 | """ 189 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 190 | bin_ip = socket.inet_aton(ip) 191 | ifreq = struct.pack('16sH2s4s8s', self.name, socket.AF_INET, '\x00'*2, bin_ip, '\x00'*8) 192 | fcntl.ioctl(sock, self.SIOCSIFADDR, ifreq) 193 | ifreq = struct.pack('16sH', self.name, self.IFF_UP|self.IFF_POINTOPOINT|self.IFF_RUNNING|self.IFF_MULTICAST) 194 | fcntl.ioctl(sock, self.SIOCSIFFLAGS, ifreq) 195 | 196 | 197 | def __repr__(self): 198 | return "<%s tunnel '%s'>" % (self.mode_name.capitalize(), self.name, ) 199 | 200 | class TunTunnel(Tunnel): 201 | """ tun handler class. """ 202 | def __init__(self, *kargs, **kwargs): 203 | super(TunTunnel, self).__init__("tun", *kargs, **kwargs) 204 | 205 | class TapTunnel(Tunnel): 206 | """ tap handler class. """ 207 | def __init__(self, *kargs, **kwargs): 208 | super(TapTunnel, self).__init__("tap", **kwargs) 209 | 210 | """ Convenient functions to open tunnels. """ 211 | tunnel = functools.partial(Tunnel, auto_open = True) 212 | open = tunnel 213 | --------------------------------------------------------------------------------