Merge pull request #299 from coreemu/develop

Merging 5.5.0 for release
This commit is contained in:
bharnden 2019-10-03 13:19:51 -07:00 committed by GitHub
commit 839a1b9368
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 1009 additions and 3331 deletions

View file

@ -11,6 +11,7 @@ insert_final_newline = true
[*.py]
indent_style = space
indent_size = 4
max_line_length = 88
[*.am]
indent_style = tab

View file

@ -19,7 +19,7 @@ scripting network emulation.
* Documentation hosted on GitHub
* <http://coreemu.github.io/core/>
* Basic Script Examples
* [Examples](daemon/examples/api)
* [Examples](daemon/examples/python)
* Custom Service Example
* [sample.py](daemon/examples/myservices/sample.py)
* Custom Emane Model Example

View file

@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
# this defines the CORE version number, must be static for AC_INIT
AC_INIT(core, 5.4.0)
AC_INIT(core, 5.5.0)
# autoconf and automake initialization
AC_CONFIG_SRCDIR([netns/version.h.in])
@ -54,6 +54,13 @@ if test "x$enable_python" = "xyes" ; then
else
want_python=no
fi
AC_ARG_ENABLE([python3],
[AS_HELP_STRING([--enable-python3],
[sets python3 flag for building packages])],
[enable_python3=yes], [enable_python3=no])
AM_CONDITIONAL([PYTHON3], [test "x$enable_python3" == "xyes"])
AC_ARG_ENABLE([daemon],
[AS_HELP_STRING([--enable-daemon[=ARG]],
[build and install the daemon with Python modules
@ -110,39 +117,61 @@ if test "x$enable_daemon" = "xyes"; then
AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname])
AM_PATH_PYTHON(2.7)
AM_CONDITIONAL([PYTHON3], [test "x$PYTHON" == "xpython3"])
AS_IF([$PYTHON -m grpc_tools.protoc -h &> /dev/null], [], [AC_MSG_ERROR([please install python grpcio-tools])])
AC_CHECK_PROG(brctl_path, brctl, $as_dir, no, $SEARCHPATH)
if test "x$brctl_path" = "xno" ; then
AC_MSG_ERROR([Could not locate brctl (from bridge-utils package).])
fi
AC_CHECK_PROG(sysctl_path, sysctl, $as_dir, no, $SEARCHPATH)
if test "x$sysctl_path" = "xno" ; then
AC_MSG_ERROR([Could not locate sysctl (from procps package).])
fi
AC_CHECK_PROG(ebtables_path, ebtables, $as_dir, no, $SEARCHPATH)
if test "x$ebtables_path" = "xno" ; then
AC_MSG_ERROR([Could not locate ebtables (from ebtables package).])
fi
AC_CHECK_PROG(ip_path, ip, $as_dir, no, $SEARCHPATH)
if test "x$ip_path" = "xno" ; then
AC_MSG_ERROR([Could not locate ip (from iproute package).])
fi
AC_CHECK_PROG(tc_path, tc, $as_dir, no, $SEARCHPATH)
if test "x$tc_path" = "xno" ; then
AC_MSG_ERROR([Could not locate tc (from iproute package).])
fi
AC_CHECK_PROG(ethtool_path, ethtool, $as_dir, no, $SEARCHPATH)
if test "x$ethtool_path" = "xno" ; then
AC_MSG_ERROR([Could not locate ethtool (from package ethtool)])
fi
AC_CHECK_PROG(mount_path, mount, $as_dir, no, $SEARCHPATH)
if test "x$mount_path" = "xno" ; then
AC_MSG_ERROR([Could not locate mount (from package mount)])
fi
AC_CHECK_PROG(umount_path, umount, $as_dir, no, $SEARCHPATH)
if test "x$umount_path" = "xno" ; then
AC_MSG_ERROR([Could not locate umount (from package mount)])
fi
AC_CHECK_PROG(convert, convert, yes, no, $SEARCHPATH)
if test "x$convert" = "xno" ; then
AC_MSG_WARN([Could not locate ImageMagick convert.])
fi
AC_CHECK_PROG(ovs_vs_path, ovs-vsctl, $as_dir, no, $SEARCHPATH)
if test "x$ovs_vs_path" = "xno" ; then
AC_MSG_WARN([Could not locate ovs-vsctl cannot use OVS nodes])
AC_MSG_WARN([Could not locate ovs-vsctl cannot use OVS mode])
fi
AC_CHECK_PROG(ovs_of_path, ovs-ofctl, $as_dir, no, $SEARCHPATH)
if test "x$ovs_of_path" = "xno" ; then
AC_MSG_WARN([Could not locate ovs-ofctl cannot use OVS nodes])
AC_MSG_WARN([Could not locate ovs-ofctl cannot use OVS mode])
fi
CFLAGS_save=$CFLAGS
@ -157,6 +186,7 @@ if test "x$enable_daemon" = "xyes"; then
CFLAGS=$CFLAGS_save
CPPFLAGS=$CPPFLAGS_save
fi
if [ test "x$enable_daemon" = "xyes" || test "x$enable_vnodedonly" = "xyes" ] ; then
want_linux_netns=yes
PKG_CHECK_MODULES(libev, libev,

View file

@ -14,11 +14,7 @@ if WANT_DOCS
DOCS = doc
endif
if PYTHON3
PYTHONLIBDIR=$(libdir)/python3/dist-packages
else
PYTHONLIBDIR=$(pythondir)
endif
PYTHONLIBDIR=$(subst site-packages,dist-packages,$(pythondir))
SUBDIRS = proto $(DOCS)

View file

@ -4,8 +4,9 @@ url = "https://pypi.org/simple"
verify_ssl = true
[scripts]
coredev = "python scripts/core-daemon -f data/core.conf -l data/logging.conf"
coretest = "python -m pytest -v tests"
core = "python scripts/core-daemon -f data/core.conf -l data/logging.conf"
test = "pytest -v tests"
test_emane = "pytest -v tests/emane"
[dev-packages]
grpcio-tools = "*"

View file

@ -1,41 +1,4 @@
import json
import logging.config
import os
import subprocess
from core import constants
# setup default null handler
logging.getLogger(__name__).addHandler(logging.NullHandler())
def load_logging_config(config_path=None):
"""
Load CORE logging configuration file.
:param str config_path: path to logging config file,
when None defaults to /etc/core/logging.conf
:return: nothing
"""
if not config_path:
config_path = os.path.join(constants.CORE_CONF_DIR, "logging.conf")
with open(config_path, "r") as log_config_file:
log_config = json.load(log_config_file)
logging.config.dictConfig(log_config)
class CoreCommandError(subprocess.CalledProcessError):
"""
Used when encountering internal CORE command errors.
"""
def __str__(self):
return "Command(%s), Status(%s):\n%s" % (self.cmd, self.returncode, self.output)
class CoreError(Exception):
"""
Used for errors when dealing with CoreEmu and Sessions.
"""
pass

View file

@ -10,8 +10,8 @@ from queue import Empty, Queue
import grpc
from core import CoreError
from core.api.grpc import core_pb2, core_pb2_grpc
from core.emane.nodes import EmaneNet
from core.emulator.data import (
ConfigData,
EventData,
@ -22,8 +22,8 @@ from core.emulator.data import (
)
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import EventTypes, LinkTypes, NodeTypes
from core.errors import CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes import nodeutils
from core.nodes.base import CoreNetworkBase
from core.nodes.docker import DockerNode
from core.nodes.ipaddress import MacAddress
@ -444,7 +444,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
if not isinstance(node.id, int):
continue
node_type = nodeutils.get_node_type(node.__class__).value
node_type = session.get_node_type(node.__class__)
model = getattr(node, "type", None)
position = core_pb2.Position(
x=node.position.x, y=node.position.y, z=node.position.z
@ -456,7 +456,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
services = [x.name for x in services]
emane_model = None
if nodeutils.is_node(node, NodeTypes.EMANE):
if isinstance(node, EmaneNet):
emane_model = node.model.name
node_proto = core_pb2.Node(
@ -464,7 +464,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
name=node.name,
emane=emane_model,
model=model,
type=node_type,
type=node_type.value,
position=position,
services=services,
)
@ -809,18 +809,18 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
interfaces.append(interface_proto)
emane_model = None
if nodeutils.is_node(node, NodeTypes.EMANE):
if isinstance(node, EmaneNet):
emane_model = node.model.name
services = [x.name for x in getattr(node, "services", [])]
position = core_pb2.Position(
x=node.position.x, y=node.position.y, z=node.position.z
)
node_type = nodeutils.get_node_type(node.__class__).value
node_type = session.get_node_type(node.__class__)
node_proto = core_pb2.Node(
id=node.id,
name=node.name,
type=node_type,
type=node_type.value,
emane=emane_model,
model=node.type,
position=position,

View file

@ -12,6 +12,7 @@ import threading
from core import utils
from core.api.tlv import coreapi
from core.emane.nodes import EmaneNet
from core.emulator.enumerations import (
ConfigDataTypes,
ConfigFlags,
@ -27,11 +28,10 @@ from core.emulator.enumerations import (
NodeTypes,
RegisterTlvs,
)
from core.nodes import nodeutils
from core.nodes.base import CoreNetworkBase, CoreNodeBase
from core.nodes.interface import GreTap
from core.nodes.ipaddress import IpAddress
from core.nodes.network import GreTapBridge
from core.nodes.network import CtrlNet, GreTapBridge
from core.nodes.physical import PhysicalNode
@ -318,13 +318,13 @@ class CoreBroker(object):
# leave this socket connected
return
logging.info(
logging.debug(
"closing connection with %s @ %s:%s", name, server.host, server.port
)
server.close()
del self.servers[name]
logging.info("adding broker server(%s): %s:%s", name, host, port)
logging.debug("adding broker server(%s): %s:%s", name, host, port)
server = CoreDistributedServer(name, host, port)
if host is not None and port is not None:
try:
@ -492,33 +492,30 @@ class CoreBroker(object):
:raises core.CoreError: when node to add net tunnel to does not exist
"""
net = self.session.get_node(node_id)
logging.info("adding net tunnel for: id(%s) %s", node_id, net)
logging.debug("adding net tunnel for: id(%s) %s", node_id, net.name)
# add other nets here that do not require tunnels
if nodeutils.is_node(net, NodeTypes.EMANE_NET):
logging.warning("emane network does not require a tunnel")
if isinstance(net, EmaneNet):
logging.debug("emane network does not require a tunnel")
return None
server_interface = getattr(net, "serverintf", None)
if (
nodeutils.is_node(net, NodeTypes.CONTROL_NET)
and server_interface is not None
):
logging.warning(
if isinstance(net, CtrlNet) and server_interface is not None:
logging.debug(
"control networks with server interfaces do not need a tunnel"
)
return None
servers = self.getserversbynode(node_id)
if len(servers) < 2:
logging.warning("not enough servers to create a tunnel: %s", servers)
logging.debug("not enough servers to create a tunnel for node: %s", node_id)
return None
hosts = []
for server in servers:
if server.host is None:
continue
logging.info("adding server host for net tunnel: %s", server.host)
logging.debug("adding server host for net tunnel: %s", server.host)
hosts.append(server.host)
if len(hosts) == 0:
@ -526,7 +523,7 @@ class CoreBroker(object):
# get IP address from API message sender (master)
if session_client.client_address != "":
address = session_client.client_address[0]
logging.info("adding session_client host: %s", address)
logging.debug("adding session_client host: %s", address)
hosts.append(address)
r = []
@ -539,7 +536,7 @@ class CoreBroker(object):
myip = host
key = self.tunnelkey(node_id, IpAddress.to_int(myip))
if key in self.tunnels.keys():
logging.info(
logging.debug(
"tunnel already exists, returning existing tunnel: %s", key
)
gt = self.tunnels[key]
@ -658,9 +655,9 @@ class CoreBroker(object):
:param int nodenum: node id to add
:return: nothing
"""
logging.info("adding net to broker: %s", nodenum)
logging.debug("adding net to broker: %s", nodenum)
self.network_nodes.add(nodenum)
logging.info("broker network nodes: %s", self.network_nodes)
logging.debug("broker network nodes: %s", self.network_nodes)
def addphys(self, nodenum):
"""
@ -824,7 +821,8 @@ class CoreBroker(object):
nodetype = message.get_tlv(NodeTlvs.TYPE.value)
if nodetype is not None:
try:
nodecls = nodeutils.get_node_class(NodeTypes(nodetype))
nodetype = NodeTypes(nodetype)
nodecls = self.session.get_node_class(nodetype)
except KeyError:
logging.warning("broker invalid node type %s", nodetype)
return handle_locally, servers

View file

@ -14,7 +14,7 @@ from builtins import range
from itertools import repeat
from queue import Empty, Queue
from core import CoreError, utils
from core import utils
from core.api.tlv import coreapi, dataconversion, structutils
from core.config import ConfigShim
from core.emulator.data import ConfigData, EventData, ExceptionData, FileData
@ -37,8 +37,9 @@ from core.emulator.enumerations import (
RegisterTlvs,
SessionTlvs,
)
from core.errors import CoreError
from core.location.mobility import BasicRangeModel
from core.nodes import nodeutils
from core.nodes.network import WlanNode
from core.services.coreservices import ServiceManager, ServiceShim
@ -1247,10 +1248,10 @@ class CoreHandler(socketserver.BaseRequestHandler):
values = []
group_strings = []
start_index = 1
logging.info("sorted groups: %s", groups)
logging.debug("sorted groups: %s", groups)
for group in groups:
services = sorted(group_map[group], key=lambda x: x.name.lower())
logging.info("sorted services for group(%s): %s", group, services)
logging.debug("sorted services for group(%s): %s", group, services)
end_index = start_index + len(services) - 1
group_strings.append("%s:%s-%s" % (group, start_index, end_index))
start_index += len(services)
@ -1603,8 +1604,8 @@ class CoreHandler(socketserver.BaseRequestHandler):
node = self.session.get_node(node_id)
# configure mobility models for WLAN added during runtime
if event_type == EventTypes.INSTANTIATION_STATE and nodeutils.is_node(
node, NodeTypes.WIRELESS_LAN
if event_type == EventTypes.INSTANTIATION_STATE and isinstance(
node, WlanNode
):
self.session.start_mobility(node_ids=(node.id,))
return ()

View file

@ -1,29 +1,20 @@
import os
from core.utils import which
COREDPY_VERSION = "@PACKAGE_VERSION@"
CORE_STATE_DIR = "@CORE_STATE_DIR@"
CORE_CONF_DIR = "@CORE_CONF_DIR@"
CORE_DATA_DIR = "@CORE_DATA_DIR@"
QUAGGA_STATE_DIR = "@CORE_STATE_DIR@/run/quagga"
FRR_STATE_DIR = "@CORE_STATE_DIR@/run/frr"
def which(command):
for path in os.environ["PATH"].split(os.pathsep):
command_path = os.path.join(path, command)
if os.path.isfile(command_path) and os.access(command_path, os.X_OK):
return command_path
VNODED_BIN = which("vnoded")
VCMD_BIN = which("vcmd")
BRCTL_BIN = which("brctl")
SYSCTL_BIN = which("sysctl")
IP_BIN = which("ip")
ETHTOOL_BIN = which("ethtool")
TC_BIN = which("tc")
EBTABLES_BIN = which("ebtables")
MOUNT_BIN = which("mount")
UMOUNT_BIN = which("umount")
OVS_BIN = which("ovs-vsctl")
OVS_FLOW_BIN = which("ovs-ofctl")
VNODED_BIN = which("vnoded", required=True)
VCMD_BIN = which("vcmd", required=True)
BRCTL_BIN = which("brctl", required=True)
SYSCTL_BIN = which("sysctl", required=True)
IP_BIN = which("ip", required=True)
ETHTOOL_BIN = which("ethtool", required=True)
TC_BIN = which("tc", required=True)
EBTABLES_BIN = which("ebtables", required=True)
MOUNT_BIN = which("mount", required=True)
UMOUNT_BIN = which("umount", required=True)
OVS_BIN = which("ovs-vsctl", required=False)
OVS_FLOW_BIN = which("ovs-ofctl", required=False)

View file

@ -7,7 +7,7 @@ import logging
import os
import threading
from core import CoreCommandError, CoreError, constants, utils
from core import utils
from core.api.tlv import coreapi, dataconversion
from core.config import ConfigGroup, ConfigShim, Configuration, ModelManager
from core.emane import emanemanifest
@ -15,6 +15,7 @@ from core.emane.bypass import EmaneBypassModel
from core.emane.commeffect import EmaneCommEffectModel
from core.emane.emanemodel import EmaneModel
from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNet
from core.emane.rfpipe import EmaneRfPipeModel
from core.emane.tdma import EmaneTdmaModel
from core.emulator.enumerations import (
@ -23,10 +24,9 @@ from core.emulator.enumerations import (
ConfigTlvs,
MessageFlags,
MessageTypes,
NodeTypes,
RegisterTlvs,
)
from core.nodes import nodeutils
from core.errors import CoreCommandError, CoreError
from core.xml import emanexml
try:
@ -54,8 +54,8 @@ DEFAULT_EMANE_PREFIX = "/usr"
class EmaneManager(ModelManager):
"""
EMANE controller object. Lives in a Session instance and is used for
building EMANE config files from all of the EmaneNode objects in this
emulation, and for controlling the EMANE daemons.
building EMANE config files for all EMANE networks in this emulation, and for
controlling the EMANE daemons.
"""
name = "emane"
@ -73,7 +73,7 @@ class EmaneManager(ModelManager):
"""
super(EmaneManager, self).__init__()
self.session = session
self._emane_nodes = {}
self._emane_nets = {}
self._emane_node_lock = threading.Lock()
self._ifccounts = {}
self._ifccountslock = threading.Lock()
@ -227,38 +227,39 @@ class EmaneManager(ModelManager):
emane_model.load(emane_prefix)
self.models[emane_model.name] = emane_model
def add_node(self, emane_node):
def add_node(self, emane_net):
"""
Add a new EmaneNode object to this Emane controller object
Add EMANE network object to this manager.
:param core.emane.nodes.EmaneNode emane_node: emane node to add
:param core.emane.nodes.EmaneNet emane_net: emane node to add
:return: nothing
"""
with self._emane_node_lock:
if emane_node.id in self._emane_nodes:
if emane_net.id in self._emane_nets:
raise KeyError(
"non-unique EMANE object id %s for %s" % (emane_node.id, emane_node)
"non-unique EMANE object id %s for %s" % (emane_net.id, emane_net)
)
self._emane_nodes[emane_node.id] = emane_node
self._emane_nets[emane_net.id] = emane_net
def getnodes(self):
"""
Return a set of CoreNodes that are linked to an EmaneNode,
Return a set of CoreNodes that are linked to an EMANE network,
e.g. containers having one or more radio interfaces.
"""
# assumes self._objslock already held
nodes = set()
for emane_node in self._emane_nodes.values():
for netif in emane_node.netifs():
for emane_net in self._emane_nets.values():
for netif in emane_net.netifs():
nodes.add(netif.node)
return nodes
def setup(self):
"""
Populate self._objs with EmaneNodes; perform distributed setup;
associate models with EmaneNodes from self.config. Returns
Emane.(SUCCESS, NOT_NEEDED, NOT_READY) in order to delay session
instantiation.
Setup duties for EMANE manager.
:return: SUCCESS, NOT_NEEDED, NOT_READY in order to delay session
instantiation
:rtype: int
"""
logging.debug("emane setup")
@ -266,13 +267,13 @@ class EmaneManager(ModelManager):
with self.session._nodes_lock:
for node_id in self.session.nodes:
node = self.session.nodes[node_id]
if nodeutils.is_node(node, NodeTypes.EMANE):
if isinstance(node, EmaneNet):
logging.debug(
"adding emane node: id(%s) name(%s)", node.id, node.name
)
self.add_node(node)
if not self._emane_nodes:
if not self._emane_nets:
logging.debug("no emane nodes in session")
return EmaneManager.NOT_NEEDED
@ -326,9 +327,12 @@ class EmaneManager(ModelManager):
def startup(self):
"""
After all the EmaneNode objects have been added, build XML files
and start the daemons. Returns Emane.(SUCCESS, NOT_NEEDED, or
NOT_READY) which is used to delay session instantiation.
After all the EMANE networks have been added, build XML files
and start the daemons.
:return: SUCCESS, NOT_NEEDED, NOT_READY in order to delay session
instantiation
:rtype: int
"""
self.reset()
r = self.setup()
@ -347,8 +351,8 @@ class EmaneManager(ModelManager):
self.startdaemons()
self.installnetifs()
for node_id in self._emane_nodes:
emane_node = self._emane_nodes[node_id]
for node_id in self._emane_nets:
emane_node = self._emane_nets[node_id]
for netif in emane_node.netifs():
nems.append(
(netif.node.name, netif.name, emane_node.getnemid(netif))
@ -373,8 +377,8 @@ class EmaneManager(ModelManager):
return
with self._emane_node_lock:
for key in sorted(self._emane_nodes.keys()):
emane_node = self._emane_nodes[key]
for key in sorted(self._emane_nets.keys()):
emane_node = self._emane_nets[key]
logging.debug(
"post startup for emane node: %s - %s",
emane_node.id,
@ -387,11 +391,11 @@ class EmaneManager(ModelManager):
def reset(self):
"""
remove all EmaneNode objects from the dictionary,
reset port numbers and nem id counters
Remove all EMANE networks from the dictionary, reset port numbers and
nem id counters
"""
with self._emane_node_lock:
self._emane_nodes.clear()
self._emane_nets.clear()
# don't clear self._ifccounts here; NEM counts are needed for buildxml
self.platformport = self.session.options.get_config_int(
@ -409,7 +413,7 @@ class EmaneManager(ModelManager):
self._ifccounts.clear()
with self._emane_node_lock:
if not self._emane_nodes:
if not self._emane_nets:
return
logging.info("stopping EMANE daemons.")
self.deinstallnetifs()
@ -449,7 +453,7 @@ class EmaneManager(ModelManager):
master = False
with self._emane_node_lock:
if self._emane_nodes:
if self._emane_nets:
master = self.session.master
logging.info("emane check distributed as master: %s.", master)
@ -459,8 +463,8 @@ class EmaneManager(ModelManager):
nemcount = 0
with self._emane_node_lock:
for key in self._emane_nodes:
emane_node = self._emane_nodes[key]
for key in self._emane_nets:
emane_node = self._emane_nets[key]
nemcount += emane_node.numnetif()
nemid = int(self.get_config("nem_id_start"))
@ -470,7 +474,7 @@ class EmaneManager(ModelManager):
# build an ordered list of servers so platform ID is deterministic
servers = []
for key in sorted(self._emane_nodes):
for key in sorted(self._emane_nets):
for server in self.session.broker.getserversbynode(key):
if server not in servers:
servers.append(server)
@ -566,11 +570,10 @@ class EmaneManager(ModelManager):
def check_node_models(self):
"""
Associate EmaneModel classes with EmaneNode nodes. The model
configurations are stored in self.configs.
Associate EMANE model classes with EMANE network nodes.
"""
for node_id in self._emane_nodes:
emane_node = self._emane_nodes[node_id]
for node_id in self._emane_nets:
emane_node = self._emane_nets[node_id]
logging.debug("checking emane model for node: %s", node_id)
# skip nodes that already have a model set
@ -596,13 +599,13 @@ class EmaneManager(ModelManager):
def nemlookup(self, nemid):
"""
Look for the given numerical NEM ID and return the first matching
EmaneNode and NEM interface.
EMANE network and NEM interface.
"""
emane_node = None
netif = None
for node_id in self._emane_nodes:
emane_node = self._emane_nodes[node_id]
for node_id in self._emane_nets:
emane_node = self._emane_nets[node_id]
netif = emane_node.getnemnetif(nemid)
if netif is not None:
break
@ -616,8 +619,8 @@ class EmaneManager(ModelManager):
Return the number of NEMs emulated locally.
"""
count = 0
for node_id in self._emane_nodes:
emane_node = self._emane_nodes[node_id]
for node_id in self._emane_nets:
emane_node = self._emane_nets[node_id]
count += len(emane_node.netifs())
return count
@ -629,20 +632,19 @@ class EmaneManager(ModelManager):
platform_xmls = {}
# assume self._objslock is already held here
for key in sorted(self._emane_nodes.keys()):
emane_node = self._emane_nodes[key]
for key in sorted(self._emane_nets.keys()):
emane_node = self._emane_nets[key]
nemid = emanexml.build_node_platform_xml(
self, ctrlnet, emane_node, nemid, platform_xmls
)
def buildnemxml(self):
"""
Builds the xxxnem.xml, xxxmac.xml, and xxxphy.xml files which
are defined on a per-EmaneNode basis.
Builds the nem, mac, and phy xml files for each EMANE network.
"""
for key in sorted(self._emane_nodes.keys()):
emane_node = self._emane_nodes[key]
emanexml.build_xml_files(self, emane_node)
for key in sorted(self._emane_nets):
emane_net = self._emane_nets[key]
emanexml.build_xml_files(self, emane_net)
def buildtransportxml(self):
"""
@ -731,13 +733,11 @@ class EmaneManager(ModelManager):
)
# multicast route is needed for OTA data
args = [constants.IP_BIN, "route", "add", otagroup, "dev", otadev]
node.network_cmd(args)
node.node_net_client.create_route(otagroup, otadev)
# multicast route is also needed for event data if on control network
if eventservicenetidx >= 0 and eventgroup != otagroup:
args = [constants.IP_BIN, "route", "add", eventgroup, "dev", eventdev]
node.network_cmd(args)
node.node_net_client.create_route(eventgroup, eventdev)
# start emane
args = emanecmd + [
@ -786,8 +786,8 @@ class EmaneManager(ModelManager):
Install TUN/TAP virtual interfaces into their proper namespaces
now that the EMANE daemons are running.
"""
for key in sorted(self._emane_nodes.keys()):
emane_node = self._emane_nodes[key]
for key in sorted(self._emane_nets.keys()):
emane_node = self._emane_nets[key]
logging.info("emane install netifs for node: %d", key)
emane_node.installnetifs()
@ -795,8 +795,8 @@ class EmaneManager(ModelManager):
"""
Uninstall TUN/TAP virtual interfaces.
"""
for key in sorted(self._emane_nodes.keys()):
emane_node = self._emane_nodes[key]
for key in sorted(self._emane_nets.keys()):
emane_node = self._emane_nets[key]
emane_node.deinstallnetifs()
def doeventmonitor(self):

View file

@ -4,10 +4,10 @@ Defines Emane Models used within CORE.
import logging
import os
from core import CoreError
from core.config import ConfigGroup, Configuration
from core.emane import emanemanifest
from core.emulator.enumerations import ConfigDataTypes
from core.errors import CoreError
from core.location.mobility import WirelessModel
from core.xml import emanexml

View file

@ -1,6 +1,5 @@
"""
nodes.py: definition of an EmaneNode class for implementing configuration
control of an EMANE emulation. An EmaneNode has several attached NEMs that
Provides an EMANE network node class, which has several attached NEMs that
share the same MAC+PHY model.
"""
@ -19,25 +18,19 @@ except ImportError:
class EmaneNet(CoreNetworkBase):
"""
EMANE network base class.
"""
apitype = NodeTypes.EMANE.value
linktype = LinkTypes.WIRELESS.value
# icon used
type = "wlan"
class EmaneNode(EmaneNet):
"""
EMANE node contains NEM configuration and causes connected nodes
to have TAP interfaces (instead of VEth). These are managed by the
Emane controller object that exists in a session.
"""
apitype = NodeTypes.EMANE.value
linktype = LinkTypes.WIRELESS.value
type = "wlan"
is_emane = True
def __init__(self, session, _id=None, name=None, start=True):
super(EmaneNode, self).__init__(session, _id, name, start)
super(EmaneNet, self).__init__(session, _id, name, start)
self.conf = ""
self.up = False
self.nemidmap = {}

View file

@ -7,7 +7,6 @@ import sys
import core.services
from core.emulator.emudata import IdGen
from core.emulator.session import Session
from core.nodes import nodemaps, nodeutils
from core.services.coreservices import ServiceManager
@ -45,7 +44,7 @@ class CoreEmu(object):
os.umask(0)
# configuration
if not config:
if config is None:
config = {}
self.config = config
@ -53,10 +52,6 @@ class CoreEmu(object):
self.session_id_gen = IdGen(_id=0)
self.sessions = {}
# set default nodes
node_map = nodemaps.NODES
nodeutils.set_node_map(node_map)
# load services
self.service_errors = []
self.load_services()
@ -77,15 +72,6 @@ class CoreEmu(object):
custom_service_errors = ServiceManager.add_services(service_path)
self.service_errors.extend(custom_service_errors)
def update_nodes(self, node_map):
"""
Updates node map used by core.
:param dict node_map: node map to update existing node map with
:return: nothing
"""
nodeutils.update_node_map(node_map)
def shutdown(self):
"""
Shutdown all CORE session.
@ -99,12 +85,13 @@ class CoreEmu(object):
session = sessions[_id]
session.shutdown()
def create_session(self, _id=None, master=True):
def create_session(self, _id=None, master=True, _cls=Session):
"""
Create a new CORE session, set to master if running standalone.
:param int _id: session id for new session
:param bool master: sets session to master
:param class _cls: Session class to use
:return: created session
:rtype: EmuSession
"""
@ -114,7 +101,7 @@ class CoreEmu(object):
if _id not in self.sessions:
break
session = Session(_id, config=self.config)
session = _cls(_id, config=self.config)
logging.info("created session: %s", _id)
if master:
session.master = True

View file

@ -1,7 +1,7 @@
from core.emulator.enumerations import LinkTypes, NodeTypes
from core.nodes import nodeutils
from core.nodes.base import CoreNetworkBase
from core.emane.nodes import EmaneNet
from core.emulator.enumerations import LinkTypes
from core.nodes.ipaddress import Ipv4Prefix, Ipv6Prefix, MacAddress
from core.nodes.physical import PhysicalNode
class IdGen(object):
@ -13,17 +13,6 @@ class IdGen(object):
return self.id
def is_net_node(node):
"""
Convenience method for testing if a legacy core node is considered a network node.
:param object node: object to test against
:return: True if object is an instance of a network node, False otherwise
:rtype: bool
"""
return isinstance(node, CoreNetworkBase)
def create_interface(node, network, interface_data):
"""
Create an interface for a node on a network using provided interface data.
@ -64,8 +53,9 @@ def link_config(network, interface, link_options, devname=None, interface_two=No
"netif2": interface_two,
}
# hacky check here, because physical and emane nodes do not conform to the same linkconfig interface
if not nodeutils.is_node(network, [NodeTypes.EMANE, NodeTypes.PHYSICAL]):
# hacky check here, because physical and emane nodes do not conform to the same
# linkconfig interface
if not isinstance(network, (EmaneNet, PhysicalNode)):
config["devname"] = devname
network.linkconfig(**config)
@ -81,7 +71,8 @@ class NodeOptions(object):
Create a NodeOptions object.
:param str name: name of node, defaults to node class name postfix with its id
:param str model: defines services for default and physical nodes, defaults to "router"
:param str model: defines services for default and physical nodes, defaults to
"router"
:param str image: image to use for docker nodes
"""
self.name = name
@ -133,7 +124,8 @@ class LinkOptions(object):
"""
Create a LinkOptions object.
:param core.emulator.enumerations.LinkTypes _type: type of link, defaults to wired
:param core.emulator.enumerations.LinkTypes _type: type of link, defaults to
wired
"""
self.type = _type
self.session = None
@ -202,12 +194,13 @@ class IpPrefixes(object):
def create_interface(self, node, name=None, mac=None):
"""
Creates interface data for linking nodes, using the nodes unique id for generation, along with a random
mac address, unless provided.
Creates interface data for linking nodes, using the nodes unique id for
generation, along with a random mac address, unless provided.
:param core.nodes.base.CoreNode node: node to create interface for
:param str name: name to set for interface, default is eth{id}
:param str mac: mac address to use for this interface, default is random generation
:param str mac: mac address to use for this interface, default is random
generation
:return: new interface data for the provided node
:rtype: InterfaceData
"""

View file

@ -14,33 +14,65 @@ import threading
import time
from multiprocessing.pool import ThreadPool
import core.nodes.base
from core import CoreError, constants, utils
from core import constants, utils
from core.api.tlv import coreapi
from core.api.tlv.broker import CoreBroker
from core.emane.emanemanager import EmaneManager
from core.emane.nodes import EmaneNet
from core.emulator.data import EventData, ExceptionData, NodeData
from core.emulator.emudata import (
IdGen,
LinkOptions,
NodeOptions,
create_interface,
is_net_node,
link_config,
)
from core.emulator.enumerations import EventTypes, ExceptionLevels, LinkTypes, NodeTypes
from core.emulator.sessionconfig import SessionConfig, SessionMetaData
from core.errors import CoreError
from core.location.corelocation import CoreLocation
from core.location.event import EventLoop
from core.location.mobility import MobilityManager
from core.nodes import nodeutils
from core.nodes.base import CoreNodeBase
from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase
from core.nodes.docker import DockerNode
from core.nodes.ipaddress import MacAddress
from core.nodes.lxd import LxcNode
from core.nodes.network import (
CtrlNet,
GreTapBridge,
HubNode,
PtpNet,
SwitchNode,
TunnelNode,
WlanNode,
)
from core.nodes.physical import PhysicalNode, Rj45Node
from core.plugins.sdt import Sdt
from core.services.coreservices import CoreServices
from core.xml import corexml, corexmldeployment
from core.xml.corexml import CoreXmlReader, CoreXmlWriter
# maps for converting from API call node type values to classes and vice versa
NODES = {
NodeTypes.DEFAULT: CoreNode,
NodeTypes.PHYSICAL: PhysicalNode,
NodeTypes.TBD: None,
NodeTypes.SWITCH: SwitchNode,
NodeTypes.HUB: HubNode,
NodeTypes.WIRELESS_LAN: WlanNode,
NodeTypes.RJ45: Rj45Node,
NodeTypes.TUNNEL: TunnelNode,
NodeTypes.KTUNNEL: None,
NodeTypes.EMANE: EmaneNet,
NodeTypes.EMANE_NET: None,
NodeTypes.TAP_BRIDGE: GreTapBridge,
NodeTypes.PEER_TO_PEER: PtpNet,
NodeTypes.CONTROL_NET: CtrlNet,
NodeTypes.DOCKER: DockerNode,
NodeTypes.LXC: LxcNode,
}
NODES_TYPE = {NODES[x]: x for x in NODES}
class Session(object):
"""
@ -121,6 +153,33 @@ class Session(object):
"host": ("DefaultRoute", "SSH"),
}
@classmethod
def get_node_class(cls, _type):
"""
Retrieve the class for a given node type.
:param core.emulator.enumerations.NodeTypes _type: node type to get class for
:return: node class
"""
node_class = NODES.get(_type)
if node_class is None:
raise CoreError("invalid node type: %s" % _type)
return node_class
@classmethod
def get_node_type(cls, _class):
"""
Retrieve node type for a given node class.
:param _class: node class to get a node type for
:return: node type
:rtype: core.emulator.enumerations.NodeTypes
"""
node_type = NODES_TYPE.get(_class)
if node_type is None:
raise CoreError("invalid node class: %s" % _class)
return node_type
def _link_nodes(self, node_one_id, node_two_id):
"""
Convenience method for retrieving nodes within link data.
@ -145,7 +204,7 @@ class Session(object):
# both node ids are provided
tunnel = self.broker.gettunnel(node_one_id, node_two_id)
logging.debug("tunnel between nodes: %s", tunnel)
if nodeutils.is_node(tunnel, NodeTypes.TAP_BRIDGE):
if isinstance(tunnel, GreTapBridge):
net_one = tunnel
if tunnel.remotenum == node_one_id:
node_one = None
@ -158,14 +217,14 @@ class Session(object):
else:
node_two = None
if is_net_node(node_one):
if isinstance(node_one, CoreNetworkBase):
if not net_one:
net_one = node_one
else:
net_two = node_one
node_one = None
if is_net_node(node_two):
if isinstance(node_two, CoreNetworkBase):
if not net_one:
net_one = node_two
else:
@ -203,9 +262,7 @@ class Session(object):
raise CoreError("no common network found for wireless link/unlink")
for common_network, interface_one, interface_two in common_networks:
if not nodeutils.is_node(
common_network, [NodeTypes.WIRELESS_LAN, NodeTypes.EMANE]
):
if not isinstance(common_network, (WlanNode, EmaneNet)):
logging.info(
"skipping common network that is not wireless/emane: %s",
common_network,
@ -268,9 +325,8 @@ class Session(object):
node_one.name,
node_two.name,
)
ptp_class = nodeutils.get_node_class(NodeTypes.PEER_TO_PEER)
start = self.state > EventTypes.DEFINITION_STATE.value
net_one = self.create_node(cls=ptp_class, start=start)
net_one = self.create_node(cls=PtpNet, start=start)
# node to network
if node_one and net_one:
@ -300,7 +356,7 @@ class Session(object):
net_one.name,
net_two.name,
)
if nodeutils.is_node(net_two, NodeTypes.RJ45):
if isinstance(net_two, Rj45Node):
interface = net_two.linknet(net_one)
else:
interface = net_one.linknet(net_two)
@ -324,12 +380,12 @@ class Session(object):
# tunnel node logic
key = link_options.key
if key and nodeutils.is_node(net_one, NodeTypes.TUNNEL):
if key and isinstance(net_one, TunnelNode):
logging.info("setting tunnel key for: %s", net_one.name)
net_one.setkey(key)
if addresses:
net_one.addrconfig(addresses)
if key and nodeutils.is_node(net_two, NodeTypes.TUNNEL):
if key and isinstance(net_two, TunnelNode):
logging.info("setting tunnel key for: %s", net_two.name)
net_two.setkey(key)
if addresses:
@ -337,14 +393,14 @@ class Session(object):
# physical node connected with tunnel
if not net_one and not net_two and (node_one or node_two):
if node_one and nodeutils.is_node(node_one, NodeTypes.PHYSICAL):
if node_one and isinstance(node_one, PhysicalNode):
logging.info("adding link for physical node: %s", node_one.name)
addresses = interface_one.get_addresses()
node_one.adoptnetif(
tunnel, interface_one.id, interface_one.mac, addresses
)
link_config(node_one, tunnel, link_options)
elif node_two and nodeutils.is_node(node_two, NodeTypes.PHYSICAL):
elif node_two and isinstance(node_two, PhysicalNode):
logging.info("adding link for physical node: %s", node_two.name)
addresses = interface_two.get_addresses()
node_two.adoptnetif(
@ -584,14 +640,11 @@ class Session(object):
:param int _id: id for node, defaults to None for generated id
:param core.emulator.emudata.NodeOptions node_options: data to create node with
:return: created node
:raises core.CoreError: when an invalid node type is given
"""
# retrieve node class for given node type
try:
node_class = nodeutils.get_node_class(_type)
except KeyError:
logging.error("invalid node type to create: %s", _type)
return None
# validate node type, get class, or throw error
node_class = self.get_node_class(_type)
# set node start based on current session state, override and check when rj45
start = self.state > EventTypes.DEFINITION_STATE.value
@ -651,10 +704,8 @@ class Session(object):
logging.debug("set node type: %s", node.type)
self.services.add_services(node, node.type, node_options.services)
# boot nodes if created after runtime, LcxNodes, Physical, and RJ45 are all PyCoreNodes
is_boot_node = isinstance(node, CoreNodeBase) and not nodeutils.is_node(
node, NodeTypes.RJ45
)
# boot nodes if created after runtime, CoreNodes, Physical, and RJ45 are all nodes
is_boot_node = isinstance(node, CoreNodeBase) and not isinstance(node, Rj45Node)
if self.state == EventTypes.RUNTIME_STATE.value and is_boot_node:
self.write_nodes()
self.add_remove_control_interface(node=node, remove=False)
@ -1178,8 +1229,8 @@ class Session(object):
"""
if state == EventTypes.RUNTIME_STATE.value:
self.emane.poststartup()
xml_file_version = self.options.get_config("xmlfilever")
if xml_file_version in ("1.0",):
# create session deployed xml
xml_file_name = os.path.join(self.session_dir, "session-deployed.xml")
xml_writer = corexml.CoreXmlWriter(self)
corexmldeployment.CoreXmlDeployment(self, xml_writer.scenario)
@ -1441,12 +1492,10 @@ class Session(object):
count = 0
for node_id in self.nodes:
node = self.nodes[node_id]
is_p2p_ctrlnet = nodeutils.is_node(
node, (NodeTypes.PEER_TO_PEER, NodeTypes.CONTROL_NET)
is_p2p_ctrlnet = isinstance(node, (PtpNet, CtrlNet))
is_tap = isinstance(node, GreTapBridge) and not isinstance(
node, TunnelNode
)
is_tap = nodeutils.is_node(
node, NodeTypes.TAP_BRIDGE
) and not nodeutils.is_node(node, NodeTypes.TUNNEL)
if is_p2p_ctrlnet or is_tap:
continue
@ -1493,7 +1542,7 @@ class Session(object):
for node_id in self.nodes:
node = self.nodes[node_id]
# TODO: determine if checking for CoreNode alone is ok
if isinstance(node, core.nodes.base.CoreNodeBase):
if isinstance(node, CoreNodeBase):
self.services.stop_services(node)
# shutdown emane
@ -1546,10 +1595,7 @@ class Session(object):
start = time.time()
for _id in self.nodes:
node = self.nodes[_id]
# TODO: PyCoreNode is not the type to check
if isinstance(node, CoreNodeBase) and not nodeutils.is_node(
node, NodeTypes.RJ45
):
if isinstance(node, CoreNodeBase) and not isinstance(node, Rj45Node):
# add a control interface if configured
logging.info(
"booting node(%s): %s",
@ -1648,8 +1694,7 @@ class Session(object):
# no controlnet needed
return None
else:
control_net_class = nodeutils.get_node_class(NodeTypes.CONTROL_NET)
prefix_spec = control_net_class.DEFAULT_PREFIX_LIST[net_index]
prefix_spec = CtrlNet.DEFAULT_PREFIX_LIST[net_index]
logging.debug("prefix spec: %s", prefix_spec)
server_interface = self.get_control_net_server_interfaces()[net_index]
@ -1676,7 +1721,7 @@ class Session(object):
if net_index == 0:
updown_script = self.options.get_config("controlnet_updown_script")
if not updown_script:
logging.warning("controlnet updown script not configured")
logging.debug("controlnet updown script not configured")
prefixes = prefix_spec.split()
if len(prefixes) > 1:
@ -1725,9 +1770,8 @@ class Session(object):
prefix = prefixes[0]
logging.info("controlnet prefix: %s - %s", type(prefix), prefix)
control_net_class = nodeutils.get_node_class(NodeTypes.CONTROL_NET)
control_net = self.create_node(
cls=control_net_class,
cls=CtrlNet,
_id=_id,
prefix=prefix,
assign_address=assign_address,

21
daemon/core/errors.py Normal file
View file

@ -0,0 +1,21 @@
"""
Provides CORE specific errors.
"""
import subprocess
class CoreCommandError(subprocess.CalledProcessError):
"""
Used when encountering internal CORE command errors.
"""
def __str__(self):
return "Command(%s), Status(%s):\n%s" % (self.cmd, self.returncode, self.output)
class CoreError(Exception):
"""
Used for errors when dealing with CoreEmu and Sessions.
"""
pass

View file

@ -11,7 +11,7 @@ import time
from builtins import int
from functools import total_ordering
from core import CoreError, utils
from core import utils
from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager
from core.emulator.data import EventData, LinkData
from core.emulator.enumerations import (
@ -23,6 +23,7 @@ from core.emulator.enumerations import (
NodeTlvs,
RegisterTlvs,
)
from core.errors import CoreError
from core.nodes.base import CoreNodeBase
from core.nodes.ipaddress import IpAddress

View file

@ -14,16 +14,15 @@ import threading
from builtins import range
from socket import AF_INET, AF_INET6
from core import CoreCommandError, constants, utils
from core import constants, utils
from core.emulator.data import LinkData, NodeData
from core.emulator.enumerations import LinkTypes, NodeTypes
from core.nodes import client, ipaddress, nodeutils
from core.nodes import client, ipaddress
from core.nodes.interface import CoreInterface, TunTap, Veth
from core.nodes.netclient import LinuxNetClient, OvsNetClient
_DEFAULT_MTU = 1500
utils.check_executables([constants.IP_BIN])
class NodeBase(object):
"""
@ -54,7 +53,7 @@ class NodeBase(object):
self.type = None
self.server = None
self.services = None
# ifindex is key, PyCoreNetIf instance is value
# ifindex is key, CoreInterface instance is value
self._netif = {}
self.ifindex = 0
self.canvas = None
@ -62,6 +61,11 @@ class NodeBase(object):
self.opaque = None
self.position = Position()
if session.options.get_config("ovs") == "True":
self.net_client = OvsNetClient(self.net_cmd)
else:
self.net_client = LinuxNetClient(self.net_cmd)
def startup(self):
"""
Each object implements its own startup method.
@ -78,6 +82,18 @@ class NodeBase(object):
"""
raise NotImplementedError
def net_cmd(self, args):
"""
Runs a command that is used to configure and setup the network on the host
system.
:param list[str]|str args: command to run
:return: combined stdout and stderr
:rtype: str
:raises CoreCommandError: when a non-zero exit status occurs
"""
return utils.check_cmd(args)
def setposition(self, x=None, y=None, z=None):
"""
Set the (x,y,z) position of the object.
@ -360,6 +376,18 @@ class CoreNodeBase(NodeBase):
return common
def node_net_cmd(self, args):
"""
Runs a command that is used to configure and setup the network within a
node.
:param list[str]|str args: command to run
:return: combined stdout and stderr
:rtype: str
:raises CoreCommandError: when a non-zero exit status occurs
"""
raise NotImplementedError
def check_cmd(self, args):
"""
Runs shell command on node.
@ -434,6 +462,12 @@ class CoreNode(CoreNodeBase):
self.lock = threading.RLock()
self._mounts = []
self.bootsh = bootsh
if session.options.get_config("ovs") == "True":
self.node_net_client = OvsNetClient(self.node_net_cmd)
else:
self.node_net_client = LinuxNetClient(self.node_net_cmd)
if start:
self.startup()
@ -489,11 +523,11 @@ class CoreNode(CoreNodeBase):
# bring up the loopback interface
logging.debug("bringing up loopback interface")
self.network_cmd([constants.IP_BIN, "link", "set", "lo", "up"])
self.node_net_client.device_up("lo")
# set hostname for node
logging.debug("setting hostname: %s", self.name)
self.network_cmd(["hostname", self.name])
self.node_net_client.set_hostname(self.name)
# mark node as up
self.up = True
@ -568,9 +602,10 @@ class CoreNode(CoreNodeBase):
"""
return self.client.cmd_output(args)
def network_cmd(self, args):
def node_net_cmd(self, args):
"""
Runs a command for a node that is used to configure and setup network interfaces.
Runs a command that is used to configure and setup the network within a
node.
:param list[str]|str args: command to run
:return: combined stdout and stderr
@ -625,15 +660,8 @@ class CoreNode(CoreNodeBase):
"""
source = os.path.abspath(source)
logging.debug("node(%s) mounting: %s at %s", self.name, source, target)
cmd = 'mkdir -p "%s" && %s -n --bind "%s" "%s"' % (
target,
constants.MOUNT_BIN,
source,
target,
)
status, output = self.client.shcmd_result(cmd)
if status:
raise CoreCommandError(status, cmd, output)
self.client.check_cmd(["mkdir", "-p", target])
self.client.check_cmd([constants.MOUNT_BIN, "-n", "--bind", source, target])
self._mounts.append((source, target))
def newifindex(self):
@ -682,22 +710,16 @@ class CoreNode(CoreNodeBase):
)
if self.up:
utils.check_cmd(
[constants.IP_BIN, "link", "set", veth.name, "netns", str(self.pid)]
)
self.network_cmd(
[constants.IP_BIN, "link", "set", veth.name, "name", ifname]
)
self.network_cmd(
[constants.ETHTOOL_BIN, "-K", ifname, "rx", "off", "tx", "off"]
)
self.net_client.device_ns(veth.name, str(self.pid))
self.node_net_client.device_name(veth.name, ifname)
self.node_net_client.checksums_off(ifname)
veth.name = ifname
if self.up:
# TODO: potentially find better way to query interface ID
# retrieve interface information
output = self.network_cmd([constants.IP_BIN, "link", "show", veth.name])
output = self.node_net_client.device_show(veth.name)
logging.debug("interface command output: %s", output)
output = output.split("\n")
veth.flow_id = int(output[0].strip().split(":")[0]) + 1
@ -707,7 +729,8 @@ class CoreNode(CoreNodeBase):
logging.debug("interface mac: %s - %s", veth.name, veth.hwaddr)
try:
# add network interface to the node. If unsuccessful, destroy the network interface and raise exception.
# add network interface to the node. If unsuccessful, destroy the
# network interface and raise exception.
self.addnetif(veth, ifindex)
except ValueError as e:
veth.shutdown()
@ -758,105 +781,47 @@ class CoreNode(CoreNodeBase):
:return: nothing
:raises CoreCommandError: when a non-zero exit status occurs
"""
self._netif[ifindex].sethwaddr(addr)
interface = self._netif[ifindex]
interface.sethwaddr(addr)
if self.up:
args = [
constants.IP_BIN,
"link",
"set",
"dev",
self.ifname(ifindex),
"address",
str(addr),
]
self.network_cmd(args)
self.node_net_client.device_mac(interface.name, str(addr))
def addaddr(self, ifindex, addr):
"""
Add interface address.
:param int ifindex: index of interface to add address to
:param str addr: address to add to interface
:param core.nodes.ipaddress.IpAddress addr: address to add to interface
:return: nothing
"""
interface = self._netif[ifindex]
interface.addaddr(addr)
if self.up:
# check if addr is ipv6
if ":" in str(addr):
args = [
constants.IP_BIN,
"addr",
"add",
str(addr),
"dev",
self.ifname(ifindex),
]
self.network_cmd(args)
else:
args = [
constants.IP_BIN,
"addr",
"add",
str(addr),
"broadcast",
"+",
"dev",
self.ifname(ifindex),
]
self.network_cmd(args)
self._netif[ifindex].addaddr(addr)
address = str(addr)
# ipv6 check
broadcast = None
if ":" not in address:
broadcast = "+"
self.node_net_client.create_address(interface.name, address, broadcast)
def deladdr(self, ifindex, addr):
"""
Delete address from an interface.
:param int ifindex: index of interface to delete address from
:param str addr: address to delete from interface
:param core.nodes.ipaddress.IpAddress addr: address to delete from interface
:return: nothing
:raises CoreCommandError: when a non-zero exit status occurs
"""
interface = self._netif[ifindex]
try:
self._netif[ifindex].deladdr(addr)
interface.deladdr(addr)
except ValueError:
logging.exception("trying to delete unknown address: %s" % addr)
if self.up:
self.network_cmd(
[
constants.IP_BIN,
"addr",
"del",
str(addr),
"dev",
self.ifname(ifindex),
]
)
def delalladdr(self, ifindex, address_types=None):
"""
Delete all addresses from an interface.
:param int ifindex: index of interface to delete address types from
:param tuple[str] address_types: address types to delete
:return: nothing
:raises CoreCommandError: when a non-zero exit status occurs
"""
if not address_types:
address_types = self.valid_address_types
interface_name = self.ifname(ifindex)
addresses = self.client.getaddr(interface_name, rescan=True)
for address_type in address_types:
if address_type not in self.valid_address_types:
raise ValueError(
"addr type must be in: %s" % " ".join(self.valid_address_types)
)
for address in addresses[address_type]:
self.deladdr(ifindex, address)
# update cached information
self.client.getaddr(interface_name, rescan=True)
self.node_net_client.delete_address(interface.name, str(addr))
def ifup(self, ifindex):
"""
@ -866,9 +831,8 @@ class CoreNode(CoreNodeBase):
:return: nothing
"""
if self.up:
self.network_cmd(
[constants.IP_BIN, "link", "set", self.ifname(ifindex), "up"]
)
interface_name = self.ifname(ifindex)
self.node_net_client.device_up(interface_name)
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
"""
@ -886,8 +850,8 @@ class CoreNode(CoreNodeBase):
addrlist = []
with self.lock:
# TODO: see if you can move this to emane specific code
if nodeutils.is_node(net, NodeTypes.EMANE):
# TODO: emane specific code
if net.is_emane is True:
ifindex = self.newtuntap(ifindex=ifindex, ifname=ifname, net=net)
# TUN/TAP is not ready for addressing yet; the device may
# take some time to appear, and installing it into a
@ -919,7 +883,7 @@ class CoreNode(CoreNodeBase):
Connect a node.
:param str ifname: name of interface to connect
:param core.nodes.CoreNodeBase othernode: node to connect to
:param core.nodes.base.CoreNode othernode: node to connect to
:param str otherifname: interface name to connect to
:return: nothing
"""
@ -930,32 +894,14 @@ class CoreNode(CoreNodeBase):
tmp2 = "tmp." + "".join(
[random.choice(string.ascii_lowercase) for _ in range(tmplen)]
)
utils.check_cmd(
[
constants.IP_BIN,
"link",
"add",
"name",
tmp1,
"type",
"veth",
"peer",
"name",
tmp2,
]
)
utils.check_cmd([constants.IP_BIN, "link", "set", tmp1, "netns", str(self.pid)])
self.network_cmd([constants.IP_BIN, "link", "set", tmp1, "name", ifname])
self.net_client.create_veth(tmp1, tmp2)
self.net_client.device_ns(tmp1, str(self.pid))
self.node_net_client.device_name(tmp1, ifname)
interface = CoreInterface(node=self, name=ifname, mtu=_DEFAULT_MTU)
self.addnetif(interface, self.newifindex())
utils.check_cmd(
[constants.IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)]
)
othernode.network_cmd(
[constants.IP_BIN, "link", "set", tmp2, "name", otherifname]
)
self.net_client.device_ns(tmp2, str(othernode.pid))
othernode.node_net_client.device_name(tmp2, otherifname)
other_interface = CoreInterface(
node=othernode, name=otherifname, mtu=_DEFAULT_MTU
)
@ -972,11 +918,9 @@ class CoreNode(CoreNodeBase):
"""
logging.info("adding file from %s to %s", srcname, filename)
directory = os.path.dirname(filename)
cmd = 'mkdir -p "%s" && mv "%s" "%s" && sync' % (directory, srcname, filename)
status, output = self.client.shcmd_result(cmd)
if status:
raise CoreCommandError(status, cmd, output)
self.client.check_cmd(["mkdir", "-p", directory])
self.client.check_cmd(["mv", srcname, filename])
self.client.check_cmd(["sync"])
def hostfilename(self, filename):
"""
@ -1050,6 +994,7 @@ class CoreNetworkBase(NodeBase):
"""
linktype = LinkTypes.WIRED.value
is_emane = False
def __init__(self, session, _id, name, start=True):
"""

View file

@ -8,7 +8,8 @@ import logging
import os
from subprocess import PIPE, Popen
from core import CoreCommandError, constants, utils
from core import constants, utils
from core.errors import CoreCommandError
class VnodeClient(object):
@ -25,7 +26,6 @@ class VnodeClient(object):
"""
self.name = name
self.ctrlchnlname = ctrlchnlname
self._addr = {}
def _verify_connection(self):
"""
@ -145,36 +145,6 @@ class VnodeClient(object):
*args
)
def redircmd(self, infd, outfd, errfd, args, wait=True):
"""
Execute a command on a node with standard input, output, and
error redirected according to the given file descriptors.
:param infd: stdin file descriptor
:param outfd: stdout file descriptor
:param errfd: stderr file descriptor
:param list[str]|str args: command arguments
:param bool wait: wait flag
:return: command status
:rtype: int
"""
self._verify_connection()
# run command, return process when not waiting
args = utils.split_args(args)
cmd = self._cmd_args() + args
logging.debug("redircmd: %s", cmd)
p = Popen(cmd, stdin=infd, stdout=outfd, stderr=errfd)
if not wait:
return p
# wait for and return exit status
status = p.wait()
if status:
logging.warning("cmd exited with status %s: %s", status, args)
return status
def term(self, sh="/bin/sh"):
"""
Open a terminal on a node.
@ -214,111 +184,3 @@ class VnodeClient(object):
:return: str
"""
return "%s -c %s -- %s" % (constants.VCMD_BIN, self.ctrlchnlname, sh)
def shcmd(self, cmd, sh="/bin/sh"):
"""
Execute a shell command.
:param str cmd: command string
:param str sh: shell to run command in
:return: command result
:rtype: int
"""
return self.cmd([sh, "-c", cmd])
def shcmd_result(self, cmd, sh="/bin/sh"):
"""
Execute a shell command and return the exist status and combined output.
:param str cmd: shell command to run
:param str sh: shell to run command in
:return: exist status and combined output
:rtype: tuple[int, str]
"""
return self.cmd_output([sh, "-c", cmd])
def getaddr(self, ifname, rescan=False):
"""
Get address for interface on node.
:param str ifname: interface name to get address for
:param bool rescan: rescan flag
:return: interface information
:rtype: dict
"""
if ifname in self._addr and not rescan:
return self._addr[ifname]
interface = {"ether": [], "inet": [], "inet6": [], "inet6link": []}
args = [constants.IP_BIN, "addr", "show", "dev", ifname]
p, stdin, stdout, stderr = self.popen(args)
stdin.close()
for line in stdout:
line = line.strip().split()
if line[0] == "link/ether":
interface["ether"].append(line[1])
elif line[0] == "inet":
interface["inet"].append(line[1])
elif line[0] == "inet6":
if line[3] == "global":
interface["inet6"].append(line[1])
elif line[3] == "link":
interface["inet6link"].append(line[1])
else:
logging.warning("unknown scope: %s" % line[3])
err = stderr.read()
stdout.close()
stderr.close()
status = p.wait()
if status:
logging.warning("nonzero exist status (%s) for cmd: %s", status, args)
if err:
logging.warning("error output: %s", err)
self._addr[ifname] = interface
return interface
def netifstats(self, ifname=None):
"""
Retrieve network interface state.
:param str ifname: name of interface to get state for
:return: interface state information
:rtype: dict
"""
stats = {}
args = ["cat", "/proc/net/dev"]
p, stdin, stdout, stderr = self.popen(args)
stdin.close()
# ignore first line
stdout.readline()
# second line has count names
tmp = stdout.readline().decode("utf-8").strip().split("|")
rxkeys = tmp[1].split()
txkeys = tmp[2].split()
for line in stdout:
line = line.decode("utf-8").strip().split()
devname, tmp = line[0].split(":")
if tmp:
line.insert(1, tmp)
stats[devname] = {"rx": {}, "tx": {}}
field = 1
for count in rxkeys:
stats[devname]["rx"][count] = int(line[field])
field += 1
for count in txkeys:
stats[devname]["tx"][count] = int(line[field])
field += 1
err = stderr.read()
stdout.close()
stderr.close()
status = p.wait()
if status:
logging.warning("nonzero exist status (%s) for cmd: %s", status, args)
if err:
logging.warning("error output: %s", err)
if ifname is not None:
return stats[ifname]
else:
return stats

View file

@ -2,8 +2,9 @@ import json
import logging
import os
from core import CoreCommandError, utils
from core import utils
from core.emulator.enumerations import NodeTypes
from core.errors import CoreCommandError
from core.nodes.base import CoreNode
@ -12,7 +13,6 @@ class DockerClient(object):
self.name = name
self.image = image
self.pid = None
self._addr = {}
def create_container(self):
utils.check_cmd(
@ -94,40 +94,6 @@ class DockerClient(object):
if status:
raise CoreCommandError(status, args, output)
def getaddr(self, ifname, rescan=False):
"""
Get address for interface on node.
:param str ifname: interface name to get address for
:param bool rescan: rescan flag
:return: interface information
:rtype: dict
"""
if ifname in self._addr and not rescan:
return self._addr[ifname]
interface = {"ether": [], "inet": [], "inet6": [], "inet6link": []}
args = ["ip", "addr", "show", "dev", ifname]
status, output = self.ns_cmd(args)
for line in output:
line = line.strip().split()
if line[0] == "link/ether":
interface["ether"].append(line[1])
elif line[0] == "inet":
interface["inet"].append(line[1])
elif line[0] == "inet6":
if line[3] == "global":
interface["inet6"].append(line[1])
elif line[3] == "link":
interface["inet6link"].append(line[1])
else:
logging.warning("unknown scope: %s" % line[3])
if status:
logging.warning("nonzero exist status (%s) for cmd: %s", status, args)
self._addr[ifname] = interface
return interface
class DockerNode(CoreNode):
apitype = NodeTypes.DOCKER.value
@ -224,7 +190,7 @@ class DockerNode(CoreNode):
raise CoreCommandError(status, args, output)
return output
def network_cmd(self, args):
def node_net_cmd(self, args):
if not self.up:
logging.debug("node down, not running network command: %s", args)
return 0

View file

@ -6,11 +6,9 @@ import logging
import time
from builtins import int, range
from core import CoreCommandError, constants, utils
from core.emulator.enumerations import NodeTypes
from core.nodes import nodeutils
utils.check_executables([constants.IP_BIN])
from core import utils
from core.errors import CoreCommandError
from core.nodes.netclient import LinuxNetClient
class CoreInterface(object):
@ -44,6 +42,7 @@ class CoreInterface(object):
self.netindex = None
# index used to find flow data
self.flow_id = None
self.net_client = LinuxNetClient(utils.check_cmd)
def startup(self):
"""
@ -219,21 +218,8 @@ class Veth(CoreInterface):
:return: nothing
:raises CoreCommandError: when there is a command exception
"""
utils.check_cmd(
[
constants.IP_BIN,
"link",
"add",
"name",
self.localname,
"type",
"veth",
"peer",
"name",
self.name,
]
)
utils.check_cmd([constants.IP_BIN, "link", "set", self.localname, "up"])
self.net_client.create_veth(self.localname, self.name)
self.net_client.device_up(self.localname)
self.up = True
def shutdown(self):
@ -247,15 +233,13 @@ class Veth(CoreInterface):
if self.node:
try:
self.node.network_cmd(
[constants.IP_BIN, "-6", "addr", "flush", "dev", self.name]
)
self.node.node_net_client.device_flush(self.name)
except CoreCommandError:
logging.exception("error shutting down interface")
if self.localname:
try:
utils.check_cmd([constants.IP_BIN, "link", "delete", self.localname])
self.net_client.delete_device(self.localname)
except CoreCommandError:
logging.info("link already removed: %s", self.localname)
@ -310,9 +294,7 @@ class TunTap(CoreInterface):
return
try:
self.node.network_cmd(
[constants.IP_BIN, "-6", "addr", "flush", "dev", self.name]
)
self.node.node_net_client.device_flush(self.name)
except CoreCommandError:
logging.exception("error shutting down tunnel tap")
@ -360,8 +342,11 @@ class TunTap(CoreInterface):
logging.debug("waiting for device local: %s", self.localname)
def localdevexists():
args = [constants.IP_BIN, "link", "show", self.localname]
return utils.cmd(args)
try:
self.net_client.device_show(self.localname)
return 0
except CoreCommandError:
return 1
self.waitfor(localdevexists)
@ -374,9 +359,8 @@ class TunTap(CoreInterface):
logging.debug("waiting for device node: %s", self.name)
def nodedevexists():
args = [constants.IP_BIN, "link", "show", self.name]
try:
self.node.network_cmd(args)
self.node.node_net_client.device_show(self.name)
return 0
except CoreCommandError:
return 1
@ -387,13 +371,12 @@ class TunTap(CoreInterface):
if result:
break
# TODO: emane specific code
# check if this is an EMANE interface; if so, continue
# waiting if EMANE is still running
# TODO: remove emane code
should_retry = count < 5
is_emane_node = nodeutils.is_node(self.net, NodeTypes.EMANE)
is_emane_running = self.node.session.emane.emanerunning(self.node)
if all([should_retry, is_emane_node, is_emane_running]):
if all([should_retry, self.net.is_emane, is_emane_running]):
count += 1
else:
raise RuntimeError("node device failed to exist")
@ -410,13 +393,9 @@ class TunTap(CoreInterface):
"""
self.waitfordevicelocal()
netns = str(self.node.pid)
utils.check_cmd(
[constants.IP_BIN, "link", "set", self.localname, "netns", netns]
)
self.node.network_cmd(
[constants.IP_BIN, "link", "set", self.localname, "name", self.name]
)
self.node.network_cmd([constants.IP_BIN, "link", "set", self.name, "up"])
self.net_client.device_ns(self.localname, netns)
self.node.node_net_client.device_name(self.localname, self.name)
self.node.node_net_client.device_up(self.name)
def setaddrs(self):
"""
@ -426,9 +405,7 @@ class TunTap(CoreInterface):
"""
self.waitfordevicenode()
for addr in self.addrlist:
self.node.network_cmd(
[constants.IP_BIN, "addr", "add", str(addr), "dev", self.name]
)
self.node.node_net_client.create_address(self.name, str(addr))
class GreTap(CoreInterface):
@ -482,25 +459,11 @@ class GreTap(CoreInterface):
if remoteip is None:
raise ValueError("missing remote IP required for GRE TAP device")
args = [
constants.IP_BIN,
"link",
"add",
self.localname,
"type",
"gretap",
"remote",
str(remoteip),
]
if localip:
args += ["local", str(localip)]
if ttl:
args += ["ttl", str(ttl)]
if key:
args += ["key", str(key)]
utils.check_cmd(args)
args = [constants.IP_BIN, "link", "set", self.localname, "up"]
utils.check_cmd(args)
self.net_client.create_gretap(
self.localname, str(remoteip), str(localip), str(ttl), str(key)
)
self.net_client.device_up(self.localname)
self.up = True
def shutdown(self):
@ -511,10 +474,8 @@ class GreTap(CoreInterface):
"""
if self.localname:
try:
args = [constants.IP_BIN, "link", "set", self.localname, "down"]
utils.check_cmd(args)
args = [constants.IP_BIN, "link", "del", self.localname]
utils.check_cmd(args)
self.net_client.device_down(self.localname)
self.net_client.delete_device(self.localname)
except CoreCommandError:
logging.exception("error during shutdown")

View file

@ -3,8 +3,9 @@ import logging
import os
import time
from core import CoreCommandError, utils
from core import utils
from core.emulator.enumerations import NodeTypes
from core.errors import CoreCommandError
from core.nodes.base import CoreNode
@ -13,7 +14,6 @@ class LxdClient(object):
self.name = name
self.image = image
self.pid = None
self._addr = {}
def create_container(self):
utils.check_cmd(
@ -90,40 +90,6 @@ class LxdClient(object):
if status:
raise CoreCommandError(status, args, output)
def getaddr(self, ifname, rescan=False):
"""
Get address for interface on node.
:param str ifname: interface name to get address for
:param bool rescan: rescan flag
:return: interface information
:rtype: dict
"""
if ifname in self._addr and not rescan:
return self._addr[ifname]
interface = {"ether": [], "inet": [], "inet6": [], "inet6link": []}
args = ["ip", "addr", "show", "dev", ifname]
status, output = self.ns_cmd_output(args)
for line in output:
line = line.strip().split()
if line[0] == "link/ether":
interface["ether"].append(line[1])
elif line[0] == "inet":
interface["inet"].append(line[1])
elif line[0] == "inet6":
if line[3] == "global":
interface["inet6"].append(line[1])
elif line[3] == "link":
interface["inet6link"].append(line[1])
else:
logging.warning("unknown scope: %s" % line[3])
if status:
logging.warning("nonzero exist status (%s) for cmd: %s", status, args)
self._addr[ifname] = interface
return interface
class LxcNode(CoreNode):
apitype = NodeTypes.LXC.value
@ -227,7 +193,7 @@ class LxcNode(CoreNode):
raise CoreCommandError(status, args, output)
return output
def network_cmd(self, args):
def node_net_cmd(self, args):
if not self.up:
logging.debug("node down, not running network command: %s", args)
return 0

View file

@ -0,0 +1,351 @@
"""
Clients for dealing with bridge/interface commands.
"""
import os
from core.constants import BRCTL_BIN, ETHTOOL_BIN, IP_BIN, OVS_BIN, TC_BIN
from core.utils import check_cmd
class LinuxNetClient(object):
"""
Client for creating Linux bridges and ip interfaces for nodes.
"""
def __init__(self, run):
"""
Create LinuxNetClient instance.
:param run: function to run commands with
"""
self.run = run
def set_hostname(self, name):
"""
Set network hostname.
:param str name: name for hostname
:return: nothing
"""
self.run(["hostname", name])
def create_route(self, route, device):
"""
Create a new route for a device.
:param str route: route to create
:param str device: device to add route to
:return: nothing
"""
self.run([IP_BIN, "route", "add", route, "dev", device])
def device_up(self, device):
"""
Bring a device up.
:param str device: device to bring up
:return: nothing
"""
self.run([IP_BIN, "link", "set", device, "up"])
def device_down(self, device):
"""
Bring a device down.
:param str device: device to bring down
:return: nothing
"""
self.run([IP_BIN, "link", "set", device, "down"])
def device_name(self, device, name):
"""
Set a device name.
:param str device: device to set name for
:param str name: name to set
:return: nothing
"""
self.run([IP_BIN, "link", "set", device, "name", name])
def device_show(self, device):
"""
Show information for a device.
:param str device: device to get information for
:return: device information
:rtype: str
"""
return self.run([IP_BIN, "link", "show", device])
def device_ns(self, device, namespace):
"""
Set netns for a device.
:param str device: device to setns for
:param str namespace: namespace to set device to
:return: nothing
"""
self.run([IP_BIN, "link", "set", device, "netns", namespace])
def device_flush(self, device):
"""
Flush device addresses.
:param str device: device to flush
:return: nothing
"""
self.run([IP_BIN, "-6", "address", "flush", "dev", device])
def device_mac(self, device, mac):
"""
Set MAC address for a device.
:param str device: device to set mac for
:param str mac: mac to set
:return: nothing
"""
self.run([IP_BIN, "link", "set", "dev", device, "address", mac])
def delete_device(self, device):
"""
Delete device.
:param str device: device to delete
:return: nothing
"""
self.run([IP_BIN, "link", "delete", device])
def delete_tc(self, device):
"""
Remove traffic control settings for a device.
:param str device: device to remove tc
:return: nothing
"""
self.run([TC_BIN, "qdisc", "del", "dev", device, "root"])
def checksums_off(self, interface_name):
"""
Turns interface checksums off.
:param str interface_name: interface to update
:return: nothing
"""
self.run([ETHTOOL_BIN, "-K", interface_name, "rx", "off", "tx", "off"])
def create_address(self, device, address, broadcast=None):
"""
Create address for a device.
:param str device: device to add address to
:param str address: address to add
:param str broadcast: broadcast address to use, default is None
:return: nothing
"""
if broadcast is not None:
self.run(
[
IP_BIN,
"address",
"add",
address,
"broadcast",
broadcast,
"dev",
device,
]
)
else:
self.run([IP_BIN, "address", "add", address, "dev", device])
def delete_address(self, device, address):
"""
Delete an address from a device.
:param str device: targeted device
:param str address: address to remove
:return: nothing
"""
self.run([IP_BIN, "address", "delete", address, "dev", device])
def create_veth(self, name, peer):
"""
Create a veth pair.
:param str name: veth name
:param str peer: peer name
:return: nothing
"""
self.run(
[IP_BIN, "link", "add", "name", name, "type", "veth", "peer", "name", peer]
)
def create_gretap(self, device, address, local, ttl, key):
"""
Create a GRE tap on a device.
:param str device: device to add tap to
:param str address: address to add tap for
:param str local: local address to tie to
:param str ttl: time to live value
:param str key: key for tap
:return: nothing
"""
cmd = [IP_BIN, "link", "add", device, "type", "gretap", "remote", address]
if local is not None:
cmd.extend(["local", local])
if ttl is not None:
cmd.extend(["ttl", ttl])
if key is not None:
cmd.extend(["key", key])
self.run(cmd)
def create_bridge(self, name):
"""
Create a Linux bridge and bring it up.
:param str name: bridge name
:return: nothing
"""
self.run([BRCTL_BIN, "addbr", name])
self.run([BRCTL_BIN, "stp", name, "off"])
self.run([BRCTL_BIN, "setfd", name, "0"])
self.device_up(name)
# turn off multicast snooping so forwarding occurs w/o IGMP joins
snoop = "/sys/devices/virtual/net/%s/bridge/multicast_snooping" % name
if os.path.exists(snoop):
with open(snoop, "w") as f:
f.write("0")
def delete_bridge(self, name):
"""
Bring down and delete a Linux bridge.
:param str name: bridge name
:return: nothing
"""
self.device_down(name)
self.run([BRCTL_BIN, "delbr", name])
def create_interface(self, bridge_name, interface_name):
"""
Create an interface associated with a Linux bridge.
:param str bridge_name: bridge name
:param str interface_name: interface name
:return: nothing
"""
self.run([BRCTL_BIN, "addif", bridge_name, interface_name])
self.device_up(interface_name)
def delete_interface(self, bridge_name, interface_name):
"""
Delete an interface associated with a Linux bridge.
:param str bridge_name: bridge name
:param str interface_name: interface name
:return: nothing
"""
self.run([BRCTL_BIN, "delif", bridge_name, interface_name])
def existing_bridges(self, _id):
"""
Checks if there are any existing Linux bridges for a node.
:param _id: node id to check bridges for
"""
output = self.run([BRCTL_BIN, "show"])
lines = output.split("\n")
for line in lines[1:]:
columns = line.split()
name = columns[0]
fields = name.split(".")
if len(fields) != 3:
continue
if fields[0] == "b" and fields[1] == _id:
return True
return False
def disable_mac_learning(self, name):
"""
Disable mac learning for a Linux bridge.
:param str name: bridge name
:return: nothing
"""
check_cmd([BRCTL_BIN, "setageing", name, "0"])
class OvsNetClient(LinuxNetClient):
"""
Client for creating OVS bridges and ip interfaces for nodes.
"""
def create_bridge(self, name):
"""
Create a OVS bridge and bring it up.
:param str name: bridge name
:return: nothing
"""
self.run([OVS_BIN, "add-br", name])
self.run([OVS_BIN, "set", "bridge", name, "stp_enable=false"])
self.run([OVS_BIN, "set", "bridge", name, "other_config:stp-max-age=6"])
self.run([OVS_BIN, "set", "bridge", name, "other_config:stp-forward-delay=4"])
self.device_up(name)
def delete_bridge(self, name):
"""
Bring down and delete a OVS bridge.
:param str name: bridge name
:return: nothing
"""
self.device_down(name)
self.run([OVS_BIN, "del-br", name])
def create_interface(self, bridge_name, interface_name):
"""
Create an interface associated with a network bridge.
:param str bridge_name: bridge name
:param str interface_name: interface name
:return: nothing
"""
self.run([OVS_BIN, "add-port", bridge_name, interface_name])
self.device_up(interface_name)
def delete_interface(self, bridge_name, interface_name):
"""
Delete an interface associated with a OVS bridge.
:param str bridge_name: bridge name
:param str interface_name: interface name
:return: nothing
"""
self.run([OVS_BIN, "del-port", bridge_name, interface_name])
def existing_bridges(self, _id):
"""
Checks if there are any existing OVS bridges for a node.
:param _id: node id to check bridges for
"""
output = self.run([OVS_BIN, "list-br"])
if output:
for line in output.split("\n"):
fields = line.split(".")
if fields[0] == "b" and fields[1] == _id:
return True
return False
def disable_mac_learning(self, name):
"""
Disable mac learning for a OVS bridge.
:param str name: bridge name
:return: nothing
"""
self.run([OVS_BIN, "set", "bridge", name, "other_config:mac-aging-time=0"])

View file

@ -9,17 +9,14 @@ import threading
import time
from socket import AF_INET, AF_INET6
from core import CoreCommandError, constants, utils
from core import constants, utils
from core.emulator.data import LinkData
from core.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs
from core.errors import CoreCommandError, CoreError
from core.nodes import ipaddress
from core.nodes.base import CoreNetworkBase
from core.nodes.interface import GreTap, Veth
utils.check_executables(
[constants.BRCTL_BIN, constants.IP_BIN, constants.EBTABLES_BIN, constants.TC_BIN]
)
ebtables_lock = threading.Lock()
@ -314,12 +311,8 @@ class CoreNetwork(CoreNetworkBase):
:return: nothing
:raises CoreCommandError: when there is a command exception
"""
utils.check_cmd([constants.BRCTL_BIN, "addbr", self.brname])
self.net_client.create_bridge(self.brname)
# turn off spanning tree protocol and forwarding delay
utils.check_cmd([constants.BRCTL_BIN, "stp", self.brname, "off"])
utils.check_cmd([constants.BRCTL_BIN, "setfd", self.brname, "0"])
utils.check_cmd([constants.IP_BIN, "link", "set", self.brname, "up"])
# create a new ebtables chain for this bridge
ebtablescmds(
utils.check_cmd,
@ -336,11 +329,6 @@ class CoreNetwork(CoreNetworkBase):
],
],
)
# turn off multicast snooping so mcast forwarding occurs w/o IGMP joins
snoop = "/sys/devices/virtual/net/%s/bridge/multicast_snooping" % self.brname
if os.path.exists(snoop):
with open(snoop, "w") as snoop_file:
snoop_file.write("0")
self.up = True
@ -356,8 +344,7 @@ class CoreNetwork(CoreNetworkBase):
ebq.stopupdateloop(self)
try:
utils.check_cmd([constants.IP_BIN, "link", "set", self.brname, "down"])
utils.check_cmd([constants.BRCTL_BIN, "delbr", self.brname])
self.net_client.delete_bridge(self.brname)
ebtablescmds(
utils.check_cmd,
[
@ -385,7 +372,8 @@ class CoreNetwork(CoreNetworkBase):
del self.session
self.up = False
# TODO: this depends on a subtype with localname defined, seems like the wrong place for this to live
# TODO: this depends on a subtype with localname defined, seems like the
# wrong place for this to live
def attach(self, netif):
"""
Attach a network interface.
@ -394,10 +382,7 @@ class CoreNetwork(CoreNetworkBase):
:return: nothing
"""
if self.up:
utils.check_cmd(
[constants.BRCTL_BIN, "addif", self.brname, netif.localname]
)
utils.check_cmd([constants.IP_BIN, "link", "set", netif.localname, "up"])
self.net_client.create_interface(self.brname, netif.localname)
CoreNetworkBase.attach(self, netif)
@ -409,9 +394,7 @@ class CoreNetwork(CoreNetworkBase):
:return: nothing
"""
if self.up:
utils.check_cmd(
[constants.BRCTL_BIN, "delif", self.brname, netif.localname]
)
self.net_client.delete_interface(self.brname, netif.localname)
CoreNetworkBase.detach(self, netif)
@ -610,10 +593,8 @@ class CoreNetwork(CoreNetworkBase):
)
self.attach(netif)
if net.up:
# this is similar to net.attach() but uses netif.name instead
# of localname
utils.check_cmd([constants.BRCTL_BIN, "addif", net.brname, netif.name])
utils.check_cmd([constants.IP_BIN, "link", "set", netif.name, "up"])
# this is similar to net.attach() but uses netif.name instead of localname
self.net_client.create_interface(net.brname, netif.name)
i = net.newifindex()
net._netif[i] = netif
with net._linked_lock:
@ -648,9 +629,7 @@ class CoreNetwork(CoreNetworkBase):
return
for addr in addrlist:
utils.check_cmd(
[constants.IP_BIN, "addr", "add", str(addr), "dev", self.brname]
)
self.net_client.create_address(self.brname, str(addr))
class GreTapBridge(CoreNetwork):
@ -822,8 +801,8 @@ class CtrlNet(CoreNetwork):
:return: nothing
:raises CoreCommandError: when there is a command exception
"""
if self.detectoldbridge():
return
if self.net_client.existing_bridges(self.id):
raise CoreError("old bridges exist for node: %s" % self.id)
CoreNetwork.startup(self)
@ -848,42 +827,7 @@ class CtrlNet(CoreNetwork):
utils.check_cmd([self.updown_script, self.brname, "startup"])
if self.serverintf:
# sets the interface as a port of the bridge
utils.check_cmd(
[constants.BRCTL_BIN, "addif", self.brname, self.serverintf]
)
# bring interface up
utils.check_cmd([constants.IP_BIN, "link", "set", self.serverintf, "up"])
def detectoldbridge(self):
"""
Occasionally, control net bridges from previously closed sessions are not cleaned up.
Check if there are old control net bridges and delete them
:return: True if an old bridge was detected, False otherwise
:rtype: bool
"""
status, output = utils.cmd_output([constants.BRCTL_BIN, "show"])
if status != 0:
logging.error("Unable to retrieve list of installed bridges")
else:
lines = output.split("\n")
for line in lines[1:]:
cols = line.split("\t")
oldbr = cols[0]
flds = cols[0].split(".")
if len(flds) == 3:
if flds[0] == "b" and flds[1] == self.id:
logging.error(
"error: An active control net bridge (%s) found. "
"An older session might still be running. "
"Stop all sessions and, if needed, delete %s to continue.",
oldbr,
oldbr,
)
return True
return False
self.net_client.create_interface(self.brname, self.serverintf)
def shutdown(self):
"""
@ -893,9 +837,7 @@ class CtrlNet(CoreNetwork):
"""
if self.serverintf is not None:
try:
utils.check_cmd(
[constants.BRCTL_BIN, "delif", self.brname, self.serverintf]
)
self.net_client.delete_interface(self.brname, self.serverintf)
except CoreCommandError:
logging.exception(
"error deleting server interface %s from bridge %s",
@ -1100,7 +1042,7 @@ class HubNode(CoreNetwork):
# TODO: move to startup method
if start:
utils.check_cmd([constants.BRCTL_BIN, "setageing", self.brname, "0"])
self.net_client.disable_mac_learning(self.brname)
class WlanNode(CoreNetwork):
@ -1131,7 +1073,7 @@ class WlanNode(CoreNetwork):
# TODO: move to startup method
if start:
utils.check_cmd([constants.BRCTL_BIN, "setageing", self.brname, "0"])
self.net_client.disable_mac_learning(self.brname)
def attach(self, netif):
"""

View file

@ -1,32 +0,0 @@
"""
Provides default node maps that can be used to run core with.
"""
import core.nodes.base
import core.nodes.docker
import core.nodes.lxd
import core.nodes.network
import core.nodes.physical
from core.emane.nodes import EmaneNet, EmaneNode
from core.emulator.enumerations import NodeTypes
from core.nodes import physical
from core.nodes.network import GreTapBridge
# legacy core nodes, that leverage linux bridges
NODES = {
NodeTypes.DEFAULT: core.nodes.base.CoreNode,
NodeTypes.PHYSICAL: physical.PhysicalNode,
NodeTypes.TBD: None,
NodeTypes.SWITCH: core.nodes.network.SwitchNode,
NodeTypes.HUB: core.nodes.network.HubNode,
NodeTypes.WIRELESS_LAN: core.nodes.network.WlanNode,
NodeTypes.RJ45: core.nodes.physical.Rj45Node,
NodeTypes.TUNNEL: core.nodes.network.TunnelNode,
NodeTypes.KTUNNEL: None,
NodeTypes.EMANE: EmaneNode,
NodeTypes.EMANE_NET: EmaneNet,
NodeTypes.TAP_BRIDGE: GreTapBridge,
NodeTypes.PEER_TO_PEER: core.nodes.network.PtpNet,
NodeTypes.CONTROL_NET: core.nodes.network.CtrlNet,
NodeTypes.DOCKER: core.nodes.docker.DockerNode,
NodeTypes.LXC: core.nodes.lxd.LxcNode,
}

View file

@ -1,97 +0,0 @@
"""
Serves as a global point for storing and retrieving node types needed during simulation.
"""
import logging
_NODE_MAP = None
def _log_map():
global _NODE_MAP
for key in _NODE_MAP:
value = _NODE_MAP[key]
name = None
if value:
name = value.__name__
logging.debug("node type (%s) - class (%s)", key.name, name)
def _convert_map(x, y):
"""
Convenience method to create a human readable version of the node map to log.
:param dict x: dictionary to reduce node items into
:param tuple y: current node item
:return: human readable name mapping of the node map
"""
x[y[0].name] = y[1]
return x
def update_node_map(node_map):
"""
Update the current node map with the provided node map values.
:param dict node_map: node map to update with
"""
global _NODE_MAP
_NODE_MAP.update(node_map)
_log_map()
def set_node_map(node_map):
"""
Set the global node map that proides a consistent way to retrieve differently configured nodes.
:param dict node_map: node map to set to
:return: nothing
"""
global _NODE_MAP
_NODE_MAP = node_map
_log_map()
def get_node_class(node_type):
"""
Retrieve the node class for a given node type.
:param int node_type: node type to retrieve class for
:return: node class
"""
global _NODE_MAP
return _NODE_MAP[node_type]
def get_node_type(node_class):
"""
Retrieve the node type given a node class.
:param class node_class: node class to get type for
:return: node type
:rtype: core.emulator.enumerations.NodeTypes
"""
global _NODE_MAP
node_type_map = {_NODE_MAP[x]: x for x in _NODE_MAP}
return node_type_map.get(node_class)
def is_node(obj, node_types):
"""
Validates if an object is one of the provided node types.
:param obj: object to check type for
:param int|tuple|list node_types: node type(s) to check against
:return: True if the object is one of the node types, False otherwise
:rtype: bool
"""
type_classes = []
if isinstance(node_types, (tuple, list)):
for node_type in node_types:
type_class = get_node_class(node_type)
type_classes.append(type_class)
else:
type_class = get_node_class(node_types)
type_classes.append(type_class)
return isinstance(obj, tuple(type_classes))

View file

@ -1,825 +0,0 @@
"""
TODO: probably goes away, or implement the usage of "unshare", or docker formal.
"""
import logging
import socket
import threading
from socket import AF_INET, AF_INET6
from core import CoreCommandError, constants, utils
from core.emulator.data import LinkData
from core.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs
from core.nodes import ipaddress
from core.nodes.base import CoreNetworkBase
from core.nodes.interface import GreTap, Veth
from core.nodes.network import EbtablesQueue, GreTapBridge
# a global object because all WLANs share the same queue
# cannot have multiple threads invoking the ebtables commnd
ebtables_queue = EbtablesQueue()
ebtables_lock = threading.Lock()
utils.check_executables([constants.IP_BIN, constants.EBTABLES_BIN, constants.TC_BIN])
def ebtables_commands(call, commands):
with ebtables_lock:
for command in commands:
call(command)
class OvsNet(CoreNetworkBase):
"""
Used to be LxBrNet.
Base class for providing Openvswitch functionality to objects that create bridges.
"""
policy = "DROP"
def __init__(self, session, _id=None, name=None, start=True, policy=None):
"""
Creates an OvsNet instance.
:param core.emulator.session.Session session: session this object is a part of
:param int _id: object id
:param str name: object name
:param bool start: start flag
:param policy: network policy
"""
CoreNetworkBase.__init__(self, session, _id, name, start)
if policy:
self.policy = policy
else:
self.policy = self.__class__.policy
session_id = self.session.short_session_id()
self.bridge_name = "b.%s.%s" % (str(self.id), session_id)
self.up = False
if start:
self.startup()
ebtables_queue.startupdateloop(self)
def startup(self):
"""
:return:
:raises CoreCommandError: when there is a command exception
"""
utils.check_cmd([constants.OVS_BIN, "add-br", self.bridge_name])
# turn off spanning tree protocol and forwarding delay
# TODO: appears stp and rstp are off by default, make sure this always holds true
# TODO: apears ovs only supports rstp forward delay and again it's off by default
utils.check_cmd([constants.IP_BIN, "link", "set", self.bridge_name, "up"])
# create a new ebtables chain for this bridge
ebtables_commands(
utils.check_cmd,
[
[constants.EBTABLES_BIN, "-N", self.bridge_name, "-P", self.policy],
[
constants.EBTABLES_BIN,
"-A",
"FORWARD",
"--logical-in",
self.bridge_name,
"-j",
self.bridge_name,
],
],
)
self.up = True
def shutdown(self):
if not self.up:
logging.info("exiting shutdown, object is not up")
return
ebtables_queue.stopupdateloop(self)
try:
utils.check_cmd([constants.IP_BIN, "link", "set", self.bridge_name, "down"])
utils.check_cmd([constants.OVS_BIN, "del-br", self.bridge_name])
ebtables_commands(
utils.check_cmd,
[
[
constants.EBTABLES_BIN,
"-D",
"FORWARD",
"--logical-in",
self.bridge_name,
"-j",
self.bridge_name,
],
[constants.EBTABLES_BIN, "-X", self.bridge_name],
],
)
except CoreCommandError:
logging.exception("error bringing bridge down and removing it")
# removes veth pairs used for bridge-to-bridge connections
for interface in self.netifs():
interface.shutdown()
self._netif.clear()
self._linked.clear()
del self.session
self.up = False
def attach(self, interface):
if self.up:
utils.check_cmd(
[constants.OVS_BIN, "add-port", self.bridge_name, interface.localname]
)
utils.check_cmd(
[constants.IP_BIN, "link", "set", interface.localname, "up"]
)
CoreNetworkBase.attach(self, interface)
def detach(self, interface):
if self.up:
utils.check_cmd(
[constants.OVS_BIN, "del-port", self.bridge_name, interface.localname]
)
CoreNetworkBase.detach(self, interface)
def linked(self, interface_one, interface_two):
# check if the network interfaces are attached to this network
if self._netif[interface_one.netifi] != interface_one:
raise ValueError("inconsistency for interface %s" % interface_one.name)
if self._netif[interface_two.netifi] != interface_two:
raise ValueError("inconsistency for interface %s" % interface_two.name)
try:
linked = self._linked[interface_one][interface_two]
except KeyError:
if self.policy == "ACCEPT":
linked = True
elif self.policy == "DROP":
linked = False
else:
raise ValueError("unknown policy: %s" % self.policy)
self._linked[interface_one][interface_two] = linked
return linked
def unlink(self, interface_one, interface_two):
"""
Unlink two PyCoreNetIfs, resulting in adding or removing ebtables
filtering rules.
"""
with self._linked_lock:
if not self.linked(interface_one, interface_two):
return
self._linked[interface_one][interface_two] = False
ebtables_queue.ebchange(self)
def link(self, interface_one, interface_two):
"""
Link two interfaces together, resulting in adding or removing
ebtables filtering rules.
"""
with self._linked_lock:
if self.linked(interface_one, interface_two):
return
self._linked[interface_one][interface_two] = True
ebtables_queue.ebchange(self)
def linkconfig(
self,
netif,
bw=None,
delay=None,
loss=None,
duplicate=None,
jitter=None,
netif2=None,
devname=None,
):
"""
Configure link parameters by applying tc queuing disciplines on the
interface.
"""
if not devname:
devname = netif.localname
tc = [constants.TC_BIN, "qdisc", "replace", "dev", devname]
parent = ["root"]
# attempt to set bandwidth and update as needed if value changed
bandwidth_changed = netif.setparam("bw", bw)
if bandwidth_changed:
# from tc-tbf(8): minimum value for burst is rate / kernel_hz
if bw > 0:
if self.up:
burst = max(2 * netif.mtu, bw / 1000)
limit = 0xFFFF # max IP payload
tbf = [
"tbf",
"rate",
str(bw),
"burst",
str(burst),
"limit",
str(limit),
]
logging.info(
"linkconfig: %s" % [tc + parent + ["handle", "1:"] + tbf]
)
utils.check_cmd(tc + parent + ["handle", "1:"] + tbf)
netif.setparam("has_tbf", True)
elif netif.getparam("has_tbf") and bw <= 0:
tcd = [] + tc
tcd[2] = "delete"
if self.up:
utils.check_cmd(tcd + parent)
netif.setparam("has_tbf", False)
# removing the parent removes the child
netif.setparam("has_netem", False)
if netif.getparam("has_tbf"):
parent = ["parent", "1:1"]
netem = ["netem"]
delay_changed = netif.setparam("delay", delay)
if loss is not None:
loss = float(loss)
loss_changed = netif.setparam("loss", loss)
if duplicate is not None:
duplicate = int(duplicate)
duplicate_changed = netif.setparam("duplicate", duplicate)
jitter_changed = netif.setparam("jitter", jitter)
# if nothing changed return
if not any(
[
bandwidth_changed,
delay_changed,
loss_changed,
duplicate_changed,
jitter_changed,
]
):
return
# jitter and delay use the same delay statement
if delay is not None:
netem += ["delay", "%sus" % delay]
else:
netem += ["delay", "0us"]
if jitter is not None:
netem += ["%sus" % jitter, "25%"]
if loss is not None and loss > 0:
netem += ["loss", "%s%%" % min(loss, 100)]
if duplicate is not None and duplicate > 0:
netem += ["duplicate", "%s%%" % min(duplicate, 100)]
if delay <= 0 and jitter <= 0 and loss <= 0 and duplicate <= 0:
# possibly remove netem if it exists and parent queue wasn"t removed
if not netif.getparam("has_netem"):
return
tc[2] = "delete"
if self.up:
logging.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"]],))
utils.check_cmd(tc + parent + ["handle", "10:"])
netif.setparam("has_netem", False)
elif len(netem) > 1:
if self.up:
logging.info(
"linkconfig: %s" % ([tc + parent + ["handle", "10:"] + netem],)
)
utils.check_cmd(tc + parent + ["handle", "10:"] + netem)
netif.setparam("has_netem", True)
def linknet(self, network):
"""
Link this bridge with another by creating a veth pair and installing
each device into each bridge.
"""
session_id = self.session.short_session_id()
try:
_id = "%x" % self.id
except TypeError:
_id = "%s" % self.id
try:
network_id = "%x" % network.id
except TypeError:
network_id = "%s" % network.id
localname = "veth%s.%s.%s" % (_id, network_id, session_id)
if len(localname) >= 16:
raise ValueError("interface local name %s too long" % localname)
name = "veth%s.%s.%s" % (network_id, _id, session_id)
if len(name) >= 16:
raise ValueError("interface name %s too long" % name)
interface = Veth(
node=None, name=name, localname=localname, mtu=1500, net=self, start=self.up
)
self.attach(interface)
if network.up:
# this is similar to net.attach() but uses netif.name instead of localname
utils.check_cmd(
[constants.OVS_BIN, "add-port", network.bridge_name, interface.name]
)
utils.check_cmd([constants.IP_BIN, "link", "set", interface.name, "up"])
network.attach(interface)
interface.net = self
interface.othernet = network
return interface
def getlinknetif(self, network):
"""
Return the interface of that links this net with another net
(that were linked using linknet()).
"""
for interface in self.netifs():
if hasattr(interface, "othernet") and interface.othernet == network:
return interface
return None
def addrconfig(self, addresses):
"""
Set addresses on the bridge.
"""
if not self.up:
return
for address in addresses:
utils.check_cmd(
[constants.IP_BIN, "addr", "add", str(address), "dev", self.bridge_name]
)
class OvsCtrlNet(OvsNet):
policy = "ACCEPT"
CTRLIF_IDX_BASE = 99 # base control interface index
DEFAULT_PREFIX_LIST = [
"172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24",
"172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24",
"172.18.0.0/24 172.18.1.0/24 172.18.2.0/24 172.18.3.0/24 172.18.4.0/24",
"172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24",
]
def __init__(
self,
session,
_id="ctrlnet",
name=None,
prefix=None,
hostid=None,
start=True,
assign_address=True,
updown_script=None,
serverintf=None,
):
self.prefix = ipaddress.Ipv4Prefix(prefix)
self.hostid = hostid
self.assign_address = assign_address
self.updown_script = updown_script
self.serverintf = serverintf
OvsNet.__init__(self, session, _id=_id, name=name, start=start)
def startup(self):
if self.detectoldbridge():
return
OvsNet.startup(self)
if self.hostid:
addr = self.prefix.addr(self.hostid)
else:
addr = self.prefix.max_addr()
message = "Added control network bridge: %s %s" % (
self.bridge_name,
self.prefix,
)
addresses = ["%s/%s" % (addr, self.prefix.prefixlen)]
if self.assign_address:
self.addrconfig(addresses=addresses)
message += " address %s" % addr
logging.info(message)
if self.updown_script:
logging.info(
"interface %s updown script %s startup called"
% (self.bridge_name, self.updown_script)
)
utils.check_cmd([self.updown_script, self.bridge_name, "startup"])
if self.serverintf:
utils.check_cmd(
[constants.OVS_BIN, "add-port", self.bridge_name, self.serverintf]
)
utils.check_cmd([constants.IP_BIN, "link", "set", self.serverintf, "up"])
def detectoldbridge(self):
"""
Occasionally, control net bridges from previously closed sessions are not cleaned up.
Check if there are old control net bridges and delete them
"""
output = utils.check_cmd([constants.OVS_BIN, "list-br"])
output = output.strip()
if output:
for line in output.split("\n"):
bride_name = line.split(".")
if bride_name[0] == "b" and bride_name[1] == self.id:
logging.error(
"older session may still be running with conflicting id for bridge: %s",
line,
)
return True
return False
def shutdown(self):
if self.serverintf:
try:
utils.check_cmd(
[constants.OVS_BIN, "del-port", self.bridge_name, self.serverintf]
)
except CoreCommandError:
logging.exception(
"error deleting server interface %s to controlnet bridge %s",
self.serverintf,
self.bridge_name,
)
if self.updown_script:
try:
logging.info(
"interface %s updown script (%s shutdown) called",
self.bridge_name,
self.updown_script,
)
utils.check_cmd([self.updown_script, self.bridge_name, "shutdown"])
except CoreCommandError:
logging.exception("error during updown script shutdown")
OvsNet.shutdown(self)
def all_link_data(self, flags):
"""
Do not include CtrlNet in link messages describing this session.
"""
return []
class OvsPtpNet(OvsNet):
policy = "ACCEPT"
def attach(self, interface):
if len(self._netif) >= 2:
raise ValueError(
"point-to-point links support at most 2 network interfaces"
)
OvsNet.attach(self, interface)
def data(self, message_type, lat=None, lon=None, alt=None):
"""
Do not generate a Node Message for point-to-point links. They are
built using a link message instead.
"""
return None
def all_link_data(self, flags):
"""
Build CORE API TLVs for a point-to-point link. One Link message describes this network.
"""
all_links = []
if len(self._netif) != 2:
return all_links
if1, if2 = self._netif.values()
unidirectional = 0
if if1.getparams() != if2.getparams():
unidirectional = 1
interface1_ip4 = None
interface1_ip4_mask = None
interface1_ip6 = None
interface1_ip6_mask = None
for address in if1.addrlist:
ip, _sep, mask = address.partition("/")
mask = int(mask)
if ipaddress.is_ipv4_address(ip):
family = AF_INET
ipl = socket.inet_pton(family, ip)
interface1_ip4 = ipaddress.IpAddress(af=family, address=ipl)
interface1_ip4_mask = mask
else:
family = AF_INET6
ipl = socket.inet_pton(family, ip)
interface1_ip6 = ipaddress.IpAddress(af=family, address=ipl)
interface1_ip6_mask = mask
interface2_ip4 = None
interface2_ip4_mask = None
interface2_ip6 = None
interface2_ip6_mask = None
for address in if2.addrlist:
ip, _sep, mask = address.partition("/")
mask = int(mask)
if ipaddress.is_ipv4_address(ip):
family = AF_INET
ipl = socket.inet_pton(family, ip)
interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl)
interface2_ip4_mask = mask
else:
family = AF_INET6
ipl = socket.inet_pton(family, ip)
interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl)
interface2_ip6_mask = mask
# TODO: not currently used
# loss=netif.getparam("loss")
link_data = LinkData(
message_type=flags,
node1_id=if1.node.id,
node2_id=if2.node.id,
link_type=self.linktype,
unidirectional=unidirectional,
delay=if1.getparam("delay"),
bandwidth=if1.getparam("bw"),
dup=if1.getparam("duplicate"),
jitter=if1.getparam("jitter"),
interface1_id=if1.node.getifindex(if1),
interface1_mac=if1.hwaddr,
interface1_ip4=interface1_ip4,
interface1_ip4_mask=interface1_ip4_mask,
interface1_ip6=interface1_ip6,
interface1_ip6_mask=interface1_ip6_mask,
interface2_id=if2.node.getifindex(if2),
interface2_mac=if2.hwaddr,
interface2_ip4=interface2_ip4,
interface2_ip4_mask=interface2_ip4_mask,
interface2_ip6=interface2_ip6,
interface2_ip6_mask=interface2_ip6_mask,
)
all_links.append(link_data)
# build a 2nd link message for the upstream link parameters
# (swap if1 and if2)
if unidirectional:
link_data = LinkData(
message_type=0,
node1_id=if2.node.id,
node2_id=if1.node.id,
delay=if1.getparam("delay"),
bandwidth=if1.getparam("bw"),
dup=if1.getparam("duplicate"),
jitter=if1.getparam("jitter"),
unidirectional=1,
interface1_id=if2.node.getifindex(if2),
interface2_id=if1.node.getifindex(if1),
)
all_links.append(link_data)
return all_links
class OvsSwitchNode(OvsNet):
apitype = NodeTypes.SWITCH.value
policy = "ACCEPT"
type = "lanswitch"
class OvsHubNode(OvsNet):
apitype = NodeTypes.HUB.value
policy = "ACCEPT"
type = "hub"
def __init__(self, session, _id=None, name=None, start=True):
"""
the Hub node forwards packets to all bridge ports by turning off
the MAC address learning
"""
OvsNet.__init__(self, session, _id, name, start)
if start:
# TODO: verify that the below flow accomplishes what is desired for a "HUB"
# TODO: replace "brctl setageing 0"
utils.check_cmd(
[constants.OVS_FLOW_BIN, "add-flow", self.bridge_name, "action=flood"]
)
class OvsWlanNode(OvsNet):
apitype = NodeTypes.WIRELESS_LAN.value
linktype = LinkTypes.WIRELESS.value
policy = "DROP"
type = "wlan"
def __init__(self, session, _id=None, name=None, start=True, policy=None):
OvsNet.__init__(self, session, _id, name, start, policy)
# wireless model such as basic range
self.model = None
# mobility model such as scripted
self.mobility = None
def attach(self, interface):
OvsNet.attach(self, interface)
if self.model:
interface.poshook = self.model.position_callback
if interface.node is None:
return
x, y, z = interface.node.position.get()
# invokes any netif.poshook
interface.setposition(x, y, z)
# self.model.setlinkparams()
def setmodel(self, model, config=None):
"""
Mobility and wireless model.
"""
logging.info("adding model %s", model.name)
if model.type == RegisterTlvs.WIRELESS.value:
self.model = model(session=self.session, _id=self.id, config=config)
if self.model.position_callback:
for interface in self.netifs():
interface.poshook = self.model.position_callback
if interface.node is not None:
x, y, z = interface.node.position.get()
interface.poshook(interface, x, y, z)
self.model.setlinkparams()
elif model.type == RegisterTlvs.MOBILITY.value:
self.mobility = model(session=self.session, _id=self.id, config=config)
def updatemodel(self, config):
if not self.model:
raise ValueError("no model set to update for node(%s)", self.id)
logging.info(
"node(%s) updating model(%s): %s", self.id, self.model.name, config
)
self.model.set_configs(config, node_id=self.id)
if self.model.position_callback:
for netif in self.netifs():
netif.poshook = self.model.position_callback
if netif.node is not None:
x, y, z = netif.node.position.get()
netif.poshook(netif, x, y, z)
self.model.updateconfig()
def all_link_data(self, flags):
all_links = OvsNet.all_link_data(self, flags)
if self.model:
all_links.extend(self.model.all_link_data(flags))
return all_links
class OvsTunnelNode(GreTapBridge):
apitype = NodeTypes.TUNNEL.value
policy = "ACCEPT"
type = "tunnel"
class OvsGreTapBridge(OvsNet):
"""
A network consisting of a bridge with a gretap device for tunneling to
another system.
"""
def __init__(
self,
session,
remoteip=None,
_id=None,
name=None,
policy="ACCEPT",
localip=None,
ttl=255,
key=None,
start=True,
):
OvsNet.__init__(
self, session=session, _id=_id, name=name, policy=policy, start=False
)
self.grekey = key
if self.grekey is None:
self.grekey = self.session.id ^ self.id
self.localnum = None
self.remotenum = None
self.remoteip = remoteip
self.localip = localip
self.ttl = ttl
if remoteip is None:
self.gretap = None
else:
self.gretap = GreTap(
node=self,
session=session,
remoteip=remoteip,
localip=localip,
ttl=ttl,
key=self.grekey,
)
if start:
self.startup()
def startup(self):
"""
Creates a bridge and adds the gretap device to it.
"""
OvsNet.startup(self)
if self.gretap:
self.attach(self.gretap)
def shutdown(self):
"""
Detach the gretap device and remove the bridge.
"""
if self.gretap:
self.detach(self.gretap)
self.gretap.shutdown()
self.gretap = None
OvsNet.shutdown(self)
def addrconfig(self, addresses):
"""
Set the remote tunnel endpoint. This is a one-time method for
creating the GreTap device, which requires the remoteip at startup.
The 1st address in the provided list is remoteip, 2nd optionally
specifies localip.
"""
if self.gretap:
raise ValueError("gretap already exists for %s" % self.name)
remoteip = addresses[0].split("/")[0]
localip = None
if len(addresses) > 1:
localip = addresses[1].split("/")[0]
self.gretap = GreTap(
session=self.session,
remoteip=remoteip,
localip=localip,
ttl=self.ttl,
key=self.grekey,
)
self.attach(self.gretap)
def setkey(self, key):
"""
Set the GRE key used for the GreTap device. This needs to be set
prior to instantiating the GreTap device (before addrconfig).
"""
self.grekey = key
OVS_NODES = {
NodeTypes.SWITCH: OvsSwitchNode,
NodeTypes.HUB: OvsHubNode,
NodeTypes.WIRELESS_LAN: OvsWlanNode,
NodeTypes.TUNNEL: OvsTunnelNode,
NodeTypes.TAP_BRIDGE: OvsGreTapBridge,
NodeTypes.PEER_TO_PEER: OvsPtpNet,
NodeTypes.CONTROL_NET: OvsCtrlNet,
}

View file

@ -7,8 +7,9 @@ import os
import subprocess
import threading
from core import CoreCommandError, constants, utils
from core import constants, utils
from core.emulator.enumerations import NodeTypes
from core.errors import CoreCommandError
from core.nodes.base import CoreNodeBase
from core.nodes.interface import CoreInterface
from core.nodes.network import CoreNetwork, GreTap
@ -99,51 +100,33 @@ class PhysicalNode(CoreNodeBase):
"""
Set hardware address for an interface.
"""
self._netif[ifindex].sethwaddr(addr)
ifname = self.ifname(ifindex)
interface = self._netif[ifindex]
interface.sethwaddr(addr)
if self.up:
self.check_cmd(
[constants.IP_BIN, "link", "set", "dev", ifname, "address", str(addr)]
)
self.net_client.device_mac(interface.name, str(addr))
def addaddr(self, ifindex, addr):
"""
Add an address to an interface.
"""
interface = self._netif[ifindex]
if self.up:
self.check_cmd(
[
constants.IP_BIN,
"addr",
"add",
str(addr),
"dev",
self.ifname(ifindex),
]
)
self._netif[ifindex].addaddr(addr)
self.net_client.create_address(interface.name, str(addr))
interface.addaddr(addr)
def deladdr(self, ifindex, addr):
"""
Delete an address from an interface.
"""
interface = self._netif[ifindex]
try:
self._netif[ifindex].deladdr(addr)
interface.deladdr(addr)
except ValueError:
logging.exception("trying to delete unknown address: %s", addr)
if self.up:
self.check_cmd(
[
constants.IP_BIN,
"addr",
"del",
str(addr),
"dev",
self.ifname(ifindex),
]
)
self.net_client.delete_address(interface.name, str(addr))
def adoptnetif(self, netif, ifindex, hwaddr, addrlist):
"""
@ -158,12 +141,8 @@ class PhysicalNode(CoreNodeBase):
# use a more reasonable name, e.g. "gt0" instead of "gt.56286.150"
if self.up:
self.check_cmd(
[constants.IP_BIN, "link", "set", "dev", netif.localname, "down"]
)
self.check_cmd(
[constants.IP_BIN, "link", "set", netif.localname, "name", netif.name]
)
self.net_client.device_down(netif.localname)
self.net_client.device_name(netif.localname, netif.name)
netif.localname = netif.name
@ -174,9 +153,7 @@ class PhysicalNode(CoreNodeBase):
self.addaddr(ifindex, addr)
if self.up:
self.check_cmd(
[constants.IP_BIN, "link", "set", "dev", netif.localname, "up"]
)
self.net_client.device_up(netif.localname)
def linkconfig(
self,
@ -334,7 +311,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
"""
# interface will also be marked up during net.attach()
self.savestate()
utils.check_cmd([constants.IP_BIN, "link", "set", self.localname, "up"])
self.net_client.device_up(self.localname)
self.up = True
def shutdown(self):
@ -348,18 +325,17 @@ class Rj45Node(CoreNodeBase, CoreInterface):
return
try:
utils.check_cmd([constants.IP_BIN, "link", "set", self.localname, "down"])
utils.check_cmd([constants.IP_BIN, "addr", "flush", "dev", self.localname])
utils.check_cmd(
[constants.TC_BIN, "qdisc", "del", "dev", self.localname, "root"]
)
self.net_client.device_down(self.localname)
self.net_client.device_flush(self.localname)
self.net_client.delete_tc(self.localname)
except CoreCommandError:
logging.exception("error shutting down")
self.up = False
self.restorestate()
# TODO: issue in that both classes inherited from provide the same method with different signatures
# TODO: issue in that both classes inherited from provide the same method with
# different signatures
def attachnet(self, net):
"""
Attach a network.
@ -369,7 +345,8 @@ class Rj45Node(CoreNodeBase, CoreInterface):
"""
CoreInterface.attachnet(self, net)
# TODO: issue in that both classes inherited from provide the same method with different signatures
# TODO: issue in that both classes inherited from provide the same method with
# different signatures
def detachnet(self):
"""
Detach a network.
@ -475,9 +452,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
:raises CoreCommandError: when there is a command exception
"""
if self.up:
utils.check_cmd(
[constants.IP_BIN, "addr", "add", str(addr), "dev", self.name]
)
self.net_client.create_address(self.name, str(addr))
CoreInterface.addaddr(self, addr)
@ -490,9 +465,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
:raises CoreCommandError: when there is a command exception
"""
if self.up:
utils.check_cmd(
[constants.IP_BIN, "addr", "del", str(addr), "dev", self.name]
)
self.net_client.delete_address(self.name, str(addr))
CoreInterface.deladdr(self, addr)
@ -506,8 +479,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
"""
self.old_up = False
self.old_addrs = []
args = [constants.IP_BIN, "addr", "show", "dev", self.localname]
output = utils.check_cmd(args)
output = self.net_client.device_show(self.localname)
for line in output.split("\n"):
items = line.split()
if len(items) < 2:
@ -533,25 +505,14 @@ class Rj45Node(CoreNodeBase, CoreInterface):
"""
for addr in self.old_addrs:
if addr[1] is None:
utils.check_cmd(
[constants.IP_BIN, "addr", "add", addr[0], "dev", self.localname]
)
self.net_client.create_address(self.localname, addr[0])
else:
utils.check_cmd(
[
constants.IP_BIN,
"addr",
"add",
addr[0],
"brd",
addr[1],
"dev",
self.localname,
]
self.net_client.create_address(
self.localname, addr[0], broadcast=addr[1]
)
if self.old_up:
utils.check_cmd([constants.IP_BIN, "link", "set", self.localname, "up"])
self.net_client.device_up(self.localname)
def setposition(self, x=None, y=None, z=None):
"""

View file

@ -7,7 +7,8 @@ import socket
from future.moves.urllib.parse import urlparse
from core import CoreError, constants
from core import constants
from core.emane.nodes import EmaneNet
from core.emulator.enumerations import (
EventTypes,
LinkTlvs,
@ -17,8 +18,9 @@ from core.emulator.enumerations import (
NodeTlvs,
NodeTypes,
)
from core.nodes import nodeutils
from core.errors import CoreError
from core.nodes.base import CoreNetworkBase, NodeBase
from core.nodes.network import WlanNode
# TODO: A named tuple may be more appropriate, than abusing a class dict like this
@ -27,14 +29,13 @@ class Bunch(object):
Helper class for recording a collection of attributes.
"""
def __init__(self, **kwds):
def __init__(self, **kwargs):
"""
Create a Bunch instance.
:param dict kwds: keyword arguments
:return:
:param dict kwargs: keyword arguments
"""
self.__dict__.update(kwds)
self.__dict__.update(kwargs)
class Sdt(object):
@ -365,9 +366,7 @@ class Sdt(object):
for net in nets:
all_links = net.all_link_data(flags=MessageFlags.ADD.value)
for link_data in all_links:
is_wireless = nodeutils.is_node(
net, (NodeTypes.WIRELESS_LAN, NodeTypes.EMANE)
)
is_wireless = isinstance(net, (WlanNode, EmaneNet))
wireless_link = link_data.message_type == LinkTypes.WIRELESS.value
if is_wireless and link_data.node1_id == net.id:
continue
@ -401,7 +400,7 @@ class Sdt(object):
def handlenodemsg(self, msg):
"""
Process a Node Message to add/delete or move a node on
the SDT display. Node properties are found in session._objs or
the SDT display. Node properties are found in a session or
self.remotes for remote nodes (or those not yet instantiated).
:param msg: node message to handle
@ -430,7 +429,8 @@ class Sdt(object):
model = "router"
nodetype = model
elif nodetype is not None:
nodetype = nodeutils.get_node_class(NodeTypes(nodetype)).type
nodetype = NodeTypes(nodetype)
nodetype = self.session.get_node_class(nodetype).type
net = True
else:
nodetype = None
@ -494,7 +494,7 @@ class Sdt(object):
def wlancheck(self, nodenum):
"""
Helper returns True if a node number corresponds to a WlanNode or EmaneNode.
Helper returns True if a node number corresponds to a WLAN or EMANE node.
:param int nodenum: node id to check
:return: True if node is wlan or emane, False otherwise
@ -509,6 +509,6 @@ class Sdt(object):
n = self.session.get_node(nodenum)
except CoreError:
return False
if nodeutils.is_node(n, (NodeTypes.WIRELESS_LAN, NodeTypes.EMANE)):
if isinstance(n, (WlanNode, EmaneNet)):
return True
return False

View file

@ -12,10 +12,11 @@ import logging
import time
from multiprocessing.pool import ThreadPool
from core import CoreCommandError, utils
from core import utils
from core.constants import which
from core.emulator.data import FileData
from core.emulator.enumerations import MessageFlags, RegisterTlvs
from core.errors import CoreCommandError
class ServiceBootError(Exception):
@ -248,6 +249,7 @@ class ServiceManager(object):
:param CoreService 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)
@ -258,13 +260,14 @@ class ServiceManager(object):
# validate dependent executables are present
for executable in service.executables:
if not which(executable):
logging.debug(
"service(%s) missing executable: %s", service.name, executable
)
raise ValueError(
"service(%s) missing executable: %s" % (service.name, executable)
)
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
@ -294,13 +297,12 @@ class ServiceManager(object):
for service in services:
if not service.name:
continue
service.on_load()
try:
cls.add(service)
except ValueError as e:
service_errors.append(service.name)
logging.debug("not loading service: %s", e)
logging.debug("not loading service(%s): %s", service.name, e)
return service_errors

View file

@ -1,5 +1,4 @@
from core.emulator.enumerations import NodeTypes
from core.nodes import nodeutils
from core.emane.nodes import EmaneNet
from core.services.coreservices import CoreService
from core.xml import emanexml
@ -22,7 +21,7 @@ class EmaneTransportService(CoreService):
transport_commands = []
for interface in node.netifs(sort=True):
network_node = node.session.get_node(interface.net.id)
if nodeutils.is_node(network_node, NodeTypes.EMANE):
if isinstance(network_node, EmaneNet):
config = node.session.emane.get_configs(
network_node.id, network_node.model.name
)

View file

@ -4,8 +4,10 @@ Assumes installation of FRR via https://deb.frrouting.org/
"""
from core import constants
from core.emulator.enumerations import LinkTypes, NodeTypes
from core.nodes import ipaddress, nodeutils
from core.emulator.enumerations import LinkTypes
from core.nodes import ipaddress
from core.nodes.network import PtpNet
from core.nodes.physical import Rj45Node
from core.services.coreservices import CoreService
@ -341,7 +343,7 @@ class FrrService(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
@ -395,7 +397,7 @@ class FRROspfv2(FrrService):
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 ""
@ -481,7 +483,7 @@ class FRROspfv3(FrrService):
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 ""

View file

@ -3,8 +3,11 @@ quagga.py: defines routing services provided by Quagga.
"""
from core import constants
from core.emulator.enumerations import LinkTypes, NodeTypes
from core.nodes import ipaddress, nodeutils
from core.emane.nodes import EmaneNet
from core.emulator.enumerations import LinkTypes
from core.nodes import ipaddress
from core.nodes.network import PtpNet, WlanNode
from core.nodes.physical import Rj45Node
from core.services.coreservices import CoreService
@ -267,7 +270,7 @@ class QuaggaService(CoreService):
for peerifc in ifc.net.netifs():
if peerifc == ifc:
continue
if nodeutils.is_node(peerifc, NodeTypes.RJ45):
if isinstance(peerifc, Rj45Node):
return True
return False
@ -321,7 +324,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 ""
@ -407,7 +410,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 ""
@ -457,9 +460,7 @@ class Ospfv3mdr(Ospfv3):
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)
):
if ifc.net is not None and isinstance(ifc.net, (WlanNode, EmaneNet)):
return (
cfg
+ """\

View file

@ -4,7 +4,8 @@ utility.py: defines miscellaneous utility services.
import os
from core import CoreCommandError, constants, utils
from core import constants, utils
from core.errors import CoreCommandError
from core.nodes.ipaddress import Ipv4Prefix, Ipv6Prefix
from core.services.coreservices import CoreService

View file

@ -6,7 +6,9 @@ import fcntl
import hashlib
import importlib
import inspect
import json
import logging
import logging.config
import os
import shlex
import subprocess
@ -14,7 +16,8 @@ import sys
from past.builtins import basestring
from core import CoreCommandError
from core import constants
from core.errors import CoreCommandError
DEVNULL = open(os.devnull, "wb")
@ -109,17 +112,6 @@ def _is_class(module, member, clazz):
return True
def _is_exe(file_path):
"""
Check if a given file path exists and is an executable file.
:param str file_path: file path to check
:return: True if the file is considered and executable file, False otherwise
:rtype: bool
"""
return os.path.isfile(file_path) and os.access(file_path, os.X_OK)
def close_onexec(fd):
"""
Close on execution of a shell process.
@ -131,17 +123,26 @@ def close_onexec(fd):
fcntl.fcntl(fd, fcntl.F_SETFD, fdflags | fcntl.FD_CLOEXEC)
def check_executables(executables):
def which(command, required):
"""
Check executables, verify they exist and are executable.
Find location of desired executable within current PATH.
:param list[str] executables: executable to check
:return: nothing
:raises EnvironmentError: when an executable doesn't exist or is not executable
:param str command: command to find location for
:param bool required: command is required to be found, false otherwise
:return: command location or None
:raises ValueError: when not found and required
"""
for executable in executables:
if not _is_exe(executable):
raise EnvironmentError("executable not found: %s" % executable)
found_path = None
for path in os.environ["PATH"].split(os.pathsep):
command_path = os.path.join(path, command)
if os.path.isfile(command_path) and os.access(command_path, os.X_OK):
found_path = command_path
break
if found_path is None and required:
raise ValueError("failed to find required executable(%s) in path" % command)
return found_path
def make_tuple(obj):
@ -167,7 +168,8 @@ def make_tuple_fromstr(s, value_type):
:return: tuple from string
:rtype: tuple
"""
# remove tuple braces and strip commands and space from all values in the tuple string
# remove tuple braces and strip commands and space from all values in the tuple
# string
values = []
for x in s.strip("(), ").split(","):
x = x.strip("' ")
@ -178,7 +180,8 @@ def make_tuple_fromstr(s, value_type):
def split_args(args):
"""
Convenience method for splitting potential string commands into a shell-like syntax list.
Convenience method for splitting potential string commands into a shell-like
syntax list.
:param list/str args: command list or string
:return: shell-like syntax list
@ -227,8 +230,8 @@ def cmd(args, wait=True):
def cmd_output(args):
"""
Execute a command on the host and return a tuple containing the exit status and result string. stderr output
is folded into the stdout result string.
Execute a command on the host and return a tuple containing the exit status and
result string. stderr output is folded into the stdout result string.
:param list[str]|str args: command arguments
:return: command status and stdout
@ -248,14 +251,15 @@ def cmd_output(args):
def check_cmd(args, **kwargs):
"""
Execute a command on the host and return a tuple containing the exit status and result string. stderr output
is folded into the stdout result string.
Execute a command on the host and return a tuple containing the exit status and
result string. stderr output is folded into the stdout result string.
:param list[str]|str args: command arguments
:param dict kwargs: keyword arguments to pass to subprocess.Popen
:return: combined stdout and stderr
:rtype: str
:raises CoreCommandError: when there is a non-zero exit status or the file to execute is not found
:raises CoreCommandError: when there is a non-zero exit status or the file to
execute is not found
"""
kwargs["stdout"] = subprocess.PIPE
kwargs["stderr"] = subprocess.STDOUT
@ -350,7 +354,7 @@ def expand_corepath(pathname, session=None, node=None):
Expand a file path given session information.
:param str pathname: file path to expand
:param core.emulator.session.Session session: core session object to expand path with
:param core.emulator.session.Session session: core session object to expand path
:param core.nodes.base.CoreNode node: node to expand path with
:return: expanded path
:rtype: str
@ -383,7 +387,8 @@ def sysctl_devname(devname):
def load_config(filename, d):
"""
Read key=value pairs from a file, into a dict. Skip comments; strip newline characters and spacing.
Read key=value pairs from a file, into a dict. Skip comments; strip newline
characters and spacing.
:param str filename: file to read into a dictionary
:param dict d: dictionary to read file into
@ -444,3 +449,18 @@ def load_classes(path, clazz):
)
return classes
def load_logging_config(config_path=None):
"""
Load CORE logging configuration file.
:param str config_path: path to logging config file,
when None defaults to /etc/core/logging.conf
:return: nothing
"""
if not config_path:
config_path = os.path.join(constants.CORE_CONF_DIR, "logging.conf")
with open(config_path, "r") as log_config_file:
log_config = json.load(log_config_file)
logging.config.dictConfig(log_config)

View file

@ -4,11 +4,12 @@ from lxml import etree
import core.nodes.base
import core.nodes.physical
from core.emane.nodes import EmaneNet
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import NodeTypes
from core.nodes import nodeutils
from core.nodes.base import CoreNetworkBase
from core.nodes.ipaddress import MacAddress
from core.nodes.network import CtrlNet
def write_xml_file(xml_element, file_path, doctype=None):
@ -408,7 +409,7 @@ class CoreXmlWriter(object):
is_network_or_rj45 = isinstance(
node, (core.nodes.base.CoreNetworkBase, core.nodes.physical.Rj45Node)
)
is_controlnet = nodeutils.is_node(node, NodeTypes.CONTROL_NET)
is_controlnet = isinstance(node, CtrlNet)
if is_network_or_rj45 and not is_controlnet:
self.write_network(node)
# device node
@ -457,7 +458,7 @@ class CoreXmlWriter(object):
interface_name = node_interface.name
# check if emane interface
if nodeutils.is_node(node_interface.net, NodeTypes.EMANE):
if isinstance(node_interface.net, EmaneNet):
nem = node_interface.net.getnemid(node_interface)
add_attribute(interface, "nem", nem)

View file

@ -4,8 +4,8 @@ import socket
from lxml import etree
from core import constants, utils
from core.emulator.enumerations import NodeTypes
from core.nodes import ipaddress, nodeutils
from core.emane.nodes import EmaneNet
from core.nodes import ipaddress
from core.nodes.base import CoreNodeBase
@ -144,7 +144,7 @@ class CoreXmlDeployment(object):
for netif in node.netifs():
emane_element = None
if nodeutils.is_node(netif.net, NodeTypes.EMANE):
if isinstance(netif.net, EmaneNet):
emane_element = add_emane_interface(host_element, netif)
parent_element = host_element

View file

@ -103,9 +103,11 @@ def build_node_platform_xml(emane_manager, control_net, node, nem_id, platform_x
"""
Create platform xml for a specific node.
:param core.emane.emanemanager.EmaneManager emane_manager: emane manager with emane configurations
:param core.nodes.network.CtrlNet control_net: control net node for this emane network
:param core.emane.nodes.EmaneNode node: node to write platform xml for
:param core.emane.emanemanager.EmaneManager emane_manager: emane manager with emane
configurations
:param core.nodes.network.CtrlNet control_net: control net node for this emane
network
:param core.emane.nodes.EmaneNet node: node to write platform xml for
:param int nem_id: nem id to use for interfaces for this node
:param dict platform_xmls: stores platform xml elements to append nem entries to
:return: the next nem id that can be used for creating platform xml files
@ -120,7 +122,7 @@ def build_node_platform_xml(emane_manager, control_net, node, nem_id, platform_x
nem_entries = {}
if node.model is None:
logging.warning("warning: EmaneNode %s has no associated model", node.name)
logging.warning("warning: EMANE network %s has no associated model", node.name)
return nem_entries
for netif in node.netifs():
@ -133,7 +135,8 @@ def build_node_platform_xml(emane_manager, control_net, node, nem_id, platform_x
"nem", id=str(nem_id), name=netif.localname, definition=nem_definition
)
# check if this is an external transport, get default config if an interface specific one does not exist
# check if this is an external transport, get default config if an interface
# specific one does not exist
config = emane_manager.getifcconfig(node.model.id, netif, node.model.name)
if is_external(config):
@ -220,8 +223,9 @@ def build_xml_files(emane_manager, node):
"""
Generate emane xml files required for node.
:param core.emane.emanemanager.EmaneManager emane_manager: emane manager with emane configurations
:param core.emane.nodes.EmaneNode node: node to write platform xml for
:param core.emane.emanemanager.EmaneManager emane_manager: emane manager with emane
configurations
:param core.emane.nodes.EmaneNet node: node to write platform xml for
:return: nothing
"""
logging.debug("building all emane xml for node(%s): %s", node, node.name)
@ -233,7 +237,7 @@ def build_xml_files(emane_manager, node):
if not config:
return
# build XML for overall network (EmaneNode) configs
# build XML for overall network EMANE configs
node.model.build_xml_files(config)
# build XML for specific interface (NEM) configs
@ -243,7 +247,7 @@ def build_xml_files(emane_manager, node):
rtype = "raw"
for netif in node.netifs():
# check for interface specific emane configuration and write xml files, if needed
# check for interface specific emane configuration and write xml files
config = emane_manager.getifcconfig(node.model.id, netif, node.model.name)
if config:
node.model.build_xml_files(config, netif)
@ -267,8 +271,9 @@ def build_transport_xml(emane_manager, node, transport_type):
"""
Build transport xml file for node and transport type.
:param core.emane.emanemanager.EmaneManager emane_manager: emane manager with emane configurations
:param core.emane.nodes.EmaneNode node: node to write platform xml for
:param core.emane.emanemanager.EmaneManager emane_manager: emane manager with emane
configurations
:param core.emane.nodes.EmaneNet node: node to write platform xml for
:param str transport_type: transport type to build xml for
:return: nothing
"""
@ -304,7 +309,7 @@ def create_phy_xml(emane_model, config, file_path):
"""
Create the phy xml document.
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create phy xml for
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create xml
:param dict config: all current configuration values
:param str file_path: path to write file to
:return: nothing
@ -323,7 +328,7 @@ def create_mac_xml(emane_model, config, file_path):
"""
Create the mac xml document.
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create phy xml for
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create xml
:param dict config: all current configuration values
:param str file_path: path to write file to
:return: nothing
@ -346,7 +351,7 @@ def create_nem_xml(
"""
Create the nem xml document.
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create phy xml for
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create xml
:param dict config: all current configuration values
:param str nem_file: nem file path to write
:param str transport_definition: transport file definition path
@ -422,7 +427,7 @@ def nem_file_name(emane_model, interface=None):
"""
Return the string name for the NEM XML file, e.g. "n3rfpipenem.xml"
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create phy xml for
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create file
:param interface: interface for this model
:return: nem xml filename
:rtype: str
@ -438,7 +443,7 @@ def shim_file_name(emane_model, interface=None):
"""
Return the string name for the SHIM XML file, e.g. "commeffectshim.xml"
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create phy xml for
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create file
:param interface: interface for this model
:return: shim xml filename
:rtype: str
@ -450,7 +455,7 @@ def mac_file_name(emane_model, interface=None):
"""
Return the string name for the MAC XML file, e.g. "n3rfpipemac.xml"
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create phy xml for
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create file
:param interface: interface for this model
:return: mac xml filename
:rtype: str
@ -462,7 +467,7 @@ def phy_file_name(emane_model, interface=None):
"""
Return the string name for the PHY XML file, e.g. "n3rfpipephy.xml"
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create phy xml for
:param core.emane.emanemodel.EmaneModel emane_model: emane model to create file
:param interface: interface for this model
:return: phy xml filename
:rtype: str

View file

@ -1,12 +1,4 @@
# Configuration file for CORE (core-gui, core-daemon)
### GUI configuration options ###
[core-gui]
# no options are presently defined; see the ~/.core preferences file
### core-daemon configuration options ###
[core-daemon]
xmlfilever = 1.0
listenaddr = localhost
port = 4038
numthreads = 1
@ -19,12 +11,11 @@ frr_sbin_search = "/usr/local/sbin /usr/sbin /usr/lib/frr"
# this may be a comma-separated list, and directory names should be unique
# and not named 'services'
#custom_services_dir = /home/username/.core/myservices
#
# uncomment to establish a standalone control backchannel for accessing nodes
# (overriden by the session option of the same name)
#controlnet = 172.16.0.0/24
#
#
# uncomment and edit to establish a distributed control backchannel
#controlnet = core1:172.16.1.0/24 core2:172.16.2.0/24 core3:172.16.3.0/24 core4:172.16.4.0/24 core5:172.16.5.0/24

View file

@ -1,44 +0,0 @@
import logging
import time
from core.location.event import EventLoop
def main():
loop = EventLoop()
def msg(arg):
delta = time.time() - loop.start
logging.debug("%s arg: %s", delta, arg)
def repeat(interval, count):
count -= 1
msg("repeat: interval: %s; remaining: %s" % (interval, count))
if count > 0:
loop.add_event(interval, repeat, interval, count)
def sleep(delay):
msg("sleep %s" % delay)
time.sleep(delay)
msg("sleep done")
def stop(arg):
msg(arg)
loop.stop()
loop.add_event(0, msg, "start")
loop.add_event(0, msg, "time zero")
for delay in 5, 4, 10, -1, 0, 9, 3, 7, 3.14:
loop.add_event(delay, msg, "time %s" % delay)
loop.run()
loop.add_event(0, repeat, 1, 5)
loop.add_event(12, sleep, 10)
loop.add_event(15.75, stop, "stop time: 15.75")
if __name__ == "__main__":
main()

View file

@ -1,212 +0,0 @@
#!/usr/bin/python -i
# Copyright (c)2010-2013 the Boeing Company.
# See the LICENSE file included in this distribution.
# A distributed example where CORE API messaging is used to create a session
# on a daemon server. The daemon server defaults to 127.0.0.1:4038
# to target a remote machine specify "-d <ip address>" parameter, it needs to be
# running the daemon with listenaddr=0.0.0.0 in the core.conf file.
# This script creates no nodes locally and therefore can be run as an
# unprivileged user.
import datetime
import optparse
import sys
from builtins import range
import core.nodes.base
import core.nodes.network
from core.api.tlv import coreapi, dataconversion
from core.api.tlv.coreapi import CoreExecuteTlv
from core.emulator.enumerations import (
CORE_API_PORT,
EventTlvs,
EventTypes,
ExecuteTlvs,
LinkTlvs,
LinkTypes,
MessageFlags,
MessageTypes,
)
from core.emulator.session import Session
from core.nodes import ipaddress
# declare classes for use with Broker
# node list (count from 1)
n = [None]
exec_num = 1
def cmd(node, exec_cmd):
"""
:param node: The node the command should be issued too
:param exec_cmd: A string with the command to be run
:return: Returns the result of the command
"""
global exec_num
# Set up the command api message
tlvdata = CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, node.id)
tlvdata += CoreExecuteTlv.pack(ExecuteTlvs.NUMBER.value, exec_num)
tlvdata += CoreExecuteTlv.pack(ExecuteTlvs.COMMAND.value, exec_cmd)
msg = coreapi.CoreExecMessage.pack(
MessageFlags.STRING.value | MessageFlags.TEXT.value, tlvdata
)
node.session.broker.handlerawmsg(msg)
exec_num += 1
# Now wait for the response
server = node.session.broker.servers["localhost"]
server.sock.settimeout(50.0)
# receive messages until we get our execute response
result = None
while True:
msghdr = server.sock.recv(coreapi.CoreMessage.header_len)
msgtype, msgflags, msglen = coreapi.CoreMessage.unpack_header(msghdr)
msgdata = server.sock.recv(msglen)
# If we get the right response return the results
print("received response message: %s" % MessageTypes(msgtype))
if msgtype == MessageTypes.EXECUTE.value:
msg = coreapi.CoreExecMessage(msgflags, msghdr, msgdata)
result = msg.get_tlv(ExecuteTlvs.RESULT.value)
break
return result
def main():
usagestr = "usage: %prog [-n] number of nodes [-d] daemon address"
parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(numnodes=5, daemon="127.0.0.1:" + str(CORE_API_PORT))
parser.add_option(
"-n", "--numnodes", dest="numnodes", type=int, help="number of nodes"
)
parser.add_option(
"-d",
"--daemon-server",
dest="daemon",
type=str,
help="daemon server IP address",
)
def usage(msg=None, err=0):
sys.stdout.write("\n")
if msg:
sys.stdout.write(msg + "\n\n")
parser.print_help()
sys.exit(err)
# parse command line options
(options, args) = parser.parse_args()
if options.numnodes < 1:
usage("invalid number of nodes: %s" % options.numnodes)
if not options.daemon:
usage("daemon server IP address (-d) is a required argument")
for a in args:
sys.stderr.write("ignoring command line argument: %s\n" % a)
start = datetime.datetime.now()
prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
session = Session(1)
server = globals().get("server")
if server:
server.addsession(session)
# distributed setup - connect to daemon server
daemonport = options.daemon.split(":")
daemonip = daemonport[0]
# Localhost is already set in the session but we change it to be the remote daemon
# This stops the remote daemon trying to build a tunnel back which would fail
daemon = "localhost"
if len(daemonport) > 1:
port = int(daemonport[1])
else:
port = CORE_API_PORT
print("connecting to daemon at %s:%d" % (daemon, port))
session.broker.addserver(daemon, daemonip, port)
# Set the local session id to match the port.
# Not necessary but seems neater.
session.broker.setupserver(daemon)
# We do not want the recvloop running as we will deal ourselves
session.broker.dorecvloop = False
# Change to configuration state on both machines
session.set_state(EventTypes.CONFIGURATION_STATE)
tlvdata = coreapi.CoreEventTlv.pack(
EventTlvs.TYPE.value, EventTypes.CONFIGURATION_STATE.value
)
session.broker.handlerawmsg(coreapi.CoreEventMessage.pack(0, tlvdata))
flags = MessageFlags.ADD.value
switch = core.nodes.network.SwitchNode(session=session, name="switch", start=False)
switch.setposition(x=80, y=50)
switch.server = daemon
switch_data = switch.data(flags)
switch_message = dataconversion.convert_node(switch_data)
session.broker.handlerawmsg(switch_message)
number_of_nodes = options.numnodes
print(
"creating %d remote nodes with addresses from %s" % (options.numnodes, prefix)
)
# create remote nodes via API
for i in range(1, number_of_nodes + 1):
node = core.nodes.base.CoreNode(
session=session, _id=i, name="n%d" % i, start=False
)
node.setposition(x=150 * i, y=150)
node.server = daemon
node_data = node.data(flags)
node_message = dataconversion.convert_node(node_data)
session.broker.handlerawmsg(node_message)
n.append(node)
# create remote links via API
for i in range(1, number_of_nodes + 1):
tlvdata = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.id)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, i)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value, LinkTypes.WIRED.value)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_NUMBER.value, 0)
tlvdata += coreapi.CoreLinkTlv.pack(
LinkTlvs.INTERFACE2_IP4.value, prefix.addr(i)
)
tlvdata += coreapi.CoreLinkTlv.pack(
LinkTlvs.INTERFACE2_IP4_MASK.value, prefix.prefixlen
)
msg = coreapi.CoreLinkMessage.pack(flags, tlvdata)
session.broker.handlerawmsg(msg)
# We change the daemon to Instantiation state
# We do not change the local session as it would try and build a tunnel and fail
tlvdata = coreapi.CoreEventTlv.pack(
EventTlvs.TYPE.value, EventTypes.INSTANTIATION_STATE.value
)
msg = coreapi.CoreEventMessage.pack(0, tlvdata)
session.broker.handlerawmsg(msg)
# Get the ip or last node and ping it from the first
print("Pinging from the first to the last node")
pingip = cmd(n[-1], "ip -4 -o addr show dev eth0").split()[3].split("/")[0]
print(cmd(n[1], "ping -c 5 " + pingip))
print("elapsed time: %s" % (datetime.datetime.now() - start))
print(
"To stop this session, use the core-cleanup script on the remote daemon server."
)
input("press enter to exit")
if __name__ == "__main__" or __name__ == "__builtin__":
main()

View file

@ -1,151 +0,0 @@
#!/usr/bin/python -i
# Copyright (c)2010-2013 the Boeing Company.
# See the LICENSE file included in this distribution.
# A distributed example where CORE API messaging is used to create a session
# distributed across the local server and one slave server. The slave server
# must be specified using the '-s <ip address>' parameter, and needs to be
# running the daemon with listenaddr=0.0.0.0 in the core.conf file.
#
import datetime
import optparse
import sys
from builtins import range
import core.nodes.base
import core.nodes.network
from core import constants
from core.api.tlv import coreapi, dataconversion
from core.emulator.enumerations import (
CORE_API_PORT,
EventTlvs,
EventTypes,
LinkTlvs,
LinkTypes,
MessageFlags,
)
from core.emulator.session import Session
from core.nodes import ipaddress
# node list (count from 1)
n = [None]
def main():
usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(numnodes=5, slave=None)
parser.add_option(
"-n", "--numnodes", dest="numnodes", type=int, help="number of nodes"
)
parser.add_option(
"-s", "--slave-server", dest="slave", type=str, help="slave server IP address"
)
def usage(msg=None, err=0):
sys.stdout.write("\n")
if msg:
sys.stdout.write(msg + "\n\n")
parser.print_help()
sys.exit(err)
# parse command line options
(options, args) = parser.parse_args()
if options.numnodes < 1:
usage("invalid number of nodes: %s" % options.numnodes)
if not options.slave:
usage("slave server IP address (-s) is a required argument")
for a in args:
sys.stderr.write("ignoring command line argument: '%s'\n" % a)
start = datetime.datetime.now()
prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
session = Session(1)
server = globals().get("server")
if server is not None:
server.addsession(session)
# distributed setup - connect to slave server
slaveport = options.slave.split(":")
slave = slaveport[0]
if len(slaveport) > 1:
port = int(slaveport[1])
else:
port = CORE_API_PORT
print("connecting to slave at %s:%d" % (slave, port))
session.broker.addserver(slave, slave, port)
session.broker.setupserver(slave)
session.set_state(EventTypes.CONFIGURATION_STATE)
tlvdata = coreapi.CoreEventTlv.pack(
EventTlvs.TYPE.value, EventTypes.CONFIGURATION_STATE.value
)
session.broker.handlerawmsg(coreapi.CoreEventMessage.pack(0, tlvdata))
switch = session.create_node(cls=core.nodes.network.SwitchNode, name="switch")
switch.setposition(x=80, y=50)
num_local = options.numnodes / 2
num_remote = options.numnodes / 2 + options.numnodes % 2
print(
"creating %d (%d local / %d remote) nodes with addresses from %s"
% (options.numnodes, num_local, num_remote, prefix)
)
for i in range(1, num_local + 1):
node = session.create_node(cls=core.nodes.base.CoreNode, name="n%d" % i, _id=i)
node.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)])
node.cmd([constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"])
node.setposition(x=150 * i, y=150)
n.append(node)
flags = MessageFlags.ADD.value
session.broker.handlerawmsg(switch.tonodemsg(flags=flags))
# create remote nodes via API
for i in range(num_local + 1, options.numnodes + 1):
node = core.nodes.base.CoreNode(
session=session, _id=i, name="n%d" % i, start=False
)
node.setposition(x=150 * i, y=150)
node.server = slave
n.append(node)
node_data = node.data(flags)
node_message = dataconversion.convert_node(node_data)
session.broker.handlerawmsg(node_message)
# create remote links via API
for i in range(num_local + 1, options.numnodes + 1):
tlvdata = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.id)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, i)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value, LinkTypes.WIRED.value)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_NUMBER.value, 0)
tlvdata += coreapi.CoreLinkTlv.pack(
LinkTlvs.INTERFACE2_IP4.value, prefix.addr(i)
)
tlvdata += coreapi.CoreLinkTlv.pack(
LinkTlvs.INTERFACE2_IP4_MASK.value, prefix.prefixlen
)
msg = coreapi.CoreLinkMessage.pack(flags, tlvdata)
session.broker.handlerawmsg(msg)
session.instantiate()
tlvdata = coreapi.CoreEventTlv.pack(
EventTlvs.TYPE.value, EventTypes.INSTANTIATION_STATE.value
)
msg = coreapi.CoreEventMessage.pack(0, tlvdata)
session.broker.handlerawmsg(msg)
# start a shell on node 1
n[1].client.term("bash")
print("elapsed time: %s" % (datetime.datetime.now() - start))
print("To stop this session, use the 'core-cleanup' script on this server")
print("and on the remote slave server.")
if __name__ == "__main__" or __name__ == "__builtin__":
main()

View file

@ -1,247 +0,0 @@
#!/usr/bin/python
# Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
"""
howmanynodes.py - This is a CORE script that creates network namespace nodes
having one virtual Ethernet interface connected to a bridge. It continues to
add nodes until an exception occurs. The number of nodes per bridge can be
specified.
"""
import datetime
import optparse
import shutil
import sys
import time
import core.nodes.base
import core.nodes.network
from core import constants
from core.emulator.session import Session
from core.nodes import ipaddress
GBD = 1024.0 * 1024.0
def linuxversion():
""" Return a string having the Linux kernel version.
"""
f = open("/proc/version", "r")
v = f.readline().split()
version_str = " ".join(v[:3])
f.close()
return version_str
MEMKEYS = ("total", "free", "buff", "cached", "stotal", "sfree")
def memfree():
""" Returns kilobytes memory [total, free, buff, cached, stotal, sfree].
useful stats are:
free memory = free + buff + cached
swap used = stotal - sfree
"""
f = open("/proc/meminfo", "r")
lines = f.readlines()
f.close()
kbs = {}
for k in MEMKEYS:
kbs[k] = 0
for l in lines:
if l[:9] == "MemTotal:":
kbs["total"] = int(l.split()[1])
elif l[:8] == "MemFree:":
kbs["free"] = int(l.split()[1])
elif l[:8] == "Buffers:":
kbs["buff"] = int(l.split()[1])
elif l[:8] == "Cached:":
kbs["cache"] = int(l.split()[1])
elif l[:10] == "SwapTotal:":
kbs["stotal"] = int(l.split()[1])
elif l[:9] == "SwapFree:":
kbs["sfree"] = int(l.split()[1])
break
return kbs
# node list (count from 1)
nodelist = [None]
switchlist = []
def main():
usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(
waittime=0.2, numnodes=0, bridges=0, retries=0, logfile=None, services=None
)
parser.add_option(
"-w",
"--waittime",
dest="waittime",
type=float,
help="number of seconds to wait between node creation"
" (default = %s)" % parser.defaults["waittime"],
)
parser.add_option(
"-n",
"--numnodes",
dest="numnodes",
type=int,
help="number of nodes (default = unlimited)",
)
parser.add_option(
"-b",
"--bridges",
dest="bridges",
type=int,
help="number of nodes per bridge; 0 = one bridge "
"(def. = %s)" % parser.defaults["bridges"],
)
parser.add_option(
"-r",
"--retry",
dest="retries",
type=int,
help="number of retries on error (default = %s)" % parser.defaults["retries"],
)
parser.add_option(
"-l",
"--log",
dest="logfile",
type=str,
help="log memory usage to this file (default = %s)"
% parser.defaults["logfile"],
)
parser.add_option(
"-s",
"--services",
dest="services",
type=str,
help="pipe-delimited list of services added to each "
"node (default = %s)\n(Example: zebra|OSPFv2|OSPFv3|"
"IPForward)" % parser.defaults["services"],
)
def usage(msg=None, err=0):
sys.stdout.write("\n")
if msg:
sys.stdout.write(msg + "\n\n")
parser.print_help()
sys.exit(err)
options, args = parser.parse_args()
for a in args:
sys.stderr.write("ignoring command line argument: %s\n" % a)
start = datetime.datetime.now()
prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
print("Testing how many network namespace nodes this machine can create.")
print(" - %s" % linuxversion())
mem = memfree()
print(
" - %.02f GB total memory (%.02f GB swap)"
% (mem["total"] / GBD, mem["stotal"] / GBD)
)
print(" - using IPv4 network prefix %s" % prefix)
print(" - using wait time of %s" % options.waittime)
print(" - using %d nodes per bridge" % options.bridges)
print(" - will retry %d times on failure" % options.retries)
print(" - adding these services to each node: %s" % options.services)
print(" ")
lfp = None
if options.logfile is not None:
# initialize a csv log file header
lfp = open(options.logfile, "a")
lfp.write("# log from howmanynodes.py %s\n" % time.ctime())
lfp.write("# options = %s\n#\n" % options)
lfp.write("# numnodes,%s\n" % ",".join(MEMKEYS))
lfp.flush()
session = Session(1)
switch = session.create_node(cls=core.nodes.network.SwitchNode)
switchlist.append(switch)
print("Added bridge %s (%d)." % (switch.brname, len(switchlist)))
i = 0
retry_count = options.retries
while True:
i += 1
# optionally add a bridge (options.bridges nodes per bridge)
try:
if 0 < options.bridges <= switch.numnetif():
switch = session.create_node(cls=core.nodes.network.SwitchNode)
switchlist.append(switch)
print(
"\nAdded bridge %s (%d) for node %d."
% (switch.brname, len(switchlist), i)
)
except Exception as e:
print(
"At %d bridges (%d nodes) caught exception:\n%s\n"
% (len(switchlist), i - 1, e)
)
break
# create a node
try:
n = session.create_node(cls=core.nodes.base.CoreNode, name="n%d" % i)
n.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)])
n.cmd([constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"])
if options.services is not None:
session.services.add_services(n, "", options.services.split("|"))
session.services.boot_services(n)
nodelist.append(n)
if i % 25 == 0:
print("\n%s nodes created " % i)
mem = memfree()
free = mem["free"] + mem["buff"] + mem["cached"]
swap = mem["stotal"] - mem["sfree"]
print("(%.02f/%.02f GB free/swap)" % (free / GBD, swap / GBD))
if lfp:
lfp.write("%d," % i)
lfp.write("%s\n" % ",".join(str(mem[x]) for x in MEMKEYS))
lfp.flush()
else:
sys.stdout.write(".")
sys.stdout.flush()
time.sleep(options.waittime)
except Exception as e:
print("At %d nodes caught exception:\n" % i, e)
if retry_count > 0:
print("\nWill retry creating node %d." % i)
shutil.rmtree(n.nodedir, ignore_errors=True)
retry_count -= 1
i -= 1
time.sleep(options.waittime)
continue
else:
print("Stopping at %d nodes!" % i)
break
if i == options.numnodes:
print("Stopping at %d nodes due to numnodes option." % i)
break
# node creation was successful at this point
retry_count = options.retries
if lfp:
lfp.flush()
lfp.close()
print("elapsed time: %s" % (datetime.datetime.now() - start))
print("Use the core-cleanup script to remove nodes and bridges.")
if __name__ == "__main__":
main()

View file

@ -1,637 +0,0 @@
#!/usr/bin/python
# Copyright (c)2011-2014 the Boeing Company.
# See the LICENSE file included in this distribution.
# create a random topology running OSPFv3 MDR, wait and then check
# that all neighbor states are either full or two-way, and check the routes
# in zebra vs those installed in the kernel.
import datetime
import optparse
import os
import random
import sys
import time
from builtins import range
from string import Template
import core.nodes.base
import core.nodes.network
from core.constants import QUAGGA_STATE_DIR
from core.emulator.session import Session
from core.nodes import ipaddress
from core.utils import check_cmd
quagga_sbin_search = ("/usr/local/sbin", "/usr/sbin", "/usr/lib/quagga")
quagga_path = "zebra"
# sanity check that zebra is installed
try:
for p in quagga_sbin_search:
if os.path.exists(os.path.join(p, "zebra")):
quagga_path = p
break
check_cmd([os.path.join(quagga_path, "zebra"), "-u", "root", "-g", "root", "-v"])
except OSError:
sys.stderr.write("ERROR: running zebra failed\n")
sys.exit(1)
class ManetNode(core.nodes.base.CoreNode):
""" An Lxc namespace node configured for Quagga OSPFv3 MANET MDR
"""
conftemp = Template(
"""\
interface eth0
ip address $ipaddr
ipv6 ospf6 instance-id 65
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 adjacencyconnectivity biconnected
ipv6 ospf6 lsafullness mincostlsa
!
router ospf6
router-id $routerid
interface eth0 area 0.0.0.0
!
ip forwarding
"""
)
confdir = "/usr/local/etc/quagga"
def __init__(self, core, ipaddr, routerid=None, _id=None, name=None, nodedir=None):
if routerid is None:
routerid = ipaddr.split("/")[0]
self.ipaddr = ipaddr
self.routerid = routerid
core.nodes.base.CoreBaseNode.__init__(self, core, _id, name, nodedir)
self.privatedir(self.confdir)
self.privatedir(QUAGGA_STATE_DIR)
def qconf(self):
return self.conftemp.substitute(ipaddr=self.ipaddr, routerid=self.routerid)
def config(self):
filename = os.path.join(self.confdir, "Quagga.conf")
f = self.opennodefile(filename, "w")
f.write(self.qconf())
f.close()
tmp = self.bootscript()
if tmp:
self.nodefile(self.bootsh, tmp, mode=0o755)
def boot(self):
self.config()
self.session.services.boot_services(self)
def bootscript(self):
return """\
#!/bin/sh -e
STATEDIR=%s
waitfile()
{
fname=$1
i=0
until [ -e $fname ]; do
i=$(($i + 1))
if [ $i -eq 10 ]; then
echo "file not found: $fname" >&2
exit 1
fi
sleep 0.1
done
}
mkdir -p $STATEDIR
%s/zebra -d -u root -g root
waitfile $STATEDIR/zebra.vty
%s/ospf6d -d -u root -g root
waitfile $STATEDIR/ospf6d.vty
vtysh -b
""" % (
QUAGGA_STATE_DIR,
quagga_path,
quagga_path,
)
class Route(object):
""" Helper class for organzing routing table entries. """
def __init__(self, prefix=None, gw=None, metric=None):
try:
self.prefix = ipaddress.Ipv4Prefix(prefix)
except Exception as e:
raise ValueError(
"Invalid prefix given to Route object: %s\n%s" % (prefix, e)
)
self.gw = gw
self.metric = metric
def __eq__(self, other):
try:
return (
self.prefix == other.prefix
and self.gw == other.gw
and self.metric == other.metric
)
except Exception:
return False
def __str__(self):
return "(%s,%s,%s)" % (self.prefix, self.gw, self.metric)
@staticmethod
def key(r):
if not r.prefix:
return 0
return r.prefix.prefix
class ManetExperiment(object):
""" A class for building an MDR network and checking and logging its state.
"""
def __init__(self, options, start):
""" Initialize with options and start time. """
self.session = None
# node list
self.nodes = []
# WLAN network
self.net = None
self.verbose = options.verbose
# dict from OptionParser
self.options = options
self.start = start
self.logbegin()
def info(self, msg):
""" Utility method for writing output to stdout. """
print(msg)
sys.stdout.flush()
self.log(msg)
def warn(self, msg):
""" Utility method for writing output to stderr. """
sys.stderr.write(msg)
sys.stderr.flush()
self.log(msg)
def logbegin(self):
""" Start logging. """
self.logfp = None
if not self.options.logfile:
return
self.logfp = open(self.options.logfile, "w")
self.log("ospfmanetmdrtest begin: %s\n" % self.start.ctime())
def logend(self):
""" End logging. """
if not self.logfp:
return
end = datetime.datetime.now()
self.log("ospfmanetmdrtest end: %s (%s)\n" % (end.ctime(), end - self.start))
self.logfp.flush()
self.logfp.close()
self.logfp = None
def log(self, msg):
""" Write to the log file, if any. """
if not self.logfp:
return
self.logfp.write(msg)
def logdata(self, nbrs, mdrs, lsdbs, krs, zrs):
""" Dump experiment parameters and data to the log file. """
self.log("ospfmantetmdrtest data:")
self.log("----- parameters -----")
self.log("%s" % self.options)
self.log("----- neighbors -----")
for rtrid in sorted(nbrs.keys()):
self.log("%s: %s" % (rtrid, nbrs[rtrid]))
self.log("----- mdr levels -----")
self.log(mdrs)
self.log("----- link state databases -----")
for rtrid in sorted(lsdbs.keys()):
self.log("%s lsdb:" % rtrid)
for line in lsdbs[rtrid].split("\n"):
self.log(line)
self.log("----- kernel routes -----")
for rtrid in sorted(krs.keys()):
msg = rtrid + ": "
for rt in krs[rtrid]:
msg += "%s" % rt
self.log(msg)
self.log("----- zebra routes -----")
for rtrid in sorted(zrs.keys()):
msg = rtrid + ": "
for rt in zrs[rtrid]:
msg += "%s" % rt
self.log(msg)
def topology(self, numnodes, linkprob, verbose=False):
""" Build a topology consisting of the given number of ManetNodes
connected to a WLAN and probabilty of links and set
the session, WLAN, and node list objects.
"""
# IP subnet
prefix = ipaddress.Ipv4Prefix("10.14.0.0/16")
self.session = Session(1)
# emulated network
self.net = self.session.create_node(cls=core.nodes.network.WlanNode)
for i in range(1, numnodes + 1):
addr = "%s/%s" % (prefix.addr(i), 32)
tmp = self.session.create_node(
cls=ManetNode, ipaddr=addr, _id="%d" % i, name="n%d" % i
)
tmp.newnetif(self.net, [addr])
self.nodes.append(tmp)
# connect nodes with probability linkprob
for i in range(numnodes):
for j in range(i + 1, numnodes):
r = random.random()
if r < linkprob:
if self.verbose:
self.info("linking (%d,%d)" % (i, j))
self.net.link(self.nodes[i].netif(0), self.nodes[j].netif(0))
# force one link to avoid partitions (should check if this is needed)
j = i
while j == i:
j = random.randint(0, numnodes - 1)
if self.verbose:
self.info("linking (%d,%d)" % (i, j))
self.net.link(self.nodes[i].netif(0), self.nodes[j].netif(0))
self.nodes[i].boot()
# run the boot.sh script on all nodes to start Quagga
for i in range(numnodes):
self.nodes[i].cmd(["./%s" % self.nodes[i].bootsh])
def compareroutes(self, node, kr, zr):
""" Compare two lists of Route objects.
"""
kr.sort(key=Route.key)
zr.sort(key=Route.key)
if kr != zr:
self.warn("kernel and zebra routes differ")
if self.verbose:
msg = "kernel: "
for r in kr:
msg += "%s " % r
msg += "\nzebra: "
for r in zr:
msg += "%s " % r
self.warn(msg)
else:
self.info(" kernel and zebra routes match")
def comparemdrlevels(self, nbrs, mdrs):
""" Check that all routers form a connected dominating set, i.e. all
routers are either MDR, BMDR, or adjacent to one.
"""
msg = "All routers form a CDS"
for n in self.nodes:
if mdrs[n.routerid] != "OTHER":
continue
connected = False
for nbr in nbrs[n.routerid]:
if mdrs[nbr] == "MDR" or mdrs[nbr] == "BMDR":
connected = True
break
if not connected:
msg = "All routers do not form a CDS"
self.warn(
"XXX %s: not in CDS; neighbors: %s" % (n.routerid, nbrs[n.routerid])
)
if self.verbose:
self.info(msg)
def comparelsdbs(self, lsdbs):
""" Check LSDBs for consistency.
"""
msg = "LSDBs of all routers are consistent"
prev = self.nodes[0]
for n in self.nodes:
db = lsdbs[n.routerid]
if lsdbs[prev.routerid] != db:
msg = "LSDBs of all routers are not consistent"
self.warn(
"XXX LSDBs inconsistent for %s and %s" % (n.routerid, prev.routerid)
)
i = 0
for entry in lsdbs[n.routerid].split("\n"):
preventries = lsdbs[prev.routerid].split("\n")
try:
preventry = preventries[i]
except IndexError:
preventry = None
if entry != preventry:
self.warn("%s: %s" % (n.routerid, entry))
self.warn("%s: %s" % (prev.routerid, preventry))
i += 1
prev = n
if self.verbose:
self.info(msg)
def checknodes(self):
""" Check the neighbor state and routing tables of all nodes. """
nbrs = {}
mdrs = {}
lsdbs = {}
krs = {}
zrs = {}
v = self.verbose
for n in self.nodes:
self.info("checking %s" % n.name)
nbrs[n.routerid] = Ospf6NeighState(n, verbose=v).run()
krs[n.routerid] = KernelRoutes(n, verbose=v).run()
zrs[n.routerid] = ZebraRoutes(n, verbose=v).run()
self.compareroutes(n, krs[n.routerid], zrs[n.routerid])
mdrs[n.routerid] = Ospf6MdrLevel(n, verbose=v).run()
lsdbs[n.routerid] = Ospf6Database(n, verbose=v).run()
self.comparemdrlevels(nbrs, mdrs)
self.comparelsdbs(lsdbs)
self.logdata(nbrs, mdrs, lsdbs, krs, zrs)
class Cmd:
""" Helper class for running a command on a node and parsing the result. """
args = ""
def __init__(self, node, verbose=False):
""" Initialize with a CoreNode (LxcNode) """
self.id = None
self.stdin = None
self.out = None
self.node = node
self.verbose = verbose
def info(self, msg):
""" Utility method for writing output to stdout."""
print(msg)
sys.stdout.flush()
def warn(self, msg):
""" Utility method for writing output to stderr. """
sys.stderr.write("XXX %s:" % self.node.routerid, msg)
sys.stderr.flush()
def run(self):
""" This is the primary method used for running this command. """
self.open()
r = self.parse()
self.cleanup()
return r
def open(self):
""" Exceute call to node.popen(). """
self.id, self.stdin, self.out, self.err = self.node.client.popen(self.args)
def parse(self):
""" This method is overloaded by child classes and should return some
result.
"""
return None
def cleanup(self):
""" Close the Popen channels."""
self.stdin.close()
self.out.close()
self.err.close()
tmp = self.id.wait()
if tmp:
self.warn("nonzero exit status:", tmp)
class VtyshCmd(Cmd):
""" Runs a vtysh command. """
def open(self):
args = ("vtysh", "-c", self.args)
self.id, self.stdin, self.out, self.err = self.node.client.popen(args)
class Ospf6NeighState(VtyshCmd):
""" Check a node for OSPFv3 neighbors in the full/two-way states. """
args = "show ipv6 ospf6 neighbor"
def parse(self):
# skip first line
self.out.readline()
nbrlist = []
for line in self.out:
field = line.split()
nbr = field[0]
state = field[3].split("/")[0]
if not state.lower() in ("full", "twoway"):
self.warn("neighbor %s state: %s" % (nbr, state))
nbrlist.append(nbr)
if len(nbrlist) == 0:
self.warn("no neighbors")
if self.verbose:
self.info(" %s has %d neighbors" % (self.node.routerid, len(nbrlist)))
return nbrlist
class Ospf6MdrLevel(VtyshCmd):
""" Retrieve the OSPFv3 MDR level for a node. """
args = "show ipv6 ospf6 mdrlevel"
def parse(self):
line = self.out.readline()
# TODO: handle multiple interfaces
field = line.split()
mdrlevel = field[4]
if mdrlevel not in ("MDR", "BMDR", "OTHER"):
self.warn("mdrlevel: %s" % mdrlevel)
if self.verbose:
self.info(" %s is %s" % (self.node.routerid, mdrlevel))
return mdrlevel
class Ospf6Database(VtyshCmd):
""" Retrieve the OSPFv3 LSDB summary for a node. """
args = "show ipv6 ospf6 database"
def parse(self):
db = ""
for line in self.out:
field = line.split()
if len(field) < 8:
continue
# filter out Age and Duration columns
filtered = field[:3] + field[4:7]
db += " ".join(filtered) + "\n"
return db
class ZebraRoutes(VtyshCmd):
""" Return a list of Route objects for a node based on its zebra
routing table.
"""
args = "show ip route"
def parse(self):
for i in range(0, 3):
# skip first three lines
self.out.readline()
r = []
prefix = None
for line in self.out:
field = line.split()
if len(field) < 1:
continue
# only use OSPFv3 selected FIB routes
elif field[0][:2] == "o>":
prefix = field[1]
metric = field[2].split("/")[1][:-1]
if field[0][2:] != "*":
continue
if field[3] == "via":
gw = field[4][:-1]
else:
gw = field[6][:-1]
r.append(Route(prefix, gw, metric))
prefix = None
elif prefix and field[0] == "*":
# already have prefix and metric from previous line
gw = field[2][:-1]
r.append(Route(prefix, gw, metric))
prefix = None
if len(r) == 0:
self.warn("no zebra routes")
if self.verbose:
self.info(" %s has %d zebra routes" % (self.node.routerid, len(r)))
return r
class KernelRoutes(Cmd):
""" Return a list of Route objects for a node based on its kernel
routing table.
"""
args = ("/sbin/ip", "route", "show")
def parse(self):
r = []
prefix = None
for line in self.out:
field = line.split()
if field[0] == "nexthop":
if not prefix:
# this saves only the first nexthop entry if multiple exist
continue
else:
prefix = field[0]
metric = field[-1]
tmp = prefix.split("/")
if len(tmp) < 2:
prefix += "/32"
if field[1] == "proto":
# nexthop entry is on the next line
continue
# nexthop IP or interface
gw = field[2]
r.append(Route(prefix, gw, metric))
prefix = None
if len(r) == 0:
self.warn("no kernel routes")
if self.verbose:
self.info(" %s has %d kernel routes" % (self.node.routerid, len(r)))
return r
def main():
usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(numnodes=10, linkprob=0.35, delay=20, seed=None)
parser.add_option(
"-n", "--numnodes", dest="numnodes", type=int, help="number of nodes"
)
parser.add_option(
"-p", "--linkprob", dest="linkprob", type=float, help="link probabilty"
)
parser.add_option(
"-d", "--delay", dest="delay", type=float, help="wait time before checking"
)
parser.add_option(
"-s",
"--seed",
dest="seed",
type=int,
help="specify integer to use for random seed",
)
parser.add_option(
"-v", "--verbose", dest="verbose", action="store_true", help="be more verbose"
)
parser.add_option(
"-l",
"--logfile",
dest="logfile",
type=str,
help="log detailed output to the specified file",
)
def usage(msg=None, err=0):
sys.stdout.write("\n")
if msg:
sys.stdout.write(msg + "\n\n")
parser.print_help()
sys.exit(err)
# parse command line options
(options, args) = parser.parse_args()
if options.numnodes < 2:
usage("invalid numnodes: %s" % options.numnodes)
if options.linkprob <= 0.0 or options.linkprob > 1.0:
usage("invalid linkprob: %s" % options.linkprob)
if options.delay < 0.0:
usage("invalid delay: %s" % options.delay)
for a in args:
sys.stderr.write("ignoring command line argument: '%s'\n" % a)
if options.seed:
random.seed(options.seed)
me = ManetExperiment(options=options, start=datetime.datetime.now())
me.info(
"creating topology: numnodes = %s; linkprob = %s"
% (options.numnodes, options.linkprob)
)
me.topology(options.numnodes, options.linkprob)
me.info("waiting %s sec" % options.delay)
time.sleep(options.delay)
me.info("checking neighbor state and routes")
me.checknodes()
me.info("done")
me.info("elapsed time: %s" % (datetime.datetime.now() - me.start))
me.logend()
return me
if __name__ == "__main__":
me = main()

View file

@ -6,11 +6,11 @@ import datetime
import parser
from builtins import range
from core import load_logging_config
from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes
from core.emulator.enumerations import EventTypes
from core.utils import load_logging_config
load_logging_config()

View file

@ -9,10 +9,10 @@ import datetime
import parser
from builtins import range
from core import load_logging_config
from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes
from core.emulator.enumerations import EventTypes, NodeTypes
from core.utils import load_logging_config
load_logging_config()

View file

@ -6,9 +6,9 @@
# nodestep
from builtins import range
from core import load_logging_config
from core.emulator.emudata import IpPrefixes
from core.emulator.enumerations import EventTypes, NodeTypes
from core.utils import load_logging_config
load_logging_config()

View file

@ -9,11 +9,11 @@ import datetime
import parser
from builtins import range
from core import load_logging_config
from core.emulator.coreemu import CoreEmu
from core.emulator.emudata import IpPrefixes, NodeOptions
from core.emulator.enumerations import EventTypes, NodeTypes
from core.location.mobility import BasicRangeModel
from core.utils import load_logging_config
load_logging_config()

View file

@ -1,50 +0,0 @@
#!/usr/bin/env python
# (c)2010-2012 the Boeing Company
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
# List and stop CORE sessions from the command line.
#
import optparse
import socket
from core.api.tlv import coreapi
from core.emulator.enumerations import CORE_API_PORT, MessageFlags, SessionTlvs
def main():
parser = optparse.OptionParser(usage="usage: %prog [-l] <sessionid>")
parser.add_option(
"-l", "--list", dest="list", action="store_true", help="list running sessions"
)
(options, args) = parser.parse_args()
if options.list is True:
num = "0"
flags = MessageFlags.STRING.value
else:
num = args[0]
flags = MessageFlags.DELETE.value
tlvdata = coreapi.CoreSessionTlv.pack(SessionTlvs.NUMBER.value, num)
message = coreapi.CoreSessionMessage.pack(flags, tlvdata)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("localhost", CORE_API_PORT))
sock.send(message)
# receive and print a session list
if options.list is True:
hdr = sock.recv(coreapi.CoreMessage.header_len)
msgtype, msgflags, msglen = coreapi.CoreMessage.unpack_header(hdr)
data = ""
if msglen:
data = sock.recv(msglen)
message = coreapi.CoreMessage(msgflags, hdr, data)
sessions = message.get_tlv(coreapi.SessionTlvs.NUMBER.value)
print("sessions: {}".format(sessions))
sock.close()
if __name__ == "__main__":
main()

View file

@ -1,4 +1,4 @@
configparser==4.0.1
configparser==4.0.2
future==0.17.1
grpcio==1.23.0
grpcio-tools==1.21.1

View file

@ -12,12 +12,12 @@ import threading
import time
from configparser import ConfigParser
from core import constants, load_logging_config
from core import constants
from core.api.grpc.server import CoreGrpcServer
from core.api.tlv.corehandlers import CoreHandler, CoreUdpHandler
from core.api.tlv.coreserver import CoreServer, CoreUdpServer
from core.emulator import enumerations
from core.utils import close_onexec
from core.utils import close_onexec, load_logging_config
def banner():
@ -44,12 +44,11 @@ def start_udp(mainserver, server_address):
mainserver.udpthread.start()
def cored(cfg, use_ovs):
def cored(cfg):
"""
Start the CoreServer object and enter the server loop.
:param dict cfg: core configuration
:param bool use_ovs: flag to determine if ovs nodes should be used
:return: nothing
"""
host = cfg["listenaddr"]
@ -60,9 +59,6 @@ def cored(cfg, use_ovs):
try:
address = (host, port)
server = CoreServer(address, CoreHandler, cfg)
if use_ovs:
from core.nodes.openvswitch import OVS_NODES
server.coreemu.update_nodes(OVS_NODES)
except:
logging.exception("error starting main server on: %s:%s", host, port)
sys.exit(1)
@ -97,7 +93,6 @@ def get_merged_config(filename):
defaults = {
"port": "%d" % enumerations.CORE_API_PORT,
"listenaddr": "localhost",
"xmlfilever": "1.0",
"numthreads": "1",
"grpcport": "50051",
"grpcaddress": "localhost",
@ -137,9 +132,9 @@ def get_merged_config(filename):
if not cfg.has_section(section):
cfg.add_section(section)
# merge command line with config file
for opt in args.__dict__:
val = args.__dict__[opt]
# merge argparse with configparser
for opt in vars(args):
val = getattr(args, opt)
if val is not None:
cfg.set(section, opt, str(val))
@ -156,11 +151,8 @@ def main():
cfg = get_merged_config("%s/core.conf" % constants.CORE_CONF_DIR)
banner()
# check if ovs flag was provided
use_ovs = len(sys.argv) == 2 and sys.argv[1] == "ovs"
try:
cored(cfg, use_ovs)
cored(cfg)
except KeyboardInterrupt:
logging.info("keyboard interrupt, stopping core daemon")

View file

@ -6,13 +6,13 @@ from xml.etree import ElementTree
import pytest
from core import CoreError
from core.emane.bypass import EmaneBypassModel
from core.emane.commeffect import EmaneCommEffectModel
from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emane.rfpipe import EmaneRfPipeModel
from core.emane.tdma import EmaneTdmaModel
from core.emulator.emudata import NodeOptions
from core.errors import CoreError
_EMANE_MODELS = [
EmaneIeee80211abgModel,

View file

@ -4,7 +4,6 @@ Unit tests for testing basic CORE networks.
import os
import stat
import subprocess
import threading
import pytest
@ -109,10 +108,6 @@ class TestCore:
p, stdin, stdout, stderr = client.popen(command)
assert not p.wait()
assert not client.icmd(command)
assert not client.redircmd(
subprocess.PIPE, subprocess.PIPE, subprocess.PIPE, command
)
assert not client.shcmd(command[0])
# check various command using command line
assert not client.cmd(command)
@ -121,15 +116,10 @@ class TestCore:
p, stdin, stdout, stderr = client.popen(command)
assert not p.wait()
assert not client.icmd(command)
assert not client.shcmd(command[0])
# check module methods
assert createclients(session.session_dir)
# check convenience methods for interface information
assert client.getaddr("eth0")
assert client.netifstats()
def test_netif(self, session, ip_prefixes):
"""
Test netif methods.

View file

@ -5,7 +5,6 @@ from queue import Queue
import grpc
import pytest
from core import CoreError
from core.api.grpc import core_pb2
from core.api.grpc.client import CoreGrpcClient
from core.config import ConfigShim
@ -18,6 +17,7 @@ from core.emulator.enumerations import (
ExceptionLevels,
NodeTypes,
)
from core.errors import CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility

View file

@ -7,7 +7,6 @@ import time
import mock
import pytest
from core import CoreError
from core.api.tlv import coreapi
from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emulator.enumerations import (
@ -24,6 +23,7 @@ from core.emulator.enumerations import (
RegisterTlvs,
SessionTlvs,
)
from core.errors import CoreError
from core.location.mobility import BasicRangeModel
from core.nodes.ipaddress import Ipv4Prefix

View file

@ -3,9 +3,10 @@ import time
import pytest
from core import CoreError, utils
from core import utils
from core.emulator.emudata import NodeOptions
from core.emulator.enumerations import NodeTypes
from core.errors import CoreError
MODELS = ["router", "host", "PC", "mdr"]

View file

@ -2,9 +2,9 @@ from xml.etree import ElementTree
import pytest
from core import CoreError
from core.emulator.emudata import LinkOptions, NodeOptions
from core.emulator.enumerations import NodeTypes
from core.errors import CoreError
from core.location.mobility import BasicRangeModel
from core.services.utility import SshService

View file

@ -42,6 +42,21 @@ Install Path | Description
/etc/init.d/core-daemon|SysV startup script for daemon
/etc/systemd/system/core-daemon.service|Systemd startup script for daemon
# Pre-Req Installing Python
You may already have these installed, and can ignore this step if so, but if
needed you can run the following to install python and pip.
```shell
# python 2
sudo apt install python
sudo apt install python-pip
# python 3
sudo apt install python3
sudo apt install python3-pip
```
# Pre-Req Python Requirements
The newly added gRPC API which depends on python library grpcio is not commonly found within system repos.
@ -91,7 +106,7 @@ Requires building from source, from the latest nightly snapshot.
```shell
# packages needed beyond what's normally required to build core on ubuntu
sudo apt install libtool libreadline-dev
sudo apt install libtool libreadline-dev autoconf
wget https://downloads.pf.itd.nrl.navy.mil/ospf-manet/nightly_snapshots/quagga-svnsnap.tgz
tar xzf quagga-svnsnap.tgz
@ -230,26 +245,26 @@ pip3 install grpcio-tools
### Ubuntu 18.04 Requirements
```shell
sudo apt install automake pkg-config gcc libev-dev bridge-utils ebtables python-dev python-setuptools tk libtk-img
sudo apt install automake pkg-config gcc libev-dev bridge-utils ebtables python-dev python-setuptools tk libtk-img ethtool
```
### Ubuntu 16.04 Requirements
```shell
sudo apt-get install automake bridge-utils ebtables python-dev libev-dev python-setuptools libtk-img
sudo apt-get install automake bridge-utils ebtables python-dev libev-dev python-setuptools libtk-img ethtool
```
### CentOS 7 with Gnome Desktop Requirements
```shell
sudo yum -y install automake gcc python-devel libev-devel tk
sudo yum -y install automake gcc python-devel libev-devel tk ethtool
```
## Build and Install
```shell
./bootstrap.sh
# use python2 or python3 depending on desired version
# $VERSION should be path to python2/3
PYTHON=$VERSION ./configure
make
sudo make install
@ -268,7 +283,7 @@ sudo apt install python3-sphinx
sudo yum install python3-sphinx
./bootstrap.sh
# use python2 or python3 depending on desired version
# $VERSION should be path to python2/3
PYTHON=$VERSION ./configure
make doc
```
@ -282,8 +297,10 @@ Build package commands, DESTDIR is used to make install into and then for packag
```shell
./bootstrap.sh
# use python2 or python3 depending on desired version
PYTHON=$VERSION ./configure
# for python2
PYTHON=python2 ./configure
# for python3
PYTHON=python3 ./configure --enable-python3
make
mkdir /tmp/core-build
make fpm DESTDIR=/tmp/core-build

View file

@ -2706,7 +2706,7 @@ proc sendNodeTypeInfo { sock reset } {
set typesinuse ""
foreach node $node_list {
set type [nodeType $node]
if { $type != "router" && $type != "OVS" } { continue }
if { $type != "router" } { continue }
set model [getNodeModel $node]
if { [lsearch $typesinuse $model] < 0 } { lappend typesinuse $model }
}
@ -2910,7 +2910,6 @@ proc getNodeTypeAPI { node } {
router { return 0x0 }
netns { return 0x0 }
jail { return 0x0 }
OVS { return 0x0 }
physical { return 0x1 }
tbd { return 0x3 }
lanswitch { return 0x4 }

View file

@ -333,7 +333,7 @@ proc redrawAll {} {
proc drawNode { c node } {
global showNodeLabels
global router pc host lanswitch rj45 hub pseudo OVS
global router pc host lanswitch rj45 hub pseudo
global curcanvas zoom
global wlan
if { $c == "" } { set c .c } ;# default canvas
@ -348,7 +348,7 @@ proc drawNode { c node } {
set cimg ""
set imgzoom $zoom
if { $zoom == 0.75 || $zoom == 1.5 } { set imgzoom 1.0 }
if { $type == "router" || $type == "OVS" } {
if { $type == "router" } {
set model [getNodeModel $node]
set cimg [getNodeTypeImage $model normal]
}
@ -1535,7 +1535,7 @@ proc raiseAll {c} {
proc button1 { c x y button } {
global node_list plot_list curcanvas zoom
global activetool activetoolp newlink curobj changed def_router_model
global router pc host lanswitch rj45 hub OVS
global router pc host lanswitch rj45 hub
global oval rectangle text
global lastX lastY
global background selectbox
@ -1607,10 +1607,7 @@ proc button1 { c x y button } {
rectangle text} $activetool] < 0 } {
if { $g_view_locked == 1 } { return }
if { $activetoolp == "routers" } {
if {$activetool != "OVS"} {
set node [newNode router]
} else {
set node [newNode OVS]}
setNodeModel $node $activetool
} else {
set node [newNode $activetool]
@ -2550,7 +2547,7 @@ proc popupConfigDialog { c } {
-side right -padx 4 -pady 4
# end Boeing
pack $wi.ftop -side top
if { $type == "router" || $type == "OVS"} {
if { $type == "router" } {
ttk::frame $wi.model -borderwidth 4
ttk::label $wi.model.label -text "Type:"

View file

@ -108,7 +108,7 @@ proc autoIPv4addr { node iface } {
set peer_node [logicalPeerByIfc $node $iface]
# find addresses of NETWORK layer peer nodes
if { [[typemodel $peer_node].layer] == "LINK" || [nodeType $peer_node] == "OVS" } {
if { [[typemodel $peer_node].layer] == "LINK" } {
foreach l2node [listLANnodes $peer_node {}] {
foreach ifc [ifcList $l2node] {
set peer [logicalPeerByIfc $l2node $ifc]

View file

@ -747,15 +747,12 @@ proc newLink { lnode1 lnode2 } {
global defLinkColor defLinkWidth
global curcanvas
global systype
if { ([nodeType $lnode1] == "lanswitch" ||[nodeType $lnode1] == "OVS") && \
if { [nodeType $lnode1] == "lanswitch" && \
[nodeType $lnode2] != "router" && \
([nodeType $lnode2] != "lanswitch" || [nodeType $lnode2] != "OVS") } {
set regular no }
if { ([nodeType $lnode2] == "lanswitch" || [nodeType $lnode2] == "OVS") && \
[nodeType $lnode2] != "lanswitch" } { set regular no }
if { [nodeType $lnode2] == "lanswitch" && \
[nodeType $lnode1] != "router" && \
([nodeType $lnode1] != "lanswitch" || [nodeType $lnode1] != "OVS" )} {
#Khaled: puts "connecting '$lnode1' (type: '[nodeType $lnode1]') to '$lnode2' (type: '[nodeType $lnode2]') "
set regular no }
[nodeType $lnode1] != "lanswitch" } { set regular no }
if { [nodeType $lnode1] == "hub" && \
[nodeType $lnode2] == "hub" } { set regular no }
# Boeing: added tunnel, ktunnel types to behave as rj45
@ -771,7 +768,7 @@ proc newLink { lnode1 lnode2 } {
set othernode $lnode1
}
# only allowed to link with certain types
if { [lsearch {router lanswitch hub pc host wlan OVS} \
if { [lsearch {router lanswitch hub pc host wlan} \
[nodeType $othernode]] < 0} {
return
}
@ -836,18 +833,13 @@ proc newLink { lnode1 lnode2 } {
} elseif {$delay != ""} {
lappend $link "delay $delay"
}
# Exclude OVS from network layer nodes IP address asignments
if { ([[typemodel $lnode2].layer] == "NETWORK") && ([nodeType $lnode2] != "OVS") } {
#Khaled: puts "Assigning '$lnode2' (type: '[nodeType $lnode2]') an automatic IP address"
if { [[typemodel $lnode2].layer] == "NETWORK" } {
if { $ipv4_addr2 == "" } { autoIPv4addr $lnode2 $ifname2 }
if { $ipv6_addr2 == "" } { autoIPv6addr $lnode2 $ifname2 }
}
# tunnels also excluded from link settings
# OVS and Lanswitch should go side by side
} elseif { (([nodeType $lnode1] == "lanswitch" || [nodeType $lnode1] == "OVS" )|| \
([nodeType $lnode2] == "lanswitch"|| [nodeType $lnode2] == "OVS") || \
} elseif { ([nodeType $lnode1] == "lanswitch" || \
[nodeType $lnode2] == "lanswitch" || \
[string first eth "$ifname1 $ifname2"] != -1) && \
[nodeType $lnode1] != "rj45" && [nodeType $lnode2] != "rj45" && \
[nodeType $lnode1] != "tunnel" && [nodeType $lnode2] != "tunnel" && \
@ -859,11 +851,9 @@ proc newLink { lnode1 lnode2 } {
}
lappend link_list $link
# Exclude OVS from Network layer node configs
if { [nodeType $lnode2] != "pseudo" &&
[nodeType $lnode1] != "wlan" &&
([[typemodel $lnode1].layer] == "NETWORK" && [nodeType $lnode1] != "OVS") } {
[[typemodel $lnode1].layer] == "NETWORK" } {
if { $ipv4_addr1 == "" && $do_auto_addressing } {
autoIPv4addr $lnode1 $ifname1
}
@ -874,8 +864,7 @@ proc newLink { lnode1 lnode2 } {
# assume wlan is always lnode1
if { [nodeType $lnode1] != "pseudo" &&
[nodeType $lnode1] != "wlan" &&
([[typemodel $lnode2].layer] == "NETWORK" && [nodeType $lnode2] != "OVS") } {
[[typemodel $lnode2].layer] == "NETWORK" } {
if { $ipv4_addr2 == "" && $do_auto_addressing } {
autoIPv4addr $lnode2 $ifname2
}

View file

@ -21,12 +21,10 @@ array set g_node_types_default {
5 {prouter router_green.gif router_green.gif \
{zebra OSPFv2 OSPFv3 IPForward} \
physical {built-in type for physical nodes}}
6 {OVS lanswitch.gif lanswitch.gif {DefaultRoute SSH OvsService} OVS {} }
}
# possible machine types for nodes
set MACHINE_TYPES "netns physical OVS"
set MACHINE_TYPES "netns physical"
# array populated from nodes.conf file
array set g_node_types { }
@ -184,7 +182,7 @@ proc getNodeTypeServices { type } {
# node type from the toolbar
proc getNodeTypeMachineType { type } {
global MACHINE_TYPES g_node_types
set default_machine_type [lindex $MACHINE_TYPES 3]
set default_machine_type [lindex $MACHINE_TYPES 0]
set i [getNodeTypeIndex $type]
if { $i < 0 } { return $default_machine_type }; # failsafe
return [lindex $g_node_types($i) 4]
@ -208,7 +206,7 @@ proc getNodeTypeProfile { type } {
# node type from the toolbar
proc getNodeTypeMachineType { type } {
global MACHINE_TYPES g_node_types
set default_machine_type [lindex $MACHINE_TYPES 3]
set default_machine_type [lindex $MACHINE_TYPES 0]
set i [getNodeTypeIndex $type]
if { $i < 0 } { return $default_machine_type }; # failsafe
return [lindex $g_node_types($i) 4]
@ -714,7 +712,6 @@ proc lanswitch.layer {} { return LINK }
proc hub.layer {} { return LINK }
proc tunnel.layer {} { return LINK }
proc wlan.layer {} { return LINK }
proc OVS.layer {} { return NETWORK }
proc router.layer {} { return NETWORK }
proc router.shellcmd { n } { return "vtysh" }

View file

@ -9,11 +9,7 @@
if WANT_PYTHON
if PYTHON3
PYTHONLIBDIR=$(libdir)/python3/dist-packages
else
PYTHONLIBDIR=$(pythondir)
endif
PYTHONLIBDIR=$(subst site-packages,dist-packages,$(pythondir))
SETUPPY = setup.py
SETUPPYFLAGS = -v

View file

@ -10,17 +10,16 @@ import sys
import ns.core
import ns.mobility
from core.nodes import nodeutils, nodemaps, ipaddress
from corens3.obj import Ns3LteNet
from corens3.obj import Ns3Session
from core.nodes import ipaddress
def ltesession(opt):
"""
Run a test LTE session.
"""
nodeutils.set_node_map(nodemaps.NODES)
session = Ns3Session(1, persistent=True, duration=opt.duration)
lte = session.create_node(cls=Ns3LteNet, name="wlan1")
lte.setsubchannels(range(25), range(50, 100))

View file

@ -27,11 +27,11 @@ import optparse
import sys
import ns.core
from core.nodes import nodeutils, nodemaps, ipaddress
from corens3.obj import Ns3Session
from corens3.obj import Ns3WifiNet
from core.nodes import ipaddress
def add_to_server(session):
"""
@ -50,7 +50,6 @@ def wifisession(opt):
"""
Run a test wifi session.
"""
nodeutils.set_node_map(nodemaps.NODES)
session = Ns3Session(1, persistent=True, duration=opt.duration)
session.name = "ns3wifi"
session.filename = session.name + ".py"

View file

@ -14,13 +14,14 @@ How to run this:
import logging
import optparse
import sys
from builtins import range
import ns.core
import ns.network
from corens3.obj import Ns3Session
from corens3.obj import Ns3WifiNet
from core.nodes import nodeutils, nodemaps, ipaddress
from core.nodes import ipaddress
def add_to_server(session):
@ -40,7 +41,6 @@ def wifisession(opt):
"""
Run a random walk wifi session.
"""
nodeutils.set_node_map(nodemaps.NODES)
session = Ns3Session(1, persistent=True, duration=opt.duration)
session.name = "ns3wifirandomwalk"
session.filename = session.name + ".py"
@ -54,7 +54,7 @@ def wifisession(opt):
prefix = ipaddress.Ipv4Prefix("10.0.0.0/16")
services_str = "zebra|OSPFv3MDR|IPForward"
nodes = []
for i in xrange(1, opt.numnodes + 1):
for i in range(1, opt.numnodes + 1):
node = session.addnode(name="n%d" % i)
node.newnetif(wifi, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)])
nodes.append(node)

View file

@ -12,17 +12,18 @@ Current issues:
import logging
import optparse
import sys
from builtins import range
from core.nodes import nodeutils, nodemaps, ipaddress
from corens3.obj import Ns3Session
from corens3.obj import Ns3WimaxNet
from core.nodes import ipaddress
def wimaxsession(opt):
"""
Run a test wimax session.
"""
nodeutils.set_node_map(nodemaps.NODES)
session = Ns3Session(1, persistent=True, duration=opt.duration)
wimax = session.create_node(cls=Ns3WimaxNet, name="wlan1")
# wimax.wimax.EnableLogComponents()
@ -33,7 +34,7 @@ def wimaxsession(opt):
# classifier = (0, 65000, 0, 65000, 1, 1)
classifier = (0, 65000, 0, 65000, 17, 1)
nodes = []
for i in xrange(1, opt.numnodes + 1):
for i in range(1, opt.numnodes + 1):
node = session.addnode(name="n%d" % i)
if i == 1:
wimax.setbasestation(node)