Merge branch 'develop' into bugfix/quagga-ipv6-only-and-fast-convergence

This commit is contained in:
bharnden 2020-04-30 13:13:53 -07:00 committed by GitHub
commit 3c49d0676a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
454 changed files with 44907 additions and 26019 deletions

View file

@ -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__))
@ -16,6 +16,5 @@ def load():
Loads all services from the modules that reside under core.services.
:return: list of services that failed to load
:rtype: list[str]
"""
return ServiceManager.add_services(_PATH)

View file

@ -1,14 +1,16 @@
"""
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"
executables = ("bird",)
group = "BIRD"
@ -34,11 +36,12 @@ 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 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"
@ -72,7 +75,10 @@ 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 node.services:
@ -113,7 +119,7 @@ class BirdService(CoreService):
cfg = ""
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";\n' % ifc.name
@ -160,18 +166,18 @@ class BirdOspf(BirdService):
@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
@ -185,21 +191,21 @@ class BirdRadv(BirdService):
@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
@ -213,15 +219,15 @@ class BirdRip(BirdService):
@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
@ -236,10 +242,10 @@ class BirdStatic(BirdService):
@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

View 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

View file

@ -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.warn("missing python docker bindings")
logging.debug("missing python docker bindings")
class DockerService(CoreService):
@ -154,7 +154,7 @@ done
@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")

View file

@ -1,6 +1,5 @@
from core.enumerations import NodeTypes
from core.misc import nodeutils
from core.service import CoreService
from core.emane.nodes import EmaneNet
from core.services.coreservices import CoreService
from core.xml import emanexml
@ -21,17 +20,25 @@ class EmaneTransportService(CoreService):
if filename == cls.configs[0]:
transport_commands = []
for interface in node.netifs(sort=True):
network_node = node.session.get_object(interface.net.objid)
if nodeutils.is_node(network_node, NodeTypes.EMANE):
config = node.session.emane.get_configs(network_node.objid, network_node.model.name)
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
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.objid, transport_commands)
""" % (
node.id,
transport_commands,
)
else:
raise ValueError

695
daemon/core/services/frr.py Normal file
View 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

View file

@ -2,17 +2,18 @@
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"
dirs = ()
@ -32,13 +33,12 @@ 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 is 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
@ -63,13 +63,14 @@ class MgenSinkService(NrlService):
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"
executables = ("nrlnhdp",)
startup = ("nrlnhdp",)
@ -90,19 +91,20 @@ class NrlNhdp(NrlService):
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"
executables = ("nrlsmf",)
startup = ("sh startsmf.sh",)
@ -111,7 +113,7 @@ class NrlSmf(NrlService):
configs = ("startsmf.sh",)
@classmethod
def generate_config(cls, node, filename, ):
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,7 +125,7 @@ class NrlSmf(NrlService):
cmd = "nrlsmf instance %s_smf" % node.name
servicenames = map(lambda x: x.name, node.services)
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:
return ""
@ -155,6 +157,7 @@ class NrlOlsr(NrlService):
"""
Optimized Link State Routing protocol for MANET networks.
"""
name = "OLSR"
executables = ("nrlolsrd",)
startup = ("nrlolsrd",)
@ -176,19 +179,20 @@ class NrlOlsr(NrlService):
cmd += " -rpipe %s_olsr" % node.name
servicenames = map(lambda x: x.name, node.services)
if "SMF" in servicenames and not "NHDP" in servicenames:
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"
executables = ("nrlolsrv2",)
startup = ("nrlolsrv2",)
@ -211,19 +215,20 @@ class NrlOlsrv2(NrlService):
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"
executables = ("olsrd",)
configs = ("/etc/olsrd/olsrd.conf",)
@ -238,13 +243,13 @@ class OlsrOrg(NrlService):
Generate the appropriate command-line based on node interfaces.
"""
cmd = cls.startup[0]
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,)
@classmethod
def generate_config(cls, node, filename):
@ -582,7 +587,7 @@ class MgenActor(NrlService):
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',)
configs = ("start_mgen_actor.sh",)
# list of startup commands, also may be generated during startup
startup = ("sh start_mgen_actor.sh",)
# list of validation commands
@ -602,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, node.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 ""
@ -615,6 +619,7 @@ class Arouted(NrlService):
"""
Adaptive Routing
"""
name = "arouted"
executables = ("arouted",)
configs = ("startarouted.sh",)
@ -627,7 +632,8 @@ class Arouted(NrlService):
"""
Return the Quagga.conf or quaggaboot.sh file contents.
"""
cfg = """
cfg = (
"""
#!/bin/sh
for f in "/tmp/%s_smf"; do
count=1
@ -641,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

View file

@ -1,12 +1,14 @@
"""
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):
@ -16,7 +18,7 @@ class Zebra(CoreService):
configs = (
"/usr/local/etc/quagga/Quagga.conf",
"quaggaboot.sh",
"/usr/local/etc/quagga/vtysh.conf"
"/usr/local/etc/quagga/vtysh.conf",
)
startup = ("sh quaggaboot.sh zebra",)
shutdown = ("killall zebra",)
@ -34,7 +36,9 @@ class Zebra(CoreService):
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):
@ -55,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"
@ -77,13 +81,17 @@ class Zebra(CoreService):
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"
@ -101,9 +109,10 @@ 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)
@ -113,10 +122,12 @@ class Zebra(CoreService):
"""
Generate a shell script used to boot the Quagga daemons.
"""
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"')
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)
@ -192,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
@ -210,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):
@ -218,6 +234,7 @@ class QuaggaService(CoreService):
Parent class for Quagga services. Defines properties and methods
common to Quagga's routing daemons.
"""
name = None
group = "Quagga"
dependencies = ("zebra",)
@ -236,13 +253,14 @@ 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 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.%d" % node.objid
return "0.0.0.%d" % node.id
@staticmethod
def rj45check(ifc):
@ -254,7 +272,7 @@ 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
@ -277,6 +295,7 @@ 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",)
@ -307,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 ""
@ -318,13 +337,12 @@ 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
@ -335,7 +353,6 @@ class Ospfv2(QuaggaService):
if cls.rj45check(ifc):
return cfg
cfg += cls.ptpcheck(ifc)
return cfg + """\
ip ospf hello-interval 2
ip ospf dead-interval 6
@ -348,6 +365,7 @@ 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",)
@ -388,7 +406,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 ""
@ -396,9 +414,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"
@ -407,19 +426,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):
@ -429,24 +435,26 @@ class Ospfv3mdr(Ospfv3):
configuration file but has hooks for adding to the
unified Quagga.conf file.
"""
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
@ -457,6 +465,7 @@ 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",)
@ -470,7 +479,7 @@ class Bgp(QuaggaService):
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"
@ -482,6 +491,7 @@ class Rip(QuaggaService):
"""
The RIP service provides IPv4 routing for wired networks.
"""
name = "RIP"
startup = ()
shutdown = ("killall ripd",)
@ -505,6 +515,7 @@ class Ripng(QuaggaService):
"""
The RIP NG service provides IPv6 routing for wired networks.
"""
name = "RIPNG"
startup = ()
shutdown = ("killall ripngd",)
@ -529,6 +540,7 @@ 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",)
@ -547,8 +559,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"
@ -558,28 +569,29 @@ class Xpimd(QuaggaService):
"""
PIM multicast routing based on XORP.
"""
name = 'Xpimd'
name = "Xpimd"
startup = ()
shutdown = ('killall xpimd',)
validate = ('pidof xpimd',)
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"

View file

@ -4,13 +4,16 @@ 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.
"""
group = "SDN"
@classmethod
@ -23,9 +26,9 @@ class OvsService(SdnService):
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')
configs = ("OvsService.sh",)
startup = ("sh OvsService.sh",)
shutdown = ("killall ovs-vswitchd", "killall ovsdb-server")
@classmethod
def generate_config(cls, node, filename):
@ -35,37 +38,36 @@ class OvsService(SdnService):
if s.name == "zebra":
has_zebra = 1
# Check whether the node is running an SDN controller
has_sdn_ctrlr = 0
for s in node.services:
if s.name == "ryuService":
has_sdn_ctrlr = 1
cfg = "#!/bin/sh\n"
cfg += "# auto-generated by OvsService (OvsService.py)\n"
cfg += "/etc/init.d/openvswitch-switch start < /dev/null\n"
cfg += "ovs-vsctl add-br ovsbr0\n"
cfg += "ifconfig ovsbr0 up\n"
cfg += "## First make sure that the ovs services are up and running\n"
cfg += "/etc/init.d/openvswitch-switch start < /dev/null\n\n"
cfg += "## create the switch itself, set the fail mode to secure, \n"
cfg += "## this stops it from routing traffic without defined flows.\n"
cfg += "## remove the -- and everything after if you want it to act as a regular switch\n"
cfg += "ovs-vsctl add-br ovsbr0 -- set Bridge ovsbr0 fail-mode=secure\n"
cfg += "\n## Now add all our interfaces as ports to the switch\n"
portnum = 1
for 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]
# 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)
@ -73,19 +75,45 @@ class OvsService(SdnService):
raise ValueError("invalid address: %s" % ifcaddr)
# add interfaces to bridge
cfg += "ovs-vsctl add-port ovsbr0 eth%s\n" % ifnum
cfg += "ovs-vsctl add-port ovsbr0 sw%s\n" % ifnum
# Make port numbers explicit so they're easier to follow in reading the script
cfg += "## Add the CORE interface to the switch\n"
cfg += (
"ovs-vsctl add-port ovsbr0 eth%s -- set Interface eth%s ofport_request=%d\n"
% (ifnum, ifnum, portnum)
)
cfg += "## And then add its sibling veth interface\n"
cfg += (
"ovs-vsctl add-port ovsbr0 sw%s -- set Interface sw%s ofport_request=%d\n"
% (ifnum, ifnum, portnum + 1)
)
cfg += "## start them up so we can send/receive data\n"
cfg += "ovs-ofctl mod-port ovsbr0 eth%s up\n" % ifnum
cfg += "ovs-ofctl mod-port ovsbr0 sw%s up\n" % ifnum
cfg += "## Bring up the lower part of the veth pair\n"
cfg += "ip link set dev rtr%s up\n" % ifnum
portnum += 2
# Add rule for default controller if there is one local (even if the controller is not local, it finds it)
cfg += "\n## We assume there will be an SDN controller on the other end of this, \n"
cfg += "## but it will still function if there's not\n"
cfg += "ovs-vsctl set-controller ovsbr0 tcp:127.0.0.1:6633\n"
cfg += "\n## Now to create some default flows, \n"
cfg += "## if the above controller will be present then you probably want to delete them\n"
# 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 += "ovs-ofctl add-flow ovsbr0 priority=1000,in_port=%d,action=output:%d\n" % (portnum, portnum + 1)
cfg += "ovs-ofctl add-flow ovsbr0 priority=1000,in_port=%d,action=output:%d\n" % (portnum + 1, portnum)
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)
)
cfg += (
"ovs-ofctl add-flow ovsbr0 priority=1000,in_port=%d,action=output:%d\n"
% (portnum + 1, portnum)
)
portnum += 2
return cfg
@ -96,9 +124,9 @@ class RyuService(SdnService):
executables = ("ryu-manager",)
group = "SDN"
dirs = ()
configs = ('ryuService.sh',)
startup = ('sh ryuService.sh',)
shutdown = ('killall ryu-manager',)
configs = ("ryuService.sh",)
startup = ("sh ryuService.sh",)
shutdown = ("killall ryu-manager",)
@classmethod
def generate_config(cls, node, filename):
@ -106,8 +134,9 @@ class RyuService(SdnService):
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

View file

@ -3,16 +3,17 @@ 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',)
startup = ('sh vpnclient.sh',)
configs = ("vpnclient.sh",)
startup = ("sh vpnclient.sh",)
shutdown = ("killall openvpn",)
validate = ("pidof openvpn",)
custom_needed = True
@ -29,7 +30,9 @@ 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
@ -37,8 +40,8 @@ class VPNClient(CoreService):
class VPNServer(CoreService):
name = "VPNServer"
group = "Security"
configs = ('vpnserver.sh',)
startup = ('sh vpnserver.sh',)
configs = ("vpnserver.sh",)
startup = ("sh vpnserver.sh",)
shutdown = ("killall openvpn",)
validate = ("pidof openvpn",)
custom_needed = True
@ -56,7 +59,9 @@ 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
@ -64,8 +69,8 @@ class VPNServer(CoreService):
class IPsec(CoreService):
name = "IPsec"
group = "Security"
configs = ('ipsec.sh',)
startup = ('sh ipsec.sh',)
configs = ("ipsec.sh",)
startup = ("sh ipsec.sh",)
shutdown = ("killall racoon",)
custom_needed = True
@ -79,20 +84,19 @@ 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',)
startup = ('sh firewall.sh',)
configs = ("firewall.sh",)
startup = ("sh firewall.sh",)
custom_needed = True
@classmethod
@ -107,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

View file

@ -2,7 +2,7 @@
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"
@ -12,7 +12,11 @@ class Ucarp(CoreService):
group = "Utility"
dirs = (UCARP_ETC,)
configs = (
UCARP_ETC + "/default.sh", UCARP_ETC + "/default-up.sh", UCARP_ETC + "/default-down.sh", "ucarpboot.sh",)
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",)
@ -39,7 +43,7 @@ class Ucarp(CoreService):
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"
@ -100,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):
"""
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
@ -122,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):
"""
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
@ -143,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"
@ -156,10 +156,6 @@ fi
"""
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
@ -169,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"

View file

@ -1,21 +1,21 @@
"""
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"
dirs = ()
@ -53,12 +53,19 @@ 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
@ -70,32 +77,26 @@ class DefaultRouteService(UtilService):
@classmethod
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"
@ -110,7 +111,7 @@ class DefaultMulticastRouteService(UtilService):
cfg += "as needed\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
if os.uname()[0] == "Linux":
rtcmd = "ip route add 224.0.0.0/4 dev"
@ -135,7 +136,7 @@ class StaticRouteService(UtilService):
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"
@ -143,31 +144,30 @@ 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",)
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 generate_config(cls, node, filename):
@ -186,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)
@ -226,14 +230,18 @@ 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",)
dirs = ("/etc/dhcp", "/var/lib/dhcp")
startup = ("touch /var/lib/dhcp/dhcpd.leases", "dhcpd")
shutdown = ("killall dhcpd",)
validate = ("pidof dhcpd",)
@ -258,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"
@ -270,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 {
@ -286,13 +295,20 @@ 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",)
@ -311,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 is 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"
@ -324,9 +340,10 @@ class FtpService(UtilService):
"""
Start a vsftpd server.
"""
name = "FTP"
configs = ("vsftpd.conf",)
dirs = ("/var/run/vsftpd/empty", "/var/ftp",)
dirs = ("/var/run/vsftpd/empty", "/var/ftp")
startup = ("vsftpd ./vsftpd.conf",)
shutdown = ("killall vsftpd",)
validate = ("pidof vsftpd",)
@ -356,12 +373,22 @@ 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",)
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",)
@ -387,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):
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', }
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"
@ -466,7 +497,7 @@ Group ${APACHE_RUN_GROUP}
AccessFileName .htaccess
<Files ~ "^\.ht">
<Files ~ "^\\.ht">
"""
cfg += permstr[version]
cfg += """\
@ -513,22 +544,22 @@ 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>
"""
@ -551,14 +582,17 @@ export LANG
@classmethod
def generatehtml(cls, node, filename):
body = """\
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 is 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
@ -568,6 +602,7 @@ class PcapService(UtilService):
"""
Pcap service for logging packets.
"""
name = "pcap"
configs = ("pcap.sh",)
dirs = ()
@ -591,11 +626,15 @@ if [ "x$1" = "xstart" ]; then
"""
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is 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
@ -622,12 +661,13 @@ class RadvdService(UtilService):
"""
cfg = "# auto-generated by RADVD service (utility.py)\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
prefixes = map(cls.subnetentry, ifc.addrlist)
prefixes = list(map(cls.subnetentry, ifc.addrlist))
if len(prefixes) < 1:
continue
cfg += """\
cfg += (
"""\
interface %s
{
AdvSendAdvert on;
@ -635,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
@ -656,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 ""
@ -667,6 +712,7 @@ class AtdService(UtilService):
"""
Atd service for scheduling at jobs
"""
name = "atd"
configs = ("startatd.sh",)
dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool")
@ -688,5 +734,6 @@ class UserDefinedService(UtilService):
"""
Dummy service allowing customization of anything.
"""
name = "UserDefined"
meta = "Customize this service to do anything upon startup."

View file

@ -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,12 +14,16 @@ 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"
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),)
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",)
@ -42,7 +49,7 @@ class XorpRtrmgr(CoreService):
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
@ -73,6 +80,7 @@ class XorpService(CoreService):
Parent class for XORP services. Defines properties and methods
common to XORP's routing daemons.
"""
name = None
executables = ("xorp_rtrmgr",)
group = "XORP"
@ -102,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")
@ -128,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"
@ -141,11 +149,12 @@ 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"
@ -164,6 +173,7 @@ class XorpOspfv2(XorpService):
not build its own configuration file but has hooks for adding to the
unified XORP configuration file.
"""
name = "XORP_OSPFv2"
@classmethod
@ -175,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"
@ -199,6 +209,7 @@ class XorpOspfv3(XorpService):
not build its own configuration file but has hooks for adding to the
unified XORP configuration file.
"""
name = "XORP_OSPFv3"
@classmethod
@ -210,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
@ -226,6 +237,7 @@ class XorpBgp(XorpService):
"""
IPv4 inter-domain routing. AS numbers and peers must be customized.
"""
name = "XORP_BGP"
custom_needed = True
@ -240,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"
@ -264,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"
@ -288,6 +300,7 @@ class XorpRipng(XorpService):
"""
RIP NG IPv6 unicast routing.
"""
name = "XORP_RIPNG"
@classmethod
@ -296,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"
@ -323,6 +329,7 @@ class XorpPimSm4(XorpService):
"""
PIM Sparse Mode IPv4 multicast routing.
"""
name = "XORP_PIMSM4"
@classmethod
@ -333,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
@ -357,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"
@ -382,6 +389,7 @@ class XorpPimSm6(XorpService):
"""
PIM Sparse Mode IPv6 multicast routing.
"""
name = "XORP_PIMSM6"
@classmethod
@ -392,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
@ -416,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"
@ -441,6 +449,7 @@ class XorpOlsr(XorpService):
"""
OLSR IPv4 unicast MANET routing.
"""
name = "XORP_OLSR"
@classmethod
@ -451,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"