'''
PyTap module that wraps the Linux TUN/TAP device
@author: Dominik George
'''
# StB, 2.4.2011: Added method "fileno()", so we can use select.select().
from fcntl import ioctl
import os
import struct
import atexit
TUNSETIFF = 0x400454ca
IFF_TUN = 0x0001
IFF_TAP = 0x0002
DEFAULT_MTU = 1500
# StB, 25.4.2011: MTU=1500 means an Ethernet frame size of 1518 Bytes! (with VLAN its max. 1522 Bytes) We need to increase the read buffer in the "read()" method. AFAIK Linux don't use VLAN as default.
# http://de.wikipedia.org/wiki/Maximum_Transmission_Unit#Beispiel_Ethernet
# http://de.wikipedia.org/w/index.php?title=Datei:Ethernetpaket.svg&filetimestamp=20090225134531
ETHERNET_HEADER_SIZE = 18
class TapDevice:
''' TUN/TAP device object '''
def __init__(self, mode = IFF_TUN, name = '', dev = '/dev/net/tun'):
'''
Initialize TUN/TAP device object
mode is either IFF_TUN or IFF_TAP to select tun or tap device mode.
name is the name of the new device. An integer will be added to
build the real device name.
dev is the device node name the control channel is connected to.
'''
# Set interface mdoe in object
self.mode = mode
# Create interface name to request from tuntap module
if name == '':
if self.mode == IFF_TUN:
self.name = 'tun%d'
elif self.mode == IFF_TAP:
self.name = 'tap%d'
elif name.endswith('%d'):
self.name = name
else:
self.name = name + '%d'
# Open control device and request interface
fd = os.open(dev, os.O_RDWR)
ifs = ioctl(fd, TUNSETIFF, struct.pack("16sH", self.name, self.mode))
# Retreive real interface name from control device
self.name = ifs[:16].strip("\x00")
# Set default MTU
self.mtu = DEFAULT_MTU
# Store fd for later
self.__fd__ = fd
# Properly close device on exit
atexit.register(self.close)
def fileno(self):
return self.__fd__
def read(self):
'''
Read data from the device. The device mtu determines how many bytes
will be read.
The data read from the device is returned in its raw form.
'''
data = os.read(self.__fd__, self.mtu + ETHERNET_HEADER_SIZE)
return data
def write(self, data):
'''
Write data to the device. No care is taken for MTU limitations or similar.
'''
os.write(self.__fd__, data)
def ifconfig(self, **args):
'''
Issue ifconfig command on the device. The method takes the following
keyword arguments:
address => IP address of the device, can be in CIDR notation (see man ifconfig)
netmask => Network mask
network => Network base address, normally set automatically
broadcast => Broadcast address, normally set automatically
mtu => Link MTU, this will also affect the read() method
hwclass => Hardware class, normally ether for ethernet
hwaddress => Hardware (MAC) address, in conjunction with hwclass
'''
ifconfig = 'ifconfig ' + self.name + ' '
# IP address ?
try:
ifconfig = ifconfig + args['address'] + ' '
except KeyError:
pass
# Network mask ?
try:
ifconfig = ifconfig + 'netmask ' + args['netmask'] + ' '
except KeyError:
pass
# Network base address ?
try:
ifconfig = ifconfig + 'network ' + args['network'] + ' '
except KeyError:
pass
# Broadcast address ?
try:
ifconfig = ifconfig + 'broadcast ' + args['broadcast'] + ' '
except KeyError:
pass
# MTU ?
try:
ifconfig = ifconfig + 'mtu ' + str(args['mtu']) + ' '
except KeyError:
pass
# Hardware address ?
try:
ifconfig = ifconfig + 'hw ' + args['hwclass'] + ' ' + args['hwaddress'] + ' '
except KeyError:
pass
# Try to set off ifconfig command
ret = os.system(ifconfig)
if ret != 0:
raise IfconfigError()
# Save MTU if ifconfig was successful so buffer sizes can be adjusted
try:
self.mtu = args['mtu']
except KeyError:
pass
def up(self):
'''
Bring up device. This will effectively run "ifconfig up" on the device.
'''
ret = os.system("ifconfig " + self.name + " up")
if ret != 0:
raise IfconfigError()
def down(self):
'''
Bring down device. This will effectively call "ifconfig down" on the device.
'''
ret = os.system("ifconfig " + self.name + " down")
if ret != 0:
raise IfconfigError()
def close(self):
'''
Close the control channel. This will effectively drop all locks and remove the
TUN/TAP device.
You must manually take care that your code does not try to operate on the interface
after closing the control channel.
'''
os.close(self.__fd__)
class IfconfigError(Exception):
''' Exception thrown if an ifconfig command returns with a non-zero exit status. '''
pass