Merge pull request #428 from coreemu/develop

Develop
This commit is contained in:
bharnden 2020-04-13 14:14:39 -07:00 committed by GitHub
commit d5dec12439
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
107 changed files with 3867 additions and 2640 deletions

View file

@ -7,10 +7,10 @@ jobs:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v1
- name: Set up Python 3.7
- name: Set up Python 3.6
uses: actions/setup-python@v1
with:
python-version: 3.7
python-version: 3.6
- name: Install pipenv
run: |
python -m pip install --upgrade pip
@ -19,11 +19,11 @@ jobs:
cp setup.py.in setup.py
cp core/constants.py.in core/constants.py
sed -i 's/True/False/g' core/constants.py
pipenv install --dev
pipenv sync --dev
- name: isort
run: |
cd daemon
pipenv run isort -c
pipenv run isort -c -df
- name: black
run: |
cd daemon

View file

@ -1,3 +1,46 @@
## 2020-04-13 CORE 6.3.0
* Features
* \#424 - added FRR IS-IS service
* Enhancements
* \#414 - update GUI OSPFv2 adjacency widget to work with FRR
* \#416 - EMANE links can now be drawn for 80211 and RF Pipe models
* \#418 #409 - code cleanup
* \#425 - added route monitor script for SDT3D integration
* a formal error will now be thrown when EMANE binding are not installed, but attempted to be used
* node positions will now default to 0,0 to avoid GUI errors, when one is not provided
* improved SDT3D integration, multiple link support and usage of custom layers
* Python GUI Enhancements
* enabled edit menu delete
* cleaned up node context menu and enabled delete
* Bugfixes
* \#427 - fixed issue in default route service
* \#426 - fixed issue reading ipsec template file
* \#420 - fixed issue with TLV API udp handler
* \#411 - allow wlan to be configured with 0 values
* \#415 - general EMANE configuration was not being saved/loaded from XML
## 2020-03-16 CORE 6.2.0
* gRPC API
* Added call to execute python script
* Enhancements
* \#371 - improved coretk gui scaling
* \#374 - display range visually for wlan in coretk gui, when configuring
* \#377 - improved coretk error dialogs
* \#379 - fixed issues with core converting between x,y and lon,lat for values that would cross utm zones
* \#384 - sdt integration moved internally to core code allowing it to work for coretk gui as well
* \#387 - coretk gui will now auto detect potential valid terminal and command to use for interacting with nodes during runtime
* \#389 - coretk gui will now attempt to reconnect to daemon without need to restart
* \#395 - coretk gui now has "save" and "save as" menu options
* \#402 - coretk will now allow terminal preference to be directly edited
* Bugfixes
* \#375 - fixed issues with emane event monitor handling data
* \#381 - executing a python script will now wait until completion before looking to join a new session
* \#391 - fixed configuring node ip addresses in coretk gui
* \#392 - fixed coretk link display when addresses are cleared out
* \#393 - coretk gui will properly clear marker annotations when switching sessions
* \#396 - Docker and LXC nodes will now properly save to XML
* \#406- WLAN bridge initialization was not ran when all nodes are disconnected
## 2020-02-20 CORE 6.1.0
* New
* config services - these services leverage a proper template engine and have configurable parameters, given enough time may replace existing services

View file

@ -2,7 +2,7 @@
CORE: Common Open Research Emulator
Copyright (c)2005-2019 the Boeing Company.
Copyright (c)2005-2020 the Boeing Company.
See the LICENSE file included in this distribution.
@ -14,28 +14,11 @@ networks to live networks. CORE consists of a GUI for drawing
topologies of lightweight virtual machines, and Python modules for
scripting network emulation.
## Documentation and Examples
## Documentation & Support
* Documentation hosted on GitHub
* <http://coreemu.github.io/core/>
* Basic Script Examples
* [Examples](daemon/examples/python)
* Custom Service Example
* [sample.py](daemon/examples/myservices/sample.py)
* Custom Emane Model Example
* [examplemodel.py](daemon/examples/myemane/examplemodel.py)
## Support
We are leveraging Discord for persistent chat rooms, voice chat, and
GitHub integration. This allows for more dynamic conversations and the
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.
<https://discord.gg/AKd7kmP>
## Building CORE
See [CORE Installation](http://coreemu.github.io/core/install.html) for detailed build instructions.
### Running CORE
See [Using the CORE GUI](http://coreemu.github.io/core/usage.html) for more details on running CORE.
* [Documentation](https://coreemu.github.io/core/)
* [Discord Channel](https://discord.gg/AKd7kmP)

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

208
daemon/Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "d702e6eed5a1362bf261543572bbffd2e8a87140b8d8cb07b99fb0d25220a2b5"
"sha256": "199897f713f6f338316b33fcbbe0001e9e55fcd5e5e24b2245a89454ce13321f"
},
"pipfile-spec": 6,
"requires": {},
@ -100,6 +100,15 @@
],
"version": "==2.8"
},
"dataclasses": {
"hashes": [
"sha256:3459118f7ede7c8bea0fe795bff7c6c2ce287d01dd226202f7c9ebc0610a7836",
"sha256:494a6dcae3b8bcf80848eea2ef64c0cc5cd307ffc263e17cdf42f3e5420808e6"
],
"index": "pypi",
"markers": "python_version == '3.6'",
"version": "==0.7"
},
"fabric": {
"hashes": [
"sha256:160331934ea60036604928e792fa8e9f813266b098ef5562aa82b88527740389",
@ -197,9 +206,10 @@
},
"mako": {
"hashes": [
"sha256:2984a6733e1d472796ceef37ad48c26f4a984bb18119bb2dbc37a44d8f6e75a4"
"sha256:3139c5d64aa5d175dbafb95027057128b5fbd05a40c53999f3905ceb53366d9d",
"sha256:8e8b53c71c7e59f3de716b6832c4e401d903af574f6962edbbbf6ecc2a5fe6c9"
],
"version": "==1.1.1"
"version": "==1.1.2"
},
"markupsafe": {
"hashes": [
@ -280,34 +290,12 @@
],
"version": "==7.0.0"
},
"protobuf": {
"hashes": [
"sha256:0bae429443cc4748be2aadfdaf9633297cfaeb24a9a02d0ab15849175ce90fab",
"sha256:24e3b6ad259544d717902777b33966a1a069208c885576254c112663e6a5bb0f",
"sha256:310a7aca6e7f257510d0c750364774034272538d51796ca31d42c3925d12a52a",
"sha256:52e586072612c1eec18e1174f8e3bb19d08f075fc2e3f91d3b16c919078469d0",
"sha256:73152776dc75f335c476d11d52ec6f0f6925774802cd48d6189f4d5d7fe753f4",
"sha256:7774bbbaac81d3ba86de646c39f154afc8156717972bf0450c9dbfa1dc8dbea2",
"sha256:82d7ac987715d8d1eb4068bf997f3053468e0ce0287e2729c30601feb6602fee",
"sha256:8eb9c93798b904f141d9de36a0ba9f9b73cc382869e67c9e642c0aba53b0fc07",
"sha256:adf0e4d57b33881d0c63bb11e7f9038f98ee0c3e334c221f0858f826e8fb0151",
"sha256:c40973a0aee65422d8cb4e7d7cbded95dfeee0199caab54d5ab25b63bce8135a",
"sha256:c77c974d1dadf246d789f6dad1c24426137c9091e930dbf50e0a29c1fcf00b1f",
"sha256:dd9aa4401c36785ea1b6fff0552c674bdd1b641319cb07ed1fe2392388e9b0d7",
"sha256:e11df1ac6905e81b815ab6fd518e79be0a58b5dc427a2cf7208980f30694b956",
"sha256:e2f8a75261c26b2f5f3442b0525d50fd79a71aeca04b5ec270fc123536188306",
"sha256:e512b7f3a4dd780f59f1bf22c302740e27b10b5c97e858a6061772668cd6f961",
"sha256:ef2c2e56aaf9ee914d3dccc3408d42661aaf7d9bb78eaa8f17b2e6282f214481",
"sha256:fac513a9dc2a74b99abd2e17109b53945e364649ca03d9f7a0b96aa8d1807d0a",
"sha256:fdfb6ad138dbbf92b5dbea3576d7c8ba7463173f7d2cb0ca1bd336ec88ddbd80"
],
"version": "==3.11.3"
},
"pycparser": {
"hashes": [
"sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3"
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
],
"version": "==2.19"
"version": "==2.20"
},
"pynacl": {
"hashes": [
@ -337,49 +325,46 @@
},
"pyproj": {
"hashes": [
"sha256:0a12982df36f55412597431676e51d3e8fcf9b3e41f18103c31edfb1fc5fa4c0",
"sha256:0b57669a568e4235f09fea9c4e498b9beca2673ea7318989569dbb750ed299c5",
"sha256:155064fde6a95f6328962386ebde043679fd744f1415e512ed88ec47760ed47c",
"sha256:189b8278784655ee2a3bfc51bde3091b5615cc982d0017edabcb10099b2ccb3f",
"sha256:1db407591f99877b551a655897da1fd95f4e82e089c8b0d29bcd8beffcffedb8",
"sha256:226e0c126d6db158dd3da8879e5efab9f05b1d67989c33fc6aa73bf70409bb12",
"sha256:2842412ea3f99383850df92dbbca837847f3e574f98f81eaa8caebc6514a26e2",
"sha256:2d2884e85b1e69ff829bfd54872c322d3d5662dc2120a17fbd1094b9c08f9dc5",
"sha256:341dc836a1a57b74494a95cff0f05029988d93e1f96ba6c190384ec757d482b2",
"sha256:3d69b6a197fc8cf3585290e272e1cdd641d6834a3c71894ec4f2b800d2210d2a",
"sha256:447d5b18d941bea180f04179946d1d4f4aa5012697d78c9a4ceac6081dd32465",
"sha256:4e8f18a8be5653e90f24b0aea74e85e10271d1c537742ede8a11b569d3583125",
"sha256:659b1d748cd7480324841da93f91097a726b898a2de0d192bc771d374006ceb4",
"sha256:6972adfe6bb40da0423c12c38617809bf50ca8b7411a20795a1c6c3d96f10942",
"sha256:75d7ed27e2e081d2036647f7b40a9e3d4f9ec4bde795925f3f7b4c6bb85f742e",
"sha256:7b623a18f70e70cbe594fa429283027c1a73d6d31c70cd04eea65845cd060b76",
"sha256:8112da72b47af9ffcc8f0f42224898ba6371680501b3657091bb7420b7dd5c03",
"sha256:9686c611893d1c182befa63157f4a1d629e7caa464adf21309cf4da5d422a264",
"sha256:98bb690ca7ea50148792f656c0366e799d70dd7e43ab8f0c733b64bd96842e1c",
"sha256:a6ede79fd7ddd176d824e0366f8d326ff8bc082d7332c9b40baf8cb8ae7d51fe",
"sha256:c7e7b6a00a701e166e5ce903159282f2969eef689fd7fb9d7bcf92aaf167e150",
"sha256:cb8c57faf91173c219739a37b909edc1c35a48a86d26be17f1a21ffd9f8728c3",
"sha256:ea6c7cbe2f277ca6b32ebad77d713681819e23b07b17a4a892878ffe245826b7",
"sha256:ec4b2146ec8fcc93c38fbd1dcb0df06e5737d588fe28d833dfb2b241d2736f54",
"sha256:f540f4af0223cb2195b0953db6c5cb45256137da430657db42ad1b076caca361"
"sha256:0d8196a5ac75fee2cf71c21066b3344427abfa8ad69b536d3404d5c7c9c0b886",
"sha256:12e378a0a21c73f96177f6cf64520f17e6b7aa02fc9cb27bd5c2d5b06ce170af",
"sha256:17738836128704d8f80b771572d77b8733841f0cb0ca42620549236ea62c4663",
"sha256:1a39175944710b225fd1943cb3b8ea0c8e059d3016360022ca10bbb7a6bfc9ae",
"sha256:2566bffb5395c9fbdb02077a0bc3e3ed0b2e4e3cadf65019e3139a8dfe27dd1d",
"sha256:3f43277f21ddaabed93b9885a4e494b785dca56e31fd37a935519d99b07807f0",
"sha256:424304beca6e0b0bc12aa46fc6d14a481ea47b1a4edec4854bb281656de38948",
"sha256:48128d794c8f52fcff2433a481e3aa2ccb0e0b3ccd51d3ad7cc10cc488c3f547",
"sha256:4a16b650722982cddedd45dfc36435b96e0ba83a2aebd4a4c247e5a68c852442",
"sha256:5161f1b5ece8a5263b64d97a32fbc473a4c6fdca5c95478e58e519ef1e97528e",
"sha256:6839ce14635ebfb01c67e456148f4f1fa04b03ef9645551b89d36593f2a3e57d",
"sha256:80e9f85ab81da75289308f23a62e1426a38411a07b0da738958d65ae8cc6c59c",
"sha256:881b44e94c781d02ecf1d9314fc7f44c09e6d54a8eac281869365999ac4db7a1",
"sha256:977542d2f8cf2981cf3ad72cedfebcd6ac56977c7aa830d9b49fa7888b56e83d",
"sha256:9bba6cbff7e23bb6d9062786d516602681b4414e9e423c138a7360e4d2a193e8",
"sha256:9bf64bba03ddc534ed3c6271ba8f9d31040f40cf8e9e7e458b6b1524a6f59082",
"sha256:9c712ceaa01488ebe6e357e1dfa2434c2304aad8a810e5d4c3d2abe21def6d58",
"sha256:b7da17e5a5c6039f85843e88c2f1ca8606d1a4cc13a87e7b68b9f51a54ef201a",
"sha256:bcdf81b3f13d2cc0354a4c3f7a567b71fcf6fe8098e519aaaee8e61f05c9de10",
"sha256:bebd3f987b7196e9d2ccfe55911b0c76ba9ce309bcabfb629ef205cbaaad37c5",
"sha256:c244e923073cd0bab74ba861ba31724aab90efda35b47a9676603c1a8e80b3ba",
"sha256:dacb94a9d570f4d9fc9369a22d44d7b3071cfe4d57d0ff2f57abd7ef6127fe41"
],
"version": "==2.5.0"
"version": "==2.6.0"
},
"pyyaml": {
"hashes": [
"sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
"sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
"sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
"sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
"sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
"sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
"sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
"sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
"sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
"sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
"sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
"sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
"sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
"sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
"sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
],
"version": "==5.3"
"version": "==5.3.1"
},
"six": {
"hashes": [
@ -421,10 +406,10 @@
},
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
"sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc",
"sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"
],
"version": "==7.0"
"version": "==7.1.1"
},
"distlib": {
"hashes": [
@ -553,26 +538,26 @@
},
"identify": {
"hashes": [
"sha256:1222b648251bdcb8deb240b294f450fbf704c7984e08baa92507e4ea10b436d5",
"sha256:d824ebe21f38325c771c41b08a95a761db1982f1fc0eee37c6c97df3f1636b96"
"sha256:a7577a1f55cee1d21953a5cf11a3c839ab87f5ef909a4cba6cf52ed72b4c6059",
"sha256:ab246293e6585a1c6361a505b68d5b501a0409310932b7de2c2ead667b564d89"
],
"version": "==1.4.11"
"version": "==1.4.13"
},
"importlib-metadata": {
"hashes": [
"sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302",
"sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b"
"sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f",
"sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"
],
"markers": "python_version < '3.8'",
"version": "==1.5.0"
"version": "==1.6.0"
},
"importlib-resources": {
"hashes": [
"sha256:6e2783b2538bd5a14678284a3962b0660c715e5a0f10243fd5e00a4b5974f50b",
"sha256:d3279fd0f6f847cced9f7acc19bd3e5df54d34f93a2e7bb5f238f81545787078"
"sha256:4019b6a9082d8ada9def02bece4a76b131518866790d58fdda0b5f8c603b36c2",
"sha256:dd98ceeef3f5ad2ef4cc287b8586da4ebad15877f351e9688987ad663a0a29b8"
],
"markers": "python_version < '3.7'",
"version": "==1.0.2"
"version": "==1.4.0"
},
"isort": {
"hashes": [
@ -591,11 +576,11 @@
},
"mock": {
"hashes": [
"sha256:2a572b715f09dd2f0a583d8aeb5bb67d7ed7a8fd31d193cf1227a99c16a67bc3",
"sha256:5e48d216809f6f393987ed56920305d8f3c647e6ed35407c1ff2ecb88a9e1151"
"sha256:3f9b2c0196c60d21838f307f5825a7b86b678cedc58ab9e50a8988187b4d81e0",
"sha256:dd33eb70232b6118298d516bbcecd26704689c386594f0f3c4f13867b2c56f72"
],
"index": "pypi",
"version": "==4.0.1"
"version": "==4.0.2"
},
"more-itertools": {
"hashes": [
@ -612,10 +597,10 @@
},
"packaging": {
"hashes": [
"sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73",
"sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"
"sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3",
"sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"
],
"version": "==20.1"
"version": "==20.3"
},
"pluggy": {
"hashes": [
@ -626,11 +611,11 @@
},
"pre-commit": {
"hashes": [
"sha256:09ebe467f43ce24377f8c2f200fe3cd2570d328eb2ce0568c8e96ce19da45fa6",
"sha256:f8d555e31e2051892c7f7b3ad9f620bd2c09271d87e9eedb2ad831737d6211eb"
"sha256:487c675916e6f99d355ec5595ad77b325689d423ef4839db1ed2f02f639c9522",
"sha256:c0aa11bce04a7b46c5544723aedf4e81a4d5f64ad1205a30a9ea12d5e81969e1"
],
"index": "pypi",
"version": "==2.1.1"
"version": "==2.2.0"
},
"protobuf": {
"hashes": [
@ -685,27 +670,27 @@
},
"pytest": {
"hashes": [
"sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d",
"sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6"
"sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172",
"sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970"
],
"index": "pypi",
"version": "==5.3.5"
"version": "==5.4.1"
},
"pyyaml": {
"hashes": [
"sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6",
"sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf",
"sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5",
"sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e",
"sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811",
"sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e",
"sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d",
"sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20",
"sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689",
"sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994",
"sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615"
"sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97",
"sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76",
"sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2",
"sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648",
"sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf",
"sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f",
"sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2",
"sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee",
"sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d",
"sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c",
"sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"
],
"version": "==5.3"
"version": "==5.3.1"
},
"six": {
"hashes": [
@ -723,24 +708,25 @@
},
"virtualenv": {
"hashes": [
"sha256:30ea90b21dabd11da5f509710ad3be2ae47d40ccbc717dfdd2efe4367c10f598",
"sha256:4a36a96d785428278edd389d9c36d763c5755844beb7509279194647b1ef47f1"
"sha256:4e399f48c6b71228bf79f5febd27e3bbb753d9d5905776a86667bc61ab628a25",
"sha256:9e81279f4a9d16d1c0654a127c2c86e5bca2073585341691882c1e66e31ef8a5"
],
"version": "==20.0.7"
"version": "==20.0.15"
},
"wcwidth": {
"hashes": [
"sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603",
"sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"
"sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1",
"sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"
],
"version": "==0.1.8"
"version": "==0.1.9"
},
"zipp": {
"hashes": [
"sha256:12248a63bbdf7548f89cb4c7cda4681e537031eda29c02ea29674bc6854460c2",
"sha256:7c0f8e91abc0dc07a5068f315c52cb30c66bfbc581e5b50704c8a2f6ebae794a"
"sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
"sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
],
"version": "==3.0.0"
"markers": "python_version < '3.8'",
"version": "==3.1.0"
}
}
}

View file

@ -26,11 +26,69 @@ from core.api.grpc.configservices_pb2 import (
SetNodeConfigServiceRequest,
SetNodeConfigServiceResponse,
)
from core.api.grpc.core_pb2 import (
ExecuteScriptRequest,
ExecuteScriptResponse,
from core.api.grpc.core_pb2 import ExecuteScriptRequest, ExecuteScriptResponse
from core.api.grpc.emane_pb2 import (
EmaneLinkRequest,
EmaneLinkResponse,
EmaneModelConfig,
GetEmaneConfigRequest,
GetEmaneConfigResponse,
GetEmaneEventChannelRequest,
GetEmaneEventChannelResponse,
GetEmaneModelConfigRequest,
GetEmaneModelConfigResponse,
GetEmaneModelConfigsRequest,
GetEmaneModelConfigsResponse,
GetEmaneModelsRequest,
GetEmaneModelsResponse,
SetEmaneConfigRequest,
SetEmaneConfigResponse,
SetEmaneModelConfigRequest,
SetEmaneModelConfigResponse,
)
from core.api.grpc.mobility_pb2 import (
GetMobilityConfigRequest,
GetMobilityConfigResponse,
GetMobilityConfigsRequest,
GetMobilityConfigsResponse,
MobilityActionRequest,
MobilityActionResponse,
MobilityConfig,
SetMobilityConfigRequest,
SetMobilityConfigResponse,
)
from core.api.grpc.services_pb2 import (
GetNodeServiceConfigsRequest,
GetNodeServiceConfigsResponse,
GetNodeServiceFileRequest,
GetNodeServiceFileResponse,
GetNodeServiceRequest,
GetNodeServiceResponse,
GetServiceDefaultsRequest,
GetServiceDefaultsResponse,
GetServicesRequest,
GetServicesResponse,
ServiceAction,
ServiceActionRequest,
ServiceActionResponse,
ServiceConfig,
ServiceDefaults,
ServiceFileConfig,
SetNodeServiceFileRequest,
SetNodeServiceFileResponse,
SetNodeServiceRequest,
SetNodeServiceResponse,
SetServiceDefaultsRequest,
SetServiceDefaultsResponse,
)
from core.api.grpc.wlan_pb2 import (
GetWlanConfigRequest,
GetWlanConfigResponse,
GetWlanConfigsRequest,
GetWlanConfigsResponse,
SetWlanConfigRequest,
SetWlanConfigResponse,
WlanConfig,
)
@ -178,11 +236,11 @@ class CoreGrpcClient:
location: core_pb2.SessionLocation = None,
hooks: List[core_pb2.Hook] = None,
emane_config: Dict[str, str] = None,
emane_model_configs: List[core_pb2.EmaneModelConfig] = None,
wlan_configs: List[core_pb2.WlanConfig] = None,
mobility_configs: List[core_pb2.MobilityConfig] = None,
service_configs: List[core_pb2.ServiceConfig] = None,
service_file_configs: List[core_pb2.ServiceFileConfig] = None,
emane_model_configs: List[EmaneModelConfig] = None,
wlan_configs: List[WlanConfig] = None,
mobility_configs: List[MobilityConfig] = None,
service_configs: List[ServiceConfig] = None,
service_file_configs: List[ServiceFileConfig] = None,
asymmetric_links: List[core_pb2.Link] = None,
config_service_configs: List[configservices_pb2.ConfigServiceConfig] = None,
) -> core_pb2.StartSessionResponse:
@ -678,7 +736,7 @@ class CoreGrpcClient:
session_id: int,
state: core_pb2.SessionState,
file_name: str,
file_data: bytes,
file_data: str,
) -> core_pb2.AddHookResponse:
"""
Add hook scripts.
@ -694,9 +752,7 @@ class CoreGrpcClient:
request = core_pb2.AddHookRequest(session_id=session_id, hook=hook)
return self.stub.AddHook(request)
def get_mobility_configs(
self, session_id: int
) -> core_pb2.GetMobilityConfigsResponse:
def get_mobility_configs(self, session_id: int) -> GetMobilityConfigsResponse:
"""
Get all mobility configurations.
@ -704,12 +760,12 @@ class CoreGrpcClient:
:return: response with a dict of node ids to mobility configurations
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.GetMobilityConfigsRequest(session_id=session_id)
request = GetMobilityConfigsRequest(session_id=session_id)
return self.stub.GetMobilityConfigs(request)
def get_mobility_config(
self, session_id: int, node_id: int
) -> core_pb2.GetMobilityConfigResponse:
) -> GetMobilityConfigResponse:
"""
Get mobility configuration for a node.
@ -718,14 +774,12 @@ class CoreGrpcClient:
:return: response with a list of configuration groups
:raises grpc.RpcError: when session or node doesn't exist
"""
request = core_pb2.GetMobilityConfigRequest(
session_id=session_id, node_id=node_id
)
request = GetMobilityConfigRequest(session_id=session_id, node_id=node_id)
return self.stub.GetMobilityConfig(request)
def set_mobility_config(
self, session_id: int, node_id: int, config: Dict[str, str]
) -> core_pb2.SetMobilityConfigResponse:
) -> SetMobilityConfigResponse:
"""
Set mobility configuration for a node.
@ -735,15 +789,15 @@ class CoreGrpcClient:
:return: response with result of success or failure
:raises grpc.RpcError: when session or node doesn't exist
"""
mobility_config = core_pb2.MobilityConfig(node_id=node_id, config=config)
request = core_pb2.SetMobilityConfigRequest(
mobility_config = MobilityConfig(node_id=node_id, config=config)
request = SetMobilityConfigRequest(
session_id=session_id, mobility_config=mobility_config
)
return self.stub.SetMobilityConfig(request)
def mobility_action(
self, session_id: int, node_id: int, action: core_pb2.ServiceAction
) -> core_pb2.MobilityActionResponse:
self, session_id: int, node_id: int, action: ServiceAction
) -> MobilityActionResponse:
"""
Send a mobility action for a node.
@ -753,23 +807,21 @@ class CoreGrpcClient:
:return: response with result of success or failure
:raises grpc.RpcError: when session or node doesn't exist
"""
request = core_pb2.MobilityActionRequest(
request = MobilityActionRequest(
session_id=session_id, node_id=node_id, action=action
)
return self.stub.MobilityAction(request)
def get_services(self) -> core_pb2.GetServicesResponse:
def get_services(self) -> GetServicesResponse:
"""
Get all currently loaded services.
:return: response with a list of services
"""
request = core_pb2.GetServicesRequest()
request = GetServicesRequest()
return self.stub.GetServices(request)
def get_service_defaults(
self, session_id: int
) -> core_pb2.GetServiceDefaultsResponse:
def get_service_defaults(self, session_id: int) -> GetServiceDefaultsResponse:
"""
Get default services for different default node models.
@ -777,12 +829,12 @@ class CoreGrpcClient:
:return: response with a dict of node model to a list of services
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.GetServiceDefaultsRequest(session_id=session_id)
request = GetServiceDefaultsRequest(session_id=session_id)
return self.stub.GetServiceDefaults(request)
def set_service_defaults(
self, session_id: int, service_defaults: Dict[str, List[str]]
) -> core_pb2.SetServiceDefaultsResponse:
) -> SetServiceDefaultsResponse:
"""
Set default services for node models.
@ -794,16 +846,14 @@ class CoreGrpcClient:
defaults = []
for node_type in service_defaults:
services = service_defaults[node_type]
default = core_pb2.ServiceDefaults(node_type=node_type, services=services)
default = ServiceDefaults(node_type=node_type, services=services)
defaults.append(default)
request = core_pb2.SetServiceDefaultsRequest(
session_id=session_id, defaults=defaults
)
request = SetServiceDefaultsRequest(session_id=session_id, defaults=defaults)
return self.stub.SetServiceDefaults(request)
def get_node_service_configs(
self, session_id: int
) -> core_pb2.GetNodeServiceConfigsResponse:
) -> GetNodeServiceConfigsResponse:
"""
Get service data for a node.
@ -811,12 +861,12 @@ class CoreGrpcClient:
:return: response with all node service configs
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.GetNodeServiceConfigsRequest(session_id=session_id)
request = GetNodeServiceConfigsRequest(session_id=session_id)
return self.stub.GetNodeServiceConfigs(request)
def get_node_service(
self, session_id: int, node_id: int, service: str
) -> core_pb2.GetNodeServiceResponse:
) -> GetNodeServiceResponse:
"""
Get service data for a node.
@ -826,14 +876,14 @@ class CoreGrpcClient:
:return: response with node service data
:raises grpc.RpcError: when session or node doesn't exist
"""
request = core_pb2.GetNodeServiceRequest(
request = GetNodeServiceRequest(
session_id=session_id, node_id=node_id, service=service
)
return self.stub.GetNodeService(request)
def get_node_service_file(
self, session_id: int, node_id: int, service: str, file_name: str
) -> core_pb2.GetNodeServiceFileResponse:
) -> GetNodeServiceFileResponse:
"""
Get a service file for a node.
@ -844,7 +894,7 @@ class CoreGrpcClient:
:return: response with file data
:raises grpc.RpcError: when session or node doesn't exist
"""
request = core_pb2.GetNodeServiceFileRequest(
request = GetNodeServiceFileRequest(
session_id=session_id, node_id=node_id, service=service, file=file_name
)
return self.stub.GetNodeServiceFile(request)
@ -859,7 +909,7 @@ class CoreGrpcClient:
startup: List[str] = None,
validate: List[str] = None,
shutdown: List[str] = None,
) -> core_pb2.SetNodeServiceResponse:
) -> SetNodeServiceResponse:
"""
Set service data for a node.
@ -874,7 +924,7 @@ class CoreGrpcClient:
:return: response with result of success or failure
:raises grpc.RpcError: when session or node doesn't exist
"""
config = core_pb2.ServiceConfig(
config = ServiceConfig(
node_id=node_id,
service=service,
files=files,
@ -883,12 +933,12 @@ class CoreGrpcClient:
validate=validate,
shutdown=shutdown,
)
request = core_pb2.SetNodeServiceRequest(session_id=session_id, config=config)
request = SetNodeServiceRequest(session_id=session_id, config=config)
return self.stub.SetNodeService(request)
def set_node_service_file(
self, session_id: int, node_id: int, service: str, file_name: str, data: bytes
) -> core_pb2.SetNodeServiceFileResponse:
self, session_id: int, node_id: int, service: str, file_name: str, data: str
) -> SetNodeServiceFileResponse:
"""
Set a service file for a node.
@ -900,21 +950,15 @@ class CoreGrpcClient:
:return: response with result of success or failure
:raises grpc.RpcError: when session or node doesn't exist
"""
config = core_pb2.ServiceFileConfig(
config = ServiceFileConfig(
node_id=node_id, service=service, file=file_name, data=data
)
request = core_pb2.SetNodeServiceFileRequest(
session_id=session_id, config=config
)
request = SetNodeServiceFileRequest(session_id=session_id, config=config)
return self.stub.SetNodeServiceFile(request)
def service_action(
self,
session_id: int,
node_id: int,
service: str,
action: core_pb2.ServiceAction,
) -> core_pb2.ServiceActionResponse:
self, session_id: int, node_id: int, service: str, action: ServiceAction
) -> ServiceActionResponse:
"""
Send an action to a service for a node.
@ -926,12 +970,12 @@ class CoreGrpcClient:
:return: response with result of success or failure
:raises grpc.RpcError: when session or node doesn't exist
"""
request = core_pb2.ServiceActionRequest(
request = ServiceActionRequest(
session_id=session_id, node_id=node_id, service=service, action=action
)
return self.stub.ServiceAction(request)
def get_wlan_configs(self, session_id: int) -> core_pb2.GetWlanConfigsResponse:
def get_wlan_configs(self, session_id: int) -> GetWlanConfigsResponse:
"""
Get all wlan configurations.
@ -939,12 +983,10 @@ class CoreGrpcClient:
:return: response with a dict of node ids to wlan configurations
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.GetWlanConfigsRequest(session_id=session_id)
request = GetWlanConfigsRequest(session_id=session_id)
return self.stub.GetWlanConfigs(request)
def get_wlan_config(
self, session_id: int, node_id: int
) -> core_pb2.GetWlanConfigResponse:
def get_wlan_config(self, session_id: int, node_id: int) -> GetWlanConfigResponse:
"""
Get wlan configuration for a node.
@ -953,12 +995,12 @@ class CoreGrpcClient:
:return: response with a list of configuration groups
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.GetWlanConfigRequest(session_id=session_id, node_id=node_id)
request = GetWlanConfigRequest(session_id=session_id, node_id=node_id)
return self.stub.GetWlanConfig(request)
def set_wlan_config(
self, session_id: int, node_id: int, config: Dict[str, str]
) -> core_pb2.SetWlanConfigResponse:
) -> SetWlanConfigResponse:
"""
Set wlan configuration for a node.
@ -968,13 +1010,11 @@ class CoreGrpcClient:
:return: response with result of success or failure
:raises grpc.RpcError: when session doesn't exist
"""
wlan_config = core_pb2.WlanConfig(node_id=node_id, config=config)
request = core_pb2.SetWlanConfigRequest(
session_id=session_id, wlan_config=wlan_config
)
wlan_config = WlanConfig(node_id=node_id, config=config)
request = SetWlanConfigRequest(session_id=session_id, wlan_config=wlan_config)
return self.stub.SetWlanConfig(request)
def get_emane_config(self, session_id: int) -> core_pb2.GetEmaneConfigResponse:
def get_emane_config(self, session_id: int) -> GetEmaneConfigResponse:
"""
Get session emane configuration.
@ -982,12 +1022,12 @@ class CoreGrpcClient:
:return: response with a list of configuration groups
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.GetEmaneConfigRequest(session_id=session_id)
request = GetEmaneConfigRequest(session_id=session_id)
return self.stub.GetEmaneConfig(request)
def set_emane_config(
self, session_id: int, config: Dict[str, str]
) -> core_pb2.SetEmaneConfigResponse:
) -> SetEmaneConfigResponse:
"""
Set session emane configuration.
@ -996,10 +1036,10 @@ class CoreGrpcClient:
:return: response with result of success or failure
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.SetEmaneConfigRequest(session_id=session_id, config=config)
request = SetEmaneConfigRequest(session_id=session_id, config=config)
return self.stub.SetEmaneConfig(request)
def get_emane_models(self, session_id: int) -> core_pb2.GetEmaneModelsResponse:
def get_emane_models(self, session_id: int) -> GetEmaneModelsResponse:
"""
Get session emane models.
@ -1007,12 +1047,12 @@ class CoreGrpcClient:
:return: response with a list of emane models
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.GetEmaneModelsRequest(session_id=session_id)
request = GetEmaneModelsRequest(session_id=session_id)
return self.stub.GetEmaneModels(request)
def get_emane_model_config(
self, session_id: int, node_id: int, model: str, interface_id: int = -1
) -> core_pb2.GetEmaneModelConfigResponse:
) -> GetEmaneModelConfigResponse:
"""
Get emane model configuration for a node or a node's interface.
@ -1023,7 +1063,7 @@ class CoreGrpcClient:
:return: response with a list of configuration groups
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.GetEmaneModelConfigRequest(
request = GetEmaneModelConfigRequest(
session_id=session_id, node_id=node_id, model=model, interface=interface_id
)
return self.stub.GetEmaneModelConfig(request)
@ -1035,7 +1075,7 @@ class CoreGrpcClient:
model: str,
config: Dict[str, str],
interface_id: int = -1,
) -> core_pb2.SetEmaneModelConfigResponse:
) -> SetEmaneModelConfigResponse:
"""
Set emane model configuration for a node or a node's interface.
@ -1047,17 +1087,15 @@ class CoreGrpcClient:
:return: response with result of success or failure
:raises grpc.RpcError: when session doesn't exist
"""
model_config = core_pb2.EmaneModelConfig(
model_config = EmaneModelConfig(
node_id=node_id, model=model, config=config, interface_id=interface_id
)
request = core_pb2.SetEmaneModelConfigRequest(
request = SetEmaneModelConfigRequest(
session_id=session_id, emane_model_config=model_config
)
return self.stub.SetEmaneModelConfig(request)
def get_emane_model_configs(
self, session_id: int
) -> core_pb2.GetEmaneModelConfigsResponse:
def get_emane_model_configs(self, session_id: int) -> GetEmaneModelConfigsResponse:
"""
Get all emane model configurations for a session.
@ -1065,7 +1103,7 @@ class CoreGrpcClient:
:return: response with a dictionary of node/interface ids to configurations
:raises grpc.RpcError: when session doesn't exist
"""
request = core_pb2.GetEmaneModelConfigsRequest(session_id=session_id)
request = GetEmaneModelConfigsRequest(session_id=session_id)
return self.stub.GetEmaneModelConfigs(request)
def save_xml(self, session_id: int, file_path: str) -> core_pb2.SaveXmlResponse:
@ -1096,7 +1134,7 @@ class CoreGrpcClient:
def emane_link(
self, session_id: int, nem_one: int, nem_two: int, linked: bool
) -> core_pb2.EmaneLinkResponse:
) -> EmaneLinkResponse:
"""
Helps broadcast wireless link/unlink between EMANE nodes.
@ -1106,7 +1144,7 @@ class CoreGrpcClient:
:param linked: True to link, False to unlink
:return: core_pb2.EmaneLinkResponse
"""
request = core_pb2.EmaneLinkRequest(
request = EmaneLinkRequest(
session_id=session_id, nem_one=nem_one, nem_two=nem_two, linked=linked
)
return self.stub.EmaneLink(request)
@ -1191,7 +1229,8 @@ class CoreGrpcClient:
@contextmanager
def context_connect(self) -> Generator:
"""
Makes a context manager based connection to the server, will close after context ends.
Makes a context manager based connection to the server, will close after
context ends.
:return: nothing
"""

View file

@ -23,14 +23,12 @@ def handle_node_event(event: NodeData) -> core_pb2.NodeEvent:
:return: node event that contains node id, name, model, position, and services
"""
position = core_pb2.Position(x=event.x_position, y=event.y_position)
services = event.services or ""
services = services.split("|")
node_proto = core_pb2.Node(
id=event.id,
name=event.name,
model=event.model,
position=position,
services=services,
services=event.services,
)
return core_pb2.NodeEvent(node=node_proto, source=event.source)
@ -80,14 +78,14 @@ def handle_link_event(event: LinkData) -> core_pb2.LinkEvent:
unidirectional=event.unidirectional,
)
link = core_pb2.Link(
type=event.link_type,
type=event.link_type.value,
node_one_id=event.node1_id,
node_two_id=event.node2_id,
interface_one=interface_one,
interface_two=interface_two,
options=options,
)
return core_pb2.LinkEvent(message_type=event.message_type, link=link)
return core_pb2.LinkEvent(message_type=event.message_type.value, link=link)
def handle_session_event(event: EventData) -> core_pb2.SessionEvent:
@ -102,7 +100,7 @@ def handle_session_event(event: EventData) -> core_pb2.SessionEvent:
event_time = float(event_time)
return core_pb2.SessionEvent(
node_id=event.node,
event=event.event_type,
event=event.event_type.value,
name=event.name,
data=event.data,
time=event_time,
@ -158,7 +156,7 @@ def handle_file_event(event: FileData) -> core_pb2.FileEvent:
:return: file event
"""
return core_pb2.FileEvent(
message_type=event.message_type,
message_type=event.message_type.value,
node_id=event.node,
name=event.name,
mode=event.mode,

View file

@ -6,7 +6,9 @@ import netaddr
from core import utils
from core.api.grpc import common_pb2, core_pb2
from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig
from core.config import ConfigurableOptions
from core.emane.nodes import EmaneNet
from core.emulator.data import LinkData
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import LinkTypes, NodeTypes
@ -26,11 +28,7 @@ def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOption
:return: node type, id, and options
"""
_id = node_proto.id
_type = node_proto.type
if _type is None:
_type = NodeTypes.DEFAULT.value
_type = NodeTypes(_type)
_type = NodeTypes(node_proto.type)
options = NodeOptions(name=node_proto.name, model=node_proto.model)
options.icon = node_proto.icon
options.opaque = node_proto.opaque
@ -224,6 +222,47 @@ def get_config_options(
return results
def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node:
"""
Convert CORE node to protobuf representation.
:param session: session containing node
:param node: node to convert
:return: node proto
"""
node_type = session.get_node_type(node.__class__)
position = core_pb2.Position(
x=node.position.x, y=node.position.y, z=node.position.z
)
services = getattr(node, "services", [])
if services is None:
services = []
services = [x.name for x in services]
config_services = getattr(node, "config_services", {})
config_services = [x for x in config_services]
emane_model = None
if isinstance(node, EmaneNet):
emane_model = node.model.name
model = getattr(node, "type", None)
node_dir = getattr(node, "nodedir", None)
channel = getattr(node, "ctrlchnlname", None)
image = getattr(node, "image", None)
return core_pb2.Node(
id=node.id,
name=node.name,
emane=emane_model,
model=model,
type=node_type.value,
position=position,
services=services,
icon=node.icon,
image=image,
config_services=config_services,
dir=node_dir,
channel=channel,
)
def get_links(session: Session, node: NodeBase):
"""
Retrieve a list of links for grpc to use
@ -233,7 +272,7 @@ def get_links(session: Session, node: NodeBase):
:return: [core.api.grpc.core_pb2.Link]
"""
links = []
for link_data in node.all_link_data(0):
for link_data in node.all_link_data():
link = convert_link(session, link_data)
links.append(link)
return links
@ -325,7 +364,7 @@ def convert_link(session: Session, link_data: LinkData) -> core_pb2.Link:
)
return core_pb2.Link(
type=link_data.link_type,
type=link_data.link_type.value,
node_one_id=link_data.node1_id,
node_two_id=link_data.node2_id,
interface_one=interface_one,
@ -368,7 +407,7 @@ def session_location(session: Session, location: core_pb2.SessionLocation) -> No
session.location.refscale = location.scale
def service_configuration(session: Session, config: core_pb2.ServiceConfig) -> None:
def service_configuration(session: Session, config: ServiceConfig) -> None:
"""
Convenience method for setting a node service configuration.
@ -390,14 +429,14 @@ def service_configuration(session: Session, config: core_pb2.ServiceConfig) -> N
service.shutdown = tuple(config.shutdown)
def get_service_configuration(service: Type[CoreService]) -> core_pb2.NodeServiceData:
def get_service_configuration(service: Type[CoreService]) -> NodeServiceData:
"""
Convenience for converting a service to service data proto.
:param service: service to get proto data for
:return: service proto data
"""
return core_pb2.NodeServiceData(
return NodeServiceData(
executables=service.executables,
dependencies=service.dependencies,
dirs=service.dirs,

View file

@ -19,6 +19,7 @@ from core.api.grpc import (
core_pb2_grpc,
grpcutils,
)
from core.api.grpc.common_pb2 import MappedConfig
from core.api.grpc.configservices_pb2 import (
ConfigService,
GetConfigServiceDefaultsRequest,
@ -34,10 +35,24 @@ from core.api.grpc.configservices_pb2 import (
SetNodeConfigServiceRequest,
SetNodeConfigServiceResponse,
)
from core.api.grpc.core_pb2 import (
ExecuteScriptResponse,
from core.api.grpc.core_pb2 import ExecuteScriptResponse
from core.api.grpc.emane_pb2 import (
EmaneLinkRequest,
EmaneLinkResponse,
GetEmaneConfigRequest,
GetEmaneConfigResponse,
GetEmaneEventChannelRequest,
GetEmaneEventChannelResponse,
GetEmaneModelConfigRequest,
GetEmaneModelConfigResponse,
GetEmaneModelConfigsRequest,
GetEmaneModelConfigsResponse,
GetEmaneModelsRequest,
GetEmaneModelsResponse,
SetEmaneConfigRequest,
SetEmaneConfigResponse,
SetEmaneModelConfigRequest,
SetEmaneModelConfigResponse,
)
from core.api.grpc.events import EventStreamer
from core.api.grpc.grpcutils import (
@ -46,7 +61,48 @@ from core.api.grpc.grpcutils import (
get_links,
get_net_stats,
)
from core.emane.nodes import EmaneNet
from core.api.grpc.mobility_pb2 import (
GetMobilityConfigRequest,
GetMobilityConfigResponse,
GetMobilityConfigsRequest,
GetMobilityConfigsResponse,
MobilityAction,
MobilityActionRequest,
MobilityActionResponse,
SetMobilityConfigRequest,
SetMobilityConfigResponse,
)
from core.api.grpc.services_pb2 import (
GetNodeServiceConfigsRequest,
GetNodeServiceConfigsResponse,
GetNodeServiceFileRequest,
GetNodeServiceFileResponse,
GetNodeServiceRequest,
GetNodeServiceResponse,
GetServiceDefaultsRequest,
GetServiceDefaultsResponse,
GetServicesRequest,
GetServicesResponse,
Service,
ServiceAction,
ServiceActionRequest,
ServiceActionResponse,
ServiceDefaults,
SetNodeServiceFileRequest,
SetNodeServiceFileResponse,
SetNodeServiceRequest,
SetNodeServiceResponse,
SetServiceDefaultsRequest,
SetServiceDefaultsResponse,
)
from core.api.grpc.wlan_pb2 import (
GetWlanConfigRequest,
GetWlanConfigResponse,
GetWlanConfigsRequest,
GetWlanConfigsResponse,
SetWlanConfigRequest,
SetWlanConfigResponse,
)
from core.emulator.coreemu import CoreEmu
from core.emulator.data import LinkData
from core.emulator.emudata import LinkOptions, NodeOptions
@ -55,8 +111,6 @@ from core.emulator.session import Session
from core.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.nodes.base import CoreNodeBase, NodeBase
from core.nodes.docker import DockerNode
from core.nodes.lxd import LxcNode
from core.services.coreservices import ServiceManager
_ONE_DAY_IN_SECONDS = 60 * 60 * 24
@ -173,7 +227,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
# add all hooks
for hook in request.hooks:
session.add_hook(hook.state, hook.file, None, hook.data)
state = EventTypes(hook.state)
session.add_hook(state, hook.file, None, hook.data)
# create nodes
_, exceptions = grpcutils.create_nodes(session, request.nodes)
@ -279,7 +334,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session.location.setrefgeo(47.57917, -122.13232, 2.0)
session.location.refscale = 150000.0
return core_pb2.CreateSessionResponse(
session_id=session.id, state=session.state
session_id=session.id, state=session.state.value
)
def DeleteSession(
@ -312,9 +367,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session = self.coreemu.sessions[session_id]
session_summary = core_pb2.SessionSummary(
id=session_id,
state=session.state,
state=session.state.value,
nodes=session.get_node_count(),
file=session.file_name,
dir=session.session_dir,
)
sessions.append(session_summary)
return core_pb2.GetSessionsResponse(sessions=sessions)
@ -485,43 +541,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
node = session.nodes[_id]
if not isinstance(node.id, int):
continue
node_type = session.get_node_type(node.__class__)
model = getattr(node, "type", None)
position = core_pb2.Position(
x=node.position.x, y=node.position.y, z=node.position.z
)
services = getattr(node, "services", [])
if services is None:
services = []
services = [x.name for x in services]
config_services = getattr(node, "config_services", {})
config_services = [x for x in config_services]
emane_model = None
if isinstance(node, EmaneNet):
emane_model = node.model.name
image = getattr(node, "image", None)
node_proto = core_pb2.Node(
id=node.id,
name=node.name,
emane=emane_model,
model=model,
type=node_type.value,
position=position,
services=services,
icon=node.icon,
image=image,
config_services=config_services,
)
if isinstance(node, (DockerNode, LxcNode)):
node_proto.image = node.image
node_proto = grpcutils.get_node_proto(session, node)
nodes.append(node_proto)
node_links = get_links(session, node)
links.extend(node_links)
session_proto = core_pb2.Session(state=session.state, nodes=nodes, links=links)
session_proto = core_pb2.Session(
state=session.state.value, nodes=nodes, links=links, dir=session.session_dir
)
return core_pb2.GetSessionResponse(session=session_proto)
def AddSessionServer(
@ -653,37 +680,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
logging.debug("get node: %s", request)
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
interfaces = []
for interface_id in node._netif:
interface = node._netif[interface_id]
interface_proto = grpcutils.interface_to_proto(interface)
interfaces.append(interface_proto)
emane_model = None
if isinstance(node, EmaneNet):
emane_model = node.model.name
services = []
if node.services:
services = [x.name for x in node.services]
position = core_pb2.Position(
x=node.position.x, y=node.position.y, z=node.position.z
)
node_type = session.get_node_type(node.__class__)
node_proto = core_pb2.Node(
id=node.id,
name=node.name,
type=node_type.value,
emane=emane_model,
model=node.type,
position=position,
services=services,
)
if isinstance(node, (DockerNode, LxcNode)):
node_proto.image = node.image
node_proto = grpcutils.get_node_proto(session, node)
return core_pb2.GetNodeResponse(node=node_proto, interfaces=interfaces)
def EditNode(
@ -705,7 +707,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
x = request.position.x
y = request.position.y
options.set_position(x, y)
lat, lon, alt = None, None, None
has_geo = request.HasField("geo")
if has_geo:
lat = request.geo.lat
@ -719,8 +720,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
if request.source:
source = request.source
if not has_geo:
node_data = node.data(0, source=source)
session.broadcast_node(node_data)
session.broadcast_node(node, source=source)
except CoreError:
result = False
return core_pb2.EditNodeResponse(result=result)
@ -896,7 +896,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
for state in session._hooks:
state_hooks = session._hooks[state]
for file_name, file_data in state_hooks:
hook = core_pb2.Hook(state=state, file=file_name, data=file_data)
hook = core_pb2.Hook(state=state.value, file=file_name, data=file_data)
hooks.append(hook)
return core_pb2.GetHooksResponse(hooks=hooks)
@ -913,12 +913,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
logging.debug("add hook: %s", request)
session = self.get_session(request.session_id, context)
hook = request.hook
session.add_hook(hook.state, hook.file, None, hook.data)
state = EventTypes(hook.state)
session.add_hook(state, hook.file, None, hook.data)
return core_pb2.AddHookResponse(result=True)
def GetMobilityConfigs(
self, request: core_pb2.GetMobilityConfigsRequest, context: ServicerContext
) -> core_pb2.GetMobilityConfigsResponse:
self, request: GetMobilityConfigsRequest, context: ServicerContext
) -> GetMobilityConfigsResponse:
"""
Retrieve all mobility configurations from a session
@ -929,7 +930,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logging.debug("get mobility configs: %s", request)
session = self.get_session(request.session_id, context)
response = core_pb2.GetMobilityConfigsResponse()
response = GetMobilityConfigsResponse()
for node_id in session.mobility.node_configurations:
model_config = session.mobility.node_configurations[node_id]
if node_id == -1:
@ -939,13 +940,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
continue
current_config = session.mobility.get_model_config(node_id, model_name)
config = get_config_options(current_config, Ns2ScriptedMobility)
mapped_config = core_pb2.MappedConfig(config=config)
mapped_config = MappedConfig(config=config)
response.configs[node_id].CopyFrom(mapped_config)
return response
def GetMobilityConfig(
self, request: core_pb2.GetMobilityConfigRequest, context: ServicerContext
) -> core_pb2.GetMobilityConfigResponse:
self, request: GetMobilityConfigRequest, context: ServicerContext
) -> GetMobilityConfigResponse:
"""
Retrieve mobility configuration of a node
@ -960,11 +961,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
request.node_id, Ns2ScriptedMobility.name
)
config = get_config_options(current_config, Ns2ScriptedMobility)
return core_pb2.GetMobilityConfigResponse(config=config)
return GetMobilityConfigResponse(config=config)
def SetMobilityConfig(
self, request: core_pb2.SetMobilityConfigRequest, context: ServicerContext
) -> core_pb2.SetMobilityConfigResponse:
self, request: SetMobilityConfigRequest, context: ServicerContext
) -> SetMobilityConfigResponse:
"""
Set mobility configuration of a node
@ -979,11 +980,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session.mobility.set_model_config(
mobility_config.node_id, Ns2ScriptedMobility.name, mobility_config.config
)
return core_pb2.SetMobilityConfigResponse(result=True)
return SetMobilityConfigResponse(result=True)
def MobilityAction(
self, request: core_pb2.MobilityActionRequest, context: ServicerContext
) -> core_pb2.MobilityActionResponse:
self, request: MobilityActionRequest, context: ServicerContext
) -> MobilityActionResponse:
"""
Take mobility action whether to start, pause, stop or none of those
@ -996,19 +997,19 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session = self.get_session(request.session_id, context)
node = self.get_node(session, request.node_id, context)
result = True
if request.action == core_pb2.MobilityAction.START:
if request.action == MobilityAction.START:
node.mobility.start()
elif request.action == core_pb2.MobilityAction.PAUSE:
elif request.action == MobilityAction.PAUSE:
node.mobility.pause()
elif request.action == core_pb2.MobilityAction.STOP:
elif request.action == MobilityAction.STOP:
node.mobility.stop(move_initial=True)
else:
result = False
return core_pb2.MobilityActionResponse(result=result)
return MobilityActionResponse(result=result)
def GetServices(
self, request: core_pb2.GetServicesRequest, context: ServicerContext
) -> core_pb2.GetServicesResponse:
self, request: GetServicesRequest, context: ServicerContext
) -> GetServicesResponse:
"""
Retrieve all the services that are running
@ -1020,13 +1021,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
services = []
for name in ServiceManager.services:
service = ServiceManager.services[name]
service_proto = core_pb2.Service(group=service.group, name=service.name)
service_proto = Service(group=service.group, name=service.name)
services.append(service_proto)
return core_pb2.GetServicesResponse(services=services)
return GetServicesResponse(services=services)
def GetServiceDefaults(
self, request: core_pb2.GetServiceDefaultsRequest, context: ServicerContext
) -> core_pb2.GetServiceDefaultsResponse:
self, request: GetServiceDefaultsRequest, context: ServicerContext
) -> GetServiceDefaultsResponse:
"""
Retrieve all the default services of all node types in a session
@ -1040,15 +1041,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
all_service_defaults = []
for node_type in session.services.default_services:
services = session.services.default_services[node_type]
service_defaults = core_pb2.ServiceDefaults(
node_type=node_type, services=services
)
service_defaults = ServiceDefaults(node_type=node_type, services=services)
all_service_defaults.append(service_defaults)
return core_pb2.GetServiceDefaultsResponse(defaults=all_service_defaults)
return GetServiceDefaultsResponse(defaults=all_service_defaults)
def SetServiceDefaults(
self, request: core_pb2.SetServiceDefaultsRequest, context: ServicerContext
) -> core_pb2.SetServiceDefaultsResponse:
self, request: SetServiceDefaultsRequest, context: ServicerContext
) -> SetServiceDefaultsResponse:
"""
Set new default services to the session after whipping out the old ones
:param request: set-service-defaults
@ -1063,11 +1062,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session.services.default_services[
service_defaults.node_type
] = service_defaults.services
return core_pb2.SetServiceDefaultsResponse(result=True)
return SetServiceDefaultsResponse(result=True)
def GetNodeServiceConfigs(
self, request: core_pb2.GetNodeServiceConfigsRequest, context: ServicerContext
) -> core_pb2.GetNodeServiceConfigsResponse:
self, request: GetNodeServiceConfigsRequest, context: ServicerContext
) -> GetNodeServiceConfigsResponse:
"""
Retrieve all node service configurations.
@ -1083,18 +1082,18 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
for name in service_configs:
service = session.services.get_service(node_id, name)
service_proto = grpcutils.get_service_configuration(service)
config = core_pb2.GetNodeServiceConfigsResponse.ServiceConfig(
config = GetNodeServiceConfigsResponse.ServiceConfig(
node_id=node_id,
service=name,
data=service_proto,
files=service.config_data,
)
configs.append(config)
return core_pb2.GetNodeServiceConfigsResponse(configs=configs)
return GetNodeServiceConfigsResponse(configs=configs)
def GetNodeService(
self, request: core_pb2.GetNodeServiceRequest, context: ServicerContext
) -> core_pb2.GetNodeServiceResponse:
self, request: GetNodeServiceRequest, context: ServicerContext
) -> GetNodeServiceResponse:
"""
Retrieve a requested service from a node
@ -1109,11 +1108,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
request.node_id, request.service, default_service=True
)
service_proto = grpcutils.get_service_configuration(service)
return core_pb2.GetNodeServiceResponse(service=service_proto)
return GetNodeServiceResponse(service=service_proto)
def GetNodeServiceFile(
self, request: core_pb2.GetNodeServiceFileRequest, context: ServicerContext
) -> core_pb2.GetNodeServiceFileResponse:
self, request: GetNodeServiceFileRequest, context: ServicerContext
) -> GetNodeServiceFileResponse:
"""
Retrieve a requested service file from a node
@ -1128,11 +1127,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
file_data = session.services.get_service_file(
node, request.service, request.file
)
return core_pb2.GetNodeServiceFileResponse(data=file_data.data)
return GetNodeServiceFileResponse(data=file_data.data)
def SetNodeService(
self, request: core_pb2.SetNodeServiceRequest, context: ServicerContext
) -> core_pb2.SetNodeServiceResponse:
self, request: SetNodeServiceRequest, context: ServicerContext
) -> SetNodeServiceResponse:
"""
Set a node service for a node
@ -1145,11 +1144,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session = self.get_session(request.session_id, context)
config = request.config
grpcutils.service_configuration(session, config)
return core_pb2.SetNodeServiceResponse(result=True)
return SetNodeServiceResponse(result=True)
def SetNodeServiceFile(
self, request: core_pb2.SetNodeServiceFileRequest, context: ServicerContext
) -> core_pb2.SetNodeServiceFileResponse:
self, request: SetNodeServiceFileRequest, context: ServicerContext
) -> SetNodeServiceFileResponse:
"""
Store the customized service file in the service config
@ -1164,11 +1163,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session.services.set_service_file(
config.node_id, config.service, config.file, config.data
)
return core_pb2.SetNodeServiceFileResponse(result=True)
return SetNodeServiceFileResponse(result=True)
def ServiceAction(
self, request: core_pb2.ServiceActionRequest, context: ServicerContext
) -> core_pb2.ServiceActionResponse:
self, request: ServiceActionRequest, context: ServicerContext
) -> ServiceActionResponse:
"""
Take action whether to start, stop, restart, validate the service or none of
the above.
@ -1190,26 +1189,26 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
context.abort(grpc.StatusCode.NOT_FOUND, "service not found")
status = -1
if request.action == core_pb2.ServiceAction.START:
if request.action == ServiceAction.START:
status = session.services.startup_service(node, service, wait=True)
elif request.action == core_pb2.ServiceAction.STOP:
elif request.action == ServiceAction.STOP:
status = session.services.stop_service(node, service)
elif request.action == core_pb2.ServiceAction.RESTART:
elif request.action == ServiceAction.RESTART:
status = session.services.stop_service(node, service)
if not status:
status = session.services.startup_service(node, service, wait=True)
elif request.action == core_pb2.ServiceAction.VALIDATE:
elif request.action == ServiceAction.VALIDATE:
status = session.services.validate_service(node, service)
result = False
if not status:
result = True
return core_pb2.ServiceActionResponse(result=result)
return ServiceActionResponse(result=result)
def GetWlanConfigs(
self, request: core_pb2.GetWlanConfigsRequest, context: ServicerContext
) -> core_pb2.GetWlanConfigsResponse:
self, request: GetWlanConfigsRequest, context: ServicerContext
) -> GetWlanConfigsResponse:
"""
Retrieve all wireless-lan configurations.
@ -1219,7 +1218,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
"""
logging.debug("get wlan configs: %s", request)
session = self.get_session(request.session_id, context)
response = core_pb2.GetWlanConfigsResponse()
response = GetWlanConfigsResponse()
for node_id in session.mobility.node_configurations:
model_config = session.mobility.node_configurations[node_id]
if node_id == -1:
@ -1229,13 +1228,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
continue
current_config = session.mobility.get_model_config(node_id, model_name)
config = get_config_options(current_config, BasicRangeModel)
mapped_config = core_pb2.MappedConfig(config=config)
mapped_config = MappedConfig(config=config)
response.configs[node_id].CopyFrom(mapped_config)
return response
def GetWlanConfig(
self, request: core_pb2.GetWlanConfigRequest, context: ServicerContext
) -> core_pb2.GetWlanConfigResponse:
self, request: GetWlanConfigRequest, context: ServicerContext
) -> GetWlanConfigResponse:
"""
Retrieve wireless-lan configuration of a node
@ -1249,11 +1248,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
request.node_id, BasicRangeModel.name
)
config = get_config_options(current_config, BasicRangeModel)
return core_pb2.GetWlanConfigResponse(config=config)
return GetWlanConfigResponse(config=config)
def SetWlanConfig(
self, request: core_pb2.SetWlanConfigRequest, context: ServicerContext
) -> core_pb2.SetWlanConfigResponse:
self, request: SetWlanConfigRequest, context: ServicerContext
) -> SetWlanConfigResponse:
"""
Set configuration data for a model
@ -1267,14 +1266,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session.mobility.set_model_config(
wlan_config.node_id, BasicRangeModel.name, wlan_config.config
)
if session.state == EventTypes.RUNTIME_STATE.value:
if session.state == EventTypes.RUNTIME_STATE:
node = self.get_node(session, wlan_config.node_id, context)
node.updatemodel(wlan_config.config)
return core_pb2.SetWlanConfigResponse(result=True)
return SetWlanConfigResponse(result=True)
def GetEmaneConfig(
self, request: core_pb2.GetEmaneConfigRequest, context: ServicerContext
) -> core_pb2.GetEmaneConfigResponse:
self, request: GetEmaneConfigRequest, context: ServicerContext
) -> GetEmaneConfigResponse:
"""
Retrieve EMANE configuration of a session
@ -1286,11 +1285,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session = self.get_session(request.session_id, context)
current_config = session.emane.get_configs()
config = get_config_options(current_config, session.emane.emane_config)
return core_pb2.GetEmaneConfigResponse(config=config)
return GetEmaneConfigResponse(config=config)
def SetEmaneConfig(
self, request: core_pb2.SetEmaneConfigRequest, context: ServicerContext
) -> core_pb2.SetEmaneConfigResponse:
self, request: SetEmaneConfigRequest, context: ServicerContext
) -> SetEmaneConfigResponse:
"""
Set EMANE configuration of a session
@ -1302,11 +1301,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
session = self.get_session(request.session_id, context)
config = session.emane.get_configs()
config.update(request.config)
return core_pb2.SetEmaneConfigResponse(result=True)
return SetEmaneConfigResponse(result=True)
def GetEmaneModels(
self, request: core_pb2.GetEmaneModelsRequest, context: ServicerContext
) -> core_pb2.GetEmaneModelsResponse:
self, request: GetEmaneModelsRequest, context: ServicerContext
) -> GetEmaneModelsResponse:
"""
Retrieve all the EMANE models in the session
@ -1321,11 +1320,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
if len(model.split("_")) != 2:
continue
models.append(model)
return core_pb2.GetEmaneModelsResponse(models=models)
return GetEmaneModelsResponse(models=models)
def GetEmaneModelConfig(
self, request: core_pb2.GetEmaneModelConfigRequest, context: ServicerContext
) -> core_pb2.GetEmaneModelConfigResponse:
self, request: GetEmaneModelConfigRequest, context: ServicerContext
) -> GetEmaneModelConfigResponse:
"""
Retrieve EMANE model configuration of a node
@ -1340,11 +1339,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
_id = get_emane_model_id(request.node_id, request.interface)
current_config = session.emane.get_model_config(_id, request.model)
config = get_config_options(current_config, model)
return core_pb2.GetEmaneModelConfigResponse(config=config)
return GetEmaneModelConfigResponse(config=config)
def SetEmaneModelConfig(
self, request: core_pb2.SetEmaneModelConfigRequest, context: ServicerContext
) -> core_pb2.SetEmaneModelConfigResponse:
self, request: SetEmaneModelConfigRequest, context: ServicerContext
) -> SetEmaneModelConfigResponse:
"""
Set EMANE model configuration of a node
@ -1358,11 +1357,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
model_config = request.emane_model_config
_id = get_emane_model_id(model_config.node_id, model_config.interface_id)
session.emane.set_model_config(_id, model_config.model, model_config.config)
return core_pb2.SetEmaneModelConfigResponse(result=True)
return SetEmaneModelConfigResponse(result=True)
def GetEmaneModelConfigs(
self, request: core_pb2.GetEmaneModelConfigsRequest, context: ServicerContext
) -> core_pb2.GetEmaneModelConfigsResponse:
self, request: GetEmaneModelConfigsRequest, context: ServicerContext
) -> GetEmaneModelConfigsResponse:
"""
Retrieve all EMANE model configurations of a session
@ -1386,14 +1385,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
current_config = session.emane.get_model_config(_id, model_name)
config = get_config_options(current_config, model)
node_id, interface = grpcutils.parse_emane_model_id(_id)
model_config = core_pb2.GetEmaneModelConfigsResponse.ModelConfig(
model_config = GetEmaneModelConfigsResponse.ModelConfig(
node_id=node_id,
model=model_name,
interface=interface,
config=config,
)
configs.append(model_config)
return core_pb2.GetEmaneModelConfigsResponse(configs=configs)
return GetEmaneModelConfigsResponse(configs=configs)
def SaveXml(
self, request: core_pb2.SaveXmlRequest, context: ServicerContext
@ -1467,8 +1466,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
return core_pb2.GetInterfacesResponse(interfaces=interfaces)
def EmaneLink(
self, request: core_pb2.EmaneLinkRequest, context: ServicerContext
) -> core_pb2.EmaneLinkResponse:
self, request: EmaneLinkRequest, context: ServicerContext
) -> EmaneLinkResponse:
"""
Helps broadcast wireless link/unlink between EMANE nodes.
@ -1492,20 +1491,20 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
if emane_one.id == emane_two.id:
if request.linked:
flag = MessageFlags.ADD.value
flag = MessageFlags.ADD
else:
flag = MessageFlags.DELETE.value
flag = MessageFlags.DELETE
link = LinkData(
message_type=flag,
link_type=LinkTypes.WIRELESS.value,
link_type=LinkTypes.WIRELESS,
node1_id=node_one.id,
node2_id=node_two.id,
network_id=emane_one.id,
)
session.broadcast_link(link)
return core_pb2.EmaneLinkResponse(result=True)
return EmaneLinkResponse(result=True)
else:
return core_pb2.EmaneLinkResponse(result=False)
return EmaneLinkResponse(result=False)
def GetConfigServices(
self, request: GetConfigServicesRequest, context: ServicerContext

View file

@ -13,7 +13,7 @@ from enum import Enum
import netaddr
from core.api.tlv import structutils
from core.emulator.enumerations import (
from core.api.tlv.enumerations import (
ConfigTlvs,
EventTlvs,
ExceptionTlvs,
@ -21,12 +21,11 @@ from core.emulator.enumerations import (
FileTlvs,
InterfaceTlvs,
LinkTlvs,
MessageFlags,
MessageTypes,
NodeTlvs,
RegisterTlvs,
SessionTlvs,
)
from core.emulator.enumerations import MessageFlags, RegisterTlvs
class CoreTlvData:

View file

@ -15,26 +15,29 @@ from queue import Empty, Queue
from core import utils
from core.api.tlv import coreapi, dataconversion, structutils
from core.config import ConfigShim
from core.emulator.data import ConfigData, EventData, ExceptionData, FileData
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import (
ConfigDataTypes,
from core.api.tlv.dataconversion import ConfigShim
from core.api.tlv.enumerations import (
ConfigFlags,
ConfigTlvs,
EventTlvs,
EventTypes,
ExceptionTlvs,
ExecuteTlvs,
FileTlvs,
LinkTlvs,
LinkTypes,
MessageFlags,
MessageTypes,
NodeTlvs,
SessionTlvs,
)
from core.emulator.data import ConfigData, EventData, ExceptionData, FileData
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import (
ConfigDataTypes,
EventTypes,
ExceptionLevels,
LinkTypes,
MessageFlags,
NodeTypes,
RegisterTlvs,
SessionTlvs,
)
from core.errors import CoreCommandError, CoreError
from core.location.mobility import BasicRangeModel
@ -47,6 +50,8 @@ class CoreHandler(socketserver.BaseRequestHandler):
The CoreHandler class uses the RequestHandler class for servicing requests.
"""
session_clients = {}
def __init__(self, request, client_address, server):
"""
Create a CoreRequestHandler instance.
@ -84,7 +89,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
thread.start()
self.session = None
self.session_clients = {}
self.coreemu = server.coreemu
utils.close_onexec(request.fileno())
socketserver.BaseRequestHandler.__init__(self, request, client_address, server)
@ -228,7 +232,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
coreapi.CoreEventTlv,
[
(EventTlvs.NODE, event_data.node),
(EventTlvs.TYPE, event_data.event_type),
(EventTlvs.TYPE, event_data.event_type.value),
(EventTlvs.NAME, event_data.name),
(EventTlvs.DATA, event_data.data),
(EventTlvs.TIME, event_data.time),
@ -265,7 +269,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
(FileTlvs.COMPRESSED_DATA, file_data.compressed_data),
],
)
message = coreapi.CoreFileMessage.pack(file_data.message_type, tlv_data)
message = coreapi.CoreFileMessage.pack(file_data.message_type.value, tlv_data)
try:
self.sendall(message)
@ -298,7 +302,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
coreapi.CoreExceptionTlv,
[
(ExceptionTlvs.NODE, exception_data.node),
(ExceptionTlvs.SESSION, exception_data.session),
(ExceptionTlvs.SESSION, str(exception_data.session)),
(ExceptionTlvs.LEVEL, exception_data.level.value),
(ExceptionTlvs.SOURCE, exception_data.source),
(ExceptionTlvs.DATE, exception_data.date),
@ -356,7 +360,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
(LinkTlvs.BURST, link_data.burst),
(LinkTlvs.SESSION, link_data.session),
(LinkTlvs.MBURST, link_data.mburst),
(LinkTlvs.TYPE, link_data.link_type),
(LinkTlvs.TYPE, link_data.link_type.value),
(LinkTlvs.GUI_ATTRIBUTES, link_data.gui_attributes),
(LinkTlvs.UNIDIRECTIONAL, link_data.unidirectional),
(LinkTlvs.EMULATION_ID, link_data.emulation_id),
@ -380,7 +384,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
],
)
message = coreapi.CoreLinkMessage.pack(link_data.message_type, tlv_data)
message = coreapi.CoreLinkMessage.pack(link_data.message_type.value, tlv_data)
try:
self.sendall(message)
@ -396,7 +400,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
logging.info(
"GUI has connected to session %d at %s", self.session.id, time.ctime()
)
tlv_data = b""
tlv_data += coreapi.CoreRegisterTlv.pack(
RegisterTlvs.EXECUTE_SERVER.value, "core-daemon"
@ -406,29 +409,29 @@ class CoreHandler(socketserver.BaseRequestHandler):
)
tlv_data += coreapi.CoreRegisterTlv.pack(RegisterTlvs.UTILITY.value, "broker")
tlv_data += coreapi.CoreRegisterTlv.pack(
self.session.location.config_type, self.session.location.name
self.session.location.config_type.value, self.session.location.name
)
tlv_data += coreapi.CoreRegisterTlv.pack(
self.session.mobility.config_type, self.session.mobility.name
self.session.mobility.config_type.value, self.session.mobility.name
)
for model_name in self.session.mobility.models:
model_class = self.session.mobility.models[model_name]
tlv_data += coreapi.CoreRegisterTlv.pack(
model_class.config_type, model_class.name
model_class.config_type.value, model_class.name
)
tlv_data += coreapi.CoreRegisterTlv.pack(
self.session.services.config_type, self.session.services.name
self.session.services.config_type.value, self.session.services.name
)
tlv_data += coreapi.CoreRegisterTlv.pack(
self.session.emane.config_type, self.session.emane.name
self.session.emane.config_type.value, self.session.emane.name
)
for model_name in self.session.emane.models:
model_class = self.session.emane.models[model_name]
tlv_data += coreapi.CoreRegisterTlv.pack(
model_class.config_type, model_class.name
model_class.config_type.value, model_class.name
)
tlv_data += coreapi.CoreRegisterTlv.pack(
self.session.options.config_type, self.session.options.name
self.session.options.config_type.value, self.session.options.name
)
tlv_data += coreapi.CoreRegisterTlv.pack(RegisterTlvs.UTILITY.value, "metadata")
@ -531,12 +534,12 @@ class CoreHandler(socketserver.BaseRequestHandler):
return
message_handler = self.message_handlers[message.message_type]
try:
# TODO: this needs to be removed, make use of the broadcast message methods
replies = message_handler(message)
self.dispatch_replies(replies, message)
except Exception:
except Exception as e:
self.send_exception(ExceptionLevels.ERROR, "corehandler", str(e))
logging.exception(
"%s: exception while handling message: %s",
threading.currentThread().getName(),
@ -635,13 +638,13 @@ class CoreHandler(socketserver.BaseRequestHandler):
:param str source: source where exception came from
:param str text: details about exception
:param int node: node id, if related to a specific node
:return:
:return: nothing
"""
exception_data = ExceptionData(
session=str(self.session.id),
session=self.session.id,
node=node,
date=time.ctime(),
level=level.value,
level=level,
source=source,
text=text,
)
@ -723,7 +726,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
if message.flags & MessageFlags.STRING.value:
self.node_status_request[node.id] = True
if self.session.state == EventTypes.RUNTIME_STATE.value:
if self.session.state == EventTypes.RUNTIME_STATE:
self.send_node_emulation_id(node.id)
elif message.flags & MessageFlags.DELETE.value:
with self._shutdown_lock:
@ -966,7 +969,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
retries = 10
# wait for session to enter RUNTIME state, to prevent GUI from
# connecting while nodes are still being instantiated
while session.state != EventTypes.RUNTIME_STATE.value:
while session.state != EventTypes.RUNTIME_STATE:
logging.debug(
"waiting for session %d to enter RUNTIME state", sid
)
@ -1375,7 +1378,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
parsed_config = ConfigShim.str_to_dict(values_str)
self.session.mobility.set_model_config(node_id, object_name, parsed_config)
if self.session.state == EventTypes.RUNTIME_STATE.value and parsed_config:
if self.session.state == EventTypes.RUNTIME_STATE and parsed_config:
try:
node = self.session.get_node(node_id)
if object_name == BasicRangeModel.name:
@ -1502,6 +1505,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
logging.error("error setting hook having state '%s'", state)
return ()
state = int(state)
state = EventTypes(state)
self.session.add_hook(state, file_name, source_name, data)
return ()
@ -1538,9 +1542,11 @@ class CoreHandler(socketserver.BaseRequestHandler):
:return: reply messages
:raises core.CoreError: when event type <= SHUTDOWN_STATE and not a known node id
"""
event_type_value = message.get_tlv(EventTlvs.TYPE.value)
event_type = EventTypes(event_type_value)
event_data = EventData(
node=message.get_tlv(EventTlvs.NODE.value),
event_type=message.get_tlv(EventTlvs.TYPE.value),
event_type=event_type,
name=message.get_tlv(EventTlvs.NAME.value),
data=message.get_tlv(EventTlvs.DATA.value),
time=message.get_tlv(EventTlvs.TIME.value),
@ -1549,7 +1555,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
if event_data.event_type is None:
raise NotImplementedError("Event message missing event type")
event_type = EventTypes(event_data.event_type)
node_id = event_data.node
logging.debug("handling event %s at %s", event_type.name, time.ctime())
@ -1667,25 +1672,19 @@ class CoreHandler(socketserver.BaseRequestHandler):
unknown.append(service_name)
continue
if (
event_type == EventTypes.STOP.value
or event_type == EventTypes.RESTART.value
):
if event_type in [EventTypes.STOP, EventTypes.RESTART]:
status = self.session.services.stop_service(node, service)
if status:
fail += f"Stop {service.name},"
if (
event_type == EventTypes.START.value
or event_type == EventTypes.RESTART.value
):
if event_type in [EventTypes.START, EventTypes.RESTART]:
status = self.session.services.startup_service(node, service)
if status:
fail += f"Start ({service.name}),"
if event_type == EventTypes.PAUSE.value:
if event_type == EventTypes.PAUSE:
status = self.session.services.validate_service(node, service)
if status:
fail += f"{service.name},"
if event_type == EventTypes.RECONFIGURE.value:
if event_type == EventTypes.RECONFIGURE:
self.session.services.service_reconfigure(node, service)
fail_data = ""
@ -1839,23 +1838,13 @@ class CoreHandler(socketserver.BaseRequestHandler):
Return API messages that describe the current session.
"""
# find all nodes and links
nodes_data = []
links_data = []
with self.session._nodes_lock:
for node_id in self.session.nodes:
node = self.session.nodes[node_id]
node_data = node.data(message_type=MessageFlags.ADD.value)
if node_data:
nodes_data.append(node_data)
node_links = node.all_link_data(flags=MessageFlags.ADD.value)
for link_data in node_links:
links_data.append(link_data)
# send all nodes first, so that they will exist for any links
for node_data in nodes_data:
self.session.broadcast_node(node_data)
self.session.broadcast_node(node, MessageFlags.ADD)
node_links = node.all_link_data(flags=MessageFlags.ADD)
links_data.extend(node_links)
for link_data in links_data:
self.session.broadcast_link(link_data)
@ -1910,14 +1899,14 @@ class CoreHandler(socketserver.BaseRequestHandler):
type=ConfigFlags.UPDATE.value,
data_types=data_types,
data_values=values,
session=str(self.session.id),
session=self.session.id,
opaque=opaque,
)
self.session.broadcast_config(config_data)
for file_name, config_data in self.session.services.all_files(service):
file_data = FileData(
message_type=MessageFlags.ADD.value,
message_type=MessageFlags.ADD,
node=node_id,
name=str(file_name),
type=opaque,
@ -1931,7 +1920,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
for state in sorted(self.session._hooks.keys()):
for file_name, config_data in self.session._hooks[state]:
file_data = FileData(
message_type=MessageFlags.ADD.value,
message_type=MessageFlags.ADD,
name=str(file_name),
type=f"hook:{state}",
data=str(config_data),
@ -1963,8 +1952,9 @@ class CoreHandler(socketserver.BaseRequestHandler):
)
self.session.broadcast_config(config_data)
node_count = self.session.get_node_count()
logging.info(
"informed GUI about %d nodes and %d links", len(nodes_data), len(links_data)
"informed GUI about %d nodes and %d links", node_count, len(links_data)
)
@ -1983,6 +1973,7 @@ class CoreUdpHandler(CoreHandler):
}
self.session = None
self.coreemu = server.mainserver.coreemu
self.tcp_handler = server.RequestHandlerClass
socketserver.BaseRequestHandler.__init__(self, request, client_address, server)
def setup(self):
@ -2052,7 +2043,7 @@ class CoreUdpHandler(CoreHandler):
current_session = self.server.mainserver.coreemu.sessions[session_id]
current_node_count = current_session.get_node_count()
if (
current_session.state == EventTypes.RUNTIME_STATE.value
current_session.state == EventTypes.RUNTIME_STATE
and current_node_count > node_count
):
node_count = current_node_count
@ -2071,7 +2062,7 @@ class CoreUdpHandler(CoreHandler):
if not isinstance(message, (coreapi.CoreNodeMessage, coreapi.CoreLinkMessage)):
return
clients = self.session_clients[self.session.id]
clients = self.tcp_handler.session_clients[self.session.id]
for client in clients:
try:
client.sendall(message.raw_message)

View file

@ -1,9 +1,14 @@
"""
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.emulator.enumerations import ConfigTlvs, NodeTlvs
from core.api.tlv.enumerations import ConfigTlvs, NodeTlvs
from core.config import ConfigGroup, ConfigurableOptions
from core.emulator.data import ConfigData
def convert_node(node_data):
@ -13,11 +18,17 @@ def convert_node(node_data):
:param core.emulator.data.NodeData node_data: node data to convert
:return: packed node message
"""
session = None
if node_data.session is not None:
session = str(node_data.session)
services = None
if node_data.services is not None:
services = "|".join([x for x in node_data.services])
tlv_data = structutils.pack_values(
coreapi.CoreNodeTlv,
[
(NodeTlvs.NUMBER, node_data.id),
(NodeTlvs.TYPE, node_data.node_type),
(NodeTlvs.TYPE, node_data.node_type.value),
(NodeTlvs.NAME, node_data.name),
(NodeTlvs.IP_ADDRESS, node_data.ip_address),
(NodeTlvs.MAC_ADDRESS, node_data.mac_address),
@ -25,12 +36,12 @@ def convert_node(node_data):
(NodeTlvs.MODEL, node_data.model),
(NodeTlvs.EMULATION_ID, node_data.emulation_id),
(NodeTlvs.EMULATION_SERVER, node_data.server),
(NodeTlvs.SESSION, node_data.session),
(NodeTlvs.SESSION, session),
(NodeTlvs.X_POSITION, int(node_data.x_position)),
(NodeTlvs.Y_POSITION, int(node_data.y_position)),
(NodeTlvs.CANVAS, node_data.canvas),
(NodeTlvs.NETWORK_ID, node_data.network_id),
(NodeTlvs.SERVICES, node_data.services),
(NodeTlvs.SERVICES, services),
(NodeTlvs.LATITUDE, str(node_data.latitude)),
(NodeTlvs.LONGITUDE, str(node_data.longitude)),
(NodeTlvs.ALTITUDE, str(node_data.altitude)),
@ -38,7 +49,7 @@ def convert_node(node_data):
(NodeTlvs.OPAQUE, node_data.opaque),
],
)
return coreapi.CoreNodeMessage.pack(node_data.message_type, tlv_data)
return coreapi.CoreNodeMessage.pack(node_data.message_type.value, tlv_data)
def convert_config(config_data):
@ -48,6 +59,9 @@ def convert_config(config_data):
: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,
[
@ -60,10 +74,109 @@ def convert_config(config_data):
(ConfigTlvs.BITMAP, config_data.bitmap),
(ConfigTlvs.POSSIBLE_VALUES, config_data.possible_values),
(ConfigTlvs.GROUPS, config_data.groups),
(ConfigTlvs.SESSION, config_data.session),
(ConfigTlvs.SESSION, session),
(ConfigTlvs.INTERFACE_NUMBER, config_data.interface_number),
(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 = []
logging.debug("configurable: %s", configurable_options)
logging.debug("configuration options: %s", configurable_options.configurations)
logging.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

@ -0,0 +1,212 @@
"""
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
PER = 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
INTERFACE1_NUMBER = 0x30
INTERFACE1_IP4 = 0x31
INTERFACE1_IP4_MASK = 0x32
INTERFACE1_MAC = 0x33
INTERFACE1_IP6 = 0x34
INTERFACE1_IP6_MASK = 0x35
INTERFACE2_NUMBER = 0x36
INTERFACE2_IP4 = 0x37
INTERFACE2_IP4_MASK = 0x38
INTERFACE2_MAC = 0x39
INTERFACE2_IP6 = 0x40
INTERFACE2_IP6_MASK = 0x41
INTERFACE1_NAME = 0x42
INTERFACE2_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
INTERFACE_NUMBER = 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

@ -7,7 +7,6 @@ from collections import OrderedDict
from typing import TYPE_CHECKING, Dict, List, Tuple, Type, Union
from core.emane.nodes import EmaneNet
from core.emulator.data import ConfigData
from core.emulator.enumerations import ConfigDataTypes
from core.nodes.network import WlanNode
@ -110,104 +109,6 @@ class ConfigurableOptions:
)
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 = []
logging.debug("configurable: %s", configurable_options)
logging.debug("configuration options: %s", configurable_options.configurations)
logging.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,
)
class ConfigurableManager:
"""
Provides convenience methods for storing and retrieving configuration options for

View file

@ -1,10 +1,10 @@
import logging
from typing import Any, Dict
import netaddr
from core import utils
from core.configservice.base import ConfigService, ConfigServiceMode
from core.nodes.base import CoreNode
GROUP_NAME = "Utility"
@ -24,16 +24,21 @@ class DefaultRouteService(ConfigService):
modes = {}
def data(self) -> Dict[str, Any]:
addresses = []
for netif in self.node.netifs():
if getattr(netif, "control", False):
# only add default routes for linked routing nodes
routes = []
for other_node in self.node.session.nodes.values():
if not isinstance(other_node, CoreNode):
continue
for addr in netif.addrlist:
logging.info("default route address: %s", addr)
net = netaddr.IPNetwork(addr)
if net[1] != net[-2]:
addresses.append(net[1])
return dict(addresses=addresses)
if other_node.type not in ["router", "mdr"]:
continue
commonnets = self.node.commonnets(other_node)
if commonnets:
_, _, router_eth = commonnets[0]
for x in router_eth.addrlist:
addr, prefix = x.split("/")
routes.append(addr)
break
return dict(routes=routes)
class DefaultMulticastRouteService(ConfigService):

View file

@ -1,5 +1,5 @@
#!/bin/sh
# auto-generated by DefaultRoute service
% for address in addresses:
ip route add default via ${address}
% for route in routes:
ip route add default via ${route}
% endfor

View file

@ -15,6 +15,7 @@ from core.emane.bypass import EmaneBypassModel
from core.emane.commeffect import EmaneCommEffectModel
from core.emane.emanemodel import EmaneModel
from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emane.linkmonitor import EmaneLinkMonitor
from core.emane.nodes import EmaneNet
from core.emane.rfpipe import EmaneRfPipeModel
from core.emane.tdma import EmaneTdmaModel
@ -28,7 +29,6 @@ from core.xml import emanexml
if TYPE_CHECKING:
from core.emulator.session import Session
try:
from emane.events import EventService
from emane.events import LocationEvent
@ -39,6 +39,9 @@ except ImportError:
from emanesh.events import LocationEvent
from emanesh.events.eventserviceexception import EventServiceException
except ImportError:
EventService = None
LocationEvent = None
EventServiceException = None
logging.debug("compatible emane python bindings not installed")
EMANE_MODELS = [
@ -60,7 +63,7 @@ class EmaneManager(ModelManager):
"""
name = "emane"
config_type = RegisterTlvs.EMULATION_SERVER.value
config_type = RegisterTlvs.EMULATION_SERVER
SUCCESS, NOT_NEEDED, NOT_READY = (0, 1, 2)
EVENTCFGVAR = "LIBEMANEEVENTSERVICECONFIG"
DEFAULT_LOG_LEVEL = 3
@ -90,6 +93,9 @@ class EmaneManager(ModelManager):
self.emane_config = EmaneGlobalModel(session)
self.set_configs(self.emane_config.default_values())
# link monitor
self.link_monitor = EmaneLinkMonitor(self)
self.service = None
self.eventchannel = None
self.event_device = None
@ -276,6 +282,10 @@ class EmaneManager(ModelManager):
logging.debug("no emane nodes in session")
return EmaneManager.NOT_NEEDED
# check if bindings were installed
if EventService is None:
raise CoreError("EMANE python bindings are not installed")
# control network bridge required for EMANE 0.9.2
# - needs to exist when eventservice binds to it (initeventservice)
otadev = self.get_config("otamanagerdevice")
@ -349,9 +359,13 @@ class EmaneManager(ModelManager):
f.write(f"{nodename} {ifname} {nemid}\n")
except IOError:
logging.exception("Error writing EMANE NEMs file: %s")
if self.links_enabled():
self.link_monitor.start()
return EmaneManager.SUCCESS
def links_enabled(self) -> bool:
return self.get_config("link_enabled") == "1"
def poststartup(self) -> None:
"""
Retransmit location events now that all NEMs are active.
@ -369,8 +383,7 @@ class EmaneManager(ModelManager):
)
emane_node.model.post_startup()
for netif in emane_node.netifs():
x, y, z = netif.node.position.get()
emane_node.setnemposition(netif, x, y, z)
netif.setposition()
def reset(self) -> None:
"""
@ -394,7 +407,9 @@ class EmaneManager(ModelManager):
with self._emane_node_lock:
if not self._emane_nets:
return
logging.info("stopping EMANE daemons.")
logging.info("stopping EMANE daemons")
if self.links_enabled():
self.link_monitor.stop()
self.deinstallnetifs()
self.stopdaemons()
self.stopeventmonitor()
@ -806,8 +821,8 @@ class EmaneManager(ModelManager):
# don"t use node.setposition(x,y,z) which generates an event
node.position.set(x, y, z)
node_data = node.data(message_type=0, lat=lat, lon=lon, alt=alt)
self.session.broadcast_node(node_data)
node.position.set_geo(lon, lat, alt)
self.session.broadcast_node(node)
return True
def emanerunning(self, node: CoreNode) -> bool:
@ -835,13 +850,43 @@ class EmaneGlobalModel:
def __init__(self, session: "Session") -> None:
self.session = session
self.nem_config = [
self.core_config = [
Configuration(
_id="platform_id_start",
_type=ConfigDataTypes.INT32,
default="1",
label="Starting Platform ID",
),
Configuration(
_id="nem_id_start",
_type=ConfigDataTypes.INT32,
default="1",
label="Starting NEM ID (core)",
)
label="Starting NEM ID",
),
Configuration(
_id="link_enabled",
_type=ConfigDataTypes.BOOL,
default="1",
label="Enable Links?",
),
Configuration(
_id="loss_threshold",
_type=ConfigDataTypes.INT32,
default="30",
label="Link Loss Threshold (%)",
),
Configuration(
_id="link_interval",
_type=ConfigDataTypes.INT32,
default="1",
label="Link Check Interval (sec)",
),
Configuration(
_id="link_timeout",
_type=ConfigDataTypes.INT32,
default="4",
label="Link Timeout (sec)",
),
]
self.emulator_config = None
self.parse_config()
@ -858,25 +903,16 @@ class EmaneGlobalModel:
"otamanagergroup": "224.1.2.8:45702",
}
self.emulator_config = emanemanifest.parse(emulator_xml, emulator_defaults)
self.emulator_config.insert(
0,
Configuration(
_id="platform_id_start",
_type=ConfigDataTypes.INT32,
default="1",
label="Starting Platform ID (core)",
),
)
def configurations(self) -> List[Configuration]:
return self.emulator_config + self.nem_config
return self.emulator_config + self.core_config
def config_groups(self) -> List[ConfigGroup]:
emulator_len = len(self.emulator_config)
config_len = len(self.configurations())
return [
ConfigGroup("Platform Attributes", 1, emulator_len),
ConfigGroup("NEM Parameters", emulator_len + 1, config_len),
ConfigGroup("CORE Configuration", emulator_len + 1, config_len),
]
def default_values(self) -> Dict[str, str]:

View file

@ -0,0 +1,333 @@
import logging
import sched
import threading
import time
from typing import TYPE_CHECKING, Dict, List, Tuple
import netaddr
from lxml import etree
from core.emulator.data import LinkData
from core.emulator.enumerations import LinkTypes, MessageFlags
from core.nodes.network import CtrlNet
try:
from emane import shell
except ImportError:
try:
from emanesh import shell
except ImportError:
logging.debug("compatible emane python bindings not installed")
if TYPE_CHECKING:
from core.emane.emanemanager import EmaneManager
DEFAULT_PORT = 47_000
MAC_COMPONENT_INDEX = 1
EMANE_RFPIPE = "rfpipemaclayer"
EMANE_80211 = "ieee80211abgmaclayer"
EMANE_TDMA = "tdmaeventschedulerradiomodel"
SINR_TABLE = "NeighborStatusTable"
NEM_SELF = 65535
class LossTable:
def __init__(self, losses: Dict[float, float]) -> None:
self.losses = losses
self.sinrs = sorted(self.losses.keys())
self.loss_lookup = {}
for index, value in enumerate(self.sinrs):
self.loss_lookup[index] = self.losses[value]
self.mac_id = None
def get_loss(self, sinr: float) -> float:
index = self._get_index(sinr)
loss = 100.0 - self.loss_lookup[index]
return loss
def _get_index(self, current_sinr: float) -> int:
for index, sinr in enumerate(self.sinrs):
if current_sinr <= sinr:
return index
return len(self.sinrs) - 1
class EmaneLink:
def __init__(self, from_nem: int, to_nem: int, sinr: float) -> None:
self.from_nem = from_nem
self.to_nem = to_nem
self.sinr = sinr
self.last_seen = None
self.updated = False
self.touch()
def update(self, sinr: float) -> None:
self.updated = self.sinr != sinr
self.sinr = sinr
self.touch()
def touch(self) -> None:
self.last_seen = time.monotonic()
def is_dead(self, timeout: int) -> bool:
return (time.monotonic() - self.last_seen) >= timeout
def __repr__(self) -> str:
return f"EmaneLink({self.from_nem}, {self.to_nem}, {self.sinr})"
class EmaneClient:
def __init__(self, address: str) -> None:
self.address = address
self.client = shell.ControlPortClient(self.address, DEFAULT_PORT)
self.nems = {}
self.setup()
def setup(self) -> None:
manifest = self.client.getManifest()
for nem_id, components in manifest.items():
# get mac config
mac_id, _, emane_model = components[MAC_COMPONENT_INDEX]
mac_config = self.client.getConfiguration(mac_id)
logging.debug(
"address(%s) nem(%s) emane(%s)", self.address, nem_id, emane_model
)
# create loss table based on current configuration
if emane_model == EMANE_80211:
loss_table = self.handle_80211(mac_config)
elif emane_model == EMANE_RFPIPE:
loss_table = self.handle_rfpipe(mac_config)
else:
logging.warning("unknown emane link model: %s", emane_model)
continue
logging.info("monitoring links nem(%s) model(%s)", nem_id, emane_model)
loss_table.mac_id = mac_id
self.nems[nem_id] = loss_table
def check_links(
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,))
table = tables[SINR_TABLE][1:][0]
for row in table:
row = row
to_nem = row[0][0]
sinr = row[5][0]
age = row[-1][0]
# exclude invalid links
is_self = to_nem == NEM_SELF
has_valid_age = 0 <= age <= 1
if is_self or not has_valid_age:
continue
# check if valid link loss
link_key = (from_nem, to_nem)
loss = loss_table.get_loss(sinr)
if loss < loss_threshold:
link = links.get(link_key)
if link:
link.update(sinr)
else:
link = EmaneLink(from_nem, to_nem, sinr)
links[link_key] = link
def handle_tdma(self, config: Dict[str, Tuple]):
pcr = config["pcrcurveuri"][0][0]
logging.debug("tdma pcr: %s", pcr)
def handle_80211(self, config: Dict[str, Tuple]) -> LossTable:
unicastrate = config["unicastrate"][0][0]
pcr = config["pcrcurveuri"][0][0]
logging.debug("80211 pcr: %s", pcr)
tree = etree.parse(pcr)
root = tree.getroot()
table = root.find("table")
losses = {}
for rate in table.iter("datarate"):
index = int(rate.get("index"))
if index == unicastrate:
for row in rate.iter("row"):
sinr = float(row.get("sinr"))
por = float(row.get("por"))
losses[sinr] = por
return LossTable(losses)
def handle_rfpipe(self, config: Dict[str, Tuple]) -> LossTable:
pcr = config["pcrcurveuri"][0][0]
logging.debug("rfpipe pcr: %s", pcr)
tree = etree.parse(pcr)
root = tree.getroot()
table = root.find("table")
losses = {}
for row in table.iter("row"):
sinr = float(row.get("sinr"))
por = float(row.get("por"))
losses[sinr] = por
return LossTable(losses)
def stop(self) -> None:
self.client.stop()
class EmaneLinkMonitor:
def __init__(self, emane_manager: "EmaneManager") -> None:
self.emane_manager = emane_manager
self.clients = []
self.links = {}
self.complete_links = set()
self.loss_threshold = None
self.link_interval = None
self.link_timeout = None
self.scheduler = None
self.running = False
def start(self) -> None:
self.loss_threshold = int(self.emane_manager.get_config("loss_threshold"))
self.link_interval = int(self.emane_manager.get_config("link_interval"))
self.link_timeout = int(self.emane_manager.get_config("link_timeout"))
self.initialize()
if not self.clients:
logging.info("no valid emane models to monitor links")
return
self.scheduler = sched.scheduler()
self.scheduler.enter(0, 0, self.check_links)
self.running = True
thread = threading.Thread(target=self.scheduler.run, daemon=True)
thread.start()
def initialize(self) -> None:
addresses = self.get_addresses()
for address in addresses:
client = EmaneClient(address)
if client.nems:
self.clients.append(client)
def get_addresses(self) -> List[str]:
addresses = []
nodes = self.emane_manager.getnodes()
for node in nodes:
for netif in node.netifs():
if isinstance(netif.net, CtrlNet):
ip4 = None
for x in netif.addrlist:
address, prefix = x.split("/")
if netaddr.valid_ipv4(address):
ip4 = address
if ip4:
addresses.append(ip4)
break
return addresses
def check_links(self) -> None:
# check for new links
previous_links = set(self.links.keys())
for client in self.clients:
try:
client.check_links(self.links, self.loss_threshold)
except shell.ControlPortException:
if self.running:
logging.exception("link monitor error")
# find new links
current_links = set(self.links.keys())
new_links = current_links - previous_links
# find updated and dead links
dead_links = []
for link_id, link in self.links.items():
complete_id = self.get_complete_id(link_id)
if link.is_dead(self.link_timeout):
dead_links.append(link_id)
elif link.updated and complete_id in self.complete_links:
link.updated = False
self.send_link(MessageFlags.NONE, complete_id)
# announce dead links
for link_id in dead_links:
complete_id = self.get_complete_id(link_id)
if complete_id in self.complete_links:
self.complete_links.remove(complete_id)
self.send_link(MessageFlags.DELETE, complete_id)
del self.links[link_id]
# announce new links
for link_id in new_links:
complete_id = self.get_complete_id(link_id)
if complete_id in self.complete_links:
continue
if self.is_complete_link(link_id):
self.complete_links.add(complete_id)
self.send_link(MessageFlags.ADD, complete_id)
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]:
value_one, value_two = link_id
if value_one < value_two:
return value_one, value_two
else:
return value_two, value_one
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:
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:
nem_one, nem_two = link_id
emane_one, netif = self.emane_manager.nemlookup(nem_one)
if not emane_one or not netif:
logging.error("invalid nem: %s", nem_one)
return
node_one = netif.node
emane_two, netif = self.emane_manager.nemlookup(nem_two)
if not emane_two or not netif:
logging.error("invalid nem: %s", nem_two)
return
node_two = netif.node
logging.debug(
"%s emane link from %s(%s) to %s(%s)",
message_type.name,
node_one.name,
nem_one,
node_two.name,
nem_two,
)
label = self.get_link_label(link_id)
self.send_message(message_type, label, node_one.id, node_two.id, emane_one.id)
def send_message(
self,
message_type: MessageFlags,
label: str,
node_one: int,
node_two: int,
emane_id: int,
) -> None:
link_data = LinkData(
message_type=message_type,
label=label,
node1_id=node_one,
node2_id=node_two,
network_id=emane_id,
link_type=LinkTypes.WIRELESS,
)
self.emane_manager.session.broadcast_link(link_data)
def stop(self) -> None:
self.running = False
for client in self.clients:
client.stop()
self.clients.clear()
self.links.clear()
self.complete_links.clear()

View file

@ -4,7 +4,7 @@ share the same MAC+PHY model.
"""
import logging
from typing import TYPE_CHECKING, Dict, List, Optional, Type
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs
@ -33,8 +33,8 @@ class EmaneNet(CoreNetworkBase):
Emane controller object that exists in a session.
"""
apitype = NodeTypes.EMANE.value
linktype = LinkTypes.WIRED.value
apitype = NodeTypes.EMANE
linktype = LinkTypes.WIRED
type = "wlan"
is_emane = True
@ -103,12 +103,12 @@ class EmaneNet(CoreNetworkBase):
set the EmaneModel associated with this node
"""
logging.info("adding model: %s", model.name)
if model.config_type == RegisterTlvs.WIRELESS.value:
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)
elif model.config_type == RegisterTlvs.MOBILITY.value:
elif model.config_type == RegisterTlvs.MOBILITY:
self.mobility = model(session=self.session, _id=self.id)
self.mobility.update_config(config)
@ -172,8 +172,7 @@ class EmaneNet(CoreNetworkBase):
# at this point we register location handlers for generating
# EMANE location events
netif.poshook = self.setnemposition
x, y, z = netif.node.position.get()
self.setnemposition(netif, x, y, z)
netif.setposition()
def deinstallnetifs(self) -> None:
"""
@ -186,28 +185,45 @@ class EmaneNet(CoreNetworkBase):
netif.shutdown()
netif.poshook = None
def setnemposition(
self, netif: CoreInterface, x: float, y: float, z: float
) -> None:
def _nem_position(
self, netif: CoreInterface
) -> Optional[Tuple[int, float, float, float]]:
"""
Publish a NEM location change event using the EMANE event service.
Creates nem position for emane event for a given interface.
:param netif: interface to get nem emane position for
:return: nem position tuple, None otherwise
"""
if self.session.emane.service is None:
logging.info("position service not available")
return
nemid = self.getnemid(netif)
ifname = netif.localname
if nemid is None:
logging.info("nemid for %s is unknown", ifname)
return
node = netif.node
x, y, z = node.getposition()
lat, lon, alt = self.session.location.getgeo(x, y, z)
event = LocationEvent()
if node.position.alt is not None:
alt = node.position.alt
# altitude must be an integer or warning is printed
# unused: yaw, pitch, roll, azimuth, elevation, velocity
alt = int(round(alt))
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
self.session.emane.service.publish(0, event)
return nemid, lon, lat, alt
def setnemposition(self, netif: CoreInterface) -> None:
"""
Publish a NEM location change event using the EMANE event service.
:param netif: interface to set nem position for
"""
if self.session.emane.service is None:
logging.info("position service not available")
return
position = self._nem_position(netif)
if position:
nemid, lon, lat, alt = position
event = LocationEvent()
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
self.session.emane.service.publish(0, event)
def setnempositions(self, moved_netifs: List[CoreInterface]) -> None:
"""
@ -223,18 +239,9 @@ class EmaneNet(CoreNetworkBase):
return
event = LocationEvent()
i = 0
for netif in moved_netifs:
nemid = self.getnemid(netif)
ifname = netif.localname
if nemid is None:
logging.info("nemid for %s is unknown", ifname)
continue
x, y, z = netif.node.getposition()
lat, lon, alt = self.session.location.getgeo(x, y, z)
# altitude must be an integer or warning is printed
alt = int(round(alt))
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
i += 1
position = self._nem_position(netif)
if position:
nemid, lon, lat, alt = position
event.append(nemid, latitude=lat, longitude=lon, altitude=alt)
self.session.emane.service.publish(0, event)

View file

@ -2,121 +2,130 @@
CORE data objects.
"""
import collections
from dataclasses import dataclass
from typing import List, Tuple
ConfigData = collections.namedtuple(
"ConfigData",
[
"message_type",
"node",
"object",
"type",
"data_types",
"data_values",
"captions",
"bitmap",
"possible_values",
"groups",
"session",
"interface_number",
"network_id",
"opaque",
],
from core.emulator.enumerations import (
EventTypes,
ExceptionLevels,
LinkTypes,
MessageFlags,
NodeTypes,
)
ConfigData.__new__.__defaults__ = (None,) * len(ConfigData._fields)
EventData = collections.namedtuple(
"EventData", ["node", "event_type", "name", "data", "time", "session"]
)
EventData.__new__.__defaults__ = (None,) * len(EventData._fields)
ExceptionData = collections.namedtuple(
"ExceptionData", ["node", "session", "level", "source", "date", "text", "opaque"]
)
ExceptionData.__new__.__defaults__ = (None,) * len(ExceptionData._fields)
@dataclass
class ConfigData:
message_type: int = None
node: int = None
object: str = None
type: int = None
data_types: Tuple[int] = None
data_values: str = None
captions: str = None
bitmap: str = None
possible_values: str = None
groups: str = None
session: int = None
interface_number: int = None
network_id: int = None
opaque: str = None
FileData = collections.namedtuple(
"FileData",
[
"message_type",
"node",
"name",
"mode",
"number",
"type",
"source",
"session",
"data",
"compressed_data",
],
)
FileData.__new__.__defaults__ = (None,) * len(FileData._fields)
NodeData = collections.namedtuple(
"NodeData",
[
"message_type",
"id",
"node_type",
"name",
"ip_address",
"mac_address",
"ip6_address",
"model",
"emulation_id",
"server",
"session",
"x_position",
"y_position",
"canvas",
"network_id",
"services",
"latitude",
"longitude",
"altitude",
"icon",
"opaque",
"source",
],
)
NodeData.__new__.__defaults__ = (None,) * len(NodeData._fields)
@dataclass
class EventData:
node: int = None
event_type: EventTypes = None
name: str = None
data: str = None
time: float = None
session: int = None
LinkData = collections.namedtuple(
"LinkData",
[
"message_type",
"node1_id",
"node2_id",
"delay",
"bandwidth",
"per",
"dup",
"jitter",
"mer",
"burst",
"session",
"mburst",
"link_type",
"gui_attributes",
"unidirectional",
"emulation_id",
"network_id",
"key",
"interface1_id",
"interface1_name",
"interface1_ip4",
"interface1_ip4_mask",
"interface1_mac",
"interface1_ip6",
"interface1_ip6_mask",
"interface2_id",
"interface2_name",
"interface2_ip4",
"interface2_ip4_mask",
"interface2_mac",
"interface2_ip6",
"interface2_ip6_mask",
"opaque",
],
)
LinkData.__new__.__defaults__ = (None,) * len(LinkData._fields)
@dataclass
class ExceptionData:
node: int = None
session: int = None
level: ExceptionLevels = None
source: str = None
date: str = None
text: str = None
opaque: str = None
@dataclass
class FileData:
message_type: MessageFlags = None
node: int = None
name: str = None
mode: str = None
number: int = None
type: str = None
source: str = None
session: int = None
data: str = None
compressed_data: str = None
@dataclass
class NodeData:
message_type: MessageFlags = None
id: int = None
node_type: NodeTypes = None
name: str = None
ip_address: str = None
mac_address: str = None
ip6_address: str = None
model: str = None
emulation_id: int = None
server: str = None
session: int = None
x_position: float = None
y_position: float = None
canvas: int = None
network_id: int = None
services: List[str] = None
latitude: float = None
longitude: float = None
altitude: float = None
icon: str = None
opaque: str = None
source: str = None
@dataclass
class LinkData:
message_type: MessageFlags = None
label: str = None
node1_id: int = None
node2_id: int = None
delay: float = None
bandwidth: float = None
per: float = None
dup: float = None
jitter: float = None
mer: float = None
burst: float = None
session: int = None
mburst: float = None
link_type: LinkTypes = None
gui_attributes: str = None
unidirectional: int = None
emulation_id: int = None
network_id: int = None
key: int = None
interface1_id: int = None
interface1_name: str = None
interface1_ip4: str = None
interface1_ip4_mask: int = None
interface1_mac: str = None
interface1_ip6: str = None
interface1_ip6_mask: int = None
interface2_id: int = None
interface2_name: str = None
interface2_ip4: str = None
interface2_ip4_mask: int = None
interface2_mac: str = None
interface2_ip6: str = None
interface2_ip6_mask: int = None
opaque: str = None

View file

@ -1,35 +1,16 @@
"""
Contains all legacy enumerations for interacting with legacy CORE code.
Common enumerations used within CORE.
"""
from enum import Enum
CORE_API_VERSION = "1.23"
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 MessageFlags(Enum):
"""
CORE message flags.
"""
NONE = 0x00
ADD = 0x01
DELETE = 0x02
CRI = 0x04
@ -39,33 +20,6 @@ class MessageFlags(Enum):
TTY = 0x40
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 NodeTypes(Enum):
"""
Node types.
@ -86,56 +40,6 @@ class NodeTypes(Enum):
LXC = 16
class Rj45Models(Enum):
"""
RJ45 model types.
"""
LINKED = 0
WIRELESS = 1
INSTALLED = 2
# Link Message TLV Types
class LinkTlvs(Enum):
"""
Link type, length, value enumerations.
"""
N1_NUMBER = 0x01
N2_NUMBER = 0x02
DELAY = 0x03
BANDWIDTH = 0x04
PER = 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
INTERFACE1_NUMBER = 0x30
INTERFACE1_IP4 = 0x31
INTERFACE1_IP4_MASK = 0x32
INTERFACE1_MAC = 0x33
INTERFACE1_IP6 = 0x34
INTERFACE1_IP6_MASK = 0x35
INTERFACE2_NUMBER = 0x36
INTERFACE2_IP4 = 0x37
INTERFACE2_IP4_MASK = 0x38
INTERFACE2_MAC = 0x39
INTERFACE2_IP6 = 0x40
INTERFACE2_IP6_MASK = 0x41
INTERFACE1_NAME = 0x42
INTERFACE2_NAME = 0x43
OPAQUE = 0x50
class LinkTypes(Enum):
"""
Link types.
@ -145,20 +49,6 @@ class LinkTypes(Enum):
WIRED = 1
class ExecuteTlvs(Enum):
"""
Execute type, length, value enumerations.
"""
NODE = 0x01
NUMBER = 0x02
TIME = 0x03
COMMAND = 0x04
RESULT = 0x05
STATUS = 0x06
SESSION = 0x0A
class RegisterTlvs(Enum):
"""
Register type, length, value enumerations.
@ -173,37 +63,6 @@ class RegisterTlvs(Enum):
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
INTERFACE_NUMBER = 0x0B
NETWORK_ID = 0x24
OPAQUE = 0x50
class ConfigFlags(Enum):
"""
Configuration flags.
"""
NONE = 0x00
REQUEST = 0x01
UPDATE = 0x02
RESET = 0x03
class ConfigDataTypes(Enum):
"""
Configuration data types.
@ -222,55 +81,6 @@ class ConfigDataTypes(Enum):
BOOL = 0x0B
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 EventTypes(Enum):
"""
Event types.
@ -293,34 +103,8 @@ class EventTypes(Enum):
RECONFIGURE = 14
INSTANTIATION_COMPLETE = 15
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
def should_start(self) -> bool:
return self.value > self.DEFINITION_STATE.value
class ExceptionLevels(Enum):

View file

@ -17,14 +17,7 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type
from core import constants, utils
from core.emane.emanemanager import EmaneManager
from core.emane.nodes import EmaneNet
from core.emulator.data import (
ConfigData,
EventData,
ExceptionData,
FileData,
LinkData,
NodeData,
)
from core.emulator.data import ConfigData, EventData, ExceptionData, FileData, LinkData
from core.emulator.distributed import DistributedController
from core.emulator.emudata import (
IdGen,
@ -34,7 +27,13 @@ from core.emulator.emudata import (
create_interface,
link_config,
)
from core.emulator.enumerations import EventTypes, ExceptionLevels, LinkTypes, NodeTypes
from core.emulator.enumerations import (
EventTypes,
ExceptionLevels,
LinkTypes,
MessageFlags,
NodeTypes,
)
from core.emulator.sessionconfig import SessionConfig
from core.errors import CoreError
from core.location.event import EventLoop
@ -112,8 +111,7 @@ class Session:
self.nodes = {}
self._nodes_lock = threading.Lock()
# TODO: should the default state be definition?
self.state = EventTypes.NONE.value
self.state = EventTypes.DEFINITION_STATE
self._state_time = time.monotonic()
self._state_file = os.path.join(self.session_dir, "state")
@ -121,7 +119,7 @@ class Session:
self._hooks = {}
self._state_hooks = {}
self.add_state_hook(
state=EventTypes.RUNTIME_STATE.value, hook=self.runtime_state_hook
state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook
)
# handlers for broadcasting information
@ -345,7 +343,7 @@ class Session:
node_one.name,
node_two.name,
)
start = self.state > EventTypes.DEFINITION_STATE.value
start = self.state.should_start()
net_one = self.create_node(cls=PtpNet, start=start)
# node to network
@ -432,7 +430,7 @@ class Session:
if node_two:
node_two.lock.release()
self.sdt.add_link(node_one_id, node_two_id, is_wireless=False)
self.sdt.add_link(node_one_id, node_two_id)
return node_one_interface, node_two_interface
def delete_link(
@ -578,7 +576,7 @@ class Session:
try:
# wireless link
if link_options.type == LinkTypes.WIRELESS.value:
if link_options.type == LinkTypes.WIRELESS:
raise CoreError("cannot update wireless link")
else:
if not node_one and not node_two:
@ -680,7 +678,7 @@ class Session:
node_class = _cls
# set node start based on current session state, override and check when rj45
start = self.state > EventTypes.DEFINITION_STATE.value
start = self.state.should_start()
enable_rj45 = self.options.get_config("enablerj45") == "1"
if _type == NodeTypes.RJ45 and not enable_rj45:
start = False
@ -755,7 +753,7 @@ class Session:
# boot nodes after runtime, CoreNodes, Physical, and RJ45 are all nodes
is_boot_node = isinstance(node, CoreNodeBase) and not isinstance(node, Rj45Node)
if self.state == EventTypes.RUNTIME_STATE.value and is_boot_node:
if self.state == EventTypes.RUNTIME_STATE and is_boot_node:
self.write_nodes()
self.add_remove_control_interface(node=node, remove=False)
self.services.boot_services(node)
@ -806,34 +804,13 @@ class Session:
using_lat_lon_alt = has_empty_position and has_lat_lon_alt
if using_lat_lon_alt:
x, y, _ = self.location.getxyz(lat, lon, alt)
# set position and broadcast
if None not in [x, y]:
node.setposition(x, y, None)
# broadcast updated location when using lat/lon/alt
if using_lat_lon_alt:
self.broadcast_node_location(node, lon, lat, alt)
def broadcast_node_location(
self, node: NodeBase, lon: float, lat: float, alt: float
) -> None:
"""
Broadcast node location to all listeners.
:param node: node to broadcast location for
:return: nothing
"""
node_data = NodeData(
message_type=0,
id=node.id,
x_position=node.position.x,
y_position=node.position.y,
latitude=lat,
longitude=lon,
altitude=alt,
)
self.broadcast_node(node_data)
node.position.set_geo(lon, lat, alt)
self.broadcast_node(node)
else:
if has_empty_position:
x, y = 0, 0
node.setposition(x, y, None)
def start_mobility(self, node_ids: List[int] = None) -> None:
"""
@ -850,10 +827,7 @@ class Session:
:return: True if active, False otherwise
"""
result = self.state in {
EventTypes.RUNTIME_STATE.value,
EventTypes.DATACOLLECT_STATE.value,
}
result = self.state in {EventTypes.RUNTIME_STATE, EventTypes.DATACOLLECT_STATE}
logging.info("session(%s) checking if active: %s", self.id, result)
return result
@ -894,7 +868,9 @@ class Session:
"""
CoreXmlWriter(self).write(file_name)
def add_hook(self, state: int, file_name: str, source_name: str, data: str) -> None:
def add_hook(
self, state: EventTypes, file_name: str, source_name: str, data: str
) -> None:
"""
Store a hook from a received file message.
@ -904,9 +880,17 @@ class Session:
:param data: hook data
:return: nothing
"""
# hack to conform with old logic until updated
state = f":{state}"
self.set_hook(state, file_name, source_name, data)
logging.info(
"setting state hook: %s - %s from %s", state, file_name, source_name
)
hook = file_name, data
state_hooks = self._hooks.setdefault(state, [])
state_hooks.append(hook)
# immediately run a hook if it is in the current state
if self.state == state:
logging.info("immediately running new state hook")
self.run_hook(hook)
def add_node_file(
self, node_id: int, source_name: str, file_name: str, data: str
@ -1019,14 +1003,23 @@ class Session:
for handler in self.exception_handlers:
handler(exception_data)
def broadcast_node(self, node_data: NodeData) -> None:
def broadcast_node(
self,
node: NodeBase,
message_type: MessageFlags = MessageFlags.NONE,
source: str = None,
) -> None:
"""
Handle node data that should be provided to node handlers.
:param node_data: node data to send out
:param node: node to broadcast
:param message_type: type of message to broadcast, None by default
:param source: source of broadcast, None by default
:return: nothing
"""
node_data = node.data(message_type, source)
if not node_data:
return
for handler in self.node_handlers:
handler(node_data)
@ -1071,10 +1064,8 @@ class Session:
:param send_event: if true, generate core API event messages
:return: nothing
"""
state_value = state.value
state_name = state.name
if self.state == state_value:
if self.state == state:
logging.info(
"session(%s) is already in state: %s, skipping change",
self.id,
@ -1082,33 +1073,32 @@ class Session:
)
return
self.state = state_value
self.state = state
self._state_time = time.monotonic()
logging.info("changing session(%s) to state %s", self.id, state_name)
self.write_state(state_value)
self.run_hooks(state_value)
self.run_state_hooks(state_value)
self.write_state(state)
self.run_hooks(state)
self.run_state_hooks(state)
if send_event:
event_data = EventData(event_type=state_value, time=str(time.monotonic()))
event_data = EventData(event_type=state, time=str(time.monotonic()))
self.broadcast_event(event_data)
def write_state(self, state: int) -> None:
def write_state(self, state: EventTypes) -> None:
"""
Write the current state to a state file in the session dir.
Write the state to a state file in the session dir.
:param state: state to write to file
:return: nothing
"""
try:
state_file = open(self._state_file, "w")
state_file.write(f"{state} {EventTypes(self.state).name}\n")
state_file.write(f"{state.value} {state.name}\n")
state_file.close()
except IOError:
logging.exception("error writing state file: %s", state)
logging.exception("error writing state file: %s", state.name)
def run_hooks(self, state: int) -> None:
def run_hooks(self, state: EventTypes) -> None:
"""
Run hook scripts upon changing states. If hooks is not specified, run all hooks
in the given state.
@ -1212,7 +1202,7 @@ class Session:
except (OSError, subprocess.CalledProcessError):
logging.exception("error running hook: %s", file_name)
def run_state_hooks(self, state: int) -> None:
def run_state_hooks(self, state: EventTypes) -> None:
"""
Run state hooks.
@ -1223,16 +1213,17 @@ class Session:
try:
hook(state)
except Exception:
state_name = EventTypes(self.state).name
message = (
f"exception occured when running {state_name} state hook: {hook}"
f"exception occured when running {state.name} state hook: {hook}"
)
logging.exception(message)
self.exception(
ExceptionLevels.ERROR, "Session.run_state_hooks", None, message
ExceptionLevels.ERROR, "Session.run_state_hooks", message
)
def add_state_hook(self, state: int, hook: Callable[[int], None]) -> None:
def add_state_hook(
self, state: EventTypes, hook: Callable[[EventTypes], None]
) -> None:
"""
Add a state hook.
@ -1259,14 +1250,14 @@ class Session:
hooks = self._state_hooks.setdefault(state, [])
hooks.remove(hook)
def runtime_state_hook(self, state: int) -> None:
def runtime_state_hook(self, state: EventTypes) -> None:
"""
Runtime state hook check.
:param state: state to check
:return: nothing
"""
if state == EventTypes.RUNTIME_STATE.value:
if state == EventTypes.RUNTIME_STATE:
self.emane.poststartup()
# create session deployed xml
@ -1413,8 +1404,8 @@ class Session:
if node:
node.shutdown()
self.check_shutdown()
self.sdt.delete_node(_id)
self.check_shutdown()
return node is not None
@ -1460,20 +1451,20 @@ class Session:
)
def exception(
self, level: ExceptionLevels, source: str, node_id: int, text: str
self, level: ExceptionLevels, source: str, text: str, node_id: int = None
) -> None:
"""
Generate and broadcast an exception event.
:param level: exception level
:param source: source name
:param node_id: node related to exception
:param text: exception message
:param node_id: node related to exception
:return: nothing
"""
exception_data = ExceptionData(
node=node_id,
session=str(self.id),
session=self.id,
level=level,
source=source,
date=time.ctime(),
@ -1510,7 +1501,7 @@ class Session:
self.mobility.startup()
# notify listeners that instantiation is complete
event = EventData(event_type=EventTypes.INSTANTIATION_COMPLETE.value)
event = EventData(event_type=EventTypes.INSTANTIATION_COMPLETE)
self.broadcast_event(event)
# assume either all nodes have booted already, or there are some
@ -1553,9 +1544,9 @@ class Session:
logging.debug(
"session(%s) checking if not in runtime state, current state: %s",
self.id,
EventTypes(self.state).name,
self.state.name,
)
if self.state == EventTypes.RUNTIME_STATE.value:
if self.state == EventTypes.RUNTIME_STATE:
logging.info("valid runtime state found, returning")
return
@ -1611,6 +1602,8 @@ class Session:
if node_count == 0:
shutdown = True
self.set_state(EventTypes.SHUTDOWN_STATE)
# clearing sdt saved data here for legacy gui
self.sdt.shutdown()
return shutdown
def short_session_id(self) -> str:
@ -1892,7 +1885,7 @@ class Session:
Return the current time we have been in the runtime state, or zero
if not in runtime.
"""
if self.state == EventTypes.RUNTIME_STATE.value:
if self.state == EventTypes.RUNTIME_STATE:
return time.monotonic() - self._state_time
else:
return 0.0

View file

@ -57,7 +57,7 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions):
label="SDT3D URL",
),
]
config_type = RegisterTlvs.UTILITY.value
config_type = RegisterTlvs.UTILITY
def __init__(self) -> None:
super().__init__()

View file

@ -11,6 +11,10 @@ from typing import TYPE_CHECKING, Dict, List
import grpc
from core.api.grpc import client, common_pb2, configservices_pb2, core_pb2
from core.api.grpc.emane_pb2 import EmaneModelConfig
from core.api.grpc.mobility_pb2 import MobilityConfig
from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig, ServiceFileConfig
from core.api.grpc.wlan_pb2 import WlanConfig
from core.gui import appconfig
from core.gui.dialogs.mobilityplayer import MobilityPlayer
from core.gui.dialogs.sessions import SessionsDialog
@ -617,9 +621,7 @@ class CoreClient:
except grpc.RpcError as e:
self.app.after(0, show_grpc_error, e, self.app, self.app)
def get_node_service(
self, node_id: int, service_name: str
) -> core_pb2.NodeServiceData:
def get_node_service(self, node_id: int, service_name: str) -> NodeServiceData:
response = self.client.get_node_service(self.session_id, node_id, service_name)
logging.debug(
"get node(%s) %s service, response: %s", node_id, service_name, response
@ -635,7 +637,7 @@ class CoreClient:
startups: List[str],
validations: List[str],
shutdowns: List[str],
) -> core_pb2.NodeServiceData:
) -> NodeServiceData:
response = self.client.set_node_service(
self.session_id,
node_id,
@ -675,7 +677,7 @@ class CoreClient:
return response.data
def set_node_service_file(
self, node_id: int, service_name: str, file_name: str, data: bytes
self, node_id: int, service_name: str, file_name: str, data: str
):
response = self.client.set_node_service_file(
self.session_id, node_id, service_name, file_name, data
@ -912,40 +914,40 @@ class CoreClient:
self.links[edge.token] = edge
logging.info("Add link between %s and %s", src_node.name, dst_node.name)
def get_wlan_configs_proto(self) -> List[core_pb2.WlanConfig]:
def get_wlan_configs_proto(self) -> List[WlanConfig]:
configs = []
for node_id, config in self.wlan_configs.items():
config = {x: config[x].value for x in config}
wlan_config = core_pb2.WlanConfig(node_id=node_id, config=config)
wlan_config = WlanConfig(node_id=node_id, config=config)
configs.append(wlan_config)
return configs
def get_mobility_configs_proto(self) -> List[core_pb2.MobilityConfig]:
def get_mobility_configs_proto(self) -> List[MobilityConfig]:
configs = []
for node_id, config in self.mobility_configs.items():
config = {x: config[x].value for x in config}
mobility_config = core_pb2.MobilityConfig(node_id=node_id, config=config)
mobility_config = MobilityConfig(node_id=node_id, config=config)
configs.append(mobility_config)
return configs
def get_emane_model_configs_proto(self) -> List[core_pb2.EmaneModelConfig]:
def get_emane_model_configs_proto(self) -> List[EmaneModelConfig]:
configs = []
for key, config in self.emane_model_configs.items():
node_id, model, interface = key
config = {x: config[x].value for x in config}
if interface is None:
interface = -1
config_proto = core_pb2.EmaneModelConfig(
config_proto = EmaneModelConfig(
node_id=node_id, interface_id=interface, model=model, config=config
)
configs.append(config_proto)
return configs
def get_service_configs_proto(self) -> List[core_pb2.ServiceConfig]:
def get_service_configs_proto(self) -> List[ServiceConfig]:
configs = []
for node_id, services in self.service_configs.items():
for name, config in services.items():
config_proto = core_pb2.ServiceConfig(
config_proto = ServiceConfig(
node_id=node_id,
service=name,
directories=config.dirs,
@ -957,12 +959,12 @@ class CoreClient:
configs.append(config_proto)
return configs
def get_service_file_configs_proto(self) -> List[core_pb2.ServiceFileConfig]:
def get_service_file_configs_proto(self) -> List[ServiceFileConfig]:
configs = []
for (node_id, file_configs) in self.file_configs.items():
for service, file_config in file_configs.items():
for file, data in file_config.items():
config_proto = core_pb2.ServiceFileConfig(
config_proto = ServiceFileConfig(
node_id=node_id, service=service, file=file, data=data
)
configs.append(config_proto)

View file

@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any, List
import grpc
from core.api.grpc import core_pb2
from core.api.grpc.services_pb2 import ServiceValidationMode
from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.themes import FRAME_PAD, PADX, PADY
@ -256,9 +256,9 @@ class ConfigServiceConfigDialog(Dialog):
label = ttk.Label(frame, text="Validation Mode")
label.grid(row=1, column=0, sticky="w", padx=PADX)
if self.validation_mode == core_pb2.ServiceValidationMode.BLOCKING:
if self.validation_mode == ServiceValidationMode.BLOCKING:
mode = "BLOCKING"
elif self.validation_mode == core_pb2.ServiceValidationMode.NON_BLOCKING:
elif self.validation_mode == ServiceValidationMode.NON_BLOCKING:
mode = "NON_BLOCKING"
else:
mode = "TIMER"

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any
import grpc
from core.api.grpc.core_pb2 import MobilityAction
from core.api.grpc.mobility_pb2 import MobilityAction
from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
from core.gui.images import ImageEnum, Images

View file

@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any, List
import grpc
from core.api.grpc import core_pb2
from core.api.grpc.services_pb2 import ServiceValidationMode
from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog
from core.gui.dialogs.dialog import Dialog
from core.gui.errors import show_grpc_error
@ -331,9 +331,9 @@ class ServiceConfigDialog(Dialog):
label = ttk.Label(frame, text="Validation Mode")
label.grid(row=1, column=0, sticky="w", padx=PADX)
if self.validation_mode == core_pb2.ServiceValidationMode.BLOCKING:
if self.validation_mode == ServiceValidationMode.BLOCKING:
mode = "BLOCKING"
elif self.validation_mode == core_pb2.ServiceValidationMode.NON_BLOCKING:
elif self.validation_mode == ServiceValidationMode.NON_BLOCKING:
mode = "NON_BLOCKING"
else:
mode = "TIMER"

View file

@ -1,6 +1,6 @@
import logging
import tkinter as tk
from typing import TYPE_CHECKING, List, Tuple
from typing import TYPE_CHECKING, Tuple
from PIL import Image, ImageTk
@ -440,7 +440,7 @@ class CanvasGraph(tk.Canvas):
if select_id is not None:
self.move(select_id, x_offset, y_offset)
def delete_selection_objects(self) -> List[CanvasNode]:
def delete_selected_objects(self) -> None:
edges = set()
nodes = []
for object_id in self.selection:
@ -484,7 +484,7 @@ class CanvasGraph(tk.Canvas):
shape.delete()
self.selection.clear()
return nodes
self.core.delete_graph_nodes(nodes)
def zoom(self, event: tk.Event, factor: float = None):
if not factor:
@ -640,16 +640,17 @@ class CanvasGraph(tk.Canvas):
self.select_box.shape_motion(x, y)
def click_context(self, event: tk.Event):
logging.info("context: %s", self.context)
if not self.context:
selected = self.get_selected(event)
canvas_node = self.nodes.get(selected)
if canvas_node:
logging.debug("node context: %s", selected)
self.context = canvas_node.create_context()
self.context.bind("<Leave>", self.hide_context)
self.context.bind("<Unmap>", self.hide_context)
self.context.post(event.x_root, event.y_root)
# else:
# self.hide_context()
else:
self.hide_context()
def press_delete(self, event: tk.Event):
"""
@ -657,8 +658,7 @@ class CanvasGraph(tk.Canvas):
"""
logging.debug("press delete key")
if not self.app.core.is_runtime():
nodes = self.delete_selection_objects()
self.core.delete_graph_nodes(nodes)
self.delete_selected_objects()
else:
logging.info("node deletion is disabled during runtime state")

View file

@ -233,17 +233,25 @@ class CanvasNode:
label="Link To Selected", command=self.wireless_link_selected
)
context.add_command(label="Select Members", state=tk.DISABLED)
context.add_command(label="Select Adjacent", state=tk.DISABLED)
context.add_command(label="Create Link To", state=tk.DISABLED)
context.add_command(label="Assign To", state=tk.DISABLED)
context.add_command(label="Move To", state=tk.DISABLED)
context.add_command(label="Cut", state=tk.DISABLED)
context.add_command(label="Copy", state=tk.DISABLED)
context.add_command(label="Paste", state=tk.DISABLED)
context.add_command(label="Delete", state=tk.DISABLED)
context.add_command(label="Hide", state=tk.DISABLED)
edit_menu = tk.Menu(context)
themes.style_menu(edit_menu)
edit_menu.add_command(label="Cut", state=tk.DISABLED)
edit_menu.add_command(label="Copy", command=self.canvas_copy)
edit_menu.add_command(label="Delete", command=self.canvas_delete)
edit_menu.add_command(label="Hide", state=tk.DISABLED)
context.add_cascade(label="Edit", menu=edit_menu)
return context
def canvas_delete(self) -> None:
self.canvas.clear_selection()
self.canvas.selection[self.id] = self
self.canvas.delete_selected_objects()
def canvas_copy(self) -> None:
self.canvas.clear_selection()
self.canvas.selection[self.id] = self
self.canvas.copy()
def show_config(self):
self.canvas.context = None
dialog = NodeConfigDialog(self.app, self.app, self)

View file

@ -152,31 +152,34 @@ class MenuAction:
dialog = ServersDialog(self.app, self.app)
dialog.show()
def edit_observer_widgets(self):
def edit_observer_widgets(self) -> None:
dialog = ObserverDialog(self.app, self.app)
dialog.show()
def show_about(self):
def show_about(self) -> None:
dialog = AboutDialog(self.app, self.app)
dialog.show()
def throughput(self):
def throughput(self) -> None:
if not self.app.core.handling_throughputs:
self.app.core.enable_throughputs()
else:
self.app.core.cancel_throughputs()
def copy(self, event: tk.Event = None):
def copy(self, event: tk.Event = None) -> None:
self.app.canvas.copy()
def paste(self, event: tk.Event = None):
def paste(self, event: tk.Event = None) -> None:
self.app.canvas.paste()
def config_throughput(self):
def delete(self, event: tk.Event = None) -> None:
self.app.canvas.delete_selected_objects()
def config_throughput(self) -> None:
dialog = ThroughputDialog(self.app, self.app)
dialog.show()
def add_recent_file_to_gui_config(self, file_path):
def add_recent_file_to_gui_config(self, file_path) -> None:
recent_files = self.app.guiconfig["recentfiles"]
num_files = len(recent_files)
if num_files == 0:

View file

@ -101,6 +101,9 @@ class Menubar(tk.Menu):
menu.add_command(
label="Paste", accelerator="Ctrl+V", command=self.menuaction.paste
)
menu.add_command(
label="Delete", accelerator="Ctrl+D", command=self.menuaction.delete
)
menu.add_separator()
menu.add_command(label="Select all", accelerator="Ctrl+A", state=tk.DISABLED)
menu.add_command(
@ -113,6 +116,7 @@ class Menubar(tk.Menu):
self.app.master.bind_all("<Control-c>", self.menuaction.copy)
self.app.master.bind_all("<Control-v>", self.menuaction.paste)
self.app.master.bind_all("<Control-d>", self.menuaction.delete)
self.edit_menu = menu
def draw_canvas_menu(self):

View file

@ -21,7 +21,7 @@ class GeoLocation:
"""
name = "location"
config_type = RegisterTlvs.UTILITY.value
config_type = RegisterTlvs.UTILITY
def __init__(self) -> None:
"""

View file

@ -36,7 +36,7 @@ class MobilityManager(ModelManager):
"""
name = "MobilityManager"
config_type = RegisterTlvs.WIRELESS.value
config_type = RegisterTlvs.WIRELESS
def __init__(self, session: "Session") -> None:
"""
@ -121,10 +121,7 @@ class MobilityManager(ModelManager):
logging.warning("Ignoring event for unknown model '%s'", model)
continue
if cls.config_type in [
RegisterTlvs.WIRELESS.value,
RegisterTlvs.MOBILITY.value,
]:
if cls.config_type in [RegisterTlvs.WIRELESS, RegisterTlvs.MOBILITY]:
model = node.mobility
else:
continue
@ -142,17 +139,11 @@ class MobilityManager(ModelManager):
)
continue
if (
event_type == EventTypes.STOP.value
or event_type == EventTypes.RESTART.value
):
if event_type in [EventTypes.STOP, EventTypes.RESTART]:
model.stop(move_initial=True)
if (
event_type == EventTypes.START.value
or event_type == EventTypes.RESTART.value
):
if event_type in [EventTypes.START, EventTypes.RESTART]:
model.start()
if event_type == EventTypes.PAUSE.value:
if event_type == EventTypes.PAUSE:
model.pause()
def sendevent(self, model: "WayPointMobility") -> None:
@ -163,13 +154,13 @@ class MobilityManager(ModelManager):
:param model: mobility model to send event for
:return: nothing
"""
event_type = EventTypes.NONE.value
event_type = EventTypes.NONE
if model.state == model.STATE_STOPPED:
event_type = EventTypes.STOP.value
event_type = EventTypes.STOP
elif model.state == model.STATE_RUNNING:
event_type = EventTypes.START.value
event_type = EventTypes.START
elif model.state == model.STATE_PAUSED:
event_type = EventTypes.PAUSE.value
event_type = EventTypes.PAUSE
start_time = int(model.lasttime - model.timezero)
end_time = int(model.endtime)
@ -212,7 +203,7 @@ class WirelessModel(ConfigurableOptions):
Used for managing arbitrary configuration parameters.
"""
config_type = RegisterTlvs.WIRELESS.value
config_type = RegisterTlvs.WIRELESS
bitmap = None
position_callback = None
@ -226,7 +217,7 @@ class WirelessModel(ConfigurableOptions):
self.session = session
self.id = _id
def all_link_data(self, flags: int) -> List:
def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List:
"""
May be used if the model can populate the GUI with wireless (green)
link lines.
@ -311,38 +302,30 @@ class BasicRangeModel(WirelessModel):
self.wlan = session.get_node(_id)
self._netifs = {}
self._netifslock = threading.Lock()
self.range = 0
self.bw = None
self.delay = None
self.loss = None
self.jitter = None
def values_from_config(self, config: Dict[str, str]) -> None:
def _get_config(self, current_value: int, config: Dict[str, str], name: str) -> int:
"""
Values to convert to link parameters.
Convenience for updating value to use from a provided configuration.
:param config: values to convert
:return: nothing
:param current_value: current config value to use when one is not provided
:param config: config to get values from
:param name: name of config value to get
:return: current config value when not provided, new value otherwise
"""
self.range = int(float(config["range"]))
logging.debug(
"basic range model configured for WLAN %d using range %d",
self.wlan.id,
self.range,
)
self.bw = int(config["bandwidth"])
if self.bw == 0:
self.bw = None
self.delay = int(config["delay"])
if self.delay == 0:
self.delay = None
self.loss = int(float(config["error"]))
if self.loss == 0:
self.loss = None
self.jitter = int(config["jitter"])
if self.jitter == 0:
self.jitter = None
value = config.get(name)
if value is not None:
if value == "":
value = None
else:
value = int(float(value))
else:
value = current_value
return value
def setlinkparams(self) -> None:
"""
@ -370,20 +353,16 @@ class BasicRangeModel(WirelessModel):
with self._netifslock:
return self._netifs[netif]
def set_position(
self, netif: CoreInterface, x: float = None, y: float = None, z: float = None
) -> None:
def set_position(self, netif: CoreInterface) -> None:
"""
A node has moved; given an interface, a new (x,y,z) position has
been set; calculate the new distance between other nodes and link or
unlink node pairs based on the configured range.
:param netif: network interface to set position for
:param x: x position
:param y: y position
:param z: z position
:return: nothing
"""
x, y, z = netif.node.position.get()
self._netifslock.acquire()
self._netifs[netif] = (x, y, z)
if x is None or y is None:
@ -485,12 +464,21 @@ class BasicRangeModel(WirelessModel):
:param config: values to update configuration
:return: nothing
"""
self.values_from_config(config)
self.range = self._get_config(self.range, config, "range")
if self.range is None:
self.range = 0
logging.debug("wlan %s set range to %s", self.wlan.name, self.range)
self.bw = self._get_config(self.bw, config, "bandwidth")
self.delay = self._get_config(self.delay, config, "delay")
self.loss = self._get_config(self.loss, config, "error")
self.jitter = self._get_config(self.jitter, config, "jitter")
self.setlinkparams()
return True
def create_link_data(
self, interface1: CoreInterface, interface2: CoreInterface, message_type: int
self,
interface1: CoreInterface,
interface2: CoreInterface,
message_type: MessageFlags,
) -> LinkData:
"""
Create a wireless link/unlink data message.
@ -505,7 +493,7 @@ class BasicRangeModel(WirelessModel):
node1_id=interface1.node.id,
node2_id=interface2.node.id,
network_id=self.wlan.id,
link_type=LinkTypes.WIRELESS.value,
link_type=LinkTypes.WIRELESS,
)
def sendlinkmsg(
@ -520,14 +508,14 @@ class BasicRangeModel(WirelessModel):
:return: nothing
"""
if unlink:
message_type = MessageFlags.DELETE.value
message_type = MessageFlags.DELETE
else:
message_type = MessageFlags.ADD.value
message_type = MessageFlags.ADD
link_data = self.create_link_data(netif, netif2, message_type)
self.session.broadcast_link(link_data)
def all_link_data(self, flags: int) -> List[LinkData]:
def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
"""
Return a list of wireless link messages for when the GUI reconnects.
@ -582,7 +570,7 @@ class WayPointMobility(WirelessModel):
"""
name = "waypoint"
config_type = RegisterTlvs.MOBILITY.value
config_type = RegisterTlvs.MOBILITY
STATE_STOPPED = 0
STATE_RUNNING = 1
@ -800,7 +788,7 @@ class WayPointMobility(WirelessModel):
"""
self.queue_copy = list(self.queue)
def loopwaypoints(self) -> None:
def loopwaypoints(self) -> bool:
"""
Restore backup copy of waypoints when looping.
@ -822,8 +810,7 @@ class WayPointMobility(WirelessModel):
:return: nothing
"""
node.position.set(x, y, z)
node_data = node.data(message_type=0)
self.session.broadcast_node(node_data)
self.session.broadcast_node(node)
def setendtime(self) -> None:
"""

View file

@ -14,7 +14,7 @@ from core import utils
from core.configservice.dependencies import ConfigServiceDependencies
from core.constants import MOUNT_BIN, VNODED_BIN
from core.emulator.data import LinkData, NodeData
from core.emulator.enumerations import LinkTypes, NodeTypes
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
from core.errors import CoreCommandError, CoreError
from core.nodes import client
from core.nodes.interface import CoreInterface, TunTap, Veth
@ -192,20 +192,12 @@ class NodeBase:
return ifindex
def data(
self,
message_type: int,
lat: float = None,
lon: float = None,
alt: float = None,
source: str = None,
) -> NodeData:
self, message_type: MessageFlags = MessageFlags.NONE, source: str = None
) -> Optional[NodeData]:
"""
Build a data object for this node.
:param message_type: purpose for the data object we are creating
:param lat: latitude
:param lon: longitude
:param alt: altitude
:param source: source of node data
:return: node data object
"""
@ -217,12 +209,10 @@ class NodeBase:
server = None
if self.server is not None:
server = self.server.name
services = self.services
if services is not None:
services = "|".join([service.name for service in services])
node_data = NodeData(
services = None
if self.services is not None:
services = [service.name for service in self.services]
return NodeData(
message_type=message_type,
id=self.id,
node_type=self.apitype,
@ -233,18 +223,16 @@ class NodeBase:
opaque=self.opaque,
x_position=x,
y_position=y,
latitude=lat,
longitude=lon,
altitude=alt,
latitude=self.position.lat,
longitude=self.position.lon,
altitude=self.position.alt,
model=model,
server=server,
services=services,
source=source,
)
return node_data
def all_link_data(self, flags: int) -> List:
def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
"""
Build CORE Link data for this object. There is no default
method for PyCoreObjs as PyCoreNodes do not implement this but
@ -422,7 +410,7 @@ class CoreNodeBase(NodeBase):
changed = super().setposition(x, y, z)
if changed:
for netif in self.netifs(sort=True):
netif.setposition(x, y, z)
netif.setposition()
def commonnets(
self, obj: "CoreNodeBase", want_ctrl: bool = False
@ -472,7 +460,7 @@ class CoreNode(CoreNodeBase):
Provides standard core node logic.
"""
apitype = NodeTypes.DEFAULT.value
apitype = NodeTypes.DEFAULT
valid_address_types = {"inet", "inet6", "inet6link"}
def __init__(
@ -982,7 +970,7 @@ class CoreNetworkBase(NodeBase):
Base class for networks
"""
linktype = LinkTypes.WIRED.value
linktype = LinkTypes.WIRED
is_emane = False
def __init__(
@ -1069,7 +1057,7 @@ class CoreNetworkBase(NodeBase):
with self._linked_lock:
del self._linked[netif]
def all_link_data(self, flags: int) -> List[LinkData]:
def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
"""
Build link data objects for this network. Each link object describes a link
between this network and a node.
@ -1143,7 +1131,7 @@ class CoreNetworkBase(NodeBase):
netif.swapparams("_params_up")
link_data = LinkData(
message_type=0,
message_type=MessageFlags.NONE,
node1_id=linked_node.id,
node2_id=self.id,
link_type=self.linktype,
@ -1173,11 +1161,13 @@ class Position:
:param x: x position
:param y: y position
:param z: z position
:return:
"""
self.x = x
self.y = y
self.z = z
self.lon = None
self.lat = None
self.alt = None
def set(self, x: float = None, y: float = None, z: float = None) -> bool:
"""
@ -1202,3 +1192,24 @@ class Position:
:return: x,y,z position tuple
"""
return self.x, self.y, self.z
def set_geo(self, lon: float, lat: float, alt: float) -> None:
"""
Set geo position lon, lat, alt.
:param lon: longitude value
:param lat: latitude value
:param alt: altitude value
:return: nothing
"""
self.lon = lon
self.lat = lat
self.alt = alt
def get_geo(self) -> Tuple[float, float, float]:
"""
Retrieve current geo position lon, lat, alt.
:return: lon, lat, alt position tuple
"""
return self.lon, self.lat, self.alt

View file

@ -72,7 +72,7 @@ class DockerClient:
class DockerNode(CoreNode):
apitype = NodeTypes.DOCKER.value
apitype = NodeTypes.DOCKER
def __init__(
self,

View file

@ -7,6 +7,7 @@ import time
from typing import TYPE_CHECKING, Callable, Dict, List, Tuple
from core import utils
from core.emulator.enumerations import MessageFlags
from core.errors import CoreCommandError
from core.nodes.netclient import get_net_client
@ -50,7 +51,7 @@ class CoreInterface:
self.addrlist = []
self.hwaddr = None
# placeholder position hook
self.poshook = lambda a, b, c, d: None
self.poshook = lambda x: None
# used with EMANE
self.transport_type = None
# node interface index
@ -209,16 +210,14 @@ class CoreInterface:
self._params = getattr(self, name)
setattr(self, name, tmp)
def setposition(self, x: float, y: float, z: float) -> None:
def setposition(self) -> None:
"""
Dispatch position hook handler.
Dispatch position hook handler when possible.
:param x: x position
:param y: y position
:param z: z position
:return: nothing
"""
self.poshook(self, x, y, z)
if self.poshook and self.node:
self.poshook(self)
def __lt__(self, other: "CoreInterface") -> bool:
"""
@ -554,7 +553,7 @@ class GreTap(CoreInterface):
"""
return None
def all_link_data(self, flags: int) -> List:
def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List:
"""
Retrieve link data.

View file

@ -66,7 +66,7 @@ class LxdClient:
class LxcNode(CoreNode):
apitype = NodeTypes.LXC.value
apitype = NodeTypes.LXC
def __init__(
self,

View file

@ -4,7 +4,9 @@ Clients for dealing with bridge/interface commands.
import json
from typing import Callable
from core.constants import ETHTOOL_BIN, IP_BIN, OVS_BIN, TC_BIN
import netaddr
from core.constants import ETHTOOL_BIN, IP_BIN, OVS_BIN, SYSCTL_BIN, TC_BIN
class LinuxNetClient:
@ -168,6 +170,10 @@ class LinuxNetClient:
)
else:
self.run(f"{IP_BIN} address add {address} dev {device}")
if netaddr.valid_ipv6(address.split("/")[0]):
# IPv6 addresses are removed by default on interface down.
# Make sure that the IPv6 address we add is not removed
self.run(f"{SYSCTL_BIN} -w net.ipv6.conf.{device}.keep_addr_on_down=1")
def delete_address(self, device: str, address: str) -> None:
"""

View file

@ -12,7 +12,7 @@ import netaddr
from core import utils
from core.constants import EBTABLES_BIN, TC_BIN
from core.emulator.data import LinkData, NodeData
from core.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes, RegisterTlvs
from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNetworkBase
from core.nodes.interface import CoreInterface, GreTap, Veth
@ -848,7 +848,7 @@ class CtrlNet(CoreNetwork):
super().shutdown()
def all_link_data(self, flags: int) -> List[LinkData]:
def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
"""
Do not include CtrlNet in link messages describing this session.
@ -879,27 +879,19 @@ class PtpNet(CoreNetwork):
super().attach(netif)
def data(
self,
message_type: int,
lat: float = None,
lon: float = None,
alt: float = None,
source: str = None,
) -> NodeData:
self, message_type: MessageFlags = MessageFlags.NONE, source: str = None
) -> Optional[NodeData]:
"""
Do not generate a Node Message for point-to-point links. They are
built using a link message instead.
:param message_type: purpose for the data object we are creating
:param lat: latitude
:param lon: longitude
:param alt: altitude
:param source: source of node data
:return: node data object
"""
return None
def all_link_data(self, flags: int) -> List[LinkData]:
def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
"""
Build CORE API TLVs for a point-to-point link. One Link message
describes this network.
@ -999,7 +991,7 @@ class SwitchNode(CoreNetwork):
Provides switch functionality within a core node.
"""
apitype = NodeTypes.SWITCH.value
apitype = NodeTypes.SWITCH
policy = "ACCEPT"
type = "lanswitch"
@ -1010,7 +1002,7 @@ class HubNode(CoreNetwork):
ports by turning off MAC address learning.
"""
apitype = NodeTypes.HUB.value
apitype = NodeTypes.HUB
policy = "ACCEPT"
type = "hub"
@ -1029,8 +1021,8 @@ class WlanNode(CoreNetwork):
Provides wireless lan functionality within a core node.
"""
apitype = NodeTypes.WIRELESS_LAN.value
linktype = LinkTypes.WIRED.value
apitype = NodeTypes.WIRELESS_LAN
linktype = LinkTypes.WIRED
policy = "DROP"
type = "wlan"
@ -1079,11 +1071,7 @@ class WlanNode(CoreNetwork):
super().attach(netif)
if self.model:
netif.poshook = self.model.position_callback
if netif.node is None:
return
x, y, z = netif.node.position.get()
# invokes any netif.poshook
netif.setposition(x, y, z)
netif.setposition()
def setmodel(self, model: "WirelessModelType", config: Dict[str, str]):
"""
@ -1094,15 +1082,13 @@ class WlanNode(CoreNetwork):
:return: nothing
"""
logging.debug("node(%s) setting model: %s", self.name, model.name)
if model.config_type == RegisterTlvs.WIRELESS.value:
if model.config_type == RegisterTlvs.WIRELESS:
self.model = model(session=self.session, _id=self.id)
for netif in self.netifs():
netif.poshook = self.model.position_callback
if netif.poshook and netif.node:
x, y, z = netif.node.position.get()
netif.poshook(netif, x, y, z)
netif.setposition()
self.updatemodel(config)
elif model.config_type == RegisterTlvs.MOBILITY.value:
elif model.config_type == RegisterTlvs.MOBILITY:
self.mobility = model(session=self.session, _id=self.id)
self.mobility.update_config(config)
@ -1119,11 +1105,9 @@ class WlanNode(CoreNetwork):
)
self.model.update_config(config)
for netif in self.netifs():
if netif.poshook and netif.node:
x, y, z = netif.node.position.get()
netif.poshook(netif, x, y, z)
netif.setposition()
def all_link_data(self, flags: int) -> List[LinkData]:
def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
"""
Retrieve all link data.
@ -1141,6 +1125,6 @@ class TunnelNode(GreTapBridge):
Provides tunnel functionality in a core node.
"""
apitype = NodeTypes.TUNNEL.value
apitype = NodeTypes.TUNNEL
policy = "ACCEPT"
type = "tunnel"

View file

@ -264,7 +264,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
network.
"""
apitype = NodeTypes.RJ45.value
apitype = NodeTypes.RJ45
type = "rj45"
def __init__(
@ -517,7 +517,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
if self.old_up:
self.net_client.device_up(self.localname)
def setposition(self, x: float = None, y: float = None, z: float = None) -> bool:
def setposition(self, x: float = None, y: float = None, z: float = None) -> None:
"""
Uses setposition from both parent classes.
@ -526,9 +526,8 @@ class Rj45Node(CoreNodeBase, CoreInterface):
:param z: z position
:return: True if position changed, False otherwise
"""
result = CoreNodeBase.setposition(self, x, y, z)
CoreInterface.setposition(self, x, y, z)
return result
CoreNodeBase.setposition(self, x, y, z)
CoreInterface.setposition(self)
def termcmdstring(self, sh: str) -> str:
"""

View file

@ -5,14 +5,14 @@ sdt.py: Scripted Display Tool (SDT3D) helper
import logging
import socket
import threading
from typing import TYPE_CHECKING, Optional, Tuple
from typing import TYPE_CHECKING, Optional
from urllib.parse import urlparse
from core import constants
from core.constants import CORE_DATA_DIR
from core.emane.nodes import EmaneNet
from core.emulator.data import LinkData, NodeData
from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags
from core.emulator.enumerations import EventTypes, MessageFlags
from core.errors import CoreError
from core.nodes.base import CoreNetworkBase, NodeBase
from core.nodes.network import WlanNode
@ -21,11 +21,19 @@ if TYPE_CHECKING:
from core.emulator.session import Session
def link_data_params(link_data: LinkData) -> Tuple[int, int, bool]:
node_one = link_data.node1_id
node_two = link_data.node2_id
is_wireless = link_data.link_type == LinkTypes.WIRELESS.value
return node_one, node_two, is_wireless
def get_link_id(node_one: int, node_two: int, network_id: int) -> str:
link_id = f"{node_one}-{node_two}"
if network_id is not None:
link_id = f"{link_id}-{network_id}"
return link_id
CORE_LAYER = "CORE"
NODE_LAYER = "CORE::Nodes"
LINK_LAYER = "CORE::Links"
CORE_LAYERS = [CORE_LAYER, LINK_LAYER, NODE_LAYER]
DEFAULT_LINK_COLOR = "red"
LINK_COLORS = ["green", "blue", "orange", "purple", "white"]
class Sdt:
@ -62,10 +70,11 @@ class Sdt:
self.lock = threading.Lock()
self.sock = None
self.connected = False
self.showerror = True
self.url = self.DEFAULT_SDT_URL
self.address = None
self.protocol = None
self.colors = {}
self.network_layers = set()
self.session.node_handlers.append(self.handle_node_update)
self.session.link_handlers.append(self.handle_link_update)
@ -90,7 +99,7 @@ class Sdt:
self.address = (self.url.hostname, self.url.port)
self.protocol = self.url.scheme
def connect(self, flags: int = 0) -> bool:
def connect(self) -> bool:
"""
Connect to the SDT address/port if enabled.
@ -100,7 +109,7 @@ class Sdt:
return False
if self.connected:
return True
if self.session.state == EventTypes.SHUTDOWN_STATE.value:
if self.session.state == EventTypes.SHUTDOWN_STATE:
return False
self.seturl()
@ -122,7 +131,7 @@ class Sdt:
self.connected = True
# refresh all objects in SDT3D when connecting after session start
if not flags & MessageFlags.ADD.value and not self.sendobjs():
if not self.sendobjs():
return False
return True
@ -165,8 +174,13 @@ class Sdt:
:return: nothing
"""
self.cmd("clear all")
for layer in self.network_layers:
self.cmd(f"delete layer,{layer}")
for layer in CORE_LAYERS[::-1]:
self.cmd(f"delete layer,{layer}")
self.disconnect()
self.showerror = True
self.network_layers.clear()
self.colors.clear()
def cmd(self, cmdstr: str) -> bool:
"""
@ -200,6 +214,10 @@ class Sdt:
:return: nothing
"""
nets = []
# create layers
for layer in CORE_LAYERS:
self.cmd(f"layer {layer}")
with self.session._nodes_lock:
for node_id in self.session.nodes:
node = self.session.nodes[node_id]
@ -210,13 +228,12 @@ class Sdt:
self.add_node(node)
for net in nets:
all_links = net.all_link_data(flags=MessageFlags.ADD.value)
all_links = net.all_link_data(flags=MessageFlags.ADD)
for link_data in all_links:
is_wireless = isinstance(net, (WlanNode, EmaneNet))
if is_wireless and link_data.node1_id == net.id:
continue
params = link_data_params(link_data)
self.add_link(*params)
self.handle_link_update(link_data)
def get_node_position(self, node: NodeBase) -> Optional[str]:
"""
@ -253,7 +270,10 @@ class Sdt:
icon = icon.replace("$CORE_DATA_DIR", constants.CORE_DATA_DIR)
icon = icon.replace("$CORE_CONF_DIR", constants.CORE_CONF_DIR)
self.cmd(f"sprite {node_type} image {icon}")
self.cmd(f'node {node.id} type {node_type} label on,"{node.name}" {pos}')
self.cmd(
f'node {node.id} nodeLayer "{NODE_LAYER}" '
f'type {node_type} label on,"{node.name}" {pos}'
)
def edit_node(self, node: NodeBase, lon: float, lat: float, alt: float) -> None:
"""
@ -302,7 +322,7 @@ class Sdt:
return
# delete node
if node_data.message_type == MessageFlags.DELETE.value:
if node_data.message_type == MessageFlags.DELETE:
self.cmd(f"delete node,{node_data.id}")
else:
x = node_data.x_position
@ -333,40 +353,95 @@ class Sdt:
pass
return result
def add_link(self, node_one: int, node_two: int, is_wireless: bool) -> None:
def get_link_line(self, network_id: int) -> str:
"""
Retrieve link line color based on network.
:param network_id: network id of link, None for wired links
:return: link line configuration
"""
network = self.session.nodes.get(network_id)
if network:
color = self.colors.get(network_id)
if not color:
index = len(self.colors) % len(LINK_COLORS)
color = LINK_COLORS[index]
self.colors[network_id] = color
else:
color = DEFAULT_LINK_COLOR
return f"{color},2"
def add_link(
self, node_one: int, node_two: int, network_id: int = None, label: str = None
) -> None:
"""
Handle adding a link in SDT.
:param node_one: node one id
:param node_two: node two id
:param is_wireless: True if link is wireless, False otherwise
:param network_id: network link is associated with, None otherwise
:param label: label for link
:return: nothing
"""
logging.debug("sdt add link: %s, %s, %s", node_one, node_two, is_wireless)
logging.debug("sdt add link: %s, %s, %s", node_one, node_two, network_id)
if not self.connect():
return
if self.wireless_net_check(node_one) or self.wireless_net_check(node_two):
return
if is_wireless:
attr = "green,2"
else:
attr = "red,2"
self.cmd(f"link {node_one},{node_two} line {attr}")
line = self.get_link_line(network_id)
link_id = get_link_id(node_one, node_two, network_id)
layer = LINK_LAYER
if network_id:
node = self.session.nodes.get(network_id)
if node:
network_name = node.name
layer = f"{layer}::{network_name}"
self.network_layers.add(layer)
link_label = ""
if label:
link_label = f'linklabel on,"{label}"'
self.cmd(
f"link {node_one},{node_two},{link_id} linkLayer {layer} line {line} "
f"{link_label}"
)
def delete_link(self, node_one: int, node_two: int) -> None:
def delete_link(self, node_one: int, node_two: int, network_id: int = None) -> None:
"""
Handle deleting a node in SDT.
Handle deleting a link in SDT.
:param node_one: node one id
:param node_two: node two id
:param network_id: network link is associated with, None otherwise
:return: nothing
"""
logging.debug("sdt delete link: %s, %s", node_one, node_two)
logging.debug("sdt delete link: %s, %s, %s", node_one, node_two, network_id)
if not self.connect():
return
if self.wireless_net_check(node_one) or self.wireless_net_check(node_two):
return
self.cmd(f"delete link,{node_one},{node_two}")
link_id = get_link_id(node_one, node_two, network_id)
self.cmd(f"delete link,{node_one},{node_two},{link_id}")
def edit_link(
self, node_one: int, node_two: int, network_id: int, label: str
) -> None:
"""
Handle editing a link in SDT.
:param node_one: node one id
:param node_two: node two id
:param network_id: network link is associated with, None otherwise
:param label: label to update
:return: nothing
"""
logging.debug("sdt edit link: %s, %s, %s", node_one, node_two, network_id)
if not self.connect():
return
if self.wireless_net_check(node_one) or self.wireless_net_check(node_two):
return
link_id = get_link_id(node_one, node_two, network_id)
link_label = f'linklabel on,"{label}"'
self.cmd(f"link {node_one},{node_two},{link_id} {link_label}")
def handle_link_update(self, link_data: LinkData) -> None:
"""
@ -375,9 +450,13 @@ class Sdt:
:param link_data: link data to handle
:return: nothing
"""
if link_data.message_type == MessageFlags.ADD.value:
params = link_data_params(link_data)
self.add_link(*params)
elif link_data.message_type == MessageFlags.DELETE.value:
params = link_data_params(link_data)
self.delete_link(*params[:2])
node_one = link_data.node1_id
node_two = link_data.node2_id
network_id = link_data.network_id
label = link_data.label
if link_data.message_type == MessageFlags.ADD:
self.add_link(node_one, node_two, network_id, label)
elif link_data.message_type == MessageFlags.DELETE:
self.delete_link(node_one, node_two, network_id)
elif link_data.message_type == MessageFlags.NONE and label:
self.edit_link(node_one, node_two, network_id, label)

View file

@ -315,7 +315,7 @@ class CoreServices:
"""
name = "services"
config_type = RegisterTlvs.UTILITY.value
config_type = RegisterTlvs.UTILITY
def __init__(self, session: "Session") -> None:
"""
@ -630,14 +630,16 @@ class CoreServices:
self.session.exception(
ExceptionLevels.ERROR,
"services",
node.id,
f"error stopping service {service.name}: {e.stderr}",
node.id,
)
logging.exception("error running stop command %s", args)
status = -1
return status
def get_service_file(self, node: CoreNode, service_name: str, filename: str) -> str:
def get_service_file(
self, node: CoreNode, service_name: str, filename: str
) -> FileData:
"""
Send a File Message when the GUI has requested a service file.
The file data is either auto-generated or comes from an existing config.
@ -645,7 +647,7 @@ class CoreServices:
:param node: node to get service file from
:param service_name: service to get file from
:param filename: file name to retrieve
:return: file message for node
:return: file data
"""
# get service to get file from
service = self.get_service(node.id, service_name, default_service=True)
@ -672,7 +674,7 @@ class CoreServices:
filetypestr = "service:%s" % service.name
return FileData(
message_type=MessageFlags.ADD.value,
message_type=MessageFlags.ADD,
node=node.id,
name=filename,
type=filetypestr,

View file

@ -211,7 +211,7 @@ bootfrr()
if grep -q "^ip route " $FRR_CONF; then
bootdaemon "staticd"
fi
for r in rip ripng ospf6 ospf bgp babel; do
for r in rip ripng ospf6 ospf bgp babel isis; do
if grep -q "^router \\<${r}\\>" $FRR_CONF; then
bootdaemon "${r}d"
fi
@ -651,3 +651,45 @@ class FRRpimd(FrrService):
@classmethod
def generatefrrifcconfig(cls, node, ifc):
return " ip mfea\n ip igmp\n ip pim\n"
class FRRIsis(FrrService):
"""
The ISIS service provides IPv4 and IPv6 routing for wired networks. It does
not build its own configuration file but has hooks for adding to the
unified frr.conf file.
"""
name = "FRRISIS"
startup = ()
shutdown = ("killall isisd",)
validate = ("pidof isisd",)
ipv4_routing = True
ipv6_routing = True
@staticmethod
def ptpcheck(ifc):
"""
Helper to detect whether interface is connected to a notional
point-to-point link.
"""
if isinstance(ifc.net, PtpNet):
return " isis network point-to-point\n"
return ""
@classmethod
def generatefrrconfig(cls, node):
cfg = "router isis DEFAULT\n"
cfg += " net 47.0001.0000.1900.%04x.00\n" % node.id
cfg += " metric-style wide\n"
cfg += " is-type level-2-only\n"
cfg += "!\n"
return cfg
@classmethod
def generatefrrifcconfig(cls, node, ifc):
cfg = " ip router isis DEFAULT\n"
cfg += " ipv6 router isis DEFAULT\n"
cfg += " isis circuit-type level-2-only\n"
cfg += cls.ptpcheck(ifc)
return cfg

View file

@ -551,7 +551,7 @@ class Babel(QuaggaService):
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
if ifc.net and ifc.net.linktype == LinkTypes.WIRELESS.value:
if ifc.net and ifc.net.linktype == LinkTypes.WIRELESS:
return " babel wireless\n no babel split-horizon\n"
else:
return " babel wired\n babel split-horizon\n"

View file

@ -84,12 +84,11 @@ class IPsec(CoreService):
cfg += "# set up static tunnel mode security assocation for service "
cfg += "(security.py)\n"
fname = "%s/examples/services/sampleIPsec" % constants.CORE_DATA_DIR
try:
cfg += open(fname, "rb").read()
with open(fname, "r") as f:
cfg += f.read()
except IOError:
logging.exception("Error opening IPsec configuration template (%s)", fname)
return cfg

View file

@ -1,13 +1,13 @@
"""
utility.py: defines miscellaneous utility services.
"""
import os
import netaddr
from core import constants, utils
from core.errors import CoreCommandError
from core.nodes.base import CoreNode
from core.services.coreservices import CoreService, ServiceMode
@ -77,27 +77,26 @@ class DefaultRouteService(UtilService):
@classmethod
def generate_config(cls, node, filename):
# only add default routes for linked routing nodes
routes = []
for other_node in node.session.nodes.values():
if not isinstance(other_node, CoreNode):
continue
if other_node.type not in ["router", "mdr"]:
continue
commonnets = node.commonnets(other_node)
if commonnets:
_, _, router_eth = commonnets[0]
for x in router_eth.addrlist:
addr, prefix = x.split("/")
routes.append(addr)
break
cfg = "#!/bin/sh\n"
cfg += "# auto-generated by DefaultRoute service (utility.py)\n"
for ifc in node.netifs():
if hasattr(ifc, "control") and ifc.control is True:
continue
cfg += "\n".join(map(cls.addrstr, ifc.addrlist))
cfg += "\n"
for route in routes:
cfg += f"ip route add default via {route}\n"
return cfg
@staticmethod
def addrstr(x):
net = netaddr.IPNetwork(x)
if net[1] == net[-2]:
return ""
else:
if os.uname()[0] == "Linux":
rtcmd = "ip route add default via"
else:
raise Exception("unknown platform")
return "%s %s" % (rtcmd, net[1])
class DefaultMulticastRouteService(UtilService):
name = "DefaultMulticastRoute"

View file

@ -8,7 +8,7 @@ import core.nodes.physical
from core.emane.nodes import EmaneNet
from core.emulator.data import LinkData
from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions
from core.emulator.enumerations import NodeTypes
from core.emulator.enumerations import EventTypes, NodeTypes
from core.nodes.base import CoreNetworkBase, CoreNodeBase, NodeBase
from core.nodes.docker import DockerNode
from core.nodes.lxd import LxcNode
@ -16,7 +16,6 @@ from core.nodes.network import CtrlNet
from core.services.coreservices import CoreService
if TYPE_CHECKING:
from core.emane.emanemanager import EmaneGlobalModel
from core.emane.emanemodel import EmaneModel
from core.emulator.session import Session
@ -69,23 +68,17 @@ def create_interface_data(interface_element: etree.Element) -> InterfaceData:
return InterfaceData(interface_id, name, mac, ip4, ip4_mask, ip6, ip6_mask)
def create_emane_config(
node_id: int, emane_config: "EmaneGlobalModel", config: Dict[str, str]
) -> etree.Element:
emane_configuration = etree.Element("emane_configuration")
add_attribute(emane_configuration, "node", node_id)
add_attribute(emane_configuration, "model", "emane")
def create_emane_config(session: "Session") -> etree.Element:
emane_configuration = etree.Element("emane_global_configuration")
config = session.emane.get_configs()
emulator_element = etree.SubElement(emane_configuration, "emulator")
for emulator_config in emane_config.emulator_config:
for emulator_config in session.emane.emane_config.emulator_config:
value = config[emulator_config.id]
add_configuration(emulator_element, emulator_config.id, value)
nem_element = etree.SubElement(emane_configuration, "nem")
for nem_config in emane_config.nem_config:
value = config[nem_config.id]
add_configuration(nem_element, nem_config.id, value)
core_element = etree.SubElement(emane_configuration, "core")
for core_config in session.emane.emane_config.core_config:
value = config[core_config.id]
add_configuration(core_element, core_config.id, value)
return emane_configuration
@ -260,7 +253,7 @@ class NetworkElement(NodeElement):
def add_type(self) -> None:
if self.node.apitype:
node_type = NodeTypes(self.node.apitype).name
node_type = self.node.apitype.name
else:
node_type = self.node.__class__.__name__
add_attribute(self.element, "type", node_type)
@ -324,7 +317,7 @@ class CoreXmlWriter:
for file_name, data in self.session._hooks[state]:
hook = etree.SubElement(hooks, "hook")
add_attribute(hook, "name", file_name)
add_attribute(hook, "state", state)
add_attribute(hook, "state", state.value)
hook.text = data
if hooks.getchildren():
@ -360,6 +353,9 @@ class CoreXmlWriter:
self.scenario.append(metadata_elements)
def write_emane_configs(self) -> None:
emane_global_configuration = create_emane_config(self.session)
self.scenario.append(emane_global_configuration)
emane_configurations = etree.Element("emane_configurations")
for node_id in self.session.emane.nodes():
all_configs = self.session.emane.get_all_configs(node_id)
@ -371,17 +367,9 @@ class CoreXmlWriter:
logging.debug(
"writing emane config node(%s) model(%s)", node_id, model_name
)
if model_name == -1:
emane_configuration = create_emane_config(
node_id, self.session.emane.emane_config, config
)
else:
model = self.session.emane.models[model_name]
emane_configuration = create_emane_model_config(
node_id, model, config
)
model = self.session.emane.models[model_name]
emane_configuration = create_emane_model_config(node_id, model, config)
emane_configurations.append(emane_configuration)
if emane_configurations.getchildren():
self.scenario.append(emane_configurations)
@ -476,7 +464,7 @@ class CoreXmlWriter:
self.write_device(node)
# add known links
links.extend(node.all_link_data(0))
links.extend(node.all_link_data())
return links
@ -613,6 +601,7 @@ class CoreXmlReader:
self.read_session_origin()
self.read_service_configs()
self.read_mobility_configs()
self.read_emane_global_config()
self.read_emane_configs()
self.read_nodes()
self.read_configservice_configs()
@ -666,13 +655,11 @@ class CoreXmlReader:
for hook in session_hooks.iterchildren():
name = hook.get("name")
state = hook.get("state")
state = get_int(hook, "state")
state = EventTypes(state)
data = hook.text
hook_type = f"hook:{state}"
logging.info("reading hook: state(%s) name(%s)", state, name)
self.session.set_hook(
hook_type, file_name=name, source_name=None, data=data
)
self.session.add_hook(state, name, None, data)
def read_session_origin(self) -> None:
session_origin = self.scenario.find("session_origin")
@ -742,6 +729,23 @@ class CoreXmlReader:
files.add(name)
service.configs = tuple(files)
def read_emane_global_config(self) -> None:
emane_global_configuration = self.scenario.find("emane_global_configuration")
if emane_global_configuration is None:
return
emulator_configuration = emane_global_configuration.find("emulator")
configs = {}
for config in emulator_configuration.iterchildren():
name = config.get("name")
value = config.get("value")
configs[name] = value
core_configuration = emane_global_configuration.find("core")
for config in core_configuration.iterchildren():
name = config.get("name")
value = config.get("value")
configs[name] = value
self.session.emane.set_configs(config=configs)
def read_emane_configs(self) -> None:
emane_configurations = self.scenario.find("emane_configurations")
if emane_configurations is None:

View file

@ -10,3 +10,7 @@ message ConfigOption {
repeated string select = 5;
string group = 6;
}
message MappedConfig {
map<string, common.ConfigOption> config = 1;
}

View file

@ -2,11 +2,12 @@ syntax = "proto3";
package core;
option java_package = "com.core.client.grpc";
option java_outer_classname = "CoreProto";
import "core/api/grpc/configservices.proto";
import "core/api/grpc/common.proto";
import "core/api/grpc/emane.proto";
import "core/api/grpc/mobility.proto";
import "core/api/grpc/services.proto";
import "core/api/grpc/wlan.proto";
service CoreApi {
// session rpc
@ -78,33 +79,33 @@ service CoreApi {
}
// mobility rpc
rpc GetMobilityConfigs (GetMobilityConfigsRequest) returns (GetMobilityConfigsResponse) {
rpc GetMobilityConfigs (mobility.GetMobilityConfigsRequest) returns (mobility.GetMobilityConfigsResponse) {
}
rpc GetMobilityConfig (GetMobilityConfigRequest) returns (GetMobilityConfigResponse) {
rpc GetMobilityConfig (mobility.GetMobilityConfigRequest) returns (mobility.GetMobilityConfigResponse) {
}
rpc SetMobilityConfig (SetMobilityConfigRequest) returns (SetMobilityConfigResponse) {
rpc SetMobilityConfig (mobility.SetMobilityConfigRequest) returns (mobility.SetMobilityConfigResponse) {
}
rpc MobilityAction (MobilityActionRequest) returns (MobilityActionResponse) {
rpc MobilityAction (mobility.MobilityActionRequest) returns (mobility.MobilityActionResponse) {
}
// service rpc
rpc GetServices (GetServicesRequest) returns (GetServicesResponse) {
rpc GetServices (services.GetServicesRequest) returns (services.GetServicesResponse) {
}
rpc GetServiceDefaults (GetServiceDefaultsRequest) returns (GetServiceDefaultsResponse) {
rpc GetServiceDefaults (services.GetServiceDefaultsRequest) returns (services.GetServiceDefaultsResponse) {
}
rpc SetServiceDefaults (SetServiceDefaultsRequest) returns (SetServiceDefaultsResponse) {
rpc SetServiceDefaults (services.SetServiceDefaultsRequest) returns (services.SetServiceDefaultsResponse) {
}
rpc GetNodeServiceConfigs (GetNodeServiceConfigsRequest) returns (GetNodeServiceConfigsResponse) {
rpc GetNodeServiceConfigs (services.GetNodeServiceConfigsRequest) returns (services.GetNodeServiceConfigsResponse) {
}
rpc GetNodeService (GetNodeServiceRequest) returns (GetNodeServiceResponse) {
rpc GetNodeService (services.GetNodeServiceRequest) returns (services.GetNodeServiceResponse) {
}
rpc GetNodeServiceFile (GetNodeServiceFileRequest) returns (GetNodeServiceFileResponse) {
rpc GetNodeServiceFile (services.GetNodeServiceFileRequest) returns (services.GetNodeServiceFileResponse) {
}
rpc SetNodeService (SetNodeServiceRequest) returns (SetNodeServiceResponse) {
rpc SetNodeService (services.SetNodeServiceRequest) returns (services.SetNodeServiceResponse) {
}
rpc SetNodeServiceFile (SetNodeServiceFileRequest) returns (SetNodeServiceFileResponse) {
rpc SetNodeServiceFile (services.SetNodeServiceFileRequest) returns (services.SetNodeServiceFileResponse) {
}
rpc ServiceAction (ServiceActionRequest) returns (ServiceActionResponse) {
rpc ServiceAction (services.ServiceActionRequest) returns (services.ServiceActionResponse) {
}
// config services
@ -122,27 +123,27 @@ service CoreApi {
}
// wlan rpc
rpc GetWlanConfigs (GetWlanConfigsRequest) returns (GetWlanConfigsResponse) {
rpc GetWlanConfigs (wlan.GetWlanConfigsRequest) returns (wlan.GetWlanConfigsResponse) {
}
rpc GetWlanConfig (GetWlanConfigRequest) returns (GetWlanConfigResponse) {
rpc GetWlanConfig (wlan.GetWlanConfigRequest) returns (wlan.GetWlanConfigResponse) {
}
rpc SetWlanConfig (SetWlanConfigRequest) returns (SetWlanConfigResponse) {
rpc SetWlanConfig (wlan.SetWlanConfigRequest) returns (wlan.SetWlanConfigResponse) {
}
// emane rpc
rpc GetEmaneConfig (GetEmaneConfigRequest) returns (GetEmaneConfigResponse) {
rpc GetEmaneConfig (emane.GetEmaneConfigRequest) returns (emane.GetEmaneConfigResponse) {
}
rpc SetEmaneConfig (SetEmaneConfigRequest) returns (SetEmaneConfigResponse) {
rpc SetEmaneConfig (emane.SetEmaneConfigRequest) returns (emane.SetEmaneConfigResponse) {
}
rpc GetEmaneModels (GetEmaneModelsRequest) returns (GetEmaneModelsResponse) {
rpc GetEmaneModels (emane.GetEmaneModelsRequest) returns (emane.GetEmaneModelsResponse) {
}
rpc GetEmaneModelConfig (GetEmaneModelConfigRequest) returns (GetEmaneModelConfigResponse) {
rpc GetEmaneModelConfig (emane.GetEmaneModelConfigRequest) returns (emane.GetEmaneModelConfigResponse) {
}
rpc SetEmaneModelConfig (SetEmaneModelConfigRequest) returns (SetEmaneModelConfigResponse) {
rpc SetEmaneModelConfig (emane.SetEmaneModelConfigRequest) returns (emane.SetEmaneModelConfigResponse) {
}
rpc GetEmaneModelConfigs (GetEmaneModelConfigsRequest) returns (GetEmaneModelConfigsResponse) {
rpc GetEmaneModelConfigs (emane.GetEmaneModelConfigsRequest) returns (emane.GetEmaneModelConfigsResponse) {
}
rpc GetEmaneEventChannel (GetEmaneEventChannelRequest) returns (GetEmaneEventChannelResponse) {
rpc GetEmaneEventChannel (emane.GetEmaneEventChannelRequest) returns (emane.GetEmaneEventChannelResponse) {
}
// xml rpc
@ -154,7 +155,7 @@ service CoreApi {
// utilities
rpc GetInterfaces (GetInterfacesRequest) returns (GetInterfacesResponse) {
}
rpc EmaneLink (EmaneLinkRequest) returns (EmaneLinkResponse) {
rpc EmaneLink (emane.EmaneLinkRequest) returns (emane.EmaneLinkResponse) {
}
rpc ExecuteScript (ExecuteScriptRequest) returns (ExecuteScriptResponse) {
}
@ -168,11 +169,11 @@ message StartSessionRequest {
repeated Hook hooks = 4;
SessionLocation location = 5;
map<string, string> emane_config = 6;
repeated WlanConfig wlan_configs = 7;
repeated EmaneModelConfig emane_model_configs = 8;
repeated MobilityConfig mobility_configs = 9;
repeated ServiceConfig service_configs = 10;
repeated ServiceFileConfig service_file_configs = 11;
repeated wlan.WlanConfig wlan_configs = 7;
repeated emane.EmaneModelConfig emane_model_configs = 8;
repeated mobility.MobilityConfig mobility_configs = 9;
repeated services.ServiceConfig service_configs = 10;
repeated services.ServiceFileConfig service_file_configs = 11;
repeated Link asymmetric_links = 12;
repeated configservices.ConfigServiceConfig config_service_configs = 13;
}
@ -515,226 +516,6 @@ message AddHookResponse {
bool result = 1;
}
message GetMobilityConfigsRequest {
int32 session_id = 1;
}
message GetMobilityConfigsResponse {
map<int32, MappedConfig> configs = 1;
}
message GetMobilityConfigRequest {
int32 session_id = 1;
int32 node_id = 2;
}
message GetMobilityConfigResponse {
map<string, common.ConfigOption> config = 1;
}
message SetMobilityConfigRequest {
int32 session_id = 1;
MobilityConfig mobility_config = 2;
}
message SetMobilityConfigResponse {
bool result = 1;
}
message MobilityActionRequest {
int32 session_id = 1;
int32 node_id = 2;
MobilityAction.Enum action = 3;
}
message MobilityActionResponse {
bool result = 1;
}
message GetServicesRequest {
}
message GetServicesResponse {
repeated Service services = 1;
}
message GetServiceDefaultsRequest {
int32 session_id = 1;
}
message GetServiceDefaultsResponse {
repeated ServiceDefaults defaults = 1;
}
message SetServiceDefaultsRequest {
int32 session_id = 1;
repeated ServiceDefaults defaults = 2;
}
message SetServiceDefaultsResponse {
bool result = 1;
}
message GetNodeServiceConfigsRequest {
int32 session_id = 1;
}
message GetNodeServiceConfigsResponse {
message ServiceConfig {
int32 node_id = 1;
string service = 2;
NodeServiceData data = 3;
map<string, string> files = 4;
}
repeated ServiceConfig configs = 1;
}
message GetNodeServiceRequest {
int32 session_id = 1;
int32 node_id = 2;
string service = 3;
}
message GetNodeServiceResponse {
NodeServiceData service = 1;
}
message GetNodeServiceFileRequest {
int32 session_id = 1;
int32 node_id = 2;
string service = 3;
string file = 4;
}
message GetNodeServiceFileResponse {
string data = 1;
}
message SetNodeServiceRequest {
int32 session_id = 1;
ServiceConfig config = 2;
}
message SetNodeServiceResponse {
bool result = 1;
}
message SetNodeServiceFileRequest {
int32 session_id = 1;
ServiceFileConfig config = 2;
}
message SetNodeServiceFileResponse {
bool result = 1;
}
message ServiceActionRequest {
int32 session_id = 1;
int32 node_id = 2;
string service = 3;
ServiceAction.Enum action = 4;
}
message ServiceActionResponse {
bool result = 1;
}
message GetWlanConfigsRequest {
int32 session_id = 1;
}
message GetWlanConfigsResponse {
map<int32, MappedConfig> configs = 1;
}
message GetWlanConfigRequest {
int32 session_id = 1;
int32 node_id = 2;
}
message GetWlanConfigResponse {
map<string, common.ConfigOption> config = 1;
}
message SetWlanConfigRequest {
int32 session_id = 1;
WlanConfig wlan_config = 2;
}
message SetWlanConfigResponse {
bool result = 1;
}
message GetEmaneConfigRequest {
int32 session_id = 1;
}
message GetEmaneConfigResponse {
map<string, common.ConfigOption> config = 1;
}
message SetEmaneConfigRequest {
int32 session_id = 1;
map<string, string> config = 2;
}
message SetEmaneConfigResponse {
bool result = 1;
}
message GetEmaneModelsRequest {
int32 session_id = 1;
}
message GetEmaneModelsResponse {
repeated string models = 1;
}
message GetEmaneModelConfigRequest {
int32 session_id = 1;
int32 node_id = 2;
int32 interface = 3;
string model = 4;
}
message GetEmaneModelConfigResponse {
map<string, common.ConfigOption> config = 1;
}
message SetEmaneModelConfigRequest {
int32 session_id = 1;
EmaneModelConfig emane_model_config = 2;
}
message SetEmaneModelConfigResponse {
bool result = 1;
}
message GetEmaneModelConfigsRequest {
int32 session_id = 1;
}
message GetEmaneModelConfigsResponse {
message ModelConfig {
int32 node_id = 1;
string model = 2;
int32 interface = 3;
map<string, common.ConfigOption> config = 4;
}
repeated ModelConfig configs = 1;
}
message GetEmaneEventChannelRequest {
int32 session_id = 1;
}
message GetEmaneEventChannelResponse {
string group = 1;
int32 port = 2;
string device = 3;
}
message SaveXmlRequest {
int32 session_id = 1;
}
@ -761,17 +542,6 @@ message GetInterfacesResponse {
repeated string interfaces = 1;
}
message EmaneLinkRequest {
int32 session_id = 1;
int32 nem_one = 2;
int32 nem_two = 3;
bool linked = 4;
}
message EmaneLinkResponse {
bool result = 1;
}
message ExecuteScriptRequest {
string script = 1;
}
@ -781,40 +551,6 @@ message ExecuteScriptResponse {
}
// data structures for messages below
message WlanConfig {
int32 node_id = 1;
map<string, string> config = 2;
}
message MobilityConfig {
int32 node_id = 1;
map<string, string> config = 2;
}
message EmaneModelConfig {
int32 node_id = 1;
int32 interface_id = 2;
string model = 3;
map<string, string> config = 4;
}
message ServiceConfig {
int32 node_id = 1;
string service = 2;
repeated string startup = 3;
repeated string validate = 4;
repeated string shutdown = 5;
repeated string files = 6;
repeated string directories = 7;
}
message ServiceFileConfig {
int32 node_id = 1;
string service = 2;
string file = 3;
string data = 4;
}
message EventType {
enum Enum {
SESSION = 0;
@ -893,31 +629,6 @@ message ConfigOptionType {
}
}
message ServiceValidationMode {
enum Enum {
BLOCKING = 0;
NON_BLOCKING = 1;
TIMER = 2;
}
}
message ServiceAction {
enum Enum {
START = 0;
STOP = 1;
RESTART = 2;
VALIDATE = 3;
}
}
message MobilityAction {
enum Enum {
START = 0;
PAUSE = 1;
STOP = 2;
}
}
message ExceptionLevel {
enum Enum {
DEFAULT = 0;
@ -934,38 +645,12 @@ message Hook {
string data = 3;
}
message ServiceDefaults {
string node_type = 1;
repeated string services = 2;
}
message Service {
string group = 1;
string name = 2;
}
message NodeServiceData {
repeated string executables = 1;
repeated string dependencies = 2;
repeated string dirs = 3;
repeated string configs = 4;
repeated string startup = 5;
repeated string validate = 6;
ServiceValidationMode.Enum validation_mode = 7;
int32 validation_timer = 8;
repeated string shutdown = 9;
string meta = 10;
}
message MappedConfig {
map<string, common.ConfigOption> config = 1;
}
message Session {
int32 id = 1;
SessionState.Enum state = 2;
repeated Node nodes = 3;
repeated Link links = 4;
string dir = 5;
}
message SessionSummary {
@ -973,6 +658,7 @@ message SessionSummary {
SessionState.Enum state = 2;
int32 nodes = 3;
string file = 4;
string dir = 5;
}
message Node {
@ -989,6 +675,8 @@ message Node {
string server = 11;
repeated string config_services = 12;
Geo geo = 13;
string dir = 14;
string channel = 15;
}
message Link {

View file

@ -0,0 +1,92 @@
syntax = "proto3";
package emane;
import "core/api/grpc/common.proto";
message GetEmaneConfigRequest {
int32 session_id = 1;
}
message GetEmaneConfigResponse {
map<string, common.ConfigOption> config = 1;
}
message SetEmaneConfigRequest {
int32 session_id = 1;
map<string, string> config = 2;
}
message SetEmaneConfigResponse {
bool result = 1;
}
message GetEmaneModelsRequest {
int32 session_id = 1;
}
message GetEmaneModelsResponse {
repeated string models = 1;
}
message GetEmaneModelConfigRequest {
int32 session_id = 1;
int32 node_id = 2;
int32 interface = 3;
string model = 4;
}
message GetEmaneModelConfigResponse {
map<string, common.ConfigOption> config = 1;
}
message SetEmaneModelConfigRequest {
int32 session_id = 1;
EmaneModelConfig emane_model_config = 2;
}
message SetEmaneModelConfigResponse {
bool result = 1;
}
message GetEmaneModelConfigsRequest {
int32 session_id = 1;
}
message GetEmaneModelConfigsResponse {
message ModelConfig {
int32 node_id = 1;
string model = 2;
int32 interface = 3;
map<string, common.ConfigOption> config = 4;
}
repeated ModelConfig configs = 1;
}
message GetEmaneEventChannelRequest {
int32 session_id = 1;
}
message GetEmaneEventChannelResponse {
string group = 1;
int32 port = 2;
string device = 3;
}
message EmaneLinkRequest {
int32 session_id = 1;
int32 nem_one = 2;
int32 nem_two = 3;
bool linked = 4;
}
message EmaneLinkResponse {
bool result = 1;
}
message EmaneModelConfig {
int32 node_id = 1;
int32 interface_id = 2;
string model = 3;
map<string, string> config = 4;
}

View file

@ -0,0 +1,54 @@
syntax = "proto3";
package mobility;
import "core/api/grpc/common.proto";
message MobilityAction {
enum Enum {
START = 0;
PAUSE = 1;
STOP = 2;
}
}
message MobilityConfig {
int32 node_id = 1;
map<string, string> config = 2;
}
message GetMobilityConfigsRequest {
int32 session_id = 1;
}
message GetMobilityConfigsResponse {
map<int32, common.MappedConfig> configs = 1;
}
message GetMobilityConfigRequest {
int32 session_id = 1;
int32 node_id = 2;
}
message GetMobilityConfigResponse {
map<string, common.ConfigOption> config = 1;
}
message SetMobilityConfigRequest {
int32 session_id = 1;
MobilityConfig mobility_config = 2;
}
message SetMobilityConfigResponse {
bool result = 1;
}
message MobilityActionRequest {
int32 session_id = 1;
int32 node_id = 2;
MobilityAction.Enum action = 3;
}
message MobilityActionResponse {
bool result = 1;
}

View file

@ -0,0 +1,149 @@
syntax = "proto3";
package services;
message ServiceConfig {
int32 node_id = 1;
string service = 2;
repeated string startup = 3;
repeated string validate = 4;
repeated string shutdown = 5;
repeated string files = 6;
repeated string directories = 7;
}
message ServiceFileConfig {
int32 node_id = 1;
string service = 2;
string file = 3;
string data = 4;
}
message ServiceValidationMode {
enum Enum {
BLOCKING = 0;
NON_BLOCKING = 1;
TIMER = 2;
}
}
message ServiceAction {
enum Enum {
START = 0;
STOP = 1;
RESTART = 2;
VALIDATE = 3;
}
}
message ServiceDefaults {
string node_type = 1;
repeated string services = 2;
}
message Service {
string group = 1;
string name = 2;
}
message NodeServiceData {
repeated string executables = 1;
repeated string dependencies = 2;
repeated string dirs = 3;
repeated string configs = 4;
repeated string startup = 5;
repeated string validate = 6;
ServiceValidationMode.Enum validation_mode = 7;
int32 validation_timer = 8;
repeated string shutdown = 9;
string meta = 10;
}
message GetServicesRequest {
}
message GetServicesResponse {
repeated Service services = 1;
}
message GetServiceDefaultsRequest {
int32 session_id = 1;
}
message GetServiceDefaultsResponse {
repeated ServiceDefaults defaults = 1;
}
message SetServiceDefaultsRequest {
int32 session_id = 1;
repeated ServiceDefaults defaults = 2;
}
message SetServiceDefaultsResponse {
bool result = 1;
}
message GetNodeServiceConfigsRequest {
int32 session_id = 1;
}
message GetNodeServiceConfigsResponse {
message ServiceConfig {
int32 node_id = 1;
string service = 2;
NodeServiceData data = 3;
map<string, string> files = 4;
}
repeated ServiceConfig configs = 1;
}
message GetNodeServiceRequest {
int32 session_id = 1;
int32 node_id = 2;
string service = 3;
}
message GetNodeServiceResponse {
NodeServiceData service = 1;
}
message GetNodeServiceFileRequest {
int32 session_id = 1;
int32 node_id = 2;
string service = 3;
string file = 4;
}
message GetNodeServiceFileResponse {
string data = 1;
}
message SetNodeServiceRequest {
int32 session_id = 1;
ServiceConfig config = 2;
}
message SetNodeServiceResponse {
bool result = 1;
}
message SetNodeServiceFileRequest {
int32 session_id = 1;
ServiceFileConfig config = 2;
}
message SetNodeServiceFileResponse {
bool result = 1;
}
message ServiceActionRequest {
int32 session_id = 1;
int32 node_id = 2;
string service = 3;
ServiceAction.Enum action = 4;
}
message ServiceActionResponse {
bool result = 1;
}

View file

@ -0,0 +1,36 @@
syntax = "proto3";
package wlan;
import "core/api/grpc/common.proto";
message WlanConfig {
int32 node_id = 1;
map<string, string> config = 2;
}
message GetWlanConfigsRequest {
int32 session_id = 1;
}
message GetWlanConfigsResponse {
map<int32, common.MappedConfig> configs = 1;
}
message GetWlanConfigRequest {
int32 session_id = 1;
int32 node_id = 2;
}
message GetWlanConfigResponse {
map<string, common.ConfigOption> config = 1;
}
message SetWlanConfigRequest {
int32 session_id = 1;
WlanConfig wlan_config = 2;
}
message SetWlanConfigResponse {
bool result = 1;
}

View file

@ -1,6 +1,7 @@
bcrypt==3.1.7
cffi==1.14.0
cryptography==2.8
dataclasses==0.7; python_version == "3.6"
fabric==2.5.0
grpcio==1.27.2
invoke==1.4.1

View file

@ -17,8 +17,8 @@ from core import constants
from core.api.grpc.server import CoreGrpcServer
from core.api.tlv.corehandlers import CoreHandler, CoreUdpHandler
from core.api.tlv.coreserver import CoreServer, CoreUdpServer
from core.api.tlv.enumerations import CORE_API_PORT
from core.constants import CORE_CONF_DIR, COREDPY_VERSION
from core.emulator.enumerations import CORE_API_PORT
from core.utils import close_onexec, load_logging_config
@ -41,8 +41,7 @@ def start_udp(mainserver, server_address):
:return: CoreUdpServer
"""
mainserver.udpserver = CoreUdpServer(server_address, CoreUdpHandler, mainserver)
mainserver.udpthread = threading.Thread(target=mainserver.udpserver.start)
mainserver.udpthread.daemon = True
mainserver.udpthread = threading.Thread(target=mainserver.udpserver.start, daemon=True)
mainserver.udpthread.start()
@ -70,8 +69,7 @@ def cored(cfg):
address_config = cfg["grpcaddress"]
port_config = cfg["grpcport"]
grpc_address = f"{address_config}:{port_config}"
grpc_thread = threading.Thread(target=grpc_server.listen, args=(grpc_address,))
grpc_thread.daemon = True
grpc_thread = threading.Thread(target=grpc_server.listen, args=(grpc_address,), daemon=True)
grpc_thread.start()
# start udp server

212
daemon/scripts/core-route-monitor Executable file
View file

@ -0,0 +1,212 @@
#!/usr/bin/env python
import argparse
import enum
import select
import socket
import subprocess
import time
from argparse import ArgumentDefaultsHelpFormatter
from functools import cmp_to_key
from queue import Queue
from threading import Thread
from typing import Dict, Tuple
from core import utils
from core.api.grpc.client import CoreGrpcClient
from core.api.grpc.core_pb2 import NodeType
SDT_HOST = "127.0.0.1"
SDT_PORT = 50000
ROUTE_LAYER = "CORE Route"
DEAD_TIME = 3
ROUTE_TIME = 3
PACKET_CHOICES = ["udp", "tcp", "icmp"]
class RouteEnum(enum.Enum):
ADD = 0
DEL = 1
class SdtClient:
def __init__(self, address: Tuple[str, int]) -> None:
self.sock = socket.create_connection(address)
self.links = []
self.send(f'layer "{ROUTE_LAYER}"')
def close(self) -> None:
self.sock.close()
def send(self, cmd: str) -> None:
sdt_cmd = f"{cmd}\n".encode()
self.sock.sendall(sdt_cmd)
def add_link(self, node1, node2) -> None:
route_id = f"{node1}-{node2}-r"
link_id = f"{node1},{node2},{route_id}"
cmd = f'link {link_id} linkLayer "{ROUTE_LAYER}" line yellow,2'
self.send(cmd)
self.links.append(link_id)
def delete_links(self) -> None:
for link_id in self.links:
cmd = f"delete link,{link_id}"
self.send(cmd)
self.links.clear()
class RouterMonitor:
def __init__(self, src_id: str, src: str, dst: str, pkt: str,
sdt_host: str, sdt_port: int) -> None:
self.queue = Queue()
self.core = CoreGrpcClient()
self.src_id = src_id
self.src = src
self.dst = dst
self.pkt = pkt
self.seen = {}
self.running = False
self.route_time = None
self.listeners = []
self.sdt = SdtClient((sdt_host, sdt_port))
self.nodes = self.get_nodes()
def get_nodes(self) -> Dict[str, str]:
nodes = {}
with self.core.context_connect():
response = self.core.get_sessions()
sessions = response.sessions
session = None
if sessions:
session = sessions[0]
if not session:
raise Exception("no current core sessions")
print("session: ", session.dir)
response = self.core.get_session(session.id)
for node in response.session.nodes:
if node.type != NodeType.DEFAULT:
continue
nodes[node.id] = node.channel
return nodes
def start(self) -> None:
self.running = True
for node_id, node in self.nodes.items():
print("listening on node: ", node)
thread = Thread(target=self.listen, args=(node_id, node), daemon=True)
thread.start()
self.listeners.append(thread)
self.manage()
def manage(self) -> None:
self.route_time = time.monotonic()
while self.running:
route_enum, node, seen = self.queue.get()
if route_enum == RouteEnum.ADD:
self.seen[node] = seen
elif node in self.seen:
del self.seen[node]
if (time.monotonic() - self.route_time) >= ROUTE_TIME:
self.manage_routes()
self.route_time = time.monotonic()
def route_sort(self, x: Tuple[str, int], y: Tuple[str, int]) -> int:
x_node = x[0]
y_node = y[0]
if x_node == self.src_id:
return 1
if y_node == self.src_id:
return -1
x_ttl, y_ttl = x[1], y[1]
return x_ttl - y_ttl
def manage_routes(self) -> None:
self.sdt.delete_links()
if not self.seen:
return
values = sorted(self.seen.items(),
key=cmp_to_key(self.route_sort),
reverse=True)
print("current route:")
for index, node_data in enumerate(values):
next_index = index + 1
if next_index == len(values):
break
next_node_id = values[next_index][0]
node_id, ttl = node_data
print(f"{node_id} -> {next_node_id}")
self.sdt.add_link(node_id, next_node_id)
def stop(self) -> None:
self.running = False
self.sdt.delete_links()
self.sdt.close()
for thread in self.listeners:
thread.join()
self.listeners.clear()
def listen(self, node_id, node) -> None:
cmd = (
f"tcpdump -lnv src host {self.src} and dst host {self.dst} and {self.pkt}"
)
node_cmd = f"vcmd -c {node} -- {cmd}"
p = subprocess.Popen(node_cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL)
current = time.monotonic()
try:
while not p.poll() and self.running:
ready, _, _ = select.select([p.stdout], [], [], 1)
if ready:
line = p.stdout.readline().strip().decode()
if line:
line = line.split("ttl", 1)[1]
ttl = int(line.split(",", 1)[0])
p.stdout.readline()
self.queue.put((RouteEnum.ADD, node_id, ttl))
current = time.monotonic()
else:
if (time.monotonic() - current) >= DEAD_TIME:
self.queue.put((RouteEnum.DEL, node_id, None))
except Exception as e:
print(f"listener error: {e}")
def main() -> None:
if not utils.which("tcpdump", required=False):
print("core-route-monitor requires tcpdump to be installed")
return
parser = argparse.ArgumentParser(
description="core route monitor",
formatter_class=ArgumentDefaultsHelpFormatter,
)
parser.add_argument("--id", required=True,
help="source node id for determining path")
parser.add_argument("--src", default="10.0.0.20",
help="source address for route monitoring")
parser.add_argument("--dst", default="10.0.2.20",
help="destination address for route monitoring")
parser.add_argument("--pkt", default="icmp", choices=PACKET_CHOICES,
help="packet type")
parser.add_argument("--sdt-host", default=SDT_HOST, help="sdt host address")
parser.add_argument("--sdt-port", type=int, default=SDT_PORT, help="sdt port")
args = parser.parse_args()
monitor = RouterMonitor(
args.id,
args.src,
args.dst,
args.pkt,
args.sdt_host,
args.sdt_port,
)
try:
monitor.start()
except KeyboardInterrupt:
monitor.stop()
print("ending route monitor")
if __name__ == "__main__":
main()

View file

@ -9,12 +9,8 @@ import socket
import sys
from core.api.tlv import coreapi
from core.emulator.enumerations import (
CORE_API_PORT,
MessageFlags,
MessageTypes,
SessionTlvs,
)
from core.api.tlv.enumerations import CORE_API_PORT, MessageTypes, SessionTlvs
from core.emulator.enumerations import MessageFlags
def print_available_tlvs(t, tlv_class):

View file

@ -34,12 +34,13 @@ setup(
version="@PACKAGE_VERSION@",
packages=find_packages(),
install_requires=[
'dataclasses;python_version=="3.6"',
"fabric",
"grpcio",
"netaddr",
"invoke",
"lxml",
"mako",
"netaddr",
"pillow",
"protobuf",
"pyproj",

View file

@ -114,7 +114,7 @@ class TestCore:
session.instantiate()
# check link data gets generated
assert ptp_node.all_link_data(MessageFlags.ADD.value)
assert ptp_node.all_link_data(MessageFlags.ADD)
# check common nets exist between linked nodes
assert node_one.commonnets(node_two)

View file

@ -7,16 +7,16 @@ from mock import patch
from core.api.grpc import core_pb2
from core.api.grpc.client import CoreGrpcClient, InterfaceHelper
from core.config import ConfigShim
from core.api.grpc.emane_pb2 import EmaneModelConfig
from core.api.grpc.mobility_pb2 import MobilityAction, MobilityConfig
from core.api.grpc.services_pb2 import ServiceAction, ServiceConfig, ServiceFileConfig
from core.api.grpc.wlan_pb2 import WlanConfig
from core.api.tlv.dataconversion import ConfigShim
from core.api.tlv.enumerations import ConfigFlags
from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emulator.data import EventData
from core.emulator.emudata import NodeOptions
from core.emulator.enumerations import (
ConfigFlags,
EventTypes,
ExceptionLevels,
NodeTypes,
)
from core.emulator.enumerations import EventTypes, ExceptionLevels, NodeTypes
from core.errors import CoreError
from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
from core.xml.corexml import CoreXmlWriter
@ -73,7 +73,7 @@ class TestGrpc:
model_node_id = 20
model_config_key = "bandwidth"
model_config_value = "500000"
model_config = core_pb2.EmaneModelConfig(
model_config = EmaneModelConfig(
node_id=model_node_id,
interface_id=-1,
model=EmaneIeee80211abgModel.name,
@ -82,21 +82,21 @@ class TestGrpc:
model_configs = [model_config]
wlan_config_key = "range"
wlan_config_value = "333"
wlan_config = core_pb2.WlanConfig(
wlan_config = WlanConfig(
node_id=wlan_node.id, config={wlan_config_key: wlan_config_value}
)
wlan_configs = [wlan_config]
mobility_config_key = "refresh_ms"
mobility_config_value = "60"
mobility_config = core_pb2.MobilityConfig(
mobility_config = MobilityConfig(
node_id=wlan_node.id, config={mobility_config_key: mobility_config_value}
)
mobility_configs = [mobility_config]
service_config = core_pb2.ServiceConfig(
service_config = ServiceConfig(
node_id=node_one.id, service="DefaultRoute", validate=["echo hello"]
)
service_configs = [service_config]
service_file_config = core_pb2.ServiceFileConfig(
service_file_config = ServiceFileConfig(
node_id=node_one.id,
service="DefaultRoute",
file="defaultroute.sh",
@ -127,7 +127,7 @@ class TestGrpc:
assert wlan_node.id in session.nodes
assert session.nodes[node_one.id].netif(0) is not None
assert session.nodes[node_two.id].netif(0) is not None
hook_file, hook_data = session._hooks[core_pb2.SessionState.RUNTIME][0]
hook_file, hook_data = session._hooks[EventTypes.RUNTIME_STATE][0]
assert hook_file == hook.file
assert hook_data == hook.data
assert session.location.refxyz == (location_x, location_y, location_z)
@ -169,7 +169,7 @@ class TestGrpc:
assert isinstance(response.state, int)
session = grpc_server.coreemu.sessions.get(response.session_id)
assert session is not None
assert session.state == response.state
assert session.state == EventTypes(response.state)
if session_id is not None:
assert response.session_id == session_id
assert session.id == session_id
@ -341,7 +341,7 @@ class TestGrpc:
# then
assert response.result is True
assert session.state == core_pb2.SessionState.DEFINITION
assert session.state == EventTypes.DEFINITION_STATE
def test_add_node(self, grpc_server):
# given
@ -447,7 +447,7 @@ class TestGrpc:
session = grpc_server.coreemu.create_session()
file_name = "test"
file_data = "echo hello"
session.add_hook(EventTypes.RUNTIME_STATE.value, file_name, None, file_data)
session.add_hook(EventTypes.RUNTIME_STATE, file_name, None, file_data)
# then
with client.context_connect():
@ -540,7 +540,7 @@ class TestGrpc:
session = grpc_server.coreemu.create_session()
switch = session.add_node(_type=NodeTypes.SWITCH)
node = session.add_node()
assert len(switch.all_link_data(0)) == 0
assert len(switch.all_link_data()) == 0
# then
interface = interface_helper.create_interface(node.id, 0)
@ -549,7 +549,7 @@ class TestGrpc:
# then
assert response.result is True
assert len(switch.all_link_data(0)) == 1
assert len(switch.all_link_data()) == 1
def test_add_link_exception(self, grpc_server, interface_helper):
# given
@ -572,7 +572,7 @@ class TestGrpc:
interface = ip_prefixes.create_interface(node)
session.add_link(node.id, switch.id, interface)
options = core_pb2.LinkOptions(bandwidth=30000)
link = switch.all_link_data(0)[0]
link = switch.all_link_data()[0]
assert options.bandwidth != link.bandwidth
# then
@ -583,7 +583,7 @@ class TestGrpc:
# then
assert response.result is True
link = switch.all_link_data(0)[0]
link = switch.all_link_data()[0]
assert options.bandwidth == link.bandwidth
def test_delete_link(self, grpc_server, ip_prefixes):
@ -833,9 +833,7 @@ class TestGrpc:
# then
with client.context_connect():
response = client.mobility_action(
session.id, wlan.id, core_pb2.MobilityAction.STOP
)
response = client.mobility_action(session.id, wlan.id, MobilityAction.STOP)
# then
assert response.result is True
@ -975,7 +973,7 @@ class TestGrpc:
# then
with client.context_connect():
response = client.service_action(
session.id, node.id, service_name, core_pb2.ServiceAction.STOP
session.id, node.id, service_name, ServiceAction.STOP
)
# then
@ -986,7 +984,6 @@ class TestGrpc:
client = CoreGrpcClient()
session = grpc_server.coreemu.create_session()
node = session.add_node()
node_data = node.data(message_type=0)
queue = Queue()
def handle_event(event_data):
@ -998,7 +995,7 @@ class TestGrpc:
with client.context_connect():
client.events(session.id, handle_event)
time.sleep(0.1)
session.broadcast_node(node_data)
session.broadcast_node(node)
# then
queue.get(timeout=5)
@ -1011,7 +1008,7 @@ class TestGrpc:
node = session.add_node()
interface = ip_prefixes.create_interface(node)
session.add_link(node.id, wlan.id, interface)
link_data = wlan.all_link_data(0)[0]
link_data = wlan.all_link_data()[0]
queue = Queue()
def handle_event(event_data):
@ -1065,7 +1062,7 @@ class TestGrpc:
client.events(session.id, handle_event)
time.sleep(0.1)
event = EventData(
event_type=EventTypes.RUNTIME_STATE.value, time=str(time.monotonic())
event_type=EventTypes.RUNTIME_STATE, time=str(time.monotonic())
)
session.broadcast_event(event)
@ -1120,7 +1117,7 @@ class TestGrpc:
with client.context_connect():
client.events(session.id, handle_event)
time.sleep(0.1)
session.exception(exception_level, source, node_id, text)
session.exception(exception_level, source, text, node_id)
# then
queue.get(timeout=5)

View file

@ -10,21 +10,18 @@ import pytest
from mock import MagicMock
from core.api.tlv import coreapi
from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emulator.enumerations import (
from core.api.tlv.enumerations import (
ConfigFlags,
ConfigTlvs,
EventTlvs,
EventTypes,
ExecuteTlvs,
FileTlvs,
LinkTlvs,
MessageFlags,
NodeTlvs,
NodeTypes,
RegisterTlvs,
SessionTlvs,
)
from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emulator.enumerations import EventTypes, MessageFlags, NodeTypes, RegisterTlvs
from core.errors import CoreError
from core.location.mobility import BasicRangeModel
@ -117,7 +114,7 @@ class TestGui:
coretlv.handle_message(message)
switch_node = coretlv.session.get_node(switch)
all_links = switch_node.all_link_data(0)
all_links = switch_node.all_link_data()
assert len(all_links) == 1
def test_link_add_net_to_node(self, coretlv):
@ -141,7 +138,7 @@ class TestGui:
coretlv.handle_message(message)
switch_node = coretlv.session.get_node(switch)
all_links = switch_node.all_link_data(0)
all_links = switch_node.all_link_data()
assert len(all_links) == 1
def test_link_add_node_to_node(self, coretlv):
@ -171,7 +168,7 @@ class TestGui:
all_links = []
for node_id in coretlv.session.nodes:
node = coretlv.session.nodes[node_id]
all_links += node.all_link_data(0)
all_links += node.all_link_data()
assert len(all_links) == 1
def test_link_update(self, coretlv):
@ -193,7 +190,7 @@ class TestGui:
)
coretlv.handle_message(message)
switch_node = coretlv.session.get_node(switch)
all_links = switch_node.all_link_data(0)
all_links = switch_node.all_link_data()
assert len(all_links) == 1
link = all_links[0]
assert link.bandwidth is None
@ -211,7 +208,7 @@ class TestGui:
coretlv.handle_message(message)
switch_node = coretlv.session.get_node(switch)
all_links = switch_node.all_link_data(0)
all_links = switch_node.all_link_data()
assert len(all_links) == 1
link = all_links[0]
assert link.bandwidth == bandwidth
@ -240,7 +237,7 @@ class TestGui:
all_links = []
for node_id in coretlv.session.nodes:
node = coretlv.session.nodes[node_id]
all_links += node.all_link_data(0)
all_links += node.all_link_data()
assert len(all_links) == 1
message = coreapi.CoreLinkMessage.create(
@ -257,7 +254,7 @@ class TestGui:
all_links = []
for node_id in coretlv.session.nodes:
node = coretlv.session.nodes[node_id]
all_links += node.all_link_data(0)
all_links += node.all_link_data()
assert len(all_links) == 0
def test_link_delete_node_to_net(self, coretlv):
@ -279,7 +276,7 @@ class TestGui:
)
coretlv.handle_message(message)
switch_node = coretlv.session.get_node(switch)
all_links = switch_node.all_link_data(0)
all_links = switch_node.all_link_data()
assert len(all_links) == 1
message = coreapi.CoreLinkMessage.create(
@ -293,7 +290,7 @@ class TestGui:
coretlv.handle_message(message)
switch_node = coretlv.session.get_node(switch)
all_links = switch_node.all_link_data(0)
all_links = switch_node.all_link_data()
assert len(all_links) == 0
def test_link_delete_net_to_node(self, coretlv):
@ -315,7 +312,7 @@ class TestGui:
)
coretlv.handle_message(message)
switch_node = coretlv.session.get_node(switch)
all_links = switch_node.all_link_data(0)
all_links = switch_node.all_link_data()
assert len(all_links) == 1
message = coreapi.CoreLinkMessage.create(
@ -329,7 +326,7 @@ class TestGui:
coretlv.handle_message(message)
switch_node = coretlv.session.get_node(switch)
all_links = switch_node.all_link_data(0)
all_links = switch_node.all_link_data()
assert len(all_links) == 0
def test_session_update(self, coretlv):
@ -376,14 +373,14 @@ class TestGui:
assert len(coretlv.coreemu.sessions) == 0
def test_file_hook_add(self, coretlv):
state = EventTypes.DATACOLLECT_STATE.value
state = EventTypes.DATACOLLECT_STATE
assert coretlv.session._hooks.get(state) is None
file_name = "test.sh"
file_data = "echo hello"
message = coreapi.CoreFileMessage.create(
MessageFlags.ADD.value,
[
(FileTlvs.TYPE, f"hook:{state}"),
(FileTlvs.TYPE, f"hook:{state.value}"),
(FileTlvs.NAME, file_name),
(FileTlvs.DATA, file_data),
],
@ -514,7 +511,7 @@ class TestGui:
coretlv.handle_message(message)
assert coretlv.session.state == state.value
assert coretlv.session.state == state
def test_event_schedule(self, coretlv):
coretlv.session.add_event = mock.MagicMock()

View file

@ -43,7 +43,7 @@ class TestLinks:
session.add_link(node_one.id, node_two.id, interface_one)
# then
assert node_two.all_link_data(0)
assert node_two.all_link_data()
assert node_one.netif(interface_one.id)
def test_net_to_node(self, session, ip_prefixes):
@ -56,7 +56,7 @@ class TestLinks:
session.add_link(node_one.id, node_two.id, interface_two=interface_two)
# then
assert node_one.all_link_data(0)
assert node_one.all_link_data()
assert node_two.netif(interface_two.id)
def test_net_to_net(self, session):
@ -68,7 +68,7 @@ class TestLinks:
session.add_link(node_one.id, node_two.id)
# then
assert node_one.all_link_data(0)
assert node_one.all_link_data()
def test_link_update(self, session, ip_prefixes):
# given

View file

@ -3,7 +3,7 @@ from xml.etree import ElementTree
import pytest
from core.emulator.emudata import LinkOptions, NodeOptions
from core.emulator.enumerations import NodeTypes
from core.emulator.enumerations import EventTypes, NodeTypes
from core.errors import CoreError
from core.location.mobility import BasicRangeModel
from core.services.utility import SshService
@ -20,7 +20,8 @@ class TestXml:
# create hook
file_name = "runtime_hook.sh"
data = "#!/bin/sh\necho hello"
session.set_hook("hook:4", file_name, None, data)
state = EventTypes.RUNTIME_STATE
session.add_hook(state, file_name, None, data)
# save xml
xml_file = tmpdir.join("session.xml")
@ -38,7 +39,7 @@ class TestXml:
session.open_xml(file_path, start=True)
# verify nodes have been recreated
runtime_hooks = session._hooks.get(4)
runtime_hooks = session._hooks.get(state)
assert runtime_hooks
runtime_hook = runtime_hooks[0]
assert file_name == runtime_hook[0]
@ -269,7 +270,7 @@ class TestXml:
switch_two = session.get_node(n2_id)
assert switch_one
assert switch_two
assert len(switch_one.all_link_data(0) + switch_two.all_link_data(0)) == 1
assert len(switch_one.all_link_data() + switch_two.all_link_data()) == 1
def test_link_options(self, session, tmpdir, ip_prefixes):
"""
@ -329,7 +330,7 @@ class TestXml:
links = []
for node_id in session.nodes:
node = session.nodes[node_id]
links += node.all_link_data(0)
links += node.all_link_data()
link = links[0]
assert link_options.per == link.per
assert link_options.bandwidth == link.bandwidth
@ -396,7 +397,7 @@ class TestXml:
links = []
for node_id in session.nodes:
node = session.nodes[node_id]
links += node.all_link_data(0)
links += node.all_link_data()
link = links[0]
assert link_options.per == link.per
assert link_options.bandwidth == link.bandwidth
@ -478,7 +479,7 @@ class TestXml:
links = []
for node_id in session.nodes:
node = session.nodes[node_id]
links += node.all_link_data(0)
links += node.all_link_data()
assert len(links) == 2
link_one = links[0]
link_two = links[1]

View file

@ -5,35 +5,71 @@
## Main Components
* CORE Daemon
* Manages emulation sessions
* Builds the emulated networks using kernel virtualization for nodes and some form of bridging and packet manipulation for virtual networks
* Nodes and networks come together via interfaces installed on nodes
* core-daemon
* Manages emulated sessions of nodes and links for a given network
* Nodes are created using Linux namespaces
* Links are created using Linux bridges and virtual ethernet peers
* Packets sent over links are manipulated using traffic control
* Controlled via the CORE GUI
* Written in python and can be scripted, given direct control of scenarios
* CORE GUI
* GUI and daemon communicate using a custom, asynchronous, sockets-based API, known as the CORE API
* Drag and drop creation for nodes and network interfaces
* Can launch terminals for emulated nodes in running scenarios
* Provides both a custo TLV API and gRPC API
* Python program that leverages a small C binary for node creation
* core-gui
* GUI and daemon communicate over the custom TLV API
* Drag and drop creation for nodes and links
* Can launch terminals for emulated nodes in running sessions
* Can save/open scenario files to recreate previous sessions
* TCL/TK program
* coresendmsg
* Command line utility for sending TLV API messages to the core-daemon
* vcmd
* Command line utility for sending shell commands to nodes
![](static/core-architecture.jpg)
![](static/architecture.png)
## Sessions
CORE can create and run multiple emulated sessions at once, below is an
overview of the states a session will transition between during typical
GUI interactions.
![](static/workflow.png)
## How Does it Work?
A CORE node is a lightweight virtual machine. The CORE framework runs on Linux. CORE uses Linux network namespace virtualization to build virtual nodes, and ties them together with virtual networks using Linux Ethernet bridging.
The CORE framework runs on Linux and uses Linux namespacing for creating
node containers. These nodes are linked together using Linux bridging and
virtual interfaces. CORE sessions are a set of nodes and links operating
together for a specific purpose.
### Linux
Linux network namespaces (also known as netns) is the primary virtualization technique used by CORE. Most recent Linux distributions have namespaces-enabled kernels out of the box. A namespace is created using the ```clone()``` system call. Each namespace has its own process environment and private network stack. Network namespaces share the same filesystem in CORE.
Linux network namespaces (also known as netns) is the primary
technique used by CORE. Most recent Linux distributions have
namespaces-enabled kernels out of the box. Each namespace has its own process
environment and private network stack. Network namespaces share the same
filesystem in CORE.
CORE combines these namespaces with Linux Ethernet bridging to form networks. Link characteristics are applied using Linux Netem queuing disciplines. Ebtables is Ethernet frame filtering on Linux bridges. Wireless networks are emulated by controlling which interfaces can send and receive with ebtables rules.
CORE combines these namespaces with Linux Ethernet bridging to form networks.
Link characteristics are applied using Linux Netem queuing disciplines.
Ebtables is Ethernet frame filtering on Linux bridges. Wireless networks are
emulated by controlling which interfaces can send and receive with ebtables
rules.
## Prior Work
The Tcl/Tk CORE GUI was originally derived from the open source [IMUNES](http://imunes.net) project from the University of Zagreb as a custom project within Boeing Research and Technology's Network Technology research group in 2004. Since then they have developed the CORE framework to use Linux virtualization, have developed a Python framework, and made numerous user- and kernel-space developments, such as support for wireless networks, IPsec, distribute emulation, simulation integration, and more. The IMUNES project also consists of userspace and kernel components.
The Tcl/Tk CORE GUI was originally derived from the open source
[IMUNES](http://imunes.net) project from the University of Zagreb as a custom
project within Boeing Research and Technology's Network Technology research
group in 2004. Since then they have developed the CORE framework to use Linux
namespacing, have developed a Python framework, and made numerous user and
kernel-space developments, such as support for wireless networks, IPsec,
distribute emulation, simulation integration, and more. The IMUNES project
also consists of userspace and kernel components.
## Open Source Project and Resources
CORE has been released by Boeing to the open source community under the BSD license. If you find CORE useful for your work, please contribute back to the project. Contributions can be as simple as reporting a bug, dropping a line of encouragement or technical suggestions to the mailing lists, or can also include submitting patches or maintaining aspects of the tool. For contributing to CORE, please visit [CORE GitHub](https://github.com/coreemu/core).
CORE has been released by Boeing to the open source community under the BSD
license. If you find CORE useful for your work, please contribute back to the
project. Contributions can be as simple as reporting a bug, dropping a line of
encouragement or technical suggestions to the mailing lists, or can also
include submitting patches or maintaining aspects of the tool.

View file

@ -5,19 +5,40 @@
## Overview
The CORE control network allows the virtual nodes to communicate with their host environment. There are two types: the primary control network and auxiliary control networks. The primary control network is used mainly for communicating with the virtual nodes from host machines and for master-slave communications in a multi-server distributed environment. Auxiliary control networks have been introduced to for routing namespace hosted emulation software traffic to the test network.
The CORE control network allows the virtual nodes to communicate with their
host environment. There are two types: the primary control network and
auxiliary control networks. The primary control network is used mainly for
communicating with the virtual nodes from host machines and for master-slave
communications in a multi-server distributed environment. Auxiliary control
networks have been introduced to for routing namespace hosted emulation
software traffic to the test network.
## Activating the Primary Control Network
Under the *Session Menu*, the *Options...* dialog has an option to set a *control network prefix*.
Under the *Session Menu*, the *Options...* dialog has an option to set a
*control network prefix*.
This can be set to a network prefix such as *172.16.0.0/24*. A bridge will be created on the host machine having the last address in the prefix range (e.g. *172.16.0.254*), and each node will have an extra *ctrl0* control interface configured with an address corresponding to its node number (e.g. *172.16.0.3* for *n3*.)
This can be set to a network prefix such as *172.16.0.0/24*. A bridge will
be created on the host machine having the last address in the prefix range
(e.g. *172.16.0.254*), and each node will have an extra *ctrl0* control
interface configured with an address corresponding to its node number
(e.g. *172.16.0.3* for *n3*.)
A default for the primary control network may also be specified by setting the *controlnet* line in the */etc/core/core.conf* configuration file which new sessions will use by default. To simultaneously run multiple sessions with control networks, the session option should be used instead of the *core.conf* default.
A default for the primary control network may also be specified by setting
the *controlnet* line in the */etc/core/core.conf* configuration file which
new sessions will use by default. To simultaneously run multiple sessions with
control networks, the session option should be used instead of the *core.conf*
default.
**NOTE: If you have a large scenario with more than 253 nodes, use a control network prefix that allows more than the suggested */24*, such as */23* or greater.**
> **NOTE:** If you have a large scenario with more than 253 nodes, use a control
network prefix that allows more than the suggested */24*, such as */23* or
greater.
**IMPORTANT: Running a session with a control network can fail if a previous session has set up a control network and the its bridge is still up. Close the previous session first or wait for it to complete. If unable to, the *core-daemon* may need to be restarted and the lingering bridge(s) removed manually.**
> **NOTE:** Running a session with a control network can fail if a previous
session has set up a control network and the its bridge is still up. Close
the previous session first or wait for it to complete. If unable to, the
*core-daemon* may need to be restarted and the lingering bridge(s) removed
manually.
```shell
# Restart the CORE Daemon
@ -30,34 +51,60 @@ for cb in $ctrlbridges; do
sudo brctl delbr $cb
done
```
**TIP: If adjustments to the primary control network configuration made in */etc/core/core.conf* do not seem to take affect, check if there is anything set in the *Session Menu*, the *Options...* dialog. They may need to be cleared. These per session settings override the defaults in */etc/core/core.conf*.**
> **NOTE:** If adjustments to the primary control network configuration made in
*/etc/core/core.conf* do not seem to take affect, check if there is anything
set in the *Session Menu*, the *Options...* dialog. They may need to be
cleared. These per session settings override the defaults in
*/etc/core/core.conf*.
## Control Network in Distributed Sessions
When the primary control network is activated for a distributed session, a control network bridge will be created on each of the slave servers, with GRE tunnels back to the master server's bridge. The slave control bridges are not assigned an address. From the host, any of the nodes (local or remote) can be accessed, just like the single server case.
When the primary control network is activated for a distributed session, a
control network bridge will be created on each of the slave servers, with
GRE tunnels back to the master server's bridge. The slave control bridges
are not assigned an address. From the host, any of the nodes (local or remote)
can be accessed, just like the single server case.
In some situations, remote emulated nodes need to communicate with the host on which they are running and not the master server. Multiple control network prefixes can be specified in the either the session option or */etc/core/core.conf*, separated by spaces and beginning with the master server. Each entry has the form *"server:prefix"*. For example, if the servers *core1*,*core2*, and *core3* are assigned with nodes in the scenario and using :file:`/etc/core/core.conf` instead of the session option:
In some situations, remote emulated nodes need to communicate with the host
on which they are running and not the master server. Multiple control network
prefixes can be specified in the either the session option or
*/etc/core/core.conf*, separated by spaces and beginning with the master
server. Each entry has the form *"server:prefix"*. For example, if the servers
*core1*,*core2*, and *core3* are assigned with nodes in the scenario and using
*/etc/core/core.conf* instead of the session option.
```shell
controlnet=core1:172.16.1.0/24 core2:172.16.2.0/24 core3:172.16.1.0/24
```
Then, the control network bridges will be assigned as follows:
Then, the control network bridges will be assigned as follows:
* core1 = 172.16.1.254 (assuming it is the master server),
* core2 = 172.16.2.254
* core3 = 172.16.3.254
Tunnels back to the master server will still be built, but it is up to the user to add appropriate routes if networking between control network prefixes is desired. The control network script may help with this.
Tunnels back to the master server will still be built, but it is up to the
user to add appropriate routes if networking between control network prefixes
is desired. The control network script may help with this.
## Control Network Script
A control network script may be specified using the *controlnet_updown_script* option in the */etc/core/core.conf* file. This script will be run after the bridge has been built (and address assigned) with the first argument being the name of the bridge, and the second argument being the keyword *"startup"*. The script will again be invoked prior to bridge removal with the second argument being the keyword *"shutdown"*.
A control network script may be specified using the *controlnet_updown_script*
option in the */etc/core/core.conf* file. This script will be run after the
bridge has been built (and address assigned) with the first argument being the
name of the bridge, and the second argument being the keyword *"startup"*.
The script will again be invoked prior to bridge removal with the second
argument being the keyword *"shutdown"*.
## Auxiliary Control Networks
Starting with EMANE 0.9.2, CORE will run EMANE instances within namespaces. Since it is advisable to separate the OTA traffic from other traffic, we will need more than single channel leading out from the namespace. Up to three auxiliary control networks may be defined. Multiple control networks are set up in */etc/core/core.conf* file. Lines *controlnet1*, *controlnet2* and *controlnet3* define the auxiliary networks.
Starting with EMANE 0.9.2, CORE will run EMANE instances within namespaces.
Since it is advisable to separate the OTA traffic from other traffic, we will
need more than single channel leading out from the namespace. Up to three
auxiliary control networks may be defined. Multiple control networks are set
up in */etc/core/core.conf* file. Lines *controlnet1*, *controlnet2* and
*controlnet3* define the auxiliary networks.
For example, having the following */etc/core/core.conf*:
@ -67,13 +114,24 @@ controlnet1 = core1:172.18.1.0/24 core2:172.18.2.0/24 core3:172.18.3.0/24
controlnet2 = core1:172.19.1.0/24 core2:172.19.2.0/24 core3:172.19.3.0/24
```
This will activate the primary and two auxiliary control networks and add interfaces *ctrl0*, *ctrl1*, *ctrl2* to each node. One use case would be to assign *ctrl1* to the OTA manager device and *ctrl2* to the Event Service device in the EMANE Options dialog box and leave *ctrl0* for CORE control traffic.
This will activate the primary and two auxiliary control networks and add
interfaces *ctrl0*, *ctrl1*, *ctrl2* to each node. One use case would be to
assign *ctrl1* to the OTA manager device and *ctrl2* to the Event Service
device in the EMANE Options dialog box and leave *ctrl0* for CORE control
traffic.
**NOTE: *controlnet0* may be used in place of *controlnet* to configure the primary control network.**
> **NOTE:** *controlnet0* may be used in place of *controlnet* to configure
>the primary control network.
Unlike the primary control network, the auxiliary control networks will not employ tunneling since their primary purpose is for efficiently transporting multicast EMANE OTA and event traffic. Note that there is no per-session configuration for auxiliary control networks.
Unlike the primary control network, the auxiliary control networks will not
employ tunneling since their primary purpose is for efficiently transporting
multicast EMANE OTA and event traffic. Note that there is no per-session
configuration for auxiliary control networks.
To extend the auxiliary control networks across a distributed test environment, host network interfaces need to be added to them. The following lines in */etc/core/core.conf* will add host devices *eth1*, *eth2* and *eth3* to *controlnet1*, *controlnet2*, *controlnet3*:
To extend the auxiliary control networks across a distributed test
environment, host network interfaces need to be added to them. The following
lines in */etc/core/core.conf* will add host devices *eth1*, *eth2* and *eth3*
to *controlnet1*, *controlnet2*, *controlnet3*:
```shell
controlnetif1 = eth1
@ -81,7 +139,9 @@ controlnetif2 = eth2
controlnetif3 = eth3
```
**NOTE: There is no need to assign an interface to the primary control network because tunnels are formed between the master and the slaves using IP addresses that are provided in *servers.conf*.**
> **NOTE:** There is no need to assign an interface to the primary control
>network because tunnels are formed between the master and the slaves using IP
>addresses that are provided in *servers.conf*.
Shown below is a representative diagram of the configuration above.

View file

@ -5,8 +5,9 @@
## Repository Overview
The CORE source consists of several different programming languages for historical reasons.
Current development focuses on the Python modules and daemon. Here is a brief description of the source directories.
The CORE source consists of several different programming languages for
historical reasons. Current development focuses on the Python modules and
daemon. Here is a brief description of the source directories.
| Directory | Description |
|---|---|
@ -14,14 +15,13 @@ Current development focuses on the Python modules and daemon. Here is a brief de
|docs|Markdown Documentation currently hosted on GitHub|
|gui|Tcl/Tk GUI|
|man|Template files for creating man pages for various CORE command line utilities|
|netns|Python C extension modules for creating CORE containers|
|netns|C program for creating CORE containers|
|scripts|Template files used for running CORE as a service|
## Getting started
To setup CORE for develop we will leverage to automated install script.
## Clone CORE Repo
```shell

View file

@ -0,0 +1,49 @@
@startuml
skinparam {
RoundCorner 8
ComponentStyle uml2
ComponentBorderColor #Black
InterfaceBorderColor #Black
InterfaceBackgroundColor #Yellow
}
package User {
component "core-gui" as gui #DeepSkyBlue
component "coresendmsg" #DeepSkyBlue
component "python scripts" as scripts #DeepSkyBlue
component vcmd #DeepSkyBlue
}
package Server {
component "core-daemon" as daemon #DarkSeaGreen
}
package Python {
component core #LightSteelBlue
}
package "Linux System" {
component nodes #SpringGreen [
nodes
(linux namespaces)
]
component links #SpringGreen [
links
(bridging and traffic manipulation)
]
}
package API {
interface TLV as tlv
interface gRPC as grpc
}
gui <..> tlv
coresendmsg <..> tlv
scripts <..> tlv
scripts <..> grpc
tlv -- daemon
grpc -- daemon
scripts -- core
daemon - core
core <..> nodes
core <..> links
vcmd <..> nodes
@enduml

View file

@ -0,0 +1,40 @@
@startuml
skinparam {
RoundCorner 8
StateBorderColor #Black
StateBackgroundColor #LightSteelBlue
}
Definition: Session XML/IMN
Definition: GUI Drawing
Definition: Scripts
Configuration: Configure Hooks
Configuration: Configure Services
Configuration: Configure WLAN / Mobility
Configuration: Configure EMANE
Instantiation: Create Nodes
Instantiation: Create Interfaces
Instantiation: Create Bridges
Instantiation: Start Services
Runtime: Interactive Shells
Runtime: Traffic Scripts
Runtime: Mobility
Runtime: Widgets
Datacollect: Collect Files
Datacollect: Other Results
Shutdown: Shutdown Services
Shutdown: Destroy Brdges
Shutdown: Destroy Interfaces
Shutdown: Destroy Nodes
Definition -> Configuration
Configuration -> Instantiation
Instantiation -> Runtime
Runtime -> Datacollect
Datacollect -> Shutdown
@enduml

View file

@ -9,21 +9,22 @@ A large emulation scenario can be deployed on multiple emulation servers and
controlled by a single GUI. The GUI, representing the entire topology, can be
run on one of the emulation servers or on a separate machine.
Each machine that will act as an emulation will require the installation of a distributed CORE package and
some configuration to allow SSH as root.
Each machine that will act as an emulation will require the installation of a
distributed CORE package and some configuration to allow SSH as root.
## Configuring SSH
Distributed CORE works using the python fabric library to run commands on remote servers over SSH.
Distributed CORE works using the python fabric library to run commands on
remote servers over SSH.
### Remote GUI Terminals
You need to have the same user defined on each server, since the user used
You need to have the same user defined on each server, since the user used
for these remote shells is the same user that is running the CORE GUI.
**Edit -> Preferences... -> Terminal program:**
Currently recommend setting this to **xterm -e** as the default
Currently recommend setting this to **xterm -e** as the default
**gnome-terminal** will not work.
May need to install xterm if, not already installed.
@ -34,7 +35,8 @@ sudo apt install xterm
### Distributed Server SSH Configuration
First the distributed servers must be configured to allow passwordless root login over SSH.
First the distributed servers must be configured to allow passwordless root
login over SSH.
On distributed server:
```shelll
@ -48,7 +50,7 @@ vi /etc/ssh/sshd_config
PermitRootLogin yes
PasswordAuthentication yes
# if desired add/modify the following line to allow SSH to
# if desired add/modify the following line to allow SSH to
# accept all env variables
AcceptEnv *
@ -72,7 +74,7 @@ sudo vi /etc/fabric.yml
# set configuration
connect_kwargs: {"key_filename": "/home/user/.ssh/core"}
```
```
On distributed server:
```shell
@ -125,7 +127,7 @@ the **all nodes** button. Servers that have assigned nodes are shown in blue in
the server list. Another option is to first select a subset of nodes, then open
the **CORE emulation servers** box and use the **selected nodes** button.
**IMPORTANT: Leave the nodes unassigned if they are to be run on the master
**IMPORTANT: Leave the nodes unassigned if they are to be run on the master
server. Do not explicitly assign the nodes to the master server.**
## GUI Visualization
@ -137,23 +139,23 @@ will draw the link with a dashed line.
Wireless nodes, i.e. those connected to a WLAN node, can be assigned to
different emulation servers and participate in the same wireless network
only if an EMANE model is used for the WLAN. The basic range model does
not work across multiple servers due to the Linux bridging and ebtables
only if an EMANE model is used for the WLAN. The basic range model does
not work across multiple servers due to the Linux bridging and ebtables
rules that are used.
**NOTE: The basic range wireless model does not support distributed emulation,
but EMANE does.**
When nodes are linked across servers **core-daemons** will automatically
When nodes are linked across servers **core-daemons** will automatically
create necessary tunnels between the nodes when executed. Care should be taken
to arrange the topology such that the number of tunnels is minimized. The
to arrange the topology such that the number of tunnels is minimized. The
tunnels carry data between servers to connect nodes as specified in the topology.
These tunnels are created using GRE tunneling, similar to the Tunnel Tool.
### EMANE Configuration and Issues
EMANE needs to have controlnet configured in **core.conf** in order to startup correctly.
The names before the addresses need to match the servers configured in
The names before the addresses need to match the servers configured in
**~/.core/servers.conf** previously.
```shell

View file

@ -5,24 +5,55 @@
## What is EMANE?
The Extendable Mobile Ad-hoc Network Emulator (EMANE) allows heterogeneous network emulation using a pluggable MAC and PHY layer architecture. The EMANE framework provides an implementation architecture for modeling different radio interface types in the form of *Network Emulation Modules* (NEMs) and incorporating these modules into a real-time emulation running in a distributed environment.
The Extendable Mobile Ad-hoc Network Emulator (EMANE) allows heterogeneous
network emulation using a pluggable MAC and PHY layer architecture. The
EMANE framework provides an implementation architecture for modeling
different radio interface types in the form of *Network Emulation Modules*
(NEMs) and incorporating these modules into a real-time emulation running
in a distributed environment.
EMANE is developed by U.S. Naval Research Labs (NRL) Code 5522 and Adjacent Link LLC, who maintain these websites:
EMANE is developed by U.S. Naval Research Labs (NRL) Code 5522 and Adjacent
Link LLC, who maintain these websites:
* <https://github.com/adjacentlink/emane>
* <http://www.adjacentlink.com/>
Instead of building Linux Ethernet bridging networks with CORE, higher-fidelity wireless networks can be emulated using EMANE bound to virtual devices. CORE emulates layers 3 and above (network, session, application) with its virtual network stacks and process space for protocols and applications, while EMANE emulates layers 1 and 2 (physical and data link) using its pluggable PHY and MAC models.
Instead of building Linux Ethernet bridging networks with CORE,
higher-fidelity wireless networks can be emulated using EMANE bound to virtual
devices. CORE emulates layers 3 and above (network, session, application) with
its virtual network stacks and process space for protocols and applications,
while EMANE emulates layers 1 and 2 (physical and data link) using its
pluggable PHY and MAC models.
The interface between CORE and EMANE is a TAP device. CORE builds the virtual node using Linux network namespaces, installs the TAP device into the namespace and instantiates one EMANE process in the namespace. The EMANE process binds a user space socket to the TAP device for sending and receiving data from CORE.
The interface between CORE and EMANE is a TAP device. CORE builds the virtual
node using Linux network namespaces, installs the TAP device into the namespace
and instantiates one EMANE process in the namespace. The EMANE process binds a
user space socket to the TAP device for sending and receiving data from CORE.
An EMANE instance sends and receives OTA (Over-The-Air) traffic to and from other EMANE instances via a control port (e.g. *ctrl0*, *ctrl1*). It also sends and receives Events to and from the Event Service using the same or a different control port. EMANE models are configured through CORE's WLAN configuration dialog. A corresponding EmaneModel Python class is sub-classed for each supported EMANE model, to provide configuration items and their mapping to XML files. This way new models can be easily supported. When CORE starts the emulation, it generates the appropriate XML files that specify the EMANE NEM configuration, and launches the EMANE daemons.
An EMANE instance sends and receives OTA (Over-The-Air) traffic to and from
other EMANE instances via a control port (e.g. *ctrl0*, *ctrl1*). It also
sends and receives Events to and from the Event Service using the same or a
different control port. EMANE models are configured through CORE's WLAN
configuration dialog. A corresponding EmaneModel Python class is sub-classed
for each supported EMANE model, to provide configuration items and their
mapping to XML files. This way new models can be easily supported. When
CORE starts the emulation, it generates the appropriate XML files that
specify the EMANE NEM configuration, and launches the EMANE daemons.
Some EMANE models support location information to determine when packets should be dropped. EMANE has an event system where location events are broadcast to all NEMs. CORE can generate these location events when nodes are moved on the canvas. The canvas size and scale dialog has controls for mapping the X,Y coordinate system to a latitude, longitude geographic system that EMANE uses. When specified in the *core.conf* configuration file, CORE can also subscribe to EMANE location events and move the nodes on the canvas as they are moved in the EMANE emulation. This would occur when an Emulation Script Generator, for example, is running a mobility script.
Some EMANE models support location information to determine when packets
should be dropped. EMANE has an event system where location events are
broadcast to all NEMs. CORE can generate these location events when nodes
are moved on the canvas. The canvas size and scale dialog has controls for
mapping the X,Y coordinate system to a latitude, longitude geographic system
that EMANE uses. When specified in the *core.conf* configuration file, CORE
can also subscribe to EMANE location events and move the nodes on the canvas
as they are moved in the EMANE emulation. This would occur when an Emulation
Script Generator, for example, is running a mobility script.
## EMANE Configuration
The CORE configuration file */etc/core/core.conf* has options specific to EMANE. An example emane section from the *core.conf* file is shown below:
The CORE configuration file */etc/core/core.conf* has options specific to
EMANE. An example emane section from the *core.conf* file is shown below:
```shell
# EMANE configuration
@ -35,7 +66,8 @@ emane_log_level = 2
emane_realtime = True
```
EMANE can be installed from deb or RPM packages or from source. See the [EMANE GitHub](https://github.com/adjacentlink/emane) for full details.
EMANE can be installed from deb or RPM packages or from source. See the
[EMANE GitHub](https://github.com/adjacentlink/emane) for full details.
Here are quick instructions for installing all EMANE packages:
@ -47,15 +79,20 @@ tar xzf emane-1.2.1-release-1.ubuntu-16_04.amd64.tar.gz
sudo dpkg -i emane-1.2.1-release-1/deb/ubuntu-16_04/amd64/*.deb
```
If you have an EMANE event generator (e.g. mobility or pathloss scripts) and want to have CORE subscribe to EMANE location events, set the following line in the */etc/core/core.conf* configuration file:
If you have an EMANE event generator (e.g. mobility or pathloss scripts) and
want to have CORE subscribe to EMANE location events, set the following line
in the */etc/core/core.conf* configuration file:
```shell
emane_event_monitor = True
```
Do not set the above option to True if you want to manually drag nodes around on the canvas to update their location in EMANE.
Do not set the above option to True if you want to manually drag nodes around
on the canvas to update their location in EMANE.
Another common issue is if installing EMANE from source, the default configure prefix will place the DTD files in */usr/local/share/emane/dtd* while CORE expects them in */usr/share/emane/dtd*.
Another common issue is if installing EMANE from source, the default configure
prefix will place the DTD files in */usr/local/share/emane/dtd* while CORE
expects them in */usr/share/emane/dtd*.
A symbolic link will fix this:
@ -65,28 +102,66 @@ sudo ln -s /usr/local/share/emane /usr/share/emane
## Custom EMANE Models
CORE supports custom developed EMANE models by way of dynamically loading user created python files that represent the model. Custom EMANE models should be placed within the path defined by **emane_models_dir** in the CORE configuration file. This path cannot end in **/emane**.
CORE supports custom developed EMANE models by way of dynamically loading user
created python files that represent the model. Custom EMANE models should be
placed within the path defined by **emane_models_dir** in the CORE
configuration file. This path cannot end in **/emane**.
Here is an example model with documentation describing functionality:
[Example Model](../daemon/examples/myemane/examplemodel.py)
## Single PC with EMANE
This section describes running CORE and EMANE on a single machine. This is the default mode of operation when building an EMANE network with CORE. The OTA manager and Event service interface are set to use *ctrl0* and the virtual nodes use the primary control channel for communicating with one another. The primary control channel is automatically activated when a scenario involves EMANE. Using the primary control channel prevents your emulation session from sending multicast traffic on your local network and interfering with other EMANE users.
This section describes running CORE and EMANE on a single machine. This is the
default mode of operation when building an EMANE network with CORE. The OTA
manager and Event service interface are set to use *ctrl0* and the virtual
nodes use the primary control channel for communicating with one another. The
primary control channel is automatically activated when a scenario involves
EMANE. Using the primary control channel prevents your emulation session from
sending multicast traffic on your local network and interfering with other
EMANE users.
EMANE is configured through a WLAN node, because it is all about emulating wireless radio networks. Once a node is linked to a WLAN cloud configured with an EMANE model, the radio interface on that node may also be configured separately (apart from the cloud.)
EMANE is configured through a WLAN node, because it is all about emulating
wireless radio networks. Once a node is linked to a WLAN cloud configured
with an EMANE model, the radio interface on that node may also be configured
separately (apart from the cloud.)
Double-click on a WLAN node to invoke the WLAN configuration dialog. Click the *EMANE* tab; when EMANE has been properly installed, EMANE wireless modules should be listed in the *EMANE Models* list. (You may need to restart the CORE daemon if it was running prior to installing the EMANE Python bindings.) Click on a model name to enable it.
Double-click on a WLAN node to invoke the WLAN configuration dialog. Click
the *EMANE* tab; when EMANE has been properly installed, EMANE wireless modules
should be listed in the *EMANE Models* list. (You may need to restart the
CORE daemon if it was running prior to installing the EMANE Python bindings.)
Click on a model name to enable it.
When an EMANE model is selected in the *EMANE Models* list, clicking on the *model options* button causes the GUI to query the CORE daemon for configuration items. Each model will have different parameters, refer to the EMANE documentation for an explanation of each item. The defaults values are presented in the dialog. Clicking *Apply* and *Apply* again will store the EMANE model selections.
When an EMANE model is selected in the *EMANE Models* list, clicking on the
*model options* button causes the GUI to query the CORE daemon for
configuration items. Each model will have different parameters, refer to the
EMANE documentation for an explanation of each item. The defaults values are
presented in the dialog. Clicking *Apply* and *Apply* again will store the
EMANE model selections.
The *EMANE options* button allows specifying some global parameters for EMANE, some of which are necessary for distributed operation.
The *EMANE options* button allows specifying some global parameters for
EMANE, some of which are necessary for distributed operation.
The RF-PIPE and IEEE 802.11abg models use a Universal PHY that supports geographic location information for determining pathloss between nodes. A default latitude and longitude location is provided by CORE and this location-based pathloss is enabled by default; this is the *pathloss mode* setting for the Universal PHY. Moving a node on the canvas while the emulation is running generates location events for EMANE. To view or change the geographic location or scale of the canvas use the *Canvas Size and Scale* dialog available from the *Canvas* menu.
The RF-PIPE and IEEE 802.11abg models use a Universal PHY that supports
geographic location information for determining pathloss between nodes. A
default latitude and longitude location is provided by CORE and this
location-based pathloss is enabled by default; this is the *pathloss mode*
setting for the Universal PHY. Moving a node on the canvas while the
emulation is running generates location events for EMANE. To view or change
the geographic location or scale of the canvas use the *Canvas Size and Scale*
dialog available from the *Canvas* menu.
Note that conversion between geographic and Cartesian coordinate systems is done using UTM (Universal Transverse Mercator) projection, where different zones of 6 degree longitude bands are defined. The location events generated by CORE may become inaccurate near the zone boundaries for very large scenarios that span multiple UTM zones. It is recommended that EMANE location scripts be used to achieve geo-location accuracy in this situation.
Note that conversion between geographic and Cartesian coordinate systems is
done using UTM (Universal Transverse Mercator) projection, where different
zones of 6 degree longitude bands are defined. The location events generated
by CORE may become inaccurate near the zone boundaries for very large scenarios
that span multiple UTM zones. It is recommended that EMANE location scripts be
used to achieve geo-location accuracy in this situation.
Clicking the green *Start* button launches the emulation and causes TAP devices to be created in the virtual nodes that are linked to the EMANE WLAN. These devices appear with interface names such as eth0, eth1, etc. The EMANE processes should now be running in each namespace. For a four node scenario:
Clicking the green *Start* button launches the emulation and causes TAP devices
to be created in the virtual nodes that are linked to the EMANE WLAN. These
devices appear with interface names such as eth0, eth1, etc. The EMANE processes
should now be running in each namespace. For a four node scenario:
```shell
ps -aef | grep emane
@ -96,30 +171,60 @@ root 1179 942 0 11:46 ? 00:00:00 emane -d --logl 3 -r -f /tmp/pycore.59992/eman
root 1239 979 0 11:46 ? 00:00:00 emane -d --logl 3 -r -f /tmp/pycore.59992/emane5.log /tmp/pycore.59992/platform5.xml
```
The example above shows the EMANE processes started by CORE. To view the configuration generated by CORE, look in the */tmp/pycore.nnnnn/* session directory for a *platform.xml* file and other XML files. One easy way to view this information is by double-clicking one of the virtual nodes, and typing *cd ..* in the shell to go up to the session directory.
The example above shows the EMANE processes started by CORE. To view the
configuration generated by CORE, look in the */tmp/pycore.nnnnn/* session
directory for a *platform.xml* file and other XML files. One easy way to view
this information is by double-clicking one of the virtual nodes, and typing
*cd ..* in the shell to go up to the session directory.
![](static/single-pc-emane.png)
## Distributed EMANE
Running CORE and EMANE distributed among two or more emulation servers is similar to running on a single machine. There are a few key configuration items that need to be set in order to be successful, and those are outlined here.
Running CORE and EMANE distributed among two or more emulation servers is
similar to running on a single machine. There are a few key configuration
items that need to be set in order to be successful, and those are outlined here.
It is a good idea to maintain separate networks for data (OTA) and control. The control network may be a shared laboratory network, for example, and you do not want multicast traffic on the data network to interfere with other EMANE users. Furthermore, control traffic could interfere with the OTA latency and thoughput and might affect emulation fidelity. The examples described here will use *eth0* as a control interface and *eth1* as a data interface, although using separate interfaces is not strictly required. Note that these interface names refer to interfaces present on the host machine, not virtual interfaces within a node.
It is a good idea to maintain separate networks for data (OTA) and control.
The control network may be a shared laboratory network, for example, and you do
not want multicast traffic on the data network to interfere with other EMANE
users. Furthermore, control traffic could interfere with the OTA latency and
throughput and might affect emulation fidelity. The examples described here will
use *eth0* as a control interface and *eth1* as a data interface, although
using separate interfaces is not strictly required. Note that these interface
names refer to interfaces present on the host machine, not virtual interfaces
within a node.
**IMPORTANT: If an auxiliary control network is used, an interface on the host has to be assigned to that network.**
**IMPORTANT: If an auxiliary control network is used, an interface on the host
has to be assigned to that network.**
Each machine that will act as an emulation server needs to have CORE and EMANE installed.
Each machine that will act as an emulation server needs to have CORE and EMANE
installed.
The IP addresses of the available servers are configured from the CORE emulation servers dialog box (choose *Session* then *Emulation servers...*). This list of servers is stored in a *~/.core/servers.conf* file. The dialog shows available servers, some or all of which may be assigned to nodes on the canvas.
The IP addresses of the available servers are configured from the CORE emulation
servers dialog box (choose *Session* then *Emulation servers...*). This list of
servers is stored in a *~/.core/servers.conf* file. The dialog shows available
servers, some or all of which may be assigned to nodes on the canvas.
Nodes need to be assigned to emulation servers. Select several nodes, right-click them, and choose *Assign to* and the name of the desired server. When a node is not assigned to any emulation server, it will be emulated locally. The local machine that the GUI connects with is considered the "master" machine, which in turn connects to the other emulation server "slaves". Public key SSH should be configured from the master to the slaves.
Nodes need to be assigned to emulation servers. Select several nodes,
right-click them, and choose *Assign to* and the name of the desired server.
When a node is not assigned to any emulation server, it will be emulated
locally. The local machine that the GUI connects with is considered the
"master" machine, which in turn connects to the other emulation server
"slaves". Public key SSH should be configured from the master to the slaves.
Under the *EMANE* tab of the EMANE WLAN, click on the *EMANE options* button. This brings up the emane configuration dialog. The *enable OTA Manager channel* should be set to *on*. The *OTA Manager device* and *Event Service device* should be set to a control network device. For example, if you have a primary and auxiliary control network (i.e. controlnet and controlnet1), and you want the OTA traffic to have its dedicated network, set the OTA Manager device to *ctrl1* and the Event Service device to *ctrl0*. The EMANE models can be configured. Click *Apply* to save these settings.
Under the *EMANE* tab of the EMANE WLAN, click on the *EMANE options* button.
This brings up the emane configuration dialog. The *enable OTA Manager channel*
should be set to *on*. The *OTA Manager device* and *Event Service device*
should be set to a control network device. For example, if you have a primary
and auxiliary control network (i.e. controlnet and controlnet1), and you want
the OTA traffic to have its dedicated network, set the OTA Manager device to
*ctrl1* and the Event Service device to *ctrl0*. The EMANE models can be
configured. Click *Apply* to save these settings.
![](static/distributed-emane-configuration.png)
**HINT:**
Here is a quick checklist for distributed emulation with EMANE.
> **NOTE:** Here is a quick checklist for distributed emulation with EMANE.
1. Follow the steps outlined for normal CORE.
2. Under the *EMANE* tab of the EMANE WLAN, click on *EMANE options*.
@ -132,10 +237,22 @@ Under the *EMANE* tab of the EMANE WLAN, click on the *EMANE options* button. Th
6. Press the *Start* button to launch the distributed emulation.
Now when the Start button is used to instantiate the emulation, the local CORE Python daemon will connect to other emulation servers that have been assigned to nodes. Each server will have its own session directory where the *platform.xml* file and other EMANE XML files are generated. The NEM IDs are automatically coordinated across servers so there is no overlap. Each server also gets its own Platform ID.
Now when the Start button is used to instantiate the emulation, the local CORE
Python daemon will connect to other emulation servers that have been assigned
to nodes. Each server will have its own session directory where the
*platform.xml* file and other EMANE XML files are generated. The NEM IDs are
automatically coordinated across servers so there is no overlap. Each server
also gets its own Platform ID.
An Ethernet device is used for disseminating multicast EMANE events, as specified in the *configure emane* dialog. EMANE's Event Service can be run with mobility or pathloss scripts as described in :ref:`Single_PC_with_EMANE`. If CORE is not subscribed to location events, it will generate them as nodes are moved on the canvas.
An Ethernet device is used for disseminating multicast EMANE events, as
specified in the *configure emane* dialog. EMANE's Event Service can be run
with mobility or pathloss scripts as described in :ref:`Single_PC_with_EMANE`.
If CORE is not subscribed to location events, it will generate them as nodes
are moved on the canvas.
Double-clicking on a node during runtime will cause the GUI to attempt to SSH to the emulation server for that node and run an interactive shell. The public key SSH configuration should be tested with all emulation servers prior to starting the emulation.
Double-clicking on a node during runtime will cause the GUI to attempt to SSH
to the emulation server for that node and run an interactive shell. The public
key SSH configuration should be tested with all emulation servers prior to
starting the emulation.
![](static/distributed-emane-network.png)

View file

@ -4,7 +4,8 @@ gRPC is the main API for interfacing with CORE.
## HTTP Proxy
Since gRPC is HTTP2 based, proxy configurations can cause issue. Clear out your proxy when running if needed.
Since gRPC is HTTP2 based, proxy configurations can cause issue. Clear out your
proxy when running if needed.
## Python Client
@ -14,7 +15,6 @@ Below is a small example using it.
```python
import logging
from builtins import range
from core.api.grpc import client, core_pb2

738
docs/gui.md Normal file
View file

@ -0,0 +1,738 @@
# Using the CORE GUI
* Table of Contents
{:toc}
The following image shows the CORE GUI:
![](static/core_screenshot.png)
## Overview
The GUI is used to draw nodes and network devices on a canvas, linking them
together to create an emulated network session.
After pressing the start button, CORE will proceed through these phases,
staying in the **runtime** phase. After the session is stopped, CORE will
proceed to the **data collection** phase before tearing down the emulated
state.
CORE can be customized to perform any action at each state. See the
**Hooks...** entry on the [Session Menu](#session-menu) for details about
when these session states are reached.
## Prerequisites
Beyond installing CORE, you must have the CORE daemon running. This is done
on the command line with either systemd or sysv.
```shell
# systemd
sudo systemctl daemon-reload
sudo systemctl start core-daemon
# sysv
sudo service core-daemon start
```
You can also invoke the daemon directly from the command line, which can be
useful if you'd like to see the logging output directly.
```shell
# direct invocation
sudo core-daemon
```
## Modes of Operation
The CORE GUI has two primary modes of operation, **Edit** and **Execute**
modes. Running the GUI, by typing **core-gui** with no options, starts in
Edit mode. Nodes are drawn on a blank canvas using the toolbar on the left
and configured from right-click menus or by double-clicking them. The GUI
does not need to be run as root.
Once editing is complete, pressing the green **Start** button (or choosing
**Execute** from the **Session** menu) instantiates the topology within the
Linux kernel and enters Execute mode. In execute mode, the user can interact
with the running emulated machines by double-clicking or right-clicking on
them. The editing toolbar disappears and is replaced by an execute toolbar,
which provides tools while running the emulation. Pressing the red **Stop**
button (or choosing **Terminate** from the **Session** menu) will destroy
the running emulation and return CORE to Edit mode.
CORE can be started directly in Execute mode by specifying **--start** and a
topology file on the command line:
```shell
core-gui --start ~/.core/configs/myfile.imn
```
Once the emulation is running, the GUI can be closed, and a prompt will appear
asking if the emulation should be terminated. The emulation may be left
running and the GUI can reconnect to an existing session at a later time.
The GUI can be run as a normal user on Linux.
The GUI can be connected to a different address or TCP port using the
**--address** and/or **--port** options. The defaults are shown below.
```shell
core-gui --address 127.0.0.1 --port 4038
```
## Toolbar
The toolbar is a row of buttons that runs vertically along the left side of the
CORE GUI window. The toolbar changes depending on the mode of operation.
### Editing Toolbar
When CORE is in Edit mode (the default), the vertical Editing Toolbar exists on
the left side of the CORE window. Below are brief descriptions for each toolbar
item, starting from the top. Most of the tools are grouped into related
sub-menus, which appear when you click on their group icon.
| Icon | Name | Description |
|---|---|---|
| ![](static/gui/select.gif) | Selection Tool | Tool for selecting, moving, configuring nodes. |
| ![](static/gui/start.gif) | Start Button | Starts Execute mode, instantiates the emulation. |
| ![](static/gui/link.gif) | Link | Allows network links to be drawn between two nodes by clicking and dragging the mouse. |
### CORE Nodes
These nodes will create a new node container and run associated services.
| Icon | Name | Description |
|---|---|---|
| ![](static/gui/router.gif) | Router | Runs Quagga OSPFv2 and OSPFv3 routing to forward packets. |
| ![](static/gui/host.gif) | Host | Emulated server machine having a default route, runs SSH server. |
| ![](static/gui/pc.gif) | PC | Basic emulated machine having a default route, runs no processes by default. |
| ![](static/gui/mdr.gif) | MDR | Runs Quagga OSPFv3 MDR routing for MANET-optimized routing. |
| ![](static/gui/router_green.gif) | PRouter | Physical router represents a real testbed machine. |
| ![](static/gui/document-properties.gif) | Edit | Bring up the custom node dialog. |
### Network Nodes
These nodes are mostly used to create a Linux bridge that serves the
purpose described below.
| Icon | Name | Description |
|---|---|---|
| ![](static/gui/hub.gif) | Hub | Ethernet hub forwards incoming packets to every connected node. |
| ![](static/gui/lanswitch.gif) | Switch | Ethernet switch intelligently forwards incoming packets to attached hosts using an Ethernet address hash table. |
| ![](static/gui/wlan.gif) | Wireless LAN | When routers are connected to this WLAN node, they join a wireless network and an antenna is drawn instead of a connecting line; the WLAN node typically controls connectivity between attached wireless nodes based on the distance between them. |
| ![](static/gui/rj45.gif) | RJ45 | RJ45 Physical Interface Tool, emulated nodes can be linked to real physical interfaces; using this tool, real networks and devices can be physically connected to the live-running emulation. |
| ![](static/gui/tunnel.gif) | Tunnel | Tool allows connecting together more than one CORE emulation using GRE tunnels. |
### Annotation Tools
| Icon | Name | Description |
|---|---|---|
| ![](static/gui/marker.gif) | Marker | For drawing marks on the canvas. |
| ![](static/gui/oval.gif) | Oval | For drawing circles on the canvas that appear in the background. |
| ![](static/gui/rectangle.gif) | Rectangle | For drawing rectangles on the canvas that appear in the background. |
| ![](static/gui/text.gif) | Text | For placing text captions on the canvas. |
### Execution Toolbar
When the Start button is pressed, CORE switches to Execute mode, and the Edit
toolbar on the left of the CORE window is replaced with the Execution toolbar
Below are the items on this toolbar, starting from the top.
| Icon | Name | Description |
|---|---|---|
| ![](static/gui/select.gif) | Selection Tool | In Execute mode, the Selection Tool can be used for moving nodes around the canvas, and double-clicking on a node will open a shell window for that node; right-clicking on a node invokes a pop-up menu of run-time options for that node. |
| ![](static/gui/stop.gif) | Stop Button | Stops Execute mode, terminates the emulation, returns CORE to edit mode. |
| ![](static/gui/observe.gif) | Observer Widgets Tool | Clicking on this magnifying glass icon invokes a menu for easily selecting an Observer Widget. The icon has a darker gray background when an Observer Widget is active, during which time moving the mouse over a node will pop up an information display for that node. |
| ![](static/gui/marker.gif) | Marker | For drawing freehand lines on the canvas, useful during demonstrations; markings are not saved. |
| ![](static/gui/twonode.gif) | Two-node Tool | Click to choose a starting and ending node, and run a one-time *traceroute* between those nodes or a continuous *ping -R* between nodes. The output is displayed in real time in a results box, while the IP addresses are parsed and the complete network path is highlighted on the CORE display. |
| ![](static/gui/run.gif) | Run Tool | This tool allows easily running a command on all or a subset of all nodes. A list box allows selecting any of the nodes. A text entry box allows entering any command. The command should return immediately, otherwise the display will block awaiting response. The *ping* command, for example, with no parameters, is not a good idea. The result of each command is displayed in a results box. The first occurrence of the special text "NODE" will be replaced with the node name. The command will not be attempted to run on nodes that are not routers, PCs, or hosts, even if they are selected. |
## Menu
The menubar runs along the top of the CORE GUI window and provides access to a
variety of features. Some of the menus are detachable, such as the *Widgets*
menu, by clicking the dashed line at the top.
### File Menu
The File menu contains options for manipulating the **.imn** Configuration
Files. Generally, these menu items should not be used in Execute mode.
| Option | Description |
|---|---|
| New | This starts a new file with an empty canvas. |
| Open | Invokes the File Open dialog box for selecting a new **.imn** or XML file to open. You can change the default path used for this dialog in the Preferences Dialog. |
| Save | Saves the current topology. If you have not yet specified a file name, the Save As dialog box is invoked. |
| Save As XML | Invokes the Save As dialog box for selecting a new **.xml** file for saving the current configuration in the XML file. |
| Save As imn | Invokes the Save As dialog box for selecting a new **.imn** topology file for saving the current configuration. Files are saved in the *IMUNES network configuration* file. |
| Export Python script | Prints Python snippets to the console, for inclusion in a CORE Python script. |
| Execute XML or Python script | Invokes a File Open dialog box for selecting an XML file to run or a Python script to run and automatically connect to. If a Python script, the script must create a new CORE Session and add this session to the daemon's list of sessions in order for this to work. |
| Execute Python script with options | Invokes a File Open dialog box for selecting a Python script to run and automatically connect to. After a selection is made, a Python Script Options dialog box is invoked to allow for command-line options to be added. The Python script must create a new CORE Session and add this session to the daemon's list of sessions in order for this to work. |
| Open current file in editor | This opens the current topology file in the **vim** text editor. First you need to save the file. Once the file has been edited with a text editor, you will need to reload the file to see your changes. The text editor can be changed from the Preferences Dialog. |
| Print | This uses the Tcl/Tk postscript command to print the current canvas to a printer. A dialog is invoked where you can specify a printing command, the default being **lpr**. The postscript output is piped to the print command. |
| Save screenshot | Saves the current canvas as a postscript graphic file. |
| Recently used files | Above the Quit menu command is a list of recently use files, if any have been opened. You can clear this list in the Preferences dialog box. You can specify the number of files to keep in this list from the Preferences dialog. Click on one of the file names listed to open that configuration file. |
| Quit | The Quit command should be used to exit the CORE GUI. CORE may prompt for termination if you are currently in Execute mode. Preferences and the recently-used files list are saved. |
### Edit Menu
| Option | Description |
|---|---|
| Undo | Attempts to undo the last edit in edit mode. |
| Redo | Attempts to redo an edit that has been undone. |
| Cut, Copy, Paste | Used to cut, copy, and paste a selection. When nodes are pasted, their node numbers are automatically incremented, and existing links are preserved with new IP addresses assigned. Services and their customizations are copied to the new node, but care should be taken as node IP addresses have changed with possibly old addresses remaining in any custom service configurations. Annotations may also be copied and pasted.
| Select All | Selects all items on the canvas. Selected items can be moved as a group. |
| Select Adjacent | Select all nodes that are linked to the already selected node(s). For wireless nodes this simply selects the WLAN node(s) that the wireless node belongs to. You can use this by clicking on a node and pressing CTRL+N to select the adjacent nodes. |
| Find... | Invokes the *Find* dialog box. The Find dialog can be used to search for nodes by name or number. Results are listed in a table that includes the node or link location and details such as IP addresses or link parameters. Clicking on a result will focus the canvas on that node or link, switching canvases if necessary. |
| Clear marker | Clears any annotations drawn with the marker tool. Also clears any markings used to indicate a node's status. |
| Preferences... | Invokes the Preferences dialog box. |
### Canvas Menu
The canvas menu provides commands for adding, removing, changing, and switching
to different editing canvases.
| Option | Description |
|---|---|
| New | Creates a new empty canvas at the right of all existing canvases. |
| Manage... | Invokes the *Manage Canvases* dialog box, where canvases may be renamed and reordered, and you can easily switch to one of the canvases by selecting it. |
| Delete | Deletes the current canvas and all items that it contains. |
| Size/scale... | Invokes a Canvas Size and Scale dialog that allows configuring the canvas size, scale, and geographic reference point. The size controls allow changing the width and height of the current canvas, in pixels or meters. The scale allows specifying how many meters are equivalent to 100 pixels. The reference point controls specify the latitude, longitude, and altitude reference point used to convert between geographic and Cartesian coordinate systems. By clicking the *Save as default* option, all new canvases will be created with these properties. The default canvas size can also be changed in the Preferences dialog box.
| Wallpaper... | Used for setting the canvas background image. |
| Previous, Next, First, Last | Used for switching the active canvas to the first, last, or adjacent canvas. |
### View Menu
The View menu features items for controlling what is displayed on the drawing
canvas.
| Option | Description |
|---|---|
| Show | Opens a submenu of items that can be displayed or hidden, such as interface names, addresses, and labels. Use these options to help declutter the display. These options are generally saved in the topology files, so scenarios have a more consistent look when copied from one computer to another. |
| Show hidden nodes | Reveal nodes that have been hidden. Nodes are hidden by selecting one or more nodes, right-clicking one and choosing *hide*. |
| Locked | Toggles locked view; when the view is locked, nodes cannot be moved around on the canvas with the mouse. This could be useful when sharing the topology with someone and you do not expect them to change things. |
| 3D GUI... | Launches a 3D GUI by running the command defined under Preferences, *3D GUI command*. This is typically a script that runs the SDT3D display. SDT is the Scripted Display Tool from NRL that is based on NASA's Java-based WorldWind virtual globe software. |
| Zoom In | Magnifies the display. You can also zoom in by clicking *zoom 100%* label in the status bar, or by pressing the **+** (plus) key. |
| Zoom Out | Reduces the size of the display. You can also zoom out by right-clicking *zoom 100%* label in the status bar or by pressing the **-** (minus) key. |
### Tools Menu
The tools menu lists different utility functions.
| Option | Description |
|---|---|
| Autorearrange all | Automatically arranges all nodes on the canvas. Nodes having a greater number of links are moved to the center. This mode can continue to run while placing nodes. To turn off this autorearrange mode, click on a blank area of the canvas with the select tool, or choose this menu option again. |
| Autorearrange selected | Automatically arranges the selected nodes on the canvas. |
| Align to grid | Moves nodes into a grid formation, starting with the smallest-numbered node in the upper-left corner of the canvas, arranging nodes in vertical columns. |
| Traffic... | Invokes the CORE Traffic Flows dialog box, which allows configuring, starting, and stopping MGEN traffic flows for the emulation. |
| IP addresses... | Invokes the IP Addresses dialog box for configuring which IPv4/IPv6 prefixes are used when automatically addressing new interfaces. |
| MAC addresses... | Invokes the MAC Addresses dialog box for configuring the starting number used as the lowest byte when generating each interface MAC address. This value should be changed when tunneling between CORE emulations to prevent MAC address conflicts. |
| Build hosts file... | Invokes the Build hosts File dialog box for generating **/etc/hosts** file entries based on IP addresses used in the emulation. |
| Renumber nodes... | Invokes the Renumber Nodes dialog box, which allows swapping one node number with another in a few clicks. |
| Experimental... | Menu of experimental options, such as a tool to convert ns-2 scripts to IMUNES imn topologies, supporting only basic ns-2 functionality, and a tool for automatically dividing up a topology into partitions. |
| Topology generator | Opens a submenu of topologies to generate. You can first select the type of node that the topology should consist of, or routers will be chosen by default. Nodes may be randomly placed, aligned in grids, or various other topology patterns. All of the supported patterns are listed in the table below. |
| Debugger... | Opens the CORE Debugger window for executing arbitrary Tcl/Tk commands. |
#### Topology Generator
| Pattern | Description |
|---|---|
| Random | Nodes are randomly placed about the canvas, but are not linked together. This can be used in conjunction with a WLAN node to quickly create a wireless network. |
| Grid | Nodes are placed in horizontal rows starting in the upper-left corner, evenly spaced to the right; nodes are not linked to each other. |
| Connected Grid | Nodes are placed in an N x M (width and height) rectangular grid, and each node is linked to the node above, below, left and right of itself. |
| Chain | Nodes are linked together one after the other in a chain. |
| Star | One node is placed in the center with N nodes surrounding it in a circular pattern, with each node linked to the center node. |
| Cycle | Nodes are arranged in a circular pattern with every node connected to its neighbor to form a closed circular path. |
| Wheel | The wheel pattern links nodes in a combination of both Star and Cycle patterns. |
| Cube | Generate a cube graph of nodes. |
| Clique | Creates a clique graph of nodes, where every node is connected to every other node. |
| Bipartite | Creates a bipartite graph of nodes, having two disjoint sets of vertices. |
### Widgets Menu
Widgets are GUI elements that allow interaction with a running emulation.
Widgets typically automate the running of commands on emulated nodes to report
status information of some type and display this on screen.
#### Periodic Widgets
These Widgets are those available from the main *Widgets* menu. More than one
of these Widgets may be run concurrently. An event loop fires once every second
that the emulation is running. If one of these Widgets is enabled, its periodic
routine will be invoked at this time. Each Widget may have a configuration
dialog box which is also accessible from the *Widgets* menu.
Here are some standard widgets:
* **Adjacency** - displays router adjacency states for Quagga's OSPFv2 and OSPFv3
routing protocols. A line is drawn from each router halfway to the router ID
of an adjacent router. The color of the line is based on the OSPF adjacency
state such as Two-way or Full. To learn about the different colors, see the
*Configure Adjacency...* menu item. The **vtysh** command is used to
dump OSPF neighbor information.
Only half of the line is drawn because each
router may be in a different adjacency state with respect to the other.
* **Throughput** - displays the kilobits-per-second throughput above each link,
using statistics gathered from the ng_pipe Netgraph node that implements each
link. If the throughput exceeds a certain threshold, the link will become
highlighted. For wireless nodes which broadcast data to all nodes in range,
the throughput rate is displayed next to the node and the node will become
circled if the threshold is exceeded.
#### Observer Widgets
These Widgets are available from the *Observer Widgets* submenu of the
*Widgets* menu, and from the Widgets Tool on the toolbar. Only one Observer Widget may
be used at a time. Mouse over a node while the session is running to pop up
an informational display about that node.
Available Observer Widgets include IPv4 and IPv6 routing tables, socket
information, list of running processes, and OSPFv2/v3 neighbor information.
Observer Widgets may be edited by the user and rearranged. Choosing *Edit...*
from the Observer Widget menu will invoke the Observer Widgets dialog. A list
of Observer Widgets is displayed along with up and down arrows for rearranging
the list. Controls are available for renaming each widget, for changing the
command that is run during mouse over, and for adding and deleting items from
the list. Note that specified commands should return immediately to avoid
delays in the GUI display. Changes are saved to a **widgets.conf** file in
the CORE configuration directory.
### Session Menu
The Session Menu has entries for starting, stopping, and managing sessions,
in addition to global options such as node types, comments, hooks, servers,
and options.
| Option | Description |
|---|---|
| Start or Stop | This starts or stops the emulation, performing the same function as the green Start or red Stop button. |
| Change sessions... | Invokes the CORE Sessions dialog box containing a list of active CORE sessions in the daemon. Basic session information such as name, node count, start time, and a thumbnail are displayed. This dialog allows connecting to different sessions, shutting them down, or starting a new session. |
| Node types... | Invokes the CORE Node Types dialog, performing the same function as the Edit button on the Network-Layer Nodes toolbar. |
| Comments... | Invokes the CORE Session Comments window where optional text comments may be specified. These comments are saved at the top of the configuration file, and can be useful for describing the topology or how to use the network. |
| Hooks... | Invokes the CORE Session Hooks window where scripts may be configured for a particular session state. The session states are defined in the [table](#session-states) below. The top of the window has a list of configured hooks, and buttons on the bottom left allow adding, editing, and removing hook scripts. The new or edit button will open a hook script editing window. A hook script is a shell script invoked on the host (not within a virtual node). |
| Reset node positions | If you have moved nodes around using the mouse or by using a mobility module, choosing this item will reset all nodes to their original position on the canvas. The node locations are remembered when you first press the Start button. |
| Emulation servers... | Invokes the CORE emulation servers dialog for configuring. |
| Change Sessions... | Invokes the Sessions dialog for switching between different running sessions. This dialog is presented during startup when one or more sessions are already running. |
| Options... | Presents per-session options, such as the IPv4 prefix to be used, if any, for a control network the ability to preserve the session directory; and an on/off switch for SDT3D support. |
#### Session States
| State | Description |
|---|---|
| definition | Used by the GUI to tell the backend to clear any state. |
| configuration | When the user presses the *Start* button, node, link, and other configuration data is sent to the backend. This state is also reached when the user customizes a service. |
| instantiation | After configuration data has been sent, just before the nodes are created. |
| runtime | All nodes and networks have been built and are running. (This is the same state at which the previously-named *global experiment script* was run.)
| datacollect | The user has pressed the *Stop* button, but before services have been stopped and nodes have been shut down. This is a good time to collect log files and other data from the nodes. |
| shutdown | All nodes and networks have been shut down and destroyed. |
### Help Menu
| Option | Description |
|---|---|
| CORE Github (www) | Link to the CORE GitHub page. |
| CORE Documentation (www) | Lnk to the CORE Documentation page. |
| About | Invokes the About dialog box for viewing version information. |
## Connecting with Physical Networks
CORE's emulated networks run in real time, so they can be connected to live
physical networks. The RJ45 tool and the Tunnel tool help with connecting to
the real world. These tools are available from the *Link-layer nodes* menu.
When connecting two or more CORE emulations together, MAC address collisions
should be avoided. CORE automatically assigns MAC addresses to interfaces when
the emulation is started, starting with **00:00:00:aa:00:00** and incrementing
the bottom byte. The starting byte should be changed on the second CORE machine
using the *MAC addresses...* option from the *Tools* menu.
### RJ45 Tool
The RJ45 node in CORE represents a physical interface on the real CORE machine.
Any real-world network device can be connected to the interface and communicate
with the CORE nodes in real time.
The main drawback is that one physical interface is required for each
connection. When the physical interface is assigned to CORE, it may not be used
for anything else. Another consideration is that the computer or network that
you are connecting to must be co-located with the CORE machine.
To place an RJ45 connection, click on the *Link-layer nodes* toolbar and select
the *RJ45 Tool* from the submenu. Click on the canvas near the node you want to
connect to. This could be a router, hub, switch, or WLAN, for example. Now
click on the *Link Tool* and draw a link between the RJ45 and the other node.
The RJ45 node will display "UNASSIGNED". Double-click the RJ45 node to assign a
physical interface. A list of available interfaces will be shown, and one may
be selected by double-clicking its name in the list, or an interface name may
be entered into the text box.
> **NOTE:** When you press the Start button to instantiate your topology, the
interface assigned to the RJ45 will be connected to the CORE topology. The
interface can no longer be used by the system. For example, if there was an
IP address assigned to the physical interface before execution, the address
will be removed and control given over to CORE. No IP address is needed; the
interface is put into promiscuous mode so it will receive all packets and
send them into the emulated world.
Multiple RJ45 nodes can be used within CORE and assigned to the same physical
interface if 802.1x VLANs are used. This allows for more RJ45 nodes than
physical ports are available, but the (e.g. switching) hardware connected to
the physical port must support the VLAN tagging, and the available bandwidth
will be shared.
You need to create separate VLAN virtual devices on the Linux host,
and then assign these devices to RJ45 nodes inside of CORE. The VLANning is
actually performed outside of CORE, so when the CORE emulated node receives a
packet, the VLAN tag will already be removed.
Here are example commands for creating VLAN devices under Linux:
```shell
ip link add link eth0 name eth0.1 type vlan id 1
ip link add link eth0 name eth0.2 type vlan id 2
ip link add link eth0 name eth0.3 type vlan id 3
```
### Tunnel Tool
The tunnel tool builds GRE tunnels between CORE emulations or other hosts.
Tunneling can be helpful when the number of physical interfaces is limited or
when the peer is located on a different network. Also a physical interface does
not need to be dedicated to CORE as with the RJ45 tool.
The peer GRE tunnel endpoint may be another CORE machine or another
host that supports GRE tunneling. When placing a Tunnel node, initially
the node will display "UNASSIGNED". This text should be replaced with the IP
address of the tunnel peer. This is the IP address of the other CORE machine or
physical machine, not an IP address of another virtual node.
> **NOTE:** Be aware of possible MTU (Maximum Transmission Unit) issues with GRE devices. The *gretap* device
has an interface MTU of 1,458 bytes; when joined to a Linux bridge, the
bridge's MTU
becomes 1,458 bytes. The Linux bridge will not perform fragmentation for
large packets if other bridge ports have a higher MTU such as 1,500 bytes.
The GRE key is used to identify flows with GRE tunneling. This allows multiple
GRE tunnels to exist between that same pair of tunnel peers. A unique number
should be used when multiple tunnels are used with the same peer. When
configuring the peer side of the tunnel, ensure that the matching keys are
used.
Here are example commands for building the other end of a tunnel on a Linux
machine. In this example, a router in CORE has the virtual address
**10.0.0.1/24** and the CORE host machine has the (real) address
**198.51.100.34/24**. The Linux box
that will connect with the CORE machine is reachable over the (real) network
at **198.51.100.76/24**.
The emulated router is linked with the Tunnel Node. In the
Tunnel Node configuration dialog, the address **198.51.100.76** is entered, with
the key set to **1**. The gretap interface on the Linux box will be assigned
an address from the subnet of the virtual router node,
**10.0.0.2/24**.
```shell
# these commands are run on the tunnel peer
sudo ip link add gt0 type gretap remote 198.51.100.34 local 198.51.100.76 key 1
sudo ip addr add 10.0.0.2/24 dev gt0
sudo ip link set dev gt0 up
```
Now the virtual router should be able to ping the Linux machine:
```shell
# from the CORE router node
ping 10.0.0.2
```
And the Linux machine should be able to ping inside the CORE emulation:
```shell
# from the tunnel peer
ping 10.0.0.1
```
To debug this configuration, **tcpdump** can be run on the gretap devices, or
on the physical interfaces on the CORE or Linux machines. Make sure that a
firewall is not blocking the GRE traffic.
### Communicating with the Host Machine
The host machine that runs the CORE GUI and/or daemon is not necessarily
accessible from a node. Running an X11 application on a node, for example,
requires some channel of communication for the application to connect with
the X server for graphical display. There are several different ways to
connect from the node to the host and vice versa.
#### Control Network
The quickest way to connect with the host machine through the primary control
network.
With a control network, the host can launch an X11 application on a node.
To run an X11 application on the node, the **SSH** service can be enabled on
the node, and SSH with X11 forwarding can be used from the host to the node.
```shell
# SSH from host to node n5 to run an X11 app
ssh -X 172.16.0.5 xclock
```
Note that the **coresendmsg** utility can be used for a node to send
messages to the CORE daemon running on the host (if the **listenaddr = 0.0.0.0**
is set in the **/etc/core/core.conf** file) to interact with the running
emulation. For example, a node may move itself or other nodes, or change
its icon based on some node state.
#### Other Methods
There are still other ways to connect a host with a node. The RJ45 Tool
can be used in conjunction with a dummy interface to access a node:
```shell
sudo modprobe dummy numdummies=1
```
A **dummy0** interface should appear on the host. Use the RJ45 tool assigned
to **dummy0**, and link this to a node in your scenario. After starting the
session, configure an address on the host.
```shell
sudo ip link show type bridge
# determine bridge name from the above command
# assign an IP address on the same network as the linked node
sudo ip addr add 10.0.1.2/24 dev b.48304.34658
```
In the example shown above, the host will have the address **10.0.1.2** and
the node linked to the RJ45 may have the address **10.0.1.1**.
## Building Sample Networks
### Wired Networks
Wired networks are created using the *Link Tool* to draw a link between two
nodes. This automatically draws a red line representing an Ethernet link and
creates new interfaces on network-layer nodes.
Double-click on the link to invoke the *link configuration* dialog box. Here
you can change the Bandwidth, Delay, Loss, and Duplicate
rate parameters for that link. You can also modify the color and width of the
link, affecting its display.
Link-layer nodes are provided for modeling wired networks. These do not create
a separate network stack when instantiated, but are implemented using Linux bridging.
These are the hub, switch, and wireless LAN nodes. The hub copies each packet from
the incoming link to every connected link, while the switch behaves more like an
Ethernet switch and keeps track of the Ethernet address of the connected peer,
forwarding unicast traffic only to the appropriate ports.
The wireless LAN (WLAN) is covered in the next section.
### Wireless Networks
The wireless LAN node allows you to build wireless networks where moving nodes
around affects the connectivity between them. Connection between a pair of nodes is stronger
when the nodes are closer while connection is weaker when the nodes are further away.
The wireless LAN, or WLAN, node appears as a small cloud. The WLAN offers
several levels of wireless emulation fidelity, depending on your modeling needs.
The WLAN tool can be extended with plug-ins for different levels of wireless
fidelity. The basic on/off range is the default setting available on all
platforms. Other plug-ins offer higher fidelity at the expense of greater
complexity and CPU usage. The availability of certain plug-ins varies depending
on platform. See the table below for a brief overview of wireless model types.
|Model|Type|Supported Platform(s)|Fidelity|Description|
|-----|----|---------------------|--------|-----------|
|Basic|on/off|Linux|Low|Ethernet bridging with ebtables|
|EMANE|Plug-in|Linux|High|TAP device connected to EMANE emulator with pluggable MAC and PHY radio types|
To quickly build a wireless network, you can first place several router nodes
onto the canvas. If you have the
Quagga MDR software installed, it is
recommended that you use the *mdr* node type for reduced routing overhead. Next
choose the *wireless LAN* from the *Link-layer nodes* submenu. First set the
desired WLAN parameters by double-clicking the cloud icon. Then you can link
all of the routers by right-clicking on the WLAN and choosing *Link to all
routers*.
Linking a router to the WLAN causes a small antenna to appear, but no red link
line is drawn. Routers can have multiple wireless links and both wireless and
wired links (however, you will need to manually configure route
redistribution.) The mdr node type will generate a routing configuration that
enables OSPFv3 with MANET extensions. This is a Boeing-developed extension to
Quagga's OSPFv3 that reduces flooding overhead and optimizes the flooding
procedure for mobile ad-hoc (MANET) networks.
The default configuration of the WLAN is set to use the basic range model,
using the *Basic* tab in the WLAN configuration dialog. Having this model
selected causes **core-daemon** to calculate the distance between nodes based
on screen pixels. A numeric range in screen pixels is set for the wireless
network using the *Range* slider. When two wireless nodes are within range of
each other, a green line is drawn between them and they are linked. Two
wireless nodes that are farther than the range pixels apart are not linked.
During Execute mode, users may move wireless nodes around by clicking and
dragging them, and wireless links will be dynamically made or broken.
The *EMANE* tab lists available EMANE models to use for wireless networking.
See the [EMANE](emane.md) chapter for details on using EMANE.
### Mobility Scripting
CORE has a few ways to script mobility.
| Option | Description |
|---|---|
| ns-2 script | The script specifies either absolute positions or waypoints with a velocity. Locations are given with Cartesian coordinates. |
| CORE API | An external entity can move nodes by sending CORE API Node messages with updated X,Y coordinates; the **coresendmsg** utility allows a shell script to generate these messages. |
| EMANE events | See [EMANE](emane.md) for details on using EMANE scripts to move nodes around. Location information is typically given as latitude, longitude, and altitude. |
For the first method, you can create a mobility script using a text
editor, or using a tool such as [BonnMotion](http://net.cs.uni-bonn.de/wg/cs/applications/bonnmotion/), and associate the script with one of the wireless
using the WLAN configuration dialog box. Click the *ns-2 mobility script...*
button, and set the *mobility script file* field in the resulting *ns2script*
configuration dialog.
Here is an example for creating a BonnMotion script for 10 nodes:
```shell
bm -f sample RandomWaypoint -n 10 -d 60 -x 1000 -y 750
bm NSFile -f sample
# use the resulting 'sample.ns_movements' file in CORE
```
When the Execute mode is started and one of the WLAN nodes has a mobility
script, a mobility script window will appear. This window contains controls for
starting, stopping, and resetting the running time for the mobility script. The
*loop* checkbox causes the script to play continuously. The *resolution* text
box contains the number of milliseconds between each timer event; lower values
cause the mobility to appear smoother but consumes greater CPU time.
The format of an ns-2 mobility script looks like:
```shell
# nodes: 3, max time: 35.000000, max x: 600.00, max y: 600.00
$node_(2) set X_ 144.0
$node_(2) set Y_ 240.0
$node_(2) set Z_ 0.00
$ns_ at 1.00 "$node_(2) setdest 130.0 280.0 15.0"
```
The first three lines set an initial position for node 2. The last line in the
above example causes node 2 to move towards the destination **(130, 280)** at
speed **15**. All units are screen coordinates, with speed in units per second.
The total script time is learned after all nodes have reached their waypoints.
Initially, the time slider in the mobility script dialog will not be
accurate.
Examples mobility scripts (and their associated topology files) can be found
in the **configs/** directory.
## Multiple Canvases
CORE supports multiple canvases for organizing emulated nodes. Nodes running on
different canvases may be linked together.
To create a new canvas, choose *New* from the *Canvas* menu. A new canvas tab
appears in the bottom left corner. Clicking on a canvas tab switches to that
canvas. Double-click on one of the tabs to invoke the *Manage Canvases* dialog
box. Here, canvases may be renamed and reordered, and you can easily switch to
one of the canvases by selecting it.
Each canvas maintains its own set of nodes and annotations. To link between
canvases, select a node and right-click on it, choose *Create link to*, choose
the target canvas from the list, and from that submenu the desired node. A
pseudo-link will be drawn, representing the link between the two nodes on
different canvases. Double-clicking on the label at the end of the arrow will
jump to the canvas that it links.
## Check Emulation Light (CEL)
The |cel| Check Emulation Light, or CEL, is located in the bottom right-hand corner
of the status bar in the CORE GUI. This is a yellow icon that indicates one or
more problems with the running emulation. Clicking on the CEL will invoke the
CEL dialog.
The Check Emulation Light dialog contains a list of exceptions received from
the CORE daemon. An exception has a time, severity level, optional node number,
and source. When the CEL is blinking, this indicates one or more fatal
exceptions. An exception with a fatal severity level indicates that one or more
of the basic pieces of emulation could not be created, such as failure to
create a bridge or namespace, or the failure to launch EMANE processes for an
EMANE-based network.
Clicking on an exception displays details for that
exception. If a node number is specified, that node is highlighted on the
canvas when the exception is selected. The exception source is a text string
to help trace where the exception occurred; "service:UserDefined" for example,
would appear for a failed validation command with the UserDefined service.
Buttons are available at the bottom of the dialog for clearing the exception
list and for viewing the CORE daemon and node log files.
> **NOTE:** In batch mode, exceptions received from the CORE daemon are displayed on
the console.
## Configuration Files
Configurations are saved to **xml** or **.imn** topology files using
the *File* menu. You
can easily edit these files with a text editor.
Any time you edit the topology
file, you will need to stop the emulation if it were running and reload the
file.
The **.imn** file format comes from IMUNES, and is
basically Tcl lists of nodes, links, etc.
Tabs and spacing in the topology files are important. The file starts by
listing every node, then links, annotations, canvases, and options. Each entity
has a block contained in braces. The first block is indented by four spaces.
Within the **network-config** block (and any *custom-*-config* block), the
indentation is one tab character.
> **NOTE:** There are several topology examples included with CORE in
the **configs/** directory.
This directory can be found in **~/.core/configs**, or
installed to the filesystem
under **/usr[/local]/share/examples/configs**.
> **NOTE:** When using the **.imn** file format, file paths for things like custom
icons may contain the special variables **$CORE_DATA_DIR** or **$CONFDIR** which
will be substituted with **/usr/share/core** or **~/.core/configs**.
> **NOTE:** Feel free to edit the files directly using your favorite text editor.
## Customizing your Topology's Look
Several annotation tools are provided for changing the way your topology is
presented. Captions may be added with the Text tool. Ovals and rectangles may
be drawn in the background, helpful for visually grouping nodes together.
During live demonstrations the marker tool may be helpful for drawing temporary
annotations on the canvas that may be quickly erased. A size and color palette
appears at the bottom of the toolbar when the marker tool is selected. Markings
are only temporary and are not saved in the topology file.
The basic node icons can be replaced with a custom image of your choice. Icons
appear best when they use the GIF or PNG format with a transparent background.
To change a node's icon, double-click the node to invoke its configuration
dialog and click on the button to the right of the node name that shows the
node's current icon.
A background image for the canvas may be set using the *Wallpaper...* option
from the *Canvas* menu. The image may be centered, tiled, or scaled to fit the
canvas size. An existing terrain, map, or network diagram could be used as a
background, for example, with CORE nodes drawn on top.
## Preferences
The *Preferences* Dialog can be accessed from the **Edit_Menu**. There are
numerous defaults that can be set with this dialog, which are stored in the
**~/.core/prefs.conf** preferences file.

View file

@ -5,7 +5,7 @@
CORE (Common Open Research Emulator) is a tool for building virtual networks. As an emulator, CORE builds a
representation of a real computer network that runs in real time, as opposed to simulation, where abstract models are
used. The live-running emulation can be connected to physical networks and routers. It provides an environment for
running real applications and protocols, taking advantage of virtualization provided by the Linux operating system.
running real applications and protocols, taking advantage of tools provided by the Linux operating system.
CORE is typically used for network and protocol research, demonstrations, application and platform testing, evaluating
networking scenarios, security studies, and increasing the size of physical test networks.
@ -21,17 +21,17 @@ networking scenarios, security studies, and increasing the size of physical test
| Topic | Description|
|-------|------------|
|[Architecture](architecture.md)|Overview of the architecture|
|[Installation](install.md)|Installing from source, packages, & other dependencies|
|[Using the GUI](usage.md)|Details on the different node types and options in the GUI|
|[Distributed](distributed.md)|Overview and detals for running CORE across multiple servers|
|[Installation](install.md)|How to install CORE and its requirements|
|[GUI](gui.md)|How to use the GUI|
|[Distributed](distributed.md)|Details for running CORE across multiple servers|
|[Python Scripting](scripting.md)|How to write python scripts for creating a CORE session|
|[gRPC API](grpc.md)|How to enable and use the gRPC API|
|[Node Types](machine.md)|Overview of node types supported within CORE|
|[Node Types](nodetypes.md)|Overview of node types supported within CORE|
|[CTRLNET](ctrlnet.md)|How to use control networks to communicate with nodes from host|
|[Services](services.md)|Overview of provided services and creating custom ones|
|[EMANE](emane.md)|Overview of EMANE integration and integrating custom EMANE models|
|[Performance](performance.md)|Notes on performance when using CORE|
|[Developers Guide](devguide.md)|Overview of topics when developing CORE|
|[Developers Guide](devguide.md)|Overview on how to contribute to CORE|
## Credits
@ -45,4 +45,4 @@ Python framework and has made significant contributions. Claudiu Danilov, Rod Sa
Phil Spagnolo, and Ian Chakeres have contributed code to CORE. Dan Mackley helped develop the CORE API, originally to
interface with a simulator. Jae Kim and Tom Henderson have supervised the project and provided direction.
Copyright (c) 2005-2018, the Boeing Company.
Copyright (c) 2005-2020, the Boeing Company.

View file

@ -10,19 +10,19 @@ This section will describe how to install CORE from source or from a pre-built p
## Required Hardware
Any computer capable of running Linux should be able to run CORE. Since the physical machine will be hosting numerous
virtual machines, as a general rule you should select a machine having as much RAM and CPU resources as possible.
containers, as a general rule you should select a machine having as much RAM and CPU resources as possible.
## Operating System
CORE requires a Linux operating system because it uses virtualization provided by the kernel. It does not run on
Windows or Mac OS X operating systems (unless it is running within a virtual machine guest.) The virtualization
CORE requires a Linux operating system because it uses namespacing provided by the kernel. It does not run on
Windows or Mac OS X operating systems (unless it is running within a virtual machine guest.) The
technology that CORE currently uses is Linux network namespaces.
Ubuntu and CentOS Linux are the recommended distributions for running CORE. However, these distributions are
not strictly required. CORE will likely work on other flavors of Linux as well, assuming dependencies are met.
**NOTE: CORE Services determine what run on each node. You may require other software packages depending on the
services you wish to use. For example, the HTTP service will require the apache2 package.**
> **NOTE:** CORE Services determine what run on each node. You may require other software packages depending on the
services you wish to use. For example, the HTTP service will require the apache2 package.
## Installed Files
@ -43,6 +43,30 @@ Install Path | Description
/etc/init.d/core-daemon|SysV startup script for daemon
/usr/lib/systemd/system/core-daemon.service|Systemd startup script for daemon
## Automated Install
There is a helper script in the root of the repository that can help automate
the CORE installation. Some steps require commands be ran as sudo and you
will be prompted for a password. This should work on Ubuntu/CentOS and will
install system dependencies, python dependencies, and CORE. This will target
system installations of python 3.6.
```shell
git clone https://github.com/coreemu/core.git
cd core
./install.sh
```
You can target newer system python versions using the **-v** flag. Assuming
these versions are actually available on your system.
```shell
# ubuntu 3.7
./install.sh -v 3.7
# centos 3.7
./install.sh -v 37
```
## Pre-Req Installing Python
Python 3.6 is the minimum required python version. Newer versions can be used if available.

View file

@ -1,22 +0,0 @@
# CORE Node Types
* Table of Contents
{:toc}
## Overview
Different node types can be configured in CORE, and each node type has a *machine type* that indicates how the node will be represented at run time. Different machine types allow for different virtualization options.
## netns nodes
The *netns* machine type is the default. This is for nodes that will be backed by Linux network namespaces. See :ref:`Linux` for a brief explanation of netns. This default machine type is very lightweight, providing a minimum amount of virtualization in order to emulate a network. Another reason this is designated as the default machine type is because this virtualization technology typically requires no changes to the kernel; it is available out-of-the-box from the latest mainstream Linux distributions.
## physical nodes
The *physical* machine type is used for nodes that represent a real Linux-based machine that will participate in the emulated network scenario. This is typically used, for example, to incorporate racks of server machines from an emulation testbed. A physical node is one that is running the CORE daemon (*core-daemon*), but will not be further partitioned into virtual machines. Services that are run on the physical node do not run in an isolated or virtualized environment, but directly on the operating system.
Physical nodes must be assigned to servers, the same way nodes are assigned to emulation servers with *Distributed Emulation*. The list of available physical nodes currently shares the same dialog box and list as the emulation servers, accessed using the *Emulation Servers...* entry from the *Session* menu.
Support for physical nodes is under development and may be improved in future releases. Currently, when any node is linked to a physical node, a dashed line is drawn to indicate network tunneling. A GRE tunneling interface will be created on the physical node and used to tunnel traffic to and from the emulated world.
Double-clicking on a physical node during runtime opens a terminal with an SSH shell to that node. Users should configure public-key SSH login as done with emulation servers.

44
docs/nodetypes.md Normal file
View file

@ -0,0 +1,44 @@
# CORE Node Types
* Table of Contents
{:toc}
## Overview
Different node types can be configured in CORE, and each node type has a
*machine type* that indicates how the node will be represented at run time.
Different machine types allow for different options.
## Netns Nodes
The *netns* machine type is the default. This is for nodes that will be
backed by Linux network namespaces. This machine type uses very little
system resources in order to emulate a network. Another reason this is
designated as the default machine type is because this technology typically
requires no changes to the kernel; it is available out-of-the-box from the
latest mainstream Linux distributions.
## Physical Nodes
The *physical* machine type is used for nodes that represent a real Linux-based
machine that will participate in the emulated network scenario. This is
typically used, for example, to incorporate racks of server machines from an
emulation testbed. A physical node is one that is running the CORE daemon
(*core-daemon*), but will not be further partitioned into containers.
Services that are run on the physical node do not run in an isolated
environment, but directly on the operating system.
Physical nodes must be assigned to servers, the same way nodes are assigned to
emulation servers with *Distributed Emulation*. The list of available physical
nodes currently shares the same dialog box and list as the emulation servers,
accessed using the *Emulation Servers...* entry from the *Session* menu.
Support for physical nodes is under development and may be improved in future
releases. Currently, when any node is linked to a physical node, a dashed line
is drawn to indicate network tunneling. A GRE tunneling interface will be
created on the physical node and used to tunnel traffic to and from the
emulated world.
Double-clicking on a physical node during runtime opens a terminal with an
SSH shell to that node. Users should configure public-key SSH login as done
with emulation servers.

View file

@ -5,9 +5,10 @@
## Overview
The top question about the performance of CORE is often *how many nodes can it handle?* The answer depends on several factors:
The top question about the performance of CORE is often *how many nodes can it
handle?* The answer depends on several factors:
| Factor | How that factor might affect performance |
| Factor | Performance Impact |
|---|---|
| Hardware | the number and speed of processors in the computer, the available processor cache, RAM memory, and front-side bus speed may greatly affect overall performance. |
| Operating system version | distribution of Linux and the specific kernel versions used will affect overall performance. |
@ -16,16 +17,30 @@ The top question about the performance of CORE is often *how many nodes can it h
| GUI usage | widgets that run periodically, mobility scenarios, and other GUI interactions generally consume CPU cycles that may be needed for emulation. |
On a typical single-CPU Xeon 3.0GHz server machine with 2GB RAM running Linux, we have found it reasonable to run 30-75 nodes running OSPFv2 and OSPFv3 routing. On this hardware CORE can instantiate 100 or more nodes, but at that point it becomes critical as to what each of the nodes is doing.
On a typical single-CPU Xeon 3.0GHz server machine with 2GB RAM running Linux,
we have found it reasonable to run 30-75 nodes running OSPFv2 and OSPFv3
routing. On this hardware CORE can instantiate 100 or more nodes, but at
that point it becomes critical as to what each of the nodes is doing.
Because this software is primarily a network emulator, the more appropriate question is *how much network traffic can it handle?* On the same 3.0GHz server described above, running Linux, about 300,000 packets-per-second can be pushed through the system. The number of hops and the size of the packets is less important. The limiting factor is the number of times that the operating system needs to handle a packet. The 300,000 pps figure represents the number of times the system as a whole needed to deal with a packet. As more network hops are added, this increases the number of context switches and decreases the throughput seen on the full length of the network path.
Because this software is primarily a network emulator, the more appropriate
question is *how much network traffic can it handle?* On the same 3.0GHz
server described above, running Linux, about 300,000 packets-per-second can
be pushed through the system. The number of hops and the size of the packets
is less important. The limiting factor is the number of times that the
operating system needs to handle a packet. The 300,000 pps figure represents
the number of times the system as a whole needed to deal with a packet. As
more network hops are added, this increases the number of context switches
and decreases the throughput seen on the full length of the network path.
**NOTE: The right question to be asking is *"how much traffic?"*, not *"how many nodes?"*.**
> **NOTE:** The right question to be asking is *"how much traffic?"*, not
*"how many nodes?"*.
For a more detailed study of performance in CORE, refer to the following publications:
For a more detailed study of performance in CORE, refer to the following
publications:
* J\. Ahrenholz, T. Goff, and B. Adamson, Integration of the CORE and EMANE Network Emulators, Proceedings of the IEEE Military Communications Conference 2011, November 2011.
* Ahrenholz, J., Comparison of CORE Network Emulation Platforms, Proceedings of the IEEE Military Communications Conference 2010, pp. 864-869, November 2010.
* J\. Ahrenholz, C. Danilov, T. Henderson, and J.H. Kim, CORE: A real-time network emulator, Proceedings of IEEE MILCOM Conference, 2008.
* J\. Ahrenholz, T. Goff, and B. Adamson, Integration of the CORE and EMANE
Network Emulators, Proceedings of the IEEE Military Communications Conference 2011, November 2011.
* Ahrenholz, J., Comparison of CORE Network Emulation Platforms, Proceedings
of the IEEE Military Communications Conference 2010, pp. 864-869, November 2010.
* J\. Ahrenholz, C. Danilov, T. Henderson, and J.H. Kim, CORE: A real-time
network emulator, Proceedings of IEEE MILCOM Conference, 2008.

View file

@ -6,11 +6,24 @@
## Overview
CORE can be used via the GUI or Python scripting. Writing your own Python scripts offers a rich programming environment with complete control over all aspects of the emulation. This chapter provides a brief introduction to scripting. Most of the documentation is available from sample scripts, or online via interactive Python.
Writing your own Python scripts offers a rich programming environment with
complete control over all aspects of the emulation. This chapter provides a
brief introduction to scripting. Most of the documentation is available from
sample scripts, or online via interactive Python.
The best starting point is the sample scripts that are included with CORE. If you have a CORE source tree, the example script files can be found under *core/daemon/examples/api/*. When CORE is installed from packages, the example script files will be in */usr/share/core/examples/api/* (or */usr/local/* prefix when installed from source.) For the most part, the example scripts are self-documenting; see the comments contained within the Python code.
The best starting point is the sample scripts that are included with CORE.
If you have a CORE source tree, the example script files can be found under
*core/daemon/examples/python/*. When CORE is installed from packages, the example
script files will be in */usr/share/core/examples/python/* (or */usr/local/*
prefix when installed from source.) For the most part, the example scripts are
self-documenting; see the comments contained within the Python code.
The scripts should be run with root privileges because they create new network namespaces. In general, a CORE Python script does not connect to the CORE daemon, in fact the *core-daemon* is just another Python script that uses the CORE Python modules and exchanges messages with the GUI. To connect the GUI to your scripts, see the included sample scripts that allow for GUI connections.
The scripts should be run with root privileges because they create new network
namespaces. In general, a CORE Python script does not connect to the CORE
daemon, in fact the *core-daemon* is just another Python script that uses
the CORE Python modules and exchanges messages with the GUI. To connect the
GUI to your scripts, see the included sample scripts that allow for GUI
connections.
Here are the basic elements of a CORE Python script:
@ -46,15 +59,23 @@ session.instantiate()
coreemu.shutdown()
```
The above script creates a CORE session having two nodes connected with a switch. The first node pings the second node with 5 ping packets; the result is displayed on screen.
The above script creates a CORE session having two nodes connected with a
switch, Then immediately shutsdown.
A good way to learn about the CORE Python modules is via interactive Python. Scripts can be run using *python -i*. Cut and paste the simple script above and you will have two nodes connected by a hub, with one node running a test ping to the other.
The CORE Python modules are documented with comments in the code. From an
interactive Python shell, you can retrieve online help about the various
classes and methods; for example *help(CoreNode)* or *help(Session)*.
The CORE Python modules are documented with comments in the code. From an interactive Python shell, you can retrieve online help about the various classes and methods; for example *help(nodes.CoreNode)* or *help(Session)*.
> **NOTE:** The CORE daemon *core-daemon* manages a list of sessions and allows
the GUI to connect and control sessions. Your Python script uses the same CORE
modules but runs independently of the daemon. The daemon does not need to be
running for your script to work.
**NOTE: The CORE daemon *core-daemon* manages a list of sessions and allows the GUI to connect and control sessions. Your Python script uses the same CORE modules but runs independently of the daemon. The daemon does not need to be running for your script to work.**
The session created by a Python script may be viewed in the GUI if certain steps are followed. The GUI has a *File Menu*, *Execute Python script...* option for running a script and automatically connecting to it. Once connected, normal GUI interaction is possible, such as moving and double-clicking nodes, activating Widgets, etc.
The session created by a Python script may be viewed in the GUI if certain
steps are followed. The GUI has a *File Menu*, *Execute Python script...*
option for running a script and automatically connecting to it. Once connected,
normal GUI interaction is possible, such as moving and double-clicking nodes,
activating Widgets, etc.
The script should have a line such as the following for running it from the GUI.
@ -63,21 +84,28 @@ if __name__ in ["__main__", "__builtin__"]:
main()
```
A script can add sessions to the core-daemon. A global *coreemu* variable is exposed to the script pointing to the *CoreEmu* object.
The example below has a fallback to a new CoreEmu object, in the case you would like to run the script standalone, outside of the core-daemon.
A script can add sessions to the core-daemon. A global *coreemu* variable is
exposed to the script pointing to the *CoreEmu* object.
The example below has a fallback to a new CoreEmu object, in the case you would
like to run the script standalone, outside of the core-daemon.
```python
coreemu = globals().get("coreemu", CoreEmu())
session = coreemu.create_session()
```
Finally, nodes and networks need to have their coordinates set to something, otherwise they will be grouped at the coordinates *<0, 0>*. First sketching the topology in the GUI and then using the *Export Python script* option may help here.
Finally, nodes and networks need to have their coordinates set to something,
otherwise they will be grouped at the coordinates *<0, 0>*. First sketching
the topology in the GUI and then using the *Export Python script* option may
help here.
```python
switch.setposition(x=80,y=50)
```
A fully-worked example script that you can launch from the GUI is available in the examples directory.
A fully-worked example script that you can launch from the GUI is available
in the examples directory.
## Configuring Services
@ -87,17 +115,14 @@ Examples setting or configuring custom services for a node.
# create session and node
coreemu = CoreEmu()
session = coreemu.create_session()
node = session.add_node()
# create and retrieve custom service
session.services.set_service(node.id, "ServiceName")
custom_service = session.services.get_service(node.id, "ServiceName")
# create node with custom services
options = NodeOptions()
options.services = ["ServiceName"]
node = session.add_node(options=options)
# set custom file data
session.services.set_service_file(node.id, "ServiceName", "FileName", "custom file data")
# set services to a node, using custom services when defined
session.services.add_services(node, node.type, ["Service1", "Service2"])
```
# Configuring EMANE Models

View file

@ -15,7 +15,7 @@ set of default services. Each service defines the per-node directories,
configuration files, startup index, starting commands, validation commands,
shutdown commands, and meta-data associated with a node.
> :warning: **Network namespace nodes do not undergo the normal Linux boot process**
> **NOTE:** **Network namespace nodes do not undergo the normal Linux boot process**
using the **init**, **upstart**, or **systemd** frameworks. These
lightweight nodes use configured CORE *services*.
@ -79,8 +79,7 @@ the service customization dialog for that service.
The dialog has three tabs for configuring the different aspects of the service:
files, directories, and startup/shutdown.
**NOTE:**
A **yellow** customize icon next to a service indicates that service
> **NOTE:** A **yellow** customize icon next to a service indicates that service
requires customization (e.g. the *Firewall* service).
A **green** customize icon indicates that a custom configuration exists.
Click the *Defaults* button when customizing a service to remove any
@ -99,8 +98,7 @@ per-node directories that are defined by the services. For example, the
the Zebra service, because Quagga running on each node needs to write separate
PID files to that directory.
**NOTE:**
The **/var/log** and **/var/run** directories are
> **NOTE:** The **/var/log** and **/var/run** directories are
mounted uniquely per-node by default.
Per-node mount targets can be found in **/tmp/pycore.nnnnn/nN.conf/**
(where *nnnnn* is the session number and *N* is the node number.)
@ -130,8 +128,7 @@ if a process is running and return zero when found. When a validate command
produces a non-zero return value, an exception is generated, which will cause
an error to be displayed in the Check Emulation Light.
**TIP:**
To start, stop, and restart services during run-time, right-click a
> **NOTE:** To start, stop, and restart services during run-time, right-click a
node and use the *Services...* menu.
## New Services

BIN
docs/static/architecture.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/static/gui/document-properties.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

BIN
docs/static/gui/host.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
docs/static/gui/hub.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

BIN
docs/static/gui/lanswitch.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

BIN
docs/static/gui/link.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 B

BIN
docs/static/gui/marker.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

BIN
docs/static/gui/mdr.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
docs/static/gui/observe.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
docs/static/gui/oval.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

BIN
docs/static/gui/pc.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
docs/static/gui/rectangle.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 B

BIN
docs/static/gui/rj45.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

BIN
docs/static/gui/router.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
docs/static/gui/router_green.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

BIN
docs/static/gui/run.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

BIN
docs/static/gui/select.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 925 B

BIN
docs/static/gui/start.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
docs/static/gui/stop.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

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