8
.github/workflows/daemon-checks.yml
vendored
|
@ -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
|
||||
|
|
43
CHANGELOG.md
|
@ -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
|
||||
|
|
29
README.md
|
@ -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)
|
||||
|
|
|
@ -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
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
212
daemon/core/api/tlv/enumerations.py
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]:
|
||||
|
|
333
daemon/core/emane/linkmonitor.py
Normal 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()
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -21,7 +21,7 @@ class GeoLocation:
|
|||
"""
|
||||
|
||||
name = "location"
|
||||
config_type = RegisterTlvs.UTILITY.value
|
||||
config_type = RegisterTlvs.UTILITY
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""
|
||||
|
|
|
@ -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:
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -72,7 +72,7 @@ class DockerClient:
|
|||
|
||||
|
||||
class DockerNode(CoreNode):
|
||||
apitype = NodeTypes.DOCKER.value
|
||||
apitype = NodeTypes.DOCKER
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ class LxdClient:
|
|||
|
||||
|
||||
class LxcNode(CoreNode):
|
||||
apitype = NodeTypes.LXC.value
|
||||
apitype = NodeTypes.LXC
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
|
@ -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:
|
||||
"""
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
"""
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -10,3 +10,7 @@ message ConfigOption {
|
|||
repeated string select = 5;
|
||||
string group = 6;
|
||||
}
|
||||
|
||||
message MappedConfig {
|
||||
map<string, common.ConfigOption> config = 1;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
92
daemon/proto/core/api/grpc/emane.proto
Normal 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;
|
||||
}
|
54
daemon/proto/core/api/grpc/mobility.proto
Normal 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;
|
||||
}
|
149
daemon/proto/core/api/grpc/services.proto
Normal 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;
|
||||
}
|
36
daemon/proto/core/api/grpc/wlan.proto
Normal 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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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()
|
|
@ -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):
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
49
docs/diagrams/architecture.plantuml
Normal 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
|
40
docs/diagrams/workflow.plantuml
Normal 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
|
|
@ -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
|
||||
|
|
183
docs/emane.md
|
@ -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)
|
||||
|
|
|
@ -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
|
@ -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.
|
||||
|
||||
|
||||
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
@ -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.
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
After Width: | Height: | Size: 26 KiB |
BIN
docs/static/core-architecture.jpg
vendored
Before Width: | Height: | Size: 38 KiB |
BIN
docs/static/core-workflow.jpg
vendored
Before Width: | Height: | Size: 21 KiB |
BIN
docs/static/gui/document-properties.gif
vendored
Normal file
After Width: | Height: | Size: 635 B |
BIN
docs/static/gui/host.gif
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
docs/static/gui/hub.gif
vendored
Normal file
After Width: | Height: | Size: 719 B |
BIN
docs/static/gui/lanswitch.gif
vendored
Normal file
After Width: | Height: | Size: 744 B |
BIN
docs/static/gui/link.gif
vendored
Normal file
After Width: | Height: | Size: 86 B |
BIN
docs/static/gui/marker.gif
vendored
Normal file
After Width: | Height: | Size: 375 B |
BIN
docs/static/gui/mdr.gif
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
docs/static/gui/observe.gif
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
docs/static/gui/oval.gif
vendored
Normal file
After Width: | Height: | Size: 174 B |
BIN
docs/static/gui/pc.gif
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
docs/static/gui/rectangle.gif
vendored
Normal file
After Width: | Height: | Size: 160 B |
BIN
docs/static/gui/rj45.gif
vendored
Normal file
After Width: | Height: | Size: 755 B |
BIN
docs/static/gui/router.gif
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
docs/static/gui/router_green.gif
vendored
Normal file
After Width: | Height: | Size: 753 B |
BIN
docs/static/gui/run.gif
vendored
Normal file
After Width: | Height: | Size: 324 B |
BIN
docs/static/gui/select.gif
vendored
Normal file
After Width: | Height: | Size: 925 B |
BIN
docs/static/gui/start.gif
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
docs/static/gui/stop.gif
vendored
Normal file
After Width: | Height: | Size: 1.2 KiB |