Commit 9fc3b3ec authored by Martín Ferrari's avatar Martín Ferrari

This should complete the interface handling backend.

parent a032a114
......@@ -11,7 +11,7 @@ replies with 221 code.
Command Subcmd Arguments Response Effect
QUIT 221 Close the netns
IF LIST [if#] 200 serialised data ip link list
IF SET if# key val 200/500 ip link set (1)
IF SET if# k v k v... 200/500 ip link set (1)
IF RTRN if# ns 200/500 ip link set netns $ns
ADDR LIST [if#] 200 serialised data ip addr list
ADDR ADD if# addr_spec 200/500 ip addr add
......@@ -32,7 +32,8 @@ PROC POLL <pid> 200 <code>/450/500 check if process alive
PROC WAIT <pid> 200 <code>/500 waitpid(pid)
PROC KILL <pid> <signal> 200/500 kill(pid, signal)
(1) valid arguments: mtu <n>, state <up|down>, name <name>, lladdr <addr>
(1) valid arguments: mtu <n>, up <0|1>, name <name>, lladdr <addr>,
broadcast <addr>, multicast <0|1>, arp <0|1>.
(2) After PROC CRTE, only secondary PROC cmds are accepted until finished.
The parameters are parsed as base64-encoded strings if they start with a '='
......
# vim:ts=4:sw=4:et:ai:sts=4
import re, socket, subprocess, sys
class interface(object):
@classmethod
def parse_ip(cls, line):
match = re.search(r'^(\d+): (\S+): <(\S+)> mtu (\d+) qdisc \S+' +
r'.*link/\S+ ([0-9a-f:]+) brd ([0-9a-f:]+)', line)
flags = match.group(3).split(",")
return cls(
index = match.group(1),
name = match.group(2),
up = "UP" in flags,
mtu = match.group(4),
lladdr = match.group(5),
arp = not ("NOARP" in flags),
broadcast = match.group(6),
multicast = "MULTICAST" in flags)
def __init__(self, index = None, name = None, up = None, mtu = None,
lladdr = None, broadcast = None, multicast = None, arp = None):
self.index = int(index) if index else None
self.name = name
self.up = up
self.mtu = int(mtu) if mtu else None
self.lladdr = lladdr
self.broadcast = broadcast
self.multicast = multicast
self.arp = arp
def __repr__(self):
s = "%s.%s(index = %s, name = %s, up = %s, mtu = %s, lladdr = %s, "
s += "broadcast = %s, multicast = %s, arp = %s)"
return s % (self.__module__, self.__class__.__name__,
self.index.__repr__(), self.name.__repr__(),
self.up.__repr__(), self.mtu.__repr__(),
self.lladdr.__repr__(), self.broadcast.__repr__(),
self.multicast.__repr__(), self.arp.__repr__())
def __sub__(self, o):
"""Compare attributes and return a new object with just the attributes
that differ set (with the value they have in the first operand). The
index remains equal to the first operand."""
name = None if self.name == o.name else self.name
up = None if self.up == o.up else self.up
mtu = None if self.mtu == o.mtu else self.mtu
lladdr = None if self.lladdr == o.lladdr else self.lladdr
broadcast = None if self.broadcast == o.broadcast else self.broadcast
multicast = None if self.multicast == o.multicast else self.multicast
arp = None if self.arp == o.arp else self.arp
return self.__class__(self.index, name, up, mtu, lladdr, broadcast,
multicast, arp)
class address(object):
@classmethod
def parse_ip(cls, line):
match = re.search(r'^inet ([0-9.]+)/(\d+)(?: brd ([0-9.]+))?', line)
if match != None:
return ipv4address(
address = match.group(1),
prefix_len = match.group(2),
broadcast = match.group(3))
match = re.search(r'^inet6 ([0-9a-f:]+)/(\d+)', line)
if match != None:
return ipv6address(
address = match.group(1),
prefix_len = match.group(2))
raise RuntimeError("Problems parsing ip command output")
def __eq__(self, o):
if not isinstance(o, address):
return False
return (self.family == o.family and self.address == o.address and
self.prefix_len == o.prefix_len and
self.broadcast == o.broadcast)
def __hash__(self):
h = (self.address.__hash__() ^ self.prefix_len.__hash__() ^
self.family.__hash__())
if hasattr(self, 'broadcast'):
h ^= self.broadcast.__hash__()
return h
class ipv4address(address):
def __init__(self, address, prefix_len, broadcast):
self.address = address
self.prefix_len = int(prefix_len)
self.broadcast = broadcast
self.family = socket.AF_INET
def __repr__(self):
s = "%s.%s(address = %s, prefix_len = %d, broadcast = %s)"
return s % (self.__module__, self.__class__.__name__,
self.address.__repr__(), self.prefix_len,
self.broadcast.__repr__())
class ipv6address(address):
def __init__(self, address, prefix_len):
self.address = address
self.prefix_len = int(prefix_len)
self.family = socket.AF_INET6
def __repr__(self):
s = "%s.%s(address = %s, prefix_len = %d)"
return s % (self.__module__, self.__class__.__name__,
self.address.__repr__(), self.prefix_len)
import re, subprocess, sys
import netns.interface
# XXX: ideally this should be replaced by netlink communication
def get_if_data():
......@@ -128,7 +23,7 @@ def get_if_data():
continue
match = re.search(r'^(\d+):\s+(.*)', line)
idx = int(match.group(1))
i = interface.parse_ip(line)
i = netns.interface.interface.parse_ip(line)
byidx[idx] = bynam[i.name] = i
return byidx, bynam
......@@ -151,7 +46,7 @@ def get_addr_data():
if match.group(3):
bynam[name] = byidx[idx] = []
continue # link info
bynam[name].append(address.parse_ip(match.group(4)))
bynam[name].append(netns.interface.address.parse_ip(match.group(4)))
return byidx, bynam
def create_if_pair(if1, if2):
......@@ -185,13 +80,13 @@ def create_if_pair(if1, if2):
return interfaces[if1.name], interfaces[if2.name]
def del_if(iface):
interface = get_real_if(iface)
execute(["ip", "link", "del", interface.name])
ifname = get_if_name(iface)
execute(["ip", "link", "del", ifname])
def set_if(iface, recover = True):
interface = get_real_if(iface)
_ils = ["ip", "link", "set", "dev", interface.name]
diff = iface - interface
orig_iface = get_real_if(iface)
_ils = ["ip", "link", "set", "dev", orig_iface.name]
diff = iface - orig_iface # Only set what's needed
cmds = []
if diff.name:
cmds.append(_ils + ["name", diff.name])
......@@ -214,50 +109,54 @@ def set_if(iface, recover = True):
execute(c)
except:
if recover:
set_if(interface, recover = False) # rollback
set_if(orig_iface, recover = False) # rollback
raise
def change_netns(iface, netns):
ifname = get_if_name(iface)
execute(["ip", "link", "set", "dev", ifname, "netns", str(netns)])
def add_addr(iface, address):
interface = get_real_if(iface)
assert address not in interface.addresses
cmd = ["ip", "addr", "add", "dev", interface.name, "local",
ifname = get_if_name(iface)
addresses = get_addr_data()[1][ifname]
assert address not in addresses
cmd = ["ip", "addr", "add", "dev", ifname, "local",
"%s/%d" % (address.address, int(address.prefix_len))]
if hasattr(address, "broadcast"):
cmd += ["broadcast", address.broadcast if address.broadcast else "+"]
execute(cmd)
interfaces = get_if_data()[0]
return interfaces[iface.index]
def del_addr(iface, address):
interface = get_real_if(iface)
assert address in interface.addresses
cmd = ["ip", "addr", "del", "dev", interface.name, "local",
ifname = get_if_name(iface)
addresses = get_addr_data()[1][ifname]
assert address in addresses
cmd = ["ip", "addr", "del", "dev", ifname, "local",
"%s/%d" % (address.address, int(address.prefix_len))]
execute(cmd)
interfaces = get_if_data()[0]
return interfaces[iface.index]
def set_addr(iface, recover = True):
interface = get_real_if(iface)
to_remove = set(interface.addresses) - set(iface.addresses)
to_add = set(iface.addresses) - set(interface.addresses)
def set_addr(iface, addresses, recover = True):
ifname = get_if_name(iface)
addresses = get_addr_data()[1][ifname]
to_remove = set(orig_addresses) - set(addresses)
to_add = set(addresses) - set(orig_addresses)
for a in to_remove:
try:
del_addr(iface, a)
del_addr(ifname, a)
except:
if recover:
set_addr(interface, recover = False) # rollback
set_addr(orig_addresses, recover = False) # rollback
raise
for a in to_add:
try:
add_addr(iface, a)
add_addr(ifname, a)
except:
if recover:
set_addr(interface, recover = False) # rollback
set_addr(orig_addresses, recover = False) # rollback
raise
return get_real_if(iface)
# Useful stuff
......@@ -271,7 +170,7 @@ def execute(cmd):
def get_real_if(iface):
ifdata = get_if_data()
if isinstance(iface, interface):
if isinstance(iface, netns.interface.interface):
if iface.index != None:
return ifdata[0][iface.index]
else:
......@@ -280,4 +179,10 @@ def get_real_if(iface):
return ifdata[0][iface]
return ifdata[1][iface]
def get_if_name(iface):
if isinstance(iface, netns.interface.interface):
if iface.name != None:
return iface
if isinstance(iface, str):
return iface
return get_real_if(iface).name
......@@ -6,8 +6,10 @@ try:
from yaml import CDumper as Dumper
except ImportError:
from yaml import Loader, Dumper
import base64, os, passfd, re, signal, socket, sys, traceback, unshare, yaml
import netns.subprocess_, netns.iproute
import base64, os, passfd, re, signal, sys, traceback, unshare, yaml
import netns.subprocess_, netns.iproute, netns.interface
# FIXME: proper and uniform handling of errors
# ============================================================================
# Server-side protocol implementation
......@@ -25,7 +27,7 @@ _proto_commands = {
"HELP": { None: ("", "") },
"IF": {
"LIST": ("", "i"),
"SET": ("iss", ""),
"SET": ("iss", "s*"),
"RTRN": ("ii", "")
},
"ADDR": {
......@@ -356,8 +358,35 @@ class Server(object):
self.reply(200, ["# Interface data follows."] +
yaml.dump(ifdata).split("\n"))
# def do_IF_SET(self, cmdname, ifnr, key, val):
# def do_IF_RTRN(self, cmdname, ifnr, netns):
def do_IF_SET(self, cmdname, ifnr, *args):
if len(args) % 2:
self.reply(500,
"Invalid number of arguments for IF SET: must be even.")
return
d = {'index': ifnr}
for i in range(len(args) / 2):
d[str(args[i * 2])] = args[i * 2 + 1]
try:
iface = netns.interface.interface(**d)
except:
self.reply(500, "Invalid parameters.")
return
try:
netns.iproute.set_if(iface)
except BaseException, e:
self.reply(500, "Error setting interface: %s." % str(e))
return
self.reply(200, "Done.")
def do_IF_RTRN(self, cmdname, ifnr, netns):
try:
netns.iproute.change_netns(ifnr, netns)
except BaseException, e:
self.reply(500, "Error returning interface: %s." % str(e))
return
self.reply(200, "Done.")
def do_ADDR_LIST(self, cmdname, ifnr = None):
addrdata = netns.iproute.get_addr_data()[0]
......@@ -369,8 +398,22 @@ class Server(object):
self.reply(200, ["# Address data follows."] +
yaml.dump(addrdata).split("\n"))
# def do_ADDR_ADD(self, cmdname, ifnr, address, prefixlen, broadcast = None):
# def do_ADDR_DEL(self, cmdname, ifnr, address, prefixlen):
def do_ADDR_ADD(self, cmdname, ifnr, address, prefixlen, broadcast = None):
if address.find(":") < 0: # crude, I know
a = netns.interface.ipv4address(address, prefixlen, broadcast)
else:
a = netns.interface.ipv6address(address, prefixlen)
netns.iproute.add_addr(ifnr, a)
self.reply(200, "Done.")
def do_ADDR_DEL(self, cmdname, ifnr, address, prefixlen):
if address.find(":") < 0: # crude, I know
a = netns.interface.ipv4address(address, prefixlen, None)
else:
a = netns.interface.ipv6address(address, prefixlen)
netns.iproute.del_addr(ifnr, a)
self.reply(200, "Done.")
# def do_ROUT_LIST(self, cmdname):
# def do_ROUT_ADD(self, cmdname, prefix, prefixlen, nexthop, ifnr):
# def do_ROUT_DEL(self, cmdname, prefix, prefixlen, nexthop, ifnr):
......@@ -528,6 +571,21 @@ class Client(object):
data = data.partition("\n")[2] # ignore first line
return yaml.load(data)
def set_if(self, interface):
cmd = ["IF", "SET", interface.index]
for k in ("name", "mtu", "lladdr", "broadcast", "up", "multicast",
"arp"):
v = getattr(interface, k)
if v != None:
cmd += [k, str(v)]
self._send_cmd(*cmd)
self._read_and_check_reply()
def change_netns(self, ifnr, netns):
self._send_cmd("IF", "RTRN", ifnr, netns)
self._read_and_check_reply()
def get_addr_data(self, ifnr = None):
if ifnr:
self._send_cmd("ADDR", "LIST", ifnr)
......@@ -537,6 +595,19 @@ class Client(object):
data = data.partition("\n")[2] # ignore first line
return yaml.load(data)
def add_addr(self, ifnr, address):
if hasattr(address, "broadcast") and address.broadcast:
self._send_cmd("ADDR", "ADD", ifnr, address.address,
address.prefix_len, address.broadcast)
else:
self._send_cmd("ADDR", "ADD", ifnr, address.address,
address.prefix_len)
self._read_and_check_reply()
def del_addr(self, ifnr, address):
self._send_cmd("ADDR", "DEL", ifnr, address.address, address.prefix_len)
self._read_and_check_reply()
def _b64(text):
text = str(text)
if filter(lambda x: ord(x) <= ord(" ") or ord(x) > ord("z")
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment