Merge branch 'develop' into ovs

This commit is contained in:
bharnden 2020-04-30 12:30:57 -07:00 committed by GitHub
commit 06e145f508
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
487 changed files with 49691 additions and 30722 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__))
@ -15,6 +15,6 @@ def load():
"""
Loads all services from the modules that reside under core.services.
:return: nothing
:return: list of services that failed to load
"""
ServiceManager.add_services(_PATH)
return ServiceManager.add_services(_PATH)

View file

@ -1,31 +1,32 @@
"""
bird.py: defines routing services provided by the BIRD Internet Routing Daemon.
"""
import netaddr
from core.service import CoreService
from core.services.coreservices import CoreService
class Bird(CoreService):
"""
Bird router support
"""
_name = "bird"
_group = "BIRD"
_depends = ()
_dirs = ("/etc/bird",)
_configs = ("/etc/bird/bird.conf",)
_startindex = 35
_startup = ("bird -c %s" % (_configs[0]),)
_shutdown = ("killall bird",)
_validate = ("pidof bird",)
name = "bird"
executables = ("bird",)
group = "BIRD"
dirs = ("/etc/bird",)
configs = ("/etc/bird/bird.conf",)
startup = ("bird -c %s" % (configs[0]),)
shutdown = ("killall bird",)
validate = ("pidof bird",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Return the bird.conf file contents.
"""
if filename == cls._configs[0]:
return cls.generateBirdConf(node, services)
if filename == cls.configs[0]:
return cls.generateBirdConf(node)
else:
raise ValueError
@ -35,16 +36,17 @@ class Bird(CoreService):
Helper to return the first IPv4 address of a node as its router ID.
"""
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, "control") and ifc.control is True:
continue
for a in ifc.addrlist:
if a.find(".") >= 0:
return a.split('/')[0]
a = a.split("/")[0]
if netaddr.valid_ipv4(a):
return a
# raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0"
@classmethod
def generateBirdConf(cls, node, services):
def generateBirdConf(cls, node):
"""
Returns configuration file text. Other services that depend on bird
will have generatebirdifcconfig() and generatebirdconfig()
@ -73,11 +75,14 @@ protocol device {
scan time 10; # Scan interfaces every 10 seconds
}
""" % (cls._name, cls.routerid(node))
""" % (
cls.name,
cls.routerid(node),
)
# Generate protocol specific configurations
for s in services:
if cls._name not in s._depends:
for s in node.services:
if cls.name not in s.dependencies:
continue
cfg += s.generatebirdconfig(node)
@ -90,15 +95,15 @@ class BirdService(CoreService):
common to Bird's routing daemons.
"""
_name = None
_group = "BIRD"
_depends = ("bird",)
_dirs = ()
_configs = ()
_startindex = 40
_startup = ()
_shutdown = ()
_meta = "The config file for this service can be found in the bird service."
name = None
executables = ("bird",)
group = "BIRD"
dependencies = ("bird",)
dirs = ()
configs = ()
startup = ()
shutdown = ()
meta = "The config file for this service can be found in the bird service."
@classmethod
def generatebirdconfig(cls, node):
@ -106,14 +111,15 @@ class BirdService(CoreService):
@classmethod
def generatebirdifcconfig(cls, node):
''' Use only bare interfaces descriptions in generated protocol
"""
Use only bare interfaces descriptions in generated protocol
configurations. This has the slight advantage of being the same
everywhere.
'''
"""
cfg = ""
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, "control") and ifc.control is True:
continue
cfg += ' interface "%s";\n' % ifc.name
@ -125,8 +131,8 @@ class BirdBgp(BirdService):
BGP BIRD Service (configuration generation)
"""
_name = "BIRD_BGP"
_custom_needed = True
name = "BIRD_BGP"
custom_needed = True
@classmethod
def generatebirdconfig(cls, node):
@ -156,22 +162,22 @@ class BirdOspf(BirdService):
OSPF BIRD Service (configuration generation)
"""
_name = "BIRD_OSPFv2"
name = "BIRD_OSPFv2"
@classmethod
def generatebirdconfig(cls, node):
cfg = 'protocol ospf {\n'
cfg += ' export filter {\n'
cfg += ' if source = RTS_BGP then {\n'
cfg += ' ospf_metric1 = 100;\n'
cfg += ' accept;\n'
cfg += ' }\n'
cfg += ' accept;\n'
cfg += ' };\n'
cfg += ' area 0.0.0.0 {\n'
cfg = "protocol ospf {\n"
cfg += " export filter {\n"
cfg += " if source = RTS_BGP then {\n"
cfg += " ospf_metric1 = 100;\n"
cfg += " accept;\n"
cfg += " }\n"
cfg += " accept;\n"
cfg += " };\n"
cfg += " area 0.0.0.0 {\n"
cfg += cls.generatebirdifcconfig(node)
cfg += ' };\n'
cfg += '}\n\n'
cfg += " };\n"
cfg += "}\n\n"
return cfg
@ -181,25 +187,25 @@ class BirdRadv(BirdService):
RADV BIRD Service (configuration generation)
"""
_name = "BIRD_RADV"
name = "BIRD_RADV"
@classmethod
def generatebirdconfig(cls, node):
cfg = '/* This is a sample config that must be customized */\n'
cfg = "/* This is a sample config that must be customized */\n"
cfg += 'protocol radv {\n'
cfg += ' # auto configuration on all interfaces\n'
cfg += "protocol radv {\n"
cfg += " # auto configuration on all interfaces\n"
cfg += cls.generatebirdifcconfig(node)
cfg += ' # Advertise DNS\n'
cfg += ' rdnss {\n'
cfg += '# lifetime mult 10;\n'
cfg += '# lifetime mult 10;\n'
cfg += '# ns 2001:0DB8:1234::11;\n'
cfg += '# ns 2001:0DB8:1234::11;\n'
cfg += '# ns 2001:0DB8:1234::12;\n'
cfg += '# ns 2001:0DB8:1234::12;\n'
cfg += ' };\n'
cfg += '}\n\n'
cfg += " # Advertise DNS\n"
cfg += " rdnss {\n"
cfg += "# lifetime mult 10;\n"
cfg += "# lifetime mult 10;\n"
cfg += "# ns 2001:0DB8:1234::11;\n"
cfg += "# ns 2001:0DB8:1234::11;\n"
cfg += "# ns 2001:0DB8:1234::12;\n"
cfg += "# ns 2001:0DB8:1234::12;\n"
cfg += " };\n"
cfg += "}\n\n"
return cfg
@ -209,19 +215,19 @@ class BirdRip(BirdService):
RIP BIRD Service (configuration generation)
"""
_name = "BIRD_RIP"
name = "BIRD_RIP"
@classmethod
def generatebirdconfig(cls, node):
cfg = 'protocol rip {\n'
cfg += ' period 10;\n'
cfg += ' garbage time 60;\n'
cfg = "protocol rip {\n"
cfg += " period 10;\n"
cfg += " garbage time 60;\n"
cfg += cls.generatebirdifcconfig(node)
cfg += ' honor neighbor;\n'
cfg += ' authentication none;\n'
cfg += ' import all;\n'
cfg += ' export all;\n'
cfg += '}\n\n'
cfg += " honor neighbor;\n"
cfg += " authentication none;\n"
cfg += " import all;\n"
cfg += " export all;\n"
cfg += "}\n\n"
return cfg
@ -231,15 +237,15 @@ class BirdStatic(BirdService):
Static Bird Service (configuration generation)
"""
_name = "BIRD_static"
_custom_needed = True
name = "BIRD_static"
custom_needed = True
@classmethod
def generatebirdconfig(cls, node):
cfg = '/* This is a sample config that must be customized */\n'
cfg += 'protocol static {\n'
cfg += '# route 0.0.0.0/0 via 198.51.100.130; # Default route. Do NOT advertise on BGP !\n'
cfg += '# route 203.0.113.0/24 reject; # Sink route\n'
cfg = "/* This is a sample config that must be customized */\n"
cfg += "protocol static {\n"
cfg += "# route 0.0.0.0/0 via 198.51.100.130; # Default route. Do NOT advertise on BGP !\n"
cfg += "# route 203.0.113.0/24 reject; # Sink route\n"
cfg += '# route 10.2.0.0/24 via "arc0"; # Secondary network\n'
cfg += '}\n\n'
cfg += "}\n\n"
return cfg

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.error("failure to import docker")
logging.debug("missing python docker bindings")
class DockerService(CoreService):
@ -112,19 +112,18 @@ class DockerService(CoreService):
This is a service which will allow running docker containers in a CORE
node.
"""
_name = "Docker"
_group = "Docker"
_depends = ()
_dirs = ('/var/lib/docker/containers/', '/run/shm', '/run/resolvconf',)
_configs = ('docker.sh',)
_startindex = 50
_startup = ('sh docker.sh',)
_shutdown = ('service docker stop',)
name = "Docker"
executables = ("docker",)
group = "Docker"
dirs = ('/var/lib/docker/containers/', '/run/shm', '/run/resolvconf',)
configs = ('docker.sh',)
startup = ('sh docker.sh',)
shutdown = ('service docker stop',)
# Container image to start
_image = ""
image = ""
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Returns a string having contents of a docker.sh script that
can be modified to start a specific docker image.
@ -139,7 +138,7 @@ class DockerService(CoreService):
# distros may just be docker
cfg += 'service docker start\n'
cfg += "# you could add a command to start a image here eg:\n"
if not cls._image:
if not cls.image:
cfg += "# docker run -d --net host --name coreDock <imagename>\n"
else:
cfg += """\
@ -150,12 +149,12 @@ until [ $result -eq 0 ]; do
# this is to alleviate contention to docker's SQLite database
sleep 0.3
done
""" % (cls._image,)
""" % (cls.image,)
return cfg
@classmethod
def on_load(cls):
logger.debug("loading custom docker services")
logging.debug("loading custom docker services")
if "Client" in globals():
client = Client(version="1.10")

View file

@ -0,0 +1,44 @@
from core.emane.nodes import EmaneNet
from core.services.coreservices import CoreService
from core.xml import emanexml
class EmaneTransportService(CoreService):
name = "transportd"
executables = ("emanetransportd", "emanegentransportxml")
group = "EMANE"
dependencies = ()
dirs = ()
configs = ("emanetransport.sh",)
startup = ("sh %s" % configs[0],)
validate = ("pidof %s" % executables[0],)
validation_timer = 0.5
shutdown = ("killall %s" % executables[0],)
@classmethod
def generate_config(cls, node, filename):
if filename == cls.configs[0]:
transport_commands = []
for interface in node.netifs(sort=True):
network_node = node.session.get_node(interface.net.id)
if isinstance(network_node, EmaneNet):
config = node.session.emane.get_configs(
network_node.id, network_node.model.name
)
if config and emanexml.is_external(config):
nem_id = network_node.getnemid(interface)
command = (
"emanetransportd -r -l 0 -d ../transportdaemon%s.xml"
% nem_id
)
transport_commands.append(command)
transport_commands = "\n".join(transport_commands)
return """
emanegentransportxml -o ../ ../platform%s.xml
%s
""" % (
node.id,
transport_commands,
)
else:
raise ValueError

695
daemon/core/services/frr.py Normal file
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,28 +2,27 @@
nrl.py: defines services provided by NRL protolib tools hosted here:
http://www.nrl.navy.mil/itd/ncs/products
"""
import netaddr
from core.misc import utils
from core.misc.ipaddress import Ipv4Prefix
from core.service import CoreService
from core import utils
from core.services.coreservices import CoreService
class NrlService(CoreService):
"""
Parent class for NRL services. Defines properties and methods
common to NRL's routing daemons.
"""""
_name = None
_group = "ProtoSvc"
_depends = ()
_dirs = ()
_configs = ()
_startindex = 45
_startup = ()
_shutdown = ()
"""
name = None
group = "ProtoSvc"
dirs = ()
configs = ()
startup = ()
shutdown = ()
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
return ""
@staticmethod
@ -34,27 +33,26 @@ class NrlService(CoreService):
interface's prefix length, so e.g. '/32' can turn into '/24'.
"""
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, "control") and ifc.control is True:
continue
for a in ifc.addrlist:
if a.find(".") >= 0:
addr = a.split('/')[0]
pre = Ipv4Prefix("%s/%s" % (addr, prefixlen))
return str(pre)
a = a.split("/")[0]
if netaddr.valid_ipv4(a):
return f"{a}/{prefixlen}"
# raise ValueError, "no IPv4 address found"
return "0.0.0.0/%s" % prefixlen
class MgenSinkService(NrlService):
_name = "MGEN_Sink"
_configs = ("sink.mgen",)
_startindex = 5
_startup = ("mgen input sink.mgen",)
_validate = ("pidof mgen",)
_shutdown = ("killall mgen",)
name = "MGEN_Sink"
executables = ("mgen",)
configs = ("sink.mgen",)
startup = ("mgen input sink.mgen",)
validate = ("pidof mgen",)
shutdown = ("killall mgen",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
cfg = "0.0 LISTEN UDP 5000\n"
for ifc in node.netifs():
name = utils.sysctl_devname(ifc.name)
@ -62,57 +60,60 @@ class MgenSinkService(NrlService):
return cfg
@classmethod
def getstartup(cls, node, services):
cmd = cls._startup[0]
def get_startup(cls, node):
cmd = cls.startup[0]
cmd += " output /tmp/mgen_%s.log" % node.name
return cmd,
return (cmd,)
class NrlNhdp(NrlService):
"""
NeighborHood Discovery Protocol for MANET networks.
"""
_name = "NHDP"
_startup = ("nrlnhdp",)
_shutdown = ("killall nrlnhdp",)
_validate = ("pidof nrlnhdp",)
name = "NHDP"
executables = ("nrlnhdp",)
startup = ("nrlnhdp",)
shutdown = ("killall nrlnhdp",)
validate = ("pidof nrlnhdp",)
@classmethod
def getstartup(cls, node, services):
def get_startup(cls, node):
"""
Generate the appropriate command-line based on node interfaces.
"""
cmd = cls._startup[0]
cmd = cls.startup[0]
cmd += " -l /var/log/nrlnhdp.log"
cmd += " -rpipe %s_nhdp" % node.name
servicenames = map(lambda x: x._name, services)
servicenames = map(lambda x: x.name, node.services)
if "SMF" in servicenames:
cmd += " -flooding ecds"
cmd += " -smfClient %s_smf" % node.name
netifs = filter(lambda x: not getattr(x, 'control', False), \
node.netifs())
netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs()))
if len(netifs) > 0:
interfacenames = map(lambda x: x.name, netifs)
cmd += " -i "
cmd += " -i ".join(interfacenames)
return cmd,
return (cmd,)
class NrlSmf(NrlService):
"""
Simplified Multicast Forwarding for MANET networks.
"""
_name = "SMF"
_startup = ("sh startsmf.sh",)
_shutdown = ("killall nrlsmf",)
_validate = ("pidof nrlsmf",)
_configs = ("startsmf.sh",)
name = "SMF"
executables = ("nrlsmf",)
startup = ("sh startsmf.sh",)
shutdown = ("killall nrlsmf",)
validate = ("pidof nrlsmf",)
configs = ("startsmf.sh",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Generate a startup script for SMF. Because nrlsmf does not
daemonize, it can cause problems in some situations when launched
@ -123,8 +124,8 @@ class NrlSmf(NrlService):
comments = ""
cmd = "nrlsmf instance %s_smf" % node.name
servicenames = map(lambda x: x._name, services)
netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
servicenames = map(lambda x: x.name, node.services)
netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs()))
if len(netifs) == 0:
return ""
@ -156,17 +157,19 @@ class NrlOlsr(NrlService):
"""
Optimized Link State Routing protocol for MANET networks.
"""
_name = "OLSR"
_startup = ("nrlolsrd",)
_shutdown = ("killall nrlolsrd",)
_validate = ("pidof nrlolsrd",)
name = "OLSR"
executables = ("nrlolsrd",)
startup = ("nrlolsrd",)
shutdown = ("killall nrlolsrd",)
validate = ("pidof nrlolsrd",)
@classmethod
def getstartup(cls, node, services):
def get_startup(cls, node):
"""
Generate the appropriate command-line based on node interfaces.
"""
cmd = cls._startup[0]
cmd = cls.startup[0]
# are multiple interfaces supported? No.
netifs = list(node.netifs())
if len(netifs) > 0:
@ -175,77 +178,81 @@ class NrlOlsr(NrlService):
cmd += " -l /var/log/nrlolsrd.log"
cmd += " -rpipe %s_olsr" % node.name
servicenames = map(lambda x: x._name, services)
if "SMF" in servicenames and not "NHDP" in servicenames:
servicenames = map(lambda x: x.name, node.services)
if "SMF" in servicenames and "NHDP" not in servicenames:
cmd += " -flooding s-mpr"
cmd += " -smfClient %s_smf" % node.name
if "zebra" in servicenames:
cmd += " -z"
return cmd,
return (cmd,)
class NrlOlsrv2(NrlService):
"""
Optimized Link State Routing protocol version 2 for MANET networks.
"""
_name = "OLSRv2"
_startup = ("nrlolsrv2",)
_shutdown = ("killall nrlolsrv2",)
_validate = ("pidof nrlolsrv2",)
name = "OLSRv2"
executables = ("nrlolsrv2",)
startup = ("nrlolsrv2",)
shutdown = ("killall nrlolsrv2",)
validate = ("pidof nrlolsrv2",)
@classmethod
def getstartup(cls, node, services):
def get_startup(cls, node):
"""
Generate the appropriate command-line based on node interfaces.
"""
cmd = cls._startup[0]
cmd = cls.startup[0]
cmd += " -l /var/log/nrlolsrv2.log"
cmd += " -rpipe %s_olsrv2" % node.name
servicenames = map(lambda x: x._name, services)
servicenames = map(lambda x: x.name, node.services)
if "SMF" in servicenames:
cmd += " -flooding ecds"
cmd += " -smfClient %s_smf" % node.name
cmd += " -p olsr"
netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs()))
if len(netifs) > 0:
interfacenames = map(lambda x: x.name, netifs)
cmd += " -i "
cmd += " -i ".join(interfacenames)
return cmd,
return (cmd,)
class OlsrOrg(NrlService):
"""
Optimized Link State Routing protocol from olsr.org for MANET networks.
"""
_name = "OLSRORG"
_configs = ("/etc/olsrd/olsrd.conf",)
_dirs = ("/etc/olsrd",)
_startup = ("olsrd",)
_shutdown = ("killall olsrd",)
_validate = ("pidof olsrd",)
name = "OLSRORG"
executables = ("olsrd",)
configs = ("/etc/olsrd/olsrd.conf",)
dirs = ("/etc/olsrd",)
startup = ("olsrd",)
shutdown = ("killall olsrd",)
validate = ("pidof olsrd",)
@classmethod
def getstartup(cls, node, services):
def get_startup(cls, node):
"""
Generate the appropriate command-line based on node interfaces.
"""
cmd = cls._startup[0]
netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
cmd = cls.startup[0]
netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs()))
if len(netifs) > 0:
interfacenames = map(lambda x: x.name, netifs)
cmd += " -i "
cmd += " -i ".join(interfacenames)
return cmd,
return (cmd,)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Generate a default olsrd config file to use the broadcast address of 255.255.255.255.
"""
@ -572,27 +579,24 @@ class MgenActor(NrlService):
"""
# a unique name is required, without spaces
_name = "MgenActor"
name = "MgenActor"
executables = ("mgen",)
# you can create your own group here
_group = "ProtoSvc"
# list of other services this service depends on
_depends = ()
group = "ProtoSvc"
# per-node directories
_dirs = ()
dirs = ()
# generated files (without a full path this file goes in the node's dir,
# e.g. /tmp/pycore.12345/n1.conf/)
_configs = ('start_mgen_actor.sh',)
# this controls the starting order vs other enabled services
_startindex = 50
configs = ("start_mgen_actor.sh",)
# list of startup commands, also may be generated during startup
_startup = ("sh start_mgen_actor.sh",)
startup = ("sh start_mgen_actor.sh",)
# list of validation commands
_validate = ("pidof mgen",)
validate = ("pidof mgen",)
# list of shutdown commands
_shutdown = ("killall mgen",)
shutdown = ("killall mgen",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Generate a startup script for MgenActor. Because mgenActor does not
daemonize, it can cause problems in some situations when launched
@ -603,8 +607,7 @@ class MgenActor(NrlService):
comments = ""
cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % node.name
servicenames = map(lambda x: x._name, services)
netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
netifs = [x for x in node.netifs() if not getattr(x, "control", False)]
if len(netifs) == 0:
return ""
@ -616,19 +619,21 @@ class Arouted(NrlService):
"""
Adaptive Routing
"""
_name = "arouted"
_configs = ("startarouted.sh",)
_startindex = NrlService._startindex + 10
_startup = ("sh startarouted.sh",)
_shutdown = ("pkill arouted",)
_validate = ("pidof arouted",)
name = "arouted"
executables = ("arouted",)
configs = ("startarouted.sh",)
startup = ("sh startarouted.sh",)
shutdown = ("pkill arouted",)
validate = ("pidof arouted",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Return the Quagga.conf or quaggaboot.sh file contents.
"""
cfg = """
cfg = (
"""
#!/bin/sh
for f in "/tmp/%s_smf"; do
count=1
@ -642,7 +647,9 @@ for f in "/tmp/%s_smf"; do
done
done
""" % node.name
"""
% node.name
)
cfg += "ip route add %s dev lo\n" % cls.firstipv4prefix(node, 24)
cfg += "arouted instance %s_smf tap %s_tap" % (node.name, node.name)
# seconds to consider a new route valid

View file

@ -1,52 +1,54 @@
"""
quagga.py: defines routing services provided by Quagga.
"""
import netaddr
from core import constants
from core.enumerations import LinkTypes, NodeTypes
from core.misc import ipaddress
from core.misc import nodeutils
from core.service import CoreService
from core.emane.nodes import EmaneNet
from core.emulator.enumerations import LinkTypes
from core.nodes.network import PtpNet, WlanNode
from core.nodes.physical import Rj45Node
from core.services.coreservices import CoreService
class Zebra(CoreService):
_name = "zebra"
_group = "Quagga"
_dirs = ("/usr/local/etc/quagga", "/var/run/quagga")
_configs = (
name = "zebra"
group = "Quagga"
dirs = ("/usr/local/etc/quagga", "/var/run/quagga")
configs = (
"/usr/local/etc/quagga/Quagga.conf",
"quaggaboot.sh",
"/usr/local/etc/quagga/vtysh.conf"
"/usr/local/etc/quagga/vtysh.conf",
)
_startindex = 35
_startup = ("sh quaggaboot.sh zebra",)
_shutdown = ("killall zebra",)
_validate = ("pidof zebra",)
startup = ("sh quaggaboot.sh zebra",)
shutdown = ("killall zebra",)
validate = ("pidof zebra",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Return the Quagga.conf or quaggaboot.sh file contents.
"""
if filename == cls._configs[0]:
return cls.generateQuaggaConf(node, services)
elif filename == cls._configs[1]:
return cls.generateQuaggaBoot(node, services)
elif filename == cls._configs[2]:
return cls.generateVtyshConf(node, services)
if filename == cls.configs[0]:
return cls.generateQuaggaConf(node)
elif filename == cls.configs[1]:
return cls.generateQuaggaBoot(node)
elif filename == cls.configs[2]:
return cls.generateVtyshConf(node)
else:
raise ValueError("file name (%s) is not a known configuration: %s",
filename, cls._configs)
raise ValueError(
"file name (%s) is not a known configuration: %s", filename, cls.configs
)
@classmethod
def generateVtyshConf(cls, node, services):
def generateVtyshConf(cls, node):
"""
Returns configuration file text.
"""
return "service integrated-vtysh-config\n"
@classmethod
def generateQuaggaConf(cls, node, services):
def generateQuaggaConf(cls, node):
"""
Returns configuration file text. Other services that depend on zebra
will have generatequaggaifcconfig() and generatequaggaconfig()
@ -57,7 +59,7 @@ class Zebra(CoreService):
for ifc in node.netifs():
cfg += "interface %s\n" % ifc.name
# include control interfaces in addressing but not routing daemons
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
cfg += " "
cfg += "\n ".join(map(cls.addrstr, ifc.addrlist))
cfg += "\n"
@ -66,34 +68,38 @@ class Zebra(CoreService):
cfgv6 = ""
want_ipv4 = False
want_ipv6 = False
for s in services:
if cls._name not in s._depends:
for s in node.services:
if cls.name not in s.dependencies:
continue
ifccfg = s.generatequaggaifcconfig(node, ifc)
if s._ipv4_routing:
if s.ipv4_routing:
want_ipv4 = True
if s._ipv6_routing:
if s.ipv6_routing:
want_ipv6 = True
cfgv6 += ifccfg
else:
cfgv4 += ifccfg
if want_ipv4:
ipv4list = filter(lambda x: ipaddress.is_ipv4_address(x.split('/')[0]), ifc.addrlist)
ipv4list = filter(
lambda x: netaddr.valid_ipv4(x.split("/")[0]), ifc.addrlist
)
cfg += " "
cfg += "\n ".join(map(cls.addrstr, ipv4list))
cfg += "\n"
cfg += cfgv4
if want_ipv6:
ipv6list = filter(lambda x: ipaddress.is_ipv6_address(x.split('/')[0]), ifc.addrlist)
ipv6list = filter(
lambda x: netaddr.valid_ipv6(x.split("/")[0]), ifc.addrlist
)
cfg += " "
cfg += "\n ".join(map(cls.addrstr, ipv6list))
cfg += "\n"
cfg += cfgv6
cfg += "!\n"
for s in services:
if cls._name not in s._depends:
for s in node.services:
if cls.name not in s.dependencies:
continue
cfg += s.generatequaggaconfig(node)
return cfg
@ -103,24 +109,25 @@ class Zebra(CoreService):
"""
helper for mapping IP addresses to zebra config statements
"""
if x.find(".") >= 0:
addr = x.split("/")[0]
if netaddr.valid_ipv4(addr):
return "ip address %s" % x
elif x.find(":") >= 0:
elif netaddr.valid_ipv6(addr):
return "ipv6 address %s" % x
else:
raise ValueError("invalid address: %s", x)
@classmethod
def generateQuaggaBoot(cls, node, services):
def generateQuaggaBoot(cls, node):
"""
Generate a shell script used to boot the Quagga daemons.
"""
try:
quagga_bin_search = node.session.config['quagga_bin_search']
quagga_sbin_search = node.session.config['quagga_sbin_search']
except KeyError:
quagga_bin_search = '"/usr/local/bin /usr/bin /usr/lib/quagga"'
quagga_sbin_search = '"/usr/local/sbin /usr/sbin /usr/lib/quagga"'
quagga_bin_search = node.session.options.get_config(
"quagga_bin_search", default='"/usr/local/bin /usr/bin /usr/lib/quagga"'
)
quagga_sbin_search = node.session.options.get_config(
"quagga_sbin_search", default='"/usr/local/sbin /usr/sbin /usr/lib/quagga"'
)
return """\
#!/bin/sh
# auto-generated by zebra service (quagga.py)
@ -196,7 +203,7 @@ bootquagga()
bootdaemon "zebra"
for r in rip ripng ospf6 ospf bgp babel; do
if grep -q "^router \<${r}\>" $QUAGGA_CONF; then
if grep -q "^router \\<${r}\\>" $QUAGGA_CONF; then
bootdaemon "${r}d"
fi
done
@ -214,7 +221,12 @@ if [ "$1" != "zebra" ]; then
fi
confcheck
bootquagga
""" % (cls._configs[0], quagga_sbin_search, quagga_bin_search, constants.QUAGGA_STATE_DIR)
""" % (
cls.configs[0],
quagga_sbin_search,
quagga_bin_search,
constants.QUAGGA_STATE_DIR,
)
class QuaggaService(CoreService):
@ -222,18 +234,18 @@ class QuaggaService(CoreService):
Parent class for Quagga services. Defines properties and methods
common to Quagga's routing daemons.
"""
_name = None
_group = "Quagga"
_depends = ("zebra",)
_dirs = ()
_configs = ()
_startindex = 40
_startup = ()
_shutdown = ()
_meta = "The config file for this service can be found in the Zebra service."
_ipv4_routing = False
_ipv6_routing = False
name = None
group = "Quagga"
dependencies = ("zebra",)
dirs = ()
configs = ()
startup = ()
shutdown = ()
meta = "The config file for this service can be found in the Zebra service."
ipv4_routing = False
ipv6_routing = False
@staticmethod
def routerid(node):
@ -241,11 +253,12 @@ class QuaggaService(CoreService):
Helper to return the first IPv4 address of a node as its router ID.
"""
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, "control") and ifc.control is True:
continue
for a in ifc.addrlist:
if a.find(".") >= 0:
return a.split('/')[0]
a = a.split("/")[0]
if netaddr.valid_ipv4(a):
return a
# raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0"
@ -259,12 +272,12 @@ class QuaggaService(CoreService):
for peerifc in ifc.net.netifs():
if peerifc == ifc:
continue
if nodeutils.is_node(peerifc, NodeTypes.RJ45):
if isinstance(peerifc, Rj45Node):
return True
return False
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
return ""
@classmethod
@ -282,11 +295,12 @@ class Ospfv2(QuaggaService):
not build its own configuration file but has hooks for adding to the
unified Quagga.conf file.
"""
_name = "OSPFv2"
_startup = ()
_shutdown = ("killall ospfd",)
_validate = ("pidof ospfd",)
_ipv4_routing = True
name = "OSPFv2"
startup = ()
shutdown = ("killall ospfd",)
validate = ("pidof ospfd",)
ipv4_routing = True
@staticmethod
def mtucheck(ifc):
@ -312,7 +326,7 @@ class Ospfv2(QuaggaService):
Helper to detect whether interface is connected to a notional
point-to-point link.
"""
if nodeutils.is_node(ifc.net, NodeTypes.PEER_TO_PEER):
if isinstance(ifc.net, PtpNet):
return " ip ospf network point-to-point\n"
return ""
@ -323,32 +337,18 @@ class Ospfv2(QuaggaService):
cfg += " router-id %s\n" % rtrid
# network 10.0.0.0/24 area 0
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
for a in ifc.addrlist:
if a.find(".") < 0:
continue
net = ipaddress.Ipv4Prefix(a)
cfg += " network %s area 0\n" % net
addr = a.split("/")[0]
if netaddr.valid_ipv4(addr):
cfg += " network %s area 0\n" % a
cfg += "!\n"
return cfg
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
return cls.mtucheck(ifc)
# cfg = cls.mtucheck(ifc)
# external RJ45 connections will use default OSPF timers
# if cls.rj45check(ifc):
# return cfg
# cfg += cls.ptpcheck(ifc)
# return cfg + """\
# ip ospf hello-interval 2
# ip ospf dead-interval 6
# ip ospf retransmit-interval 5
# """
class Ospfv3(QuaggaService):
@ -357,12 +357,13 @@ class Ospfv3(QuaggaService):
not build its own configuration file but has hooks for adding to the
unified Quagga.conf file.
"""
_name = "OSPFv3"
_startup = ()
_shutdown = ("killall ospf6d",)
_validate = ("pidof ospf6d",)
_ipv4_routing = True
_ipv6_routing = True
name = "OSPFv3"
startup = ()
shutdown = ("killall ospf6d",)
validate = ("pidof ospf6d",)
ipv4_routing = True
ipv6_routing = True
@staticmethod
def minmtu(ifc):
@ -397,7 +398,7 @@ class Ospfv3(QuaggaService):
Helper to detect whether interface is connected to a notional
point-to-point link.
"""
if nodeutils.is_node(ifc.net, NodeTypes.PEER_TO_PEER):
if isinstance(ifc.net, PtpNet):
return " ipv6 ospf6 network point-to-point\n"
return ""
@ -405,9 +406,10 @@ class Ospfv3(QuaggaService):
def generatequaggaconfig(cls, node):
cfg = "router ospf6\n"
rtrid = cls.routerid(node)
cfg += " instance-id 65\n"
cfg += " router-id %s\n" % rtrid
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
cfg += " interface %s area 0.0.0.0\n" % ifc.name
cfg += "!\n"
@ -416,19 +418,6 @@ class Ospfv3(QuaggaService):
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
return cls.mtucheck(ifc)
# cfg = cls.mtucheck(ifc)
# external RJ45 connections will use default OSPF timers
# if cls.rj45check(ifc):
# return cfg
# cfg += cls.ptpcheck(ifc)
# return cfg + """\
# ipv6 ospf6 hello-interval 2
# ipv6 ospf6 dead-interval 6
# ipv6 ospf6 retransmit-interval 5
# """
class Ospfv3mdr(Ospfv3):
@ -438,24 +427,26 @@ class Ospfv3mdr(Ospfv3):
configuration file but has hooks for adding to the
unified Quagga.conf file.
"""
_name = "OSPFv3MDR"
_ipv4_routing = True
name = "OSPFv3MDR"
ipv4_routing = True
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
cfg = cls.mtucheck(ifc)
# Uncomment the following line to use Address Family Translation for IPv4
cfg += " ipv6 ospf6 instance-id 65\n"
if ifc.net is not None and nodeutils.is_node(ifc.net, (NodeTypes.WIRELESS_LAN, NodeTypes.EMANE)):
return cfg + """\
if ifc.net is not None and isinstance(ifc.net, (WlanNode, EmaneNet)):
return (
cfg
+ """\
ipv6 ospf6 hello-interval 2
ipv6 ospf6 dead-interval 6
ipv6 ospf6 retransmit-interval 5
ipv6 ospf6 network manet-designated-router
ipv6 ospf6 diffhellos
ipv6 ospf6 twohoprefresh 3
ipv6 ospf6 adjacencyconnectivity uniconnected
ipv6 ospf6 lsafullness mincostlsa
"""
)
else:
return cfg
@ -466,20 +457,21 @@ class Bgp(QuaggaService):
Peers must be manually configured, with a full mesh for those
having the same AS number.
"""
_name = "BGP"
_startup = ()
_shutdown = ("killall bgpd",)
_validate = ("pidof bgpd",)
_custom_needed = True
_ipv4_routing = True
_ipv6_routing = True
name = "BGP"
startup = ()
shutdown = ("killall bgpd",)
validate = ("pidof bgpd",)
custom_needed = True
ipv4_routing = True
ipv6_routing = True
@classmethod
def generatequaggaconfig(cls, node):
cfg = "!\n! BGP configuration\n!\n"
cfg += "! You should configure the AS number below,\n"
cfg += "! along with this router's peers.\n!\n"
cfg += "router bgp %s\n" % node.objid
cfg += "router bgp %s\n" % node.id
rtrid = cls.routerid(node)
cfg += " bgp router-id %s\n" % rtrid
cfg += " redistribute connected\n"
@ -491,11 +483,12 @@ class Rip(QuaggaService):
"""
The RIP service provides IPv4 routing for wired networks.
"""
_name = "RIP"
_startup = ()
_shutdown = ("killall ripd",)
_validate = ("pidof ripd",)
_ipv4_routing = True
name = "RIP"
startup = ()
shutdown = ("killall ripd",)
validate = ("pidof ripd",)
ipv4_routing = True
@classmethod
def generatequaggaconfig(cls, node):
@ -514,11 +507,12 @@ class Ripng(QuaggaService):
"""
The RIP NG service provides IPv6 routing for wired networks.
"""
_name = "RIPNG"
_startup = ()
_shutdown = ("killall ripngd",)
_validate = ("pidof ripngd",)
_ipv6_routing = True
name = "RIPNG"
startup = ()
shutdown = ("killall ripngd",)
validate = ("pidof ripngd",)
ipv6_routing = True
@classmethod
def generatequaggaconfig(cls, node):
@ -538,11 +532,12 @@ class Babel(QuaggaService):
The Babel service provides a loop-avoiding distance-vector routing
protocol for IPv6 and IPv4 with fast convergence properties.
"""
_name = "Babel"
_startup = ()
_shutdown = ("killall babeld",)
_validate = ("pidof babeld",)
_ipv6_routing = True
name = "Babel"
startup = ()
shutdown = ("killall babeld",)
validate = ("pidof babeld",)
ipv6_routing = True
@classmethod
def generatequaggaconfig(cls, node):
@ -556,8 +551,7 @@ class Babel(QuaggaService):
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
type = "wired"
if ifc.net and ifc.net.linktype == LinkTypes.WIRELESS.value:
if ifc.net and ifc.net.linktype == LinkTypes.WIRELESS:
return " babel wireless\n no babel split-horizon\n"
else:
return " babel wired\n babel split-horizon\n"
@ -567,28 +561,29 @@ class Xpimd(QuaggaService):
"""
PIM multicast routing based on XORP.
"""
_name = 'Xpimd'
_startup = ()
_shutdown = ('killall xpimd',)
_validate = ('pidof xpimd',)
_ipv4_routing = True
name = "Xpimd"
startup = ()
shutdown = ("killall xpimd",)
validate = ("pidof xpimd",)
ipv4_routing = True
@classmethod
def generatequaggaconfig(cls, node):
ifname = 'eth0'
ifname = "eth0"
for ifc in node.netifs():
if ifc.name != 'lo':
if ifc.name != "lo":
ifname = ifc.name
break
cfg = 'router mfea\n!\n'
cfg += 'router igmp\n!\n'
cfg += 'router pim\n'
cfg += ' !ip pim rp-address 10.0.0.1\n'
cfg += ' ip pim bsr-candidate %s\n' % ifname
cfg += ' ip pim rp-candidate %s\n' % ifname
cfg += ' !ip pim spt-threshold interval 10 bytes 80000\n'
cfg = "router mfea\n!\n"
cfg += "router igmp\n!\n"
cfg += "router pim\n"
cfg += " !ip pim rp-address 10.0.0.1\n"
cfg += " ip pim bsr-candidate %s\n" % ifname
cfg += " ip pim rp-candidate %s\n" % ifname
cfg += " !ip pim spt-threshold interval 10 bytes 80000\n"
return cfg
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
return ' ip mfea\n ip igmp\n ip pim\n'
return " ip mfea\n ip igmp\n ip pim\n"

View file

@ -4,51 +4,40 @@ sdn.py defines services to start Open vSwitch and the Ryu SDN Controller.
import re
from core.service import CoreService
import netaddr
from core.services.coreservices import CoreService
class SdnService(CoreService):
"""
Parent class for SDN services.
"""
_name = None
_group = "SDN"
_depends = ()
_dirs = ()
_configs = ()
_startindex = 50
_startup = ()
_shutdown = ()
group = "SDN"
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
return ""
class OvsService(SdnService):
_name = "OvsService"
_group = "SDN"
_depends = ()
_dirs = ("/etc/openvswitch", "/var/run/openvswitch", "/var/log/openvswitch")
_configs = ('OvsService.sh',)
_startindex = 50
_startup = ('sh OvsService.sh',)
_shutdown = ('killall ovs-vswitchd', 'killall ovsdb-server')
name = "OvsService"
executables = ("ovs-ofctl", "ovs-vsctl")
group = "SDN"
dirs = ("/etc/openvswitch", "/var/run/openvswitch", "/var/log/openvswitch")
configs = ("OvsService.sh",)
startup = ("sh OvsService.sh",)
shutdown = ("killall ovs-vswitchd", "killall ovsdb-server")
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
# Check whether the node is running zebra
has_zebra = 0
for s in services:
if s._name == "zebra":
for s in node.services:
if s.name == "zebra":
has_zebra = 1
# Check whether the node is running an SDN controller
has_sdn_ctrlr = 0
for s in services:
if s._name == "ryuService":
has_sdn_ctrlr = 1
cfg = "#!/bin/sh\n"
cfg += "# auto-generated by OvsService (OvsService.py)\n"
cfg += "## First make sure that the ovs services are up and running\n"
@ -61,7 +50,7 @@ class OvsService(SdnService):
cfg += "\n## Now add all our interfaces as ports to the switch\n"
portnum = 1
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
ifnumstr = re.findall(r"\d+", ifc.name)
ifnum = ifnumstr[0]
@ -69,17 +58,16 @@ class OvsService(SdnService):
# create virtual interfaces
cfg += "## Create a veth pair to send the data to\n"
cfg += "ip link add rtr%s type veth peer name sw%s\n" % (ifnum, ifnum)
# cfg += "ifconfig rtr%s up\n" % ifnum
# cfg += "ifconfig sw%s up\n" % ifnum
# remove ip address of eths because quagga/zebra will assign same IPs to rtr interfaces
# or assign them manually to rtr interfaces if zebra is not running
for ifcaddr in ifc.addrlist:
if ifcaddr.find(".") >= 0:
addr = ifcaddr.split("/")[0]
if netaddr.valid_ipv4(addr):
cfg += "ip addr del %s dev %s\n" % (ifcaddr, ifc.name)
if has_zebra == 0:
cfg += "ip addr add %s dev rtr%s\n" % (ifcaddr, ifnum)
elif ifcaddr.find(":") >= 0:
elif netaddr.valid_ipv6(addr):
cfg += "ip -6 addr del %s dev %s\n" % (ifcaddr, ifc.name)
if has_zebra == 0:
cfg += "ip -6 addr add %s dev rtr%s\n" % (ifcaddr, ifnum)
@ -109,7 +97,7 @@ class OvsService(SdnService):
# Setup default flows
portnum = 1
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
cfg += "## Take the data from the CORE interface and put it on the veth and vice versa\n"
cfg += "ovs-ofctl add-flow ovsbr0 priority=1000,in_port=%d,action=output:%d\n" % (portnum, portnum + 1)
@ -120,23 +108,23 @@ class OvsService(SdnService):
class RyuService(SdnService):
_name = "ryuService"
_group = "SDN"
_depends = ()
_dirs = ()
_configs = ('ryuService.sh',)
_startindex = 50
_startup = ('sh ryuService.sh',)
_shutdown = ('killall ryu-manager',)
name = "ryuService"
executables = ("ryu-manager",)
group = "SDN"
dirs = ()
configs = ("ryuService.sh",)
startup = ("sh ryuService.sh",)
shutdown = ("killall ryu-manager",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Return a string that will be written to filename, or sent to the
GUI for user customization.
"""
app_path = "/usr/local/lib/python2.7/dist-packages/ryu/app"
cfg = "#!/bin/sh\n"
cfg += "# auto-generated by ryuService (ryuService.py)\n"
cfg += '/usr/local/bin/ryu-manager --observe-links %s/ofctl_rest.py %s/rest_topology.py' % (app_path, app_path)
cfg += (
"ryu-manager --observe-links ryu.app.ofctl_rest ryu.app.rest_topology &\n"
)
return cfg

View file

@ -3,23 +3,23 @@ security.py: defines security services (vpnclient, vpnserver, ipsec and
firewall)
"""
import logging
from core import constants
from core import logger
from core.service import CoreService
from core.services.coreservices import CoreService
class VPNClient(CoreService):
_name = "VPNClient"
_group = "Security"
_configs = ('vpnclient.sh',)
_startindex = 60
_startup = ('sh vpnclient.sh',)
_shutdown = ("killall openvpn",)
_validate = ("pidof openvpn",)
_custom_needed = True
name = "VPNClient"
group = "Security"
configs = ("vpnclient.sh",)
startup = ("sh vpnclient.sh",)
shutdown = ("killall openvpn",)
validate = ("pidof openvpn",)
custom_needed = True
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Return the client.conf and vpnclient.sh file contents to
"""
@ -30,23 +30,24 @@ class VPNClient(CoreService):
try:
cfg += open(fname, "rb").read()
except IOError:
logger.exception("Error opening VPN client configuration template (%s)", fname)
logging.exception(
"Error opening VPN client configuration template (%s)", fname
)
return cfg
class VPNServer(CoreService):
_name = "VPNServer"
_group = "Security"
_configs = ('vpnserver.sh',)
_startindex = 50
_startup = ('sh vpnserver.sh',)
_shutdown = ("killall openvpn",)
_validate = ("pidof openvpn",)
_custom_needed = True
name = "VPNServer"
group = "Security"
configs = ("vpnserver.sh",)
startup = ("sh vpnserver.sh",)
shutdown = ("killall openvpn",)
validate = ("pidof openvpn",)
custom_needed = True
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Return the sample server.conf and vpnserver.sh file contents to
GUI for user customization.
@ -58,22 +59,23 @@ class VPNServer(CoreService):
try:
cfg += open(fname, "rb").read()
except IOError:
logger.exception("Error opening VPN server configuration template (%s)", fname)
logging.exception(
"Error opening VPN server configuration template (%s)", fname
)
return cfg
class IPsec(CoreService):
_name = "IPsec"
_group = "Security"
_configs = ('ipsec.sh',)
_startindex = 60
_startup = ('sh ipsec.sh',)
_shutdown = ("killall racoon",)
_custom_needed = True
name = "IPsec"
group = "Security"
configs = ("ipsec.sh",)
startup = ("sh ipsec.sh",)
shutdown = ("killall racoon",)
custom_needed = True
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Return the ipsec.conf and racoon.conf file contents to
GUI for user customization.
@ -82,25 +84,23 @@ class IPsec(CoreService):
cfg += "# set up static tunnel mode security assocation for service "
cfg += "(security.py)\n"
fname = "%s/examples/services/sampleIPsec" % constants.CORE_DATA_DIR
try:
cfg += open(fname, "rb").read()
with open(fname, "r") as f:
cfg += f.read()
except IOError:
logger.exception("Error opening IPsec configuration template (%s)", fname)
logging.exception("Error opening IPsec configuration template (%s)", fname)
return cfg
class Firewall(CoreService):
_name = "Firewall"
_group = "Security"
_configs = ('firewall.sh',)
_startindex = 20
_startup = ('sh firewall.sh',)
_custom_needed = True
name = "Firewall"
group = "Security"
configs = ("firewall.sh",)
startup = ("sh firewall.sh",)
custom_needed = True
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Return the firewall rule examples to GUI for user customization.
"""
@ -111,6 +111,57 @@ class Firewall(CoreService):
try:
cfg += open(fname, "rb").read()
except IOError:
logger.exception("Error opening Firewall configuration template (%s)", fname)
logging.exception(
"Error opening Firewall configuration template (%s)", fname
)
return cfg
class Nat(CoreService):
"""
IPv4 source NAT service.
"""
name = "NAT"
executables = ("iptables",)
group = "Security"
configs = ("nat.sh",)
startup = ("sh nat.sh",)
custom_needed = False
@classmethod
def generateifcnatrule(cls, ifc, line_prefix=""):
"""
Generate a NAT line for one interface.
"""
cfg = line_prefix + "iptables -t nat -A POSTROUTING -o "
cfg += ifc.name + " -j MASQUERADE\n"
cfg += line_prefix + "iptables -A FORWARD -i " + ifc.name
cfg += " -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
cfg += line_prefix + "iptables -A FORWARD -i "
cfg += ifc.name + " -j DROP\n"
return cfg
@classmethod
def generate_config(cls, node, filename):
"""
NAT out the first interface
"""
cfg = "#!/bin/sh\n"
cfg += "# generated by security.py\n"
cfg += "# NAT out the first interface by default\n"
have_nat = False
for ifc in node.netifs():
if hasattr(ifc, "control") and ifc.control is True:
continue
if have_nat:
cfg += cls.generateifcnatrule(ifc, line_prefix="#")
else:
have_nat = True
cfg += "# NAT out the " + ifc.name + " interface\n"
cfg += cls.generateifcnatrule(ifc)
cfg += "\n"
return cfg

View file

@ -1,38 +0,0 @@
from inspect import isclass
from sys import maxint
from core.service import CoreService
class Startup(CoreService):
"""
A CORE service to start other services in order, serially
"""
_name = 'startup'
_group = 'Utility'
_depends = ()
_dirs = ()
_configs = ('startup.sh',)
_startindex = maxint
_startup = ('sh startup.sh',)
_shutdown = ()
_validate = ()
@staticmethod
def is_startup_service(s):
return isinstance(s, Startup) or (isclass(s) and issubclass(s, Startup))
@classmethod
def generateconfig(cls, node, filename, services):
if filename != cls._configs[0]:
return ''
script = '#!/bin/sh\n' \
'# auto-generated by Startup (startup.py)\n\n' \
'exec > startup.log 2>&1\n\n'
for s in sorted(services, key=lambda x: x._startindex):
if cls.is_startup_service(s) or len(str(s._starttime)) > 0:
continue
start = '\n'.join(s.getstartup(node, services))
if start:
script += start + '\n'
return script

View file

@ -2,46 +2,48 @@
ucarp.py: defines high-availability IP address controlled by ucarp
"""
from core.service import CoreService
from core.services.coreservices import CoreService
UCARP_ETC = "/usr/local/etc/ucarp"
class Ucarp(CoreService):
_name = "ucarp"
_group = "Utility"
_depends = ( )
_dirs = (UCARP_ETC,)
_configs = (
UCARP_ETC + "/default.sh", UCARP_ETC + "/default-up.sh", UCARP_ETC + "/default-down.sh", "ucarpboot.sh",)
_startindex = 65
_startup = ("sh ucarpboot.sh",)
_shutdown = ("killall ucarp",)
_validate = ("pidof ucarp",)
name = "ucarp"
group = "Utility"
dirs = (UCARP_ETC,)
configs = (
UCARP_ETC + "/default.sh",
UCARP_ETC + "/default-up.sh",
UCARP_ETC + "/default-down.sh",
"ucarpboot.sh",
)
startup = ("sh ucarpboot.sh",)
shutdown = ("killall ucarp",)
validate = ("pidof ucarp",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Return the default file contents
"""
if filename == cls._configs[0]:
return cls.generateUcarpConf(node, services)
elif filename == cls._configs[1]:
return cls.generateVipUp(node, services)
elif filename == cls._configs[2]:
return cls.generateVipDown(node, services)
elif filename == cls._configs[3]:
return cls.generateUcarpBoot(node, services)
if filename == cls.configs[0]:
return cls.generateUcarpConf(node)
elif filename == cls.configs[1]:
return cls.generateVipUp(node)
elif filename == cls.configs[2]:
return cls.generateVipDown(node)
elif filename == cls.configs[3]:
return cls.generateUcarpBoot(node)
else:
raise ValueError
@classmethod
def generateUcarpConf(cls, node, services):
def generateUcarpConf(cls, node):
"""
Returns configuration file text.
"""
try:
ucarp_bin = node.session.cfg['ucarp_bin']
ucarp_bin = node.session.cfg["ucarp_bin"]
except KeyError:
ucarp_bin = "/usr/sbin/ucarp"
@ -102,19 +104,18 @@ STOP_SCRIPT=${UCARP_CFGDIR}/default-down.sh
UCARP_OPTS="$OPTIONS -b $UCARP_BASE -k $SKEW -i $INTERFACE -v $INSTANCE_ID -p $PASSWORD -u $START_SCRIPT -d $STOP_SCRIPT -a $VIRTUAL_ADDRESS -s $SOURCE_ADDRESS -f $FACILITY $XPARAM"
${UCARP_EXEC} -B ${UCARP_OPTS}
""" % (ucarp_bin, UCARP_ETC)
""" % (
ucarp_bin,
UCARP_ETC,
)
@classmethod
def generateUcarpBoot(cls, node, services):
def generateUcarpBoot(cls, node):
"""
Generate a shell script used to boot the Ucarp daemons.
"""
try:
ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError:
ucarp_bin = "/usr/sbin/ucarp"
return """\
return (
"""\
#!/bin/sh
# Location of the UCARP config directory
UCARP_CFGDIR=%s
@ -124,18 +125,15 @@ chmod a+x ${UCARP_CFGDIR}/*.sh
# Start the default ucarp daemon configuration
${UCARP_CFGDIR}/default.sh
""" % UCARP_ETC
"""
% UCARP_ETC
)
@classmethod
def generateVipUp(cls, node, services):
def generateVipUp(cls, node):
"""
Generate a shell script used to start the virtual ip
"""
try:
ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError:
ucarp_bin = "/usr/sbin/ucarp"
return """\
#!/bin/bash
@ -145,7 +143,7 @@ exec 2> /dev/null
IP="${2}"
NET="${3}"
if [ -z "$NET" ]; then
NET="24"
NET="24"
fi
/sbin/ip addr add ${IP}/${NET} dev "$1"
@ -154,14 +152,10 @@ fi
"""
@classmethod
def generateVipDown(cls, node, services):
def generateVipDown(cls, node):
"""
Generate a shell script used to stop the virtual ip
"""
try:
ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError:
ucarp_bin = "/usr/sbin/ucarp"
return """\
#!/bin/bash
@ -171,7 +165,7 @@ exec 2> /dev/null
IP="${2}"
NET="${3}"
if [ -z "$NET" ]; then
NET="24"
NET="24"
fi
/sbin/ip addr del ${IP}/${NET} dev "$1"

View file

@ -1,50 +1,47 @@
"""
utility.py: defines miscellaneous utility services.
"""
import os
from core import CoreCommandError
from core import constants
from core.misc import utils
from core.misc.ipaddress import Ipv4Prefix
from core.misc.ipaddress import Ipv6Prefix
from core.service import CoreService
import netaddr
from core import constants, utils
from core.errors import CoreCommandError
from core.nodes.base import CoreNode
from core.services.coreservices import CoreService, ServiceMode
class UtilService(CoreService):
"""
Parent class for utility services.
"""
_name = None
_group = "Utility"
_depends = ()
_dirs = ()
_configs = ()
_startindex = 80
_startup = ()
_shutdown = ()
name = None
group = "Utility"
dirs = ()
configs = ()
startup = ()
shutdown = ()
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
return ""
class IPForwardService(UtilService):
_name = "IPForward"
_configs = ("ipforward.sh",)
_startindex = 5
_startup = ("sh ipforward.sh",)
name = "IPForward"
configs = ("ipforward.sh",)
startup = ("sh ipforward.sh",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
if os.uname()[0] == "Linux":
return cls.generateconfiglinux(node, filename, services)
return cls.generateconfiglinux(node, filename)
else:
raise Exception("unknown platform")
@classmethod
def generateconfiglinux(cls, node, filename, services):
def generateconfiglinux(cls, node, filename):
cfg = """\
#!/bin/sh
# auto-generated by IPForward service (utility.py)
@ -56,64 +53,65 @@ class IPForwardService(UtilService):
%(sysctl)s -w net.ipv4.conf.default.send_redirects=0
%(sysctl)s -w net.ipv4.conf.all.rp_filter=0
%(sysctl)s -w net.ipv4.conf.default.rp_filter=0
""" % {'sysctl': constants.SYSCTL_BIN}
""" % {
"sysctl": constants.SYSCTL_BIN
}
for ifc in node.netifs():
name = utils.sysctl_devname(ifc.name)
cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (constants.SYSCTL_BIN, name)
cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % \
(constants.SYSCTL_BIN, name)
cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (
constants.SYSCTL_BIN,
name,
)
cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % (
constants.SYSCTL_BIN,
name,
)
cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (constants.SYSCTL_BIN, name)
return cfg
class DefaultRouteService(UtilService):
_name = "DefaultRoute"
_configs = ("defaultroute.sh",)
_startup = ("sh defaultroute.sh",)
name = "DefaultRoute"
configs = ("defaultroute.sh",)
startup = ("sh defaultroute.sh",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
# only add default routes for linked routing nodes
routes = []
for other_node in node.session.nodes.values():
if not isinstance(other_node, CoreNode):
continue
if other_node.type not in ["router", "mdr"]:
continue
commonnets = node.commonnets(other_node)
if commonnets:
_, _, router_eth = commonnets[0]
for x in router_eth.addrlist:
addr, prefix = x.split("/")
routes.append(addr)
break
cfg = "#!/bin/sh\n"
cfg += "# auto-generated by DefaultRoute service (utility.py)\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
continue
cfg += "\n".join(map(cls.addrstr, ifc.addrlist))
cfg += "\n"
for route in routes:
cfg += f"ip route add default via {route}\n"
return cfg
@staticmethod
def addrstr(x):
if x.find(":") >= 0:
net = Ipv6Prefix(x)
fam = "inet6 ::"
else:
net = Ipv4Prefix(x)
fam = "inet 0.0.0.0"
if net.max_addr() == net.min_addr():
return ""
else:
if os.uname()[0] == "Linux":
rtcmd = "ip route add default via"
else:
raise Exception("unknown platform")
return "%s %s" % (rtcmd, net.min_addr())
class DefaultMulticastRouteService(UtilService):
_name = "DefaultMulticastRoute"
_configs = ("defaultmroute.sh",)
_startup = ("sh defaultmroute.sh",)
name = "DefaultMulticastRoute"
configs = ("defaultmroute.sh",)
startup = ("sh defaultmroute.sh",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
cfg = "#!/bin/sh\n"
cfg += "# auto-generated by DefaultMulticastRoute service (utility.py)\n"
cfg += "# the first interface is chosen below; please change it "
cfg += "as needed\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, "control") and ifc.control is True:
continue
if os.uname()[0] == "Linux":
rtcmd = "ip route add 224.0.0.0/4 dev"
@ -126,19 +124,19 @@ class DefaultMulticastRouteService(UtilService):
class StaticRouteService(UtilService):
_name = "StaticRoute"
_configs = ("staticroute.sh",)
_startup = ("sh staticroute.sh",)
_custom_needed = True
name = "StaticRoute"
configs = ("staticroute.sh",)
startup = ("sh staticroute.sh",)
custom_needed = True
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
cfg = "#!/bin/sh\n"
cfg += "# auto-generated by StaticRoute service (utility.py)\n#\n"
cfg += "# NOTE: this service must be customized to be of any use\n"
cfg += "# Below are samples that you can uncomment and edit.\n#\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
cfg += "\n".join(map(cls.routestr, ifc.addrlist))
cfg += "\n"
@ -146,40 +144,39 @@ class StaticRouteService(UtilService):
@staticmethod
def routestr(x):
if x.find(":") >= 0:
net = Ipv6Prefix(x)
fam = "inet6"
addr = x.split("/")[0]
if netaddr.valid_ipv6(addr):
dst = "3ffe:4::/64"
else:
net = Ipv4Prefix(x)
fam = "inet"
dst = "10.9.8.0/24"
if net.max_addr() == net.min_addr():
net = netaddr.IPNetwork(x)
if net[-2] == net[1]:
return ""
else:
if os.uname()[0] == "Linux":
rtcmd = "#/sbin/ip route add %s via" % dst
else:
raise Exception("unknown platform")
return "%s %s" % (rtcmd, net.min_addr())
return "%s %s" % (rtcmd, net[1])
class SshService(UtilService):
_name = "SSH"
_configs = ("startsshd.sh", "/etc/ssh/sshd_config",)
_dirs = ("/etc/ssh", "/var/run/sshd",)
_startup = ("sh startsshd.sh",)
_shutdown = ("killall sshd",)
_validate = ()
name = "SSH"
configs = ("startsshd.sh", "/etc/ssh/sshd_config")
dirs = ("/etc/ssh", "/var/run/sshd")
startup = ("sh startsshd.sh",)
shutdown = ("killall sshd",)
validate = ()
validation_mode = ServiceMode.BLOCKING
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Use a startup script for launching sshd in order to wait for host
key generation.
"""
sshcfgdir = cls._dirs[0]
sshstatedir = cls._dirs[1]
sshcfgdir = cls.dirs[0]
sshstatedir = cls.dirs[1]
sshlibdir = "/usr/lib/openssh"
if filename == "startsshd.sh":
return """\
@ -189,7 +186,11 @@ ssh-keygen -q -t rsa -N "" -f %s/ssh_host_rsa_key
chmod 655 %s
# wait until RSA host key has been generated to launch sshd
/usr/sbin/sshd -f %s/sshd_config
""" % (sshcfgdir, sshstatedir, sshcfgdir)
""" % (
sshcfgdir,
sshstatedir,
sshcfgdir,
)
else:
return """\
# auto-generated by SSH service (utility.py)
@ -229,19 +230,23 @@ AcceptEnv LANG LC_*
Subsystem sftp %s/sftp-server
UsePAM yes
UseDNS no
""" % (sshcfgdir, sshstatedir, sshlibdir)
""" % (
sshcfgdir,
sshstatedir,
sshlibdir,
)
class DhcpService(UtilService):
_name = "DHCP"
_configs = ("/etc/dhcp/dhcpd.conf",)
_dirs = ("/etc/dhcp",)
_startup = ("dhcpd",)
_shutdown = ("killall dhcpd",)
_validate = ("pidof dhcpd",)
name = "DHCP"
configs = ("/etc/dhcp/dhcpd.conf",)
dirs = ("/etc/dhcp", "/var/lib/dhcp")
startup = ("touch /var/lib/dhcp/dhcpd.leases", "dhcpd")
shutdown = ("killall dhcpd",)
validate = ("pidof dhcpd",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Generate a dhcpd config file using the network address of
each interface.
@ -261,7 +266,7 @@ max-lease-time 7200;
ddns-update-style none;
"""
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
cfg += "\n".join(map(cls.subnetentry, ifc.addrlist))
cfg += "\n"
@ -273,14 +278,15 @@ ddns-update-style none;
Generate a subnet declaration block given an IPv4 prefix string
for inclusion in the dhcpd3 config file.
"""
if x.find(":") >= 0:
addr = x.split("/")[0]
if netaddr.valid_ipv6(addr):
return ""
else:
addr = x.split("/")[0]
net = Ipv4Prefix(x)
net = netaddr.IPNetwork(x)
# divide the address space in half
rangelow = net.addr(net.num_addr() / 2)
rangehigh = net.max_addr()
index = (net.size - 2) / 2
rangelow = net[index]
rangehigh = net[-2]
return """
subnet %s netmask %s {
pool {
@ -289,21 +295,28 @@ subnet %s netmask %s {
option routers %s;
}
}
""" % (net.prefix_str(), net.netmask_str(), rangelow, rangehigh, addr)
""" % (
net.ip,
net.netmask,
rangelow,
rangehigh,
addr,
)
class DhcpClientService(UtilService):
"""
Use a DHCP client for all interfaces for addressing.
"""
_name = "DHCPClient"
_configs = ("startdhcpclient.sh",)
_startup = ("sh startdhcpclient.sh",)
_shutdown = ("killall dhclient",)
_validate = ("pidof dhclient",)
name = "DHCPClient"
configs = ("startdhcpclient.sh",)
startup = ("sh startdhcpclient.sh",)
shutdown = ("killall dhclient",)
validate = ("pidof dhclient",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Generate a script to invoke dhclient on all interfaces.
"""
@ -314,7 +327,7 @@ class DhcpClientService(UtilService):
cfg += "#mkdir -p /var/run/resolvconf/interface\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, "control") and ifc.control is True:
continue
cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % ifc.name
cfg += " /var/run/resolvconf/resolv.conf\n"
@ -327,15 +340,16 @@ class FtpService(UtilService):
"""
Start a vsftpd server.
"""
_name = "FTP"
_configs = ("vsftpd.conf",)
_dirs = ("/var/run/vsftpd/empty", "/var/ftp",)
_startup = ("vsftpd ./vsftpd.conf",)
_shutdown = ("killall vsftpd",)
_validate = ("pidof vsftpd",)
name = "FTP"
configs = ("vsftpd.conf",)
dirs = ("/var/run/vsftpd/empty", "/var/ftp")
startup = ("vsftpd ./vsftpd.conf",)
shutdown = ("killall vsftpd",)
validate = ("pidof vsftpd",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Generate a vsftpd.conf configuration file.
"""
@ -359,28 +373,38 @@ class HttpService(UtilService):
"""
Start an apache server.
"""
_name = "HTTP"
_configs = ("/etc/apache2/apache2.conf", "/etc/apache2/envvars",
"/var/www/index.html",)
_dirs = ("/etc/apache2", "/var/run/apache2", "/var/log/apache2",
"/run/lock", "/var/lock/apache2", "/var/www",)
_startup = ("chown www-data /var/lock/apache2", "apache2ctl start",)
_shutdown = ("apache2ctl stop",)
_validate = ("pidof apache2",)
name = "HTTP"
configs = (
"/etc/apache2/apache2.conf",
"/etc/apache2/envvars",
"/var/www/index.html",
)
dirs = (
"/etc/apache2",
"/var/run/apache2",
"/var/log/apache2",
"/run/lock",
"/var/lock/apache2",
"/var/www",
)
startup = ("chown www-data /var/lock/apache2", "apache2ctl start")
shutdown = ("apache2ctl stop",)
validate = ("pidof apache2",)
APACHEVER22, APACHEVER24 = (22, 24)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Generate an apache2.conf configuration file.
"""
if filename == cls._configs[0]:
return cls.generateapache2conf(node, filename, services)
elif filename == cls._configs[1]:
return cls.generateenvvars(node, filename, services)
elif filename == cls._configs[2]:
return cls.generatehtml(node, filename, services)
if filename == cls.configs[0]:
return cls.generateapache2conf(node, filename)
elif filename == cls.configs[1]:
return cls.generateenvvars(node, filename)
elif filename == cls.configs[2]:
return cls.generatehtml(node, filename)
else:
return ""
@ -390,38 +414,42 @@ class HttpService(UtilService):
Detect the apache2 version using the 'a2query' command.
"""
try:
status, result = utils.cmd_output(['a2query', '-v'])
except CoreCommandError:
status = -1
result = utils.cmd("a2query -v")
status = 0
except CoreCommandError as e:
status = e.returncode
result = e.stderr
if status == 0 and result[:3] == '2.4':
if status == 0 and result[:3] == "2.4":
return cls.APACHEVER24
return cls.APACHEVER22
@classmethod
def generateapache2conf(cls, node, filename, services):
lockstr = {cls.APACHEVER22:
'LockFile ${APACHE_LOCK_DIR}/accept.lock\n',
cls.APACHEVER24:
'Mutex file:${APACHE_LOCK_DIR} default\n', }
mpmstr = {cls.APACHEVER22: '', cls.APACHEVER24:
'LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n', }
def generateapache2conf(cls, node, filename):
lockstr = {
cls.APACHEVER22: "LockFile ${APACHE_LOCK_DIR}/accept.lock\n",
cls.APACHEVER24: "Mutex file:${APACHE_LOCK_DIR} default\n",
}
mpmstr = {
cls.APACHEVER22: "",
cls.APACHEVER24: "LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n",
}
permstr = {cls.APACHEVER22:
' Order allow,deny\n Deny from all\n Satisfy all\n',
cls.APACHEVER24:
' Require all denied\n', }
permstr = {
cls.APACHEVER22: " Order allow,deny\n Deny from all\n Satisfy all\n",
cls.APACHEVER24: " Require all denied\n",
}
authstr = {cls.APACHEVER22:
'LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so\n',
cls.APACHEVER24:
'LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so\n', }
authstr = {
cls.APACHEVER22: "LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so\n",
cls.APACHEVER24: "LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so\n",
}
permstr2 = {cls.APACHEVER22:
'\t\tOrder allow,deny\n\t\tallow from all\n',
cls.APACHEVER24:
'\t\tRequire all granted\n', }
permstr2 = {
cls.APACHEVER22: "\t\tOrder allow,deny\n\t\tallow from all\n",
cls.APACHEVER24: "\t\tRequire all granted\n",
}
version = cls.detectversionfromcmd()
cfg = "# apache2.conf generated by utility.py:HttpService\n"
@ -469,7 +497,7 @@ Group ${APACHE_RUN_GROUP}
AccessFileName .htaccess
<Files ~ "^\.ht">
<Files ~ "^\\.ht">
"""
cfg += permstr[version]
cfg += """\
@ -516,29 +544,29 @@ ServerSignature On
TraceEnable Off
<VirtualHost *:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
ServerAdmin webmaster@localhost
DocumentRoot /var/www
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory /var/www/>
Options Indexes FollowSymLinks MultiViews
AllowOverride None
"""
cfg += permstr2[version]
cfg += """\
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access.log combined
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
"""
return cfg
@classmethod
def generateenvvars(cls, node, filename, services):
def generateenvvars(cls, node, filename):
return """\
# this file is used by apache2ctl - generated by utility.py:HttpService
# these settings come from a default Ubuntu apache2 installation
@ -553,15 +581,18 @@ export LANG
"""
@classmethod
def generatehtml(cls, node, filename, services):
body = """\
def generatehtml(cls, node, filename):
body = (
"""\
<!-- generated by utility.py:HttpService -->
<h1>%s web server</h1>
<p>This is the default web page for this server.</p>
<p>The web server software is running but no content has been added, yet.</p>
""" % node.name
"""
% node.name
)
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, "control") and ifc.control is True:
continue
body += "<li>%s - %s</li>\n" % (ifc.name, ifc.addrlist)
return "<html><body>%s</body></html>" % body
@ -571,17 +602,17 @@ class PcapService(UtilService):
"""
Pcap service for logging packets.
"""
_name = "pcap"
_configs = ("pcap.sh",)
_dirs = ()
_startindex = 1
_startup = ("sh pcap.sh start",)
_shutdown = ("sh pcap.sh stop",)
_validate = ("pidof tcpdump",)
_meta = "logs network traffic to pcap packet capture files"
name = "pcap"
configs = ("pcap.sh",)
dirs = ()
startup = ("sh pcap.sh start",)
shutdown = ("sh pcap.sh stop",)
validate = ("pidof tcpdump",)
meta = "logs network traffic to pcap packet capture files"
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Generate a startpcap.sh traffic logging script.
"""
@ -595,11 +626,15 @@ if [ "x$1" = "xstart" ]; then
"""
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
cfg += '# '
if hasattr(ifc, "control") and ifc.control is True:
cfg += "# "
redir = "< /dev/null"
cfg += "tcpdump ${DUMPOPTS} -w %s.%s.pcap -i %s %s &\n" % \
(node.name, ifc.name, ifc.name, redir)
cfg += "tcpdump ${DUMPOPTS} -w %s.%s.pcap -i %s %s &\n" % (
node.name,
ifc.name,
ifc.name,
redir,
)
cfg += """
elif [ "x$1" = "xstop" ]; then
@ -611,27 +646,28 @@ fi;
class RadvdService(UtilService):
_name = "radvd"
_configs = ("/etc/radvd/radvd.conf",)
_dirs = ("/etc/radvd",)
_startup = ("radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",)
_shutdown = ("pkill radvd",)
_validate = ("pidof radvd",)
name = "radvd"
configs = ("/etc/radvd/radvd.conf",)
dirs = ("/etc/radvd",)
startup = ("radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",)
shutdown = ("pkill radvd",)
validate = ("pidof radvd",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Generate a RADVD router advertisement daemon config file
using the network address of each interface.
"""
cfg = "# auto-generated by RADVD service (utility.py)\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, "control") and ifc.control is True:
continue
prefixes = map(cls.subnetentry, ifc.addrlist)
prefixes = list(map(cls.subnetentry, ifc.addrlist))
if len(prefixes) < 1:
continue
cfg += """\
cfg += (
"""\
interface %s
{
AdvSendAdvert on;
@ -639,18 +675,23 @@ interface %s
MaxRtrAdvInterval 10;
AdvDefaultPreference low;
AdvHomeAgentFlag off;
""" % ifc.name
"""
% ifc.name
)
for prefix in prefixes:
if prefix == "":
continue
cfg += """\
cfg += (
"""\
prefix %s
{
AdvOnLink on;
AdvAutonomous on;
AdvRouterAddr on;
};
""" % prefix
"""
% prefix
)
cfg += "};\n"
return cfg
@ -660,9 +701,9 @@ interface %s
Generate a subnet declaration block given an IPv6 prefix string
for inclusion in the RADVD config file.
"""
if x.find(":") >= 0:
net = Ipv6Prefix(x)
return str(net)
addr = x.split("/")[0]
if netaddr.valid_ipv6(addr):
return x
else:
return ""
@ -671,14 +712,15 @@ class AtdService(UtilService):
"""
Atd service for scheduling at jobs
"""
_name = "atd"
_configs = ("startatd.sh",)
_dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool")
_startup = ("sh startatd.sh",)
_shutdown = ("pkill atd",)
name = "atd"
configs = ("startatd.sh",)
dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool")
startup = ("sh startatd.sh",)
shutdown = ("pkill atd",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
return """
#!/bin/sh
echo 00001 > /var/spool/cron/atjobs/.SEQ
@ -692,6 +734,6 @@ class UserDefinedService(UtilService):
"""
Dummy service allowing customization of anything.
"""
_name = "UserDefined"
_startindex = 50
_meta = "Customize this service to do anything upon startup."
name = "UserDefined"
meta = "Customize this service to do anything upon startup."

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,18 +14,21 @@ class XorpRtrmgr(CoreService):
XORP router manager service builds a config.boot file based on other
enabled XORP services, and launches necessary daemons upon startup.
"""
_name = "xorp_rtrmgr"
_group = "XORP"
_depends = ()
_dirs = ("/etc/xorp",)
_configs = ("/etc/xorp/config.boot",)
_startindex = 35
_startup = ("xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid" % (_configs[0], _name, _name),)
_shutdown = ("killall xorp_rtrmgr",)
_validate = ("pidof xorp_rtrmgr",)
name = "xorp_rtrmgr"
executables = ("xorp_rtrmgr",)
group = "XORP"
dirs = ("/etc/xorp",)
configs = ("/etc/xorp/config.boot",)
startup = (
"xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid"
% (configs[0], name, name),
)
shutdown = ("killall xorp_rtrmgr",)
validate = ("pidof xorp_rtrmgr",)
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
"""
Returns config.boot configuration file text. Other services that
depend on this will have generatexorpconfig() hooks that are
@ -38,12 +44,12 @@ class XorpRtrmgr(CoreService):
cfg += " }\n"
cfg += "}\n\n"
for s in services:
for s in node.services:
try:
s._depends.index(cls._name)
s.dependencies.index(cls.name)
cfg += s.generatexorpconfig(node)
except ValueError:
logger.exception("error getting value from service: %s", cls._name)
logging.exception("error getting value from service: %s", cls.name)
return cfg
@ -74,15 +80,16 @@ class XorpService(CoreService):
Parent class for XORP services. Defines properties and methods
common to XORP's routing daemons.
"""
_name = None
_group = "XORP"
_depends = ("xorp_rtrmgr",)
_dirs = ()
_configs = ()
_startindex = 40
_startup = ()
_shutdown = ()
_meta = "The config file for this service can be found in the xorp_rtrmgr service."
name = None
executables = ("xorp_rtrmgr",)
group = "XORP"
dependencies = ("xorp_rtrmgr",)
dirs = ()
configs = ()
startup = ()
shutdown = ()
meta = "The config file for this service can be found in the xorp_rtrmgr service."
@staticmethod
def fea(forwarding):
@ -103,7 +110,7 @@ class XorpService(CoreService):
"""
names = []
for ifc in ifcs:
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
names.append(ifc.name)
names.append("register_vif")
@ -129,7 +136,7 @@ class XorpService(CoreService):
cfg += " policy-statement export-connected {\n"
cfg += "\tterm 100 {\n"
cfg += "\t from {\n"
cfg += "\t\tprotocol: \"connected\"\n"
cfg += '\t\tprotocol: "connected"\n'
cfg += "\t }\n"
cfg += "\t}\n"
cfg += " }\n"
@ -142,16 +149,17 @@ class XorpService(CoreService):
Helper to return the first IPv4 address of a node as its router ID.
"""
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
for a in ifc.addrlist:
if a.find(".") >= 0:
return a.split('/')[0]
a = a.split("/")[0]
if netaddr.valid_ipv4(a):
return a
# raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0"
@classmethod
def generateconfig(cls, node, filename, services):
def generate_config(cls, node, filename):
return ""
@classmethod
@ -165,7 +173,8 @@ class XorpOspfv2(XorpService):
not build its own configuration file but has hooks for adding to the
unified XORP configuration file.
"""
_name = "XORP_OSPFv2"
name = "XORP_OSPFv2"
@classmethod
def generatexorpconfig(cls, node):
@ -176,14 +185,14 @@ class XorpOspfv2(XorpService):
cfg += "\trouter-id: %s\n" % rtrid
cfg += "\tarea 0.0.0.0 {\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
cfg += "\t interface %s {\n" % ifc.name
cfg += "\t\tvif %s {\n" % ifc.name
for a in ifc.addrlist:
if a.find(".") < 0:
continue
addr = a.split("/")[0]
if not netaddr.valid_ipv4(addr):
continue
cfg += "\t\t address %s {\n" % addr
cfg += "\t\t }\n"
cfg += "\t\t}\n"
@ -200,7 +209,8 @@ class XorpOspfv3(XorpService):
not build its own configuration file but has hooks for adding to the
unified XORP configuration file.
"""
_name = "XORP_OSPFv3"
name = "XORP_OSPFv3"
@classmethod
def generatexorpconfig(cls, node):
@ -211,7 +221,7 @@ class XorpOspfv3(XorpService):
cfg += "\trouter-id: %s\n" % rtrid
cfg += "\tarea 0.0.0.0 {\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
cfg += "\t interface %s {\n" % ifc.name
cfg += "\t\tvif %s {\n" % ifc.name
@ -227,8 +237,9 @@ class XorpBgp(XorpService):
"""
IPv4 inter-domain routing. AS numbers and peers must be customized.
"""
_name = "XORP_BGP"
_custom_needed = True
name = "XORP_BGP"
custom_needed = True
@classmethod
def generatexorpconfig(cls, node):
@ -241,7 +252,7 @@ class XorpBgp(XorpService):
cfg += " bgp {\n"
cfg += "\tbgp-id: %s\n" % rtrid
cfg += "\tlocal-as: 65001 /* change this */\n"
cfg += "\texport: \"export-connected\"\n"
cfg += '\texport: "export-connected"\n'
cfg += "\tpeer 10.0.1.1 { /* change this */\n"
cfg += "\t local-ip: 10.0.1.1\n"
cfg += "\t as: 65002\n"
@ -257,7 +268,7 @@ class XorpRip(XorpService):
RIP IPv4 unicast routing.
"""
_name = "XORP_RIP"
name = "XORP_RIP"
@classmethod
def generatexorpconfig(cls, node):
@ -265,16 +276,16 @@ class XorpRip(XorpService):
cfg += cls.policyexportconnected()
cfg += "\nprotocols {\n"
cfg += " rip {\n"
cfg += "\texport: \"export-connected\"\n"
cfg += '\texport: "export-connected"\n'
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
cfg += "\tinterface %s {\n" % ifc.name
cfg += "\t vif %s {\n" % ifc.name
for a in ifc.addrlist:
if a.find(".") < 0:
continue
addr = a.split("/")[0]
if not netaddr.valid_ipv4(addr):
continue
cfg += "\t\taddress %s {\n" % addr
cfg += "\t\t disable: false\n"
cfg += "\t\t}\n"
@ -289,7 +300,8 @@ class XorpRipng(XorpService):
"""
RIP NG IPv6 unicast routing.
"""
_name = "XORP_RIPNG"
name = "XORP_RIPNG"
@classmethod
def generatexorpconfig(cls, node):
@ -297,19 +309,12 @@ class XorpRipng(XorpService):
cfg += cls.policyexportconnected()
cfg += "\nprotocols {\n"
cfg += " ripng {\n"
cfg += "\texport: \"export-connected\"\n"
cfg += '\texport: "export-connected"\n'
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
cfg += "\tinterface %s {\n" % ifc.name
cfg += "\t vif %s {\n" % ifc.name
# for a in ifc.addrlist:
# if a.find(":") < 0:
# continue
# addr = a.split("/")[0]
# cfg += "\t\taddress %s {\n" % addr
# cfg += "\t\t disable: false\n"
# cfg += "\t\t}\n"
cfg += "\t\taddress %s {\n" % ifc.hwaddr.tolinklocal()
cfg += "\t\t disable: false\n"
cfg += "\t\t}\n"
@ -324,7 +329,8 @@ class XorpPimSm4(XorpService):
"""
PIM Sparse Mode IPv4 multicast routing.
"""
_name = "XORP_PIMSM4"
name = "XORP_PIMSM4"
@classmethod
def generatexorpconfig(cls, node):
@ -334,7 +340,7 @@ class XorpPimSm4(XorpService):
cfg += " igmp {\n"
names = []
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
names.append(ifc.name)
cfg += "\tinterface %s {\n" % ifc.name
@ -358,12 +364,12 @@ class XorpPimSm4(XorpService):
cfg += "\tbootstrap {\n"
cfg += "\t cand-bsr {\n"
cfg += "\t\tscope-zone 224.0.0.0/4 {\n"
cfg += "\t\t cand-bsr-by-vif-name: \"%s\"\n" % names[0]
cfg += '\t\t cand-bsr-by-vif-name: "%s"\n' % names[0]
cfg += "\t\t}\n"
cfg += "\t }\n"
cfg += "\t cand-rp {\n"
cfg += "\t\tgroup-prefix 224.0.0.0/4 {\n"
cfg += "\t\t cand-rp-by-vif-name: \"%s\"\n" % names[0]
cfg += '\t\t cand-rp-by-vif-name: "%s"\n' % names[0]
cfg += "\t\t}\n"
cfg += "\t }\n"
cfg += "\t}\n"
@ -383,7 +389,8 @@ class XorpPimSm6(XorpService):
"""
PIM Sparse Mode IPv6 multicast routing.
"""
_name = "XORP_PIMSM6"
name = "XORP_PIMSM6"
@classmethod
def generatexorpconfig(cls, node):
@ -393,7 +400,7 @@ class XorpPimSm6(XorpService):
cfg += " mld {\n"
names = []
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
names.append(ifc.name)
cfg += "\tinterface %s {\n" % ifc.name
@ -417,12 +424,12 @@ class XorpPimSm6(XorpService):
cfg += "\tbootstrap {\n"
cfg += "\t cand-bsr {\n"
cfg += "\t\tscope-zone ff00::/8 {\n"
cfg += "\t\t cand-bsr-by-vif-name: \"%s\"\n" % names[0]
cfg += '\t\t cand-bsr-by-vif-name: "%s"\n' % names[0]
cfg += "\t\t}\n"
cfg += "\t }\n"
cfg += "\t cand-rp {\n"
cfg += "\t\tgroup-prefix ff00::/8 {\n"
cfg += "\t\t cand-rp-by-vif-name: \"%s\"\n" % names[0]
cfg += '\t\t cand-rp-by-vif-name: "%s"\n' % names[0]
cfg += "\t\t}\n"
cfg += "\t }\n"
cfg += "\t}\n"
@ -442,7 +449,8 @@ class XorpOlsr(XorpService):
"""
OLSR IPv4 unicast MANET routing.
"""
_name = "XORP_OLSR"
name = "XORP_OLSR"
@classmethod
def generatexorpconfig(cls, node):
@ -452,14 +460,14 @@ class XorpOlsr(XorpService):
cfg += " olsr4 {\n"
cfg += "\tmain-address: %s\n" % rtrid
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control is True:
if hasattr(ifc, "control") and ifc.control is True:
continue
cfg += "\tinterface %s {\n" % ifc.name
cfg += "\t vif %s {\n" % ifc.name
for a in ifc.addrlist:
if a.find(".") < 0:
continue
addr = a.split("/")[0]
if not netaddr.valid_ipv4(addr):
continue
cfg += "\t\taddress %s {\n" % addr
cfg += "\t\t}\n"
cfg += "\t }\n"