"flags": Attribute.Invisible | Attribute.ReadOnly,
"validation_function": validation.is_string,
}),
+ "tun_key" : dict({
+ "name": "tun_key",
+ "help": "Randomly selected TUNneling protocol cryptographic key. "
+ "Endpoints must agree to use the minimum (in lexicographic order) "
+ "of both the remote and local sides.",
+ "type": Attribute.STRING,
+ "flags": Attribute.Invisible | Attribute.ReadOnly,
+ "validation_function": validation.is_string,
+ }),
"tun_addr" : dict({
"name": "tun_addr",
"help": "IP address of the tunnel endpoint",
def _make_tun_iface(self, parameters):
return self._make_generic(parameters, self._interfaces.TunIface)
+ def _make_tap_iface(self, parameters):
+ return self._make_generic(parameters, self._interfaces.TapIface)
+
def _make_netpipe(self, parameters):
return self._make_generic(parameters, self._interfaces.NetPipe)
import subprocess
import os
import os.path
+import random
import tunproto
# These get initialized when the iface is connected to the internet
self.has_internet = False
+
+ # Generate an initial random cryptographic key to use for tunnelling
+ # Upon connection, both endpoints will agree on a common one based on
+ # this one.
+ self.tun_key = ( ''.join(map(chr, [
+ r.getrandbits(8)
+ for i in xrange(32)
+ for r in (random.SystemRandom(),) ])
+ ).encode("base64").strip() )
def __str__(self):
return "%s<ip:%s/%s up mac:%s>" % (
self.tun_port = port
class TunIface(object):
+ _PROTO_MAP = tunproto.TUN_PROTO_MAP
+ _KIND = 'TUN'
+
def __init__(self, api=None):
if not api:
api = plcapi.PLCAPI()
# They're part of the TUN standard attribute set
self.tun_port = None
self.tun_addr = None
+ self.tun_key = None
# These get initialized when the iface is connected to its peer
self.peer_iface = None
def add_address(self, address, netprefix, broadcast):
if (self.address or self.netprefix or self.netmask) is not None:
- raise RuntimeError, "Cannot add more than one address to TUN interfaces"
+ raise RuntimeError, "Cannot add more than one address to %s interfaces" % (self._KIND,)
if broadcast:
- raise ValueError, "TUN interfaces cannot broadcast in PlanetLab"
+ raise ValueError, "%s interfaces cannot broadcast in PlanetLab" % (self._KIND,)
self.address = address
self.netprefix = netprefix
def validate(self):
if not self.node:
- raise RuntimeError, "Unconnected TUN iface - missing node"
- if self.peer_iface and self.peer_proto not in tunproto.PROTO_MAP:
+ raise RuntimeError, "Unconnected %s iface - missing node" % (self._KIND,)
+ if self.peer_iface and self.peer_proto not in self._PROTO_MAP:
raise RuntimeError, "Unsupported tunnelling protocol: %s" % (self.peer_proto,)
if not self.address or not self.netprefix or not self.netmask:
- raise RuntimeError, "Misconfigured TUN iface - missing address"
+ raise RuntimeError, "Misconfigured %s iface - missing address" % (self._KIND,)
def prepare(self, home_path, listening):
if not self.peer_iface and (self.peer_proto and (listening or (self.peer_addr and self.peer_port))):
self.peer_port)
if self.peer_iface:
if not self.peer_proto_impl:
- self.peer_proto_impl = tunproto.PROTO_MAP[self.peer_proto](
- self, self.peer_iface, home_path, listening)
+ self.peer_proto_impl = self._PROTO_MAP[self.peer_proto](
+ self, self.peer_iface, home_path, self.tun_key, listening)
self.peer_proto_impl.port = self.tun_port
self.peer_proto_impl.prepare()
else:
return None
+class TapIface(TunIface):
+ _PROTO_MAP = tunproto.TAP_PROTO_MAP
+ _KIND = 'TAP'
+
# Yep, it does nothing - yet
class Internet(object):
def __init__(self, api=None):
NODE = "Node"
NODEIFACE = "NodeInterface"
TUNIFACE = "TunInterface"
+TAPIFACE = "TapInterface"
APPLICATION = "Application"
DEPENDENCY = "Dependency"
NEPIDEPENDENCY = "NepiDependency"
raise RuntimeError, "Use of TUN interfaces requires emulation"
iface.node = node
node.required_vsys.update(('fd_tuntap', 'vif_up'))
+ node.required_packages.add('python-crypto')
def connect_tun_iface_peer(proto, testbed_instance, iface_guid, peer_iface_guid):
iface = testbed_instance._elements[iface_guid]
iface.peer_iface = peer_iface
iface.peer_proto = \
iface.tun_proto = proto
+ iface.tun_key = peer_iface.tun_key
def crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data):
iface = testbed_instance._elements[iface_guid]
iface.peer_addr = peer_iface_data.get("tun_addr")
iface.peer_proto = peer_iface_data.get("tun_proto")
iface.peer_port = peer_iface_data.get("tun_port")
+ iface.tun_key = min(iface.tun_key, peer_iface_data.get("tun_key"))
iface.tun_proto = proto
preconfigure_tuniface(testbed_instance, iface_guid)
element = testbed_instance._make_tun_iface(parameters)
testbed_instance.elements[guid] = element
+def create_tapiface(testbed_instance, guid):
+ parameters = testbed_instance._get_parameters(guid)
+ element = testbed_instance._make_tap_iface(parameters)
+ testbed_instance.elements[guid] = element
+
def create_application(testbed_instance, guid):
parameters = testbed_instance._get_parameters(guid)
element = testbed_instance._make_application(parameters)
"init_code": connect_tun_iface_node,
"can_cross": False
}),
+ dict({
+ "from": (TESTBED_ID, NODE, "devs"),
+ "to": (TESTBED_ID, TAPIFACE, "node"),
+ "init_code": connect_tun_iface_node,
+ "can_cross": False
+ }),
dict({
"from": (TESTBED_ID, NODEIFACE, "inet"),
"to": (TESTBED_ID, INTERNET, "devs"),
"init_code": functools.partial(connect_tun_iface_peer,"udp"),
"can_cross": False
}),
+ dict({
+ "from": (TESTBED_ID, TAPIFACE, "tcp"),
+ "to": (TESTBED_ID, TAPIFACE, "tcp"),
+ "init_code": functools.partial(connect_tun_iface_peer,"tcp"),
+ "can_cross": False
+ }),
+ dict({
+ "from": (TESTBED_ID, TAPIFACE, "udp"),
+ "to": (TESTBED_ID, TAPIFACE, "udp"),
+ "init_code": functools.partial(connect_tun_iface_peer,"udp"),
+ "can_cross": False
+ }),
dict({
"from": (TESTBED_ID, TUNIFACE, "tcp"),
"to": (None, None, "tcp"),
"compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
"can_cross": True
}),
+ dict({
+ "from": (TESTBED_ID, TAPIFACE, "tcp"),
+ "to": (None, None, "tcp"),
+ "init_code": functools.partial(crossconnect_tun_iface_peer_init,"tcp"),
+ "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"tcp"),
+ "can_cross": True
+ }),
+ dict({
+ "from": (TESTBED_ID, TAPIFACE, "udp"),
+ "to": (None, None, "udp"),
+ "init_code": functools.partial(crossconnect_tun_iface_peer_init,"udp"),
+ "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
+ "can_cross": True
+ }),
]
attributes = dict({
}),
})
-create_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, APPLICATION ]
+create_order = [ INTERNET, NODE, NODEIFACE, TAPIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, APPLICATION ]
-configure_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, APPLICATION ]
+configure_order = [ INTERNET, NODE, NODEIFACE, TAPIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, APPLICATION ]
factories_info = dict({
NODE: dict({
}),
TUNIFACE: dict({
"allow_addresses": True,
- "help": "Virtual TUN network interface",
+ "help": "Virtual TUN network interface (layer 3)",
"category": "devices",
"create_function": create_tuniface,
"preconfigure_function": preconfigure_tuniface,
"configure_function": postconfigure_tuniface,
+ "box_attributes": [
+ "up", "device_name", "mtu", "snat",
+ "txqueuelen",
+ "tun_proto", "tun_addr", "tun_port", "tun_key"
+ ],
+ "traces": ["packets"],
+ "connector_types": ["node","udp","tcp"]
+ }),
+ TAPIFACE: dict({
+ "allow_addresses": True,
+ "help": "Virtual TAP network interface (layer 2)",
+ "category": "devices",
+ "create_function": create_tapiface,
+ "preconfigure_function": preconfigure_tuniface,
+ "configure_function": postconfigure_tuniface,
"box_attributes": [
"up", "device_name", "mtu", "snat",
"txqueuelen",
default = None,
help =
"See mode. This specifies the interface's transmission queue length. " )
+parser.add_option(
+ "-u", "--udp", dest="udp", metavar="PORT", type="int",
+ default = None,
+ help =
+ "Bind to the specified UDP port locally, and send UDP datagrams to the "
+ "remote endpoint, creating a tunnel through UDP rather than TCP." )
+parser.add_option(
+ "-k", "--key", dest="cipher_key", metavar="KEY",
+ default = None,
+ help =
+ "Specify a symmetric encryption key with which to protect packets across "
+ "the tunnel. python-crypto must be installed on the system." )
(options, remaining_args) = parser.parse_args(sys.argv[1:])
+buf
)
+def encrypt(packet, crypter):
+ # pad
+ padding = crypter.block_size - len(packet) % crypter.block_size
+ packet += chr(padding) * padding
+
+ # encrypt
+ return crypter.encrypt(packet)
+
+def decrypt(packet, crypter):
+ # decrypt
+ packet = crypter.decrypt(packet)
+
+ # un-pad
+ padding = ord(packet[-1])
+ if not (0 < padding <= crypter.block_size):
+ # wrong padding
+ raise RuntimeError, "Truncated packet"
+ packet = packet[:-padding]
+
+ return packet
+
abortme = False
def tun_fwd(tun, remote):
global abortme
with_pi = options.mode.startswith('pl-')
ether_mode = tun_name.startswith('tap')
+ crypto_mode = False
+ try:
+ if options.cipher_key:
+ import Crypto.Cipher.AES
+ import hashlib
+
+ hashed_key = hashlib.sha256(options.cipher_key).digest()
+ crypter = Crypto.Cipher.AES.new(
+ hashed_key,
+ Crypto.Cipher.AES.MODE_ECB)
+ crypto_mode = True
+ except:
+ import traceback
+ traceback.print_exc()
+ crypto_mode = False
+ crypter = None
+
+ if crypto_mode:
+ print >>sys.stderr, "Packets are transmitted in CIPHER"
+ else:
+ print >>sys.stderr, "Packets are transmitted in PLAINTEXT"
+
# Limited frame parsing, to preserve packet boundaries.
# Which is needed, since /dev/net/tun is unbuffered
fwbuf = ""
# check to see if we can write
if remote in wrdy and packetReady(fwbuf, ether_mode):
packet, fwbuf = pullPacket(fwbuf, ether_mode)
- os.write(remote.fileno(), packet)
+ try:
+ if crypto_mode:
+ enpacket = encrypt(packet, crypter)
+ else:
+ enpacket = packet
+ os.write(remote.fileno(), enpacket)
+ except:
+ if not options.udp:
+ # in UDP mode, we ignore errors - packet loss man...
+ raise
print >>sys.stderr, '>', formatPacket(packet, ether_mode)
if tun in wrdy and packetReady(bkbuf, ether_mode):
packet, bkbuf = pullPacket(bkbuf, ether_mode)
packet = piStrip(packet)
fwbuf += packet
if remote in rdrdy:
- packet = os.read(remote.fileno(),2000) # remote.read blocks until it gets 2k!
+ try:
+ packet = os.read(remote.fileno(),2000) # remote.read blocks until it gets 2k!
+ if crypto_mode:
+ packet = decrypt(packet, crypter)
+ except:
+ if not options.udp:
+ # in UDP mode, we ignore errors - packet loss man...
+ raise
bkbuf += packet
try:
- # connect to remote endpoint
- if remaining_args and not remaining_args[0].startswith('-'):
- print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
- rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
- rsock.connect((remaining_args[0],options.port))
+ if options.udp:
+ # connect to remote endpoint
+ if remaining_args and not remaining_args[0].startswith('-'):
+ print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.udp)
+ print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
+ rsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0)
+ rsock.bind((hostaddr,options.udp))
+ rsock.connect((remaining_args[0],options.port))
+ else:
+ print >>sys.stderr, "Error: need a remote endpoint in UDP mode"
+ raise AssertionError, "Error: need a remote endpoint in UDP mode"
+ remote = os.fdopen(rsock.fileno(), 'r+b', 0)
else:
- print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.port)
- lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
- lsock.bind((hostaddr,options.port))
- lsock.listen(1)
- rsock,raddr = lsock.accept()
- remote = os.fdopen(rsock.fileno(), 'r+b', 0)
+ # connect to remote endpoint
+ if remaining_args and not remaining_args[0].startswith('-'):
+ print >>sys.stderr, "Connecting to: %s:%d" % (remaining_args[0],options.port)
+ rsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ rsock.connect((remaining_args[0],options.port))
+ else:
+ print >>sys.stderr, "Listening at: %s:%d" % (hostaddr,options.port)
+ lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+ lsock.bind((hostaddr,options.port))
+ lsock.listen(1)
+ rsock,raddr = lsock.accept()
+ remote = os.fdopen(rsock.fileno(), 'r+b', 0)
print >>sys.stderr, "Connected"
from nepi.util import server
class TunProtoBase(object):
- def __init__(self, local, peer, home_path):
+ def __init__(self, local, peer, home_path, key):
# Weak references, since ifaces do have a reference to the
# tunneling protocol implementation - we don't want strong
# circular references.
self.port = 15000
self.mode = 'pl-tun'
+ self.key = key
self.home_path = home_path
"-m", str(self.mode),
"-p", str(local_port if listen else peer_port),
"-A", str(local_addr),
- "-M", str(local_mask)]
+ "-M", str(local_mask),
+ "-k", str(self.key)]
if local_snat:
args.append("-S")
class TunProtoUDP(TunProtoBase):
- def __init__(self, local, peer, home_path, listening):
- super(TunProtoTCP, self).__init__(local, peer, home_path)
+ def __init__(self, local, peer, home_path, key, listening):
+ super(TunProtoUDP, self).__init__(local, peer, home_path, key)
self.listening = listening
def prepare(self):
pass
def setup(self):
- self.launch_async('udp', False, ("-U",))
+ self.async_launch('udp', False, ("-u",str(self.port)))
def shutdown(self):
self.kill()
class TunProtoTCP(TunProtoBase):
- def __init__(self, local, peer, home_path, listening):
- super(TunProtoTCP, self).__init__(local, peer, home_path)
+ def __init__(self, local, peer, home_path, key, listening):
+ super(TunProtoTCP, self).__init__(local, peer, home_path, key)
self.listening = listening
def prepare(self):
def shutdown(self):
self.kill()
-PROTO_MAP = {
+class TapProtoUDP(TunProtoUDP):
+ def __init__(self, local, peer, home_path, key, listening):
+ super(TapProtoUDP, self).__init__(local, peer, home_path, key, listening)
+ self.mode = 'pl-tap'
+
+class TapProtoTCP(TunProtoTCP):
+ def __init__(self, local, peer, home_path, key, listening):
+ super(TapProtoTCP, self).__init__(local, peer, home_path, key, listening)
+ self.mode = 'pl-tap'
+
+
+
+TUN_PROTO_MAP = {
'tcp' : TunProtoTCP,
'udp' : TunProtoUDP,
}
+TAP_PROTO_MAP = {
+ 'tcp' : TapProtoTCP,
+ 'udp' : TapProtoUDP,
+}
+
pass
@test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
- def test_tun_ping(self):
+ def _pingtest(self, TunClass, ConnectionProto):
instance = self.make_instance()
instance.defer_create(2, "Node")
instance.defer_create(6, "NodeInterface")
instance.defer_connect(3, "devs", 6, "node")
instance.defer_connect(6, "inet", 5, "devs")
- instance.defer_create(7, "TunInterface")
+ instance.defer_create(7, TunClass)
instance.defer_add_address(7, "192.168.2.2", 24, False)
instance.defer_connect(2, "devs", 7, "node")
- instance.defer_create(8, "TunInterface")
+ instance.defer_create(8, TunClass)
instance.defer_add_address(8, "192.168.2.3", 24, False)
instance.defer_connect(3, "devs", 8, "node")
- instance.defer_connect(7, "tcp", 8, "tcp")
+ instance.defer_connect(7, ConnectionProto, 8, ConnectionProto)
instance.defer_create(9, "Application")
instance.defer_create_set(9, "command", "ping -qc1 {#[GUID-8].addr[0].[Address]#}")
instance.defer_add_trace(9, "stdout")
self.assertTrue(re.match(comp_result, ping_result, re.MULTILINE),
"Unexpected trace:\n" + ping_result)
+ @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
+ def test_tun_ping(self):
+ self._pingtest("TunInterface", "tcp")
+
+ @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
+ def test_tun_ping_udp(self):
+ self._pingtest("TunInterface", "udp")
+
+ @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
+ def test_tap_ping(self):
+ self._pingtest("TapInterface", "tcp")
+
+ @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
+ def test_tap_ping_udp(self):
+ self._pingtest("TapInterface", "udp")
+
@test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
def test_nepi_depends(self):
instance = self.make_instance()