diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 8a5c579ac..4fc6b8733 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -7,7 +7,7 @@ import shutil import threading from threading import RLock -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union import netaddr @@ -27,7 +27,7 @@ from core.configservice.base import ConfigService from core.services.coreservices import CoreService - CoreServices = List[CoreService] + CoreServices = List[Union[CoreService, Type[CoreService]]] ConfigServiceType = Type[ConfigService] _DEFAULT_MTU = 1500 diff --git a/daemon/core/services/bird.py b/daemon/core/services/bird.py index 16f0bb84b..a50529426 100644 --- a/daemon/core/services/bird.py +++ b/daemon/core/services/bird.py @@ -1,8 +1,11 @@ """ bird.py: defines routing services provided by the BIRD Internet Routing Daemon. """ +from typing import Optional, Tuple + import netaddr +from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -11,27 +14,27 @@ class Bird(CoreService): Bird router support """ - name = "bird" - executables = ("bird",) - group = "BIRD" - dirs = ("/etc/bird",) - configs = ("/etc/bird/bird.conf",) - startup = ("bird -c %s" % (configs[0]),) - shutdown = ("killall bird",) - validate = ("pidof bird",) + name: str = "bird" + group: str = "BIRD" + executables: Tuple[str, ...] = ("bird",) + dirs: Tuple[str, ...] = ("/etc/bird",) + configs: Tuple[str, ...] = ("/etc/bird/bird.conf",) + startup: Tuple[str, ...] = ("bird -c %s" % (configs[0]),) + shutdown: Tuple[str, ...] = ("killall bird",) + validate: Tuple[str, ...] = ("pidof bird",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the bird.conf file contents. """ if filename == cls.configs[0]: - return cls.generateBirdConf(node) + return cls.generate_bird_config(node) else: raise ValueError @staticmethod - def routerid(node): + def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ @@ -40,15 +43,13 @@ def routerid(node): a = a.split("/")[0] if netaddr.valid_ipv4(a): return a - # raise ValueError, "no IPv4 address found for router ID" return "0.0.0.0" @classmethod - def generateBirdConf(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: """ Returns configuration file text. Other services that depend on bird - will have generatebirdifcconfig() and generatebirdconfig() - hooks that are invoked here. + will have hooks that are invoked here. """ cfg = """\ /* Main configuration file for BIRD. This is ony a template, @@ -75,15 +76,16 @@ def generateBirdConf(cls, node): """ % ( cls.name, - cls.routerid(node), + cls.router_id(node), ) - # Generate protocol specific configurations + # generate protocol specific configurations for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, BirdService) or issubclass(s, BirdService)): + continue cfg += s.generate_bird_config(node) - return cfg @@ -93,32 +95,26 @@ class BirdService(CoreService): common to Bird's routing daemons. """ - name = None - executables = ("bird",) - group = "BIRD" - dependencies = ("bird",) - dirs = () - configs = () - startup = () - shutdown = () - meta = "The config file for this service can be found in the bird service." + name: Optional[str] = None + group: str = "BIRD" + executables: Tuple[str, ...] = ("bird",) + dependencies: Tuple[str, ...] = ("bird",) + meta: str = "The config file for this service can be found in the bird service." @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: return "" @classmethod - def generate_bird_iface_config(cls, node): + def generate_bird_iface_config(cls, node: CoreNode) -> str: """ Use only bare interfaces descriptions in generated protocol configurations. This has the slight advantage of being the same everywhere. """ cfg = "" - for iface in node.get_ifaces(control=False): cfg += ' interface "%s";\n' % iface.name - return cfg @@ -127,11 +123,11 @@ class BirdBgp(BirdService): BGP BIRD Service (configuration generation) """ - name = "BIRD_BGP" - custom_needed = True + name: str = "BIRD_BGP" + custom_needed: bool = True @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: return """ /* This is a sample config that should be customized with appropriate AS numbers * and peers; add one section like this for each neighbor */ @@ -158,10 +154,10 @@ class BirdOspf(BirdService): OSPF BIRD Service (configuration generation) """ - name = "BIRD_OSPFv2" + name: str = "BIRD_OSPFv2" @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: cfg = "protocol ospf {\n" cfg += " export filter {\n" cfg += " if source = RTS_BGP then {\n" @@ -174,7 +170,6 @@ def generate_bird_config(cls, node): cfg += cls.generate_bird_iface_config(node) cfg += " };\n" cfg += "}\n\n" - return cfg @@ -183,12 +178,11 @@ class BirdRadv(BirdService): RADV BIRD Service (configuration generation) """ - name = "BIRD_RADV" + name: str = "BIRD_RADV" @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: cfg = "/* This is a sample config that must be customized */\n" - cfg += "protocol radv {\n" cfg += " # auto configuration on all interfaces\n" cfg += cls.generate_bird_iface_config(node) @@ -202,7 +196,6 @@ def generate_bird_config(cls, node): cfg += "# ns 2001:0DB8:1234::12;\n" cfg += " };\n" cfg += "}\n\n" - return cfg @@ -211,10 +204,10 @@ class BirdRip(BirdService): RIP BIRD Service (configuration generation) """ - name = "BIRD_RIP" + name: str = "BIRD_RIP" @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: cfg = "protocol rip {\n" cfg += " period 10;\n" cfg += " garbage time 60;\n" @@ -224,7 +217,6 @@ def generate_bird_config(cls, node): cfg += " import all;\n" cfg += " export all;\n" cfg += "}\n\n" - return cfg @@ -233,11 +225,11 @@ class BirdStatic(BirdService): Static Bird Service (configuration generation) """ - name = "BIRD_static" - custom_needed = True + name: str = "BIRD_static" + custom_needed: bool = True @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: cfg = "/* This is a sample config that must be customized */\n" cfg += "protocol static {\n" cfg += "# route 0.0.0.0/0 via 198.51.100.130; # Default route. Do NOT advertise on BGP !\n" diff --git a/daemon/core/services/emaneservices.py b/daemon/core/services/emaneservices.py index da438bab0..ef188faba 100644 --- a/daemon/core/services/emaneservices.py +++ b/daemon/core/services/emaneservices.py @@ -1,23 +1,26 @@ +from typing import Tuple + from core.emane.nodes import EmaneNet from core.errors import CoreError +from core.nodes.base import CoreNode from core.services.coreservices import CoreService from core.xml import emanexml class EmaneTransportService(CoreService): - name = "transportd" - executables = ("emanetransportd", "emanegentransportxml") - group = "EMANE" - dependencies = () - dirs = () - configs = ("emanetransport.sh",) - startup = ("sh %s" % configs[0],) - validate = ("pidof %s" % executables[0],) - validation_timer = 0.5 - shutdown = ("killall %s" % executables[0],) + name: str = "transportd" + group: str = "EMANE" + executables: Tuple[str, ...] = ("emanetransportd", "emanegentransportxml") + dependencies: Tuple[str, ...] = () + dirs: Tuple[str, ...] = () + configs: Tuple[str, ...] = ("emanetransport.sh",) + startup: Tuple[str, ...] = ("sh %s" % configs[0],) + validate: Tuple[str, ...] = ("pidof %s" % executables[0],) + validation_timer: float = 0.5 + shutdown: Tuple[str, ...] = ("killall %s" % executables[0],) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: if filename == cls.configs[0]: transport_commands = [] for iface in node.get_ifaces(): diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 97a8b3341..e75d8f565 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -2,60 +2,63 @@ frr.py: defines routing services provided by FRRouting. Assumes installation of FRR via https://deb.frrouting.org/ """ +from typing import Optional, Tuple + import netaddr from core import constants from core.emane.nodes import EmaneNet +from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface from core.nodes.network import PtpNet, WlanNode from core.nodes.physical import Rj45Node from core.services.coreservices import CoreService class FRRZebra(CoreService): - name = "FRRzebra" - group = "FRR" - dirs = ("/usr/local/etc/frr", "/var/run/frr", "/var/log/frr") - configs = ( + name: str = "FRRzebra" + group: str = "FRR" + dirs: Tuple[str, ...] = ("/usr/local/etc/frr", "/var/run/frr", "/var/log/frr") + configs: Tuple[str, ...] = ( "/usr/local/etc/frr/frr.conf", "frrboot.sh", "/usr/local/etc/frr/vtysh.conf", "/usr/local/etc/frr/daemons", ) - startup = ("sh frrboot.sh zebra",) - shutdown = ("killall zebra",) - validate = ("pidof zebra",) + startup: Tuple[str, ...] = ("sh frrboot.sh zebra",) + shutdown: Tuple[str, ...] = ("killall zebra",) + validate: Tuple[str, ...] = ("pidof zebra",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the frr.conf or frrboot.sh file contents. """ if filename == cls.configs[0]: - return cls.generateFrrConf(node) + return cls.generate_frr_conf(node) elif filename == cls.configs[1]: - return cls.generateFrrBoot(node) + return cls.generate_frr_boot(node) elif filename == cls.configs[2]: - return cls.generateVtyshConf(node) + return cls.generate_vtysh_conf(node) elif filename == cls.configs[3]: - return cls.generateFrrDaemons(node) + return cls.generate_frr_daemons(node) else: raise ValueError( "file name (%s) is not a known configuration: %s", filename, cls.configs ) @classmethod - def generateVtyshConf(cls, node): + def generate_vtysh_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. """ return "service integrated-vtysh-config\n" @classmethod - def generateFrrConf(cls, node): + def generate_frr_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. Other services that depend on zebra - will have generatefrrifcconfig() and generatefrrconfig() - hooks that are invoked here. + will have hooks that are invoked here. """ # we could verify here that filename == frr.conf cfg = "" @@ -108,7 +111,7 @@ def generateFrrConf(cls, node): return cfg @staticmethod - def addrstr(x): + def addrstr(x: str) -> str: """ helper for mapping IP addresses to zebra config statements """ @@ -121,7 +124,7 @@ def addrstr(x): raise ValueError("invalid address: %s", x) @classmethod - def generateFrrBoot(cls, node): + def generate_frr_boot(cls, node: CoreNode) -> str: """ Generate a shell script used to boot the FRR daemons. """ @@ -244,7 +247,7 @@ def generateFrrBoot(cls, node): return cfg @classmethod - def generateFrrDaemons(cls, node): + def generate_frr_daemons(cls, node: CoreNode) -> str: """ Returns configuration file text. """ @@ -317,20 +320,15 @@ class FrrService(CoreService): common to FRR's routing daemons. """ - name = None - group = "FRR" - dependencies = ("FRRzebra",) - dirs = () - configs = () - startup = () - shutdown = () - meta = "The config file for this service can be found in the Zebra service." - - ipv4_routing = False - ipv6_routing = False + name: Optional[str] = None + group: str = "FRR" + dependencies: Tuple[str, ...] = ("FRRzebra",) + meta: str = "The config file for this service can be found in the Zebra service." + ipv4_routing: bool = False + ipv6_routing: bool = False @staticmethod - def routerid(node): + def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ @@ -339,11 +337,10 @@ def routerid(node): a = a.split("/")[0] if netaddr.valid_ipv4(a): return a - # raise ValueError, "no IPv4 address found for router ID" return "0.0.0.0" @staticmethod - def rj45check(iface): + def rj45check(iface: CoreInterface) -> bool: """ Helper to detect whether interface is connected an external RJ45 link. @@ -357,15 +354,15 @@ def rj45check(iface): return False @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" @classmethod - def generate_frr_iface_config(cls, node, iface): + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: return "" @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: return "" @@ -376,14 +373,13 @@ class FRROspfv2(FrrService): unified frr.conf file. """ - name = "FRROSPFv2" - startup = () - shutdown = ("killall ospfd",) - validate = ("pidof ospfd",) - ipv4_routing = True + name: str = "FRROSPFv2" + shutdown: Tuple[str, ...] = ("killall ospfd",) + validate: Tuple[str, ...] = ("pidof ospfd",) + ipv4_routing: bool = True @staticmethod - def mtucheck(iface): + def mtu_check(iface: CoreInterface) -> str: """ Helper to detect MTU mismatch and add the appropriate OSPF mtu-ignore command. This is needed when e.g. a node is linked via a @@ -401,7 +397,7 @@ def mtucheck(iface): return "" @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -411,9 +407,9 @@ def ptpcheck(iface): return "" @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router ospf\n" - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 for iface in node.get_ifaces(control=False): @@ -426,8 +422,8 @@ def generate_frr_config(cls, node): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): - return cls.mtucheck(iface) + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + return cls.mtu_check(iface) class FRROspfv3(FrrService): @@ -437,15 +433,14 @@ class FRROspfv3(FrrService): unified frr.conf file. """ - name = "FRROSPFv3" - startup = () - shutdown = ("killall ospf6d",) - validate = ("pidof ospf6d",) - ipv4_routing = True - ipv6_routing = True + name: str = "FRROSPFv3" + shutdown: Tuple[str, ...] = ("killall ospf6d",) + validate: Tuple[str, ...] = ("pidof ospf6d",) + ipv4_routing: bool = True + ipv6_routing: bool = True @staticmethod - def minmtu(iface): + def min_mtu(iface: CoreInterface) -> int: """ Helper to discover the minimum MTU of interfaces linked with the given interface. @@ -459,20 +454,20 @@ def minmtu(iface): return mtu @classmethod - def mtucheck(cls, iface): + def mtu_check(cls, iface: CoreInterface) -> str: """ Helper to detect MTU mismatch and add the appropriate OSPFv3 ifmtu command. This is needed when e.g. a node is linked via a GreTap device. """ - minmtu = cls.minmtu(iface) + minmtu = cls.min_mtu(iface) if minmtu < iface.mtu: return " ipv6 ospf6 ifmtu %d\n" % minmtu else: return "" @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -482,9 +477,9 @@ def ptpcheck(iface): return "" @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router ospf6\n" - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " router-id %s\n" % rtrid for iface in node.get_ifaces(control=False): cfg += " interface %s area 0.0.0.0\n" % iface.name @@ -492,14 +487,13 @@ def generate_frr_config(cls, node): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): - return cls.mtucheck(iface) + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + return cls.mtu_check(iface) # cfg = cls.mtucheck(ifc) # external RJ45 connections will use default OSPF timers # if cls.rj45check(ifc): # return cfg # cfg += cls.ptpcheck(ifc) - # return cfg + """\ @@ -516,21 +510,20 @@ class FRRBgp(FrrService): having the same AS number. """ - name = "FRRBGP" - startup = () - shutdown = ("killall bgpd",) - validate = ("pidof bgpd",) - custom_needed = True - ipv4_routing = True - ipv6_routing = True + name: str = "FRRBGP" + shutdown: Tuple[str, ...] = ("killall bgpd",) + validate: Tuple[str, ...] = ("pidof bgpd",) + custom_needed: bool = True + ipv4_routing: bool = True + ipv6_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "!\n! BGP configuration\n!\n" cfg += "! You should configure the AS number below,\n" cfg += "! along with this router's peers.\n!\n" cfg += "router bgp %s\n" % node.id - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " bgp router-id %s\n" % rtrid cfg += " redistribute connected\n" cfg += "! neighbor 1.2.3.4 remote-as 555\n!\n" @@ -542,14 +535,13 @@ class FRRRip(FrrService): The RIP service provides IPv4 routing for wired networks. """ - name = "FRRRIP" - startup = () - shutdown = ("killall ripd",) - validate = ("pidof ripd",) - ipv4_routing = True + name: str = "FRRRIP" + shutdown: Tuple[str, ...] = ("killall ripd",) + validate: Tuple[str, ...] = ("pidof ripd",) + ipv4_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = """\ router rip redistribute static @@ -566,14 +558,13 @@ class FRRRipng(FrrService): The RIP NG service provides IPv6 routing for wired networks. """ - name = "FRRRIPNG" - startup = () - shutdown = ("killall ripngd",) - validate = ("pidof ripngd",) - ipv6_routing = True + name: str = "FRRRIPNG" + shutdown: Tuple[str, ...] = ("killall ripngd",) + validate: Tuple[str, ...] = ("pidof ripngd",) + ipv6_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = """\ router ripng redistribute static @@ -591,14 +582,13 @@ class FRRBabel(FrrService): protocol for IPv6 and IPv4 with fast convergence properties. """ - name = "FRRBabel" - startup = () - shutdown = ("killall babeld",) - validate = ("pidof babeld",) - ipv6_routing = True + name: str = "FRRBabel" + shutdown: Tuple[str, ...] = ("killall babeld",) + validate: Tuple[str, ...] = ("pidof babeld",) + ipv6_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router babel\n" for iface in node.get_ifaces(control=False): cfg += " network %s\n" % iface.name @@ -606,7 +596,7 @@ def generate_frr_config(cls, node): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: if iface.net and isinstance(iface.net, (EmaneNet, WlanNode)): return " babel wireless\n no babel split-horizon\n" else: @@ -618,14 +608,13 @@ class FRRpimd(FrrService): PIM multicast routing based on XORP. """ - name = "FRRpimd" - startup = () - shutdown = ("killall pimd",) - validate = ("pidof pimd",) - ipv4_routing = True + name: str = "FRRpimd" + shutdown: Tuple[str, ...] = ("killall pimd",) + validate: Tuple[str, ...] = ("pidof pimd",) + ipv4_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: ifname = "eth0" for iface in node.get_ifaces(): if iface.name != "lo": @@ -641,7 +630,7 @@ def generate_frr_config(cls, node): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: return " ip mfea\n ip igmp\n ip pim\n" @@ -652,15 +641,14 @@ class FRRIsis(FrrService): unified frr.conf file. """ - name = "FRRISIS" - startup = () - shutdown = ("killall isisd",) - validate = ("pidof isisd",) - ipv4_routing = True - ipv6_routing = True + name: str = "FRRISIS" + shutdown: Tuple[str, ...] = ("killall isisd",) + validate: Tuple[str, ...] = ("pidof isisd",) + ipv4_routing: bool = True + ipv6_routing: bool = True @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -670,7 +658,7 @@ def ptpcheck(iface): return "" @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router isis DEFAULT\n" cfg += " net 47.0001.0000.1900.%04x.00\n" % node.id cfg += " metric-style wide\n" @@ -679,9 +667,9 @@ def generate_frr_config(cls, node): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: cfg = " ip router isis DEFAULT\n" cfg += " ipv6 router isis DEFAULT\n" cfg += " isis circuit-type level-2-only\n" - cfg += cls.ptpcheck(iface) + cfg += cls.ptp_check(iface) return cfg diff --git a/daemon/core/services/nrl.py b/daemon/core/services/nrl.py index 38b90d488..9933b1300 100644 --- a/daemon/core/services/nrl.py +++ b/daemon/core/services/nrl.py @@ -2,9 +2,12 @@ nrl.py: defines services provided by NRL protolib tools hosted here: http://www.nrl.navy.mil/itd/ncs/products """ +from typing import Optional, Tuple + import netaddr from core import utils +from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -14,19 +17,15 @@ class NrlService(CoreService): common to NRL's routing daemons. """ - name = None - group = "ProtoSvc" - dirs = () - configs = () - startup = () - shutdown = () + name: Optional[str] = None + group: str = "ProtoSvc" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" @staticmethod - def firstipv4prefix(node, prefixlen=24): + def firstipv4prefix(node: CoreNode, prefixlen: int = 24) -> str: """ Similar to QuaggaService.routerid(). Helper to return the first IPv4 prefix of a node, using the supplied prefix length. This ignores the @@ -37,20 +36,19 @@ def firstipv4prefix(node, prefixlen=24): a = a.split("/")[0] if netaddr.valid_ipv4(a): return f"{a}/{prefixlen}" - # raise ValueError, "no IPv4 address found" return "0.0.0.0/%s" % prefixlen class MgenSinkService(NrlService): - name = "MGEN_Sink" - executables = ("mgen",) - configs = ("sink.mgen",) - startup = ("mgen input sink.mgen",) - validate = ("pidof mgen",) - shutdown = ("killall mgen",) + name: str = "MGEN_Sink" + executables: Tuple[str, ...] = ("mgen",) + configs: Tuple[str, ...] = ("sink.mgen",) + startup: Tuple[str, ...] = ("mgen input sink.mgen",) + validate: Tuple[str, ...] = ("pidof mgen",) + shutdown: Tuple[str, ...] = ("killall mgen",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: cfg = "0.0 LISTEN UDP 5000\n" for iface in node.get_ifaces(): name = utils.sysctl_devname(iface.name) @@ -58,7 +56,7 @@ def generate_config(cls, node, filename): return cfg @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: cmd = cls.startup[0] cmd += " output /tmp/mgen_%s.log" % node.name return (cmd,) @@ -69,32 +67,29 @@ class NrlNhdp(NrlService): NeighborHood Discovery Protocol for MANET networks. """ - name = "NHDP" - executables = ("nrlnhdp",) - startup = ("nrlnhdp",) - shutdown = ("killall nrlnhdp",) - validate = ("pidof nrlnhdp",) + name: str = "NHDP" + executables: Tuple[str, ...] = ("nrlnhdp",) + startup: Tuple[str, ...] = ("nrlnhdp",) + shutdown: Tuple[str, ...] = ("killall nrlnhdp",) + validate: Tuple[str, ...] = ("pidof nrlnhdp",) @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ cmd = cls.startup[0] cmd += " -l /var/log/nrlnhdp.log" cmd += " -rpipe %s_nhdp" % node.name - servicenames = map(lambda x: x.name, node.services) if "SMF" in servicenames: cmd += " -flooding ecds" cmd += " -smfClient %s_smf" % node.name - ifaces = node.get_ifaces(control=False) if len(ifaces) > 0: iface_names = map(lambda x: x.name, ifaces) cmd += " -i " cmd += " -i ".join(iface_names) - return (cmd,) @@ -103,15 +98,15 @@ class NrlSmf(NrlService): Simplified Multicast Forwarding for MANET networks. """ - name = "SMF" - executables = ("nrlsmf",) - startup = ("sh startsmf.sh",) - shutdown = ("killall nrlsmf",) - validate = ("pidof nrlsmf",) - configs = ("startsmf.sh",) + name: str = "SMF" + executables: Tuple[str, ...] = ("nrlsmf",) + startup: Tuple[str, ...] = ("sh startsmf.sh",) + shutdown: Tuple[str, ...] = ("killall nrlsmf",) + validate: Tuple[str, ...] = ("pidof nrlsmf",) + configs: Tuple[str, ...] = ("startsmf.sh",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a startup script for SMF. Because nrlsmf does not daemonize, it can cause problems in some situations when launched @@ -146,7 +141,6 @@ def generate_config(cls, node, filename): cmd += " hash MD5" cmd += " log /var/log/nrlsmf.log" - cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" return cfg @@ -156,14 +150,14 @@ class NrlOlsr(NrlService): Optimized Link State Routing protocol for MANET networks. """ - name = "OLSR" - executables = ("nrlolsrd",) - startup = ("nrlolsrd",) - shutdown = ("killall nrlolsrd",) - validate = ("pidof nrlolsrd",) + name: str = "OLSR" + executables: Tuple[str, ...] = ("nrlolsrd",) + startup: Tuple[str, ...] = ("nrlolsrd",) + shutdown: Tuple[str, ...] = ("killall nrlolsrd",) + validate: Tuple[str, ...] = ("pidof nrlolsrd",) @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ @@ -175,14 +169,12 @@ def get_startup(cls, node): cmd += " -i %s" % iface.name cmd += " -l /var/log/nrlolsrd.log" cmd += " -rpipe %s_olsr" % node.name - servicenames = map(lambda x: x.name, node.services) if "SMF" in servicenames and "NHDP" not in servicenames: cmd += " -flooding s-mpr" cmd += " -smfClient %s_smf" % node.name if "zebra" in servicenames: cmd += " -z" - return (cmd,) @@ -191,34 +183,30 @@ class NrlOlsrv2(NrlService): Optimized Link State Routing protocol version 2 for MANET networks. """ - name = "OLSRv2" - executables = ("nrlolsrv2",) - startup = ("nrlolsrv2",) - shutdown = ("killall nrlolsrv2",) - validate = ("pidof nrlolsrv2",) + name: str = "OLSRv2" + executables: Tuple[str, ...] = ("nrlolsrv2",) + startup: Tuple[str, ...] = ("nrlolsrv2",) + shutdown: Tuple[str, ...] = ("killall nrlolsrv2",) + validate: Tuple[str, ...] = ("pidof nrlolsrv2",) @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ cmd = cls.startup[0] cmd += " -l /var/log/nrlolsrv2.log" cmd += " -rpipe %s_olsrv2" % node.name - servicenames = map(lambda x: x.name, node.services) if "SMF" in servicenames: cmd += " -flooding ecds" cmd += " -smfClient %s_smf" % node.name - cmd += " -p olsr" - ifaces = node.get_ifaces(control=False) if len(ifaces) > 0: iface_names = map(lambda x: x.name, ifaces) cmd += " -i " cmd += " -i ".join(iface_names) - return (cmd,) @@ -227,16 +215,16 @@ class OlsrOrg(NrlService): Optimized Link State Routing protocol from olsr.org for MANET networks. """ - name = "OLSRORG" - executables = ("olsrd",) - configs = ("/etc/olsrd/olsrd.conf",) - dirs = ("/etc/olsrd",) - startup = ("olsrd",) - shutdown = ("killall olsrd",) - validate = ("pidof olsrd",) + name: str = "OLSRORG" + executables: Tuple[str, ...] = ("olsrd",) + configs: Tuple[str, ...] = ("/etc/olsrd/olsrd.conf",) + dirs: Tuple[str, ...] = ("/etc/olsrd",) + startup: Tuple[str, ...] = ("olsrd",) + shutdown: Tuple[str, ...] = ("killall olsrd",) + validate: Tuple[str, ...] = ("pidof olsrd",) @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ @@ -246,13 +234,13 @@ def get_startup(cls, node): iface_names = map(lambda x: x.name, ifaces) cmd += " -i " cmd += " -i ".join(iface_names) - return (cmd,) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ - Generate a default olsrd config file to use the broadcast address of 255.255.255.255. + Generate a default olsrd config file to use the broadcast address of + 255.255.255.255. """ cfg = """\ # @@ -577,24 +565,16 @@ class MgenActor(NrlService): """ # a unique name is required, without spaces - name = "MgenActor" - executables = ("mgen",) - # you can create your own group here - group = "ProtoSvc" - # per-node directories - dirs = () - # generated files (without a full path this file goes in the node's dir, - # e.g. /tmp/pycore.12345/n1.conf/) - configs = ("start_mgen_actor.sh",) - # list of startup commands, also may be generated during startup - startup = ("sh start_mgen_actor.sh",) - # list of validation commands - validate = ("pidof mgen",) - # list of shutdown commands - shutdown = ("killall mgen",) + name: str = "MgenActor" + group: str = "ProtoSvc" + executables: Tuple[str, ...] = ("mgen",) + configs: Tuple[str, ...] = ("start_mgen_actor.sh",) + startup: Tuple[str, ...] = ("sh start_mgen_actor.sh",) + validate: Tuple[str, ...] = ("pidof mgen",) + shutdown: Tuple[str, ...] = ("killall mgen",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a startup script for MgenActor. Because mgenActor does not daemonize, it can cause problems in some situations when launched @@ -604,11 +584,9 @@ def generate_config(cls, node, filename): cfg += "# auto-generated by nrl.py:MgenActor.generateconfig()\n" comments = "" cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % node.name - ifaces = node.get_ifaces(control=False) if len(ifaces) == 0: return "" - cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" return cfg @@ -618,15 +596,15 @@ class Arouted(NrlService): Adaptive Routing """ - name = "arouted" - executables = ("arouted",) - configs = ("startarouted.sh",) - startup = ("sh startarouted.sh",) - shutdown = ("pkill arouted",) - validate = ("pidof arouted",) + name: str = "arouted" + executables: Tuple[str, ...] = ("arouted",) + configs: Tuple[str, ...] = ("startarouted.sh",) + startup: Tuple[str, ...] = ("sh startarouted.sh",) + shutdown: Tuple[str, ...] = ("pkill arouted",) + validate: Tuple[str, ...] = ("pidof arouted",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the Quagga.conf or quaggaboot.sh file contents. """ diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index 41cfa3d88..30d14353e 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -1,65 +1,68 @@ """ quagga.py: defines routing services provided by Quagga. """ +from typing import Optional, Tuple + import netaddr from core import constants from core.emane.nodes import EmaneNet from core.emulator.enumerations import LinkTypes +from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface from core.nodes.network import PtpNet, WlanNode from core.nodes.physical import Rj45Node from core.services.coreservices import CoreService class Zebra(CoreService): - name = "zebra" - group = "Quagga" - dirs = ("/usr/local/etc/quagga", "/var/run/quagga") - configs = ( + name: str = "zebra" + group: str = "Quagga" + dirs: Tuple[str, ...] = ("/usr/local/etc/quagga", "/var/run/quagga") + configs: Tuple[str, ...] = ( "/usr/local/etc/quagga/Quagga.conf", "quaggaboot.sh", "/usr/local/etc/quagga/vtysh.conf", ) - startup = ("sh quaggaboot.sh zebra",) - shutdown = ("killall zebra",) - validate = ("pidof zebra",) + startup: Tuple[str, ...] = ("sh quaggaboot.sh zebra",) + shutdown: Tuple[str, ...] = ("killall zebra",) + validate: Tuple[str, ...] = ("pidof zebra",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the Quagga.conf or quaggaboot.sh file contents. """ if filename == cls.configs[0]: - return cls.generateQuaggaConf(node) + return cls.generate_quagga_conf(node) elif filename == cls.configs[1]: - return cls.generateQuaggaBoot(node) + return cls.generate_quagga_boot(node) elif filename == cls.configs[2]: - return cls.generateVtyshConf(node) + return cls.generate_vtysh_conf(node) else: raise ValueError( "file name (%s) is not a known configuration: %s", filename, cls.configs ) @classmethod - def generateVtyshConf(cls, node): + def generate_vtysh_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. """ return "service integrated-vtysh-config\n" @classmethod - def generateQuaggaConf(cls, node): + def generate_quagga_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. Other services that depend on zebra - will have generatequaggaifcconfig() and generatequaggaconfig() - hooks that are invoked here. + will have hooks that are invoked here. """ # we could verify here that filename == Quagga.conf cfg = "" for iface in node.get_ifaces(): cfg += "interface %s\n" % iface.name # include control interfaces in addressing but not routing daemons - if hasattr(iface, "control") and iface.control is True: + if getattr(iface, "control", False): cfg += " " cfg += "\n ".join(map(cls.addrstr, iface.addrlist)) cfg += "\n" @@ -71,6 +74,8 @@ def generateQuaggaConf(cls, node): for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, QuaggaService) or issubclass(s, QuaggaService)): + continue iface_config = s.generate_quagga_iface_config(node, iface) if s.ipv4_routing: want_ipv4 = True @@ -101,11 +106,13 @@ def generateQuaggaConf(cls, node): for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, QuaggaService) or issubclass(s, QuaggaService)): + continue cfg += s.generate_quagga_config(node) return cfg @staticmethod - def addrstr(x): + def addrstr(x: str) -> str: """ helper for mapping IP addresses to zebra config statements """ @@ -118,7 +125,7 @@ def addrstr(x): raise ValueError("invalid address: %s", x) @classmethod - def generateQuaggaBoot(cls, node): + def generate_quagga_boot(cls, node: CoreNode) -> str: """ Generate a shell script used to boot the Quagga daemons. """ @@ -235,20 +242,15 @@ class QuaggaService(CoreService): common to Quagga's routing daemons. """ - name = None - group = "Quagga" - dependencies = ("zebra",) - dirs = () - configs = () - startup = () - shutdown = () - meta = "The config file for this service can be found in the Zebra service." - - ipv4_routing = False - ipv6_routing = False + name: Optional[str] = None + group: str = "Quagga" + dependencies: Tuple[str, ...] = (Zebra.name,) + meta: str = "The config file for this service can be found in the Zebra service." + ipv4_routing: bool = False + ipv6_routing: bool = False @staticmethod - def routerid(node): + def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ @@ -257,11 +259,10 @@ def routerid(node): a = a.split("/")[0] if netaddr.valid_ipv4(a): return a - # raise ValueError, "no IPv4 address found for router ID" - return "0.0.0.%d" % node.id + return f"0.0.0.{node.id:d}" @staticmethod - def rj45check(iface): + def rj45check(iface: CoreInterface) -> bool: """ Helper to detect whether interface is connected an external RJ45 link. @@ -275,15 +276,15 @@ def rj45check(iface): return False @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" @classmethod - def generate_quagga_iface_config(cls, node, iface): + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: return "" @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: return "" @@ -294,14 +295,13 @@ class Ospfv2(QuaggaService): unified Quagga.conf file. """ - name = "OSPFv2" - startup = () - shutdown = ("killall ospfd",) - validate = ("pidof ospfd",) - ipv4_routing = True + name: str = "OSPFv2" + shutdown: Tuple[str, ...] = ("killall ospfd",) + validate: Tuple[str, ...] = ("pidof ospfd",) + ipv4_routing: bool = True @staticmethod - def mtucheck(iface): + def mtu_check(iface: CoreInterface) -> str: """ Helper to detect MTU mismatch and add the appropriate OSPF mtu-ignore command. This is needed when e.g. a node is linked via a @@ -319,7 +319,7 @@ def mtucheck(iface): return "" @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -329,9 +329,9 @@ def ptpcheck(iface): return "" @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = "router ospf\n" - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 for iface in node.get_ifaces(control=False): @@ -343,12 +343,12 @@ def generate_quagga_config(cls, node): return cfg @classmethod - def generate_quagga_iface_config(cls, node, iface): - cfg = cls.mtucheck(iface) + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + cfg = cls.mtu_check(iface) # external RJ45 connections will use default OSPF timers if cls.rj45check(iface): return cfg - cfg += cls.ptpcheck(iface) + cfg += cls.ptp_check(iface) return ( cfg + """\ @@ -366,15 +366,14 @@ class Ospfv3(QuaggaService): unified Quagga.conf file. """ - name = "OSPFv3" - startup = () - shutdown = ("killall ospf6d",) - validate = ("pidof ospf6d",) - ipv4_routing = True - ipv6_routing = True + name: str = "OSPFv3" + shutdown: Tuple[str, ...] = ("killall ospf6d",) + validate: Tuple[str, ...] = ("pidof ospf6d",) + ipv4_routing: bool = True + ipv6_routing: bool = True @staticmethod - def minmtu(iface): + def min_mtu(iface: CoreInterface) -> int: """ Helper to discover the minimum MTU of interfaces linked with the given interface. @@ -388,20 +387,20 @@ def minmtu(iface): return mtu @classmethod - def mtucheck(cls, iface): + def mtu_check(cls, iface: CoreInterface) -> str: """ Helper to detect MTU mismatch and add the appropriate OSPFv3 ifmtu command. This is needed when e.g. a node is linked via a GreTap device. """ - minmtu = cls.minmtu(iface) + minmtu = cls.min_mtu(iface) if minmtu < iface.mtu: return " ipv6 ospf6 ifmtu %d\n" % minmtu else: return "" @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -411,9 +410,9 @@ def ptpcheck(iface): return "" @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = "router ospf6\n" - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " instance-id 65\n" cfg += " router-id %s\n" % rtrid for iface in node.get_ifaces(control=False): @@ -422,8 +421,8 @@ def generate_quagga_config(cls, node): return cfg @classmethod - def generate_quagga_iface_config(cls, node, iface): - return cls.mtucheck(iface) + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + return cls.mtu_check(iface) class Ospfv3mdr(Ospfv3): @@ -434,12 +433,12 @@ class Ospfv3mdr(Ospfv3): unified Quagga.conf file. """ - name = "OSPFv3MDR" - ipv4_routing = True + name: str = "OSPFv3MDR" + ipv4_routing: bool = True @classmethod - def generate_quagga_iface_config(cls, node, iface): - cfg = cls.mtucheck(iface) + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + cfg = cls.mtu_check(iface) if iface.net is not None and isinstance(iface.net, (WlanNode, EmaneNet)): return ( cfg @@ -464,21 +463,20 @@ class Bgp(QuaggaService): having the same AS number. """ - name = "BGP" - startup = () - shutdown = ("killall bgpd",) - validate = ("pidof bgpd",) - custom_needed = True - ipv4_routing = True - ipv6_routing = True + name: str = "BGP" + shutdown: Tuple[str, ...] = ("killall bgpd",) + validate: Tuple[str, ...] = ("pidof bgpd",) + custom_needed: bool = True + ipv4_routing: bool = True + ipv6_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = "!\n! BGP configuration\n!\n" cfg += "! You should configure the AS number below,\n" cfg += "! along with this router's peers.\n!\n" cfg += "router bgp %s\n" % node.id - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " bgp router-id %s\n" % rtrid cfg += " redistribute connected\n" cfg += "! neighbor 1.2.3.4 remote-as 555\n!\n" @@ -490,14 +488,13 @@ class Rip(QuaggaService): The RIP service provides IPv4 routing for wired networks. """ - name = "RIP" - startup = () - shutdown = ("killall ripd",) - validate = ("pidof ripd",) - ipv4_routing = True + name: str = "RIP" + shutdown: Tuple[str, ...] = ("killall ripd",) + validate: Tuple[str, ...] = ("pidof ripd",) + ipv4_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = """\ router rip redistribute static @@ -514,14 +511,13 @@ class Ripng(QuaggaService): The RIP NG service provides IPv6 routing for wired networks. """ - name = "RIPNG" - startup = () - shutdown = ("killall ripngd",) - validate = ("pidof ripngd",) - ipv6_routing = True + name: str = "RIPNG" + shutdown: Tuple[str, ...] = ("killall ripngd",) + validate: Tuple[str, ...] = ("pidof ripngd",) + ipv6_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = """\ router ripng redistribute static @@ -539,14 +535,13 @@ class Babel(QuaggaService): protocol for IPv6 and IPv4 with fast convergence properties. """ - name = "Babel" - startup = () - shutdown = ("killall babeld",) - validate = ("pidof babeld",) - ipv6_routing = True + name: str = "Babel" + shutdown: Tuple[str, ...] = ("killall babeld",) + validate: Tuple[str, ...] = ("pidof babeld",) + ipv6_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = "router babel\n" for iface in node.get_ifaces(control=False): cfg += " network %s\n" % iface.name @@ -554,7 +549,7 @@ def generate_quagga_config(cls, node): return cfg @classmethod - def generate_quagga_iface_config(cls, node, iface): + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: if iface.net and iface.net.linktype == LinkTypes.WIRELESS: return " babel wireless\n no babel split-horizon\n" else: @@ -566,14 +561,13 @@ class Xpimd(QuaggaService): PIM multicast routing based on XORP. """ - name = "Xpimd" - startup = () - shutdown = ("killall xpimd",) - validate = ("pidof xpimd",) - ipv4_routing = True + name: str = "Xpimd" + shutdown: Tuple[str, ...] = ("killall xpimd",) + validate: Tuple[str, ...] = ("pidof xpimd",) + ipv4_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: ifname = "eth0" for iface in node.get_ifaces(): if iface.name != "lo": @@ -589,5 +583,5 @@ def generate_quagga_config(cls, node): return cfg @classmethod - def generate_quagga_iface_config(cls, node, iface): + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: return " ip mfea\n ip igmp\n ip pim\n" diff --git a/daemon/core/services/sdn.py b/daemon/core/services/sdn.py index 71ab815ff..1f17201dd 100644 --- a/daemon/core/services/sdn.py +++ b/daemon/core/services/sdn.py @@ -3,9 +3,11 @@ """ import re +from typing import Tuple import netaddr +from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -14,24 +16,28 @@ class SdnService(CoreService): Parent class for SDN services. """ - group = "SDN" + group: str = "SDN" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" class OvsService(SdnService): - name = "OvsService" - executables = ("ovs-ofctl", "ovs-vsctl") - group = "SDN" - dirs = ("/etc/openvswitch", "/var/run/openvswitch", "/var/log/openvswitch") - configs = ("OvsService.sh",) - startup = ("sh OvsService.sh",) - shutdown = ("killall ovs-vswitchd", "killall ovsdb-server") + name: str = "OvsService" + group: str = "SDN" + executables: Tuple[str, ...] = ("ovs-ofctl", "ovs-vsctl") + dirs: Tuple[str, ...] = ( + "/etc/openvswitch", + "/var/run/openvswitch", + "/var/log/openvswitch", + ) + configs: Tuple[str, ...] = ("OvsService.sh",) + startup: Tuple[str, ...] = ("sh OvsService.sh",) + shutdown: Tuple[str, ...] = ("killall ovs-vswitchd", "killall ovsdb-server") @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: # Check whether the node is running zebra has_zebra = 0 for s in node.services: @@ -46,8 +52,8 @@ def generate_config(cls, node, filename): cfg += "## this stops it from routing traffic without defined flows.\n" cfg += "## remove the -- and everything after if you want it to act as a regular switch\n" cfg += "ovs-vsctl add-br ovsbr0 -- set Bridge ovsbr0 fail-mode=secure\n" - cfg += "\n## Now add all our interfaces as ports to the switch\n" + portnum = 1 for iface in node.get_ifaces(control=False): ifnumstr = re.findall(r"\d+", iface.name) @@ -111,21 +117,19 @@ def generate_config(cls, node, filename): % (portnum + 1, portnum) ) portnum += 2 - return cfg class RyuService(SdnService): - name = "ryuService" - executables = ("ryu-manager",) - group = "SDN" - dirs = () - configs = ("ryuService.sh",) - startup = ("sh ryuService.sh",) - shutdown = ("killall ryu-manager",) + name: str = "ryuService" + group: str = "SDN" + executables: Tuple[str, ...] = ("ryu-manager",) + configs: Tuple[str, ...] = ("ryuService.sh",) + startup: Tuple[str, ...] = ("sh ryuService.sh",) + shutdown: Tuple[str, ...] = ("killall ryu-manager",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return a string that will be written to filename, or sent to the GUI for user customization. diff --git a/daemon/core/services/security.py b/daemon/core/services/security.py index 91c942f1c..b813579e7 100644 --- a/daemon/core/services/security.py +++ b/daemon/core/services/security.py @@ -4,78 +4,79 @@ """ import logging +from typing import Tuple from core import constants +from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface from core.services.coreservices import CoreService class VPNClient(CoreService): - name = "VPNClient" - group = "Security" - configs = ("vpnclient.sh",) - startup = ("sh vpnclient.sh",) - shutdown = ("killall openvpn",) - validate = ("pidof openvpn",) - custom_needed = True + name: str = "VPNClient" + group: str = "Security" + configs: Tuple[str, ...] = ("vpnclient.sh",) + startup: Tuple[str, ...] = ("sh vpnclient.sh",) + shutdown: Tuple[str, ...] = ("killall openvpn",) + validate: Tuple[str, ...] = ("pidof openvpn",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the client.conf and vpnclient.sh file contents to """ cfg = "#!/bin/sh\n" cfg += "# custom VPN Client configuration for service (security.py)\n" - fname = "%s/examples/services/sampleVPNClient" % constants.CORE_DATA_DIR - + fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleVPNClient" try: - cfg += open(fname, "rb").read() + with open(fname, "r") as f: + cfg += f.read() except IOError: logging.exception( - "Error opening VPN client configuration template (%s)", fname + "error opening VPN client configuration template (%s)", fname ) - return cfg class VPNServer(CoreService): - name = "VPNServer" - group = "Security" - configs = ("vpnserver.sh",) - startup = ("sh vpnserver.sh",) - shutdown = ("killall openvpn",) - validate = ("pidof openvpn",) - custom_needed = True + name: str = "VPNServer" + group: str = "Security" + configs: Tuple[str, ...] = ("vpnserver.sh",) + startup: Tuple[str, ...] = ("sh vpnserver.sh",) + shutdown: Tuple[str, ...] = ("killall openvpn",) + validate: Tuple[str, ...] = ("pidof openvpn",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the sample server.conf and vpnserver.sh file contents to GUI for user customization. """ cfg = "#!/bin/sh\n" cfg += "# custom VPN Server Configuration for service (security.py)\n" - fname = "%s/examples/services/sampleVPNServer" % constants.CORE_DATA_DIR - + fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleVPNServer" try: - cfg += open(fname, "rb").read() + with open(fname, "r") as f: + cfg += f.read() except IOError: logging.exception( "Error opening VPN server configuration template (%s)", fname ) - return cfg class IPsec(CoreService): - name = "IPsec" - group = "Security" - configs = ("ipsec.sh",) - startup = ("sh ipsec.sh",) - shutdown = ("killall racoon",) - custom_needed = True + name: str = "IPsec" + group: str = "Security" + configs: Tuple[str, ...] = ("ipsec.sh",) + startup: Tuple[str, ...] = ("sh ipsec.sh",) + shutdown: Tuple[str, ...] = ("killall racoon",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the ipsec.conf and racoon.conf file contents to GUI for user customization. @@ -83,7 +84,7 @@ def generate_config(cls, node, filename): cfg = "#!/bin/sh\n" cfg += "# set up static tunnel mode security assocation for service " cfg += "(security.py)\n" - fname = "%s/examples/services/sampleIPsec" % constants.CORE_DATA_DIR + fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleIPsec" try: with open(fname, "r") as f: cfg += f.read() @@ -93,28 +94,27 @@ def generate_config(cls, node, filename): class Firewall(CoreService): - name = "Firewall" - group = "Security" - configs = ("firewall.sh",) - startup = ("sh firewall.sh",) - custom_needed = True + name: str = "Firewall" + group: str = "Security" + configs: Tuple[str, ...] = ("firewall.sh",) + startup: Tuple[str, ...] = ("sh firewall.sh",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the firewall rule examples to GUI for user customization. """ cfg = "#!/bin/sh\n" cfg += "# custom node firewall rules for service (security.py)\n" - fname = "%s/examples/services/sampleFirewall" % constants.CORE_DATA_DIR - + fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleFirewall" try: - cfg += open(fname, "rb").read() + with open(fname, "r") as f: + cfg += f.read() except IOError: logging.exception( "Error opening Firewall configuration template (%s)", fname ) - return cfg @@ -123,30 +123,28 @@ class Nat(CoreService): IPv4 source NAT service. """ - name = "NAT" - executables = ("iptables",) - group = "Security" - configs = ("nat.sh",) - startup = ("sh nat.sh",) - custom_needed = False + name: str = "NAT" + group: str = "Security" + executables: Tuple[str, ...] = ("iptables",) + configs: Tuple[str, ...] = ("nat.sh",) + startup: Tuple[str, ...] = ("sh nat.sh",) + custom_needed: bool = False @classmethod - def generate_iface_nat_rule(cls, iface, line_prefix=""): + def generate_iface_nat_rule(cls, iface: CoreInterface, prefix: str = "") -> str: """ Generate a NAT line for one interface. """ - cfg = line_prefix + "iptables -t nat -A POSTROUTING -o " + cfg = prefix + "iptables -t nat -A POSTROUTING -o " cfg += iface.name + " -j MASQUERADE\n" - - cfg += line_prefix + "iptables -A FORWARD -i " + iface.name + cfg += prefix + "iptables -A FORWARD -i " + iface.name cfg += " -m state --state RELATED,ESTABLISHED -j ACCEPT\n" - - cfg += line_prefix + "iptables -A FORWARD -i " + cfg += prefix + "iptables -A FORWARD -i " cfg += iface.name + " -j DROP\n" return cfg @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ NAT out the first interface """ @@ -156,7 +154,7 @@ def generate_config(cls, node, filename): have_nat = False for iface in node.get_ifaces(control=False): if have_nat: - cfg += cls.generate_iface_nat_rule(iface, line_prefix="#") + cfg += cls.generate_iface_nat_rule(iface, prefix="#") else: have_nat = True cfg += "# NAT out the " + iface.name + " interface\n" diff --git a/daemon/core/services/ucarp.py b/daemon/core/services/ucarp.py index 1eb801797..8ac92dd34 100644 --- a/daemon/core/services/ucarp.py +++ b/daemon/core/services/ucarp.py @@ -1,52 +1,52 @@ """ ucarp.py: defines high-availability IP address controlled by ucarp """ +from typing import Tuple +from core.nodes.base import CoreNode from core.services.coreservices import CoreService UCARP_ETC = "/usr/local/etc/ucarp" class Ucarp(CoreService): - name = "ucarp" - group = "Utility" - dirs = (UCARP_ETC,) - configs = ( + name: str = "ucarp" + group: str = "Utility" + dirs: Tuple[str, ...] = (UCARP_ETC,) + configs: Tuple[str, ...] = ( UCARP_ETC + "/default.sh", UCARP_ETC + "/default-up.sh", UCARP_ETC + "/default-down.sh", "ucarpboot.sh", ) - startup = ("sh ucarpboot.sh",) - shutdown = ("killall ucarp",) - validate = ("pidof ucarp",) + startup: Tuple[str, ...] = ("sh ucarpboot.sh",) + shutdown: Tuple[str, ...] = ("killall ucarp",) + validate: Tuple[str, ...] = ("pidof ucarp",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the default file contents """ if filename == cls.configs[0]: - return cls.generateUcarpConf(node) + return cls.generate_ucarp_conf(node) elif filename == cls.configs[1]: - return cls.generateVipUp(node) + return cls.generate_vip_up(node) elif filename == cls.configs[2]: - return cls.generateVipDown(node) + return cls.generate_vip_down(node) elif filename == cls.configs[3]: - return cls.generateUcarpBoot(node) + return cls.generate_ucarp_boot(node) else: raise ValueError @classmethod - def generateUcarpConf(cls, node): + def generate_ucarp_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. """ - try: - ucarp_bin = node.session.cfg["ucarp_bin"] - except KeyError: - ucarp_bin = "/usr/sbin/ucarp" - + ucarp_bin = node.session.options.get_config( + "ucarp_bin", default="/usr/sbin/ucarp" + ) return """\ #!/bin/sh # Location of UCARP executable @@ -110,7 +110,7 @@ def generateUcarpConf(cls, node): ) @classmethod - def generateUcarpBoot(cls, node): + def generate_ucarp_boot(cls, node: CoreNode) -> str: """ Generate a shell script used to boot the Ucarp daemons. """ @@ -130,7 +130,7 @@ def generateUcarpBoot(cls, node): ) @classmethod - def generateVipUp(cls, node): + def generate_vip_up(cls, node: CoreNode) -> str: """ Generate a shell script used to start the virtual ip """ @@ -152,7 +152,7 @@ def generateVipUp(cls, node): """ @classmethod - def generateVipDown(cls, node): + def generate_vip_down(cls, node: CoreNode) -> str: """ Generate a shell script used to stop the virtual ip """ diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 273318e15..a44037f6c 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -1,12 +1,13 @@ """ utility.py: defines miscellaneous utility services. """ -import os +from typing import Optional, Tuple import netaddr from core import constants, utils from core.errors import CoreCommandError +from core.nodes.base import CoreNode from core.services.coreservices import CoreService, ServiceMode @@ -15,32 +16,25 @@ class UtilService(CoreService): Parent class for utility services. """ - name = None - group = "Utility" - dirs = () - configs = () - startup = () - shutdown = () + name: Optional[str] = None + group: str = "Utility" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" class IPForwardService(UtilService): - name = "IPForward" - configs = ("ipforward.sh",) - startup = ("sh ipforward.sh",) + name: str = "IPForward" + configs: Tuple[str, ...] = ("ipforward.sh",) + startup: Tuple[str, ...] = ("sh ipforward.sh",) @classmethod - def generate_config(cls, node, filename): - if os.uname()[0] == "Linux": - return cls.generateconfiglinux(node, filename) - else: - raise Exception("unknown platform") + def generate_config(cls, node: CoreNode, filename: str) -> str: + return cls.generateconfiglinux(node, filename) @classmethod - def generateconfiglinux(cls, node, filename): + def generateconfiglinux(cls, node: CoreNode, filename: str) -> str: cfg = """\ #!/bin/sh # auto-generated by IPForward service (utility.py) @@ -70,12 +64,12 @@ def generateconfiglinux(cls, node, filename): class DefaultRouteService(UtilService): - name = "DefaultRoute" - configs = ("defaultroute.sh",) - startup = ("sh defaultroute.sh",) + name: str = "DefaultRoute" + configs: Tuple[str, ...] = ("defaultroute.sh",) + startup: Tuple[str, ...] = ("sh defaultroute.sh",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: routes = [] ifaces = node.get_ifaces() if ifaces: @@ -93,22 +87,18 @@ def generate_config(cls, node, filename): class DefaultMulticastRouteService(UtilService): - name = "DefaultMulticastRoute" - configs = ("defaultmroute.sh",) - startup = ("sh defaultmroute.sh",) + name: str = "DefaultMulticastRoute" + configs: Tuple[str, ...] = ("defaultmroute.sh",) + startup: Tuple[str, ...] = ("sh defaultmroute.sh",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: cfg = "#!/bin/sh\n" cfg += "# auto-generated by DefaultMulticastRoute service (utility.py)\n" cfg += "# the first interface is chosen below; please change it " cfg += "as needed\n" - for iface in node.get_ifaces(control=False): - if os.uname()[0] == "Linux": - rtcmd = "ip route add 224.0.0.0/4 dev" - else: - raise Exception("unknown platform") + rtcmd = "ip route add 224.0.0.0/4 dev" cfg += "%s %s\n" % (rtcmd, iface.name) cfg += "\n" break @@ -116,13 +106,13 @@ def generate_config(cls, node, filename): class StaticRouteService(UtilService): - name = "StaticRoute" - configs = ("staticroute.sh",) - startup = ("sh staticroute.sh",) - custom_needed = True + name: str = "StaticRoute" + configs: Tuple[str, ...] = ("staticroute.sh",) + startup: Tuple[str, ...] = ("sh staticroute.sh",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: cfg = "#!/bin/sh\n" cfg += "# auto-generated by StaticRoute service (utility.py)\n#\n" cfg += "# NOTE: this service must be customized to be of any use\n" @@ -133,7 +123,7 @@ def generate_config(cls, node, filename): return cfg @staticmethod - def routestr(x): + def routestr(x: str) -> str: addr = x.split("/")[0] if netaddr.valid_ipv6(addr): dst = "3ffe:4::/64" @@ -143,24 +133,20 @@ def routestr(x): if net[-2] == net[1]: return "" else: - if os.uname()[0] == "Linux": - rtcmd = "#/sbin/ip route add %s via" % dst - else: - raise Exception("unknown platform") + rtcmd = "#/sbin/ip route add %s via" % dst return "%s %s" % (rtcmd, net[1]) class SshService(UtilService): - name = "SSH" - configs = ("startsshd.sh", "/etc/ssh/sshd_config") - dirs = ("/etc/ssh", "/var/run/sshd") - startup = ("sh startsshd.sh",) - shutdown = ("killall sshd",) - validate = () - validation_mode = ServiceMode.BLOCKING + name: str = "SSH" + configs: Tuple[str, ...] = ("startsshd.sh", "/etc/ssh/sshd_config") + dirs: Tuple[str, ...] = ("/etc/ssh", "/var/run/sshd") + startup: Tuple[str, ...] = ("sh startsshd.sh",) + shutdown: Tuple[str, ...] = ("killall sshd",) + validation_mode: ServiceMode = ServiceMode.BLOCKING @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Use a startup script for launching sshd in order to wait for host key generation. @@ -228,15 +214,15 @@ def generate_config(cls, node, filename): class DhcpService(UtilService): - name = "DHCP" - configs = ("/etc/dhcp/dhcpd.conf",) - dirs = ("/etc/dhcp", "/var/lib/dhcp") - startup = ("touch /var/lib/dhcp/dhcpd.leases", "dhcpd") - shutdown = ("killall dhcpd",) - validate = ("pidof dhcpd",) + name: str = "DHCP" + configs: Tuple[str, ...] = ("/etc/dhcp/dhcpd.conf",) + dirs: Tuple[str, ...] = ("/etc/dhcp", "/var/lib/dhcp") + startup: Tuple[str, ...] = ("touch /var/lib/dhcp/dhcpd.leases", "dhcpd") + shutdown: Tuple[str, ...] = ("killall dhcpd",) + validate: Tuple[str, ...] = ("pidof dhcpd",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a dhcpd config file using the network address of each interface. @@ -261,7 +247,7 @@ def generate_config(cls, node, filename): return cfg @staticmethod - def subnetentry(x): + def subnetentry(x: str) -> str: """ Generate a subnet declaration block given an IPv4 prefix string for inclusion in the dhcpd3 config file. @@ -297,14 +283,14 @@ class DhcpClientService(UtilService): Use a DHCP client for all interfaces for addressing. """ - name = "DHCPClient" - configs = ("startdhcpclient.sh",) - startup = ("sh startdhcpclient.sh",) - shutdown = ("killall dhclient",) - validate = ("pidof dhclient",) + name: str = "DHCPClient" + configs: Tuple[str, ...] = ("startdhcpclient.sh",) + startup: Tuple[str, ...] = ("sh startdhcpclient.sh",) + shutdown: Tuple[str, ...] = ("killall dhclient",) + validate: Tuple[str, ...] = ("pidof dhclient",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a script to invoke dhclient on all interfaces. """ @@ -313,7 +299,6 @@ def generate_config(cls, node, filename): cfg += "# uncomment this mkdir line and symlink line to enable client-" cfg += "side DNS\n# resolution based on the DHCP server response.\n" cfg += "#mkdir -p /var/run/resolvconf/interface\n" - for iface in node.get_ifaces(control=False): cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % iface.name cfg += " /var/run/resolvconf/resolv.conf\n" @@ -327,15 +312,15 @@ class FtpService(UtilService): Start a vsftpd server. """ - name = "FTP" - configs = ("vsftpd.conf",) - dirs = ("/var/run/vsftpd/empty", "/var/ftp") - startup = ("vsftpd ./vsftpd.conf",) - shutdown = ("killall vsftpd",) - validate = ("pidof vsftpd",) + name: str = "FTP" + configs: Tuple[str, ...] = ("vsftpd.conf",) + dirs: Tuple[str, ...] = ("/var/run/vsftpd/empty", "/var/ftp") + startup: Tuple[str, ...] = ("vsftpd ./vsftpd.conf",) + shutdown: Tuple[str, ...] = ("killall vsftpd",) + validate: Tuple[str, ...] = ("pidof vsftpd",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a vsftpd.conf configuration file. """ @@ -360,13 +345,13 @@ class HttpService(UtilService): Start an apache server. """ - name = "HTTP" - configs = ( + name: str = "HTTP" + configs: Tuple[str, ...] = ( "/etc/apache2/apache2.conf", "/etc/apache2/envvars", "/var/www/index.html", ) - dirs = ( + dirs: Tuple[str, ...] = ( "/etc/apache2", "/var/run/apache2", "/var/log/apache2", @@ -374,14 +359,14 @@ class HttpService(UtilService): "/var/lock/apache2", "/var/www", ) - startup = ("chown www-data /var/lock/apache2", "apache2ctl start") - shutdown = ("apache2ctl stop",) - validate = ("pidof apache2",) - - APACHEVER22, APACHEVER24 = (22, 24) + startup: Tuple[str, ...] = ("chown www-data /var/lock/apache2", "apache2ctl start") + shutdown: Tuple[str, ...] = ("apache2ctl stop",) + validate: Tuple[str, ...] = ("pidof apache2",) + APACHEVER22: int = 22 + APACHEVER24: int = 24 @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate an apache2.conf configuration file. """ @@ -395,7 +380,7 @@ def generate_config(cls, node, filename): return "" @classmethod - def detectversionfromcmd(cls): + def detectversionfromcmd(cls) -> int: """ Detect the apache2 version using the 'a2query' command. """ @@ -405,14 +390,12 @@ def detectversionfromcmd(cls): except CoreCommandError as e: status = e.returncode result = e.stderr - if status == 0 and result[:3] == "2.4": return cls.APACHEVER24 - return cls.APACHEVER22 @classmethod - def generateapache2conf(cls, node, filename): + def generateapache2conf(cls, node: CoreNode, filename: str) -> str: lockstr = { cls.APACHEVER22: "LockFile ${APACHE_LOCK_DIR}/accept.lock\n", cls.APACHEVER24: "Mutex file:${APACHE_LOCK_DIR} default\n", @@ -421,22 +404,18 @@ def generateapache2conf(cls, node, filename): cls.APACHEVER22: "", cls.APACHEVER24: "LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n", } - permstr = { cls.APACHEVER22: " Order allow,deny\n Deny from all\n Satisfy all\n", cls.APACHEVER24: " Require all denied\n", } - authstr = { cls.APACHEVER22: "LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so\n", cls.APACHEVER24: "LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so\n", } - permstr2 = { cls.APACHEVER22: "\t\tOrder allow,deny\n\t\tallow from all\n", cls.APACHEVER24: "\t\tRequire all granted\n", } - version = cls.detectversionfromcmd() cfg = "# apache2.conf generated by utility.py:HttpService\n" cfg += lockstr[version] @@ -552,7 +531,7 @@ def generateapache2conf(cls, node, filename): return cfg @classmethod - def generateenvvars(cls, node, filename): + def generateenvvars(cls, node: CoreNode, filename: str) -> str: return """\ # this file is used by apache2ctl - generated by utility.py:HttpService # these settings come from a default Ubuntu apache2 installation @@ -567,7 +546,7 @@ def generateenvvars(cls, node, filename): """ @classmethod - def generatehtml(cls, node, filename): + def generatehtml(cls, node: CoreNode, filename: str) -> str: body = ( """\ @@ -587,16 +566,15 @@ class PcapService(UtilService): Pcap service for logging packets. """ - name = "pcap" - configs = ("pcap.sh",) - dirs = () - startup = ("sh pcap.sh start",) - shutdown = ("sh pcap.sh stop",) - validate = ("pidof tcpdump",) - meta = "logs network traffic to pcap packet capture files" + name: str = "pcap" + configs: Tuple[str, ...] = ("pcap.sh",) + startup: Tuple[str, ...] = ("sh pcap.sh start",) + shutdown: Tuple[str, ...] = ("sh pcap.sh stop",) + validate: Tuple[str, ...] = ("pidof tcpdump",) + meta: str = "logs network traffic to pcap packet capture files" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a startpcap.sh traffic logging script. """ @@ -630,15 +608,17 @@ def generate_config(cls, node, filename): class RadvdService(UtilService): - name = "radvd" - configs = ("/etc/radvd/radvd.conf",) - dirs = ("/etc/radvd",) - startup = ("radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",) - shutdown = ("pkill radvd",) - validate = ("pidof radvd",) + name: str = "radvd" + configs: Tuple[str, ...] = ("/etc/radvd/radvd.conf",) + dirs: Tuple[str, ...] = ("/etc/radvd",) + startup: Tuple[str, ...] = ( + "radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log", + ) + shutdown: Tuple[str, ...] = ("pkill radvd",) + validate: Tuple[str, ...] = ("pidof radvd",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a RADVD router advertisement daemon config file using the network address of each interface. @@ -678,7 +658,7 @@ def generate_config(cls, node, filename): return cfg @staticmethod - def subnetentry(x): + def subnetentry(x: str) -> str: """ Generate a subnet declaration block given an IPv6 prefix string for inclusion in the RADVD config file. @@ -695,14 +675,14 @@ class AtdService(UtilService): Atd service for scheduling at jobs """ - name = "atd" - configs = ("startatd.sh",) - dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool") - startup = ("sh startatd.sh",) - shutdown = ("pkill atd",) + name: str = "atd" + configs: Tuple[str, ...] = ("startatd.sh",) + dirs: Tuple[str, ...] = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool") + startup: Tuple[str, ...] = ("sh startatd.sh",) + shutdown: Tuple[str, ...] = ("pkill atd",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return """ #!/bin/sh echo 00001 > /var/spool/cron/atjobs/.SEQ @@ -717,5 +697,5 @@ class UserDefinedService(UtilService): Dummy service allowing customization of anything. """ - name = "UserDefined" - meta = "Customize this service to do anything upon startup." + name: str = "UserDefined" + meta: str = "Customize this service to do anything upon startup." diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 776b1d166..420823773 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -2,10 +2,12 @@ xorp.py: defines routing services provided by the XORP routing suite. """ -import logging +from typing import Optional, Tuple import netaddr +from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface from core.services.coreservices import CoreService @@ -15,20 +17,20 @@ class XorpRtrmgr(CoreService): enabled XORP services, and launches necessary daemons upon startup. """ - name = "xorp_rtrmgr" - executables = ("xorp_rtrmgr",) - group = "XORP" - dirs = ("/etc/xorp",) - configs = ("/etc/xorp/config.boot",) - startup = ( + name: str = "xorp_rtrmgr" + group: str = "XORP" + executables: Tuple[str, ...] = ("xorp_rtrmgr",) + dirs: Tuple[str, ...] = ("/etc/xorp",) + configs: Tuple[str, ...] = ("/etc/xorp/config.boot",) + startup: Tuple[str, ...] = ( "xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid" % (configs[0], name, name), ) - shutdown = ("killall xorp_rtrmgr",) - validate = ("pidof xorp_rtrmgr",) + shutdown: Tuple[str, ...] = ("killall xorp_rtrmgr",) + validate: Tuple[str, ...] = ("pidof xorp_rtrmgr",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Returns config.boot configuration file text. Other services that depend on this will have generatexorpconfig() hooks that are @@ -45,16 +47,15 @@ def generate_config(cls, node, filename): cfg += "}\n\n" for s in node.services: - try: - s.dependencies.index(cls.name) - cfg += s.generatexorpconfig(node) - except ValueError: - logging.exception("error getting value from service: %s", cls.name) - + if cls.name not in s.dependencies: + continue + if not (isinstance(s, XorpService) or issubclass(s, XorpService)): + continue + cfg += s.generate_xorp_config(node) return cfg @staticmethod - def addrstr(x): + def addrstr(x: str) -> str: """ helper for mapping IP addresses to XORP config statements """ @@ -65,7 +66,7 @@ def addrstr(x): return cfg @staticmethod - def lladdrstr(iface): + def lladdrstr(iface: CoreInterface) -> str: """ helper for adding link-local address entries (required by OSPFv3) """ @@ -81,18 +82,16 @@ class XorpService(CoreService): common to XORP's routing daemons. """ - name = None - executables = ("xorp_rtrmgr",) - group = "XORP" - dependencies = ("xorp_rtrmgr",) - dirs = () - configs = () - startup = () - shutdown = () - meta = "The config file for this service can be found in the xorp_rtrmgr service." + name: Optional[str] = None + group: str = "XORP" + executables: Tuple[str, ...] = ("xorp_rtrmgr",) + dependencies: Tuple[str, ...] = ("xorp_rtrmgr",) + meta: str = ( + "The config file for this service can be found in the xorp_rtrmgr service." + ) @staticmethod - def fea(forwarding): + def fea(forwarding: str) -> str: """ Helper to add a forwarding engine entry to the config file. """ @@ -104,17 +103,14 @@ def fea(forwarding): return cfg @staticmethod - def mfea(forwarding, ifaces): + def mfea(forwarding, node: CoreNode) -> str: """ Helper to add a multicast forwarding engine entry to the config file. """ names = [] - for iface in ifaces: - if hasattr(iface, "control") and iface.control is True: - continue + for iface in node.get_ifaces(control=False): names.append(iface.name) names.append("register_vif") - cfg = "plumbing {\n" cfg += " %s {\n" % forwarding for name in names: @@ -128,7 +124,7 @@ def mfea(forwarding, ifaces): return cfg @staticmethod - def policyexportconnected(): + def policyexportconnected() -> str: """ Helper to add a policy statement for exporting connected routes. """ @@ -144,7 +140,7 @@ def policyexportconnected(): return cfg @staticmethod - def routerid(node): + def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ @@ -153,15 +149,14 @@ def routerid(node): a = a.split("/")[0] if netaddr.valid_ipv4(a): return a - # raise ValueError, "no IPv4 address found for router ID" return "0.0.0.0" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: return "" @@ -172,12 +167,12 @@ class XorpOspfv2(XorpService): unified XORP configuration file. """ - name = "XORP_OSPFv2" + name: str = "XORP_OSPFv2" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding4") - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += "\nprotocols {\n" cfg += " ospf4 {\n" cfg += "\trouter-id: %s\n" % rtrid @@ -206,12 +201,12 @@ class XorpOspfv3(XorpService): unified XORP configuration file. """ - name = "XORP_OSPFv3" + name: str = "XORP_OSPFv3" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding6") - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += "\nprotocols {\n" cfg += " ospf6 0 { /* Instance ID 0 */\n" cfg += "\trouter-id: %s\n" % rtrid @@ -232,16 +227,16 @@ class XorpBgp(XorpService): IPv4 inter-domain routing. AS numbers and peers must be customized. """ - name = "XORP_BGP" - custom_needed = True + name: str = "XORP_BGP" + custom_needed: bool = True @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = "/* This is a sample config that should be customized with\n" cfg += " appropriate AS numbers and peers */\n" cfg += cls.fea("unicast-forwarding4") cfg += cls.policyexportconnected() - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += "\nprotocols {\n" cfg += " bgp {\n" cfg += "\tbgp-id: %s\n" % rtrid @@ -262,10 +257,10 @@ class XorpRip(XorpService): RIP IPv4 unicast routing. """ - name = "XORP_RIP" + name: str = "XORP_RIP" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding4") cfg += cls.policyexportconnected() cfg += "\nprotocols {\n" @@ -293,10 +288,10 @@ class XorpRipng(XorpService): RIP NG IPv6 unicast routing. """ - name = "XORP_RIPNG" + name: str = "XORP_RIPNG" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding6") cfg += cls.policyexportconnected() cfg += "\nprotocols {\n" @@ -320,12 +315,11 @@ class XorpPimSm4(XorpService): PIM Sparse Mode IPv4 multicast routing. """ - name = "XORP_PIMSM4" + name: str = "XORP_PIMSM4" @classmethod - def generatexorpconfig(cls, node): - cfg = cls.mfea("mfea4", node.get_ifaces()) - + def generate_xorp_config(cls, node: CoreNode) -> str: + cfg = cls.mfea("mfea4", node) cfg += "\nprotocols {\n" cfg += " igmp {\n" names = [] @@ -338,7 +332,6 @@ def generatexorpconfig(cls, node): cfg += "\t}\n" cfg += " }\n" cfg += "}\n" - cfg += "\nprotocols {\n" cfg += " pimsm4 {\n" @@ -361,10 +354,8 @@ def generatexorpconfig(cls, node): cfg += "\t\t}\n" cfg += "\t }\n" cfg += "\t}\n" - cfg += " }\n" cfg += "}\n" - cfg += "\nprotocols {\n" cfg += " fib2mrib {\n" cfg += "\tdisable: false\n" @@ -378,12 +369,11 @@ class XorpPimSm6(XorpService): PIM Sparse Mode IPv6 multicast routing. """ - name = "XORP_PIMSM6" + name: str = "XORP_PIMSM6" @classmethod - def generatexorpconfig(cls, node): - cfg = cls.mfea("mfea6", node.get_ifaces()) - + def generate_xorp_config(cls, node: CoreNode) -> str: + cfg = cls.mfea("mfea6", node) cfg += "\nprotocols {\n" cfg += " mld {\n" names = [] @@ -396,7 +386,6 @@ def generatexorpconfig(cls, node): cfg += "\t}\n" cfg += " }\n" cfg += "}\n" - cfg += "\nprotocols {\n" cfg += " pimsm6 {\n" @@ -419,10 +408,8 @@ def generatexorpconfig(cls, node): cfg += "\t\t}\n" cfg += "\t }\n" cfg += "\t}\n" - cfg += " }\n" cfg += "}\n" - cfg += "\nprotocols {\n" cfg += " fib2mrib {\n" cfg += "\tdisable: false\n" @@ -436,12 +423,12 @@ class XorpOlsr(XorpService): OLSR IPv4 unicast MANET routing. """ - name = "XORP_OLSR" + name: str = "XORP_OLSR" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding4") - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += "\nprotocols {\n" cfg += " olsr4 {\n" cfg += "\tmain-address: %s\n" % rtrid