Compare commits

...

292 commits

Author SHA1 Message Date
3f2db35e68
updated to 9.0.3 merge 2023-09-21 18:59:32 +01:00
56a0511f21
updated to 9.0.3 2023-09-21 18:54:08 +01:00
bharnden
20071eed2e
Merge pull request #808 from coreemu/develop
fix issues with updated documentation
2023-08-01 10:18:11 -07:00
Blake Harnden
62a09c7570 docs: added github repo to documentation, fixed url paths for images in tutorials 2023-08-01 10:02:31 -07:00
bharnden
c37fa33ffe
Merge pull request #807 from coreemu/develop
CORE 9.0.3
2023-08-01 09:37:38 -07:00
Blake Harnden
7060a6842f bumped versions for release and updated changelog 2023-08-01 09:12:17 -07:00
bharnden
8da068159f
Merge pull request #805 from schrc3b6/develop
Fix templating error in radvd.conf
2023-08-01 08:43:50 -07:00
Blake Harnden
e3715e188c daemon: adjusted cmd utilities to support running different sets of commands for each node 2023-07-31 11:08:05 -07:00
schrc3b6
722fbde7d0 Fix templating error in radvd.conf
When configuring radvd service an undefined object error is thrown.
The data function in RadvdService returns a dict with ifaces. The radvd
template however, expects a dict with a values key.

I changed the key in the template because "ifaces" is way more descriptive
than "values".

Original Error that is fixed:
Traceback (most recent call last):
  File "/opt/core/venv/lib/python3.9/site-packages/core/configservice/base.py", line 471, in render_template
    return self._render(template, data)
  File "/opt/core/venv/lib/python3.9/site-packages/core/configservice/base.py", line 439, in _render
    return template.render_unicode(
  File "/opt/core/venv/lib/python3.9/site-packages/mako/template.py", line 444, in render_unicode
    return runtime._render(
  File "/opt/core/venv/lib/python3.9/site-packages/mako/runtime.py", line 874, in _render
    _render_context(
  File "/opt/core/venv/lib/python3.9/site-packages/mako/runtime.py", line 916, in _render_context
    _exec_template(inherit, lclcontext, args=args, kwargs=kwargs)
  File "/opt/core/venv/lib/python3.9/site-packages/mako/runtime.py", line 943, in _exec_template
    callable_(context, *args, **kwargs)
  File "/opt/core/venv/lib/python3.9/site-packages/core/configservices/utilservices/templates/etc/radvd/radvd.conf", line 2, in render_body
    % for ifname, prefixes in values:
TypeError: 'Undefined' object is not iterable
2023-07-28 16:03:49 +02:00
Blake Harnden
2cb8ec2fb2 install: updates to centos dockerfiles 2023-07-25 16:00:59 -07:00
Blake Harnden
544ad9b638 install: updated dockerfile for repo based ubuntu install 2023-07-25 11:12:24 -07:00
Blake Harnden
648f5425b5 install: update pyyaml to 6.0.1 2023-07-25 10:53:08 -07:00
Blake Harnden
359f6940e1 install: fixed issue with dual pyyaml reqs 2023-07-25 10:40:30 -07:00
Blake Harnden
2547c61a7c install: update pyyaml to 5.4.1 2023-07-25 10:35:47 -07:00
Blake Harnden
f8f3f8169c docs: adjustment to install for docker 2023-07-25 10:17:53 -07:00
Blake Harnden
12c07e0175 services: fixed pcap service not properly backgrounding command to avoid blocking 2023-07-15 10:27:41 -07:00
Blake Harnden
d63aa03343 build: updated grpcio dependency versions to fix grpcio vulnerabilities 2023-07-07 08:57:45 -07:00
Blake Harnden
db89fbf066 updated and fixed formatting with proper version of python black 2023-06-13 21:49:50 -07:00
Blake Harnden
a80796ac72 daemon: added initial podman node support 2023-06-13 17:00:53 -07:00
Blake Harnden
c76bc2ee8a daemon: updates for docker nodes to read and use the environ file for the launched process for all nsenter commands 2023-06-12 16:30:02 -07:00
Blake Harnden
e60b0f0de1 daemon: fixed issue bringing back addresses with no broadcast on rj45 nodes 2023-06-12 10:00:38 -07:00
Blake Harnden
e0e4b05b7f gui/grpc: updated GetConfigServiceDefaults to require a session and node id, this will allow retrieving data with the context of the associated node 2023-06-08 20:40:49 -07:00
Blake Harnden
41e473eefc docs: updated gui docs to include notes on running commands within nodes and using xhost for gui applications 2023-06-08 15:42:50 -07:00
Blake Harnden
12d7a1ff2a gui: fixed issue allowing duplicate named hook scripts 2023-06-08 14:59:40 -07:00
Blake Harnden
cbc35b74f8 docs: updated formatting on tutorial files 2023-06-08 14:43:02 -07:00
Blake Harnden
81230edac3 daemon: updated utils.cmd to use returncode instead of wait(), removed redundant default encoding value for calls to decode/encode 2023-06-08 14:34:24 -07:00
Blake Harnden
94f070e0ff cleanup: fixed issue cleaning up device names with @ in them 2023-06-08 11:36:09 -07:00
Blake Harnden
d04f8d69d2 docs: adding tutorial 2 and 3 2023-06-07 17:48:49 -07:00
Blake Harnden
0339073868 docs: added tutorial 5 2023-06-06 15:06:41 -07:00
Blake Harnden
9d88eba1f5 docs: added tutorials overview and tutorial 6 2023-06-06 09:40:44 -07:00
Blake Harnden
0b1a44e9b2 docs: cleanup tutorial scripts 2023-06-05 14:22:08 -07:00
Blake Harnden
2176fcc5a3 docs: adding tutorial 7 2023-06-05 13:57:06 -07:00
Blake Harnden
c554983436 docs: adding tutorial 4 2023-06-05 13:27:34 -07:00
Blake Harnden
75a92f3a38 docs: adjustments for tutorial 1 to working order 2023-06-05 13:07:59 -07:00
Blake J. Harnden
580916f2f0 docs: adding tutorial 1 images 2023-06-05 11:26:14 -07:00
Blake J. Harnden
a5727e3355 docs: adding tutorial 1 2023-06-05 11:11:00 -07:00
Blake Harnden
01585b6ec5 docs: fixed example emane model code to properly load platform config 2023-05-01 08:40:48 -07:00
Blake Harnden
d2008b1e5a docs: improved navigation, fixed type for warning in emane/gpsd 2023-04-20 12:01:39 -07:00
Blake Harnden
374ddb677c github: fixed branch name 2023-04-18 12:21:36 -07:00
Blake Harnden
8281382bd6 github: adding workflow for generating documentation on push to master 2023-04-18 12:21:13 -07:00
bharnden
946d161c11
Merge pull request #787 from coreemu/enhancement/documentation
Enhancement/documentation
2023-04-18 12:12:57 -07:00
Blake Harnden
fac8cfae08 daemon: fixed issue with session data collect transition not properly stopping config services 2023-04-15 00:25:41 -07:00
Blake Harnden
4a02d4bed9 gui: improve type hinting for ShowVar.state() 2023-04-13 21:48:39 -07:00
Blake Harnden
18ac8d5620 daemon: updated formatting for frr changes 2023-04-13 21:25:49 -07:00
Blake Harnden
d9f2ca8491 Merge branch 'gsomlo-gls-frr-sbin-fedora' into develop 2023-04-13 21:21:12 -07:00
Blake Harnden
49049befc8 Merge branch 'gls-frr-sbin-fedora' of https://github.com/gsomlo/core into gsomlo-gls-frr-sbin-fedora 2023-04-13 21:20:35 -07:00
bharnden
32bcd0a345
Merge pull request #774 from laerling/develop
Fix pcap service: Missing parameter
2023-04-13 21:09:34 -07:00
Blake Harnden
899800b925 daemon/gui: coverted remaining usages of old string formatting to f strings 2023-04-13 16:43:09 -07:00
Blake Harnden
4dba1bbe32 gui: updated usage of .format to f strings and small code cleanup for colorpicker 2023-04-13 16:30:35 -07:00
Blake Harnden
e7351b594d gui: updated core.gui to not use deprecated type hinting 2023-04-13 15:53:16 -07:00
Blake Harnden
69f05a6712 daemon: updated top level core modules from using deprecated type hinting 2023-04-13 15:48:02 -07:00
Blake Harnden
921bfdf527 daemon: updated core.services to avoid using deprecated type hinting, also updated string formatting to f strings 2023-04-13 15:39:40 -07:00
Blake Harnden
7f58224f43 daemon: updated core.nodes to avoid using deprecated type hinting 2023-04-13 13:32:23 -07:00
Blake Harnden
f9505b3173 daemon: updated core.scripts and core.xml to avoid using deprecated type hinting 2023-04-13 13:27:22 -07:00
Blake Harnden
7ea950f8ec daemon: updated core.location and core.plugins to avoid using deprecated type hinting 2023-04-13 12:29:16 -07:00
Blake Harnden
8abf2561bf daemon: updated core.emulator to avoid using deprecated type hinting 2023-04-13 12:23:44 -07:00
Blake Harnden
4c222d1a7a daemon: updated core.emane to avoid using deprecated type hinting 2023-04-13 12:18:24 -07:00
Blake Harnden
3d722a7721 daemon: updating core.configservice and core.configservices to avoid deprecated type hinting 2023-04-13 11:58:58 -07:00
Blake Harnden
e770bcd47c daemon: update deprecated typing for core.api 2023-04-13 11:22:37 -07:00
Blake Harnden
6ff2abf0b8 daemon: fixed issue for CoreError messages in new hooks module, updated new modules to all use non deprecated type hinting 2023-04-12 16:53:08 -07:00
Blake Harnden
da3cebe1cd daemon: added refactored cleaned up logic for future handling of broadcasting data 2023-04-12 16:14:58 -07:00
Blake Harnden
b6b300207b daemon: added convenience method to check if a session is running, which is used in multiple places, providing a cleaner and easier to read experience 2023-04-12 14:44:51 -07:00
Blake Harnden
cdc8c7360d daemon: added refactored cleaned up code to use for hooks 2023-04-12 11:58:25 -07:00
Blake Harnden
a6a09d9e56 daemon: added new cleaned up controlnet code, for future use 2023-04-11 12:45:50 -07:00
Blake Harnden
cafbb15b1e daemon: small cleanup in distributed start 2023-04-11 10:09:40 -07:00
Blake Harnden
c444304040 docs: fixed issue with bad imports in grpc doc examples 2023-04-10 11:19:46 -07:00
Blake Harnden
2eb29525de gui: fix issue deleting nodes/links attached to rj45 nodes 2023-04-10 08:13:01 -07:00
Blake Harnden
fcf1448ab6 docs: small documentation cleanup 2023-03-28 21:51:27 -07:00
Blake Harnden
15df06d834 docs: updates to reformat and adjust documentation in regards to using specific services 2023-03-28 21:47:27 -07:00
Blake Harnden
0053ddb57d docs: updates to leverage mkdocs material admonitions 2023-03-28 21:42:43 -07:00
Blake Harnden
03fe74c195 gui: fix issue when joining a session with an rj45 node and assigning an interface, whichw ill later cause an error when configuring the rj45 node 2023-03-28 12:37:25 -07:00
Blake Harnden
d52e0c4547 grpc: adjustment when starting a session to not update session options for empty values, since they may override values set in /etc/core/core.conf 2023-03-21 15:34:13 -07:00
laerling
b4ed8bc9c5 Fix pcap service: Mako dict parameter missing 2023-03-21 00:47:27 +01:00
Blake Harnden
5b41b4e5be docs: brought out hardware in the loop concepts to a higher level for easier visbility, rather than being hidden within the gui docs 2023-03-13 16:22:08 -07:00
Blake Harnden
59f814eac0 install: fixed bad indentation for local install line, causes an error not finding expected files 2023-03-09 10:49:11 -08:00
Blake Harnden
078e0df329 docs: initial changes to support using mkdocs material 2023-03-07 21:49:50 -08:00
Blake Harnden
785cf82ba3 docs: added complete install examples based on dockerfile commands 2023-03-07 10:46:31 -08:00
Blake Harnden
d09f777645 update centos dockerfile to avoid installing unwanted python system packages 2023-03-07 10:43:11 -08:00
Blake Harnden
f8d2b47fa9 install: updated Pillow dependency 2023-03-03 10:00:59 -08:00
bharnden
c44dc521fb
Merge pull request #767 from coreemu/develop
CORE 9.0.2
2023-03-02 22:12:35 -08:00
Blake Harnden
04e778e97f updated changelog for 9.0.2 release 2023-03-02 21:04:59 -08:00
Blake Harnden
d45eeb6d2e updated dockerfiles to run commands to save space optimally 2023-03-02 08:34:54 -08:00
Blake Harnden
fec400ac2e bumped versions for next release 2023-03-01 21:51:05 -08:00
Blake Harnden
c1ad39631d install: updated poetry.lock file to latest dependencies, also updated invoke to 1.7.3 to avoid python 3.10+ issues 2023-02-27 12:49:39 -08:00
Blake Harnden
b92e4ed6b9 install: fixed deprecated syntax when using poetry to install only the main packages 2023-02-27 11:08:51 -08:00
Blake Harnden
3e5c8c894f daemon: fixed issue for LXC nodes and not properly picking up configured image name to use and for writing to xml 2023-02-21 12:06:01 -08:00
Blake Harnden
f10c7cac45 docs: reformatted bird documentation and removed invalid example link 2023-02-06 09:22:00 -08:00
Blake Harnden
0ffcc10953 docs: add example for installing emane using downloaded protoc 2023-02-03 16:52:14 -08:00
Blake Harnden
4dca3eac39 docs: updated install page to remark on solutions for the common issue a user may run into when docker is installed 2023-02-03 16:45:47 -08:00
Blake Harnden
93272d6ed7 daemon: formatting changes related to updating the python black formatter 2023-02-03 15:53:44 -08:00
Blake Harnden
4c351b0d72 install: updated version of the python black library being used, updated poetry.lock in response to help correct issue with click 2023-02-03 15:46:52 -08:00
Blake Harnden
56a287c9c0 install: updated poetry.lock to correct poetry warning, also updates some transitive dependencies for the better 2023-02-03 15:34:16 -08:00
Blake Harnden
d4997bbc04 daemon: fixed p2p nodes to disable mac learning, with new usage to tie together all wired links, this feature is not desired 2023-02-03 15:05:03 -08:00
Blake Harnden
4f2e20a0a0 daemon: updated usage of sysctl to properly leverage utility function to correct device name 2023-02-01 08:40:23 -08:00
Blake Harnden
5abbc1680a daemon: improve node boot logging to display config and legacy services 2023-01-24 09:17:15 -08:00
Blake Harnden
c91facd6e3 daemon: fix to properly check for setting broadcast support 2023-01-24 08:28:25 -08:00
Blake Harnden
d215330426 docs: fix example for scripts ran from gui and how to optionally retrieve the coreemu instance 2023-01-19 16:13:00 -08:00
Blake Harnden
db2f57ca35 install: removed unused help option for build task 2022-12-20 16:11:05 -08:00
Blake Harnden
a3892d6b0e grpc: updated node events to leverage the common grpcutils.get_node_proto so that service and emane config data will be included 2022-12-09 10:56:30 -08:00
Blake Harnden
f422d05215 gui: fixed issue not displaying emane configs that are generally configured for a node, was attempting only interface specific or emane net fallback 2022-12-09 10:29:41 -08:00
Blake Harnden
9ccb1880a1 docs: removed usages of pygui 2022-12-09 10:22:12 -08:00
Blake Harnden
88a52f6cd2 grpc: fix node events to include the config services on a node, allows dynamically added nodes to show their services within the gui 2022-12-09 09:32:49 -08:00
Blake Harnden
a84e689478 updated emane demo example files provided by gui to align with latest changes 2022-12-05 10:54:34 -08:00
Blake Harnden
a787f46719 daemon: fixed issue when setting default wlan config for a wlan node, using the session id instead of the node id 2022-12-05 10:26:46 -08:00
Blake Harnden
b430de226d Merge branch 'develop' of https://github.com/coreemu/core into develop 2022-12-05 01:15:29 -08:00
Blake Harnden
e176a02460 docs: fixed documentation to point to the right files 2022-12-05 01:15:09 -08:00
Blake Harnden
e96322c9af docs: update install instruction for emane to denote passing PYTHON to the emane helper task 2022-11-30 10:47:17 -08:00
bharnden
ecf380c884
Merge pull request #718 from coreemu/develop
release 9.0.1
2022-11-28 14:11:55 -08:00
Blake Harnden
6c52029795 updated dockerfiles to align with protobuf changes 2022-11-28 14:02:46 -08:00
Blake Harnden
41b231b577 updated version and changelog for next release 2022-11-28 11:10:00 -08:00
Blake Harnden
f8a20f8dbb install: updated grpcio-tools in pyproject.toml to 1.49.1 to properly match automated install, also updated protobuf to 4.21.9 to fix issue with import 2022-11-28 10:17:44 -08:00
249cdf1dab
Added rust toolchain 2022-11-20 15:08:52 +00:00
bharnden
5ab71377cc
Merge pull request #712 from coreemu/develop
merge develop for 9.0.0 release
2022-11-18 14:46:29 -08:00
Blake Harnden
50bc4343c3 updated changelog for next release 2022-11-18 11:09:56 -08:00
Blake Harnden
c6898c7c0e examples: updated python switch example to show the need to use ids for switch/hub interfaces 2022-11-10 09:54:31 -08:00
Blake Harnden
568b1360a2 examples: fixed python emane example to leverage current internal api 2022-11-03 12:08:40 -07:00
Blake Harnden
898a4f7c84 gui: updated custom nodes to use config services and updated labels to reflect that 2022-10-14 21:40:40 -07:00
Blake Harnden
4c9c6e9f8c install: bumped grpcio versions to 1.49.1 2022-10-14 12:14:20 -07:00
Blake Harnden
8e2593c9e0 gui: fixed error related to finalizing linked nodes due to recent throughput correction 2022-10-14 10:57:00 -07:00
Blake Harnden
f43e8f7646 install: updated dockerfiles for installing using the script based method for centos/ubuntu 2022-10-14 10:42:31 -07:00
Blake Harnden
7067b54a00 install: update tasks.py to use current latest git commit for ospf mdr 2022-10-14 08:56:33 -07:00
Blake Harnden
9c71e0144c install: updated poetry version to install in setup.sh to 1.2.1 2022-10-14 08:43:12 -07:00
Blake Harnden
0e627afeb0 install: moved all dockerfiles to a specific directory and updated package files to account for recent changes and install ospf mdr and emane 2022-10-13 23:33:22 -07:00
Blake Harnden
82739ce3af install: updates to make python3.9 the minimum required version, updates to various dependencies to account for known vulnerabilities 2022-10-13 10:15:38 -07:00
Blake Harnden
47991cd35f updated workflow to use newer version 2022-10-12 21:35:22 -07:00
Blake Harnden
16b0decde7 install: corrected pytest update 2022-10-12 21:28:19 -07:00
Blake Harnden
d203b7d3ca install: updated pytest to working version 2022-10-12 21:15:22 -07:00
Blake Harnden
e0a21fb099 gui: improved throughputs start/stop to coincide with session start and stop based on a tracked variable 2022-10-12 20:53:59 -07:00
Blake Harnden
03775c2c3c daemon/grpc: shifted kill signal handling to grpc server who will handle complete shutdown, no longer done by CoreEmu directly 2022-10-12 14:34:24 -07:00
Blake Harnden
b5b7b8cdf9 grpc: updates to fix throughput parsing, accounting for new connection changes, now supports throughput for network to network links 2022-10-12 14:04:48 -07:00
Blake Harnden
9218fb0b6f docs: added notes about new wireless node type to GUI documentation 2022-10-12 08:48:23 -07:00
Blake Harnden
0d1fa0049a docs: update quickstart readme for installation covering new package option 2022-10-07 13:05:16 -07:00
Blake Harnden
c067de6792 docs: updates to install page to cover all install types and PATH issues 2022-10-07 12:52:53 -07:00
Blake Harnden
4b2d33c898 Merge branch 'develop' of https://github.com/coreemu/core into develop 2022-09-29 14:03:53 -07:00
Blake Harnden
ee2cdf7716 install: updated automated install to place virtual environment to /opt/core/venv to be consistent with new package location 2022-09-29 13:58:09 -07:00
afonsofrancof
6631fc95ed Added ftp 2022-09-28 22:15:15 +01:00
afonsofrancof
909142b446 Added ftp 2022-09-28 22:14:48 +01:00
e8f0ce79d5 Update 'README.md' 2022-09-28 21:24:35 +01:00
afonsofrancof
5013b53605 Added text editors 2022-09-28 21:17:34 +01:00
afonsofrancof
7e87ad24fa Added text editors 2022-09-28 21:17:16 +01:00
afonsofrancof
2f2dbe26d1 Added firefox dependencies 2022-09-28 21:19:20 +01:00
afonsofrancof
33e13e2381 Added firefox dependencies 2022-09-28 21:18:36 +01:00
2a7904e2f1 Update 'README.md' 2022-09-28 20:31:35 +01:00
47ae24ad59 Update 'README.md' 2022-09-28 20:11:24 +01:00
3b22c7038f Update 'README.md' 2022-09-28 20:10:49 +01:00
afonsofrancof
9c4a0fda32 Dependencies updated 2022-09-28 21:07:51 +01:00
Blake Harnden
78eb03cc65 install: updated option to skip system python packages for setup.sh to be more appropriately named 2022-09-14 22:43:33 -07:00
Blake Harnden
5a81283fca install: added option to skip python system packages for setup.sh 2022-09-14 21:03:24 -07:00
Blake Harnden
281a848bbf grpc: fixed documentation mistake in grpc server 2022-09-13 12:46:56 -07:00
Blake Harnden
e25d1c72b3 daemon: fixed bad config generation for bgp config service 2022-09-13 12:41:24 -07:00
Blake Harnden
88ccd1f194 gui: fixed issue updating preferences, resulting in nodes changing to a default icon 2022-09-10 10:06:40 -07:00
Blake Harnden
d045fc0d51 daemon: removed session state file, information should be available through grpc if needed 2022-09-09 14:22:30 -07:00
Blake Harnden
e56d93f0fe daemon: removed generation of the session nodes file, this information should be available through grpc 2022-08-31 17:03:31 -07:00
Blake Harnden
382ff6d49b docs: updated install doc to provide a better example for installing emane python bindings into a virtual environment 2022-08-30 12:25:34 -07:00
bharnden
5202b2fa04
Merge pull request #700 from coreemu/enhancement/opt-core
Enhancement/opt core
2022-08-30 12:08:28 -07:00
Blake Harnden
e4abefe23b grpc: added nem id and port to interface data returned from GetNode 2022-08-03 17:21:31 -07:00
Blake Harnden
018865b2a2 docs: added install note about upgrading pip to avoid building from source related issues 2022-08-02 13:49:38 -07:00
Blake Harnden
c91d8df790 install: added task using a subset of the previous auto install to support auto setup and building of core packages 2022-07-29 14:12:17 -07:00
Blake Harnden
a201fd2903 install: add option to avoid y/n prompt when uninstalling core during post install script 2022-07-29 14:03:45 -07:00
Blake Harnden
d06659ff82 install: remove debian config from centos package dockerfile 2022-07-29 12:03:20 -07:00
Blake Harnden
d8632da96b install: fixed install.md error for docker build command, fixed default dockerfile example for centos package install to not use NO_VENV 2022-07-27 16:41:23 -07:00
Blake Harnden
fcf6f30302 install: updates to support building deb/rpm packages that contain python wheels and install core from a single file, updates to core to install scripts by way of python directly 2022-07-27 16:00:10 -07:00
Blake Harnden
cd6bb319ad Merge branch 'develop' of https://github.com/coreemu/core into develop 2022-07-21 15:41:48 -07:00
Blake Harnden
d63722c0ed install: add option to avoid installing python system dependencies, in case the python version being used is from source 2022-07-21 15:35:31 -07:00
bharnden
273f68a1ec
Merge pull request #692 from coreemu/node-updates
Node updates
2022-06-29 11:55:45 -07:00
Blake Harnden
fe1593b51f update dockerfiles to fix default branch name in the oracle linux dockerfile and update pip in the centos dockerfile to get back to working order 2022-06-27 09:38:52 -07:00
Blake Harnden
3c28ea373a daemon: adjustments to fix terminal command string generation for docker/lxd nodes to account for being on a distributed server 2022-06-10 14:53:49 -07:00
Blake Harnden
9c69881aad daemon: updates to expose node.create_cmd and not be private, added utility functions for running multiple commands on multiple nodes more efficiently 2022-06-10 14:23:06 -07:00
Blake Harnden
60a48c7084 daemon: update node commands to make use of shlex.quote for shell=True commands 2022-06-10 12:12:25 -07:00
Blake Harnden
9c265ab283 daemon: updates to change hostname settings to replace _ to - due to _ being an invalid character 2022-06-10 10:01:48 -07:00
Blake Harnden
9991942e7b daemon: cleanup code for lxd based nodes and properly implement command exectution 2022-06-10 09:20:49 -07:00
Blake Harnden
469f8f087a grpc: removed removed api call from client code get_node_links, this information is included in get node 2022-06-08 14:17:13 -07:00
Blake Harnden
1aa9d4bccf examples: restore docker switch example to previous state 2022-06-08 14:06:54 -07:00
Blake Harnden
1d718aeda2 update configservice example file to a proper name 2022-06-08 14:00:20 -07:00
bharnden
bb49947550
Merge pull request #690 from coreemu/docker-updates
Docker updates
2022-06-08 13:57:06 -07:00
Blake Harnden
e5d6299f0a daemon: fixed error in requirements check and sorted executable definitions 2022-06-01 11:12:20 -07:00
Blake Harnden
9fa3e77d12 daemon: added further python docs to docker.py 2022-05-31 14:25:49 -07:00
Blake Harnden
f40de9f838 install: added dockerfile for oracle linux support 2022-05-27 20:54:47 -07:00
Blake Harnden
d77ed9c473 install: adjustments to install scripts to better support alternative versions of python, PYTHON_DEP added for system package adjustments 2022-05-27 20:10:48 -07:00
Blake Harnden
7173e488cb daemon: fixed issue with not properly starting emane event monitoring when enabled 2022-05-27 10:57:26 -07:00
Blake Harnden
bbcd4664ff daemon: removed session.add_node_file as it is not needed and was only used by the old tlv api 2022-05-27 10:43:59 -07:00
Blake Harnden
31bc0c6497 scripts: fixed bug in setting type when creating nodes using core-cli 2022-05-27 10:15:34 -07:00
Blake Harnden
28d3deeee8 install: updated and added dockerfiles for ubuntu/centos, updated notes on install page to correlate 2022-05-25 21:43:52 -07:00
Blake Harnden
2e3e085522 daemon: adjustments to revamp how core nodes are created in session.add_node, nodes now provide a create_options function for node specific options that are type hinted 2022-05-25 10:51:42 -07:00
Blake Harnden
03e646031c daemon: updated docker nodes to start using tail to keep alive, adjustments to support more robust volume mounting and adding symlinks to the nodes directory for bind/volume mounts 2022-05-18 14:50:08 -07:00
Blake Harnden
fd3be57f57 daemon: added initial support to docker nodes for setting up volumes and bind mounts 2022-05-11 13:51:02 -07:00
Blake Harnden
25c3c42b40 Merge branch 'develop' into docker-updates 2022-05-11 09:19:10 -07:00
Blake Harnden
844404c765 install: bumped version to 9.0.0 for next release 2022-05-11 09:19:00 -07:00
Blake Harnden
6f60fba18b install: updated pyproj version to avoid the need to build from source on newer distros 2022-05-10 13:54:51 -07:00
Blake Harnden
52b875df36 install: updated locked poetry packages and specifically bumped grpc tooling to a newer version for better binary install support 2022-05-10 13:24:19 -07:00
Blake Harnden
5f1e722331 install: adjustments to better support specifying an alternative python for install using PYTHON env variable 2022-05-10 11:51:36 -07:00
Blake Harnden
33f3eccdcf Merge branch 'develop' into docker-updates 2022-05-09 21:11:51 -07:00
Blake Harnden
8ba169c758 daemon: fixed naming issues with wireless/emane model attributes, fixed issue with emane controlnet updown script 2022-05-09 21:11:14 -07:00
Blake Harnden
2094694ca3 daemon: cleaned up docker node code, updates to use nsenter in most cases 2022-05-05 21:54:08 -07:00
Blake Harnden
2e4d0e0cea daemon: changes to generate random names for all wireless edge veths, to avoid names that would be an invalid device name 2022-05-02 15:01:03 -07:00
Blake Harnden
2ab2c27d49 daemon/test: improved MoveNodesRequest to be hashable using dataclass itself, fixed grpc test not properly using a wrapped MoveNodesRequest object 2022-04-28 23:20:17 -07:00
Blake Harnden
aa8ea40ce6 daemon: moved SetQeueue into utils to be leveraged by others, updated MoveNodesStreamer to leverage SetQueue, this will allow a means to stream node movements, but if position changes happen faster than processing, the latest position will override prior pushes and the latest position will be pulled off the queue 2022-04-28 16:12:31 -07:00
Blake Harnden
fe0bc2b405 services: removed no longer relevant service arouted and associated logic, fixed nrlsmf to put the flood command before configured interfaces 2022-04-26 14:11:48 -07:00
Blake Harnden
0a0248d8b2 gui: fixed issue where wireless edges were not properly attempting to arc common edges when added/deleted 2022-04-26 10:16:36 -07:00
Blake Harnden
0b420cfc07 grpc: update to fix sending protobuf format wireless config for joining session 2022-04-19 16:29:33 -07:00
Blake Harnden
62a060588d grpc: update to make sure retrieving session data returns wireless configs for nodes 2022-04-19 16:19:19 -07:00
Blake Harnden
7348942a67 daemon: add a new config value for the wireless network to allow a baseline loss value to be set 2022-04-18 16:46:59 -07:00
Blake Harnden
964e3aaf39 daemon: updated wireless network to configure static delay/bandwidth/jitter and calculate loss only, also will link nodes initially if enabled or run calculation when not at startup 2022-04-15 14:56:21 -07:00
Blake Harnden
ce5c155327 daemon: updated xml code to write/read wireless configs 2022-04-14 16:50:01 -07:00
Blake Harnden
d20cb1ef58 daemon/gui: added support to configure wireless network for position calculations or not 2022-04-14 16:31:14 -07:00
Blake Harnden
d124820a86 daemon: fixed error in handling errors when rendering config service templates 2022-04-14 10:48:00 -07:00
Blake Harnden
e5c06fe47c daemon: fixed wireless movement callback to not calculate config for position callback on links that are down 2022-04-13 13:43:58 -07:00
Blake Harnden
f811373f0b gui: updated config service config dialog to resize better, also now display the rendered tab first, for a more user friendly default 2022-04-11 15:55:32 -07:00
Blake Harnden
c40fb2b15d daemon: updated frr services to use consistent configuration for iface config for ospfv2, enabled opaque lsa support for ospf by default 2022-04-11 10:54:27 -07:00
Blake Harnden
bd6f789cef daemon/gui/grpc: added support to retrieve rendered config service files, added support for grpc to access this, and update gui to leverage this call to provide a rendered view of files based on the current scenario, also allows editing the rendered output to use as the input when running the scenario 2022-04-08 22:24:07 -07:00
Gabriel Somlo
314eee33f6 frrboot: add fedora sbin path to default
Add the Fedora default location for FRR binaries (/usr/libexec/frr/*.)
to CORE's frrboot default "sbin" search path

Signed-off-by: Gabriel Somlo <gsomlo@gmail.com>
2022-04-06 13:41:00 -04:00
Blake Harnden
443c0e708f daemon: removed linktype and apitype from node classes, as they are no longer needed, made adjustments where needed to compensate 2022-04-05 15:22:01 -07:00
Blake Harnden
7440c1d949 daemon: removed node.linktype as it is not needed, updated the one usage to align with other cases 2022-04-05 13:57:49 -07:00
Blake Harnden
5ee561b210 daemon: fixed sdt icons due to legacy gui removal, updated node.type to node.model to avoid variables with the same names with different meanings 2022-04-05 13:39:57 -07:00
Blake Harnden
ea751727b4 daemon: refactoring to tracking wireless link information 2022-04-05 12:05:37 -07:00
Blake Harnden
440c06c040 daemon: updates to refactor and clean up SessionConfig to contain its own logic 2022-04-04 15:13:31 -07:00
Blake Harnden
409b6809e6 daemon: adjustment for wireless link config to avoid has_netem issues for bidirectional configuration 2022-04-04 09:18:18 -07:00
Blake Harnden
388ae44cf2 daemon: small adjustment/cleanup to shutting down all sessions from coreemu 2022-04-04 09:15:34 -07:00
Blake Harnden
543e9982c0 daemon: apply wireless link label even for 0 to force gui update for now 2022-04-02 22:08:59 -07:00
Blake Harnden
42cd1a1019 daemon: force wireless link config options2 to have netem to force a clear/update 2022-04-01 21:30:42 -07:00
Blake Harnden
559cac18e8 grpc: fixed wireless config client/server 2022-04-01 21:23:58 -07:00
Blake Harnden
9d97699b1f daemon: fixed issue starting session and clearing session options, small cleanup to SessionOptions init 2022-04-01 11:46:28 -07:00
bharnden
08637d35b3
Merge pull request #682 from coreemu/enhancement/core-player
Enhancement/core player
2022-03-31 16:55:48 -07:00
Blake Harnden
5d4642006c daemon: added core player writer and player to core library and added wireless event handling, fixed issues with wireless nodes being written to xml 2022-03-31 16:31:20 -07:00
Blake Harnden
ba0e4adb04 Merge branch 'develop' into enhancement/core-player 2022-03-31 09:57:53 -07:00
bharnden
ace0183db5
Merge pull request #681 from coreemu/enhancement/wireless-node
Enhancement/wireless node
2022-03-31 09:56:34 -07:00
Blake Harnden
84acb82c18 daemon/grpc/gui: further updates for a new working wireless node, added grpc support for control and additions to gui for creation 2022-03-30 21:13:28 -07:00
Blake Harnden
e4a6ecf3c2 daemon: initial code to support a new style wireless approach 2022-03-29 12:16:07 -07:00
Blake Harnden
73aaa8ca18 daemon: updated bad doc string 2022-03-25 10:53:52 -07:00
Blake Harnden
1fa0ac25b5 gui: removed host from node model types and updated node config to only display type 2022-03-24 17:15:44 -07:00
Blake Harnden
26f0848cb4 daemon: update iface to use desired name when configuring, gui: dont assign mac to non container nodes 2022-03-22 21:08:31 -07:00
Blake Harnden
346364d705 Merge branch 'develop-notcl' into develop 2022-03-22 10:03:57 -07:00
Blake Harnden
d83bfed608 merged latest updates from develop 2022-03-22 10:03:03 -07:00
bharnden
e3466f0669
Merge pull request #677 from coreemu/enhancement/consistent-linking
Enhancement/consistent linking
2022-03-22 09:47:29 -07:00
bharnden
1841fb1110
Merge pull request #676 from coreemu/develop
Develop merge for 8.2.0
2022-03-21 21:47:35 -07:00
Blake Harnden
d7b2c3cac3 updated changelog for 8.2.0 release 2022-03-21 21:43:24 -07:00
Blake Harnden
6d8ae4af2e core-cli: add geo position to node output for query session and node 2022-03-21 21:35:05 -07:00
Blake Harnden
2df8aa4379 updated version to 8.2.0 for next release 2022-03-21 21:04:43 -07:00
Blake Harnden
b941395100 core-cli: updates to add back json output 2022-03-21 20:59:18 -07:00
Blake Harnden
5398cdd2d5 core-cli: fixed xml load call to use the right parameter type 2022-03-21 15:57:13 -07:00
Blake Harnden
7ed007496c daemon: add linked method to session and grpc support leveraging new linking semantics, update add_link validation check 2022-03-21 15:42:14 -07:00
Blake Harnden
e5e14ad67b grpc: added wlan/emane links to grpc calls to allow knowing initial wireless link state, same as before changes 2022-03-21 09:55:57 -07:00
Blake Harnden
dc9b6adc98 daemon/gui: adjustments to account for network node wired links having proper interface ids, which allow for unique configuration with the new linking semantics 2022-03-18 15:53:13 -07:00
Blake Harnden
b71272519d daemon: revamp to align gre tunnels for distributed to align with changes, also moved gre tunnels for wired links to occur directly when linking, allowing runtime distributed functionality, also updates to phyical node to leverage a core node, but avoid using namespacing 2022-03-18 12:31:04 -07:00
Blake Harnden
8c24e9cfa6 daemon: updates to fix rj45 to account for iface link revamp 2022-03-17 21:21:03 -07:00
Blake Harnden
3c8f6a9512 daemon: fix convert interface grpc utility to properly get ip4/ip6 addresses 2022-03-17 16:44:12 -07:00
Blake Harnden
4f58d5d8eb daemon: removed unused EmaneNet function 2022-03-17 16:43:31 -07:00
Blake Harnden
7b16f9cb74 daemon: moved node cmd to base, allowing interface config to not need a node parameter 2022-03-17 16:31:03 -07:00
Blake Harnden
cd7f1a641e daemon: initial pass to revamp how node linking and link management is done, provides a consistent way to link all wired nodes and allows them to be configured for tc for the same behavior across the board 2022-03-17 15:28:38 -07:00
Blake Harnden
e59fc02ec3 scripts: initial core-player script to playback .core files 2022-03-11 13:29:24 -08:00
Blake Harnden
d684b8eb5a docs: updated emane python example to use updated set_config 2022-03-11 11:04:01 -08:00
Blake Harnden
6cf2793e30 install: removed netgraph checks in configure.ac, removed referenced to netgraph in docs 2022-03-08 16:50:09 -08:00
Blake Harnden
f0750d78b0 docs: updated plantuml files to remove tlv/imn references and regenerated images 2022-03-08 16:10:22 -08:00
Blake Harnden
41db531e97 daemon: removed some unused Session functions and change to make proper use of set_user 2022-03-08 15:50:19 -08:00
Blake Harnden
dde339cc46 daemon: removed bitmap, which is not used 2022-03-08 14:32:01 -08:00
Blake Harnden
c8a589ef76 daemon: removing python tlv related code and tests related to it 2022-03-08 14:18:47 -08:00
Blake Harnden
dc7bb37252 install: removed tcl based gui, updated configure.ac, Makefile.am, and tasks.py to account for removal 2022-03-08 12:00:42 -08:00
Blake Harnden
f324cfbc4a daemon: updated core.conf to change .core references to .coregui 2022-03-08 11:36:57 -08:00
Blake Harnden
3eed1a09fb docs: removing references to imn, tcl, tk, tlv, and .core, removed unused icons 2022-03-08 11:30:03 -08:00
Blake Harnden
f545726ed5 daemon: small improvements to command error reporting, as well as catching and reporting config service get text dynamic exceptions 2022-03-04 09:55:40 -08:00
Blake Harnden
0536747d9a daemon/gui: adjustments to how a session instantiates, allowing the new python gui to move to a failed start state, this allows inpecting the failed nodes to see what went wrong 2022-03-04 09:19:56 -08:00
Blake Harnden
2eef7076f4 daemon: updated how local services are loaded to leverage full core path imports, avoiding name collisions with external modules 2022-03-02 12:42:53 -08:00
Blake Harnden
a42697ecce daemon: consolidated the code for CoreNode commands into the class itself 2022-02-28 22:05:01 -08:00
Blake Harnden
b085105a6b install: updated install instructions to use the right command for centos example 2022-02-28 10:03:34 -08:00
Blake Harnden
074a2263ce daemon: updates to network base to set default behavior for abstract function 2022-02-25 15:06:14 -08:00
Blake Harnden
e557b402b6 daemon: fix PhysicalNode instantiation issues, remove old functions that are no longer use and update session to use the new variation 2022-02-25 14:46:03 -08:00
Blake Harnden
2dd3839396 docs: updated emane docs to remove old GUI references and update the example pictures to include the configuration dialog of the new gui 2022-02-25 12:17:51 -08:00
Blake Harnden
58ffd045e2 daemon: pulling in an old legacy gui related fix for now, that was not merged back 2022-02-25 11:05:14 -08:00
Blake Harnden
482ad037f4 docs: updates to cleanup formatting on tables and denote that the python gui is the default in index.md 2022-02-25 10:38:14 -08:00
Blake Harnden
a7588a2188 install: updated ospf check to use sudo to account for sbin potentially not being on path 2022-02-18 15:25:41 -08:00
bharnden
bcf7429785
Merge pull request #654 from coreemu/develop
8.1.0 merge
2022-02-18 09:12:33 -08:00
Blake Harnden
cdde1c89ee added 8.1.0 notes to changelog 2022-02-18 09:08:22 -08:00
Blake Harnden
a341691d69 install: poetry updates to fix vulnerability issues with protobuf, py, and cryptography 2022-02-18 00:33:18 -08:00
Blake Harnden
458b7f15ce pygui: fixed antenna image to properly show alpha png 2022-02-18 00:24:30 -08:00
Blake Harnden
96f2408e01 daemon: fixed deadlock issue when starting/stopping nftables queue 2022-02-17 23:44:30 -08:00
Blake Harnden
113be65078 install: adjustments to have a better workflow for installation 2022-02-17 15:17:07 -08:00
Blake Harnden
9d32c432db install: updates to README and install docs for installing to run ensurepath after setup.sh ends 2022-02-17 15:03:09 -08:00
Blake Harnden
3c64654598 services: fix missing configurations for ospfv2 in config services 2022-02-17 10:14:04 -08:00
Blake Harnden
0fcc532c0d docs: updated note on poetry version installed 2022-02-11 21:04:12 -08:00
Blake Harnden
e80d22096c install: bumping version to 8.1.0 for next release 2022-02-10 16:17:22 -08:00
Blake Harnden
a1e9fc02fd install: bumped pinned poetry version to 1.1.12 to avoid installation issues related to poetry 2022-02-10 16:11:43 -08:00
Blake Harnden
47b02c3e7b docs: removed notes on needing to install ebtables legacy 2022-02-09 12:47:49 -08:00
Blake Harnden
0e2219f6c8 daemon: fixed issue creating directory for files when needed, within node 2022-02-05 12:19:24 -08:00
Blake Harnden
490a4acf24 daemon: fixed issues related to rj45 2022-02-05 00:46:25 -08:00
Blake Harnden
efb97d1a5d daemon: updates to remove the delay for processing wlan changes along with code to support it 2022-02-05 00:39:11 -08:00
Blake Harnden
43737a42e4 daemon: update nftables bridge tables to use priority -1 to beat default inet table rules if present 2022-02-03 14:29:42 -08:00
Blake Harnden
44b7b6a27e daemon: update config service files to use paths for retrieving templates and generating files in the same consistent way 2022-02-03 14:28:56 -08:00
Blake Harnden
df5ff02f95 docs: fix for bad config service example 2022-02-01 21:26:03 -08:00
Blake Harnden
8f767208e0 daemon: small adjustment to fix xml related issues parsing links for now, until bigger refactoring 2022-01-26 11:35:23 -08:00
Blake Harnden
d5b05a39e8 gui: adjustment to update drawing asymmetric edge data when joining a session 2022-01-26 11:19:30 -08:00
Blake Harnden
6791269eeb daemon: refactored interfaces to store configuration options as link options, instead of using a dictionary 2022-01-25 21:39:52 -08:00
Blake Harnden
e9b83b0d28 daemon: refactored how interfaces are configured, updated link edits to allow proper bi-directional support for network to network interfaces, improved and added more unit tests for link add/edit/delete 2022-01-25 09:13:39 -08:00
552 changed files with 14630 additions and 57206 deletions

View file

@ -4,13 +4,13 @@ on: [push]
jobs:
build:
runs-on: ubuntu-18.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v1
- name: Set up Python 3.6
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.6
python-version: 3.9
- name: install poetry
run: |
python -m pip install --upgrade pip

21
.github/workflows/documentation.yml vendored Normal file
View file

@ -0,0 +1,21 @@
name: documentation
on:
push:
branches:
- master
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.x
- uses: actions/cache@v2
with:
key: ${{ github.ref }}
path: .cache
- run: pip install mkdocs-material
- run: mkdocs gh-deploy --force

7
.gitignore vendored
View file

@ -14,9 +14,13 @@ config.h.in
config.log
config.status
configure
configure~
debian
stamp-h1
# python virtual environments
venv
# generated protobuf files
*_pb2.py
*_pb2_grpc.py
@ -58,3 +62,6 @@ daemon/setup.py
# python
__pycache__
# ignore core player files
*.core

View file

@ -1,3 +1,122 @@
## 2023-08-01 CORE 9.0.3
* Installation
* updated various dependencies
* Documentation
* improved GUI docs to include node interaction and note xhost usage
* \#780 - fixed gRPC examples
* \#787 - complete documentation revamp to leverage mkdocs material
* \#790 - fixed custom emane model example
* core-daemon
* update type hinting to avoid deprecated imports
* updated commands ran within docker based nodes to have proper environment variables
* fixed issue improperly setting session options over gRPC
* \#668 - add fedora sbin path to frr service
* \#774 - fixed pcap configservice
* \#805 - fixed radvd configservice template error
* core-gui
* update type hinting to avoid deprecated imports
* fixed issue allowing duplicate named hook scripts
* fixed issue joining sessions with RJ45 nodes
* utility scripts
* fixed issue in core-cleanup for removing devices
## 2023-03-02 CORE 9.0.2
* Installation
* updated python dependencies, including invoke to resolve python 3.10+ issues
* improved example dockerfiles to use less space for built images
* Documentation
* updated emane install instructions
* added Docker related issues to install instructions
* core-daemon
* fixed issue using invalid device name in sysctl commands
* updated PTP nodes to properly disable mac learning for their linux bridge
* fixed issue for LXC nodes to properly use a configured image name and write it to XML
* \#742 - fixed issue with bad wlan node id being used
* \#744 - fixed issue not properly setting broadcast address
* core-gui
* fixed sample1.xml to remove SSH service
* fixed emane demo examples
* fixed issue displaying emane configs generally configured for a node
## 2022-11-28 CORE 9.0.1
* Installation
* updated protobuf and grpcio-tools versions in pyproject.toml to account for bad version mix
## 2022-11-18 CORE 9.0.0
* Breaking Changes
* removed session nodes file
* removed session state file
* emane now runs in one process per nem with unique control ports
* grpc client has been refactored and updated
* removed tcl/legacy gui, imn file support and the tlv api
* link configuration is now different, but consistent, for wired links
* Installation
* added packaging for single file distribution
* python3.9 is now the minimum required version
* updated Dockerfile examples
* updated various python dependencies
* virtual environment is now installed to /opt/core/venv
* Documentation
* updated emane invoke task examples
* revamped install documentation
* added wireless node notes
* core-gui
* updated config services to display rendered templated and allow editing
* fixed node icon issue when updating preferences
* \#89 - throughput widget now works for hubs/switches
* \#691 - fixed custom nodes to properly use config services
* gRPC API
* add linked call to support linking and unlinking interfaces without destroying them
* fixed issue during start session clearing out session options
* added call to get rendered config service files
* removed get_node_links from links from client
* nem id and nem port have been added to GetNode and AddLink calls
* core-daemon
* wired links always create two veth pairs joined by a bridge
* node interfaces are now configured within the container to apply to outgoing traffic
* session.add_node now uses NodeOptions, allowing for node specific options
* fixed issue with xml reading node canvas values
* removed Session.add_node_file
* fixed get requirements logic
* fixed docker/lxd node support terminal commands on remote servers
* improved docker node command execution time using nsenter
* new wireless node type added to support dynamic loss based on distance
* \#513 - add and deleting distributed links during runtime is now supported
* \#703 - fixed issue not starting emane event listening service
## 2022-03-21 CORE 8.2.0
* core-gui
* improved failed starts to trigger runtime to allow node investigation
* core-daemon
* improved default service loading to use a full import path
* updated session instantiation to always set to a runtime state
* core-cli
* \#672 - fixed xml loading
* \#578 - restored json flag and added geo output to session overview
* Documentation
* updated emane example and documentation
* improved table markdown
## 2022-02-18 CORE 8.1.0
* Installation
* updated dependency versions to account for known vulnerabilities
* GUI
* fixed issue drawing asymmetric link configurations when joining a session
* daemon
* fixed issue getting templates and creating files for config services
* added by directional support for network to network links
* \#647 - fixed issue when creating RJ45 nodes
* \#646 - fixed issue when creating files for Docker nodes
* \#645 - improved wlan change updates to account for all updates with no delay
* services
* fixed file generation for OSPFv2 config service
## 2022-01-12 CORE 8.0.0
*Breaking Changes

View file

@ -1,16 +1,23 @@
# syntax=docker/dockerfile:1
FROM ubuntu:20.04
LABEL Description="CORE Docker Image"
FROM ubuntu:22.04
LABEL Description="CORE Docker Ubuntu Image"
# define variables
ARG DEBIAN_FRONTEND=noninteractive
ARG PREFIX=/usr/local
ARG BRANCH=master
ARG CORE_TARBALL=core.tar.gz
ARG OSPF_TARBALL=ospf.tar.gz
ARG PROTOC_VERSION=3.19.6
ARG VENV_PATH=/opt/core/venv
ENV DEBIAN_FRONTEND=noninteractive
ENV PATH="$PATH:${VENV_PATH}/bin"
WORKDIR /opt
# install system dependencies
RUN apt-get update && \
RUN apt-get update -y && \
apt-get install -y software-properties-common
RUN add-apt-repository "deb http://archive.ubuntu.com/ubuntu jammy universe"
RUN apt-get update -y && \
apt-get install -y --no-install-recommends \
automake \
bash \
@ -25,76 +32,95 @@ RUN apt-get update && \
libev-dev \
libreadline-dev \
libtool \
libtk-img \
make \
nftables \
python3 \
python3-pip \
python3-tk \
pkg-config \
systemctl \
tk \
wget \
xauth \
xterm \
&& apt-get clean
# install python dependencies
RUN python3 -m pip install \
grpcio==1.27.2 \
grpcio-tools==1.27.2 \
poetry==1.1.7
# retrieve, build, and install core
RUN wget -q -O ${CORE_TARBALL} https://api.github.com/repos/coreemu/core/tarball/${BRANCH} && \
tar xf ${CORE_TARBALL} && \
cd coreemu-core* && \
./bootstrap.sh && \
./configure && \
make -j $(nproc) && \
wireshark \
vim \
build-essential \
nano \
firefox \
net-tools \
rsync \
openssh-server \
openssh-client \
vsftpd \
atftpd \
atftp \
mini-httpd \
lynx \
tcpdump \
iperf \
iperf3 \
tshark \
openssh-sftp-server \
bind9 \
bind9-utils \
openvpn \
isc-dhcp-server \
isc-dhcp-client \
whois \
ipcalc \
socat \
hping3 \
libgtk-3-0 \
librest-0.7-0 \
libgtk-3-common \
dconf-gsettings-backend \
libsoup-gnome2.4-1 \
libsoup2.4-1 \
dconf-service \
x11-xserver-utils \
ftp \
git \
sudo \
wget \
tzdata \
libpcap-dev \
libpcre3-dev \
libprotobuf-dev \
libxml2-dev \
protobuf-compiler \
unzip \
uuid-dev \
iproute2 \
vlc \
iputils-ping && \
apt-get autoremove -y
# install core
RUN git clone https://github.com/coreemu/core && \
cd core && \
git checkout ${BRANCH} && \
./setup.sh && \
PATH=/root/.local/bin:$PATH inv install -v -p ${PREFIX} && \
cd /opt && \
rm -rf ospf-mdr
# install emane
RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-x86_64.zip && \
mkdir protoc && \
unzip protoc-${PROTOC_VERSION}-linux-x86_64.zip -d protoc && \
git clone https://github.com/adjacentlink/emane.git && \
cd emane && \
./autogen.sh && \
./configure --prefix=/usr && \
make -j$(nproc) && \
make install && \
cd daemon && \
python3 -m poetry build -f wheel && \
python3 -m pip install dist/* && \
cp scripts/* ${PREFIX}/bin && \
mkdir /etc/core && \
cp -n data/core.conf /etc/core && \
cp -n data/logging.conf /etc/core && \
mkdir -p ${PREFIX}/share/core && \
cp -r examples ${PREFIX}/share/core && \
echo '\
[Unit]\n\
Description=Common Open Research Emulator Service\n\
After=network.target\n\
\n\
[Service]\n\
Type=simple\n\
ExecStart=/usr/local/bin/core-daemon\n\
TasksMax=infinity\n\
\n\
[Install]\n\
WantedBy=multi-user.target\
' > /lib/systemd/system/core-daemon.service && \
cd ../.. && \
rm ${CORE_TARBALL} && \
rm -rf coreemu-core*
# retrieve, build, and install ospf mdr
RUN wget -q -O ${OSPF_TARBALL} https://github.com/USNavalResearchLaboratory/ospf-mdr/tarball/master && \
tar xf ${OSPF_TARBALL} && \
cd USNavalResearchLaboratory-ospf-mdr* && \
./bootstrap.sh && \
./configure --disable-doc --enable-user=root --enable-group=root \
--with-cflags=-ggdb --sysconfdir=/usr/local/etc/quagga --enable-vtysh \
--localstatedir=/var/run/quagga && \
make -j $(nproc) && \
make install && \
cd .. && \
rm ${OSPF_TARBALL} && \
rm -rf USNavalResearchLaboratory-ospf-mdr*
# retrieve and install emane packages
RUN wget -q https://adjacentlink.com/downloads/emane/emane-1.2.7-release-1.ubuntu-20_04.amd64.tar.gz && \
tar xf emane*.tar.gz && \
cd emane-1.2.7-release-1/debs/ubuntu-20_04/amd64 && \
apt-get install -y ./emane*.deb ./python3-emane_*.deb && \
cd ../../../.. && \
rm emane-1.2.7-release-1.ubuntu-20_04.amd64.tar.gz && \
rm -rf emane-1.2.7-release-1
CMD ["systemctl", "start", "core-daemon"]
cd src/python && \
make clean && \
PATH=/opt/protoc/bin:$PATH make && \
${VENV_PATH}/bin/python -m pip install . && \
cd /opt && \
rm -rf protoc && \
rm -rf emane && \
rm -f protoc-${PROTOC_VERSION}-linux-x86_64.zip
WORKDIR /root
CMD /opt/core/venv/bin/core-daemon

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,12 +15,13 @@ 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
# extra files to include with distribution tarball
EXTRA_DIST = bootstrap.sh \
package \
LICENSE \
README.md \
ASSIGNMENT_OF_COPYRIGHT.pdf \
@ -51,7 +48,7 @@ fpm -s dir -t deb -n core-distributed \
--description "Common Open Research Emulator Distributed Package" \
--url https://github.com/coreemu/core \
--vendor "$(PACKAGE_VENDOR)" \
-p core_distributed_VERSION_ARCH.deb \
-p core-distributed_VERSION_ARCH.deb \
-v $(PACKAGE_VERSION) \
-d "ethtool" \
-d "procps" \
@ -62,7 +59,8 @@ fpm -s dir -t deb -n core-distributed \
-d "libev4" \
-d "openssh-server" \
-d "xterm" \
-C $(DESTDIR)
netns/vnoded=/usr/bin/ \
netns/vcmd=/usr/bin/
endef
define fpm-distributed-rpm =
@ -72,7 +70,7 @@ fpm -s dir -t rpm -n core-distributed \
--description "Common Open Research Emulator Distributed Package" \
--url https://github.com/coreemu/core \
--vendor "$(PACKAGE_VENDOR)" \
-p core_distributed_VERSION_ARCH.rpm \
-p core-distributed_VERSION_ARCH.rpm \
-v $(PACKAGE_VERSION) \
-d "ethtool" \
-d "procps-ng" \
@ -83,12 +81,75 @@ fpm -s dir -t rpm -n core-distributed \
-d "net-tools" \
-d "openssh-server" \
-d "xterm" \
-C $(DESTDIR)
netns/vnoded=/usr/bin/ \
netns/vcmd=/usr/bin/
endef
.PHONY: fpm-distributed
fpm-distributed: clean-local-fpm
$(MAKE) -C netns install DESTDIR=$(DESTDIR)
define fpm-rpm =
fpm -s dir -t rpm -n core \
-m "$(PACKAGE_MAINTAINERS)" \
--license "BSD" \
--description "core vnoded/vcmd and system dependencies" \
--url https://github.com/coreemu/core \
--vendor "$(PACKAGE_VENDOR)" \
-p core_VERSION_ARCH.rpm \
-v $(PACKAGE_VERSION) \
--rpm-init package/core-daemon \
--after-install package/after-install.sh \
--after-remove package/after-remove.sh \
-d "ethtool" \
-d "tk" \
-d "procps-ng" \
-d "bash >= 3.0" \
-d "ebtables" \
-d "iproute" \
-d "libev" \
-d "net-tools" \
-d "nftables" \
netns/vnoded=/usr/bin/ \
netns/vcmd=/usr/bin/ \
package/etc/core.conf=/etc/core/ \
package/etc/logging.conf=/etc/core/ \
package/examples=/opt/core/ \
daemon/dist/core-$(PACKAGE_VERSION)-py3-none-any.whl=/opt/core/
endef
define fpm-deb =
fpm -s dir -t deb -n core \
-m "$(PACKAGE_MAINTAINERS)" \
--license "BSD" \
--description "core vnoded/vcmd and system dependencies" \
--url https://github.com/coreemu/core \
--vendor "$(PACKAGE_VENDOR)" \
-p core_VERSION_ARCH.deb \
-v $(PACKAGE_VERSION) \
--deb-systemd package/core-daemon.service \
--deb-no-default-config-files \
--after-install package/after-install.sh \
--after-remove package/after-remove.sh \
-d "ethtool" \
-d "tk" \
-d "libtk-img" \
-d "procps" \
-d "libc6 >= 2.14" \
-d "bash >= 3.0" \
-d "ebtables" \
-d "iproute2" \
-d "libev4" \
-d "nftables" \
netns/vnoded=/usr/bin/ \
netns/vcmd=/usr/bin/ \
package/etc/core.conf=/etc/core/ \
package/etc/logging.conf=/etc/core/ \
package/examples=/opt/core/ \
daemon/dist/core-$(PACKAGE_VERSION)-py3-none-any.whl=/opt/core/
endef
.PHONY: fpm
fpm: clean-local-fpm
cd daemon && poetry build -f wheel
$(call fpm-deb)
$(call fpm-rpm)
$(call fpm-distributed-deb)
$(call fpm-distributed-rpm)
@ -115,7 +176,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 +183,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)

117
README.md
View file

@ -1,3 +1,13 @@
# Index
- CORE
- Docker Setup
- Precompiled container image
- Build container image from source
- Adding extra packages
- Useful commands
- License
# CORE
CORE: Common Open Research Emulator
@ -6,37 +16,92 @@ Copyright (c)2005-2022 the Boeing Company.
See the LICENSE file included in this distribution.
## About
# Docker Setup
The Common Open Research Emulator (CORE) is a tool for emulating
networks on one or more machines. You can connect these emulated
networks to live networks. CORE consists of a GUI for drawing
topologies of lightweight virtual machines, and Python modules for
scripting network emulation.
Here you have 2 choices
## Quick Start
## Precompiled container image
The following should get you up and running on Ubuntu 18+ and CentOS 7+
from a clean install, it will prompt you for sudo password. This would
install CORE into a python3 virtual environment and install
[OSPF MDR](https://github.com/USNavalResearchLaboratory/ospf-mdr) from source.
For more detailed installation see [here](https://coreemu.github.io/core/install.html).
```bash
# Start container
sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged --restart unless-stopped git.olympuslab.net/afonso/core-extra:latest
```
## Build container image from source
```bash
# Clone the repo
git clone https://gitea.olympuslab.net/afonso/core-extra.git
# cd into the directory
cd core-extra
# build the docker image
sudo docker build -t core-extra .
# start container
sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged --restart unless-stopped core-extra
```shell
git clone https://github.com/coreemu/core.git
cd core
./setup.sh
# Ubuntu
inv install
# CentOS
./install.sh -p /usr
```
## Documentation & Support
### Adding extra packages
We are leveraging GitHub hosted documentation and Discord for persistent
chat rooms. This allows for more dynamic conversations and the
capability to respond faster. Feel free to join us at the link below.
To add extra packages you must modify the Dockerfile and then compile the docker image.
If you install it after starting the container it will, by docker nature, be reverted on the next boot of the container.
* [Documentation](https://coreemu.github.io/core/)
* [Discord Channel](https://discord.gg/AKd7kmP)
# Useful commands
I have the following functions on my fish shell
to help me better use core
THIS ONLY WORKS ON FISH, MODIFY FOR BASH OR ZSH
```fish
# RUN CORE GUI
function core
xhost +local:root
sudo docker exec -it core core-gui
end
# RUN BASH INSIDE THE CONTAINER
function core-bash
sudo docker exec -it core /bin/bash
end
# LAUNCH NODE BASH ON THE HOST MACHINE
function launch-term --argument nodename
sudo docker exec -it core xterm -bg black -fg white -fa 'DejaVu Sans Mono' -fs 16 -e vcmd -c /tmp/pycore.1/$nodename -- /bin/bash
end
#TO RUN ANY OTHER COMMAND
sudo docker exec -it core COMAND_GOES_HERE
```
## LICENSE
Copyright (c) 2005-2018, the Boeing Company.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.

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

@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
# this defines the CORE version number, must be static for AC_INIT
AC_INIT(core, 8.0.0)
AC_INIT(core, 9.0.3)
# autoconf and automake initialization
AC_CONFIG_SRCDIR([netns/version.h.in])
@ -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,28 +83,7 @@ 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)
AM_PATH_PYTHON(3.9)
AS_IF([$PYTHON -m grpc_tools.protoc -h &> /dev/null], [], [AC_MSG_ERROR([please install python grpcio-tools])])
AC_CHECK_PROG(sysctl_path, sysctl, $as_dir, no, $SEARCHPATH)
@ -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.
#
@ -25,10 +21,7 @@ DISTCLEANFILES = Makefile.in
# files to include with distribution tarball
EXTRA_DIST = core \
data \
doc/conf.py.in \
examples \
scripts \
tests \
setup.cfg \
poetry.lock \

View file

@ -4,19 +4,28 @@ gRpc client for interfacing with CORE.
import logging
import threading
from collections.abc import Callable, Generator, Iterable
from contextlib import contextmanager
from pathlib import Path
from queue import Queue
from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple
from typing import Any, Optional
import grpc
from core.api.grpc import core_pb2, core_pb2_grpc, emane_pb2, wrappers
from core.api.grpc.configservices_pb2 import (
GetConfigServiceDefaultsRequest,
GetConfigServiceRenderedRequest,
GetNodeConfigServiceRequest,
)
from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest
from core.api.grpc.core_pb2 import (
ExecuteScriptRequest,
GetConfigRequest,
GetWirelessConfigRequest,
LinkedRequest,
WirelessConfigRequest,
WirelessLinkedRequest,
)
from core.api.grpc.emane_pb2 import (
EmaneLinkRequest,
GetEmaneEventChannelRequest,
@ -43,17 +52,19 @@ from core.api.grpc.wlan_pb2 import (
WlanConfig,
WlanLinkRequest,
)
from core.api.grpc.wrappers import LinkOptions
from core.emulator.data import IpPrefixes
from core.errors import CoreError
from core.utils import SetQueue
logger = logging.getLogger(__name__)
class MoveNodesStreamer:
def __init__(self, session_id: int = None, source: str = None) -> None:
self.session_id = session_id
self.source = source
self.queue: Queue = Queue()
def __init__(self, session_id: int, source: str = None) -> None:
self.session_id: int = session_id
self.source: Optional[str] = source
self.queue: SetQueue = SetQueue()
def send_position(self, node_id: int, x: float, y: float) -> None:
position = wrappers.Position(x=x, y=y)
@ -150,12 +161,12 @@ def throughput_listener(
stream: Any, handler: Callable[[wrappers.ThroughputsEvent], None]
) -> None:
"""
Listen for throughput events and provide them to the handler.
Listen for throughput events and provide them to the handler.
:param stream: grpc stream that will provide events
:param handler: function that handles an event
:return: nothing
"""
:param stream: grpc stream that will provide events
:param handler: function that handles an event
:return: nothing
"""
try:
for event_proto in stream:
event = wrappers.ThroughputsEvent.from_proto(event_proto)
@ -225,7 +236,7 @@ class CoreGrpcClient:
def start_session(
self, session: wrappers.Session, definition: bool = False
) -> Tuple[bool, List[str]]:
) -> tuple[bool, list[str]]:
"""
Start a session.
@ -275,7 +286,7 @@ class CoreGrpcClient:
response = self.stub.DeleteSession(request)
return response.result
def get_sessions(self) -> List[wrappers.SessionSummary]:
def get_sessions(self) -> list[wrappers.SessionSummary]:
"""
Retrieves all currently known sessions.
@ -344,7 +355,7 @@ class CoreGrpcClient:
self,
session_id: int,
handler: Callable[[wrappers.Event], None],
events: List[wrappers.EventType] = None,
events: list[wrappers.EventType] = None,
) -> grpc.Future:
"""
Listen for session events.
@ -418,7 +429,7 @@ class CoreGrpcClient:
def get_node(
self, session_id: int, node_id: int
) -> Tuple[wrappers.Node, List[wrappers.Interface], List[wrappers.Link]]:
) -> tuple[wrappers.Node, list[wrappers.Interface], list[wrappers.Link]]:
"""
Get node details.
@ -526,7 +537,7 @@ class CoreGrpcClient:
command: str,
wait: bool = True,
shell: bool = False,
) -> Tuple[int, str]:
) -> tuple[int, str]:
"""
Send command to a node and get the output.
@ -563,26 +574,9 @@ class CoreGrpcClient:
response = self.stub.GetNodeTerminal(request)
return response.terminal
def get_node_links(self, session_id: int, node_id: int) -> List[wrappers.Link]:
"""
Get current links for a node.
:param session_id: session id
:param node_id: node id
:return: list of links
:raises grpc.RpcError: when session or node doesn't exist
"""
request = core_pb2.GetNodeLinksRequest(session_id=session_id, node_id=node_id)
response = self.stub.GetNodeLinks(request)
links = []
for link_proto in response.links:
link = wrappers.Link.from_proto(link_proto)
links.append(link)
return links
def add_link(
self, session_id: int, link: wrappers.Link, source: str = None
) -> Tuple[bool, wrappers.Interface, wrappers.Interface]:
) -> tuple[bool, wrappers.Interface, wrappers.Interface]:
"""
Add a link between nodes.
@ -653,7 +647,7 @@ class CoreGrpcClient:
def get_mobility_config(
self, session_id: int, node_id: int
) -> Dict[str, wrappers.ConfigOption]:
) -> dict[str, wrappers.ConfigOption]:
"""
Get mobility configuration for a node.
@ -667,7 +661,7 @@ class CoreGrpcClient:
return wrappers.ConfigOption.from_dict(response.config)
def set_mobility_config(
self, session_id: int, node_id: int, config: Dict[str, str]
self, session_id: int, node_id: int, config: dict[str, str]
) -> bool:
"""
Set mobility configuration for a node.
@ -713,7 +707,7 @@ class CoreGrpcClient:
response = self.stub.GetConfig(request)
return wrappers.CoreConfig.from_proto(response)
def get_service_defaults(self, session_id: int) -> List[wrappers.ServiceDefault]:
def get_service_defaults(self, session_id: int) -> list[wrappers.ServiceDefault]:
"""
Get default services for different default node models.
@ -730,7 +724,7 @@ class CoreGrpcClient:
return defaults
def set_service_defaults(
self, session_id: int, service_defaults: Dict[str, List[str]]
self, session_id: int, service_defaults: dict[str, list[str]]
) -> bool:
"""
Set default services for node models.
@ -741,9 +735,9 @@ class CoreGrpcClient:
:raises grpc.RpcError: when session doesn't exist
"""
defaults = []
for node_type in service_defaults:
services = service_defaults[node_type]
default = ServiceDefaults(node_type=node_type, services=services)
for model in service_defaults:
services = service_defaults[model]
default = ServiceDefaults(model=model, services=services)
defaults.append(default)
request = SetServiceDefaultsRequest(session_id=session_id, defaults=defaults)
response = self.stub.SetServiceDefaults(request)
@ -836,7 +830,7 @@ class CoreGrpcClient:
def get_wlan_config(
self, session_id: int, node_id: int
) -> Dict[str, wrappers.ConfigOption]:
) -> dict[str, wrappers.ConfigOption]:
"""
Get wlan configuration for a node.
@ -850,7 +844,7 @@ class CoreGrpcClient:
return wrappers.ConfigOption.from_dict(response.config)
def set_wlan_config(
self, session_id: int, node_id: int, config: Dict[str, str]
self, session_id: int, node_id: int, config: dict[str, str]
) -> bool:
"""
Set wlan configuration for a node.
@ -868,7 +862,7 @@ class CoreGrpcClient:
def get_emane_model_config(
self, session_id: int, node_id: int, model: str, iface_id: int = -1
) -> Dict[str, wrappers.ConfigOption]:
) -> dict[str, wrappers.ConfigOption]:
"""
Get emane model configuration for a node or a node's interface.
@ -916,7 +910,7 @@ class CoreGrpcClient:
with open(file_path, "w") as xml_file:
xml_file.write(response.data)
def open_xml(self, file_path: Path, start: bool = False) -> Tuple[bool, int]:
def open_xml(self, file_path: Path, start: bool = False) -> tuple[bool, int]:
"""
Load a local scenario XML file to open as a new session.
@ -947,7 +941,7 @@ class CoreGrpcClient:
response = self.stub.EmaneLink(request)
return response.result
def get_ifaces(self) -> List[str]:
def get_ifaces(self) -> list[str]:
"""
Retrieves a list of interfaces available on the host machine that are not
a part of a CORE session.
@ -958,20 +952,26 @@ class CoreGrpcClient:
response = self.stub.GetInterfaces(request)
return list(response.ifaces)
def get_config_service_defaults(self, name: str) -> wrappers.ConfigServiceDefaults:
def get_config_service_defaults(
self, session_id: int, node_id: int, name: str
) -> wrappers.ConfigServiceDefaults:
"""
Retrieves config service default values.
:param session_id: session id to get node from
:param node_id: node id to get service data from
:param name: name of service to get defaults for
:return: config service defaults
"""
request = GetConfigServiceDefaultsRequest(name=name)
request = GetConfigServiceDefaultsRequest(
name=name, session_id=session_id, node_id=node_id
)
response = self.stub.GetConfigServiceDefaults(request)
return wrappers.ConfigServiceDefaults.from_proto(response)
def get_node_config_service(
self, session_id: int, node_id: int, name: str
) -> Dict[str, str]:
) -> dict[str, str]:
"""
Retrieves information for a specific config service on a node.
@ -987,6 +987,23 @@ class CoreGrpcClient:
response = self.stub.GetNodeConfigService(request)
return dict(response.config)
def get_config_service_rendered(
self, session_id: int, node_id: int, name: str
) -> dict[str, str]:
"""
Retrieve the rendered config service files for a node.
:param session_id: id of session
:param node_id: id of node
:param name: name of service
:return: dict mapping names of files to rendered data
"""
request = GetConfigServiceRenderedRequest(
session_id=session_id, node_id=node_id, name=name
)
response = self.stub.GetConfigServiceRendered(request)
return dict(response.rendered)
def get_emane_event_channel(
self, session_id: int, nem_id: int
) -> wrappers.EmaneEventChannel:
@ -1049,6 +1066,81 @@ class CoreGrpcClient:
"""
self.stub.EmanePathlosses(streamer.iter())
def linked(
self,
session_id: int,
node1_id: int,
node2_id: int,
iface1_id: int,
iface2_id: int,
linked: bool,
) -> None:
"""
Link or unlink an existing core wired link.
:param session_id: session containing the link
:param node1_id: first node in link
:param node2_id: second node in link
:param iface1_id: node1 interface
:param iface2_id: node2 interface
:param linked: True to connect link, False to disconnect
:return: nothing
"""
request = LinkedRequest(
session_id=session_id,
node1_id=node1_id,
node2_id=node2_id,
iface1_id=iface1_id,
iface2_id=iface2_id,
linked=linked,
)
self.stub.Linked(request)
def wireless_linked(
self,
session_id: int,
wireless_id: int,
node1_id: int,
node2_id: int,
linked: bool,
) -> None:
request = WirelessLinkedRequest(
session_id=session_id,
wireless_id=wireless_id,
node1_id=node1_id,
node2_id=node2_id,
linked=linked,
)
self.stub.WirelessLinked(request)
def wireless_config(
self,
session_id: int,
wireless_id: int,
node1_id: int,
node2_id: int,
options1: LinkOptions,
options2: LinkOptions = None,
) -> None:
if options2 is None:
options2 = options1
request = WirelessConfigRequest(
session_id=session_id,
wireless_id=wireless_id,
node1_id=node1_id,
node2_id=node2_id,
options1=options1.to_proto(),
options2=options2.to_proto(),
)
self.stub.WirelessConfig(request)
def get_wireless_config(
self, session_id: int, node_id: int
) -> dict[str, wrappers.ConfigOption]:
request = GetWirelessConfigRequest(session_id=session_id, node_id=node_id)
response = self.stub.GetWirelessConfig(request)
return wrappers.ConfigOption.from_dict(response.config)
def connect(self) -> None:
"""
Open connection to server, must be closed manually.
@ -1071,7 +1163,7 @@ class CoreGrpcClient:
self.channel = None
@contextmanager
def context_connect(self) -> Generator:
def context_connect(self) -> Generator[None, None, None]:
"""
Makes a context manager based connection to the server, will close after
context ends.

View file

@ -1,9 +1,10 @@
import logging
from collections.abc import Iterable
from queue import Empty, Queue
from typing import Iterable, Optional
from typing import Optional
from core.api.grpc import core_pb2
from core.api.grpc.grpcutils import convert_link
from core.api.grpc import core_pb2, grpcutils
from core.api.grpc.grpcutils import convert_link_data
from core.emulator.data import (
ConfigData,
EventData,
@ -17,28 +18,18 @@ from core.emulator.session import Session
logger = logging.getLogger(__name__)
def handle_node_event(node_data: NodeData) -> core_pb2.Event:
def handle_node_event(session: Session, node_data: NodeData) -> core_pb2.Event:
"""
Handle node event when there is a node event
:param session: session node is from
:param node_data: node data
:return: node event that contains node id, name, model, position, and services
"""
node = node_data.node
x, y, _ = node.position.get()
position = core_pb2.Position(x=x, y=y)
lon, lat, alt = node.position.get_geo()
geo = core_pb2.Geo(lon=lon, lat=lat, alt=alt)
services = [x.name for x in node.services]
node_proto = core_pb2.Node(
id=node.id,
name=node.name,
model=node.type,
icon=node.icon,
position=position,
geo=geo,
services=services,
)
emane_configs = grpcutils.get_emane_model_configs_dict(session)
node_emane_configs = emane_configs.get(node.id, [])
node_proto = grpcutils.get_node_proto(session, node, node_emane_configs)
message_type = node_data.message_type.value
node_event = core_pb2.NodeEvent(message_type=message_type, node=node_proto)
return core_pb2.Event(node_event=node_event, source=node_data.source)
@ -51,7 +42,7 @@ def handle_link_event(link_data: LinkData) -> core_pb2.Event:
:param link_data: link data
:return: link event that has message type and link information
"""
link = convert_link(link_data)
link = convert_link_data(link_data)
message_type = link_data.message_type.value
link_event = core_pb2.LinkEvent(message_type=message_type, link=link)
return core_pb2.Event(link_event=link_event, source=link_data.source)
@ -189,7 +180,7 @@ class EventStreamer:
try:
data = self.queue.get(timeout=1)
if isinstance(data, NodeData):
event = handle_node_event(data)
event = handle_node_event(self.session, data)
elif isinstance(data, LinkData):
event = handle_link_event(data)
elif isinstance(data, EventData):

View file

@ -1,7 +1,7 @@
import logging
import time
from pathlib import Path
from typing import Any, Dict, List, Tuple, Type, Union
from typing import Any, Optional, Union
import grpc
from grpc import ServicerContext
@ -17,17 +17,27 @@ from core.api.grpc.services_pb2 import (
ServiceDefaults,
)
from core.config import ConfigurableOptions
from core.emane.nodes import EmaneNet
from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions
from core.emane.nodes import EmaneNet, EmaneOptions
from core.emulator.data import InterfaceData, LinkData, LinkOptions
from core.emulator.enumerations import LinkTypes, NodeTypes
from core.emulator.links import CoreLink
from core.emulator.session import Session
from core.errors import CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
from core.nodes.docker import DockerNode
from core.nodes.base import (
CoreNode,
CoreNodeBase,
CoreNodeOptions,
NodeBase,
NodeOptions,
Position,
)
from core.nodes.docker import DockerNode, DockerOptions
from core.nodes.interface import CoreInterface
from core.nodes.lxd import LxcNode
from core.nodes.network import CtrlNet, PtpNet, WlanNode
from core.nodes.lxd import LxcNode, LxcOptions
from core.nodes.network import CoreNetwork, CtrlNet, PtpNet, WlanNode
from core.nodes.podman import PodmanNode, PodmanOptions
from core.nodes.wireless import WirelessNode
from core.services.coreservices import CoreService
logger = logging.getLogger(__name__)
@ -53,34 +63,33 @@ class CpuUsage:
return (total_diff - idle_diff) / total_diff
def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOptions]:
def add_node_data(
_class: type[NodeBase], node_proto: core_pb2.Node
) -> tuple[Position, NodeOptions]:
"""
Convert node protobuf message to data for creating a node.
:param _class: node class to create options from
:param node_proto: node proto message
:return: node type, id, and options
"""
_id = node_proto.id
_type = NodeTypes(node_proto.type)
options = NodeOptions(
name=node_proto.name,
model=node_proto.model,
icon=node_proto.icon,
image=node_proto.image,
services=node_proto.services,
config_services=node_proto.config_services,
canvas=node_proto.canvas,
)
if node_proto.emane:
options.emane = node_proto.emane
if node_proto.server:
options.server = node_proto.server
position = node_proto.position
options.set_position(position.x, position.y)
options = _class.create_options()
options.icon = node_proto.icon
options.canvas = node_proto.canvas
if isinstance(options, CoreNodeOptions):
options.model = node_proto.model
options.services = node_proto.services
options.config_services = node_proto.config_services
if isinstance(options, EmaneOptions):
options.emane_model = node_proto.emane
if isinstance(options, (DockerOptions, LxcOptions, PodmanOptions)):
options.image = node_proto.image
position = Position()
position.set(node_proto.position.x, node_proto.position.y)
if node_proto.HasField("geo"):
geo = node_proto.geo
options.set_location(geo.lat, geo.lon, geo.alt)
return _type, _id, options
position.set_geo(geo.lon, geo.lat, geo.alt)
return position, options
def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData:
@ -109,8 +118,8 @@ def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData:
def add_link_data(
link_proto: core_pb2.Link
) -> Tuple[InterfaceData, InterfaceData, LinkOptions, LinkTypes]:
link_proto: core_pb2.Link,
) -> tuple[InterfaceData, InterfaceData, LinkOptions]:
"""
Convert link proto to link interfaces and options data.
@ -119,7 +128,6 @@ def add_link_data(
"""
iface1_data = link_iface(link_proto.iface1)
iface2_data = link_iface(link_proto.iface2)
link_type = LinkTypes(link_proto.type)
options = LinkOptions()
options_proto = link_proto.options
if options_proto:
@ -134,12 +142,12 @@ def add_link_data(
options.buffer = options_proto.buffer
options.unidirectional = options_proto.unidirectional
options.key = options_proto.key
return iface1_data, iface2_data, options, link_type
return iface1_data, iface2_data, options
def create_nodes(
session: Session, node_protos: List[core_pb2.Node]
) -> Tuple[List[NodeBase], List[Exception]]:
session: Session, node_protos: list[core_pb2.Node]
) -> tuple[list[NodeBase], list[Exception]]:
"""
Create nodes using a thread pool and wait for completion.
@ -149,9 +157,17 @@ def create_nodes(
"""
funcs = []
for node_proto in node_protos:
_type, _id, options = add_node_data(node_proto)
_type = NodeTypes(node_proto.type)
_class = session.get_node_class(_type)
args = (_class, _id, options)
position, options = add_node_data(_class, node_proto)
args = (
_class,
node_proto.id or None,
node_proto.name or None,
node_proto.server or None,
position,
options,
)
funcs.append((session.add_node, args, {}))
start = time.monotonic()
results, exceptions = utils.threadpool(funcs)
@ -161,8 +177,8 @@ def create_nodes(
def create_links(
session: Session, link_protos: List[core_pb2.Link]
) -> Tuple[List[NodeBase], List[Exception]]:
session: Session, link_protos: list[core_pb2.Link]
) -> tuple[list[NodeBase], list[Exception]]:
"""
Create links using a thread pool and wait for completion.
@ -174,8 +190,8 @@ def create_links(
for link_proto in link_protos:
node1_id = link_proto.node1_id
node2_id = link_proto.node2_id
iface1, iface2, options, link_type = add_link_data(link_proto)
args = (node1_id, node2_id, iface1, iface2, options, link_type)
iface1, iface2, options = add_link_data(link_proto)
args = (node1_id, node2_id, iface1, iface2, options)
funcs.append((session.add_link, args, {}))
start = time.monotonic()
results, exceptions = utils.threadpool(funcs)
@ -185,8 +201,8 @@ def create_links(
def edit_links(
session: Session, link_protos: List[core_pb2.Link]
) -> Tuple[List[None], List[Exception]]:
session: Session, link_protos: list[core_pb2.Link]
) -> tuple[list[None], list[Exception]]:
"""
Edit links using a thread pool and wait for completion.
@ -198,8 +214,8 @@ def edit_links(
for link_proto in link_protos:
node1_id = link_proto.node1_id
node2_id = link_proto.node2_id
iface1, iface2, options, link_type = add_link_data(link_proto)
args = (node1_id, node2_id, iface1.id, iface2.id, options, link_type)
iface1, iface2, options = add_link_data(link_proto)
args = (node1_id, node2_id, iface1.id, iface2.id, options)
funcs.append((session.update_link, args, {}))
start = time.monotonic()
results, exceptions = utils.threadpool(funcs)
@ -220,10 +236,26 @@ def convert_value(value: Any) -> str:
return value
def convert_session_options(session: Session) -> dict[str, common_pb2.ConfigOption]:
config_options = {}
for option in session.options.options:
value = session.options.get(option.id)
config_option = common_pb2.ConfigOption(
label=option.label,
name=option.id,
value=value,
type=option.type.value,
select=option.options,
group="Options",
)
config_options[option.id] = config_option
return config_options
def get_config_options(
config: Dict[str, str],
configurable_options: Union[ConfigurableOptions, Type[ConfigurableOptions]],
) -> Dict[str, common_pb2.ConfigOption]:
config: dict[str, str],
configurable_options: Union[ConfigurableOptions, type[ConfigurableOptions]],
) -> dict[str, common_pb2.ConfigOption]:
"""
Retrieve configuration options in a form that is used by the grpc server.
@ -252,7 +284,7 @@ def get_config_options(
def get_node_proto(
session: Session, node: NodeBase, emane_configs: List[NodeEmaneConfig]
session: Session, node: NodeBase, emane_configs: list[NodeEmaneConfig]
) -> core_pb2.Node:
"""
Convert CORE node to protobuf representation.
@ -270,7 +302,6 @@ def get_node_proto(
lat=node.position.lat, lon=node.position.lon, alt=node.position.alt
)
services = [x.name for x in node.services]
model = node.type
node_dir = None
config_services = []
if isinstance(node, CoreNodeBase):
@ -281,9 +312,9 @@ def get_node_proto(
channel = str(node.ctrlchnlname)
emane_model = None
if isinstance(node, EmaneNet):
emane_model = node.model.name
emane_model = node.wireless_model.name
image = None
if isinstance(node, (DockerNode, LxcNode)):
if isinstance(node, (DockerNode, LxcNode, PodmanNode)):
image = node.image
# check for wlan config
wlan_config = session.mobility.get_configs(
@ -291,6 +322,21 @@ def get_node_proto(
)
if wlan_config:
wlan_config = get_config_options(wlan_config, BasicRangeModel)
# check for wireless config
wireless_config = None
if isinstance(node, WirelessNode):
configs = node.get_config()
wireless_config = {}
for config in configs.values():
config_option = common_pb2.ConfigOption(
label=config.label,
name=config.id,
value=config.default,
type=config.type.value,
select=config.options,
group=config.group,
)
wireless_config[config.id] = config_option
# check for mobility config
mobility_config = session.mobility.get_configs(
node.id, config_type=Ns2ScriptedMobility.name
@ -325,7 +371,7 @@ def get_node_proto(
id=node.id,
name=node.name,
emane=emane_model,
model=model,
model=node.model,
type=node_type.value,
position=position,
geo=geo,
@ -337,6 +383,7 @@ def get_node_proto(
channel=channel,
canvas=node.canvas,
wlan_config=wlan_config,
wireless_config=wireless_config,
mobility_config=mobility_config,
service_configs=service_configs,
config_service_configs=config_service_configs,
@ -344,61 +391,84 @@ def get_node_proto(
)
def get_links(node: NodeBase):
def get_links(session: Session, node: NodeBase) -> list[core_pb2.Link]:
"""
Retrieve a list of links for grpc to use.
:param session: session to get links for node
:param node: node to get links from
:return: protobuf links
"""
link_protos = []
for core_link in session.link_manager.node_links(node):
link_protos.extend(convert_core_link(core_link))
if isinstance(node, (WlanNode, EmaneNet)):
for link_data in node.links():
link_protos.append(convert_link_data(link_data))
return link_protos
def convert_iface(iface: CoreInterface) -> core_pb2.Interface:
"""
Convert interface to protobuf.
:param iface: interface to convert
:return: protobuf interface
"""
if isinstance(iface.node, CoreNetwork):
return core_pb2.Interface(id=iface.id)
else:
ip4 = iface.get_ip4()
ip4_mask = ip4.prefixlen if ip4 else None
ip4 = str(ip4.ip) if ip4 else None
ip6 = iface.get_ip6()
ip6_mask = ip6.prefixlen if ip6 else None
ip6 = str(ip6.ip) if ip6 else None
mac = str(iface.mac) if iface.mac else None
return core_pb2.Interface(
id=iface.id,
name=iface.name,
mac=mac,
ip4=ip4,
ip4_mask=ip4_mask,
ip6=ip6,
ip6_mask=ip6_mask,
)
def convert_core_link(core_link: CoreLink) -> list[core_pb2.Link]:
"""
Convert core link to protobuf data.
:param core_link: core link to convert
:return: protobuf link data
"""
links = []
for link in node.links():
link_proto = convert_link(link)
links.append(link_proto)
node1, iface1 = core_link.node1, core_link.iface1
node2, iface2 = core_link.node2, core_link.iface2
unidirectional = core_link.is_unidirectional()
link = convert_link(node1, iface1, node2, iface2, iface1.options, unidirectional)
links.append(link)
if unidirectional:
link = convert_link(
node2, iface2, node1, iface1, iface2.options, unidirectional
)
links.append(link)
return links
def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface:
return core_pb2.Interface(
id=iface_data.id,
name=iface_data.name,
mac=iface_data.mac,
ip4=iface_data.ip4,
ip4_mask=iface_data.ip4_mask,
ip6=iface_data.ip6,
ip6_mask=iface_data.ip6_mask,
)
def convert_link_options(options_data: LinkOptions) -> core_pb2.LinkOptions:
return core_pb2.LinkOptions(
jitter=options_data.jitter,
key=options_data.key,
mburst=options_data.mburst,
mer=options_data.mer,
loss=options_data.loss,
bandwidth=options_data.bandwidth,
burst=options_data.burst,
delay=options_data.delay,
dup=options_data.dup,
buffer=options_data.buffer,
unidirectional=options_data.unidirectional,
)
def convert_link(link_data: LinkData) -> core_pb2.Link:
def convert_link_data(link_data: LinkData) -> core_pb2.Link:
"""
Convert link_data into core protobuf link.
:param link_data: link to convert
:return: core protobuf Link
"""
iface1 = None
if link_data.iface1 is not None:
iface1 = convert_iface(link_data.iface1)
iface1 = convert_iface_data(link_data.iface1)
iface2 = None
if link_data.iface2 is not None:
iface2 = convert_iface(link_data.iface2)
iface2 = convert_iface_data(link_data.iface2)
options = convert_link_options(link_data.options)
return core_pb2.Link(
type=link_data.type.value,
@ -413,27 +483,134 @@ def convert_link(link_data: LinkData) -> core_pb2.Link:
)
def get_net_stats() -> Dict[str, Dict]:
def convert_iface_data(iface_data: InterfaceData) -> core_pb2.Interface:
"""
Retrieve status about the current interfaces in the system
Convert interface data to protobuf.
:return: send and receive status of the interfaces in the system
:param iface_data: interface data to convert
:return: interface protobuf
"""
with open("/proc/net/dev", "r") as f:
data = f.readlines()[2:]
return core_pb2.Interface(
id=iface_data.id,
name=iface_data.name,
mac=iface_data.mac,
ip4=iface_data.ip4,
ip4_mask=iface_data.ip4_mask,
ip6=iface_data.ip6,
ip6_mask=iface_data.ip6_mask,
)
def convert_link_options(options: LinkOptions) -> core_pb2.LinkOptions:
"""
Convert link options to protobuf.
:param options: link options to convert
:return: link options protobuf
"""
return core_pb2.LinkOptions(
jitter=options.jitter,
key=options.key,
mburst=options.mburst,
mer=options.mer,
loss=options.loss,
bandwidth=options.bandwidth,
burst=options.burst,
delay=options.delay,
dup=options.dup,
buffer=options.buffer,
unidirectional=options.unidirectional,
)
def convert_options_proto(options: core_pb2.LinkOptions) -> LinkOptions:
return LinkOptions(
delay=options.delay,
bandwidth=options.bandwidth,
loss=options.loss,
dup=options.dup,
jitter=options.jitter,
mer=options.mer,
burst=options.burst,
mburst=options.mburst,
buffer=options.buffer,
unidirectional=options.unidirectional,
key=options.key,
)
def convert_link(
node1: NodeBase,
iface1: Optional[CoreInterface],
node2: NodeBase,
iface2: Optional[CoreInterface],
options: LinkOptions,
unidirectional: bool,
) -> core_pb2.Link:
"""
Convert link objects to link protobuf.
:param node1: first node in link
:param iface1: node1 interface
:param node2: second node in link
:param iface2: node2 interface
:param options: link options
:param unidirectional: if this link is considered unidirectional
:return: protobuf link
"""
if iface1 is not None:
iface1 = convert_iface(iface1)
if iface2 is not None:
iface2 = convert_iface(iface2)
is_node1_wireless = isinstance(node1, (WlanNode, EmaneNet))
is_node2_wireless = isinstance(node2, (WlanNode, EmaneNet))
if not (is_node1_wireless or is_node2_wireless):
options = convert_link_options(options)
options.unidirectional = unidirectional
else:
options = None
return core_pb2.Link(
type=LinkTypes.WIRED.value,
node1_id=node1.id,
node2_id=node2.id,
iface1=iface1,
iface2=iface2,
options=options,
network_id=None,
label=None,
color=None,
)
def parse_proc_net_dev(lines: list[str]) -> dict[str, dict[str, float]]:
"""
Parse lines of output from /proc/net/dev.
:param lines: lines of /proc/net/dev
:return: parsed device to tx/rx values
"""
stats = {}
for line in data:
for line in lines[2:]:
line = line.strip()
if not line:
continue
line = line.split()
line[0] = line[0].strip(":")
stats[line[0]] = {"rx": float(line[1]), "tx": float(line[9])}
return stats
def get_net_stats() -> dict[str, dict[str, float]]:
"""
Retrieve status about the current interfaces in the system
:return: send and receive status of the interfaces in the system
"""
with open("/proc/net/dev", "r") as f:
lines = f.readlines()[2:]
return parse_proc_net_dev(lines)
def session_location(session: Session, location: core_pb2.SessionLocation) -> None:
"""
Set session location based on location proto.
@ -490,39 +667,14 @@ def get_service_configuration(service: CoreService) -> NodeServiceData:
)
def iface_to_data(iface: CoreInterface) -> InterfaceData:
ip4 = iface.get_ip4()
ip4_addr = str(ip4.ip) if ip4 else None
ip4_mask = ip4.prefixlen if ip4 else None
ip6 = iface.get_ip6()
ip6_addr = str(ip6.ip) if ip6 else None
ip6_mask = ip6.prefixlen if ip6 else None
return InterfaceData(
id=iface.node_id,
name=iface.name,
mac=str(iface.mac),
ip4=ip4_addr,
ip4_mask=ip4_mask,
ip6=ip6_addr,
ip6_mask=ip6_mask,
)
def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface:
def iface_to_proto(session: Session, iface: CoreInterface) -> core_pb2.Interface:
"""
Convenience for converting a core interface to the protobuf representation.
:param node_id: id of node to convert interface for
:param session: session interface belongs to
:param iface: interface to convert
:return: interface proto
"""
if iface.node and iface.node.id == node_id:
_id = iface.node_id
else:
_id = iface.net_id
net_id = iface.net.id if iface.net else None
node_id = iface.node.id if iface.node else None
net2_id = iface.othernet.id if iface.othernet else None
ip4_net = iface.get_ip4()
ip4 = str(ip4_net.ip) if ip4_net else None
ip4_mask = ip4_net.prefixlen if ip4_net else None
@ -530,11 +682,13 @@ def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface:
ip6 = str(ip6_net.ip) if ip6_net else None
ip6_mask = ip6_net.prefixlen if ip6_net else None
mac = str(iface.mac) if iface.mac else None
nem_id = None
nem_port = None
if isinstance(iface.net, EmaneNet):
nem_id = session.emane.get_nem_id(iface)
nem_port = session.emane.get_nem_port(iface)
return core_pb2.Interface(
id=_id,
net_id=net_id,
net2_id=net2_id,
node_id=node_id,
id=iface.id,
name=iface.name,
mac=mac,
mtu=iface.mtu,
@ -543,6 +697,8 @@ def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface:
ip4_mask=ip4_mask,
ip6=ip6,
ip6_mask=ip6_mask,
nem_id=nem_id,
nem_port=nem_port,
)
@ -573,7 +729,13 @@ def get_nem_id(
return nem_id
def get_emane_model_configs_dict(session: Session) -> Dict[int, List[NodeEmaneConfig]]:
def get_emane_model_configs_dict(session: Session) -> dict[int, list[NodeEmaneConfig]]:
"""
Get emane model configuration protobuf data.
:param session: session to get emane model configuration for
:return: dict of emane model protobuf configurations
"""
configs = {}
for _id, model_configs in session.emane.node_configs.items():
for model_name in model_configs:
@ -590,7 +752,13 @@ def get_emane_model_configs_dict(session: Session) -> Dict[int, List[NodeEmaneCo
return configs
def get_hooks(session: Session) -> List[core_pb2.Hook]:
def get_hooks(session: Session) -> list[core_pb2.Hook]:
"""
Retrieve hook protobuf data for a session.
:param session: session to get hooks for
:return: list of hook protobufs
"""
hooks = []
for state in session.hooks:
state_hooks = session.hooks[state]
@ -600,10 +768,16 @@ def get_hooks(session: Session) -> List[core_pb2.Hook]:
return hooks
def get_default_services(session: Session) -> List[ServiceDefaults]:
def get_default_services(session: Session) -> list[ServiceDefaults]:
"""
Retrieve the default service sets for a given session.
:param session: session to get default service sets for
:return: list of default service sets
"""
default_services = []
for name, services in session.services.default_services.items():
default_service = ServiceDefaults(node_type=name, services=services)
for model, services in session.services.default_services.items():
default_service = ServiceDefaults(model=model, services=services)
default_services.append(default_service)
return default_services
@ -611,6 +785,14 @@ def get_default_services(session: Session) -> List[ServiceDefaults]:
def get_mobility_node(
session: Session, node_id: int, context: ServicerContext
) -> Union[WlanNode, EmaneNet]:
"""
Get mobility node.
:param session: session to get node from
:param node_id: id of node to get
:param context: grpc context
:return: wlan or emane node
"""
try:
return session.get_node(node_id, WlanNode)
except CoreError:
@ -621,17 +803,26 @@ def get_mobility_node(
def convert_session(session: Session) -> wrappers.Session:
links = []
nodes = []
"""
Convert session to its wrapped version.
:param session: session to convert
:return: wrapped session data
"""
emane_configs = get_emane_model_configs_dict(session)
nodes = []
links = []
for _id in session.nodes:
node = session.nodes[_id]
if not isinstance(node, (PtpNet, CtrlNet)):
node_emane_configs = emane_configs.get(node.id, [])
node_proto = get_node_proto(session, node, node_emane_configs)
nodes.append(node_proto)
node_links = get_links(node)
links.extend(node_links)
if isinstance(node, (WlanNode, EmaneNet)):
for link_data in node.links():
links.append(convert_link_data(link_data))
for core_link in session.link_manager.links():
links.extend(convert_core_link(core_link))
default_services = get_default_services(session)
x, y, z = session.location.refxyz
lat, lon, alt = session.location.refgeo
@ -640,7 +831,7 @@ def convert_session(session: Session) -> wrappers.Session:
)
hooks = get_hooks(session)
session_file = str(session.file_path) if session.file_path else None
options = get_config_options(session.options.get_configs(), session.options)
options = convert_session_options(session)
servers = [
core_pb2.Server(name=x.name, host=x.host)
for x in session.distributed.servers.values()
@ -665,6 +856,15 @@ def convert_session(session: Session) -> wrappers.Session:
def configure_node(
session: Session, node: core_pb2.Node, core_node: NodeBase, context: ServicerContext
) -> None:
"""
Configure a node using all provided protobuf data.
:param session: session for node
:param node: node protobuf data
:param core_node: session node
:param context: grpc context
:return: nothing
"""
for emane_config in node.emane_configs:
_id = utils.iface_config_id(node.id, emane_config.iface_id)
config = {k: v.value for k, v in emane_config.config.items()}
@ -675,6 +875,9 @@ def configure_node(
if node.mobility_config:
config = {k: v.value for k, v in node.mobility_config.items()}
session.mobility.set_model_config(node.id, Ns2ScriptedMobility.name, config)
if isinstance(core_node, WirelessNode) and node.wireless_config:
config = {k: v.value for k, v in node.wireless_config.items()}
core_node.set_config(config)
for service_name, service_config in node.service_configs.items():
data = service_config.data
config = ServiceConfig(

View file

@ -1,12 +1,15 @@
import atexit
import logging
import os
import re
import signal
import sys
import tempfile
import time
from collections.abc import Iterable
from concurrent import futures
from pathlib import Path
from typing import Iterable, Optional, Pattern, Type
from re import Pattern
from typing import Optional
import grpc
from grpc import ServicerContext
@ -23,10 +26,22 @@ from core.api.grpc.configservices_pb2 import (
ConfigService,
GetConfigServiceDefaultsRequest,
GetConfigServiceDefaultsResponse,
GetConfigServiceRenderedRequest,
GetConfigServiceRenderedResponse,
GetNodeConfigServiceRequest,
GetNodeConfigServiceResponse,
)
from core.api.grpc.core_pb2 import ExecuteScriptResponse
from core.api.grpc.core_pb2 import (
ExecuteScriptResponse,
GetWirelessConfigRequest,
GetWirelessConfigResponse,
LinkedRequest,
LinkedResponse,
WirelessConfigRequest,
WirelessConfigResponse,
WirelessLinkedRequest,
WirelessLinkedResponse,
)
from core.api.grpc.emane_pb2 import (
EmaneLinkRequest,
EmaneLinkResponse,
@ -79,19 +94,20 @@ from core.emulator.data import InterfaceData, LinkData, LinkOptions
from core.emulator.enumerations import (
EventTypes,
ExceptionLevels,
LinkTypes,
MessageFlags,
NodeTypes,
)
from core.emulator.session import NT, Session
from core.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes.base import CoreNode, NodeBase
from core.nodes.network import WlanNode
from core.nodes.network import CoreNetwork, WlanNode
from core.nodes.wireless import WirelessNode
from core.services.coreservices import ServiceManager
logger = logging.getLogger(__name__)
_ONE_DAY_IN_SECONDS: int = 60 * 60 * 24
_INTERFACE_REGEX: Pattern = re.compile(r"veth(?P<node>[0-9a-fA-F]+)")
_INTERFACE_REGEX: Pattern[str] = re.compile(r"beth(?P<node>[0-9a-fA-F]+)")
_MAX_WORKERS = 1000
@ -107,11 +123,20 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
self.coreemu: CoreEmu = coreemu
self.running: bool = True
self.server: Optional[grpc.Server] = None
atexit.register(self._exit_handler)
# catch signals
signal.signal(signal.SIGHUP, self._signal_handler)
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler)
signal.signal(signal.SIGUSR1, self._signal_handler)
signal.signal(signal.SIGUSR2, self._signal_handler)
def _exit_handler(self) -> None:
logger.debug("catching exit, stop running")
def _signal_handler(self, signal_number: int, _) -> None:
logger.info("caught signal: %s", signal_number)
self.coreemu.shutdown()
self.running = False
if self.server:
self.server.stop(None)
sys.exit(signal_number)
def _is_running(self, context) -> bool:
return self.running and context.is_active()
@ -148,7 +173,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
return session
def get_node(
self, session: Session, node_id: int, context: ServicerContext, _class: Type[NT]
self, session: Session, node_id: int, context: ServicerContext, _class: type[NT]
) -> NT:
"""
Retrieve node given session and node id
@ -187,7 +212,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
def validate_service(
self, name: str, context: ServicerContext
) -> Type[ConfigService]:
) -> type[ConfigService]:
"""
Validates a configuration service is a valid known service.
@ -248,18 +273,19 @@ 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()
for option in request.session.options.values():
session.options.set_config(option.name, option.value)
if option.value:
session.options.set(option.name, option.value)
session.metadata = dict(request.session.metadata)
# add servers
@ -288,17 +314,25 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
# create links
links = []
asym_links = []
edit_links = []
known_links = set()
for link in request.session.links:
if link.options.unidirectional:
asym_links.append(link)
iface1 = link.iface1.id if link.iface1 else None
iface2 = link.iface2.id if link.iface2 else None
if link.node1_id < link.node2_id:
link_id = (link.node1_id, iface1, link.node2_id, iface2)
else:
link_id = (link.node2_id, iface2, link.node1_id, iface1)
if link_id in known_links:
edit_links.append(link)
else:
known_links.add(link_id)
links.append(link)
_, exceptions = grpcutils.create_links(session, links)
if exceptions:
exceptions = [str(x) for x in exceptions]
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
_, exceptions = grpcutils.edit_links(session, asym_links)
_, exceptions = grpcutils.edit_links(session, edit_links)
if exceptions:
exceptions = [str(x) for x in exceptions]
return core_pb2.StartSessionResponse(result=False, exceptions=exceptions)
@ -370,11 +404,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
self, request: core_pb2.GetSessionsRequest, context: ServicerContext
) -> core_pb2.GetSessionsResponse:
"""
Delete the session
Get all currently known session overviews.
:param request: get-session request
:param request: get sessions request
:param context: context object
:return: a delete-session response
:return: a get sessions response
"""
logger.debug("get sessions: %s", request)
sessions = []
@ -461,7 +495,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
while self._is_running(context):
now = time.monotonic()
stats = get_net_stats()
# calculate average
if last_check is not None:
interval = now - last_check
@ -478,7 +511,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
(current_rxtx["tx"] - previous_rxtx["tx"]) * 8.0 / interval
)
throughput = rx_kbps + tx_kbps
if key.startswith("veth"):
if key.startswith("beth"):
key = key.split(".")
node_id = _INTERFACE_REGEX.search(key[0]).group("node")
node_id = int(node_id, base=16)
@ -504,7 +537,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
bridge_throughput.throughput = throughput
except ValueError:
pass
yield throughputs_event
last_check = now
@ -532,9 +564,17 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logger.debug("add node: %s", request)
session = self.get_session(request.session_id, context)
_type, _id, options = grpcutils.add_node_data(request.node)
_type = NodeTypes(request.node.type)
_class = session.get_node_class(_type)
node = session.add_node(_class, _id, options)
position, options = grpcutils.add_node_data(_class, request.node)
node = session.add_node(
_class,
request.node.id or None,
request.node.name or None,
request.node.server or None,
position,
options,
)
grpcutils.configure_node(session, request.node, node, context)
source = request.source if request.source else None
session.broadcast_node(node, MessageFlags.ADD, source)
@ -556,12 +596,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
ifaces = []
for iface_id in node.ifaces:
iface = node.ifaces[iface_id]
iface_proto = grpcutils.iface_to_proto(request.node_id, iface)
iface_proto = grpcutils.iface_to_proto(session, iface)
ifaces.append(iface_proto)
emane_configs = grpcutils.get_emane_model_configs_dict(session)
node_emane_configs = emane_configs.get(node.id, [])
node_proto = grpcutils.get_node_proto(session, node, node_emane_configs)
links = get_links(node)
links = get_links(session, node)
return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces, links=links)
def MoveNode(
@ -697,18 +737,22 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
node2_id = request.link.node2_id
self.get_node(session, node1_id, context, NodeBase)
self.get_node(session, node2_id, context, NodeBase)
iface1_data, iface2_data, options, link_type = grpcutils.add_link_data(
request.link
)
iface1_data, iface2_data, options = grpcutils.add_link_data(request.link)
node1_iface, node2_iface = session.add_link(
node1_id, node2_id, iface1_data, iface2_data, options, link_type
node1_id, node2_id, iface1_data, iface2_data, options
)
iface1_data = None
if node1_iface:
iface1_data = grpcutils.iface_to_data(node1_iface)
if isinstance(node1_iface.node, CoreNetwork):
iface1_data = InterfaceData(id=node1_iface.id)
else:
iface1_data = node1_iface.get_data()
iface2_data = None
if node2_iface:
iface2_data = grpcutils.iface_to_data(node2_iface)
if isinstance(node2_iface.node, CoreNetwork):
iface2_data = InterfaceData(id=node2_iface.id)
else:
iface2_data = node2_iface.get_data()
source = request.source if request.source else None
link_data = LinkData(
message_type=MessageFlags.ADD,
@ -723,9 +767,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
iface1_proto = None
iface2_proto = None
if node1_iface:
iface1_proto = grpcutils.iface_to_proto(node1_id, node1_iface)
iface1_proto = grpcutils.iface_to_proto(session, node1_iface)
if node2_iface:
iface2_proto = grpcutils.iface_to_proto(node2_id, node2_iface)
iface2_proto = grpcutils.iface_to_proto(session, node2_iface)
return core_pb2.AddLinkResponse(
result=True, iface1=iface1_proto, iface2=iface2_proto
)
@ -904,7 +948,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session.services.default_services.clear()
for service_defaults in request.defaults:
session.services.default_services[
service_defaults.node_type
service_defaults.model
] = service_defaults.services
return SetServiceDefaultsResponse(result=True)
@ -1062,7 +1106,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
node_id = request.wlan_config.node_id
config = request.wlan_config.config
session.mobility.set_model_config(node_id, BasicRangeModel.name, config)
if session.state == EventTypes.RUNTIME_STATE:
if session.is_running():
node = self.get_node(session, node_id, context, WlanNode)
node.updatemodel(config)
return SetWlanConfigResponse(result=True)
@ -1135,7 +1179,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
logger.debug("open xml: %s", request)
session = self.coreemu.create_session()
temp = tempfile.NamedTemporaryFile(delete=False)
temp.write(request.data.encode("utf-8"))
temp.write(request.data.encode())
temp.close()
temp_path = Path(temp.name)
file_path = Path(request.file)
@ -1155,7 +1199,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
self, request: core_pb2.GetInterfacesRequest, context: ServicerContext
) -> core_pb2.GetInterfacesResponse:
"""
Retrieve all the interfaces of the system including bridges, virtual ethernet, and loopback
Retrieve all the interfaces of the system including bridges, virtual ethernet,
and loopback.
:param request: get-interfaces request
:param context: context object
@ -1180,32 +1225,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logger.debug("emane link: %s", request)
session = self.get_session(request.session_id, context)
nem1 = request.nem1
iface1 = session.emane.get_iface(nem1)
if not iface1:
context.abort(grpc.StatusCode.NOT_FOUND, f"nem one {nem1} not found")
node1 = iface1.node
nem2 = request.nem2
iface2 = session.emane.get_iface(nem2)
if not iface2:
context.abort(grpc.StatusCode.NOT_FOUND, f"nem two {nem2} not found")
node2 = iface2.node
if iface1.net == iface2.net:
if request.linked:
flag = MessageFlags.ADD
else:
flag = MessageFlags.DELETE
color = session.get_link_color(iface1.net.id)
link = LinkData(
message_type=flag,
type=LinkTypes.WIRELESS,
node1_id=node1.id,
node2_id=node2.id,
network_id=iface1.net.id,
color=color,
)
flag = MessageFlags.ADD if request.linked else MessageFlags.DELETE
link = session.emane.get_nem_link(request.nem1, request.nem2, flag)
if link:
session.broadcast_link(link)
return EmaneLinkResponse(result=True)
else:
@ -1232,6 +1254,27 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
config = {x.id: x.default for x in service.default_configs}
return GetNodeConfigServiceResponse(config=config)
def GetConfigServiceRendered(
self, request: GetConfigServiceRenderedRequest, context: ServicerContext
) -> GetConfigServiceRenderedResponse:
"""
Retrieves the rendered file data for a given config service on a node.
:param request: config service render request
:param context: grpc context
:return: rendered config service files
"""
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context, CoreNode)
self.validate_service(request.name, context)
service = node.config_services.get(request.name)
if not service:
context.abort(
grpc.StatusCode.NOT_FOUND, f"unknown node service {request.name}"
)
rendered = service.get_rendered_templates()
return GetConfigServiceRenderedResponse(rendered=rendered)
def GetConfigServiceDefaults(
self, request: GetConfigServiceDefaultsRequest, context: ServicerContext
) -> GetConfigServiceDefaultsResponse:
@ -1242,8 +1285,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
:param context: grpc context
:return: get config service defaults response
"""
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context, CoreNode)
service_class = self.validate_service(request.name, context)
service = service_class(None)
service = service_class(node)
templates = service.get_templates()
config = {}
for configuration in service.default_configs:
@ -1291,18 +1336,21 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
) -> WlanLinkResponse:
session = self.get_session(request.session_id, context)
wlan = self.get_node(session, request.wlan, context, WlanNode)
if not isinstance(wlan.model, BasicRangeModel):
if not isinstance(wlan.wireless_model, BasicRangeModel):
context.abort(
grpc.StatusCode.NOT_FOUND,
f"wlan node {request.wlan} does not using BasicRangeModel",
f"wlan node {request.wlan} is not using BasicRangeModel",
)
node1 = self.get_node(session, request.node1_id, context, CoreNode)
node2 = self.get_node(session, request.node2_id, context, CoreNode)
node1_iface, node2_iface = None, None
for net, iface1, iface2 in node1.commonnets(node2):
if net == wlan:
node1_iface = iface1
node2_iface = iface2
for iface in node1.get_ifaces(control=False):
if iface.net == wlan:
node1_iface = iface
break
for iface in node2.get_ifaces(control=False):
if iface.net == wlan:
node2_iface = iface
break
result = False
if node1_iface and node2_iface:
@ -1310,7 +1358,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
wlan.link(node1_iface, node2_iface)
else:
wlan.unlink(node1_iface, node2_iface)
wlan.model.sendlinkmsg(node1_iface, node2_iface, unlink=not request.linked)
wlan.wireless_model.sendlinkmsg(
node1_iface, node2_iface, unlink=not request.linked
)
result = True
return WlanLinkResponse(result=result)
@ -1327,3 +1377,60 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
nem2 = grpcutils.get_nem_id(session, node2, request.iface2_id, context)
session.emane.publish_pathloss(nem1, nem2, request.rx1, request.rx2)
return EmanePathlossesResponse()
def Linked(
self, request: LinkedRequest, context: ServicerContext
) -> LinkedResponse:
session = self.get_session(request.session_id, context)
session.linked(
request.node1_id,
request.node2_id,
request.iface1_id,
request.iface2_id,
request.linked,
)
return LinkedResponse()
def WirelessLinked(
self, request: WirelessLinkedRequest, context: ServicerContext
) -> WirelessLinkedResponse:
session = self.get_session(request.session_id, context)
wireless = self.get_node(session, request.wireless_id, context, WirelessNode)
wireless.link_control(request.node1_id, request.node2_id, request.linked)
return WirelessLinkedResponse()
def WirelessConfig(
self, request: WirelessConfigRequest, context: ServicerContext
) -> WirelessConfigResponse:
session = self.get_session(request.session_id, context)
wireless = self.get_node(session, request.wireless_id, context, WirelessNode)
options1 = request.options1
options2 = options1
if request.HasField("options2"):
options2 = request.options2
options1 = grpcutils.convert_options_proto(options1)
options2 = grpcutils.convert_options_proto(options2)
wireless.link_config(request.node1_id, request.node2_id, options1, options2)
return WirelessConfigResponse()
def GetWirelessConfig(
self, request: GetWirelessConfigRequest, context: ServicerContext
) -> GetWirelessConfigResponse:
session = self.get_session(request.session_id, context)
try:
wireless = session.get_node(request.node_id, WirelessNode)
configs = wireless.get_config()
except CoreError:
configs = {x.id: x for x in WirelessNode.options}
config_options = {}
for config in configs.values():
config_option = common_pb2.ConfigOption(
label=config.label,
name=config.id,
value=config.default,
type=config.type.value,
select=config.options,
group=config.group,
)
config_options[config.id] = config_option
return GetWirelessConfigResponse(config=config_options)

View file

@ -1,7 +1,7 @@
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple
from typing import Any, Optional
from core.api.grpc import (
common_pb2,
@ -67,6 +67,8 @@ class NodeType(Enum):
CONTROL_NET = 13
DOCKER = 15
LXC = 16
WIRELESS = 17
PODMAN = 18
class LinkType(Enum):
@ -113,13 +115,13 @@ class EventType:
class ConfigService:
group: str
name: str
executables: List[str]
dependencies: List[str]
directories: List[str]
files: List[str]
startup: List[str]
validate: List[str]
shutdown: List[str]
executables: list[str]
dependencies: list[str]
directories: list[str]
files: list[str]
startup: list[str]
validate: list[str]
shutdown: list[str]
validation_mode: ConfigServiceValidationMode
validation_timer: int
validation_period: float
@ -146,8 +148,8 @@ class ConfigService:
class ConfigServiceConfig:
node_id: int
name: str
templates: Dict[str, str]
config: Dict[str, str]
templates: dict[str, str]
config: dict[str, str]
@classmethod
def from_proto(
@ -163,15 +165,15 @@ class ConfigServiceConfig:
@dataclass
class ConfigServiceData:
templates: Dict[str, str] = field(default_factory=dict)
config: Dict[str, str] = field(default_factory=dict)
templates: dict[str, str] = field(default_factory=dict)
config: dict[str, str] = field(default_factory=dict)
@dataclass
class ConfigServiceDefaults:
templates: Dict[str, str]
config: Dict[str, "ConfigOption"]
modes: Dict[str, Dict[str, str]]
templates: dict[str, str]
config: dict[str, "ConfigOption"]
modes: dict[str, dict[str, str]]
@classmethod
def from_proto(
@ -209,25 +211,25 @@ class Service:
@dataclass
class ServiceDefault:
node_type: str
services: List[str]
model: str
services: list[str]
@classmethod
def from_proto(cls, proto: services_pb2.ServiceDefaults) -> "ServiceDefault":
return ServiceDefault(node_type=proto.node_type, services=list(proto.services))
return ServiceDefault(model=proto.model, services=list(proto.services))
@dataclass
class NodeServiceData:
executables: List[str] = field(default_factory=list)
dependencies: List[str] = field(default_factory=list)
dirs: List[str] = field(default_factory=list)
configs: List[str] = field(default_factory=list)
startup: List[str] = field(default_factory=list)
validate: List[str] = field(default_factory=list)
executables: list[str] = field(default_factory=list)
dependencies: list[str] = field(default_factory=list)
dirs: list[str] = field(default_factory=list)
configs: list[str] = field(default_factory=list)
startup: list[str] = field(default_factory=list)
validate: list[str] = field(default_factory=list)
validation_mode: ServiceValidationMode = ServiceValidationMode.NON_BLOCKING
validation_timer: int = 5
shutdown: List[str] = field(default_factory=list)
shutdown: list[str] = field(default_factory=list)
meta: str = None
@classmethod
@ -265,7 +267,7 @@ class NodeServiceConfig:
node_id: int
service: str
data: NodeServiceData
files: Dict[str, str] = field(default_factory=dict)
files: dict[str, str] = field(default_factory=dict)
@classmethod
def from_proto(cls, proto: services_pb2.NodeServiceConfig) -> "NodeServiceConfig":
@ -281,11 +283,11 @@ class NodeServiceConfig:
class ServiceConfig:
node_id: int
service: str
files: List[str] = None
directories: List[str] = None
startup: List[str] = None
validate: List[str] = None
shutdown: List[str] = None
files: list[str] = None
directories: list[str] = None
startup: list[str] = None
validate: list[str] = None
shutdown: list[str] = None
def to_proto(self) -> services_pb2.ServiceConfig:
return services_pb2.ServiceConfig(
@ -338,8 +340,8 @@ class InterfaceThroughput:
@dataclass
class ThroughputsEvent:
session_id: int
bridge_throughputs: List[BridgeThroughput]
iface_throughputs: List[InterfaceThroughput]
bridge_throughputs: list[BridgeThroughput]
iface_throughputs: list[InterfaceThroughput]
@classmethod
def from_proto(cls, proto: core_pb2.ThroughputsEvent) -> "ThroughputsEvent":
@ -427,19 +429,19 @@ class ConfigOption:
label: str = None
type: ConfigOptionType = None
group: str = None
select: List[str] = None
select: list[str] = None
@classmethod
def from_dict(
cls, config: Dict[str, common_pb2.ConfigOption]
) -> Dict[str, "ConfigOption"]:
cls, config: dict[str, common_pb2.ConfigOption]
) -> dict[str, "ConfigOption"]:
d = {}
for key, value in config.items():
d[key] = ConfigOption.from_proto(value)
return d
@classmethod
def to_dict(cls, config: Dict[str, "ConfigOption"]) -> Dict[str, str]:
def to_dict(cls, config: dict[str, "ConfigOption"]) -> dict[str, str]:
return {k: v.value for k, v in config.items()}
@classmethod
@ -480,6 +482,8 @@ class Interface:
mtu: int = None
node_id: int = None
net2_id: int = None
nem_id: int = None
nem_port: int = None
@classmethod
def from_proto(cls, proto: core_pb2.Interface) -> "Interface":
@ -496,6 +500,8 @@ class Interface:
mtu=proto.mtu,
node_id=proto.node_id,
net2_id=proto.net2_id,
nem_id=proto.nem_id,
nem_port=proto.nem_port,
)
def to_proto(self) -> core_pb2.Interface:
@ -637,6 +643,15 @@ class SessionSummary:
dir=proto.dir,
)
def to_proto(self) -> core_pb2.SessionSummary:
return core_pb2.SessionSummary(
id=self.id,
state=self.state.value,
nodes=self.nodes,
file=self.file,
dir=self.dir,
)
@dataclass
class Hook:
@ -657,7 +672,7 @@ class EmaneModelConfig:
node_id: int
model: str
iface_id: int = -1
config: Dict[str, ConfigOption] = None
config: dict[str, ConfigOption] = None
@classmethod
def from_proto(cls, proto: emane_pb2.GetEmaneModelConfig) -> "EmaneModelConfig":
@ -711,8 +726,8 @@ class Node:
type: NodeType = NodeType.DEFAULT
model: str = None
position: Position = Position(x=0, y=0)
services: Set[str] = field(default_factory=set)
config_services: Set[str] = field(default_factory=set)
services: set[str] = field(default_factory=set)
config_services: set[str] = field(default_factory=set)
emane: str = None
icon: str = None
image: str = None
@ -723,18 +738,19 @@ class Node:
canvas: int = None
# configurations
emane_model_configs: Dict[
Tuple[str, Optional[int]], Dict[str, ConfigOption]
emane_model_configs: dict[
tuple[str, Optional[int]], dict[str, ConfigOption]
] = field(default_factory=dict, repr=False)
wlan_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False)
mobility_config: Dict[str, ConfigOption] = field(default_factory=dict, repr=False)
service_configs: Dict[str, NodeServiceData] = field(
wlan_config: dict[str, ConfigOption] = field(default_factory=dict, repr=False)
wireless_config: dict[str, ConfigOption] = field(default_factory=dict, repr=False)
mobility_config: dict[str, ConfigOption] = field(default_factory=dict, repr=False)
service_configs: dict[str, NodeServiceData] = field(
default_factory=dict, repr=False
)
service_file_configs: Dict[str, Dict[str, str]] = field(
service_file_configs: dict[str, dict[str, str]] = field(
default_factory=dict, repr=False
)
config_service_configs: Dict[str, ConfigServiceData] = field(
config_service_configs: dict[str, ConfigServiceData] = field(
default_factory=dict, repr=False
)
@ -761,7 +777,7 @@ class Node:
id=proto.id,
name=proto.name,
type=NodeType(proto.type),
model=proto.model,
model=proto.model or None,
position=Position.from_proto(proto.position),
services=set(proto.services),
config_services=set(proto.config_services),
@ -779,6 +795,7 @@ class Node:
service_file_configs=service_file_configs,
config_service_configs=config_service_configs,
emane_model_configs=emane_configs,
wireless_config=ConfigOption.from_dict(proto.wireless_config),
)
def to_proto(self) -> core_pb2.Node:
@ -830,20 +847,21 @@ class Node:
service_configs=service_configs,
config_service_configs=config_service_configs,
emane_configs=emane_configs,
wireless_config={k: v.to_proto() for k, v in self.wireless_config.items()},
)
def set_wlan(self, config: Dict[str, str]) -> None:
def set_wlan(self, config: dict[str, str]) -> None:
for key, value in config.items():
option = ConfigOption(name=key, value=value)
self.wlan_config[key] = option
def set_mobility(self, config: Dict[str, str]) -> None:
def set_mobility(self, config: dict[str, str]) -> None:
for key, value in config.items():
option = ConfigOption(name=key, value=value)
self.mobility_config[key] = option
def set_emane_model(
self, model: str, config: Dict[str, str], iface_id: int = None
self, model: str, config: dict[str, str], iface_id: int = None
) -> None:
key = (model, iface_id)
config_options = self.emane_model_configs.setdefault(key, {})
@ -856,27 +874,25 @@ class Node:
class Session:
id: int = None
state: SessionState = SessionState.DEFINITION
nodes: Dict[int, Node] = field(default_factory=dict)
links: List[Link] = field(default_factory=list)
nodes: dict[int, Node] = field(default_factory=dict)
links: list[Link] = field(default_factory=list)
dir: str = None
user: str = None
default_services: Dict[str, Set[str]] = field(default_factory=dict)
default_services: dict[str, set[str]] = field(default_factory=dict)
location: SessionLocation = SessionLocation(
x=0.0, y=0.0, z=0.0, lat=47.57917, lon=-122.13232, alt=2.0, scale=150.0
)
hooks: Dict[str, Hook] = field(default_factory=dict)
metadata: Dict[str, str] = field(default_factory=dict)
hooks: dict[str, Hook] = field(default_factory=dict)
metadata: dict[str, str] = field(default_factory=dict)
file: Path = None
options: Dict[str, ConfigOption] = field(default_factory=dict)
servers: List[Server] = field(default_factory=list)
options: dict[str, ConfigOption] = field(default_factory=dict)
servers: list[Server] = field(default_factory=list)
@classmethod
def from_proto(cls, proto: core_pb2.Session) -> "Session":
nodes: Dict[int, Node] = {x.id: Node.from_proto(x) for x in proto.nodes}
nodes: dict[int, Node] = {x.id: Node.from_proto(x) for x in proto.nodes}
links = [Link.from_proto(x) for x in proto.links]
default_services = {
x.node_type: set(x.services) for x in proto.default_services
}
default_services = {x.model: set(x.services) for x in proto.default_services}
hooks = {x.file: Hook.from_proto(x) for x in proto.hooks}
file_path = Path(proto.file) if proto.file else None
options = ConfigOption.from_dict(proto.options)
@ -904,9 +920,9 @@ class Session:
options = {k: v.to_proto() for k, v in self.options.items()}
servers = [x.to_proto() for x in self.servers]
default_services = []
for node_type, services in self.default_services.items():
for model, services in self.default_services.items():
default_service = services_pb2.ServiceDefaults(
node_type=node_type, services=services
model=model, services=services
)
default_services.append(default_service)
file = str(self.file) if self.file else None
@ -972,7 +988,7 @@ class Session:
self.links.append(link)
return link
def set_options(self, config: Dict[str, str]) -> None:
def set_options(self, config: dict[str, str]) -> None:
for key, value in config.items():
option = ConfigOption(name=key, value=value)
self.options[key] = option
@ -980,9 +996,9 @@ class Session:
@dataclass
class CoreConfig:
services: List[Service] = field(default_factory=list)
config_services: List[ConfigService] = field(default_factory=list)
emane_models: List[str] = field(default_factory=list)
services: list[Service] = field(default_factory=list)
config_services: list[ConfigService] = field(default_factory=list)
emane_models: list[str] = field(default_factory=list)
@classmethod
def from_proto(cls, proto: core_pb2.GetConfigResponse) -> "CoreConfig":
@ -1073,7 +1089,7 @@ class ConfigEvent:
node_id: int
object: str
type: int
data_types: List[int]
data_types: list[int]
data_values: str
captions: str
bitmap: str
@ -1093,7 +1109,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,
@ -1185,13 +1200,13 @@ class EmanePathlossesRequest:
)
@dataclass
@dataclass(frozen=True)
class MoveNodesRequest:
session_id: int
node_id: int
source: str = None
position: Position = None
geo: Geo = None
source: str = field(compare=False, default=None)
position: Position = field(compare=False, default=None)
geo: Geo = field(compare=False, default=None)
def to_proto(self) -> core_pb2.MoveNodesRequest:
position = self.position.to_proto() if self.position else None

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

@ -5,7 +5,7 @@ Common support for configurable CORE objects.
import logging
from collections import OrderedDict
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Type, Union
from typing import TYPE_CHECKING, Any, Optional, Union
from core.emane.nodes import EmaneNet
from core.emulator.enumerations import ConfigDataTypes
@ -17,9 +17,9 @@ logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from core.location.mobility import WirelessModel
WirelessModelType = Type[WirelessModel]
WirelessModelType = type[WirelessModel]
_BOOL_OPTIONS: Set[str] = {"0", "1"}
_BOOL_OPTIONS: set[str] = {"0", "1"}
@dataclass
@ -43,7 +43,8 @@ class Configuration:
type: ConfigDataTypes
label: str = None
default: str = ""
options: List[str] = field(default_factory=list)
options: list[str] = field(default_factory=list)
group: str = "Configuration"
def __post_init__(self) -> None:
self.label = self.label if self.label else self.id
@ -78,6 +79,7 @@ class ConfigBool(Configuration):
"""
type: ConfigDataTypes = ConfigDataTypes.BOOL
value: bool = False
@dataclass
@ -87,6 +89,7 @@ class ConfigFloat(Configuration):
"""
type: ConfigDataTypes = ConfigDataTypes.FLOAT
value: float = 0.0
@dataclass
@ -96,6 +99,7 @@ class ConfigInt(Configuration):
"""
type: ConfigDataTypes = ConfigDataTypes.INT32
value: int = 0
@dataclass
@ -105,6 +109,7 @@ class ConfigString(Configuration):
"""
type: ConfigDataTypes = ConfigDataTypes.STRING
value: str = ""
class ConfigurableOptions:
@ -113,11 +118,10 @@ class ConfigurableOptions:
"""
name: Optional[str] = None
bitmap: Optional[str] = None
options: List[Configuration] = []
options: list[Configuration] = []
@classmethod
def configurations(cls) -> List[Configuration]:
def configurations(cls) -> list[Configuration]:
"""
Provides the configurations for this class.
@ -126,7 +130,7 @@ class ConfigurableOptions:
return cls.options
@classmethod
def config_groups(cls) -> List[ConfigGroup]:
def config_groups(cls) -> list[ConfigGroup]:
"""
Defines how configurations are grouped.
@ -135,7 +139,7 @@ class ConfigurableOptions:
return [ConfigGroup("Options", 1, len(cls.configurations()))]
@classmethod
def default_values(cls) -> Dict[str, str]:
def default_values(cls) -> dict[str, str]:
"""
Provides an ordered mapping of configuration keys to default values.
@ -161,7 +165,7 @@ class ConfigurableManager:
"""
self.node_configurations = {}
def nodes(self) -> List[int]:
def nodes(self) -> list[int]:
"""
Retrieves the ids of all node configurations known by this manager.
@ -204,7 +208,7 @@ class ConfigurableManager:
def set_configs(
self,
config: Dict[str, str],
config: dict[str, str],
node_id: int = _default_node,
config_type: str = _default_type,
) -> None:
@ -246,7 +250,7 @@ class ConfigurableManager:
def get_configs(
self, node_id: int = _default_node, config_type: str = _default_type
) -> Optional[Dict[str, str]]:
) -> Optional[dict[str, str]]:
"""
Retrieve configurations for a node and configuration type.
@ -260,7 +264,7 @@ class ConfigurableManager:
result = node_configs.get(config_type)
return result
def get_all_configs(self, node_id: int = _default_node) -> Dict[str, Any]:
def get_all_configs(self, node_id: int = _default_node) -> dict[str, Any]:
"""
Retrieve all current configuration types for a node.
@ -280,11 +284,11 @@ class ModelManager(ConfigurableManager):
Creates a ModelManager object.
"""
super().__init__()
self.models: Dict[str, Any] = {}
self.node_models: Dict[int, str] = {}
self.models: dict[str, Any] = {}
self.node_models: dict[int, str] = {}
def set_model_config(
self, node_id: int, model_name: str, config: Dict[str, str] = None
self, node_id: int, model_name: str, config: dict[str, str] = None
) -> None:
"""
Set configuration data for a model.
@ -313,7 +317,7 @@ class ModelManager(ConfigurableManager):
# set configuration
self.set_configs(model_config, node_id=node_id, config_type=model_name)
def get_model_config(self, node_id: int, model_name: str) -> Dict[str, str]:
def get_model_config(self, node_id: int, model_name: str) -> dict[str, str]:
"""
Retrieve configuration data for a model.
@ -338,7 +342,7 @@ class ModelManager(ConfigurableManager):
self,
node: Union[WlanNode, EmaneNet],
model_class: "WirelessModelType",
config: Dict[str, str] = None,
config: dict[str, str] = None,
) -> None:
"""
Set model and model configuration for node.
@ -357,7 +361,7 @@ class ModelManager(ConfigurableManager):
def get_models(
self, node: Union[WlanNode, EmaneNet]
) -> List[Tuple[Type, Dict[str, str]]]:
) -> list[tuple[type, dict[str, str]]]:
"""
Return a list of model classes and values for a net if one has been
configured. This is invoked when exporting a session to XML.

View file

@ -5,7 +5,7 @@ import logging
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, List, Optional
from typing import Any, Optional
from mako import exceptions
from mako.lookup import TemplateLookup
@ -19,6 +19,20 @@ logger = logging.getLogger(__name__)
TEMPLATES_DIR: str = "templates"
def get_template_path(file_path: Path) -> str:
"""
Utility to convert a given file path to a valid template path format.
:param file_path: file path to convert
:return: template path
"""
if file_path.is_absolute():
template_path = str(file_path.relative_to("/"))
else:
template_path = str(file_path)
return template_path
class ConfigServiceMode(enum.Enum):
BLOCKING = 0
NON_BLOCKING = 1
@ -29,6 +43,10 @@ class ConfigServiceBootError(Exception):
pass
class ConfigServiceTemplateError(Exception):
pass
@dataclass
class ShadowDir:
path: str
@ -49,7 +67,7 @@ class ConfigService(abc.ABC):
validation_timer: int = 5
# directories to shadow and copy files from
shadow_directories: List[ShadowDir] = []
shadow_directories: list[ShadowDir] = []
def __init__(self, node: CoreNode) -> None:
"""
@ -61,9 +79,9 @@ class ConfigService(abc.ABC):
class_file = inspect.getfile(self.__class__)
templates_path = Path(class_file).parent.joinpath(TEMPLATES_DIR)
self.templates: TemplateLookup = TemplateLookup(directories=templates_path)
self.config: Dict[str, Configuration] = {}
self.custom_templates: Dict[str, str] = {}
self.custom_config: Dict[str, str] = {}
self.config: dict[str, Configuration] = {}
self.custom_templates: dict[str, str] = {}
self.custom_config: dict[str, str] = {}
configs = self.default_configs[:]
self._define_config(configs)
@ -90,47 +108,47 @@ class ConfigService(abc.ABC):
@property
@abc.abstractmethod
def directories(self) -> List[str]:
def directories(self) -> list[str]:
raise NotImplementedError
@property
@abc.abstractmethod
def files(self) -> List[str]:
def files(self) -> list[str]:
raise NotImplementedError
@property
@abc.abstractmethod
def default_configs(self) -> List[Configuration]:
def default_configs(self) -> list[Configuration]:
raise NotImplementedError
@property
@abc.abstractmethod
def modes(self) -> Dict[str, Dict[str, str]]:
def modes(self) -> dict[str, dict[str, str]]:
raise NotImplementedError
@property
@abc.abstractmethod
def executables(self) -> List[str]:
def executables(self) -> list[str]:
raise NotImplementedError
@property
@abc.abstractmethod
def dependencies(self) -> List[str]:
def dependencies(self) -> list[str]:
raise NotImplementedError
@property
@abc.abstractmethod
def startup(self) -> List[str]:
def startup(self) -> list[str]:
raise NotImplementedError
@property
@abc.abstractmethod
def validate(self) -> List[str]:
def validate(self) -> list[str]:
raise NotImplementedError
@property
@abc.abstractmethod
def shutdown(self) -> List[str]:
def shutdown(self) -> list[str]:
raise NotImplementedError
@property
@ -258,7 +276,7 @@ class ConfigService(abc.ABC):
f"failure to create service directory: {directory}"
)
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
"""
Returns key/value data, used when rendering file templates.
@ -285,7 +303,7 @@ class ConfigService(abc.ABC):
"""
raise CoreError(f"service({self.name}) unknown template({name})")
def get_templates(self) -> Dict[str, str]:
def get_templates(self) -> dict[str, str]:
"""
Retrieves mapping of file names to templates for all cases, which
includes custom templates, file templates, and text templates.
@ -295,21 +313,51 @@ class ConfigService(abc.ABC):
templates = {}
for file in self.files:
file_path = Path(file)
if file_path.is_absolute():
template_path = str(file_path.relative_to("/"))
else:
template_path = str(file_path)
template_path = get_template_path(file_path)
if file in self.custom_templates:
template = self.custom_templates[file]
template = self.clean_text(template)
elif self.templates.has_template(template_path):
template = self.templates.get_template(template_path).source
else:
template = self.get_text_template(file)
try:
template = self.get_text_template(file)
except Exception as e:
raise ConfigServiceTemplateError(
f"node({self.node.name}) service({self.name}) file({file}) "
f"failure getting template: {e}"
)
template = self.clean_text(template)
templates[file] = template
return templates
def get_rendered_templates(self) -> dict[str, str]:
templates = {}
data = self.data()
for file in sorted(self.files):
rendered = self._get_rendered_template(file, data)
templates[file] = rendered
return templates
def _get_rendered_template(self, file: str, data: dict[str, Any]) -> str:
file_path = Path(file)
template_path = get_template_path(file_path)
if file in self.custom_templates:
text = self.custom_templates[file]
rendered = self.render_text(text, data)
elif self.templates.has_template(template_path):
rendered = self.render_template(template_path, data)
else:
try:
text = self.get_text_template(file)
except Exception as e:
raise ConfigServiceTemplateError(
f"node({self.node.name}) service({self.name}) file({file}) "
f"failure getting template: {e}"
)
rendered = self.render_text(text, data)
return rendered
def create_files(self) -> None:
"""
Creates service files inside associated node.
@ -321,15 +369,8 @@ class ConfigService(abc.ABC):
logger.debug(
"node(%s) service(%s) template(%s)", self.node.name, self.name, file
)
rendered = self._get_rendered_template(file, data)
file_path = Path(file)
if file in self.custom_templates:
text = self.custom_templates[file]
rendered = self.render_text(text, data)
elif self.templates.has_template(file_path.name):
rendered = self.render_template(file_path.name, data)
else:
text = self.get_text_template(file)
rendered = self.render_text(text, data)
self.node.create_file(file_path, rendered)
def run_startup(self, wait: bool) -> None:
@ -385,7 +426,7 @@ class ConfigService(abc.ABC):
f"node({self.node.name}) service({self.name}) failed to validate"
)
def _render(self, template: Template, data: Dict[str, Any] = None) -> str:
def _render(self, template: Template, data: dict[str, Any] = None) -> str:
"""
Renders template providing all associated data to template.
@ -399,7 +440,7 @@ class ConfigService(abc.ABC):
node=self.node, config=self.render_config(), **data
)
def render_text(self, text: str, data: Dict[str, Any] = None) -> str:
def render_text(self, text: str, data: dict[str, Any] = None) -> str:
"""
Renders text based template providing all associated data to template.
@ -417,24 +458,24 @@ class ConfigService(abc.ABC):
f"{exceptions.text_error_template().render_unicode()}"
)
def render_template(self, basename: str, data: Dict[str, Any] = None) -> str:
def render_template(self, template_path: str, data: dict[str, Any] = None) -> str:
"""
Renders file based template providing all associated data to template.
:param basename: base name for file to render
:param template_path: path of file to render
:param data: service specific defined data for template
:return: rendered template
"""
try:
template = self.templates.get_template(basename)
template = self.templates.get_template(template_path)
return self._render(template, data)
except Exception:
raise CoreError(
f"node({self.node.name}) service({self.name}) "
f"{exceptions.text_error_template().render_template()}"
f"node({self.node.name}) service({self.name}) file({template_path})"
f"{exceptions.text_error_template().render_unicode()}"
)
def _define_config(self, configs: List[Configuration]) -> None:
def _define_config(self, configs: list[Configuration]) -> None:
"""
Initializes default configuration data.
@ -444,7 +485,7 @@ class ConfigService(abc.ABC):
for config in configs:
self.config[config.id] = config
def render_config(self) -> Dict[str, str]:
def render_config(self) -> dict[str, str]:
"""
Returns configuration data key/value pairs for rendering a template.
@ -455,7 +496,7 @@ class ConfigService(abc.ABC):
else:
return {k: v.default for k, v in self.config.items()}
def set_config(self, data: Dict[str, str]) -> None:
def set_config(self, data: dict[str, str]) -> None:
"""
Set configuration data from key/value pairs.

View file

@ -1,5 +1,5 @@
import logging
from typing import TYPE_CHECKING, Dict, List, Set
from typing import TYPE_CHECKING
logger = logging.getLogger(__name__)
@ -12,16 +12,16 @@ class ConfigServiceDependencies:
Generates sets of services to start in order of their dependencies.
"""
def __init__(self, services: Dict[str, "ConfigService"]) -> None:
def __init__(self, services: dict[str, "ConfigService"]) -> None:
"""
Create a ConfigServiceDependencies instance.
:param services: services for determining dependency sets
"""
# helpers to check validity
self.dependents: Dict[str, Set[str]] = {}
self.started: Set[str] = set()
self.node_services: Dict[str, "ConfigService"] = {}
self.dependents: dict[str, set[str]] = {}
self.started: set[str] = set()
self.node_services: dict[str, "ConfigService"] = {}
for service in services.values():
self.node_services[service.name] = service
for dependency in service.dependencies:
@ -29,11 +29,11 @@ class ConfigServiceDependencies:
dependents.add(service.name)
# used to find paths
self.path: List["ConfigService"] = []
self.visited: Set[str] = set()
self.visiting: Set[str] = set()
self.path: list["ConfigService"] = []
self.visited: set[str] = set()
self.visiting: set[str] = set()
def startup_paths(self) -> List[List["ConfigService"]]:
def startup_paths(self) -> list[list["ConfigService"]]:
"""
Find startup path sets based on service dependencies.
@ -54,8 +54,8 @@ class ConfigServiceDependencies:
if self.started != set(self.node_services):
raise ValueError(
"failure to start all services: %s != %s"
% (self.started, self.node_services.keys())
f"failure to start all services: {self.started} != "
f"{self.node_services.keys()}"
)
return paths
@ -70,7 +70,7 @@ class ConfigServiceDependencies:
self.visited.clear()
self.visiting.clear()
def _start(self, service: "ConfigService") -> List["ConfigService"]:
def _start(self, service: "ConfigService") -> list["ConfigService"]:
"""
Starts a oath for checking dependencies for a given service.
@ -81,7 +81,7 @@ class ConfigServiceDependencies:
self._reset()
return self._visit(service)
def _visit(self, current_service: "ConfigService") -> List["ConfigService"]:
def _visit(self, current_service: "ConfigService") -> list["ConfigService"]:
"""
Visits a service when discovering dependency chains for service.
@ -96,14 +96,14 @@ class ConfigServiceDependencies:
for service_name in current_service.dependencies:
if service_name not in self.node_services:
raise ValueError(
"required dependency was not included in node services: %s"
% service_name
"required dependency was not included in node "
f"services: {service_name}"
)
if service_name in self.visiting:
raise ValueError(
"cyclic dependency at service(%s): %s"
% (current_service.name, service_name)
f"cyclic dependency at service({current_service.name}): "
f"{service_name}"
)
if service_name not in self.visited:

View file

@ -2,7 +2,6 @@ import logging
import pathlib
import pkgutil
from pathlib import Path
from typing import Dict, List, Type
from core import configservices, utils
from core.configservice.base import ConfigService
@ -20,9 +19,9 @@ class ConfigServiceManager:
"""
Create a ConfigServiceManager instance.
"""
self.services: Dict[str, Type[ConfigService]] = {}
self.services: dict[str, type[ConfigService]] = {}
def get_service(self, name: str) -> Type[ConfigService]:
def get_service(self, name: str) -> type[ConfigService]:
"""
Retrieve a service by name.
@ -35,7 +34,7 @@ class ConfigServiceManager:
raise CoreError(f"service does not exist {name}")
return service_class
def add(self, service: Type[ConfigService]) -> None:
def add(self, service: type[ConfigService]) -> None:
"""
Add service to manager, checking service requirements have been met.
@ -62,7 +61,7 @@ class ConfigServiceManager:
# make service available
self.services[name] = service
def load_locals(self) -> List[str]:
def load_locals(self) -> list[str]:
"""
Search and add config service from local core module.
@ -81,7 +80,7 @@ class ConfigServiceManager:
logger.debug("not loading config service(%s): %s", service.name, e)
return errors
def load(self, path: Path) -> List[str]:
def load(self, path: Path) -> list[str]:
"""
Search path provided for config services and add them for being managed.

View file

@ -1,17 +1,29 @@
import abc
from typing import Any, Dict, List
from typing import Any
from core.config import Configuration
from core.configservice.base import ConfigService, ConfigServiceMode
from core.emane.nodes import EmaneNet
from core.nodes.base import CoreNodeBase
from core.nodes.base import CoreNodeBase, NodeBase
from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import WlanNode
from core.nodes.network import PtpNet, WlanNode
from core.nodes.physical import Rj45Node
from core.nodes.wireless import WirelessNode
GROUP: str = "FRR"
FRR_STATE_DIR: str = "/var/run/frr"
def is_wireless(node: NodeBase) -> bool:
"""
Check if the node is a wireless type node.
:param node: node to check type for
:return: True if wireless type, False otherwise
"""
return isinstance(node, (WlanNode, EmaneNet, WirelessNode))
def has_mtu_mismatch(iface: CoreInterface) -> bool:
"""
Helper to detect MTU mismatch and add the appropriate FRR
@ -53,32 +65,47 @@ def get_router_id(node: CoreNodeBase) -> str:
return "0.0.0.0"
def rj45_check(iface: CoreInterface) -> bool:
"""
Helper to detect whether interface is connected an external RJ45
link.
"""
if iface.net:
for peer_iface in iface.net.get_ifaces():
if peer_iface == iface:
continue
if isinstance(peer_iface.node, Rj45Node):
return True
return False
class FRRZebra(ConfigService):
name: str = "FRRzebra"
group: str = GROUP
directories: List[str] = ["/usr/local/etc/frr", "/var/run/frr", "/var/log/frr"]
files: List[str] = [
directories: list[str] = ["/usr/local/etc/frr", "/var/run/frr", "/var/log/frr"]
files: list[str] = [
"/usr/local/etc/frr/frr.conf",
"frrboot.sh",
"/usr/local/etc/frr/vtysh.conf",
"/usr/local/etc/frr/daemons",
]
executables: List[str] = ["zebra"]
dependencies: List[str] = []
startup: List[str] = ["bash frrboot.sh zebra"]
validate: List[str] = ["pidof zebra"]
shutdown: List[str] = ["killall zebra"]
executables: list[str] = ["zebra"]
dependencies: list[str] = []
startup: list[str] = ["bash frrboot.sh zebra"]
validate: list[str] = ["pidof zebra"]
shutdown: list[str] = ["killall zebra"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
frr_conf = self.files[0]
frr_bin_search = self.node.session.options.get_config(
frr_bin_search = self.node.session.options.get(
"frr_bin_search", default="/usr/local/bin /usr/bin /usr/lib/frr"
).strip('"')
frr_sbin_search = self.node.session.options.get_config(
"frr_sbin_search", default="/usr/local/sbin /usr/sbin /usr/lib/frr"
frr_sbin_search = self.node.session.options.get(
"frr_sbin_search",
default="/usr/local/sbin /usr/sbin /usr/lib/frr /usr/libexec/frr",
).strip('"')
services = []
@ -119,16 +146,16 @@ class FRRZebra(ConfigService):
class FrrService(abc.ABC):
group: str = GROUP
directories: List[str] = []
files: List[str] = []
executables: List[str] = []
dependencies: List[str] = ["FRRzebra"]
startup: List[str] = []
validate: List[str] = []
shutdown: List[str] = []
directories: list[str] = []
files: list[str] = []
executables: list[str] = []
dependencies: list[str] = ["FRRzebra"]
startup: list[str] = []
validate: list[str] = []
shutdown: list[str] = []
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
ipv4_routing: bool = False
ipv6_routing: bool = False
@ -149,8 +176,8 @@ class FRROspfv2(FrrService, ConfigService):
"""
name: str = "FRROSPFv2"
shutdown: List[str] = ["killall ospfd"]
validate: List[str] = ["pidof ospfd"]
shutdown: list[str] = ["killall ospfd"]
validate: list[str] = ["pidof ospfd"]
ipv4_routing: bool = True
def frr_config(self) -> str:
@ -158,7 +185,7 @@ class FRROspfv2(FrrService, ConfigService):
addresses = []
for iface in self.node.get_ifaces(control=False):
for ip4 in iface.ip4s:
addresses.append(str(ip4.ip))
addresses.append(str(ip4))
data = dict(router_id=router_id, addresses=addresses)
text = """
router ospf
@ -166,15 +193,31 @@ class FRROspfv2(FrrService, ConfigService):
% for addr in addresses:
network ${addr} area 0
% endfor
ospf opaque-lsa
!
"""
return self.render_text(text, data)
def frr_iface_config(self, iface: CoreInterface) -> str:
if has_mtu_mismatch(iface):
return "ip ospf mtu-ignore"
else:
return ""
has_mtu = has_mtu_mismatch(iface)
has_rj45 = rj45_check(iface)
is_ptp = isinstance(iface.net, PtpNet)
data = dict(has_mtu=has_mtu, is_ptp=is_ptp, has_rj45=has_rj45)
text = """
% if has_mtu:
ip ospf mtu-ignore
% endif
% if has_rj45:
<% return STOP_RENDERING %>
% endif
% if is_ptp:
ip ospf network point-to-point
% endif
ip ospf hello-interval 2
ip ospf dead-interval 6
ip ospf retransmit-interval 5
"""
return self.render_text(text, data)
class FRROspfv3(FrrService, ConfigService):
@ -185,8 +228,8 @@ class FRROspfv3(FrrService, ConfigService):
"""
name: str = "FRROSPFv3"
shutdown: List[str] = ["killall ospf6d"]
validate: List[str] = ["pidof ospf6d"]
shutdown: list[str] = ["killall ospf6d"]
validate: list[str] = ["pidof ospf6d"]
ipv4_routing: bool = True
ipv6_routing: bool = True
@ -222,8 +265,8 @@ class FRRBgp(FrrService, ConfigService):
"""
name: str = "FRRBGP"
shutdown: List[str] = ["killall bgpd"]
validate: List[str] = ["pidof bgpd"]
shutdown: list[str] = ["killall bgpd"]
validate: list[str] = ["pidof bgpd"]
custom_needed: bool = True
ipv4_routing: bool = True
ipv6_routing: bool = True
@ -252,8 +295,8 @@ class FRRRip(FrrService, ConfigService):
"""
name: str = "FRRRIP"
shutdown: List[str] = ["killall ripd"]
validate: List[str] = ["pidof ripd"]
shutdown: list[str] = ["killall ripd"]
validate: list[str] = ["pidof ripd"]
ipv4_routing: bool = True
def frr_config(self) -> str:
@ -277,8 +320,8 @@ class FRRRipng(FrrService, ConfigService):
"""
name: str = "FRRRIPNG"
shutdown: List[str] = ["killall ripngd"]
validate: List[str] = ["pidof ripngd"]
shutdown: list[str] = ["killall ripngd"]
validate: list[str] = ["pidof ripngd"]
ipv6_routing: bool = True
def frr_config(self) -> str:
@ -303,8 +346,8 @@ class FRRBabel(FrrService, ConfigService):
"""
name: str = "FRRBabel"
shutdown: List[str] = ["killall babeld"]
validate: List[str] = ["pidof babeld"]
shutdown: list[str] = ["killall babeld"]
validate: list[str] = ["pidof babeld"]
ipv6_routing: bool = True
def frr_config(self) -> str:
@ -324,7 +367,7 @@ class FRRBabel(FrrService, ConfigService):
return self.render_text(text, data)
def frr_iface_config(self, iface: CoreInterface) -> str:
if isinstance(iface.net, (WlanNode, EmaneNet)):
if is_wireless(iface.net):
text = """
babel wireless
no babel split-horizon
@ -343,8 +386,8 @@ class FRRpimd(FrrService, ConfigService):
"""
name: str = "FRRpimd"
shutdown: List[str] = ["killall pimd"]
validate: List[str] = ["pidof pimd"]
shutdown: list[str] = ["killall pimd"]
validate: list[str] = ["pidof pimd"]
ipv4_routing: bool = True
def frr_config(self) -> str:

View file

@ -48,6 +48,10 @@ bootdaemon()
flags="$flags -6"
fi
if [ "$1" = "ospfd" ]; then
flags="$flags --apiserver"
fi
#force FRR to use CORE generated conf file
flags="$flags -d -f $FRR_CONF"
$FRR_SBIN_DIR/$1 $flags

View file

@ -1,4 +1,4 @@
from typing import Any, Dict, List
from typing import Any
from core import utils
from core.config import Configuration
@ -10,18 +10,18 @@ GROUP: str = "ProtoSvc"
class MgenSinkService(ConfigService):
name: str = "MGEN_Sink"
group: str = GROUP
directories: List[str] = []
files: List[str] = ["mgensink.sh", "sink.mgen"]
executables: List[str] = ["mgen"]
dependencies: List[str] = []
startup: List[str] = ["bash mgensink.sh"]
validate: List[str] = ["pidof mgen"]
shutdown: List[str] = ["killall mgen"]
directories: list[str] = []
files: list[str] = ["mgensink.sh", "sink.mgen"]
executables: list[str] = ["mgen"]
dependencies: list[str] = []
startup: list[str] = ["bash mgensink.sh"]
validate: list[str] = ["pidof mgen"]
shutdown: list[str] = ["killall mgen"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
ifnames = []
for iface in self.node.get_ifaces():
name = utils.sysctl_devname(iface.name)
@ -32,18 +32,18 @@ class MgenSinkService(ConfigService):
class NrlNhdp(ConfigService):
name: str = "NHDP"
group: str = GROUP
directories: List[str] = []
files: List[str] = ["nrlnhdp.sh"]
executables: List[str] = ["nrlnhdp"]
dependencies: List[str] = []
startup: List[str] = ["bash nrlnhdp.sh"]
validate: List[str] = ["pidof nrlnhdp"]
shutdown: List[str] = ["killall nrlnhdp"]
directories: list[str] = []
files: list[str] = ["nrlnhdp.sh"]
executables: list[str] = ["nrlnhdp"]
dependencies: list[str] = []
startup: list[str] = ["bash nrlnhdp.sh"]
validate: list[str] = ["pidof nrlnhdp"]
shutdown: list[str] = ["killall nrlnhdp"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
has_smf = "SMF" in self.node.config_services
ifnames = []
for iface in self.node.get_ifaces(control=False):
@ -54,19 +54,18 @@ class NrlNhdp(ConfigService):
class NrlSmf(ConfigService):
name: str = "SMF"
group: str = GROUP
directories: List[str] = []
files: List[str] = ["startsmf.sh"]
executables: List[str] = ["nrlsmf", "killall"]
dependencies: List[str] = []
startup: List[str] = ["bash startsmf.sh"]
validate: List[str] = ["pidof nrlsmf"]
shutdown: List[str] = ["killall nrlsmf"]
directories: list[str] = []
files: list[str] = ["startsmf.sh"]
executables: list[str] = ["nrlsmf", "killall"]
dependencies: list[str] = []
startup: list[str] = ["bash startsmf.sh"]
validate: list[str] = ["pidof nrlsmf"]
shutdown: list[str] = ["killall nrlsmf"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
has_arouted = "arouted" in self.node.config_services
def data(self) -> dict[str, Any]:
has_nhdp = "NHDP" in self.node.config_services
has_olsr = "OLSR" in self.node.config_services
ifnames = []
@ -78,29 +77,25 @@ class NrlSmf(ConfigService):
ip4_prefix = f"{ip4.ip}/{24}"
break
return dict(
has_arouted=has_arouted,
has_nhdp=has_nhdp,
has_olsr=has_olsr,
ifnames=ifnames,
ip4_prefix=ip4_prefix,
has_nhdp=has_nhdp, has_olsr=has_olsr, ifnames=ifnames, ip4_prefix=ip4_prefix
)
class NrlOlsr(ConfigService):
name: str = "OLSR"
group: str = GROUP
directories: List[str] = []
files: List[str] = ["nrlolsrd.sh"]
executables: List[str] = ["nrlolsrd"]
dependencies: List[str] = []
startup: List[str] = ["bash nrlolsrd.sh"]
validate: List[str] = ["pidof nrlolsrd"]
shutdown: List[str] = ["killall nrlolsrd"]
directories: list[str] = []
files: list[str] = ["nrlolsrd.sh"]
executables: list[str] = ["nrlolsrd"]
dependencies: list[str] = []
startup: list[str] = ["bash nrlolsrd.sh"]
validate: list[str] = ["pidof nrlolsrd"]
shutdown: list[str] = ["killall nrlolsrd"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
has_smf = "SMF" in self.node.config_services
has_zebra = "zebra" in self.node.config_services
ifname = None
@ -113,18 +108,18 @@ class NrlOlsr(ConfigService):
class NrlOlsrv2(ConfigService):
name: str = "OLSRv2"
group: str = GROUP
directories: List[str] = []
files: List[str] = ["nrlolsrv2.sh"]
executables: List[str] = ["nrlolsrv2"]
dependencies: List[str] = []
startup: List[str] = ["bash nrlolsrv2.sh"]
validate: List[str] = ["pidof nrlolsrv2"]
shutdown: List[str] = ["killall nrlolsrv2"]
directories: list[str] = []
files: list[str] = ["nrlolsrv2.sh"]
executables: list[str] = ["nrlolsrv2"]
dependencies: list[str] = []
startup: list[str] = ["bash nrlolsrv2.sh"]
validate: list[str] = ["pidof nrlolsrv2"]
shutdown: list[str] = ["killall nrlolsrv2"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
has_smf = "SMF" in self.node.config_services
ifnames = []
for iface in self.node.get_ifaces(control=False):
@ -135,18 +130,18 @@ class NrlOlsrv2(ConfigService):
class OlsrOrg(ConfigService):
name: str = "OLSRORG"
group: str = GROUP
directories: List[str] = ["/etc/olsrd"]
files: List[str] = ["olsrd.sh", "/etc/olsrd/olsrd.conf"]
executables: List[str] = ["olsrd"]
dependencies: List[str] = []
startup: List[str] = ["bash olsrd.sh"]
validate: List[str] = ["pidof olsrd"]
shutdown: List[str] = ["killall olsrd"]
directories: list[str] = ["/etc/olsrd"]
files: list[str] = ["olsrd.sh", "/etc/olsrd/olsrd.conf"]
executables: list[str] = ["olsrd"]
dependencies: list[str] = []
startup: list[str] = ["bash olsrd.sh"]
validate: list[str] = ["pidof olsrd"]
shutdown: list[str] = ["killall olsrd"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
has_smf = "SMF" in self.node.config_services
ifnames = []
for iface in self.node.get_ifaces(control=False):
@ -157,37 +152,13 @@ class OlsrOrg(ConfigService):
class MgenActor(ConfigService):
name: str = "MgenActor"
group: str = GROUP
directories: List[str] = []
files: List[str] = ["start_mgen_actor.sh"]
executables: List[str] = ["mgen"]
dependencies: List[str] = []
startup: List[str] = ["bash start_mgen_actor.sh"]
validate: List[str] = ["pidof mgen"]
shutdown: List[str] = ["killall mgen"]
directories: list[str] = []
files: list[str] = ["start_mgen_actor.sh"]
executables: list[str] = ["mgen"]
dependencies: list[str] = []
startup: list[str] = ["bash start_mgen_actor.sh"]
validate: list[str] = ["pidof mgen"]
shutdown: list[str] = ["killall mgen"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
class Arouted(ConfigService):
name: str = "arouted"
group: str = GROUP
directories: List[str] = []
files: List[str] = ["startarouted.sh"]
executables: List[str] = ["arouted"]
dependencies: List[str] = []
startup: List[str] = ["bash startarouted.sh"]
validate: List[str] = ["pidof arouted"]
shutdown: List[str] = ["pkill arouted"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
ip4_prefix = None
for iface in self.node.get_ifaces(control=False):
ip4 = iface.get_ip4()
if ip4:
ip4_prefix = f"{ip4.ip}/{24}"
break
return dict(ip4_prefix=ip4_prefix)
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}

View file

@ -1,15 +0,0 @@
#!/bin/sh
for f in "/tmp/${node.name}_smf"; do
count=1
until [ -e "$f" ]; do
if [ $count -eq 10 ]; then
echo "ERROR: nrlmsf pipe not found: $f" >&2
exit 1
fi
sleep 0.1
count=$(($count + 1))
done
done
ip route add ${ip4_prefix} dev lo
arouted instance ${node.name}_smf tap ${node.name}_tap stability 10 2>&1 > /var/log/arouted.log &

View file

@ -1,8 +1,5 @@
<%
ifaces = ",".join(ifnames)
arouted = ""
if has_arouted:
arouted = "tap %s_tap unicast %s push lo,%s resequence on" % (node.name, ip4_prefix, ifnames[0])
if has_nhdp:
flood = "ecds"
elif has_olsr:
@ -12,4 +9,4 @@
%>
#!/bin/sh
# auto-generated by NrlSmf service
nrlsmf instance ${node.name}_smf ${ifaces} ${arouted} ${flood} hash MD5 log /var/log/nrlsmf.log < /dev/null > /dev/null 2>&1 &
nrlsmf instance ${node.name}_smf ${flood} ${ifaces} hash MD5 log /var/log/nrlsmf.log < /dev/null > /dev/null 2>&1 &

View file

@ -1,19 +1,31 @@
import abc
import logging
from typing import Any, Dict, List
from typing import Any
from core.config import Configuration
from core.configservice.base import ConfigService, ConfigServiceMode
from core.emane.nodes import EmaneNet
from core.nodes.base import CoreNodeBase
from core.nodes.base import CoreNodeBase, NodeBase
from core.nodes.interface import DEFAULT_MTU, CoreInterface
from core.nodes.network import WlanNode
from core.nodes.network import PtpNet, WlanNode
from core.nodes.physical import Rj45Node
from core.nodes.wireless import WirelessNode
logger = logging.getLogger(__name__)
GROUP: str = "Quagga"
QUAGGA_STATE_DIR: str = "/var/run/quagga"
def is_wireless(node: NodeBase) -> bool:
"""
Check if the node is a wireless type node.
:param node: node to check type for
:return: True if wireless type, False otherwise
"""
return isinstance(node, (WlanNode, EmaneNet, WirelessNode))
def has_mtu_mismatch(iface: CoreInterface) -> bool:
"""
Helper to detect MTU mismatch and add the appropriate OSPF
@ -55,29 +67,43 @@ def get_router_id(node: CoreNodeBase) -> str:
return "0.0.0.0"
def rj45_check(iface: CoreInterface) -> bool:
"""
Helper to detect whether interface is connected an external RJ45
link.
"""
if iface.net:
for peer_iface in iface.net.get_ifaces():
if peer_iface == iface:
continue
if isinstance(peer_iface.node, Rj45Node):
return True
return False
class Zebra(ConfigService):
name: str = "zebra"
group: str = GROUP
directories: List[str] = ["/usr/local/etc/quagga", "/var/run/quagga"]
files: List[str] = [
directories: list[str] = ["/usr/local/etc/quagga", "/var/run/quagga"]
files: list[str] = [
"/usr/local/etc/quagga/Quagga.conf",
"quaggaboot.sh",
"/usr/local/etc/quagga/vtysh.conf",
]
executables: List[str] = ["zebra"]
dependencies: List[str] = []
startup: List[str] = ["bash quaggaboot.sh zebra"]
validate: List[str] = ["pidof zebra"]
shutdown: List[str] = ["killall zebra"]
executables: list[str] = ["zebra"]
dependencies: list[str] = []
startup: list[str] = ["bash quaggaboot.sh zebra"]
validate: list[str] = ["pidof zebra"]
shutdown: list[str] = ["killall zebra"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
quagga_bin_search = self.node.session.options.get_config(
def data(self) -> dict[str, Any]:
quagga_bin_search = self.node.session.options.get(
"quagga_bin_search", default="/usr/local/bin /usr/bin /usr/lib/quagga"
).strip('"')
quagga_sbin_search = self.node.session.options.get_config(
quagga_sbin_search = self.node.session.options.get(
"quagga_sbin_search", default="/usr/local/sbin /usr/sbin /usr/lib/quagga"
).strip('"')
quagga_state_dir = QUAGGA_STATE_DIR
@ -105,7 +131,13 @@ class Zebra(ConfigService):
ip4s.append(str(ip4))
for ip6 in iface.ip6s:
ip6s.append(str(ip6))
ifaces.append((iface, ip4s, ip6s, iface.control))
configs = []
if not iface.control:
for service in services:
config = service.quagga_iface_config(iface)
if config:
configs.append(config.split("\n"))
ifaces.append((iface, ip4s, ip6s, configs))
return dict(
quagga_bin_search=quagga_bin_search,
@ -121,16 +153,16 @@ class Zebra(ConfigService):
class QuaggaService(abc.ABC):
group: str = GROUP
directories: List[str] = []
files: List[str] = []
executables: List[str] = []
dependencies: List[str] = ["zebra"]
startup: List[str] = []
validate: List[str] = []
shutdown: List[str] = []
directories: list[str] = []
files: list[str] = []
executables: list[str] = []
dependencies: list[str] = ["zebra"]
startup: list[str] = []
validate: list[str] = []
shutdown: list[str] = []
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
ipv4_routing: bool = False
ipv6_routing: bool = False
@ -151,22 +183,37 @@ class Ospfv2(QuaggaService, ConfigService):
"""
name: str = "OSPFv2"
validate: List[str] = ["pidof ospfd"]
shutdown: List[str] = ["killall ospfd"]
validate: list[str] = ["pidof ospfd"]
shutdown: list[str] = ["killall ospfd"]
ipv4_routing: bool = True
def quagga_iface_config(self, iface: CoreInterface) -> str:
if has_mtu_mismatch(iface):
return "ip ospf mtu-ignore"
else:
return ""
has_mtu = has_mtu_mismatch(iface)
has_rj45 = rj45_check(iface)
is_ptp = isinstance(iface.net, PtpNet)
data = dict(has_mtu=has_mtu, is_ptp=is_ptp, has_rj45=has_rj45)
text = """
% if has_mtu:
ip ospf mtu-ignore
% endif
% if has_rj45:
<% return STOP_RENDERING %>
% endif
% if is_ptp:
ip ospf network point-to-point
% endif
ip ospf hello-interval 2
ip ospf dead-interval 6
ip ospf retransmit-interval 5
"""
return self.render_text(text, data)
def quagga_config(self) -> str:
router_id = get_router_id(self.node)
addresses = []
for iface in self.node.get_ifaces(control=False):
for ip4 in iface.ip4s:
addresses.append(str(ip4.ip))
addresses.append(str(ip4))
data = dict(router_id=router_id, addresses=addresses)
text = """
router ospf
@ -187,8 +234,8 @@ class Ospfv3(QuaggaService, ConfigService):
"""
name: str = "OSPFv3"
shutdown: List[str] = ["killall ospf6d"]
validate: List[str] = ["pidof ospf6d"]
shutdown: list[str] = ["killall ospf6d"]
validate: list[str] = ["pidof ospf6d"]
ipv4_routing: bool = True
ipv6_routing: bool = True
@ -229,7 +276,7 @@ class Ospfv3mdr(Ospfv3):
def quagga_iface_config(self, iface: CoreInterface) -> str:
config = super().quagga_iface_config(iface)
if isinstance(iface.net, (WlanNode, EmaneNet)):
if is_wireless(iface.net):
config = self.clean_text(
f"""
{config}
@ -253,15 +300,12 @@ class Bgp(QuaggaService, ConfigService):
"""
name: str = "BGP"
shutdown: List[str] = ["killall bgpd"]
validate: List[str] = ["pidof bgpd"]
shutdown: list[str] = ["killall bgpd"]
validate: list[str] = ["pidof bgpd"]
ipv4_routing: bool = True
ipv6_routing: bool = True
def quagga_config(self) -> str:
return ""
def quagga_iface_config(self, iface: CoreInterface) -> str:
router_id = get_router_id(self.node)
text = f"""
! BGP configuration
@ -275,6 +319,9 @@ class Bgp(QuaggaService, ConfigService):
"""
return self.clean_text(text)
def quagga_iface_config(self, iface: CoreInterface) -> str:
return ""
class Rip(QuaggaService, ConfigService):
"""
@ -282,8 +329,8 @@ class Rip(QuaggaService, ConfigService):
"""
name: str = "RIP"
shutdown: List[str] = ["killall ripd"]
validate: List[str] = ["pidof ripd"]
shutdown: list[str] = ["killall ripd"]
validate: list[str] = ["pidof ripd"]
ipv4_routing: bool = True
def quagga_config(self) -> str:
@ -307,8 +354,8 @@ class Ripng(QuaggaService, ConfigService):
"""
name: str = "RIPNG"
shutdown: List[str] = ["killall ripngd"]
validate: List[str] = ["pidof ripngd"]
shutdown: list[str] = ["killall ripngd"]
validate: list[str] = ["pidof ripngd"]
ipv6_routing: bool = True
def quagga_config(self) -> str:
@ -333,8 +380,8 @@ class Babel(QuaggaService, ConfigService):
"""
name: str = "Babel"
shutdown: List[str] = ["killall babeld"]
validate: List[str] = ["pidof babeld"]
shutdown: list[str] = ["killall babeld"]
validate: list[str] = ["pidof babeld"]
ipv6_routing: bool = True
def quagga_config(self) -> str:
@ -354,7 +401,7 @@ class Babel(QuaggaService, ConfigService):
return self.render_text(text, data)
def quagga_iface_config(self, iface: CoreInterface) -> str:
if isinstance(iface.net, (WlanNode, EmaneNet)):
if is_wireless(iface.net):
text = """
babel wireless
no babel split-horizon
@ -373,8 +420,8 @@ class Xpimd(QuaggaService, ConfigService):
"""
name: str = "Xpimd"
shutdown: List[str] = ["killall xpimd"]
validate: List[str] = ["pidof xpimd"]
shutdown: list[str] = ["killall xpimd"]
validate: list[str] = ["pidof xpimd"]
ipv4_routing: bool = True
def quagga_config(self) -> str:

View file

@ -1,4 +1,4 @@
% for iface, ip4s, ip6s, is_control in ifaces:
% for iface, ip4s, ip6s, configs in ifaces:
interface ${iface.name}
% if want_ip4:
% for addr in ip4s:
@ -10,13 +10,11 @@ interface ${iface.name}
ipv6 address ${addr}
% endfor
% endif
% if not is_control:
% for service in services:
% for line in service.quagga_iface_config(iface).split("\n"):
% for config in configs:
% for line in config:
${line}
% endfor
% endfor
% endif
% endfor
!
% endfor

View file

@ -1,4 +1,4 @@
from typing import Any, Dict, List
from typing import Any
from core.config import ConfigString, Configuration
from core.configservice.base import ConfigService, ConfigServiceMode
@ -9,41 +9,41 @@ GROUP_NAME: str = "Security"
class VpnClient(ConfigService):
name: str = "VPNClient"
group: str = GROUP_NAME
directories: List[str] = []
files: List[str] = ["vpnclient.sh"]
executables: List[str] = ["openvpn", "ip", "killall"]
dependencies: List[str] = []
startup: List[str] = ["bash vpnclient.sh"]
validate: List[str] = ["pidof openvpn"]
shutdown: List[str] = ["killall openvpn"]
directories: list[str] = []
files: list[str] = ["vpnclient.sh"]
executables: list[str] = ["openvpn", "ip", "killall"]
dependencies: list[str] = []
startup: list[str] = ["bash vpnclient.sh"]
validate: list[str] = ["pidof openvpn"]
shutdown: list[str] = ["killall openvpn"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = [
default_configs: list[Configuration] = [
ConfigString(id="keydir", label="Key Dir", default="/etc/core/keys"),
ConfigString(id="keyname", label="Key Name", default="client1"),
ConfigString(id="server", label="Server", default="10.0.2.10"),
]
modes: Dict[str, Dict[str, str]] = {}
modes: dict[str, dict[str, str]] = {}
class VpnServer(ConfigService):
name: str = "VPNServer"
group: str = GROUP_NAME
directories: List[str] = []
files: List[str] = ["vpnserver.sh"]
executables: List[str] = ["openvpn", "ip", "killall"]
dependencies: List[str] = []
startup: List[str] = ["bash vpnserver.sh"]
validate: List[str] = ["pidof openvpn"]
shutdown: List[str] = ["killall openvpn"]
directories: list[str] = []
files: list[str] = ["vpnserver.sh"]
executables: list[str] = ["openvpn", "ip", "killall"]
dependencies: list[str] = []
startup: list[str] = ["bash vpnserver.sh"]
validate: list[str] = ["pidof openvpn"]
shutdown: list[str] = ["killall openvpn"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = [
default_configs: list[Configuration] = [
ConfigString(id="keydir", label="Key Dir", default="/etc/core/keys"),
ConfigString(id="keyname", label="Key Name", default="server"),
ConfigString(id="subnet", label="Subnet", default="10.0.200.0"),
]
modes: Dict[str, Dict[str, str]] = {}
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
address = None
for iface in self.node.get_ifaces(control=False):
ip4 = iface.get_ip4()
@ -56,48 +56,48 @@ class VpnServer(ConfigService):
class IPsec(ConfigService):
name: str = "IPsec"
group: str = GROUP_NAME
directories: List[str] = []
files: List[str] = ["ipsec.sh"]
executables: List[str] = ["racoon", "ip", "setkey", "killall"]
dependencies: List[str] = []
startup: List[str] = ["bash ipsec.sh"]
validate: List[str] = ["pidof racoon"]
shutdown: List[str] = ["killall racoon"]
directories: list[str] = []
files: list[str] = ["ipsec.sh"]
executables: list[str] = ["racoon", "ip", "setkey", "killall"]
dependencies: list[str] = []
startup: list[str] = ["bash ipsec.sh"]
validate: list[str] = ["pidof racoon"]
shutdown: list[str] = ["killall racoon"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
class Firewall(ConfigService):
name: str = "Firewall"
group: str = GROUP_NAME
directories: List[str] = []
files: List[str] = ["firewall.sh"]
executables: List[str] = ["iptables"]
dependencies: List[str] = []
startup: List[str] = ["bash firewall.sh"]
validate: List[str] = []
shutdown: List[str] = []
directories: list[str] = []
files: list[str] = ["firewall.sh"]
executables: list[str] = ["iptables"]
dependencies: list[str] = []
startup: list[str] = ["bash firewall.sh"]
validate: list[str] = []
shutdown: list[str] = []
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
class Nat(ConfigService):
name: str = "NAT"
group: str = GROUP_NAME
directories: List[str] = []
files: List[str] = ["nat.sh"]
executables: List[str] = ["iptables"]
dependencies: List[str] = []
startup: List[str] = ["bash nat.sh"]
validate: List[str] = []
shutdown: List[str] = []
directories: list[str] = []
files: list[str] = ["nat.sh"]
executables: list[str] = ["iptables"]
dependencies: list[str] = []
startup: list[str] = ["bash nat.sh"]
validate: list[str] = []
shutdown: list[str] = []
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
ifnames = []
for iface in self.node.get_ifaces(control=False):
ifnames.append(iface.name)

View file

@ -1,4 +1,4 @@
from typing import Any, Dict, List
from typing import Any
import netaddr
@ -12,18 +12,18 @@ GROUP_NAME = "Utility"
class DefaultRouteService(ConfigService):
name: str = "DefaultRoute"
group: str = GROUP_NAME
directories: List[str] = []
files: List[str] = ["defaultroute.sh"]
executables: List[str] = ["ip"]
dependencies: List[str] = []
startup: List[str] = ["bash defaultroute.sh"]
validate: List[str] = []
shutdown: List[str] = []
directories: list[str] = []
files: list[str] = ["defaultroute.sh"]
executables: list[str] = ["ip"]
dependencies: list[str] = []
startup: list[str] = ["bash defaultroute.sh"]
validate: list[str] = []
shutdown: list[str] = []
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
# only add default routes for linked routing nodes
routes = []
ifaces = self.node.get_ifaces()
@ -40,18 +40,18 @@ class DefaultRouteService(ConfigService):
class DefaultMulticastRouteService(ConfigService):
name: str = "DefaultMulticastRoute"
group: str = GROUP_NAME
directories: List[str] = []
files: List[str] = ["defaultmroute.sh"]
executables: List[str] = []
dependencies: List[str] = []
startup: List[str] = ["bash defaultmroute.sh"]
validate: List[str] = []
shutdown: List[str] = []
directories: list[str] = []
files: list[str] = ["defaultmroute.sh"]
executables: list[str] = []
dependencies: list[str] = []
startup: list[str] = ["bash defaultmroute.sh"]
validate: list[str] = []
shutdown: list[str] = []
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
ifname = None
for iface in self.node.get_ifaces(control=False):
ifname = iface.name
@ -62,18 +62,18 @@ class DefaultMulticastRouteService(ConfigService):
class StaticRouteService(ConfigService):
name: str = "StaticRoute"
group: str = GROUP_NAME
directories: List[str] = []
files: List[str] = ["staticroute.sh"]
executables: List[str] = []
dependencies: List[str] = []
startup: List[str] = ["bash staticroute.sh"]
validate: List[str] = []
shutdown: List[str] = []
directories: list[str] = []
files: list[str] = ["staticroute.sh"]
executables: list[str] = []
dependencies: list[str] = []
startup: list[str] = ["bash staticroute.sh"]
validate: list[str] = []
shutdown: list[str] = []
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
routes = []
for iface in self.node.get_ifaces(control=False):
for ip in iface.ips():
@ -90,18 +90,18 @@ class StaticRouteService(ConfigService):
class IpForwardService(ConfigService):
name: str = "IPForward"
group: str = GROUP_NAME
directories: List[str] = []
files: List[str] = ["ipforward.sh"]
executables: List[str] = ["sysctl"]
dependencies: List[str] = []
startup: List[str] = ["bash ipforward.sh"]
validate: List[str] = []
shutdown: List[str] = []
directories: list[str] = []
files: list[str] = ["ipforward.sh"]
executables: list[str] = ["sysctl"]
dependencies: list[str] = []
startup: list[str] = ["bash ipforward.sh"]
validate: list[str] = []
shutdown: list[str] = []
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
devnames = []
for iface in self.node.get_ifaces():
devname = utils.sysctl_devname(iface.name)
@ -112,18 +112,18 @@ class IpForwardService(ConfigService):
class SshService(ConfigService):
name: str = "SSH"
group: str = GROUP_NAME
directories: List[str] = ["/etc/ssh", "/var/run/sshd"]
files: List[str] = ["startsshd.sh", "/etc/ssh/sshd_config"]
executables: List[str] = ["sshd"]
dependencies: List[str] = []
startup: List[str] = ["bash startsshd.sh"]
validate: List[str] = []
shutdown: List[str] = ["killall sshd"]
directories: list[str] = ["/etc/ssh", "/var/run/sshd"]
files: list[str] = ["startsshd.sh", "/etc/ssh/sshd_config"]
executables: list[str] = ["sshd"]
dependencies: list[str] = []
startup: list[str] = ["bash startsshd.sh"]
validate: list[str] = []
shutdown: list[str] = ["killall sshd"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
return dict(
sshcfgdir=self.directories[0],
sshstatedir=self.directories[1],
@ -134,18 +134,18 @@ class SshService(ConfigService):
class DhcpService(ConfigService):
name: str = "DHCP"
group: str = GROUP_NAME
directories: List[str] = ["/etc/dhcp", "/var/lib/dhcp"]
files: List[str] = ["/etc/dhcp/dhcpd.conf"]
executables: List[str] = ["dhcpd"]
dependencies: List[str] = []
startup: List[str] = ["touch /var/lib/dhcp/dhcpd.leases", "dhcpd"]
validate: List[str] = ["pidof dhcpd"]
shutdown: List[str] = ["killall dhcpd"]
directories: list[str] = ["/etc/dhcp", "/var/lib/dhcp"]
files: list[str] = ["/etc/dhcp/dhcpd.conf"]
executables: list[str] = ["dhcpd"]
dependencies: list[str] = []
startup: list[str] = ["touch /var/lib/dhcp/dhcpd.leases", "dhcpd"]
validate: list[str] = ["pidof dhcpd"]
shutdown: list[str] = ["killall dhcpd"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
subnets = []
for iface in self.node.get_ifaces(control=False):
for ip4 in iface.ip4s:
@ -162,18 +162,18 @@ class DhcpService(ConfigService):
class DhcpClientService(ConfigService):
name: str = "DHCPClient"
group: str = GROUP_NAME
directories: List[str] = []
files: List[str] = ["startdhcpclient.sh"]
executables: List[str] = ["dhclient"]
dependencies: List[str] = []
startup: List[str] = ["bash startdhcpclient.sh"]
validate: List[str] = ["pidof dhclient"]
shutdown: List[str] = ["killall dhclient"]
directories: list[str] = []
files: list[str] = ["startdhcpclient.sh"]
executables: list[str] = ["dhclient"]
dependencies: list[str] = []
startup: list[str] = ["bash startdhcpclient.sh"]
validate: list[str] = ["pidof dhclient"]
shutdown: list[str] = ["killall dhclient"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
ifnames = []
for iface in self.node.get_ifaces(control=False):
ifnames.append(iface.name)
@ -183,56 +183,56 @@ class DhcpClientService(ConfigService):
class FtpService(ConfigService):
name: str = "FTP"
group: str = GROUP_NAME
directories: List[str] = ["/var/run/vsftpd/empty", "/var/ftp"]
files: List[str] = ["vsftpd.conf"]
executables: List[str] = ["vsftpd"]
dependencies: List[str] = []
startup: List[str] = ["vsftpd ./vsftpd.conf"]
validate: List[str] = ["pidof vsftpd"]
shutdown: List[str] = ["killall vsftpd"]
directories: list[str] = ["/var/run/vsftpd/empty", "/var/ftp"]
files: list[str] = ["vsftpd.conf"]
executables: list[str] = ["vsftpd"]
dependencies: list[str] = []
startup: list[str] = ["vsftpd ./vsftpd.conf"]
validate: list[str] = ["pidof vsftpd"]
shutdown: list[str] = ["killall vsftpd"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
class PcapService(ConfigService):
name: str = "pcap"
group: str = GROUP_NAME
directories: List[str] = []
files: List[str] = ["pcap.sh"]
executables: List[str] = ["tcpdump"]
dependencies: List[str] = []
startup: List[str] = ["bash pcap.sh start"]
validate: List[str] = ["pidof tcpdump"]
shutdown: List[str] = ["bash pcap.sh stop"]
directories: list[str] = []
files: list[str] = ["pcap.sh"]
executables: list[str] = ["tcpdump"]
dependencies: list[str] = []
startup: list[str] = ["bash pcap.sh start"]
validate: list[str] = ["pidof tcpdump"]
shutdown: list[str] = ["bash pcap.sh stop"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
ifnames = []
for iface in self.node.get_ifaces(control=False):
ifnames.append(iface.name)
return dict()
return dict(ifnames=ifnames)
class RadvdService(ConfigService):
name: str = "radvd"
group: str = GROUP_NAME
directories: List[str] = ["/etc/radvd", "/var/run/radvd"]
files: List[str] = ["/etc/radvd/radvd.conf"]
executables: List[str] = ["radvd"]
dependencies: List[str] = []
startup: List[str] = [
directories: list[str] = ["/etc/radvd", "/var/run/radvd"]
files: list[str] = ["/etc/radvd/radvd.conf"]
executables: list[str] = ["radvd"]
dependencies: list[str] = []
startup: list[str] = [
"radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log"
]
validate: List[str] = ["pidof radvd"]
shutdown: List[str] = ["pkill radvd"]
validate: list[str] = ["pidof radvd"]
shutdown: list[str] = ["pkill radvd"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
ifaces = []
for iface in self.node.get_ifaces(control=False):
prefixes = []
@ -247,22 +247,22 @@ class RadvdService(ConfigService):
class AtdService(ConfigService):
name: str = "atd"
group: str = GROUP_NAME
directories: List[str] = ["/var/spool/cron/atjobs", "/var/spool/cron/atspool"]
files: List[str] = ["startatd.sh"]
executables: List[str] = ["atd"]
dependencies: List[str] = []
startup: List[str] = ["bash startatd.sh"]
validate: List[str] = ["pidof atd"]
shutdown: List[str] = ["pkill atd"]
directories: list[str] = ["/var/spool/cron/atjobs", "/var/spool/cron/atspool"]
files: list[str] = ["startatd.sh"]
executables: list[str] = ["atd"]
dependencies: list[str] = []
startup: list[str] = ["bash startatd.sh"]
validate: list[str] = ["pidof atd"]
shutdown: list[str] = ["pkill atd"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
class HttpService(ConfigService):
name: str = "HTTP"
group: str = GROUP_NAME
directories: List[str] = [
directories: list[str] = [
"/etc/apache2",
"/var/run/apache2",
"/var/log/apache2",
@ -270,21 +270,21 @@ class HttpService(ConfigService):
"/var/lock/apache2",
"/var/www",
]
files: List[str] = [
files: list[str] = [
"/etc/apache2/apache2.conf",
"/etc/apache2/envvars",
"/var/www/index.html",
]
executables: List[str] = ["apache2ctl"]
dependencies: List[str] = []
startup: List[str] = ["chown www-data /var/lock/apache2", "apache2ctl start"]
validate: List[str] = ["pidof apache2"]
shutdown: List[str] = ["apache2ctl stop"]
executables: list[str] = ["apache2ctl"]
dependencies: list[str] = []
startup: list[str] = ["chown www-data /var/lock/apache2", "apache2ctl start"]
validate: list[str] = ["pidof apache2"]
shutdown: list[str] = ["apache2ctl stop"]
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
default_configs: List[Configuration] = []
modes: Dict[str, Dict[str, str]] = {}
default_configs: list[Configuration] = []
modes: dict[str, dict[str, str]] = {}
def data(self) -> Dict[str, Any]:
def data(self) -> dict[str, Any]:
ifaces = []
for iface in self.node.get_ifaces(control=False):
ifaces.append(iface)

View file

@ -1,5 +1,5 @@
# auto-generated by RADVD service (utility.py)
% for ifname, prefixes in values:
% for ifname, prefixes in ifaces:
interface ${ifname}
{
AdvSendAdvert on;

View file

@ -13,4 +13,5 @@ sysctl -w net.ipv4.conf.default.rp_filter=0
sysctl -w net.ipv4.conf.${devname}.forwarding=1
sysctl -w net.ipv4.conf.${devname}.send_redirects=0
sysctl -w net.ipv4.conf.${devname}.rp_filter=0
sysctl -w net.ipv6.conf.${devname}.forwarding=1
% endfor

View file

@ -3,7 +3,7 @@
# (-s snap length, -C limit pcap file length, -n disable name resolution)
if [ "x$1" = "xstart" ]; then
% for ifname in ifnames:
tcpdump -s 12288 -C 10 -n -w ${node.name}.${ifname}.pcap -i ${ifname} < /dev/null &
tcpdump -s 12288 -C 10 -n -w ${node.name}.${ifname}.pcap -i ${ifname} > /dev/null 2>&1 &
% endfor
elif [ "x$1" = "xstop" ]; then
mkdir -p $SESSION_DIR/pcap

View file

@ -6,18 +6,18 @@ import logging
import os
import threading
from enum import Enum
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union
from typing import TYPE_CHECKING, Optional, Union
from core import utils
from core.emane.emanemodel import EmaneModel
from core.emane.linkmonitor import EmaneLinkMonitor
from core.emane.modelmanager import EmaneModelManager
from core.emane.nodes import EmaneNet
from core.emane.nodes import EmaneNet, TunTap
from core.emulator.data import LinkData
from core.emulator.enumerations import LinkTypes, MessageFlags, RegisterTlvs
from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNetworkBase, CoreNode, NodeBase
from core.nodes.interface import CoreInterface, TunTap
from core.nodes.base import CoreNode, NodeBase
from core.nodes.interface import CoreInterface
from core.xml import emanexml
logger = logging.getLogger(__name__)
@ -45,8 +45,6 @@ except ImportError:
EventServiceException = None
logger.debug("compatible emane python bindings not installed")
DEFAULT_EMANE_PREFIX = "/usr"
DEFAULT_DEV = "ctrl0"
DEFAULT_LOG_LEVEL: int = 3
@ -128,32 +126,32 @@ class EmaneManager:
"""
super().__init__()
self.session: "Session" = session
self.nems_to_ifaces: Dict[int, CoreInterface] = {}
self.ifaces_to_nems: Dict[CoreInterface, int] = {}
self._emane_nets: Dict[int, EmaneNet] = {}
self.nems_to_ifaces: dict[int, CoreInterface] = {}
self.ifaces_to_nems: dict[CoreInterface, int] = {}
self._emane_nets: dict[int, EmaneNet] = {}
self._emane_node_lock: threading.Lock = threading.Lock()
# port numbers are allocated from these counters
self.platformport: int = self.session.options.get_config_int(
self.platformport: int = self.session.options.get_int(
"emane_platform_port", 8100
)
self.transformport: int = self.session.options.get_config_int(
self.transformport: int = self.session.options.get_int(
"emane_transform_port", 8200
)
self.doeventloop: bool = False
self.eventmonthread: Optional[threading.Thread] = None
# model for global EMANE configuration options
self.node_configs: Dict[int, Dict[str, Dict[str, str]]] = {}
self.node_models: Dict[int, str] = {}
self.node_configs: dict[int, dict[str, dict[str, str]]] = {}
self.node_models: dict[int, str] = {}
# link monitor
self.link_monitor: EmaneLinkMonitor = EmaneLinkMonitor(self)
# emane event monitoring
self.services: Dict[str, EmaneEventService] = {}
self.nem_service: Dict[int, EmaneEventService] = {}
self.services: dict[str, EmaneEventService] = {}
self.nem_service: dict[int, EmaneEventService] = {}
def next_nem_id(self, iface: CoreInterface) -> int:
nem_id = self.session.options.get_config_int("nem_id_start")
nem_id = self.session.options.get_int("nem_id_start")
while nem_id in self.nems_to_ifaces:
nem_id += 1
self.nems_to_ifaces[nem_id] = iface
@ -163,7 +161,7 @@ class EmaneManager:
def get_config(
self, key: int, model: str, default: bool = True
) -> Optional[Dict[str, str]]:
) -> Optional[dict[str, str]]:
"""
Get the current or default configuration for an emane model.
@ -183,7 +181,7 @@ class EmaneManager:
config = model_class.default_values()
return config
def set_config(self, key: int, model: str, config: Dict[str, str] = None) -> None:
def set_config(self, key: int, model: str, config: dict[str, str] = None) -> None:
"""
Sets and update the provided configuration against the default model
or currently set emane model configuration.
@ -201,7 +199,7 @@ class EmaneManager:
model_configs = self.node_configs.setdefault(key, {})
model_configs[model] = model_config
def get_model(self, model_name: str) -> Type[EmaneModel]:
def get_model(self, model_name: str) -> type[EmaneModel]:
"""
Convenience method for getting globally loaded emane models.
@ -213,7 +211,7 @@ class EmaneManager:
def get_iface_config(
self, emane_net: EmaneNet, iface: CoreInterface
) -> Dict[str, str]:
) -> dict[str, str]:
"""
Retrieve configuration for a given interface, first checking for interface
specific config, node specific config, network specific config, and finally
@ -223,12 +221,10 @@ class EmaneManager:
:param iface: interface running emane
:return: net, node, or interface model configuration
"""
model_name = emane_net.model.name
config = None
model_name = emane_net.wireless_model.name
# try to retrieve interface specific configuration
if iface.node_id is not None:
key = utils.iface_config_id(iface.node.id, iface.node_id)
config = self.get_config(key, model_name, default=False)
key = utils.iface_config_id(iface.node.id, iface.id)
config = self.get_config(key, model_name, default=False)
# attempt to retrieve node specific config, when iface config is not present
if not config:
config = self.get_config(iface.node.id, model_name, default=False)
@ -239,7 +235,7 @@ class EmaneManager:
config = self.get_config(emane_net.id, model_name, default=False)
# return default config values, when a config is not present
if not config:
config = emane_net.model.default_values()
config = emane_net.wireless_model.default_values()
return config
def config_reset(self, node_id: int = None) -> None:
@ -264,7 +260,7 @@ class EmaneManager:
)
self._emane_nets[emane_net.id] = emane_net
def getnodes(self) -> Set[CoreNode]:
def getnodes(self) -> set[CoreNode]:
"""
Return a set of CoreNodes that are linked to an EMANE network,
e.g. containers having one or more radio interfaces.
@ -272,7 +268,8 @@ class EmaneManager:
nodes = set()
for emane_net in self._emane_nets.values():
for iface in emane_net.get_ifaces():
nodes.add(iface.node)
if isinstance(iface.node, CoreNode):
nodes.add(iface.node)
return nodes
def setup(self) -> EmaneState:
@ -323,7 +320,7 @@ class EmaneManager:
for emane_net, iface in self.get_ifaces():
self.start_iface(emane_net, iface)
def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None:
def start_iface(self, emane_net: EmaneNet, iface: TunTap) -> None:
nem_id = self.next_nem_id(iface)
nem_port = self.get_nem_port(iface)
logger.info(
@ -338,10 +335,10 @@ class EmaneManager:
self.start_daemon(iface)
self.install_iface(iface, config)
def get_ifaces(self) -> List[Tuple[EmaneNet, CoreInterface]]:
def get_ifaces(self) -> list[tuple[EmaneNet, TunTap]]:
ifaces = []
for emane_net in self._emane_nets.values():
if not emane_net.model:
if not emane_net.wireless_model:
logger.error("emane net(%s) has no model", emane_net.name)
continue
for iface in emane_net.get_ifaces():
@ -352,11 +349,12 @@ class EmaneManager:
iface.name,
)
continue
ifaces.append((emane_net, iface))
return sorted(ifaces, key=lambda x: (x[1].node.id, x[1].node_id))
if isinstance(iface, TunTap):
ifaces.append((emane_net, iface))
return sorted(ifaces, key=lambda x: (x[1].node.id, x[1].id))
def setup_control_channels(
self, nem_id: int, iface: CoreInterface, config: Dict[str, str]
self, nem_id: int, iface: CoreInterface, config: dict[str, str]
) -> None:
node = iface.node
# setup ota device
@ -384,6 +382,8 @@ class EmaneManager:
service = EmaneEventService(
self, event_net.brname, eventgroup, int(eventport)
)
if self.doeventmonitor():
service.start()
self.services[event_net.brname] = service
self.nem_service[nem_id] = service
except EventServiceException:
@ -419,7 +419,7 @@ class EmaneManager:
def get_nem_position(
self, iface: CoreInterface
) -> Optional[Tuple[int, float, float, int]]:
) -> Optional[tuple[int, float, float, int]]:
"""
Retrieves nem position for a given interface.
@ -453,7 +453,7 @@ class EmaneManager:
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
self.publish_event(nemid, event, send_all=True)
def set_nem_positions(self, moved_ifaces: List[CoreInterface]) -> None:
def set_nem_positions(self, moved_ifaces: list[CoreInterface]) -> None:
"""
Several NEMs have moved, from e.g. a WaypointMobilityModel
calculation. Generate an EMANE Location Event having several
@ -480,11 +480,11 @@ class EmaneManager:
try:
with path.open("a") as f:
f.write(f"{iface.node.name} {iface.name} {nem_id}\n")
except IOError:
except OSError:
logger.exception("error writing to emane nem file")
def links_enabled(self) -> bool:
return self.session.options.get_config_int("link_enabled") == 1
return self.session.options.get_int("link_enabled") == 1
def poststartup(self) -> None:
"""
@ -498,7 +498,7 @@ class EmaneManager:
"post startup for emane node: %s - %s", emane_net.id, emane_net.name
)
for iface in emane_net.get_ifaces():
emane_net.model.post_startup(iface)
emane_net.wireless_model.post_startup(iface)
if events_enabled:
iface.setposition()
@ -550,9 +550,11 @@ class EmaneManager:
emane_net = self._emane_nets[node_id]
logger.debug("checking emane model for node: %s", node_id)
# skip nodes that already have a model set
if emane_net.model:
if emane_net.wireless_model:
logger.debug(
"node(%s) already has model(%s)", emane_net.id, emane_net.model.name
"node(%s) already has model(%s)",
emane_net.id,
emane_net.wireless_model.name,
)
continue
# set model configured for node, due to legacy messaging configuration
@ -602,8 +604,8 @@ class EmaneManager:
"""
node = iface.node
loglevel = str(DEFAULT_LOG_LEVEL)
cfgloglevel = self.session.options.get_config_int("emane_log_level")
realtime = self.session.options.get_config_bool("emane_realtime", default=True)
cfgloglevel = self.session.options.get_int("emane_log_level", 2)
realtime = self.session.options.get_bool("emane_realtime", True)
if cfgloglevel:
logger.info("setting user-defined emane log level: %d", cfgloglevel)
loglevel = str(cfgloglevel)
@ -622,9 +624,9 @@ class EmaneManager:
args = f"{emanecmd} -f {log_file} {platform_xml}"
node.host_cmd(args, cwd=self.session.directory)
def install_iface(self, iface: CoreInterface, config: Dict[str, str]) -> None:
def install_iface(self, iface: TunTap, config: dict[str, str]) -> None:
external = config.get("external", "0")
if isinstance(iface, TunTap) and external == "0":
if external == "0":
iface.set_ips()
# at this point we register location handlers for generating
# EMANE location events
@ -636,20 +638,13 @@ class EmaneManager:
"""
Returns boolean whether or not EMANE events will be monitored.
"""
# this support must be explicitly turned on; by default, CORE will
# generate the EMANE events when nodes are moved
return self.session.options.get_config_bool("emane_event_monitor")
return self.session.options.get_bool("emane_event_monitor", False)
def genlocationevents(self) -> bool:
"""
Returns boolean whether or not EMANE events will be generated.
"""
# By default, CORE generates EMANE location events when nodes
# are moved; this can be explicitly disabled in core.conf
tmp = self.session.options.get_config_bool("emane_event_generate")
if tmp is None:
tmp = not self.doeventmonitor()
return tmp
return self.session.options.get_bool("emane_event_generate", True)
def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None:
"""
@ -732,9 +727,6 @@ class EmaneManager:
self.session.broadcast_node(node)
return True
def is_emane_net(self, net: Optional[CoreNetworkBase]) -> bool:
return isinstance(net, EmaneNet)
def emanerunning(self, node: CoreNode) -> bool:
"""
Return True if an EMANE process associated with the given node is running,

View file

@ -1,6 +1,5 @@
import logging
from pathlib import Path
from typing import Dict, List
from core.config import Configuration
from core.emulator.enumerations import ConfigDataTypes
@ -33,7 +32,7 @@ def _type_value(config_type: str) -> ConfigDataTypes:
return ConfigDataTypes[config_type]
def _get_possible(config_type: str, config_regex: str) -> List[str]:
def _get_possible(config_type: str, config_regex: str) -> list[str]:
"""
Retrieve possible config value options based on emane regexes.
@ -51,7 +50,7 @@ def _get_possible(config_type: str, config_regex: str) -> List[str]:
return []
def _get_default(config_type_name: str, config_value: List[str]) -> str:
def _get_default(config_type_name: str, config_value: list[str]) -> str:
"""
Convert default configuration values to one used by core.
@ -74,7 +73,7 @@ def _get_default(config_type_name: str, config_value: List[str]) -> str:
return config_default
def parse(manifest_path: Path, defaults: Dict[str, str]) -> List[Configuration]:
def parse(manifest_path: Path, defaults: dict[str, str]) -> list[Configuration]:
"""
Parses a valid emane manifest file and converts the provided configuration values
into ones used by core.

View file

@ -3,7 +3,7 @@ Defines Emane Models used within CORE.
"""
import logging
from pathlib import Path
from typing import Dict, List, Optional, Set
from typing import Optional
from core.config import ConfigBool, ConfigGroup, ConfigString, Configuration
from core.emane import emanemanifest
@ -28,38 +28,38 @@ class EmaneModel(WirelessModel):
# default platform configuration settings
platform_controlport: str = "controlportendpoint"
platform_xml: str = "nemmanager.xml"
platform_defaults: Dict[str, str] = {
platform_defaults: dict[str, str] = {
"eventservicedevice": DEFAULT_DEV,
"eventservicegroup": "224.1.2.8:45703",
"otamanagerdevice": DEFAULT_DEV,
"otamanagergroup": "224.1.2.8:45702",
}
platform_config: List[Configuration] = []
platform_config: list[Configuration] = []
# default mac configuration settings
mac_library: Optional[str] = None
mac_xml: Optional[str] = None
mac_defaults: Dict[str, str] = {}
mac_config: List[Configuration] = []
mac_defaults: dict[str, str] = {}
mac_config: list[Configuration] = []
# default phy configuration settings, using the universal model
phy_library: Optional[str] = None
phy_xml: str = "emanephy.xml"
phy_defaults: Dict[str, str] = {
phy_defaults: dict[str, str] = {
"subid": "1",
"propagationmodel": "2ray",
"noisemode": "none",
}
phy_config: List[Configuration] = []
phy_config: list[Configuration] = []
# support for external configurations
external_config: List[Configuration] = [
external_config: list[Configuration] = [
ConfigBool(id="external", default="0"),
ConfigString(id="platformendpoint", default="127.0.0.1:40001"),
ConfigString(id="transportendpoint", default="127.0.0.1:50002"),
]
config_ignore: Set[str] = set()
config_ignore: set[str] = set()
@classmethod
def load(cls, emane_prefix: Path) -> None:
@ -94,7 +94,7 @@ class EmaneModel(WirelessModel):
cls.platform_config.pop(controlport_index)
@classmethod
def configurations(cls) -> List[Configuration]:
def configurations(cls) -> list[Configuration]:
"""
Returns the combination all all configurations (mac, phy, and external).
@ -105,7 +105,7 @@ class EmaneModel(WirelessModel):
)
@classmethod
def config_groups(cls) -> List[ConfigGroup]:
def config_groups(cls) -> list[ConfigGroup]:
"""
Returns the defined configuration groups.
@ -122,7 +122,7 @@ class EmaneModel(WirelessModel):
ConfigGroup("External Parameters", phy_len + 1, config_len),
]
def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None:
def build_xml_files(self, config: dict[str, str], iface: CoreInterface) -> None:
"""
Builds xml files for this emane model. Creates a nem.xml file that points to
both mac.xml and phy.xml definitions.
@ -146,7 +146,7 @@ class EmaneModel(WirelessModel):
"""
logger.debug("emane model(%s) has no post setup tasks", self.name)
def update(self, moved_ifaces: List[CoreInterface]) -> None:
def update(self, moved_ifaces: list[CoreInterface]) -> None:
"""
Invoked from MobilityModel when nodes are moved; this causes
emane location events to be generated for the nodes in the moved

View file

@ -2,7 +2,7 @@ import logging
import sched
import threading
import time
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
from typing import TYPE_CHECKING, Optional
from lxml import etree
@ -34,10 +34,10 @@ NEM_SELF: int = 65535
class LossTable:
def __init__(self, losses: Dict[float, float]) -> None:
self.losses: Dict[float, float] = losses
self.sinrs: List[float] = sorted(self.losses.keys())
self.loss_lookup: Dict[int, float] = {}
def __init__(self, losses: dict[float, float]) -> None:
self.losses: dict[float, float] = losses
self.sinrs: list[float] = sorted(self.losses.keys())
self.loss_lookup: dict[int, float] = {}
for index, value in enumerate(self.sinrs):
self.loss_lookup[index] = self.losses[value]
self.mac_id: Optional[str] = None
@ -84,7 +84,7 @@ class EmaneClient:
self.client: shell.ControlPortClient = shell.ControlPortClient(
self.address, port
)
self.nems: Dict[int, LossTable] = {}
self.nems: dict[int, LossTable] = {}
self.setup()
def setup(self) -> None:
@ -110,7 +110,7 @@ class EmaneClient:
self.nems[nem_id] = loss_table
def check_links(
self, links: Dict[Tuple[int, int], EmaneLink], loss_threshold: int
self, links: dict[tuple[int, int], EmaneLink], loss_threshold: int
) -> None:
for from_nem, loss_table in self.nems.items():
tables = self.client.getStatisticTable(loss_table.mac_id, (SINR_TABLE,))
@ -138,11 +138,11 @@ class EmaneClient:
link = EmaneLink(from_nem, to_nem, sinr)
links[link_key] = link
def handle_tdma(self, config: Dict[str, Tuple]):
def handle_tdma(self, config: dict[str, tuple]):
pcr = config["pcrcurveuri"][0][0]
logger.debug("tdma pcr: %s", pcr)
def handle_80211(self, config: Dict[str, Tuple]) -> LossTable:
def handle_80211(self, config: dict[str, tuple]) -> LossTable:
unicastrate = config["unicastrate"][0][0]
pcr = config["pcrcurveuri"][0][0]
logger.debug("80211 pcr: %s", pcr)
@ -159,7 +159,7 @@ class EmaneClient:
losses[sinr] = por
return LossTable(losses)
def handle_rfpipe(self, config: Dict[str, Tuple]) -> LossTable:
def handle_rfpipe(self, config: dict[str, tuple]) -> LossTable:
pcr = config["pcrcurveuri"][0][0]
logger.debug("rfpipe pcr: %s", pcr)
tree = etree.parse(pcr)
@ -179,9 +179,9 @@ class EmaneClient:
class EmaneLinkMonitor:
def __init__(self, emane_manager: "EmaneManager") -> None:
self.emane_manager: "EmaneManager" = emane_manager
self.clients: List[EmaneClient] = []
self.links: Dict[Tuple[int, int], EmaneLink] = {}
self.complete_links: Set[Tuple[int, int]] = set()
self.clients: list[EmaneClient] = []
self.links: dict[tuple[int, int], EmaneLink] = {}
self.complete_links: set[tuple[int, int]] = set()
self.loss_threshold: Optional[int] = None
self.link_interval: Optional[int] = None
self.link_timeout: Optional[int] = None
@ -190,9 +190,9 @@ class EmaneLinkMonitor:
def start(self) -> None:
options = self.emane_manager.session.options
self.loss_threshold = options.get_config_int("loss_threshold")
self.link_interval = options.get_config_int("link_interval")
self.link_timeout = options.get_config_int("link_timeout")
self.loss_threshold = options.get_int("loss_threshold")
self.link_interval = options.get_int("link_interval")
self.link_timeout = options.get_int("link_timeout")
self.initialize()
if not self.clients:
logger.info("no valid emane models to monitor links")
@ -210,7 +210,7 @@ class EmaneLinkMonitor:
if client.nems:
self.clients.append(client)
def get_addresses(self) -> List[Tuple[str, int]]:
def get_addresses(self) -> list[tuple[str, int]]:
addresses = []
nodes = self.emane_manager.getnodes()
for node in nodes:
@ -273,25 +273,25 @@ class EmaneLinkMonitor:
if self.running:
self.scheduler.enter(self.link_interval, 0, self.check_links)
def get_complete_id(self, link_id: Tuple[int, int]) -> Tuple[int, int]:
def get_complete_id(self, link_id: tuple[int, int]) -> tuple[int, int]:
value1, value2 = link_id
if value1 < value2:
return value1, value2
else:
return value2, value1
def is_complete_link(self, link_id: Tuple[int, int]) -> bool:
def is_complete_link(self, link_id: tuple[int, int]) -> bool:
reverse_id = link_id[1], link_id[0]
return link_id in self.links and reverse_id in self.links
def get_link_label(self, link_id: Tuple[int, int]) -> str:
def get_link_label(self, link_id: tuple[int, int]) -> str:
source_id = tuple(sorted(link_id))
source_link = self.links[source_id]
dest_id = link_id[::-1]
dest_link = self.links[dest_id]
return f"{source_link.sinr:.1f} / {dest_link.sinr:.1f}"
def send_link(self, message_type: MessageFlags, link_id: Tuple[int, int]) -> None:
def send_link(self, message_type: MessageFlags, link_id: tuple[int, int]) -> None:
nem1, nem2 = link_id
link = self.emane_manager.get_nem_link(nem1, nem2, message_type)
if link:

View file

@ -1,7 +1,6 @@
import logging
import pkgutil
from pathlib import Path
from typing import Dict, List, Type
from core import utils
from core.emane import models as emane_models
@ -12,10 +11,10 @@ logger = logging.getLogger(__name__)
class EmaneModelManager:
models: Dict[str, Type[EmaneModel]] = {}
models: dict[str, type[EmaneModel]] = {}
@classmethod
def load_locals(cls, emane_prefix: Path) -> List[str]:
def load_locals(cls, emane_prefix: Path) -> list[str]:
"""
Load local core emane models and make them available.
@ -38,7 +37,7 @@ class EmaneModelManager:
return errors
@classmethod
def load(cls, path: Path, emane_prefix: Path) -> List[str]:
def load(cls, path: Path, emane_prefix: Path) -> list[str]:
"""
Search and load custom emane models and make them available.
@ -63,7 +62,7 @@ class EmaneModelManager:
return errors
@classmethod
def get(cls, name: str) -> Type[EmaneModel]:
def get(cls, name: str) -> type[EmaneModel]:
model = cls.models.get(name)
if model is None:
raise CoreError(f"emame model does not exist {name}")

View file

@ -2,7 +2,6 @@
EMANE Bypass model for CORE
"""
from pathlib import Path
from typing import List, Set
from core.config import ConfigBool, Configuration
from core.emane import emanemodel
@ -12,11 +11,11 @@ class EmaneBypassModel(emanemodel.EmaneModel):
name: str = "emane_bypass"
# values to ignore, when writing xml files
config_ignore: Set[str] = {"none"}
config_ignore: set[str] = {"none"}
# mac definitions
mac_library: str = "bypassmaclayer"
mac_config: List[Configuration] = [
mac_config: list[Configuration] = [
ConfigBool(
id="none",
default="0",
@ -26,7 +25,7 @@ class EmaneBypassModel(emanemodel.EmaneModel):
# phy definitions
phy_library: str = "bypassphylayer"
phy_config: List[Configuration] = []
phy_config: list[Configuration] = []
@classmethod
def load(cls, emane_prefix: Path) -> None:

View file

@ -4,7 +4,6 @@ commeffect.py: EMANE CommEffect model for CORE
import logging
from pathlib import Path
from typing import Dict, List
from lxml import etree
@ -42,12 +41,12 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
name: str = "emane_commeffect"
shim_library: str = "commeffectshim"
shim_xml: str = "commeffectshim.xml"
shim_defaults: Dict[str, str] = {}
config_shim: List[Configuration] = []
shim_defaults: dict[str, str] = {}
config_shim: list[Configuration] = []
# comm effect does not need the default phy and external configurations
phy_config: List[Configuration] = []
external_config: List[Configuration] = []
phy_config: list[Configuration] = []
external_config: list[Configuration] = []
@classmethod
def load(cls, emane_prefix: Path) -> None:
@ -56,11 +55,11 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults)
@classmethod
def configurations(cls) -> List[Configuration]:
def configurations(cls) -> list[Configuration]:
return cls.platform_config + cls.config_shim
@classmethod
def config_groups(cls) -> List[ConfigGroup]:
def config_groups(cls) -> list[ConfigGroup]:
platform_len = len(cls.platform_config)
return [
ConfigGroup("Platform Parameters", 1, platform_len),
@ -71,7 +70,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
),
]
def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None:
def build_xml_files(self, config: dict[str, str], iface: CoreInterface) -> None:
"""
Build the necessary nem and commeffect XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build

View file

@ -4,7 +4,6 @@ tdma.py: EMANE TDMA model bindings for CORE
import logging
from pathlib import Path
from typing import Set
from core import constants, utils
from core.config import ConfigString
@ -28,7 +27,7 @@ class EmaneTdmaModel(emanemodel.EmaneModel):
default_schedule: Path = (
constants.CORE_DATA_DIR / "examples" / "tdma" / "schedule.xml"
)
config_ignore: Set[str] = {schedule_name}
config_ignore: set[str] = {schedule_name}
@classmethod
def load(cls, emane_prefix: Path) -> None:

View file

@ -4,19 +4,15 @@ share the same MAC+PHY model.
"""
import logging
from typing import TYPE_CHECKING, Dict, List, Optional, Type
import time
from dataclasses import dataclass
from typing import TYPE_CHECKING, Callable, Optional, Union
from core.emulator.data import InterfaceData, LinkData, LinkOptions
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import (
EventTypes,
LinkTypes,
MessageFlags,
NodeTypes,
RegisterTlvs,
)
from core.errors import CoreError
from core.nodes.base import CoreNetworkBase, CoreNode
from core.emulator.enumerations import MessageFlags, RegisterTlvs
from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNetworkBase, CoreNode, NodeOptions
from core.nodes.interface import CoreInterface
logger = logging.getLogger(__name__)
@ -24,10 +20,7 @@ logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from core.emane.emanemodel import EmaneModel
from core.emulator.session import Session
from core.location.mobility import WirelessModel, WayPointMobility
OptionalEmaneModel = Optional[EmaneModel]
WirelessModelType = Type[WirelessModel]
from core.location.mobility import WayPointMobility
try:
from emane.events import LocationEvent
@ -39,6 +32,120 @@ except ImportError:
logger.debug("compatible emane python bindings not installed")
class TunTap(CoreInterface):
"""
TUN/TAP virtual device in TAP mode
"""
def __init__(
self,
_id: int,
name: str,
localname: str,
use_ovs: bool,
node: CoreNode = None,
server: "DistributedServer" = None,
) -> None:
super().__init__(_id, name, localname, use_ovs, node=node, server=server)
self.node: CoreNode = node
def startup(self) -> None:
"""
Startup logic for a tunnel tap.
:return: nothing
"""
self.up = True
def shutdown(self) -> None:
"""
Shutdown functionality for a tunnel tap.
:return: nothing
"""
if not self.up:
return
self.up = False
def waitfor(
self, func: Callable[[], int], attempts: int = 10, maxretrydelay: float = 0.25
) -> bool:
"""
Wait for func() to return zero with exponential backoff.
:param func: function to wait for a result of zero
:param attempts: number of attempts to wait for a zero result
:param maxretrydelay: maximum retry delay
:return: True if wait succeeded, False otherwise
"""
delay = 0.01
result = False
for i in range(1, attempts + 1):
r = func()
if r == 0:
result = True
break
msg = f"attempt {i} failed with nonzero exit status {r}"
if i < attempts + 1:
msg += ", retrying..."
logger.info(msg)
time.sleep(delay)
delay += delay
if delay > maxretrydelay:
delay = maxretrydelay
else:
msg += ", giving up"
logger.info(msg)
return result
def nodedevexists(self) -> int:
"""
Checks if device exists.
:return: 0 if device exists, 1 otherwise
"""
try:
self.node.node_net_client.device_show(self.name)
return 0
except CoreCommandError:
return 1
def waitfordevicenode(self) -> None:
"""
Check for presence of a node device - tap device may not appear right away waits.
:return: nothing
"""
logger.debug("waiting for device node: %s", self.name)
count = 0
while True:
result = self.waitfor(self.nodedevexists)
if result:
break
should_retry = count < 5
is_emane_running = self.node.session.emane.emanerunning(self.node)
if all([should_retry, is_emane_running]):
count += 1
else:
raise RuntimeError("node device failed to exist")
def set_ips(self) -> None:
"""
Set interface ip addresses.
:return: nothing
"""
self.waitfordevicenode()
for ip in self.ips():
self.node.node_net_client.create_address(self.name, str(ip))
@dataclass
class EmaneOptions(NodeOptions):
emane_model: str = None
"""name of emane model to associate an emane network to"""
class EmaneNet(CoreNetworkBase):
"""
EMANE node contains NEM configuration and causes connected nodes
@ -46,22 +153,26 @@ class EmaneNet(CoreNetworkBase):
Emane controller object that exists in a session.
"""
apitype: NodeTypes = NodeTypes.EMANE
linktype: LinkTypes = LinkTypes.WIRED
type: str = "wlan"
has_custom_iface: bool = True
def __init__(
self,
session: "Session",
_id: int = None,
name: str = None,
server: DistributedServer = None,
options: EmaneOptions = None,
) -> None:
super().__init__(session, _id, name, server)
options = options or EmaneOptions()
super().__init__(session, _id, name, server, options)
self.conf: str = ""
self.model: "OptionalEmaneModel" = None
self.mobility: Optional[WayPointMobility] = None
model_class = self.session.emane.get_model(options.emane_model)
self.wireless_model: Optional["EmaneModel"] = model_class(self.session, self.id)
if self.session.is_running():
self.session.emane.add_node(self)
@classmethod
def create_options(cls) -> EmaneOptions:
return EmaneOptions()
def linkconfig(
self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None
@ -69,18 +180,15 @@ class EmaneNet(CoreNetworkBase):
"""
The CommEffect model supports link configuration.
"""
if not self.model:
if not self.wireless_model:
return
self.model.linkconfig(iface, options, iface2)
def config(self, conf: str) -> None:
self.conf = conf
self.wireless_model.linkconfig(iface, options, iface2)
def startup(self) -> None:
pass
self.up = True
def shutdown(self) -> None:
pass
self.up = False
def link(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
pass
@ -88,30 +196,37 @@ class EmaneNet(CoreNetworkBase):
def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
pass
def linknet(self, net: "CoreNetworkBase") -> CoreInterface:
raise CoreError("emane networks cannot be linked to other networks")
def updatemodel(self, config: dict[str, str]) -> None:
"""
Update configuration for the current model.
def updatemodel(self, config: Dict[str, str]) -> None:
if not self.model:
:param config: configuration to update model with
:return: nothing
"""
if not self.wireless_model:
raise CoreError(f"no model set to update for node({self.name})")
logger.info("node(%s) updating model(%s): %s", self.id, self.model.name, config)
self.model.update_config(config)
logger.info(
"node(%s) updating model(%s): %s", self.id, self.wireless_model.name, config
)
self.wireless_model.update_config(config)
def setmodel(self, model: "WirelessModelType", config: Dict[str, str]) -> None:
def setmodel(
self,
model: Union[type["EmaneModel"], type["WayPointMobility"]],
config: dict[str, str],
) -> None:
"""
set the EmaneModel associated with this node
"""
if model.config_type == RegisterTlvs.WIRELESS:
# EmaneModel really uses values from ConfigurableManager
# when buildnemxml() is called, not during init()
self.model = model(session=self.session, _id=self.id)
self.model.update_config(config)
self.wireless_model = model(session=self.session, _id=self.id)
self.wireless_model.update_config(config)
elif model.config_type == RegisterTlvs.MOBILITY:
self.mobility = model(session=self.session, _id=self.id)
self.mobility.update_config(config)
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
links = super().links(flags)
def links(self, flags: MessageFlags = MessageFlags.NONE) -> list[LinkData]:
links = []
emane_manager = self.session.emane
# gather current emane links
nem_ids = set()
@ -132,22 +247,44 @@ class EmaneNet(CoreNetworkBase):
# ignore incomplete links
if (nem2, nem1) not in emane_links:
continue
link = emane_manager.get_nem_link(nem1, nem2)
link = emane_manager.get_nem_link(nem1, nem2, flags)
if link:
links.append(link)
return links
def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
# TUN/TAP is not ready for addressing yet; the device may
# take some time to appear, and installing it into a
# namespace after it has been bound removes addressing;
# save addresses with the interface now
iface_id = node.newtuntap(iface_data.id, iface_data.name)
node.attachnet(iface_id, self)
iface = node.get_iface(iface_id)
iface.set_mac(iface_data.mac)
for ip in iface_data.get_ips():
iface.add_ip(ip)
if self.session.state == EventTypes.RUNTIME_STATE:
def create_tuntap(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
"""
Create a tuntap interface for the provided node.
:param node: node to create tuntap interface for
:param iface_data: interface data to create interface with
:return: created tuntap interface
"""
with node.lock:
if iface_data.id is not None and iface_data.id in node.ifaces:
raise CoreError(
f"node({self.id}) interface({iface_data.id}) already exists"
)
iface_id = (
iface_data.id if iface_data.id is not None else node.next_iface_id()
)
name = iface_data.name if iface_data.name is not None else f"eth{iface_id}"
session_id = self.session.short_session_id()
localname = f"tap{node.id}.{iface_id}.{session_id}"
iface = TunTap(iface_id, name, localname, self.session.use_ovs(), node=node)
if iface_data.mac:
iface.set_mac(iface_data.mac)
for ip in iface_data.get_ips():
iface.add_ip(ip)
node.ifaces[iface_id] = iface
self.attach(iface)
if self.up:
iface.startup()
if self.session.is_running():
self.session.emane.start_iface(self, iface)
return iface
def adopt_iface(self, iface: CoreInterface, name: str) -> None:
raise CoreError(
f"emane network({self.name}) do not support adopting interfaces"
)

View file

@ -0,0 +1,67 @@
from collections.abc import Callable
from typing import TypeVar, Union
from core.emulator.data import (
ConfigData,
EventData,
ExceptionData,
FileData,
LinkData,
NodeData,
)
from core.errors import CoreError
T = TypeVar(
"T", bound=Union[EventData, ExceptionData, NodeData, LinkData, FileData, ConfigData]
)
class BroadcastManager:
def __init__(self) -> None:
"""
Creates a BroadcastManager instance.
"""
self.handlers: dict[type[T], set[Callable[[T], None]]] = {}
def send(self, data: T) -> None:
"""
Retrieve handlers for data, and run all current handlers.
:param data: data to provide to handlers
:return: nothing
"""
handlers = self.handlers.get(type(data), set())
for handler in handlers:
handler(data)
def add_handler(self, data_type: type[T], handler: Callable[[T], None]) -> None:
"""
Add a handler for a given data type.
:param data_type: type of data to add handler for
:param handler: handler to add
:return: nothing
"""
handlers = self.handlers.setdefault(data_type, set())
if handler in handlers:
raise CoreError(
f"cannot add data({data_type}) handler({repr(handler)}), "
f"already exists"
)
handlers.add(handler)
def remove_handler(self, data_type: type[T], handler: Callable[[T], None]) -> None:
"""
Remove a handler for a given data type.
:param data_type: type of data to remove handler for
:param handler: handler to remove
:return: nothing
"""
handlers = self.handlers.get(data_type, set())
if handler not in handlers:
raise CoreError(
f"cannot remove data({data_type}) handler({repr(handler)}), "
f"does not exist"
)
handlers.remove(handler)

View file

@ -0,0 +1,239 @@
import logging
from typing import TYPE_CHECKING, Optional
from core import utils
from core.emulator.data import InterfaceData
from core.errors import CoreError
from core.nodes.base import CoreNode
from core.nodes.interface import DEFAULT_MTU
from core.nodes.network import CtrlNet
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from core.emulator.session import Session
CTRL_NET_ID: int = 9001
ETC_HOSTS_PATH: str = "/etc/hosts"
class ControlNetManager:
def __init__(self, session: "Session") -> None:
self.session: "Session" = session
self.etc_hosts_header: str = f"CORE session {self.session.id} host entries"
def _etc_hosts_enabled(self) -> bool:
"""
Determines if /etc/hosts should be configured.
:return: True if /etc/hosts should be configured, False otherwise
"""
return self.session.options.get_bool("update_etc_hosts", False)
def _get_server_ifaces(
self,
) -> tuple[None, Optional[str], Optional[str], Optional[str]]:
"""
Retrieve control net server interfaces.
:return: control net server interfaces
"""
d0 = self.session.options.get("controlnetif0")
if d0:
logger.error("controlnet0 cannot be assigned with a host interface")
d1 = self.session.options.get("controlnetif1")
d2 = self.session.options.get("controlnetif2")
d3 = self.session.options.get("controlnetif3")
return None, d1, d2, d3
def _get_prefixes(
self,
) -> tuple[Optional[str], Optional[str], Optional[str], Optional[str]]:
"""
Retrieve control net prefixes.
:return: control net prefixes
"""
p = self.session.options.get("controlnet")
p0 = self.session.options.get("controlnet0")
p1 = self.session.options.get("controlnet1")
p2 = self.session.options.get("controlnet2")
p3 = self.session.options.get("controlnet3")
if not p0 and p:
p0 = p
return p0, p1, p2, p3
def update_etc_hosts(self) -> None:
"""
Add the IP addresses of control interfaces to the /etc/hosts file.
:return: nothing
"""
if not self._etc_hosts_enabled():
return
control_net = self.get_control_net(0)
entries = ""
for iface in control_net.get_ifaces():
name = iface.node.name
for ip in iface.ips():
entries += f"{ip.ip} {name}\n"
logger.info("adding entries to /etc/hosts")
utils.file_munge(ETC_HOSTS_PATH, self.etc_hosts_header, entries)
def clear_etc_hosts(self) -> None:
"""
Clear IP addresses of control interfaces from the /etc/hosts file.
:return: nothing
"""
if not self._etc_hosts_enabled():
return
logger.info("removing /etc/hosts file entries")
utils.file_demunge(ETC_HOSTS_PATH, self.etc_hosts_header)
def get_control_net_index(self, dev: str) -> int:
"""
Retrieve control net index.
:param dev: device to get control net index for
:return: control net index, -1 otherwise
"""
if dev[0:4] == "ctrl" and int(dev[4]) in (0, 1, 2, 3):
index = int(dev[4])
if index == 0:
return index
if index < 4 and self._get_prefixes()[index] is not None:
return index
return -1
def get_control_net(self, index: int) -> Optional[CtrlNet]:
"""
Retrieve a control net based on index.
:param index: control net index
:return: control net when available, None otherwise
"""
try:
return self.session.get_node(CTRL_NET_ID + index, CtrlNet)
except CoreError:
return None
def add_control_net(
self, index: int, conf_required: bool = True
) -> Optional[CtrlNet]:
"""
Create a control network bridge as necessary. The conf_reqd flag,
when False, causes a control network bridge to be added even if
one has not been configured.
:param index: network index to add
:param conf_required: flag to check if conf is required
:return: control net node
"""
logger.info(
"checking to add control net index(%s) conf_required(%s)",
index,
conf_required,
)
# check for valid index
if not (0 <= index <= 3):
raise CoreError(f"invalid control net index({index})")
# return any existing control net bridge
control_net = self.get_control_net(index)
if control_net:
logger.info("control net index(%s) already exists", index)
return control_net
# retrieve prefix for current index
index_prefix = self._get_prefixes()[index]
if not index_prefix:
if conf_required:
return None
else:
index_prefix = CtrlNet.DEFAULT_PREFIX_LIST[index]
# retrieve valid prefix from old style values
prefixes = index_prefix.split()
if len(prefixes) > 1:
# a list of per-host prefixes is provided
try:
prefix = prefixes[0].split(":", 1)[1]
except IndexError:
prefix = prefixes[0]
else:
prefix = prefixes[0]
# use the updown script for control net 0 only
updown_script = None
if index == 0:
updown_script = self.session.options.get("controlnet_updown_script")
# build a new controlnet bridge
_id = CTRL_NET_ID + index
server_iface = self._get_server_ifaces()[index]
logger.info(
"adding controlnet(%s) prefix(%s) updown(%s) server interface(%s)",
_id,
prefix,
updown_script,
server_iface,
)
options = CtrlNet.create_options()
options.prefix = prefix
options.updown_script = updown_script
options.serverintf = server_iface
control_net = self.session.create_node(CtrlNet, False, _id, options=options)
control_net.brname = f"ctrl{index}.{self.session.short_session_id()}"
control_net.startup()
return control_net
def remove_control_net(self, index: int) -> None:
"""
Removes control net.
:param index: index of control net to remove
:return: nothing
"""
control_net = self.get_control_net(index)
if control_net:
logger.info("removing control net index(%s)", index)
self.session.delete_node(control_net.id)
def add_control_iface(self, node: CoreNode, index: int) -> None:
"""
Adds a control net interface to a node.
:param node: node to add control net interface to
:param index: index of control net to add interface to
:return: nothing
:raises CoreError: if control net doesn't exist, interface already exists,
or there is an error creating the interface
"""
control_net = self.get_control_net(index)
if not control_net:
raise CoreError(f"control net index({index}) does not exist")
iface_id = control_net.CTRLIF_IDX_BASE + index
if node.ifaces.get(iface_id):
raise CoreError(f"control iface({iface_id}) already exists")
try:
logger.info(
"node(%s) adding control net index(%s) interface(%s)",
node.name,
index,
iface_id,
)
ip4 = control_net.prefix[node.id]
ip4_mask = control_net.prefix.prefixlen
iface_data = InterfaceData(
id=iface_id,
name=f"ctrl{index}",
mac=utils.random_mac(),
ip4=ip4,
ip4_mask=ip4_mask,
mtu=DEFAULT_MTU,
)
iface = node.create_iface(iface_data)
control_net.attach(iface)
iface.control = True
except ValueError:
raise CoreError(
f"error adding control net interface to node({node.id}), "
f"invalid control net prefix({control_net.prefix}), "
"a longer prefix length may be required"
)

View file

@ -1,12 +1,7 @@
import atexit
import logging
import os
import signal
import sys
from pathlib import Path
from typing import Dict, List, Type
import core.services
from core import utils
from core.configservice.manager import ConfigServiceManager
from core.emane.modelmanager import EmaneModelManager
@ -19,31 +14,12 @@ logger = logging.getLogger(__name__)
DEFAULT_EMANE_PREFIX: str = "/usr"
def signal_handler(signal_number: int, _) -> None:
"""
Handle signals and force an exit with cleanup.
:param signal_number: signal number
:param _: ignored
:return: nothing
"""
logger.info("caught signal: %s", signal_number)
sys.exit(signal_number)
signal.signal(signal.SIGHUP, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGUSR1, signal_handler)
signal.signal(signal.SIGUSR2, signal_handler)
class CoreEmu:
"""
Provides logic for creating and configuring CORE sessions and the nodes within them.
"""
def __init__(self, config: Dict[str, str] = None) -> None:
def __init__(self, config: dict[str, str] = None) -> None:
"""
Create a CoreEmu object.
@ -54,13 +30,13 @@ class CoreEmu:
# configuration
config = config if config else {}
self.config: Dict[str, str] = config
self.config: dict[str, str] = config
# session management
self.sessions: Dict[int, Session] = {}
self.sessions: dict[int, Session] = {}
# load services
self.service_errors: List[str] = []
self.service_errors: list[str] = []
self.service_manager: ConfigServiceManager = ConfigServiceManager()
self._load_services()
@ -71,9 +47,6 @@ class CoreEmu:
# check executables exist on path
self._validate_env()
# catch exit event
atexit.register(self.shutdown)
def _validate_env(self) -> None:
"""
Validates executables CORE depends on exist on path.
@ -92,7 +65,7 @@ class CoreEmu:
:return: nothing
"""
# load default services
self.service_errors = core.services.load()
self.service_errors = ServiceManager.load_locals()
# load custom services
service_paths = self.config.get("custom_services_dir")
logger.debug("custom service paths: %s", service_paths)
@ -141,13 +114,11 @@ class CoreEmu:
:return: nothing
"""
logger.info("shutting down all sessions")
sessions = self.sessions.copy()
self.sessions.clear()
for _id in sessions:
session = sessions[_id]
while self.sessions:
_, session = self.sessions.popitem()
session.shutdown()
def create_session(self, _id: int = None, _cls: Type[Session] = Session) -> Session:
def create_session(self, _id: int = None, _cls: type[Session] = Session) -> Session:
"""
Create a new CORE session.

View file

@ -2,7 +2,7 @@
CORE data objects.
"""
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, List, Optional, Tuple
from typing import TYPE_CHECKING, Any, Optional
import netaddr
@ -24,7 +24,7 @@ class ConfigData:
node: int = None
object: str = None
type: int = None
data_types: Tuple[int] = None
data_types: tuple[int] = None
data_values: str = None
captions: str = None
bitmap: str = None
@ -81,8 +81,8 @@ class NodeOptions:
model: Optional[str] = "PC"
canvas: int = None
icon: str = None
services: List[str] = field(default_factory=list)
config_services: List[str] = field(default_factory=list)
services: list[str] = field(default_factory=list)
config_services: list[str] = field(default_factory=list)
x: float = None
y: float = None
lat: float = None
@ -92,6 +92,10 @@ class NodeOptions:
image: str = None
emane: str = None
legacy: bool = False
# src, dst
binds: list[tuple[str, str]] = field(default_factory=list)
# src, dst, unique, delete
volumes: list[tuple[str, str, bool, bool]] = field(default_factory=list)
def set_position(self, x: float, y: float) -> None:
"""
@ -144,7 +148,7 @@ class InterfaceData:
ip6_mask: int = None
mtu: int = None
def get_ips(self) -> List[str]:
def get_ips(self) -> list[str]:
"""
Returns a list of ip4 and ip6 addresses when present.
@ -176,6 +180,67 @@ class LinkOptions:
key: int = None
buffer: int = None
def update(self, options: "LinkOptions") -> bool:
"""
Updates current options with values from other options.
:param options: options to update with
:return: True if any value has changed, False otherwise
"""
changed = False
if options.delay is not None and 0 <= options.delay != self.delay:
self.delay = options.delay
changed = True
if options.bandwidth is not None and 0 <= options.bandwidth != self.bandwidth:
self.bandwidth = options.bandwidth
changed = True
if options.loss is not None and 0 <= options.loss != self.loss:
self.loss = options.loss
changed = True
if options.dup is not None and 0 <= options.dup != self.dup:
self.dup = options.dup
changed = True
if options.jitter is not None and 0 <= options.jitter != self.jitter:
self.jitter = options.jitter
changed = True
if options.buffer is not None and 0 <= options.buffer != self.buffer:
self.buffer = options.buffer
changed = True
return changed
def is_clear(self) -> bool:
"""
Checks if the current option values represent a clear state.
:return: True if the current values should clear, False otherwise
"""
clear = self.delay is None or self.delay <= 0
clear &= self.jitter is None or self.jitter <= 0
clear &= self.loss is None or self.loss <= 0
clear &= self.dup is None or self.dup <= 0
clear &= self.bandwidth is None or self.bandwidth <= 0
clear &= self.buffer is None or self.buffer <= 0
return clear
def __eq__(self, other: Any) -> bool:
"""
Custom logic to check if this link options is equivalent to another.
:param other: other object to check
:return: True if they are both link options with the same values,
False otherwise
"""
if not isinstance(other, LinkOptions):
return False
return (
self.delay == other.delay
and self.jitter == other.jitter
and self.loss == other.loss
and self.dup == other.dup
and self.bandwidth == other.bandwidth
and self.buffer == other.buffer
)
@dataclass
class LinkData:

View file

@ -8,13 +8,14 @@ import threading
from collections import OrderedDict
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Callable, Dict, Tuple
from typing import TYPE_CHECKING, Callable
import netaddr
from fabric import Connection
from invoke import UnexpectedExit
from core import utils
from core.emulator.links import CoreLink
from core.errors import CoreCommandError, CoreError
from core.executables import get_requirements
from core.nodes.interface import GreTap
@ -47,7 +48,7 @@ class DistributedServer:
self.lock: threading.Lock = threading.Lock()
def remote_cmd(
self, cmd: str, env: Dict[str, str] = None, cwd: str = None, wait: bool = True
self, cmd: str, env: dict[str, str] = None, cwd: str = None, wait: bool = True
) -> str:
"""
Run command remotely using server connection.
@ -104,7 +105,7 @@ class DistributedServer:
"""
with self.lock:
temp = NamedTemporaryFile(delete=False)
temp.write(data.encode("utf-8"))
temp.write(data.encode())
temp.close()
self.conn.put(temp.name, str(dst_path))
os.unlink(temp.name)
@ -122,11 +123,9 @@ class DistributedController:
:param session: session
"""
self.session: "Session" = session
self.servers: Dict[str, DistributedServer] = OrderedDict()
self.tunnels: Dict[int, Tuple[GreTap, GreTap]] = {}
self.address: str = self.session.options.get_config(
"distributed_address", default=None
)
self.servers: dict[str, DistributedServer] = OrderedDict()
self.tunnels: dict[int, tuple[GreTap, GreTap]] = {}
self.address: str = self.session.options.get("distributed_address")
def add_server(self, name: str, host: str) -> None:
"""
@ -183,24 +182,38 @@ class DistributedController:
def start(self) -> None:
"""
Start distributed network tunnels.
Start distributed network tunnels for control networks.
:return: nothing
"""
mtu = self.session.options.get_config_int("mtu")
for node_id in self.session.nodes:
node = self.session.nodes[node_id]
if not isinstance(node, CoreNetwork):
continue
if isinstance(node, CtrlNet) and node.serverintf is not None:
mtu = self.session.options.get_int("mtu")
for node in self.session.nodes.values():
if not isinstance(node, CtrlNet) or node.serverintf is not None:
continue
for name in self.servers:
server = self.servers[name]
self.create_gre_tunnel(node, server, mtu, True)
def create_gre_tunnels(self, core_link: CoreLink) -> None:
"""
Creates gre tunnels for a core link with a ptp network connection.
:param core_link: core link to create gre tunnel for
:return: nothing
"""
if not self.servers:
return
if not core_link.ptp:
raise CoreError(
"attempted to create gre tunnel for core link without a ptp network"
)
mtu = self.session.options.get_int("mtu")
for server in self.servers.values():
self.create_gre_tunnel(core_link.ptp, server, mtu, True)
def create_gre_tunnel(
self, node: CoreNetwork, server: DistributedServer, mtu: int, start: bool
) -> Tuple[GreTap, GreTap]:
) -> tuple[GreTap, GreTap]:
"""
Create gre tunnel using a pair of gre taps between the local and remote server.

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.
@ -38,6 +49,8 @@ class NodeTypes(Enum):
CONTROL_NET = 13
DOCKER = 15
LXC = 16
WIRELESS = 17
PODMAN = 18
class LinkTypes(Enum):

View file

@ -0,0 +1,145 @@
import logging
import subprocess
from collections.abc import Callable
from pathlib import Path
from core.emulator.enumerations import EventTypes
from core.errors import CoreError
logger = logging.getLogger(__name__)
class HookManager:
"""
Provides functionality for managing and running script/callback hooks.
"""
def __init__(self) -> None:
"""
Create a HookManager instance.
"""
self.script_hooks: dict[EventTypes, dict[str, str]] = {}
self.callback_hooks: dict[EventTypes, list[Callable[[], None]]] = {}
def reset(self) -> None:
"""
Clear all current hooks.
:return: nothing
"""
self.script_hooks.clear()
self.callback_hooks.clear()
def add_script_hook(self, state: EventTypes, file_name: str, data: str) -> None:
"""
Add a hook script to run for a given state.
:param state: state to run hook on
:param file_name: hook file name
:param data: file data
:return: nothing
"""
logger.info("setting state hook: %s - %s", state, file_name)
state_hooks = self.script_hooks.setdefault(state, {})
if file_name in state_hooks:
raise CoreError(
f"adding duplicate state({state.name}) hook script({file_name})"
)
state_hooks[file_name] = data
def delete_script_hook(self, state: EventTypes, file_name: str) -> None:
"""
Delete a script hook from a given state.
:param state: state to delete script hook from
:param file_name: name of script to delete
:return: nothing
"""
state_hooks = self.script_hooks.get(state, {})
if file_name not in state_hooks:
raise CoreError(
f"deleting state({state.name}) hook script({file_name}) "
"that does not exist"
)
del state_hooks[file_name]
def add_callback_hook(
self, state: EventTypes, hook: Callable[[EventTypes], None]
) -> None:
"""
Add a hook callback to run for a state.
:param state: state to add hook for
:param hook: callback to run
:return: nothing
"""
hooks = self.callback_hooks.setdefault(state, [])
if hook in hooks:
name = getattr(callable, "__name__", repr(hook))
raise CoreError(
f"adding duplicate state({state.name}) hook callback({name})"
)
hooks.append(hook)
def delete_callback_hook(
self, state: EventTypes, hook: Callable[[EventTypes], None]
) -> None:
"""
Delete a state hook.
:param state: state to delete hook for
:param hook: hook to delete
:return: nothing
"""
hooks = self.callback_hooks.get(state, [])
if hook not in hooks:
name = getattr(callable, "__name__", repr(hook))
raise CoreError(
f"deleting state({state.name}) hook callback({name}) "
"that does not exist"
)
hooks.remove(hook)
def run_hooks(
self, state: EventTypes, directory: Path, env: dict[str, str]
) -> None:
"""
Run all hooks for the current state.
:param state: state to run hooks for
:param directory: directory to run script hooks within
:param env: environment to run script hooks with
:return: nothing
"""
for state_hooks in self.script_hooks.get(state, {}):
for file_name, data in state_hooks.items():
logger.info("running hook %s", file_name)
file_path = directory / file_name
log_path = directory / f"{file_name}.log"
try:
with file_path.open("w") as f:
f.write(data)
with log_path.open("w") as f:
args = ["/bin/sh", file_name]
subprocess.check_call(
args,
stdout=f,
stderr=subprocess.STDOUT,
close_fds=True,
cwd=directory,
env=env,
)
except (OSError, subprocess.CalledProcessError) as e:
raise CoreError(
f"failure running state({state.name}) "
f"hook script({file_name}): {e}"
)
for hook in self.callback_hooks.get(state, []):
try:
hook()
except Exception as e:
name = getattr(callable, "__name__", repr(hook))
raise CoreError(
f"failure running state({state.name}) "
f"hook callback({name}): {e}"
)

View file

@ -0,0 +1,257 @@
"""
Provides functionality for maintaining information about known links
for a session.
"""
import logging
from collections.abc import ValuesView
from dataclasses import dataclass
from typing import Optional
from core.emulator.data import LinkData, LinkOptions
from core.emulator.enumerations import LinkTypes, MessageFlags
from core.errors import CoreError
from core.nodes.base import NodeBase
from core.nodes.interface import CoreInterface
from core.nodes.network import PtpNet
logger = logging.getLogger(__name__)
LinkKeyType = tuple[int, Optional[int], int, Optional[int]]
def create_key(
node1: NodeBase,
iface1: Optional[CoreInterface],
node2: NodeBase,
iface2: Optional[CoreInterface],
) -> LinkKeyType:
"""
Creates a unique key for tracking links.
:param node1: first node in link
:param iface1: node1 interface
:param node2: second node in link
:param iface2: node2 interface
:return: link key
"""
iface1_id = iface1.id if iface1 else None
iface2_id = iface2.id if iface2 else None
if node1.id < node2.id:
return node1.id, iface1_id, node2.id, iface2_id
else:
return node2.id, iface2_id, node1.id, iface1_id
@dataclass
class CoreLink:
"""
Provides a core link data structure.
"""
node1: NodeBase
iface1: Optional[CoreInterface]
node2: NodeBase
iface2: Optional[CoreInterface]
ptp: PtpNet = None
label: str = None
color: str = None
def key(self) -> LinkKeyType:
"""
Retrieve the key for this link.
:return: link key
"""
return create_key(self.node1, self.iface1, self.node2, self.iface2)
def is_unidirectional(self) -> bool:
"""
Checks if this link is considered unidirectional, due to current
iface configurations.
:return: True if unidirectional, False otherwise
"""
unidirectional = False
if self.iface1 and self.iface2:
unidirectional = self.iface1.options != self.iface2.options
return unidirectional
def options(self) -> LinkOptions:
"""
Retrieve the options for this link.
:return: options for this link
"""
if self.is_unidirectional():
options = self.iface1.options
else:
if self.iface1:
options = self.iface1.options
else:
options = self.iface2.options
return options
def get_data(self, message_type: MessageFlags, source: str = None) -> LinkData:
"""
Create link data for this link.
:param message_type: link data message type
:param source: source for this data
:return: link data
"""
iface1_data = self.iface1.get_data() if self.iface1 else None
iface2_data = self.iface2.get_data() if self.iface2 else None
return LinkData(
message_type=message_type,
type=LinkTypes.WIRED,
node1_id=self.node1.id,
node2_id=self.node2.id,
iface1=iface1_data,
iface2=iface2_data,
options=self.options(),
label=self.label,
color=self.color,
source=source,
)
def get_data_unidirectional(self, source: str = None) -> LinkData:
"""
Create other unidirectional link data.
:param source: source for this data
:return: unidirectional link data
"""
iface1_data = self.iface1.get_data() if self.iface1 else None
iface2_data = self.iface2.get_data() if self.iface2 else None
return LinkData(
message_type=MessageFlags.NONE,
type=LinkTypes.WIRED,
node1_id=self.node2.id,
node2_id=self.node1.id,
iface1=iface2_data,
iface2=iface1_data,
options=self.iface2.options,
label=self.label,
color=self.color,
source=source,
)
class LinkManager:
"""
Provides core link management.
"""
def __init__(self) -> None:
"""
Create a LinkManager instance.
"""
self._links: dict[LinkKeyType, CoreLink] = {}
self._node_links: dict[int, dict[LinkKeyType, CoreLink]] = {}
def add(self, core_link: CoreLink) -> None:
"""
Add a core link to be tracked.
:param core_link: link to track
:return: nothing
"""
node1, iface1 = core_link.node1, core_link.iface1
node2, iface2 = core_link.node2, core_link.iface2
if core_link.key() in self._links:
raise CoreError(
f"node1({node1.name}) iface1({iface1.id}) "
f"node2({node2.name}) iface2({iface2.id}) link already exists"
)
logger.info(
"adding link from node(%s:%s) to node(%s:%s)",
node1.name,
iface1.name if iface1 else None,
node2.name,
iface2.name if iface2 else None,
)
self._links[core_link.key()] = core_link
node1_links = self._node_links.setdefault(node1.id, {})
node1_links[core_link.key()] = core_link
node2_links = self._node_links.setdefault(node2.id, {})
node2_links[core_link.key()] = core_link
def delete(
self,
node1: NodeBase,
iface1: Optional[CoreInterface],
node2: NodeBase,
iface2: Optional[CoreInterface],
) -> CoreLink:
"""
Remove a link from being tracked.
:param node1: first node in link
:param iface1: node1 interface
:param node2: second node in link
:param iface2: node2 interface
:return: removed core link
"""
key = create_key(node1, iface1, node2, iface2)
if key not in self._links:
raise CoreError(
f"node1({node1.name}) iface1({iface1.id}) "
f"node2({node2.name}) iface2({iface2.id}) is not linked"
)
logger.info(
"deleting link from node(%s:%s) to node(%s:%s)",
node1.name,
iface1.name if iface1 else None,
node2.name,
iface2.name if iface2 else None,
)
node1_links = self._node_links[node1.id]
node1_links.pop(key)
node2_links = self._node_links[node2.id]
node2_links.pop(key)
return self._links.pop(key)
def reset(self) -> None:
"""
Resets and clears all tracking information.
:return: nothing
"""
self._links.clear()
self._node_links.clear()
def get_link(
self,
node1: NodeBase,
iface1: Optional[CoreInterface],
node2: NodeBase,
iface2: Optional[CoreInterface],
) -> Optional[CoreLink]:
"""
Retrieve a link for provided values.
:param node1: first node in link
:param iface1: interface for node1
:param node2: second node in link
:param iface2: interface for node2
:return: core link if present, None otherwise
"""
key = create_key(node1, iface1, node2, iface2)
return self._links.get(key)
def links(self) -> ValuesView[CoreLink]:
"""
Retrieve all known links
:return: iterator for all known links
"""
return self._links.values()
def node_links(self, node: NodeBase) -> ValuesView[CoreLink]:
"""
Retrieve all links for a given node.
:param node: node to get links for
:return: node links
"""
return self._node_links.get(node.id, {}).values()

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,16 @@
from typing import Any, List
from typing import Optional
from core.config import (
ConfigBool,
ConfigInt,
ConfigString,
ConfigurableManager,
ConfigurableOptions,
Configuration,
)
from core.emulator.enumerations import RegisterTlvs
from core.config import ConfigBool, ConfigInt, ConfigString, Configuration
from core.errors import CoreError
from core.plugins.sdt import Sdt
class SessionConfig(ConfigurableManager, ConfigurableOptions):
class SessionConfig:
"""
Provides session configuration.
"""
name: str = "session"
options: List[Configuration] = [
options: list[Configuration] = [
ConfigString(id="controlnet", label="Control Network"),
ConfigString(id="controlnet0", label="Control Network 0"),
ConfigString(id="controlnet1", label="Control Network 1"),
@ -42,34 +34,54 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
ConfigInt(id="link_timeout", default="4", label="EMANE Link Timeout (sec)"),
ConfigInt(id="mtu", default="0", label="MTU for All Devices"),
]
config_type: RegisterTlvs = RegisterTlvs.UTILITY
def __init__(self) -> None:
super().__init__()
self.set_configs(self.default_values())
def get_config(
self,
_id: str,
node_id: int = ConfigurableManager._default_node,
config_type: str = ConfigurableManager._default_type,
default: Any = None,
) -> str:
def __init__(self, config: dict[str, str] = None) -> None:
"""
Retrieves a specific configuration for a node and configuration type.
Create a SessionConfig instance.
:param _id: specific configuration to retrieve
:param node_id: node id to store configuration for
:param config_type: configuration type to store configuration for
:param default: default value to return when value is not found
:return: configuration value
:param config: configuration to initialize with
"""
value = super().get_config(_id, node_id, config_type, default)
if value == "":
value = default
return value
self._config: dict[str, str] = {x.id: x.default for x in self.options}
self._config.update(config or {})
def get_config_bool(self, name: str, default: Any = None) -> bool:
def update(self, config: dict[str, str]) -> None:
"""
Update current configuration with provided values.
:param config: configuration to update with
:return: nothing
"""
self._config.update(config)
def set(self, name: str, value: str) -> None:
"""
Set a configuration value.
:param name: name of configuration to set
:param value: value to set
:return: nothing
"""
self._config[name] = value
def get(self, name: str, default: str = None) -> Optional[str]:
"""
Retrieve configuration value.
:param name: name of configuration to get
:param default: value to return as default
:return: return found configuration value or default
"""
return self._config.get(name, default)
def all(self) -> dict[str, str]:
"""
Retrieve all configuration options.
:return: configuration value dict
"""
return self._config
def get_bool(self, name: str, default: bool = None) -> bool:
"""
Get configuration value as a boolean.
@ -77,12 +89,15 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
:param default: default value if not found
:return: boolean for configuration value
"""
value = self.get_config(name)
value = self._config.get(name)
if value is None and default is None:
raise CoreError(f"missing session options for {name}")
if value is None:
return default
return value.lower() == "true"
else:
return value.lower() == "true"
def get_config_int(self, name: str, default: Any = None) -> int:
def get_int(self, name: str, default: int = None) -> int:
"""
Get configuration value as int.
@ -90,17 +105,10 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
:param default: default value if not found
:return: int for configuration value
"""
value = self.get_config(name, default=default)
if value is not None:
value = int(value)
return value
def config_reset(self, node_id: int = None) -> None:
"""
Clear prior configuration files and reset to default values.
:param node_id: node id to store configuration for
:return: nothing
"""
super().config_reset(node_id)
self.set_configs(self.default_values())
value = self._config.get(name)
if value is None and default is None:
raise CoreError(f"missing session options for {name}")
if value is None:
return default
else:
return int(value)

View file

@ -11,7 +11,7 @@ class CoreCommandError(subprocess.CalledProcessError):
def __str__(self) -> str:
return (
f"Command({self.cmd}), Status({self.returncode}):\n"
f"command({self.cmd}), status({self.returncode}):\n"
f"stdout: {self.output}\nstderr: {self.stderr}"
)

View file

@ -1,34 +1,33 @@
from typing import List
BASH: str = "bash"
VNODED: str = "vnoded"
VCMD: str = "vcmd"
SYSCTL: str = "sysctl"
IP: str = "ip"
ETHTOOL: str = "ethtool"
TC: str = "tc"
IP: str = "ip"
MOUNT: str = "mount"
UMOUNT: str = "umount"
OVS_VSCTL: str = "ovs-vsctl"
TEST: str = "test"
NFTABLES: str = "nft"
OVS_VSCTL: str = "ovs-vsctl"
SYSCTL: str = "sysctl"
TC: str = "tc"
TEST: str = "test"
UMOUNT: str = "umount"
VCMD: str = "vcmd"
VNODED: str = "vnoded"
COMMON_REQUIREMENTS: List[str] = [
COMMON_REQUIREMENTS: list[str] = [
BASH,
NFTABLES,
ETHTOOL,
IP,
MOUNT,
NFTABLES,
SYSCTL,
TC,
UMOUNT,
TEST,
UMOUNT,
VCMD,
VNODED,
]
VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD]
OVS_REQUIREMENTS: List[str] = [OVS_VSCTL]
OVS_REQUIREMENTS: list[str] = [OVS_VSCTL]
def get_requirements(use_ovs: bool) -> List[str]:
def get_requirements(use_ovs: bool) -> list[str]:
"""
Retrieve executable requirements needed to run CORE.
@ -38,6 +37,4 @@ def get_requirements(use_ovs: bool) -> List[str]:
requirements = COMMON_REQUIREMENTS
if use_ovs:
requirements += OVS_REQUIREMENTS
else:
requirements += VCMD_REQUIREMENTS
return requirements

View file

@ -3,7 +3,7 @@ import math
import tkinter as tk
from tkinter import PhotoImage, font, messagebox, ttk
from tkinter.ttk import Progressbar
from typing import Any, Dict, Optional, Type
from typing import Any, Optional
import grpc
@ -45,7 +45,7 @@ class Application(ttk.Frame):
self.show_infobar: tk.BooleanVar = tk.BooleanVar(value=False)
# fonts
self.fonts_size: Dict[str, int] = {}
self.fonts_size: dict[str, int] = {}
self.icon_text_font: Optional[font.Font] = None
self.edge_font: Optional[font.Font] = None
@ -145,7 +145,7 @@ class Application(ttk.Frame):
self.statusbar = StatusBar(self.right_frame, self)
self.statusbar.grid(sticky=tk.EW, columnspan=2)
def display_info(self, frame_class: Type[InfoFrameBase], **kwargs: Any) -> None:
def display_info(self, frame_class: type[InfoFrameBase], **kwargs: Any) -> None:
if not self.show_infobar.get():
return
self.clear_info()

View file

@ -1,7 +1,7 @@
import os
import shutil
from pathlib import Path
from typing import Dict, List, Optional, Type
from typing import Optional
import yaml
@ -26,7 +26,7 @@ LOCAL_XMLS_PATH: Path = DATA_PATH.joinpath("xmls").absolute()
LOCAL_MOBILITY_PATH: Path = DATA_PATH.joinpath("mobility").absolute()
# configuration data
TERMINALS: Dict[str, str] = {
TERMINALS: dict[str, str] = {
"xterm": "xterm -e",
"aterm": "aterm -e",
"eterm": "eterm -e",
@ -36,7 +36,7 @@ TERMINALS: Dict[str, str] = {
"xfce4-terminal": "xfce4-terminal -x",
"gnome-terminal": "gnome-terminal --window --",
}
EDITORS: List[str] = ["$EDITOR", "vim", "emacs", "gedit", "nano", "vi"]
EDITORS: list[str] = ["$EDITOR", "vim", "emacs", "gedit", "nano", "vi"]
class IndentDumper(yaml.Dumper):
@ -46,17 +46,17 @@ class IndentDumper(yaml.Dumper):
class CustomNode(yaml.YAMLObject):
yaml_tag: str = "!CustomNode"
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
def __init__(self, name: str, image: str, services: List[str]) -> None:
def __init__(self, name: str, image: str, services: list[str]) -> None:
self.name: str = name
self.image: str = image
self.services: List[str] = services
self.services: list[str] = services
class CoreServer(yaml.YAMLObject):
yaml_tag: str = "!CoreServer"
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
def __init__(self, name: str, address: str) -> None:
self.name: str = name
@ -65,7 +65,7 @@ class CoreServer(yaml.YAMLObject):
class Observer(yaml.YAMLObject):
yaml_tag: str = "!Observer"
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
def __init__(self, name: str, cmd: str) -> None:
self.name: str = name
@ -74,7 +74,7 @@ class Observer(yaml.YAMLObject):
class PreferencesConfig(yaml.YAMLObject):
yaml_tag: str = "!PreferencesConfig"
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
def __init__(
self,
@ -95,7 +95,7 @@ class PreferencesConfig(yaml.YAMLObject):
class LocationConfig(yaml.YAMLObject):
yaml_tag: str = "!LocationConfig"
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
def __init__(
self,
@ -118,17 +118,17 @@ class LocationConfig(yaml.YAMLObject):
class IpConfigs(yaml.YAMLObject):
yaml_tag: str = "!IpConfigs"
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
def __init__(self, **kwargs) -> None:
self.__setstate__(kwargs)
def __setstate__(self, kwargs):
self.ip4s: List[str] = kwargs.get(
self.ip4s: list[str] = kwargs.get(
"ip4s", ["10.0.0.0", "192.168.0.0", "172.16.0.0"]
)
self.ip4: str = kwargs.get("ip4", self.ip4s[0])
self.ip6s: List[str] = kwargs.get("ip6s", ["2001::", "2002::", "a::"])
self.ip6s: list[str] = kwargs.get("ip6s", ["2001::", "2002::", "a::"])
self.ip6: str = kwargs.get("ip6", self.ip6s[0])
self.enable_ip4: bool = kwargs.get("enable_ip4", True)
self.enable_ip6: bool = kwargs.get("enable_ip6", True)
@ -136,16 +136,16 @@ class IpConfigs(yaml.YAMLObject):
class GuiConfig(yaml.YAMLObject):
yaml_tag: str = "!GuiConfig"
yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader
yaml_loader: type[yaml.SafeLoader] = yaml.SafeLoader
def __init__(
self,
preferences: PreferencesConfig = None,
location: LocationConfig = None,
servers: List[CoreServer] = None,
nodes: List[CustomNode] = None,
recentfiles: List[str] = None,
observers: List[Observer] = None,
servers: list[CoreServer] = None,
nodes: list[CustomNode] = None,
recentfiles: list[str] = None,
observers: list[Observer] = None,
scale: float = 1.0,
ips: IpConfigs = None,
mac: str = "00:00:00:aa:00:00",
@ -158,16 +158,16 @@ class GuiConfig(yaml.YAMLObject):
self.location: LocationConfig = location
if servers is None:
servers = []
self.servers: List[CoreServer] = servers
self.servers: list[CoreServer] = servers
if nodes is None:
nodes = []
self.nodes: List[CustomNode] = nodes
self.nodes: list[CustomNode] = nodes
if recentfiles is None:
recentfiles = []
self.recentfiles: List[str] = recentfiles
self.recentfiles: list[str] = recentfiles
if observers is None:
observers = []
self.observers: List[Observer] = observers
self.observers: list[Observer] = observers
self.scale: float = scale
if ips is None:
ips = IpConfigs()

View file

@ -6,9 +6,10 @@ import json
import logging
import os
import tkinter as tk
from collections.abc import Iterable
from pathlib import Path
from tkinter import messagebox
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple
from typing import TYPE_CHECKING, Optional
import grpc
@ -16,6 +17,7 @@ from core.api.grpc import client, configservices_pb2, core_pb2
from core.api.grpc.wrappers import (
ConfigOption,
ConfigService,
ConfigServiceDefaults,
EmaneModelConfig,
Event,
ExceptionEvent,
@ -55,7 +57,7 @@ GUI_SOURCE = "gui"
CPU_USAGE_DELAY = 3
def to_dict(config: Dict[str, ConfigOption]) -> Dict[str, str]:
def to_dict(config: dict[str, ConfigOption]) -> dict[str, str]:
return {x: y.value for x, y in config.items()}
@ -70,27 +72,30 @@ class CoreClient:
self.session: Optional[Session] = None
self.user = getpass.getuser()
# menu options
self.show_throughputs: tk.BooleanVar = tk.BooleanVar(value=False)
# global service settings
self.services: Dict[str, Set[str]] = {}
self.config_services_groups: Dict[str, Set[str]] = {}
self.config_services: Dict[str, ConfigService] = {}
self.services: dict[str, set[str]] = {}
self.config_services_groups: dict[str, set[str]] = {}
self.config_services: dict[str, ConfigService] = {}
# loaded configuration data
self.emane_models: List[str] = []
self.servers: Dict[str, CoreServer] = {}
self.custom_nodes: Dict[str, NodeDraw] = {}
self.custom_observers: Dict[str, Observer] = {}
self.emane_models: list[str] = []
self.servers: dict[str, CoreServer] = {}
self.custom_nodes: dict[str, NodeDraw] = {}
self.custom_observers: dict[str, Observer] = {}
self.read_config()
# helpers
self.iface_to_edge: Dict[Tuple[int, ...], CanvasEdge] = {}
self.iface_to_edge: dict[tuple[int, ...], CanvasEdge] = {}
self.ifaces_manager: InterfaceManager = InterfaceManager(self.app)
self.observer: Optional[str] = None
# session data
self.mobility_players: Dict[int, MobilityPlayer] = {}
self.canvas_nodes: Dict[int, CanvasNode] = {}
self.links: Dict[str, CanvasEdge] = {}
self.mobility_players: dict[int, MobilityPlayer] = {}
self.canvas_nodes: dict[int, CanvasNode] = {}
self.links: dict[str, CanvasEdge] = {}
self.handling_throughputs: Optional[grpc.Future] = None
self.handling_cpu_usage: Optional[grpc.Future] = None
self.handling_events: Optional[grpc.Future] = None
@ -242,9 +247,10 @@ class CoreClient:
logger.warning("unknown node event: %s", event)
def enable_throughputs(self) -> None:
self.handling_throughputs = self.client.throughputs(
self.session.id, self.handle_throughputs
)
if not self.handling_throughputs:
self.handling_throughputs = self.client.throughputs(
self.session.id, self.handle_throughputs
)
def cancel_throughputs(self) -> None:
if self.handling_throughputs:
@ -368,7 +374,7 @@ class CoreClient:
# existing session
sessions = self.client.get_sessions()
if session_id:
session_ids = set(x.id for x in sessions)
session_ids = {x.id for x in sessions}
if session_id not in session_ids:
self.app.show_error(
"Join Session Error",
@ -397,23 +403,25 @@ class CoreClient:
except grpc.RpcError as e:
self.app.show_grpc_exception("Edit Node Error", e)
def get_links(self, definition: bool = False) -> List[Link]:
def get_links(self, definition: bool = False) -> list[Link]:
if not definition:
self.ifaces_manager.set_macs([x.link for x in self.links.values()])
links = []
for edge in self.links.values():
link = edge.link
if not definition:
if link.iface1 and not link.iface1.mac:
node1 = self.session.nodes[link.node1_id]
node2 = self.session.nodes[link.node2_id]
if nutils.is_container(node1) and link.iface1 and not link.iface1.mac:
link.iface1.mac = self.ifaces_manager.next_mac()
if link.iface2 and not link.iface2.mac:
if nutils.is_container(node2) and link.iface2 and not link.iface2.mac:
link.iface2.mac = self.ifaces_manager.next_mac()
links.append(link)
if edge.asymmetric_link:
links.append(edge.asymmetric_link)
return links
def start_session(self, definition: bool = False) -> Tuple[bool, List[str]]:
def start_session(self, definition: bool = False) -> tuple[bool, list[str]]:
self.session.links = self.get_links(definition)
self.session.metadata = self.get_metadata()
self.session.servers.clear()
@ -429,13 +437,15 @@ class CoreClient:
definition,
result,
)
if self.show_throughputs.get():
self.enable_throughputs()
except grpc.RpcError as e:
self.app.show_grpc_exception("Start Session Error", e)
return result, exceptions
def stop_session(self, session_id: int = None) -> bool:
if not session_id:
session_id = self.session.id
session_id = session_id or self.session.id
self.cancel_throughputs()
result = False
try:
result = self.client.stop_session(session_id)
@ -453,7 +463,7 @@ class CoreClient:
self.mobility_players[node.id] = mobility_player
mobility_player.show()
def get_metadata(self) -> Dict[str, str]:
def get_metadata(self) -> dict[str, str]:
# create canvas data
canvas_config = self.app.manager.get_metadata()
canvas_config = json.dumps(canvas_config)
@ -644,7 +654,7 @@ class CoreClient:
self.session.nodes[node.id] = node
return node
def deleted_canvas_nodes(self, canvas_nodes: List[CanvasNode]) -> None:
def deleted_canvas_nodes(self, canvas_nodes: list[CanvasNode]) -> None:
"""
remove the nodes selected by the user and anything related to that node
such as link, configurations, interfaces
@ -665,14 +675,14 @@ class CoreClient:
self.links[edge.token] = edge
src_node = edge.src.core_node
dst_node = edge.dst.core_node
if nutils.is_container(src_node):
if edge.link.iface1:
src_iface_id = edge.link.iface1.id
self.iface_to_edge[(src_node.id, src_iface_id)] = edge
if nutils.is_container(dst_node):
if edge.link.iface2:
dst_iface_id = edge.link.iface2.id
self.iface_to_edge[(dst_node.id, dst_iface_id)] = edge
def get_wlan_configs(self) -> List[Tuple[int, Dict[str, str]]]:
def get_wlan_configs(self) -> list[tuple[int, dict[str, str]]]:
configs = []
for node in self.session.nodes.values():
if node.type != NodeType.WIRELESS_LAN:
@ -683,7 +693,7 @@ class CoreClient:
configs.append((node.id, config))
return configs
def get_mobility_configs(self) -> List[Tuple[int, Dict[str, str]]]:
def get_mobility_configs(self) -> list[tuple[int, dict[str, str]]]:
configs = []
for node in self.session.nodes.values():
if not nutils.is_mobility(node):
@ -694,7 +704,7 @@ class CoreClient:
configs.append((node.id, config))
return configs
def get_emane_model_configs(self) -> List[EmaneModelConfig]:
def get_emane_model_configs(self) -> list[EmaneModelConfig]:
configs = []
for node in self.session.nodes.values():
for key, config in node.emane_model_configs.items():
@ -708,7 +718,7 @@ class CoreClient:
configs.append(config)
return configs
def get_service_configs(self) -> List[ServiceConfig]:
def get_service_configs(self) -> list[ServiceConfig]:
configs = []
for node in self.session.nodes.values():
if not nutils.is_container(node):
@ -728,7 +738,7 @@ class CoreClient:
configs.append(config)
return configs
def get_service_file_configs(self) -> List[ServiceFileConfig]:
def get_service_file_configs(self) -> list[ServiceFileConfig]:
configs = []
for node in self.session.nodes.values():
if not nutils.is_container(node):
@ -741,9 +751,17 @@ class CoreClient:
configs.append(config)
return configs
def get_config_service_rendered(self, node_id: int, name: str) -> dict[str, str]:
return self.client.get_config_service_rendered(self.session.id, node_id, name)
def get_config_service_defaults(
self, node_id: int, name: str
) -> ConfigServiceDefaults:
return self.client.get_config_service_defaults(self.session.id, node_id, name)
def get_config_service_configs_proto(
self
) -> List[configservices_pb2.ConfigServiceConfig]:
self,
) -> list[configservices_pb2.ConfigServiceConfig]:
config_service_protos = []
for node in self.session.nodes.values():
if not nutils.is_container(node):
@ -765,7 +783,7 @@ class CoreClient:
_, output = self.client.node_command(self.session.id, node_id, self.observer)
return output
def get_wlan_config(self, node_id: int) -> Dict[str, ConfigOption]:
def get_wlan_config(self, node_id: int) -> dict[str, ConfigOption]:
config = self.client.get_wlan_config(self.session.id, node_id)
logger.debug(
"get wlan configuration from node %s, result configuration: %s",
@ -774,7 +792,10 @@ class CoreClient:
)
return config
def get_mobility_config(self, node_id: int) -> Dict[str, ConfigOption]:
def get_wireless_config(self, node_id: int) -> dict[str, ConfigOption]:
return self.client.get_wireless_config(self.session.id, node_id)
def get_mobility_config(self, node_id: int) -> dict[str, ConfigOption]:
config = self.client.get_mobility_config(self.session.id, node_id)
logger.debug(
"get mobility config from node %s, result configuration: %s",
@ -785,7 +806,7 @@ class CoreClient:
def get_emane_model_config(
self, node_id: int, model: str, iface_id: int = None
) -> Dict[str, ConfigOption]:
) -> dict[str, ConfigOption]:
if iface_id is None:
iface_id = -1
config = self.client.get_emane_model_config(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -1,131 +1,77 @@
<?xml version='1.0' encoding='UTF-8'?>
<scenario name="/home/developer/.core/configs/emane-demo-antenna.xml">
<scenario name="/tmp/tmpd4t2sxy2">
<networks>
<network id="5" name="wlan5" model="emane_rfpipe" type="EMANE">
<position x="388" y="555" lat="47.57412169587584" lon="-122.12709380504643" alt="2.0"/>
<network id="5" name="wlan5" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
<position x="388.0" y="555.0" lat="47.574121408201655" lon="-122.12709602379641" alt="2.0"/>
</network>
</networks>
<devices>
<device id="1" name="n1" type="mdr" class="" image="">
<position x="258" y="147" lat="47.577830502987744" lon="-122.12884551985047" alt="2.0"/>
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
<position x="258.0" y="147.0" lat="47.57783021533393" lon="-122.12884773860046" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
<service name="OSPFv3MDR"/>
</services>
</device>
<device id="2" name="n2" type="mdr" class="" image="">
<position x="526" y="147" lat="47.577830502987744" lon="-122.12523429240828" alt="2.0"/>
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
<position x="526.0" y="147.0" lat="47.57783021533393" lon="-122.12523651115826" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
<service name="OSPFv3MDR"/>
</services>
</device>
<device id="3" name="n3" type="mdr" class="" image="">
<position x="241" y="387" lat="47.57564888355958" lon="-122.12907459024791" alt="2.0"/>
<device id="3" name="n3" icon="" canvas="0" type="mdr" class="" image="">
<position x="241.0" y="387.0" lat="47.575648595893774" lon="-122.1290768089979" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
<service name="OSPFv3MDR"/>
</services>
</device>
<device id="4" name="n4" type="mdr" class="" image="">
<position x="529" y="385" lat="47.57566706409707" lon="-122.1251938682205" alt="2.0"/>
<device id="4" name="n4" icon="" canvas="0" type="mdr" class="" image="">
<position x="529.0" y="385.0" lat="47.57566677643136" lon="-122.12519608697049" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
<service name="OSPFv3MDR"/>
</services>
</device>
</devices>
<links>
<link node1="5" node2="1">
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
<link node1="1" node2="5">
<iface1 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
</link>
<link node1="5" node2="2">
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
<link node1="2" node2="5">
<iface1 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
</link>
<link node1="5" node2="3">
<iface2 nem="3" id="0" name="eth0" mac="02:02:00:00:00:03" ip4="10.0.0.3" ip4_mask="32" ip6="2001::3" ip6_mask="128"/>
<link node1="3" node2="5">
<iface1 nem="3" id="0" name="eth0" mac="02:02:00:00:00:03" ip4="10.0.0.3" ip4_mask="32" ip6="2001::3" ip6_mask="128"/>
</link>
<link node1="5" node2="4">
<iface2 nem="4" id="0" name="eth0" mac="02:02:00:00:00:04" ip4="10.0.0.4" ip4_mask="32" ip6="2001::4" ip6_mask="128"/>
<link node1="4" node2="5">
<iface1 nem="4" id="0" name="eth0" mac="02:02:00:00:00:04" ip4="10.0.0.4" ip4_mask="32" ip6="2001::4" ip6_mask="128"/>
</link>
</links>
<emane_global_configuration>
<emulator>
<configuration name="antennaprofilemanifesturi" value="/tmp/emane/antennaprofile.xml"/>
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</emulator>
<core>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
</core>
</emane_global_configuration>
<emane_configurations>
<emane_configuration node="5" model="emane_rfpipe">
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
<configuration name="enablepromiscuousmode" value="0"/>
<configuration name="flowcontrolenable" value="0"/>
<configuration name="flowcontroltokens" value="10"/>
<configuration name="jitter" value="0.000000"/>
<configuration name="neighbormetricdeletetime" value="60.000000"/>
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
<configuration name="radiometricenable" value="0"/>
<configuration name="radiometricreportinterval" value="1.000000"/>
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
<configuration name="fading.nakagami.m0" value="0.750000"/>
<configuration name="fading.nakagami.m1" value="1.000000"/>
<configuration name="fading.nakagami.m2" value="200.000000"/>
<configuration name="fixedantennagain" value="0.000000"/>
<configuration name="fixedantennagainenable" value="1"/>
<configuration name="frequency" value="2347000000"/>
<configuration name="frequencyofinterest" value="2347000000"/>
<configuration name="noisebinsize" value="20"/>
<configuration name="noisemaxclampenable" value="0"/>
<configuration name="noisemaxmessagepropagation" value="200000"/>
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="none"/>
<configuration name="propagationmodel" value="2ray"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
<configuration name="txpower" value="0.000000"/>
</phy>
<external>
<configuration name="external" value="0"/>
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
</external>
</emane_configuration>
<emane_configuration node="1" model="emane_rfpipe">
<platform>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</platform>
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
@ -140,6 +86,17 @@
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="compatibilitymode" value="1"/>
<configuration name="dopplershiftenable" value="1"/>
<configuration name="excludesamesubidfromfilterenable" value="1"/>
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
<configuration name="fading.lognormal.dmu" value="5.000000"/>
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
<configuration name="fading.lognormal.lmean" value="0.005000"/>
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
@ -156,7 +113,10 @@
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="outofband"/>
<configuration name="processingpoolsize" value="0"/>
<configuration name="propagationmodel" value="precomputed"/>
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
<configuration name="stats.receivepowertableenable" value="1"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
@ -169,6 +129,23 @@
</external>
</emane_configuration>
<emane_configuration node="2" model="emane_rfpipe">
<platform>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</platform>
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
@ -183,6 +160,17 @@
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="compatibilitymode" value="1"/>
<configuration name="dopplershiftenable" value="1"/>
<configuration name="excludesamesubidfromfilterenable" value="1"/>
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
<configuration name="fading.lognormal.dmu" value="5.000000"/>
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
<configuration name="fading.lognormal.lmean" value="0.005000"/>
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
@ -199,7 +187,10 @@
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="outofband"/>
<configuration name="processingpoolsize" value="0"/>
<configuration name="propagationmodel" value="precomputed"/>
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
<configuration name="stats.receivepowertableenable" value="1"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
@ -212,6 +203,23 @@
</external>
</emane_configuration>
<emane_configuration node="3" model="emane_rfpipe">
<platform>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</platform>
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
@ -226,6 +234,17 @@
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="compatibilitymode" value="1"/>
<configuration name="dopplershiftenable" value="1"/>
<configuration name="excludesamesubidfromfilterenable" value="1"/>
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
<configuration name="fading.lognormal.dmu" value="5.000000"/>
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
<configuration name="fading.lognormal.lmean" value="0.005000"/>
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
@ -242,7 +261,10 @@
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="outofband"/>
<configuration name="processingpoolsize" value="0"/>
<configuration name="propagationmodel" value="precomputed"/>
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
<configuration name="stats.receivepowertableenable" value="1"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
@ -255,6 +277,23 @@
</external>
</emane_configuration>
<emane_configuration node="4" model="emane_rfpipe">
<platform>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</platform>
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
@ -269,6 +308,17 @@
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="compatibilitymode" value="1"/>
<configuration name="dopplershiftenable" value="1"/>
<configuration name="excludesamesubidfromfilterenable" value="1"/>
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
<configuration name="fading.lognormal.dmu" value="5.000000"/>
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
<configuration name="fading.lognormal.lmean" value="0.005000"/>
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
@ -285,7 +335,84 @@
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="outofband"/>
<configuration name="processingpoolsize" value="0"/>
<configuration name="propagationmodel" value="precomputed"/>
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
<configuration name="stats.receivepowertableenable" value="1"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
<configuration name="txpower" value="0.000000"/>
</phy>
<external>
<configuration name="external" value="0"/>
<configuration name="platformendpoint" value="127.0.0.1:40001"/>
<configuration name="transportendpoint" value="127.0.0.1:50002"/>
</external>
</emane_configuration>
<emane_configuration node="5" model="emane_rfpipe">
<platform>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</platform>
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
<configuration name="enablepromiscuousmode" value="0"/>
<configuration name="flowcontrolenable" value="0"/>
<configuration name="flowcontroltokens" value="10"/>
<configuration name="jitter" value="0.000000"/>
<configuration name="neighbormetricdeletetime" value="60.000000"/>
<configuration name="pcrcurveuri" value="/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"/>
<configuration name="radiometricenable" value="0"/>
<configuration name="radiometricreportinterval" value="1.000000"/>
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="compatibilitymode" value="1"/>
<configuration name="dopplershiftenable" value="1"/>
<configuration name="excludesamesubidfromfilterenable" value="1"/>
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
<configuration name="fading.lognormal.dmu" value="5.000000"/>
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
<configuration name="fading.lognormal.lmean" value="0.005000"/>
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
<configuration name="fading.nakagami.m0" value="0.750000"/>
<configuration name="fading.nakagami.m1" value="1.000000"/>
<configuration name="fading.nakagami.m2" value="200.000000"/>
<configuration name="fixedantennagain" value="0.000000"/>
<configuration name="fixedantennagainenable" value="1"/>
<configuration name="frequency" value="2347000000"/>
<configuration name="frequencyofinterest" value="2347000000"/>
<configuration name="noisebinsize" value="20"/>
<configuration name="noisemaxclampenable" value="0"/>
<configuration name="noisemaxmessagepropagation" value="200000"/>
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="none"/>
<configuration name="processingpoolsize" value="0"/>
<configuration name="propagationmodel" value="2ray"/>
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
<configuration name="stats.receivepowertableenable" value="1"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
@ -298,7 +425,7 @@
</external>
</emane_configuration>
</emane_configurations>
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
<session_origin lat="47.579166412353516" lon="-122.13232421875" alt="2.0" scale="150.0"/>
<session_options>
<configuration name="controlnet" value="172.16.0.0/24"/>
<configuration name="controlnet0" value=""/>
@ -311,10 +438,19 @@
<configuration name="enablesdt" value="0"/>
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
<configuration name="ovs" value="0"/>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
<configuration name="mtu" value="0"/>
</session_options>
<session_metadata>
<configuration name="canvas c1" value="{name {Canvas1}}"/>
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
<configuration name="shapes" value="[]"/>
<configuration name="edges" value="[]"/>
<configuration name="hidden" value="[]"/>
<configuration name="canvas" value="{&quot;gridlines&quot;: true, &quot;canvases&quot;: [{&quot;id&quot;: 1, &quot;wallpaper&quot;: null, &quot;wallpaper_style&quot;: 1, &quot;fit_image&quot;: false, &quot;dimensions&quot;: [1000, 750]}]}"/>
</session_metadata>
<default_services>
<node type="mdr">

View file

@ -1,66 +1,55 @@
<?xml version='1.0' encoding='UTF-8'?>
<scenario name="/home/developer/.core/configs/emane-demo-eel.xml">
<scenario name="/tmp/tmp2mkcwn17">
<networks>
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
<network id="3" name="wlan3" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
<position x="282.0" y="317.0" lat="47.5762849109534" lon="-122.12852434509814" alt="2.0"/>
</network>
</networks>
<devices>
<device id="1" name="n1" type="mdr" class="" image="">
<position x="153" y="172" lat="47.57760325520506" lon="-122.13026036642295" alt="2.0"/>
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
<position x="153.0" y="172.0" lat="47.577602967549986" lon="-122.13026258517293" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
<service name="OSPFv3MDR"/>
</services>
</device>
<device id="2" name="n2" type="mdr" class="" image="">
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
<position x="393.0" y="171.0" lat="47.57761205748029" lon="-122.1270286501501" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
<service name="OSPFv3MDR"/>
</services>
</device>
</devices>
<links>
<link node1="3" node2="1">
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
<link node1="1" node2="3">
<iface1 id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
</link>
<link node1="3" node2="2">
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
<link node1="2" node2="3">
<iface1 id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
</link>
</links>
<emane_global_configuration>
<emulator>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</emulator>
<core>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
</core>
</emane_global_configuration>
<emane_configurations>
<emane_configuration node="3" model="emane_rfpipe">
<platform>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</platform>
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
@ -75,6 +64,17 @@
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="compatibilitymode" value="1"/>
<configuration name="dopplershiftenable" value="1"/>
<configuration name="excludesamesubidfromfilterenable" value="1"/>
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
<configuration name="fading.lognormal.dmu" value="5.000000"/>
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
<configuration name="fading.lognormal.lmean" value="0.005000"/>
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
@ -91,7 +91,10 @@
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="none"/>
<configuration name="processingpoolsize" value="0"/>
<configuration name="propagationmodel" value="precomputed"/>
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
<configuration name="stats.receivepowertableenable" value="1"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
@ -104,7 +107,7 @@
</external>
</emane_configuration>
</emane_configurations>
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
<session_origin lat="47.579166412353516" lon="-122.13232421875" alt="2.0" scale="150.0"/>
<session_options>
<configuration name="controlnet" value="172.16.0.0/24"/>
<configuration name="controlnet0" value=""/>
@ -117,10 +120,19 @@
<configuration name="enablesdt" value="0"/>
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
<configuration name="ovs" value="0"/>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
<configuration name="mtu" value="0"/>
</session_options>
<session_metadata>
<configuration name="canvas c1" value="{name {Canvas1}}"/>
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
<configuration name="shapes" value="[]"/>
<configuration name="edges" value="[]"/>
<configuration name="hidden" value="[]"/>
<configuration name="canvas" value="{&quot;gridlines&quot;: true, &quot;canvases&quot;: [{&quot;id&quot;: 1, &quot;wallpaper&quot;: null, &quot;wallpaper_style&quot;: 1, &quot;fit_image&quot;: false, &quot;dimensions&quot;: [1000, 750]}]}"/>
</session_metadata>
<default_services>
<node type="mdr">

View file

@ -1,66 +1,55 @@
<?xml version='1.0' encoding='UTF-8'?>
<scenario name="/home/developer/.core/configs/emane-demo-files.xml">
<scenario name="/tmp/tmpsj4dhmce">
<networks>
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
<network id="3" name="wlan3" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
<position x="282.0" y="317.0" lat="47.5762849109534" lon="-122.12852434509814" alt="2.0"/>
</network>
</networks>
<devices>
<device id="1" name="n1" type="mdr" class="" image="">
<position x="153" y="173" lat="47.57759416527324" lon="-122.13026036642295" alt="2.0"/>
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
<position x="153.0" y="173.0" lat="47.57759387761812" lon="-122.13026258517293" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
<service name="OSPFv3MDR"/>
</services>
</device>
<device id="2" name="n2" type="mdr" class="" image="">
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
<position x="393.0" y="171.0" lat="47.57761205748029" lon="-122.1270286501501" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
<service name="OSPFv3MDR"/>
</services>
</device>
</devices>
<links>
<link node1="3" node2="1">
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
<link node1="1" node2="3">
<iface1 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
</link>
<link node1="3" node2="2">
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
<link node1="2" node2="3">
<iface1 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
</link>
</links>
<emane_global_configuration>
<emulator>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</emulator>
<core>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
</core>
</emane_global_configuration>
<emane_configurations>
<emane_configuration node="3" model="emane_rfpipe">
<platform>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</platform>
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
@ -75,6 +64,17 @@
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="compatibilitymode" value="1"/>
<configuration name="dopplershiftenable" value="1"/>
<configuration name="excludesamesubidfromfilterenable" value="1"/>
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
<configuration name="fading.lognormal.dmu" value="5.000000"/>
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
<configuration name="fading.lognormal.lmean" value="0.005000"/>
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
@ -91,7 +91,10 @@
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="none"/>
<configuration name="processingpoolsize" value="0"/>
<configuration name="propagationmodel" value="2ray"/>
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
<configuration name="stats.receivepowertableenable" value="1"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
@ -104,7 +107,7 @@
</external>
</emane_configuration>
</emane_configurations>
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
<session_origin lat="47.579166412353516" lon="-122.13232421875" alt="2.0" scale="150.0"/>
<session_options>
<configuration name="controlnet" value="172.16.0.0/24"/>
<configuration name="controlnet0" value=""/>
@ -117,10 +120,19 @@
<configuration name="enablesdt" value="0"/>
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
<configuration name="ovs" value="0"/>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
<configuration name="mtu" value="0"/>
</session_options>
<session_metadata>
<configuration name="canvas c1" value="{name {Canvas1}}"/>
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
<configuration name="shapes" value="[]"/>
<configuration name="edges" value="[]"/>
<configuration name="hidden" value="[]"/>
<configuration name="canvas" value="{&quot;gridlines&quot;: true, &quot;canvases&quot;: [{&quot;id&quot;: 1, &quot;wallpaper&quot;: null, &quot;wallpaper_style&quot;: 1, &quot;fit_image&quot;: false, &quot;dimensions&quot;: [1000, 750]}]}"/>
</session_metadata>
<default_services>
<node type="mdr">

View file

@ -1,66 +1,55 @@
<?xml version='1.0' encoding='UTF-8'?>
<scenario name="/home/developer/.core/configs/emane-demo-gpsd.xml">
<scenario name="/tmp/tmp081pn3j9">
<networks>
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
<network id="3" name="wlan3" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
<position x="282.0" y="317.0" lat="47.5762849109534" lon="-122.12852434509814" alt="2.0"/>
</network>
</networks>
<devices>
<device id="1" name="n1" type="mdr" class="" image="">
<position x="153" y="173" lat="47.57759416527324" lon="-122.13026036642295" alt="2.0"/>
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
<position x="153.0" y="173.0" lat="47.57759387761812" lon="-122.13026258517293" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
<service name="OSPFv3MDR"/>
</services>
</device>
<device id="2" name="n2" type="mdr" class="" image="">
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
<position x="393.0" y="171.0" lat="47.57761205748029" lon="-122.1270286501501" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
<service name="OSPFv3MDR"/>
</services>
</device>
</devices>
<links>
<link node1="3" node2="1">
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
<link node1="1" node2="3">
<iface1 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
</link>
<link node1="3" node2="2">
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
<link node1="2" node2="3">
<iface1 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
</link>
</links>
<emane_global_configuration>
<emulator>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</emulator>
<core>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
</core>
</emane_global_configuration>
<emane_configurations>
<emane_configuration node="3" model="emane_rfpipe">
<platform>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</platform>
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
@ -75,6 +64,17 @@
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="compatibilitymode" value="1"/>
<configuration name="dopplershiftenable" value="1"/>
<configuration name="excludesamesubidfromfilterenable" value="1"/>
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
<configuration name="fading.lognormal.dmu" value="5.000000"/>
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
<configuration name="fading.lognormal.lmean" value="0.005000"/>
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
@ -91,7 +91,10 @@
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="none"/>
<configuration name="processingpoolsize" value="0"/>
<configuration name="propagationmodel" value="2ray"/>
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
<configuration name="stats.receivepowertableenable" value="1"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
@ -104,7 +107,7 @@
</external>
</emane_configuration>
</emane_configurations>
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
<session_origin lat="47.579166412353516" lon="-122.13232421875" alt="2.0" scale="150.0"/>
<session_options>
<configuration name="controlnet" value="172.16.0.0/24"/>
<configuration name="controlnet0" value=""/>
@ -117,10 +120,19 @@
<configuration name="enablesdt" value="0"/>
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
<configuration name="ovs" value="0"/>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
<configuration name="mtu" value="0"/>
</session_options>
<session_metadata>
<configuration name="canvas c1" value="{name {Canvas1}}"/>
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
<configuration name="shapes" value="[]"/>
<configuration name="edges" value="[]"/>
<configuration name="hidden" value="[]"/>
<configuration name="canvas" value="{&quot;gridlines&quot;: true, &quot;canvases&quot;: [{&quot;id&quot;: 1, &quot;wallpaper&quot;: null, &quot;wallpaper_style&quot;: 1, &quot;fit_image&quot;: false, &quot;dimensions&quot;: [1000, 750]}]}"/>
</session_metadata>
<default_services>
<node type="mdr">

View file

@ -1,66 +1,55 @@
<?xml version='1.0' encoding='UTF-8'?>
<scenario name="/home/developer/.core/configs/emane-demo-precomputed.xml">
<scenario name="/tmp/tmpfokvqh5k">
<networks>
<network id="3" name="wlan3" model="emane_rfpipe" type="EMANE">
<position x="282" y="317" lat="47.57628519861569" lon="-122.12852212634816" alt="2.0"/>
<network id="3" name="wlan3" icon="" canvas="0" model="emane_rfpipe" type="EMANE">
<position x="282.0" y="317.0" lat="47.5762849109534" lon="-122.12852434509814" alt="2.0"/>
</network>
</networks>
<devices>
<device id="1" name="n1" type="mdr" class="" image="">
<position x="153" y="172" lat="47.57760325520506" lon="-122.13026036642295" alt="2.0"/>
<device id="1" name="n1" icon="" canvas="0" type="mdr" class="" image="">
<position x="153.0" y="172.0" lat="47.577602967549986" lon="-122.13026258517293" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
<service name="OSPFv3MDR"/>
</services>
</device>
<device id="2" name="n2" type="mdr" class="" image="">
<position x="393" y="171" lat="47.57761234513531" lon="-122.12702643140011" alt="2.0"/>
<device id="2" name="n2" icon="" canvas="0" type="mdr" class="" image="">
<position x="393.0" y="171.0" lat="47.57761205748029" lon="-122.1270286501501" alt="2.0"/>
<services>
<service name="zebra"/>
<service name="OSPFv3MDR"/>
<service name="IPForward"/>
<service name="OSPFv3MDR"/>
</services>
</device>
</devices>
<links>
<link node1="3" node2="1">
<iface2 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
<link node1="1" node2="3">
<iface1 nem="1" id="0" name="eth0" mac="02:02:00:00:00:01" ip4="10.0.0.1" ip4_mask="32" ip6="2001::1" ip6_mask="128"/>
</link>
<link node1="3" node2="2">
<iface2 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
<link node1="2" node2="3">
<iface1 nem="2" id="0" name="eth0" mac="02:02:00:00:00:02" ip4="10.0.0.2" ip4_mask="32" ip6="2001::2" ip6_mask="128"/>
</link>
</links>
<emane_global_configuration>
<emulator>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="controlportendpoint" value="0.0.0.0:47000"/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</emulator>
<core>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
</core>
</emane_global_configuration>
<emane_configurations>
<emane_configuration node="3" model="emane_rfpipe">
<platform>
<configuration name="antennaprofilemanifesturi" value=""/>
<configuration name="eventservicedevice" value="ctrl0"/>
<configuration name="eventservicegroup" value="224.1.2.8:45703"/>
<configuration name="eventservicettl" value="1"/>
<configuration name="otamanagerchannelenable" value="1"/>
<configuration name="otamanagerdevice" value="ctrl0"/>
<configuration name="otamanagergroup" value="224.1.2.8:45702"/>
<configuration name="otamanagerloopback" value="0"/>
<configuration name="otamanagermtu" value="0"/>
<configuration name="otamanagerpartcheckthreshold" value="2"/>
<configuration name="otamanagerparttimeoutthreshold" value="5"/>
<configuration name="otamanagerttl" value="1"/>
<configuration name="stats.event.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxeventcountrows" value="0"/>
<configuration name="stats.ota.maxpacketcountrows" value="0"/>
</platform>
<mac>
<configuration name="datarate" value="1000000"/>
<configuration name="delay" value="0.000000"/>
@ -75,6 +64,17 @@
</mac>
<phy>
<configuration name="bandwidth" value="1000000"/>
<configuration name="compatibilitymode" value="1"/>
<configuration name="dopplershiftenable" value="1"/>
<configuration name="excludesamesubidfromfilterenable" value="1"/>
<configuration name="fading.lognormal.dlthresh" value="0.250000"/>
<configuration name="fading.lognormal.dmu" value="5.000000"/>
<configuration name="fading.lognormal.dsigma" value="1.000000"/>
<configuration name="fading.lognormal.duthresh" value="0.750000"/>
<configuration name="fading.lognormal.lmean" value="0.005000"/>
<configuration name="fading.lognormal.lstddev" value="0.001000"/>
<configuration name="fading.lognormal.maxpathloss" value="100.000000"/>
<configuration name="fading.lognormal.minpathloss" value="0.000000"/>
<configuration name="fading.model" value="none"/>
<configuration name="fading.nakagami.distance0" value="100.000000"/>
<configuration name="fading.nakagami.distance1" value="250.000000"/>
@ -91,7 +91,10 @@
<configuration name="noisemaxsegmentduration" value="1000000"/>
<configuration name="noisemaxsegmentoffset" value="300000"/>
<configuration name="noisemode" value="none"/>
<configuration name="processingpoolsize" value="0"/>
<configuration name="propagationmodel" value="precomputed"/>
<configuration name="rxsensitivitypromiscuousmodeenable" value="0"/>
<configuration name="stats.receivepowertableenable" value="1"/>
<configuration name="subid" value="1"/>
<configuration name="systemnoisefigure" value="4.000000"/>
<configuration name="timesyncthreshold" value="10000"/>
@ -104,7 +107,7 @@
</external>
</emane_configuration>
</emane_configurations>
<session_origin lat="47.5791667" lon="-122.132322" alt="2.0" scale="150.0"/>
<session_origin lat="47.579166412353516" lon="-122.13232421875" alt="2.0" scale="150.0"/>
<session_options>
<configuration name="controlnet" value="172.16.0.0/24"/>
<configuration name="controlnet0" value=""/>
@ -117,10 +120,19 @@
<configuration name="enablesdt" value="0"/>
<configuration name="sdturl" value="tcp://127.0.0.1:50000/"/>
<configuration name="ovs" value="0"/>
<configuration name="platform_id_start" value="1"/>
<configuration name="nem_id_start" value="1"/>
<configuration name="link_enabled" value="1"/>
<configuration name="loss_threshold" value="30"/>
<configuration name="link_interval" value="1"/>
<configuration name="link_timeout" value="4"/>
<configuration name="mtu" value="0"/>
</session_options>
<session_metadata>
<configuration name="canvas c1" value="{name {Canvas1}}"/>
<configuration name="global_options" value="interface_names=no ip_addresses=yes ipv6_addresses=yes node_labels=yes link_labels=yes show_api=no background_images=no annotations=yes grid=yes traffic_start=0"/>
<configuration name="shapes" value="[]"/>
<configuration name="edges" value="[]"/>
<configuration name="hidden" value="[]"/>
<configuration name="canvas" value="{&quot;gridlines&quot;: true, &quot;canvases&quot;: [{&quot;id&quot;: 1, &quot;wallpaper&quot;: null, &quot;wallpaper_style&quot;: 1, &quot;fit_image&quot;: false, &quot;dimensions&quot;: [1000, 750]}]}"/>
</session_metadata>
<default_services>
<node type="mdr">

View file

@ -95,10 +95,9 @@
<service name="DefaultRoute"/>
</services>
</device>
<device id="14" name="n14" icon="" canvas="0" type="host" class="" image="">
<device id="14" name="n14" icon="" canvas="0" type="PC" class="" image="">
<position x="348.0" y="228.0" lat="47.57709392893491" lon="-122.12763501296686" alt="2.0"/>
<services>
<service name="SSH"/>
<service name="DefaultRoute"/>
</services>
</device>
@ -260,9 +259,5 @@
<service name="OSPFv3"/>
<service name="IPForward"/>
</node>
<node type="host">
<service name="DefaultRoute"/>
<service name="SSH"/>
</node>
</default_services>
</scenario>

View file

@ -3,7 +3,7 @@ check engine light
"""
import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Dict, Optional
from typing import TYPE_CHECKING, Optional
from core.api.grpc.wrappers import ExceptionEvent, ExceptionLevel
from core.gui.dialogs.dialog import Dialog
@ -19,7 +19,7 @@ class AlertsDialog(Dialog):
super().__init__(app, "Alerts")
self.tree: Optional[ttk.Treeview] = None
self.codetext: Optional[CodeText] = None
self.alarm_map: Dict[int, ExceptionEvent] = {}
self.alarm_map: dict[int, ExceptionEvent] = {}
self.draw()
def draw(self) -> None:

View file

@ -4,7 +4,7 @@ set wallpaper
import logging
import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, List, Optional
from typing import TYPE_CHECKING, Optional
from core.gui import images
from core.gui.appconfig import BACKGROUNDS_PATH
@ -32,7 +32,7 @@ class CanvasWallpaperDialog(Dialog):
)
self.filename: tk.StringVar = tk.StringVar(value=self.canvas.wallpaper_file)
self.image_label: Optional[ttk.Label] = None
self.options: List[ttk.Radiobutton] = []
self.options: list[ttk.Radiobutton] = []
self.draw()
def draw(self) -> None:

View file

@ -3,7 +3,7 @@ custom color picker
"""
import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Optional, Tuple
from typing import TYPE_CHECKING, Optional
from core.gui import validation
from core.gui.dialogs.dialog import Dialog
@ -13,6 +13,36 @@ if TYPE_CHECKING:
from core.gui.app import Application
def get_rgb(red: int, green: int, blue: int) -> str:
"""
Convert rgb integers to an rgb hex code (#<red><green><blue>).
:param red: red value
:param green: green value
:param blue: blue value
:return: rgb hex code
"""
return f"#{red:02x}{green:02x}{blue:02x}"
def get_rgb_values(hex_code: str) -> tuple[int, int, int]:
"""
Convert a valid rgb hex code (#<red><green><blue>) to rgb integers.
:param hex_code: valid rgb hex code
:return: a tuple of red, blue, and green values
"""
if len(hex_code) == 4:
red = hex_code[1]
green = hex_code[2]
blue = hex_code[3]
else:
red = hex_code[1:3]
green = hex_code[3:5]
blue = hex_code[5:]
return int(red, 16), int(green, 16), int(blue, 16)
class ColorPickerDialog(Dialog):
def __init__(
self, master: tk.BaseWidget, app: "Application", initcolor: str = "#000000"
@ -27,7 +57,7 @@ class ColorPickerDialog(Dialog):
self.blue_label: Optional[ttk.Label] = None
self.display: Optional[tk.Frame] = None
self.color: str = initcolor
red, green, blue = self.get_rgb(initcolor)
red, green, blue = get_rgb_values(initcolor)
self.red: tk.IntVar = tk.IntVar(value=red)
self.blue: tk.IntVar = tk.IntVar(value=blue)
self.green: tk.IntVar = tk.IntVar(value=green)
@ -66,7 +96,7 @@ class ColorPickerDialog(Dialog):
)
scale.grid(row=0, column=2, sticky=tk.EW, padx=PADX)
self.red_label = ttk.Label(
frame, background="#%02x%02x%02x" % (self.red.get(), 0, 0), width=5
frame, background=get_rgb(self.red.get(), 0, 0), width=5
)
self.red_label.grid(row=0, column=3, sticky=tk.EW)
@ -89,7 +119,7 @@ class ColorPickerDialog(Dialog):
)
scale.grid(row=0, column=2, sticky=tk.EW, padx=PADX)
self.green_label = ttk.Label(
frame, background="#%02x%02x%02x" % (0, self.green.get(), 0), width=5
frame, background=get_rgb(0, self.green.get(), 0), width=5
)
self.green_label.grid(row=0, column=3, sticky=tk.EW)
@ -112,7 +142,7 @@ class ColorPickerDialog(Dialog):
)
scale.grid(row=0, column=2, sticky=tk.EW, padx=PADX)
self.blue_label = ttk.Label(
frame, background="#%02x%02x%02x" % (0, 0, self.blue.get()), width=5
frame, background=get_rgb(0, 0, self.blue.get()), width=5
)
self.blue_label.grid(row=0, column=3, sticky=tk.EW)
@ -150,39 +180,27 @@ class ColorPickerDialog(Dialog):
self.color = self.hex.get()
self.destroy()
def get_hex(self) -> str:
"""
convert current RGB values into hex color
"""
red = self.red_entry.get()
blue = self.blue_entry.get()
green = self.green_entry.get()
return "#%02x%02x%02x" % (int(red), int(green), int(blue))
def current_focus(self, focus: str) -> None:
self.focus = focus
def update_color(self, arg1=None, arg2=None, arg3=None) -> None:
if self.focus == "rgb":
red = self.red_entry.get()
blue = self.blue_entry.get()
green = self.green_entry.get()
red = int(self.red_entry.get() or 0)
blue = int(self.blue_entry.get() or 0)
green = int(self.green_entry.get() or 0)
self.set_scale(red, green, blue)
if red and blue and green:
hex_code = "#%02x%02x%02x" % (int(red), int(green), int(blue))
self.hex.set(hex_code)
self.display.config(background=hex_code)
self.set_label(red, green, blue)
hex_code = get_rgb(red, green, blue)
self.hex.set(hex_code)
self.display.config(background=hex_code)
self.set_label(red, green, blue)
elif self.focus == "hex":
hex_code = self.hex.get()
if len(hex_code) == 4 or len(hex_code) == 7:
red, green, blue = self.get_rgb(hex_code)
else:
return
self.set_entry(red, green, blue)
self.set_scale(red, green, blue)
self.display.config(background=hex_code)
self.set_label(str(red), str(green), str(blue))
red, green, blue = get_rgb_values(hex_code)
self.set_entry(red, green, blue)
self.set_scale(red, green, blue)
self.display.config(background=hex_code)
self.set_label(red, green, blue)
def scale_callback(self, var: tk.IntVar, color_var: tk.IntVar) -> None:
color_var.set(var.get())
@ -199,21 +217,7 @@ class ColorPickerDialog(Dialog):
self.green.set(green)
self.blue.set(blue)
def set_label(self, red: str, green: str, blue: str) -> None:
self.red_label.configure(background="#%02x%02x%02x" % (int(red), 0, 0))
self.green_label.configure(background="#%02x%02x%02x" % (0, int(green), 0))
self.blue_label.configure(background="#%02x%02x%02x" % (0, 0, int(blue)))
def get_rgb(self, hex_code: str) -> Tuple[int, int, int]:
"""
convert a valid hex code to RGB values
"""
if len(hex_code) == 4:
red = hex_code[1]
green = hex_code[2]
blue = hex_code[3]
else:
red = hex_code[1:3]
green = hex_code[3:5]
blue = hex_code[5:]
return int(red, 16), int(green, 16), int(blue, 16)
def set_label(self, red: int, green: int, blue: int) -> None:
self.red_label.configure(background=get_rgb(red, 0, 0))
self.green_label.configure(background=get_rgb(0, green, 0))
self.blue_label.configure(background=get_rgb(0, 0, blue))

View file

@ -4,7 +4,7 @@ Service configuration dialog
import logging
import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Dict, List, Optional, Set
from typing import TYPE_CHECKING, Optional
import grpc
@ -34,24 +34,23 @@ class ConfigServiceConfigDialog(Dialog):
self.core: "CoreClient" = app.core
self.node: Node = node
self.service_name: str = service_name
self.radiovar: tk.IntVar = tk.IntVar()
self.radiovar.set(2)
self.directories: List[str] = []
self.templates: List[str] = []
self.dependencies: List[str] = []
self.executables: List[str] = []
self.startup_commands: List[str] = []
self.validation_commands: List[str] = []
self.shutdown_commands: List[str] = []
self.default_startup: List[str] = []
self.default_validate: List[str] = []
self.default_shutdown: List[str] = []
self.radiovar: tk.IntVar = tk.IntVar(value=2)
self.directories: list[str] = []
self.templates: list[str] = []
self.rendered: dict[str, str] = {}
self.dependencies: list[str] = []
self.executables: list[str] = []
self.startup_commands: list[str] = []
self.validation_commands: list[str] = []
self.shutdown_commands: list[str] = []
self.default_startup: list[str] = []
self.default_validate: list[str] = []
self.default_shutdown: list[str] = []
self.validation_mode: Optional[ServiceValidationMode] = None
self.validation_time: Optional[int] = None
self.validation_period: tk.StringVar = tk.StringVar()
self.modes: List[str] = []
self.mode_configs: Dict[str, Dict[str, str]] = {}
self.validation_period: tk.DoubleVar = tk.DoubleVar()
self.modes: list[str] = []
self.mode_configs: dict[str, dict[str, str]] = {}
self.notebook: Optional[ttk.Notebook] = None
self.templates_combobox: Optional[ttk.Combobox] = None
self.modes_combobox: Optional[ttk.Combobox] = None
@ -61,13 +60,14 @@ class ConfigServiceConfigDialog(Dialog):
self.validation_time_entry: Optional[ttk.Entry] = None
self.validation_mode_entry: Optional[ttk.Entry] = None
self.template_text: Optional[CodeText] = None
self.rendered_text: Optional[CodeText] = None
self.validation_period_entry: Optional[ttk.Entry] = None
self.original_service_files: Dict[str, str] = {}
self.temp_service_files: Dict[str, str] = {}
self.modified_files: Set[str] = set()
self.original_service_files: dict[str, str] = {}
self.temp_service_files: dict[str, str] = {}
self.modified_files: set[str] = set()
self.config_frame: Optional[ConfigFrame] = None
self.default_config: Dict[str, str] = {}
self.config: Dict[str, ConfigOption] = {}
self.default_config: dict[str, str] = {}
self.config: dict[str, ConfigOption] = {}
self.has_error: bool = False
self.load()
if not self.has_error:
@ -87,14 +87,18 @@ class ConfigServiceConfigDialog(Dialog):
self.validation_mode = service.validation_mode
self.validation_time = service.validation_timer
self.validation_period.set(service.validation_period)
defaults = self.core.client.get_config_service_defaults(self.service_name)
defaults = self.core.get_config_service_defaults(
self.node.id, self.service_name
)
self.original_service_files = defaults.templates
self.temp_service_files = dict(self.original_service_files)
self.modes = sorted(defaults.modes)
self.mode_configs = defaults.modes
self.config = ConfigOption.from_dict(defaults.config)
self.default_config = {x.name: x.value for x in self.config.values()}
self.rendered = self.core.get_config_service_rendered(
self.node.id, self.service_name
)
service_config = self.node.config_service_configs.get(self.service_name)
if service_config:
for key, value in service_config.config.items():
@ -110,7 +114,6 @@ class ConfigServiceConfigDialog(Dialog):
def draw(self) -> None:
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
# draw notebook
self.notebook = ttk.Notebook(self.top)
self.notebook.grid(sticky=tk.NSEW, pady=PADY)
@ -125,6 +128,7 @@ class ConfigServiceConfigDialog(Dialog):
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
tab.grid(sticky=tk.NSEW)
tab.columnconfigure(0, weight=1)
tab.rowconfigure(2, weight=1)
self.notebook.add(tab, text="Directories/Files")
label = ttk.Label(
@ -137,33 +141,54 @@ class ConfigServiceConfigDialog(Dialog):
frame.columnconfigure(1, weight=1)
label = ttk.Label(frame, text="Directories")
label.grid(row=0, column=0, sticky=tk.W, padx=PADX)
directories_combobox = ttk.Combobox(
frame, values=self.directories, state="readonly"
)
state = "readonly" if self.directories else tk.DISABLED
directories_combobox = ttk.Combobox(frame, values=self.directories, state=state)
directories_combobox.grid(row=0, column=1, sticky=tk.EW, pady=PADY)
if self.directories:
directories_combobox.current(0)
label = ttk.Label(frame, text="Templates")
label = ttk.Label(frame, text="Files")
label.grid(row=1, column=0, sticky=tk.W, padx=PADX)
state = "readonly" if self.templates else tk.DISABLED
self.templates_combobox = ttk.Combobox(
frame, values=self.templates, state="readonly"
frame, values=self.templates, state=state
)
self.templates_combobox.bind(
"<<ComboboxSelected>>", self.handle_template_changed
)
self.templates_combobox.grid(row=1, column=1, sticky=tk.EW, pady=PADY)
self.template_text = CodeText(tab)
# draw file template tab
notebook = ttk.Notebook(tab)
notebook.rowconfigure(0, weight=1)
notebook.columnconfigure(0, weight=1)
notebook.grid(sticky=tk.NSEW, pady=PADY)
# draw rendered file tab
rendered_tab = ttk.Frame(notebook, padding=FRAME_PAD)
rendered_tab.grid(sticky=tk.NSEW)
rendered_tab.rowconfigure(0, weight=1)
rendered_tab.columnconfigure(0, weight=1)
notebook.add(rendered_tab, text="Rendered")
self.rendered_text = CodeText(rendered_tab)
self.rendered_text.grid(sticky=tk.NSEW)
self.rendered_text.text.bind("<FocusOut>", self.update_template_file_data)
# draw template file tab
template_tab = ttk.Frame(notebook, padding=FRAME_PAD)
template_tab.grid(sticky=tk.NSEW)
template_tab.rowconfigure(0, weight=1)
template_tab.columnconfigure(0, weight=1)
notebook.add(template_tab, text="Template")
self.template_text = CodeText(template_tab)
self.template_text.grid(sticky=tk.NSEW)
tab.rowconfigure(self.template_text.grid_info()["row"], weight=1)
self.template_text.text.bind("<FocusOut>", self.update_template_file_data)
if self.templates:
self.templates_combobox.current(0)
self.template_text.text.delete(1.0, "end")
self.template_text.text.insert(
"end", self.temp_service_files[self.templates[0]]
)
self.template_text.text.bind("<FocusOut>", self.update_template_file_data)
template_name = self.templates[0]
temp_data = self.temp_service_files[template_name]
self.template_text.set_text(temp_data)
rendered_data = self.rendered[template_name]
self.rendered_text.set_text(rendered_data)
else:
self.template_text.text.configure(state=tk.DISABLED)
self.rendered_text.text.configure(state=tk.DISABLED)
def draw_tab_config(self) -> None:
tab = ttk.Frame(self.notebook, padding=FRAME_PAD)
@ -243,7 +268,7 @@ class ConfigServiceConfigDialog(Dialog):
label = ttk.Label(frame, text="Validation Time")
label.grid(row=0, column=0, sticky=tk.W, padx=PADX)
self.validation_time_entry = ttk.Entry(frame)
self.validation_time_entry.insert("end", self.validation_time)
self.validation_time_entry.insert("end", str(self.validation_time))
self.validation_time_entry.config(state=tk.DISABLED)
self.validation_time_entry.grid(row=0, column=1, sticky=tk.EW, pady=PADY)
@ -323,9 +348,11 @@ class ConfigServiceConfigDialog(Dialog):
self.destroy()
def handle_template_changed(self, event: tk.Event) -> None:
template = self.templates_combobox.get()
self.template_text.text.delete(1.0, "end")
self.template_text.text.insert("end", self.temp_service_files[template])
template_name = self.templates_combobox.get()
temp_data = self.temp_service_files[template_name]
self.template_text.set_text(temp_data)
rendered = self.rendered[template_name]
self.rendered_text.set_text(rendered)
def handle_mode_changed(self, event: tk.Event) -> None:
mode = self.modes_combobox.get()
@ -333,10 +360,13 @@ class ConfigServiceConfigDialog(Dialog):
logger.info("mode config: %s", config)
self.config_frame.set_values(config)
def update_template_file_data(self, event: tk.Event) -> None:
scrolledtext = event.widget
def update_template_file_data(self, _event: tk.Event) -> None:
template = self.templates_combobox.get()
self.temp_service_files[template] = scrolledtext.get(1.0, "end")
self.temp_service_files[template] = self.rendered_text.get_text()
if self.rendered[template] != self.temp_service_files[template]:
self.modified_files.add(template)
return
self.temp_service_files[template] = self.template_text.get_text()
if self.temp_service_files[template] != self.original_service_files[template]:
self.modified_files.add(template)
else:
@ -351,14 +381,24 @@ class ConfigServiceConfigDialog(Dialog):
return has_custom_templates or has_custom_config
def click_defaults(self) -> None:
# clear all saved state data
self.modified_files.clear()
self.node.config_service_configs.pop(self.service_name, None)
self.temp_service_files = dict(self.original_service_files)
# reset session definition and retrieve default rendered templates
self.core.start_session(definition=True)
self.rendered = self.core.get_config_service_rendered(
self.node.id, self.service_name
)
logger.info(
"cleared config service config: %s", self.node.config_service_configs
)
self.temp_service_files = dict(self.original_service_files)
filename = self.templates_combobox.get()
self.template_text.text.delete(1.0, "end")
self.template_text.text.insert("end", self.temp_service_files[filename])
# reset current selected file data and config data, if present
template_name = self.templates_combobox.get()
temp_data = self.temp_service_files[template_name]
self.template_text.set_text(temp_data)
rendered_data = self.rendered[template_name]
self.rendered_text.set_text(rendered_data)
if self.config_frame:
logger.info("resetting defaults: %s", self.default_config)
self.config_frame.set_values(self.default_config)
@ -367,7 +407,7 @@ class ConfigServiceConfigDialog(Dialog):
pass
def append_commands(
self, commands: List[str], listbox: tk.Listbox, to_add: List[str]
self, commands: list[str], listbox: tk.Listbox, to_add: list[str]
) -> None:
for cmd in to_add:
commands.append(cmd)

View file

@ -4,7 +4,7 @@ copy service config dialog
import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Dict, Optional
from typing import TYPE_CHECKING, Optional
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
@ -29,7 +29,7 @@ class CopyServiceConfigDialog(Dialog):
self.service: str = service
self.file_name: str = file_name
self.listbox: Optional[tk.Listbox] = None
self.nodes: Dict[str, int] = {}
self.nodes: dict[str, int] = {}
self.draw()
def draw(self) -> None:

View file

@ -2,7 +2,7 @@ import logging
import tkinter as tk
from pathlib import Path
from tkinter import ttk
from typing import TYPE_CHECKING, Optional, Set
from typing import TYPE_CHECKING, Optional
from PIL.ImageTk import PhotoImage
@ -21,13 +21,13 @@ if TYPE_CHECKING:
class ServicesSelectDialog(Dialog):
def __init__(
self, master: tk.BaseWidget, app: "Application", current_services: Set[str]
self, master: tk.BaseWidget, app: "Application", current_services: set[str]
) -> None:
super().__init__(app, "Node Services", master=master)
super().__init__(app, "Node Config Services", master=master)
self.groups: Optional[ListboxScroll] = None
self.services: Optional[CheckboxList] = None
self.current: Optional[ListboxScroll] = None
self.current_services: Set[str] = current_services
self.current_services: set[str] = current_services
self.draw()
def draw(self) -> None:
@ -45,7 +45,7 @@ class ServicesSelectDialog(Dialog):
label_frame.columnconfigure(0, weight=1)
self.groups = ListboxScroll(label_frame)
self.groups.grid(sticky=tk.NSEW)
for group in sorted(self.app.core.services):
for group in sorted(self.app.core.config_services_groups):
self.groups.listbox.insert(tk.END, group)
self.groups.listbox.bind("<<ListboxSelect>>", self.handle_group_change)
self.groups.listbox.selection_set(0)
@ -86,7 +86,7 @@ class ServicesSelectDialog(Dialog):
index = selection[0]
group = self.groups.listbox.get(index)
self.services.clear()
for name in sorted(self.app.core.services[group]):
for name in sorted(self.app.core.config_services_groups[group]):
checked = name in self.current_services
self.services.add(name, checked)
@ -114,7 +114,7 @@ class CustomNodesDialog(Dialog):
self.image_button: Optional[ttk.Button] = None
self.image: Optional[PhotoImage] = None
self.image_file: Optional[str] = None
self.services: Set[str] = set()
self.services: set[str] = set()
self.selected: Optional[str] = None
self.selected_index: Optional[int] = None
self.draw()
@ -147,7 +147,7 @@ class CustomNodesDialog(Dialog):
frame, text="Icon", compound=tk.LEFT, command=self.click_icon
)
self.image_button.grid(sticky=tk.EW, pady=PADY)
button = ttk.Button(frame, text="Services", command=self.click_services)
button = ttk.Button(frame, text="Config Services", command=self.click_services)
button.grid(sticky=tk.EW)
def draw_node_buttons(self) -> None:

View file

@ -4,7 +4,7 @@ emane configuration
import tkinter as tk
import webbrowser
from tkinter import ttk
from typing import TYPE_CHECKING, Dict, List, Optional
from typing import TYPE_CHECKING, Optional
import grpc
@ -37,11 +37,13 @@ class EmaneModelDialog(Dialog):
self.has_error: bool = False
try:
config = self.node.emane_model_configs.get((self.model, self.iface_id))
if not config:
config = self.node.emane_model_configs.get((self.model, None))
if not config:
config = self.app.core.get_emane_model_config(
self.node.id, self.model, self.iface_id
)
self.config: Dict[str, ConfigOption] = config
self.config: dict[str, ConfigOption] = config
self.draw()
except grpc.RpcError as e:
self.app.show_grpc_exception("Get EMANE Config Error", e)
@ -80,7 +82,7 @@ class EmaneConfigDialog(Dialog):
self.node: Node = node
self.radiovar: tk.IntVar = tk.IntVar()
self.radiovar.set(1)
self.emane_models: List[str] = [
self.emane_models: list[str] = [
x.split("_")[1] for x in self.app.core.emane_models
]
model = self.node.emane.split("_")[1]

View file

@ -1,5 +1,5 @@
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox, ttk
from typing import TYPE_CHECKING, Optional
from core.api.grpc.wrappers import Hook, SessionState
@ -91,6 +91,13 @@ class HookDialog(Dialog):
self.hook.file = file_name
self.hook.data = data
else:
if file_name in self.app.core.session.hooks:
messagebox.showerror(
"Hook Error",
f"Hook {file_name} already exists!",
parent=self.master,
)
return
self.hook = Hook(state=state, file=file_name, data=data)
self.destroy()

View file

@ -1,6 +1,6 @@
import tkinter as tk
from tkinter import messagebox, ttk
from typing import TYPE_CHECKING, List, Optional
from typing import TYPE_CHECKING, Optional
import netaddr
@ -17,8 +17,8 @@ class IpConfigDialog(Dialog):
super().__init__(app, "IP Configuration")
self.ip4: str = self.app.guiconfig.ips.ip4
self.ip6: str = self.app.guiconfig.ips.ip6
self.ip4s: List[str] = self.app.guiconfig.ips.ip4s
self.ip6s: List[str] = self.app.guiconfig.ips.ip6s
self.ip4s: list[str] = self.app.guiconfig.ips.ip4s
self.ip6s: list[str] = self.app.guiconfig.ips.ip6s
self.ip4_entry: Optional[ttk.Entry] = None
self.ip4_listbox: Optional[ListboxScroll] = None
self.ip6_entry: Optional[ttk.Entry] = None

View file

@ -3,7 +3,7 @@ mobility configuration
"""
import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Dict, Optional
from typing import TYPE_CHECKING, Optional
import grpc
@ -26,7 +26,7 @@ class MobilityConfigDialog(Dialog):
config = self.node.mobility_config
if not config:
config = self.app.core.get_mobility_config(self.node.id)
self.config: Dict[str, ConfigOption] = config
self.config: dict[str, ConfigOption] = config
self.draw()
except grpc.RpcError as e:
self.app.show_grpc_exception("Get Mobility Config Error", e)

View file

@ -2,7 +2,7 @@ import logging
import tkinter as tk
from functools import partial
from tkinter import messagebox, ttk
from typing import TYPE_CHECKING, Dict, Optional
from typing import TYPE_CHECKING, Optional
import netaddr
from PIL.ImageTk import PhotoImage
@ -190,7 +190,7 @@ class NodeConfigDialog(Dialog):
if self.node.server:
server = self.node.server
self.server: tk.StringVar = tk.StringVar(value=server)
self.ifaces: Dict[int, InterfaceData] = {}
self.ifaces: dict[int, InterfaceData] = {}
self.draw()
def draw(self) -> None:
@ -230,13 +230,8 @@ class NodeConfigDialog(Dialog):
if nutils.is_model(self.node):
label = ttk.Label(frame, text="Type")
label.grid(row=row, column=0, sticky=tk.EW, padx=PADX, pady=PADY)
combobox = ttk.Combobox(
frame,
textvariable=self.type,
values=list(nutils.NODE_MODELS),
state=combo_state,
)
combobox.grid(row=row, column=1, sticky=tk.EW)
entry = ttk.Entry(frame, textvariable=self.type, state=tk.DISABLED)
entry.grid(row=row, column=1, sticky=tk.EW)
row += 1
# container image field
@ -275,7 +270,7 @@ class NodeConfigDialog(Dialog):
ifaces_scroll.listbox.bind("<<ListboxSelect>>", self.iface_select)
# interfaces
if self.canvas_node.ifaces:
if nutils.is_container(self.node):
self.draw_ifaces()
self.draw_spacer()

View file

@ -4,7 +4,7 @@ core node services
import logging
import tkinter as tk
from tkinter import messagebox, ttk
from typing import TYPE_CHECKING, Optional, Set
from typing import TYPE_CHECKING, Optional
from core.api.grpc.wrappers import Node
from core.gui.dialogs.configserviceconfig import ConfigServiceConfigDialog
@ -20,7 +20,7 @@ if TYPE_CHECKING:
class NodeConfigServiceDialog(Dialog):
def __init__(
self, app: "Application", node: Node, services: Set[str] = None
self, app: "Application", node: Node, services: set[str] = None
) -> None:
title = f"{node.name} Config Services"
super().__init__(app, title)
@ -30,7 +30,7 @@ class NodeConfigServiceDialog(Dialog):
self.current: Optional[ListboxScroll] = None
if services is None:
services = set(node.config_services)
self.current_services: Set[str] = services
self.current_services: set[str] = services
self.protocol("WM_DELETE_WINDOW", self.click_cancel)
self.draw()

View file

@ -3,7 +3,7 @@ core node services
"""
import tkinter as tk
from tkinter import messagebox, ttk
from typing import TYPE_CHECKING, Optional, Set
from typing import TYPE_CHECKING, Optional
from core.api.grpc.wrappers import Node
from core.gui.dialogs.dialog import Dialog
@ -24,7 +24,7 @@ class NodeServiceDialog(Dialog):
self.services: Optional[CheckboxList] = None
self.current: Optional[ListboxScroll] = None
services = set(node.services)
self.current_services: Set[str] = services
self.current_services: set[str] = services
self.protocol("WM_DELETE_WINDOW", self.click_cancel)
self.draw()

View file

@ -1,6 +1,6 @@
import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Dict, Optional
from typing import TYPE_CHECKING, Optional
from core.gui import nodeutils as nutils
from core.gui.dialogs.dialog import Dialog
@ -17,7 +17,7 @@ class RunToolDialog(Dialog):
self.cmd: tk.StringVar = tk.StringVar(value="ps ax")
self.result: Optional[CodeText] = None
self.node_list: Optional[ListboxScroll] = None
self.executable_nodes: Dict[str, int] = {}
self.executable_nodes: dict[str, int] = {}
self.store_nodes()
self.draw()

View file

@ -2,7 +2,7 @@ import logging
import tkinter as tk
from pathlib import Path
from tkinter import filedialog, messagebox, ttk
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
from typing import TYPE_CHECKING, Optional
import grpc
from PIL.ImageTk import PhotoImage
@ -35,21 +35,21 @@ class ServiceConfigDialog(Dialog):
self.service_name: str = service_name
self.radiovar: tk.IntVar = tk.IntVar(value=2)
self.metadata: str = ""
self.filenames: List[str] = []
self.dependencies: List[str] = []
self.executables: List[str] = []
self.startup_commands: List[str] = []
self.validation_commands: List[str] = []
self.shutdown_commands: List[str] = []
self.default_startup: List[str] = []
self.default_validate: List[str] = []
self.default_shutdown: List[str] = []
self.filenames: list[str] = []
self.dependencies: list[str] = []
self.executables: list[str] = []
self.startup_commands: list[str] = []
self.validation_commands: list[str] = []
self.shutdown_commands: list[str] = []
self.default_startup: list[str] = []
self.default_validate: list[str] = []
self.default_shutdown: list[str] = []
self.validation_mode: Optional[ServiceValidationMode] = None
self.validation_time: Optional[int] = None
self.validation_period: Optional[float] = None
self.directory_entry: Optional[ttk.Entry] = None
self.default_directories: List[str] = []
self.temp_directories: List[str] = []
self.default_directories: list[str] = []
self.temp_directories: list[str] = []
self.documentnew_img: PhotoImage = self.app.get_enum_icon(
ImageEnum.DOCUMENTNEW, width=ICON_SIZE
)
@ -67,10 +67,10 @@ class ServiceConfigDialog(Dialog):
self.validation_mode_entry: Optional[ttk.Entry] = None
self.service_file_data: Optional[CodeText] = None
self.validation_period_entry: Optional[ttk.Entry] = None
self.original_service_files: Dict[str, str] = {}
self.original_service_files: dict[str, str] = {}
self.default_config: Optional[NodeServiceData] = None
self.temp_service_files: Dict[str, str] = {}
self.modified_files: Set[str] = set()
self.temp_service_files: dict[str, str] = {}
self.modified_files: set[str] = set()
self.has_error: bool = False
self.load()
if not self.has_error:
@ -558,13 +558,13 @@ class ServiceConfigDialog(Dialog):
@classmethod
def append_commands(
cls, commands: List[str], listbox: tk.Listbox, to_add: List[str]
cls, commands: list[str], listbox: tk.Listbox, to_add: list[str]
) -> None:
for cmd in to_add:
commands.append(cmd)
listbox.insert(tk.END, cmd)
def get_commands(self) -> Tuple[List[str], List[str], List[str]]:
def get_commands(self) -> tuple[list[str], list[str], list[str]]:
startup = self.startup_commands_listbox.get(0, "end")
shutdown = self.shutdown_commands_listbox.get(0, "end")
validate = self.validate_commands_listbox.get(0, "end")

View file

@ -1,7 +1,7 @@
import logging
import tkinter as tk
from tkinter import messagebox, ttk
from typing import TYPE_CHECKING, List, Optional
from typing import TYPE_CHECKING, Optional
import grpc
@ -30,7 +30,7 @@ class SessionsDialog(Dialog):
self.protocol("WM_DELETE_WINDOW", self.on_closing)
self.draw()
def get_sessions(self) -> List[SessionSummary]:
def get_sessions(self) -> list[SessionSummary]:
try:
sessions = self.app.core.client.get_sessions()
logger.info("sessions: %s", sessions)

View file

@ -3,7 +3,7 @@ shape input dialog
"""
import tkinter as tk
from tkinter import font, ttk
from typing import TYPE_CHECKING, List, Optional, Union
from typing import TYPE_CHECKING, Optional, Union
from core.gui.dialogs.colorpicker import ColorPickerDialog
from core.gui.dialogs.dialog import Dialog
@ -16,8 +16,8 @@ if TYPE_CHECKING:
from core.gui.graph.graph import CanvasGraph
from core.gui.graph.shape import Shape
FONT_SIZES: List[int] = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]
BORDER_WIDTH: List[int] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
FONT_SIZES: list[int] = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72]
BORDER_WIDTH: list[int] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
class ShapeDialog(Dialog):
@ -168,7 +168,7 @@ class ShapeDialog(Dialog):
self.add_text()
self.destroy()
def make_font(self) -> List[Union[int, str]]:
def make_font(self) -> list[Union[int, str]]:
"""
create font for text or shape label
"""

View file

@ -0,0 +1,55 @@
import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Optional
import grpc
from core.api.grpc.wrappers import ConfigOption, Node
from core.gui.dialogs.dialog import Dialog
from core.gui.themes import PADX, PADY
from core.gui.widgets import ConfigFrame
if TYPE_CHECKING:
from core.gui.app import Application
from core.gui.graph.node import CanvasNode
class WirelessConfigDialog(Dialog):
def __init__(self, app: "Application", canvas_node: "CanvasNode"):
super().__init__(app, f"Wireless Configuration - {canvas_node.core_node.name}")
self.node: Node = canvas_node.core_node
self.config_frame: Optional[ConfigFrame] = None
self.config: dict[str, ConfigOption] = {}
try:
config = self.node.wireless_config
if not config:
config = self.app.core.get_wireless_config(self.node.id)
self.config: dict[str, ConfigOption] = config
self.draw()
except grpc.RpcError as e:
self.app.show_grpc_exception("Wireless Config Error", e)
self.has_error: bool = True
self.destroy()
def draw(self) -> None:
self.top.columnconfigure(0, weight=1)
self.top.rowconfigure(0, weight=1)
self.config_frame = ConfigFrame(self.top, self.app, self.config)
self.config_frame.draw_config()
self.config_frame.grid(sticky=tk.NSEW, pady=PADY)
self.draw_buttons()
def draw_buttons(self) -> None:
frame = ttk.Frame(self.top)
frame.grid(sticky=tk.EW)
for i in range(2):
frame.columnconfigure(i, weight=1)
button = ttk.Button(frame, text="Apply", command=self.click_apply)
button.grid(row=0, column=0, padx=PADX, sticky=tk.EW)
button = ttk.Button(frame, text="Cancel", command=self.destroy)
button.grid(row=0, column=1, sticky=tk.EW)
def click_apply(self) -> None:
self.config_frame.parse_config()
self.node.wireless_config = self.config
self.destroy()

View file

@ -1,6 +1,6 @@
import tkinter as tk
from tkinter import ttk
from typing import TYPE_CHECKING, Dict, Optional
from typing import TYPE_CHECKING, Optional
import grpc
@ -27,13 +27,13 @@ class WlanConfigDialog(Dialog):
self.config_frame: Optional[ConfigFrame] = None
self.range_entry: Optional[ttk.Entry] = None
self.has_error: bool = False
self.ranges: Dict[int, int] = {}
self.ranges: dict[int, int] = {}
self.positive_int: int = self.app.master.register(self.validate_and_update)
try:
config = self.node.wlan_config
if not config:
config = self.app.core.get_wlan_config(self.node.id)
self.config: Dict[str, ConfigOption] = config
self.config: dict[str, ConfigOption] = config
self.init_draw_range()
self.draw()
except grpc.RpcError as e:

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