daemon: added type hinting throughout all services and made small tweaks/fixes that were ran across

This commit is contained in:
Blake Harnden 2020-06-18 12:54:36 -07:00
parent 250bc6e1f5
commit cd74a44558
11 changed files with 560 additions and 636 deletions

View file

@ -7,7 +7,7 @@ import os
import shutil import shutil
import threading import threading
from threading import RLock 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 import netaddr
@ -27,7 +27,7 @@ if TYPE_CHECKING:
from core.configservice.base import ConfigService from core.configservice.base import ConfigService
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
CoreServices = List[CoreService] CoreServices = List[Union[CoreService, Type[CoreService]]]
ConfigServiceType = Type[ConfigService] ConfigServiceType = Type[ConfigService]
_DEFAULT_MTU = 1500 _DEFAULT_MTU = 1500

View file

@ -1,8 +1,11 @@
""" """
bird.py: defines routing services provided by the BIRD Internet Routing Daemon. bird.py: defines routing services provided by the BIRD Internet Routing Daemon.
""" """
from typing import Optional, Tuple
import netaddr import netaddr
from core.nodes.base import CoreNode
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
@ -11,27 +14,27 @@ class Bird(CoreService):
Bird router support Bird router support
""" """
name = "bird" name: str = "bird"
executables = ("bird",) group: str = "BIRD"
group = "BIRD" executables: Tuple[str, ...] = ("bird",)
dirs = ("/etc/bird",) dirs: Tuple[str, ...] = ("/etc/bird",)
configs = ("/etc/bird/bird.conf",) configs: Tuple[str, ...] = ("/etc/bird/bird.conf",)
startup = ("bird -c %s" % (configs[0]),) startup: Tuple[str, ...] = ("bird -c %s" % (configs[0]),)
shutdown = ("killall bird",) shutdown: Tuple[str, ...] = ("killall bird",)
validate = ("pidof bird",) validate: Tuple[str, ...] = ("pidof bird",)
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
""" """
Return the bird.conf file contents. Return the bird.conf file contents.
""" """
if filename == cls.configs[0]: if filename == cls.configs[0]:
return cls.generateBirdConf(node) return cls.generate_bird_config(node)
else: else:
raise ValueError raise ValueError
@staticmethod @staticmethod
def routerid(node): def router_id(node: CoreNode) -> str:
""" """
Helper to return the first IPv4 address of a node as its router ID. Helper to return the first IPv4 address of a node as its router ID.
""" """
@ -40,15 +43,13 @@ class Bird(CoreService):
a = a.split("/")[0] a = a.split("/")[0]
if netaddr.valid_ipv4(a): if netaddr.valid_ipv4(a):
return a return a
# raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0" return "0.0.0.0"
@classmethod @classmethod
def generateBirdConf(cls, node): def generate_bird_config(cls, node: CoreNode) -> str:
""" """
Returns configuration file text. Other services that depend on bird Returns configuration file text. Other services that depend on bird
will have generatebirdifcconfig() and generatebirdconfig() will have hooks that are invoked here.
hooks that are invoked here.
""" """
cfg = """\ cfg = """\
/* Main configuration file for BIRD. This is ony a template, /* Main configuration file for BIRD. This is ony a template,
@ -75,15 +76,16 @@ protocol device {
""" % ( """ % (
cls.name, cls.name,
cls.routerid(node), cls.router_id(node),
) )
# Generate protocol specific configurations # generate protocol specific configurations
for s in node.services: for s in node.services:
if cls.name not in s.dependencies: if cls.name not in s.dependencies:
continue continue
if not (isinstance(s, BirdService) or issubclass(s, BirdService)):
continue
cfg += s.generate_bird_config(node) cfg += s.generate_bird_config(node)
return cfg return cfg
@ -93,32 +95,26 @@ class BirdService(CoreService):
common to Bird's routing daemons. common to Bird's routing daemons.
""" """
name = None name: Optional[str] = None
executables = ("bird",) group: str = "BIRD"
group = "BIRD" executables: Tuple[str, ...] = ("bird",)
dependencies = ("bird",) dependencies: Tuple[str, ...] = ("bird",)
dirs = () meta: str = "The config file for this service can be found in the bird service."
configs = ()
startup = ()
shutdown = ()
meta = "The config file for this service can be found in the bird service."
@classmethod @classmethod
def generate_bird_config(cls, node): def generate_bird_config(cls, node: CoreNode) -> str:
return "" return ""
@classmethod @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 Use only bare interfaces descriptions in generated protocol
configurations. This has the slight advantage of being the same configurations. This has the slight advantage of being the same
everywhere. everywhere.
""" """
cfg = "" cfg = ""
for iface in node.get_ifaces(control=False): for iface in node.get_ifaces(control=False):
cfg += ' interface "%s";\n' % iface.name cfg += ' interface "%s";\n' % iface.name
return cfg return cfg
@ -127,11 +123,11 @@ class BirdBgp(BirdService):
BGP BIRD Service (configuration generation) BGP BIRD Service (configuration generation)
""" """
name = "BIRD_BGP" name: str = "BIRD_BGP"
custom_needed = True custom_needed: bool = True
@classmethod @classmethod
def generate_bird_config(cls, node): def generate_bird_config(cls, node: CoreNode) -> str:
return """ return """
/* This is a sample config that should be customized with appropriate AS numbers /* This is a sample config that should be customized with appropriate AS numbers
* and peers; add one section like this for each neighbor */ * and peers; add one section like this for each neighbor */
@ -158,10 +154,10 @@ class BirdOspf(BirdService):
OSPF BIRD Service (configuration generation) OSPF BIRD Service (configuration generation)
""" """
name = "BIRD_OSPFv2" name: str = "BIRD_OSPFv2"
@classmethod @classmethod
def generate_bird_config(cls, node): def generate_bird_config(cls, node: CoreNode) -> str:
cfg = "protocol ospf {\n" cfg = "protocol ospf {\n"
cfg += " export filter {\n" cfg += " export filter {\n"
cfg += " if source = RTS_BGP then {\n" cfg += " if source = RTS_BGP then {\n"
@ -174,7 +170,6 @@ class BirdOspf(BirdService):
cfg += cls.generate_bird_iface_config(node) cfg += cls.generate_bird_iface_config(node)
cfg += " };\n" cfg += " };\n"
cfg += "}\n\n" cfg += "}\n\n"
return cfg return cfg
@ -183,12 +178,11 @@ class BirdRadv(BirdService):
RADV BIRD Service (configuration generation) RADV BIRD Service (configuration generation)
""" """
name = "BIRD_RADV" name: str = "BIRD_RADV"
@classmethod @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 = "/* This is a sample config that must be customized */\n"
cfg += "protocol radv {\n" cfg += "protocol radv {\n"
cfg += " # auto configuration on all interfaces\n" cfg += " # auto configuration on all interfaces\n"
cfg += cls.generate_bird_iface_config(node) cfg += cls.generate_bird_iface_config(node)
@ -202,7 +196,6 @@ class BirdRadv(BirdService):
cfg += "# ns 2001:0DB8:1234::12;\n" cfg += "# ns 2001:0DB8:1234::12;\n"
cfg += " };\n" cfg += " };\n"
cfg += "}\n\n" cfg += "}\n\n"
return cfg return cfg
@ -211,10 +204,10 @@ class BirdRip(BirdService):
RIP BIRD Service (configuration generation) RIP BIRD Service (configuration generation)
""" """
name = "BIRD_RIP" name: str = "BIRD_RIP"
@classmethod @classmethod
def generate_bird_config(cls, node): def generate_bird_config(cls, node: CoreNode) -> str:
cfg = "protocol rip {\n" cfg = "protocol rip {\n"
cfg += " period 10;\n" cfg += " period 10;\n"
cfg += " garbage time 60;\n" cfg += " garbage time 60;\n"
@ -224,7 +217,6 @@ class BirdRip(BirdService):
cfg += " import all;\n" cfg += " import all;\n"
cfg += " export all;\n" cfg += " export all;\n"
cfg += "}\n\n" cfg += "}\n\n"
return cfg return cfg
@ -233,11 +225,11 @@ class BirdStatic(BirdService):
Static Bird Service (configuration generation) Static Bird Service (configuration generation)
""" """
name = "BIRD_static" name: str = "BIRD_static"
custom_needed = True custom_needed: bool = True
@classmethod @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 = "/* This is a sample config that must be customized */\n"
cfg += "protocol static {\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" cfg += "# route 0.0.0.0/0 via 198.51.100.130; # Default route. Do NOT advertise on BGP !\n"

View file

@ -1,23 +1,26 @@
from typing import Tuple
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.errors import CoreError from core.errors import CoreError
from core.nodes.base import CoreNode
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
from core.xml import emanexml from core.xml import emanexml
class EmaneTransportService(CoreService): class EmaneTransportService(CoreService):
name = "transportd" name: str = "transportd"
executables = ("emanetransportd", "emanegentransportxml") group: str = "EMANE"
group = "EMANE" executables: Tuple[str, ...] = ("emanetransportd", "emanegentransportxml")
dependencies = () dependencies: Tuple[str, ...] = ()
dirs = () dirs: Tuple[str, ...] = ()
configs = ("emanetransport.sh",) configs: Tuple[str, ...] = ("emanetransport.sh",)
startup = ("sh %s" % configs[0],) startup: Tuple[str, ...] = ("sh %s" % configs[0],)
validate = ("pidof %s" % executables[0],) validate: Tuple[str, ...] = ("pidof %s" % executables[0],)
validation_timer = 0.5 validation_timer: float = 0.5
shutdown = ("killall %s" % executables[0],) shutdown: Tuple[str, ...] = ("killall %s" % executables[0],)
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
if filename == cls.configs[0]: if filename == cls.configs[0]:
transport_commands = [] transport_commands = []
for iface in node.get_ifaces(): for iface in node.get_ifaces():

View file

@ -2,60 +2,63 @@
frr.py: defines routing services provided by FRRouting. frr.py: defines routing services provided by FRRouting.
Assumes installation of FRR via https://deb.frrouting.org/ Assumes installation of FRR via https://deb.frrouting.org/
""" """
from typing import Optional, Tuple
import netaddr import netaddr
from core import constants from core import constants
from core.emane.nodes import EmaneNet 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.network import PtpNet, WlanNode
from core.nodes.physical import Rj45Node from core.nodes.physical import Rj45Node
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
class FRRZebra(CoreService): class FRRZebra(CoreService):
name = "FRRzebra" name: str = "FRRzebra"
group = "FRR" group: str = "FRR"
dirs = ("/usr/local/etc/frr", "/var/run/frr", "/var/log/frr") dirs: Tuple[str, ...] = ("/usr/local/etc/frr", "/var/run/frr", "/var/log/frr")
configs = ( configs: Tuple[str, ...] = (
"/usr/local/etc/frr/frr.conf", "/usr/local/etc/frr/frr.conf",
"frrboot.sh", "frrboot.sh",
"/usr/local/etc/frr/vtysh.conf", "/usr/local/etc/frr/vtysh.conf",
"/usr/local/etc/frr/daemons", "/usr/local/etc/frr/daemons",
) )
startup = ("sh frrboot.sh zebra",) startup: Tuple[str, ...] = ("sh frrboot.sh zebra",)
shutdown = ("killall zebra",) shutdown: Tuple[str, ...] = ("killall zebra",)
validate = ("pidof zebra",) validate: Tuple[str, ...] = ("pidof zebra",)
@classmethod @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. Return the frr.conf or frrboot.sh file contents.
""" """
if filename == cls.configs[0]: if filename == cls.configs[0]:
return cls.generateFrrConf(node) return cls.generate_frr_conf(node)
elif filename == cls.configs[1]: elif filename == cls.configs[1]:
return cls.generateFrrBoot(node) return cls.generate_frr_boot(node)
elif filename == cls.configs[2]: elif filename == cls.configs[2]:
return cls.generateVtyshConf(node) return cls.generate_vtysh_conf(node)
elif filename == cls.configs[3]: elif filename == cls.configs[3]:
return cls.generateFrrDaemons(node) return cls.generate_frr_daemons(node)
else: else:
raise ValueError( raise ValueError(
"file name (%s) is not a known configuration: %s", filename, cls.configs "file name (%s) is not a known configuration: %s", filename, cls.configs
) )
@classmethod @classmethod
def generateVtyshConf(cls, node): def generate_vtysh_conf(cls, node: CoreNode) -> str:
""" """
Returns configuration file text. Returns configuration file text.
""" """
return "service integrated-vtysh-config\n" return "service integrated-vtysh-config\n"
@classmethod @classmethod
def generateFrrConf(cls, node): def generate_frr_conf(cls, node: CoreNode) -> str:
""" """
Returns configuration file text. Other services that depend on zebra Returns configuration file text. Other services that depend on zebra
will have generatefrrifcconfig() and generatefrrconfig() will have hooks that are invoked here.
hooks that are invoked here.
""" """
# we could verify here that filename == frr.conf # we could verify here that filename == frr.conf
cfg = "" cfg = ""
@ -108,7 +111,7 @@ class FRRZebra(CoreService):
return cfg return cfg
@staticmethod @staticmethod
def addrstr(x): def addrstr(x: str) -> str:
""" """
helper for mapping IP addresses to zebra config statements helper for mapping IP addresses to zebra config statements
""" """
@ -121,7 +124,7 @@ class FRRZebra(CoreService):
raise ValueError("invalid address: %s", x) raise ValueError("invalid address: %s", x)
@classmethod @classmethod
def generateFrrBoot(cls, node): def generate_frr_boot(cls, node: CoreNode) -> str:
""" """
Generate a shell script used to boot the FRR daemons. Generate a shell script used to boot the FRR daemons.
""" """
@ -244,7 +247,7 @@ bootfrr
return cfg return cfg
@classmethod @classmethod
def generateFrrDaemons(cls, node): def generate_frr_daemons(cls, node: CoreNode) -> str:
""" """
Returns configuration file text. Returns configuration file text.
""" """
@ -317,20 +320,15 @@ class FrrService(CoreService):
common to FRR's routing daemons. common to FRR's routing daemons.
""" """
name = None name: Optional[str] = None
group = "FRR" group: str = "FRR"
dependencies = ("FRRzebra",) dependencies: Tuple[str, ...] = ("FRRzebra",)
dirs = () meta: str = "The config file for this service can be found in the Zebra service."
configs = () ipv4_routing: bool = False
startup = () ipv6_routing: bool = False
shutdown = ()
meta = "The config file for this service can be found in the Zebra service."
ipv4_routing = False
ipv6_routing = False
@staticmethod @staticmethod
def routerid(node): def router_id(node: CoreNode) -> str:
""" """
Helper to return the first IPv4 address of a node as its router ID. Helper to return the first IPv4 address of a node as its router ID.
""" """
@ -339,11 +337,10 @@ class FrrService(CoreService):
a = a.split("/")[0] a = a.split("/")[0]
if netaddr.valid_ipv4(a): if netaddr.valid_ipv4(a):
return a return a
# raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0" return "0.0.0.0"
@staticmethod @staticmethod
def rj45check(iface): def rj45check(iface: CoreInterface) -> bool:
""" """
Helper to detect whether interface is connected an external RJ45 Helper to detect whether interface is connected an external RJ45
link. link.
@ -357,15 +354,15 @@ class FrrService(CoreService):
return False return False
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
return "" return ""
@classmethod @classmethod
def generate_frr_iface_config(cls, node, iface): def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str:
return "" return ""
@classmethod @classmethod
def generate_frr_config(cls, node): def generate_frr_config(cls, node: CoreNode) -> str:
return "" return ""
@ -376,14 +373,13 @@ class FRROspfv2(FrrService):
unified frr.conf file. unified frr.conf file.
""" """
name = "FRROSPFv2" name: str = "FRROSPFv2"
startup = () shutdown: Tuple[str, ...] = ("killall ospfd",)
shutdown = ("killall ospfd",) validate: Tuple[str, ...] = ("pidof ospfd",)
validate = ("pidof ospfd",) ipv4_routing: bool = True
ipv4_routing = True
@staticmethod @staticmethod
def mtucheck(iface): def mtu_check(iface: CoreInterface) -> str:
""" """
Helper to detect MTU mismatch and add the appropriate OSPF 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 mtu-ignore command. This is needed when e.g. a node is linked via a
@ -401,7 +397,7 @@ class FRROspfv2(FrrService):
return "" return ""
@staticmethod @staticmethod
def ptpcheck(iface): def ptp_check(iface: CoreInterface) -> str:
""" """
Helper to detect whether interface is connected to a notional Helper to detect whether interface is connected to a notional
point-to-point link. point-to-point link.
@ -411,9 +407,9 @@ class FRROspfv2(FrrService):
return "" return ""
@classmethod @classmethod
def generate_frr_config(cls, node): def generate_frr_config(cls, node: CoreNode) -> str:
cfg = "router ospf\n" cfg = "router ospf\n"
rtrid = cls.routerid(node) rtrid = cls.router_id(node)
cfg += " router-id %s\n" % rtrid cfg += " router-id %s\n" % rtrid
# network 10.0.0.0/24 area 0 # network 10.0.0.0/24 area 0
for iface in node.get_ifaces(control=False): for iface in node.get_ifaces(control=False):
@ -426,8 +422,8 @@ class FRROspfv2(FrrService):
return cfg return cfg
@classmethod @classmethod
def generate_frr_iface_config(cls, node, iface): def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str:
return cls.mtucheck(iface) return cls.mtu_check(iface)
class FRROspfv3(FrrService): class FRROspfv3(FrrService):
@ -437,15 +433,14 @@ class FRROspfv3(FrrService):
unified frr.conf file. unified frr.conf file.
""" """
name = "FRROSPFv3" name: str = "FRROSPFv3"
startup = () shutdown: Tuple[str, ...] = ("killall ospf6d",)
shutdown = ("killall ospf6d",) validate: Tuple[str, ...] = ("pidof ospf6d",)
validate = ("pidof ospf6d",) ipv4_routing: bool = True
ipv4_routing = True ipv6_routing: bool = True
ipv6_routing = True
@staticmethod @staticmethod
def minmtu(iface): def min_mtu(iface: CoreInterface) -> int:
""" """
Helper to discover the minimum MTU of interfaces linked with the Helper to discover the minimum MTU of interfaces linked with the
given interface. given interface.
@ -459,20 +454,20 @@ class FRROspfv3(FrrService):
return mtu return mtu
@classmethod @classmethod
def mtucheck(cls, iface): def mtu_check(cls, iface: CoreInterface) -> str:
""" """
Helper to detect MTU mismatch and add the appropriate OSPFv3 Helper to detect MTU mismatch and add the appropriate OSPFv3
ifmtu command. This is needed when e.g. a node is linked via a ifmtu command. This is needed when e.g. a node is linked via a
GreTap device. GreTap device.
""" """
minmtu = cls.minmtu(iface) minmtu = cls.min_mtu(iface)
if minmtu < iface.mtu: if minmtu < iface.mtu:
return " ipv6 ospf6 ifmtu %d\n" % minmtu return " ipv6 ospf6 ifmtu %d\n" % minmtu
else: else:
return "" return ""
@staticmethod @staticmethod
def ptpcheck(iface): def ptp_check(iface: CoreInterface) -> str:
""" """
Helper to detect whether interface is connected to a notional Helper to detect whether interface is connected to a notional
point-to-point link. point-to-point link.
@ -482,9 +477,9 @@ class FRROspfv3(FrrService):
return "" return ""
@classmethod @classmethod
def generate_frr_config(cls, node): def generate_frr_config(cls, node: CoreNode) -> str:
cfg = "router ospf6\n" cfg = "router ospf6\n"
rtrid = cls.routerid(node) rtrid = cls.router_id(node)
cfg += " router-id %s\n" % rtrid cfg += " router-id %s\n" % rtrid
for iface in node.get_ifaces(control=False): for iface in node.get_ifaces(control=False):
cfg += " interface %s area 0.0.0.0\n" % iface.name cfg += " interface %s area 0.0.0.0\n" % iface.name
@ -492,14 +487,13 @@ class FRROspfv3(FrrService):
return cfg return cfg
@classmethod @classmethod
def generate_frr_iface_config(cls, node, iface): def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str:
return cls.mtucheck(iface) return cls.mtu_check(iface)
# cfg = cls.mtucheck(ifc) # cfg = cls.mtucheck(ifc)
# external RJ45 connections will use default OSPF timers # external RJ45 connections will use default OSPF timers
# if cls.rj45check(ifc): # if cls.rj45check(ifc):
# return cfg # return cfg
# cfg += cls.ptpcheck(ifc) # cfg += cls.ptpcheck(ifc)
# return cfg + """\ # return cfg + """\
@ -516,21 +510,20 @@ class FRRBgp(FrrService):
having the same AS number. having the same AS number.
""" """
name = "FRRBGP" name: str = "FRRBGP"
startup = () shutdown: Tuple[str, ...] = ("killall bgpd",)
shutdown = ("killall bgpd",) validate: Tuple[str, ...] = ("pidof bgpd",)
validate = ("pidof bgpd",) custom_needed: bool = True
custom_needed = True ipv4_routing: bool = True
ipv4_routing = True ipv6_routing: bool = True
ipv6_routing = True
@classmethod @classmethod
def generate_frr_config(cls, node): def generate_frr_config(cls, node: CoreNode) -> str:
cfg = "!\n! BGP configuration\n!\n" cfg = "!\n! BGP configuration\n!\n"
cfg += "! You should configure the AS number below,\n" cfg += "! You should configure the AS number below,\n"
cfg += "! along with this router's peers.\n!\n" cfg += "! along with this router's peers.\n!\n"
cfg += "router bgp %s\n" % node.id cfg += "router bgp %s\n" % node.id
rtrid = cls.routerid(node) rtrid = cls.router_id(node)
cfg += " bgp router-id %s\n" % rtrid cfg += " bgp router-id %s\n" % rtrid
cfg += " redistribute connected\n" cfg += " redistribute connected\n"
cfg += "! neighbor 1.2.3.4 remote-as 555\n!\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. The RIP service provides IPv4 routing for wired networks.
""" """
name = "FRRRIP" name: str = "FRRRIP"
startup = () shutdown: Tuple[str, ...] = ("killall ripd",)
shutdown = ("killall ripd",) validate: Tuple[str, ...] = ("pidof ripd",)
validate = ("pidof ripd",) ipv4_routing: bool = True
ipv4_routing = True
@classmethod @classmethod
def generate_frr_config(cls, node): def generate_frr_config(cls, node: CoreNode) -> str:
cfg = """\ cfg = """\
router rip router rip
redistribute static redistribute static
@ -566,14 +558,13 @@ class FRRRipng(FrrService):
The RIP NG service provides IPv6 routing for wired networks. The RIP NG service provides IPv6 routing for wired networks.
""" """
name = "FRRRIPNG" name: str = "FRRRIPNG"
startup = () shutdown: Tuple[str, ...] = ("killall ripngd",)
shutdown = ("killall ripngd",) validate: Tuple[str, ...] = ("pidof ripngd",)
validate = ("pidof ripngd",) ipv6_routing: bool = True
ipv6_routing = True
@classmethod @classmethod
def generate_frr_config(cls, node): def generate_frr_config(cls, node: CoreNode) -> str:
cfg = """\ cfg = """\
router ripng router ripng
redistribute static redistribute static
@ -591,14 +582,13 @@ class FRRBabel(FrrService):
protocol for IPv6 and IPv4 with fast convergence properties. protocol for IPv6 and IPv4 with fast convergence properties.
""" """
name = "FRRBabel" name: str = "FRRBabel"
startup = () shutdown: Tuple[str, ...] = ("killall babeld",)
shutdown = ("killall babeld",) validate: Tuple[str, ...] = ("pidof babeld",)
validate = ("pidof babeld",) ipv6_routing: bool = True
ipv6_routing = True
@classmethod @classmethod
def generate_frr_config(cls, node): def generate_frr_config(cls, node: CoreNode) -> str:
cfg = "router babel\n" cfg = "router babel\n"
for iface in node.get_ifaces(control=False): for iface in node.get_ifaces(control=False):
cfg += " network %s\n" % iface.name cfg += " network %s\n" % iface.name
@ -606,7 +596,7 @@ class FRRBabel(FrrService):
return cfg return cfg
@classmethod @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)): if iface.net and isinstance(iface.net, (EmaneNet, WlanNode)):
return " babel wireless\n no babel split-horizon\n" return " babel wireless\n no babel split-horizon\n"
else: else:
@ -618,14 +608,13 @@ class FRRpimd(FrrService):
PIM multicast routing based on XORP. PIM multicast routing based on XORP.
""" """
name = "FRRpimd" name: str = "FRRpimd"
startup = () shutdown: Tuple[str, ...] = ("killall pimd",)
shutdown = ("killall pimd",) validate: Tuple[str, ...] = ("pidof pimd",)
validate = ("pidof pimd",) ipv4_routing: bool = True
ipv4_routing = True
@classmethod @classmethod
def generate_frr_config(cls, node): def generate_frr_config(cls, node: CoreNode) -> str:
ifname = "eth0" ifname = "eth0"
for iface in node.get_ifaces(): for iface in node.get_ifaces():
if iface.name != "lo": if iface.name != "lo":
@ -641,7 +630,7 @@ class FRRpimd(FrrService):
return cfg return cfg
@classmethod @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" return " ip mfea\n ip igmp\n ip pim\n"
@ -652,15 +641,14 @@ class FRRIsis(FrrService):
unified frr.conf file. unified frr.conf file.
""" """
name = "FRRISIS" name: str = "FRRISIS"
startup = () shutdown: Tuple[str, ...] = ("killall isisd",)
shutdown = ("killall isisd",) validate: Tuple[str, ...] = ("pidof isisd",)
validate = ("pidof isisd",) ipv4_routing: bool = True
ipv4_routing = True ipv6_routing: bool = True
ipv6_routing = True
@staticmethod @staticmethod
def ptpcheck(iface): def ptp_check(iface: CoreInterface) -> str:
""" """
Helper to detect whether interface is connected to a notional Helper to detect whether interface is connected to a notional
point-to-point link. point-to-point link.
@ -670,7 +658,7 @@ class FRRIsis(FrrService):
return "" return ""
@classmethod @classmethod
def generate_frr_config(cls, node): def generate_frr_config(cls, node: CoreNode) -> str:
cfg = "router isis DEFAULT\n" cfg = "router isis DEFAULT\n"
cfg += " net 47.0001.0000.1900.%04x.00\n" % node.id cfg += " net 47.0001.0000.1900.%04x.00\n" % node.id
cfg += " metric-style wide\n" cfg += " metric-style wide\n"
@ -679,9 +667,9 @@ class FRRIsis(FrrService):
return cfg return cfg
@classmethod @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 = " ip router isis DEFAULT\n"
cfg += " ipv6 router isis DEFAULT\n" cfg += " ipv6 router isis DEFAULT\n"
cfg += " isis circuit-type level-2-only\n" cfg += " isis circuit-type level-2-only\n"
cfg += cls.ptpcheck(iface) cfg += cls.ptp_check(iface)
return cfg return cfg

View file

@ -2,9 +2,12 @@
nrl.py: defines services provided by NRL protolib tools hosted here: nrl.py: defines services provided by NRL protolib tools hosted here:
http://www.nrl.navy.mil/itd/ncs/products http://www.nrl.navy.mil/itd/ncs/products
""" """
from typing import Optional, Tuple
import netaddr import netaddr
from core import utils from core import utils
from core.nodes.base import CoreNode
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
@ -14,19 +17,15 @@ class NrlService(CoreService):
common to NRL's routing daemons. common to NRL's routing daemons.
""" """
name = None name: Optional[str] = None
group = "ProtoSvc" group: str = "ProtoSvc"
dirs = ()
configs = ()
startup = ()
shutdown = ()
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
return "" return ""
@staticmethod @staticmethod
def firstipv4prefix(node, prefixlen=24): def firstipv4prefix(node: CoreNode, prefixlen: int = 24) -> str:
""" """
Similar to QuaggaService.routerid(). Helper to return the first IPv4 Similar to QuaggaService.routerid(). Helper to return the first IPv4
prefix of a node, using the supplied prefix length. This ignores the prefix of a node, using the supplied prefix length. This ignores the
@ -37,20 +36,19 @@ class NrlService(CoreService):
a = a.split("/")[0] a = a.split("/")[0]
if netaddr.valid_ipv4(a): if netaddr.valid_ipv4(a):
return f"{a}/{prefixlen}" return f"{a}/{prefixlen}"
# raise ValueError, "no IPv4 address found"
return "0.0.0.0/%s" % prefixlen return "0.0.0.0/%s" % prefixlen
class MgenSinkService(NrlService): class MgenSinkService(NrlService):
name = "MGEN_Sink" name: str = "MGEN_Sink"
executables = ("mgen",) executables: Tuple[str, ...] = ("mgen",)
configs = ("sink.mgen",) configs: Tuple[str, ...] = ("sink.mgen",)
startup = ("mgen input sink.mgen",) startup: Tuple[str, ...] = ("mgen input sink.mgen",)
validate = ("pidof mgen",) validate: Tuple[str, ...] = ("pidof mgen",)
shutdown = ("killall mgen",) shutdown: Tuple[str, ...] = ("killall mgen",)
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
cfg = "0.0 LISTEN UDP 5000\n" cfg = "0.0 LISTEN UDP 5000\n"
for iface in node.get_ifaces(): for iface in node.get_ifaces():
name = utils.sysctl_devname(iface.name) name = utils.sysctl_devname(iface.name)
@ -58,7 +56,7 @@ class MgenSinkService(NrlService):
return cfg return cfg
@classmethod @classmethod
def get_startup(cls, node): def get_startup(cls, node: CoreNode) -> Tuple[str, ...]:
cmd = cls.startup[0] cmd = cls.startup[0]
cmd += " output /tmp/mgen_%s.log" % node.name cmd += " output /tmp/mgen_%s.log" % node.name
return (cmd,) return (cmd,)
@ -69,32 +67,29 @@ class NrlNhdp(NrlService):
NeighborHood Discovery Protocol for MANET networks. NeighborHood Discovery Protocol for MANET networks.
""" """
name = "NHDP" name: str = "NHDP"
executables = ("nrlnhdp",) executables: Tuple[str, ...] = ("nrlnhdp",)
startup = ("nrlnhdp",) startup: Tuple[str, ...] = ("nrlnhdp",)
shutdown = ("killall nrlnhdp",) shutdown: Tuple[str, ...] = ("killall nrlnhdp",)
validate = ("pidof nrlnhdp",) validate: Tuple[str, ...] = ("pidof nrlnhdp",)
@classmethod @classmethod
def get_startup(cls, node): def get_startup(cls, node: CoreNode) -> Tuple[str, ...]:
""" """
Generate the appropriate command-line based on node interfaces. Generate the appropriate command-line based on node interfaces.
""" """
cmd = cls.startup[0] cmd = cls.startup[0]
cmd += " -l /var/log/nrlnhdp.log" cmd += " -l /var/log/nrlnhdp.log"
cmd += " -rpipe %s_nhdp" % node.name cmd += " -rpipe %s_nhdp" % node.name
servicenames = map(lambda x: x.name, node.services) servicenames = map(lambda x: x.name, node.services)
if "SMF" in servicenames: if "SMF" in servicenames:
cmd += " -flooding ecds" cmd += " -flooding ecds"
cmd += " -smfClient %s_smf" % node.name cmd += " -smfClient %s_smf" % node.name
ifaces = node.get_ifaces(control=False) ifaces = node.get_ifaces(control=False)
if len(ifaces) > 0: if len(ifaces) > 0:
iface_names = map(lambda x: x.name, ifaces) iface_names = map(lambda x: x.name, ifaces)
cmd += " -i " cmd += " -i "
cmd += " -i ".join(iface_names) cmd += " -i ".join(iface_names)
return (cmd,) return (cmd,)
@ -103,15 +98,15 @@ class NrlSmf(NrlService):
Simplified Multicast Forwarding for MANET networks. Simplified Multicast Forwarding for MANET networks.
""" """
name = "SMF" name: str = "SMF"
executables = ("nrlsmf",) executables: Tuple[str, ...] = ("nrlsmf",)
startup = ("sh startsmf.sh",) startup: Tuple[str, ...] = ("sh startsmf.sh",)
shutdown = ("killall nrlsmf",) shutdown: Tuple[str, ...] = ("killall nrlsmf",)
validate = ("pidof nrlsmf",) validate: Tuple[str, ...] = ("pidof nrlsmf",)
configs = ("startsmf.sh",) configs: Tuple[str, ...] = ("startsmf.sh",)
@classmethod @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 Generate a startup script for SMF. Because nrlsmf does not
daemonize, it can cause problems in some situations when launched daemonize, it can cause problems in some situations when launched
@ -146,7 +141,6 @@ class NrlSmf(NrlService):
cmd += " hash MD5" cmd += " hash MD5"
cmd += " log /var/log/nrlsmf.log" cmd += " log /var/log/nrlsmf.log"
cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n"
return cfg return cfg
@ -156,14 +150,14 @@ class NrlOlsr(NrlService):
Optimized Link State Routing protocol for MANET networks. Optimized Link State Routing protocol for MANET networks.
""" """
name = "OLSR" name: str = "OLSR"
executables = ("nrlolsrd",) executables: Tuple[str, ...] = ("nrlolsrd",)
startup = ("nrlolsrd",) startup: Tuple[str, ...] = ("nrlolsrd",)
shutdown = ("killall nrlolsrd",) shutdown: Tuple[str, ...] = ("killall nrlolsrd",)
validate = ("pidof nrlolsrd",) validate: Tuple[str, ...] = ("pidof nrlolsrd",)
@classmethod @classmethod
def get_startup(cls, node): def get_startup(cls, node: CoreNode) -> Tuple[str, ...]:
""" """
Generate the appropriate command-line based on node interfaces. Generate the appropriate command-line based on node interfaces.
""" """
@ -175,14 +169,12 @@ class NrlOlsr(NrlService):
cmd += " -i %s" % iface.name cmd += " -i %s" % iface.name
cmd += " -l /var/log/nrlolsrd.log" cmd += " -l /var/log/nrlolsrd.log"
cmd += " -rpipe %s_olsr" % node.name cmd += " -rpipe %s_olsr" % node.name
servicenames = map(lambda x: x.name, node.services) servicenames = map(lambda x: x.name, node.services)
if "SMF" in servicenames and "NHDP" not in servicenames: if "SMF" in servicenames and "NHDP" not in servicenames:
cmd += " -flooding s-mpr" cmd += " -flooding s-mpr"
cmd += " -smfClient %s_smf" % node.name cmd += " -smfClient %s_smf" % node.name
if "zebra" in servicenames: if "zebra" in servicenames:
cmd += " -z" cmd += " -z"
return (cmd,) return (cmd,)
@ -191,34 +183,30 @@ class NrlOlsrv2(NrlService):
Optimized Link State Routing protocol version 2 for MANET networks. Optimized Link State Routing protocol version 2 for MANET networks.
""" """
name = "OLSRv2" name: str = "OLSRv2"
executables = ("nrlolsrv2",) executables: Tuple[str, ...] = ("nrlolsrv2",)
startup = ("nrlolsrv2",) startup: Tuple[str, ...] = ("nrlolsrv2",)
shutdown = ("killall nrlolsrv2",) shutdown: Tuple[str, ...] = ("killall nrlolsrv2",)
validate = ("pidof nrlolsrv2",) validate: Tuple[str, ...] = ("pidof nrlolsrv2",)
@classmethod @classmethod
def get_startup(cls, node): def get_startup(cls, node: CoreNode) -> Tuple[str, ...]:
""" """
Generate the appropriate command-line based on node interfaces. Generate the appropriate command-line based on node interfaces.
""" """
cmd = cls.startup[0] cmd = cls.startup[0]
cmd += " -l /var/log/nrlolsrv2.log" cmd += " -l /var/log/nrlolsrv2.log"
cmd += " -rpipe %s_olsrv2" % node.name cmd += " -rpipe %s_olsrv2" % node.name
servicenames = map(lambda x: x.name, node.services) servicenames = map(lambda x: x.name, node.services)
if "SMF" in servicenames: if "SMF" in servicenames:
cmd += " -flooding ecds" cmd += " -flooding ecds"
cmd += " -smfClient %s_smf" % node.name cmd += " -smfClient %s_smf" % node.name
cmd += " -p olsr" cmd += " -p olsr"
ifaces = node.get_ifaces(control=False) ifaces = node.get_ifaces(control=False)
if len(ifaces) > 0: if len(ifaces) > 0:
iface_names = map(lambda x: x.name, ifaces) iface_names = map(lambda x: x.name, ifaces)
cmd += " -i " cmd += " -i "
cmd += " -i ".join(iface_names) cmd += " -i ".join(iface_names)
return (cmd,) return (cmd,)
@ -227,16 +215,16 @@ class OlsrOrg(NrlService):
Optimized Link State Routing protocol from olsr.org for MANET networks. Optimized Link State Routing protocol from olsr.org for MANET networks.
""" """
name = "OLSRORG" name: str = "OLSRORG"
executables = ("olsrd",) executables: Tuple[str, ...] = ("olsrd",)
configs = ("/etc/olsrd/olsrd.conf",) configs: Tuple[str, ...] = ("/etc/olsrd/olsrd.conf",)
dirs = ("/etc/olsrd",) dirs: Tuple[str, ...] = ("/etc/olsrd",)
startup = ("olsrd",) startup: Tuple[str, ...] = ("olsrd",)
shutdown = ("killall olsrd",) shutdown: Tuple[str, ...] = ("killall olsrd",)
validate = ("pidof olsrd",) validate: Tuple[str, ...] = ("pidof olsrd",)
@classmethod @classmethod
def get_startup(cls, node): def get_startup(cls, node: CoreNode) -> Tuple[str, ...]:
""" """
Generate the appropriate command-line based on node interfaces. Generate the appropriate command-line based on node interfaces.
""" """
@ -246,13 +234,13 @@ class OlsrOrg(NrlService):
iface_names = map(lambda x: x.name, ifaces) iface_names = map(lambda x: x.name, ifaces)
cmd += " -i " cmd += " -i "
cmd += " -i ".join(iface_names) cmd += " -i ".join(iface_names)
return (cmd,) return (cmd,)
@classmethod @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 = """\ cfg = """\
# #
@ -577,24 +565,16 @@ class MgenActor(NrlService):
""" """
# a unique name is required, without spaces # a unique name is required, without spaces
name = "MgenActor" name: str = "MgenActor"
executables = ("mgen",) group: str = "ProtoSvc"
# you can create your own group here executables: Tuple[str, ...] = ("mgen",)
group = "ProtoSvc" configs: Tuple[str, ...] = ("start_mgen_actor.sh",)
# per-node directories startup: Tuple[str, ...] = ("sh start_mgen_actor.sh",)
dirs = () validate: Tuple[str, ...] = ("pidof mgen",)
# generated files (without a full path this file goes in the node's dir, shutdown: Tuple[str, ...] = ("killall mgen",)
# 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",)
@classmethod @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 Generate a startup script for MgenActor. Because mgenActor does not
daemonize, it can cause problems in some situations when launched daemonize, it can cause problems in some situations when launched
@ -604,11 +584,9 @@ class MgenActor(NrlService):
cfg += "# auto-generated by nrl.py:MgenActor.generateconfig()\n" cfg += "# auto-generated by nrl.py:MgenActor.generateconfig()\n"
comments = "" comments = ""
cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % node.name cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % node.name
ifaces = node.get_ifaces(control=False) ifaces = node.get_ifaces(control=False)
if len(ifaces) == 0: if len(ifaces) == 0:
return "" return ""
cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n"
return cfg return cfg
@ -618,15 +596,15 @@ class Arouted(NrlService):
Adaptive Routing Adaptive Routing
""" """
name = "arouted" name: str = "arouted"
executables = ("arouted",) executables: Tuple[str, ...] = ("arouted",)
configs = ("startarouted.sh",) configs: Tuple[str, ...] = ("startarouted.sh",)
startup = ("sh startarouted.sh",) startup: Tuple[str, ...] = ("sh startarouted.sh",)
shutdown = ("pkill arouted",) shutdown: Tuple[str, ...] = ("pkill arouted",)
validate = ("pidof arouted",) validate: Tuple[str, ...] = ("pidof arouted",)
@classmethod @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. Return the Quagga.conf or quaggaboot.sh file contents.
""" """

View file

@ -1,65 +1,68 @@
""" """
quagga.py: defines routing services provided by Quagga. quagga.py: defines routing services provided by Quagga.
""" """
from typing import Optional, Tuple
import netaddr import netaddr
from core import constants from core import constants
from core.emane.nodes import EmaneNet from core.emane.nodes import EmaneNet
from core.emulator.enumerations import LinkTypes 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.network import PtpNet, WlanNode
from core.nodes.physical import Rj45Node from core.nodes.physical import Rj45Node
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
class Zebra(CoreService): class Zebra(CoreService):
name = "zebra" name: str = "zebra"
group = "Quagga" group: str = "Quagga"
dirs = ("/usr/local/etc/quagga", "/var/run/quagga") dirs: Tuple[str, ...] = ("/usr/local/etc/quagga", "/var/run/quagga")
configs = ( configs: Tuple[str, ...] = (
"/usr/local/etc/quagga/Quagga.conf", "/usr/local/etc/quagga/Quagga.conf",
"quaggaboot.sh", "quaggaboot.sh",
"/usr/local/etc/quagga/vtysh.conf", "/usr/local/etc/quagga/vtysh.conf",
) )
startup = ("sh quaggaboot.sh zebra",) startup: Tuple[str, ...] = ("sh quaggaboot.sh zebra",)
shutdown = ("killall zebra",) shutdown: Tuple[str, ...] = ("killall zebra",)
validate = ("pidof zebra",) validate: Tuple[str, ...] = ("pidof zebra",)
@classmethod @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. Return the Quagga.conf or quaggaboot.sh file contents.
""" """
if filename == cls.configs[0]: if filename == cls.configs[0]:
return cls.generateQuaggaConf(node) return cls.generate_quagga_conf(node)
elif filename == cls.configs[1]: elif filename == cls.configs[1]:
return cls.generateQuaggaBoot(node) return cls.generate_quagga_boot(node)
elif filename == cls.configs[2]: elif filename == cls.configs[2]:
return cls.generateVtyshConf(node) return cls.generate_vtysh_conf(node)
else: else:
raise ValueError( raise ValueError(
"file name (%s) is not a known configuration: %s", filename, cls.configs "file name (%s) is not a known configuration: %s", filename, cls.configs
) )
@classmethod @classmethod
def generateVtyshConf(cls, node): def generate_vtysh_conf(cls, node: CoreNode) -> str:
""" """
Returns configuration file text. Returns configuration file text.
""" """
return "service integrated-vtysh-config\n" return "service integrated-vtysh-config\n"
@classmethod @classmethod
def generateQuaggaConf(cls, node): def generate_quagga_conf(cls, node: CoreNode) -> str:
""" """
Returns configuration file text. Other services that depend on zebra Returns configuration file text. Other services that depend on zebra
will have generatequaggaifcconfig() and generatequaggaconfig() will have hooks that are invoked here.
hooks that are invoked here.
""" """
# we could verify here that filename == Quagga.conf # we could verify here that filename == Quagga.conf
cfg = "" cfg = ""
for iface in node.get_ifaces(): for iface in node.get_ifaces():
cfg += "interface %s\n" % iface.name cfg += "interface %s\n" % iface.name
# include control interfaces in addressing but not routing daemons # 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 += " "
cfg += "\n ".join(map(cls.addrstr, iface.addrlist)) cfg += "\n ".join(map(cls.addrstr, iface.addrlist))
cfg += "\n" cfg += "\n"
@ -71,6 +74,8 @@ class Zebra(CoreService):
for s in node.services: for s in node.services:
if cls.name not in s.dependencies: if cls.name not in s.dependencies:
continue continue
if not (isinstance(s, QuaggaService) or issubclass(s, QuaggaService)):
continue
iface_config = s.generate_quagga_iface_config(node, iface) iface_config = s.generate_quagga_iface_config(node, iface)
if s.ipv4_routing: if s.ipv4_routing:
want_ipv4 = True want_ipv4 = True
@ -101,11 +106,13 @@ class Zebra(CoreService):
for s in node.services: for s in node.services:
if cls.name not in s.dependencies: if cls.name not in s.dependencies:
continue continue
if not (isinstance(s, QuaggaService) or issubclass(s, QuaggaService)):
continue
cfg += s.generate_quagga_config(node) cfg += s.generate_quagga_config(node)
return cfg return cfg
@staticmethod @staticmethod
def addrstr(x): def addrstr(x: str) -> str:
""" """
helper for mapping IP addresses to zebra config statements helper for mapping IP addresses to zebra config statements
""" """
@ -118,7 +125,7 @@ class Zebra(CoreService):
raise ValueError("invalid address: %s", x) raise ValueError("invalid address: %s", x)
@classmethod @classmethod
def generateQuaggaBoot(cls, node): def generate_quagga_boot(cls, node: CoreNode) -> str:
""" """
Generate a shell script used to boot the Quagga daemons. Generate a shell script used to boot the Quagga daemons.
""" """
@ -235,20 +242,15 @@ class QuaggaService(CoreService):
common to Quagga's routing daemons. common to Quagga's routing daemons.
""" """
name = None name: Optional[str] = None
group = "Quagga" group: str = "Quagga"
dependencies = ("zebra",) dependencies: Tuple[str, ...] = (Zebra.name,)
dirs = () meta: str = "The config file for this service can be found in the Zebra service."
configs = () ipv4_routing: bool = False
startup = () ipv6_routing: bool = False
shutdown = ()
meta = "The config file for this service can be found in the Zebra service."
ipv4_routing = False
ipv6_routing = False
@staticmethod @staticmethod
def routerid(node): def router_id(node: CoreNode) -> str:
""" """
Helper to return the first IPv4 address of a node as its router ID. Helper to return the first IPv4 address of a node as its router ID.
""" """
@ -257,11 +259,10 @@ class QuaggaService(CoreService):
a = a.split("/")[0] a = a.split("/")[0]
if netaddr.valid_ipv4(a): if netaddr.valid_ipv4(a):
return a return a
# raise ValueError, "no IPv4 address found for router ID" return f"0.0.0.{node.id:d}"
return "0.0.0.%d" % node.id
@staticmethod @staticmethod
def rj45check(iface): def rj45check(iface: CoreInterface) -> bool:
""" """
Helper to detect whether interface is connected an external RJ45 Helper to detect whether interface is connected an external RJ45
link. link.
@ -275,15 +276,15 @@ class QuaggaService(CoreService):
return False return False
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
return "" return ""
@classmethod @classmethod
def generate_quagga_iface_config(cls, node, iface): def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str:
return "" return ""
@classmethod @classmethod
def generate_quagga_config(cls, node): def generate_quagga_config(cls, node: CoreNode) -> str:
return "" return ""
@ -294,14 +295,13 @@ class Ospfv2(QuaggaService):
unified Quagga.conf file. unified Quagga.conf file.
""" """
name = "OSPFv2" name: str = "OSPFv2"
startup = () shutdown: Tuple[str, ...] = ("killall ospfd",)
shutdown = ("killall ospfd",) validate: Tuple[str, ...] = ("pidof ospfd",)
validate = ("pidof ospfd",) ipv4_routing: bool = True
ipv4_routing = True
@staticmethod @staticmethod
def mtucheck(iface): def mtu_check(iface: CoreInterface) -> str:
""" """
Helper to detect MTU mismatch and add the appropriate OSPF 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 mtu-ignore command. This is needed when e.g. a node is linked via a
@ -319,7 +319,7 @@ class Ospfv2(QuaggaService):
return "" return ""
@staticmethod @staticmethod
def ptpcheck(iface): def ptp_check(iface: CoreInterface) -> str:
""" """
Helper to detect whether interface is connected to a notional Helper to detect whether interface is connected to a notional
point-to-point link. point-to-point link.
@ -329,9 +329,9 @@ class Ospfv2(QuaggaService):
return "" return ""
@classmethod @classmethod
def generate_quagga_config(cls, node): def generate_quagga_config(cls, node: CoreNode) -> str:
cfg = "router ospf\n" cfg = "router ospf\n"
rtrid = cls.routerid(node) rtrid = cls.router_id(node)
cfg += " router-id %s\n" % rtrid cfg += " router-id %s\n" % rtrid
# network 10.0.0.0/24 area 0 # network 10.0.0.0/24 area 0
for iface in node.get_ifaces(control=False): for iface in node.get_ifaces(control=False):
@ -343,12 +343,12 @@ class Ospfv2(QuaggaService):
return cfg return cfg
@classmethod @classmethod
def generate_quagga_iface_config(cls, node, iface): def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str:
cfg = cls.mtucheck(iface) cfg = cls.mtu_check(iface)
# external RJ45 connections will use default OSPF timers # external RJ45 connections will use default OSPF timers
if cls.rj45check(iface): if cls.rj45check(iface):
return cfg return cfg
cfg += cls.ptpcheck(iface) cfg += cls.ptp_check(iface)
return ( return (
cfg cfg
+ """\ + """\
@ -366,15 +366,14 @@ class Ospfv3(QuaggaService):
unified Quagga.conf file. unified Quagga.conf file.
""" """
name = "OSPFv3" name: str = "OSPFv3"
startup = () shutdown: Tuple[str, ...] = ("killall ospf6d",)
shutdown = ("killall ospf6d",) validate: Tuple[str, ...] = ("pidof ospf6d",)
validate = ("pidof ospf6d",) ipv4_routing: bool = True
ipv4_routing = True ipv6_routing: bool = True
ipv6_routing = True
@staticmethod @staticmethod
def minmtu(iface): def min_mtu(iface: CoreInterface) -> int:
""" """
Helper to discover the minimum MTU of interfaces linked with the Helper to discover the minimum MTU of interfaces linked with the
given interface. given interface.
@ -388,20 +387,20 @@ class Ospfv3(QuaggaService):
return mtu return mtu
@classmethod @classmethod
def mtucheck(cls, iface): def mtu_check(cls, iface: CoreInterface) -> str:
""" """
Helper to detect MTU mismatch and add the appropriate OSPFv3 Helper to detect MTU mismatch and add the appropriate OSPFv3
ifmtu command. This is needed when e.g. a node is linked via a ifmtu command. This is needed when e.g. a node is linked via a
GreTap device. GreTap device.
""" """
minmtu = cls.minmtu(iface) minmtu = cls.min_mtu(iface)
if minmtu < iface.mtu: if minmtu < iface.mtu:
return " ipv6 ospf6 ifmtu %d\n" % minmtu return " ipv6 ospf6 ifmtu %d\n" % minmtu
else: else:
return "" return ""
@staticmethod @staticmethod
def ptpcheck(iface): def ptp_check(iface: CoreInterface) -> str:
""" """
Helper to detect whether interface is connected to a notional Helper to detect whether interface is connected to a notional
point-to-point link. point-to-point link.
@ -411,9 +410,9 @@ class Ospfv3(QuaggaService):
return "" return ""
@classmethod @classmethod
def generate_quagga_config(cls, node): def generate_quagga_config(cls, node: CoreNode) -> str:
cfg = "router ospf6\n" cfg = "router ospf6\n"
rtrid = cls.routerid(node) rtrid = cls.router_id(node)
cfg += " instance-id 65\n" cfg += " instance-id 65\n"
cfg += " router-id %s\n" % rtrid cfg += " router-id %s\n" % rtrid
for iface in node.get_ifaces(control=False): for iface in node.get_ifaces(control=False):
@ -422,8 +421,8 @@ class Ospfv3(QuaggaService):
return cfg return cfg
@classmethod @classmethod
def generate_quagga_iface_config(cls, node, iface): def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str:
return cls.mtucheck(iface) return cls.mtu_check(iface)
class Ospfv3mdr(Ospfv3): class Ospfv3mdr(Ospfv3):
@ -434,12 +433,12 @@ class Ospfv3mdr(Ospfv3):
unified Quagga.conf file. unified Quagga.conf file.
""" """
name = "OSPFv3MDR" name: str = "OSPFv3MDR"
ipv4_routing = True ipv4_routing: bool = True
@classmethod @classmethod
def generate_quagga_iface_config(cls, node, iface): def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str:
cfg = cls.mtucheck(iface) cfg = cls.mtu_check(iface)
if iface.net is not None and isinstance(iface.net, (WlanNode, EmaneNet)): if iface.net is not None and isinstance(iface.net, (WlanNode, EmaneNet)):
return ( return (
cfg cfg
@ -464,21 +463,20 @@ class Bgp(QuaggaService):
having the same AS number. having the same AS number.
""" """
name = "BGP" name: str = "BGP"
startup = () shutdown: Tuple[str, ...] = ("killall bgpd",)
shutdown = ("killall bgpd",) validate: Tuple[str, ...] = ("pidof bgpd",)
validate = ("pidof bgpd",) custom_needed: bool = True
custom_needed = True ipv4_routing: bool = True
ipv4_routing = True ipv6_routing: bool = True
ipv6_routing = True
@classmethod @classmethod
def generate_quagga_config(cls, node): def generate_quagga_config(cls, node: CoreNode) -> str:
cfg = "!\n! BGP configuration\n!\n" cfg = "!\n! BGP configuration\n!\n"
cfg += "! You should configure the AS number below,\n" cfg += "! You should configure the AS number below,\n"
cfg += "! along with this router's peers.\n!\n" cfg += "! along with this router's peers.\n!\n"
cfg += "router bgp %s\n" % node.id cfg += "router bgp %s\n" % node.id
rtrid = cls.routerid(node) rtrid = cls.router_id(node)
cfg += " bgp router-id %s\n" % rtrid cfg += " bgp router-id %s\n" % rtrid
cfg += " redistribute connected\n" cfg += " redistribute connected\n"
cfg += "! neighbor 1.2.3.4 remote-as 555\n!\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. The RIP service provides IPv4 routing for wired networks.
""" """
name = "RIP" name: str = "RIP"
startup = () shutdown: Tuple[str, ...] = ("killall ripd",)
shutdown = ("killall ripd",) validate: Tuple[str, ...] = ("pidof ripd",)
validate = ("pidof ripd",) ipv4_routing: bool = True
ipv4_routing = True
@classmethod @classmethod
def generate_quagga_config(cls, node): def generate_quagga_config(cls, node: CoreNode) -> str:
cfg = """\ cfg = """\
router rip router rip
redistribute static redistribute static
@ -514,14 +511,13 @@ class Ripng(QuaggaService):
The RIP NG service provides IPv6 routing for wired networks. The RIP NG service provides IPv6 routing for wired networks.
""" """
name = "RIPNG" name: str = "RIPNG"
startup = () shutdown: Tuple[str, ...] = ("killall ripngd",)
shutdown = ("killall ripngd",) validate: Tuple[str, ...] = ("pidof ripngd",)
validate = ("pidof ripngd",) ipv6_routing: bool = True
ipv6_routing = True
@classmethod @classmethod
def generate_quagga_config(cls, node): def generate_quagga_config(cls, node: CoreNode) -> str:
cfg = """\ cfg = """\
router ripng router ripng
redistribute static redistribute static
@ -539,14 +535,13 @@ class Babel(QuaggaService):
protocol for IPv6 and IPv4 with fast convergence properties. protocol for IPv6 and IPv4 with fast convergence properties.
""" """
name = "Babel" name: str = "Babel"
startup = () shutdown: Tuple[str, ...] = ("killall babeld",)
shutdown = ("killall babeld",) validate: Tuple[str, ...] = ("pidof babeld",)
validate = ("pidof babeld",) ipv6_routing: bool = True
ipv6_routing = True
@classmethod @classmethod
def generate_quagga_config(cls, node): def generate_quagga_config(cls, node: CoreNode) -> str:
cfg = "router babel\n" cfg = "router babel\n"
for iface in node.get_ifaces(control=False): for iface in node.get_ifaces(control=False):
cfg += " network %s\n" % iface.name cfg += " network %s\n" % iface.name
@ -554,7 +549,7 @@ class Babel(QuaggaService):
return cfg return cfg
@classmethod @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: if iface.net and iface.net.linktype == LinkTypes.WIRELESS:
return " babel wireless\n no babel split-horizon\n" return " babel wireless\n no babel split-horizon\n"
else: else:
@ -566,14 +561,13 @@ class Xpimd(QuaggaService):
PIM multicast routing based on XORP. PIM multicast routing based on XORP.
""" """
name = "Xpimd" name: str = "Xpimd"
startup = () shutdown: Tuple[str, ...] = ("killall xpimd",)
shutdown = ("killall xpimd",) validate: Tuple[str, ...] = ("pidof xpimd",)
validate = ("pidof xpimd",) ipv4_routing: bool = True
ipv4_routing = True
@classmethod @classmethod
def generate_quagga_config(cls, node): def generate_quagga_config(cls, node: CoreNode) -> str:
ifname = "eth0" ifname = "eth0"
for iface in node.get_ifaces(): for iface in node.get_ifaces():
if iface.name != "lo": if iface.name != "lo":
@ -589,5 +583,5 @@ class Xpimd(QuaggaService):
return cfg return cfg
@classmethod @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" return " ip mfea\n ip igmp\n ip pim\n"

View file

@ -3,9 +3,11 @@ sdn.py defines services to start Open vSwitch and the Ryu SDN Controller.
""" """
import re import re
from typing import Tuple
import netaddr import netaddr
from core.nodes.base import CoreNode
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
@ -14,24 +16,28 @@ class SdnService(CoreService):
Parent class for SDN services. Parent class for SDN services.
""" """
group = "SDN" group: str = "SDN"
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
return "" return ""
class OvsService(SdnService): class OvsService(SdnService):
name = "OvsService" name: str = "OvsService"
executables = ("ovs-ofctl", "ovs-vsctl") group: str = "SDN"
group = "SDN" executables: Tuple[str, ...] = ("ovs-ofctl", "ovs-vsctl")
dirs = ("/etc/openvswitch", "/var/run/openvswitch", "/var/log/openvswitch") dirs: Tuple[str, ...] = (
configs = ("OvsService.sh",) "/etc/openvswitch",
startup = ("sh OvsService.sh",) "/var/run/openvswitch",
shutdown = ("killall ovs-vswitchd", "killall ovsdb-server") "/var/log/openvswitch",
)
configs: Tuple[str, ...] = ("OvsService.sh",)
startup: Tuple[str, ...] = ("sh OvsService.sh",)
shutdown: Tuple[str, ...] = ("killall ovs-vswitchd", "killall ovsdb-server")
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
# Check whether the node is running zebra # Check whether the node is running zebra
has_zebra = 0 has_zebra = 0
for s in node.services: for s in node.services:
@ -46,8 +52,8 @@ class OvsService(SdnService):
cfg += "## this stops it from routing traffic without defined flows.\n" 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 += "## 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 += "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" cfg += "\n## Now add all our interfaces as ports to the switch\n"
portnum = 1 portnum = 1
for iface in node.get_ifaces(control=False): for iface in node.get_ifaces(control=False):
ifnumstr = re.findall(r"\d+", iface.name) ifnumstr = re.findall(r"\d+", iface.name)
@ -111,21 +117,19 @@ class OvsService(SdnService):
% (portnum + 1, portnum) % (portnum + 1, portnum)
) )
portnum += 2 portnum += 2
return cfg return cfg
class RyuService(SdnService): class RyuService(SdnService):
name = "ryuService" name: str = "ryuService"
executables = ("ryu-manager",) group: str = "SDN"
group = "SDN" executables: Tuple[str, ...] = ("ryu-manager",)
dirs = () configs: Tuple[str, ...] = ("ryuService.sh",)
configs = ("ryuService.sh",) startup: Tuple[str, ...] = ("sh ryuService.sh",)
startup = ("sh ryuService.sh",) shutdown: Tuple[str, ...] = ("killall ryu-manager",)
shutdown = ("killall ryu-manager",)
@classmethod @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 Return a string that will be written to filename, or sent to the
GUI for user customization. GUI for user customization.

View file

@ -4,78 +4,79 @@ firewall)
""" """
import logging import logging
from typing import Tuple
from core import constants from core import constants
from core.nodes.base import CoreNode
from core.nodes.interface import CoreInterface
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
class VPNClient(CoreService): class VPNClient(CoreService):
name = "VPNClient" name: str = "VPNClient"
group = "Security" group: str = "Security"
configs = ("vpnclient.sh",) configs: Tuple[str, ...] = ("vpnclient.sh",)
startup = ("sh vpnclient.sh",) startup: Tuple[str, ...] = ("sh vpnclient.sh",)
shutdown = ("killall openvpn",) shutdown: Tuple[str, ...] = ("killall openvpn",)
validate = ("pidof openvpn",) validate: Tuple[str, ...] = ("pidof openvpn",)
custom_needed = True custom_needed: bool = True
@classmethod @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 Return the client.conf and vpnclient.sh file contents to
""" """
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# custom VPN Client configuration for service (security.py)\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: try:
cfg += open(fname, "rb").read() with open(fname, "r") as f:
cfg += f.read()
except IOError: except IOError:
logging.exception( logging.exception(
"Error opening VPN client configuration template (%s)", fname "error opening VPN client configuration template (%s)", fname
) )
return cfg return cfg
class VPNServer(CoreService): class VPNServer(CoreService):
name = "VPNServer" name: str = "VPNServer"
group = "Security" group: str = "Security"
configs = ("vpnserver.sh",) configs: Tuple[str, ...] = ("vpnserver.sh",)
startup = ("sh vpnserver.sh",) startup: Tuple[str, ...] = ("sh vpnserver.sh",)
shutdown = ("killall openvpn",) shutdown: Tuple[str, ...] = ("killall openvpn",)
validate = ("pidof openvpn",) validate: Tuple[str, ...] = ("pidof openvpn",)
custom_needed = True custom_needed: bool = True
@classmethod @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 Return the sample server.conf and vpnserver.sh file contents to
GUI for user customization. GUI for user customization.
""" """
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# custom VPN Server Configuration for service (security.py)\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: try:
cfg += open(fname, "rb").read() with open(fname, "r") as f:
cfg += f.read()
except IOError: except IOError:
logging.exception( logging.exception(
"Error opening VPN server configuration template (%s)", fname "Error opening VPN server configuration template (%s)", fname
) )
return cfg return cfg
class IPsec(CoreService): class IPsec(CoreService):
name = "IPsec" name: str = "IPsec"
group = "Security" group: str = "Security"
configs = ("ipsec.sh",) configs: Tuple[str, ...] = ("ipsec.sh",)
startup = ("sh ipsec.sh",) startup: Tuple[str, ...] = ("sh ipsec.sh",)
shutdown = ("killall racoon",) shutdown: Tuple[str, ...] = ("killall racoon",)
custom_needed = True custom_needed: bool = True
@classmethod @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 Return the ipsec.conf and racoon.conf file contents to
GUI for user customization. GUI for user customization.
@ -83,7 +84,7 @@ class IPsec(CoreService):
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# set up static tunnel mode security assocation for service " cfg += "# set up static tunnel mode security assocation for service "
cfg += "(security.py)\n" cfg += "(security.py)\n"
fname = "%s/examples/services/sampleIPsec" % constants.CORE_DATA_DIR fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleIPsec"
try: try:
with open(fname, "r") as f: with open(fname, "r") as f:
cfg += f.read() cfg += f.read()
@ -93,28 +94,27 @@ class IPsec(CoreService):
class Firewall(CoreService): class Firewall(CoreService):
name = "Firewall" name: str = "Firewall"
group = "Security" group: str = "Security"
configs = ("firewall.sh",) configs: Tuple[str, ...] = ("firewall.sh",)
startup = ("sh firewall.sh",) startup: Tuple[str, ...] = ("sh firewall.sh",)
custom_needed = True custom_needed: bool = True
@classmethod @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. Return the firewall rule examples to GUI for user customization.
""" """
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# custom node firewall rules for service (security.py)\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: try:
cfg += open(fname, "rb").read() with open(fname, "r") as f:
cfg += f.read()
except IOError: except IOError:
logging.exception( logging.exception(
"Error opening Firewall configuration template (%s)", fname "Error opening Firewall configuration template (%s)", fname
) )
return cfg return cfg
@ -123,30 +123,28 @@ class Nat(CoreService):
IPv4 source NAT service. IPv4 source NAT service.
""" """
name = "NAT" name: str = "NAT"
executables = ("iptables",) group: str = "Security"
group = "Security" executables: Tuple[str, ...] = ("iptables",)
configs = ("nat.sh",) configs: Tuple[str, ...] = ("nat.sh",)
startup = ("sh nat.sh",) startup: Tuple[str, ...] = ("sh nat.sh",)
custom_needed = False custom_needed: bool = False
@classmethod @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. 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 += iface.name + " -j MASQUERADE\n"
cfg += prefix + "iptables -A FORWARD -i " + iface.name
cfg += line_prefix + "iptables -A FORWARD -i " + iface.name
cfg += " -m state --state RELATED,ESTABLISHED -j ACCEPT\n" cfg += " -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
cfg += prefix + "iptables -A FORWARD -i "
cfg += line_prefix + "iptables -A FORWARD -i "
cfg += iface.name + " -j DROP\n" cfg += iface.name + " -j DROP\n"
return cfg return cfg
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
""" """
NAT out the first interface NAT out the first interface
""" """
@ -156,7 +154,7 @@ class Nat(CoreService):
have_nat = False have_nat = False
for iface in node.get_ifaces(control=False): for iface in node.get_ifaces(control=False):
if have_nat: if have_nat:
cfg += cls.generate_iface_nat_rule(iface, line_prefix="#") cfg += cls.generate_iface_nat_rule(iface, prefix="#")
else: else:
have_nat = True have_nat = True
cfg += "# NAT out the " + iface.name + " interface\n" cfg += "# NAT out the " + iface.name + " interface\n"

View file

@ -1,52 +1,52 @@
""" """
ucarp.py: defines high-availability IP address controlled by ucarp 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 from core.services.coreservices import CoreService
UCARP_ETC = "/usr/local/etc/ucarp" UCARP_ETC = "/usr/local/etc/ucarp"
class Ucarp(CoreService): class Ucarp(CoreService):
name = "ucarp" name: str = "ucarp"
group = "Utility" group: str = "Utility"
dirs = (UCARP_ETC,) dirs: Tuple[str, ...] = (UCARP_ETC,)
configs = ( configs: Tuple[str, ...] = (
UCARP_ETC + "/default.sh", UCARP_ETC + "/default.sh",
UCARP_ETC + "/default-up.sh", UCARP_ETC + "/default-up.sh",
UCARP_ETC + "/default-down.sh", UCARP_ETC + "/default-down.sh",
"ucarpboot.sh", "ucarpboot.sh",
) )
startup = ("sh ucarpboot.sh",) startup: Tuple[str, ...] = ("sh ucarpboot.sh",)
shutdown = ("killall ucarp",) shutdown: Tuple[str, ...] = ("killall ucarp",)
validate = ("pidof ucarp",) validate: Tuple[str, ...] = ("pidof ucarp",)
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
""" """
Return the default file contents Return the default file contents
""" """
if filename == cls.configs[0]: if filename == cls.configs[0]:
return cls.generateUcarpConf(node) return cls.generate_ucarp_conf(node)
elif filename == cls.configs[1]: elif filename == cls.configs[1]:
return cls.generateVipUp(node) return cls.generate_vip_up(node)
elif filename == cls.configs[2]: elif filename == cls.configs[2]:
return cls.generateVipDown(node) return cls.generate_vip_down(node)
elif filename == cls.configs[3]: elif filename == cls.configs[3]:
return cls.generateUcarpBoot(node) return cls.generate_ucarp_boot(node)
else: else:
raise ValueError raise ValueError
@classmethod @classmethod
def generateUcarpConf(cls, node): def generate_ucarp_conf(cls, node: CoreNode) -> str:
""" """
Returns configuration file text. Returns configuration file text.
""" """
try: ucarp_bin = node.session.options.get_config(
ucarp_bin = node.session.cfg["ucarp_bin"] "ucarp_bin", default="/usr/sbin/ucarp"
except KeyError: )
ucarp_bin = "/usr/sbin/ucarp"
return """\ return """\
#!/bin/sh #!/bin/sh
# Location of UCARP executable # Location of UCARP executable
@ -110,7 +110,7 @@ ${UCARP_EXEC} -B ${UCARP_OPTS}
) )
@classmethod @classmethod
def generateUcarpBoot(cls, node): def generate_ucarp_boot(cls, node: CoreNode) -> str:
""" """
Generate a shell script used to boot the Ucarp daemons. Generate a shell script used to boot the Ucarp daemons.
""" """
@ -130,7 +130,7 @@ ${UCARP_CFGDIR}/default.sh
) )
@classmethod @classmethod
def generateVipUp(cls, node): def generate_vip_up(cls, node: CoreNode) -> str:
""" """
Generate a shell script used to start the virtual ip Generate a shell script used to start the virtual ip
""" """
@ -152,7 +152,7 @@ fi
""" """
@classmethod @classmethod
def generateVipDown(cls, node): def generate_vip_down(cls, node: CoreNode) -> str:
""" """
Generate a shell script used to stop the virtual ip Generate a shell script used to stop the virtual ip
""" """

View file

@ -1,12 +1,13 @@
""" """
utility.py: defines miscellaneous utility services. utility.py: defines miscellaneous utility services.
""" """
import os from typing import Optional, Tuple
import netaddr import netaddr
from core import constants, utils from core import constants, utils
from core.errors import CoreCommandError from core.errors import CoreCommandError
from core.nodes.base import CoreNode
from core.services.coreservices import CoreService, ServiceMode from core.services.coreservices import CoreService, ServiceMode
@ -15,32 +16,25 @@ class UtilService(CoreService):
Parent class for utility services. Parent class for utility services.
""" """
name = None name: Optional[str] = None
group = "Utility" group: str = "Utility"
dirs = ()
configs = ()
startup = ()
shutdown = ()
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
return "" return ""
class IPForwardService(UtilService): class IPForwardService(UtilService):
name = "IPForward" name: str = "IPForward"
configs = ("ipforward.sh",) configs: Tuple[str, ...] = ("ipforward.sh",)
startup = ("sh ipforward.sh",) startup: Tuple[str, ...] = ("sh ipforward.sh",)
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
if os.uname()[0] == "Linux":
return cls.generateconfiglinux(node, filename) return cls.generateconfiglinux(node, filename)
else:
raise Exception("unknown platform")
@classmethod @classmethod
def generateconfiglinux(cls, node, filename): def generateconfiglinux(cls, node: CoreNode, filename: str) -> str:
cfg = """\ cfg = """\
#!/bin/sh #!/bin/sh
# auto-generated by IPForward service (utility.py) # auto-generated by IPForward service (utility.py)
@ -70,12 +64,12 @@ class IPForwardService(UtilService):
class DefaultRouteService(UtilService): class DefaultRouteService(UtilService):
name = "DefaultRoute" name: str = "DefaultRoute"
configs = ("defaultroute.sh",) configs: Tuple[str, ...] = ("defaultroute.sh",)
startup = ("sh defaultroute.sh",) startup: Tuple[str, ...] = ("sh defaultroute.sh",)
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
routes = [] routes = []
ifaces = node.get_ifaces() ifaces = node.get_ifaces()
if ifaces: if ifaces:
@ -93,22 +87,18 @@ class DefaultRouteService(UtilService):
class DefaultMulticastRouteService(UtilService): class DefaultMulticastRouteService(UtilService):
name = "DefaultMulticastRoute" name: str = "DefaultMulticastRoute"
configs = ("defaultmroute.sh",) configs: Tuple[str, ...] = ("defaultmroute.sh",)
startup = ("sh defaultmroute.sh",) startup: Tuple[str, ...] = ("sh defaultmroute.sh",)
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# auto-generated by DefaultMulticastRoute service (utility.py)\n" cfg += "# auto-generated by DefaultMulticastRoute service (utility.py)\n"
cfg += "# the first interface is chosen below; please change it " cfg += "# the first interface is chosen below; please change it "
cfg += "as needed\n" cfg += "as needed\n"
for iface in node.get_ifaces(control=False): for iface in node.get_ifaces(control=False):
if os.uname()[0] == "Linux":
rtcmd = "ip route add 224.0.0.0/4 dev" rtcmd = "ip route add 224.0.0.0/4 dev"
else:
raise Exception("unknown platform")
cfg += "%s %s\n" % (rtcmd, iface.name) cfg += "%s %s\n" % (rtcmd, iface.name)
cfg += "\n" cfg += "\n"
break break
@ -116,13 +106,13 @@ class DefaultMulticastRouteService(UtilService):
class StaticRouteService(UtilService): class StaticRouteService(UtilService):
name = "StaticRoute" name: str = "StaticRoute"
configs = ("staticroute.sh",) configs: Tuple[str, ...] = ("staticroute.sh",)
startup = ("sh staticroute.sh",) startup: Tuple[str, ...] = ("sh staticroute.sh",)
custom_needed = True custom_needed: bool = True
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# auto-generated by StaticRoute service (utility.py)\n#\n" cfg += "# auto-generated by StaticRoute service (utility.py)\n#\n"
cfg += "# NOTE: this service must be customized to be of any use\n" cfg += "# NOTE: this service must be customized to be of any use\n"
@ -133,7 +123,7 @@ class StaticRouteService(UtilService):
return cfg return cfg
@staticmethod @staticmethod
def routestr(x): def routestr(x: str) -> str:
addr = x.split("/")[0] addr = x.split("/")[0]
if netaddr.valid_ipv6(addr): if netaddr.valid_ipv6(addr):
dst = "3ffe:4::/64" dst = "3ffe:4::/64"
@ -143,24 +133,20 @@ class StaticRouteService(UtilService):
if net[-2] == net[1]: if net[-2] == net[1]:
return "" return ""
else: else:
if os.uname()[0] == "Linux":
rtcmd = "#/sbin/ip route add %s via" % dst rtcmd = "#/sbin/ip route add %s via" % dst
else:
raise Exception("unknown platform")
return "%s %s" % (rtcmd, net[1]) return "%s %s" % (rtcmd, net[1])
class SshService(UtilService): class SshService(UtilService):
name = "SSH" name: str = "SSH"
configs = ("startsshd.sh", "/etc/ssh/sshd_config") configs: Tuple[str, ...] = ("startsshd.sh", "/etc/ssh/sshd_config")
dirs = ("/etc/ssh", "/var/run/sshd") dirs: Tuple[str, ...] = ("/etc/ssh", "/var/run/sshd")
startup = ("sh startsshd.sh",) startup: Tuple[str, ...] = ("sh startsshd.sh",)
shutdown = ("killall sshd",) shutdown: Tuple[str, ...] = ("killall sshd",)
validate = () validation_mode: ServiceMode = ServiceMode.BLOCKING
validation_mode = ServiceMode.BLOCKING
@classmethod @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 Use a startup script for launching sshd in order to wait for host
key generation. key generation.
@ -228,15 +214,15 @@ UseDNS no
class DhcpService(UtilService): class DhcpService(UtilService):
name = "DHCP" name: str = "DHCP"
configs = ("/etc/dhcp/dhcpd.conf",) configs: Tuple[str, ...] = ("/etc/dhcp/dhcpd.conf",)
dirs = ("/etc/dhcp", "/var/lib/dhcp") dirs: Tuple[str, ...] = ("/etc/dhcp", "/var/lib/dhcp")
startup = ("touch /var/lib/dhcp/dhcpd.leases", "dhcpd") startup: Tuple[str, ...] = ("touch /var/lib/dhcp/dhcpd.leases", "dhcpd")
shutdown = ("killall dhcpd",) shutdown: Tuple[str, ...] = ("killall dhcpd",)
validate = ("pidof dhcpd",) validate: Tuple[str, ...] = ("pidof dhcpd",)
@classmethod @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 Generate a dhcpd config file using the network address of
each interface. each interface.
@ -261,7 +247,7 @@ ddns-update-style none;
return cfg return cfg
@staticmethod @staticmethod
def subnetentry(x): def subnetentry(x: str) -> str:
""" """
Generate a subnet declaration block given an IPv4 prefix string Generate a subnet declaration block given an IPv4 prefix string
for inclusion in the dhcpd3 config file. for inclusion in the dhcpd3 config file.
@ -297,14 +283,14 @@ class DhcpClientService(UtilService):
Use a DHCP client for all interfaces for addressing. Use a DHCP client for all interfaces for addressing.
""" """
name = "DHCPClient" name: str = "DHCPClient"
configs = ("startdhcpclient.sh",) configs: Tuple[str, ...] = ("startdhcpclient.sh",)
startup = ("sh startdhcpclient.sh",) startup: Tuple[str, ...] = ("sh startdhcpclient.sh",)
shutdown = ("killall dhclient",) shutdown: Tuple[str, ...] = ("killall dhclient",)
validate = ("pidof dhclient",) validate: Tuple[str, ...] = ("pidof dhclient",)
@classmethod @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. Generate a script to invoke dhclient on all interfaces.
""" """
@ -313,7 +299,6 @@ class DhcpClientService(UtilService):
cfg += "# uncomment this mkdir line and symlink line to enable client-" cfg += "# uncomment this mkdir line and symlink line to enable client-"
cfg += "side DNS\n# resolution based on the DHCP server response.\n" cfg += "side DNS\n# resolution based on the DHCP server response.\n"
cfg += "#mkdir -p /var/run/resolvconf/interface\n" cfg += "#mkdir -p /var/run/resolvconf/interface\n"
for iface in node.get_ifaces(control=False): for iface in node.get_ifaces(control=False):
cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % iface.name cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % iface.name
cfg += " /var/run/resolvconf/resolv.conf\n" cfg += " /var/run/resolvconf/resolv.conf\n"
@ -327,15 +312,15 @@ class FtpService(UtilService):
Start a vsftpd server. Start a vsftpd server.
""" """
name = "FTP" name: str = "FTP"
configs = ("vsftpd.conf",) configs: Tuple[str, ...] = ("vsftpd.conf",)
dirs = ("/var/run/vsftpd/empty", "/var/ftp") dirs: Tuple[str, ...] = ("/var/run/vsftpd/empty", "/var/ftp")
startup = ("vsftpd ./vsftpd.conf",) startup: Tuple[str, ...] = ("vsftpd ./vsftpd.conf",)
shutdown = ("killall vsftpd",) shutdown: Tuple[str, ...] = ("killall vsftpd",)
validate = ("pidof vsftpd",) validate: Tuple[str, ...] = ("pidof vsftpd",)
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
""" """
Generate a vsftpd.conf configuration file. Generate a vsftpd.conf configuration file.
""" """
@ -360,13 +345,13 @@ class HttpService(UtilService):
Start an apache server. Start an apache server.
""" """
name = "HTTP" name: str = "HTTP"
configs = ( configs: Tuple[str, ...] = (
"/etc/apache2/apache2.conf", "/etc/apache2/apache2.conf",
"/etc/apache2/envvars", "/etc/apache2/envvars",
"/var/www/index.html", "/var/www/index.html",
) )
dirs = ( dirs: Tuple[str, ...] = (
"/etc/apache2", "/etc/apache2",
"/var/run/apache2", "/var/run/apache2",
"/var/log/apache2", "/var/log/apache2",
@ -374,14 +359,14 @@ class HttpService(UtilService):
"/var/lock/apache2", "/var/lock/apache2",
"/var/www", "/var/www",
) )
startup = ("chown www-data /var/lock/apache2", "apache2ctl start") startup: Tuple[str, ...] = ("chown www-data /var/lock/apache2", "apache2ctl start")
shutdown = ("apache2ctl stop",) shutdown: Tuple[str, ...] = ("apache2ctl stop",)
validate = ("pidof apache2",) validate: Tuple[str, ...] = ("pidof apache2",)
APACHEVER22: int = 22
APACHEVER22, APACHEVER24 = (22, 24) APACHEVER24: int = 24
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
""" """
Generate an apache2.conf configuration file. Generate an apache2.conf configuration file.
""" """
@ -395,7 +380,7 @@ class HttpService(UtilService):
return "" return ""
@classmethod @classmethod
def detectversionfromcmd(cls): def detectversionfromcmd(cls) -> int:
""" """
Detect the apache2 version using the 'a2query' command. Detect the apache2 version using the 'a2query' command.
""" """
@ -405,14 +390,12 @@ class HttpService(UtilService):
except CoreCommandError as e: except CoreCommandError as e:
status = e.returncode status = e.returncode
result = e.stderr result = e.stderr
if status == 0 and result[:3] == "2.4": if status == 0 and result[:3] == "2.4":
return cls.APACHEVER24 return cls.APACHEVER24
return cls.APACHEVER22 return cls.APACHEVER22
@classmethod @classmethod
def generateapache2conf(cls, node, filename): def generateapache2conf(cls, node: CoreNode, filename: str) -> str:
lockstr = { lockstr = {
cls.APACHEVER22: "LockFile ${APACHE_LOCK_DIR}/accept.lock\n", cls.APACHEVER22: "LockFile ${APACHE_LOCK_DIR}/accept.lock\n",
cls.APACHEVER24: "Mutex file:${APACHE_LOCK_DIR} default\n", cls.APACHEVER24: "Mutex file:${APACHE_LOCK_DIR} default\n",
@ -421,22 +404,18 @@ class HttpService(UtilService):
cls.APACHEVER22: "", cls.APACHEVER22: "",
cls.APACHEVER24: "LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n", cls.APACHEVER24: "LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n",
} }
permstr = { permstr = {
cls.APACHEVER22: " Order allow,deny\n Deny from all\n Satisfy all\n", cls.APACHEVER22: " Order allow,deny\n Deny from all\n Satisfy all\n",
cls.APACHEVER24: " Require all denied\n", cls.APACHEVER24: " Require all denied\n",
} }
authstr = { authstr = {
cls.APACHEVER22: "LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so\n", 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", cls.APACHEVER24: "LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so\n",
} }
permstr2 = { permstr2 = {
cls.APACHEVER22: "\t\tOrder allow,deny\n\t\tallow from all\n", cls.APACHEVER22: "\t\tOrder allow,deny\n\t\tallow from all\n",
cls.APACHEVER24: "\t\tRequire all granted\n", cls.APACHEVER24: "\t\tRequire all granted\n",
} }
version = cls.detectversionfromcmd() version = cls.detectversionfromcmd()
cfg = "# apache2.conf generated by utility.py:HttpService\n" cfg = "# apache2.conf generated by utility.py:HttpService\n"
cfg += lockstr[version] cfg += lockstr[version]
@ -552,7 +531,7 @@ TraceEnable Off
return cfg return cfg
@classmethod @classmethod
def generateenvvars(cls, node, filename): def generateenvvars(cls, node: CoreNode, filename: str) -> str:
return """\ return """\
# this file is used by apache2ctl - generated by utility.py:HttpService # this file is used by apache2ctl - generated by utility.py:HttpService
# these settings come from a default Ubuntu apache2 installation # these settings come from a default Ubuntu apache2 installation
@ -567,7 +546,7 @@ export LANG
""" """
@classmethod @classmethod
def generatehtml(cls, node, filename): def generatehtml(cls, node: CoreNode, filename: str) -> str:
body = ( body = (
"""\ """\
<!-- generated by utility.py:HttpService --> <!-- generated by utility.py:HttpService -->
@ -587,16 +566,15 @@ class PcapService(UtilService):
Pcap service for logging packets. Pcap service for logging packets.
""" """
name = "pcap" name: str = "pcap"
configs = ("pcap.sh",) configs: Tuple[str, ...] = ("pcap.sh",)
dirs = () startup: Tuple[str, ...] = ("sh pcap.sh start",)
startup = ("sh pcap.sh start",) shutdown: Tuple[str, ...] = ("sh pcap.sh stop",)
shutdown = ("sh pcap.sh stop",) validate: Tuple[str, ...] = ("pidof tcpdump",)
validate = ("pidof tcpdump",) meta: str = "logs network traffic to pcap packet capture files"
meta = "logs network traffic to pcap packet capture files"
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
""" """
Generate a startpcap.sh traffic logging script. Generate a startpcap.sh traffic logging script.
""" """
@ -630,15 +608,17 @@ fi;
class RadvdService(UtilService): class RadvdService(UtilService):
name = "radvd" name: str = "radvd"
configs = ("/etc/radvd/radvd.conf",) configs: Tuple[str, ...] = ("/etc/radvd/radvd.conf",)
dirs = ("/etc/radvd",) dirs: Tuple[str, ...] = ("/etc/radvd",)
startup = ("radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",) startup: Tuple[str, ...] = (
shutdown = ("pkill radvd",) "radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",
validate = ("pidof radvd",) )
shutdown: Tuple[str, ...] = ("pkill radvd",)
validate: Tuple[str, ...] = ("pidof radvd",)
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
""" """
Generate a RADVD router advertisement daemon config file Generate a RADVD router advertisement daemon config file
using the network address of each interface. using the network address of each interface.
@ -678,7 +658,7 @@ interface %s
return cfg return cfg
@staticmethod @staticmethod
def subnetentry(x): def subnetentry(x: str) -> str:
""" """
Generate a subnet declaration block given an IPv6 prefix string Generate a subnet declaration block given an IPv6 prefix string
for inclusion in the RADVD config file. for inclusion in the RADVD config file.
@ -695,14 +675,14 @@ class AtdService(UtilService):
Atd service for scheduling at jobs Atd service for scheduling at jobs
""" """
name = "atd" name: str = "atd"
configs = ("startatd.sh",) configs: Tuple[str, ...] = ("startatd.sh",)
dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool") dirs: Tuple[str, ...] = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool")
startup = ("sh startatd.sh",) startup: Tuple[str, ...] = ("sh startatd.sh",)
shutdown = ("pkill atd",) shutdown: Tuple[str, ...] = ("pkill atd",)
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
return """ return """
#!/bin/sh #!/bin/sh
echo 00001 > /var/spool/cron/atjobs/.SEQ echo 00001 > /var/spool/cron/atjobs/.SEQ
@ -717,5 +697,5 @@ class UserDefinedService(UtilService):
Dummy service allowing customization of anything. Dummy service allowing customization of anything.
""" """
name = "UserDefined" name: str = "UserDefined"
meta = "Customize this service to do anything upon startup." meta: str = "Customize this service to do anything upon startup."

View file

@ -2,10 +2,12 @@
xorp.py: defines routing services provided by the XORP routing suite. xorp.py: defines routing services provided by the XORP routing suite.
""" """
import logging from typing import Optional, Tuple
import netaddr import netaddr
from core.nodes.base import CoreNode
from core.nodes.interface import CoreInterface
from core.services.coreservices import CoreService from core.services.coreservices import CoreService
@ -15,20 +17,20 @@ class XorpRtrmgr(CoreService):
enabled XORP services, and launches necessary daemons upon startup. enabled XORP services, and launches necessary daemons upon startup.
""" """
name = "xorp_rtrmgr" name: str = "xorp_rtrmgr"
executables = ("xorp_rtrmgr",) group: str = "XORP"
group = "XORP" executables: Tuple[str, ...] = ("xorp_rtrmgr",)
dirs = ("/etc/xorp",) dirs: Tuple[str, ...] = ("/etc/xorp",)
configs = ("/etc/xorp/config.boot",) configs: Tuple[str, ...] = ("/etc/xorp/config.boot",)
startup = ( startup: Tuple[str, ...] = (
"xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid" "xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid"
% (configs[0], name, name), % (configs[0], name, name),
) )
shutdown = ("killall xorp_rtrmgr",) shutdown: Tuple[str, ...] = ("killall xorp_rtrmgr",)
validate = ("pidof xorp_rtrmgr",) validate: Tuple[str, ...] = ("pidof xorp_rtrmgr",)
@classmethod @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 Returns config.boot configuration file text. Other services that
depend on this will have generatexorpconfig() hooks that are depend on this will have generatexorpconfig() hooks that are
@ -45,16 +47,15 @@ class XorpRtrmgr(CoreService):
cfg += "}\n\n" cfg += "}\n\n"
for s in node.services: for s in node.services:
try: if cls.name not in s.dependencies:
s.dependencies.index(cls.name) continue
cfg += s.generatexorpconfig(node) if not (isinstance(s, XorpService) or issubclass(s, XorpService)):
except ValueError: continue
logging.exception("error getting value from service: %s", cls.name) cfg += s.generate_xorp_config(node)
return cfg return cfg
@staticmethod @staticmethod
def addrstr(x): def addrstr(x: str) -> str:
""" """
helper for mapping IP addresses to XORP config statements helper for mapping IP addresses to XORP config statements
""" """
@ -65,7 +66,7 @@ class XorpRtrmgr(CoreService):
return cfg return cfg
@staticmethod @staticmethod
def lladdrstr(iface): def lladdrstr(iface: CoreInterface) -> str:
""" """
helper for adding link-local address entries (required by OSPFv3) helper for adding link-local address entries (required by OSPFv3)
""" """
@ -81,18 +82,16 @@ class XorpService(CoreService):
common to XORP's routing daemons. common to XORP's routing daemons.
""" """
name = None name: Optional[str] = None
executables = ("xorp_rtrmgr",) group: str = "XORP"
group = "XORP" executables: Tuple[str, ...] = ("xorp_rtrmgr",)
dependencies = ("xorp_rtrmgr",) dependencies: Tuple[str, ...] = ("xorp_rtrmgr",)
dirs = () meta: str = (
configs = () "The config file for this service can be found in the xorp_rtrmgr service."
startup = () )
shutdown = ()
meta = "The config file for this service can be found in the xorp_rtrmgr service."
@staticmethod @staticmethod
def fea(forwarding): def fea(forwarding: str) -> str:
""" """
Helper to add a forwarding engine entry to the config file. Helper to add a forwarding engine entry to the config file.
""" """
@ -104,17 +103,14 @@ class XorpService(CoreService):
return cfg return cfg
@staticmethod @staticmethod
def mfea(forwarding, ifaces): def mfea(forwarding, node: CoreNode) -> str:
""" """
Helper to add a multicast forwarding engine entry to the config file. Helper to add a multicast forwarding engine entry to the config file.
""" """
names = [] names = []
for iface in ifaces: for iface in node.get_ifaces(control=False):
if hasattr(iface, "control") and iface.control is True:
continue
names.append(iface.name) names.append(iface.name)
names.append("register_vif") names.append("register_vif")
cfg = "plumbing {\n" cfg = "plumbing {\n"
cfg += " %s {\n" % forwarding cfg += " %s {\n" % forwarding
for name in names: for name in names:
@ -128,7 +124,7 @@ class XorpService(CoreService):
return cfg return cfg
@staticmethod @staticmethod
def policyexportconnected(): def policyexportconnected() -> str:
""" """
Helper to add a policy statement for exporting connected routes. Helper to add a policy statement for exporting connected routes.
""" """
@ -144,7 +140,7 @@ class XorpService(CoreService):
return cfg return cfg
@staticmethod @staticmethod
def routerid(node): def router_id(node: CoreNode) -> str:
""" """
Helper to return the first IPv4 address of a node as its router ID. Helper to return the first IPv4 address of a node as its router ID.
""" """
@ -153,15 +149,14 @@ class XorpService(CoreService):
a = a.split("/")[0] a = a.split("/")[0]
if netaddr.valid_ipv4(a): if netaddr.valid_ipv4(a):
return a return a
# raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0" return "0.0.0.0"
@classmethod @classmethod
def generate_config(cls, node, filename): def generate_config(cls, node: CoreNode, filename: str) -> str:
return "" return ""
@classmethod @classmethod
def generatexorpconfig(cls, node): def generate_xorp_config(cls, node: CoreNode) -> str:
return "" return ""
@ -172,12 +167,12 @@ class XorpOspfv2(XorpService):
unified XORP configuration file. unified XORP configuration file.
""" """
name = "XORP_OSPFv2" name: str = "XORP_OSPFv2"
@classmethod @classmethod
def generatexorpconfig(cls, node): def generate_xorp_config(cls, node: CoreNode) -> str:
cfg = cls.fea("unicast-forwarding4") cfg = cls.fea("unicast-forwarding4")
rtrid = cls.routerid(node) rtrid = cls.router_id(node)
cfg += "\nprotocols {\n" cfg += "\nprotocols {\n"
cfg += " ospf4 {\n" cfg += " ospf4 {\n"
cfg += "\trouter-id: %s\n" % rtrid cfg += "\trouter-id: %s\n" % rtrid
@ -206,12 +201,12 @@ class XorpOspfv3(XorpService):
unified XORP configuration file. unified XORP configuration file.
""" """
name = "XORP_OSPFv3" name: str = "XORP_OSPFv3"
@classmethod @classmethod
def generatexorpconfig(cls, node): def generate_xorp_config(cls, node: CoreNode) -> str:
cfg = cls.fea("unicast-forwarding6") cfg = cls.fea("unicast-forwarding6")
rtrid = cls.routerid(node) rtrid = cls.router_id(node)
cfg += "\nprotocols {\n" cfg += "\nprotocols {\n"
cfg += " ospf6 0 { /* Instance ID 0 */\n" cfg += " ospf6 0 { /* Instance ID 0 */\n"
cfg += "\trouter-id: %s\n" % rtrid cfg += "\trouter-id: %s\n" % rtrid
@ -232,16 +227,16 @@ class XorpBgp(XorpService):
IPv4 inter-domain routing. AS numbers and peers must be customized. IPv4 inter-domain routing. AS numbers and peers must be customized.
""" """
name = "XORP_BGP" name: str = "XORP_BGP"
custom_needed = True custom_needed: bool = True
@classmethod @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 = "/* This is a sample config that should be customized with\n"
cfg += " appropriate AS numbers and peers */\n" cfg += " appropriate AS numbers and peers */\n"
cfg += cls.fea("unicast-forwarding4") cfg += cls.fea("unicast-forwarding4")
cfg += cls.policyexportconnected() cfg += cls.policyexportconnected()
rtrid = cls.routerid(node) rtrid = cls.router_id(node)
cfg += "\nprotocols {\n" cfg += "\nprotocols {\n"
cfg += " bgp {\n" cfg += " bgp {\n"
cfg += "\tbgp-id: %s\n" % rtrid cfg += "\tbgp-id: %s\n" % rtrid
@ -262,10 +257,10 @@ class XorpRip(XorpService):
RIP IPv4 unicast routing. RIP IPv4 unicast routing.
""" """
name = "XORP_RIP" name: str = "XORP_RIP"
@classmethod @classmethod
def generatexorpconfig(cls, node): def generate_xorp_config(cls, node: CoreNode) -> str:
cfg = cls.fea("unicast-forwarding4") cfg = cls.fea("unicast-forwarding4")
cfg += cls.policyexportconnected() cfg += cls.policyexportconnected()
cfg += "\nprotocols {\n" cfg += "\nprotocols {\n"
@ -293,10 +288,10 @@ class XorpRipng(XorpService):
RIP NG IPv6 unicast routing. RIP NG IPv6 unicast routing.
""" """
name = "XORP_RIPNG" name: str = "XORP_RIPNG"
@classmethod @classmethod
def generatexorpconfig(cls, node): def generate_xorp_config(cls, node: CoreNode) -> str:
cfg = cls.fea("unicast-forwarding6") cfg = cls.fea("unicast-forwarding6")
cfg += cls.policyexportconnected() cfg += cls.policyexportconnected()
cfg += "\nprotocols {\n" cfg += "\nprotocols {\n"
@ -320,12 +315,11 @@ class XorpPimSm4(XorpService):
PIM Sparse Mode IPv4 multicast routing. PIM Sparse Mode IPv4 multicast routing.
""" """
name = "XORP_PIMSM4" name: str = "XORP_PIMSM4"
@classmethod @classmethod
def generatexorpconfig(cls, node): def generate_xorp_config(cls, node: CoreNode) -> str:
cfg = cls.mfea("mfea4", node.get_ifaces()) cfg = cls.mfea("mfea4", node)
cfg += "\nprotocols {\n" cfg += "\nprotocols {\n"
cfg += " igmp {\n" cfg += " igmp {\n"
names = [] names = []
@ -338,7 +332,6 @@ class XorpPimSm4(XorpService):
cfg += "\t}\n" cfg += "\t}\n"
cfg += " }\n" cfg += " }\n"
cfg += "}\n" cfg += "}\n"
cfg += "\nprotocols {\n" cfg += "\nprotocols {\n"
cfg += " pimsm4 {\n" cfg += " pimsm4 {\n"
@ -361,10 +354,8 @@ class XorpPimSm4(XorpService):
cfg += "\t\t}\n" cfg += "\t\t}\n"
cfg += "\t }\n" cfg += "\t }\n"
cfg += "\t}\n" cfg += "\t}\n"
cfg += " }\n" cfg += " }\n"
cfg += "}\n" cfg += "}\n"
cfg += "\nprotocols {\n" cfg += "\nprotocols {\n"
cfg += " fib2mrib {\n" cfg += " fib2mrib {\n"
cfg += "\tdisable: false\n" cfg += "\tdisable: false\n"
@ -378,12 +369,11 @@ class XorpPimSm6(XorpService):
PIM Sparse Mode IPv6 multicast routing. PIM Sparse Mode IPv6 multicast routing.
""" """
name = "XORP_PIMSM6" name: str = "XORP_PIMSM6"
@classmethod @classmethod
def generatexorpconfig(cls, node): def generate_xorp_config(cls, node: CoreNode) -> str:
cfg = cls.mfea("mfea6", node.get_ifaces()) cfg = cls.mfea("mfea6", node)
cfg += "\nprotocols {\n" cfg += "\nprotocols {\n"
cfg += " mld {\n" cfg += " mld {\n"
names = [] names = []
@ -396,7 +386,6 @@ class XorpPimSm6(XorpService):
cfg += "\t}\n" cfg += "\t}\n"
cfg += " }\n" cfg += " }\n"
cfg += "}\n" cfg += "}\n"
cfg += "\nprotocols {\n" cfg += "\nprotocols {\n"
cfg += " pimsm6 {\n" cfg += " pimsm6 {\n"
@ -419,10 +408,8 @@ class XorpPimSm6(XorpService):
cfg += "\t\t}\n" cfg += "\t\t}\n"
cfg += "\t }\n" cfg += "\t }\n"
cfg += "\t}\n" cfg += "\t}\n"
cfg += " }\n" cfg += " }\n"
cfg += "}\n" cfg += "}\n"
cfg += "\nprotocols {\n" cfg += "\nprotocols {\n"
cfg += " fib2mrib {\n" cfg += " fib2mrib {\n"
cfg += "\tdisable: false\n" cfg += "\tdisable: false\n"
@ -436,12 +423,12 @@ class XorpOlsr(XorpService):
OLSR IPv4 unicast MANET routing. OLSR IPv4 unicast MANET routing.
""" """
name = "XORP_OLSR" name: str = "XORP_OLSR"
@classmethod @classmethod
def generatexorpconfig(cls, node): def generate_xorp_config(cls, node: CoreNode) -> str:
cfg = cls.fea("unicast-forwarding4") cfg = cls.fea("unicast-forwarding4")
rtrid = cls.routerid(node) rtrid = cls.router_id(node)
cfg += "\nprotocols {\n" cfg += "\nprotocols {\n"
cfg += " olsr4 {\n" cfg += " olsr4 {\n"
cfg += "\tmain-address: %s\n" % rtrid cfg += "\tmain-address: %s\n" % rtrid