def __createNetns(self, phyIfaceIndex): netnsName = self.__getNetnsName() (pvdIfaceName, pvdIfaceIndex) = self.__getPvdIfaceParams() netns.create(netnsName) LOG.debug('network namespace {0} created'.format(netnsName)) # create a virtual interface where PvD parameters are going to be configured, then move the interface to the new network namespace self.ipRoot.link_create(ifname=pvdIfaceName, index=pvdIfaceIndex, kind=self.__PVD_IFACE_TYPE, link=phyIfaceIndex) LOG.debug('macvlan {0} created in default network namespace'.format(pvdIfaceName)) pvdIfaceIndex = self.ipRoot.link_lookup(ifname=pvdIfaceName) self.ipRoot.link('set', index=pvdIfaceIndex[0], net_ns_fd=netnsName) LOG.debug('macvlan {0} moved to network namespace {1}'.format(pvdIfaceName, netnsName)) # change the namespace and get new NETLINK handles to operate in new namespace netns.setns(netnsName) LOG.debug('network namespace switched to {0}'.format(netnsName)) ip = IPRoute() ipdb = IPDB() ipdb.register_callback(self.__onIfaceStateChange) # disable kernel to auto-configure the interface associated with the PvD, let the pvdman to solely control interface configuration acceptRaConfFile = self.__ACCEPT_RA_CONF_FILE.replace(self.__IFACENAME_REPLACE_PATTERN, pvdIfaceName) acceptRaConfFile = open(acceptRaConfFile, 'w') acceptRaConfFile.write('0') LOG.debug('processing of RAs by kernel disabled in {0}'.format(acceptRaConfFile.name)) # return to a default network namespace to not cause a colision with other modules # ip and ipdb handles continue to work in the target network namespace netns.setns(self.__NETNS_DEFAULT_NAME) LOG.debug('network namespace switched to default') # get new index since interface has been moved to a different namespace loIfaceIndex = ip.link_lookup(ifname=self.__LOOPBACK_IFACE_NAME) if (len(loIfaceIndex) > 0): loIfaceIndex = loIfaceIndex[0] pvdIfaceIndex = ip.link_lookup(ifname=pvdIfaceName) if (len(pvdIfaceIndex) > 0): pvdIfaceIndex = pvdIfaceIndex[0] # start interfaces ip.link_up(loIfaceIndex) ip.link_up(pvdIfaceIndex) # clear network configuration if exists ip.flush_addr(index=pvdIfaceIndex) ip.flush_routes(index=pvdIfaceIndex) ip.flush_rules(index=pvdIfaceIndex) LOG.debug('macvlan {0} in network namespace {1} initialized'.format(pvdIfaceName, netnsName)) return (netnsName, pvdIfaceName, ip)
def main(): global arpcache, neighborcache, modgatecnt, ipdb, event_callback, bess, ipr # for holding unresolved ARP queries arpcache = {} # for holding list of registered neighbors neighborcache = {} # for holding gate count per route module modgatecnt = {} # for interacting with kernel ipdb = IPDB() ipr = IPRoute() # for bess client bess = BESS() # connect to bessd connect_bessd() # program current routes bootstrap_routes() # listen for netlink events print('Registering netlink event listener callback...'), event_callback = ipdb.register_callback(netlink_event_listener) print('Done.') signal.signal(signal.SIGHUP, reconfigure) signal.signal(signal.SIGINT, cleanup) signal.signal(signal.SIGTERM, cleanup) signal.pause()
def run(self): logging.info('Started LinkNeg...') iface = self.get_mopt('iface') state = bool_it(self.get_mopt('state')) admin_speed = self.get_opt('speed', default=0) timeout = self.get_opt('timeout', default=10) ip = IPDB() self.oper_state = ip.interfaces[iface]['operstate'] wd = ip.watchdog(ifname=iface) cuid = ip.register_callback(self._cb) wd.wait(timeout=timeout) ip.unregister_callback(cuid) ip.release() admin_state = 'UP' if state else 'DOWN' oper_state = self.oper_state if admin_state == oper_state and admin_speed: oper_speed = self.get_speed(iface) else: oper_speed = 0 res_data = {'admin_state': admin_state, 'oper_state': oper_state} if admin_speed: res_data['admin_speed'] = "%s Mb/s" % admin_speed res_data['oper_speed'] = "%s Mb/s" % oper_speed if admin_state == oper_state and admin_speed == oper_speed: self.set_pass(res_data) else: self.set_fail(res_data)
def run(self): logging.info('Started LinkNeg...') iface = self.get_mopt('iface') state = bool_it(self.get_mopt('state')) timeout = self.get_opt('timeout', default=10) ip = IPDB() self.oper_state = ip.interfaces[iface]['operstate'] wd = ip.watchdog(ifname=iface) cuid = ip.register_callback(self._cb) wd.wait(timeout=timeout) ip.unregister_callback(cuid) ip.release() admin_state = 'UP' if state else 'DOWN' oper_state = self.oper_state res_data = {'admin_state': admin_state, 'oper_state': oper_state} if admin_state == oper_state: self.set_pass(res_data) else: self.set_fail(res_data)
class Pingu(object): def __init__(self, configuration=None): if configuration is None: configuration = { "host": "8.8.8.8", "gateways_file": None, "interfaces": { "enx0c5b8f279a64": { "metric": 100, "count": 10, "max_lost": 5, "max_delay": 100, "reset_script": None, "reset_script_grace_period": 0, }, "wlo1": { "metric": 50, } }, "period": 5 } self.log = logging.getLogger("py-pingu") self.gateways = {} self.ipdb = IPDB() self.pyroute = None self.event_queue = Queue() self.DEFAULT_PROTO = configuration.get("proto", PINGU_PROTO) self.gw_lock = Lock() self.sockets = {} self.route_monitor = Thread(target=self.route_monitor_thread) self.next_check_timestamps = {} self.loop_event = Event() self.exited = Event() self.exited.clear() self.base_period = configuration.get("period", 5) self.configuration = configuration self.configure_scapy() self.load_gw_from_file() def load_gw_from_file(self): filename = self.configuration.get("gateways_file", None) if filename: try: if not os.path.isfile(filename): self.log.info("Gateways file not found.") with open(filename, "r") as f: self.gateways = json.load(f) self.log.info("Loaded %s gateways from the gateways file", len(self.gateways)) except Exception as ex: self.log.error("Error while loading gateways file: %s" % filename, exc_info=True) def save_gw_file(self): filename = self.configuration.get("gateways_file", None) try: if not filename: return with open(filename, "w") as file: json.dump(self.gateways, file) except Exception as ex: self.log.error("Error while writing gateways file: %s" % filename, exc_info=True) def configure_scapy(self): scapyconf.sniff_promisc = 0 def get_ip(self, idx, addrs): for a in addrs: if a["index"] == idx: return self.get_attribute(a, "IFA_ADDRESS") def set_gw_for_iface(self, iface, gw): self.gateways[iface] = gw self.save_gw_file() def route_monitor_thread(self): while not self.exited.is_set(): with IPRoute() as self.pyroute: try: message = self.event_queue.get(timeout=0.5) except Empty as ex: continue try: links = self.pyroute.get_links() with self.gw_lock: iface = self.get_iface_name( message.get_attr("RTA_OIF"), links) if iface not in self.configuration["interfaces"]: continue if message["dst_len"] == 0 and message["src_len"] == 0 and \ message["table"] == 254 and message["proto"] != self.DEFAULT_PROTO: gw = self.get_attribute(message, "RTA_GATEWAY") with IPRoute() as ipr2: kwargs = dict(dst_len=0, src_len=0, type=message['type'], scope=message['scope'], oif=message.get_attr("RTA_OIF")) # # route scope is == 253 if the destination network is on the local host, # so the fetched gateway will be null # # eg. ip route add default dev eth0 # if gw is not None and message['scope'] != 253: kwargs["gateway"] = gw ipr2.route("del", **kwargs) self.log.info( "Fetched new default gw for interface %s: %s " % (iface, gw)) self.set_gw_for_iface(iface, gw) self.next_check_timestamps[iface] = -1 self.loop_event.set() except Exception as ex: self.log.exception("Exception in route_monitor_thread") finally: self.event_queue.task_done() def get_attribute(self, message, name): for x in message["attrs"]: if x[0] == name: return x[1] def get_iface_name(self, index, links): for l in links: if l["index"] == index: return self.get_attribute(l, "IFLA_IFNAME") def load_route_table(self): self.log.info("Loading routing table") with IPDB() as ipdb: routes = ipdb.routes.filter({"dst": "default", "table": 254}) with self.gw_lock: for r in routes: name = ipdb.interfaces[r["route"].oif].ifname if name not in self.configuration["interfaces"]: continue gw = r["route"].gateway self.log.info("Fetched gateway for %s: %s" % (name, gw)) self.set_gw_for_iface(name, gw) with r["route"] as to_del: to_del.remove() self.log.info("Loaded %s gateways from routing table" % len(self.gateways)) def get_gw_mac_address(self, iface): for i in range(5): arp_req_ip_dst = self.gateways[iface] source_hw_addr = get_if_hwaddr(iface) arp_req = Ether(dst="ff:ff:ff:ff:ff:ff", src=source_hw_addr) / \ ARP(pdst=arp_req_ip_dst, psrc=get_if_addr(iface), hwsrc=source_hw_addr) ans, unans = sndrcv(self.sockets[iface], arp_req, timeout=1, verbose=0) if len(ans) < 1: continue return ans[0][1].src raise ConnectionError("ARP Resolution Failed for %s (%s)" % (self.gateways[iface], iface)) def use_l2_packet(self, interface): addrfamily, mac = get_if_raw_hwaddr(interface) return addrfamily in [ARPHDR_ETHER, ARPHDR_LOOPBACK] def check_interface(self, interface): try: if interface not in self.gateways: self.log.debug("No gateway fetched for %s" % interface) return False if hasattr(self.ipdb.interfaces[interface], "carrier" ) and self.ipdb.interfaces[interface].carrier == 0: return False count = self.configuration["interfaces"][interface].get( "count", DEFAULT_COUNT) max_lost = self.configuration["interfaces"][interface].get( "max_lost", DEFAULT_MAX_LOST) max_delay = self.configuration["interfaces"][interface].get( "max_delay", DEFAULT_MAX_DELAY) delays = [] id = random.randint(1, 65535) if_addr = get_if_addr(interface) if self.use_l2_packet(interface): try: mac_address_gw = self.get_gw_mac_address(interface) except ConnectionError as ex: self.log.error(ex.args[0]) return False if_hw_addr = get_if_hwaddr(interface) header = Ether(dst=mac_address_gw, src=if_hw_addr) else: # if using an L3 socket for this interface -> # add scapy route to route L3 traffic on the probed interface # the route will not be added to the kernel routing table scapyconf.route.add(net='%s/32' % self.configuration["host"], dev=interface) header = None for i in range(count): if self.exited.is_set(): return if header: packet = header / \ IP(src=if_addr, dst=self.configuration["host"]) / \ ICMP(id=id, seq=i + 1) else: packet = IP(src=if_addr, dst=self.configuration["host"]) / \ ICMP(id=id, seq=i + 1) ans, unans = sndrcv(self.sockets[interface], packet, timeout=1, verbose=0) # self.log.debug("Ping sent") if len(ans) > 0: if ans[0][1][ICMP].type == 0 and ans[0][1][ICMP].id == id: rx = ans[0][1] tx = ans[0][0] delta = rx.time - tx.sent_time delays.append(delta) else: self.log.debug( "[%s] Missed ping seq=%s - ICMP Recv Type: %s (must be 0) - Id: %s (must be %s) " % (interface, i + 1, ans[0][1][ICMP].type, ans[0][1][ICMP].id, id)) else: self.log.debug("[%s] Missed ping id=%s seq=%s - Timeout" % (interface, id, i + 1)) self.exited.wait(timeout=DEFAULT_DELAY / 1000) # if using an L3 socket for this interface -> remove scapy route if header is None: scapyconf.route.delt(net='%s/32' % self.configuration["host"], dev=interface) if len(delays) == 0: return False ok_count = len(delays) delay_avg = sum(delays) / ok_count is_ok = (count - ok_count) <= max_lost and (delay_avg * 1000) < max_delay self.log.debug( "[%s %s] Ping %s via %s result - lost: %s/%s, delay: %0.0f ms" % (interface, "OK" if is_ok else "FAIL", self.configuration["host"], self.gateways[interface], (count - ok_count), count, delay_avg * 1000)) return is_ok except PermissionError as pe: raise pe except Exception as ex: self.log.exception("check_interface error: ") return False def metric(self, interface): return self.configuration["interfaces"][interface] def activate_interface(self, name): if name not in self.gateways: self.log.warning("Missing default gw for ", name) with IPDB() as ipdb: existing_route = None try: existing_route = ipdb.routes[{ 'oif': ipdb.interfaces[name].index, 'proto': self.DEFAULT_PROTO, 'dst': 'default' }] except KeyError as ex: pass if existing_route is not None: if existing_route["priority"] == self.configuration["interfaces"][name]["metric"] and \ existing_route["gateway"] == self.gateways[name]: return # esco se gia' esiste # altrimenti cancello if len(existing_route) > 0: ipdb.routes.remove({ 'oif': ipdb.interfaces[name].index, 'proto': self.DEFAULT_PROTO, 'dst': 'default' }).commit() metric = self.configuration["interfaces"][name]["metric"] ipdb.routes.add({ 'oif': ipdb.interfaces[name].index, 'dst': 'default', 'proto': self.DEFAULT_PROTO, 'gateway': self.gateways[name], "priority": metric }).commit() self.log.info("[INSTALLED] %s via %s (metric %s)" % (name, self.gateways[name], metric)) def deactivate_interface(self, name): with IPDB() as ipdb: try: ipdb.routes.remove({ 'oif': ipdb.interfaces[name].index, 'proto': self.DEFAULT_PROTO, 'dst': 'default' }) ipdb.commit() self.log.info("[REMOVED] %s via %s" % (name, self.gateways.get(name, "---"))) except KeyError as ex: pass def callback(self, ipdb, message, action): if "ROUTE" in message["event"]: self.log.debug("Event detected: %s " % message["event"]) if message["event"] == "RTM_NEWROUTE": self.event_queue.put(message) def print_fetched_gws(self, a, b): with self.gw_lock: self.log.info("Fetched gateways:\n %s\n " % json.dumps(self.gateways)) def get_interface_next_check(self, interface): return time.time() + self.configuration["interfaces"][interface].get( "period", self.base_period) def load_next_checks(self): for i in self.configuration["interfaces"].keys(): self.next_check_timestamps[i] = -1 def run_on_interface(self, interface): try: if self.use_l2_packet(interface): self.sockets[interface] = L2Socket( iface=interface, filter="arp or (icmp and src host %s)" % self.configuration["host"]) else: self.sockets[interface] = L3PacketSocket(iface=interface) except Exception as ex: if "permission" in str(ex): self.log.exception("Error while opening filter: ") # if "tcpdump" in str(ex).lower(): # self.log.error("py-pingu requires tcpdump executable, please install tcpdump.") # exit(1) return with self.gw_lock: if self.check_interface(interface): self.activate_interface(interface) else: try: self.run_reset_script(interface) except Exception as ex: self.log.exception("Error while executing reset script - ", exc_info=True) self.deactivate_interface(interface) def run_reset_script(self, interface): script = self.configuration["interfaces"][interface].get( "reset_script", None) if script is None: return grace_period = self.configuration["interfaces"][interface].get( "reset_script_grace_period", 600) last = self.configuration["interfaces"][interface].get( "last_reset_script_run", 0) now = time.time() if now - last < grace_period: return if not os.path.isfile(script): self.log.warning("Unable to find reset script %s for %s" % (script, interface)) return self.log.info("Executing reset script for %s" % interface) p = subprocess.Popen(script, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True) # allow external program to work p.wait() self.configuration["interfaces"][interface][ "last_reset_script_run"] = now def fetch_next_interface(self): v = min(self.next_check_timestamps, key=self.next_check_timestamps.get) now = time.time() expiration = self.next_check_timestamps[v] delta = expiration - now return v, delta if delta > 0 else 0 def on_sigint(self, a, b): self.exited.set() self.loop_event.set() def run(self): self.ipdb.register_callback(self.callback, ) self.log.info("Welcome to py-pingu! ") self.log.info(json.dumps(self.configuration, indent=2)) self.route_monitor.start() signal.signal(signal.SIGINT, self.on_sigint) signal.signal(signal.SIGUSR1, self.print_fetched_gws) self.load_route_table() self.load_next_checks() while not self.exited.is_set(): name, period = self.fetch_next_interface() if period > 0: if self.loop_event.wait(timeout=period): self.loop_event.clear() continue try: self.log.debug("Probing %s" % name) ifaces = get_if_list() if name not in ifaces: self.log.debug("Interface %s does not exists." % name) continue self.run_on_interface(name) except Exception as ex: self.log.exception("Error in main loop:", exc_info=True) finally: self.next_check_timestamps[ name] = self.get_interface_next_check(name) self.log.info("Exit signal received")
# pp.pprint(netlink_message) def new_dev_callback(ipdb, netlink_message, action): if action == 'RTM_NEWLINK': #pp.pprint(netlink_message) nldict = dict(netlink_message) print "handling call back" ifindex = nldict['index'] ifnametup = nldict['attrs'][0] ifname = ifnametup[1] nsPath = check_output([ "sudo", "docker", "inspect", "--format", " '{{.NetworkSettings.SandboxKey}}'", ifname[:-1] ]) nsPath = nsPath.rstrip('\r\n')[1:] nsPath = json.dumps(str(nsPath)).replace("'", "") nsPath = nsPath.replace('"', "") out = check_output(["sudo", "ls", "-Li", nsPath]) inum = out.split(" ")[0] print inum ifInum[ct.c_uint(ifindex)] = ct.c_uint64(int(inum)) print "ifindex", ifindex, ifInum[ct.c_uint(ifindex)] #addr_callback = ipdb.register_callback(new_address_callback) dev_callback = ipdb.register_callback(new_dev_callback) input() #ipdb.unregister_callback(addr_callback) ipdb.unregister_callback(dev_callback)
ipdb.interfaces.br0.commit() except Exception: pass # create IPDB instance ip = IPDB() # create watchdogs wd0 = ip.watchdog(ifname='br0') wd1 = ip.watchdog(ifname='bala_port0') wd2 = ip.watchdog(ifname='bala_port1') # create bridge ip.create(kind='bridge', ifname='br0').commit() # wait the bridge to be created wd0.wait() # register callback ip.register_callback(cb) # create ports ip.create(kind='dummy', ifname='bala_port0').commit() ip.create(kind='dummy', ifname='bala_port1').commit() # sleep for interfaces wd1.wait() wd2.wait() ip.unregister_callback(cb) # cleanup for i in ('bala_port0', 'bala_port1', 'br0'): try: ip.interfaces[i].remove().commit() except: pass
except Exception: pass # create IPDB instance ip = IPDB() # create watchdogs wd0 = ip.watchdog(ifname='br0') wd1 = ip.watchdog(ifname='bala_port0') wd2 = ip.watchdog(ifname='bala_port1') # create bridge ip.create(kind='bridge', ifname='br0').commit() # wait the bridge to be created wd0.wait() # register callback ip.register_callback(cb) # create ports ip.create(kind='dummy', ifname='bala_port0').commit() ip.create(kind='dummy', ifname='bala_port1').commit() # sleep for interfaces wd1.wait() wd2.wait() ip.unregister_callback(cb) # cleanup for i in ('bala_port0', 'bala_port1', 'br0'): try: ip.interfaces[i].remove().commit() except: pass
from pyroute2.common import uifname p0 = uifname() ### # # "Pre" callbacks are executed before the message # gets processed by IPDB, and in synchronous manner. # Normally, you will not need these callbacks, but # they can be useful to perform some hacks # def cb(ipdb, msg, action): if action == 'RTM_NEWLINK': msg['flags'] = 1234 # create IPDB instance ip = IPDB() # register "pre" callback ip.register_callback(cb, mode='pre') # create an interface ip.create(kind='dummy', ifname=p0).commit() # assert flags assert ip.interfaces[p0].flags == 1234 # cleanup ip.interfaces[p0].remove() ip.interfaces[p0].commit() # release Netlink socket ip.release()
class DevTracker(object): def __init__(self, devlist, logger=None): self.devlist = devlist self.queue = queue.Queue() self.ipdb = IPDB() self.logger = logger or default_logger def _get_current(self): for dev in self.devlist: if not dev in self.ipdb.interfaces: continue devinfo = self.ipdb.by_name[dev] for addr, preflen in devinfo["ipaddr"]: if whichipversion(addr) == 4: addr = "{}/{}".format(addr, preflen) prefix = ipaddress.ip_interface(addr).network msg = { "action": "RTM_NEWADDR", "device": dev, "address": prefix, } self.queue.put(msg) def start(self): self.logger.debug("start to track devices: %s", " ".join(self.devlist)) self._get_current() def ipdb_callback(ipdb, msg, action): if not action in ("RTM_NEWADDR", "RTM_DELADDR"): return if msg["family"] != 2: # XXX: IPv4 only :( return tracked_dev = None tracked_dev_addr = None for attr in msg["attrs"]: if attr[0] == "IFA_LABEL" and attr[1] in self.devlist: tracked_dev = attr[1] elif attr[0] == "IFA_ADDRESS": tracked_dev_addr = attr[1] if not tracked_dev or not tracked_dev_addr: return addr = "{}/{}".format(tracked_dev_addr, msg["prefixlen"]) prefix = ipaddress.ip_interface(addr).network msg = { "action": action, "device": tracked_dev, "address": prefix, } try: self.logger.debug("device addr change: %s", str(msg)) self.queue.put(msg) except queue.Full: self.logger.error("devtracker queue full for msg %s", str(msg)) self.cbid = self.ipdb.register_callback(ipdb_callback) def stop(self): self.ipdb.unregister_callback(self.cbid) def queued(self): return (not self.queue.empty()) def pop(self): try: return self.queue.get(block=False) except queue.Empty: return None
class PvdManager: __NETNS_PREFIX = 'mifpvd-' __netnsIdGenerator = 0; __PVD_IFACE_BASENAME = 'mifpvd' __PVD_IFACE_TYPE = 'macvlan' __NETNSDIRNAME_REPLACE_PATTERN = '%NETNS_NAME%' __DNS_CONF_FILE = '/etc/netns/' + __NETNSDIRNAME_REPLACE_PATTERN + '/resolv.conf' __IFACENAME_REPLACE_PATTERN = '%IFACE_NAME%' __ACCEPT_RA_CONF_FILE = '/proc/sys/net/ipv6/conf/' + __IFACENAME_REPLACE_PATTERN + '/accept_ra' __LOOPBACK_IFACE_NAME = 'lo' __NETNS_DEFAULT_PROC = '/proc/1/ns/net' __NETNS_DEFAULT_NAME = 'mifpvd-default' __DEFAULT_ROUTE_ADDRESS = '::' __LINK_LOCAL_PREFIX = 'fe80::' __LINK_LOCAL_PREFIX_LENGTH = 64 ''' PRIVATE METHODS ''' def __init__(self): LOG.debug('PvdManager initialization started') self.pvds = {} self.ipRoot = IPRoute() self.ipdbRoot = IPDB() self.ipdbRoot.register_callback(self.__onIfaceStateChange) # create a symbolic link to be able to return to a default network namespace self.__createDefaultNetnsSymlink() # register a cleanup handler to remove configured PvDs and associated components at exit atexit.register(self.cleanup) self.operation_in_progress = False # debugging... self.pvdserver = None LOG.debug('cleanup handler initialized') LOG.debug('PvdManager initialization finished') def __onIfaceStateChange(self, ipdb, msg, action): pass ''' if (ipdb == self.ipdbRoot): netnsName = 'root' else: netnsName = 'netns' if (action == 'RTM_NEWLINK' or action == 'RTM_DELLINK'): for attr in msg['attrs']: if attr[0] == 'IFLA_IFNAME': ifaceName = attr[1] elif attr[0] == 'IFLA_OPERSTATE': ifaceState = attr[1] if (action == 'RTM_NEWLINK'): LOG.debug(netnsName + ': ' + ifaceName + ' ADDED, state: ' + ifaceState) elif (action == 'RTM_DELLINK'): LOG.debug(netnsName + ': ' + ifaceName + ' DELETED, state: ' + ifaceState) ''' def __getNetnsName(self): netnsName = None while (not netnsName or netnsName in netns.listnetns()): self.__netnsIdGenerator += 1; netnsName = self.__NETNS_PREFIX + str(self.__netnsIdGenerator) return netnsName def __getPvdIfaceParams(self): # use interface base name if available, add suffix otherwise pvdIfaceName = self.__PVD_IFACE_BASENAME pvdIfaceSuffix = 0 while (len(self.ipRoot.link_lookup(ifname=pvdIfaceName)) > 0): pvdIfaceSuffix += 1; pvdIfaceName = self.__PVD_IFACE_BASENAME + '-' + str(pvdIfaceSuffix) # find the largest index among the existing interfaces, use the next one pvdIfaceIndex = 1 existingIfaceIndices = [iface['index'] for iface in self.ipRoot.get_links()] if (len(existingIfaceIndices) > 0): existingIfaceIndices.sort() pvdIfaceIndex = existingIfaceIndices[-1] + 1 return (pvdIfaceName, pvdIfaceIndex) def __getDnsConfPath(self, netnsName): dnsConfFile = self.__DNS_CONF_FILE.replace(self.__NETNSDIRNAME_REPLACE_PATTERN, netnsName) dnsConfDir = dnsConfFile[0:dnsConfFile.rfind('/')] return (dnsConfDir, dnsConfFile) def __getDefaultNetnsSymlinkPath(self): netnsDir = netns.NETNS_RUN_DIR if (not netnsDir.endswith('/')): netnsDir += '/' return netnsDir + self.__NETNS_DEFAULT_NAME def __createDefaultNetnsSymlink(self): symlinkPath = self.__getDefaultNetnsSymlinkPath() if (not os.path.exists(os.path.dirname(symlinkPath))): os.makedirs(os.path.dirname(symlinkPath)) if (os.path.exists(symlinkPath) and os.path.islink(symlinkPath)): os.unlink(symlinkPath) os.symlink(self.__NETNS_DEFAULT_PROC, symlinkPath) LOG.debug('symlink {0}->{1} created'.format(symlinkPath, os.readlink(symlinkPath))) def __removeDefaultNetnsSymlink(self): symlinkPath = self.__getDefaultNetnsSymlinkPath() if (os.path.exists(symlinkPath) and os.path.islink(symlinkPath)): # need to read the link target for logging prior to delete a link symlinkTarget = os.readlink(symlinkPath) os.unlink(symlinkPath) LOG.debug('symlink {0}->{1} removed'.format(symlinkPath, symlinkTarget)) def __createNetns(self, phyIfaceIndex): netnsName = self.__getNetnsName() (pvdIfaceName, pvdIfaceIndex) = self.__getPvdIfaceParams() netns.create(netnsName) LOG.debug('network namespace {0} created'.format(netnsName)) # create a virtual interface where PvD parameters are going to be configured, then move the interface to the new network namespace self.ipRoot.link_create(ifname=pvdIfaceName, index=pvdIfaceIndex, kind=self.__PVD_IFACE_TYPE, link=phyIfaceIndex) LOG.debug('macvlan {0} created in default network namespace'.format(pvdIfaceName)) pvdIfaceIndex = self.ipRoot.link_lookup(ifname=pvdIfaceName) self.ipRoot.link('set', index=pvdIfaceIndex[0], net_ns_fd=netnsName) LOG.debug('macvlan {0} moved to network namespace {1}'.format(pvdIfaceName, netnsName)) # change the namespace and get new NETLINK handles to operate in new namespace netns.setns(netnsName) LOG.debug('network namespace switched to {0}'.format(netnsName)) ip = IPRoute() ipdb = IPDB() ipdb.register_callback(self.__onIfaceStateChange) # disable kernel to auto-configure the interface associated with the PvD, let the pvdman to solely control interface configuration acceptRaConfFile = self.__ACCEPT_RA_CONF_FILE.replace(self.__IFACENAME_REPLACE_PATTERN, pvdIfaceName) acceptRaConfFile = open(acceptRaConfFile, 'w') acceptRaConfFile.write('0') LOG.debug('processing of RAs by kernel disabled in {0}'.format(acceptRaConfFile.name)) # return to a default network namespace to not cause a colision with other modules # ip and ipdb handles continue to work in the target network namespace netns.setns(self.__NETNS_DEFAULT_NAME) LOG.debug('network namespace switched to default') # get new index since interface has been moved to a different namespace loIfaceIndex = ip.link_lookup(ifname=self.__LOOPBACK_IFACE_NAME) if (len(loIfaceIndex) > 0): loIfaceIndex = loIfaceIndex[0] pvdIfaceIndex = ip.link_lookup(ifname=pvdIfaceName) if (len(pvdIfaceIndex) > 0): pvdIfaceIndex = pvdIfaceIndex[0] # start interfaces ip.link_up(loIfaceIndex) ip.link_up(pvdIfaceIndex) # clear network configuration if exists ip.flush_addr(index=pvdIfaceIndex) ip.flush_routes(index=pvdIfaceIndex) ip.flush_rules(index=pvdIfaceIndex) LOG.debug('macvlan {0} in network namespace {1} initialized'.format(pvdIfaceName, netnsName)) return (netnsName, pvdIfaceName, ip) def __configureNetwork(self, ifaceName, pvdInfo, ip): if (pvdInfo): ifaceIndex = ip.link_lookup(ifname=ifaceName) if (len(ifaceIndex) > 0): ifaceIndex = ifaceIndex[0] # clear network configuration if exists ip.flush_addr(index=ifaceIndex) ip.flush_routes(index=ifaceIndex) ip.flush_rules(index=ifaceIndex) # set new network configuration if (pvdInfo.mtu): ip.link('set', index=ifaceIndex, mtu=pvdInfo.mtu.mtu) LOG.debug('MTU {0} on {1} configured'.format(pvdInfo.mtu.mtu, ifaceName)) # get interface MAC address to derive the IPv6 address from iface = ip.get_links(ifaceIndex)[0] mac = iface.get_attr('IFLA_ADDRESS') # add link-local IPv6 address ipAddress = str(netaddr.EUI(mac).ipv6(netaddr.IPAddress(self.__LINK_LOCAL_PREFIX))) ip.addr('add', index=ifaceIndex, address=ipAddress, prefixlen=self.__LINK_LOCAL_PREFIX_LENGTH, rtproto='RTPROT_RA', family=socket.AF_INET6) LOG.debug('link-local IP address {0}/{1} on {2} configured'.format(ipAddress, self.__LINK_LOCAL_PREFIX_LENGTH, ifaceName)) # add PvD-related IPv6 addresses if (pvdInfo.prefixes): for prefix in pvdInfo.prefixes: # TODO: PrefixInfo should contain IPAddress instead of str for prefix ipAddress = str(netaddr.EUI(mac).ipv6(netaddr.IPAddress(prefix.prefix))) ip.addr('add', index=ifaceIndex, address=ipAddress, prefixlen=prefix.prefixLength, rtproto='RTPROT_RA', family=socket.AF_INET6) LOG.debug('IP address {0}/{1} on {2} configured'.format(ipAddress, prefix.prefixLength, ifaceName)) # add PvD-related routes if (pvdInfo.routes): for route in pvdInfo.routes: # some routes may be added during interface prefix configuration, skip them if already there try: # TODO: RouteInfo should contain IPAddress instead of str for prefix # TODO: PvdInfo should contain IPAddress instead of str for routerAddress ip.route('add', dst=route.prefix, mask=route.prefixLength, gateway=pvdInfo.routerAddress, oif=ifaceIndex, rtproto='RTPROT_RA', family=socket.AF_INET6) LOG.debug('route to {0}/{1} via {2} on {3} configured'.format(route.prefix, route.prefixLength, pvdInfo.routerAddress, ifaceName)) except: LOG.warning('cannot configure route to {0}/{1} via {2} on {3}'.format(route.prefix, route.prefixLength, pvdInfo.routerAddress, ifaceName)) # add link-local route try: ip.route('add', dst=self.__LINK_LOCAL_PREFIX, mask=self.__LINK_LOCAL_PREFIX_LENGTH, oif=ifaceIndex, rtproto='RTPROT_RA', family=socket.AF_INET6) LOG.debug('link-local route to {0}/{1} on {2} configured'.format(self.__LINK_LOCAL_PREFIX, self.__LINK_LOCAL_PREFIX_LENGTH, ifaceName)) except: LOG.warning('cannot configure link-local route to {0}/{1} on {2}'.format(self.__LINK_LOCAL_PREFIX, self.__LINK_LOCAL_PREFIX_LENGTH, ifaceName)) # add default route try: ip.route('add', dst=self.__DEFAULT_ROUTE_ADDRESS, gateway=pvdInfo.routerAddress, oif=ifaceIndex, rtproto='RTPROT_RA', family=socket.AF_INET6) LOG.debug('default route via {0} on {1} configured'.format(pvdInfo.routerAddress, ifaceName)) except: LOG.warning('cannot configure default route via {0} on {1}'.format(pvdInfo.routerAddress, ifaceName)) def __configureDns(self, pvdInfo, netnsName): # configure DNS data in resolv.conf if (pvdInfo): # delete existing resolv.conf file for a given namespace (dnsConfDir, dnsConfFile) = self.__getDnsConfPath(netnsName) shutil.rmtree(dnsConfDir, True) if (pvdInfo.rdnsses or pvdInfo.dnssls): # create new resolv.conf file for a given namespace os.makedirs(dnsConfDir) dnsConfFile = open(dnsConfFile, 'w') dnsConfFile.write('# Autogenerated by pvdman\n') dnsConfFile.write('# PvD ID: ' + pvdInfo.pvdId + '\n\n') if (pvdInfo.rdnsses): for rdnss in pvdInfo.rdnsses: if (rdnss.addresses): dnsConfFile.write('\n'.join('{} {}'.format('nameserver', address) for address in rdnss.addresses) + '\n\n') LOG.debug('RDNSS in {0} configured'.format(dnsConfFile.name)) if (pvdInfo.dnssls): for dnssl in pvdInfo.dnssls: if (dnssl.domainNames): dnsConfFile.write('search ' + ' '.join('{}'.format(domainName) for domainName in dnssl.domainNames)) LOG.debug('DNSSL in {0} configured'.format(dnsConfFile.name)) def __createPvd(self, phyIfaceName, pvdInfo): phyIfaceIndex = self.ipRoot.link_lookup(ifname=phyIfaceName) if (len(phyIfaceIndex) > 0): phyIfaceIndex = phyIfaceIndex[0] if (self.pvds.get((phyIfaceName, pvdInfo.pvdId)) is None): # create a new network namespace to isolate the PvD configuration (netnsName, pvdIfaceName, ip) = self.__createNetns(phyIfaceIndex) # create a record to track the configured PvDs pvd = Pvd(pvdInfo.pvdId, pvdInfo, phyIfaceName, pvdIfaceName, netnsName) # configure the network namespace with the data received in RA message self.__configureNetwork(pvdIfaceName, pvdInfo, ip) self.__configureDns(pvdInfo, netnsName) # if PvD configuration completed successfully, add PvD record to the PvD manager's log self.pvds[(phyIfaceName, pvd.pvdId)] = pvd LOG.info('PvD {0} received through {1} CONFIGURED in network namespace {2} on macvlan {3}, type {4}'.format(pvd.pvdId, pvd.phyIfaceName, pvd.netnsName, pvd.pvdIfaceName, pvd.pvdInfo.pvdType)) self.pvdserver.stateChanged ("new_pvd", pvdInfo.pvdId) else: raise Exception('PvD duplicate error: PvD {0} is already configured on {1}'.format(pvdInfo.pvdId, phyIfaceName)) else: raise Exception('Interface {0} does not exist'.format(phyIfaceName)) def __updatePvd(self, phyIfaceName, pvdInfo): pvd = self.pvds.get((phyIfaceName, pvdInfo.pvdId)) if (pvd): if (pvd.pvdInfo == pvdInfo): # if PvD parameters did not change, just update the timestamp in the PvD manager's log pvd.updateTimestamp() LOG.info('PvD {0} received through {1} UNCHANGED, timestamp UPDATED, type {2}'.format(pvd.pvdId, pvd.phyIfaceName, pvd.pvdInfo.pvdType)) else: # if any of the PvD parameters has changed, reconfigure the PvD netns.setns(pvd.netnsName) ip = IPRoute() # return to a default network namespace to not cause a colision with other modules # ip handle continues to work in the target network namespace netns.setns(self.__NETNS_DEFAULT_NAME) self.__configureNetwork(pvd.pvdIfaceName, pvdInfo, ip) self.__configureDns(pvdInfo, pvd.netnsName) # update the PvD record in the PvD manager's log pvd.pvdInfo = pvdInfo pvd.updateTimestamp() LOG.info('PvD {0} received through {1} RECONFIGURED in network namespace {2} on macvlan {3}, type {4}'.format(pvd.pvdId, pvd.phyIfaceName, pvd.netnsName, pvd.pvdIfaceName, pvd.pvdInfo.pvdType)) self.pvdserver.stateChanged ("updated", pvdInfo.pvdId) else: raise Exception('There is no PvD {0} configured on {1}'.format(pvdInfo.pvdId, phyIfaceName)) def __removePvd(self, phyIfaceName, pvdId): pvd = self.pvds.get((phyIfaceName, pvdId)) if (pvd): # remove the network namespace associated with the PvD (this in turn removes the PvD network configuration as well) if (pvd.netnsName in netns.listnetns()): netns.remove(pvd.netnsName) # remove the directory containing PvD-related DNS information (dnsConfDir, dnsConfFile) = self.__getDnsConfPath(pvd.netnsName) if (os.path.exists(dnsConfDir)): shutil.rmtree(dnsConfDir, True) # remove the PvD record from the PvD manager's log del self.pvds[(phyIfaceName, pvdId)] LOG.info('PvD {0} received through {1} REMOVED, network namespace {2} deleted, DNS directory {3} deleted, type {4}'.format(pvd.pvdId, pvd.phyIfaceName, pvd.netnsName, dnsConfDir, pvd.pvdInfo.pvdType)) self.pvdserver.stateChanged ("deleted", pvdId) else: raise Exception('There is no PvD {0} configured on {1}'.format(pvdInfo.pvdId, phyIfaceName)) ''' PUBLIC METHODS ''' def setPvd(self, phyIfaceName, pvdInfo): ''' Configures the PvD parameters associated with a given physical network interface. This function is idempotent and can be safely invoked multiple times with the same or different parameters. If no PvD with a given ID is configured on a given interface, new PvD will be created. If PvD with a given ID is already configured on the interface, PvD parameters will be reconfigured if necessary. ''' self.operation_in_progress = True # debugging... if (self.pvds.get((phyIfaceName, pvdInfo.pvdId)) is None): self.__createPvd(phyIfaceName, pvdInfo) else: self.__updatePvd(phyIfaceName, pvdInfo) self.operation_in_progress = False # debugging... def removePvd(self, phyIfaceName, pvdId): self.operation_in_progress = True # debugging... self.__removePvd(phyIfaceName, pvdId) self.operation_in_progress = False # debugging... def listPvds(self): pvdData = [] for pvdKey, pvd in self.pvds.items(): pvdData.append((pvd.phyIfaceName, pvd.pvdId)) return pvdData def getPvds(self): pvdData = [] for pvdKey, pvd in self.pvds.items(): pvdData.append((pvd.pvdId, pvd.netnsName, pvd.phyIfaceName, pvd.pvdInfo.pvd_properties)) return pvdData def getPvdInfo(self, phyIfaceName, pvdId): return self.pvds.get((phyIfaceName, pvdId)) def TEST_createPvd ( self, phyIfaceName="tunnelX", pvdId="317a088c-ab67-43a3-bcf0-23c26f623a2d" ): netnsName = "VPNTEST" pvdIfaceName = netnsName pvdInfo = PvdInfo ( pvdId, PvdType.EXPLICIT, None, None, None, None, None, None, None, None, {"type":["voice", "cellular"], "bandwidth":"1 Mbps", "pricing":"0,01 $/MB", "id":pvdId } ) pvd = Pvd ( pvdInfo.pvdId, pvdInfo, phyIfaceName, pvdIfaceName, netnsName ) self.pvds[(phyIfaceName, pvd.pvdId)] = pvd LOG.info('PvD {0} received through {1} CONFIGURED in network namespace {2} on macvlan {3}, type {4}'.format(pvd.pvdId, pvd.phyIfaceName, pvd.netnsName, pvd.pvdIfaceName, pvd.pvdInfo.pvdType)) def cleanup(self): LOG.debug('PvdManager cleanup started') # create a deep copy of dictionary keys before deletion because Python cannot delete dictionary items while iterating over them pvdKeys = [key for key in self.pvds.keys()] for (phyIfaceName, pvdId) in pvdKeys: self.__removePvd(phyIfaceName, pvdId) # remove a symbolic link to a default network namespace self.__removeDefaultNetnsSymlink() LOG.debug('PvdManager cleanup finished')
class NetlinkClient(object): def __init__(self): self.neighbours = [] self.unresolvedneighbours = [] self.ip = IPDB(ignore_rtables=[254]) self.ip_uuid = self.ip.register_callback(self.callback) self.server = eventlet.listen(('127.0.0.1', 55652)) self.socket = None self.serve = True self.pool = eventlet.GreenPool() self.not_connect = True def callback(self, ipdb, msg, action): if action is 'RTM_NEWNEIGH': self.add_neighbour(msg) if action is 'RTM_DELNEIGH': self.remove_neighbour(msg) if action is 'RTM_NEWLINK': self.notify(['ifaceTable', self.ifaceTable(ipdb)]) if action is 'RTM_DELLINK': self.notify(['ifaceTable', self.ifaceTable(ipdb)]) if action is 'RTM_NEWADDR': log.info("RTM_NEWADDR happened at %s", str(datetime.now())) self.notify(['ifaceTable', self.ifaceTable(ipdb)]) if action is 'RTM_DELADDR': log.info("RTM_DELADDR happened at %s", str(datetime.now())) self.notify(['ifaceTable', self.ifaceTable(ipdb)]) def add_neighbour(self, msg): attributes = msg['attrs'] ip_addr = attributes[0][1] if attributes[1][0] is 'NDA_LLADDR': mac_addr = attributes[1][1] iface_index = msg['ifindex'] host = {'ipaddr': ip_addr, 'mac_addr': mac_addr, 'ifindex': iface_index} if host not in self.neighbours: self.notify(['add_neigh', host]) self.neighbours.append(host) if ip_addr in self.unresolvedneighbours: self.unresolvedneighbours = list(filter(lambda x: x != ip_addr, self.unresolvedneighbours) ) else: if ip_addr not in self.unresolvedneighbours: self.unresolvedneighbours.append(ip_addr) self.notify(['unresolved', self.unresolvedneighbours]) def remove_neighbour(self, msg): attributes = msg['attrs'] ip_addr = attributes[0][1] if attributes[1][0] is 'NDA_LLADDR': mac_addr = attributes[1][1] iface_index = msg['ifindex'] host = {'ipaddr': ip_addr, 'mac_addr': mac_addr, 'ifindex': iface_index} self.notify(['remove_neigh', host]) self.neighbours = list(filter( lambda x: x != host, self.neighbours)) def notify(self, rheamsg): notification = pickle.dumps(rheamsg) if self.socket is not None: self.socket.send(notification) recv = self.socket.recv(8192) def ifaceTable(self, ipdb): ifaces = ipdb.by_name.keys() table = [] for iface in ifaces: mac_addr = ipdb.interfaces[iface]['address'] ip_addresses = ipdb.interfaces[iface]['ipaddr'] ifindex = ipdb.interfaces[iface]['index'] state = ipdb.interfaces[iface]['operstate'] table.append({'ifname': iface, 'mac-address': mac_addr, 'IP-Addresses': [x for x in ip_addresses], 'ifindex': ifindex, 'state': state}) return table def neighbourtable(self): return self.neighbours def returnunresolvedhost(self): return self.unresolvedneighbours def process_requests(self, ipdb, request): if request[0] == 'ifaceTable': res = self.ifaceTable(ipdb) result = ['ifaceTable', res] return pickle.dumps(result) if request[0] == 'neighbourtable': res = self.neighbourtable() result = ['neighbourtable', res] return pickle.dumps(result) if request[0] == 'get_unresolved': res = self.returnunresolvedhost() result = ['unresolved', res] return pickle.dumps(result) def handle_request(self, sock): is_active = True while is_active: received = sock.recv(8192) if len(received) != 0: request = pickle.loads(received) response = self.process_requests(self.ip, request) sock.send(response) if len(received) == 0: is_active = False sock.close() sock.close() def try_connect(self): while self.not_connect: try: self.socket = eventlet.connect(('127.0.0.1', 55651)) except socket.error as e: pass else: self.not_connect = False def serve_forever(self): while self.serve: nl_sock, address = self.server.accept() self.pool.spawn_n(self.handle_request, nl_sock) log.info("Rhea has contacted us") self.try_connect()