commit 78706926301040c668b6690edeef044218468a67 Author: Ondrej Lichtner olichtne@redhat.com Date: Wed Aug 13 18:34:53 2014 +0200
slave: network namespace support
This patch adds network namespace support to lnst slaves. NetTestSlave: * New network namespaces are created using slave methods add_namespace and del_namespace. New network namespaces created by forking the lnst-slave process and using the unshare system call through libc. The namespaces are stored in the _net_namespaces dictionary in the SlaveMethods class. The process that manages the new namespaces communicates with the parent process through a Pipe object. * To move an interface to a different network namespace we can use the set_if_netns and return_if_netns methods. * The ServerHandler class now uses two ConnectionHandler objects - one for the controller and commands and the new one for communication with different network namespaces. * The ServerHandler class is also capable of sending messages to these namespaces by the send_data_to_netns method. * The Server class now support two new message types: "to_netns", "from_netns" that encapsulate a message that should be routed to a different process. InterfaceManager: * The Device class now has a new attribute _netns that is None if the device is in the current network namespace or contains the name of the namespace that was created by this process. This is used in order to not remove Device objects from the InterfaceManager when we move them to different network namespaces. * I also implemented the reconnect_netlink method that creates a new netlink socket so that processes in the new namespace don't use the socket inherited from the parent process. NmConfigDevice: * Modified the is_nm_managed methods - when working with network namespaces NM should not be used for configuration.
Signed-off-by: Ondrej Lichtner olichtne@redhat.com Signed-off-by: Jiri Pirko jiri@resnulli.us
lnst/Slave/InterfaceManager.py | 24 ++++++- lnst/Slave/NetTestSlave.py | 146 +++++++++++++++++++++++++++++++++++++++- lnst/Slave/NmConfigDevice.py | 10 ++- 3 files changed, 174 insertions(+), 6 deletions(-) --- diff --git a/lnst/Slave/InterfaceManager.py b/lnst/Slave/InterfaceManager.py index 94a5c02..78cc46b 100644 --- a/lnst/Slave/InterfaceManager.py +++ b/lnst/Slave/InterfaceManager.py @@ -50,6 +50,13 @@ class InterfaceManager(object): def clear_if_mapping(self): self._id_mapping = {}
+ def reconnect_netlink(self): + if self._nl_socket != None: + self._nl_socket.close() + self._nl_socket = None + self._nl_socket = IPRSocket() + self._nl_socket.bind() + def get_nl_socket(self): return self._nl_socket
@@ -93,8 +100,10 @@ class InterfaceManager(object): self._devices[msg['index']] = dev elif msg['header']['type'] == RTM_DELLINK: if msg['index'] in self._devices: - self._devices[msg['index']].del_link() - del self._devices[msg['index']] + dev = self._devices[msg['index']] + if dev.get_netns() != None: + dev.del_link() + del self._devices[msg['index']] else: return
@@ -199,6 +208,7 @@ class Device(object): self._state = None self._master = {"primary": None, "other": []} self._slaves = [] + self._netns = None
self._if_manager = if_manager
@@ -209,6 +219,7 @@ class Device(object): self._state = nl_msg.get_attr("IFLA_OPERSTATE") self._ip = None #TODO self.set_master(nl_msg.get_attr("IFLA_MASTER"), primary=True) + self._netns = None
self._initialized = True
@@ -216,8 +227,10 @@ class Device(object): if self._if_index == nl_msg['index']: self._hwaddr = normalize_hwaddr(nl_msg.get_attr("IFLA_ADDRESS")) self._name = nl_msg.get_attr("IFLA_IFNAME") + self._state = nl_msg.get_attr("IFLA_OPERSTATE") self._ip = None #TODO self.set_master(nl_msg.get_attr("IFLA_MASTER"), primary=True) + self._netns = None
link = nl_msg.get_attr("IFLA_LINK") if link != None: @@ -374,3 +387,10 @@ class Device(object): self._conf.down() else: exec_cmd("ip link set %s down" % self._name) + + def set_netns(self, netns): + self._netns = netns + return + + def get_netns(self): + return self._netns diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py index 2cc0cca..780cd9a 100644 --- a/lnst/Slave/NetTestSlave.py +++ b/lnst/Slave/NetTestSlave.py @@ -18,6 +18,8 @@ import sys, traceback import datetime import socket import dbus +import ctypes +import multiprocessing from time import sleep from xmlrpclib import Binary from tempfile import NamedTemporaryFile @@ -43,11 +45,14 @@ class SlaveMethods: ''' Exported xmlrpc methods ''' - def __init__(self, command_context, log_ctl, if_manager): + def __init__(self, command_context, log_ctl, if_manager, net_namespaces, + server_handler): self._packet_captures = {} self._if_manager = if_manager self._command_context = command_context self._log_ctl = log_ctl + self._net_namespaces = net_namespaces + self._server_handler = server_handler
self._capture_files = {} self._copy_targets = {} @@ -92,6 +97,10 @@ class SlaveMethods: self._cache.del_old_entries() self.reset_file_transfers() self._remove_capture_files() + + for netns in self._net_namespaces.keys(): + self.del_namespace(netns) + self._net_namespaces = {} return "bye"
def kill_cmds(self): @@ -161,6 +170,8 @@ class SlaveMethods: def start_packet_capture(self, filt): files = {} for if_id, dev in self._if_manager.get_mapped_devices().iteritems(): + if dev.get_netns() != None: + continue dev_name = dev.get_name()
df_handle = NamedTemporaryFile(delete=False) @@ -256,6 +267,11 @@ class SlaveMethods: logging.info("Performing machine cleanup.") self._command_context.cleanup() self._if_manager.deconfigure_all() + + for netns in self._net_namespaces.keys(): + self.del_namespace(netns) + self._net_namespaces = {} + self._if_manager.clear_if_mapping() self._cache.del_old_entries() self.restore_system_config() @@ -371,9 +387,83 @@ class SlaveMethods: lnst_config.set_option("environment", "use_nm", self._bkp_nm_opt_val) return val
+ def add_namespace(self, netns): + if netns in self._net_namespaces: + logging.debug("Network namespace %s already exists." % netns) + else: + logging.debug("Creating network namespace %s." % netns) + read_pipe, write_pipe = multiprocessing.Pipe() + pid = os.fork() + if pid != 0: + self._net_namespaces[netns] = {"pid": pid, + "pipe": read_pipe} + self._server_handler.add_netns(netns, read_pipe) + return None + elif pid == 0: + #create new network namespace + libc_name = ctypes.util.find_library("c") + CLONE_NEWNET = 0x40000000 #from sched.h + libc = ctypes.CDLL(libc_name) + libc.unshare(CLONE_NEWNET) + + #set ctl socket to pipe to main netns + self._server_handler.close_s_sock() + self._server_handler.close_c_sock() + self._server_handler.clear_connections() + self._server_handler.clear_netns_connections() + + self._if_manager.reconnect_netlink() + self._server_handler.add_connection('netlink', + self._if_manager.get_nl_socket()) + + self._server_handler.set_netns(netns) + self._server_handler.set_ctl_sock((write_pipe, "root_netns")) + + self._log_ctl.disable_logging() + self._log_ctl.set_connection(write_pipe) + + self._if_manager.rescan_devices() + logging.debug("Created network namespace %s" % netns) + return True + else: + raise Exception("Fork failed!") + + def del_namespace(self, netns): + if netns not in self._net_namespaces: + logging.debug("Network namespace %s doesn't exist." % netns) + return False + else: + netns_pid = self._net_namespaces[netns]["pid"] + os.kill(netns_pid, signal.SIGTERM) + os.waitpid(netns_pid, 0) + logging.debug("Network namespace %s removed." % netns) + + self._net_namespaces[netns]["pipe"].close() + self._server_handler.del_netns(netns) + del self._net_namespaces[netns] + return True + + def set_if_netns(self, if_id, netns): + netns_pid = self._net_namespaces[netns]["pid"] + + device = self._if_manager.get_mapped_device(if_id) + dev_name = device.get_name() + device.set_netns(netns) + + exec_cmd("ip link set %s netns %d" % (dev_name, netns_pid)) + return True + + def return_if_netns(self, if_id): + device = self._if_manager.get_mapped_device(if_id) + dev_name = device.get_name() + ppid = os.getppid() + exec_cmd("ip link set %s netns %d" % (dev_name, ppid)) + return True + class ServerHandler(object): def __init__(self, addr): self._connection_handler = ConnectionHandler() + self._netns_con_handler = ConnectionHandler() try: self._s_socket = socket.socket() self._s_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) @@ -384,6 +474,7 @@ class ServerHandler(object): exit(1)
self._c_socket = None + self._netns = None
def accept_connection(self): self._c_socket, addr = self._s_socket.accept() @@ -400,8 +491,26 @@ class ServerHandler(object): else: return None
+ def set_ctl_sock(self, sock): + if self._c_socket != None: + self._c_socket.close() + self._c_socket = None + self._c_socket = sock + self._connection_handler.add_connection(self._c_socket[1], + self._c_socket[0]) + + def close_s_sock(self): + self._s_socket.close() + self._s_socket = None + + def close_c_sock(self): + self._c_socket[0].close() + self._connection_handler.remove_connection(self._c_socket[0]) + self._c_socket = None + def get_messages(self): messages = self._connection_handler.check_connections() + messages += self._netns_con_handler.check_connections()
#push ctl messages to the end of message queue, this ensures that #update messages are handled first @@ -422,10 +531,21 @@ class ServerHandler(object):
def send_data_to_ctl(self, data): if self._c_socket != None: + if self._netns != None: + data = {"type": "from_netns", + "netns": self._netns, + "data": data} return send_data(self._c_socket[0], data) else: return False
+ def send_data_to_netns(self, netns, data): + netns_con = self._netns_con_handler.get_connection(netns) + if netns_con == None: + raise Exception("No such namespace!") + else: + return send_data(netns_con, data) + def add_connection(self, id, connection): self._connection_handler.add_connection(id, connection)
@@ -441,6 +561,19 @@ class ServerHandler(object): self.remove_connection(key) self.add_connection(key, connection)
+ def set_netns(self, netns): + self._netns = netns + + def add_netns(self, netns, connection): + self._netns_con_handler.add_connection(netns, connection) + + def del_netns(self, netns): + connection = self._netns_con_handler.get_connection(netns) + self._netns_con_handler.remove_connection(connection) + + def clear_netns_connections(self): + self._netns_con_handler.clear_connections() + class NetTestSlave: def __init__(self, log_ctl, port = DefaultRPCPort): die_when_parent_die() @@ -448,8 +581,12 @@ class NetTestSlave: self._cmd_context = NetTestCommandContext() self._server_handler = ServerHandler(("", port)) self._if_manager = InterfaceManager(self._server_handler) + + self._net_namespaces = {} + self._methods = SlaveMethods(self._cmd_context, log_ctl, - self._if_manager) + self._if_manager, self._net_namespaces, + self._server_handler)
self.register_die_signal(signal.SIGHUP) self.register_die_signal(signal.SIGINT) @@ -538,6 +675,11 @@ class NetTestSlave: self._cmd_context.del_cmd(cmd) elif msg["type"] == "netlink": self._if_manager.handle_netlink_msgs(msg["data"]) + elif msg["type"] == "from_netns": + self._server_handler.send_data_to_ctl(msg["data"]) + elif msg["type"] == "to_netns": + netns = msg["netns"] + self._server_handler.send_data_to_netns(netns, msg["data"]) else: raise Exception("Recieved unknown command")
diff --git a/lnst/Slave/NmConfigDevice.py b/lnst/Slave/NmConfigDevice.py index 460fd26..e07c0ba 100644 --- a/lnst/Slave/NmConfigDevice.py +++ b/lnst/Slave/NmConfigDevice.py @@ -137,6 +137,8 @@ class NmConfigDeviceGeneric(object):
@classmethod def is_nm_managed(cls, dev_config, if_manager): + if dev_config["netns"] != None: + return False return is_nm_managed_by_name(dev_config["name"])
def _wait_for_state(self, new_state, old_state, reason): @@ -808,5 +810,9 @@ type_class_mapping = { }
def is_nm_managed(dev_config, if_manager): - return type_class_mapping[dev_config["type"]].is_nm_managed(dev_config, - if_manager) + if lnst_config.get_option("environment", "use_nm") and\ + check_process_running("NetworkManager"): + return type_class_mapping[dev_config["type"]].is_nm_managed(dev_config, + if_manager) + else: + return False
lnst-developers@lists.fedorahosted.org