Merge branch 'develop' into ovs
This commit is contained in:
commit
06e145f508
487 changed files with 49691 additions and 30722 deletions
|
@ -6,7 +6,7 @@ __all__ is automatically loaded by the main core module.
|
|||
"""
|
||||
import os
|
||||
|
||||
from core.service import ServiceManager
|
||||
from core.services.coreservices import ServiceManager
|
||||
|
||||
_PATH = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
@ -15,6 +15,6 @@ def load():
|
|||
"""
|
||||
Loads all services from the modules that reside under core.services.
|
||||
|
||||
:return: nothing
|
||||
:return: list of services that failed to load
|
||||
"""
|
||||
ServiceManager.add_services(_PATH)
|
||||
return ServiceManager.add_services(_PATH)
|
||||
|
|
|
@ -1,31 +1,32 @@
|
|||
"""
|
||||
bird.py: defines routing services provided by the BIRD Internet Routing Daemon.
|
||||
"""
|
||||
import netaddr
|
||||
|
||||
from core.service import CoreService
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
|
||||
class Bird(CoreService):
|
||||
"""
|
||||
Bird router support
|
||||
"""
|
||||
_name = "bird"
|
||||
_group = "BIRD"
|
||||
_depends = ()
|
||||
_dirs = ("/etc/bird",)
|
||||
_configs = ("/etc/bird/bird.conf",)
|
||||
_startindex = 35
|
||||
_startup = ("bird -c %s" % (_configs[0]),)
|
||||
_shutdown = ("killall bird",)
|
||||
_validate = ("pidof bird",)
|
||||
|
||||
name = "bird"
|
||||
executables = ("bird",)
|
||||
group = "BIRD"
|
||||
dirs = ("/etc/bird",)
|
||||
configs = ("/etc/bird/bird.conf",)
|
||||
startup = ("bird -c %s" % (configs[0]),)
|
||||
shutdown = ("killall bird",)
|
||||
validate = ("pidof bird",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Return the bird.conf file contents.
|
||||
"""
|
||||
if filename == cls._configs[0]:
|
||||
return cls.generateBirdConf(node, services)
|
||||
if filename == cls.configs[0]:
|
||||
return cls.generateBirdConf(node)
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
|
@ -35,16 +36,17 @@ class Bird(CoreService):
|
|||
Helper to return the first IPv4 address of a node as its router ID.
|
||||
"""
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control == True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
for a in ifc.addrlist:
|
||||
if a.find(".") >= 0:
|
||||
return a.split('/')[0]
|
||||
a = a.split("/")[0]
|
||||
if netaddr.valid_ipv4(a):
|
||||
return a
|
||||
# raise ValueError, "no IPv4 address found for router ID"
|
||||
return "0.0.0.0"
|
||||
|
||||
@classmethod
|
||||
def generateBirdConf(cls, node, services):
|
||||
def generateBirdConf(cls, node):
|
||||
"""
|
||||
Returns configuration file text. Other services that depend on bird
|
||||
will have generatebirdifcconfig() and generatebirdconfig()
|
||||
|
@ -73,11 +75,14 @@ protocol device {
|
|||
scan time 10; # Scan interfaces every 10 seconds
|
||||
}
|
||||
|
||||
""" % (cls._name, cls.routerid(node))
|
||||
""" % (
|
||||
cls.name,
|
||||
cls.routerid(node),
|
||||
)
|
||||
|
||||
# Generate protocol specific configurations
|
||||
for s in services:
|
||||
if cls._name not in s._depends:
|
||||
for s in node.services:
|
||||
if cls.name not in s.dependencies:
|
||||
continue
|
||||
cfg += s.generatebirdconfig(node)
|
||||
|
||||
|
@ -90,15 +95,15 @@ class BirdService(CoreService):
|
|||
common to Bird's routing daemons.
|
||||
"""
|
||||
|
||||
_name = None
|
||||
_group = "BIRD"
|
||||
_depends = ("bird",)
|
||||
_dirs = ()
|
||||
_configs = ()
|
||||
_startindex = 40
|
||||
_startup = ()
|
||||
_shutdown = ()
|
||||
_meta = "The config file for this service can be found in the bird service."
|
||||
name = None
|
||||
executables = ("bird",)
|
||||
group = "BIRD"
|
||||
dependencies = ("bird",)
|
||||
dirs = ()
|
||||
configs = ()
|
||||
startup = ()
|
||||
shutdown = ()
|
||||
meta = "The config file for this service can be found in the bird service."
|
||||
|
||||
@classmethod
|
||||
def generatebirdconfig(cls, node):
|
||||
|
@ -106,14 +111,15 @@ class BirdService(CoreService):
|
|||
|
||||
@classmethod
|
||||
def generatebirdifcconfig(cls, node):
|
||||
''' 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
|
||||
everywhere.
|
||||
'''
|
||||
"""
|
||||
cfg = ""
|
||||
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control == True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += ' interface "%s";\n' % ifc.name
|
||||
|
||||
|
@ -125,8 +131,8 @@ class BirdBgp(BirdService):
|
|||
BGP BIRD Service (configuration generation)
|
||||
"""
|
||||
|
||||
_name = "BIRD_BGP"
|
||||
_custom_needed = True
|
||||
name = "BIRD_BGP"
|
||||
custom_needed = True
|
||||
|
||||
@classmethod
|
||||
def generatebirdconfig(cls, node):
|
||||
|
@ -156,22 +162,22 @@ class BirdOspf(BirdService):
|
|||
OSPF BIRD Service (configuration generation)
|
||||
"""
|
||||
|
||||
_name = "BIRD_OSPFv2"
|
||||
name = "BIRD_OSPFv2"
|
||||
|
||||
@classmethod
|
||||
def generatebirdconfig(cls, node):
|
||||
cfg = 'protocol ospf {\n'
|
||||
cfg += ' export filter {\n'
|
||||
cfg += ' if source = RTS_BGP then {\n'
|
||||
cfg += ' ospf_metric1 = 100;\n'
|
||||
cfg += ' accept;\n'
|
||||
cfg += ' }\n'
|
||||
cfg += ' accept;\n'
|
||||
cfg += ' };\n'
|
||||
cfg += ' area 0.0.0.0 {\n'
|
||||
cfg = "protocol ospf {\n"
|
||||
cfg += " export filter {\n"
|
||||
cfg += " if source = RTS_BGP then {\n"
|
||||
cfg += " ospf_metric1 = 100;\n"
|
||||
cfg += " accept;\n"
|
||||
cfg += " }\n"
|
||||
cfg += " accept;\n"
|
||||
cfg += " };\n"
|
||||
cfg += " area 0.0.0.0 {\n"
|
||||
cfg += cls.generatebirdifcconfig(node)
|
||||
cfg += ' };\n'
|
||||
cfg += '}\n\n'
|
||||
cfg += " };\n"
|
||||
cfg += "}\n\n"
|
||||
|
||||
return cfg
|
||||
|
||||
|
@ -181,25 +187,25 @@ class BirdRadv(BirdService):
|
|||
RADV BIRD Service (configuration generation)
|
||||
"""
|
||||
|
||||
_name = "BIRD_RADV"
|
||||
name = "BIRD_RADV"
|
||||
|
||||
@classmethod
|
||||
def generatebirdconfig(cls, node):
|
||||
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 += ' # auto configuration on all interfaces\n'
|
||||
cfg += "protocol radv {\n"
|
||||
cfg += " # auto configuration on all interfaces\n"
|
||||
cfg += cls.generatebirdifcconfig(node)
|
||||
cfg += ' # Advertise DNS\n'
|
||||
cfg += ' rdnss {\n'
|
||||
cfg += '# lifetime mult 10;\n'
|
||||
cfg += '# lifetime mult 10;\n'
|
||||
cfg += '# ns 2001:0DB8:1234::11;\n'
|
||||
cfg += '# ns 2001:0DB8:1234::11;\n'
|
||||
cfg += '# ns 2001:0DB8:1234::12;\n'
|
||||
cfg += '# ns 2001:0DB8:1234::12;\n'
|
||||
cfg += ' };\n'
|
||||
cfg += '}\n\n'
|
||||
cfg += " # Advertise DNS\n"
|
||||
cfg += " rdnss {\n"
|
||||
cfg += "# lifetime mult 10;\n"
|
||||
cfg += "# lifetime mult 10;\n"
|
||||
cfg += "# ns 2001:0DB8:1234::11;\n"
|
||||
cfg += "# ns 2001:0DB8:1234::11;\n"
|
||||
cfg += "# ns 2001:0DB8:1234::12;\n"
|
||||
cfg += "# ns 2001:0DB8:1234::12;\n"
|
||||
cfg += " };\n"
|
||||
cfg += "}\n\n"
|
||||
|
||||
return cfg
|
||||
|
||||
|
@ -209,19 +215,19 @@ class BirdRip(BirdService):
|
|||
RIP BIRD Service (configuration generation)
|
||||
"""
|
||||
|
||||
_name = "BIRD_RIP"
|
||||
name = "BIRD_RIP"
|
||||
|
||||
@classmethod
|
||||
def generatebirdconfig(cls, node):
|
||||
cfg = 'protocol rip {\n'
|
||||
cfg += ' period 10;\n'
|
||||
cfg += ' garbage time 60;\n'
|
||||
cfg = "protocol rip {\n"
|
||||
cfg += " period 10;\n"
|
||||
cfg += " garbage time 60;\n"
|
||||
cfg += cls.generatebirdifcconfig(node)
|
||||
cfg += ' honor neighbor;\n'
|
||||
cfg += ' authentication none;\n'
|
||||
cfg += ' import all;\n'
|
||||
cfg += ' export all;\n'
|
||||
cfg += '}\n\n'
|
||||
cfg += " honor neighbor;\n"
|
||||
cfg += " authentication none;\n"
|
||||
cfg += " import all;\n"
|
||||
cfg += " export all;\n"
|
||||
cfg += "}\n\n"
|
||||
|
||||
return cfg
|
||||
|
||||
|
@ -231,15 +237,15 @@ class BirdStatic(BirdService):
|
|||
Static Bird Service (configuration generation)
|
||||
"""
|
||||
|
||||
_name = "BIRD_static"
|
||||
_custom_needed = True
|
||||
name = "BIRD_static"
|
||||
custom_needed = True
|
||||
|
||||
@classmethod
|
||||
def generatebirdconfig(cls, node):
|
||||
cfg = '/* This is a sample config that must be customized */\n'
|
||||
cfg += 'protocol static {\n'
|
||||
cfg += '# route 0.0.0.0/0 via 198.51.100.130; # Default route. Do NOT advertise on BGP !\n'
|
||||
cfg += '# route 203.0.113.0/24 reject; # Sink route\n'
|
||||
cfg = "/* This is a sample config that must be customized */\n"
|
||||
cfg += "protocol static {\n"
|
||||
cfg += "# route 0.0.0.0/0 via 198.51.100.130; # Default route. Do NOT advertise on BGP !\n"
|
||||
cfg += "# route 203.0.113.0/24 reject; # Sink route\n"
|
||||
cfg += '# route 10.2.0.0/24 via "arc0"; # Secondary network\n'
|
||||
cfg += '}\n\n'
|
||||
cfg += "}\n\n"
|
||||
return cfg
|
||||
|
|
913
daemon/core/services/coreservices.py
Normal file
913
daemon/core/services/coreservices.py
Normal file
|
@ -0,0 +1,913 @@
|
|||
"""
|
||||
Definition of CoreService class that is subclassed to define
|
||||
startup services and routing for nodes. A service is typically a daemon
|
||||
program launched when a node starts that provides some sort of service.
|
||||
The CoreServices class handles configuration messages for sending
|
||||
a list of available services to the GUI and for configuring individual
|
||||
services.
|
||||
"""
|
||||
|
||||
import enum
|
||||
import logging
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Iterable, List, Tuple, Type
|
||||
|
||||
from core import utils
|
||||
from core.constants import which
|
||||
from core.emulator.data import FileData
|
||||
from core.emulator.enumerations import ExceptionLevels, MessageFlags, RegisterTlvs
|
||||
from core.errors import CoreCommandError
|
||||
from core.nodes.base import CoreNode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.emulator.session import Session
|
||||
|
||||
|
||||
class ServiceBootError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ServiceMode(enum.Enum):
|
||||
BLOCKING = 0
|
||||
NON_BLOCKING = 1
|
||||
TIMER = 2
|
||||
|
||||
|
||||
class ServiceDependencies:
|
||||
"""
|
||||
Can generate boot paths for services, based on their dependencies. Will validate
|
||||
that all services will be booted and that all dependencies exist within the services provided.
|
||||
"""
|
||||
|
||||
def __init__(self, services: List["CoreService"]) -> None:
|
||||
# helpers to check validity
|
||||
self.dependents = {}
|
||||
self.booted = set()
|
||||
self.node_services = {}
|
||||
for service in services:
|
||||
self.node_services[service.name] = service
|
||||
for dependency in service.dependencies:
|
||||
dependents = self.dependents.setdefault(dependency, set())
|
||||
dependents.add(service.name)
|
||||
|
||||
# used to find paths
|
||||
self.path = []
|
||||
self.visited = set()
|
||||
self.visiting = set()
|
||||
|
||||
def boot_paths(self) -> List[List["CoreService"]]:
|
||||
"""
|
||||
Generates the boot paths for the services provided to the class.
|
||||
|
||||
:return: list of services to boot, in order
|
||||
"""
|
||||
paths = []
|
||||
for name in self.node_services:
|
||||
service = self.node_services[name]
|
||||
if service.name in self.booted:
|
||||
logging.debug(
|
||||
"skipping service that will already be booted: %s", service.name
|
||||
)
|
||||
continue
|
||||
|
||||
path = self._start(service)
|
||||
if path:
|
||||
paths.append(path)
|
||||
|
||||
if self.booted != set(self.node_services):
|
||||
raise ValueError(
|
||||
"failure to boot all services: %s != %s"
|
||||
% (self.booted, self.node_services.keys())
|
||||
)
|
||||
|
||||
return paths
|
||||
|
||||
def _reset(self) -> None:
|
||||
self.path = []
|
||||
self.visited.clear()
|
||||
self.visiting.clear()
|
||||
|
||||
def _start(self, service: "CoreService") -> List["CoreService"]:
|
||||
logging.debug("starting service dependency check: %s", service.name)
|
||||
self._reset()
|
||||
return self._visit(service)
|
||||
|
||||
def _visit(self, current_service: "CoreService") -> List["CoreService"]:
|
||||
logging.debug("visiting service(%s): %s", current_service.name, self.path)
|
||||
self.visited.add(current_service.name)
|
||||
self.visiting.add(current_service.name)
|
||||
|
||||
# dive down
|
||||
for service_name in current_service.dependencies:
|
||||
if service_name not in self.node_services:
|
||||
raise ValueError(
|
||||
"required dependency was not included in node services: %s"
|
||||
% service_name
|
||||
)
|
||||
|
||||
if service_name in self.visiting:
|
||||
raise ValueError(
|
||||
"cyclic dependency at service(%s): %s"
|
||||
% (current_service.name, service_name)
|
||||
)
|
||||
|
||||
if service_name not in self.visited:
|
||||
service = self.node_services[service_name]
|
||||
self._visit(service)
|
||||
|
||||
# add service when bottom is found
|
||||
logging.debug("adding service to boot path: %s", current_service.name)
|
||||
self.booted.add(current_service.name)
|
||||
self.path.append(current_service)
|
||||
self.visiting.remove(current_service.name)
|
||||
|
||||
# rise back up
|
||||
for service_name in self.dependents.get(current_service.name, []):
|
||||
if service_name not in self.visited:
|
||||
service = self.node_services[service_name]
|
||||
self._visit(service)
|
||||
|
||||
return self.path
|
||||
|
||||
|
||||
class ServiceShim:
|
||||
keys = [
|
||||
"dirs",
|
||||
"files",
|
||||
"startidx",
|
||||
"cmdup",
|
||||
"cmddown",
|
||||
"cmdval",
|
||||
"meta",
|
||||
"starttime",
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def tovaluelist(cls, node: CoreNode, service: "CoreService") -> str:
|
||||
"""
|
||||
Convert service properties into a string list of key=value pairs,
|
||||
separated by "|".
|
||||
|
||||
:param node: node to get value list for
|
||||
:param service: service to get value list for
|
||||
:return: value list string
|
||||
"""
|
||||
start_time = 0
|
||||
start_index = 0
|
||||
valmap = [
|
||||
service.dirs,
|
||||
service.configs,
|
||||
start_index,
|
||||
service.startup,
|
||||
service.shutdown,
|
||||
service.validate,
|
||||
service.meta,
|
||||
start_time,
|
||||
]
|
||||
if not service.custom:
|
||||
valmap[1] = service.get_configs(node)
|
||||
valmap[3] = service.get_startup(node)
|
||||
vals = ["%s=%s" % (x, y) for x, y in zip(cls.keys, valmap)]
|
||||
return "|".join(vals)
|
||||
|
||||
@classmethod
|
||||
def fromvaluelist(cls, service: "CoreService", values: List[str]) -> None:
|
||||
"""
|
||||
Convert list of values into properties for this instantiated
|
||||
(customized) service.
|
||||
|
||||
:param service: service to get value list for
|
||||
:param values: value list to set properties from
|
||||
:return: nothing
|
||||
"""
|
||||
# TODO: support empty value? e.g. override default meta with ''
|
||||
for key in cls.keys:
|
||||
try:
|
||||
cls.setvalue(service, key, values[cls.keys.index(key)])
|
||||
except IndexError:
|
||||
# old config does not need to have new keys
|
||||
logging.exception("error indexing into key")
|
||||
|
||||
@classmethod
|
||||
def setvalue(cls, service: "CoreService", key: str, value: str) -> None:
|
||||
"""
|
||||
Set values for this service.
|
||||
|
||||
:param service: service to get value list for
|
||||
:param key: key to set value for
|
||||
:param value: value of key to set
|
||||
:return: nothing
|
||||
"""
|
||||
if key not in cls.keys:
|
||||
raise ValueError("key `%s` not in `%s`" % (key, cls.keys))
|
||||
# this handles data conversion to int, string, and tuples
|
||||
if value:
|
||||
if key == "startidx":
|
||||
value = int(value)
|
||||
elif key == "meta":
|
||||
value = str(value)
|
||||
else:
|
||||
value = utils.make_tuple_fromstr(value, str)
|
||||
|
||||
if key == "dirs":
|
||||
service.dirs = value
|
||||
elif key == "files":
|
||||
service.configs = value
|
||||
elif key == "cmdup":
|
||||
service.startup = value
|
||||
elif key == "cmddown":
|
||||
service.shutdown = value
|
||||
elif key == "cmdval":
|
||||
service.validate = value
|
||||
elif key == "meta":
|
||||
service.meta = value
|
||||
|
||||
@classmethod
|
||||
def servicesfromopaque(cls, opaque: str) -> List[str]:
|
||||
"""
|
||||
Build a list of services from an opaque data string.
|
||||
|
||||
:param opaque: opaque data string
|
||||
:return: services
|
||||
"""
|
||||
servicesstring = opaque.split(":")
|
||||
if servicesstring[0] != "service":
|
||||
return []
|
||||
return servicesstring[1].split(",")
|
||||
|
||||
|
||||
class ServiceManager:
|
||||
"""
|
||||
Manages services available for CORE nodes to use.
|
||||
"""
|
||||
|
||||
services = {}
|
||||
|
||||
@classmethod
|
||||
def add(cls, service: "CoreService") -> None:
|
||||
"""
|
||||
Add a service to manager.
|
||||
|
||||
:param service: service to add
|
||||
:return: nothing
|
||||
:raises ValueError: when service cannot be loaded
|
||||
"""
|
||||
name = service.name
|
||||
logging.debug("loading service: class(%s) name(%s)", service.__name__, name)
|
||||
|
||||
# avoid duplicate services
|
||||
if name in cls.services:
|
||||
raise ValueError("duplicate service being added: %s" % name)
|
||||
|
||||
# validate dependent executables are present
|
||||
for executable in service.executables:
|
||||
which(executable, required=True)
|
||||
|
||||
# validate service on load succeeds
|
||||
try:
|
||||
service.on_load()
|
||||
except Exception as e:
|
||||
logging.exception("error during service(%s) on load", service.name)
|
||||
raise ValueError(e)
|
||||
|
||||
# make service available
|
||||
cls.services[name] = service
|
||||
|
||||
@classmethod
|
||||
def get(cls, name: str) -> Type["CoreService"]:
|
||||
"""
|
||||
Retrieve a service from the manager.
|
||||
|
||||
:param name: name of the service to retrieve
|
||||
:return: service if it exists, None otherwise
|
||||
"""
|
||||
return cls.services.get(name)
|
||||
|
||||
@classmethod
|
||||
def add_services(cls, path: str) -> List[str]:
|
||||
"""
|
||||
Method for retrieving all CoreServices from a given path.
|
||||
|
||||
:param path: path to retrieve services from
|
||||
:return: list of core services that failed to load
|
||||
"""
|
||||
service_errors = []
|
||||
services = utils.load_classes(path, CoreService)
|
||||
for service in services:
|
||||
if not service.name:
|
||||
continue
|
||||
|
||||
try:
|
||||
cls.add(service)
|
||||
except ValueError as e:
|
||||
service_errors.append(service.name)
|
||||
logging.debug("not loading service(%s): %s", service.name, e)
|
||||
return service_errors
|
||||
|
||||
|
||||
class CoreServices:
|
||||
"""
|
||||
Class for interacting with a list of available startup services for
|
||||
nodes. Mostly used to convert a CoreService into a Config API
|
||||
message. This class lives in the Session object and remembers
|
||||
the default services configured for each node type, and any
|
||||
custom service configuration. A CoreService is not a Configurable.
|
||||
"""
|
||||
|
||||
name = "services"
|
||||
config_type = RegisterTlvs.UTILITY
|
||||
|
||||
def __init__(self, session: "Session") -> None:
|
||||
"""
|
||||
Creates a CoreServices instance.
|
||||
|
||||
:param session: session this manager is tied to
|
||||
"""
|
||||
self.session = session
|
||||
# dict of default services tuples, key is node type
|
||||
self.default_services = {}
|
||||
# dict of node ids to dict of custom services by name
|
||||
self.custom_services = {}
|
||||
|
||||
def reset(self) -> None:
|
||||
"""
|
||||
Called when config message with reset flag is received
|
||||
"""
|
||||
self.custom_services.clear()
|
||||
|
||||
def get_default_services(self, node_type: str) -> List[Type["CoreService"]]:
|
||||
"""
|
||||
Get the list of default services that should be enabled for a
|
||||
node for the given node type.
|
||||
|
||||
:param node_type: node type to get default services for
|
||||
:return: default services
|
||||
"""
|
||||
logging.debug("getting default services for type: %s", node_type)
|
||||
results = []
|
||||
defaults = self.default_services.get(node_type, [])
|
||||
for name in defaults:
|
||||
logging.debug("checking for service with service manager: %s", name)
|
||||
service = ServiceManager.get(name)
|
||||
if not service:
|
||||
logging.warning("default service %s is unknown", name)
|
||||
else:
|
||||
results.append(service)
|
||||
return results
|
||||
|
||||
def get_service(
|
||||
self, node_id: int, service_name: str, default_service: bool = False
|
||||
) -> "CoreService":
|
||||
"""
|
||||
Get any custom service configured for the given node that matches the specified
|
||||
service name. If no custom service is found, return the specified service.
|
||||
|
||||
:param node_id: object id to get service from
|
||||
:param service_name: name of service to retrieve
|
||||
:param default_service: True to return default service when custom does
|
||||
not exist, False returns None
|
||||
:return: custom service from the node
|
||||
"""
|
||||
node_services = self.custom_services.setdefault(node_id, {})
|
||||
default = None
|
||||
if default_service:
|
||||
default = ServiceManager.get(service_name)
|
||||
return node_services.get(service_name, default)
|
||||
|
||||
def set_service(self, node_id: int, service_name: str) -> None:
|
||||
"""
|
||||
Store service customizations in an instantiated service object
|
||||
using a list of values that came from a config message.
|
||||
|
||||
:param node_id: object id to set custom service for
|
||||
:param service_name: name of service to set
|
||||
:return: nothing
|
||||
"""
|
||||
logging.debug("setting custom service(%s) for node: %s", service_name, node_id)
|
||||
service = self.get_service(node_id, service_name)
|
||||
if not service:
|
||||
service_class = ServiceManager.get(service_name)
|
||||
service = service_class()
|
||||
|
||||
# add the custom service to dict
|
||||
node_services = self.custom_services.setdefault(node_id, {})
|
||||
node_services[service.name] = service
|
||||
|
||||
def add_services(
|
||||
self, node: CoreNode, node_type: str, services: List[str] = None
|
||||
) -> None:
|
||||
"""
|
||||
Add services to a node.
|
||||
|
||||
:param node: node to add services to
|
||||
:param node_type: node type to add services to
|
||||
:param services: names of services to add to node
|
||||
:return: nothing
|
||||
"""
|
||||
if not services:
|
||||
logging.info(
|
||||
"using default services for node(%s) type(%s)", node.name, node_type
|
||||
)
|
||||
services = self.default_services.get(node_type, [])
|
||||
logging.info("setting services for node(%s): %s", node.name, services)
|
||||
for service_name in services:
|
||||
service = self.get_service(node.id, service_name, default_service=True)
|
||||
if not service:
|
||||
logging.warning(
|
||||
"unknown service(%s) for node(%s)", service_name, node.name
|
||||
)
|
||||
continue
|
||||
node.services.append(service)
|
||||
|
||||
def all_configs(self) -> List[Tuple[int, Type["CoreService"]]]:
|
||||
"""
|
||||
Return (node_id, service) tuples for all stored configs. Used when reconnecting
|
||||
to a session or opening XML.
|
||||
|
||||
:return: list of tuples of node ids and services
|
||||
"""
|
||||
configs = []
|
||||
for node_id in self.custom_services:
|
||||
custom_services = self.custom_services[node_id]
|
||||
for name in custom_services:
|
||||
service = custom_services[name]
|
||||
configs.append((node_id, service))
|
||||
return configs
|
||||
|
||||
def all_files(self, service: "CoreService") -> List[Tuple[str, str]]:
|
||||
"""
|
||||
Return all customized files stored with a service.
|
||||
Used when reconnecting to a session or opening XML.
|
||||
|
||||
:param service: service to get files for
|
||||
:return: list of all custom service files
|
||||
"""
|
||||
files = []
|
||||
if not service.custom:
|
||||
return files
|
||||
|
||||
for filename in service.configs:
|
||||
data = service.config_data.get(filename)
|
||||
if data is None:
|
||||
continue
|
||||
files.append((filename, data))
|
||||
|
||||
return files
|
||||
|
||||
def boot_services(self, node: CoreNode) -> None:
|
||||
"""
|
||||
Start all services on a node.
|
||||
|
||||
:param node: node to start services on
|
||||
:return: nothing
|
||||
"""
|
||||
boot_paths = ServiceDependencies(node.services).boot_paths()
|
||||
funcs = []
|
||||
for boot_path in boot_paths:
|
||||
args = (node, boot_path)
|
||||
funcs.append((self._start_boot_paths, args, {}))
|
||||
result, exceptions = utils.threadpool(funcs)
|
||||
if exceptions:
|
||||
raise ServiceBootError(*exceptions)
|
||||
|
||||
def _start_boot_paths(self, node: CoreNode, boot_path: List["CoreService"]) -> None:
|
||||
"""
|
||||
Start all service boot paths found, based on dependencies.
|
||||
|
||||
:param node: node to start services on
|
||||
:param boot_path: service to start in dependent order
|
||||
:return: nothing
|
||||
"""
|
||||
logging.info(
|
||||
"booting node(%s) services: %s",
|
||||
node.name,
|
||||
" -> ".join([x.name for x in boot_path]),
|
||||
)
|
||||
for service in boot_path:
|
||||
service = self.get_service(node.id, service.name, default_service=True)
|
||||
try:
|
||||
self.boot_service(node, service)
|
||||
except Exception:
|
||||
logging.exception("exception booting service: %s", service.name)
|
||||
raise
|
||||
|
||||
def boot_service(self, node: CoreNode, service: "CoreService") -> None:
|
||||
"""
|
||||
Start a service on a node. Create private dirs, generate config
|
||||
files, and execute startup commands.
|
||||
|
||||
:param node: node to boot services on
|
||||
:param service: service to start
|
||||
:return: nothing
|
||||
"""
|
||||
logging.info(
|
||||
"starting node(%s) service(%s) validation(%s)",
|
||||
node.name,
|
||||
service.name,
|
||||
service.validation_mode.name,
|
||||
)
|
||||
|
||||
# create service directories
|
||||
for directory in service.dirs:
|
||||
try:
|
||||
node.privatedir(directory)
|
||||
except (CoreCommandError, ValueError) as e:
|
||||
logging.warning(
|
||||
"error mounting private dir '%s' for service '%s': %s",
|
||||
directory,
|
||||
service.name,
|
||||
e,
|
||||
)
|
||||
|
||||
# create service files
|
||||
self.create_service_files(node, service)
|
||||
|
||||
# run startup
|
||||
wait = service.validation_mode == ServiceMode.BLOCKING
|
||||
status = self.startup_service(node, service, wait)
|
||||
if status:
|
||||
raise ServiceBootError(
|
||||
"node(%s) service(%s) error during startup" % (node.name, service.name)
|
||||
)
|
||||
|
||||
# blocking mode is finished
|
||||
if wait:
|
||||
return
|
||||
|
||||
# timer mode, sleep and return
|
||||
if service.validation_mode == ServiceMode.TIMER:
|
||||
time.sleep(service.validation_timer)
|
||||
# non-blocking, attempt to validate periodically, up to validation_timer time
|
||||
elif service.validation_mode == ServiceMode.NON_BLOCKING:
|
||||
start = time.monotonic()
|
||||
while True:
|
||||
status = self.validate_service(node, service)
|
||||
if not status:
|
||||
break
|
||||
|
||||
if time.monotonic() - start > service.validation_timer:
|
||||
break
|
||||
|
||||
time.sleep(service.validation_period)
|
||||
|
||||
if status:
|
||||
raise ServiceBootError(
|
||||
"node(%s) service(%s) failed validation" % (node.name, service.name)
|
||||
)
|
||||
|
||||
def copy_service_file(self, node: CoreNode, filename: str, cfg: str) -> bool:
|
||||
"""
|
||||
Given a configured service filename and config, determine if the
|
||||
config references an existing file that should be copied.
|
||||
Returns True for local files, False for generated.
|
||||
|
||||
:param node: node to copy service for
|
||||
:param filename: file name for a configured service
|
||||
:param cfg: configuration string
|
||||
:return: True if successful, False otherwise
|
||||
"""
|
||||
if cfg[:7] == "file://":
|
||||
src = cfg[7:]
|
||||
src = src.split("\n")[0]
|
||||
src = utils.expand_corepath(src, node.session, node)
|
||||
# TODO: glob here
|
||||
node.nodefilecopy(filename, src, mode=0o644)
|
||||
return True
|
||||
return False
|
||||
|
||||
def validate_service(self, node: CoreNode, service: "CoreService") -> int:
|
||||
"""
|
||||
Run the validation command(s) for a service.
|
||||
|
||||
:param node: node to validate service for
|
||||
:param service: service to validate
|
||||
:return: service validation status
|
||||
"""
|
||||
logging.debug("validating node(%s) service(%s)", node.name, service.name)
|
||||
cmds = service.validate
|
||||
if not service.custom:
|
||||
cmds = service.get_validate(node)
|
||||
|
||||
status = 0
|
||||
for cmd in cmds:
|
||||
logging.debug("validating service(%s) using: %s", service.name, cmd)
|
||||
try:
|
||||
node.cmd(cmd)
|
||||
except CoreCommandError as e:
|
||||
logging.debug(
|
||||
"node(%s) service(%s) validate failed", node.name, service.name
|
||||
)
|
||||
logging.debug("cmd(%s): %s", e.cmd, e.output)
|
||||
status = -1
|
||||
break
|
||||
|
||||
return status
|
||||
|
||||
def stop_services(self, node: CoreNode) -> None:
|
||||
"""
|
||||
Stop all services on a node.
|
||||
|
||||
:param node: node to stop services on
|
||||
:return: nothing
|
||||
"""
|
||||
for service in node.services:
|
||||
self.stop_service(node, service)
|
||||
|
||||
def stop_service(self, node: CoreNode, service: "CoreService") -> int:
|
||||
"""
|
||||
Stop a service on a node.
|
||||
|
||||
:param node: node to stop a service on
|
||||
:param service: service to stop
|
||||
:return: status for stopping the services
|
||||
"""
|
||||
status = 0
|
||||
for args in service.shutdown:
|
||||
try:
|
||||
node.cmd(args)
|
||||
except CoreCommandError as e:
|
||||
self.session.exception(
|
||||
ExceptionLevels.ERROR,
|
||||
"services",
|
||||
f"error stopping service {service.name}: {e.stderr}",
|
||||
node.id,
|
||||
)
|
||||
logging.exception("error running stop command %s", args)
|
||||
status = -1
|
||||
return status
|
||||
|
||||
def get_service_file(
|
||||
self, node: CoreNode, service_name: str, filename: str
|
||||
) -> FileData:
|
||||
"""
|
||||
Send a File Message when the GUI has requested a service file.
|
||||
The file data is either auto-generated or comes from an existing config.
|
||||
|
||||
:param node: node to get service file from
|
||||
:param service_name: service to get file from
|
||||
:param filename: file name to retrieve
|
||||
:return: file data
|
||||
"""
|
||||
# get service to get file from
|
||||
service = self.get_service(node.id, service_name, default_service=True)
|
||||
if not service:
|
||||
raise ValueError("invalid service: %s", service_name)
|
||||
|
||||
# retrieve config files for default/custom service
|
||||
if service.custom:
|
||||
config_files = service.configs
|
||||
else:
|
||||
config_files = service.get_configs(node)
|
||||
|
||||
if filename not in config_files:
|
||||
raise ValueError(
|
||||
"unknown service(%s) config file: %s", service_name, filename
|
||||
)
|
||||
|
||||
# get the file data
|
||||
data = service.config_data.get(filename)
|
||||
if data is None:
|
||||
data = "%s" % service.generate_config(node, filename)
|
||||
else:
|
||||
data = "%s" % data
|
||||
|
||||
filetypestr = "service:%s" % service.name
|
||||
return FileData(
|
||||
message_type=MessageFlags.ADD,
|
||||
node=node.id,
|
||||
name=filename,
|
||||
type=filetypestr,
|
||||
data=data,
|
||||
)
|
||||
|
||||
def set_service_file(
|
||||
self, node_id: int, service_name: str, file_name: str, data: str
|
||||
) -> None:
|
||||
"""
|
||||
Receive a File Message from the GUI and store the customized file
|
||||
in the service config. The filename must match one from the list of
|
||||
config files in the service.
|
||||
|
||||
:param node_id: node id to set service file
|
||||
:param service_name: service name to set file for
|
||||
:param file_name: file name to set
|
||||
:param data: data for file to set
|
||||
:return: nothing
|
||||
"""
|
||||
# attempt to set custom service, if needed
|
||||
self.set_service(node_id, service_name)
|
||||
|
||||
# retrieve custom service
|
||||
service = self.get_service(node_id, service_name)
|
||||
if service is None:
|
||||
logging.warning("received file name for unknown service: %s", service_name)
|
||||
return
|
||||
|
||||
# validate file being set is valid
|
||||
config_files = service.configs
|
||||
if file_name not in config_files:
|
||||
logging.warning(
|
||||
"received unknown file(%s) for service(%s)", file_name, service_name
|
||||
)
|
||||
return
|
||||
|
||||
# set custom service file data
|
||||
service.config_data[file_name] = data
|
||||
|
||||
def startup_service(
|
||||
self, node: CoreNode, service: "CoreService", wait: bool = False
|
||||
) -> int:
|
||||
"""
|
||||
Startup a node service.
|
||||
|
||||
:param node: node to reconfigure service for
|
||||
:param service: service to reconfigure
|
||||
:param wait: determines if we should wait to validate startup
|
||||
:return: status of startup
|
||||
"""
|
||||
cmds = service.startup
|
||||
if not service.custom:
|
||||
cmds = service.get_startup(node)
|
||||
|
||||
status = 0
|
||||
for cmd in cmds:
|
||||
try:
|
||||
node.cmd(cmd, wait)
|
||||
except CoreCommandError:
|
||||
logging.exception("error starting command")
|
||||
status = -1
|
||||
return status
|
||||
|
||||
def create_service_files(self, node: CoreNode, service: "CoreService") -> None:
|
||||
"""
|
||||
Creates node service files.
|
||||
|
||||
:param node: node to reconfigure service for
|
||||
:param service: service to reconfigure
|
||||
:return: nothing
|
||||
"""
|
||||
# get values depending on if custom or not
|
||||
config_files = service.configs
|
||||
if not service.custom:
|
||||
config_files = service.get_configs(node)
|
||||
|
||||
for file_name in config_files:
|
||||
logging.debug(
|
||||
"generating service config custom(%s): %s", service.custom, file_name
|
||||
)
|
||||
if service.custom:
|
||||
cfg = service.config_data.get(file_name)
|
||||
if cfg is None:
|
||||
cfg = service.generate_config(node, file_name)
|
||||
|
||||
# cfg may have a file:/// url for copying from a file
|
||||
try:
|
||||
if self.copy_service_file(node, file_name, cfg):
|
||||
continue
|
||||
except IOError:
|
||||
logging.exception("error copying service file: %s", file_name)
|
||||
continue
|
||||
else:
|
||||
cfg = service.generate_config(node, file_name)
|
||||
|
||||
node.nodefile(file_name, cfg)
|
||||
|
||||
def service_reconfigure(self, node: CoreNode, service: "CoreService") -> None:
|
||||
"""
|
||||
Reconfigure a node service.
|
||||
|
||||
:param node: node to reconfigure service for
|
||||
:param service: service to reconfigure
|
||||
:return: nothing
|
||||
"""
|
||||
config_files = service.configs
|
||||
if not service.custom:
|
||||
config_files = service.get_configs(node)
|
||||
|
||||
for file_name in config_files:
|
||||
if file_name[:7] == "file:///":
|
||||
# TODO: implement this
|
||||
raise NotImplementedError
|
||||
|
||||
cfg = service.config_data.get(file_name)
|
||||
if cfg is None:
|
||||
cfg = service.generate_config(node, file_name)
|
||||
|
||||
node.nodefile(file_name, cfg)
|
||||
|
||||
|
||||
class CoreService:
|
||||
"""
|
||||
Parent class used for defining services.
|
||||
"""
|
||||
|
||||
# service name should not include spaces
|
||||
name = None
|
||||
|
||||
# executables that must exist for service to run
|
||||
executables = ()
|
||||
|
||||
# sets service requirements that must be started prior to this service starting
|
||||
dependencies = ()
|
||||
|
||||
# group string allows grouping services together
|
||||
group = None
|
||||
|
||||
# private, per-node directories required by this service
|
||||
dirs = ()
|
||||
|
||||
# config files written by this service
|
||||
configs = ()
|
||||
|
||||
# config file data
|
||||
config_data = {}
|
||||
|
||||
# list of startup commands
|
||||
startup = ()
|
||||
|
||||
# list of shutdown commands
|
||||
shutdown = ()
|
||||
|
||||
# list of validate commands
|
||||
validate = ()
|
||||
|
||||
# validation mode, used to determine startup success
|
||||
validation_mode = ServiceMode.NON_BLOCKING
|
||||
|
||||
# time to wait in seconds for determining if service started successfully
|
||||
validation_timer = 5
|
||||
|
||||
# validation period in seconds, how frequent validation is attempted
|
||||
validation_period = 0.5
|
||||
|
||||
# metadata associated with this service
|
||||
meta = None
|
||||
|
||||
# custom configuration text
|
||||
custom = False
|
||||
custom_needed = False
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
Services are not necessarily instantiated. Classmethods may be used
|
||||
against their config. Services are instantiated when a custom
|
||||
configuration is used to override their default parameters.
|
||||
"""
|
||||
self.custom = True
|
||||
self.config_data = self.__class__.config_data.copy()
|
||||
|
||||
@classmethod
|
||||
def on_load(cls) -> None:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_configs(cls, node: CoreNode) -> Iterable[str]:
|
||||
"""
|
||||
Return the tuple of configuration file filenames. This default method
|
||||
returns the cls._configs tuple, but this method may be overriden to
|
||||
provide node-specific filenames that may be based on other services.
|
||||
|
||||
:param node: node to generate config for
|
||||
:return: configuration files
|
||||
"""
|
||||
return cls.configs
|
||||
|
||||
@classmethod
|
||||
def generate_config(cls, node: CoreNode, filename: str) -> None:
|
||||
"""
|
||||
Generate configuration file given a node object. The filename is
|
||||
provided to allow for multiple config files.
|
||||
Return the configuration string to be written to a file or sent
|
||||
to the GUI for customization.
|
||||
|
||||
:param node: node to generate config for
|
||||
:param filename: file name to generate config for
|
||||
:return: nothing
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@classmethod
|
||||
def get_startup(cls, node: CoreNode) -> Iterable[str]:
|
||||
"""
|
||||
Return the tuple of startup commands. This default method
|
||||
returns the cls.startup tuple, but this method may be
|
||||
overridden to provide node-specific commands that may be
|
||||
based on other services.
|
||||
|
||||
:param node: node to get startup for
|
||||
:return: startup commands
|
||||
"""
|
||||
return cls.startup
|
||||
|
||||
@classmethod
|
||||
def get_validate(cls, node: CoreNode) -> Iterable[str]:
|
||||
"""
|
||||
Return the tuple of validate commands. This default method
|
||||
returns the cls.validate tuple, but this method may be
|
||||
overridden to provide node-specific commands that may be
|
||||
based on other services.
|
||||
|
||||
:param node: node to validate
|
||||
:return: validation commands
|
||||
"""
|
||||
return cls.validate
|
|
@ -97,14 +97,14 @@ Limitations:
|
|||
depending on how many nodes you have.
|
||||
"""
|
||||
|
||||
from core import logger
|
||||
from core.service import CoreService
|
||||
from core.service import ServiceManager
|
||||
import logging
|
||||
|
||||
from core.services.coreservices import CoreService, ServiceManager
|
||||
|
||||
try:
|
||||
from docker import Client
|
||||
except ImportError:
|
||||
logger.error("failure to import docker")
|
||||
logging.debug("missing python docker bindings")
|
||||
|
||||
|
||||
class DockerService(CoreService):
|
||||
|
@ -112,19 +112,18 @@ class DockerService(CoreService):
|
|||
This is a service which will allow running docker containers in a CORE
|
||||
node.
|
||||
"""
|
||||
_name = "Docker"
|
||||
_group = "Docker"
|
||||
_depends = ()
|
||||
_dirs = ('/var/lib/docker/containers/', '/run/shm', '/run/resolvconf',)
|
||||
_configs = ('docker.sh',)
|
||||
_startindex = 50
|
||||
_startup = ('sh docker.sh',)
|
||||
_shutdown = ('service docker stop',)
|
||||
name = "Docker"
|
||||
executables = ("docker",)
|
||||
group = "Docker"
|
||||
dirs = ('/var/lib/docker/containers/', '/run/shm', '/run/resolvconf',)
|
||||
configs = ('docker.sh',)
|
||||
startup = ('sh docker.sh',)
|
||||
shutdown = ('service docker stop',)
|
||||
# Container image to start
|
||||
_image = ""
|
||||
image = ""
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Returns a string having contents of a docker.sh script that
|
||||
can be modified to start a specific docker image.
|
||||
|
@ -139,7 +138,7 @@ class DockerService(CoreService):
|
|||
# distros may just be docker
|
||||
cfg += 'service docker start\n'
|
||||
cfg += "# you could add a command to start a image here eg:\n"
|
||||
if not cls._image:
|
||||
if not cls.image:
|
||||
cfg += "# docker run -d --net host --name coreDock <imagename>\n"
|
||||
else:
|
||||
cfg += """\
|
||||
|
@ -150,12 +149,12 @@ until [ $result -eq 0 ]; do
|
|||
# this is to alleviate contention to docker's SQLite database
|
||||
sleep 0.3
|
||||
done
|
||||
""" % (cls._image,)
|
||||
""" % (cls.image,)
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def on_load(cls):
|
||||
logger.debug("loading custom docker services")
|
||||
logging.debug("loading custom docker services")
|
||||
|
||||
if "Client" in globals():
|
||||
client = Client(version="1.10")
|
||||
|
|
44
daemon/core/services/emaneservices.py
Normal file
44
daemon/core/services/emaneservices.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from core.emane.nodes import EmaneNet
|
||||
from core.services.coreservices import CoreService
|
||||
from core.xml import emanexml
|
||||
|
||||
|
||||
class EmaneTransportService(CoreService):
|
||||
name = "transportd"
|
||||
executables = ("emanetransportd", "emanegentransportxml")
|
||||
group = "EMANE"
|
||||
dependencies = ()
|
||||
dirs = ()
|
||||
configs = ("emanetransport.sh",)
|
||||
startup = ("sh %s" % configs[0],)
|
||||
validate = ("pidof %s" % executables[0],)
|
||||
validation_timer = 0.5
|
||||
shutdown = ("killall %s" % executables[0],)
|
||||
|
||||
@classmethod
|
||||
def generate_config(cls, node, filename):
|
||||
if filename == cls.configs[0]:
|
||||
transport_commands = []
|
||||
for interface in node.netifs(sort=True):
|
||||
network_node = node.session.get_node(interface.net.id)
|
||||
if isinstance(network_node, EmaneNet):
|
||||
config = node.session.emane.get_configs(
|
||||
network_node.id, network_node.model.name
|
||||
)
|
||||
if config and emanexml.is_external(config):
|
||||
nem_id = network_node.getnemid(interface)
|
||||
command = (
|
||||
"emanetransportd -r -l 0 -d ../transportdaemon%s.xml"
|
||||
% nem_id
|
||||
)
|
||||
transport_commands.append(command)
|
||||
transport_commands = "\n".join(transport_commands)
|
||||
return """
|
||||
emanegentransportxml -o ../ ../platform%s.xml
|
||||
%s
|
||||
""" % (
|
||||
node.id,
|
||||
transport_commands,
|
||||
)
|
||||
else:
|
||||
raise ValueError
|
695
daemon/core/services/frr.py
Normal file
695
daemon/core/services/frr.py
Normal file
|
@ -0,0 +1,695 @@
|
|||
"""
|
||||
frr.py: defines routing services provided by FRRouting.
|
||||
Assumes installation of FRR via https://deb.frrouting.org/
|
||||
"""
|
||||
import netaddr
|
||||
|
||||
from core import constants
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.nodes.network import PtpNet, WlanNode
|
||||
from core.nodes.physical import Rj45Node
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
|
||||
class FRRZebra(CoreService):
|
||||
name = "FRRzebra"
|
||||
group = "FRR"
|
||||
dirs = ("/usr/local/etc/frr", "/var/run/frr", "/var/log/frr")
|
||||
configs = (
|
||||
"/usr/local/etc/frr/frr.conf",
|
||||
"frrboot.sh",
|
||||
"/usr/local/etc/frr/vtysh.conf",
|
||||
"/usr/local/etc/frr/daemons",
|
||||
)
|
||||
startup = ("sh frrboot.sh zebra",)
|
||||
shutdown = ("killall zebra",)
|
||||
validate = ("pidof zebra",)
|
||||
|
||||
@classmethod
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Return the frr.conf or frrboot.sh file contents.
|
||||
"""
|
||||
if filename == cls.configs[0]:
|
||||
return cls.generateFrrConf(node)
|
||||
elif filename == cls.configs[1]:
|
||||
return cls.generateFrrBoot(node)
|
||||
elif filename == cls.configs[2]:
|
||||
return cls.generateVtyshConf(node)
|
||||
elif filename == cls.configs[3]:
|
||||
return cls.generateFrrDaemons(node)
|
||||
else:
|
||||
raise ValueError(
|
||||
"file name (%s) is not a known configuration: %s", filename, cls.configs
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def generateVtyshConf(cls, node):
|
||||
"""
|
||||
Returns configuration file text.
|
||||
"""
|
||||
return "service integrated-vtysh-config\n"
|
||||
|
||||
@classmethod
|
||||
def generateFrrConf(cls, node):
|
||||
"""
|
||||
Returns configuration file text. Other services that depend on zebra
|
||||
will have generatefrrifcconfig() and generatefrrconfig()
|
||||
hooks that are invoked here.
|
||||
"""
|
||||
# we could verify here that filename == frr.conf
|
||||
cfg = ""
|
||||
for ifc in node.netifs():
|
||||
cfg += "interface %s\n" % ifc.name
|
||||
# include control interfaces in addressing but not routing daemons
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
cfg += " "
|
||||
cfg += "\n ".join(map(cls.addrstr, ifc.addrlist))
|
||||
cfg += "\n"
|
||||
continue
|
||||
cfgv4 = ""
|
||||
cfgv6 = ""
|
||||
want_ipv4 = False
|
||||
want_ipv6 = False
|
||||
for s in node.services:
|
||||
if cls.name not in s.dependencies:
|
||||
continue
|
||||
ifccfg = s.generatefrrifcconfig(node, ifc)
|
||||
if s.ipv4_routing:
|
||||
want_ipv4 = True
|
||||
if s.ipv6_routing:
|
||||
want_ipv6 = True
|
||||
cfgv6 += ifccfg
|
||||
else:
|
||||
cfgv4 += ifccfg
|
||||
|
||||
if want_ipv4:
|
||||
ipv4list = filter(
|
||||
lambda x: netaddr.valid_ipv4(x.split("/")[0]), ifc.addrlist
|
||||
)
|
||||
cfg += " "
|
||||
cfg += "\n ".join(map(cls.addrstr, ipv4list))
|
||||
cfg += "\n"
|
||||
cfg += cfgv4
|
||||
if want_ipv6:
|
||||
ipv6list = filter(
|
||||
lambda x: netaddr.valid_ipv6(x.split("/")[0]), ifc.addrlist
|
||||
)
|
||||
cfg += " "
|
||||
cfg += "\n ".join(map(cls.addrstr, ipv6list))
|
||||
cfg += "\n"
|
||||
cfg += cfgv6
|
||||
cfg += "!\n"
|
||||
|
||||
for s in node.services:
|
||||
if cls.name not in s.dependencies:
|
||||
continue
|
||||
cfg += s.generatefrrconfig(node)
|
||||
return cfg
|
||||
|
||||
@staticmethod
|
||||
def addrstr(x):
|
||||
"""
|
||||
helper for mapping IP addresses to zebra config statements
|
||||
"""
|
||||
addr = x.split("/")[0]
|
||||
if netaddr.valid_ipv4(addr):
|
||||
return "ip address %s" % x
|
||||
elif netaddr.valid_ipv6(addr):
|
||||
return "ipv6 address %s" % x
|
||||
else:
|
||||
raise ValueError("invalid address: %s", x)
|
||||
|
||||
@classmethod
|
||||
def generateFrrBoot(cls, node):
|
||||
"""
|
||||
Generate a shell script used to boot the FRR daemons.
|
||||
"""
|
||||
frr_bin_search = node.session.options.get_config(
|
||||
"frr_bin_search", default='"/usr/local/bin /usr/bin /usr/lib/frr"'
|
||||
)
|
||||
frr_sbin_search = node.session.options.get_config(
|
||||
"frr_sbin_search", default='"/usr/local/sbin /usr/sbin /usr/lib/frr"'
|
||||
)
|
||||
cfg = """\
|
||||
#!/bin/sh
|
||||
# auto-generated by zebra service (frr.py)
|
||||
FRR_CONF=%s
|
||||
FRR_SBIN_SEARCH=%s
|
||||
FRR_BIN_SEARCH=%s
|
||||
FRR_STATE_DIR=%s
|
||||
|
||||
searchforprog()
|
||||
{
|
||||
prog=$1
|
||||
searchpath=$@
|
||||
ret=
|
||||
for p in $searchpath; do
|
||||
if [ -x $p/$prog ]; then
|
||||
ret=$p
|
||||
break
|
||||
fi
|
||||
done
|
||||
echo $ret
|
||||
}
|
||||
|
||||
confcheck()
|
||||
{
|
||||
CONF_DIR=`dirname $FRR_CONF`
|
||||
# if /etc/frr exists, point /etc/frr/frr.conf -> CONF_DIR
|
||||
if [ "$CONF_DIR" != "/etc/frr" ] && [ -d /etc/frr ] && [ ! -e /etc/frr/frr.conf ]; then
|
||||
ln -s $CONF_DIR/frr.conf /etc/frr/frr.conf
|
||||
fi
|
||||
# if /etc/frr exists, point /etc/frr/vtysh.conf -> CONF_DIR
|
||||
if [ "$CONF_DIR" != "/etc/frr" ] && [ -d /etc/frr ] && [ ! -e /etc/frr/vtysh.conf ]; then
|
||||
ln -s $CONF_DIR/vtysh.conf /etc/frr/vtysh.conf
|
||||
fi
|
||||
}
|
||||
|
||||
bootdaemon()
|
||||
{
|
||||
FRR_SBIN_DIR=$(searchforprog $1 $FRR_SBIN_SEARCH)
|
||||
if [ "z$FRR_SBIN_DIR" = "z" ]; then
|
||||
echo "ERROR: FRR's '$1' daemon not found in search path:"
|
||||
echo " $FRR_SBIN_SEARCH"
|
||||
return 1
|
||||
fi
|
||||
|
||||
flags=""
|
||||
|
||||
if [ "$1" = "pimd" ] && \\
|
||||
grep -E -q '^[[:space:]]*router[[:space:]]+pim6[[:space:]]*$' $FRR_CONF; then
|
||||
flags="$flags -6"
|
||||
fi
|
||||
|
||||
#force FRR to use CORE generated conf file
|
||||
flags="$flags -d -f $FRR_CONF"
|
||||
$FRR_SBIN_DIR/$1 $flags
|
||||
|
||||
if [ "$?" != "0" ]; then
|
||||
echo "ERROR: FRR's '$1' daemon failed to start!:"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
bootfrr()
|
||||
{
|
||||
FRR_BIN_DIR=$(searchforprog 'vtysh' $FRR_BIN_SEARCH)
|
||||
if [ "z$FRR_BIN_DIR" = "z" ]; then
|
||||
echo "ERROR: FRR's 'vtysh' program not found in search path:"
|
||||
echo " $FRR_BIN_SEARCH"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# fix /var/run/frr permissions
|
||||
id -u frr 2>/dev/null >/dev/null
|
||||
if [ "$?" = "0" ]; then
|
||||
chown frr $FRR_STATE_DIR
|
||||
fi
|
||||
|
||||
bootdaemon "zebra"
|
||||
if grep -q "^ip route " $FRR_CONF; then
|
||||
bootdaemon "staticd"
|
||||
fi
|
||||
for r in rip ripng ospf6 ospf bgp babel isis; do
|
||||
if grep -q "^router \\<${r}\\>" $FRR_CONF; then
|
||||
bootdaemon "${r}d"
|
||||
fi
|
||||
done
|
||||
|
||||
if grep -E -q '^[[:space:]]*router[[:space:]]+pim6?[[:space:]]*$' $FRR_CONF; then
|
||||
bootdaemon "pimd"
|
||||
fi
|
||||
|
||||
$FRR_BIN_DIR/vtysh -b
|
||||
}
|
||||
|
||||
if [ "$1" != "zebra" ]; then
|
||||
echo "WARNING: '$1': all FRR daemons are launched by the 'zebra' service!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
confcheck
|
||||
bootfrr
|
||||
""" % (
|
||||
cls.configs[0],
|
||||
frr_sbin_search,
|
||||
frr_bin_search,
|
||||
constants.FRR_STATE_DIR,
|
||||
)
|
||||
for ifc in node.netifs():
|
||||
cfg += f"ip link set dev {ifc.name} down\n"
|
||||
cfg += "sleep 1\n"
|
||||
cfg += f"ip link set dev {ifc.name} up\n"
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def generateFrrDaemons(cls, node):
|
||||
"""
|
||||
Returns configuration file text.
|
||||
"""
|
||||
return """\
|
||||
#
|
||||
# When activation a daemon at the first time, a config file, even if it is
|
||||
# empty, has to be present *and* be owned by the user and group "frr", else
|
||||
# the daemon will not be started by /etc/init.d/frr. The permissions should
|
||||
# be u=rw,g=r,o=.
|
||||
# When using "vtysh" such a config file is also needed. It should be owned by
|
||||
# group "frrvty" and set to ug=rw,o= though. Check /etc/pam.d/frr, too.
|
||||
#
|
||||
# The watchfrr and zebra daemons are always started.
|
||||
#
|
||||
bgpd=yes
|
||||
ospfd=yes
|
||||
ospf6d=yes
|
||||
ripd=yes
|
||||
ripngd=yes
|
||||
isisd=yes
|
||||
pimd=yes
|
||||
ldpd=yes
|
||||
nhrpd=yes
|
||||
eigrpd=yes
|
||||
babeld=yes
|
||||
sharpd=yes
|
||||
pbrd=yes
|
||||
bfdd=yes
|
||||
fabricd=yes
|
||||
|
||||
#
|
||||
# If this option is set the /etc/init.d/frr script automatically loads
|
||||
# the config via "vtysh -b" when the servers are started.
|
||||
# Check /etc/pam.d/frr if you intend to use "vtysh"!
|
||||
#
|
||||
vtysh_enable=yes
|
||||
zebra_options=" -A 127.0.0.1 -s 90000000"
|
||||
bgpd_options=" -A 127.0.0.1"
|
||||
ospfd_options=" -A 127.0.0.1"
|
||||
ospf6d_options=" -A ::1"
|
||||
ripd_options=" -A 127.0.0.1"
|
||||
ripngd_options=" -A ::1"
|
||||
isisd_options=" -A 127.0.0.1"
|
||||
pimd_options=" -A 127.0.0.1"
|
||||
ldpd_options=" -A 127.0.0.1"
|
||||
nhrpd_options=" -A 127.0.0.1"
|
||||
eigrpd_options=" -A 127.0.0.1"
|
||||
babeld_options=" -A 127.0.0.1"
|
||||
sharpd_options=" -A 127.0.0.1"
|
||||
pbrd_options=" -A 127.0.0.1"
|
||||
staticd_options="-A 127.0.0.1"
|
||||
bfdd_options=" -A 127.0.0.1"
|
||||
fabricd_options="-A 127.0.0.1"
|
||||
|
||||
# The list of daemons to watch is automatically generated by the init script.
|
||||
#watchfrr_options=""
|
||||
|
||||
# for debugging purposes, you can specify a "wrap" command to start instead
|
||||
# of starting the daemon directly, e.g. to use valgrind on ospfd:
|
||||
# ospfd_wrap="/usr/bin/valgrind"
|
||||
# or you can use "all_wrap" for all daemons, e.g. to use perf record:
|
||||
# all_wrap="/usr/bin/perf record --call-graph -"
|
||||
# the normal daemon command is added to this at the end.
|
||||
"""
|
||||
|
||||
|
||||
class FrrService(CoreService):
|
||||
"""
|
||||
Parent class for FRR services. Defines properties and methods
|
||||
common to FRR's routing daemons.
|
||||
"""
|
||||
|
||||
name = None
|
||||
group = "FRR"
|
||||
dependencies = ("FRRzebra",)
|
||||
dirs = ()
|
||||
configs = ()
|
||||
startup = ()
|
||||
shutdown = ()
|
||||
meta = "The config file for this service can be found in the Zebra service."
|
||||
|
||||
ipv4_routing = False
|
||||
ipv6_routing = False
|
||||
|
||||
@staticmethod
|
||||
def routerid(node):
|
||||
"""
|
||||
Helper to return the first IPv4 address of a node as its router ID.
|
||||
"""
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
for a in ifc.addrlist:
|
||||
a = a.split("/")[0]
|
||||
if netaddr.valid_ipv4(a):
|
||||
return a
|
||||
# raise ValueError, "no IPv4 address found for router ID"
|
||||
return "0.0.0.0"
|
||||
|
||||
@staticmethod
|
||||
def rj45check(ifc):
|
||||
"""
|
||||
Helper to detect whether interface is connected an external RJ45
|
||||
link.
|
||||
"""
|
||||
if ifc.net:
|
||||
for peerifc in ifc.net.netifs():
|
||||
if peerifc == ifc:
|
||||
continue
|
||||
if isinstance(peerifc, Rj45Node):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def generate_config(cls, node, filename):
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def generatefrrifcconfig(cls, node, ifc):
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def generatefrrconfig(cls, node):
|
||||
return ""
|
||||
|
||||
|
||||
class FRROspfv2(FrrService):
|
||||
"""
|
||||
The OSPFv2 service provides IPv4 routing for wired networks. It does
|
||||
not build its own configuration file but has hooks for adding to the
|
||||
unified frr.conf file.
|
||||
"""
|
||||
|
||||
name = "FRROSPFv2"
|
||||
startup = ()
|
||||
shutdown = ("killall ospfd",)
|
||||
validate = ("pidof ospfd",)
|
||||
ipv4_routing = True
|
||||
|
||||
@staticmethod
|
||||
def mtucheck(ifc):
|
||||
"""
|
||||
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
|
||||
GreTap device.
|
||||
"""
|
||||
if ifc.mtu != 1500:
|
||||
# a workaround for PhysicalNode GreTap, which has no knowledge of
|
||||
# the other nodes/nets
|
||||
return " ip ospf mtu-ignore\n"
|
||||
if not ifc.net:
|
||||
return ""
|
||||
for i in ifc.net.netifs():
|
||||
if i.mtu != ifc.mtu:
|
||||
return " ip ospf mtu-ignore\n"
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def ptpcheck(ifc):
|
||||
"""
|
||||
Helper to detect whether interface is connected to a notional
|
||||
point-to-point link.
|
||||
"""
|
||||
if isinstance(ifc.net, PtpNet):
|
||||
return " ip ospf network point-to-point\n"
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def generatefrrconfig(cls, node):
|
||||
cfg = "router ospf\n"
|
||||
rtrid = cls.routerid(node)
|
||||
cfg += " router-id %s\n" % rtrid
|
||||
# network 10.0.0.0/24 area 0
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
for a in ifc.addrlist:
|
||||
addr = a.split("/")[0]
|
||||
if not netaddr.valid_ipv4(addr):
|
||||
continue
|
||||
cfg += " network %s area 0\n" % a
|
||||
cfg += "!\n"
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def generatefrrifcconfig(cls, node, ifc):
|
||||
return cls.mtucheck(ifc)
|
||||
|
||||
|
||||
class FRROspfv3(FrrService):
|
||||
"""
|
||||
The OSPFv3 service provides IPv6 routing for wired networks. It does
|
||||
not build its own configuration file but has hooks for adding to the
|
||||
unified frr.conf file.
|
||||
"""
|
||||
|
||||
name = "FRROSPFv3"
|
||||
startup = ()
|
||||
shutdown = ("killall ospf6d",)
|
||||
validate = ("pidof ospf6d",)
|
||||
ipv4_routing = True
|
||||
ipv6_routing = True
|
||||
|
||||
@staticmethod
|
||||
def minmtu(ifc):
|
||||
"""
|
||||
Helper to discover the minimum MTU of interfaces linked with the
|
||||
given interface.
|
||||
"""
|
||||
mtu = ifc.mtu
|
||||
if not ifc.net:
|
||||
return mtu
|
||||
for i in ifc.net.netifs():
|
||||
if i.mtu < mtu:
|
||||
mtu = i.mtu
|
||||
return mtu
|
||||
|
||||
@classmethod
|
||||
def mtucheck(cls, ifc):
|
||||
"""
|
||||
Helper to detect MTU mismatch and add the appropriate OSPFv3
|
||||
ifmtu command. This is needed when e.g. a node is linked via a
|
||||
GreTap device.
|
||||
"""
|
||||
minmtu = cls.minmtu(ifc)
|
||||
if minmtu < ifc.mtu:
|
||||
return " ipv6 ospf6 ifmtu %d\n" % minmtu
|
||||
else:
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def ptpcheck(ifc):
|
||||
"""
|
||||
Helper to detect whether interface is connected to a notional
|
||||
point-to-point link.
|
||||
"""
|
||||
if isinstance(ifc.net, PtpNet):
|
||||
return " ipv6 ospf6 network point-to-point\n"
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def generatefrrconfig(cls, node):
|
||||
cfg = "router ospf6\n"
|
||||
rtrid = cls.routerid(node)
|
||||
cfg += " router-id %s\n" % rtrid
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += " interface %s area 0.0.0.0\n" % ifc.name
|
||||
cfg += "!\n"
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def generatefrrifcconfig(cls, node, ifc):
|
||||
return cls.mtucheck(ifc)
|
||||
# cfg = cls.mtucheck(ifc)
|
||||
# external RJ45 connections will use default OSPF timers
|
||||
# if cls.rj45check(ifc):
|
||||
# return cfg
|
||||
# cfg += cls.ptpcheck(ifc)
|
||||
|
||||
# return cfg + """\
|
||||
|
||||
|
||||
# ipv6 ospf6 hello-interval 2
|
||||
# ipv6 ospf6 dead-interval 6
|
||||
# ipv6 ospf6 retransmit-interval 5
|
||||
# """
|
||||
|
||||
|
||||
class FRRBgp(FrrService):
|
||||
"""
|
||||
The BGP service provides interdomain routing.
|
||||
Peers must be manually configured, with a full mesh for those
|
||||
having the same AS number.
|
||||
"""
|
||||
|
||||
name = "FRRBGP"
|
||||
startup = ()
|
||||
shutdown = ("killall bgpd",)
|
||||
validate = ("pidof bgpd",)
|
||||
custom_needed = True
|
||||
ipv4_routing = True
|
||||
ipv6_routing = True
|
||||
|
||||
@classmethod
|
||||
def generatefrrconfig(cls, node):
|
||||
cfg = "!\n! BGP configuration\n!\n"
|
||||
cfg += "! You should configure the AS number below,\n"
|
||||
cfg += "! along with this router's peers.\n!\n"
|
||||
cfg += "router bgp %s\n" % node.id
|
||||
rtrid = cls.routerid(node)
|
||||
cfg += " bgp router-id %s\n" % rtrid
|
||||
cfg += " redistribute connected\n"
|
||||
cfg += "! neighbor 1.2.3.4 remote-as 555\n!\n"
|
||||
return cfg
|
||||
|
||||
|
||||
class FRRRip(FrrService):
|
||||
"""
|
||||
The RIP service provides IPv4 routing for wired networks.
|
||||
"""
|
||||
|
||||
name = "FRRRIP"
|
||||
startup = ()
|
||||
shutdown = ("killall ripd",)
|
||||
validate = ("pidof ripd",)
|
||||
ipv4_routing = True
|
||||
|
||||
@classmethod
|
||||
def generatefrrconfig(cls, node):
|
||||
cfg = """\
|
||||
router rip
|
||||
redistribute static
|
||||
redistribute connected
|
||||
redistribute ospf
|
||||
network 0.0.0.0/0
|
||||
!
|
||||
"""
|
||||
return cfg
|
||||
|
||||
|
||||
class FRRRipng(FrrService):
|
||||
"""
|
||||
The RIP NG service provides IPv6 routing for wired networks.
|
||||
"""
|
||||
|
||||
name = "FRRRIPNG"
|
||||
startup = ()
|
||||
shutdown = ("killall ripngd",)
|
||||
validate = ("pidof ripngd",)
|
||||
ipv6_routing = True
|
||||
|
||||
@classmethod
|
||||
def generatefrrconfig(cls, node):
|
||||
cfg = """\
|
||||
router ripng
|
||||
redistribute static
|
||||
redistribute connected
|
||||
redistribute ospf6
|
||||
network ::/0
|
||||
!
|
||||
"""
|
||||
return cfg
|
||||
|
||||
|
||||
class FRRBabel(FrrService):
|
||||
"""
|
||||
The Babel service provides a loop-avoiding distance-vector routing
|
||||
protocol for IPv6 and IPv4 with fast convergence properties.
|
||||
"""
|
||||
|
||||
name = "FRRBabel"
|
||||
startup = ()
|
||||
shutdown = ("killall babeld",)
|
||||
validate = ("pidof babeld",)
|
||||
ipv6_routing = True
|
||||
|
||||
@classmethod
|
||||
def generatefrrconfig(cls, node):
|
||||
cfg = "router babel\n"
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += " network %s\n" % ifc.name
|
||||
cfg += " redistribute static\n redistribute ipv4 connected\n"
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def generatefrrifcconfig(cls, node, ifc):
|
||||
if ifc.net and isinstance(ifc.net, (EmaneNet, WlanNode)):
|
||||
return " babel wireless\n no babel split-horizon\n"
|
||||
else:
|
||||
return " babel wired\n babel split-horizon\n"
|
||||
|
||||
|
||||
class FRRpimd(FrrService):
|
||||
"""
|
||||
PIM multicast routing based on XORP.
|
||||
"""
|
||||
|
||||
name = "FRRpimd"
|
||||
startup = ()
|
||||
shutdown = ("killall pimd",)
|
||||
validate = ("pidof pimd",)
|
||||
ipv4_routing = True
|
||||
|
||||
@classmethod
|
||||
def generatefrrconfig(cls, node):
|
||||
ifname = "eth0"
|
||||
for ifc in node.netifs():
|
||||
if ifc.name != "lo":
|
||||
ifname = ifc.name
|
||||
break
|
||||
cfg = "router mfea\n!\n"
|
||||
cfg += "router igmp\n!\n"
|
||||
cfg += "router pim\n"
|
||||
cfg += " !ip pim rp-address 10.0.0.1\n"
|
||||
cfg += " ip pim bsr-candidate %s\n" % ifname
|
||||
cfg += " ip pim rp-candidate %s\n" % ifname
|
||||
cfg += " !ip pim spt-threshold interval 10 bytes 80000\n"
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def generatefrrifcconfig(cls, node, ifc):
|
||||
return " ip mfea\n ip igmp\n ip pim\n"
|
||||
|
||||
|
||||
class FRRIsis(FrrService):
|
||||
"""
|
||||
The ISIS service provides IPv4 and IPv6 routing for wired networks. It does
|
||||
not build its own configuration file but has hooks for adding to the
|
||||
unified frr.conf file.
|
||||
"""
|
||||
|
||||
name = "FRRISIS"
|
||||
startup = ()
|
||||
shutdown = ("killall isisd",)
|
||||
validate = ("pidof isisd",)
|
||||
ipv4_routing = True
|
||||
ipv6_routing = True
|
||||
|
||||
@staticmethod
|
||||
def ptpcheck(ifc):
|
||||
"""
|
||||
Helper to detect whether interface is connected to a notional
|
||||
point-to-point link.
|
||||
"""
|
||||
if isinstance(ifc.net, PtpNet):
|
||||
return " isis network point-to-point\n"
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def generatefrrconfig(cls, node):
|
||||
cfg = "router isis DEFAULT\n"
|
||||
cfg += " net 47.0001.0000.1900.%04x.00\n" % node.id
|
||||
cfg += " metric-style wide\n"
|
||||
cfg += " is-type level-2-only\n"
|
||||
cfg += "!\n"
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def generatefrrifcconfig(cls, node, ifc):
|
||||
cfg = " ip router isis DEFAULT\n"
|
||||
cfg += " ipv6 router isis DEFAULT\n"
|
||||
cfg += " isis circuit-type level-2-only\n"
|
||||
cfg += cls.ptpcheck(ifc)
|
||||
return cfg
|
|
@ -2,28 +2,27 @@
|
|||
nrl.py: defines services provided by NRL protolib tools hosted here:
|
||||
http://www.nrl.navy.mil/itd/ncs/products
|
||||
"""
|
||||
import netaddr
|
||||
|
||||
from core.misc import utils
|
||||
from core.misc.ipaddress import Ipv4Prefix
|
||||
from core.service import CoreService
|
||||
from core import utils
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
|
||||
class NrlService(CoreService):
|
||||
"""
|
||||
Parent class for NRL services. Defines properties and methods
|
||||
common to NRL's routing daemons.
|
||||
"""""
|
||||
_name = None
|
||||
_group = "ProtoSvc"
|
||||
_depends = ()
|
||||
_dirs = ()
|
||||
_configs = ()
|
||||
_startindex = 45
|
||||
_startup = ()
|
||||
_shutdown = ()
|
||||
"""
|
||||
|
||||
name = None
|
||||
group = "ProtoSvc"
|
||||
dirs = ()
|
||||
configs = ()
|
||||
startup = ()
|
||||
shutdown = ()
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
|
@ -34,27 +33,26 @@ class NrlService(CoreService):
|
|||
interface's prefix length, so e.g. '/32' can turn into '/24'.
|
||||
"""
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control == True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
for a in ifc.addrlist:
|
||||
if a.find(".") >= 0:
|
||||
addr = a.split('/')[0]
|
||||
pre = Ipv4Prefix("%s/%s" % (addr, prefixlen))
|
||||
return str(pre)
|
||||
a = a.split("/")[0]
|
||||
if netaddr.valid_ipv4(a):
|
||||
return f"{a}/{prefixlen}"
|
||||
# raise ValueError, "no IPv4 address found"
|
||||
return "0.0.0.0/%s" % prefixlen
|
||||
|
||||
|
||||
class MgenSinkService(NrlService):
|
||||
_name = "MGEN_Sink"
|
||||
_configs = ("sink.mgen",)
|
||||
_startindex = 5
|
||||
_startup = ("mgen input sink.mgen",)
|
||||
_validate = ("pidof mgen",)
|
||||
_shutdown = ("killall mgen",)
|
||||
name = "MGEN_Sink"
|
||||
executables = ("mgen",)
|
||||
configs = ("sink.mgen",)
|
||||
startup = ("mgen input sink.mgen",)
|
||||
validate = ("pidof mgen",)
|
||||
shutdown = ("killall mgen",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
cfg = "0.0 LISTEN UDP 5000\n"
|
||||
for ifc in node.netifs():
|
||||
name = utils.sysctl_devname(ifc.name)
|
||||
|
@ -62,57 +60,60 @@ class MgenSinkService(NrlService):
|
|||
return cfg
|
||||
|
||||
@classmethod
|
||||
def getstartup(cls, node, services):
|
||||
cmd = cls._startup[0]
|
||||
def get_startup(cls, node):
|
||||
cmd = cls.startup[0]
|
||||
cmd += " output /tmp/mgen_%s.log" % node.name
|
||||
return cmd,
|
||||
return (cmd,)
|
||||
|
||||
|
||||
class NrlNhdp(NrlService):
|
||||
"""
|
||||
NeighborHood Discovery Protocol for MANET networks.
|
||||
"""
|
||||
_name = "NHDP"
|
||||
_startup = ("nrlnhdp",)
|
||||
_shutdown = ("killall nrlnhdp",)
|
||||
_validate = ("pidof nrlnhdp",)
|
||||
|
||||
name = "NHDP"
|
||||
executables = ("nrlnhdp",)
|
||||
startup = ("nrlnhdp",)
|
||||
shutdown = ("killall nrlnhdp",)
|
||||
validate = ("pidof nrlnhdp",)
|
||||
|
||||
@classmethod
|
||||
def getstartup(cls, node, services):
|
||||
def get_startup(cls, node):
|
||||
"""
|
||||
Generate the appropriate command-line based on node interfaces.
|
||||
"""
|
||||
cmd = cls._startup[0]
|
||||
cmd = cls.startup[0]
|
||||
cmd += " -l /var/log/nrlnhdp.log"
|
||||
cmd += " -rpipe %s_nhdp" % node.name
|
||||
|
||||
servicenames = map(lambda x: x._name, services)
|
||||
servicenames = map(lambda x: x.name, node.services)
|
||||
if "SMF" in servicenames:
|
||||
cmd += " -flooding ecds"
|
||||
cmd += " -smfClient %s_smf" % node.name
|
||||
|
||||
netifs = filter(lambda x: not getattr(x, 'control', False), \
|
||||
node.netifs())
|
||||
netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs()))
|
||||
if len(netifs) > 0:
|
||||
interfacenames = map(lambda x: x.name, netifs)
|
||||
cmd += " -i "
|
||||
cmd += " -i ".join(interfacenames)
|
||||
|
||||
return cmd,
|
||||
return (cmd,)
|
||||
|
||||
|
||||
class NrlSmf(NrlService):
|
||||
"""
|
||||
Simplified Multicast Forwarding for MANET networks.
|
||||
"""
|
||||
_name = "SMF"
|
||||
_startup = ("sh startsmf.sh",)
|
||||
_shutdown = ("killall nrlsmf",)
|
||||
_validate = ("pidof nrlsmf",)
|
||||
_configs = ("startsmf.sh",)
|
||||
|
||||
name = "SMF"
|
||||
executables = ("nrlsmf",)
|
||||
startup = ("sh startsmf.sh",)
|
||||
shutdown = ("killall nrlsmf",)
|
||||
validate = ("pidof nrlsmf",)
|
||||
configs = ("startsmf.sh",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Generate a startup script for SMF. Because nrlsmf does not
|
||||
daemonize, it can cause problems in some situations when launched
|
||||
|
@ -123,8 +124,8 @@ class NrlSmf(NrlService):
|
|||
comments = ""
|
||||
cmd = "nrlsmf instance %s_smf" % node.name
|
||||
|
||||
servicenames = map(lambda x: x._name, services)
|
||||
netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
|
||||
servicenames = map(lambda x: x.name, node.services)
|
||||
netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs()))
|
||||
if len(netifs) == 0:
|
||||
return ""
|
||||
|
||||
|
@ -156,17 +157,19 @@ class NrlOlsr(NrlService):
|
|||
"""
|
||||
Optimized Link State Routing protocol for MANET networks.
|
||||
"""
|
||||
_name = "OLSR"
|
||||
_startup = ("nrlolsrd",)
|
||||
_shutdown = ("killall nrlolsrd",)
|
||||
_validate = ("pidof nrlolsrd",)
|
||||
|
||||
name = "OLSR"
|
||||
executables = ("nrlolsrd",)
|
||||
startup = ("nrlolsrd",)
|
||||
shutdown = ("killall nrlolsrd",)
|
||||
validate = ("pidof nrlolsrd",)
|
||||
|
||||
@classmethod
|
||||
def getstartup(cls, node, services):
|
||||
def get_startup(cls, node):
|
||||
"""
|
||||
Generate the appropriate command-line based on node interfaces.
|
||||
"""
|
||||
cmd = cls._startup[0]
|
||||
cmd = cls.startup[0]
|
||||
# are multiple interfaces supported? No.
|
||||
netifs = list(node.netifs())
|
||||
if len(netifs) > 0:
|
||||
|
@ -175,77 +178,81 @@ class NrlOlsr(NrlService):
|
|||
cmd += " -l /var/log/nrlolsrd.log"
|
||||
cmd += " -rpipe %s_olsr" % node.name
|
||||
|
||||
servicenames = map(lambda x: x._name, services)
|
||||
if "SMF" in servicenames and not "NHDP" in servicenames:
|
||||
servicenames = map(lambda x: x.name, node.services)
|
||||
if "SMF" in servicenames and "NHDP" not in servicenames:
|
||||
cmd += " -flooding s-mpr"
|
||||
cmd += " -smfClient %s_smf" % node.name
|
||||
if "zebra" in servicenames:
|
||||
cmd += " -z"
|
||||
|
||||
return cmd,
|
||||
return (cmd,)
|
||||
|
||||
|
||||
class NrlOlsrv2(NrlService):
|
||||
"""
|
||||
Optimized Link State Routing protocol version 2 for MANET networks.
|
||||
"""
|
||||
_name = "OLSRv2"
|
||||
_startup = ("nrlolsrv2",)
|
||||
_shutdown = ("killall nrlolsrv2",)
|
||||
_validate = ("pidof nrlolsrv2",)
|
||||
|
||||
name = "OLSRv2"
|
||||
executables = ("nrlolsrv2",)
|
||||
startup = ("nrlolsrv2",)
|
||||
shutdown = ("killall nrlolsrv2",)
|
||||
validate = ("pidof nrlolsrv2",)
|
||||
|
||||
@classmethod
|
||||
def getstartup(cls, node, services):
|
||||
def get_startup(cls, node):
|
||||
"""
|
||||
Generate the appropriate command-line based on node interfaces.
|
||||
"""
|
||||
cmd = cls._startup[0]
|
||||
cmd = cls.startup[0]
|
||||
cmd += " -l /var/log/nrlolsrv2.log"
|
||||
cmd += " -rpipe %s_olsrv2" % node.name
|
||||
|
||||
servicenames = map(lambda x: x._name, services)
|
||||
servicenames = map(lambda x: x.name, node.services)
|
||||
if "SMF" in servicenames:
|
||||
cmd += " -flooding ecds"
|
||||
cmd += " -smfClient %s_smf" % node.name
|
||||
|
||||
cmd += " -p olsr"
|
||||
|
||||
netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
|
||||
netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs()))
|
||||
if len(netifs) > 0:
|
||||
interfacenames = map(lambda x: x.name, netifs)
|
||||
cmd += " -i "
|
||||
cmd += " -i ".join(interfacenames)
|
||||
|
||||
return cmd,
|
||||
return (cmd,)
|
||||
|
||||
|
||||
class OlsrOrg(NrlService):
|
||||
"""
|
||||
Optimized Link State Routing protocol from olsr.org for MANET networks.
|
||||
"""
|
||||
_name = "OLSRORG"
|
||||
_configs = ("/etc/olsrd/olsrd.conf",)
|
||||
_dirs = ("/etc/olsrd",)
|
||||
_startup = ("olsrd",)
|
||||
_shutdown = ("killall olsrd",)
|
||||
_validate = ("pidof olsrd",)
|
||||
|
||||
name = "OLSRORG"
|
||||
executables = ("olsrd",)
|
||||
configs = ("/etc/olsrd/olsrd.conf",)
|
||||
dirs = ("/etc/olsrd",)
|
||||
startup = ("olsrd",)
|
||||
shutdown = ("killall olsrd",)
|
||||
validate = ("pidof olsrd",)
|
||||
|
||||
@classmethod
|
||||
def getstartup(cls, node, services):
|
||||
def get_startup(cls, node):
|
||||
"""
|
||||
Generate the appropriate command-line based on node interfaces.
|
||||
"""
|
||||
cmd = cls._startup[0]
|
||||
netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
|
||||
cmd = cls.startup[0]
|
||||
netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs()))
|
||||
if len(netifs) > 0:
|
||||
interfacenames = map(lambda x: x.name, netifs)
|
||||
cmd += " -i "
|
||||
cmd += " -i ".join(interfacenames)
|
||||
|
||||
return cmd,
|
||||
return (cmd,)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Generate a default olsrd config file to use the broadcast address of 255.255.255.255.
|
||||
"""
|
||||
|
@ -572,27 +579,24 @@ class MgenActor(NrlService):
|
|||
"""
|
||||
|
||||
# a unique name is required, without spaces
|
||||
_name = "MgenActor"
|
||||
name = "MgenActor"
|
||||
executables = ("mgen",)
|
||||
# you can create your own group here
|
||||
_group = "ProtoSvc"
|
||||
# list of other services this service depends on
|
||||
_depends = ()
|
||||
group = "ProtoSvc"
|
||||
# per-node directories
|
||||
_dirs = ()
|
||||
dirs = ()
|
||||
# generated files (without a full path this file goes in the node's dir,
|
||||
# e.g. /tmp/pycore.12345/n1.conf/)
|
||||
_configs = ('start_mgen_actor.sh',)
|
||||
# this controls the starting order vs other enabled services
|
||||
_startindex = 50
|
||||
configs = ("start_mgen_actor.sh",)
|
||||
# list of startup commands, also may be generated during startup
|
||||
_startup = ("sh start_mgen_actor.sh",)
|
||||
startup = ("sh start_mgen_actor.sh",)
|
||||
# list of validation commands
|
||||
_validate = ("pidof mgen",)
|
||||
validate = ("pidof mgen",)
|
||||
# list of shutdown commands
|
||||
_shutdown = ("killall mgen",)
|
||||
shutdown = ("killall mgen",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Generate a startup script for MgenActor. Because mgenActor does not
|
||||
daemonize, it can cause problems in some situations when launched
|
||||
|
@ -603,8 +607,7 @@ class MgenActor(NrlService):
|
|||
comments = ""
|
||||
cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % node.name
|
||||
|
||||
servicenames = map(lambda x: x._name, services)
|
||||
netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
|
||||
netifs = [x for x in node.netifs() if not getattr(x, "control", False)]
|
||||
if len(netifs) == 0:
|
||||
return ""
|
||||
|
||||
|
@ -616,19 +619,21 @@ class Arouted(NrlService):
|
|||
"""
|
||||
Adaptive Routing
|
||||
"""
|
||||
_name = "arouted"
|
||||
_configs = ("startarouted.sh",)
|
||||
_startindex = NrlService._startindex + 10
|
||||
_startup = ("sh startarouted.sh",)
|
||||
_shutdown = ("pkill arouted",)
|
||||
_validate = ("pidof arouted",)
|
||||
|
||||
name = "arouted"
|
||||
executables = ("arouted",)
|
||||
configs = ("startarouted.sh",)
|
||||
startup = ("sh startarouted.sh",)
|
||||
shutdown = ("pkill arouted",)
|
||||
validate = ("pidof arouted",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Return the Quagga.conf or quaggaboot.sh file contents.
|
||||
"""
|
||||
cfg = """
|
||||
cfg = (
|
||||
"""
|
||||
#!/bin/sh
|
||||
for f in "/tmp/%s_smf"; do
|
||||
count=1
|
||||
|
@ -642,7 +647,9 @@ for f in "/tmp/%s_smf"; do
|
|||
done
|
||||
done
|
||||
|
||||
""" % node.name
|
||||
"""
|
||||
% node.name
|
||||
)
|
||||
cfg += "ip route add %s dev lo\n" % cls.firstipv4prefix(node, 24)
|
||||
cfg += "arouted instance %s_smf tap %s_tap" % (node.name, node.name)
|
||||
# seconds to consider a new route valid
|
||||
|
|
|
@ -1,52 +1,54 @@
|
|||
"""
|
||||
quagga.py: defines routing services provided by Quagga.
|
||||
"""
|
||||
import netaddr
|
||||
|
||||
from core import constants
|
||||
from core.enumerations import LinkTypes, NodeTypes
|
||||
from core.misc import ipaddress
|
||||
from core.misc import nodeutils
|
||||
from core.service import CoreService
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emulator.enumerations import LinkTypes
|
||||
from core.nodes.network import PtpNet, WlanNode
|
||||
from core.nodes.physical import Rj45Node
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
|
||||
class Zebra(CoreService):
|
||||
_name = "zebra"
|
||||
_group = "Quagga"
|
||||
_dirs = ("/usr/local/etc/quagga", "/var/run/quagga")
|
||||
_configs = (
|
||||
name = "zebra"
|
||||
group = "Quagga"
|
||||
dirs = ("/usr/local/etc/quagga", "/var/run/quagga")
|
||||
configs = (
|
||||
"/usr/local/etc/quagga/Quagga.conf",
|
||||
"quaggaboot.sh",
|
||||
"/usr/local/etc/quagga/vtysh.conf"
|
||||
"/usr/local/etc/quagga/vtysh.conf",
|
||||
)
|
||||
_startindex = 35
|
||||
_startup = ("sh quaggaboot.sh zebra",)
|
||||
_shutdown = ("killall zebra",)
|
||||
_validate = ("pidof zebra",)
|
||||
startup = ("sh quaggaboot.sh zebra",)
|
||||
shutdown = ("killall zebra",)
|
||||
validate = ("pidof zebra",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Return the Quagga.conf or quaggaboot.sh file contents.
|
||||
"""
|
||||
if filename == cls._configs[0]:
|
||||
return cls.generateQuaggaConf(node, services)
|
||||
elif filename == cls._configs[1]:
|
||||
return cls.generateQuaggaBoot(node, services)
|
||||
elif filename == cls._configs[2]:
|
||||
return cls.generateVtyshConf(node, services)
|
||||
if filename == cls.configs[0]:
|
||||
return cls.generateQuaggaConf(node)
|
||||
elif filename == cls.configs[1]:
|
||||
return cls.generateQuaggaBoot(node)
|
||||
elif filename == cls.configs[2]:
|
||||
return cls.generateVtyshConf(node)
|
||||
else:
|
||||
raise ValueError("file name (%s) is not a known configuration: %s",
|
||||
filename, cls._configs)
|
||||
raise ValueError(
|
||||
"file name (%s) is not a known configuration: %s", filename, cls.configs
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def generateVtyshConf(cls, node, services):
|
||||
def generateVtyshConf(cls, node):
|
||||
"""
|
||||
Returns configuration file text.
|
||||
"""
|
||||
return "service integrated-vtysh-config\n"
|
||||
|
||||
@classmethod
|
||||
def generateQuaggaConf(cls, node, services):
|
||||
def generateQuaggaConf(cls, node):
|
||||
"""
|
||||
Returns configuration file text. Other services that depend on zebra
|
||||
will have generatequaggaifcconfig() and generatequaggaconfig()
|
||||
|
@ -57,7 +59,7 @@ class Zebra(CoreService):
|
|||
for ifc in node.netifs():
|
||||
cfg += "interface %s\n" % ifc.name
|
||||
# include control interfaces in addressing but not routing daemons
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
cfg += " "
|
||||
cfg += "\n ".join(map(cls.addrstr, ifc.addrlist))
|
||||
cfg += "\n"
|
||||
|
@ -66,34 +68,38 @@ class Zebra(CoreService):
|
|||
cfgv6 = ""
|
||||
want_ipv4 = False
|
||||
want_ipv6 = False
|
||||
for s in services:
|
||||
if cls._name not in s._depends:
|
||||
for s in node.services:
|
||||
if cls.name not in s.dependencies:
|
||||
continue
|
||||
ifccfg = s.generatequaggaifcconfig(node, ifc)
|
||||
if s._ipv4_routing:
|
||||
if s.ipv4_routing:
|
||||
want_ipv4 = True
|
||||
if s._ipv6_routing:
|
||||
if s.ipv6_routing:
|
||||
want_ipv6 = True
|
||||
cfgv6 += ifccfg
|
||||
else:
|
||||
cfgv4 += ifccfg
|
||||
|
||||
if want_ipv4:
|
||||
ipv4list = filter(lambda x: ipaddress.is_ipv4_address(x.split('/')[0]), ifc.addrlist)
|
||||
ipv4list = filter(
|
||||
lambda x: netaddr.valid_ipv4(x.split("/")[0]), ifc.addrlist
|
||||
)
|
||||
cfg += " "
|
||||
cfg += "\n ".join(map(cls.addrstr, ipv4list))
|
||||
cfg += "\n"
|
||||
cfg += cfgv4
|
||||
if want_ipv6:
|
||||
ipv6list = filter(lambda x: ipaddress.is_ipv6_address(x.split('/')[0]), ifc.addrlist)
|
||||
ipv6list = filter(
|
||||
lambda x: netaddr.valid_ipv6(x.split("/")[0]), ifc.addrlist
|
||||
)
|
||||
cfg += " "
|
||||
cfg += "\n ".join(map(cls.addrstr, ipv6list))
|
||||
cfg += "\n"
|
||||
cfg += cfgv6
|
||||
cfg += "!\n"
|
||||
|
||||
for s in services:
|
||||
if cls._name not in s._depends:
|
||||
for s in node.services:
|
||||
if cls.name not in s.dependencies:
|
||||
continue
|
||||
cfg += s.generatequaggaconfig(node)
|
||||
return cfg
|
||||
|
@ -103,24 +109,25 @@ class Zebra(CoreService):
|
|||
"""
|
||||
helper for mapping IP addresses to zebra config statements
|
||||
"""
|
||||
if x.find(".") >= 0:
|
||||
addr = x.split("/")[0]
|
||||
if netaddr.valid_ipv4(addr):
|
||||
return "ip address %s" % x
|
||||
elif x.find(":") >= 0:
|
||||
elif netaddr.valid_ipv6(addr):
|
||||
return "ipv6 address %s" % x
|
||||
else:
|
||||
raise ValueError("invalid address: %s", x)
|
||||
|
||||
@classmethod
|
||||
def generateQuaggaBoot(cls, node, services):
|
||||
def generateQuaggaBoot(cls, node):
|
||||
"""
|
||||
Generate a shell script used to boot the Quagga daemons.
|
||||
"""
|
||||
try:
|
||||
quagga_bin_search = node.session.config['quagga_bin_search']
|
||||
quagga_sbin_search = node.session.config['quagga_sbin_search']
|
||||
except KeyError:
|
||||
quagga_bin_search = '"/usr/local/bin /usr/bin /usr/lib/quagga"'
|
||||
quagga_sbin_search = '"/usr/local/sbin /usr/sbin /usr/lib/quagga"'
|
||||
quagga_bin_search = node.session.options.get_config(
|
||||
"quagga_bin_search", default='"/usr/local/bin /usr/bin /usr/lib/quagga"'
|
||||
)
|
||||
quagga_sbin_search = node.session.options.get_config(
|
||||
"quagga_sbin_search", default='"/usr/local/sbin /usr/sbin /usr/lib/quagga"'
|
||||
)
|
||||
return """\
|
||||
#!/bin/sh
|
||||
# auto-generated by zebra service (quagga.py)
|
||||
|
@ -196,7 +203,7 @@ bootquagga()
|
|||
|
||||
bootdaemon "zebra"
|
||||
for r in rip ripng ospf6 ospf bgp babel; do
|
||||
if grep -q "^router \<${r}\>" $QUAGGA_CONF; then
|
||||
if grep -q "^router \\<${r}\\>" $QUAGGA_CONF; then
|
||||
bootdaemon "${r}d"
|
||||
fi
|
||||
done
|
||||
|
@ -214,7 +221,12 @@ if [ "$1" != "zebra" ]; then
|
|||
fi
|
||||
confcheck
|
||||
bootquagga
|
||||
""" % (cls._configs[0], quagga_sbin_search, quagga_bin_search, constants.QUAGGA_STATE_DIR)
|
||||
""" % (
|
||||
cls.configs[0],
|
||||
quagga_sbin_search,
|
||||
quagga_bin_search,
|
||||
constants.QUAGGA_STATE_DIR,
|
||||
)
|
||||
|
||||
|
||||
class QuaggaService(CoreService):
|
||||
|
@ -222,18 +234,18 @@ class QuaggaService(CoreService):
|
|||
Parent class for Quagga services. Defines properties and methods
|
||||
common to Quagga's routing daemons.
|
||||
"""
|
||||
_name = None
|
||||
_group = "Quagga"
|
||||
_depends = ("zebra",)
|
||||
_dirs = ()
|
||||
_configs = ()
|
||||
_startindex = 40
|
||||
_startup = ()
|
||||
_shutdown = ()
|
||||
_meta = "The config file for this service can be found in the Zebra service."
|
||||
|
||||
_ipv4_routing = False
|
||||
_ipv6_routing = False
|
||||
name = None
|
||||
group = "Quagga"
|
||||
dependencies = ("zebra",)
|
||||
dirs = ()
|
||||
configs = ()
|
||||
startup = ()
|
||||
shutdown = ()
|
||||
meta = "The config file for this service can be found in the Zebra service."
|
||||
|
||||
ipv4_routing = False
|
||||
ipv6_routing = False
|
||||
|
||||
@staticmethod
|
||||
def routerid(node):
|
||||
|
@ -241,11 +253,12 @@ class QuaggaService(CoreService):
|
|||
Helper to return the first IPv4 address of a node as its router ID.
|
||||
"""
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control == True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
for a in ifc.addrlist:
|
||||
if a.find(".") >= 0:
|
||||
return a.split('/')[0]
|
||||
a = a.split("/")[0]
|
||||
if netaddr.valid_ipv4(a):
|
||||
return a
|
||||
# raise ValueError, "no IPv4 address found for router ID"
|
||||
return "0.0.0.0"
|
||||
|
||||
|
@ -259,12 +272,12 @@ class QuaggaService(CoreService):
|
|||
for peerifc in ifc.net.netifs():
|
||||
if peerifc == ifc:
|
||||
continue
|
||||
if nodeutils.is_node(peerifc, NodeTypes.RJ45):
|
||||
if isinstance(peerifc, Rj45Node):
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
|
@ -282,11 +295,12 @@ class Ospfv2(QuaggaService):
|
|||
not build its own configuration file but has hooks for adding to the
|
||||
unified Quagga.conf file.
|
||||
"""
|
||||
_name = "OSPFv2"
|
||||
_startup = ()
|
||||
_shutdown = ("killall ospfd",)
|
||||
_validate = ("pidof ospfd",)
|
||||
_ipv4_routing = True
|
||||
|
||||
name = "OSPFv2"
|
||||
startup = ()
|
||||
shutdown = ("killall ospfd",)
|
||||
validate = ("pidof ospfd",)
|
||||
ipv4_routing = True
|
||||
|
||||
@staticmethod
|
||||
def mtucheck(ifc):
|
||||
|
@ -312,7 +326,7 @@ class Ospfv2(QuaggaService):
|
|||
Helper to detect whether interface is connected to a notional
|
||||
point-to-point link.
|
||||
"""
|
||||
if nodeutils.is_node(ifc.net, NodeTypes.PEER_TO_PEER):
|
||||
if isinstance(ifc.net, PtpNet):
|
||||
return " ip ospf network point-to-point\n"
|
||||
return ""
|
||||
|
||||
|
@ -323,32 +337,18 @@ class Ospfv2(QuaggaService):
|
|||
cfg += " router-id %s\n" % rtrid
|
||||
# network 10.0.0.0/24 area 0
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
for a in ifc.addrlist:
|
||||
if a.find(".") < 0:
|
||||
continue
|
||||
net = ipaddress.Ipv4Prefix(a)
|
||||
cfg += " network %s area 0\n" % net
|
||||
addr = a.split("/")[0]
|
||||
if netaddr.valid_ipv4(addr):
|
||||
cfg += " network %s area 0\n" % a
|
||||
cfg += "!\n"
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def generatequaggaifcconfig(cls, node, ifc):
|
||||
return cls.mtucheck(ifc)
|
||||
# cfg = cls.mtucheck(ifc)
|
||||
# external RJ45 connections will use default OSPF timers
|
||||
# if cls.rj45check(ifc):
|
||||
# return cfg
|
||||
# cfg += cls.ptpcheck(ifc)
|
||||
|
||||
# return cfg + """\
|
||||
|
||||
|
||||
# ip ospf hello-interval 2
|
||||
# ip ospf dead-interval 6
|
||||
# ip ospf retransmit-interval 5
|
||||
# """
|
||||
|
||||
|
||||
class Ospfv3(QuaggaService):
|
||||
|
@ -357,12 +357,13 @@ class Ospfv3(QuaggaService):
|
|||
not build its own configuration file but has hooks for adding to the
|
||||
unified Quagga.conf file.
|
||||
"""
|
||||
_name = "OSPFv3"
|
||||
_startup = ()
|
||||
_shutdown = ("killall ospf6d",)
|
||||
_validate = ("pidof ospf6d",)
|
||||
_ipv4_routing = True
|
||||
_ipv6_routing = True
|
||||
|
||||
name = "OSPFv3"
|
||||
startup = ()
|
||||
shutdown = ("killall ospf6d",)
|
||||
validate = ("pidof ospf6d",)
|
||||
ipv4_routing = True
|
||||
ipv6_routing = True
|
||||
|
||||
@staticmethod
|
||||
def minmtu(ifc):
|
||||
|
@ -397,7 +398,7 @@ class Ospfv3(QuaggaService):
|
|||
Helper to detect whether interface is connected to a notional
|
||||
point-to-point link.
|
||||
"""
|
||||
if nodeutils.is_node(ifc.net, NodeTypes.PEER_TO_PEER):
|
||||
if isinstance(ifc.net, PtpNet):
|
||||
return " ipv6 ospf6 network point-to-point\n"
|
||||
return ""
|
||||
|
||||
|
@ -405,9 +406,10 @@ class Ospfv3(QuaggaService):
|
|||
def generatequaggaconfig(cls, node):
|
||||
cfg = "router ospf6\n"
|
||||
rtrid = cls.routerid(node)
|
||||
cfg += " instance-id 65\n"
|
||||
cfg += " router-id %s\n" % rtrid
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += " interface %s area 0.0.0.0\n" % ifc.name
|
||||
cfg += "!\n"
|
||||
|
@ -416,19 +418,6 @@ class Ospfv3(QuaggaService):
|
|||
@classmethod
|
||||
def generatequaggaifcconfig(cls, node, ifc):
|
||||
return cls.mtucheck(ifc)
|
||||
# cfg = cls.mtucheck(ifc)
|
||||
# external RJ45 connections will use default OSPF timers
|
||||
# if cls.rj45check(ifc):
|
||||
# return cfg
|
||||
# cfg += cls.ptpcheck(ifc)
|
||||
|
||||
# return cfg + """\
|
||||
|
||||
|
||||
# ipv6 ospf6 hello-interval 2
|
||||
# ipv6 ospf6 dead-interval 6
|
||||
# ipv6 ospf6 retransmit-interval 5
|
||||
# """
|
||||
|
||||
|
||||
class Ospfv3mdr(Ospfv3):
|
||||
|
@ -438,24 +427,26 @@ class Ospfv3mdr(Ospfv3):
|
|||
configuration file but has hooks for adding to the
|
||||
unified Quagga.conf file.
|
||||
"""
|
||||
_name = "OSPFv3MDR"
|
||||
_ipv4_routing = True
|
||||
|
||||
name = "OSPFv3MDR"
|
||||
ipv4_routing = True
|
||||
|
||||
@classmethod
|
||||
def generatequaggaifcconfig(cls, node, ifc):
|
||||
cfg = cls.mtucheck(ifc)
|
||||
# Uncomment the following line to use Address Family Translation for IPv4
|
||||
cfg += " ipv6 ospf6 instance-id 65\n"
|
||||
if ifc.net is not None and nodeutils.is_node(ifc.net, (NodeTypes.WIRELESS_LAN, NodeTypes.EMANE)):
|
||||
return cfg + """\
|
||||
if ifc.net is not None and isinstance(ifc.net, (WlanNode, EmaneNet)):
|
||||
return (
|
||||
cfg
|
||||
+ """\
|
||||
ipv6 ospf6 hello-interval 2
|
||||
ipv6 ospf6 dead-interval 6
|
||||
ipv6 ospf6 retransmit-interval 5
|
||||
ipv6 ospf6 network manet-designated-router
|
||||
ipv6 ospf6 diffhellos
|
||||
ipv6 ospf6 twohoprefresh 3
|
||||
ipv6 ospf6 adjacencyconnectivity uniconnected
|
||||
ipv6 ospf6 lsafullness mincostlsa
|
||||
"""
|
||||
)
|
||||
else:
|
||||
return cfg
|
||||
|
||||
|
@ -466,20 +457,21 @@ class Bgp(QuaggaService):
|
|||
Peers must be manually configured, with a full mesh for those
|
||||
having the same AS number.
|
||||
"""
|
||||
_name = "BGP"
|
||||
_startup = ()
|
||||
_shutdown = ("killall bgpd",)
|
||||
_validate = ("pidof bgpd",)
|
||||
_custom_needed = True
|
||||
_ipv4_routing = True
|
||||
_ipv6_routing = True
|
||||
|
||||
name = "BGP"
|
||||
startup = ()
|
||||
shutdown = ("killall bgpd",)
|
||||
validate = ("pidof bgpd",)
|
||||
custom_needed = True
|
||||
ipv4_routing = True
|
||||
ipv6_routing = True
|
||||
|
||||
@classmethod
|
||||
def generatequaggaconfig(cls, node):
|
||||
cfg = "!\n! BGP configuration\n!\n"
|
||||
cfg += "! You should configure the AS number below,\n"
|
||||
cfg += "! along with this router's peers.\n!\n"
|
||||
cfg += "router bgp %s\n" % node.objid
|
||||
cfg += "router bgp %s\n" % node.id
|
||||
rtrid = cls.routerid(node)
|
||||
cfg += " bgp router-id %s\n" % rtrid
|
||||
cfg += " redistribute connected\n"
|
||||
|
@ -491,11 +483,12 @@ class Rip(QuaggaService):
|
|||
"""
|
||||
The RIP service provides IPv4 routing for wired networks.
|
||||
"""
|
||||
_name = "RIP"
|
||||
_startup = ()
|
||||
_shutdown = ("killall ripd",)
|
||||
_validate = ("pidof ripd",)
|
||||
_ipv4_routing = True
|
||||
|
||||
name = "RIP"
|
||||
startup = ()
|
||||
shutdown = ("killall ripd",)
|
||||
validate = ("pidof ripd",)
|
||||
ipv4_routing = True
|
||||
|
||||
@classmethod
|
||||
def generatequaggaconfig(cls, node):
|
||||
|
@ -514,11 +507,12 @@ class Ripng(QuaggaService):
|
|||
"""
|
||||
The RIP NG service provides IPv6 routing for wired networks.
|
||||
"""
|
||||
_name = "RIPNG"
|
||||
_startup = ()
|
||||
_shutdown = ("killall ripngd",)
|
||||
_validate = ("pidof ripngd",)
|
||||
_ipv6_routing = True
|
||||
|
||||
name = "RIPNG"
|
||||
startup = ()
|
||||
shutdown = ("killall ripngd",)
|
||||
validate = ("pidof ripngd",)
|
||||
ipv6_routing = True
|
||||
|
||||
@classmethod
|
||||
def generatequaggaconfig(cls, node):
|
||||
|
@ -538,11 +532,12 @@ class Babel(QuaggaService):
|
|||
The Babel service provides a loop-avoiding distance-vector routing
|
||||
protocol for IPv6 and IPv4 with fast convergence properties.
|
||||
"""
|
||||
_name = "Babel"
|
||||
_startup = ()
|
||||
_shutdown = ("killall babeld",)
|
||||
_validate = ("pidof babeld",)
|
||||
_ipv6_routing = True
|
||||
|
||||
name = "Babel"
|
||||
startup = ()
|
||||
shutdown = ("killall babeld",)
|
||||
validate = ("pidof babeld",)
|
||||
ipv6_routing = True
|
||||
|
||||
@classmethod
|
||||
def generatequaggaconfig(cls, node):
|
||||
|
@ -556,8 +551,7 @@ class Babel(QuaggaService):
|
|||
|
||||
@classmethod
|
||||
def generatequaggaifcconfig(cls, node, ifc):
|
||||
type = "wired"
|
||||
if ifc.net and ifc.net.linktype == LinkTypes.WIRELESS.value:
|
||||
if ifc.net and ifc.net.linktype == LinkTypes.WIRELESS:
|
||||
return " babel wireless\n no babel split-horizon\n"
|
||||
else:
|
||||
return " babel wired\n babel split-horizon\n"
|
||||
|
@ -567,28 +561,29 @@ class Xpimd(QuaggaService):
|
|||
"""
|
||||
PIM multicast routing based on XORP.
|
||||
"""
|
||||
_name = 'Xpimd'
|
||||
_startup = ()
|
||||
_shutdown = ('killall xpimd',)
|
||||
_validate = ('pidof xpimd',)
|
||||
_ipv4_routing = True
|
||||
|
||||
name = "Xpimd"
|
||||
startup = ()
|
||||
shutdown = ("killall xpimd",)
|
||||
validate = ("pidof xpimd",)
|
||||
ipv4_routing = True
|
||||
|
||||
@classmethod
|
||||
def generatequaggaconfig(cls, node):
|
||||
ifname = 'eth0'
|
||||
ifname = "eth0"
|
||||
for ifc in node.netifs():
|
||||
if ifc.name != 'lo':
|
||||
if ifc.name != "lo":
|
||||
ifname = ifc.name
|
||||
break
|
||||
cfg = 'router mfea\n!\n'
|
||||
cfg += 'router igmp\n!\n'
|
||||
cfg += 'router pim\n'
|
||||
cfg += ' !ip pim rp-address 10.0.0.1\n'
|
||||
cfg += ' ip pim bsr-candidate %s\n' % ifname
|
||||
cfg += ' ip pim rp-candidate %s\n' % ifname
|
||||
cfg += ' !ip pim spt-threshold interval 10 bytes 80000\n'
|
||||
cfg = "router mfea\n!\n"
|
||||
cfg += "router igmp\n!\n"
|
||||
cfg += "router pim\n"
|
||||
cfg += " !ip pim rp-address 10.0.0.1\n"
|
||||
cfg += " ip pim bsr-candidate %s\n" % ifname
|
||||
cfg += " ip pim rp-candidate %s\n" % ifname
|
||||
cfg += " !ip pim spt-threshold interval 10 bytes 80000\n"
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def generatequaggaifcconfig(cls, node, ifc):
|
||||
return ' ip mfea\n ip igmp\n ip pim\n'
|
||||
return " ip mfea\n ip igmp\n ip pim\n"
|
||||
|
|
|
@ -4,51 +4,40 @@ sdn.py defines services to start Open vSwitch and the Ryu SDN Controller.
|
|||
|
||||
import re
|
||||
|
||||
from core.service import CoreService
|
||||
import netaddr
|
||||
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
|
||||
class SdnService(CoreService):
|
||||
"""
|
||||
Parent class for SDN services.
|
||||
"""
|
||||
_name = None
|
||||
_group = "SDN"
|
||||
_depends = ()
|
||||
_dirs = ()
|
||||
_configs = ()
|
||||
_startindex = 50
|
||||
_startup = ()
|
||||
_shutdown = ()
|
||||
|
||||
group = "SDN"
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
return ""
|
||||
|
||||
|
||||
class OvsService(SdnService):
|
||||
_name = "OvsService"
|
||||
_group = "SDN"
|
||||
_depends = ()
|
||||
_dirs = ("/etc/openvswitch", "/var/run/openvswitch", "/var/log/openvswitch")
|
||||
_configs = ('OvsService.sh',)
|
||||
_startindex = 50
|
||||
_startup = ('sh OvsService.sh',)
|
||||
_shutdown = ('killall ovs-vswitchd', 'killall ovsdb-server')
|
||||
name = "OvsService"
|
||||
executables = ("ovs-ofctl", "ovs-vsctl")
|
||||
group = "SDN"
|
||||
dirs = ("/etc/openvswitch", "/var/run/openvswitch", "/var/log/openvswitch")
|
||||
configs = ("OvsService.sh",)
|
||||
startup = ("sh OvsService.sh",)
|
||||
shutdown = ("killall ovs-vswitchd", "killall ovsdb-server")
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
# Check whether the node is running zebra
|
||||
has_zebra = 0
|
||||
for s in services:
|
||||
if s._name == "zebra":
|
||||
for s in node.services:
|
||||
if s.name == "zebra":
|
||||
has_zebra = 1
|
||||
|
||||
# Check whether the node is running an SDN controller
|
||||
has_sdn_ctrlr = 0
|
||||
for s in services:
|
||||
if s._name == "ryuService":
|
||||
has_sdn_ctrlr = 1
|
||||
|
||||
cfg = "#!/bin/sh\n"
|
||||
cfg += "# auto-generated by OvsService (OvsService.py)\n"
|
||||
cfg += "## First make sure that the ovs services are up and running\n"
|
||||
|
@ -61,7 +50,7 @@ class OvsService(SdnService):
|
|||
cfg += "\n## Now add all our interfaces as ports to the switch\n"
|
||||
portnum = 1
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
ifnumstr = re.findall(r"\d+", ifc.name)
|
||||
ifnum = ifnumstr[0]
|
||||
|
@ -69,17 +58,16 @@ class OvsService(SdnService):
|
|||
# create virtual interfaces
|
||||
cfg += "## Create a veth pair to send the data to\n"
|
||||
cfg += "ip link add rtr%s type veth peer name sw%s\n" % (ifnum, ifnum)
|
||||
# cfg += "ifconfig rtr%s up\n" % ifnum
|
||||
# cfg += "ifconfig sw%s up\n" % ifnum
|
||||
|
||||
# remove ip address of eths because quagga/zebra will assign same IPs to rtr interfaces
|
||||
# or assign them manually to rtr interfaces if zebra is not running
|
||||
for ifcaddr in ifc.addrlist:
|
||||
if ifcaddr.find(".") >= 0:
|
||||
addr = ifcaddr.split("/")[0]
|
||||
if netaddr.valid_ipv4(addr):
|
||||
cfg += "ip addr del %s dev %s\n" % (ifcaddr, ifc.name)
|
||||
if has_zebra == 0:
|
||||
cfg += "ip addr add %s dev rtr%s\n" % (ifcaddr, ifnum)
|
||||
elif ifcaddr.find(":") >= 0:
|
||||
elif netaddr.valid_ipv6(addr):
|
||||
cfg += "ip -6 addr del %s dev %s\n" % (ifcaddr, ifc.name)
|
||||
if has_zebra == 0:
|
||||
cfg += "ip -6 addr add %s dev rtr%s\n" % (ifcaddr, ifnum)
|
||||
|
@ -109,7 +97,7 @@ class OvsService(SdnService):
|
|||
# Setup default flows
|
||||
portnum = 1
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += "## Take the data from the CORE interface and put it on the veth and vice versa\n"
|
||||
cfg += "ovs-ofctl add-flow ovsbr0 priority=1000,in_port=%d,action=output:%d\n" % (portnum, portnum + 1)
|
||||
|
@ -120,23 +108,23 @@ class OvsService(SdnService):
|
|||
|
||||
|
||||
class RyuService(SdnService):
|
||||
_name = "ryuService"
|
||||
_group = "SDN"
|
||||
_depends = ()
|
||||
_dirs = ()
|
||||
_configs = ('ryuService.sh',)
|
||||
_startindex = 50
|
||||
_startup = ('sh ryuService.sh',)
|
||||
_shutdown = ('killall ryu-manager',)
|
||||
name = "ryuService"
|
||||
executables = ("ryu-manager",)
|
||||
group = "SDN"
|
||||
dirs = ()
|
||||
configs = ("ryuService.sh",)
|
||||
startup = ("sh ryuService.sh",)
|
||||
shutdown = ("killall ryu-manager",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Return a string that will be written to filename, or sent to the
|
||||
GUI for user customization.
|
||||
"""
|
||||
app_path = "/usr/local/lib/python2.7/dist-packages/ryu/app"
|
||||
cfg = "#!/bin/sh\n"
|
||||
cfg += "# auto-generated by ryuService (ryuService.py)\n"
|
||||
cfg += '/usr/local/bin/ryu-manager --observe-links %s/ofctl_rest.py %s/rest_topology.py' % (app_path, app_path)
|
||||
cfg += (
|
||||
"ryu-manager --observe-links ryu.app.ofctl_rest ryu.app.rest_topology &\n"
|
||||
)
|
||||
return cfg
|
||||
|
|
|
@ -3,23 +3,23 @@ security.py: defines security services (vpnclient, vpnserver, ipsec and
|
|||
firewall)
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from core import constants
|
||||
from core import logger
|
||||
from core.service import CoreService
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
|
||||
class VPNClient(CoreService):
|
||||
_name = "VPNClient"
|
||||
_group = "Security"
|
||||
_configs = ('vpnclient.sh',)
|
||||
_startindex = 60
|
||||
_startup = ('sh vpnclient.sh',)
|
||||
_shutdown = ("killall openvpn",)
|
||||
_validate = ("pidof openvpn",)
|
||||
_custom_needed = True
|
||||
name = "VPNClient"
|
||||
group = "Security"
|
||||
configs = ("vpnclient.sh",)
|
||||
startup = ("sh vpnclient.sh",)
|
||||
shutdown = ("killall openvpn",)
|
||||
validate = ("pidof openvpn",)
|
||||
custom_needed = True
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Return the client.conf and vpnclient.sh file contents to
|
||||
"""
|
||||
|
@ -30,23 +30,24 @@ class VPNClient(CoreService):
|
|||
try:
|
||||
cfg += open(fname, "rb").read()
|
||||
except IOError:
|
||||
logger.exception("Error opening VPN client configuration template (%s)", fname)
|
||||
logging.exception(
|
||||
"Error opening VPN client configuration template (%s)", fname
|
||||
)
|
||||
|
||||
return cfg
|
||||
|
||||
|
||||
class VPNServer(CoreService):
|
||||
_name = "VPNServer"
|
||||
_group = "Security"
|
||||
_configs = ('vpnserver.sh',)
|
||||
_startindex = 50
|
||||
_startup = ('sh vpnserver.sh',)
|
||||
_shutdown = ("killall openvpn",)
|
||||
_validate = ("pidof openvpn",)
|
||||
_custom_needed = True
|
||||
name = "VPNServer"
|
||||
group = "Security"
|
||||
configs = ("vpnserver.sh",)
|
||||
startup = ("sh vpnserver.sh",)
|
||||
shutdown = ("killall openvpn",)
|
||||
validate = ("pidof openvpn",)
|
||||
custom_needed = True
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Return the sample server.conf and vpnserver.sh file contents to
|
||||
GUI for user customization.
|
||||
|
@ -58,22 +59,23 @@ class VPNServer(CoreService):
|
|||
try:
|
||||
cfg += open(fname, "rb").read()
|
||||
except IOError:
|
||||
logger.exception("Error opening VPN server configuration template (%s)", fname)
|
||||
logging.exception(
|
||||
"Error opening VPN server configuration template (%s)", fname
|
||||
)
|
||||
|
||||
return cfg
|
||||
|
||||
|
||||
class IPsec(CoreService):
|
||||
_name = "IPsec"
|
||||
_group = "Security"
|
||||
_configs = ('ipsec.sh',)
|
||||
_startindex = 60
|
||||
_startup = ('sh ipsec.sh',)
|
||||
_shutdown = ("killall racoon",)
|
||||
_custom_needed = True
|
||||
name = "IPsec"
|
||||
group = "Security"
|
||||
configs = ("ipsec.sh",)
|
||||
startup = ("sh ipsec.sh",)
|
||||
shutdown = ("killall racoon",)
|
||||
custom_needed = True
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Return the ipsec.conf and racoon.conf file contents to
|
||||
GUI for user customization.
|
||||
|
@ -82,25 +84,23 @@ class IPsec(CoreService):
|
|||
cfg += "# set up static tunnel mode security assocation for service "
|
||||
cfg += "(security.py)\n"
|
||||
fname = "%s/examples/services/sampleIPsec" % constants.CORE_DATA_DIR
|
||||
|
||||
try:
|
||||
cfg += open(fname, "rb").read()
|
||||
with open(fname, "r") as f:
|
||||
cfg += f.read()
|
||||
except IOError:
|
||||
logger.exception("Error opening IPsec configuration template (%s)", fname)
|
||||
|
||||
logging.exception("Error opening IPsec configuration template (%s)", fname)
|
||||
return cfg
|
||||
|
||||
|
||||
class Firewall(CoreService):
|
||||
_name = "Firewall"
|
||||
_group = "Security"
|
||||
_configs = ('firewall.sh',)
|
||||
_startindex = 20
|
||||
_startup = ('sh firewall.sh',)
|
||||
_custom_needed = True
|
||||
name = "Firewall"
|
||||
group = "Security"
|
||||
configs = ("firewall.sh",)
|
||||
startup = ("sh firewall.sh",)
|
||||
custom_needed = True
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Return the firewall rule examples to GUI for user customization.
|
||||
"""
|
||||
|
@ -111,6 +111,57 @@ class Firewall(CoreService):
|
|||
try:
|
||||
cfg += open(fname, "rb").read()
|
||||
except IOError:
|
||||
logger.exception("Error opening Firewall configuration template (%s)", fname)
|
||||
logging.exception(
|
||||
"Error opening Firewall configuration template (%s)", fname
|
||||
)
|
||||
|
||||
return cfg
|
||||
|
||||
|
||||
class Nat(CoreService):
|
||||
"""
|
||||
IPv4 source NAT service.
|
||||
"""
|
||||
|
||||
name = "NAT"
|
||||
executables = ("iptables",)
|
||||
group = "Security"
|
||||
configs = ("nat.sh",)
|
||||
startup = ("sh nat.sh",)
|
||||
custom_needed = False
|
||||
|
||||
@classmethod
|
||||
def generateifcnatrule(cls, ifc, line_prefix=""):
|
||||
"""
|
||||
Generate a NAT line for one interface.
|
||||
"""
|
||||
cfg = line_prefix + "iptables -t nat -A POSTROUTING -o "
|
||||
cfg += ifc.name + " -j MASQUERADE\n"
|
||||
|
||||
cfg += line_prefix + "iptables -A FORWARD -i " + ifc.name
|
||||
cfg += " -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
|
||||
|
||||
cfg += line_prefix + "iptables -A FORWARD -i "
|
||||
cfg += ifc.name + " -j DROP\n"
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
NAT out the first interface
|
||||
"""
|
||||
cfg = "#!/bin/sh\n"
|
||||
cfg += "# generated by security.py\n"
|
||||
cfg += "# NAT out the first interface by default\n"
|
||||
have_nat = False
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
if have_nat:
|
||||
cfg += cls.generateifcnatrule(ifc, line_prefix="#")
|
||||
else:
|
||||
have_nat = True
|
||||
cfg += "# NAT out the " + ifc.name + " interface\n"
|
||||
cfg += cls.generateifcnatrule(ifc)
|
||||
cfg += "\n"
|
||||
return cfg
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
from inspect import isclass
|
||||
from sys import maxint
|
||||
|
||||
from core.service import CoreService
|
||||
|
||||
|
||||
class Startup(CoreService):
|
||||
"""
|
||||
A CORE service to start other services in order, serially
|
||||
"""
|
||||
_name = 'startup'
|
||||
_group = 'Utility'
|
||||
_depends = ()
|
||||
_dirs = ()
|
||||
_configs = ('startup.sh',)
|
||||
_startindex = maxint
|
||||
_startup = ('sh startup.sh',)
|
||||
_shutdown = ()
|
||||
_validate = ()
|
||||
|
||||
@staticmethod
|
||||
def is_startup_service(s):
|
||||
return isinstance(s, Startup) or (isclass(s) and issubclass(s, Startup))
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
if filename != cls._configs[0]:
|
||||
return ''
|
||||
script = '#!/bin/sh\n' \
|
||||
'# auto-generated by Startup (startup.py)\n\n' \
|
||||
'exec > startup.log 2>&1\n\n'
|
||||
for s in sorted(services, key=lambda x: x._startindex):
|
||||
if cls.is_startup_service(s) or len(str(s._starttime)) > 0:
|
||||
continue
|
||||
start = '\n'.join(s.getstartup(node, services))
|
||||
if start:
|
||||
script += start + '\n'
|
||||
return script
|
|
@ -2,46 +2,48 @@
|
|||
ucarp.py: defines high-availability IP address controlled by ucarp
|
||||
"""
|
||||
|
||||
from core.service import CoreService
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
UCARP_ETC = "/usr/local/etc/ucarp"
|
||||
|
||||
|
||||
class Ucarp(CoreService):
|
||||
_name = "ucarp"
|
||||
_group = "Utility"
|
||||
_depends = ( )
|
||||
_dirs = (UCARP_ETC,)
|
||||
_configs = (
|
||||
UCARP_ETC + "/default.sh", UCARP_ETC + "/default-up.sh", UCARP_ETC + "/default-down.sh", "ucarpboot.sh",)
|
||||
_startindex = 65
|
||||
_startup = ("sh ucarpboot.sh",)
|
||||
_shutdown = ("killall ucarp",)
|
||||
_validate = ("pidof ucarp",)
|
||||
name = "ucarp"
|
||||
group = "Utility"
|
||||
dirs = (UCARP_ETC,)
|
||||
configs = (
|
||||
UCARP_ETC + "/default.sh",
|
||||
UCARP_ETC + "/default-up.sh",
|
||||
UCARP_ETC + "/default-down.sh",
|
||||
"ucarpboot.sh",
|
||||
)
|
||||
startup = ("sh ucarpboot.sh",)
|
||||
shutdown = ("killall ucarp",)
|
||||
validate = ("pidof ucarp",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Return the default file contents
|
||||
"""
|
||||
if filename == cls._configs[0]:
|
||||
return cls.generateUcarpConf(node, services)
|
||||
elif filename == cls._configs[1]:
|
||||
return cls.generateVipUp(node, services)
|
||||
elif filename == cls._configs[2]:
|
||||
return cls.generateVipDown(node, services)
|
||||
elif filename == cls._configs[3]:
|
||||
return cls.generateUcarpBoot(node, services)
|
||||
if filename == cls.configs[0]:
|
||||
return cls.generateUcarpConf(node)
|
||||
elif filename == cls.configs[1]:
|
||||
return cls.generateVipUp(node)
|
||||
elif filename == cls.configs[2]:
|
||||
return cls.generateVipDown(node)
|
||||
elif filename == cls.configs[3]:
|
||||
return cls.generateUcarpBoot(node)
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
@classmethod
|
||||
def generateUcarpConf(cls, node, services):
|
||||
def generateUcarpConf(cls, node):
|
||||
"""
|
||||
Returns configuration file text.
|
||||
"""
|
||||
try:
|
||||
ucarp_bin = node.session.cfg['ucarp_bin']
|
||||
ucarp_bin = node.session.cfg["ucarp_bin"]
|
||||
except KeyError:
|
||||
ucarp_bin = "/usr/sbin/ucarp"
|
||||
|
||||
|
@ -102,19 +104,18 @@ STOP_SCRIPT=${UCARP_CFGDIR}/default-down.sh
|
|||
UCARP_OPTS="$OPTIONS -b $UCARP_BASE -k $SKEW -i $INTERFACE -v $INSTANCE_ID -p $PASSWORD -u $START_SCRIPT -d $STOP_SCRIPT -a $VIRTUAL_ADDRESS -s $SOURCE_ADDRESS -f $FACILITY $XPARAM"
|
||||
|
||||
${UCARP_EXEC} -B ${UCARP_OPTS}
|
||||
""" % (ucarp_bin, UCARP_ETC)
|
||||
""" % (
|
||||
ucarp_bin,
|
||||
UCARP_ETC,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def generateUcarpBoot(cls, node, services):
|
||||
def generateUcarpBoot(cls, node):
|
||||
"""
|
||||
Generate a shell script used to boot the Ucarp daemons.
|
||||
"""
|
||||
|
||||
try:
|
||||
ucarp_bin = node.session.cfg['ucarp_bin']
|
||||
except KeyError:
|
||||
ucarp_bin = "/usr/sbin/ucarp"
|
||||
return """\
|
||||
return (
|
||||
"""\
|
||||
#!/bin/sh
|
||||
# Location of the UCARP config directory
|
||||
UCARP_CFGDIR=%s
|
||||
|
@ -124,18 +125,15 @@ chmod a+x ${UCARP_CFGDIR}/*.sh
|
|||
# Start the default ucarp daemon configuration
|
||||
${UCARP_CFGDIR}/default.sh
|
||||
|
||||
""" % UCARP_ETC
|
||||
"""
|
||||
% UCARP_ETC
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def generateVipUp(cls, node, services):
|
||||
def generateVipUp(cls, node):
|
||||
"""
|
||||
Generate a shell script used to start the virtual ip
|
||||
"""
|
||||
try:
|
||||
ucarp_bin = node.session.cfg['ucarp_bin']
|
||||
except KeyError:
|
||||
ucarp_bin = "/usr/sbin/ucarp"
|
||||
|
||||
return """\
|
||||
#!/bin/bash
|
||||
|
||||
|
@ -145,7 +143,7 @@ exec 2> /dev/null
|
|||
IP="${2}"
|
||||
NET="${3}"
|
||||
if [ -z "$NET" ]; then
|
||||
NET="24"
|
||||
NET="24"
|
||||
fi
|
||||
|
||||
/sbin/ip addr add ${IP}/${NET} dev "$1"
|
||||
|
@ -154,14 +152,10 @@ fi
|
|||
"""
|
||||
|
||||
@classmethod
|
||||
def generateVipDown(cls, node, services):
|
||||
def generateVipDown(cls, node):
|
||||
"""
|
||||
Generate a shell script used to stop the virtual ip
|
||||
"""
|
||||
try:
|
||||
ucarp_bin = node.session.cfg['ucarp_bin']
|
||||
except KeyError:
|
||||
ucarp_bin = "/usr/sbin/ucarp"
|
||||
return """\
|
||||
#!/bin/bash
|
||||
|
||||
|
@ -171,7 +165,7 @@ exec 2> /dev/null
|
|||
IP="${2}"
|
||||
NET="${3}"
|
||||
if [ -z "$NET" ]; then
|
||||
NET="24"
|
||||
NET="24"
|
||||
fi
|
||||
|
||||
/sbin/ip addr del ${IP}/${NET} dev "$1"
|
||||
|
|
|
@ -1,50 +1,47 @@
|
|||
"""
|
||||
utility.py: defines miscellaneous utility services.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from core import CoreCommandError
|
||||
from core import constants
|
||||
from core.misc import utils
|
||||
from core.misc.ipaddress import Ipv4Prefix
|
||||
from core.misc.ipaddress import Ipv6Prefix
|
||||
from core.service import CoreService
|
||||
import netaddr
|
||||
|
||||
from core import constants, utils
|
||||
from core.errors import CoreCommandError
|
||||
from core.nodes.base import CoreNode
|
||||
from core.services.coreservices import CoreService, ServiceMode
|
||||
|
||||
|
||||
class UtilService(CoreService):
|
||||
"""
|
||||
Parent class for utility services.
|
||||
"""
|
||||
_name = None
|
||||
_group = "Utility"
|
||||
_depends = ()
|
||||
_dirs = ()
|
||||
_configs = ()
|
||||
_startindex = 80
|
||||
_startup = ()
|
||||
_shutdown = ()
|
||||
|
||||
name = None
|
||||
group = "Utility"
|
||||
dirs = ()
|
||||
configs = ()
|
||||
startup = ()
|
||||
shutdown = ()
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
return ""
|
||||
|
||||
|
||||
class IPForwardService(UtilService):
|
||||
_name = "IPForward"
|
||||
_configs = ("ipforward.sh",)
|
||||
_startindex = 5
|
||||
_startup = ("sh ipforward.sh",)
|
||||
name = "IPForward"
|
||||
configs = ("ipforward.sh",)
|
||||
startup = ("sh ipforward.sh",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
if os.uname()[0] == "Linux":
|
||||
return cls.generateconfiglinux(node, filename, services)
|
||||
return cls.generateconfiglinux(node, filename)
|
||||
else:
|
||||
raise Exception("unknown platform")
|
||||
|
||||
@classmethod
|
||||
def generateconfiglinux(cls, node, filename, services):
|
||||
def generateconfiglinux(cls, node, filename):
|
||||
cfg = """\
|
||||
#!/bin/sh
|
||||
# auto-generated by IPForward service (utility.py)
|
||||
|
@ -56,64 +53,65 @@ class IPForwardService(UtilService):
|
|||
%(sysctl)s -w net.ipv4.conf.default.send_redirects=0
|
||||
%(sysctl)s -w net.ipv4.conf.all.rp_filter=0
|
||||
%(sysctl)s -w net.ipv4.conf.default.rp_filter=0
|
||||
""" % {'sysctl': constants.SYSCTL_BIN}
|
||||
""" % {
|
||||
"sysctl": constants.SYSCTL_BIN
|
||||
}
|
||||
for ifc in node.netifs():
|
||||
name = utils.sysctl_devname(ifc.name)
|
||||
cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (constants.SYSCTL_BIN, name)
|
||||
cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % \
|
||||
(constants.SYSCTL_BIN, name)
|
||||
cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (
|
||||
constants.SYSCTL_BIN,
|
||||
name,
|
||||
)
|
||||
cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % (
|
||||
constants.SYSCTL_BIN,
|
||||
name,
|
||||
)
|
||||
cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (constants.SYSCTL_BIN, name)
|
||||
return cfg
|
||||
|
||||
|
||||
class DefaultRouteService(UtilService):
|
||||
_name = "DefaultRoute"
|
||||
_configs = ("defaultroute.sh",)
|
||||
_startup = ("sh defaultroute.sh",)
|
||||
name = "DefaultRoute"
|
||||
configs = ("defaultroute.sh",)
|
||||
startup = ("sh defaultroute.sh",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
# only add default routes for linked routing nodes
|
||||
routes = []
|
||||
for other_node in node.session.nodes.values():
|
||||
if not isinstance(other_node, CoreNode):
|
||||
continue
|
||||
if other_node.type not in ["router", "mdr"]:
|
||||
continue
|
||||
commonnets = node.commonnets(other_node)
|
||||
if commonnets:
|
||||
_, _, router_eth = commonnets[0]
|
||||
for x in router_eth.addrlist:
|
||||
addr, prefix = x.split("/")
|
||||
routes.append(addr)
|
||||
break
|
||||
cfg = "#!/bin/sh\n"
|
||||
cfg += "# auto-generated by DefaultRoute service (utility.py)\n"
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
continue
|
||||
cfg += "\n".join(map(cls.addrstr, ifc.addrlist))
|
||||
cfg += "\n"
|
||||
for route in routes:
|
||||
cfg += f"ip route add default via {route}\n"
|
||||
return cfg
|
||||
|
||||
@staticmethod
|
||||
def addrstr(x):
|
||||
if x.find(":") >= 0:
|
||||
net = Ipv6Prefix(x)
|
||||
fam = "inet6 ::"
|
||||
else:
|
||||
net = Ipv4Prefix(x)
|
||||
fam = "inet 0.0.0.0"
|
||||
if net.max_addr() == net.min_addr():
|
||||
return ""
|
||||
else:
|
||||
if os.uname()[0] == "Linux":
|
||||
rtcmd = "ip route add default via"
|
||||
else:
|
||||
raise Exception("unknown platform")
|
||||
return "%s %s" % (rtcmd, net.min_addr())
|
||||
|
||||
|
||||
class DefaultMulticastRouteService(UtilService):
|
||||
_name = "DefaultMulticastRoute"
|
||||
_configs = ("defaultmroute.sh",)
|
||||
_startup = ("sh defaultmroute.sh",)
|
||||
name = "DefaultMulticastRoute"
|
||||
configs = ("defaultmroute.sh",)
|
||||
startup = ("sh defaultmroute.sh",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
cfg = "#!/bin/sh\n"
|
||||
cfg += "# auto-generated by DefaultMulticastRoute service (utility.py)\n"
|
||||
cfg += "# the first interface is chosen below; please change it "
|
||||
cfg += "as needed\n"
|
||||
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control == True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
if os.uname()[0] == "Linux":
|
||||
rtcmd = "ip route add 224.0.0.0/4 dev"
|
||||
|
@ -126,19 +124,19 @@ class DefaultMulticastRouteService(UtilService):
|
|||
|
||||
|
||||
class StaticRouteService(UtilService):
|
||||
_name = "StaticRoute"
|
||||
_configs = ("staticroute.sh",)
|
||||
_startup = ("sh staticroute.sh",)
|
||||
_custom_needed = True
|
||||
name = "StaticRoute"
|
||||
configs = ("staticroute.sh",)
|
||||
startup = ("sh staticroute.sh",)
|
||||
custom_needed = True
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
cfg = "#!/bin/sh\n"
|
||||
cfg += "# auto-generated by StaticRoute service (utility.py)\n#\n"
|
||||
cfg += "# NOTE: this service must be customized to be of any use\n"
|
||||
cfg += "# Below are samples that you can uncomment and edit.\n#\n"
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += "\n".join(map(cls.routestr, ifc.addrlist))
|
||||
cfg += "\n"
|
||||
|
@ -146,40 +144,39 @@ class StaticRouteService(UtilService):
|
|||
|
||||
@staticmethod
|
||||
def routestr(x):
|
||||
if x.find(":") >= 0:
|
||||
net = Ipv6Prefix(x)
|
||||
fam = "inet6"
|
||||
addr = x.split("/")[0]
|
||||
if netaddr.valid_ipv6(addr):
|
||||
dst = "3ffe:4::/64"
|
||||
else:
|
||||
net = Ipv4Prefix(x)
|
||||
fam = "inet"
|
||||
dst = "10.9.8.0/24"
|
||||
if net.max_addr() == net.min_addr():
|
||||
net = netaddr.IPNetwork(x)
|
||||
if net[-2] == net[1]:
|
||||
return ""
|
||||
else:
|
||||
if os.uname()[0] == "Linux":
|
||||
rtcmd = "#/sbin/ip route add %s via" % dst
|
||||
else:
|
||||
raise Exception("unknown platform")
|
||||
return "%s %s" % (rtcmd, net.min_addr())
|
||||
return "%s %s" % (rtcmd, net[1])
|
||||
|
||||
|
||||
class SshService(UtilService):
|
||||
_name = "SSH"
|
||||
_configs = ("startsshd.sh", "/etc/ssh/sshd_config",)
|
||||
_dirs = ("/etc/ssh", "/var/run/sshd",)
|
||||
_startup = ("sh startsshd.sh",)
|
||||
_shutdown = ("killall sshd",)
|
||||
_validate = ()
|
||||
name = "SSH"
|
||||
configs = ("startsshd.sh", "/etc/ssh/sshd_config")
|
||||
dirs = ("/etc/ssh", "/var/run/sshd")
|
||||
startup = ("sh startsshd.sh",)
|
||||
shutdown = ("killall sshd",)
|
||||
validate = ()
|
||||
validation_mode = ServiceMode.BLOCKING
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Use a startup script for launching sshd in order to wait for host
|
||||
key generation.
|
||||
"""
|
||||
sshcfgdir = cls._dirs[0]
|
||||
sshstatedir = cls._dirs[1]
|
||||
sshcfgdir = cls.dirs[0]
|
||||
sshstatedir = cls.dirs[1]
|
||||
sshlibdir = "/usr/lib/openssh"
|
||||
if filename == "startsshd.sh":
|
||||
return """\
|
||||
|
@ -189,7 +186,11 @@ ssh-keygen -q -t rsa -N "" -f %s/ssh_host_rsa_key
|
|||
chmod 655 %s
|
||||
# wait until RSA host key has been generated to launch sshd
|
||||
/usr/sbin/sshd -f %s/sshd_config
|
||||
""" % (sshcfgdir, sshstatedir, sshcfgdir)
|
||||
""" % (
|
||||
sshcfgdir,
|
||||
sshstatedir,
|
||||
sshcfgdir,
|
||||
)
|
||||
else:
|
||||
return """\
|
||||
# auto-generated by SSH service (utility.py)
|
||||
|
@ -229,19 +230,23 @@ AcceptEnv LANG LC_*
|
|||
Subsystem sftp %s/sftp-server
|
||||
UsePAM yes
|
||||
UseDNS no
|
||||
""" % (sshcfgdir, sshstatedir, sshlibdir)
|
||||
""" % (
|
||||
sshcfgdir,
|
||||
sshstatedir,
|
||||
sshlibdir,
|
||||
)
|
||||
|
||||
|
||||
class DhcpService(UtilService):
|
||||
_name = "DHCP"
|
||||
_configs = ("/etc/dhcp/dhcpd.conf",)
|
||||
_dirs = ("/etc/dhcp",)
|
||||
_startup = ("dhcpd",)
|
||||
_shutdown = ("killall dhcpd",)
|
||||
_validate = ("pidof dhcpd",)
|
||||
name = "DHCP"
|
||||
configs = ("/etc/dhcp/dhcpd.conf",)
|
||||
dirs = ("/etc/dhcp", "/var/lib/dhcp")
|
||||
startup = ("touch /var/lib/dhcp/dhcpd.leases", "dhcpd")
|
||||
shutdown = ("killall dhcpd",)
|
||||
validate = ("pidof dhcpd",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Generate a dhcpd config file using the network address of
|
||||
each interface.
|
||||
|
@ -261,7 +266,7 @@ max-lease-time 7200;
|
|||
ddns-update-style none;
|
||||
"""
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += "\n".join(map(cls.subnetentry, ifc.addrlist))
|
||||
cfg += "\n"
|
||||
|
@ -273,14 +278,15 @@ ddns-update-style none;
|
|||
Generate a subnet declaration block given an IPv4 prefix string
|
||||
for inclusion in the dhcpd3 config file.
|
||||
"""
|
||||
if x.find(":") >= 0:
|
||||
addr = x.split("/")[0]
|
||||
if netaddr.valid_ipv6(addr):
|
||||
return ""
|
||||
else:
|
||||
addr = x.split("/")[0]
|
||||
net = Ipv4Prefix(x)
|
||||
net = netaddr.IPNetwork(x)
|
||||
# divide the address space in half
|
||||
rangelow = net.addr(net.num_addr() / 2)
|
||||
rangehigh = net.max_addr()
|
||||
index = (net.size - 2) / 2
|
||||
rangelow = net[index]
|
||||
rangehigh = net[-2]
|
||||
return """
|
||||
subnet %s netmask %s {
|
||||
pool {
|
||||
|
@ -289,21 +295,28 @@ subnet %s netmask %s {
|
|||
option routers %s;
|
||||
}
|
||||
}
|
||||
""" % (net.prefix_str(), net.netmask_str(), rangelow, rangehigh, addr)
|
||||
""" % (
|
||||
net.ip,
|
||||
net.netmask,
|
||||
rangelow,
|
||||
rangehigh,
|
||||
addr,
|
||||
)
|
||||
|
||||
|
||||
class DhcpClientService(UtilService):
|
||||
"""
|
||||
Use a DHCP client for all interfaces for addressing.
|
||||
"""
|
||||
_name = "DHCPClient"
|
||||
_configs = ("startdhcpclient.sh",)
|
||||
_startup = ("sh startdhcpclient.sh",)
|
||||
_shutdown = ("killall dhclient",)
|
||||
_validate = ("pidof dhclient",)
|
||||
|
||||
name = "DHCPClient"
|
||||
configs = ("startdhcpclient.sh",)
|
||||
startup = ("sh startdhcpclient.sh",)
|
||||
shutdown = ("killall dhclient",)
|
||||
validate = ("pidof dhclient",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Generate a script to invoke dhclient on all interfaces.
|
||||
"""
|
||||
|
@ -314,7 +327,7 @@ class DhcpClientService(UtilService):
|
|||
cfg += "#mkdir -p /var/run/resolvconf/interface\n"
|
||||
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control == True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % ifc.name
|
||||
cfg += " /var/run/resolvconf/resolv.conf\n"
|
||||
|
@ -327,15 +340,16 @@ class FtpService(UtilService):
|
|||
"""
|
||||
Start a vsftpd server.
|
||||
"""
|
||||
_name = "FTP"
|
||||
_configs = ("vsftpd.conf",)
|
||||
_dirs = ("/var/run/vsftpd/empty", "/var/ftp",)
|
||||
_startup = ("vsftpd ./vsftpd.conf",)
|
||||
_shutdown = ("killall vsftpd",)
|
||||
_validate = ("pidof vsftpd",)
|
||||
|
||||
name = "FTP"
|
||||
configs = ("vsftpd.conf",)
|
||||
dirs = ("/var/run/vsftpd/empty", "/var/ftp")
|
||||
startup = ("vsftpd ./vsftpd.conf",)
|
||||
shutdown = ("killall vsftpd",)
|
||||
validate = ("pidof vsftpd",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Generate a vsftpd.conf configuration file.
|
||||
"""
|
||||
|
@ -359,28 +373,38 @@ class HttpService(UtilService):
|
|||
"""
|
||||
Start an apache server.
|
||||
"""
|
||||
_name = "HTTP"
|
||||
_configs = ("/etc/apache2/apache2.conf", "/etc/apache2/envvars",
|
||||
"/var/www/index.html",)
|
||||
_dirs = ("/etc/apache2", "/var/run/apache2", "/var/log/apache2",
|
||||
"/run/lock", "/var/lock/apache2", "/var/www",)
|
||||
_startup = ("chown www-data /var/lock/apache2", "apache2ctl start",)
|
||||
_shutdown = ("apache2ctl stop",)
|
||||
_validate = ("pidof apache2",)
|
||||
|
||||
name = "HTTP"
|
||||
configs = (
|
||||
"/etc/apache2/apache2.conf",
|
||||
"/etc/apache2/envvars",
|
||||
"/var/www/index.html",
|
||||
)
|
||||
dirs = (
|
||||
"/etc/apache2",
|
||||
"/var/run/apache2",
|
||||
"/var/log/apache2",
|
||||
"/run/lock",
|
||||
"/var/lock/apache2",
|
||||
"/var/www",
|
||||
)
|
||||
startup = ("chown www-data /var/lock/apache2", "apache2ctl start")
|
||||
shutdown = ("apache2ctl stop",)
|
||||
validate = ("pidof apache2",)
|
||||
|
||||
APACHEVER22, APACHEVER24 = (22, 24)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Generate an apache2.conf configuration file.
|
||||
"""
|
||||
if filename == cls._configs[0]:
|
||||
return cls.generateapache2conf(node, filename, services)
|
||||
elif filename == cls._configs[1]:
|
||||
return cls.generateenvvars(node, filename, services)
|
||||
elif filename == cls._configs[2]:
|
||||
return cls.generatehtml(node, filename, services)
|
||||
if filename == cls.configs[0]:
|
||||
return cls.generateapache2conf(node, filename)
|
||||
elif filename == cls.configs[1]:
|
||||
return cls.generateenvvars(node, filename)
|
||||
elif filename == cls.configs[2]:
|
||||
return cls.generatehtml(node, filename)
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
@ -390,38 +414,42 @@ class HttpService(UtilService):
|
|||
Detect the apache2 version using the 'a2query' command.
|
||||
"""
|
||||
try:
|
||||
status, result = utils.cmd_output(['a2query', '-v'])
|
||||
except CoreCommandError:
|
||||
status = -1
|
||||
result = utils.cmd("a2query -v")
|
||||
status = 0
|
||||
except CoreCommandError as e:
|
||||
status = e.returncode
|
||||
result = e.stderr
|
||||
|
||||
if status == 0 and result[:3] == '2.4':
|
||||
if status == 0 and result[:3] == "2.4":
|
||||
return cls.APACHEVER24
|
||||
|
||||
return cls.APACHEVER22
|
||||
|
||||
@classmethod
|
||||
def generateapache2conf(cls, node, filename, services):
|
||||
lockstr = {cls.APACHEVER22:
|
||||
'LockFile ${APACHE_LOCK_DIR}/accept.lock\n',
|
||||
cls.APACHEVER24:
|
||||
'Mutex file:${APACHE_LOCK_DIR} default\n', }
|
||||
mpmstr = {cls.APACHEVER22: '', cls.APACHEVER24:
|
||||
'LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n', }
|
||||
def generateapache2conf(cls, node, filename):
|
||||
lockstr = {
|
||||
cls.APACHEVER22: "LockFile ${APACHE_LOCK_DIR}/accept.lock\n",
|
||||
cls.APACHEVER24: "Mutex file:${APACHE_LOCK_DIR} default\n",
|
||||
}
|
||||
mpmstr = {
|
||||
cls.APACHEVER22: "",
|
||||
cls.APACHEVER24: "LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n",
|
||||
}
|
||||
|
||||
permstr = {cls.APACHEVER22:
|
||||
' Order allow,deny\n Deny from all\n Satisfy all\n',
|
||||
cls.APACHEVER24:
|
||||
' Require all denied\n', }
|
||||
permstr = {
|
||||
cls.APACHEVER22: " Order allow,deny\n Deny from all\n Satisfy all\n",
|
||||
cls.APACHEVER24: " Require all denied\n",
|
||||
}
|
||||
|
||||
authstr = {cls.APACHEVER22:
|
||||
'LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so\n',
|
||||
cls.APACHEVER24:
|
||||
'LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so\n', }
|
||||
authstr = {
|
||||
cls.APACHEVER22: "LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so\n",
|
||||
cls.APACHEVER24: "LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so\n",
|
||||
}
|
||||
|
||||
permstr2 = {cls.APACHEVER22:
|
||||
'\t\tOrder allow,deny\n\t\tallow from all\n',
|
||||
cls.APACHEVER24:
|
||||
'\t\tRequire all granted\n', }
|
||||
permstr2 = {
|
||||
cls.APACHEVER22: "\t\tOrder allow,deny\n\t\tallow from all\n",
|
||||
cls.APACHEVER24: "\t\tRequire all granted\n",
|
||||
}
|
||||
|
||||
version = cls.detectversionfromcmd()
|
||||
cfg = "# apache2.conf generated by utility.py:HttpService\n"
|
||||
|
@ -469,7 +497,7 @@ Group ${APACHE_RUN_GROUP}
|
|||
|
||||
AccessFileName .htaccess
|
||||
|
||||
<Files ~ "^\.ht">
|
||||
<Files ~ "^\\.ht">
|
||||
"""
|
||||
cfg += permstr[version]
|
||||
cfg += """\
|
||||
|
@ -516,29 +544,29 @@ ServerSignature On
|
|||
TraceEnable Off
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerAdmin webmaster@localhost
|
||||
DocumentRoot /var/www
|
||||
<Directory />
|
||||
Options FollowSymLinks
|
||||
AllowOverride None
|
||||
</Directory>
|
||||
<Directory /var/www/>
|
||||
Options Indexes FollowSymLinks MultiViews
|
||||
AllowOverride None
|
||||
ServerAdmin webmaster@localhost
|
||||
DocumentRoot /var/www
|
||||
<Directory />
|
||||
Options FollowSymLinks
|
||||
AllowOverride None
|
||||
</Directory>
|
||||
<Directory /var/www/>
|
||||
Options Indexes FollowSymLinks MultiViews
|
||||
AllowOverride None
|
||||
"""
|
||||
cfg += permstr2[version]
|
||||
cfg += """\
|
||||
</Directory>
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
LogLevel warn
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</Directory>
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
LogLevel warn
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
</VirtualHost>
|
||||
|
||||
"""
|
||||
return cfg
|
||||
|
||||
@classmethod
|
||||
def generateenvvars(cls, node, filename, services):
|
||||
def generateenvvars(cls, node, filename):
|
||||
return """\
|
||||
# this file is used by apache2ctl - generated by utility.py:HttpService
|
||||
# these settings come from a default Ubuntu apache2 installation
|
||||
|
@ -553,15 +581,18 @@ export LANG
|
|||
"""
|
||||
|
||||
@classmethod
|
||||
def generatehtml(cls, node, filename, services):
|
||||
body = """\
|
||||
def generatehtml(cls, node, filename):
|
||||
body = (
|
||||
"""\
|
||||
<!-- generated by utility.py:HttpService -->
|
||||
<h1>%s web server</h1>
|
||||
<p>This is the default web page for this server.</p>
|
||||
<p>The web server software is running but no content has been added, yet.</p>
|
||||
""" % node.name
|
||||
"""
|
||||
% node.name
|
||||
)
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control == True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
body += "<li>%s - %s</li>\n" % (ifc.name, ifc.addrlist)
|
||||
return "<html><body>%s</body></html>" % body
|
||||
|
@ -571,17 +602,17 @@ class PcapService(UtilService):
|
|||
"""
|
||||
Pcap service for logging packets.
|
||||
"""
|
||||
_name = "pcap"
|
||||
_configs = ("pcap.sh",)
|
||||
_dirs = ()
|
||||
_startindex = 1
|
||||
_startup = ("sh pcap.sh start",)
|
||||
_shutdown = ("sh pcap.sh stop",)
|
||||
_validate = ("pidof tcpdump",)
|
||||
_meta = "logs network traffic to pcap packet capture files"
|
||||
|
||||
name = "pcap"
|
||||
configs = ("pcap.sh",)
|
||||
dirs = ()
|
||||
startup = ("sh pcap.sh start",)
|
||||
shutdown = ("sh pcap.sh stop",)
|
||||
validate = ("pidof tcpdump",)
|
||||
meta = "logs network traffic to pcap packet capture files"
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Generate a startpcap.sh traffic logging script.
|
||||
"""
|
||||
|
@ -595,11 +626,15 @@ if [ "x$1" = "xstart" ]; then
|
|||
|
||||
"""
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control == True:
|
||||
cfg += '# '
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
cfg += "# "
|
||||
redir = "< /dev/null"
|
||||
cfg += "tcpdump ${DUMPOPTS} -w %s.%s.pcap -i %s %s &\n" % \
|
||||
(node.name, ifc.name, ifc.name, redir)
|
||||
cfg += "tcpdump ${DUMPOPTS} -w %s.%s.pcap -i %s %s &\n" % (
|
||||
node.name,
|
||||
ifc.name,
|
||||
ifc.name,
|
||||
redir,
|
||||
)
|
||||
cfg += """
|
||||
|
||||
elif [ "x$1" = "xstop" ]; then
|
||||
|
@ -611,27 +646,28 @@ fi;
|
|||
|
||||
|
||||
class RadvdService(UtilService):
|
||||
_name = "radvd"
|
||||
_configs = ("/etc/radvd/radvd.conf",)
|
||||
_dirs = ("/etc/radvd",)
|
||||
_startup = ("radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",)
|
||||
_shutdown = ("pkill radvd",)
|
||||
_validate = ("pidof radvd",)
|
||||
name = "radvd"
|
||||
configs = ("/etc/radvd/radvd.conf",)
|
||||
dirs = ("/etc/radvd",)
|
||||
startup = ("radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",)
|
||||
shutdown = ("pkill radvd",)
|
||||
validate = ("pidof radvd",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Generate a RADVD router advertisement daemon config file
|
||||
using the network address of each interface.
|
||||
"""
|
||||
cfg = "# auto-generated by RADVD service (utility.py)\n"
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control == True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
prefixes = map(cls.subnetentry, ifc.addrlist)
|
||||
prefixes = list(map(cls.subnetentry, ifc.addrlist))
|
||||
if len(prefixes) < 1:
|
||||
continue
|
||||
cfg += """\
|
||||
cfg += (
|
||||
"""\
|
||||
interface %s
|
||||
{
|
||||
AdvSendAdvert on;
|
||||
|
@ -639,18 +675,23 @@ interface %s
|
|||
MaxRtrAdvInterval 10;
|
||||
AdvDefaultPreference low;
|
||||
AdvHomeAgentFlag off;
|
||||
""" % ifc.name
|
||||
"""
|
||||
% ifc.name
|
||||
)
|
||||
for prefix in prefixes:
|
||||
if prefix == "":
|
||||
continue
|
||||
cfg += """\
|
||||
cfg += (
|
||||
"""\
|
||||
prefix %s
|
||||
{
|
||||
AdvOnLink on;
|
||||
AdvAutonomous on;
|
||||
AdvRouterAddr on;
|
||||
};
|
||||
""" % prefix
|
||||
"""
|
||||
% prefix
|
||||
)
|
||||
cfg += "};\n"
|
||||
return cfg
|
||||
|
||||
|
@ -660,9 +701,9 @@ interface %s
|
|||
Generate a subnet declaration block given an IPv6 prefix string
|
||||
for inclusion in the RADVD config file.
|
||||
"""
|
||||
if x.find(":") >= 0:
|
||||
net = Ipv6Prefix(x)
|
||||
return str(net)
|
||||
addr = x.split("/")[0]
|
||||
if netaddr.valid_ipv6(addr):
|
||||
return x
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
@ -671,14 +712,15 @@ class AtdService(UtilService):
|
|||
"""
|
||||
Atd service for scheduling at jobs
|
||||
"""
|
||||
_name = "atd"
|
||||
_configs = ("startatd.sh",)
|
||||
_dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool")
|
||||
_startup = ("sh startatd.sh",)
|
||||
_shutdown = ("pkill atd",)
|
||||
|
||||
name = "atd"
|
||||
configs = ("startatd.sh",)
|
||||
dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool")
|
||||
startup = ("sh startatd.sh",)
|
||||
shutdown = ("pkill atd",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
return """
|
||||
#!/bin/sh
|
||||
echo 00001 > /var/spool/cron/atjobs/.SEQ
|
||||
|
@ -692,6 +734,6 @@ class UserDefinedService(UtilService):
|
|||
"""
|
||||
Dummy service allowing customization of anything.
|
||||
"""
|
||||
_name = "UserDefined"
|
||||
_startindex = 50
|
||||
_meta = "Customize this service to do anything upon startup."
|
||||
|
||||
name = "UserDefined"
|
||||
meta = "Customize this service to do anything upon startup."
|
||||
|
|
|
@ -2,8 +2,11 @@
|
|||
xorp.py: defines routing services provided by the XORP routing suite.
|
||||
"""
|
||||
|
||||
from core import logger
|
||||
from core.service import CoreService
|
||||
import logging
|
||||
|
||||
import netaddr
|
||||
|
||||
from core.services.coreservices import CoreService
|
||||
|
||||
|
||||
class XorpRtrmgr(CoreService):
|
||||
|
@ -11,18 +14,21 @@ class XorpRtrmgr(CoreService):
|
|||
XORP router manager service builds a config.boot file based on other
|
||||
enabled XORP services, and launches necessary daemons upon startup.
|
||||
"""
|
||||
_name = "xorp_rtrmgr"
|
||||
_group = "XORP"
|
||||
_depends = ()
|
||||
_dirs = ("/etc/xorp",)
|
||||
_configs = ("/etc/xorp/config.boot",)
|
||||
_startindex = 35
|
||||
_startup = ("xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid" % (_configs[0], _name, _name),)
|
||||
_shutdown = ("killall xorp_rtrmgr",)
|
||||
_validate = ("pidof xorp_rtrmgr",)
|
||||
|
||||
name = "xorp_rtrmgr"
|
||||
executables = ("xorp_rtrmgr",)
|
||||
group = "XORP"
|
||||
dirs = ("/etc/xorp",)
|
||||
configs = ("/etc/xorp/config.boot",)
|
||||
startup = (
|
||||
"xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid"
|
||||
% (configs[0], name, name),
|
||||
)
|
||||
shutdown = ("killall xorp_rtrmgr",)
|
||||
validate = ("pidof xorp_rtrmgr",)
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
"""
|
||||
Returns config.boot configuration file text. Other services that
|
||||
depend on this will have generatexorpconfig() hooks that are
|
||||
|
@ -38,12 +44,12 @@ class XorpRtrmgr(CoreService):
|
|||
cfg += " }\n"
|
||||
cfg += "}\n\n"
|
||||
|
||||
for s in services:
|
||||
for s in node.services:
|
||||
try:
|
||||
s._depends.index(cls._name)
|
||||
s.dependencies.index(cls.name)
|
||||
cfg += s.generatexorpconfig(node)
|
||||
except ValueError:
|
||||
logger.exception("error getting value from service: %s", cls._name)
|
||||
logging.exception("error getting value from service: %s", cls.name)
|
||||
|
||||
return cfg
|
||||
|
||||
|
@ -74,15 +80,16 @@ class XorpService(CoreService):
|
|||
Parent class for XORP services. Defines properties and methods
|
||||
common to XORP's routing daemons.
|
||||
"""
|
||||
_name = None
|
||||
_group = "XORP"
|
||||
_depends = ("xorp_rtrmgr",)
|
||||
_dirs = ()
|
||||
_configs = ()
|
||||
_startindex = 40
|
||||
_startup = ()
|
||||
_shutdown = ()
|
||||
_meta = "The config file for this service can be found in the xorp_rtrmgr service."
|
||||
|
||||
name = None
|
||||
executables = ("xorp_rtrmgr",)
|
||||
group = "XORP"
|
||||
dependencies = ("xorp_rtrmgr",)
|
||||
dirs = ()
|
||||
configs = ()
|
||||
startup = ()
|
||||
shutdown = ()
|
||||
meta = "The config file for this service can be found in the xorp_rtrmgr service."
|
||||
|
||||
@staticmethod
|
||||
def fea(forwarding):
|
||||
|
@ -103,7 +110,7 @@ class XorpService(CoreService):
|
|||
"""
|
||||
names = []
|
||||
for ifc in ifcs:
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
names.append(ifc.name)
|
||||
names.append("register_vif")
|
||||
|
@ -129,7 +136,7 @@ class XorpService(CoreService):
|
|||
cfg += " policy-statement export-connected {\n"
|
||||
cfg += "\tterm 100 {\n"
|
||||
cfg += "\t from {\n"
|
||||
cfg += "\t\tprotocol: \"connected\"\n"
|
||||
cfg += '\t\tprotocol: "connected"\n'
|
||||
cfg += "\t }\n"
|
||||
cfg += "\t}\n"
|
||||
cfg += " }\n"
|
||||
|
@ -142,16 +149,17 @@ class XorpService(CoreService):
|
|||
Helper to return the first IPv4 address of a node as its router ID.
|
||||
"""
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
for a in ifc.addrlist:
|
||||
if a.find(".") >= 0:
|
||||
return a.split('/')[0]
|
||||
a = a.split("/")[0]
|
||||
if netaddr.valid_ipv4(a):
|
||||
return a
|
||||
# raise ValueError, "no IPv4 address found for router ID"
|
||||
return "0.0.0.0"
|
||||
|
||||
@classmethod
|
||||
def generateconfig(cls, node, filename, services):
|
||||
def generate_config(cls, node, filename):
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
|
@ -165,7 +173,8 @@ class XorpOspfv2(XorpService):
|
|||
not build its own configuration file but has hooks for adding to the
|
||||
unified XORP configuration file.
|
||||
"""
|
||||
_name = "XORP_OSPFv2"
|
||||
|
||||
name = "XORP_OSPFv2"
|
||||
|
||||
@classmethod
|
||||
def generatexorpconfig(cls, node):
|
||||
|
@ -176,14 +185,14 @@ class XorpOspfv2(XorpService):
|
|||
cfg += "\trouter-id: %s\n" % rtrid
|
||||
cfg += "\tarea 0.0.0.0 {\n"
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += "\t interface %s {\n" % ifc.name
|
||||
cfg += "\t\tvif %s {\n" % ifc.name
|
||||
for a in ifc.addrlist:
|
||||
if a.find(".") < 0:
|
||||
continue
|
||||
addr = a.split("/")[0]
|
||||
if not netaddr.valid_ipv4(addr):
|
||||
continue
|
||||
cfg += "\t\t address %s {\n" % addr
|
||||
cfg += "\t\t }\n"
|
||||
cfg += "\t\t}\n"
|
||||
|
@ -200,7 +209,8 @@ class XorpOspfv3(XorpService):
|
|||
not build its own configuration file but has hooks for adding to the
|
||||
unified XORP configuration file.
|
||||
"""
|
||||
_name = "XORP_OSPFv3"
|
||||
|
||||
name = "XORP_OSPFv3"
|
||||
|
||||
@classmethod
|
||||
def generatexorpconfig(cls, node):
|
||||
|
@ -211,7 +221,7 @@ class XorpOspfv3(XorpService):
|
|||
cfg += "\trouter-id: %s\n" % rtrid
|
||||
cfg += "\tarea 0.0.0.0 {\n"
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += "\t interface %s {\n" % ifc.name
|
||||
cfg += "\t\tvif %s {\n" % ifc.name
|
||||
|
@ -227,8 +237,9 @@ class XorpBgp(XorpService):
|
|||
"""
|
||||
IPv4 inter-domain routing. AS numbers and peers must be customized.
|
||||
"""
|
||||
_name = "XORP_BGP"
|
||||
_custom_needed = True
|
||||
|
||||
name = "XORP_BGP"
|
||||
custom_needed = True
|
||||
|
||||
@classmethod
|
||||
def generatexorpconfig(cls, node):
|
||||
|
@ -241,7 +252,7 @@ class XorpBgp(XorpService):
|
|||
cfg += " bgp {\n"
|
||||
cfg += "\tbgp-id: %s\n" % rtrid
|
||||
cfg += "\tlocal-as: 65001 /* change this */\n"
|
||||
cfg += "\texport: \"export-connected\"\n"
|
||||
cfg += '\texport: "export-connected"\n'
|
||||
cfg += "\tpeer 10.0.1.1 { /* change this */\n"
|
||||
cfg += "\t local-ip: 10.0.1.1\n"
|
||||
cfg += "\t as: 65002\n"
|
||||
|
@ -257,7 +268,7 @@ class XorpRip(XorpService):
|
|||
RIP IPv4 unicast routing.
|
||||
"""
|
||||
|
||||
_name = "XORP_RIP"
|
||||
name = "XORP_RIP"
|
||||
|
||||
@classmethod
|
||||
def generatexorpconfig(cls, node):
|
||||
|
@ -265,16 +276,16 @@ class XorpRip(XorpService):
|
|||
cfg += cls.policyexportconnected()
|
||||
cfg += "\nprotocols {\n"
|
||||
cfg += " rip {\n"
|
||||
cfg += "\texport: \"export-connected\"\n"
|
||||
cfg += '\texport: "export-connected"\n'
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += "\tinterface %s {\n" % ifc.name
|
||||
cfg += "\t vif %s {\n" % ifc.name
|
||||
for a in ifc.addrlist:
|
||||
if a.find(".") < 0:
|
||||
continue
|
||||
addr = a.split("/")[0]
|
||||
if not netaddr.valid_ipv4(addr):
|
||||
continue
|
||||
cfg += "\t\taddress %s {\n" % addr
|
||||
cfg += "\t\t disable: false\n"
|
||||
cfg += "\t\t}\n"
|
||||
|
@ -289,7 +300,8 @@ class XorpRipng(XorpService):
|
|||
"""
|
||||
RIP NG IPv6 unicast routing.
|
||||
"""
|
||||
_name = "XORP_RIPNG"
|
||||
|
||||
name = "XORP_RIPNG"
|
||||
|
||||
@classmethod
|
||||
def generatexorpconfig(cls, node):
|
||||
|
@ -297,19 +309,12 @@ class XorpRipng(XorpService):
|
|||
cfg += cls.policyexportconnected()
|
||||
cfg += "\nprotocols {\n"
|
||||
cfg += " ripng {\n"
|
||||
cfg += "\texport: \"export-connected\"\n"
|
||||
cfg += '\texport: "export-connected"\n'
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += "\tinterface %s {\n" % ifc.name
|
||||
cfg += "\t vif %s {\n" % ifc.name
|
||||
# for a in ifc.addrlist:
|
||||
# if a.find(":") < 0:
|
||||
# continue
|
||||
# addr = a.split("/")[0]
|
||||
# cfg += "\t\taddress %s {\n" % addr
|
||||
# cfg += "\t\t disable: false\n"
|
||||
# cfg += "\t\t}\n"
|
||||
cfg += "\t\taddress %s {\n" % ifc.hwaddr.tolinklocal()
|
||||
cfg += "\t\t disable: false\n"
|
||||
cfg += "\t\t}\n"
|
||||
|
@ -324,7 +329,8 @@ class XorpPimSm4(XorpService):
|
|||
"""
|
||||
PIM Sparse Mode IPv4 multicast routing.
|
||||
"""
|
||||
_name = "XORP_PIMSM4"
|
||||
|
||||
name = "XORP_PIMSM4"
|
||||
|
||||
@classmethod
|
||||
def generatexorpconfig(cls, node):
|
||||
|
@ -334,7 +340,7 @@ class XorpPimSm4(XorpService):
|
|||
cfg += " igmp {\n"
|
||||
names = []
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
names.append(ifc.name)
|
||||
cfg += "\tinterface %s {\n" % ifc.name
|
||||
|
@ -358,12 +364,12 @@ class XorpPimSm4(XorpService):
|
|||
cfg += "\tbootstrap {\n"
|
||||
cfg += "\t cand-bsr {\n"
|
||||
cfg += "\t\tscope-zone 224.0.0.0/4 {\n"
|
||||
cfg += "\t\t cand-bsr-by-vif-name: \"%s\"\n" % names[0]
|
||||
cfg += '\t\t cand-bsr-by-vif-name: "%s"\n' % names[0]
|
||||
cfg += "\t\t}\n"
|
||||
cfg += "\t }\n"
|
||||
cfg += "\t cand-rp {\n"
|
||||
cfg += "\t\tgroup-prefix 224.0.0.0/4 {\n"
|
||||
cfg += "\t\t cand-rp-by-vif-name: \"%s\"\n" % names[0]
|
||||
cfg += '\t\t cand-rp-by-vif-name: "%s"\n' % names[0]
|
||||
cfg += "\t\t}\n"
|
||||
cfg += "\t }\n"
|
||||
cfg += "\t}\n"
|
||||
|
@ -383,7 +389,8 @@ class XorpPimSm6(XorpService):
|
|||
"""
|
||||
PIM Sparse Mode IPv6 multicast routing.
|
||||
"""
|
||||
_name = "XORP_PIMSM6"
|
||||
|
||||
name = "XORP_PIMSM6"
|
||||
|
||||
@classmethod
|
||||
def generatexorpconfig(cls, node):
|
||||
|
@ -393,7 +400,7 @@ class XorpPimSm6(XorpService):
|
|||
cfg += " mld {\n"
|
||||
names = []
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
names.append(ifc.name)
|
||||
cfg += "\tinterface %s {\n" % ifc.name
|
||||
|
@ -417,12 +424,12 @@ class XorpPimSm6(XorpService):
|
|||
cfg += "\tbootstrap {\n"
|
||||
cfg += "\t cand-bsr {\n"
|
||||
cfg += "\t\tscope-zone ff00::/8 {\n"
|
||||
cfg += "\t\t cand-bsr-by-vif-name: \"%s\"\n" % names[0]
|
||||
cfg += '\t\t cand-bsr-by-vif-name: "%s"\n' % names[0]
|
||||
cfg += "\t\t}\n"
|
||||
cfg += "\t }\n"
|
||||
cfg += "\t cand-rp {\n"
|
||||
cfg += "\t\tgroup-prefix ff00::/8 {\n"
|
||||
cfg += "\t\t cand-rp-by-vif-name: \"%s\"\n" % names[0]
|
||||
cfg += '\t\t cand-rp-by-vif-name: "%s"\n' % names[0]
|
||||
cfg += "\t\t}\n"
|
||||
cfg += "\t }\n"
|
||||
cfg += "\t}\n"
|
||||
|
@ -442,7 +449,8 @@ class XorpOlsr(XorpService):
|
|||
"""
|
||||
OLSR IPv4 unicast MANET routing.
|
||||
"""
|
||||
_name = "XORP_OLSR"
|
||||
|
||||
name = "XORP_OLSR"
|
||||
|
||||
@classmethod
|
||||
def generatexorpconfig(cls, node):
|
||||
|
@ -452,14 +460,14 @@ class XorpOlsr(XorpService):
|
|||
cfg += " olsr4 {\n"
|
||||
cfg += "\tmain-address: %s\n" % rtrid
|
||||
for ifc in node.netifs():
|
||||
if hasattr(ifc, 'control') and ifc.control is True:
|
||||
if hasattr(ifc, "control") and ifc.control is True:
|
||||
continue
|
||||
cfg += "\tinterface %s {\n" % ifc.name
|
||||
cfg += "\t vif %s {\n" % ifc.name
|
||||
for a in ifc.addrlist:
|
||||
if a.find(".") < 0:
|
||||
continue
|
||||
addr = a.split("/")[0]
|
||||
if not netaddr.valid_ipv4(addr):
|
||||
continue
|
||||
cfg += "\t\taddress %s {\n" % addr
|
||||
cfg += "\t\t}\n"
|
||||
cfg += "\t }\n"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue