Merge pull request #534 from coreemu/develop

merge for 7.3.0
This commit is contained in:
bharnden 2020-12-02 10:24:17 -08:00 committed by GitHub
commit a57b838f19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 2582 additions and 536 deletions

View file

@ -1,3 +1,24 @@
## 2020-12-02 CORE 7.3.0
* core-daemon
* fixed issue where emane global configuration was not being sent to core-gui
* updated controlnet names on host to be prefixed with ctrl
* fixed RJ45 link shutdown from core-gui causing an error
* fixed emane external transport xml generation
* \#517 - update to account for radvd required directory
* \#514 - support added for session specific environment files
* \#529 - updated to configure netem limit based on delay or user specified, requires kernel 3.3+
* core-pygui
* fixed issue drawing wlan/emane link options when it should not have
* edge labels are now placed a set distance from nodes like original gui
* link color/width are now saved to xml files
* added support to configure buffer size for links
* \#525 - added support for multiple wired links between the same nodes
* \#526 - added option to hide/show links with 100% loss
* Documentation
* \#527 - typo in service documentation
* \#515 - added examples to docs for using EMANE features within a CORE context
## 2020-09-29 CORE 7.2.1
* core-daemon

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, 7.2.1)
AC_INIT(core, 7.3.0)
# autoconf and automake initialization
AC_CONFIG_SRCDIR([netns/version.h.in])

View file

@ -128,6 +128,7 @@ def add_link_data(
options.mer = options_proto.mer
options.burst = options_proto.burst
options.mburst = options_proto.mburst
options.buffer = options_proto.buffer
options.unidirectional = options_proto.unidirectional
options.key = options_proto.key
return iface1_data, iface2_data, options, link_type
@ -329,6 +330,7 @@ def convert_link_options(options_data: LinkOptions) -> core_pb2.LinkOptions:
burst=options_data.burst,
delay=options_data.delay,
dup=options_data.dup,
buffer=options_data.buffer,
unidirectional=options_data.unidirectional,
)

View file

@ -972,6 +972,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
mburst=options_proto.mburst,
unidirectional=options_proto.unidirectional,
key=options_proto.key,
buffer=options_proto.buffer,
)
session.update_link(node1_id, node2_id, iface1_id, iface2_id, options)
iface1 = InterfaceData(id=iface1_id)

View file

@ -457,6 +457,7 @@ class LinkOptions:
delay: int = 0
dup: int = 0
unidirectional: bool = False
buffer: int = 0
@classmethod
def from_proto(cls, proto: core_pb2.LinkOptions) -> "LinkOptions":
@ -471,6 +472,7 @@ class LinkOptions:
delay=proto.delay,
dup=proto.dup,
unidirectional=proto.unidirectional,
buffer=proto.buffer,
)
def to_proto(self) -> core_pb2.LinkOptions:
@ -485,6 +487,7 @@ class LinkOptions:
delay=self.delay,
dup=self.dup,
unidirectional=self.unidirectional,
buffer=self.buffer,
)

View file

@ -52,6 +52,7 @@ from core.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
from core.nodes.network import WlanNode
from core.nodes.physical import Rj45Node
from core.services.coreservices import ServiceManager, ServiceShim
@ -787,20 +788,30 @@ class CoreHandler(socketserver.BaseRequestHandler):
options = LinkOptions()
options.delay = message.get_tlv(LinkTlvs.DELAY.value)
options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value)
options.loss = message.get_tlv(LinkTlvs.LOSS.value)
options.dup = message.get_tlv(LinkTlvs.DUP.value)
options.jitter = message.get_tlv(LinkTlvs.JITTER.value)
options.mer = message.get_tlv(LinkTlvs.MER.value)
options.burst = message.get_tlv(LinkTlvs.BURST.value)
options.mburst = message.get_tlv(LinkTlvs.MBURST.value)
options.unidirectional = message.get_tlv(LinkTlvs.UNIDIRECTIONAL.value)
options.key = message.get_tlv(LinkTlvs.KEY.value)
loss = message.get_tlv(LinkTlvs.LOSS.value)
dup = message.get_tlv(LinkTlvs.DUP.value)
if loss is not None:
options.loss = float(loss)
if dup is not None:
options.dup = int(dup)
if message.flags & MessageFlags.ADD.value:
self.session.add_link(
node1_id, node2_id, iface1_data, iface2_data, options, link_type
)
elif message.flags & MessageFlags.DELETE.value:
node1 = self.session.get_node(node1_id, NodeBase)
node2 = self.session.get_node(node2_id, NodeBase)
if isinstance(node1, Rj45Node):
iface1_data.id = node1.iface_id
if isinstance(node2, Rj45Node):
iface2_data.id = node2.iface_id
self.session.delete_link(
node1_id, node2_id, iface1_data.id, iface2_data.id, link_type
)
@ -1851,7 +1862,15 @@ class CoreHandler(socketserver.BaseRequestHandler):
)
self.session.broadcast_config(config_data)
# send emane model info
# send global emane config
config = self.session.emane.get_configs()
logging.debug("global emane config: values(%s)", config)
config_data = ConfigShim.config_data(
0, None, ConfigFlags.UPDATE.value, self.session.emane.emane_config, config
)
self.session.broadcast_config(config_data)
# send emane model configs
for node_id in self.session.emane.nodes():
emane_configs = self.session.emane.get_all_configs(node_id)
for model_name in emane_configs:

View file

@ -5,7 +5,7 @@ from core.config import Configuration
from core.configservice.base import ConfigService, ConfigServiceMode
from core.emane.nodes import EmaneNet
from core.nodes.base import CoreNodeBase
from core.nodes.interface import CoreInterface
from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import WlanNode
GROUP: str = "FRR"
@ -18,7 +18,7 @@ def has_mtu_mismatch(iface: CoreInterface) -> bool:
mtu-ignore command. This is needed when e.g. a node is linked via a
GreTap device.
"""
if iface.mtu != 1500:
if iface.mtu != DEFAULT_MTU:
return True
if not iface.net:
return False

View file

@ -6,7 +6,7 @@ from core.config import Configuration
from core.configservice.base import ConfigService, ConfigServiceMode
from core.emane.nodes import EmaneNet
from core.nodes.base import CoreNodeBase
from core.nodes.interface import CoreInterface
from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import WlanNode
GROUP: str = "Quagga"
@ -19,7 +19,7 @@ def has_mtu_mismatch(iface: CoreInterface) -> bool:
mtu-ignore command. This is needed when e.g. a node is linked via a
GreTap device.
"""
if iface.mtu != 1500:
if iface.mtu != DEFAULT_MTU:
return True
if not iface.net:
return False

View file

@ -217,7 +217,7 @@ class PcapService(ConfigService):
class RadvdService(ConfigService):
name: str = "radvd"
group: str = GROUP_NAME
directories: List[str] = ["/etc/radvd"]
directories: List[str] = ["/etc/radvd", "/var/run/radvd"]
files: List[str] = ["/etc/radvd/radvd.conf"]
executables: List[str] = ["radvd"]
dependencies: List[str] = []

View file

@ -172,6 +172,7 @@ class LinkOptions:
mburst: int = None
unidirectional: int = None
key: int = None
buffer: int = None
@dataclass

View file

@ -12,6 +12,7 @@ import sys
import tempfile
import threading
import time
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar
from core import constants, utils
@ -997,28 +998,25 @@ class Session:
env["SESSION_USER"] = str(self.user)
if state:
env["SESSION_STATE"] = str(self.state)
# attempt to read and add environment config file
environment_config_file = os.path.join(constants.CORE_CONF_DIR, "environment")
try:
if os.path.isfile(environment_config_file):
utils.load_config(environment_config_file, env)
except IOError:
logging.warning(
"environment configuration file does not exist: %s",
environment_config_file,
)
# attempt to read and add user environment file
# try reading and merging optional environments from:
# /etc/core/environment
# /home/user/.core/environment
# /tmp/pycore.<session id>/environment
core_env_path = Path(constants.CORE_CONF_DIR) / "environment"
session_env_path = Path(self.session_dir) / "environment"
if self.user:
environment_user_file = os.path.join(
"/home", self.user, ".core", "environment"
)
try:
utils.load_config(environment_user_file, env)
except IOError:
logging.debug(
"user core environment settings file not present: %s",
environment_user_file,
)
user_home_path = Path(f"~{self.user}").expanduser()
user_env1 = user_home_path / ".core" / "environment"
user_env2 = user_home_path / ".coregui" / "environment"
paths = [core_env_path, user_env1, user_env2, session_env_path]
else:
paths = [core_env_path, session_env_path]
for path in paths:
if path.is_file():
try:
utils.load_config(path, env)
except IOError:
logging.exception("error reading environment file: %s", path)
return env
def set_thumbnail(self, thumb_file: str) -> None:
@ -1447,12 +1445,14 @@ class Session:
)
control_net = self.create_node(
CtrlNet,
True,
prefix,
start=False,
prefix=prefix,
_id=_id,
updown_script=updown_script,
serverintf=server_iface,
)
control_net.brname = f"ctrl{net_index}.{self.short_session_id()}"
control_net.startup()
return control_net
def add_remove_control_iface(

View file

@ -87,14 +87,14 @@ class CoreClient:
self.read_config()
# helpers
self.iface_to_edge: Dict[Tuple[int, ...], Tuple[int, ...]] = {}
self.iface_to_edge: Dict[Tuple[int, ...], CanvasEdge] = {}
self.ifaces_manager: InterfaceManager = InterfaceManager(self.app)
self.observer: Optional[str] = None
# session data
self.mobility_players: Dict[int, MobilityPlayer] = {}
self.canvas_nodes: Dict[int, CanvasNode] = {}
self.links: Dict[Tuple[int, int], CanvasEdge] = {}
self.links: Dict[str, CanvasEdge] = {}
self.handling_throughputs: Optional[grpc.Future] = None
self.handling_cpu_usage: Optional[grpc.Future] = None
self.handling_events: Optional[grpc.Future] = None
@ -225,11 +225,9 @@ class CoreClient:
self.app.canvas.add_wired_edge(canvas_node1, canvas_node2, event.link)
self.app.canvas.organize()
elif event.message_type == MessageType.DELETE:
self.app.canvas.delete_wired_edge(canvas_node1, canvas_node2)
self.app.canvas.delete_wired_edge(event.link)
elif event.message_type == MessageType.NONE:
self.app.canvas.update_wired_edge(
canvas_node1, canvas_node2, event.link
)
self.app.canvas.update_wired_edge(event.link)
else:
logging.warning("unknown link event: %s", event)
@ -383,6 +381,17 @@ class CoreClient:
except ValueError:
logging.exception("unknown shape: %s", shape_type)
# load edges config
edges_config = config.get("edges")
if edges_config:
edges_config = json.loads(edges_config)
logging.info("edges config: %s", edges_config)
for edge_config in edges_config:
edge = self.links[edge_config["token"]]
edge.width = edge_config["width"]
edge.color = edge_config["color"]
edge.redraw()
def create_new_session(self) -> None:
"""
Create a new session
@ -570,7 +579,15 @@ class CoreClient:
shapes.append(shape.metadata())
shapes = json.dumps(shapes)
metadata = {"canvas": canvas_config, "shapes": shapes}
# create edges config
edges_config = []
for edge in self.links.values():
edge_config = dict(token=edge.token, width=edge.width, color=edge.color)
edges_config.append(edge_config)
edges_config = json.dumps(edges_config)
# save metadata
metadata = dict(canvas=canvas_config, shapes=shapes, edges=edges_config)
response = self.client.set_session_metadata(self.session.id, metadata)
logging.debug("set session metadata %s, result: %s", metadata, response)
@ -877,7 +894,7 @@ class CoreClient:
def create_link(
self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode
) -> None:
) -> Link:
"""
Create core link for a pair of canvas nodes, with token referencing
the canvas edge.
@ -888,15 +905,9 @@ class CoreClient:
src_iface = None
if NodeUtils.is_container_node(src_node.type):
src_iface = self.create_iface(canvas_src_node)
self.iface_to_edge[(src_node.id, src_iface.id)] = edge.token
edge.src_iface = src_iface
canvas_src_node.ifaces[src_iface.id] = src_iface
dst_iface = None
if NodeUtils.is_container_node(dst_node.type):
dst_iface = self.create_iface(canvas_dst_node)
self.iface_to_edge[(dst_node.id, dst_iface.id)] = edge.token
edge.dst_iface = dst_iface
canvas_dst_node.ifaces[dst_iface.id] = dst_iface
link = Link(
type=LinkType.WIRED,
node1_id=src_node.id,
@ -904,9 +915,21 @@ class CoreClient:
iface1=src_iface,
iface2=dst_iface,
)
edge.set_link(link)
self.links[edge.token] = edge
logging.info("added link between %s and %s", src_node.name, dst_node.name)
return link
def save_edge(
self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode
) -> None:
self.links[edge.token] = edge
src_node = canvas_src_node.core_node
dst_node = canvas_dst_node.core_node
if NodeUtils.is_container_node(src_node.type):
src_iface_id = edge.link.iface1.id
self.iface_to_edge[(src_node.id, src_iface_id)] = edge
if NodeUtils.is_container_node(dst_node.type):
dst_iface_id = edge.link.iface2.id
self.iface_to_edge[(dst_node.id, dst_iface_id)] = edge
def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]:
configs = []

View file

@ -0,0 +1,340 @@
<?xml version='1.0' encoding='UTF-8'?>
<scenario name="/home/developer/.core/configs/emane-demo-antenna.xml">
<networks>
<network id="5" name="wlan5" model="emane_rfpipe" type="EMANE">
<position x="388" y="555" lat="47.57412169587584" lon="-122.12709380504643" alt="2.0"/>
</network>
</networks>
<devices>
<device id="1" name="n1" type="mdr" class="" image="">
<position x="258" y="147" lat="47.577830502987744" lon="-122.12884551985047" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</services>
</device>
<device id="2" name="n2" type="mdr" class="" image="">
<position x="526" y="147" lat="47.577830502987744" lon="-122.12523429240828" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</services>
</device>
<device id="3" name="n3" type="mdr" class="" image="">
<position x="241" y="387" lat="47.57564888355958" lon="-122.12907459024791" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</services>
</device>
<device id="4" name="n4" type="mdr" class="" image="">
<position x="529" y="385" lat="47.57566706409707" lon="-122.1251938682205" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</services>
</device>
</devices>
<links>
<link node1="5" node2="1">
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
</link>
<link node1="5" node2="2">
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
</link>
<link node1="5" node2="3">
<iface2 nem="3" id="0" name="eth0" mac="02:02:00:00:00:03" ip4="10.0.0.3" ip4_mask="32" ip6="2001::3" ip6_mask="128"/>
</link>
<link node1="5" node2="4">
<iface2 nem="4" id="0" name="eth0" mac="02:02:00:00:00:04" ip4="10.0.0.4" ip4_mask="32" ip6="2001::4" ip6_mask="128"/>
</link>
</links>
<emane_global_configuration>
<emulator>
<configuration name="antennaprofilemanifesturi" value="/tmp/emane/antennaprofile.xml"/>
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</emulator>
<core>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
</core>
</emane_global_configuration>
<emane_configurations>
<emane_configuration node="5" model="emane_rfpipe">
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
<configuration name="enablepromiscuousmode" value="0"/>
<configuration name="flowcontrolenable" value="0"/>
<configuration name="flowcontroltokens" value="10"/>
<configuration name="jitter" value="0.000000"/>
<configuration name="neighbormetricdeletetime" value="60.000000"/>
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
<configuration name="radiometricenable" value="0"/>
<configuration name="radiometricreportinterval" value="1.000000"/>
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
<configuration name="fading.nakagami.m0" value="0.750000"/>
<configuration name="fading.nakagami.m1" value="1.000000"/>
<configuration name="fading.nakagami.m2" value="200.000000"/>
<configuration name="fixedantennagain" value="0.000000"/>
<configuration name="fixedantennagainenable" value="1"/>
<configuration name="frequency" value="2347000000"/>
<configuration name="frequencyofinterest" value="2347000000"/>
<configuration name="noisebinsize" value="20"/>
<configuration name="noisemaxclampenable" value="0"/>
<configuration name="noisemaxmessagepropagation" value="200000"/>
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="none"/>
<configuration name="propagationmodel" value="2ray"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
<configuration name="txpower" value="0.000000"/>
</phy>
<external>
<configuration name="external" value="0"/>
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
</external>
</emane_configuration>
<emane_configuration node="1" model="emane_rfpipe">
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
<configuration name="enablepromiscuousmode" value="0"/>
<configuration name="flowcontrolenable" value="0"/>
<configuration name="flowcontroltokens" value="10"/>
<configuration name="jitter" value="0.000000"/>
<configuration name="neighbormetricdeletetime" value="60.000000"/>
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
<configuration name="radiometricenable" value="0"/>
<configuration name="radiometricreportinterval" value="1.000000"/>
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
<configuration name="fading.nakagami.m0" value="0.750000"/>
<configuration name="fading.nakagami.m1" value="1.000000"/>
<configuration name="fading.nakagami.m2" value="200.000000"/>
<configuration name="fixedantennagain" value="0.000000"/>
<configuration name="fixedantennagainenable" value="0"/>
<configuration name="frequency" value="2347000000"/>
<configuration name="frequencyofinterest" value="2347000000"/>
<configuration name="noisebinsize" value="20"/>
<configuration name="noisemaxclampenable" value="0"/>
<configuration name="noisemaxmessagepropagation" value="200000"/>
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="outofband"/>
<configuration name="propagationmodel" value="precomputed"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
<configuration name="txpower" value="0.000000"/>
</phy>
<external>
<configuration name="external" value="0"/>
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
</external>
</emane_configuration>
<emane_configuration node="2" model="emane_rfpipe">
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
<configuration name="enablepromiscuousmode" value="0"/>
<configuration name="flowcontrolenable" value="0"/>
<configuration name="flowcontroltokens" value="10"/>
<configuration name="jitter" value="0.000000"/>
<configuration name="neighbormetricdeletetime" value="60.000000"/>
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
<configuration name="radiometricenable" value="0"/>
<configuration name="radiometricreportinterval" value="1.000000"/>
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
<configuration name="fading.nakagami.m0" value="0.750000"/>
<configuration name="fading.nakagami.m1" value="1.000000"/>
<configuration name="fading.nakagami.m2" value="200.000000"/>
<configuration name="fixedantennagain" value="0.000000"/>
<configuration name="fixedantennagainenable" value="1"/>
<configuration name="frequency" value="2347000000"/>
<configuration name="frequencyofinterest" value="2347000000"/>
<configuration name="noisebinsize" value="20"/>
<configuration name="noisemaxclampenable" value="0"/>
<configuration name="noisemaxmessagepropagation" value="200000"/>
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="outofband"/>
<configuration name="propagationmodel" value="precomputed"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
<configuration name="txpower" value="0.000000"/>
</phy>
<external>
<configuration name="external" value="0"/>
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
</external>
</emane_configuration>
<emane_configuration node="3" model="emane_rfpipe">
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
<configuration name="enablepromiscuousmode" value="0"/>
<configuration name="flowcontrolenable" value="0"/>
<configuration name="flowcontroltokens" value="10"/>
<configuration name="jitter" value="0.000000"/>
<configuration name="neighbormetricdeletetime" value="60.000000"/>
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
<configuration name="radiometricenable" value="0"/>
<configuration name="radiometricreportinterval" value="1.000000"/>
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
<configuration name="fading.nakagami.m0" value="0.750000"/>
<configuration name="fading.nakagami.m1" value="1.000000"/>
<configuration name="fading.nakagami.m2" value="200.000000"/>
<configuration name="fixedantennagain" value="5.000000"/>
<configuration name="fixedantennagainenable" value="1"/>
<configuration name="frequency" value="2347000000"/>
<configuration name="frequencyofinterest" value="2347000000"/>
<configuration name="noisebinsize" value="20"/>
<configuration name="noisemaxclampenable" value="0"/>
<configuration name="noisemaxmessagepropagation" value="200000"/>
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="outofband"/>
<configuration name="propagationmodel" value="precomputed"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
<configuration name="txpower" value="0.000000"/>
</phy>
<external>
<configuration name="external" value="0"/>
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
</external>
</emane_configuration>
<emane_configuration node="4" model="emane_rfpipe">
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
<configuration name="enablepromiscuousmode" value="0"/>
<configuration name="flowcontrolenable" value="0"/>
<configuration name="flowcontroltokens" value="10"/>
<configuration name="jitter" value="0.000000"/>
<configuration name="neighbormetricdeletetime" value="60.000000"/>
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
<configuration name="radiometricenable" value="0"/>
<configuration name="radiometricreportinterval" value="1.000000"/>
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
<configuration name="fading.nakagami.m0" value="0.750000"/>
<configuration name="fading.nakagami.m1" value="1.000000"/>
<configuration name="fading.nakagami.m2" value="200.000000"/>
<configuration name="fixedantennagain" value="0.000000"/>
<configuration name="fixedantennagainenable" value="0"/>
<configuration name="frequency" value="2347000000"/>
<configuration name="frequencyofinterest" value="2347000000"/>
<configuration name="noisebinsize" value="20"/>
<configuration name="noisemaxclampenable" value="0"/>
<configuration name="noisemaxmessagepropagation" value="200000"/>
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="outofband"/>
<configuration name="propagationmodel" value="precomputed"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
<configuration name="txpower" value="0.000000"/>
</phy>
<external>
<configuration name="external" value="0"/>
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
</external>
</emane_configuration>
</emane_configurations>
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
<session_options>
<configuration name="controlnet" value="172.16.0.0/24"/>
<configuration name="controlnet0" value=""/>
<configuration name="controlnet1" value=""/>
<configuration name="controlnet2" value=""/>
<configuration name="controlnet3" value=""/>
<configuration name="controlnet_updown_script" value=""/>
<configuration name="enablerj45" value="1"/>
<configuration name="preservedir" value="0"/>
<configuration name="enablesdt" value="0"/>
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
<configuration name="ovs" value="0"/>
</session_options>
<session_metadata>
<configuration name="canvas c1" value="{name {Canvas1}}"/>
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
</session_metadata>
<default_services>
<node type="mdr">
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</node>
<node type="PC">
<service name="DefaultRoute"/>
</node>
<node type="prouter"/>
<node type="router">
<service name="zebra"/>
<service name="OSPFv2"/>
<service name="OSPFv3"/>
<service name="IPForward"/>
</node>
<node type="host">
<service name="DefaultRoute"/>
<service name="SSH"/>
</node>
</default_services>
</scenario>

View file

@ -0,0 +1,146 @@
<?xml version='1.0' encoding='UTF-8'?>
<scenario name="/home/developer/.core/configs/emane-demo-eel.xml">
<networks>
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
</network>
</networks>
<devices>
<device id="1" name="n1" type="mdr" class="" image="">
<position x="153" y="172" lat="47.57760325520506" lon="-122.13026036642295" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</services>
</device>
<device id="2" name="n2" type="mdr" class="" image="">
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</services>
</device>
</devices>
<links>
<link node1="3" node2="1">
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
</link>
<link node1="3" node2="2">
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
</link>
</links>
<emane_global_configuration>
<emulator>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</emulator>
<core>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
</core>
</emane_global_configuration>
<emane_configurations>
<emane_configuration node="3" model="emane_rfpipe">
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
<configuration name="enablepromiscuousmode" value="0"/>
<configuration name="flowcontrolenable" value="0"/>
<configuration name="flowcontroltokens" value="10"/>
<configuration name="jitter" value="0.000000"/>
<configuration name="neighbormetricdeletetime" value="60.000000"/>
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
<configuration name="radiometricenable" value="0"/>
<configuration name="radiometricreportinterval" value="1.000000"/>
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
<configuration name="fading.nakagami.m0" value="0.750000"/>
<configuration name="fading.nakagami.m1" value="1.000000"/>
<configuration name="fading.nakagami.m2" value="200.000000"/>
<configuration name="fixedantennagain" value="0.000000"/>
<configuration name="fixedantennagainenable" value="1"/>
<configuration name="frequency" value="2347000000"/>
<configuration name="frequencyofinterest" value="2347000000"/>
<configuration name="noisebinsize" value="20"/>
<configuration name="noisemaxclampenable" value="0"/>
<configuration name="noisemaxmessagepropagation" value="200000"/>
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="none"/>
<configuration name="propagationmodel" value="precomputed"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
<configuration name="txpower" value="0.000000"/>
</phy>
<external>
<configuration name="external" value="0"/>
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
</external>
</emane_configuration>
</emane_configurations>
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
<session_options>
<configuration name="controlnet" value="172.16.0.0/24"/>
<configuration name="controlnet0" value=""/>
<configuration name="controlnet1" value=""/>
<configuration name="controlnet2" value=""/>
<configuration name="controlnet3" value=""/>
<configuration name="controlnet_updown_script" value=""/>
<configuration name="enablerj45" value="1"/>
<configuration name="preservedir" value="0"/>
<configuration name="enablesdt" value="0"/>
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
<configuration name="ovs" value="0"/>
</session_options>
<session_metadata>
<configuration name="canvas c1" value="{name {Canvas1}}"/>
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
</session_metadata>
<default_services>
<node type="mdr">
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</node>
<node type="PC">
<service name="DefaultRoute"/>
</node>
<node type="prouter"/>
<node type="router">
<service name="zebra"/>
<service name="OSPFv2"/>
<service name="OSPFv3"/>
<service name="IPForward"/>
</node>
<node type="host">
<service name="DefaultRoute"/>
<service name="SSH"/>
</node>
</default_services>
</scenario>

View file

@ -0,0 +1,146 @@
<?xml version='1.0' encoding='UTF-8'?>
<scenario name="/home/developer/.core/configs/emane-demo-files.xml">
<networks>
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
</network>
</networks>
<devices>
<device id="1" name="n1" type="mdr" class="" image="">
<position x="153" y="173" lat="47.57759416527324" lon="-122.13026036642295" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</services>
</device>
<device id="2" name="n2" type="mdr" class="" image="">
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</services>
</device>
</devices>
<links>
<link node1="3" node2="1">
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
</link>
<link node1="3" node2="2">
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
</link>
</links>
<emane_global_configuration>
<emulator>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</emulator>
<core>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
</core>
</emane_global_configuration>
<emane_configurations>
<emane_configuration node="3" model="emane_rfpipe">
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
<configuration name="enablepromiscuousmode" value="0"/>
<configuration name="flowcontrolenable" value="0"/>
<configuration name="flowcontroltokens" value="10"/>
<configuration name="jitter" value="0.000000"/>
<configuration name="neighbormetricdeletetime" value="60.000000"/>
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
<configuration name="radiometricenable" value="0"/>
<configuration name="radiometricreportinterval" value="1.000000"/>
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
<configuration name="fading.nakagami.m0" value="0.750000"/>
<configuration name="fading.nakagami.m1" value="1.000000"/>
<configuration name="fading.nakagami.m2" value="200.000000"/>
<configuration name="fixedantennagain" value="0.000000"/>
<configuration name="fixedantennagainenable" value="1"/>
<configuration name="frequency" value="2347000000"/>
<configuration name="frequencyofinterest" value="2347000000"/>
<configuration name="noisebinsize" value="20"/>
<configuration name="noisemaxclampenable" value="0"/>
<configuration name="noisemaxmessagepropagation" value="200000"/>
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="none"/>
<configuration name="propagationmodel" value="2ray"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
<configuration name="txpower" value="0.000000"/>
</phy>
<external>
<configuration name="external" value="0"/>
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
</external>
</emane_configuration>
</emane_configurations>
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
<session_options>
<configuration name="controlnet" value="172.16.0.0/24"/>
<configuration name="controlnet0" value=""/>
<configuration name="controlnet1" value=""/>
<configuration name="controlnet2" value=""/>
<configuration name="controlnet3" value=""/>
<configuration name="controlnet_updown_script" value=""/>
<configuration name="enablerj45" value="1"/>
<configuration name="preservedir" value="0"/>
<configuration name="enablesdt" value="0"/>
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
<configuration name="ovs" value="0"/>
</session_options>
<session_metadata>
<configuration name="canvas c1" value="{name {Canvas1}}"/>
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
</session_metadata>
<default_services>
<node type="mdr">
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</node>
<node type="PC">
<service name="DefaultRoute"/>
</node>
<node type="prouter"/>
<node type="router">
<service name="zebra"/>
<service name="OSPFv2"/>
<service name="OSPFv3"/>
<service name="IPForward"/>
</node>
<node type="host">
<service name="DefaultRoute"/>
<service name="SSH"/>
</node>
</default_services>
</scenario>

View file

@ -0,0 +1,146 @@
<?xml version='1.0' encoding='UTF-8'?>
<scenario name="/home/developer/.core/configs/emane-demo-gpsd.xml">
<networks>
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
</network>
</networks>
<devices>
<device id="1" name="n1" type="mdr" class="" image="">
<position x="153" y="173" lat="47.57759416527324" lon="-122.13026036642295" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</services>
</device>
<device id="2" name="n2" type="mdr" class="" image="">
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</services>
</device>
</devices>
<links>
<link node1="3" node2="1">
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
</link>
<link node1="3" node2="2">
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
</link>
</links>
<emane_global_configuration>
<emulator>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</emulator>
<core>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
</core>
</emane_global_configuration>
<emane_configurations>
<emane_configuration node="3" model="emane_rfpipe">
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
<configuration name="enablepromiscuousmode" value="0"/>
<configuration name="flowcontrolenable" value="0"/>
<configuration name="flowcontroltokens" value="10"/>
<configuration name="jitter" value="0.000000"/>
<configuration name="neighbormetricdeletetime" value="60.000000"/>
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
<configuration name="radiometricenable" value="0"/>
<configuration name="radiometricreportinterval" value="1.000000"/>
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
<configuration name="fading.nakagami.m0" value="0.750000"/>
<configuration name="fading.nakagami.m1" value="1.000000"/>
<configuration name="fading.nakagami.m2" value="200.000000"/>
<configuration name="fixedantennagain" value="0.000000"/>
<configuration name="fixedantennagainenable" value="1"/>
<configuration name="frequency" value="2347000000"/>
<configuration name="frequencyofinterest" value="2347000000"/>
<configuration name="noisebinsize" value="20"/>
<configuration name="noisemaxclampenable" value="0"/>
<configuration name="noisemaxmessagepropagation" value="200000"/>
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="none"/>
<configuration name="propagationmodel" value="2ray"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
<configuration name="txpower" value="0.000000"/>
</phy>
<external>
<configuration name="external" value="0"/>
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
</external>
</emane_configuration>
</emane_configurations>
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
<session_options>
<configuration name="controlnet" value="172.16.0.0/24"/>
<configuration name="controlnet0" value=""/>
<configuration name="controlnet1" value=""/>
<configuration name="controlnet2" value=""/>
<configuration name="controlnet3" value=""/>
<configuration name="controlnet_updown_script" value=""/>
<configuration name="enablerj45" value="1"/>
<configuration name="preservedir" value="0"/>
<configuration name="enablesdt" value="0"/>
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
<configuration name="ovs" value="0"/>
</session_options>
<session_metadata>
<configuration name="canvas c1" value="{name {Canvas1}}"/>
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
</session_metadata>
<default_services>
<node type="mdr">
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</node>
<node type="PC">
<service name="DefaultRoute"/>
</node>
<node type="prouter"/>
<node type="router">
<service name="zebra"/>
<service name="OSPFv2"/>
<service name="OSPFv3"/>
<service name="IPForward"/>
</node>
<node type="host">
<service name="DefaultRoute"/>
<service name="SSH"/>
</node>
</default_services>
</scenario>

View file

@ -0,0 +1,146 @@
<?xml version='1.0' encoding='UTF-8'?>
<scenario name="/home/developer/.core/configs/emane-demo-precomputed.xml">
<networks>
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
</network>
</networks>
<devices>
<device id="1" name="n1" type="mdr" class="" image="">
<position x="153" y="172" lat="47.57760325520506" lon="-122.13026036642295" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</services>
</device>
<device id="2" name="n2" type="mdr" class="" image="">
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</services>
</device>
</devices>
<links>
<link node1="3" node2="1">
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
</link>
<link node1="3" node2="2">
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
</link>
</links>
<emane_global_configuration>
<emulator>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</emulator>
<core>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
</core>
</emane_global_configuration>
<emane_configurations>
<emane_configuration node="3" model="emane_rfpipe">
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
<configuration name="enablepromiscuousmode" value="0"/>
<configuration name="flowcontrolenable" value="0"/>
<configuration name="flowcontroltokens" value="10"/>
<configuration name="jitter" value="0.000000"/>
<configuration name="neighbormetricdeletetime" value="60.000000"/>
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
<configuration name="radiometricenable" value="0"/>
<configuration name="radiometricreportinterval" value="1.000000"/>
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
<configuration name="fading.nakagami.m0" value="0.750000"/>
<configuration name="fading.nakagami.m1" value="1.000000"/>
<configuration name="fading.nakagami.m2" value="200.000000"/>
<configuration name="fixedantennagain" value="0.000000"/>
<configuration name="fixedantennagainenable" value="1"/>
<configuration name="frequency" value="2347000000"/>
<configuration name="frequencyofinterest" value="2347000000"/>
<configuration name="noisebinsize" value="20"/>
<configuration name="noisemaxclampenable" value="0"/>
<configuration name="noisemaxmessagepropagation" value="200000"/>
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="none"/>
<configuration name="propagationmodel" value="precomputed"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
<configuration name="txpower" value="0.000000"/>
</phy>
<external>
<configuration name="external" value="0"/>
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
</external>
</emane_configuration>
</emane_configurations>
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
<session_options>
<configuration name="controlnet" value="172.16.0.0/24"/>
<configuration name="controlnet0" value=""/>
<configuration name="controlnet1" value=""/>
<configuration name="controlnet2" value=""/>
<configuration name="controlnet3" value=""/>
<configuration name="controlnet_updown_script" value=""/>
<configuration name="enablerj45" value="1"/>
<configuration name="preservedir" value="0"/>
<configuration name="enablesdt" value="0"/>
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
<configuration name="ovs" value="0"/>
</session_options>
<session_metadata>
<configuration name="canvas c1" value="{name {Canvas1}}"/>
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
</session_metadata>
<default_services>
<node type="mdr">
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
</node>
<node type="PC">
<service name="DefaultRoute"/>
</node>
<node type="prouter"/>
<node type="router">
<service name="zebra"/>
<service name="OSPFv2"/>
<service name="OSPFv3"/>
<service name="IPForward"/>
</node>
<node type="host">
<service name="DefaultRoute"/>
<service name="SSH"/>
</node>
</default_services>
</scenario>

View file

@ -49,16 +49,18 @@ class LinkConfigurationDialog(Dialog):
self.jitter: tk.StringVar = tk.StringVar()
self.loss: tk.StringVar = tk.StringVar()
self.duplicate: tk.StringVar = tk.StringVar()
self.buffer: tk.StringVar = tk.StringVar()
self.down_bandwidth: tk.StringVar = tk.StringVar()
self.down_delay: tk.StringVar = tk.StringVar()
self.down_jitter: tk.StringVar = tk.StringVar()
self.down_loss: tk.StringVar = tk.StringVar()
self.down_duplicate: tk.StringVar = tk.StringVar()
self.down_buffer: tk.StringVar = tk.StringVar()
self.color: tk.StringVar = tk.StringVar(value="#000000")
self.color: tk.StringVar = tk.StringVar(value=self.edge.color)
self.color_button: Optional[tk.Button] = None
self.width: tk.DoubleVar = tk.DoubleVar()
self.width: tk.DoubleVar = tk.DoubleVar(value=self.edge.width)
self.load_link_config()
self.symmetric_frame: Optional[ttk.Frame] = None
@ -68,10 +70,14 @@ class LinkConfigurationDialog(Dialog):
def draw(self) -> None:
self.top.columnconfigure(0, weight=1)
source_name = self.app.canvas.nodes[self.edge.src].core_node.name
dest_name = self.app.canvas.nodes[self.edge.dst].core_node.name
src_label = self.app.canvas.nodes[self.edge.src].core_node.name
if self.edge.link.iface1:
src_label += f":{self.edge.link.iface1.name}"
dst_label = self.app.canvas.nodes[self.edge.dst].core_node.name
if self.edge.link.iface2:
dst_label += f":{self.edge.link.iface2.name}"
label = ttk.Label(
self.top, text=f"Link from {source_name} to {dest_name}", anchor=tk.CENTER
self.top, text=f"{src_label} to {dst_label}", anchor=tk.CENTER
)
label.grid(row=0, column=0, sticky=tk.EW, pady=PADY)
@ -183,6 +189,19 @@ class LinkConfigurationDialog(Dialog):
entry.grid(row=row, column=2, sticky=tk.EW, pady=PADY)
row = row + 1
label = ttk.Label(frame, text="Buffer (Packets)")
label.grid(row=row, column=0, sticky=tk.EW)
entry = validation.PositiveIntEntry(
frame, empty_enabled=False, textvariable=self.buffer
)
entry.grid(row=row, column=1, sticky=tk.EW, pady=PADY)
if not self.is_symmetric:
entry = validation.PositiveIntEntry(
frame, empty_enabled=False, textvariable=self.down_buffer
)
entry.grid(row=row, column=2, sticky=tk.EW, pady=PADY)
row = row + 1
label = ttk.Label(frame, text="Color")
label.grid(row=row, column=0, sticky=tk.EW)
self.color_button = tk.Button(
@ -213,16 +232,22 @@ class LinkConfigurationDialog(Dialog):
self.color_button.config(background=color)
def click_apply(self) -> None:
self.app.canvas.itemconfigure(self.edge.id, width=self.width.get())
self.app.canvas.itemconfigure(self.edge.id, fill=self.color.get())
self.edge.width = self.width.get()
self.edge.color = self.color.get()
link = self.edge.link
bandwidth = get_int(self.bandwidth)
jitter = get_int(self.jitter)
delay = get_int(self.delay)
duplicate = get_int(self.duplicate)
buffer = get_int(self.buffer)
loss = get_float(self.loss)
options = LinkOptions(
bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, loss=loss
bandwidth=bandwidth,
jitter=jitter,
delay=delay,
dup=duplicate,
loss=loss,
buffer=buffer,
)
link.options = options
iface1_id = link.iface1.id if link.iface1 else None
@ -239,6 +264,7 @@ class LinkConfigurationDialog(Dialog):
down_jitter = get_int(self.down_jitter)
down_delay = get_int(self.down_delay)
down_duplicate = get_int(self.down_duplicate)
down_buffer = get_int(self.down_buffer)
down_loss = get_float(self.down_loss)
options = LinkOptions(
bandwidth=down_bandwidth,
@ -246,6 +272,7 @@ class LinkConfigurationDialog(Dialog):
delay=down_delay,
dup=down_duplicate,
loss=down_loss,
buffer=down_buffer,
unidirectional=True,
)
self.edge.asymmetric_link = Link(
@ -265,7 +292,8 @@ class LinkConfigurationDialog(Dialog):
self.app.core.edit_link(self.edge.asymmetric_link)
# update edge label
self.edge.draw_link_options()
self.edge.redraw()
self.edge.check_options()
self.destroy()
def change_symmetry(self) -> None:
@ -299,6 +327,7 @@ class LinkConfigurationDialog(Dialog):
self.duplicate.set(str(link.options.dup))
self.loss.set(str(link.options.loss))
self.delay.set(str(link.options.delay))
self.buffer.set(str(link.options.buffer))
if not self.is_symmetric:
asym_link = self.edge.asymmetric_link
self.down_bandwidth.set(str(asym_link.options.bandwidth))
@ -306,3 +335,4 @@ class LinkConfigurationDialog(Dialog):
self.down_duplicate.set(str(asym_link.options.dup))
self.down_loss.set(str(asym_link.options.loss))
self.down_delay.set(str(asym_link.options.delay))
self.down_buffer.set(str(asym_link.options.buffer))

View file

@ -14,19 +14,23 @@ from core.gui.utils import bandwidth_text, delay_jitter_text
if TYPE_CHECKING:
from core.gui.graph.graph import CanvasGraph
TEXT_DISTANCE: float = 0.30
TEXT_DISTANCE: int = 60
EDGE_WIDTH: int = 3
EDGE_COLOR: str = "#ff0000"
EDGE_LOSS: float = 100.0
WIRELESS_WIDTH: float = 3
WIRELESS_COLOR: str = "#009933"
ARC_DISTANCE: int = 50
def create_edge_token(src: int, dst: int, network: int = None) -> Tuple[int, ...]:
values = [src, dst]
if network is not None:
values.append(network)
return tuple(sorted(values))
def create_wireless_token(src: int, dst: int, network: int) -> str:
return f"{src}-{dst}-{network}"
def create_edge_token(link: Link) -> str:
iface1_id = link.iface1.id if link.iface1 else None
iface2_id = link.iface2.id if link.iface2 else None
return f"{link.node1_id}-{iface1_id}-{link.node2_id}-{iface2_id}"
def arc_edges(edges) -> None:
@ -67,17 +71,13 @@ class Edge:
self.src: int = src
self.dst: int = dst
self.arc: int = 0
self.token: Optional[Tuple[int, ...]] = None
self.token: Optional[str] = None
self.src_label: Optional[int] = None
self.middle_label: Optional[int] = None
self.dst_label: Optional[int] = None
self.color: str = EDGE_COLOR
self.width: int = EDGE_WIDTH
@classmethod
def create_token(cls, src: int, dst: int) -> Tuple[int, ...]:
return tuple(sorted([src, dst]))
def scaled_width(self) -> float:
return self.width * self.canvas.app.app_scale
@ -156,15 +156,17 @@ class Edge:
def node_label_positions(self) -> Tuple[Tuple[float, float], Tuple[float, float]]:
src_x, src_y, _, _, dst_x, dst_y = self.canvas.coords(self.id)
v1 = dst_x - src_x
v2 = dst_y - src_y
ux = TEXT_DISTANCE * v1
uy = TEXT_DISTANCE * v2
src_x = src_x + ux
src_y = src_y + uy
dst_x = dst_x - ux
dst_y = dst_y - uy
return (src_x, src_y), (dst_x, dst_y)
v_x, v_y = dst_x - src_x, dst_y - src_y
v_len = math.sqrt(v_x ** 2 + v_y ** 2)
if v_len == 0:
u_x, u_y = 0.0, 0.0
else:
u_x, u_y = v_x / v_len, v_y / v_len
offset_x, offset_y = TEXT_DISTANCE * u_x, TEXT_DISTANCE * u_y
return (
(src_x + offset_x, src_y + offset_y),
(dst_x - offset_x, dst_y - offset_y),
)
def src_label_text(self, text: str) -> None:
if self.src_label is None:
@ -240,15 +242,17 @@ class CanvasWirelessEdge(Edge):
canvas: "CanvasGraph",
src: int,
dst: int,
network_id: int,
token: str,
src_pos: Tuple[float, float],
dst_pos: Tuple[float, float],
token: Tuple[int, ...],
link: Link,
) -> None:
logging.debug("drawing wireless link from node %s to node %s", src, dst)
super().__init__(canvas, src, dst)
self.network_id: int = network_id
self.link: Link = link
self.token: Tuple[int, ...] = token
self.token: str = token
self.width: float = WIRELESS_WIDTH
color = link.color if link.color else WIRELESS_COLOR
self.color: str = color
@ -282,11 +286,10 @@ class CanvasEdge(Edge):
Create an instance of canvas edge object
"""
super().__init__(canvas, src)
self.src_iface: Optional[Interface] = None
self.dst_iface: Optional[Interface] = None
self.text_src: Optional[int] = None
self.text_dst: Optional[int] = None
self.link: Optional[Link] = None
self.linked_wireless: bool = False
self.asymmetric_link: Optional[Link] = None
self.throughput: Optional[float] = None
self.draw(src_pos, dst_pos, tk.NORMAL)
@ -303,10 +306,6 @@ class CanvasEdge(Edge):
self.canvas.tag_bind(self.id, "<ButtonRelease-3>", self.show_context)
self.canvas.tag_bind(self.id, "<Button-1>", self.show_info)
def set_link(self, link: Link) -> None:
self.link = link
self.draw_labels()
def iface_label(self, iface: Interface) -> str:
label = ""
if iface.name and self.canvas.show_iface_names.get():
@ -332,12 +331,25 @@ class CanvasEdge(Edge):
src_text, dst_text = self.create_node_labels()
self.src_label_text(src_text)
self.dst_label_text(dst_text)
self.draw_link_options()
if not self.linked_wireless:
self.draw_link_options()
def redraw(self) -> None:
super().redraw()
self.draw_labels()
def check_options(self) -> None:
if not self.link.options:
return
if self.link.options.loss == EDGE_LOSS:
state = tk.HIDDEN
self.canvas.addtag_withtag(tags.LOSS_EDGES, self.id)
else:
state = tk.NORMAL
self.canvas.dtag(self.id, tags.LOSS_EDGES)
if self.canvas.show_loss_links.state() == tk.HIDDEN:
self.canvas.itemconfigure(self.id, state=state)
def set_throughput(self, throughput: float) -> None:
throughput = 0.001 * throughput
text = f"{throughput:.3f} kbps"
@ -350,36 +362,21 @@ class CanvasEdge(Edge):
width = self.scaled_width()
self.canvas.itemconfig(self.id, fill=color, width=width)
def complete(self, dst: int) -> None:
def clear_throughput(self) -> None:
self.clear_middle_label()
if not self.linked_wireless:
self.draw_link_options()
def complete(self, dst: int, linked_wireless: bool) -> None:
self.dst = dst
self.token = create_edge_token(self.src, self.dst)
self.linked_wireless = linked_wireless
dst_pos = self.canvas.coords(self.dst)
self.move_dst(dst_pos)
self.check_wireless()
logging.debug("Draw wired link from node %s to node %s", self.src, dst)
def is_wireless(self) -> bool:
src_node = self.canvas.nodes[self.src]
dst_node = self.canvas.nodes[self.dst]
src_node_type = src_node.core_node.type
dst_node_type = dst_node.core_node.type
is_src_wireless = NodeUtils.is_wireless_node(src_node_type)
is_dst_wireless = NodeUtils.is_wireless_node(dst_node_type)
# update the wlan/EMANE network
wlan_network = self.canvas.wireless_network
if is_src_wireless and not is_dst_wireless:
if self.src not in wlan_network:
wlan_network[self.src] = set()
wlan_network[self.src].add(self.dst)
elif not is_src_wireless and is_dst_wireless:
if self.dst not in wlan_network:
wlan_network[self.dst] = set()
wlan_network[self.dst].add(self.src)
return is_src_wireless or is_dst_wireless
logging.debug("draw wired link from node %s to node %s", self.src, dst)
def check_wireless(self) -> None:
if self.is_wireless():
if self.linked_wireless:
self.canvas.itemconfig(self.id, state=tk.HIDDEN)
self.canvas.dtag(self.id, tags.EDGE)
self._check_antenna()

View file

@ -21,8 +21,10 @@ from core.gui.graph.edges import (
EDGE_WIDTH,
CanvasEdge,
CanvasWirelessEdge,
Edge,
arc_edges,
create_edge_token,
create_wireless_token,
)
from core.gui.graph.enums import GraphMode, ScaleOption
from core.gui.graph.node import CanvasNode
@ -69,9 +71,9 @@ class CanvasGraph(tk.Canvas):
self.selected: Optional[int] = None
self.node_draw: Optional[NodeDraw] = None
self.nodes: Dict[int, CanvasNode] = {}
self.edges: Dict[int, CanvasEdge] = {}
self.edges: Dict[str, CanvasEdge] = {}
self.shapes: Dict[int, Shape] = {}
self.wireless_edges: Dict[Tuple[int, ...], CanvasWirelessEdge] = {}
self.wireless_edges: Dict[str, CanvasWirelessEdge] = {}
# map wireless/EMANE node to the set of MDRs connected to that node
self.wireless_network: Dict[int, Set[int]] = {}
@ -108,6 +110,7 @@ class CanvasGraph(tk.Canvas):
self.show_wireless: ShowVar = ShowVar(self, tags.WIRELESS_EDGE, value=True)
self.show_grid: ShowVar = ShowVar(self, tags.GRIDLINE, value=True)
self.show_annotations: ShowVar = ShowVar(self, tags.ANNOTATION, value=True)
self.show_loss_links: ShowVar = ShowVar(self, tags.LOSS_EDGES, value=True)
self.show_iface_names: BooleanVar = BooleanVar(value=False)
self.show_ip4s: BooleanVar = BooleanVar(value=True)
self.show_ip6s: BooleanVar = BooleanVar(value=True)
@ -145,6 +148,7 @@ class CanvasGraph(tk.Canvas):
self.show_iface_names.set(False)
self.show_ip4s.set(True)
self.show_ip6s.set(True)
self.show_loss_links.set(True)
# delete any existing drawn items
for tag in tags.RESET_TAGS:
@ -206,14 +210,9 @@ class CanvasGraph(tk.Canvas):
iface_id = iface_throughput.iface_id
throughput = iface_throughput.throughput
iface_to_edge_id = (node_id, iface_id)
token = self.core.iface_to_edge.get(iface_to_edge_id)
if not token:
continue
edge = self.edges.get(token)
edge = self.core.iface_to_edge.get(iface_to_edge_id)
if edge:
edge.set_throughput(throughput)
else:
del self.core.iface_to_edge[iface_to_edge_id]
def draw_grid(self) -> None:
"""
@ -230,7 +229,7 @@ class CanvasGraph(tk.Canvas):
self.tag_lower(self.rect)
def add_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
token = create_edge_token(src.id, dst.id)
token = create_edge_token(link)
if token in self.edges and link.options.unidirectional:
edge = self.edges[token]
edge.asymmetric_link = link
@ -240,71 +239,52 @@ class CanvasGraph(tk.Canvas):
src_pos = (node1.position.x, node1.position.y)
dst_pos = (node2.position.x, node2.position.y)
edge = CanvasEdge(self, src.id, src_pos, dst_pos)
edge.token = token
edge.dst = dst.id
edge.set_link(link)
edge.check_wireless()
src.edges.add(edge)
dst.edges.add(edge)
self.edges[edge.token] = edge
self.core.links[edge.token] = edge
if link.iface1:
iface1 = link.iface1
self.core.iface_to_edge[(node1.id, iface1.id)] = token
src.ifaces[iface1.id] = iface1
edge.src_iface = iface1
if link.iface2:
iface2 = link.iface2
self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token
dst.ifaces[iface2.id] = iface2
edge.dst_iface = iface2
self.complete_edge(src, dst, edge, link)
def delete_wired_edge(self, src: CanvasNode, dst: CanvasNode) -> None:
token = create_edge_token(src.id, dst.id)
def delete_wired_edge(self, link: Link) -> None:
token = create_edge_token(link)
edge = self.edges.get(token)
if not edge:
return
self.delete_edge(edge)
if edge:
self.delete_edge(edge)
def update_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
token = create_edge_token(src.id, dst.id)
def update_wired_edge(self, link: Link) -> None:
token = create_edge_token(link)
edge = self.edges.get(token)
if not edge:
return
edge.link.options = deepcopy(link.options)
if edge:
edge.link.options = deepcopy(link.options)
edge.draw_link_options()
edge.check_options()
def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None:
network_id = link.network_id if link.network_id else None
token = create_edge_token(src.id, dst.id, network_id)
token = create_wireless_token(src.id, dst.id, network_id)
if token in self.wireless_edges:
logging.warning("ignoring link that already exists: %s", link)
return
src_pos = self.coords(src.id)
dst_pos = self.coords(dst.id)
edge = CanvasWirelessEdge(self, src.id, dst.id, src_pos, dst_pos, token, link)
edge = CanvasWirelessEdge(
self, src.id, dst.id, network_id, token, src_pos, dst_pos, link
)
self.wireless_edges[token] = edge
src.wireless_edges.add(edge)
dst.wireless_edges.add(edge)
self.tag_raise(src.id)
self.tag_raise(dst.id)
# update arcs when there are multiple links
common_edges = list(src.wireless_edges & dst.wireless_edges)
arc_edges(common_edges)
self.arc_common_edges(edge)
def delete_wireless_edge(
self, src: CanvasNode, dst: CanvasNode, link: Link
) -> None:
network_id = link.network_id if link.network_id else None
token = create_edge_token(src.id, dst.id, network_id)
token = create_wireless_token(src.id, dst.id, network_id)
if token not in self.wireless_edges:
return
edge = self.wireless_edges.pop(token)
edge.delete()
src.wireless_edges.remove(edge)
dst.wireless_edges.remove(edge)
# update arcs when there are multiple links
common_edges = list(src.wireless_edges & dst.wireless_edges)
arc_edges(common_edges)
self.arc_common_edges(edge)
def update_wireless_edge(
self, src: CanvasNode, dst: CanvasNode, link: Link
@ -312,7 +292,7 @@ class CanvasGraph(tk.Canvas):
if not link.label:
return
network_id = link.network_id if link.network_id else None
token = create_edge_token(src.id, dst.id, network_id)
token = create_wireless_token(src.id, dst.id, network_id)
if token not in self.wireless_edges:
self.add_wireless_edge(src, dst, link)
else:
@ -453,12 +433,6 @@ class CanvasGraph(tk.Canvas):
edge.delete()
return
# ignore repeated edges
token = create_edge_token(edge.src, self.selected)
if token in self.edges:
edge.delete()
return
# rj45 nodes can only support one link
if NodeUtils.is_rj45_node(src_node.core_node.type) and src_node.edges:
edge.delete()
@ -467,13 +441,23 @@ class CanvasGraph(tk.Canvas):
edge.delete()
return
# set dst node and snap edge to center
edge.complete(self.selected)
# only 1 link between bridge based nodes
is_src_bridge = NodeUtils.is_bridge_node(src_node.core_node)
is_dst_bridge = NodeUtils.is_bridge_node(dst_node.core_node)
common_links = src_node.edges & dst_node.edges
if all([is_src_bridge, is_dst_bridge, common_links]):
edge.delete()
return
self.edges[edge.token] = edge
src_node.edges.add(edge)
dst_node.edges.add(edge)
self.core.create_link(edge, src_node, dst_node)
# finalize edge creation
self.complete_edge(src_node, dst_node, edge)
def arc_common_edges(self, edge: Edge) -> None:
src_node = self.nodes[edge.src]
dst_node = self.nodes[edge.dst]
common_edges = list(src_node.edges & dst_node.edges)
common_edges += list(src_node.wireless_edges & dst_node.wireless_edges)
arc_edges(common_edges)
def select_object(self, object_id: int, choose_multiple: bool = False) -> None:
"""
@ -532,10 +516,10 @@ class CanvasGraph(tk.Canvas):
edge.delete()
# update node connected to edge being deleted
other_id = edge.src
other_iface = edge.src_iface
other_iface = edge.link.iface1
if edge.src == object_id:
other_id = edge.dst
other_iface = edge.dst_iface
other_iface = edge.link.iface2
other_node = self.nodes[other_id]
other_node.edges.remove(edge)
if other_iface:
@ -557,12 +541,12 @@ class CanvasGraph(tk.Canvas):
del self.edges[edge.token]
src_node = self.nodes[edge.src]
src_node.edges.discard(edge)
if edge.src_iface:
del src_node.ifaces[edge.src_iface.id]
if edge.link.iface1:
del src_node.ifaces[edge.link.iface1.id]
dst_node = self.nodes[edge.dst]
dst_node.edges.discard(edge)
if edge.dst_iface:
del dst_node.ifaces[edge.dst_iface.id]
if edge.link.iface2:
del dst_node.ifaces[edge.link.iface2.id]
src_wireless = NodeUtils.is_wireless_node(src_node.core_node.type)
if src_wireless:
dst_node.delete_antenna()
@ -570,6 +554,7 @@ class CanvasGraph(tk.Canvas):
if dst_wireless:
src_node.delete_antenna()
self.core.deleted_canvas_edges([edge])
self.arc_common_edges(edge)
def zoom(self, event: tk.Event, factor: float = None) -> None:
if not factor:
@ -901,19 +886,41 @@ class CanvasGraph(tk.Canvas):
def is_selection_mode(self) -> bool:
return self.mode == GraphMode.SELECT
def create_edge(self, source: CanvasNode, dest: CanvasNode) -> None:
def create_edge(self, src: CanvasNode, dst: CanvasNode) -> CanvasEdge:
"""
create an edge between source node and destination node
"""
token = create_edge_token(source.id, dest.id)
if token not in self.edges:
pos = (source.core_node.position.x, source.core_node.position.y)
edge = CanvasEdge(self, source.id, pos, pos)
edge.complete(dest.id)
self.edges[edge.token] = edge
self.nodes[source.id].edges.add(edge)
self.nodes[dest.id].edges.add(edge)
self.core.create_link(edge, source, dest)
pos = (src.core_node.position.x, src.core_node.position.y)
edge = CanvasEdge(self, src.id, pos, pos)
self.complete_edge(src, dst, edge)
return edge
def complete_edge(
self,
src: CanvasNode,
dst: CanvasNode,
edge: CanvasEdge,
link: Optional[Link] = None,
) -> None:
linked_wireless = self.is_linked_wireless(src.id, dst.id)
edge.complete(dst.id, linked_wireless)
if link is None:
link = self.core.create_link(edge, src, dst)
edge.link = link
if link.iface1:
iface1 = link.iface1
src.ifaces[iface1.id] = iface1
if link.iface2:
iface2 = link.iface2
dst.ifaces[iface2.id] = iface2
src.edges.add(edge)
dst.edges.add(edge)
edge.token = create_edge_token(edge.link)
self.arc_common_edges(edge)
edge.draw_labels()
edge.check_options()
self.edges[edge.token] = edge
self.core.save_edge(edge, src, dst)
def copy(self) -> None:
if self.core.is_runtime():
@ -967,13 +974,12 @@ class CanvasGraph(tk.Canvas):
if edge.src not in to_copy_ids or edge.dst not in to_copy_ids:
if canvas_node.id == edge.src:
dst_node = self.nodes[edge.dst]
self.create_edge(node, dst_node)
token = create_edge_token(node.id, dst_node.id)
copy_edge = self.create_edge(node, dst_node)
elif canvas_node.id == edge.dst:
src_node = self.nodes[edge.src]
self.create_edge(src_node, node)
token = create_edge_token(src_node.id, node.id)
copy_edge = self.edges[token]
copy_edge = self.create_edge(src_node, node)
else:
continue
copy_link = copy_edge.link
iface1_id = copy_link.iface1.id if copy_link.iface1 else None
iface2_id = copy_link.iface2.id if copy_link.iface2 else None
@ -1000,13 +1006,11 @@ class CanvasGraph(tk.Canvas):
# copy link and link config
for edge in to_copy_edges:
src_node_id = copy_map[edge.token[0]]
dst_node_id = copy_map[edge.token[1]]
src_node_id = copy_map[edge.src]
dst_node_id = copy_map[edge.dst]
src_node_copy = self.nodes[src_node_id]
dst_node_copy = self.nodes[dst_node_id]
self.create_edge(src_node_copy, dst_node_copy)
token = create_edge_token(src_node_copy.id, dst_node_copy.id)
copy_edge = self.edges[token]
copy_edge = self.create_edge(src_node_copy, dst_node_copy)
copy_link = copy_edge.link
iface1_id = copy_link.iface1.id if copy_link.iface1 else None
iface2_id = copy_link.iface2.id if copy_link.iface2 else None
@ -1035,10 +1039,29 @@ class CanvasGraph(tk.Canvas):
)
self.tag_raise(tags.NODE)
def is_linked_wireless(self, src: int, dst: int) -> bool:
src_node = self.nodes[src]
dst_node = self.nodes[dst]
src_node_type = src_node.core_node.type
dst_node_type = dst_node.core_node.type
is_src_wireless = NodeUtils.is_wireless_node(src_node_type)
is_dst_wireless = NodeUtils.is_wireless_node(dst_node_type)
# update the wlan/EMANE network
wlan_network = self.wireless_network
if is_src_wireless and not is_dst_wireless:
if src not in wlan_network:
wlan_network[src] = set()
wlan_network[src].add(dst)
elif not is_src_wireless and is_dst_wireless:
if dst not in wlan_network:
wlan_network[dst] = set()
wlan_network[dst].add(src)
return is_src_wireless or is_dst_wireless
def clear_throughputs(self) -> None:
for edge in self.edges.values():
edge.clear_middle_label()
edge.draw_link_options()
edge.clear_throughput()
def scale_graph(self) -> None:
for nid, canvas_node in self.nodes.items():

View file

@ -247,14 +247,18 @@ class CanvasNode:
)
unlink_menu = tk.Menu(self.context)
for edge in self.edges:
other_id = edge.src
if self.id == other_id:
link = edge.link
if self.id == edge.src:
other_id = edge.dst
other_iface = link.iface2.name if link.iface2 else None
else:
other_id = edge.src
other_iface = link.iface1.name if link.iface1 else None
other_node = self.canvas.nodes[other_id]
other_name = other_node.core_node.name
label = f"{other_name}:{other_iface}" if other_iface else other_name
func_unlink = functools.partial(self.click_unlink, edge)
unlink_menu.add_command(
label=other_node.core_node.name, command=func_unlink
)
unlink_menu.add_command(label=label, command=func_unlink)
themes.style_menu(unlink_menu)
self.context.add_cascade(label="Unlink", menu=unlink_menu)
edit_menu = tk.Menu(self.context)
@ -318,10 +322,10 @@ class CanvasNode:
for edge in self.edges:
if self.id == edge.src:
other_id = edge.dst
edge_iface_id = edge.src_iface.id
edge_iface_id = edge.link.iface1.id
else:
other_id = edge.src
edge_iface_id = edge.dst_iface.id
edge_iface_id = edge.link.iface2.id
if edge_iface_id != iface_id:
continue
other_node = self.canvas.nodes[other_id]

View file

@ -5,6 +5,7 @@ GRIDLINE: str = "gridline"
SHAPE: str = "shape"
SHAPE_TEXT: str = "shapetext"
EDGE: str = "edge"
LOSS_EDGES: str = "loss-edge"
LINK_LABEL: str = "linklabel"
WIRELESS_EDGE: str = "wireless"
ANTENNA: str = "antenna"

View file

@ -196,10 +196,10 @@ class InterfaceManager:
for edge in canvas_node.edges:
src_node = canvas.nodes[edge.src]
dst_node = canvas.nodes[edge.dst]
iface = edge.src_iface
iface = edge.link.iface1
check_node = src_node
if src_node == canvas_node:
iface = edge.dst_iface
iface = edge.link.iface2
check_node = dst_node
if check_node.core_node.id in visited:
continue

View file

@ -172,6 +172,11 @@ class Menubar(tk.Menu):
command=self.canvas.show_links.click_handler,
variable=self.canvas.show_links,
)
menu.add_checkbutton(
label="Loss Links",
command=self.canvas.show_loss_links.click_handler,
variable=self.canvas.show_loss_links,
)
menu.add_checkbutton(
label="Wireless Links",
command=self.canvas.show_wireless.click_handler,

View file

@ -62,12 +62,17 @@ class NodeUtils:
IMAGE_NODES: Set[NodeType] = {NodeType.DOCKER, NodeType.LXC}
WIRELESS_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
RJ45_NODES: Set[NodeType] = {NodeType.RJ45}
BRIDGE_NODES: Set[NodeType] = {NodeType.HUB, NodeType.SWITCH}
IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET}
MOBILITY_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE}
NODE_MODELS: Set[str] = {"router", "host", "PC", "mdr", "prouter"}
ROUTER_NODES: Set[str] = {"router", "mdr"}
ANTENNA_ICON: PhotoImage = None
@classmethod
def is_bridge_node(cls, node: Node) -> bool:
return node.type in cls.BRIDGE_NODES
@classmethod
def is_mobility(cls, node: Node) -> bool:
return node.type in cls.MOBILITY_NODES

View file

@ -30,8 +30,6 @@ if TYPE_CHECKING:
CoreServices = List[Union[CoreService, Type[CoreService]]]
ConfigServiceType = Type[ConfigService]
_DEFAULT_MTU = 1500
class NodeBase(abc.ABC):
"""

View file

@ -19,6 +19,8 @@ if TYPE_CHECKING:
from core.emulator.session import Session
from core.nodes.base import CoreNetworkBase, CoreNode
DEFAULT_MTU: int = 1500
class CoreInterface:
"""
@ -338,7 +340,7 @@ class Veth(CoreInterface):
node: "CoreNode",
name: str,
localname: str,
mtu: int = 1500,
mtu: int = DEFAULT_MTU,
server: "DistributedServer" = None,
start: bool = True,
) -> None:
@ -403,7 +405,7 @@ class TunTap(CoreInterface):
node: "CoreNode",
name: str,
localname: str,
mtu: int = 1500,
mtu: int = DEFAULT_MTU,
server: "DistributedServer" = None,
start: bool = True,
) -> None:

View file

@ -3,6 +3,7 @@ Defines network nodes used within core.
"""
import logging
import math
import threading
import time
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type
@ -447,77 +448,63 @@ class CoreNetwork(CoreNetworkBase):
:param iface2: interface two
:return: nothing
"""
devname = iface.localname
tc = f"{TC} qdisc replace dev {devname}"
parent = "root"
changed = False
bw = options.bandwidth
if iface.setparam("bw", bw):
# from tc-tbf(8): minimum value for burst is rate / kernel_hz
burst = max(2 * iface.mtu, int(bw / 1000))
# max IP payload
limit = 0xFFFF
tbf = f"tbf rate {bw} burst {burst} limit {limit}"
if bw > 0:
if self.up:
cmd = f"{tc} {parent} handle 1: {tbf}"
iface.host_cmd(cmd)
iface.setparam("has_tbf", True)
changed = True
elif iface.getparam("has_tbf") and bw <= 0:
if self.up:
cmd = f"{TC} qdisc delete dev {devname} {parent}"
iface.host_cmd(cmd)
iface.setparam("has_tbf", False)
# removing the parent removes the child
iface.setparam("has_netem", False)
changed = True
if iface.getparam("has_tbf"):
parent = "parent 1:1"
netem = "netem"
delay = options.delay
changed = max(changed, iface.setparam("delay", delay))
loss = options.loss
if loss is not None:
loss = float(loss)
changed = max(changed, iface.setparam("loss", loss))
duplicate = options.dup
if duplicate is not None:
duplicate = int(duplicate)
changed = max(changed, iface.setparam("duplicate", duplicate))
jitter = options.jitter
changed = max(changed, iface.setparam("jitter", jitter))
# determine if any settings have changed
changed = any(
[
iface.setparam("bw", options.bandwidth),
iface.setparam("delay", options.delay),
iface.setparam("loss", options.loss),
iface.setparam("duplicate", options.dup),
iface.setparam("jitter", options.jitter),
iface.setparam("buffer", options.buffer),
]
)
if not changed:
return
# jitter and delay use the same delay statement
if delay is not None:
netem += f" delay {delay}us"
if jitter is not None:
if delay is None:
netem += f" delay 0us {jitter}us 25%"
else:
netem += f" {jitter}us 25%"
if loss is not None and loss > 0:
netem += f" loss {min(loss, 100)}%"
if duplicate is not None and duplicate > 0:
netem += f" duplicate {min(duplicate, 100)}%"
delay_check = delay is None or delay <= 0
jitter_check = jitter is None or jitter <= 0
loss_check = loss is None or loss <= 0
duplicate_check = duplicate is None or duplicate <= 0
if all([delay_check, jitter_check, loss_check, duplicate_check]):
# possibly remove netem if it exists and parent queue wasn't removed
# delete tc configuration or create and add it
devname = iface.localname
if all(
[
options.delay is None or options.delay <= 0,
options.jitter is None or options.jitter <= 0,
options.loss is None or options.loss <= 0,
options.dup is None or options.dup <= 0,
options.bandwidth is None or options.bandwidth <= 0,
options.buffer is None or options.buffer <= 0,
]
):
if not iface.getparam("has_netem"):
return
if self.up:
cmd = f"{TC} qdisc delete dev {devname} {parent} handle 10:"
cmd = f"{TC} qdisc delete dev {devname} root handle 10:"
iface.host_cmd(cmd)
iface.setparam("has_netem", False)
elif len(netem) > 1:
else:
netem = ""
if options.bandwidth is not None:
limit = 1000
bw = options.bandwidth / 1000
if options.buffer is not None and options.buffer > 0:
limit = options.buffer
elif options.delay and options.bandwidth:
delay = options.delay / 1000
limit = max(2, math.ceil((2 * bw * delay) / (8 * iface.mtu)))
netem += f" rate {bw}kbit"
netem += f" limit {limit}"
if options.delay is not None:
netem += f" delay {options.delay}us"
if options.jitter is not None:
if options.delay is None:
netem += f" delay 0us {options.jitter}us 25%"
else:
netem += f" {options.jitter}us 25%"
if options.loss is not None and options.loss > 0:
netem += f" loss {min(options.loss, 100)}%"
if options.dup is not None and options.dup > 0:
netem += f" duplicate {min(options.dup, 100)}%"
if self.up:
cmd = f"{TC} qdisc replace dev {devname} {parent} handle 10: {netem}"
cmd = f"{TC} qdisc replace dev {devname} root handle 10: netem {netem}"
iface.host_cmd(cmd)
iface.setparam("has_netem", True)

View file

@ -13,7 +13,7 @@ from core.emulator.enumerations import NodeTypes, TransportType
from core.errors import CoreCommandError, CoreError
from core.executables import MOUNT, TEST, UMOUNT
from core.nodes.base import CoreNetworkBase, CoreNodeBase
from core.nodes.interface import CoreInterface
from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import CoreNetwork, GreTap
if TYPE_CHECKING:
@ -252,7 +252,7 @@ class Rj45Node(CoreNodeBase):
session: "Session",
_id: int = None,
name: str = None,
mtu: int = 1500,
mtu: int = DEFAULT_MTU,
server: DistributedServer = None,
) -> None:
"""

View file

@ -8,7 +8,7 @@ import netaddr
from core.emane.nodes import EmaneNet
from core.nodes.base import CoreNode
from core.nodes.interface import CoreInterface
from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import PtpNet, WlanNode
from core.nodes.physical import Rj45Node
from core.services.coreservices import CoreService
@ -384,7 +384,7 @@ class FRROspfv2(FrrService):
mtu-ignore command. This is needed when e.g. a node is linked via a
GreTap device.
"""
if iface.mtu != 1500:
if iface.mtu != DEFAULT_MTU:
# a workaround for PhysicalNode GreTap, which has no knowledge of
# the other nodes/nets
return " ip ospf mtu-ignore\n"

View file

@ -8,7 +8,7 @@ import netaddr
from core.emane.nodes import EmaneNet
from core.emulator.enumerations import LinkTypes
from core.nodes.base import CoreNode
from core.nodes.interface import CoreInterface
from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import PtpNet, WlanNode
from core.nodes.physical import Rj45Node
from core.services.coreservices import CoreService
@ -301,7 +301,7 @@ class Ospfv2(QuaggaService):
mtu-ignore command. This is needed when e.g. a node is linked via a
GreTap device.
"""
if iface.mtu != 1500:
if iface.mtu != DEFAULT_MTU:
# a workaround for PhysicalNode GreTap, which has no knowledge of
# the other nodes/nets
return " ip ospf mtu-ignore\n"

View file

@ -603,7 +603,7 @@ fi;
class RadvdService(UtilService):
name: str = "radvd"
configs: Tuple[str, ...] = ("/etc/radvd/radvd.conf",)
dirs: Tuple[str, ...] = ("/etc/radvd",)
dirs: Tuple[str, ...] = ("/etc/radvd", "/var/run/radvd")
startup: Tuple[str, ...] = (
"radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",
)

View file

@ -15,6 +15,7 @@ import random
import shlex
import shutil
import sys
from pathlib import Path
from subprocess import PIPE, STDOUT, Popen
from typing import (
TYPE_CHECKING,
@ -315,27 +316,25 @@ def sysctl_devname(devname: str) -> Optional[str]:
return devname.replace(".", "/")
def load_config(filename: str, d: Dict[str, str]) -> None:
def load_config(file_path: Path, d: Dict[str, str]) -> None:
"""
Read key=value pairs from a file, into a dict. Skip comments; strip newline
characters and spacing.
:param filename: file to read into a dictionary
:param d: dictionary to read file into
:param file_path: file path to read data from
:param d: dictionary to config into
:return: nothing
"""
with open(filename, "r") as f:
with file_path.open("r") as f:
lines = f.readlines()
for line in lines:
if line[:1] == "#":
continue
try:
key, value = line.split("=", 1)
d[key] = value.strip()
except ValueError:
logging.exception("error reading file to dict: %s", filename)
logging.exception("error reading file to dict: %s", file_path)
def load_classes(path: str, clazz: Generic[T]) -> T:

View file

@ -192,12 +192,13 @@ def build_platform_xml(
add_param(nem_element, platform_endpoint, config[platform_endpoint])
transport_endpoint = "transportendpoint"
add_param(nem_element, transport_endpoint, config[transport_endpoint])
else:
transport_name = transport_file_name(iface)
transport_element = etree.SubElement(
nem_element, "transport", definition=transport_name
)
add_param(transport_element, "device", iface.name)
# define transport element
transport_name = transport_file_name(iface)
transport_element = etree.SubElement(
nem_element, "transport", definition=transport_name
)
add_param(transport_element, "device", iface.name)
# add nem element to platform element
platform_element.append(nem_element)

View file

@ -1,7 +1,9 @@
"""
Example custom emane model.
"""
from typing import Dict, List, Optional, Set
from core.config import Configuration
from core.emane import emanemanifest, emanemodel
@ -9,41 +11,45 @@ class ExampleModel(emanemodel.EmaneModel):
"""
Custom emane model.
:var str name: defines the emane model name that will show up in the GUI
:cvar name: defines the emane model name that will show up in the GUI
Mac Definition:
:var str mac_library: defines that mac library that the model will reference
:var str mac_xml: defines the mac manifest file that will be parsed to obtain configuration options,
:cvar mac_library: defines that mac library that the model will reference
:cvar mac_xml: defines the mac manifest file that will be parsed to obtain configuration options,
that will be displayed within the GUI
:var dict mac_mac_defaults: allows you to override options that are maintained within the manifest file above
:var list mac_mac_config: parses the manifest file and converts configurations into core supported formats
:cvar mac_defaults: allows you to override options that are maintained within the manifest file above
:cvar mac_config: parses the manifest file and converts configurations into core supported formats
Phy Definition:
NOTE: phy configuration will default to the universal model as seen below and the below section does not
have to be included
:var str phy_library: defines that phy library that the model will reference, used if you need to
:cvar phy_library: defines that phy library that the model will reference, used if you need to
provide a custom phy
:var str phy_xml: defines the phy manifest file that will be parsed to obtain configuration options,
:cvar phy_xml: defines the phy manifest file that will be parsed to obtain configuration options,
that will be displayed within the GUI
:var dict phy_defaults: allows you to override options that are maintained within the manifest file above
:cvar phy_defaults: allows you to override options that are maintained within the manifest file above
or for the default universal model
:var list phy_config: parses the manifest file and converts configurations into core supported formats
:cvar phy_config: parses the manifest file and converts configurations into core supported formats
Custom Override Options:
NOTE: these options default to what's seen below and do not have to be included
:var set config_ignore: allows you to ignore options within phy/mac, used typically if you needed to add
:cvar config_ignore: allows you to ignore options within phy/mac, used typically if you needed to add
a custom option for display within the gui
"""
name = "emane_example"
mac_library = "rfpipemaclayer"
mac_xml = "/usr/share/emane/manifest/rfpipemaclayer.xml"
mac_defaults = {
name: str = "emane_example"
mac_library: str = "rfpipemaclayer"
mac_xml: str = "/usr/share/emane/manifest/rfpipemaclayer.xml"
mac_defaults: Dict[str, str] = {
"pcrcurveuri": "/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"
}
mac_config = emanemanifest.parse(mac_xml, mac_defaults)
phy_library = None
phy_xml = "/usr/share/emane/manifest/emanephy.xml"
phy_defaults = {"subid": "1", "propagationmodel": "2ray", "noisemode": "none"}
phy_config = emanemanifest.parse(phy_xml, phy_defaults)
config_ignore = set()
mac_config: List[Configuration] = emanemanifest.parse(mac_xml, mac_defaults)
phy_library: Optional[str] = None
phy_xml: str = "/usr/share/emane/manifest/emanephy.xml"
phy_defaults: Dict[str, str] = {
"subid": "1",
"propagationmodel": "2ray",
"noisemode": "none",
}
phy_config: List[Configuration] = emanemanifest.parse(phy_xml, phy_defaults)
config_ignore: Set[str] = set()

View file

@ -0,0 +1,115 @@
"""
Simple example custom service, used to drive shell commands on a node.
"""
from typing import Tuple
from core.nodes.base import CoreNode
from core.services.coreservices import CoreService, ServiceMode
class ExampleService(CoreService):
"""
Example Custom CORE Service
:cvar name: name used as a unique ID for this service and is required, no spaces
:cvar group: allows you to group services within the GUI under a common name
:cvar executables: executables this service depends on to function, if executable is
not on the path, service will not be loaded
:cvar dependencies: services that this service depends on for startup, tuple of
service names
:cvar dirs: directories that this service will create within a node
:cvar configs: files that this service will generate, without a full path this file
goes in the node's directory e.g. /tmp/pycore.12345/n1.conf/myfile
:cvar startup: commands used to start this service, any non-zero exit code will
cause a failure
:cvar validate: commands used to validate that a service was started, any non-zero
exit code will cause a failure
:cvar validation_mode: validation mode, used to determine startup success.
NON_BLOCKING - runs startup commands, and validates success with validation commands
BLOCKING - runs startup commands, and validates success with the startup commands themselves
TIMER - runs startup commands, and validates success by waiting for "validation_timer" alone
:cvar validation_timer: time in seconds for a service to wait for validation, before
determining success in TIMER/NON_BLOCKING modes.
:cvar validation_period: period in seconds to wait before retrying validation,
only used in NON_BLOCKING mode
:cvar shutdown: shutdown commands to stop this service
"""
name: str = "ExampleService"
group: str = "Utility"
executables: Tuple[str, ...] = ()
dependencies: Tuple[str, ...] = ()
dirs: Tuple[str, ...] = ()
configs: Tuple[str, ...] = ("myservice1.sh", "myservice2.sh")
startup: Tuple[str, ...] = tuple(f"sh {x}" for x in configs)
validate: Tuple[str, ...] = ()
validation_mode: ServiceMode = ServiceMode.NON_BLOCKING
validation_timer: int = 5
validation_period: float = 0.5
shutdown: Tuple[str, ...] = ()
@classmethod
def on_load(cls) -> None:
"""
Provides a way to run some arbitrary logic when the service is loaded, possibly
to help facilitate dynamic settings for the environment.
:return: nothing
"""
pass
@classmethod
def get_configs(cls, node: CoreNode) -> Tuple[str, ...]:
"""
Provides a way to dynamically generate the config files from the node a service
will run. Defaults to the class definition and can be left out entirely if not
needed.
:param node: core node that the service is being ran on
:return: tuple of config files to create
"""
return cls.configs
@classmethod
def generate_config(cls, node: CoreNode, filename: str) -> str:
"""
Returns a string representation for a file, given the node the service is
starting on the config filename that this information will be used for. This
must be defined, if "configs" are defined.
:param node: core node that the service is being ran on
:param filename: configuration file to generate
:return: configuration file content
"""
cfg = "#!/bin/sh\n"
if filename == cls.configs[0]:
cfg += "# auto-generated by MyService (sample.py)\n"
for iface in node.get_ifaces():
cfg += f'echo "Node {node.name} has interface {iface.name}"\n'
elif filename == cls.configs[1]:
cfg += "echo hello"
return cfg
@classmethod
def get_startup(cls, node: CoreNode) -> Tuple[str, ...]:
"""
Provides a way to dynamically generate the startup commands from the node a
service will run. Defaults to the class definition and can be left out entirely
if not needed.
:param node: core node that the service is being ran on
:return: tuple of startup commands to run
"""
return cls.startup
@classmethod
def get_validate(cls, node: CoreNode) -> Tuple[str, ...]:
"""
Provides a way to dynamically generate the validate commands from the node a
service will run. Defaults to the class definition and can be left out entirely
if not needed.
:param node: core node that the service is being ran on
:return: tuple of commands to validate service startup with
"""
return cls.validate

View file

@ -1,110 +0,0 @@
"""
Simple example for a user-defined service.
"""
from core.services.coreservices import CoreService, ServiceMode
class MyService(CoreService):
"""
Custom CORE Service
:var str name: name used as a unique ID for this service and is required, no spaces
:var str group: allows you to group services within the GUI under a common name
:var tuple executables: executables this service depends on to function, if executable is
not on the path, service will not be loaded
:var tuple dependencies: services that this service depends on for startup, tuple of service names
:var tuple dirs: directories that this service will create within a node
:var tuple configs: files that this service will generate, without a full path this file goes in
the node's directory e.g. /tmp/pycore.12345/n1.conf/myfile
:var tuple startup: commands used to start this service, any non-zero exit code will cause a failure
:var tuple validate: commands used to validate that a service was started, any non-zero exit code
will cause a failure
:var ServiceMode validation_mode: validation mode, used to determine startup success.
NON_BLOCKING - runs startup commands, and validates success with validation commands
BLOCKING - runs startup commands, and validates success with the startup commands themselves
TIMER - runs startup commands, and validates success by waiting for "validation_timer" alone
:var int validation_timer: time in seconds for a service to wait for validation, before determining
success in TIMER/NON_BLOCKING modes.
:var float validation_validation_period: period in seconds to wait before retrying validation,
only used in NON_BLOCKING mode
:var tuple shutdown: shutdown commands to stop this service
"""
name = "MyService"
group = "Utility"
executables = ()
dependencies = ()
dirs = ()
configs = ("myservice1.sh", "myservice2.sh")
startup = tuple(f"sh {x}" for x in configs)
validate = ()
validation_mode = ServiceMode.NON_BLOCKING
validation_timer = 5
validation_period = 0.5
shutdown = ()
@classmethod
def on_load(cls):
"""
Provides a way to run some arbitrary logic when the service is loaded, possibly to help facilitate
dynamic settings for the environment.
:return: nothing
"""
pass
@classmethod
def get_configs(cls, node):
"""
Provides a way to dynamically generate the config files from the node a service will run.
Defaults to the class definition and can be left out entirely if not needed.
:param node: core node that the service is being ran on
:return: tuple of config files to create
"""
return cls.configs
@classmethod
def generate_config(cls, node, filename):
"""
Returns a string representation for a file, given the node the service is starting on the config filename
that this information will be used for. This must be defined, if "configs" are defined.
:param node: core node that the service is being ran on
:param str filename: configuration file to generate
:return: configuration file content
:rtype: str
"""
cfg = "#!/bin/sh\n"
if filename == cls.configs[0]:
cfg += "# auto-generated by MyService (sample.py)\n"
for iface in node.get_ifaces():
cfg += f'echo "Node {node.name} has interface {iface.name}"\n'
elif filename == cls.configs[1]:
cfg += "echo hello"
return cfg
@classmethod
def get_startup(cls, node):
"""
Provides a way to dynamically generate the startup commands from the node a service will run.
Defaults to the class definition and can be left out entirely if not needed.
:param node: core node that the service is being ran on
:return: tuple of startup commands to run
"""
return cls.startup
@classmethod
def get_validate(cls, node):
"""
Provides a way to dynamically generate the validate commands from the node a service will run.
Defaults to the class definition and can be left out entirely if not needed.
:param node: core node that the service is being ran on
:return: tuple of commands to validate service startup with
"""
return cls.validate

View file

@ -777,6 +777,7 @@ message LinkOptions {
int64 delay = 8;
int32 dup = 9;
bool unidirectional = 10;
int32 buffer = 11;
}
message Interface {

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "core"
version = "7.2.1"
version = "7.3.0"
description = "CORE Common Open Research Emulator"
authors = ["Boeing Research and Technology"]
license = "BSD-2-Clause"

View file

@ -83,6 +83,7 @@ class TestLinks:
loss = 25
dup = 25
jitter = 10
buffer = 100
node1 = session.add_node(CoreNode)
node2 = session.add_node(SwitchNode)
iface1_data = ip_prefixes.create_iface(node1)
@ -93,10 +94,16 @@ class TestLinks:
assert iface1.getparam("loss") != loss
assert iface1.getparam("duplicate") != dup
assert iface1.getparam("jitter") != jitter
assert iface1.getparam("buffer") != buffer
# when
options = LinkOptions(
delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter
delay=delay,
bandwidth=bandwidth,
loss=loss,
dup=dup,
jitter=jitter,
buffer=buffer,
)
session.update_link(
node1.id, node2.id, iface1_id=iface1_data.id, options=options
@ -108,6 +115,7 @@ class TestLinks:
assert iface1.getparam("loss") == loss
assert iface1.getparam("duplicate") == dup
assert iface1.getparam("jitter") == jitter
assert iface1.getparam("buffer") == buffer
def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes):
# given
@ -116,6 +124,7 @@ class TestLinks:
loss = 25
dup = 25
jitter = 10
buffer = 100
node1 = session.add_node(SwitchNode)
node2 = session.add_node(CoreNode)
iface2_data = ip_prefixes.create_iface(node2)
@ -126,10 +135,16 @@ class TestLinks:
assert iface2.getparam("loss") != loss
assert iface2.getparam("duplicate") != dup
assert iface2.getparam("jitter") != jitter
assert iface2.getparam("buffer") != buffer
# when
options = LinkOptions(
delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter
delay=delay,
bandwidth=bandwidth,
loss=loss,
dup=dup,
jitter=jitter,
buffer=buffer,
)
session.update_link(
node1.id, node2.id, iface2_id=iface2_data.id, options=options
@ -141,6 +156,7 @@ class TestLinks:
assert iface2.getparam("loss") == loss
assert iface2.getparam("duplicate") == dup
assert iface2.getparam("jitter") == jitter
assert iface2.getparam("buffer") == buffer
def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes):
# given
@ -149,6 +165,7 @@ class TestLinks:
loss = 25
dup = 25
jitter = 10
buffer = 100
node1 = session.add_node(CoreNode)
node2 = session.add_node(CoreNode)
iface1_data = ip_prefixes.create_iface(node1)
@ -161,15 +178,22 @@ class TestLinks:
assert iface1.getparam("loss") != loss
assert iface1.getparam("duplicate") != dup
assert iface1.getparam("jitter") != jitter
assert iface1.getparam("buffer") != buffer
assert iface2.getparam("delay") != delay
assert iface2.getparam("bw") != bandwidth
assert iface2.getparam("loss") != loss
assert iface2.getparam("duplicate") != dup
assert iface2.getparam("jitter") != jitter
assert iface2.getparam("buffer") != buffer
# when
options = LinkOptions(
delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter
delay=delay,
bandwidth=bandwidth,
loss=loss,
dup=dup,
jitter=jitter,
buffer=buffer,
)
session.update_link(node1.id, node2.id, iface1_data.id, iface2_data.id, options)
@ -179,11 +203,13 @@ class TestLinks:
assert iface1.getparam("loss") == loss
assert iface1.getparam("duplicate") == dup
assert iface1.getparam("jitter") == jitter
assert iface1.getparam("buffer") == buffer
assert iface2.getparam("delay") == delay
assert iface2.getparam("bw") == bandwidth
assert iface2.getparam("loss") == loss
assert iface2.getparam("duplicate") == dup
assert iface2.getparam("jitter") == jitter
assert iface2.getparam("buffer") == buffer
def test_delete_ptp(self, session: Session, ip_prefixes: IpPrefixes):
# given

View file

@ -50,6 +50,26 @@ can also subscribe to EMANE location events and move the nodes on the canvas
as they are moved in the EMANE emulation. This would occur when an Emulation
Script Generator, for example, is running a mobility script.
## EMANE in CORE
This section will cover some high level topics and examples for running and
using EMANE in CORE.
You can find more detailed tutorials and examples at the
[EMANE Tutorial](https://github.com/adjacentlink/emane-tutorial/wiki).
Every topic below assumes CORE, EMANE, and OSPF MDR have been installed.
> **WARNING:** demo files will be found within the new `core-pygui`
|Topic|Model|Description|
|---|---|---|
|[XML Files](emane/files.md)|RF Pipe|Overview of generated XML files used to drive EMANE|
|[GPSD](emane/gpsd.md)|RF Pipe|Overview of running and integrating gpsd with EMANE|
|[Precomputed](emane/precomputed.md)|RF Pipe|Overview of using the precomputed propagation model|
|[EEL](emane/eel.md)|RF Pipe|Overview of using the Emulation Event Log (EEL) Generator|
|[Antenna Profiles](emane/antenna.md)|RF Pipe|Overview of using antenna profiles in EMANE|
## EMANE Configuration
The CORE configuration file **/etc/core/core.conf** has options specific to
@ -96,7 +116,61 @@ placed within the path defined by **emane_models_dir** in the CORE
configuration file. This path cannot end in **/emane**.
Here is an example model with documentation describing functionality:
[Example Model](../daemon/examples/myemane/examplemodel.py)
```python
"""
Example custom emane model.
"""
from typing import Dict, List, Optional, Set
from core.config import Configuration
from core.emane import emanemanifest, emanemodel
class ExampleModel(emanemodel.EmaneModel):
"""
Custom emane model.
:cvar name: defines the emane model name that will show up in the GUI
Mac Definition:
:cvar mac_library: defines that mac library that the model will reference
:cvar mac_xml: defines the mac manifest file that will be parsed to obtain configuration options,
that will be displayed within the GUI
:cvar mac_defaults: allows you to override options that are maintained within the manifest file above
:cvar mac_config: parses the manifest file and converts configurations into core supported formats
Phy Definition:
NOTE: phy configuration will default to the universal model as seen below and the below section does not
have to be included
:cvar phy_library: defines that phy library that the model will reference, used if you need to
provide a custom phy
:cvar phy_xml: defines the phy manifest file that will be parsed to obtain configuration options,
that will be displayed within the GUI
:cvar phy_defaults: allows you to override options that are maintained within the manifest file above
or for the default universal model
:cvar phy_config: parses the manifest file and converts configurations into core supported formats
Custom Override Options:
NOTE: these options default to what's seen below and do not have to be included
:cvar config_ignore: allows you to ignore options within phy/mac, used typically if you needed to add
a custom option for display within the gui
"""
name: str = "emane_example"
mac_library: str = "rfpipemaclayer"
mac_xml: str = "/usr/share/emane/manifest/rfpipemaclayer.xml"
mac_defaults: Dict[str, str] = {
"pcrcurveuri": "/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"
}
mac_config: List[Configuration] = emanemanifest.parse(mac_xml, mac_defaults)
phy_library: Optional[str] = None
phy_xml: str = "/usr/share/emane/manifest/emanephy.xml"
phy_defaults: Dict[str, str] = {
"subid": "1", "propagationmodel": "2ray", "noisemode": "none"
}
phy_config: List[Configuration] = emanemanifest.parse(phy_xml, phy_defaults)
config_ignore: Set[str] = set()
```
## Single PC with EMANE

435
docs/emane/antenna.md Normal file
View file

@ -0,0 +1,435 @@
# EMANE Antenna Profiles
* Table of Contents
{:toc}
## Overview
Introduction to using the EMANE antenna profile in CORE, based on the example
EMANE Demo linked below.
[EMANE Demo 6](https://github.com/adjacentlink/emane-tutorial/wiki/Demonstration-6)
for more specifics.
## Demo Setup
We will need to create some files in advance of starting this session.
Create directory to place antenna profile files.
```shell
mkdir /tmp/emane
```
Create `/tmp/emane/antennaprofile.xml` with the following contents.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE profiles SYSTEM "file:///usr/share/emane/dtd/antennaprofile.dtd">
<profiles>
<profile id="1"
antennapatternuri="/tmp/emane/antenna30dsector.xml"
blockagepatternuri="/tmp/emane/blockageaft.xml">
<placement north="0" east="0" up="0"/>
</profile>
<profile id="2"
antennapatternuri="/tmp/emane/antenna30dsector.xml">
<placement north="0" east="0" up="0"/>
</profile>
</profiles>
```
Create `/tmp/emane/antenna30dsector.xml` with the following contents.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE antennaprofile SYSTEM "file:///usr/share/emane/dtd/antennaprofile.dtd">
<!-- 30degree sector antenna pattern with main beam at +6dB and gain decreasing by 3dB every 5 degrees in elevation or bearing.-->
<antennaprofile>
<antennapattern>
<elevation min='-90' max='-16'>
<bearing min='0' max='359'>
<gain value='-200'/>
</bearing>
</elevation>
<elevation min='-15' max='-11'>
<bearing min='0' max='5'>
<gain value='0'/>
</bearing>
<bearing min='6' max='10'>
<gain value='-3'/>
</bearing>
<bearing min='11' max='15'>
<gain value='-6'/>
</bearing>
<bearing min='16' max='344'>
<gain value='-200'/>
</bearing>
<bearing min='345' max='349'>
<gain value='-6'/>
</bearing>
<bearing min='350' max='354'>
<gain value='-3'/>
</bearing>
<bearing min='355' max='359'>
<gain value='0'/>
</bearing>
</elevation>
<elevation min='-10' max='-6'>
<bearing min='0' max='5'>
<gain value='3'/>
</bearing>
<bearing min='6' max='10'>
<gain value='0'/>
</bearing>
<bearing min='11' max='15'>
<gain value='-3'/>
</bearing>
<bearing min='16' max='344'>
<gain value='-200'/>
</bearing>
<bearing min='345' max='349'>
<gain value='-3'/>
</bearing>
<bearing min='350' max='354'>
<gain value='0'/>
</bearing>
<bearing min='355' max='359'>
<gain value='3'/>
</bearing>
</elevation>
<elevation min='-5' max='-1'>
<bearing min='0' max='5'>
<gain value='6'/>
</bearing>
<bearing min='6' max='10'>
<gain value='3'/>
</bearing>
<bearing min='11' max='15'>
<gain value='0'/>
</bearing>
<bearing min='16' max='344'>
<gain value='-200'/>
</bearing>
<bearing min='345' max='349'>
<gain value='0'/>
</bearing>
<bearing min='350' max='354'>
<gain value='3'/>
</bearing>
<bearing min='355' max='359'>
<gain value='6'/>
</bearing>
</elevation>
<elevation min='0' max='5'>
<bearing min='0' max='5'>
<gain value='6'/>
</bearing>
<bearing min='6' max='10'>
<gain value='3'/>
</bearing>
<bearing min='11' max='15'>
<gain value='0'/>
</bearing>
<bearing min='16' max='344'>
<gain value='-200'/>
</bearing>
<bearing min='345' max='349'>
<gain value='0'/>
</bearing>
<bearing min='350' max='354'>
<gain value='3'/>
</bearing>
<bearing min='355' max='359'>
<gain value='6'/>
</bearing>
</elevation>
<elevation min='6' max='10'>
<bearing min='0' max='5'>
<gain value='3'/>
</bearing>
<bearing min='6' max='10'>
<gain value='0'/>
</bearing>
<bearing min='11' max='15'>
<gain value='-3'/>
</bearing>
<bearing min='16' max='344'>
<gain value='-200'/>
</bearing>
<bearing min='345' max='349'>
<gain value='-3'/>
</bearing>
<bearing min='350' max='354'>
<gain value='0'/>
</bearing>
<bearing min='355' max='359'>
<gain value='3'/>
</bearing>
</elevation>
<elevation min='11' max='15'>
<bearing min='0' max='5'>
<gain value='0'/>
</bearing>
<bearing min='6' max='10'>
<gain value='-3'/>
</bearing>
<bearing min='11' max='15'>
<gain value='-6'/>
</bearing>
<bearing min='16' max='344'>
<gain value='-200'/>
</bearing>
<bearing min='345' max='349'>
<gain value='-6'/>
</bearing>
<bearing min='350' max='354'>
<gain value='-3'/>
</bearing>
<bearing min='355' max='359'>
<gain value='0'/>
</bearing>
</elevation>
<elevation min='16' max='90'>
<bearing min='0' max='359'>
<gain value='-200'/>
</bearing>
</elevation>
</antennapattern>
</antennaprofile>
```
Create `/tmp/emane/blockageaft.xml` with the following contents.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE antennaprofile SYSTEM "file:///usr/share/emane/dtd/antennaprofile.dtd">
<!-- blockage pattern: 1) entire aft in bearing (90 to 270) blocked 2) elevation below -10 blocked, 3) elevation from -10 to -1 is at -10dB to -1 dB 3) elevation from 0 to 90 no blockage-->
<antennaprofile>
<blockagepattern>
<elevation min='-90' max='-11'>
<bearing min='0' max='359'>
<gain value='-200'/>
</bearing>
</elevation>
<elevation min='-10' max='-10'>
<bearing min='0' max='89'>
<gain value='-10'/>
</bearing>
<bearing min='90' max='270'>
<gain value='-200'/>
</bearing>
<bearing min='271' max='359'>
<gain value='-10'/>
</bearing>
</elevation>
<elevation min='-9' max='-9'>
<bearing min='0' max='89'>
<gain value='-9'/>
</bearing>
<bearing min='90' max='270'>
<gain value='-200'/>
</bearing>
<bearing min='271' max='359'>
<gain value='-9'/>
</bearing>
</elevation>
<elevation min='-8' max='-8'>
<bearing min='0' max='89'>
<gain value='-8'/>
</bearing>
<bearing min='90' max='270'>
<gain value='-200'/>
</bearing>
<bearing min='271' max='359'>
<gain value='-8'/>
</bearing>
</elevation>
<elevation min='-7' max='-7'>
<bearing min='0' max='89'>
<gain value='-7'/>
</bearing>
<bearing min='90' max='270'>
<gain value='-200'/>
</bearing>
<bearing min='271' max='359'>
<gain value='-7'/>
</bearing>
</elevation>
<elevation min='-6' max='-6'>
<bearing min='0' max='89'>
<gain value='-6'/>
</bearing>
<bearing min='90' max='270'>
<gain value='-200'/>
</bearing>
<bearing min='271' max='359'>
<gain value='-6'/>
</bearing>
</elevation>
<elevation min='-5' max='-5'>
<bearing min='0' max='89'>
<gain value='-5'/>
</bearing>
<bearing min='90' max='270'>
<gain value='-200'/>
</bearing>
<bearing min='271' max='359'>
<gain value='-5'/>
</bearing>
</elevation>
<elevation min='-4' max='-4'>
<bearing min='0' max='89'>
<gain value='-4'/>
</bearing>
<bearing min='90' max='270'>
<gain value='-200'/>
</bearing>
<bearing min='271' max='359'>
<gain value='-4'/>
</bearing>
</elevation>
<elevation min='-3' max='-3'>
<bearing min='0' max='89'>
<gain value='-3'/>
</bearing>
<bearing min='90' max='270'>
<gain value='-200'/>
</bearing>
<bearing min='271' max='359'>
<gain value='-3'/>
</bearing>
</elevation>
<elevation min='-2' max='-2'>
<bearing min='0' max='89'>
<gain value='-2'/>
</bearing>
<bearing min='90' max='270'>
<gain value='-200'/>
</bearing>
<bearing min='271' max='359'>
<gain value='-2'/>
</bearing>
</elevation>
<elevation min='-1' max='-1'>
<bearing min='0' max='89'>
<gain value='-1'/>
</bearing>
<bearing min='90' max='270'>
<gain value='-200'/>
</bearing>
<bearing min='271' max='359'>
<gain value='-1'/>
</bearing>
</elevation>
<elevation min='0' max='90'>
<bearing min='0' max='89'>
<gain value='0'/>
</bearing>
<bearing min='90' max='270'>
<gain value='-200'/>
</bearing>
<bearing min='271' max='359'>
<gain value='0'/>
</bearing>
</elevation>
</blockagepattern>
</antennaprofile>
```
## Run Demo
1. Select `Open...` within the GUI
1. Load `emane-demo-antenna.xml`
1. Click ![Start Button](../static/gui/start.gif)
1. After startup completes, double click n1 to bring up the nodes terminal
## Example Demo
This demo will cover running an EMANE event service to feed in antenna,
location, and pathloss events to demonstrate how antenna profiles
can be used.
### EMANE Event Dump
On n1 lets dump EMANE events, so when we later run the EMANE event service
you can monitor when and what is sent.
```shell
root@n1:/tmp/pycore.44917/n1.conf# emaneevent-dump -i ctrl0
```
### Send EMANE Events
On the host machine create the following to send EMANE events.
> **WARNING:** make sure to set the `eventservicedevice` to the proper control
> network value
Create `eventservice.xml` with the following contents.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE eventservice SYSTEM "file:///usr/share/emane/dtd/eventservice.dtd">
<eventservice>
<param name="eventservicegroup" value="224.1.2.8:45703"/>
<param name="eventservicedevice" value="b.9001.da"/>
<generator definition="eelgenerator.xml"/>
</eventservice>
```
Create `eelgenerator.xml` with the following contents.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE eventgenerator SYSTEM "file:///usr/share/emane/dtd/eventgenerator.dtd">
<eventgenerator library="eelgenerator">
<param name="inputfile" value="scenario.eel" />
<paramlist name="loader">
<item value="commeffect:eelloadercommeffect:delta"/>
<item value="location,velocity,orientation:eelloaderlocation:delta"/>
<item value="pathloss:eelloaderpathloss:delta"/>
<item value="antennaprofile:eelloaderantennaprofile:delta"/>
</paramlist>
</eventgenerator>
```
Create `scenario.eel` with the following contents.
```shell
0.0 nem:1 antennaprofile 1,0.0,0.0
0.0 nem:4 antennaprofile 2,0.0,0.0
#
0.0 nem:1 pathloss nem:2,60 nem:3,60 nem:4,60
0.0 nem:2 pathloss nem:3,60 nem:4,60
0.0 nem:3 pathloss nem:4,60
#
0.0 nem:1 location gps 40.025495,-74.315441,3.0
0.0 nem:2 location gps 40.025495,-74.312501,3.0
0.0 nem:3 location gps 40.023235,-74.315441,3.0
0.0 nem:4 location gps 40.023235,-74.312501,3.0
0.0 nem:4 velocity 180.0,0.0,10.0
#
30.0 nem:1 velocity 20.0,0.0,10.0
30.0 nem:1 orientation 0.0,0.0,10.0
30.0 nem:1 antennaprofile 1,60.0,0.0
30.0 nem:4 velocity 270.0,0.0,10.0
#
60.0 nem:1 antennaprofile 1,105.0,0.0
60.0 nem:4 antennaprofile 2,45.0,0.0
#
90.0 nem:1 velocity 90.0,0.0,10.0
90.0 nem:1 orientation 0.0,0.0,0.0
90.0 nem:1 antennaprofile 1,45.0,0.0
```
Run the EMANE event service, monitor what is output on n1 for events
dumped and see the link changes within the CORE GUI.
```shell
emaneeventservice -l 3 eventservice.xml
```
### Stages
The events sent will trigger 4 different states.
* State 1
* n2 and n3 see each other
* n4 and n3 are pointing away
* State 2
* n2 and n3 see each other
* n1 and n2 see each other
* n4 and n3 see each other
* State 3
* n2 and n3 see each other
* n4 and n3 are pointing at each other but blocked
* State 4
* n2 and n3 see each other
* n4 and n3 see each other

103
docs/emane/eel.md Normal file
View file

@ -0,0 +1,103 @@
# EMANE Emulation Event Log (EEL) Generator
* Table of Contents
{:toc}
## Overview
Introduction to using the EMANE event service and eel files to provide events.
[EMANE Demo 1](https://github.com/adjacentlink/emane-tutorial/wiki/Demonstration-1)
for more specifics.
## Run Demo
1. Select `Open...` within the GUI
1. Load `emane-demo-eel.xml`
1. Click ![Start Button](../static/gui/start.gif)
1. After startup completes, double click n1 to bring up the nodes terminal
## Example Demo
This demo will go over defining an EMANE event service and eel file to drive
an emane event service.
### Viewing Events
On n1 we will use the EMANE event dump utility to listen to events.
```shell
root@n1:/tmp/pycore.46777/n1.conf# emaneevent-dump -i ctrl0
```
### Sending Events
On the host machine we will create the following files and start the
EMANE event service targeting the control network.
> **WARNING:** make sure to set the `eventservicedevice` to the proper control
> network value
Create `eventservice.xml` with the following contents.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE eventservice SYSTEM "file:///usr/share/emane/dtd/eventservice.dtd">
<eventservice>
<param name="eventservicegroup" value="224.1.2.8:45703"/>
<param name="eventservicedevice" value="b.9001.f"/>
<generator definition="eelgenerator.xml"/>
</eventservice>
```
Next we will create the `eelgenerator.xml` file. The EEL Generator is actually
a plugin that loads sentence parsing plugins. The sentence parsing plugins know
how to convert certain sentences, in this case commeffect, location, velocity,
orientation, pathloss and antennaprofile sentences, into their corresponding
emane event equivalents.
* commeffect:eelloadercommeffect:delta
* location,velocity,orientation:eelloaderlocation:delta
* pathloss:eelloaderpathloss:delta
* antennaprofile:eelloaderantennaprofile:delta
These configuration items tell the EEL Generator which sentences to map to
which plugin and whether to issue delta or full updates.
Create `eelgenerator.xml` with the following contents.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE eventgenerator SYSTEM "file:///usr/share/emane/dtd/eventgenerator.dtd">
<eventgenerator library="eelgenerator">
<param name="inputfile" value="scenario.eel" />
<paramlist name="loader">
<item value="commeffect:eelloadercommeffect:delta"/>
<item value="location,velocity,orientation:eelloaderlocation:delta"/>
<item value="pathloss:eelloaderpathloss:delta"/>
<item value="antennaprofile:eelloaderantennaprofile:delta"/>
</paramlist>
</eventgenerator>
```
Finally, create `scenario.eel` with the following contents.
```shell
0.0 nem:1 pathloss nem:2,90.0
0.0 nem:2 pathloss nem:1,90.0
0.0 nem:1 location gps 40.031075,-74.523518,3.000000
0.0 nem:2 location gps 40.031165,-74.523412,3.000000
```
Start the EMANE event service using the files created above.
```shell
emaneeventservice eventservice.xml -l 3
```
### Sent Events
If we go back to look at our original terminal we will see the events logged
out to the terminal.
```shell
root@n1:/tmp/pycore.46777/n1.conf# emaneevent-dump -i ctrl0
[1601858142.917224] nem: 0 event: 100 len: 66 seq: 1 [Location]
UUID: 0af267be-17d3-4103-9f76-6f697e13bcec
(1, {'latitude': 40.031075, 'altitude': 3.0, 'longitude': -74.823518})
(2, {'latitude': 40.031165, 'altitude': 3.0, 'longitude': -74.523412})
[1601858142.917466] nem: 1 event: 101 len: 14 seq: 2 [Pathloss]
UUID: 0af267be-17d3-4103-9f76-6f697e13bcec
(2, {'forward': 90.0, 'reverse': 90.0})
[1601858142.917889] nem: 2 event: 101 len: 14 seq: 3 [Pathloss]
UUID: 0af267be-17d3-4103-9f76-6f697e13bcec
(1, {'forward': 90.0, 'reverse': 90.0})
```

160
docs/emane/files.md Normal file
View file

@ -0,0 +1,160 @@
# EMANE XML Files
* Table of Contents
{:toc}
## Overview
Introduction to the XML files generated by CORE used to drive EMANE for
a given node.
[EMANE Demo 0](https://github.com/adjacentlink/emane-tutorial/wiki/Demonstration-0)
may provide more helpful details.
## Run Demo
1. Select `Open...` within the GUI
1. Load `emane-demo-files.xml`
1. Click ![Start Button](../static/gui/start.gif)
1. After startup completes, double click n1 to bring up the nodes terminal
## Example Demo
We will take a look at the files generated in the example demo provided. In this
case we are running the RF Pipe model.
### Generated Files
|Name|Description|
|---|---|
|\<node name>-platform.xml|configuration file for the emulator instances|
|\<interface name>-nem.xml|configuration for creating a NEM|
|\<interface name>-mac.xml|configuration for defining a NEMs MAC layer|
|\<interface name>-phy.xml|configuration for defining a NEMs PHY layer|
|\<interface name>-trans-virtual.xml|configuration when a virtual transport is being used|
|\<interface name>-trans.xml|configuration when a raw transport is being used|
### Listing File
Below are the files within n1 after starting the demo session.
```shell
root@n1:/tmp/pycore.46777/n1.conf# ls
eth0-mac.xml eth0-trans-virtual.xml n1-platform.xml var.log
eth0-nem.xml ipforward.sh quaggaboot.sh var.run
eth0-phy.xml n1-emane.log usr.local.etc.quagga var.run.quagga
```
### Platform XML
The root configuration file used to run EMANE for a node is the platform xml file.
In this demo we are looking at `n1-platform.xml`.
* lists all configuration values set for the platform
* The unique nem id given for each interface that EMANE will create for this node
* The path to the file(s) used for definition for a given nem
```shell
root@n1:/tmp/pycore.46777/n1.conf# cat n1-platform.xml
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE platform SYSTEM "file:///usr/share/emane/dtd/platform.dtd">
<platform>
<param name="antennaprofilemanifesturi" value=""/>
<param name="controlportendpoint" value="0.0.0.0:47000"/>
<param name="eventservicedevice" value="ctrl0"/>
<param name="eventservicegroup" value="224.1.2.8:45703"/>
<param name="eventservicettl" value="1"/>
<param name="otamanagerchannelenable" value="1"/>
<param name="otamanagerdevice" value="ctrl0"/>
<param name="otamanagergroup" value="224.1.2.8:45702"/>
<param name="otamanagerloopback" value="0"/>
<param name="otamanagermtu" value="0"/>
<param name="otamanagerpartcheckthreshold" value="2"/>
<param name="otamanagerparttimeoutthreshold" value="5"/>
<param name="otamanagerttl" value="1"/>
<param name="stats.event.maxeventcountrows" value="0"/>
<param name="stats.ota.maxeventcountrows" value="0"/>
<param name="stats.ota.maxpacketcountrows" value="0"/>
<nem id="1" name="tap1.0.f" definition="eth0-nem.xml">
<transport definition="eth0-trans-virtual.xml">
<param name="device" value="eth0"/>
</transport>
</nem>
</platform>
```
### NEM XML
The nem definition will contain reference to the transport, mac, and phy xml
definitions being used for a given nem.
```shell
root@n1:/tmp/pycore.46777/n1.conf# cat eth0-nem.xml
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE nem SYSTEM "file:///usr/share/emane/dtd/nem.dtd">
<nem name="emane_rfpipe NEM">
<transport definition="eth0-trans-virtual.xml"/>
<mac definition="eth0-mac.xml"/>
<phy definition="eth0-phy.xml"/>
</nem>
```
### MAC XML
MAC layer configuration settings would be found in this file. CORE will write
out all values, even if the value is a default value.
```shell
root@n1:/tmp/pycore.46777/n1.conf# cat eth0-mac.xml
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE mac SYSTEM "file:///usr/share/emane/dtd/mac.dtd">
<mac name="emane_rfpipe MAC" library="rfpipemaclayer">
<param name="datarate" value="1000000"/>
<param name="delay" value="0.000000"/>
<param name="enablepromiscuousmode" value="0"/>
<param name="flowcontrolenable" value="0"/>
<param name="flowcontroltokens" value="10"/>
<param name="jitter" value="0.000000"/>
<param name="neighbormetricdeletetime" value="60.000000"/>
<param name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
<param name="radiometricenable" value="0"/>
<param name="radiometricreportinterval" value="1.000000"/>
</mac>
```
### PHY XML
PHY layer configuration settings would be found in this file. CORE will write
out all values, even if the value is a default value.
```shell
root@n1:/tmp/pycore.46777/n1.conf# cat eth0-phy.xml
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE phy SYSTEM "file:///usr/share/emane/dtd/phy.dtd">
<phy name="emane_rfpipe PHY">
<param name="bandwidth" value="1000000"/>
<param name="fading.model" value="none"/>
<param name="fading.nakagami.distance0" value="100.000000"/>
<param name="fading.nakagami.distance1" value="250.000000"/>
<param name="fading.nakagami.m0" value="0.750000"/>
<param name="fading.nakagami.m1" value="1.000000"/>
<param name="fading.nakagami.m2" value="200.000000"/>
<param name="fixedantennagain" value="0.000000"/>
<param name="fixedantennagainenable" value="1"/>
<param name="frequency" value="2347000000"/>
<param name="frequencyofinterest" value="2347000000"/>
<param name="noisebinsize" value="20"/>
<param name="noisemaxclampenable" value="0"/>
<param name="noisemaxmessagepropagation" value="200000"/>
<param name="noisemaxsegmentduration" value="1000000"/>
<param name="noisemaxsegmentoffset" value="300000"/>
<param name="noisemode" value="none"/>
<param name="propagationmodel" value="2ray"/>
<param name="subid" value="1"/>
<param name="systemnoisefigure" value="4.000000"/>
<param name="timesyncthreshold" value="10000"/>
<param name="txpower" value="0.000000"/>
</phy>
```
### Transport XML
```shell
root@n1:/tmp/pycore.46777/n1.conf# cat eth0-trans-virtual.xml
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE transport SYSTEM "file:///usr/share/emane/dtd/transport.dtd">
<transport name="Virtual Transport" library="transvirtual">
<param name="bitrate" value="0"/>
<param name="devicepath" value="/dev/net/tun"/>
</transport>
```

103
docs/emane/gpsd.md Normal file
View file

@ -0,0 +1,103 @@
# EMANE GPSD Integration
* Table of Contents
{:toc}
## Overview
Introduction to integrating gpsd in CORE with EMANE.
[EMANE Demo 0](https://github.com/adjacentlink/emane-tutorial/wiki/Demonstration-0)
may provide more helpful details.
> **WARNING:** requires installation of [gpsd](https://gpsd.gitlab.io/gpsd/index.html)
## Run Demo
1. Select `Open...` within the GUI
1. Load `emane-demo-gpsd.xml`
1. Click ![Start Button](../static/gui/start.gif)
1. After startup completes, double click n1 to bring up the nodes terminal
## Example Demo
This section will cover how to run a gpsd location agent within EMANE, that will
write out locations to a pseudo terminal file. That file can be read in by the
gpsd server and make EMANE location events available to gpsd clients.
### EMANE GPSD Event Daemon
First create an `eventdaemon.xml` file on n1 with the following contents.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE eventdaemon SYSTEM "file:///usr/share/emane/dtd/eventdaemon.dtd">
<eventdaemon nemid="1">
<param name="eventservicegroup" value="224.1.2.8:45703"/>
<param name="eventservicedevice" value="ctrl0"/>
<agent definition="gpsdlocationagent.xml"/>
</eventdaemon>
```
Then create the `gpsdlocationagent.xml` file on n1 with the following contents.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE eventagent SYSTEM "file:///usr/share/emane/dtd/eventagent.dtd">
<eventagent library="gpsdlocationagent">
<param name="pseudoterminalfile" value="gps.pty"/>
</eventagent>
```
Start the EMANE event agent. This will facilitate feeding location events
out to a pseudo terminal file defined above.
```shell
emaneeventd eventdaemon.xml -r -d -l 3 -f emaneeventd.log
```
Start gpsd, reading in the pseudo terminal file.
```shell
gpsd -G -n -b $(cat gps.pty)
```
### EMANE EEL Event Daemon
EEL Events will be played out from the actual host machine over the designated
control network interface. Create the following files in the same directory
somewhere on your host.
> **NOTE:** make sure the below eventservicedevice matches the control network
> device being used on the host for EMANE
Create `eventservice.xml` on the host machine with the following contents.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE eventservice SYSTEM "file:///usr/share/emane/dtd/eventservice.dtd">
<eventservice>
<param name="eventservicegroup" value="224.1.2.8:45703"/>
<param name="eventservicedevice" value="b.9001.1"/>
<generator definition="eelgenerator.xml"/>
</eventservice>
```
Create `eelgenerator.xml` on the host machine with the following contents.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE eventgenerator SYSTEM "file:///usr/share/emane/dtd/eventgenerator.dtd">
<eventgenerator library="eelgenerator">
<param name="inputfile" value="scenario.eel" />
<paramlist name="loader">
<item value="commeffect:eelloadercommeffect:delta"/>
<item value="location,velocity,orientation:eelloaderlocation:delta"/>
<item value="pathloss:eelloaderpathloss:delta"/>
<item value="antennaprofile:eelloaderantennaprofile:delta"/>
</paramlist>
</eventgenerator>
```
Create `scenario.eel` file with the following contents.
```shell
0.0 nem:1 location gps 40.031075,-74.523518,3.000000
0.0 nem:2 location gps 40.031165,-74.523412,3.000000
```
Start the EEL event service, which will send the events defined in the file above
over the control network to all EMANE nodes. These location events will be received
and provided to gpsd. This allow gpsd client to connect to and get gps locations.
```shell
emaneeventservice eventservice.xml -l 3
```

80
docs/emane/precomputed.md Normal file
View file

@ -0,0 +1,80 @@
# EMANE Procomputed
* Table of Contents
{:toc}
## Overview
Introduction to using the precomputed propagation model.
[EMANE Demo 1](https://github.com/adjacentlink/emane-tutorial/wiki/Demonstration-1)
for more specifics.
## Run Demo
1. Select `Open...` within the GUI
1. Load `emane-demo-precomputed.xml`
1. Click ![Start Button](../static/gui/start.gif)
1. After startup completes, double click n1 to bring up the nodes terminal
## Example Demo
This demo is uing the RF Pipe model witht he propagation model set to
precomputed.
### Failed Pings
Due to using precomputed and having not sent any pathloss events, the nodes
cannot ping eachother yet.
Open a terminal on n1.
```shell
root@n1:/tmp/pycore.46777/n1.conf# ping 10.0.0.2
connect: Network is unreachable
```
### EMANE Shell
You can leverage `emanesh` to investigate why packets are being dropped.
```shell
root@n1:/tmp/pycore.46777/n1.conf# emanesh localhost get table nems phy BroadcastPacketDropTable0 UnicastPacketDropTable0
nem 1 phy BroadcastPacketDropTable0
| NEM | Out-of-Band | Rx Sensitivity | Propagation Model | Gain Location | Gain Horizon | Gain Profile | Not FOI | Spectrum Clamp | Fade Location | Fade Algorithm | Fade Select |
| 2 | 0 | 0 | 169 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
nem 1 phy UnicastPacketDropTable0
| NEM | Out-of-Band | Rx Sensitivity | Propagation Model | Gain Location | Gain Horizon | Gain Profile | Not FOI | Spectrum Clamp | Fade Location | Fade Algorithm | Fade Select |
```
In the example above we can see that the reason packets are being dropped is due to
the propogation model and that is because we have not issued any pathloss events.
You can run another command to validate if you have received any pathloss events.
```shell
root@n1:/tmp/pycore.46777/n1.conf# emanesh localhost get table nems phy PathlossEventInfoTable
nem 1 phy PathlossEventInfoTable
| NEM | Forward Pathloss | Reverse Pathloss |
```
### Pathloss Events
On the host we will send pathloss events from all nems to all other nems.
> **NOTE:** make sure properly specify the right control network device
```shell
emaneevent-pathloss 1:2 90 -i <controlnet device>
```
Now if we check for pathloss events on n2 we will see what was just sent above.
```shell
root@n1:/tmp/pycore.46777/n1.conf# emanesh localhost get table nems phy PathlossEventInfoTable
nem 1 phy PathlossEventInfoTable
| NEM | Forward Pathloss | Reverse Pathloss |
| 2 | 90.0 | 90.0
```
You should also now be able to ping n1 from n2.
```shell
root@n1:/tmp/pycore.46777/n1.conf# ping -c 3 10.0.0.2
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=3.06 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=2.12 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=1.99 ms
--- 10.0.0.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 1.991/2.393/3.062/0.479 ms
```

View file

@ -9,63 +9,14 @@ or build and install a python wheel.
> **WARNING:** if Docker is installed, the default iptable rules will block CORE traffic
### Tools Used
The following tools will be leveraged during installation:
|Tool|Description|
|---|---|
|[pip](https://pip.pypa.io/en/stable/)|used to install pipx|
|[pipx](https://pipxproject.github.io/pipx/)|used to install standalone python tools (invoke, poetry)|
|[invoke](http://www.pyinvoke.org/)|used to run provided tasks (install, uninstall, reinstall, etc)|
|[poetry](https://python-poetry.org/)|used to install python virtual environment or building a python wheel|
### Files
The following is a list of files that would be installed after running the automated installation.
> **NOTE:** the default install prefix is /usr/local, but can be changed as noted below
* executable files
* <prefix>/bin/{core-daemon, core-gui, vcmd, vnoded, etc}
* tcl/tk gui files
* <prefix>/lib/core
* <prefix>/share/core/icons
* example imn files
* <prefix>/share/core/examples
* python files
* poetry virtual env
* `cd <repo>/daemon && poetry env info`
* ~/.cache/pypoetry/virtualenvs/
* local python install
* default install path for python3 installation of a wheel
* `python3 -c "import core; print(core.__file__)"`
* configuration files
* /etc/core/{core.conf, logging.conf}
* ospf mdr repository files
* <repo>/../ospf-mdr
* emane repository files
* <repo>/../emane
### Installed Executables
After the installation complete it will have installed the following scripts.
| Name | Description |
|---|---|
| core-cleanup | tool to help removed lingering core created containers, bridges, directories |
| core-cli | tool to query, open xml files, and send commands using gRPC |
| core-daemon | runs the backed core server providing TLV and gRPC APIs |
| core-gui | runs the legacy tcl/tk based GUI |
| core-imn-to-xml | tool to help automate converting a .imn file to .xml format |
| core-manage | tool to add, remove, or check for services, models, and node types |
| core-pygui | runs the new python/tk based GUI |
| core-python | provides a convenience for running the core python virtual environment |
| core-route-monitor | tool to help monitor traffic across nodes and feed that to SDT |
| core-service-update | tool to update automate modifying a legacy service to match current naming |
| coresendmsg | tool to send TLV API commands from command line |
### Required Hardware
### Requirements
Any computer capable of running Linux should be able to run CORE. Since the physical machine will be hosting numerous
containers, as a general rule you should select a machine having as much RAM and CPU resources as possible.
* Linux Kernel v3.3+
* iproute2 4.5+ is a requirement for bridge related commands
* ebtables not backed by nftables
### Supported Linux Distributions
Plan is to support recent Ubuntu and CentOS LTS releases.
@ -89,11 +40,58 @@ sudo yum install -y kernel-modules-extra
sudo modprobe sch_netem
```
### Utility Requirements
The following are known dependencies that will result in errors when not met.
### Tools Used
The following tools will be leveraged during installation:
* iproute2 4.5+ is a requirement for bridge related commands
* ebtables not backed by nftables
|Tool|Description|
|---|---|
|[pip](https://pip.pypa.io/en/stable/)|used to install pipx|
|[pipx](https://pipxproject.github.io/pipx/)|used to install standalone python tools (invoke, poetry)|
|[invoke](http://www.pyinvoke.org/)|used to run provided tasks (install, uninstall, reinstall, etc)|
|[poetry](https://python-poetry.org/)|used to install python virtual environment or building a python wheel|
### Files
The following is a list of files that would be installed after running the automated installation.
> **NOTE:** the default install prefix is /usr/local, but can be changed as noted below
* executable files
* `<prefix>/bin/{core-daemon, core-gui, vcmd, vnoded, etc}`
* tcl/tk gui files
* `<prefix>/lib/core`
* `<prefix>/share/core/icons`
* example imn files
* `<prefix>/share/core/examples`
* python files
* poetry virtual env
* `cd <repo>/daemon && poetry env info`
* `~/.cache/pypoetry/virtualenvs/`
* local python install
* default install path for python3 installation of a wheel
* `python3 -c "import core; print(core.__file__)"`
* configuration files
* `/etc/core/{core.conf, logging.conf}`
* ospf mdr repository files
* `<repo>/../ospf-mdr`
* emane repository files
* `<repo>/../emane`
### Installed Executables
After the installation complete it will have installed the following scripts.
| Name | Description |
|---|---|
| core-cleanup | tool to help removed lingering core created containers, bridges, directories |
| core-cli | tool to query, open xml files, and send commands using gRPC |
| core-daemon | runs the backed core server providing TLV and gRPC APIs |
| core-gui | runs the legacy tcl/tk based GUI |
| core-imn-to-xml | tool to help automate converting a .imn file to .xml format |
| core-manage | tool to add, remove, or check for services, models, and node types |
| core-pygui | runs the new python/tk based GUI |
| core-python | provides a convenience for running the core python virtual environment |
| core-route-monitor | tool to help monitor traffic across nodes and feed that to SDT |
| core-service-update | tool to update automate modifying a legacy service to match current naming |
| coresendmsg | tool to send TLV API commands from command line |
## Upgrading from Older Release
Please make sure to uninstall any previous installations of CORE cleanly

View file

@ -180,68 +180,77 @@ consider contributing it to the CORE project.
Below is the skeleton for a custom service with some documentation. Most
people would likely only setup the required class variables **(name/group)**.
Then define the **configs** (files they want to generate) and implement the
**generate_confifs** function to dynamically create the files wanted. Finally
**generate_config** function to dynamically create the files wanted. Finally
the **startup** commands would be supplied, which typically tends to be
running the shell files generated.
```python
"""
Simple example custom service, used to drive shell commands on a node.
"""
from typing import Tuple
from core.nodes.base import CoreNode
from core.services.coreservices import CoreService, ServiceMode
class MyService(CoreService):
class ExampleService(CoreService):
"""
Custom CORE Service
Example Custom CORE Service
:var str name: name used as a unique ID for this service and is required, no spaces
:var str group: allows you to group services within the GUI under a common name
:var tuple executables: executables this service depends on to function, if executable is
:cvar name: name used as a unique ID for this service and is required, no spaces
:cvar group: allows you to group services within the GUI under a common name
:cvar executables: executables this service depends on to function, if executable is
not on the path, service will not be loaded
:var tuple dependencies: services that this service depends on for startup, tuple of service names
:var tuple dirs: directories that this service will create within a node
:var tuple configs: files that this service will generate, without a full path this file goes in
the node's directory e.g. /tmp/pycore.12345/n1.conf/myfile
:var tuple startup: commands used to start this service, any non-zero exit code will cause a failure
:var tuple validate: commands used to validate that a service was started, any non-zero exit code
will cause a failure
:var ServiceMode validation_mode: validation mode, used to determine startup success.
:cvar dependencies: services that this service depends on for startup, tuple of
service names
:cvar dirs: directories that this service will create within a node
:cvar configs: files that this service will generate, without a full path this file
goes in the node's directory e.g. /tmp/pycore.12345/n1.conf/myfile
:cvar startup: commands used to start this service, any non-zero exit code will
cause a failure
:cvar validate: commands used to validate that a service was started, any non-zero
exit code will cause a failure
:cvar validation_mode: validation mode, used to determine startup success.
NON_BLOCKING - runs startup commands, and validates success with validation commands
BLOCKING - runs startup commands, and validates success with the startup commands themselves
TIMER - runs startup commands, and validates success by waiting for "validation_timer" alone
:var int validation_timer: time in seconds for a service to wait for validation, before determining
success in TIMER/NON_BLOCKING modes.
:var float validation_validation_period: period in seconds to wait before retrying validation,
:cvar validation_timer: time in seconds for a service to wait for validation, before
determining success in TIMER/NON_BLOCKING modes.
:cvar validation_period: period in seconds to wait before retrying validation,
only used in NON_BLOCKING mode
:var tuple shutdown: shutdown commands to stop this service
:cvar shutdown: shutdown commands to stop this service
"""
name = "MyService"
group = "Utility"
executables = ()
dependencies = ()
dirs = ()
configs = ("myservice1.sh", "myservice2.sh")
startup = tuple(f"sh {x}" for x in configs)
validate = ()
validation_mode = ServiceMode.NON_BLOCKING
validation_timer = 5
validation_period = 0.5
shutdown = ()
name: str = "ExampleService"
group: str = "Utility"
executables: Tuple[str, ...] = ()
dependencies: Tuple[str, ...] = ()
dirs: Tuple[str, ...] = ()
configs: Tuple[str, ...] = ("myservice1.sh", "myservice2.sh")
startup: Tuple[str, ...] = tuple(f"sh {x}" for x in configs)
validate: Tuple[str, ...] = ()
validation_mode: ServiceMode = ServiceMode.NON_BLOCKING
validation_timer: int = 5
validation_period: float = 0.5
shutdown: Tuple[str, ...] = ()
@classmethod
def on_load(cls):
def on_load(cls) -> None:
"""
Provides a way to run some arbitrary logic when the service is loaded, possibly to help facilitate
dynamic settings for the environment.
Provides a way to run some arbitrary logic when the service is loaded, possibly
to help facilitate dynamic settings for the environment.
:return: nothing
"""
pass
@classmethod
def get_configs(cls, node):
def get_configs(cls, node: CoreNode) -> Tuple[str, ...]:
"""
Provides a way to dynamically generate the config files from the node a service will run.
Defaults to the class definition and can be left out entirely if not needed.
Provides a way to dynamically generate the config files from the node a service
will run. Defaults to the class definition and can be left out entirely if not
needed.
:param node: core node that the service is being ran on
:return: tuple of config files to create
@ -249,32 +258,31 @@ class MyService(CoreService):
return cls.configs
@classmethod
def generate_config(cls, node, filename):
def generate_config(cls, node: CoreNode, filename: str) -> str:
"""
Returns a string representation for a file, given the node the service is starting on the config filename
that this information will be used for. This must be defined, if "configs" are defined.
Returns a string representation for a file, given the node the service is
starting on the config filename that this information will be used for. This
must be defined, if "configs" are defined.
:param node: core node that the service is being ran on
:param str filename: configuration file to generate
:param filename: configuration file to generate
:return: configuration file content
:rtype: str
"""
cfg = "#!/bin/sh\n"
if filename == cls.configs[0]:
cfg += "# auto-generated by MyService (sample.py)\n"
for ifc in node.get_ifaces():
cfg += f'echo "Node {node.name} has interface {ifc.name}"\n'
for iface in node.get_ifaces():
cfg += f'echo "Node {node.name} has interface {iface.name}"\n'
elif filename == cls.configs[1]:
cfg += "echo hello"
return cfg
@classmethod
def get_startup(cls, node):
def get_startup(cls, node: CoreNode) -> Tuple[str, ...]:
"""
Provides a way to dynamically generate the startup commands from the node a service will run.
Defaults to the class definition and can be left out entirely if not needed.
Provides a way to dynamically generate the startup commands from the node a
service will run. Defaults to the class definition and can be left out entirely
if not needed.
:param node: core node that the service is being ran on
:return: tuple of startup commands to run
@ -282,10 +290,11 @@ class MyService(CoreService):
return cls.startup
@classmethod
def get_validate(cls, node):
def get_validate(cls, node: CoreNode) -> Tuple[str, ...]:
"""
Provides a way to dynamically generate the validate commands from the node a service will run.
Defaults to the class definition and can be left out entirely if not needed.
Provides a way to dynamically generate the validate commands from the node a
service will run. Defaults to the class definition and can be left out entirely
if not needed.
:param node: core node that the service is being ran on
:return: tuple of commands to validate service startup with