Merge branch 'develop-notcl' into develop

This commit is contained in:
Blake Harnden 2022-03-22 10:03:57 -07:00
commit 346364d705
270 changed files with 276 additions and 49532 deletions

View file

@ -6,10 +6,6 @@ if WANT_DOCS
DOCS = docs man
endif
if WANT_GUI
GUI = gui
endif
if WANT_DAEMON
DAEMON = daemon
endif
@ -19,7 +15,7 @@ if WANT_NETNS
endif
# keep docs last due to dependencies on binaries
SUBDIRS = $(GUI) $(DAEMON) $(NETNS) $(DOCS)
SUBDIRS = $(DAEMON) $(NETNS) $(DOCS)
ACLOCAL_AMFLAGS = -I config
@ -115,7 +111,6 @@ $(info creating file $1 from $1.in)
-e 's,[@]CORE_STATE_DIR[@],$(CORE_STATE_DIR),g' \
-e 's,[@]CORE_DATA_DIR[@],$(CORE_DATA_DIR),g' \
-e 's,[@]CORE_CONF_DIR[@],$(CORE_CONF_DIR),g' \
-e 's,[@]CORE_GUI_CONF_DIR[@],$(CORE_GUI_CONF_DIR),g' \
< $1.in > $1
endef
@ -123,7 +118,6 @@ all: change-files
.PHONY: change-files
change-files:
$(call change-files,gui/core-gui-legacy)
$(call change-files,daemon/core/constants.py)
$(call change-files,netns/setup.py)

View file

@ -1,9 +1,5 @@
#!/bin/sh
#
# (c)2010-2012 the Boeing Company
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
# Bootstrap the autoconf system.
#

View file

@ -30,25 +30,14 @@ AC_SUBST(CORE_CONF_DIR)
AC_SUBST(CORE_DATA_DIR)
AC_SUBST(CORE_STATE_DIR)
# CORE GUI configuration files and preferences in CORE_GUI_CONF_DIR
# scenario files in ~/.core/configs/
AC_ARG_WITH([guiconfdir],
[AS_HELP_STRING([--with-guiconfdir=dir],
[specify GUI configuration directory])],
[CORE_GUI_CONF_DIR="$with_guiconfdir"],
[CORE_GUI_CONF_DIR="\$\${HOME}/.core"])
AC_SUBST(CORE_GUI_CONF_DIR)
AC_ARG_ENABLE([gui],
[AS_HELP_STRING([--enable-gui[=ARG]],
[build and install the GUI (default is yes)])],
[], [enable_gui=yes])
AC_SUBST(enable_gui)
# documentation option
AC_ARG_ENABLE([docs],
[AS_HELP_STRING([--enable-docs[=ARG]],
[build python documentation (default is no)])],
[], [enable_docs=no])
AC_SUBST(enable_docs)
# python option
AC_ARG_ENABLE([python],
[AS_HELP_STRING([--enable-python[=ARG]],
[build and install the python bindings (default is yes)])],
@ -94,27 +83,6 @@ if test "x$enable_daemon" = "xyes"; then
want_python=yes
want_linux_netns=yes
# Checks for libraries.
AC_CHECK_LIB([netgraph], [NgMkSockNode])
# Checks for header files.
AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h stdint.h stdlib.h string.h sys/ioctl.h sys/mount.h sys/socket.h sys/time.h termios.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_C_INLINE
AC_TYPE_INT32_T
AC_TYPE_PID_T
AC_TYPE_SIZE_T
AC_TYPE_SSIZE_T
AC_TYPE_UINT32_T
AC_TYPE_UINT8_T
# Checks for library functions.
AC_FUNC_FORK
AC_FUNC_MALLOC
AC_FUNC_REALLOC
AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname])
AM_PATH_PYTHON(3.6)
AS_IF([$PYTHON -m grpc_tools.protoc -h &> /dev/null], [], [AC_MSG_ERROR([please install python grpcio-tools])])
@ -171,6 +139,25 @@ fi
if [ test "x$enable_daemon" = "xyes" || test "x$enable_vnodedonly" = "xyes" ] ; then
want_linux_netns=yes
# Checks for header files.
AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h stdint.h stdlib.h string.h sys/ioctl.h sys/mount.h sys/socket.h sys/time.h termios.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_C_INLINE
AC_TYPE_INT32_T
AC_TYPE_PID_T
AC_TYPE_SIZE_T
AC_TYPE_SSIZE_T
AC_TYPE_UINT32_T
AC_TYPE_UINT8_T
# Checks for library functions.
AC_FUNC_FORK
AC_FUNC_MALLOC
AC_FUNC_REALLOC
AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname])
PKG_CHECK_MODULES(libev, libev,
AC_MSG_RESULT([found libev using pkgconfig OK])
AC_SUBST(libev_CFLAGS)
@ -209,7 +196,6 @@ if [test "x$want_python" = "xyes" && test "x$enable_docs" = "xyes"] ; then
fi
# Variable substitutions
AM_CONDITIONAL(WANT_GUI, test x$enable_gui = xyes)
AM_CONDITIONAL(WANT_DAEMON, test x$enable_daemon = xyes)
AM_CONDITIONAL(WANT_DOCS, test x$want_docs = xyes)
AM_CONDITIONAL(WANT_PYTHON, test x$want_python = xyes)
@ -224,9 +210,6 @@ fi
# Output files
AC_CONFIG_FILES([Makefile
gui/version.tcl
gui/Makefile
gui/icons/Makefile
man/Makefile
docs/Makefile
daemon/Makefile
@ -248,17 +231,12 @@ Build:
Prefix: ${prefix}
Exec Prefix: ${exec_prefix}
GUI:
GUI path: ${CORE_LIB_DIR}
GUI config: ${CORE_GUI_CONF_DIR}
Daemon:
Daemon path: ${bindir}
Daemon config: ${CORE_CONF_DIR}
Python: ${PYTHON}
Features to build:
Build GUI: ${enable_gui}
Build Daemon: ${enable_daemon}
Documentation: ${want_docs}

View file

@ -1,8 +1,4 @@
# CORE
# (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
# Makefile for building netns components.
#

View file

@ -243,13 +243,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
# clear previous state and setup for creation
session.clear()
session.directory.mkdir(exist_ok=True)
if request.definition:
state = EventTypes.DEFINITION_STATE
else:
state = EventTypes.CONFIGURATION_STATE
session.directory.mkdir(exist_ok=True)
session.set_state(state)
session.user = request.session.user
if request.session.user:
session.set_user(request.session.user)
# session options
session.options.config_reset()

View file

@ -1102,7 +1102,6 @@ class ConfigEvent:
data_types=list(proto.data_types),
data_values=proto.data_values,
captions=proto.captions,
bitmap=proto.bitmap,
possible_values=proto.possible_values,
groups=proto.groups,
iface_id=proto.iface_id,

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,60 +0,0 @@
"""
Defines core server for handling TCP connections.
"""
import socketserver
from core.emulator.coreemu import CoreEmu
class CoreServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
"""
TCP server class, manages sessions and spawns request handlers for
incoming connections.
"""
daemon_threads = True
allow_reuse_address = True
def __init__(self, server_address, handler_class, config=None):
"""
Server class initialization takes configuration data and calls
the socketserver constructor.
:param tuple[str, int] server_address: server host and port to use
:param class handler_class: request handler
:param dict config: configuration setting
"""
self.coreemu = CoreEmu(config)
self.config = config
socketserver.TCPServer.__init__(self, server_address, handler_class)
class CoreUdpServer(socketserver.ThreadingMixIn, socketserver.UDPServer):
"""
UDP server class, manages sessions and spawns request handlers for
incoming connections.
"""
daemon_threads = True
allow_reuse_address = True
def __init__(self, server_address, handler_class, mainserver):
"""
Server class initialization takes configuration data and calls
the SocketServer constructor
:param server_address:
:param class handler_class: request handler
:param mainserver:
"""
self.mainserver = mainserver
socketserver.UDPServer.__init__(self, server_address, handler_class)
def start(self):
"""
Thread target to run concurrently with the TCP server.
:return: nothing
"""
self.serve_forever()

View file

@ -1,178 +0,0 @@
"""
Converts CORE data objects into legacy API messages.
"""
import logging
from collections import OrderedDict
from typing import Dict, List
from core.api.tlv import coreapi, structutils
from core.api.tlv.enumerations import ConfigTlvs, NodeTlvs
from core.config import ConfigGroup, ConfigurableOptions
from core.emulator.data import ConfigData, NodeData
logger = logging.getLogger(__name__)
def convert_node(node_data: NodeData):
"""
Convenience method for converting NodeData to a packed TLV message.
:param core.emulator.data.NodeData node_data: node data to convert
:return: packed node message
"""
node = node_data.node
services = None
if node.services is not None:
services = "|".join([x.name for x in node.services])
server = None
if node.server is not None:
server = node.server.name
tlv_data = structutils.pack_values(
coreapi.CoreNodeTlv,
[
(NodeTlvs.NUMBER, node.id),
(NodeTlvs.TYPE, node.apitype.value),
(NodeTlvs.NAME, node.name),
(NodeTlvs.MODEL, node.type),
(NodeTlvs.EMULATION_SERVER, server),
(NodeTlvs.X_POSITION, int(node.position.x)),
(NodeTlvs.Y_POSITION, int(node.position.y)),
(NodeTlvs.CANVAS, node.canvas),
(NodeTlvs.SERVICES, services),
(NodeTlvs.LATITUDE, str(node.position.lat)),
(NodeTlvs.LONGITUDE, str(node.position.lon)),
(NodeTlvs.ALTITUDE, str(node.position.alt)),
(NodeTlvs.ICON, node.icon),
],
)
return coreapi.CoreNodeMessage.pack(node_data.message_type.value, tlv_data)
def convert_config(config_data):
"""
Convenience method for converting ConfigData to a packed TLV message.
:param core.emulator.data.ConfigData config_data: config data to convert
:return: packed message
"""
session = None
if config_data.session is not None:
session = str(config_data.session)
tlv_data = structutils.pack_values(
coreapi.CoreConfigTlv,
[
(ConfigTlvs.NODE, config_data.node),
(ConfigTlvs.OBJECT, config_data.object),
(ConfigTlvs.TYPE, config_data.type),
(ConfigTlvs.DATA_TYPES, config_data.data_types),
(ConfigTlvs.VALUES, config_data.data_values),
(ConfigTlvs.CAPTIONS, config_data.captions),
(ConfigTlvs.BITMAP, config_data.bitmap),
(ConfigTlvs.POSSIBLE_VALUES, config_data.possible_values),
(ConfigTlvs.GROUPS, config_data.groups),
(ConfigTlvs.SESSION, session),
(ConfigTlvs.IFACE_ID, config_data.iface_id),
(ConfigTlvs.NETWORK_ID, config_data.network_id),
(ConfigTlvs.OPAQUE, config_data.opaque),
],
)
return coreapi.CoreConfMessage.pack(config_data.message_type, tlv_data)
class ConfigShim:
"""
Provides helper methods for converting newer configuration values into TLV
compatible formats.
"""
@classmethod
def str_to_dict(cls, key_values: str) -> Dict[str, str]:
"""
Converts a TLV key/value string into an ordered mapping.
:param key_values:
:return: ordered mapping of key/value pairs
"""
key_values = key_values.split("|")
values = OrderedDict()
for key_value in key_values:
key, value = key_value.split("=", 1)
values[key] = value
return values
@classmethod
def groups_to_str(cls, config_groups: List[ConfigGroup]) -> str:
"""
Converts configuration groups to a TLV formatted string.
:param config_groups: configuration groups to format
:return: TLV configuration group string
"""
group_strings = []
for config_group in config_groups:
group_string = (
f"{config_group.name}:{config_group.start}-{config_group.stop}"
)
group_strings.append(group_string)
return "|".join(group_strings)
@classmethod
def config_data(
cls,
flags: int,
node_id: int,
type_flags: int,
configurable_options: ConfigurableOptions,
config: Dict[str, str],
) -> ConfigData:
"""
Convert this class to a Config API message. Some TLVs are defined
by the class, but node number, conf type flags, and values must
be passed in.
:param flags: message flags
:param node_id: node id
:param type_flags: type flags
:param configurable_options: options to create config data for
:param config: configuration values for options
:return: configuration data object
"""
key_values = None
captions = None
data_types = []
possible_values = []
logger.debug("configurable: %s", configurable_options)
logger.debug("configuration options: %s", configurable_options.configurations)
logger.debug("configuration data: %s", config)
for configuration in configurable_options.configurations():
if not captions:
captions = configuration.label
else:
captions += f"|{configuration.label}"
data_types.append(configuration.type.value)
options = ",".join(configuration.options)
possible_values.append(options)
_id = configuration.id
config_value = config.get(_id, configuration.default)
key_value = f"{_id}={config_value}"
if not key_values:
key_values = key_value
else:
key_values += f"|{key_value}"
groups_str = cls.groups_to_str(configurable_options.config_groups())
return ConfigData(
message_type=flags,
node=node_id,
object=configurable_options.name,
type=type_flags,
data_types=tuple(data_types),
data_values=key_values,
captions=captions,
possible_values="|".join(possible_values),
bitmap=configurable_options.bitmap,
groups=groups_str,
)

View file

@ -1,212 +0,0 @@
"""
Enumerations specific to the CORE TLV API.
"""
from enum import Enum
CORE_API_PORT = 4038
class MessageTypes(Enum):
"""
CORE message types.
"""
NODE = 0x01
LINK = 0x02
EXECUTE = 0x03
REGISTER = 0x04
CONFIG = 0x05
FILE = 0x06
INTERFACE = 0x07
EVENT = 0x08
SESSION = 0x09
EXCEPTION = 0x0A
class NodeTlvs(Enum):
"""
Node type, length, value enumerations.
"""
NUMBER = 0x01
TYPE = 0x02
NAME = 0x03
IP_ADDRESS = 0x04
MAC_ADDRESS = 0x05
IP6_ADDRESS = 0x06
MODEL = 0x07
EMULATION_SERVER = 0x08
SESSION = 0x0A
X_POSITION = 0x20
Y_POSITION = 0x21
CANVAS = 0x22
EMULATION_ID = 0x23
NETWORK_ID = 0x24
SERVICES = 0x25
LATITUDE = 0x30
LONGITUDE = 0x31
ALTITUDE = 0x32
ICON = 0x42
OPAQUE = 0x50
class LinkTlvs(Enum):
"""
Link type, length, value enumerations.
"""
N1_NUMBER = 0x01
N2_NUMBER = 0x02
DELAY = 0x03
BANDWIDTH = 0x04
LOSS = 0x05
DUP = 0x06
JITTER = 0x07
MER = 0x08
BURST = 0x09
SESSION = 0x0A
MBURST = 0x10
TYPE = 0x20
GUI_ATTRIBUTES = 0x21
UNIDIRECTIONAL = 0x22
EMULATION_ID = 0x23
NETWORK_ID = 0x24
KEY = 0x25
IFACE1_NUMBER = 0x30
IFACE1_IP4 = 0x31
IFACE1_IP4_MASK = 0x32
IFACE1_MAC = 0x33
IFACE1_IP6 = 0x34
IFACE1_IP6_MASK = 0x35
IFACE2_NUMBER = 0x36
IFACE2_IP4 = 0x37
IFACE2_IP4_MASK = 0x38
IFACE2_MAC = 0x39
IFACE2_IP6 = 0x40
IFACE2_IP6_MASK = 0x41
IFACE1_NAME = 0x42
IFACE2_NAME = 0x43
OPAQUE = 0x50
class ExecuteTlvs(Enum):
"""
Execute type, length, value enumerations.
"""
NODE = 0x01
NUMBER = 0x02
TIME = 0x03
COMMAND = 0x04
RESULT = 0x05
STATUS = 0x06
SESSION = 0x0A
class ConfigTlvs(Enum):
"""
Configuration type, length, value enumerations.
"""
NODE = 0x01
OBJECT = 0x02
TYPE = 0x03
DATA_TYPES = 0x04
VALUES = 0x05
CAPTIONS = 0x06
BITMAP = 0x07
POSSIBLE_VALUES = 0x08
GROUPS = 0x09
SESSION = 0x0A
IFACE_ID = 0x0B
NETWORK_ID = 0x24
OPAQUE = 0x50
class ConfigFlags(Enum):
"""
Configuration flags.
"""
NONE = 0x00
REQUEST = 0x01
UPDATE = 0x02
RESET = 0x03
class FileTlvs(Enum):
"""
File type, length, value enumerations.
"""
NODE = 0x01
NAME = 0x02
MODE = 0x03
NUMBER = 0x04
TYPE = 0x05
SOURCE_NAME = 0x06
SESSION = 0x0A
DATA = 0x10
COMPRESSED_DATA = 0x11
class InterfaceTlvs(Enum):
"""
Interface type, length, value enumerations.
"""
NODE = 0x01
NUMBER = 0x02
NAME = 0x03
IP_ADDRESS = 0x04
MASK = 0x05
MAC_ADDRESS = 0x06
IP6_ADDRESS = 0x07
IP6_MASK = 0x08
TYPE = 0x09
SESSION = 0x0A
STATE = 0x0B
EMULATION_ID = 0x23
NETWORK_ID = 0x24
class EventTlvs(Enum):
"""
Event type, length, value enumerations.
"""
NODE = 0x01
TYPE = 0x02
NAME = 0x03
DATA = 0x04
TIME = 0x05
SESSION = 0x0A
class SessionTlvs(Enum):
"""
Session type, length, value enumerations.
"""
NUMBER = 0x01
NAME = 0x02
FILE = 0x03
NODE_COUNT = 0x04
DATE = 0x05
THUMB = 0x06
USER = 0x07
OPAQUE = 0x0A
class ExceptionTlvs(Enum):
"""
Exception type, length, value enumerations.
"""
NODE = 0x01
SESSION = 0x02
LEVEL = 0x03
SOURCE = 0x04
DATE = 0x05
TEXT = 0x06
OPAQUE = 0x0A

View file

@ -1,45 +0,0 @@
"""
Utilities for working with python struct data.
"""
import logging
logger = logging.getLogger(__name__)
def pack_values(clazz, packers):
"""
Pack values for a given legacy class.
:param class clazz: class that will provide a pack method
:param list packers: a list of tuples that are used to pack values and transform them
:return: packed data string of all values
"""
# iterate through tuples of values to pack
logger.debug("packing: %s", packers)
data = b""
for packer in packers:
# check if a transformer was provided for valid values
transformer = None
if len(packer) == 2:
tlv_type, value = packer
elif len(packer) == 3:
tlv_type, value, transformer = packer
else:
raise RuntimeError("packer had more than 3 arguments")
# only pack actual values and avoid packing empty strings
# protobuf defaults to empty strings and does no imply a value to set
if value is None or (isinstance(value, str) and not value):
continue
# transform values as needed
if transformer:
value = transformer(value)
# pack and add to existing data
logger.debug("packing: %s - %s type(%s)", tlv_type, value, type(value))
data += clazz.pack(tlv_type.value, value)
return data

View file

@ -113,7 +113,6 @@ class ConfigurableOptions:
"""
name: Optional[str] = None
bitmap: Optional[str] = None
options: List[Configuration] = []
@classmethod

View file

@ -20,6 +20,17 @@ class MessageFlags(Enum):
TTY = 0x40
class ConfigFlags(Enum):
"""
Configuration flags.
"""
NONE = 0x00
REQUEST = 0x01
UPDATE = 0x02
RESET = 0x03
class NodeTypes(Enum):
"""
Node types.

View file

@ -588,26 +588,6 @@ class Session:
node.position.set_geo(lon, lat, alt)
self.sdt.edit_node(node, lon, lat, alt)
def start_mobility(self, node_ids: List[int] = None) -> None:
"""
Start mobility for the provided node ids.
:param node_ids: nodes to start mobility for
:return: nothing
"""
self.mobility.startup(node_ids)
def is_active(self) -> bool:
"""
Determine if this session is considered to be active.
(Runtime or Data collect states)
:return: True if active, False otherwise
"""
result = self.state in {EventTypes.RUNTIME_STATE, EventTypes.DATACOLLECT_STATE}
logger.info("session(%s) checking if active: %s", self.id, result)
return result
def open_xml(self, file_path: Path, start: bool = False) -> None:
"""
Import a session from the EmulationScript XML format.
@ -703,23 +683,6 @@ class Session:
self.mobility.config_reset()
self.link_colors.clear()
def start_events(self) -> None:
"""
Start event loop.
:return: nothing
"""
self.event_loop.run()
def mobility_event(self, event_data: EventData) -> None:
"""
Handle a mobility event.
:param event_data: event data to handle
:return: nothing
"""
self.mobility.handleevent(event_data)
def set_location(self, lat: float, lon: float, alt: float, scale: float) -> None:
"""
Set session geospatial location.
@ -978,15 +941,14 @@ class Session:
env["SESSION_STATE"] = str(self.state)
# try reading and merging optional environments from:
# /etc/core/environment
# /home/user/.core/environment
# /home/user/.coregui/environment
# /tmp/pycore.<session id>/environment
core_env_path = constants.CORE_CONF_DIR / "environment"
session_env_path = self.directory / "environment"
if self.user:
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]
user_env = user_home_path / ".coregui" / "environment"
paths = [core_env_path, user_env, session_env_path]
else:
paths = [core_env_path, session_env_path]
for path in paths:
@ -997,21 +959,6 @@ class Session:
logger.exception("error reading environment file: %s", path)
return env
def set_thumbnail(self, thumb_file: Path) -> None:
"""
Set the thumbnail filename. Move files from /tmp to session dir.
:param thumb_file: tumbnail file to set for session
:return: nothing
"""
if not thumb_file.is_file():
logger.error("thumbnail file to set does not exist: %s", thumb_file)
self.thumbnail = None
return
dst_path = self.directory / thumb_file.name
shutil.copy(thumb_file, dst_path)
self.thumbnail = dst_path
def set_user(self, user: str) -> None:
"""
Set the username for this session. Update the permissions of the
@ -1020,14 +967,13 @@ class Session:
:param user: user to give write permissions to for the session directory
:return: nothing
"""
if user:
try:
uid = pwd.getpwnam(user).pw_uid
gid = self.directory.stat().st_gid
os.chown(self.directory, uid, gid)
except IOError:
logger.exception("failed to set permission on %s", self.directory)
self.user = user
try:
uid = pwd.getpwnam(user).pw_uid
gid = self.directory.stat().st_gid
os.chown(self.directory, uid, gid)
except IOError:
logger.exception("failed to set permission on %s", self.directory)
def create_node(
self, _class: Type[NT], start: bool, *args: Any, **kwargs: Any

View file

@ -225,7 +225,6 @@ class WirelessModel(ConfigurableOptions):
"""
config_type: RegisterTlvs = RegisterTlvs.WIRELESS
bitmap: str = None
position_callback: Callable[[CoreInterface], None] = None
def __init__(self, session: "Session", _id: int) -> None:

View file

@ -109,114 +109,6 @@ class ServiceDependencies:
return self.boot_paths
class ServiceShim:
keys: List[str] = [
"dirs",
"files",
"startidx",
"cmdup",
"cmddown",
"cmdval",
"meta",
"starttime",
]
@classmethod
def tovaluelist(cls, node: CoreNode, service: "CoreService") -> str:
"""
Convert service properties into a string list of key=value pairs,
separated by "|".
:param node: node to get value list for
:param service: service to get value list for
:return: value list string
"""
start_time = 0
start_index = 0
valmap = [
service.dirs,
service.configs,
start_index,
service.startup,
service.shutdown,
service.validate,
service.meta,
start_time,
]
if not service.custom:
valmap[1] = service.get_configs(node)
valmap[3] = service.get_startup(node)
vals = ["%s=%s" % (x, y) for x, y in zip(cls.keys, valmap)]
return "|".join(vals)
@classmethod
def fromvaluelist(cls, service: "CoreService", values: List[str]) -> None:
"""
Convert list of values into properties for this instantiated
(customized) service.
:param service: service to get value list for
:param values: value list to set properties from
:return: nothing
"""
# TODO: support empty value? e.g. override default meta with ''
for key in cls.keys:
try:
cls.setvalue(service, key, values[cls.keys.index(key)])
except IndexError:
# old config does not need to have new keys
logger.exception("error indexing into key")
@classmethod
def setvalue(cls, service: "CoreService", key: str, value: str) -> None:
"""
Set values for this service.
:param service: service to get value list for
:param key: key to set value for
:param value: value of key to set
:return: nothing
"""
if key not in cls.keys:
raise ValueError("key `%s` not in `%s`" % (key, cls.keys))
# this handles data conversion to int, string, and tuples
if value:
if key == "startidx":
value = int(value)
elif key == "starttime":
value = float(value)
elif key == "meta":
value = str(value)
else:
value = utils.make_tuple_fromstr(value, str)
if key == "dirs":
service.dirs = value
elif key == "files":
service.configs = value
elif key == "cmdup":
service.startup = value
elif key == "cmddown":
service.shutdown = value
elif key == "cmdval":
service.validate = value
elif key == "meta":
service.meta = value
@classmethod
def servicesfromopaque(cls, opaque: str) -> List[str]:
"""
Build a list of services from an opaque data string.
:param opaque: opaque data string
:return: services
"""
servicesstring = opaque.split(":")
if servicesstring[0] != "service":
return []
return servicesstring[1].split(",")
class ServiceManager:
"""
Manages services available for CORE nodes to use.

View file

@ -1,7 +1,5 @@
[core-daemon]
#distributed_address = 127.0.0.1
listenaddr = localhost
port = 4038
grpcaddress = localhost
grpcport = 50051
quagga_bin_search = "/usr/local/bin /usr/bin /usr/lib/quagga"
@ -12,8 +10,8 @@ frr_sbin_search = "/usr/local/sbin /usr/sbin /usr/lib/frr"
# uncomment the following line to load custom services from the specified dir
# this may be a comma-separated list, and directory names should be unique
# and not named 'services'
#custom_services_dir = /home/username/.core/myservices
#custom_config_services_dir = /home/username/.coregui/custom_services
#custom_services_dir = /home/<user>/.coregui/custom_services
#custom_config_services_dir = /home/<user>/.coregui/custom_services
# uncomment to establish a standalone control backchannel for accessing nodes
# (overriden by the session option of the same name)
@ -48,7 +46,7 @@ emane_platform_port = 8101
emane_transform_port = 8201
emane_event_generate = True
emane_event_monitor = False
#emane_models_dir = /home/username/.core/myemane
#emane_models_dir = /home/<user>/.coregui/custom_emane
# EMANE log level range [0,4] default: 2
#emane_log_level = 2
emane_realtime = True

View file

@ -1,8 +1,4 @@
# CORE
# (c)2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
# Builds html and pdf documentation using Sphinx.
#

View file

@ -282,12 +282,11 @@ message ConfigEvent {
repeated int32 data_types = 5;
string data_values = 6;
string captions = 7;
string bitmap = 8;
string possible_values = 9;
string groups = 10;
int32 iface_id = 11;
int32 network_id = 12;
string opaque = 13;
string possible_values = 8;
string groups = 9;
int32 iface_id = 10;
int32 network_id = 11;
string opaque = 12;
}
message ExceptionEvent {

View file

@ -8,19 +8,15 @@ message handlers are defined and some support for sending messages.
import argparse
import logging
import os
import sys
import threading
import time
from configparser import ConfigParser
from pathlib import Path
from core import constants
from core.api.grpc.server import CoreGrpcServer
from core.api.tlv.corehandlers import CoreHandler, CoreUdpHandler
from core.api.tlv.coreserver import CoreServer, CoreUdpServer
from core.api.tlv.enumerations import CORE_API_PORT
from core.constants import CORE_CONF_DIR, COREDPY_VERSION
from core.utils import close_onexec, load_logging_config
from core.emulator.coreemu import CoreEmu
from core.utils import load_logging_config
logger = logging.getLogger(__name__)
@ -34,20 +30,6 @@ def banner():
logger.info("CORE daemon v.%s started %s", constants.COREDPY_VERSION, time.ctime())
def start_udp(mainserver, server_address):
"""
Start a thread running a UDP server on the same host,port for
connectionless requests.
:param CoreServer mainserver: main core tcp server to piggy back off of
:param server_address:
:return: CoreUdpServer
"""
mainserver.udpserver = CoreUdpServer(server_address, CoreUdpHandler, mainserver)
mainserver.udpthread = threading.Thread(target=mainserver.udpserver.start, daemon=True)
mainserver.udpthread.start()
def cored(cfg):
"""
Start the CoreServer object and enter the server loop.
@ -55,34 +37,13 @@ def cored(cfg):
:param dict cfg: core configuration
:return: nothing
"""
host = cfg["listenaddr"]
port = int(cfg["port"])
if host == "" or host is None:
host = "localhost"
try:
address = (host, port)
server = CoreServer(address, CoreHandler, cfg)
except:
logger.exception("error starting main server on: %s:%s", host, port)
sys.exit(1)
# initialize grpc api
grpc_server = CoreGrpcServer(server.coreemu)
coreemu = CoreEmu(cfg)
grpc_server = CoreGrpcServer(coreemu)
address_config = cfg["grpcaddress"]
port_config = cfg["grpcport"]
grpc_address = f"{address_config}:{port_config}"
grpc_thread = threading.Thread(target=grpc_server.listen, args=(grpc_address,), daemon=True)
grpc_thread.start()
# start udp server
start_udp(server, address)
# close handlers
close_onexec(server.fileno())
logger.info("CORE TLV API TCP/UDP listening on: %s:%s", host, port)
server.serve_forever()
grpc_server.listen(grpc_address)
def get_merged_config(filename):
@ -98,49 +59,38 @@ def get_merged_config(filename):
default_grpc_port = "50051"
default_address = "localhost"
defaults = {
"port": str(CORE_API_PORT),
"listenaddr": default_address,
"grpcport": default_grpc_port,
"grpcaddress": default_address,
"logfile": default_log
}
parser = argparse.ArgumentParser(
description=f"CORE daemon v.{COREDPY_VERSION} instantiates Linux network namespace nodes.")
parser.add_argument("-f", "--configfile", dest="configfile",
help=f"read config from specified file; default = {filename}")
parser.add_argument("-p", "--port", dest="port", type=int,
help=f"port number to listen on; default = {CORE_API_PORT}")
parser.add_argument("--ovs", action="store_true", help="enable experimental ovs mode, default is false")
parser.add_argument("--grpc-port", dest="grpcport",
help=f"grpc port to listen on; default {default_grpc_port}")
parser.add_argument("--grpc-address", dest="grpcaddress",
help=f"grpc address to listen on; default {default_address}")
parser.add_argument("-l", "--logfile", help=f"core logging configuration; default {default_log}")
# parse command line options
args = parser.parse_args()
# convert ovs to internal format
args.ovs = "1" if args.ovs else "0"
# read the config file
if args.configfile is not None:
filename = args.configfile
del args.configfile
cfg = ConfigParser(defaults)
cfg.read(filename)
section = "core-daemon"
if not cfg.has_section(section):
cfg.add_section(section)
# merge argparse with configparser
for opt in vars(args):
val = getattr(args, opt)
if val is not None:
cfg.set(section, opt, str(val))
return dict(cfg.items(section))

View file

@ -1,70 +0,0 @@
#!/usr/bin/env python3
import argparse
import re
import sys
from pathlib import Path
from core import utils
from core.api.grpc.client import CoreGrpcClient
from core.errors import CoreCommandError
if __name__ == "__main__":
# parse flags
parser = argparse.ArgumentParser(description="Converts CORE imn files to xml")
parser.add_argument("-f", "--file", dest="file", help="imn file to convert")
parser.add_argument(
"-d", "--dest", dest="dest", default=None, help="destination for xml file, defaults to same location as imn"
)
args = parser.parse_args()
# validate provided file exists
imn_file = Path(args.file)
if not imn_file.exists():
print(f"{args.file} does not exist")
sys.exit(1)
# validate destination
if args.dest is not None:
dest = Path(args.dest)
if not dest.exists() or not dest.is_dir():
print(f"{dest.resolve()} does not exist or is not a directory")
sys.exit(1)
xml_file = Path(dest, imn_file.with_suffix(".xml").name)
else:
xml_file = Path(imn_file.with_suffix(".xml").name)
# validate xml file
if xml_file.exists():
print(f"{xml_file.resolve()} already exists")
sys.exit(1)
# run provided imn using core-gui batch mode
try:
print(f"running {imn_file.resolve()} in batch mode")
output = utils.cmd(f"core-gui --batch {imn_file.resolve()}")
last_line = output.split("\n")[-1].strip()
# check for active session
if last_line == "Another session is active.":
print("need to restart core-daemon or shutdown previous batch session")
sys.exit(1)
# parse session id
m = re.search(r"Session id is (\d+)\.", last_line)
if not m:
print(f"failed to find session id: {output}")
sys.exit(1)
session_id = int(m.group(1))
print(f"created session {session_id}")
# save xml and delete session
client = CoreGrpcClient()
with client.context_connect():
print(f"saving xml {xml_file.resolve()}")
client.save_xml(session_id, str(xml_file))
print(f"deleting session {session_id}")
client.delete_session(session_id)
except CoreCommandError as e:
print(f"core-gui batch failed for {imn_file.resolve()}: {e}")
sys.exit(1)

View file

@ -1,247 +0,0 @@
#!/usr/bin/env python3
"""
core-manage: Helper tool to add, remove, or check for services, models, and
node types in a CORE installation.
"""
import ast
import optparse
import os
import re
import sys
from core import services
from core.constants import CORE_CONF_DIR
class FileUpdater:
"""
Helper class for changing configuration files.
"""
actions = ("add", "remove", "check")
targets = ("service", "model", "nodetype")
def __init__(self, action, target, data, options):
"""
"""
self.action = action
self.target = target
self.data = data
self.options = options
self.verbose = options.verbose
self.search, self.filename = self.get_filename(target)
def process(self):
""" Invoke update_file() using a helper method depending on target.
"""
if self.verbose:
txt = "Updating"
if self.action == "check":
txt = "Checking"
sys.stdout.write(f"{txt} file: {self.filename}\n")
if self.target == "service":
r = self.update_file(fn=self.update_services)
elif self.target == "model":
r = self.update_file(fn=self.update_emane_models)
elif self.target == "nodetype":
r = self.update_nodes_conf()
if self.verbose:
txt = ""
if not r:
txt = "NOT "
if self.action == "check":
sys.stdout.write(f"String {txt} found.\n")
else:
sys.stdout.write(f"File {txt} updated.\n")
return r
def update_services(self, line):
""" Modify the __init__.py file having this format:
__all__ = ["quagga", "nrl", "xorp", "bird", ]
Returns True or False when "check" is the action, a modified line
otherwise.
"""
line = line.strip("\n")
key, valstr = line.split("= ")
vals = ast.literal_eval(valstr)
r = self.update_keyvals(key, vals)
if self.action == "check":
return r
valstr = str(r)
return "= ".join([key, valstr]) + "\n"
def update_emane_models(self, line):
""" Modify the core.conf file having this format:
emane_models = RfPipe, Ieee80211abg, CommEffect, Bypass
Returns True or False when "check" is the action, a modified line
otherwise.
"""
line = line.strip("\n")
key, valstr = line.split("= ")
vals = valstr.split(", ")
r = self.update_keyvals(key, vals)
if self.action == "check":
return r
valstr = ", ".join(r)
return "= ".join([key, valstr]) + "\n"
def update_keyvals(self, key, vals):
""" Perform self.action on (key, vals).
Returns True or False when "check" is the action, a modified line
otherwise.
"""
if self.action == "check":
if self.data in vals:
return True
else:
return False
elif self.action == "add":
if self.data not in vals:
vals.append(self.data)
elif self.action == "remove":
try:
vals.remove(self.data)
except ValueError:
pass
return vals
def get_filename(self, target):
""" Return search string and filename based on target.
"""
if target == "service":
filename = os.path.abspath(services.__file__)
search = "__all__ ="
elif target == "model":
filename = os.path.join(CORE_CONF_DIR, "core.conf")
search = "emane_models ="
elif target == "nodetype":
if self.options.userpath is None:
raise ValueError("missing user path")
filename = os.path.join(self.options.userpath, "nodes.conf")
search = self.data
else:
raise ValueError("unknown target")
if not os.path.exists(filename):
raise ValueError(f"file {filename} does not exist")
return search, filename
def update_file(self, fn=None):
""" Open a file and search for self.search, invoking the supplied
function on the matching line. Write file changes if necessary.
Returns True if the file has changed (or action is "check" and the
search string is found), False otherwise.
"""
changed = False
output = "" # this accumulates output, assumes input is small
with open(self.filename, "r") as f:
for line in f:
if line[:len(self.search)] == self.search:
r = fn(line) # line may be modified by fn() here
if self.action == "check":
return r
else:
if line != r:
changed = True
line = r
output += line
if changed:
with open(self.filename, "w") as f:
f.write(output)
return changed
def update_nodes_conf(self):
""" Add/remove/check entries from nodes.conf. This file
contains a Tcl-formatted array of node types. The array index must be
properly set for new entries. Uses self.{action, filename, search,
data} variables as input and returns the same value as update_file().
"""
changed = False
output = "" # this accumulates output, assumes input is small
with open(self.filename, "r") as f:
for line in f:
# make sure data is not added twice
if line.find(self.search) >= 0:
if self.action == "check":
return True
elif self.action == "add":
return False
elif self.action == "remove":
changed = True
continue
else:
output += line
if self.action == "add":
index = int(re.match("^\d+", line).group(0))
output += str(index + 1) + " " + self.data + "\n"
changed = True
if changed:
with open(self.filename, "w") as f:
f.write(output)
return changed
def main():
actions = ", ".join(FileUpdater.actions)
targets = ", ".join(FileUpdater.targets)
usagestr = "usage: %prog [-h] [options] <action> <target> <string>\n"
usagestr += "\nHelper tool to add, remove, or check for "
usagestr += "services, models, and node types\nin a CORE installation.\n"
usagestr += "\nExamples:\n %prog add service newrouting"
usagestr += "\n %prog -v check model RfPipe"
usagestr += "\n %prog --userpath=\"$HOME/.core\" add nodetype \"{ftp ftp.gif ftp.gif {DefaultRoute FTP} netns {FTP server} }\" \n"
usagestr += f"\nArguments:\n <action> should be one of: {actions}"
usagestr += f"\n <target> should be one of: {targets}"
usagestr += f"\n <string> is the text to {actions}"
parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(userpath=None, verbose=False, )
parser.add_option("--userpath", dest="userpath", type="string",
help="use the specified user path (e.g. \"$HOME/.core" \
"\") to access nodes.conf")
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
help="be verbose when performing action")
def usage(msg=None, err=0):
sys.stdout.write("\n")
if msg:
sys.stdout.write(msg + "\n\n")
parser.print_help()
sys.exit(err)
(options, args) = parser.parse_args()
if len(args) != 3:
usage("Missing required arguments!", 1)
action = args[0]
if action not in FileUpdater.actions:
usage(f"invalid action {action}", 1)
target = args[1]
if target not in FileUpdater.targets:
usage(f"invalid target {target}", 1)
if target == "nodetype" and not options.userpath:
usage(f"user path option required for this target ({target})")
data = args[2]
try:
up = FileUpdater(action, target, data, options)
r = up.process()
except Exception as e:
sys.stderr.write(f"Exception: {e}\n")
sys.exit(1)
if not r:
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()

View file

@ -1,279 +0,0 @@
#!/usr/bin/env python3
"""
coresendmsg: utility for generating CORE messages
"""
import optparse
import os
import socket
import sys
from core.api.tlv import coreapi
from core.api.tlv.enumerations import CORE_API_PORT, MessageTypes, SessionTlvs
from core.emulator.enumerations import MessageFlags
def print_available_tlvs(t, tlv_class):
"""
Print a TLV list.
"""
print(f"TLVs available for {t} message:")
for tlv in sorted([tlv for tlv in tlv_class.tlv_type_map], key=lambda x: x.name):
print(tlv.name.lower())
def print_examples(name):
"""
Print example usage of this script.
"""
examples = [
("node number=3 x_position=125 y_position=525",
"move node number 3 to x,y=(125,525)"),
("node number=4 icon=/usr/local/share/core/icons/normal/router_red.gif",
"change node number 4\"s icon to red"),
("node flags=add number=5 type=0 name=\"n5\" x_position=500 y_position=500",
"add a new router node n5"),
("link n1_number=2 n2_number=3 delay=15000",
"set a 15ms delay on the link between n2 and n3"),
("link n1_number=2 n2_number=3 gui_attributes=\"color=blue\"",
"change the color of the link between n2 and n3"),
("link flags=add n1_number=4 n2_number=5 interface1_ip4=\"10.0.3.2\" "
"interface1_ip4_mask=24 interface2_ip4=\"10.0.3.1\" interface2_ip4_mask=24",
"link node n5 with n4 using the given interface addresses"),
("execute flags=string,text node=1 number=1000 command=\"uname -a\" -l",
"run a command on node 1 and wait for the result"),
("execute node=2 number=1001 command=\"killall ospfd\"",
"run a command on node 2 and ignore the result"),
("file flags=add node=1 name=\"/var/log/test.log\" data=\"hello world.\"",
"write a test.log file on node 1 with the given contents"),
("file flags=add node=2 name=\"test.log\" source_name=\"./test.log\"",
"move a test.log file from host to node 2"),
]
print(f"Example {name} invocations:")
for cmd, descr in examples:
print(f" {name} {cmd}\n\t\t{descr}")
def receive_message(sock):
"""
Retrieve a message from a socket and return the CoreMessage object or
None upon disconnect. Socket data beyond the first message is dropped.
"""
try:
# large receive buffer used for UDP sockets, instead of just receiving
# the 4-byte header
data = sock.recv(4096)
msghdr = data[:coreapi.CoreMessage.header_len]
except KeyboardInterrupt:
print("CTRL+C pressed")
sys.exit(1)
if len(msghdr) == 0:
return None
msgdata = None
msgtype, msgflags, msglen = coreapi.CoreMessage.unpack_header(msghdr)
if msglen:
msgdata = data[coreapi.CoreMessage.header_len:]
try:
msgcls = coreapi.CLASS_MAP[msgtype]
except KeyError:
msg = coreapi.CoreMessage(msgflags, msghdr, msgdata)
msg.message_type = msgtype
print(f"unimplemented CORE message type: {msg.type_str()}")
return msg
if len(data) > msglen + coreapi.CoreMessage.header_len:
data_size = len(data) - (msglen + coreapi.CoreMessage.header_len)
print(f"received a message of type {msgtype}, dropping {data_size} bytes of extra data")
return msgcls(msgflags, msghdr, msgdata)
def connect_to_session(sock, requested):
"""
Use Session Messages to retrieve the current list of sessions and
connect to the first one.
"""
# request the session list
tlvdata = coreapi.CoreSessionTlv.pack(SessionTlvs.NUMBER.value, "")
flags = MessageFlags.STRING.value
smsg = coreapi.CoreSessionMessage.pack(flags, tlvdata)
sock.sendall(smsg)
print("waiting for session list...")
smsgreply = receive_message(sock)
if smsgreply is None:
print("disconnected")
return False
sessstr = smsgreply.get_tlv(SessionTlvs.NUMBER.value)
if sessstr is None:
print("missing session numbers")
return False
# join the first session (that is not our own connection)
tmp, localport = sock.getsockname()
sessions = sessstr.split("|")
sessions.remove(str(localport))
if len(sessions) == 0:
print("no sessions to join")
return False
if not requested:
session = sessions[0]
elif requested in sessions:
session = requested
else:
print("requested session not found!")
return False
print(f"joining session: {session}")
tlvdata = coreapi.CoreSessionTlv.pack(SessionTlvs.NUMBER.value, session)
flags = MessageFlags.ADD.value
smsg = coreapi.CoreSessionMessage.pack(flags, tlvdata)
sock.sendall(smsg)
return True
def receive_response(sock, opt):
"""
Receive and print a CORE message from the given socket.
"""
print("waiting for response...")
msg = receive_message(sock)
if msg is None:
print(f"disconnected from {opt.address}:{opt.port}")
sys.exit(0)
print(f"received message: {msg}")
def main():
"""
Parse command-line arguments to build and send a CORE message.
"""
types = [message_type.name.lower() for message_type in MessageTypes]
flags = [flag.name.lower() for flag in MessageFlags]
types_usage = " ".join(types)
flags_usage = " ".join(flags)
usagestr = (
"usage: %prog [-h|-H] [options] [message-type] [flags=flags] "
"[message-TLVs]\n\n"
f"Supported message types:\n {types_usage}\n"
f"Supported message flags (flags=f1,f2,...):\n {flags_usage}"
)
parser = optparse.OptionParser(usage=usagestr)
default_address = "localhost"
default_session = None
default_tcp = False
parser.set_defaults(
port=CORE_API_PORT,
address=default_address,
session=default_session,
listen=False,
examples=False,
tlvs=False,
tcp=default_tcp
)
parser.add_option("-H", dest="examples", action="store_true",
help="show example usage help message and exit")
parser.add_option("-p", "--port", dest="port", type=int,
help=f"TCP port to connect to, default: {CORE_API_PORT}")
parser.add_option("-a", "--address", dest="address", type=str,
help=f"Address to connect to, default: {default_address}")
parser.add_option("-s", "--session", dest="session", type=str,
help=f"Session to join, default: {default_session}")
parser.add_option("-l", "--listen", dest="listen", action="store_true",
help="Listen for a response message and print it.")
parser.add_option("-t", "--list-tlvs", dest="tlvs", action="store_true",
help="List TLVs for the specified message type.")
parser.add_option("--tcp", dest="tcp", action="store_true",
help=f"Use TCP instead of UDP and connect to a session default: {default_tcp}")
def usage(msg=None, err=0):
print()
if msg:
print(f"{msg}\n")
parser.print_help()
sys.exit(err)
# parse command line opt
opt, args = parser.parse_args()
if opt.examples:
print_examples(os.path.basename(sys.argv[0]))
sys.exit(0)
if len(args) == 0:
usage("Please specify a message type to send.")
# given a message type t, determine the message and TLV classes
t = args.pop(0)
t = t.lower()
if t not in types:
usage(f"Unknown message type requested: {t}")
message_type = MessageTypes[t.upper()]
msg_cls = coreapi.CLASS_MAP[message_type.value]
tlv_cls = msg_cls.tlv_class
# list TLV types for this message type
if opt.tlvs:
print_available_tlvs(t, tlv_cls)
sys.exit(0)
# build a message consisting of TLVs from "type=value" arguments
flagstr = ""
tlvdata = b""
for a in args:
typevalue = a.split("=")
if len(typevalue) < 2:
usage(f"Use \"type=value\" syntax instead of \"{a}\".")
tlv_typestr = typevalue[0].lower()
tlv_valstr = "=".join(typevalue[1:])
if tlv_typestr == "flags":
flagstr = tlv_valstr
continue
try:
tlv_type = tlv_cls.tlv_type_map[tlv_typestr.upper()]
tlvdata += tlv_cls.pack_string(tlv_type.value, tlv_valstr)
except KeyError:
usage(f"Unknown TLV: \"{tlv_typestr}\"")
flags = 0
for f in flagstr.split(","):
if f == "":
continue
try:
flag_enum = MessageFlags[f.upper()]
n = flag_enum.value
flags |= n
except KeyError:
usage(f"Invalid flag \"{f}\".")
msg = msg_cls.pack(flags, tlvdata)
if opt.tcp:
protocol = socket.SOCK_STREAM
else:
protocol = socket.SOCK_DGRAM
sock = socket.socket(socket.AF_INET, protocol)
sock.setblocking(True)
try:
sock.connect((opt.address, opt.port))
except Exception as e:
print(f"Error connecting to {opt.address}:{opt.port}:\n\t{e}")
sys.exit(1)
if opt.tcp and not connect_to_session(sock, opt.session):
print("warning: continuing without joining a session!")
sock.sendall(msg)
if opt.listen:
receive_response(sock, opt)
if opt.tcp:
sock.shutdown(socket.SHUT_RDWR)
sock.close()
sys.exit(0)
if __name__ == "__main__":
main()

View file

@ -7,11 +7,9 @@ import time
import mock
import pytest
from mock.mock import MagicMock
from core.api.grpc.client import InterfaceHelper
from core.api.grpc.server import CoreGrpcServer
from core.api.tlv.corehandlers import CoreHandler
from core.emulator.coreemu import CoreEmu
from core.emulator.data import IpPrefixes
from core.emulator.distributed import DistributedServer
@ -104,17 +102,6 @@ def module_grpc(global_coreemu):
grpc_server.server.stop(None)
@pytest.fixture(scope="module")
def module_coretlv(patcher, global_coreemu, global_session):
request_mock = MagicMock()
request_mock.fileno = MagicMock(return_value=1)
server = MockServer(global_coreemu)
request_handler = CoreHandler(request_mock, "", server)
request_handler.session = global_session
request_handler.add_session_handlers()
yield request_handler
@pytest.fixture
def grpc_server(module_grpc):
yield module_grpc
@ -130,16 +117,6 @@ def session(global_session):
global_session.clear()
@pytest.fixture
def coretlv(module_coretlv):
session = module_coretlv.session
session.set_state(EventTypes.CONFIGURATION_STATE)
coreemu = module_coretlv.coreemu
coreemu.sessions[session.id] = session
yield module_coretlv
coreemu.shutdown()
def pytest_addoption(parser):
parser.addoption("--distributed", help="distributed server address")
parser.addoption("--mock", action="store_true", help="run without mocking")

View file

@ -31,8 +31,6 @@ from core.api.grpc.wrappers import (
SessionLocation,
SessionState,
)
from core.api.tlv.dataconversion import ConfigShim
from core.api.tlv.enumerations import ConfigFlags
from core.emane.models.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNet
from core.emulator.data import EventData, IpPrefixes, NodeData, NodeOptions
@ -814,30 +812,6 @@ class TestGrpc:
# then
queue.get(timeout=5)
def test_config_events(self, grpc_server: CoreGrpcServer):
# given
client = CoreGrpcClient()
session = grpc_server.coreemu.create_session()
queue = Queue()
def handle_event(event: Event) -> None:
assert event.session_id == session.id
assert event.config_event is not None
queue.put(event)
# then
with client.context_connect():
client.events(session.id, handle_event)
time.sleep(0.1)
session_config = session.options.get_configs()
config_data = ConfigShim.config_data(
0, None, ConfigFlags.UPDATE.value, session.options, session_config
)
session.broadcast_config(config_data)
# then
queue.get(timeout=5)
def test_exception_events(self, grpc_server: CoreGrpcServer):
# given
client = CoreGrpcClient()

View file

@ -1,915 +0,0 @@
"""
Tests for testing tlv message handling.
"""
import time
from pathlib import Path
from typing import Optional
import mock
import netaddr
import pytest
from mock import MagicMock
from core.api.tlv import coreapi
from core.api.tlv.corehandlers import CoreHandler
from core.api.tlv.enumerations import (
ConfigFlags,
ConfigTlvs,
EventTlvs,
ExecuteTlvs,
FileTlvs,
LinkTlvs,
NodeTlvs,
SessionTlvs,
)
from core.emane.models.ieee80211abg import EmaneIeee80211abgModel
from core.emulator.enumerations import EventTypes, MessageFlags, NodeTypes, RegisterTlvs
from core.errors import CoreError
from core.location.mobility import BasicRangeModel
from core.nodes.base import CoreNode, NodeBase
from core.nodes.network import SwitchNode, WlanNode
def dict_to_str(values) -> str:
return "|".join(f"{x}={values[x]}" for x in values)
class TestGui:
@pytest.mark.parametrize(
"node_type, model",
[
(NodeTypes.DEFAULT, "PC"),
(NodeTypes.EMANE, None),
(NodeTypes.HUB, None),
(NodeTypes.SWITCH, None),
(NodeTypes.WIRELESS_LAN, None),
(NodeTypes.TUNNEL, None),
],
)
def test_node_add(
self, coretlv: CoreHandler, node_type: NodeTypes, model: Optional[str]
):
node_id = 1
name = "node1"
message = coreapi.CoreNodeMessage.create(
MessageFlags.ADD.value,
[
(NodeTlvs.NUMBER, node_id),
(NodeTlvs.TYPE, node_type.value),
(NodeTlvs.NAME, name),
(NodeTlvs.X_POSITION, 0),
(NodeTlvs.Y_POSITION, 0),
(NodeTlvs.MODEL, model),
],
)
coretlv.handle_message(message)
node = coretlv.session.get_node(node_id, NodeBase)
assert node
assert node.name == name
def test_node_update(self, coretlv: CoreHandler):
node_id = 1
coretlv.session.add_node(CoreNode, _id=node_id)
x = 50
y = 100
message = coreapi.CoreNodeMessage.create(
0,
[
(NodeTlvs.NUMBER, node_id),
(NodeTlvs.X_POSITION, x),
(NodeTlvs.Y_POSITION, y),
],
)
coretlv.handle_message(message)
node = coretlv.session.get_node(node_id, NodeBase)
assert node is not None
assert node.position.x == x
assert node.position.y == y
def test_node_delete(self, coretlv: CoreHandler):
node_id = 1
coretlv.session.add_node(CoreNode, _id=node_id)
message = coreapi.CoreNodeMessage.create(
MessageFlags.DELETE.value, [(NodeTlvs.NUMBER, node_id)]
)
coretlv.handle_message(message)
with pytest.raises(CoreError):
coretlv.session.get_node(node_id, NodeBase)
def test_link_add_node_to_net(self, coretlv: CoreHandler):
node1_id = 1
coretlv.session.add_node(CoreNode, _id=node1_id)
switch_id = 2
coretlv.session.add_node(SwitchNode, _id=switch_id)
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
iface1_ip4 = str(ip_prefix[node1_id])
message = coreapi.CoreLinkMessage.create(
MessageFlags.ADD.value,
[
(LinkTlvs.N1_NUMBER, node1_id),
(LinkTlvs.N2_NUMBER, switch_id),
(LinkTlvs.IFACE1_NUMBER, 0),
(LinkTlvs.IFACE1_IP4, iface1_ip4),
(LinkTlvs.IFACE1_IP4_MASK, 24),
],
)
coretlv.handle_message(message)
assert len(coretlv.session.link_manager.links()) == 1
def test_link_add_net_to_node(self, coretlv: CoreHandler):
node1_id = 1
coretlv.session.add_node(CoreNode, _id=node1_id)
switch_id = 2
coretlv.session.add_node(SwitchNode, _id=switch_id)
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
iface2_ip4 = str(ip_prefix[node1_id])
message = coreapi.CoreLinkMessage.create(
MessageFlags.ADD.value,
[
(LinkTlvs.N1_NUMBER, switch_id),
(LinkTlvs.N2_NUMBER, node1_id),
(LinkTlvs.IFACE2_NUMBER, 0),
(LinkTlvs.IFACE2_IP4, iface2_ip4),
(LinkTlvs.IFACE2_IP4_MASK, 24),
],
)
coretlv.handle_message(message)
assert len(coretlv.session.link_manager.links()) == 1
def test_link_add_node_to_node(self, coretlv: CoreHandler):
node1_id = 1
coretlv.session.add_node(CoreNode, _id=node1_id)
node2_id = 2
coretlv.session.add_node(CoreNode, _id=node2_id)
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
iface1_ip4 = str(ip_prefix[node1_id])
iface2_ip4 = str(ip_prefix[node2_id])
message = coreapi.CoreLinkMessage.create(
MessageFlags.ADD.value,
[
(LinkTlvs.N1_NUMBER, node1_id),
(LinkTlvs.N2_NUMBER, node2_id),
(LinkTlvs.IFACE1_NUMBER, 0),
(LinkTlvs.IFACE1_IP4, iface1_ip4),
(LinkTlvs.IFACE1_IP4_MASK, 24),
(LinkTlvs.IFACE2_NUMBER, 0),
(LinkTlvs.IFACE2_IP4, iface2_ip4),
(LinkTlvs.IFACE2_IP4_MASK, 24),
],
)
coretlv.handle_message(message)
assert len(coretlv.session.link_manager.links()) == 1
def test_link_update(self, coretlv: CoreHandler):
node1_id = 1
coretlv.session.add_node(CoreNode, _id=node1_id)
switch_id = 2
coretlv.session.add_node(SwitchNode, _id=switch_id)
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
iface1_ip4 = str(ip_prefix[node1_id])
message = coreapi.CoreLinkMessage.create(
MessageFlags.ADD.value,
[
(LinkTlvs.N1_NUMBER, node1_id),
(LinkTlvs.N2_NUMBER, switch_id),
(LinkTlvs.IFACE1_NUMBER, 0),
(LinkTlvs.IFACE1_IP4, iface1_ip4),
(LinkTlvs.IFACE1_IP4_MASK, 24),
],
)
coretlv.handle_message(message)
assert len(coretlv.session.link_manager.links()) == 1
link = list(coretlv.session.link_manager.links())[0]
assert link.options().bandwidth is None
bandwidth = 50000
message = coreapi.CoreLinkMessage.create(
0,
[
(LinkTlvs.N1_NUMBER, node1_id),
(LinkTlvs.N2_NUMBER, switch_id),
(LinkTlvs.IFACE1_NUMBER, 0),
(LinkTlvs.IFACE2_NUMBER, 0),
(LinkTlvs.BANDWIDTH, bandwidth),
],
)
coretlv.handle_message(message)
assert link.options().bandwidth == bandwidth
def test_link_delete_node_to_node(self, coretlv: CoreHandler):
node1_id = 1
coretlv.session.add_node(CoreNode, _id=node1_id)
node2_id = 2
coretlv.session.add_node(CoreNode, _id=node2_id)
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
iface1_ip4 = str(ip_prefix[node1_id])
iface2_ip4 = str(ip_prefix[node2_id])
message = coreapi.CoreLinkMessage.create(
MessageFlags.ADD.value,
[
(LinkTlvs.N1_NUMBER, node1_id),
(LinkTlvs.N2_NUMBER, node2_id),
(LinkTlvs.IFACE1_NUMBER, 0),
(LinkTlvs.IFACE1_IP4, iface1_ip4),
(LinkTlvs.IFACE1_IP4_MASK, 24),
(LinkTlvs.IFACE2_IP4, iface2_ip4),
(LinkTlvs.IFACE2_IP4_MASK, 24),
],
)
coretlv.handle_message(message)
assert len(coretlv.session.link_manager.links()) == 1
message = coreapi.CoreLinkMessage.create(
MessageFlags.DELETE.value,
[
(LinkTlvs.N1_NUMBER, node1_id),
(LinkTlvs.N2_NUMBER, node2_id),
(LinkTlvs.IFACE1_NUMBER, 0),
(LinkTlvs.IFACE2_NUMBER, 0),
],
)
coretlv.handle_message(message)
assert len(coretlv.session.link_manager.links()) == 0
def test_link_delete_node_to_net(self, coretlv: CoreHandler):
node1_id = 1
coretlv.session.add_node(CoreNode, _id=node1_id)
switch_id = 2
coretlv.session.add_node(SwitchNode, _id=switch_id)
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
iface1_ip4 = str(ip_prefix[node1_id])
message = coreapi.CoreLinkMessage.create(
MessageFlags.ADD.value,
[
(LinkTlvs.N1_NUMBER, node1_id),
(LinkTlvs.N2_NUMBER, switch_id),
(LinkTlvs.IFACE1_NUMBER, 0),
(LinkTlvs.IFACE1_IP4, iface1_ip4),
(LinkTlvs.IFACE1_IP4_MASK, 24),
],
)
coretlv.handle_message(message)
assert len(coretlv.session.link_manager.links()) == 1
message = coreapi.CoreLinkMessage.create(
MessageFlags.DELETE.value,
[
(LinkTlvs.N1_NUMBER, node1_id),
(LinkTlvs.N2_NUMBER, switch_id),
(LinkTlvs.IFACE1_NUMBER, 0),
(LinkTlvs.IFACE2_NUMBER, 0),
],
)
coretlv.handle_message(message)
assert len(coretlv.session.link_manager.links()) == 0
def test_link_delete_net_to_node(self, coretlv: CoreHandler):
node1_id = 1
coretlv.session.add_node(CoreNode, _id=node1_id)
switch_id = 2
coretlv.session.add_node(SwitchNode, _id=switch_id)
ip_prefix = netaddr.IPNetwork("10.0.0.0/24")
iface1_ip4 = str(ip_prefix[node1_id])
message = coreapi.CoreLinkMessage.create(
MessageFlags.ADD.value,
[
(LinkTlvs.N1_NUMBER, node1_id),
(LinkTlvs.N2_NUMBER, switch_id),
(LinkTlvs.IFACE1_NUMBER, 0),
(LinkTlvs.IFACE1_IP4, iface1_ip4),
(LinkTlvs.IFACE1_IP4_MASK, 24),
],
)
coretlv.handle_message(message)
assert len(coretlv.session.link_manager.links()) == 1
message = coreapi.CoreLinkMessage.create(
MessageFlags.DELETE.value,
[
(LinkTlvs.N1_NUMBER, switch_id),
(LinkTlvs.N2_NUMBER, node1_id),
(LinkTlvs.IFACE1_NUMBER, 0),
(LinkTlvs.IFACE2_NUMBER, 0),
],
)
coretlv.handle_message(message)
assert len(coretlv.session.link_manager.links()) == 0
def test_session_update(self, coretlv: CoreHandler):
session_id = coretlv.session.id
name = "test"
message = coreapi.CoreSessionMessage.create(
0, [(SessionTlvs.NUMBER, str(session_id)), (SessionTlvs.NAME, name)]
)
coretlv.handle_message(message)
assert coretlv.session.name == name
def test_session_query(self, coretlv: CoreHandler):
coretlv.dispatch_replies = mock.MagicMock()
message = coreapi.CoreSessionMessage.create(MessageFlags.STRING.value, [])
coretlv.handle_message(message)
args, _ = coretlv.dispatch_replies.call_args
replies = args[0]
assert len(replies) == 1
def test_session_join(self, coretlv: CoreHandler):
coretlv.dispatch_replies = mock.MagicMock()
session_id = coretlv.session.id
message = coreapi.CoreSessionMessage.create(
MessageFlags.ADD.value, [(SessionTlvs.NUMBER, str(session_id))]
)
coretlv.handle_message(message)
assert coretlv.session.id == session_id
def test_session_delete(self, coretlv: CoreHandler):
assert len(coretlv.coreemu.sessions) == 1
session_id = coretlv.session.id
message = coreapi.CoreSessionMessage.create(
MessageFlags.DELETE.value, [(SessionTlvs.NUMBER, str(session_id))]
)
coretlv.handle_message(message)
assert len(coretlv.coreemu.sessions) == 0
def test_file_hook_add(self, coretlv: CoreHandler):
state = EventTypes.DATACOLLECT_STATE
assert coretlv.session.hooks.get(state) is None
file_name = "test.sh"
file_data = "echo hello"
message = coreapi.CoreFileMessage.create(
MessageFlags.ADD.value,
[
(FileTlvs.TYPE, f"hook:{state.value}"),
(FileTlvs.NAME, file_name),
(FileTlvs.DATA, file_data),
],
)
coretlv.handle_message(message)
hooks = coretlv.session.hooks.get(state)
assert len(hooks) == 1
name, data = hooks[0]
assert file_name == name
assert file_data == data
def test_file_service_file_set(self, coretlv: CoreHandler):
node = coretlv.session.add_node(CoreNode)
service = "DefaultRoute"
file_name = "defaultroute.sh"
file_data = "echo hello"
message = coreapi.CoreFileMessage.create(
MessageFlags.ADD.value,
[
(FileTlvs.NODE, node.id),
(FileTlvs.TYPE, f"service:{service}"),
(FileTlvs.NAME, file_name),
(FileTlvs.DATA, file_data),
],
)
coretlv.handle_message(message)
service_file = coretlv.session.services.get_service_file(
node, service, file_name
)
assert file_data == service_file.data
def test_file_node_file_copy(self, request, coretlv: CoreHandler):
file_path = Path("/var/log/test/node.log")
node = coretlv.session.add_node(CoreNode)
node.makenodedir()
file_data = "echo hello"
message = coreapi.CoreFileMessage.create(
MessageFlags.ADD.value,
[
(FileTlvs.NODE, node.id),
(FileTlvs.NAME, str(file_path)),
(FileTlvs.DATA, file_data),
],
)
coretlv.handle_message(message)
if not request.config.getoption("mock"):
expected_path = node.directory / "var.log/test" / file_path.name
assert expected_path.exists()
def test_exec_node_tty(self, coretlv: CoreHandler):
coretlv.dispatch_replies = mock.MagicMock()
node = coretlv.session.add_node(CoreNode)
message = coreapi.CoreExecMessage.create(
MessageFlags.TTY.value,
[
(ExecuteTlvs.NODE, node.id),
(ExecuteTlvs.NUMBER, 1),
(ExecuteTlvs.COMMAND, "bash"),
],
)
coretlv.handle_message(message)
args, _ = coretlv.dispatch_replies.call_args
replies = args[0]
assert len(replies) == 1
def test_exec_local_command(self, request, coretlv: CoreHandler):
if request.config.getoption("mock"):
pytest.skip("mocking calls")
coretlv.dispatch_replies = mock.MagicMock()
node = coretlv.session.add_node(CoreNode)
cmd = "echo hello"
message = coreapi.CoreExecMessage.create(
MessageFlags.TEXT.value | MessageFlags.LOCAL.value,
[
(ExecuteTlvs.NODE, node.id),
(ExecuteTlvs.NUMBER, 1),
(ExecuteTlvs.COMMAND, cmd),
],
)
coretlv.handle_message(message)
args, _ = coretlv.dispatch_replies.call_args
replies = args[0]
assert len(replies) == 1
def test_exec_node_command(self, coretlv: CoreHandler):
coretlv.dispatch_replies = mock.MagicMock()
node = coretlv.session.add_node(CoreNode)
cmd = "echo hello"
message = coreapi.CoreExecMessage.create(
MessageFlags.TEXT.value,
[
(ExecuteTlvs.NODE, node.id),
(ExecuteTlvs.NUMBER, 1),
(ExecuteTlvs.COMMAND, cmd),
],
)
node.cmd = MagicMock(return_value="hello")
coretlv.handle_message(message)
node.cmd.assert_called_with(cmd)
@pytest.mark.parametrize(
"state",
[
EventTypes.SHUTDOWN_STATE,
EventTypes.RUNTIME_STATE,
EventTypes.DATACOLLECT_STATE,
EventTypes.CONFIGURATION_STATE,
EventTypes.DEFINITION_STATE,
],
)
def test_event_state(self, coretlv: CoreHandler, state: EventTypes):
message = coreapi.CoreEventMessage.create(0, [(EventTlvs.TYPE, state.value)])
coretlv.handle_message(message)
assert coretlv.session.state == state
def test_event_schedule(self, coretlv: CoreHandler):
coretlv.session.add_event = mock.MagicMock()
node = coretlv.session.add_node(CoreNode)
message = coreapi.CoreEventMessage.create(
MessageFlags.ADD.value,
[
(EventTlvs.TYPE, EventTypes.SCHEDULED.value),
(EventTlvs.TIME, str(time.monotonic() + 100)),
(EventTlvs.NODE, node.id),
(EventTlvs.NAME, "event"),
(EventTlvs.DATA, "data"),
],
)
coretlv.handle_message(message)
coretlv.session.add_event.assert_called_once()
def test_event_save_xml(self, coretlv: CoreHandler, tmpdir):
xml_file = tmpdir.join("coretlv.session.xml")
file_path = xml_file.strpath
coretlv.session.add_node(CoreNode)
message = coreapi.CoreEventMessage.create(
0,
[(EventTlvs.TYPE, EventTypes.FILE_SAVE.value), (EventTlvs.NAME, file_path)],
)
coretlv.handle_message(message)
assert Path(file_path).exists()
def test_event_open_xml(self, coretlv: CoreHandler, tmpdir):
xml_file = tmpdir.join("coretlv.session.xml")
file_path = Path(xml_file.strpath)
node = coretlv.session.add_node(CoreNode)
coretlv.session.save_xml(file_path)
coretlv.session.delete_node(node.id)
message = coreapi.CoreEventMessage.create(
0,
[
(EventTlvs.TYPE, EventTypes.FILE_OPEN.value),
(EventTlvs.NAME, str(file_path)),
],
)
coretlv.handle_message(message)
assert coretlv.session.get_node(node.id, NodeBase)
@pytest.mark.parametrize(
"state",
[
EventTypes.START,
EventTypes.STOP,
EventTypes.RESTART,
EventTypes.PAUSE,
EventTypes.RECONFIGURE,
],
)
def test_event_service(self, coretlv: CoreHandler, state: EventTypes):
coretlv.session.broadcast_event = mock.MagicMock()
node = coretlv.session.add_node(CoreNode)
message = coreapi.CoreEventMessage.create(
0,
[
(EventTlvs.TYPE, state.value),
(EventTlvs.NODE, node.id),
(EventTlvs.NAME, "service:DefaultRoute"),
],
)
coretlv.handle_message(message)
coretlv.session.broadcast_event.assert_called_once()
@pytest.mark.parametrize(
"state",
[
EventTypes.START,
EventTypes.STOP,
EventTypes.RESTART,
EventTypes.PAUSE,
EventTypes.RECONFIGURE,
],
)
def test_event_mobility(self, coretlv: CoreHandler, state: EventTypes):
message = coreapi.CoreEventMessage.create(
0, [(EventTlvs.TYPE, state.value), (EventTlvs.NAME, "mobility:ns2script")]
)
coretlv.handle_message(message)
def test_register_gui(self, coretlv: CoreHandler):
message = coreapi.CoreRegMessage.create(0, [(RegisterTlvs.GUI, "gui")])
coretlv.handle_message(message)
def test_register_xml(self, coretlv: CoreHandler, tmpdir):
xml_file = tmpdir.join("coretlv.session.xml")
file_path = xml_file.strpath
node = coretlv.session.add_node(CoreNode)
coretlv.session.save_xml(file_path)
coretlv.session.delete_node(node.id)
message = coreapi.CoreRegMessage.create(
0, [(RegisterTlvs.EXECUTE_SERVER, file_path)]
)
coretlv.session.instantiate()
coretlv.handle_message(message)
assert coretlv.coreemu.sessions[1].get_node(node.id, CoreNode)
def test_register_python(self, coretlv: CoreHandler, tmpdir):
xml_file = tmpdir.join("test.py")
file_path = xml_file.strpath
with open(file_path, "w") as f:
f.write("from core.nodes.base import CoreNode\n")
f.write("coreemu = globals()['coreemu']\n")
f.write(f"session = coreemu.sessions[{coretlv.session.id}]\n")
f.write("session.add_node(CoreNode)\n")
message = coreapi.CoreRegMessage.create(
0, [(RegisterTlvs.EXECUTE_SERVER, file_path)]
)
coretlv.session.instantiate()
coretlv.handle_message(message)
assert len(coretlv.session.nodes) == 1
def test_config_all(self, coretlv: CoreHandler):
message = coreapi.CoreConfMessage.create(
MessageFlags.ADD.value,
[(ConfigTlvs.OBJECT, "all"), (ConfigTlvs.TYPE, ConfigFlags.RESET.value)],
)
coretlv.session.location.refxyz = (10, 10, 10)
coretlv.handle_message(message)
assert coretlv.session.location.refxyz == (0, 0, 0)
def test_config_options_request(self, coretlv: CoreHandler):
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.OBJECT, "session"),
(ConfigTlvs.TYPE, ConfigFlags.REQUEST.value),
],
)
coretlv.handle_broadcast_config = mock.MagicMock()
coretlv.handle_message(message)
coretlv.handle_broadcast_config.assert_called_once()
def test_config_options_update(self, coretlv: CoreHandler):
test_key = "test"
test_value = "test"
values = {test_key: test_value}
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.OBJECT, "session"),
(ConfigTlvs.TYPE, ConfigFlags.UPDATE.value),
(ConfigTlvs.VALUES, dict_to_str(values)),
],
)
coretlv.handle_message(message)
assert coretlv.session.options.get_config(test_key) == test_value
def test_config_location_reset(self, coretlv: CoreHandler):
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.OBJECT, "location"),
(ConfigTlvs.TYPE, ConfigFlags.RESET.value),
],
)
coretlv.session.location.refxyz = (10, 10, 10)
coretlv.handle_message(message)
assert coretlv.session.location.refxyz == (0, 0, 0)
def test_config_location_update(self, coretlv: CoreHandler):
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.OBJECT, "location"),
(ConfigTlvs.TYPE, ConfigFlags.UPDATE.value),
(ConfigTlvs.VALUES, "10|10|70|50|0|0.5"),
],
)
coretlv.handle_message(message)
assert coretlv.session.location.refxyz == (10, 10, 0.0)
assert coretlv.session.location.refgeo == (70, 50, 0)
assert coretlv.session.location.refscale == 0.5
def test_config_metadata_request(self, coretlv: CoreHandler):
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.OBJECT, "metadata"),
(ConfigTlvs.TYPE, ConfigFlags.REQUEST.value),
],
)
coretlv.handle_broadcast_config = mock.MagicMock()
coretlv.handle_message(message)
coretlv.handle_broadcast_config.assert_called_once()
def test_config_metadata_update(self, coretlv: CoreHandler):
test_key = "test"
test_value = "test"
values = {test_key: test_value}
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.OBJECT, "metadata"),
(ConfigTlvs.TYPE, ConfigFlags.UPDATE.value),
(ConfigTlvs.VALUES, dict_to_str(values)),
],
)
coretlv.handle_message(message)
assert coretlv.session.metadata[test_key] == test_value
def test_config_broker_request(self, coretlv: CoreHandler):
server = "test"
host = "10.0.0.1"
port = 50000
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.OBJECT, "broker"),
(ConfigTlvs.TYPE, ConfigFlags.UPDATE.value),
(ConfigTlvs.VALUES, f"{server}:{host}:{port}"),
],
)
coretlv.session.distributed.add_server = mock.MagicMock()
coretlv.handle_message(message)
coretlv.session.distributed.add_server.assert_called_once_with(server, host)
def test_config_services_request_all(self, coretlv: CoreHandler):
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.OBJECT, "services"),
(ConfigTlvs.TYPE, ConfigFlags.REQUEST.value),
],
)
coretlv.handle_broadcast_config = mock.MagicMock()
coretlv.handle_message(message)
coretlv.handle_broadcast_config.assert_called_once()
def test_config_services_request_specific(self, coretlv: CoreHandler):
node = coretlv.session.add_node(CoreNode)
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.NODE, node.id),
(ConfigTlvs.OBJECT, "services"),
(ConfigTlvs.TYPE, ConfigFlags.REQUEST.value),
(ConfigTlvs.OPAQUE, "service:DefaultRoute"),
],
)
coretlv.handle_broadcast_config = mock.MagicMock()
coretlv.handle_message(message)
coretlv.handle_broadcast_config.assert_called_once()
def test_config_services_request_specific_file(self, coretlv: CoreHandler):
node = coretlv.session.add_node(CoreNode)
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.NODE, node.id),
(ConfigTlvs.OBJECT, "services"),
(ConfigTlvs.TYPE, ConfigFlags.REQUEST.value),
(ConfigTlvs.OPAQUE, "service:DefaultRoute:defaultroute.sh"),
],
)
coretlv.session.broadcast_file = mock.MagicMock()
coretlv.handle_message(message)
coretlv.session.broadcast_file.assert_called_once()
def test_config_services_reset(self, coretlv: CoreHandler):
node = coretlv.session.add_node(CoreNode)
service = "DefaultRoute"
coretlv.session.services.set_service(node.id, service)
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.OBJECT, "services"),
(ConfigTlvs.TYPE, ConfigFlags.RESET.value),
],
)
assert coretlv.session.services.get_service(node.id, service) is not None
coretlv.handle_message(message)
assert coretlv.session.services.get_service(node.id, service) is None
def test_config_services_set(self, coretlv: CoreHandler):
node = coretlv.session.add_node(CoreNode)
service = "DefaultRoute"
values = {"meta": "metadata"}
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.NODE, node.id),
(ConfigTlvs.OBJECT, "services"),
(ConfigTlvs.TYPE, ConfigFlags.UPDATE.value),
(ConfigTlvs.OPAQUE, f"service:{service}"),
(ConfigTlvs.VALUES, dict_to_str(values)),
],
)
assert coretlv.session.services.get_service(node.id, service) is None
coretlv.handle_message(message)
assert coretlv.session.services.get_service(node.id, service) is not None
def test_config_mobility_reset(self, coretlv: CoreHandler):
wlan = coretlv.session.add_node(WlanNode)
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.OBJECT, "MobilityManager"),
(ConfigTlvs.TYPE, ConfigFlags.RESET.value),
],
)
coretlv.session.mobility.set_model_config(wlan.id, BasicRangeModel.name, {})
assert len(coretlv.session.mobility.node_configurations) == 1
coretlv.handle_message(message)
assert len(coretlv.session.mobility.node_configurations) == 0
def test_config_mobility_model_request(self, coretlv: CoreHandler):
wlan = coretlv.session.add_node(WlanNode)
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.NODE, wlan.id),
(ConfigTlvs.OBJECT, BasicRangeModel.name),
(ConfigTlvs.TYPE, ConfigFlags.REQUEST.value),
],
)
coretlv.handle_broadcast_config = mock.MagicMock()
coretlv.handle_message(message)
coretlv.handle_broadcast_config.assert_called_once()
def test_config_mobility_model_update(self, coretlv: CoreHandler):
wlan = coretlv.session.add_node(WlanNode)
config_key = "range"
config_value = "1000"
values = {config_key: config_value}
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.NODE, wlan.id),
(ConfigTlvs.OBJECT, BasicRangeModel.name),
(ConfigTlvs.TYPE, ConfigFlags.UPDATE.value),
(ConfigTlvs.VALUES, dict_to_str(values)),
],
)
coretlv.handle_message(message)
config = coretlv.session.mobility.get_model_config(
wlan.id, BasicRangeModel.name
)
assert config[config_key] == config_value
def test_config_emane_model_request(self, coretlv: CoreHandler):
wlan = coretlv.session.add_node(WlanNode)
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.NODE, wlan.id),
(ConfigTlvs.OBJECT, EmaneIeee80211abgModel.name),
(ConfigTlvs.TYPE, ConfigFlags.REQUEST.value),
],
)
coretlv.handle_broadcast_config = mock.MagicMock()
coretlv.handle_message(message)
coretlv.handle_broadcast_config.assert_called_once()
def test_config_emane_model_update(self, coretlv: CoreHandler):
wlan = coretlv.session.add_node(WlanNode)
config_key = "distance"
config_value = "50051"
values = {config_key: config_value}
message = coreapi.CoreConfMessage.create(
0,
[
(ConfigTlvs.NODE, wlan.id),
(ConfigTlvs.OBJECT, EmaneIeee80211abgModel.name),
(ConfigTlvs.TYPE, ConfigFlags.UPDATE.value),
(ConfigTlvs.VALUES, dict_to_str(values)),
],
)
coretlv.handle_message(message)
config = coretlv.session.emane.get_config(wlan.id, EmaneIeee80211abgModel.name)
assert config[config_key] == config_value

View file

@ -10,17 +10,14 @@
* Nodes are created using Linux namespaces
* Links are created using Linux bridges and virtual ethernet peers
* Packets sent over links are manipulated using traffic control
* Controlled via the CORE GUI
* Provides both a custom TLV API and gRPC API
* Python program that leverages a small C binary for node creation
* Provides gRPC API
* core-gui
* GUI and daemon communicate over the custom TLV API
* GUI and daemon communicate over gRPC API
* Drag and drop creation for nodes and links
* Can launch terminals for emulated nodes in running sessions
* Can save/open scenario files to recreate previous sessions
* TCL/TK program
* coresendmsg
* Command line utility for sending TLV API messages to the core-daemon
* vnoded
* Command line utility for creating CORE node namespaces
* vcmd
* Command line utility for sending shell commands to nodes
@ -55,21 +52,10 @@ Nftables provides Ethernet frame filtering on Linux bridges. Wireless networks a
emulated by controlling which interfaces can send and receive with nftables
rules.
## Prior Work
The Tcl/Tk CORE GUI was originally derived from the open source
[IMUNES](http://imunes.net) project from the University of Zagreb as a custom
project within Boeing Research and Technology's Network Technology research
group in 2004. Since then they have developed the CORE framework to use Linux
namespacing, have developed a Python framework, and made numerous user and
kernel-space developments, such as support for wireless networks, IPsec,
distribute emulation, simulation integration, and more. The IMUNES project
also consists of userspace and kernel components.
## Open Source Project and Resources
CORE has been released by Boeing to the open source community under the BSD
license. If you find CORE useful for your work, please contribute back to the
project. Contributions can be as simple as reporting a bug, dropping a line of
encouragement or technical suggestions to the mailing lists, or can also
include submitting patches or maintaining aspects of the tool.
encouragement, or can also include submitting patches or maintaining aspects
of the tool.

View file

@ -5,7 +5,7 @@
## Repository Overview
The CORE source consists of several different programming languages for
The CORE source consists of several programming languages for
historical reasons. Current development focuses on the Python modules and
daemon. Here is a brief description of the source directories.
@ -13,7 +13,6 @@ daemon. Here is a brief description of the source directories.
|-----------|--------------------------------------------------------------------------------------|
| daemon | Python CORE daemon/gui code that handles receiving API calls and creating containers |
| docs | Markdown Documentation currently hosted on GitHub |
| gui | Tcl/Tk GUI |
| man | Template files for creating man pages for various CORE command line utilities |
| netns | C program for creating CORE containers |
@ -58,7 +57,7 @@ sudo core-daemon
# run python gui
core-pygui
# run tcl gui
# run gui
core-gui
# run mocked unit tests

View file

@ -1,5 +1,5 @@
@startuml
skinparam {
skinparam {
RoundCorner 8
ComponentStyle uml2
ComponentBorderColor #Black
@ -9,7 +9,6 @@ skinparam {
package User {
component "core-gui" as gui #DeepSkyBlue
component "coresendmsg" #DeepSkyBlue
component "python scripts" as scripts #DeepSkyBlue
component vcmd #DeepSkyBlue
}
@ -19,11 +18,11 @@ package Server {
package Python {
component core #LightSteelBlue
}
package "Linux System" {
package "Linux System" {
component nodes #SpringGreen [
nodes
(linux namespaces)
]
]
component links #SpringGreen [
links
(bridging and traffic manipulation)
@ -31,19 +30,15 @@ package "Linux System" {
}
package API {
interface TLV as tlv
interface gRPC as grpc
}
gui <..> tlv
coresendmsg <..> tlv
scripts <..> tlv
gui <..> grpc
scripts <..> grpc
tlv -- daemon
grpc -- daemon
scripts -- core
daemon - core
core <..> nodes
core <..> links
vcmd <..> nodes
@enduml
@enduml

View file

@ -1,11 +1,11 @@
@startuml
skinparam {
skinparam {
RoundCorner 8
StateBorderColor #Black
StateBackgroundColor #LightSteelBlue
}
Definition: Session XML/IMN
Definition: Session XML
Definition: GUI Drawing
Definition: Scripts
@ -37,4 +37,4 @@ Configuration -> Instantiation
Instantiation -> Runtime
Runtime -> Datacollect
Datacollect -> Shutdown
@enduml
@enduml

View file

@ -124,7 +124,7 @@ connect_kwargs: {"key_filename": "/home/user/.ssh/core"}
Within the core-gui navigate to menu option:
**Session -> Emulation servers...**
**Session -> Servers...**
Within the dialog box presented, add or modify an existing server if present
to use the name, address, and port for the a server you plan to use.
@ -132,12 +132,6 @@ to use the name, address, and port for the a server you plan to use.
Server configurations are loaded and written to in a configuration file for
the GUI.
**~/.core/servers.conf**
```conf
# name address port
server2 192.168.0.2 4038
```
## Assigning Nodes
The user needs to assign nodes to emulation servers in the scenario. Making no

View file

@ -80,7 +80,7 @@ EMANE. An example emane section from the **core.conf** file is shown below:
emane_platform_port = 8101
emane_transform_port = 8201
emane_event_monitor = False
#emane_models_dir = /home/username/.core/myemane
#emane_models_dir = /home/<user>/.coregui/custom_emane
# EMANE log level range [0,4] default: 2
emane_log_level = 2
emane_realtime = True
@ -242,7 +242,7 @@ directory to find the generated EMANE xml files. One easy way to view
this information is by double-clicking one of the virtual nodes and listing the files
in the shell.
![](static/single-pc-emane.png)
![](static/emane-single-pc.png)
## Distributed EMANE
@ -277,7 +277,7 @@ it will be emulated locally.
Using the EMANE node configuration dialog. You can change the EMANE model
being used, along with changing any configuration setting from their defaults.
![](static/distributed-emane-configuration.png)
![](static/emane-configuration.png)
> **NOTE:** Here is a quick checklist for distributed emulation with EMANE.
@ -304,5 +304,3 @@ Double-clicking on a node during runtime will cause the GUI to attempt to SSH
to the emulation server for that node and run an interactive shell. The public
key SSH configuration should be tested with all emulation servers prior to
starting the emulation.
![](static/distributed-emane-network.png)

View file

@ -335,7 +335,7 @@ Create `/tmp/emane/blockageaft.xml` with the following contents.
## Run Demo
1. Select `Open...` within the GUI
1. Load `emane-demo-antenna.xml`
1. Click ![Start Button](../static/gui/start.gif)
1. Click ![Start Button](../static/gui/start.png)
1. After startup completes, double click n1 to bring up the nodes terminal
## Example Demo

View file

@ -11,7 +11,7 @@ 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. Click ![Start Button](../static/gui/start.png)
1. After startup completes, double click n1 to bring up the nodes terminal
## Example Demo

View file

@ -12,7 +12,7 @@ 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. Click ![Start Button](../static/gui/start.png)
1. After startup completes, double click n1 to bring up the nodes terminal
## Example Demo
@ -21,14 +21,14 @@ 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|
| 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.

View file

@ -13,7 +13,7 @@ may provide more helpful details.
## Run Demo
1. Select `Open...` within the GUI
1. Load `emane-demo-gpsd.xml`
1. Click ![Start Button](../static/gui/start.gif)
1. Click ![Start Button](../static/gui/start.png)
1. After startup completes, double click n1 to bring up the nodes terminal
## Example Demo

View file

@ -11,7 +11,7 @@ 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. Click ![Start Button](../static/gui/start.png)
1. After startup completes, double click n1 to bring up the nodes terminal
## Example Demo

View file

@ -1,12 +1,10 @@
# Using the CORE GUI
# CORE GUI
* Table of Contents
{:toc}
The following image shows the CORE GUI:
![](static/core_screenshot.png)
![](static/core-gui.png)
## Overview
@ -28,45 +26,54 @@ Beyond installing CORE, you must have the CORE daemon running. This is done
on the command line with either systemd or sysv.
```shell
# systemd
# systemd service
sudo systemctl daemon-reload
sudo systemctl start core-daemon
# sysv
sudo service core-daemon start
```
You can also invoke the daemon directly from the command line, which can be
useful if you'd like to see the logging output directly.
```shell
# direct invocation
sudo core-daemon
```
## GUI Files
The GUI will create a directory in your home directory on first run called
~/.coregui. This directory will help layout various files that the GUI may use.
* .coregui/
* backgrounds/
* place backgrounds used for display in the GUI
* custom_emane/
* place to keep custom emane models to use with the core-daemon
* custom_services/
* place to keep custom services to use with the core-daemon
* icons/
* icons the GUI uses along with customs icons desired
* mobility/
* place to keep custom mobility files
* scripts/
* place to keep core related scripts
* xmls/
* place to keep saved session xml files
* gui.log
* log file when running the gui, look here when issues occur for exceptions etc
* config.yaml
* configuration file used to save/load various gui related settings (custom nodes, layouts, addresses, etc)
## Modes of Operation
The CORE GUI has two primary modes of operation, **Edit** and **Execute**
modes. Running the GUI, by typing **core-gui** with no options, starts in
Edit mode. Nodes are drawn on a blank canvas using the toolbar on the left
modes. Running the GUI, by typing **core-pygui** with no options, starts in
Edit mode. Nodes are drawn on a blank canvas using the toolbar on the left
and configured from right-click menus or by double-clicking them. The GUI
does not need to be run as root.
Once editing is complete, pressing the green **Start** button (or choosing
**Execute** from the **Session** menu) instantiates the topology within the
Linux kernel and enters Execute mode. In execute mode, the user can interact
with the running emulated machines by double-clicking or right-clicking on
them. The editing toolbar disappears and is replaced by an execute toolbar,
which provides tools while running the emulation. Pressing the red **Stop**
button (or choosing **Terminate** from the **Session** menu) will destroy
the running emulation and return CORE to Edit mode.
CORE can be started directly in Execute mode by specifying **--start** and a
topology file on the command line:
```shell
core-gui --start ~/.core/configs/myfile.imn
```
Once editing is complete, pressing the green **Start** button instantiates
the topology and enters Execute mode. In execute mode,
the user can interact with the running emulated machines by double-clicking or
right-clicking on them. The editing toolbar disappears and is replaced by an
execute toolbar, which provides tools while running the emulation. Pressing
the red **Stop** button will destroy the running emulation and return CORE
to Edit mode.
Once the emulation is running, the GUI can be closed, and a prompt will appear
asking if the emulation should be terminated. The emulation may be left
@ -74,11 +81,22 @@ running and the GUI can reconnect to an existing session at a later time.
The GUI can be run as a normal user on Linux.
The GUI can be connected to a different address or TCP port using the
**--address** and/or **--port** options. The defaults are shown below.
The GUI currently provides the following options on startup.
```shell
core-gui --address 127.0.0.1 --port 4038
usage: core-gui [-h] [-l {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [-p]
[-s SESSION] [--create-dir]
CORE Python GUI
optional arguments:
-h, --help show this help message and exit
-l {DEBUG,INFO,WARNING,ERROR,CRITICAL}, --level {DEBUG,INFO,WARNING,ERROR,CRITICAL}
logging level
-p, --proxy enable proxy
-s SESSION, --session SESSION
session id to join
--create-dir create gui directory and exit
```
## Toolbar
@ -95,22 +113,21 @@ sub-menus, which appear when you click on their group icon.
| Icon | Name | Description |
|----------------------------|----------------|----------------------------------------------------------------------------------------|
| ![](static/gui/select.gif) | Selection Tool | Tool for selecting, moving, configuring nodes. |
| ![](static/gui/start.gif) | Start Button | Starts Execute mode, instantiates the emulation. |
| ![](static/gui/link.gif) | Link | Allows network links to be drawn between two nodes by clicking and dragging the mouse. |
| ![](static/gui/select.png) | Selection Tool | Tool for selecting, moving, configuring nodes. |
| ![](static/gui/start.png) | Start Button | Starts Execute mode, instantiates the emulation. |
| ![](static/gui/link.png) | Link | Allows network links to be drawn between two nodes by clicking and dragging the mouse. |
### CORE Nodes
These nodes will create a new node container and run associated services.
| Icon | Name | Description |
|-----------------------------------------|---------|------------------------------------------------------------------------------|
| ![](static/gui/router.gif) | Router | Runs Quagga OSPFv2 and OSPFv3 routing to forward packets. |
| ![](static/gui/host.gif) | Host | Emulated server machine having a default route, runs SSH server. |
| ![](static/gui/pc.gif) | PC | Basic emulated machine having a default route, runs no processes by default. |
| ![](static/gui/mdr.gif) | MDR | Runs Quagga OSPFv3 MDR routing for MANET-optimized routing. |
| ![](static/gui/router_green.gif) | PRouter | Physical router represents a real testbed machine. |
| ![](static/gui/document-properties.gif) | Edit | Bring up the custom node dialog. |
| Icon | Name | Description |
|----------------------------|---------|------------------------------------------------------------------------------|
| ![](static/gui/router.png) | Router | Runs Quagga OSPFv2 and OSPFv3 routing to forward packets. |
| ![](static/gui/host.png) | Host | Emulated server machine having a default route, runs SSH server. |
| ![](static/gui/pc.png) | PC | Basic emulated machine having a default route, runs no processes by default. |
| ![](static/gui/mdr.png) | MDR | Runs Quagga OSPFv3 MDR routing for MANET-optimized routing. |
| ![](static/gui/router.png) | PRouter | Physical router represents a real testbed machine. |
### Network Nodes
@ -119,20 +136,20 @@ purpose described below.
| Icon | Name | Description |
|-------------------------------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ![](static/gui/hub.gif) | Hub | Ethernet hub forwards incoming packets to every connected node. |
| ![](static/gui/lanswitch.gif) | Switch | Ethernet switch intelligently forwards incoming packets to attached hosts using an Ethernet address hash table. |
| ![](static/gui/wlan.gif) | Wireless LAN | When routers are connected to this WLAN node, they join a wireless network and an antenna is drawn instead of a connecting line; the WLAN node typically controls connectivity between attached wireless nodes based on the distance between them. |
| ![](static/gui/rj45.gif) | RJ45 | RJ45 Physical Interface Tool, emulated nodes can be linked to real physical interfaces; using this tool, real networks and devices can be physically connected to the live-running emulation. |
| ![](static/gui/tunnel.gif) | Tunnel | Tool allows connecting together more than one CORE emulation using GRE tunnels. |
| ![](static/gui/hub.png) | Hub | Ethernet hub forwards incoming packets to every connected node. |
| ![](static/gui/lanswitch.png) | Switch | Ethernet switch intelligently forwards incoming packets to attached hosts using an Ethernet address hash table. |
| ![](static/gui/wlan.png) | Wireless LAN | When routers are connected to this WLAN node, they join a wireless network and an antenna is drawn instead of a connecting line; the WLAN node typically controls connectivity between attached wireless nodes based on the distance between them. |
| ![](static/gui/rj45.png) | RJ45 | RJ45 Physical Interface Tool, emulated nodes can be linked to real physical interfaces; using this tool, real networks and devices can be physically connected to the live-running emulation. |
| ![](static/gui/tunnel.png) | Tunnel | Tool allows connecting together more than one CORE emulation using GRE tunnels. |
### Annotation Tools
| Icon | Name | Description |
|-------------------------------|-----------|---------------------------------------------------------------------|
| ![](static/gui/marker.gif) | Marker | For drawing marks on the canvas. |
| ![](static/gui/oval.gif) | Oval | For drawing circles on the canvas that appear in the background. |
| ![](static/gui/rectangle.gif) | Rectangle | For drawing rectangles on the canvas that appear in the background. |
| ![](static/gui/text.gif) | Text | For placing text captions on the canvas. |
| ![](static/gui/marker.png) | Marker | For drawing marks on the canvas. |
| ![](static/gui/oval.png) | Oval | For drawing circles on the canvas that appear in the background. |
| ![](static/gui/rectangle.png) | Rectangle | For drawing rectangles on the canvas that appear in the background. |
| ![](static/gui/text.png) | Text | For placing text captions on the canvas. |
### Execution Toolbar
@ -140,14 +157,12 @@ When the Start button is pressed, CORE switches to Execute mode, and the Edit
toolbar on the left of the CORE window is replaced with the Execution toolbar
Below are the items on this toolbar, starting from the top.
| Icon | Name | Description |
|-----------------------------|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ![](static/gui/select.gif) | Selection Tool | In Execute mode, the Selection Tool can be used for moving nodes around the canvas, and double-clicking on a node will open a shell window for that node; right-clicking on a node invokes a pop-up menu of run-time options for that node. |
| ![](static/gui/stop.gif) | Stop Button | Stops Execute mode, terminates the emulation, returns CORE to edit mode. |
| ![](static/gui/observe.gif) | Observer Widgets Tool | Clicking on this magnifying glass icon invokes a menu for easily selecting an Observer Widget. The icon has a darker gray background when an Observer Widget is active, during which time moving the mouse over a node will pop up an information display for that node. |
| ![](static/gui/marker.gif) | Marker | For drawing freehand lines on the canvas, useful during demonstrations; markings are not saved. |
| ![](static/gui/twonode.gif) | Two-node Tool | Click to choose a starting and ending node, and run a one-time *traceroute* between those nodes or a continuous *ping -R* between nodes. The output is displayed in real time in a results box, while the IP addresses are parsed and the complete network path is highlighted on the CORE display. |
| ![](static/gui/run.gif) | Run Tool | This tool allows easily running a command on all or a subset of all nodes. A list box allows selecting any of the nodes. A text entry box allows entering any command. The command should return immediately, otherwise the display will block awaiting response. The *ping* command, for example, with no parameters, is not a good idea. The result of each command is displayed in a results box. The first occurrence of the special text "NODE" will be replaced with the node name. The command will not be attempted to run on nodes that are not routers, PCs, or hosts, even if they are selected. |
| Icon | Name | Description |
|----------------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ![](static/gui/stop.png) | Stop Button | Stops Execute mode, terminates the emulation, returns CORE to edit mode. |
| ![](static/gui/select.png) | Selection Tool | In Execute mode, the Selection Tool can be used for moving nodes around the canvas, and double-clicking on a node will open a shell window for that node; right-clicking on a node invokes a pop-up menu of run-time options for that node. |
| ![](static/gui/marker.png) | Marker | For drawing freehand lines on the canvas, useful during demonstrations; markings are not saved. |
| ![](static/gui/run.png) | Run Tool | This tool allows easily running a command on all or a subset of all nodes. A list box allows selecting any of the nodes. A text entry box allows entering any command. The command should return immediately, otherwise the display will block awaiting response. The *ping* command, for example, with no parameters, is not a good idea. The result of each command is displayed in a results box. The first occurrence of the special text "NODE" will be replaced with the node name. The command will not be attempted to run on nodes that are not routers, PCs, or hosts, even if they are selected. |
## Menu
@ -157,98 +172,61 @@ menu, by clicking the dashed line at the top.
### File Menu
The File menu contains options for manipulating the **.imn** Configuration
Files. Generally, these menu items should not be used in Execute mode.
The File menu contains options for saving and opening saved sessions.
| Option | Description |
|------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| New | This starts a new file with an empty canvas. |
| Open | Invokes the File Open dialog box for selecting a new **.imn** or XML file to open. You can change the default path used for this dialog in the Preferences Dialog. |
| Save | Saves the current topology. If you have not yet specified a file name, the Save As dialog box is invoked. |
| Save As XML | Invokes the Save As dialog box for selecting a new **.xml** file for saving the current configuration in the XML file. |
| Save As imn | Invokes the Save As dialog box for selecting a new **.imn** topology file for saving the current configuration. Files are saved in the *IMUNES network configuration* file. |
| Export Python script | Prints Python snippets to the console, for inclusion in a CORE Python script. |
| Execute XML or Python script | Invokes a File Open dialog box for selecting an XML file to run or a Python script to run and automatically connect to. If a Python script, the script must create a new CORE Session and add this session to the daemon's list of sessions in order for this to work. |
| Execute Python script with options | Invokes a File Open dialog box for selecting a Python script to run and automatically connect to. After a selection is made, a Python Script Options dialog box is invoked to allow for command-line options to be added. The Python script must create a new CORE Session and add this session to the daemon's list of sessions in order for this to work. |
| Open current file in editor | This opens the current topology file in the **vim** text editor. First you need to save the file. Once the file has been edited with a text editor, you will need to reload the file to see your changes. The text editor can be changed from the Preferences Dialog. |
| Print | This uses the Tcl/Tk postscript command to print the current canvas to a printer. A dialog is invoked where you can specify a printing command, the default being **lpr**. The postscript output is piped to the print command. |
| Save screenshot | Saves the current canvas as a postscript graphic file. |
| Recently used files | Above the Quit menu command is a list of recently use files, if any have been opened. You can clear this list in the Preferences dialog box. You can specify the number of files to keep in this list from the Preferences dialog. Click on one of the file names listed to open that configuration file. |
| Quit | The Quit command should be used to exit the CORE GUI. CORE may prompt for termination if you are currently in Execute mode. Preferences and the recently-used files list are saved. |
| Option | Description |
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| New Session | This starts a new session with an empty canvas. |
| Save | Saves the current topology. If you have not yet specified a file name, the Save As dialog box is invoked. |
| Save As | Invokes the Save As dialog box for selecting a new **.xml** file for saving the current configuration in the XML file. |
| Open | Invokes the File Open dialog box for selecting a new XML file to open. |
| Recently used files | Above the Quit menu command is a list of recently use files, if any have been opened. You can clear this list in the Preferences dialog box. You can specify the number of files to keep in this list from the Preferences dialog. Click on one of the file names listed to open that configuration file. |
| Execute Python Script | Invokes a File Open dialog box for selecting a Python script to run and automatically connect to. After a selection is made, a Python Script Options dialog box is invoked to allow for command-line options to be added. The Python script must create a new CORE Session and add this session to the daemon's list of sessions in order for this to work. |
| Quit | The Quit command should be used to exit the CORE GUI. CORE may prompt for termination if you are currently in Execute mode. Preferences and the recently-used files list are saved. |
### Edit Menu
| Option | Description |
|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Undo | Attempts to undo the last edit in edit mode. |
| Redo | Attempts to redo an edit that has been undone. |
| Cut, Copy, Paste | Used to cut, copy, and paste a selection. When nodes are pasted, their node numbers are automatically incremented, and existing links are preserved with new IP addresses assigned. Services and their customizations are copied to the new node, but care should be taken as node IP addresses have changed with possibly old addresses remaining in any custom service configurations. Annotations may also be copied and pasted. |
| Select All | Selects all items on the canvas. Selected items can be moved as a group. |
| Select Adjacent | Select all nodes that are linked to the already selected node(s). For wireless nodes this simply selects the WLAN node(s) that the wireless node belongs to. You can use this by clicking on a node and pressing CTRL+N to select the adjacent nodes. |
| Find... | Invokes the *Find* dialog box. The Find dialog can be used to search for nodes by name or number. Results are listed in a table that includes the node or link location and details such as IP addresses or link parameters. Clicking on a result will focus the canvas on that node or link, switching canvases if necessary. |
| Clear marker | Clears any annotations drawn with the marker tool. Also clears any markings used to indicate a node's status. |
| Preferences... | Invokes the Preferences dialog box. |
| Option | Description |
|--------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Preferences | Invokes the Preferences dialog box. |
| Custom Nodes | Custom node creation dialog box. |
| Undo | (Disabled) Attempts to undo the last edit in edit mode. |
| Redo | (Disabled) Attempts to redo an edit that has been undone. |
| Cut, Copy, Paste, Delete | Used to cut, copy, paste, and delete a selection. When nodes are pasted, their node numbers are automatically incremented, and existing links are preserved with new IP addresses assigned. Services and their customizations are copied to the new node, but care should be taken as node IP addresses have changed with possibly old addresses remaining in any custom service configurations. Annotations may also be copied and pasted. |
### Canvas Menu
The canvas menu provides commands for adding, removing, changing, and switching
to different editing canvases.
The canvas menu provides commands related to the editing canvas.
| Option | Description |
|-----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| New | Creates a new empty canvas at the right of all existing canvases. |
| Manage... | Invokes the *Manage Canvases* dialog box, where canvases may be renamed and reordered, and you can easily switch to one of the canvases by selecting it. |
| Delete | Deletes the current canvas and all items that it contains. |
| Size/scale... | Invokes a Canvas Size and Scale dialog that allows configuring the canvas size, scale, and geographic reference point. The size controls allow changing the width and height of the current canvas, in pixels or meters. The scale allows specifying how many meters are equivalent to 100 pixels. The reference point controls specify the latitude, longitude, and altitude reference point used to convert between geographic and Cartesian coordinate systems. By clicking the *Save as default* option, all new canvases will be created with these properties. The default canvas size can also be changed in the Preferences dialog box. |
| Wallpaper... | Used for setting the canvas background image. |
| Previous, Next, First, Last | Used for switching the active canvas to the first, last, or adjacent canvas. |
| Option | Description |
|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Size/scale | Invokes a Canvas Size and Scale dialog that allows configuring the canvas size, scale, and geographic reference point. The size controls allow changing the width and height of the current canvas, in pixels or meters. The scale allows specifying how many meters are equivalent to 100 pixels. The reference point controls specify the latitude, longitude, and altitude reference point used to convert between geographic and Cartesian coordinate systems. By clicking the *Save as default* option, all new canvases will be created with these properties. The default canvas size can also be changed in the Preferences dialog box. |
| Wallpaper | Used for setting the canvas background image. |
### View Menu
The View menu features items for controlling what is displayed on the drawing
canvas.
The View menu features items for toggling on and off their display on the canvas.
| Option | Description |
|-------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Show | Opens a submenu of items that can be displayed or hidden, such as interface names, addresses, and labels. Use these options to help declutter the display. These options are generally saved in the topology files, so scenarios have a more consistent look when copied from one computer to another. |
| Show hidden nodes | Reveal nodes that have been hidden. Nodes are hidden by selecting one or more nodes, right-clicking one and choosing *hide*. |
| Locked | Toggles locked view; when the view is locked, nodes cannot be moved around on the canvas with the mouse. This could be useful when sharing the topology with someone and you do not expect them to change things. |
| 3D GUI... | Launches a 3D GUI by running the command defined under Preferences, *3D GUI command*. This is typically a script that runs the SDT3D display. SDT is the Scripted Display Tool from NRL that is based on NASA's Java-based WorldWind virtual globe software. |
| Zoom In | Magnifies the display. You can also zoom in by clicking *zoom 100%* label in the status bar, or by pressing the **+** (plus) key. |
| Zoom Out | Reduces the size of the display. You can also zoom out by right-clicking *zoom 100%* label in the status bar or by pressing the **-** (minus) key. |
| Option | Description |
|-----------------|-----------------------------------|
| Interface Names | Display interface names on links. |
| IPv4 Addresses | Display IPv4 addresses on links. |
| IPv6 Addresses | Display IPv6 addresses on links. |
| Node Labels | Display node names. |
| Link Labels | Display link labels. |
| Annotations | Display annotations. |
| Canvas Grid | Display the canvas grid. |
### Tools Menu
The tools menu lists different utility functions.
| Option | Description |
|------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Autorearrange all | Automatically arranges all nodes on the canvas. Nodes having a greater number of links are moved to the center. This mode can continue to run while placing nodes. To turn off this autorearrange mode, click on a blank area of the canvas with the select tool, or choose this menu option again. |
| Autorearrange selected | Automatically arranges the selected nodes on the canvas. |
| Align to grid | Moves nodes into a grid formation, starting with the smallest-numbered node in the upper-left corner of the canvas, arranging nodes in vertical columns. |
| Traffic... | Invokes the CORE Traffic Flows dialog box, which allows configuring, starting, and stopping MGEN traffic flows for the emulation. |
| IP addresses... | Invokes the IP Addresses dialog box for configuring which IPv4/IPv6 prefixes are used when automatically addressing new interfaces. |
| MAC addresses... | Invokes the MAC Addresses dialog box for configuring the starting number used as the lowest byte when generating each interface MAC address. This value should be changed when tunneling between CORE emulations to prevent MAC address conflicts. |
| Build hosts file... | Invokes the Build hosts File dialog box for generating **/etc/hosts** file entries based on IP addresses used in the emulation. |
| Renumber nodes... | Invokes the Renumber Nodes dialog box, which allows swapping one node number with another in a few clicks. |
| Experimental... | Menu of experimental options, such as a tool to convert ns-2 scripts to IMUNES imn topologies, supporting only basic ns-2 functionality, and a tool for automatically dividing up a topology into partitions. |
| Topology generator | Opens a submenu of topologies to generate. You can first select the type of node that the topology should consist of, or routers will be chosen by default. Nodes may be randomly placed, aligned in grids, or various other topology patterns. All of the supported patterns are listed in the table below. |
| Debugger... | Opens the CORE Debugger window for executing arbitrary Tcl/Tk commands. |
#### Topology Generator
| Pattern | Description |
|----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Random | Nodes are randomly placed about the canvas, but are not linked together. This can be used in conjunction with a WLAN node to quickly create a wireless network. |
| Grid | Nodes are placed in horizontal rows starting in the upper-left corner, evenly spaced to the right; nodes are not linked to each other. |
| Connected Grid | Nodes are placed in an N x M (width and height) rectangular grid, and each node is linked to the node above, below, left and right of itself. |
| Chain | Nodes are linked together one after the other in a chain. |
| Star | One node is placed in the center with N nodes surrounding it in a circular pattern, with each node linked to the center node. |
| Cycle | Nodes are arranged in a circular pattern with every node connected to its neighbor to form a closed circular path. |
| Wheel | The wheel pattern links nodes in a combination of both Star and Cycle patterns. |
| Cube | Generate a cube graph of nodes. |
| Clique | Creates a clique graph of nodes, where every node is connected to every other node. |
| Bipartite | Creates a bipartite graph of nodes, having two disjoint sets of vertices. |
| Option | Description |
|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Find | Display find dialog used for highlighting a node on the canvas. |
| Auto Grid | Automatically layout nodes in a grid. |
| IP addresses | Invokes the IP Addresses dialog box for configuring which IPv4/IPv6 prefixes are used when automatically addressing new interfaces. |
| MAC addresses | Invokes the MAC Addresses dialog box for configuring the starting number used as the lowest byte when generating each interface MAC address. This value should be changed when tunneling between CORE emulations to prevent MAC address conflicts. |
### Widgets Menu
@ -275,30 +253,29 @@ Here are some standard widgets:
Only half of the line is drawn because each
router may be in a different adjacency state with respect to the other.
* **Throughput** - displays the kilobits-per-second throughput above each link,
using statistics gathered from the ng_pipe Netgraph node that implements each
link. If the throughput exceeds a certain threshold, the link will become
highlighted. For wireless nodes which broadcast data to all nodes in range,
the throughput rate is displayed next to the node and the node will become
circled if the threshold is exceeded.
using statistics gathered from each link. If the throughput exceeds a certain
threshold, the link will become highlighted. For wireless nodes which broadcast
data to all nodes in range, the throughput rate is displayed next to the node and
the node will become circled if the threshold is exceeded.
#### Observer Widgets
These Widgets are available from the *Observer Widgets* submenu of the
*Widgets* menu, and from the Widgets Tool on the toolbar. Only one Observer Widget may
These Widgets are available from the **Observer Widgets** submenu of the
**Widgets** menu, and from the Widgets Tool on the toolbar. Only one Observer Widget may
be used at a time. Mouse over a node while the session is running to pop up
an informational display about that node.
Available Observer Widgets include IPv4 and IPv6 routing tables, socket
information, list of running processes, and OSPFv2/v3 neighbor information.
Observer Widgets may be edited by the user and rearranged. Choosing *Edit...*
from the Observer Widget menu will invoke the Observer Widgets dialog. A list
of Observer Widgets is displayed along with up and down arrows for rearranging
the list. Controls are available for renaming each widget, for changing the
command that is run during mouse over, and for adding and deleting items from
the list. Note that specified commands should return immediately to avoid
delays in the GUI display. Changes are saved to a **widgets.conf** file in
the CORE configuration directory.
Observer Widgets may be edited by the user and rearranged. Choosing
**Widgets->Observer Widgets->Edit Observers** from the Observer Widget menu will
invoke the Observer Widgets dialog. A list of Observer Widgets is displayed along
with up and down arrows for rearranging the list. Controls are available for
renaming each widget, for changing the command that is run during mouse over, and
for adding and deleting items from the list. Note that specified commands should
return immediately to avoid delays in the GUI display. Changes are saved to a
**config.yaml** file in the CORE configuration directory.
### Session Menu
@ -306,28 +283,23 @@ The Session Menu has entries for starting, stopping, and managing sessions,
in addition to global options such as node types, comments, hooks, servers,
and options.
| Option | Description |
|----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Start or Stop | This starts or stops the emulation, performing the same function as the green Start or red Stop button. |
| Change sessions... | Invokes the CORE Sessions dialog box containing a list of active CORE sessions in the daemon. Basic session information such as name, node count, start time, and a thumbnail are displayed. This dialog allows connecting to different sessions, shutting them down, or starting a new session. |
| Node types... | Invokes the CORE Node Types dialog, performing the same function as the Edit button on the Network-Layer Nodes toolbar. |
| Comments... | Invokes the CORE Session Comments window where optional text comments may be specified. These comments are saved at the top of the configuration file, and can be useful for describing the topology or how to use the network. |
| Hooks... | Invokes the CORE Session Hooks window where scripts may be configured for a particular session state. The session states are defined in the [table](#session-states) below. The top of the window has a list of configured hooks, and buttons on the bottom left allow adding, editing, and removing hook scripts. The new or edit button will open a hook script editing window. A hook script is a shell script invoked on the host (not within a virtual node). |
| Reset node positions | If you have moved nodes around using the mouse or by using a mobility module, choosing this item will reset all nodes to their original position on the canvas. The node locations are remembered when you first press the Start button. |
| Emulation servers... | Invokes the CORE emulation servers dialog for configuring. |
| Change Sessions... | Invokes the Sessions dialog for switching between different running sessions. This dialog is presented during startup when one or more sessions are already running. |
| Options... | Presents per-session options, such as the IPv4 prefix to be used, if any, for a control network the ability to preserve the session directory; and an on/off switch for SDT3D support. |
| Option | Description |
|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Sessions | Invokes the CORE Sessions dialog box containing a list of active CORE sessions in the daemon. Basic session information such as name, node count, start time, and a thumbnail are displayed. This dialog allows connecting to different sessions, shutting them down, or starting a new session. |
| Servers | Invokes the CORE emulation servers dialog for configuring. |
| Options | Presents per-session options, such as the IPv4 prefix to be used, if any, for a control network the ability to preserve the session directory; and an on/off switch for SDT3D support. |
| Hooks | Invokes the CORE Session Hooks window where scripts may be configured for a particular session state. The session states are defined in the [table](#session-states) below. The top of the window has a list of configured hooks, and buttons on the bottom left allow adding, editing, and removing hook scripts. The new or edit button will open a hook script editing window. A hook script is a shell script invoked on the host (not within a virtual node). |
#### Session States
| State | Description |
|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| definition | Used by the GUI to tell the backend to clear any state. |
| configuration | When the user presses the *Start* button, node, link, and other configuration data is sent to the backend. This state is also reached when the user customizes a service. |
| instantiation | After configuration data has been sent, just before the nodes are created. |
| runtime | All nodes and networks have been built and are running. (This is the same state at which the previously-named *global experiment script* was run.) |
| datacollect | The user has pressed the *Stop* button, but before services have been stopped and nodes have been shut down. This is a good time to collect log files and other data from the nodes. |
| shutdown | All nodes and networks have been shut down and destroyed. |
| Definition | Used by the GUI to tell the backend to clear any state. |
| Configuration | When the user presses the *Start* button, node, link, and other configuration data is sent to the backend. This state is also reached when the user customizes a service. |
| Instantiation | After configuration data has been sent, just before the nodes are created. |
| Runtime | All nodes and networks have been built and are running. (This is the same state at which the previously-named *global experiment script* was run.) |
| Datacollect | The user has pressed the *Stop* button, but before services have been stopped and nodes have been shut down. This is a good time to collect log files and other data from the nodes. |
| Shutdown | All nodes and networks have been shut down and destroyed. |
### Help Menu
@ -341,13 +313,13 @@ and options.
CORE's emulated networks run in real time, so they can be connected to live
physical networks. The RJ45 tool and the Tunnel tool help with connecting to
the real world. These tools are available from the *Link-layer nodes* menu.
the real world. These tools are available from the **Link-layer nodes** menu.
When connecting two or more CORE emulations together, MAC address collisions
should be avoided. CORE automatically assigns MAC addresses to interfaces when
the emulation is started, starting with **00:00:00:aa:00:00** and incrementing
the bottom byte. The starting byte should be changed on the second CORE machine
using the *MAC addresses...* option from the *Tools* menu.
using the **Tools->MAC Addresses** option the menu.
### RJ45 Tool
@ -360,8 +332,8 @@ connection. When the physical interface is assigned to CORE, it may not be used
for anything else. Another consideration is that the computer or network that
you are connecting to must be co-located with the CORE machine.
To place an RJ45 connection, click on the *Link-layer nodes* toolbar and select
the *RJ45 Tool* from the submenu. Click on the canvas near the node you want to
To place an RJ45 connection, click on the **Link-layer nodes** toolbar and select
the **RJ45 Tool** from the submenu. Click on the canvas near the node you want to
connect to. This could be a router, hub, switch, or WLAN, for example. Now
click on the *Link Tool* and draw a link between the RJ45 and the other node.
The RJ45 node will display "UNASSIGNED". Double-click the RJ45 node to assign a
@ -459,7 +431,7 @@ firewall is not blocking the GRE traffic.
The host machine that runs the CORE GUI and/or daemon is not necessarily
accessible from a node. Running an X11 application on a node, for example,
requires some channel of communication for the application to connect with
the X server for graphical display. There are several different ways to
the X server for graphical display. There are different ways to
connect from the node to the host and vice versa.
#### Control Network
@ -476,12 +448,6 @@ the node, and SSH with X11 forwarding can be used from the host to the node.
ssh -X 172.16.0.5 xclock
```
Note that the **coresendmsg** utility can be used for a node to send
messages to the CORE daemon running on the host (if the **listenaddr = 0.0.0.0**
is set in the **/etc/core/core.conf** file) to interact with the running
emulation. For example, a node may move itself or other nodes, or change
its icon based on some node state.
#### Other Methods
There are still other ways to connect a host with a node. The RJ45 Tool
@ -509,11 +475,11 @@ the node linked to the RJ45 may have the address **10.0.1.1**.
### Wired Networks
Wired networks are created using the *Link Tool* to draw a link between two
Wired networks are created using the **Link Tool** to draw a link between two
nodes. This automatically draws a red line representing an Ethernet link and
creates new interfaces on network-layer nodes.
Double-click on the link to invoke the *link configuration* dialog box. Here
Double-click on the link to invoke the **link configuration** dialog box. Here
you can change the Bandwidth, Delay, Loss, and Duplicate
rate parameters for that link. You can also modify the color and width of the
link, affecting its display.
@ -550,11 +516,10 @@ on platform. See the table below for a brief overview of wireless model types.
To quickly build a wireless network, you can first place several router nodes
onto the canvas. If you have the
Quagga MDR software installed, it is
recommended that you use the *mdr* node type for reduced routing overhead. Next
choose the *wireless LAN* from the *Link-layer nodes* submenu. First set the
recommended that you use the **mdr** node type for reduced routing overhead. Next
choose the **WLAN** from the **Link-layer nodes** submenu. First set the
desired WLAN parameters by double-clicking the cloud icon. Then you can link
all of the routers by right-clicking on the WLAN and choosing *Link to all
routers*.
all selected right-clicking on the WLAN and choosing **Link to Selected**.
Linking a router to the WLAN causes a small antenna to appear, but no red link
line is drawn. Routers can have multiple wireless links and both wireless and
@ -564,28 +529,27 @@ enables OSPFv3 with MANET extensions. This is a Boeing-developed extension to
Quagga's OSPFv3 that reduces flooding overhead and optimizes the flooding
procedure for mobile ad-hoc (MANET) networks.
The default configuration of the WLAN is set to use the basic range model,
using the *Basic* tab in the WLAN configuration dialog. Having this model
The default configuration of the WLAN is set to use the basic range model. Having this model
selected causes **core-daemon** to calculate the distance between nodes based
on screen pixels. A numeric range in screen pixels is set for the wireless
network using the *Range* slider. When two wireless nodes are within range of
network using the **Range** slider. When two wireless nodes are within range of
each other, a green line is drawn between them and they are linked. Two
wireless nodes that are farther than the range pixels apart are not linked.
During Execute mode, users may move wireless nodes around by clicking and
dragging them, and wireless links will be dynamically made or broken.
The *EMANE* tab lists available EMANE models to use for wireless networking.
The **EMANE Nodes** leverage available EMANE models to use for wireless networking.
See the [EMANE](emane.md) chapter for details on using EMANE.
### Mobility Scripting
CORE has a few ways to script mobility.
| Option | Description |
|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ns-2 script | The script specifies either absolute positions or waypoints with a velocity. Locations are given with Cartesian coordinates. |
| CORE API | An external entity can move nodes by sending CORE API Node messages with updated X,Y coordinates; the **coresendmsg** utility allows a shell script to generate these messages. |
| EMANE events | See [EMANE](emane.md) for details on using EMANE scripts to move nodes around. Location information is typically given as latitude, longitude, and altitude. |
| Option | Description |
|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ns-2 script | The script specifies either absolute positions or waypoints with a velocity. Locations are given with Cartesian coordinates. |
| gRPC API | An external entity can move nodes by leveraging the gRPC API |
| EMANE events | See [EMANE](emane.md) for details on using EMANE scripts to move nodes around. Location information is typically given as latitude, longitude, and altitude. |
For the first method, you can create a mobility script using a text
editor, or using a tool such as [BonnMotion](http://net.cs.uni-bonn.de/wg/cs/applications/bonnmotion/), and associate the script with one of the wireless
@ -604,7 +568,7 @@ bm NSFile -f sample
When the Execute mode is started and one of the WLAN nodes has a mobility
script, a mobility script window will appear. This window contains controls for
starting, stopping, and resetting the running time for the mobility script. The
*loop* checkbox causes the script to play continuously. The *resolution* text
**loop** checkbox causes the script to play continuously. The **resolution** text
box contains the number of milliseconds between each timer event; lower values
cause the mobility to appear smoother but consumes greater CPU time.
@ -628,79 +592,28 @@ accurate.
Examples mobility scripts (and their associated topology files) can be found
in the **configs/** directory.
## Multiple Canvases
## Alerts
CORE supports multiple canvases for organizing emulated nodes. Nodes running on
different canvases may be linked together.
The alerts button is located in the bottom right-hand corner
of the status bar in the CORE GUI. This will change colors to indicate one or
more problems with the running emulation. Clicking on the alerts button will invoke the
alerts dialog.
To create a new canvas, choose *New* from the *Canvas* menu. A new canvas tab
appears in the bottom left corner. Clicking on a canvas tab switches to that
canvas. Double-click on one of the tabs to invoke the *Manage Canvases* dialog
box. Here, canvases may be renamed and reordered, and you can easily switch to
one of the canvases by selecting it.
Each canvas maintains its own set of nodes and annotations. To link between
canvases, select a node and right-click on it, choose *Create link to*, choose
the target canvas from the list, and from that submenu the desired node. A
pseudo-link will be drawn, representing the link between the two nodes on
different canvases. Double-clicking on the label at the end of the arrow will
jump to the canvas that it links.
## Check Emulation Light (CEL)
The |cel| Check Emulation Light, or CEL, is located in the bottom right-hand corner
of the status bar in the CORE GUI. This is a yellow icon that indicates one or
more problems with the running emulation. Clicking on the CEL will invoke the
CEL dialog.
The Check Emulation Light dialog contains a list of exceptions received from
the CORE daemon. An exception has a time, severity level, optional node number,
and source. When the CEL is blinking, this indicates one or more fatal
exceptions. An exception with a fatal severity level indicates that one or more
The alerts dialog contains a list of alerts received from
the CORE daemon. An alert has a time, severity level, optional node number,
and source. When the alerts button is red, this indicates one or more fatal
exceptions. An alert with a fatal severity level indicates that one or more
of the basic pieces of emulation could not be created, such as failure to
create a bridge or namespace, or the failure to launch EMANE processes for an
EMANE-based network.
Clicking on an exception displays details for that
exception. If a node number is specified, that node is highlighted on the
canvas when the exception is selected. The exception source is a text string
Clicking on an alert displays details for that
exceptio. The exception source is a text string
to help trace where the exception occurred; "service:UserDefined" for example,
would appear for a failed validation command with the UserDefined service.
Buttons are available at the bottom of the dialog for clearing the exception
list and for viewing the CORE daemon and node log files.
> **NOTE:** In batch mode, exceptions received from the CORE daemon are displayed on
the console.
## Configuration Files
Configurations are saved to **xml** or **.imn** topology files using
the *File* menu. You
can easily edit these files with a text editor.
Any time you edit the topology
file, you will need to stop the emulation if it were running and reload the
file.
The **.imn** file format comes from IMUNES, and is
basically Tcl lists of nodes, links, etc.
Tabs and spacing in the topology files are important. The file starts by
listing every node, then links, annotations, canvases, and options. Each entity
has a block contained in braces. The first block is indented by four spaces.
Within the **network-config** block (and any *custom-*-config* block), the
indentation is one tab character.
> **NOTE:** There are several topology examples included with CORE in
the **configs/** directory.
This directory can be found in **~/.core/configs**, or
installed to the filesystem
under **/usr[/local]/share/examples/configs**.
> **NOTE:** When using the **.imn** file format, file paths for things like custom
icons may contain the special variables **$CORE_DATA_DIR** or **$CONFDIR** which
will be substituted with **/usr/share/core** or **~/.core/configs**.
> **NOTE:** Feel free to edit the files directly using your favorite text editor.
A button is available at the bottom of the dialog for clearing the exception
list.
## Customizing your Topology's Look
@ -723,12 +636,3 @@ A background image for the canvas may be set using the *Wallpaper...* option
from the *Canvas* menu. The image may be centered, tiled, or scaled to fit the
canvas size. An existing terrain, map, or network diagram could be used as a
background, for example, with CORE nodes drawn on top.
## Preferences
The *Preferences* Dialog can be accessed from the **Edit_Menu**. There are
numerous defaults that can be set with this dialog, which are stored in the
**~/.core/prefs.conf** preferences file.

View file

@ -23,8 +23,7 @@ networking scenarios, security studies, and increasing the size of physical test
| [Installation](install.md) | How to install CORE and its requirements |
| [Architecture](architecture.md) | Overview of the architecture |
| [Node Types](nodetypes.md) | Overview of node types supported within CORE |
| [Python GUI](pygui.md) | How to use the default python based GUI |
| [Legacy GUI (deprecated)](gui.md) | How to use the deprecated Tcl based GUI |
| [GUI](gui.md) | How to use the GUI |
| [Python API](python.md) | Covers how to control core directly using python |
| [gRPC API](grpc.md) | Covers how control core using gRPC |
| [Distributed](distributed.md) | Details for running CORE across multiple servers |
@ -34,17 +33,3 @@ networking scenarios, security studies, and increasing the size of physical test
| [EMANE](emane.md) | Overview of EMANE integration and integrating custom EMANE models |
| [Performance](performance.md) | Notes on performance when using CORE |
| [Developers Guide](devguide.md) | Overview on how to contribute to CORE |
## Credits
The CORE project was derived from the open source IMUNES project from the University of Zagreb in 2004. In 2006,
changes for CORE were released back to that project, some items of which were adopted. Marko Zec <zec@fer.hr> is the
primary developer from the University of Zagreb responsible for the IMUNES (GUI) and VirtNet (kernel) projects. Ana
Kukec and Miljenko Mikuc are known contributors.
Jeff Ahrenholz has been the primary Boeing developer of CORE, and has written this manual. Tom Goff designed the
Python framework and has made significant contributions. Claudiu Danilov, Rod Santiago, Kevin Larson, Gary Pei,
Phil Spagnolo, and Ian Chakeres have contributed code to CORE. Dan Mackley helped develop the CORE API, originally to
interface with a simulator. Jae Kim and Tom Henderson have supervised the project and provided direction.
Copyright (c) 2005-2020, the Boeing Company.

View file

@ -51,11 +51,6 @@ The following is a list of files that would be installed after running the autom
* 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`
@ -77,15 +72,11 @@ After the installation complete it will have installed the following scripts.
|---------------------|------------------------------------------------------------------------------|
| 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-daemon | runs the backed core server providing a gRPC API |
| core-gui | starts 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
@ -237,7 +228,7 @@ sudo docker exec -it core core-gui
```
## Running User Scripts
If you create your own python scripts to run CORE directly or using the gRPC/TLV
If you create your own python scripts to run CORE directly or using the gRPC
APIs you will need to make sure you are running them within context of the
installed virtual environment. To help support this CORE provides the `core-python`
executable. This executable will allow you to enter CORE's python virtual

View file

@ -1,649 +0,0 @@
# (BETA) Python GUI
* Table of Contents
{:toc}
![](static/core-pygui.png)
## Overview
The GUI is used to draw nodes and network devices on a canvas, linking them
together to create an emulated network session.
After pressing the start button, CORE will proceed through these phases,
staying in the **runtime** phase. After the session is stopped, CORE will
proceed to the **data collection** phase before tearing down the emulated
state.
CORE can be customized to perform any action at each state. See the
**Hooks...** entry on the [Session Menu](#session-menu) for details about
when these session states are reached.
## Prerequisites
Beyond installing CORE, you must have the CORE daemon running. This is done
on the command line with either systemd or sysv.
```shell
# systemd service
sudo systemctl daemon-reload
sudo systemctl start core-daemon
# sysv service
sudo service core-daemon start
# direct invocation
sudo core-daemon
```
## GUI Files
> **NOTE:** Previously the BETA GUI placed files under ~/.coretk, this has been
> updated to be ~/.coregui. The prior config file named ~/.coretk/gui.yaml is
> also now known as ~/.coregui/config.yaml and has a slightly different format
The GUI will create a directory in your home directory on first run called
~/.coregui. This directory will help layout various files that the GUI may use.
* .coregui/
* backgrounds/
* place backgrounds used for display in the GUI
* custom_emane/
* place to keep custom emane models to use with the core-daemon
* custom_services/
* place to keep custom services to use with the core-daemon
* icons/
* icons the GUI uses along with customs icons desired
* mobility/
* place to keep custom mobility files
* scripts/
* place to keep core related scripts
* xmls/
* place to keep saved session xml files
* gui.log
* log file when running the gui, look here when issues occur for exceptions etc
* config.yaml
* configuration file used to save/load various gui related settings (custom nodes, layouts, addresses, etc)
## Modes of Operation
The CORE GUI has two primary modes of operation, **Edit** and **Execute**
modes. Running the GUI, by typing **core-pygui** with no options, starts in
Edit mode. Nodes are drawn on a blank canvas using the toolbar on the left
and configured from right-click menus or by double-clicking them. The GUI
does not need to be run as root.
Once editing is complete, pressing the green **Start** button instantiates
the topology and enters Execute mode. In execute mode,
the user can interact with the running emulated machines by double-clicking or
right-clicking on them. The editing toolbar disappears and is replaced by an
execute toolbar, which provides tools while running the emulation. Pressing
the red **Stop** button will destroy the running emulation and return CORE
to Edit mode.
Once the emulation is running, the GUI can be closed, and a prompt will appear
asking if the emulation should be terminated. The emulation may be left
running and the GUI can reconnect to an existing session at a later time.
The GUI can be run as a normal user on Linux.
The python GUI currently provides the following options on startup.
```shell
usage: core-pygui [-h] [-l {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [-p]
CORE Python GUI
optional arguments:
-h, --help show this help message and exit
-l {DEBUG,INFO,WARNING,ERROR,CRITICAL}, --level {DEBUG,INFO,WARNING,ERROR,CRITICAL}
logging level
-p, --proxy enable proxy
```
## Toolbar
The toolbar is a row of buttons that runs vertically along the left side of the
CORE GUI window. The toolbar changes depending on the mode of operation.
### Editing Toolbar
When CORE is in Edit mode (the default), the vertical Editing Toolbar exists on
the left side of the CORE window. Below are brief descriptions for each toolbar
item, starting from the top. Most of the tools are grouped into related
sub-menus, which appear when you click on their group icon.
| Icon | Name | Description |
|------------------------------|----------------|----------------------------------------------------------------------------------------|
| ![](static/pygui/select.png) | Selection Tool | Tool for selecting, moving, configuring nodes. |
| ![](static/pygui/start.png) | Start Button | Starts Execute mode, instantiates the emulation. |
| ![](static/pygui/link.png) | Link | Allows network links to be drawn between two nodes by clicking and dragging the mouse. |
### CORE Nodes
These nodes will create a new node container and run associated services.
| Icon | Name | Description |
|------------------------------|---------|------------------------------------------------------------------------------|
| ![](static/pygui/router.png) | Router | Runs Quagga OSPFv2 and OSPFv3 routing to forward packets. |
| ![](static/pygui/host.png) | Host | Emulated server machine having a default route, runs SSH server. |
| ![](static/pygui/pc.png) | PC | Basic emulated machine having a default route, runs no processes by default. |
| ![](static/pygui/mdr.png) | MDR | Runs Quagga OSPFv3 MDR routing for MANET-optimized routing. |
| ![](static/pygui/router.png) | PRouter | Physical router represents a real testbed machine. |
### Network Nodes
These nodes are mostly used to create a Linux bridge that serves the
purpose described below.
| Icon | Name | Description |
|---------------------------------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ![](static/pygui/hub.png) | Hub | Ethernet hub forwards incoming packets to every connected node. |
| ![](static/pygui/lanswitch.png) | Switch | Ethernet switch intelligently forwards incoming packets to attached hosts using an Ethernet address hash table. |
| ![](static/pygui/wlan.png) | Wireless LAN | When routers are connected to this WLAN node, they join a wireless network and an antenna is drawn instead of a connecting line; the WLAN node typically controls connectivity between attached wireless nodes based on the distance between them. |
| ![](static/pygui/rj45.png) | RJ45 | RJ45 Physical Interface Tool, emulated nodes can be linked to real physical interfaces; using this tool, real networks and devices can be physically connected to the live-running emulation. |
| ![](static/pygui/tunnel.png) | Tunnel | Tool allows connecting together more than one CORE emulation using GRE tunnels. |
### Annotation Tools
| Icon | Name | Description |
|---------------------------------|-----------|---------------------------------------------------------------------|
| ![](static/pygui/marker.png) | Marker | For drawing marks on the canvas. |
| ![](static/pygui/oval.png) | Oval | For drawing circles on the canvas that appear in the background. |
| ![](static/pygui/rectangle.png) | Rectangle | For drawing rectangles on the canvas that appear in the background. |
| ![](static/pygui/text.png) | Text | For placing text captions on the canvas. |
### Execution Toolbar
When the Start button is pressed, CORE switches to Execute mode, and the Edit
toolbar on the left of the CORE window is replaced with the Execution toolbar
Below are the items on this toolbar, starting from the top.
| Icon | Name | Description |
|------------------------------|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ![](static/pygui/stop.png) | Stop Button | Stops Execute mode, terminates the emulation, returns CORE to edit mode. |
| ![](static/pygui/select.png) | Selection Tool | In Execute mode, the Selection Tool can be used for moving nodes around the canvas, and double-clicking on a node will open a shell window for that node; right-clicking on a node invokes a pop-up menu of run-time options for that node. |
| ![](static/pygui/marker.png) | Marker | For drawing freehand lines on the canvas, useful during demonstrations; markings are not saved. |
| ![](static/pygui/run.png) | Run Tool | This tool allows easily running a command on all or a subset of all nodes. A list box allows selecting any of the nodes. A text entry box allows entering any command. The command should return immediately, otherwise the display will block awaiting response. The *ping* command, for example, with no parameters, is not a good idea. The result of each command is displayed in a results box. The first occurrence of the special text "NODE" will be replaced with the node name. The command will not be attempted to run on nodes that are not routers, PCs, or hosts, even if they are selected. |
## Menu
The menubar runs along the top of the CORE GUI window and provides access to a
variety of features. Some of the menus are detachable, such as the *Widgets*
menu, by clicking the dashed line at the top.
### File Menu
The File menu contains options for manipulating the **.imn** Configuration
Files. Generally, these menu items should not be used in Execute mode.
| Option | Description |
|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| New Session | This starts a new session with an empty canvas. |
| Save | Saves the current topology. If you have not yet specified a file name, the Save As dialog box is invoked. |
| Save As | Invokes the Save As dialog box for selecting a new **.xml** file for saving the current configuration in the XML file. |
| Open | Invokes the File Open dialog box for selecting a new XML file to open. |
| Recently used files | Above the Quit menu command is a list of recently use files, if any have been opened. You can clear this list in the Preferences dialog box. You can specify the number of files to keep in this list from the Preferences dialog. Click on one of the file names listed to open that configuration file. |
| Execute Python Script | Invokes a File Open dialog box for selecting a Python script to run and automatically connect to. After a selection is made, a Python Script Options dialog box is invoked to allow for command-line options to be added. The Python script must create a new CORE Session and add this session to the daemon's list of sessions in order for this to work. |
| Quit | The Quit command should be used to exit the CORE GUI. CORE may prompt for termination if you are currently in Execute mode. Preferences and the recently-used files list are saved. |
### Edit Menu
| Option | Description |
|--------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Preferences | Invokes the Preferences dialog box. |
| Custom Nodes | Custom node creation dialog box. |
| Undo | (Disabled) Attempts to undo the last edit in edit mode. |
| Redo | (Disabled) Attempts to redo an edit that has been undone. |
| Cut, Copy, Paste, Delete | Used to cut, copy, paste, and delete a selection. When nodes are pasted, their node numbers are automatically incremented, and existing links are preserved with new IP addresses assigned. Services and their customizations are copied to the new node, but care should be taken as node IP addresses have changed with possibly old addresses remaining in any custom service configurations. Annotations may also be copied and pasted. |
### Canvas Menu
The canvas menu provides commands related to the editing canvas.
| Option | Description |
|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Size/scale | Invokes a Canvas Size and Scale dialog that allows configuring the canvas size, scale, and geographic reference point. The size controls allow changing the width and height of the current canvas, in pixels or meters. The scale allows specifying how many meters are equivalent to 100 pixels. The reference point controls specify the latitude, longitude, and altitude reference point used to convert between geographic and Cartesian coordinate systems. By clicking the *Save as default* option, all new canvases will be created with these properties. The default canvas size can also be changed in the Preferences dialog box. |
| Wallpaper | Used for setting the canvas background image. |
### View Menu
The View menu features items for toggling on and off their display on the canvas.
| Option | Description |
|-----------------|-----------------------------------|
| Interface Names | Display interface names on links. |
| IPv4 Addresses | Display IPv4 addresses on links. |
| IPv6 Addresses | Display IPv6 addresses on links. |
| Node Labels | Display node names. |
| Link Labels | Display link labels. |
| Annotations | Display annotations. |
| Canvas Grid | Display the canvas grid. |
### Tools Menu
The tools menu lists different utility functions.
| Option | Description |
|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Find | Display find dialog used for highlighting a node on the canvas. |
| Auto Grid | Automatically layout nodes in a grid. |
| IP addresses | Invokes the IP Addresses dialog box for configuring which IPv4/IPv6 prefixes are used when automatically addressing new interfaces. |
| MAC addresses | Invokes the MAC Addresses dialog box for configuring the starting number used as the lowest byte when generating each interface MAC address. This value should be changed when tunneling between CORE emulations to prevent MAC address conflicts. |
### Widgets Menu
Widgets are GUI elements that allow interaction with a running emulation.
Widgets typically automate the running of commands on emulated nodes to report
status information of some type and display this on screen.
#### Periodic Widgets
These Widgets are those available from the main *Widgets* menu. More than one
of these Widgets may be run concurrently. An event loop fires once every second
that the emulation is running. If one of these Widgets is enabled, its periodic
routine will be invoked at this time. Each Widget may have a configuration
dialog box which is also accessible from the *Widgets* menu.
Here are some standard widgets:
* **Adjacency** - displays router adjacency states for Quagga's OSPFv2 and OSPFv3
routing protocols. A line is drawn from each router halfway to the router ID
of an adjacent router. The color of the line is based on the OSPF adjacency
state such as Two-way or Full. To learn about the different colors, see the
*Configure Adjacency...* menu item. The **vtysh** command is used to
dump OSPF neighbor information.
Only half of the line is drawn because each
router may be in a different adjacency state with respect to the other.
* **Throughput** - displays the kilobits-per-second throughput above each link,
using statistics gathered from the ng_pipe Netgraph node that implements each
link. If the throughput exceeds a certain threshold, the link will become
highlighted. For wireless nodes which broadcast data to all nodes in range,
the throughput rate is displayed next to the node and the node will become
circled if the threshold is exceeded.
#### Observer Widgets
These Widgets are available from the **Observer Widgets** submenu of the
**Widgets** menu, and from the Widgets Tool on the toolbar. Only one Observer Widget may
be used at a time. Mouse over a node while the session is running to pop up
an informational display about that node.
Available Observer Widgets include IPv4 and IPv6 routing tables, socket
information, list of running processes, and OSPFv2/v3 neighbor information.
Observer Widgets may be edited by the user and rearranged. Choosing
**Widgets->Observer Widgets->Edit Observers** from the Observer Widget menu will
invoke the Observer Widgets dialog. A list of Observer Widgets is displayed along
with up and down arrows for rearranging the list. Controls are available for
renaming each widget, for changing the command that is run during mouse over, and
for adding and deleting items from the list. Note that specified commands should
return immediately to avoid delays in the GUI display. Changes are saved to a
**config.yaml** file in the CORE configuration directory.
### Session Menu
The Session Menu has entries for starting, stopping, and managing sessions,
in addition to global options such as node types, comments, hooks, servers,
and options.
| Option | Description |
|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Sessions | Invokes the CORE Sessions dialog box containing a list of active CORE sessions in the daemon. Basic session information such as name, node count, start time, and a thumbnail are displayed. This dialog allows connecting to different sessions, shutting them down, or starting a new session. |
| Servers | Invokes the CORE emulation servers dialog for configuring. |
| Options | Presents per-session options, such as the IPv4 prefix to be used, if any, for a control network the ability to preserve the session directory; and an on/off switch for SDT3D support. |
| Hooks | Invokes the CORE Session Hooks window where scripts may be configured for a particular session state. The session states are defined in the [table](#session-states) below. The top of the window has a list of configured hooks, and buttons on the bottom left allow adding, editing, and removing hook scripts. The new or edit button will open a hook script editing window. A hook script is a shell script invoked on the host (not within a virtual node). |
#### Session States
| State | Description |
|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Definition | Used by the GUI to tell the backend to clear any state. |
| Configuration | When the user presses the *Start* button, node, link, and other configuration data is sent to the backend. This state is also reached when the user customizes a service. |
| Instantiation | After configuration data has been sent, just before the nodes are created. |
| Runtime | All nodes and networks have been built and are running. (This is the same state at which the previously-named *global experiment script* was run.) |
| Datacollect | The user has pressed the *Stop* button, but before services have been stopped and nodes have been shut down. This is a good time to collect log files and other data from the nodes. |
| Shutdown | All nodes and networks have been shut down and destroyed. |
### Help Menu
| Option | Description |
|--------------------------|---------------------------------------------------------------|
| CORE Github (www) | Link to the CORE GitHub page. |
| CORE Documentation (www) | Lnk to the CORE Documentation page. |
| About | Invokes the About dialog box for viewing version information. |
## Connecting with Physical Networks
CORE's emulated networks run in real time, so they can be connected to live
physical networks. The RJ45 tool and the Tunnel tool help with connecting to
the real world. These tools are available from the **Link-layer nodes** menu.
When connecting two or more CORE emulations together, MAC address collisions
should be avoided. CORE automatically assigns MAC addresses to interfaces when
the emulation is started, starting with **00:00:00:aa:00:00** and incrementing
the bottom byte. The starting byte should be changed on the second CORE machine
using the **Tools->MAC Addresses** option the menu.
### RJ45 Tool
The RJ45 node in CORE represents a physical interface on the real CORE machine.
Any real-world network device can be connected to the interface and communicate
with the CORE nodes in real time.
The main drawback is that one physical interface is required for each
connection. When the physical interface is assigned to CORE, it may not be used
for anything else. Another consideration is that the computer or network that
you are connecting to must be co-located with the CORE machine.
To place an RJ45 connection, click on the **Link-layer nodes** toolbar and select
the **RJ45 Tool** from the submenu. Click on the canvas near the node you want to
connect to. This could be a router, hub, switch, or WLAN, for example. Now
click on the *Link Tool* and draw a link between the RJ45 and the other node.
The RJ45 node will display "UNASSIGNED". Double-click the RJ45 node to assign a
physical interface. A list of available interfaces will be shown, and one may
be selected by double-clicking its name in the list, or an interface name may
be entered into the text box.
> **NOTE:** When you press the Start button to instantiate your topology, the
interface assigned to the RJ45 will be connected to the CORE topology. The
interface can no longer be used by the system.
Multiple RJ45 nodes can be used within CORE and assigned to the same physical
interface if 802.1x VLANs are used. This allows for more RJ45 nodes than
physical ports are available, but the (e.g. switching) hardware connected to
the physical port must support the VLAN tagging, and the available bandwidth
will be shared.
You need to create separate VLAN virtual devices on the Linux host,
and then assign these devices to RJ45 nodes inside of CORE. The VLANning is
actually performed outside of CORE, so when the CORE emulated node receives a
packet, the VLAN tag will already be removed.
Here are example commands for creating VLAN devices under Linux:
```shell
ip link add link eth0 name eth0.1 type vlan id 1
ip link add link eth0 name eth0.2 type vlan id 2
ip link add link eth0 name eth0.3 type vlan id 3
```
### Tunnel Tool
The tunnel tool builds GRE tunnels between CORE emulations or other hosts.
Tunneling can be helpful when the number of physical interfaces is limited or
when the peer is located on a different network. Also a physical interface does
not need to be dedicated to CORE as with the RJ45 tool.
The peer GRE tunnel endpoint may be another CORE machine or another
host that supports GRE tunneling. When placing a Tunnel node, initially
the node will display "UNASSIGNED". This text should be replaced with the IP
address of the tunnel peer. This is the IP address of the other CORE machine or
physical machine, not an IP address of another virtual node.
> **NOTE:** Be aware of possible MTU (Maximum Transmission Unit) issues with GRE devices. The *gretap* device
has an interface MTU of 1,458 bytes; when joined to a Linux bridge, the
bridge's MTU
becomes 1,458 bytes. The Linux bridge will not perform fragmentation for
large packets if other bridge ports have a higher MTU such as 1,500 bytes.
The GRE key is used to identify flows with GRE tunneling. This allows multiple
GRE tunnels to exist between that same pair of tunnel peers. A unique number
should be used when multiple tunnels are used with the same peer. When
configuring the peer side of the tunnel, ensure that the matching keys are
used.
Here are example commands for building the other end of a tunnel on a Linux
machine. In this example, a router in CORE has the virtual address
**10.0.0.1/24** and the CORE host machine has the (real) address
**198.51.100.34/24**. The Linux box
that will connect with the CORE machine is reachable over the (real) network
at **198.51.100.76/24**.
The emulated router is linked with the Tunnel Node. In the
Tunnel Node configuration dialog, the address **198.51.100.76** is entered, with
the key set to **1**. The gretap interface on the Linux box will be assigned
an address from the subnet of the virtual router node,
**10.0.0.2/24**.
```shell
# these commands are run on the tunnel peer
sudo ip link add gt0 type gretap remote 198.51.100.34 local 198.51.100.76 key 1
sudo ip addr add 10.0.0.2/24 dev gt0
sudo ip link set dev gt0 up
```
Now the virtual router should be able to ping the Linux machine:
```shell
# from the CORE router node
ping 10.0.0.2
```
And the Linux machine should be able to ping inside the CORE emulation:
```shell
# from the tunnel peer
ping 10.0.0.1
```
To debug this configuration, **tcpdump** can be run on the gretap devices, or
on the physical interfaces on the CORE or Linux machines. Make sure that a
firewall is not blocking the GRE traffic.
### Communicating with the Host Machine
The host machine that runs the CORE GUI and/or daemon is not necessarily
accessible from a node. Running an X11 application on a node, for example,
requires some channel of communication for the application to connect with
the X server for graphical display. There are several different ways to
connect from the node to the host and vice versa.
#### Control Network
The quickest way to connect with the host machine through the primary control
network.
With a control network, the host can launch an X11 application on a node.
To run an X11 application on the node, the **SSH** service can be enabled on
the node, and SSH with X11 forwarding can be used from the host to the node.
```shell
# SSH from host to node n5 to run an X11 app
ssh -X 172.16.0.5 xclock
```
Note that the **coresendmsg** utility can be used for a node to send
messages to the CORE daemon running on the host (if the **listenaddr = 0.0.0.0**
is set in the **/etc/core/core.conf** file) to interact with the running
emulation. For example, a node may move itself or other nodes, or change
its icon based on some node state.
#### Other Methods
There are still other ways to connect a host with a node. The RJ45 Tool
can be used in conjunction with a dummy interface to access a node:
```shell
sudo modprobe dummy numdummies=1
```
A **dummy0** interface should appear on the host. Use the RJ45 tool assigned
to **dummy0**, and link this to a node in your scenario. After starting the
session, configure an address on the host.
```shell
sudo ip link show type bridge
# determine bridge name from the above command
# assign an IP address on the same network as the linked node
sudo ip addr add 10.0.1.2/24 dev b.48304.34658
```
In the example shown above, the host will have the address **10.0.1.2** and
the node linked to the RJ45 may have the address **10.0.1.1**.
## Building Sample Networks
### Wired Networks
Wired networks are created using the **Link Tool** to draw a link between two
nodes. This automatically draws a red line representing an Ethernet link and
creates new interfaces on network-layer nodes.
Double-click on the link to invoke the **link configuration** dialog box. Here
you can change the Bandwidth, Delay, Loss, and Duplicate
rate parameters for that link. You can also modify the color and width of the
link, affecting its display.
Link-layer nodes are provided for modeling wired networks. These do not create
a separate network stack when instantiated, but are implemented using Linux bridging.
These are the hub, switch, and wireless LAN nodes. The hub copies each packet from
the incoming link to every connected link, while the switch behaves more like an
Ethernet switch and keeps track of the Ethernet address of the connected peer,
forwarding unicast traffic only to the appropriate ports.
The wireless LAN (WLAN) is covered in the next section.
### Wireless Networks
The wireless LAN node allows you to build wireless networks where moving nodes
around affects the connectivity between them. Connection between a pair of nodes is stronger
when the nodes are closer while connection is weaker when the nodes are further away.
The wireless LAN, or WLAN, node appears as a small cloud. The WLAN offers
several levels of wireless emulation fidelity, depending on your modeling needs.
The WLAN tool can be extended with plug-ins for different levels of wireless
fidelity. The basic on/off range is the default setting available on all
platforms. Other plug-ins offer higher fidelity at the expense of greater
complexity and CPU usage. The availability of certain plug-ins varies depending
on platform. See the table below for a brief overview of wireless model types.
| Model | Type | Supported Platform(s) | Fidelity | Description |
|-------|---------|-----------------------|----------|-------------------------------------------------------------------------------|
| Basic | on/off | Linux | Low | Ethernet bridging with nftables |
| EMANE | Plug-in | Linux | High | TAP device connected to EMANE emulator with pluggable MAC and PHY radio types |
To quickly build a wireless network, you can first place several router nodes
onto the canvas. If you have the
Quagga MDR software installed, it is
recommended that you use the **mdr** node type for reduced routing overhead. Next
choose the **WLAN** from the **Link-layer nodes** submenu. First set the
desired WLAN parameters by double-clicking the cloud icon. Then you can link
all selected right-clicking on the WLAN and choosing **Link to Selected**.
Linking a router to the WLAN causes a small antenna to appear, but no red link
line is drawn. Routers can have multiple wireless links and both wireless and
wired links (however, you will need to manually configure route
redistribution.) The mdr node type will generate a routing configuration that
enables OSPFv3 with MANET extensions. This is a Boeing-developed extension to
Quagga's OSPFv3 that reduces flooding overhead and optimizes the flooding
procedure for mobile ad-hoc (MANET) networks.
The default configuration of the WLAN is set to use the basic range model. Having this model
selected causes **core-daemon** to calculate the distance between nodes based
on screen pixels. A numeric range in screen pixels is set for the wireless
network using the **Range** slider. When two wireless nodes are within range of
each other, a green line is drawn between them and they are linked. Two
wireless nodes that are farther than the range pixels apart are not linked.
During Execute mode, users may move wireless nodes around by clicking and
dragging them, and wireless links will be dynamically made or broken.
The **EMANE Nodes** leverage available EMANE models to use for wireless networking.
See the [EMANE](emane.md) chapter for details on using EMANE.
### Mobility Scripting
CORE has a few ways to script mobility.
| Option | Description |
|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ns-2 script | The script specifies either absolute positions or waypoints with a velocity. Locations are given with Cartesian coordinates. |
| CORE API | An external entity can move nodes by sending CORE API Node messages with updated X,Y coordinates; the **coresendmsg** utility allows a shell script to generate these messages. |
| EMANE events | See [EMANE](emane.md) for details on using EMANE scripts to move nodes around. Location information is typically given as latitude, longitude, and altitude. |
For the first method, you can create a mobility script using a text
editor, or using a tool such as [BonnMotion](http://net.cs.uni-bonn.de/wg/cs/applications/bonnmotion/), and associate the script with one of the wireless
using the WLAN configuration dialog box. Click the *ns-2 mobility script...*
button, and set the *mobility script file* field in the resulting *ns2script*
configuration dialog.
Here is an example for creating a BonnMotion script for 10 nodes:
```shell
bm -f sample RandomWaypoint -n 10 -d 60 -x 1000 -y 750
bm NSFile -f sample
# use the resulting 'sample.ns_movements' file in CORE
```
When the Execute mode is started and one of the WLAN nodes has a mobility
script, a mobility script window will appear. This window contains controls for
starting, stopping, and resetting the running time for the mobility script. The
**loop** checkbox causes the script to play continuously. The **resolution** text
box contains the number of milliseconds between each timer event; lower values
cause the mobility to appear smoother but consumes greater CPU time.
The format of an ns-2 mobility script looks like:
```shell
# nodes: 3, max time: 35.000000, max x: 600.00, max y: 600.00
$node_(2) set X_ 144.0
$node_(2) set Y_ 240.0
$node_(2) set Z_ 0.00
$ns_ at 1.00 "$node_(2) setdest 130.0 280.0 15.0"
```
The first three lines set an initial position for node 2. The last line in the
above example causes node 2 to move towards the destination **(130, 280)** at
speed **15**. All units are screen coordinates, with speed in units per second.
The total script time is learned after all nodes have reached their waypoints.
Initially, the time slider in the mobility script dialog will not be
accurate.
Examples mobility scripts (and their associated topology files) can be found
in the **configs/** directory.
## Alerts
The alerts button is located in the bottom right-hand corner
of the status bar in the CORE GUI. This will change colors to indicate one or
more problems with the running emulation. Clicking on the alerts button will invoke the
alerts dialog.
The alerts dialog contains a list of alerts received from
the CORE daemon. An alert has a time, severity level, optional node number,
and source. When the alerts button is red, this indicates one or more fatal
exceptions. An alert with a fatal severity level indicates that one or more
of the basic pieces of emulation could not be created, such as failure to
create a bridge or namespace, or the failure to launch EMANE processes for an
EMANE-based network.
Clicking on an alert displays details for that
exceptio. The exception source is a text string
to help trace where the exception occurred; "service:UserDefined" for example,
would appear for a failed validation command with the UserDefined service.
A button is available at the bottom of the dialog for clearing the exception
list.
## Customizing your Topology's Look
Several annotation tools are provided for changing the way your topology is
presented. Captions may be added with the Text tool. Ovals and rectangles may
be drawn in the background, helpful for visually grouping nodes together.
During live demonstrations the marker tool may be helpful for drawing temporary
annotations on the canvas that may be quickly erased. A size and color palette
appears at the bottom of the toolbar when the marker tool is selected. Markings
are only temporary and are not saved in the topology file.
The basic node icons can be replaced with a custom image of your choice. Icons
appear best when they use the GIF or PNG format with a transparent background.
To change a node's icon, double-click the node to invoke its configuration
dialog and click on the button to the right of the node name that shows the
node's current icon.
A background image for the canvas may be set using the *Wallpaper...* option
from the *Canvas* menu. The image may be centered, tiled, or scaled to fit the
canvas size. An existing terrain, map, or network diagram could be used as a
background, for example, with CORE nodes drawn on top.

View file

@ -63,13 +63,6 @@ toolbar, or choose *Node types...* from the *Session* menu. Note that
any new services selected are not applied to existing nodes if the nodes have
been customized.
The node types are saved in a **~/.core/nodes.conf** file, not with the
**.imn** file. Keep this in mind when changing the default services for
existing node types; it may be better to simply create a new node type. It is
recommended that you do not change the default built-in node types. The
**nodes.conf** file can be copied between CORE machines to save your custom
types.
## Customizing a Service
A service can be fully customized for a particular node. From the node's
@ -151,11 +144,11 @@ ideas for a service before adding a new service type.
defines one or more classes to be imported. You can create multiple Python
files that will be imported.
2. Put these files in a directory such as /home/username/.core/myservices
Note that the last component of this directory name **myservices** should not
be named something like **services** which conflicts with an existing module.
2. Put these files in a directory such as `/home/<user>/.coregui/custom_services`
Note that the last component of this directory name **custom_services** should not
be named the same as any python module, due to naming conflicts.
3. Add a **custom_services_dir = /home/username/.core/myservices** entry to the
3. Add a **custom_services_dir = `/home/<user>/.coregui/custom_services`** entry to the
/etc/core/core.conf file.
**NOTE:**
@ -165,7 +158,7 @@ ideas for a service before adding a new service type.
or **services**.
4. Restart the CORE daemon (core-daemon). Any import errors (Python syntax)
should be displayed in the /var/log/core-daemon.log log file (or on screen).
should be displayed in the daemon output.
5. Start using your custom service on your nodes. You can create a new node
type that uses your service, or change the default services for an existing

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View file

Before

Width:  |  Height:  |  Size: 637 KiB

After

Width:  |  Height:  |  Size: 637 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 753 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

View file

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

View file

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 642 B

After

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 719 B

View file

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 744 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 B

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 B

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
docs/static/gui/pc.gif vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 828 B

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 B

View file

Before

Width:  |  Height:  |  Size: 259 B

After

Width:  |  Height:  |  Size: 259 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 B

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 B

View file

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 925 B

View file

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 B

View file

Before

Width:  |  Height:  |  Size: 314 B

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 799 B

View file

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 B

View file

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 744 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 635 B

Some files were not shown because too many files have changed in this diff Show more