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. ...@@ -11,7 +11,7 @@ replies with 221 code.
Command Subcmd Arguments Response Effect Command Subcmd Arguments Response Effect
QUIT 221 Close the netns QUIT 221 Close the netns
IF LIST [if#] 200 serialised data ip link list 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 IF RTRN if# ns 200/500 ip link set netns $ns
ADDR LIST [if#] 200 serialised data ip addr list ADDR LIST [if#] 200 serialised data ip addr list
ADDR ADD if# addr_spec 200/500 ip addr add 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 ...@@ -32,7 +32,8 @@ PROC POLL <pid> 200 <code>/450/500 check if process alive
PROC WAIT <pid> 200 <code>/500 waitpid(pid) PROC WAIT <pid> 200 <code>/500 waitpid(pid)
PROC KILL <pid> <signal> 200/500 kill(pid, signal) 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. (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 '=' The parameters are parsed as base64-encoded strings if they start with a '='
......
# vim:ts=4:sw=4:et:ai:sts=4 # vim:ts=4:sw=4:et:ai:sts=4
import re, socket, subprocess, sys import re, subprocess, sys
import netns.interface
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)
# XXX: ideally this should be replaced by netlink communication # XXX: ideally this should be replaced by netlink communication
def get_if_data(): def get_if_data():
...@@ -128,7 +23,7 @@ def get_if_data(): ...@@ -128,7 +23,7 @@ def get_if_data():
continue continue
match = re.search(r'^(\d+):\s+(.*)', line) match = re.search(r'^(\d+):\s+(.*)', line)
idx = int(match.group(1)) idx = int(match.group(1))
i = interface.parse_ip(line) i = netns.interface.interface.parse_ip(line)
byidx[idx] = bynam[i.name] = i byidx[idx] = bynam[i.name] = i
return byidx, bynam return byidx, bynam
...@@ -151,7 +46,7 @@ def get_addr_data(): ...@@ -151,7 +46,7 @@ def get_addr_data():
if match.group(3): if match.group(3):
bynam[name] = byidx[idx] = [] bynam[name] = byidx[idx] = []
continue # link info 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 return byidx, bynam
def create_if_pair(if1, if2): def create_if_pair(if1, if2):
...@@ -185,13 +80,13 @@ def create_if_pair(if1, if2): ...@@ -185,13 +80,13 @@ def create_if_pair(if1, if2):
return interfaces[if1.name], interfaces[if2.name] return interfaces[if1.name], interfaces[if2.name]
def del_if(iface): def del_if(iface):
interface = get_real_if(iface) ifname = get_if_name(iface)
execute(["ip", "link", "del", interface.name]) execute(["ip", "link", "del", ifname])
def set_if(iface, recover = True): def set_if(iface, recover = True):
interface = get_real_if(iface) orig_iface = get_real_if(iface)
_ils = ["ip", "link", "set", "dev", interface.name] _ils = ["ip", "link", "set", "dev", orig_iface.name]
diff = iface - interface diff = iface - orig_iface # Only set what's needed
cmds = [] cmds = []
if diff.name: if diff.name:
cmds.append(_ils + ["name", diff.name]) cmds.append(_ils + ["name", diff.name])
...@@ -214,50 +109,54 @@ def set_if(iface, recover = True): ...@@ -214,50 +109,54 @@ def set_if(iface, recover = True):
execute(c) execute(c)
except: except:
if recover: if recover:
set_if(interface, recover = False) # rollback set_if(orig_iface, recover = False) # rollback
raise 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): def add_addr(iface, address):
interface = get_real_if(iface) ifname = get_if_name(iface)
assert address not in interface.addresses addresses = get_addr_data()[1][ifname]
cmd = ["ip", "addr", "add", "dev", interface.name, "local", assert address not in addresses
cmd = ["ip", "addr", "add", "dev", ifname, "local",
"%s/%d" % (address.address, int(address.prefix_len))] "%s/%d" % (address.address, int(address.prefix_len))]
if hasattr(address, "broadcast"): if hasattr(address, "broadcast"):
cmd += ["broadcast", address.broadcast if address.broadcast else "+"] cmd += ["broadcast", address.broadcast if address.broadcast else "+"]
execute(cmd) execute(cmd)
interfaces = get_if_data()[0]
return interfaces[iface.index]
def del_addr(iface, address): def del_addr(iface, address):
interface = get_real_if(iface) ifname = get_if_name(iface)
assert address in interface.addresses addresses = get_addr_data()[1][ifname]
cmd = ["ip", "addr", "del", "dev", interface.name, "local", assert address in addresses
cmd = ["ip", "addr", "del", "dev", ifname, "local",
"%s/%d" % (address.address, int(address.prefix_len))] "%s/%d" % (address.address, int(address.prefix_len))]
execute(cmd) execute(cmd)
interfaces = get_if_data()[0]
return interfaces[iface.index]
def set_addr(iface, recover = True): def set_addr(iface, addresses, recover = True):
interface = get_real_if(iface) ifname = get_if_name(iface)
to_remove = set(interface.addresses) - set(iface.addresses) addresses = get_addr_data()[1][ifname]
to_add = set(iface.addresses) - set(interface.addresses) to_remove = set(orig_addresses) - set(addresses)
to_add = set(addresses) - set(orig_addresses)
for a in to_remove: for a in to_remove:
try: try:
del_addr(iface, a) del_addr(ifname, a)
except: except:
if recover: if recover:
set_addr(interface, recover = False) # rollback set_addr(orig_addresses, recover = False) # rollback
raise raise
for a in to_add: for a in to_add:
try: try:
add_addr(iface, a) add_addr(ifname, a)
except: except:
if recover: if recover:
set_addr(interface, recover = False) # rollback set_addr(orig_addresses, recover = False) # rollback
raise raise
return get_real_if(iface)
# Useful stuff # Useful stuff
...@@ -271,7 +170,7 @@ def execute(cmd): ...@@ -271,7 +170,7 @@ def execute(cmd):
def get_real_if(iface): def get_real_if(iface):
ifdata = get_if_data() ifdata = get_if_data()
if isinstance(iface, interface): if isinstance(iface, netns.interface.interface):
if iface.index != None: if iface.index != None:
return ifdata[0][iface.index] return ifdata[0][iface.index]
else: else:
...@@ -280,4 +179,10 @@ def get_real_if(iface): ...@@ -280,4 +179,10 @@ def get_real_if(iface):
return ifdata[0][iface] return ifdata[0][iface]
return ifdata[1][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: ...@@ -6,8 +6,10 @@ try:
from yaml import CDumper as Dumper from yaml import CDumper as Dumper
except ImportError: except ImportError:
from yaml import Loader, Dumper from yaml import Loader, Dumper
import base64, os, passfd, re, signal, socket, sys, traceback, unshare, yaml import base64, os, passfd, re, signal, sys, traceback, unshare, yaml
import netns.subprocess_, netns.iproute import netns.subprocess_, netns.iproute, netns.interface
# FIXME: proper and uniform handling of errors
# ============================================================================ # ============================================================================
# Server-side protocol implementation # Server-side protocol implementation
...@@ -25,7 +27,7 @@ _proto_commands = { ...@@ -25,7 +27,7 @@ _proto_commands = {
"HELP": { None: ("", "") }, "HELP": { None: ("", "") },
"IF": { "IF": {
"LIST": ("", "i"), "LIST": ("", "i"),
"SET": ("iss", ""), "SET": ("iss", "s*"),
"RTRN": ("ii", "") "RTRN": ("ii", "")
}, },
"ADDR": { "ADDR": {
...@@ -356,8 +358,35 @@ class Server(object): ...@@ -356,8 +358,35 @@ class Server(object):
self.reply(200, ["# Interface data follows."] + self.reply(200, ["# Interface data follows."] +
yaml.dump(ifdata).split("\n")) yaml.dump(ifdata).split("\n"))
# def do_IF_SET(self, cmdname, ifnr, key, val): def do_IF_SET(self, cmdname, ifnr, *args):
# def do_IF_RTRN(self, cmdname, ifnr, netns): 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): def do_ADDR_LIST(self, cmdname, ifnr = None):
addrdata = netns.iproute.get_addr_data()[0] addrdata = netns.iproute.get_addr_data()[0]
...@@ -369,8 +398,22 @@ class Server(object): ...@@ -369,8 +398,22 @@ class Server(object):
self.reply(200, ["# Address data follows."] + self.reply(200, ["# Address data follows."] +
yaml.dump(addrdata).split("\n")) yaml.dump(addrdata).split("\n"))
# def do_ADDR_ADD(self, cmdname, ifnr, address, prefixlen, broadcast = None): def do_ADDR_ADD(self, cmdname, ifnr, address, prefixlen, broadcast = None):
# def do_ADDR_DEL(self, cmdname, ifnr, address, prefixlen): 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_LIST(self, cmdname):
# def do_ROUT_ADD(self, cmdname, prefix, prefixlen, nexthop, ifnr): # def do_ROUT_ADD(self, cmdname, prefix, prefixlen, nexthop, ifnr):
# def do_ROUT_DEL(self, cmdname, prefix, prefixlen, nexthop, ifnr): # def do_ROUT_DEL(self, cmdname, prefix, prefixlen, nexthop, ifnr):
...@@ -528,6 +571,21 @@ class Client(object): ...@@ -528,6 +571,21 @@ class Client(object):
data = data.partition("\n")[2] # ignore first line data = data.partition("\n")[2] # ignore first line
return yaml.load(data) 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): def get_addr_data(self, ifnr = None):
if ifnr: if ifnr:
self._send_cmd("ADDR", "LIST", ifnr) self._send_cmd("ADDR", "LIST", ifnr)
...@@ -537,6 +595,19 @@ class Client(object): ...@@ -537,6 +595,19 @@ class Client(object):
data = data.partition("\n")[2] # ignore first line data = data.partition("\n")[2] # ignore first line
return yaml.load(data) 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): def _b64(text):
text = str(text) text = str(text)
if filter(lambda x: ord(x) <= ord(" ") or ord(x) > ord("z") 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