"max": 1,
"min": 0
}),
+ "gre": dict({
+ "help": "IP or Ethernet tunneling using the GRE protocol",
+ "name": "gre",
+ "max": 1,
+ "min": 0
+ }),
"fd->": dict({
"help": "TUN device file descriptor provider",
"name": "fd->",
"init_code": functools.partial(connect_tun_iface_peer,"udp"),
"can_cross": False
}),
+ dict({
+ "from": (TESTBED_ID, TUNIFACE, "gre"),
+ "to": (TESTBED_ID, TUNIFACE, "gre"),
+ "init_code": functools.partial(connect_tun_iface_peer,"gre"),
+ "can_cross": False
+ }),
dict({
"from": (TESTBED_ID, TAPIFACE, "tcp"),
"to": (TESTBED_ID, TAPIFACE, "tcp"),
"init_code": functools.partial(connect_tun_iface_peer,"udp"),
"can_cross": False
}),
+ dict({
+ "from": (TESTBED_ID, TAPIFACE, "gre"),
+ "to": (TESTBED_ID, TAPIFACE, "gre"),
+ "init_code": functools.partial(connect_tun_iface_peer,"gre"),
+ "can_cross": False
+ }),
dict({
"from": (TESTBED_ID, TUNIFACE, "tcp"),
"to": (None, None, "tcp"),
"compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
"can_cross": True
}),
+ dict({
+ "from": (TESTBED_ID, TUNIFACE, "gre"),
+ "to": (None, None, "gre"),
+ "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"gre"),
+ "can_cross": True
+ }),
dict({
"from": (TESTBED_ID, TAPIFACE, "tcp"),
"to": (None, None, "tcp"),
"compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
"can_cross": True
}),
+ # EGRE is an extension of PlanetLab, so we can't connect externally
+ # if the other testbed isn't another PlanetLab
+ dict({
+ "from": (TESTBED_ID, TAPIFACE, "gre"),
+ "to": (TESTBED_ID, None, "gre"),
+ "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"gre"),
+ "can_cross": True
+ }),
]
attributes = dict({
# Start (and prestart) node after ifaces, because the node needs the ifaces in order to set up routes
start_order = [ INTERNET, NODEIFACE, Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NODE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(APPLICATION) ]
+# cleanup order
+shutdown_order = [ Parallel(APPLICATION), Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NETPIPE), Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), NODEIFACE, Parallel(NODE) ]
+
factories_info = dict({
NODE: dict({
"help": "Virtualized Node (V-Server style)",
"tun_proto", "tun_addr", "tun_port", "tun_key"
],
"traces": ["packets", "pcap"],
- "connector_types": ["node","udp","tcp","fd->"],
+ "connector_types": ["node","udp","tcp","fd->","gre"],
"tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
}),
TAPIFACE: dict({
"tun_proto", "tun_addr", "tun_port", "tun_key"
],
"traces": ["packets", "pcap"],
- "connector_types": ["node","udp","tcp","fd->"],
+ "connector_types": ["node","udp","tcp","fd->","gre"],
"tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
}),
APPLICATION: dict({
"range": (2000,30000),
"validation_function": validation.is_integer_range(2000,30000)
}),
+ "dedicated_slice": dict({
+ "name": "dedicatedSlice",
+ "help": "Set to True if the slice will be dedicated to this experiment. "
+ "NEPI will perform node and slice cleanup, making sure slices are "
+ "in a clean, repeatable state before running the experiment.",
+ "type": Attribute.BOOL,
+ "value": False,
+ "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
+ "validation_function": validation.is_bool
+ }),
})
supported_recovery_policies = [
import functools
import time
import base64
+import traceback
import tunchannel
"-m", "--mode", dest="mode", metavar="MODE",
default = "none",
help =
- "Set mode. One of none, tun, tap, pl-tun, pl-tap. In any mode except none, a TUN/TAP will be created "
+ "Set mode. One of none, tun, tap, pl-tun, pl-tap, pl-gre-ip, pl-gre-eth. In any mode except none, a TUN/TAP will be created "
"by using the proper interface (tunctl for tun/tap, /vsys/fd_tuntap.control for pl-tun/pl-tap), "
"and it will be brought up (with ifconfig for tun/tap, with /vsys/vif_up for pl-tun/pl-tap). You have "
"to specify an VIF_ADDRESS and VIF_MASK in any case (except for none).")
help =
"Specify a symmetric encryption key with which to protect packets across "
"the tunnel. python-crypto must be installed on the system." )
+parser.add_option(
+ "-K", "--gre-key", dest="gre_key", metavar="KEY", type="int",
+ default = None,
+ help =
+ "Specify a demultiplexing 32-bit numeric key for GRE." )
parser.add_option(
"-N", "--no-capture", dest="no_capture",
action = "store_true",
processcond.release()
self.lockfile = lockfile
- fcntl.flock(self.lockfile, fcntl.LOCK_EX)
+
+ while True:
+ try:
+ fcntl.flock(self.lockfile, fcntl.LOCK_EX)
+ break
+ except (OSError, IOError), e:
+ if e.args[0] != os.errno.EINTR:
+ raise
def __del__(self):
processcond = self.__class__.processcond
# close TUN object
tun.close()
+def noopen(tun_path, tun_name):
+ print >>sys.stderr, "Using tun:", tun_name
+ return None
+def noclose(tun_path, tun_name, tun):
+ pass
+
def tuntap_alloc(kind, tun_path, tun_name):
args = ["tunctl"]
if kind == "tun":
name = c_tun_name.value
return str(fd), name
+_name_reservation = None
+def pl_tuntap_namealloc(kind, tun_path, tun_name):
+ global _name_reservation
+ # Serialize access
+ lockfile = open("/tmp/nepi-tun-connect.lock", "a")
+ _name_reservation = lock = HostLock(lockfile)
+
+ # We need to do this, fd_tuntap is the only one who can
+ # tell us our slice id (this script runs as root, so no uid),
+ # and the pattern of device names accepted by vsys scripts
+ tunalloc_so = ctypes.cdll.LoadLibrary("./tunalloc.so")
+ c_tun_name = ctypes.c_char_p("\x00"*IFNAMSIZ) # the string will be mutated!
+ nkind= {"tun":IFF_TUN,
+ "tap":IFF_TAP}[kind]
+ fd = tunalloc_so.tun_alloc(nkind, c_tun_name)
+ name = c_tun_name.value
+ os.close(fd)
+
+ base = name[:name.index('-')+1]
+ existing = set(map(str.strip,os.popen("ip a | grep -o '%s[0-9]*'" % (base,)).read().strip().split('\n')))
+
+ for i in xrange(9000,10000):
+ name = base + str(i)
+ if name not in existing:
+ break
+ else:
+ raise RuntimeError, "Could not assign interface name"
+
+ return None, name
+
def pl_vif_start(tun_path, tun_name):
+ global _name_reservation
+
out = []
def outreader():
stdout = open("/vsys/vif_up.out","r")
# Serialize access to vsys
lockfile = open("/tmp/nepi-tun-connect.lock", "a")
- lock = HostLock(lockfile)
-
+ lock = _name_reservation or HostLock(lockfile)
+ _name_reservation = None
+
stdin = open("/vsys/vif_up.in","w")
t = threading.Thread(target=outreader)
stdin.write("pointopoint=%s\n" % (options.vif_pointopoint,))
if options.vif_txqueuelen is not None:
stdin.write("txqueuelen=%d\n" % (options.vif_txqueuelen,))
+ if options.mode.startswith('pl-gre'):
+ stdin.write("gre=%d\n" % (options.gre_key,))
+ stdin.write("remote=%s\n" % (remaining_args[0],))
stdin.close()
t.join()
stdout = open("/vsys/vif_down.out","r")
out.append(stdout.read())
stdout.close()
- time.sleep(1)
+
+ while True:
+ ifaces = set(map(str.strip,os.popen("ip a | grep -o '%s'" % (tun_name,)).read().strip().split('\n')))
+ if tun_name in ifaces:
+ time.sleep(1)
+ else:
+ break
# Serialize access to vsys
lockfile = open("/tmp/nepi-tun-connect.lock", "a")
dealloc=nop,
start=pl_vif_start,
stop=pl_vif_stop),
+ 'pl-gre-ip' : dict(alloc=functools.partial(pl_tuntap_namealloc, "tun"),
+ tunopen=noopen, tunclose=tunclose,
+ dealloc=nop,
+ start=pl_vif_start,
+ stop=pl_vif_stop),
+ 'pl-gre-eth': dict(alloc=functools.partial(pl_tuntap_namealloc, "tap"),
+ tunopen=noopen, tunclose=noclose,
+ dealloc=nop,
+ start=pl_vif_start,
+ stop=pl_vif_stop),
}
tun_path = options.tun_path
passfd.sendfd(sock, tun.fileno(), '0')
# just wait forever
- def tun_fwd(tun, remote):
+ def tun_fwd(tun, remote, **kw):
while not TERMINATE:
time.sleep(1)
remote = None
+ elif options.mode.startswith('pl-gre'):
+ # just wait forever
+ def tun_fwd(tun, remote, **kw):
+ while not TERMINATE:
+ time.sleep(1)
+ remote = remaining_args[0]
elif options.udp:
# connect to remote endpoint
if remaining_args and not remaining_args[0].startswith('-'):
try:
modeinfo['stop'](tun_path, tun_name)
except:
- pass
+ traceback.print_exc()
try:
modeinfo['tunclose'](tun_path, tun_name, tun)
except:
- pass
+ traceback.print_exc()
try:
modeinfo['dealloc'](tun_path, tun_name)
except:
- pass
+ traceback.print_exc()
print >>sys.stderr, "TERMINATED GRACEFULLY"
local.node.wait_dependencies()
cmd = ( (
- "cd %(home)s && gcc -fPIC -shared tunalloc.c -o tunalloc.so"
+ "cd %(home)s && "
+ "gcc -fPIC -shared tunalloc.c -o tunalloc.so && "
+
+ "wget -q -c -O python-iovec-src.tar.gz %(iovec_url)s && "
+ "mkdir -p python-iovec && "
+ "cd python-iovec && "
+ "tar xzf ../python-iovec-src.tar.gz --strip-components=1 && "
+ "python setup.py build && "
+ "python setup.py install --install-lib .. && "
+ "cd .. "
+
+ ( " && "
"wget -q -c -O python-passfd-src.tar.gz %(passfd_url)s && "
"mkdir -p python-passfd && "
"python setup.py install --install-lib .. "
if local.tun_proto == "fd" else ""
- ) )
+ )
+ )
% {
'home' : server.shell_escape(self.home_path),
'passfd_url' : "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-passfd/archive/2a6472c64c87.tar.gz",
+ 'iovec_url' : "http://yans.pl.sophia.inria.fr/code/hgwebdir.cgi/python-iovec/archive/tip.tar.gz",
} )
(out,err),proc = server.popen_ssh_command(
cmd,
args.extend([
"--pass-fd", passfd_arg
])
+ elif check_proto == 'gre':
+ args.extend([
+ "-K", str(min(local_port, peer_port))
+ ])
else:
args.extend([
"-p", str(local_port if listen else peer_port),
out = out.strip()
- match = re.match(r"Using +tun: +([-a-zA-Z0-9]*) +.*",out)
+ match = re.match(r"Using +tun: +([-a-zA-Z0-9]*).*",out)
if match:
self._if_name = match.group(1)
+ elif out:
+ self._logger.debug("if_name: %r does not match expected pattern", out)
+ time.sleep(1)
+ else:
+ pself._logger.warn("if_name: Could not get interface name")
return self._if_name
def async_launch(self, check_proto, listen, extra_args=[]):
def launch(self, check_proto='fd', listen=False, extra_args=[]):
super(TunProtoFD, self).launch(check_proto, listen, extra_args)
+class TunProtoGRE(TunProtoBase):
+ def __init__(self, local, peer, home_path, key, listening):
+ super(TunProtoGRE, self).__init__(local, peer, home_path, key)
+ self.listening = listening
+ self.mode = 'pl-gre-ip'
+
+ def prepare(self):
+ pass
+
+ def setup(self):
+ self.async_launch('gre', False)
+
+ def shutdown(self):
+ self.kill()
+
+ def destroy(self):
+ self.waitkill()
+
+ def launch(self, check_proto='gre', listen=False, extra_args=[]):
+ super(TunProtoGRE, self).launch(check_proto, listen, extra_args)
+
class TunProtoTCP(TunProtoBase):
def __init__(self, local, peer, home_path, key, listening):
super(TunProtoTCP, self).__init__(local, peer, home_path, key)
super(TapProtoFD, self).__init__(local, peer, home_path, key, listening)
self.mode = 'pl-tap'
+class TapProtoGRE(TunProtoGRE):
+ def __init__(self, local, peer, home_path, key, listening):
+ super(TapProtoGRE, self).__init__(local, peer, home_path, key, listening)
+ self.mode = 'pl-gre-eth'
+
TUN_PROTO_MAP = {
'tcp' : TunProtoTCP,
'udp' : TunProtoUDP,
'fd' : TunProtoFD,
+ 'gre' : TunProtoGRE,
}
TAP_PROTO_MAP = {
'tcp' : TapProtoTCP,
'udp' : TapProtoUDP,
'fd' : TapProtoFD,
+ 'gre' : TapProtoGRE,
}
import traceback
import functools
import collections
+import ctypes
+import time
def ipfmt(ip):
ipbytes = map(ord,ip.decode("hex"))
}
def etherProto(packet, len=len):
if len(packet) > 14:
- if packet[12:14] == "\x81\x00":
+ if packet[12] == "\x81" and packet[13] == "\x00":
# tagged
return packet[16:18]
else:
def tun_fwd(tun, remote, with_pi, ether_mode, cipher_key, udp, TERMINATE, stderr=sys.stderr, reconnect=None, rwrite=None, rread=None, tunqueue=1000, tunkqueue=1000,
cipher='AES',
- len=len, max=max, OSError=OSError, select=select.select, selecterror=select.error, piWrap=piWrap, piStrip=piStrip, os=os, socket=socket):
+ len=len, max=max, OSError=OSError, select=select.select, selecterror=select.error, os=os, socket=socket,
+ retrycodes=(os.errno.EWOULDBLOCK, os.errno.EAGAIN, os.errno.EINTR) ):
crypto_mode = False
try:
if cipher_key:
rnonblock = nonblock(remote)
tnonblock = nonblock(tun)
+ # Pick up TUN/TAP writing method
+ if with_pi:
+ try:
+ import iovec
+
+ # We have iovec, so we can skip PI injection
+ # and use iovec which does it natively
+ if ether_mode:
+ twrite = iovec.ethpiwrite
+ tread = iovec.piread2
+ else:
+ twrite = iovec.ippiwrite
+ tread = iovec.piread2
+ except ImportError:
+ # We have to inject PI headers pythonically
+ def twrite(fd, packet, oswrite=os.write, piWrap=piWrap, ether_mode=ether_mode):
+ return oswrite(fd, piWrap(packet, ether_mode))
+
+ # For reading, we strip PI headers with buffer slicing and that's it
+ def tread(fd, maxlen, osread=os.read, piStrip=piStrip):
+ return piStrip(osread(fd, maxlen))
+ else:
+ # No need to inject PI headers
+ twrite = os.write
+ tread = os.read
+
# Limited frame parsing, to preserve packet boundaries.
# Which is needed, since /dev/net/tun is unbuffered
maxbkbuf = maxfwbuf = max(10,tunqueue-tunkqueue)
tunhurry = max(0,maxbkbuf/2)
fwbuf = collections.deque()
bkbuf = collections.deque()
+ nfwbuf = 0
+ nbkbuf = 0
if ether_mode:
packetReady = bool
pullPacket = collections.deque.popleft
+ reschedule = collections.deque.appendleft
else:
packetReady = _packetReady
pullPacket = _pullPacket
+ reschedule = collections.deque.appendleft
tunfd = tun.fileno()
os_read = os.read
os_write = os.write
packet = pullPacket(fwbuf)
if crypto_mode:
- enpacket = encrypt_(packet, crypter)
- else:
- enpacket = packet
+ packet = encrypt_(packet, crypter)
- # try twice - sometimes it barks the first time,
- # due to ICMP Port Unreachable packets from previous writes
- try:
- rwrite(remote, enpacket)
- except socket.error:
- rwrite(remote, enpacket)
+ rwrite(remote, packet)
#wr += 1
if not rnonblock or not packetReady(fwbuf):
# This except handles the entire While block on PURPOSE
# as an optimization (setting a try/except block is expensive)
# The only operation that can raise this exception is rwrite
- if e.errno == os.errno.EWOULDBLOCK:
+ if e.errno in retrycodes:
# re-schedule packet
- fwbuf.insert(0, packet)
+ reschedule(fwbuf, packet)
else:
raise
except:
elif not udp:
# in UDP mode, we ignore errors - packet loss man...
raise
- traceback.print_exc(file=sys.stderr)
+ #traceback.print_exc(file=sys.stderr)
if tun in wrdy:
try:
- while 1:
+ for x in xrange(50):
packet = pullPacket(bkbuf)
- if with_pi:
- packet = piWrap(packet, ether_mode)
- os_write(tunfd, packet)
+ twrite(tunfd, packet)
#wt += 1
# Do not inject packets into the TUN faster than they arrive, unless we're falling
# we'll have high packet loss.
if not tnonblock or len(bkbuf) < tunhurry or not packetReady(bkbuf):
break
+ else:
+ # Give some time for the kernel to process the packets
+ time.sleep(0)
except OSError,e:
# This except handles the entire While block on PURPOSE
# as an optimization (setting a try/except block is expensive)
# The only operation that can raise this exception is os_write
- if e.errno == os.errno.EWOULDBLOCK:
+ if e.errno in retrycodes:
# re-schedule packet
- bkbuf.insert(0, packet)
+ reschedule(bkbuf, packet)
else:
raise
if tun in rdrdy:
try:
while 1:
- packet = os_read(tunfd,2000) # tun.read blocks until it gets 2k!
+ packet = tread(tunfd,2000) # tun.read blocks until it gets 2k!
#rt += 1
- if with_pi:
- packet = piStrip(packet)
fwbuf.append(packet)
if not tnonblock or len(fwbuf) >= maxfwbuf:
# This except handles the entire While block on PURPOSE
# as an optimization (setting a try/except block is expensive)
# The only operation that can raise this exception is os_read
- if e.errno != os.errno.EWOULDBLOCK:
+ if e.errno not in retrycodes:
raise
if remote in rdrdy:
try:
try:
while 1:
- # Try twice, sometimes it barks the first time,
- # due to ICMP Port Unreachable packets from previous writes
- try:
- packet = rread(remote,2000)
- except socket.error:
- packet = rread(remote,2000)
+ packet = rread(remote,2000)
#rr += 1
if crypto_mode:
# This except handles the entire While block on PURPOSE
# as an optimization (setting a try/except block is expensive)
# The only operation that can raise this exception is rread
- if e.errno != os.errno.EWOULDBLOCK:
+ if e.errno not in retrycodes:
raise
except Exception, e:
if reconnect is not None:
elif not udp:
# in UDP mode, we ignore errors - packet loss man...
raise
- traceback.print_exc(file=sys.stderr)
+ #traceback.print_exc(file=sys.stderr)
#print >>sys.stderr, "rr:%d\twr:%d\trt:%d\twt:%d" % (rr,wr,rt,wt)
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_tun_ping_gre(self):
+ self._pingtest("TunInterface", "gre")
+
@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")
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_tap_ping_gre(self):
+ self._pingtest("TapInterface", "gre")
+
@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()
def test_plpl_crossconnect_tcp(self):
self._test_plpl_crossconnect("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_plpl_crossconnect_gre(self):
+ self._test_plpl_crossconnect("gre")
+
@test_util.skipUnless(test_util.pl_auth() is not None,
"Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
def test_plpl_crossconnect_udp_recover(self):