Compare commits
37 Commits
cli-dev
...
revert-929
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a5578bc11 | ||
| 0152b7e64f | |||
| 1e314ba3b1 | |||
| f0e7a99739 | |||
|
|
929b40983c | ||
|
|
f82b45a91e | ||
|
|
0751e26172 | ||
|
|
7b2ef173eb | ||
|
|
6a7ab04e1c | ||
|
|
7eff5ef555 | ||
|
|
880bbdf50c | ||
|
|
18cdd1dd46 | ||
|
|
3bbbbe52c0 | ||
|
|
1f43b11177 | ||
|
|
8b7ad05ad6 | ||
|
|
7badc83530 | ||
|
|
5c031d8157 | ||
|
|
7465819ef4 | ||
|
|
901133c84c | ||
|
|
64788a1997 | ||
|
|
7ffbdda7ea | ||
|
|
2e95bd2fd2 | ||
|
|
e569eb3e5b | ||
|
|
266a669e5e | ||
|
|
a21312ee61 | ||
|
|
73771be70d | ||
|
|
27ae736f11 | ||
|
|
cb1ad33cae | ||
|
|
2681ee9a8e | ||
|
|
799414ad39 | ||
|
|
6b73530943 | ||
|
|
2efa0d9e7b | ||
|
|
798a32b23e | ||
|
|
00aa20a8af | ||
|
|
11e2c356fa | ||
|
|
95426e0baa | ||
|
|
347d43dcef |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,3 +1,10 @@
|
|||||||
.obsidian
|
.obsidian
|
||||||
venv
|
venv
|
||||||
__pycache__
|
__pycache__
|
||||||
|
*.log
|
||||||
|
.idea/*
|
||||||
|
*/.idea
|
||||||
|
*.idea
|
||||||
|
/.idea
|
||||||
|
.idea/
|
||||||
|
2024-bsc-sebastian-lenzlinger.iml
|
||||||
|
|||||||
308
.idea/workspace.xml
generated
308
.idea/workspace.xml
generated
@@ -1,308 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="AutoImportSettings">
|
|
||||||
<option name="autoReloadType" value="SELECTIVE" />
|
|
||||||
</component>
|
|
||||||
<component name="ChangeListManager">
|
|
||||||
<list default="true" id="7a3ac8e1-7fbf-4aa7-9cf9-a51d7ade8503" name="Changes" comment="UNTESTED REFACTORING: Move more functionality into Metadata Model classes to ensure data is available and better passable between functions.">
|
|
||||||
<change afterPath="$PROJECT_DIR$/code/iottb/logger.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/code/iottb/models/capture_metadata_model.py" beforeDir="false" afterPath="$PROJECT_DIR$/code/iottb/models/capture_metadata_model.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/code/iottb/models/device_metadata_model.py" beforeDir="false" afterPath="$PROJECT_DIR$/code/iottb/models/device_metadata_model.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/code/iottb/subcommands/add_device.py" beforeDir="false" afterPath="$PROJECT_DIR$/code/iottb/subcommands/add_device.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/code/iottb/subcommands/capture.py" beforeDir="false" afterPath="$PROJECT_DIR$/code/iottb/subcommands/capture.py" afterDir="false" />
|
|
||||||
</list>
|
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
||||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
|
||||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
|
||||||
</component>
|
|
||||||
<component name="FileTemplateManagerImpl">
|
|
||||||
<option name="RECENT_TEMPLATES">
|
|
||||||
<list>
|
|
||||||
<option value="Python Script" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="Git.Settings">
|
|
||||||
<option name="PUSH_AUTO_UPDATE" value="true" />
|
|
||||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
|
||||||
<map>
|
|
||||||
<entry key="$PROJECT_DIR$" value="sync" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
|
||||||
</component>
|
|
||||||
<component name="ProblemsViewState">
|
|
||||||
<option name="selectedTabId" value="CurrentFile" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectColorInfo">{
|
|
||||||
"associatedIndex": 3
|
|
||||||
}</component>
|
|
||||||
<component name="ProjectId" id="2fYAAba0AnH9jx9D0JkB8Xbuv0r" />
|
|
||||||
<component name="ProjectViewState">
|
|
||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
|
||||||
<option name="showLibraryContents" value="true" />
|
|
||||||
</component>
|
|
||||||
<component name="PropertiesComponent"><![CDATA[{
|
|
||||||
"keyToString": {
|
|
||||||
"ASKED_ADD_EXTERNAL_FILES": "true",
|
|
||||||
"ASKED_MARK_IGNORED_FILES_AS_EXCLUDED": "true",
|
|
||||||
"ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
|
||||||
"Python.__init__.executor": "Run",
|
|
||||||
"Python.__main__.executor": "Debug",
|
|
||||||
"Python.iotdb.executor": "Debug",
|
|
||||||
"Python.main.executor": "Run",
|
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
|
||||||
"SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
|
||||||
"git-widget-placeholder": "cli-dev",
|
|
||||||
"last_opened_file_path": "/home/slnopriv/projects/2024-bsc-sebastian-lenzlinger/code/iottb/logger.py",
|
|
||||||
"node.js.detected.package.eslint": "true",
|
|
||||||
"node.js.detected.package.tslint": "true",
|
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
|
||||||
"nodejs_package_manager_path": "npm",
|
|
||||||
"settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable",
|
|
||||||
"vue.rearranger.settings.migration": "true"
|
|
||||||
}
|
|
||||||
}]]></component>
|
|
||||||
<component name="RecentsManager">
|
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
|
||||||
<recent name="$PROJECT_DIR$/archive" />
|
|
||||||
<recent name="$PROJECT_DIR$" />
|
|
||||||
<recent name="$PROJECT_DIR$/code/misc/archive" />
|
|
||||||
<recent name="$PROJECT_DIR$/code/misc" />
|
|
||||||
<recent name="$PROJECT_DIR$/code/kydcap/utils" />
|
|
||||||
</key>
|
|
||||||
</component>
|
|
||||||
<component name="RunManager" selected="Python.__main__">
|
|
||||||
<configuration name="__init__" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
|
||||||
<module name="2024-bsc-sebastian-lenzlinger" />
|
|
||||||
<option name="ENV_FILES" value="" />
|
|
||||||
<option name="INTERPRETER_OPTIONS" value="" />
|
|
||||||
<option name="PARENT_ENVS" value="true" />
|
|
||||||
<envs>
|
|
||||||
<env name="PYTHONUNBUFFERED" value="1" />
|
|
||||||
</envs>
|
|
||||||
<option name="SDK_HOME" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/code/kydcap" />
|
|
||||||
<option name="IS_MODULE_SDK" value="true" />
|
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
||||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
|
||||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/code/kydcap/__init__.py" />
|
|
||||||
<option name="PARAMETERS" value="" />
|
|
||||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
|
||||||
<option name="EMULATE_TERMINAL" value="false" />
|
|
||||||
<option name="MODULE_MODE" value="false" />
|
|
||||||
<option name="REDIRECT_INPUT" value="false" />
|
|
||||||
<option name="INPUT_FILE" value="" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<configuration name="__main__" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
|
||||||
<module name="2024-bsc-sebastian-lenzlinger" />
|
|
||||||
<option name="ENV_FILES" value="" />
|
|
||||||
<option name="INTERPRETER_OPTIONS" value="" />
|
|
||||||
<option name="PARENT_ENVS" value="true" />
|
|
||||||
<envs>
|
|
||||||
<env name="PYTHONUNBUFFERED" value="1" />
|
|
||||||
</envs>
|
|
||||||
<option name="SDK_HOME" value="" />
|
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/code/iottb" />
|
|
||||||
<option name="IS_MODULE_SDK" value="true" />
|
|
||||||
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
||||||
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
||||||
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
|
|
||||||
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/code/iottb/__main__.py" />
|
|
||||||
<option name="PARAMETERS" value="add --root /tmp/test --guided" />
|
|
||||||
<option name="SHOW_COMMAND_LINE" value="false" />
|
|
||||||
<option name="EMULATE_TERMINAL" value="false" />
|
|
||||||
<option name="MODULE_MODE" value="false" />
|
|
||||||
<option name="REDIRECT_INPUT" value="false" />
|
|
||||||
<option name="INPUT_FILE" value="" />
|
|
||||||
<method v="2" />
|
|
||||||
</configuration>
|
|
||||||
<list>
|
|
||||||
<item itemvalue="Python.__main__" />
|
|
||||||
<item itemvalue="Python.__init__" />
|
|
||||||
</list>
|
|
||||||
<recent_temporary>
|
|
||||||
<list>
|
|
||||||
<item itemvalue="Python.__main__" />
|
|
||||||
<item itemvalue="Python.__init__" />
|
|
||||||
</list>
|
|
||||||
</recent_temporary>
|
|
||||||
</component>
|
|
||||||
<component name="SharedIndexes">
|
|
||||||
<attachedChunks>
|
|
||||||
<set>
|
|
||||||
<option value="bundled-js-predefined-1d06a55b98c1-74d2a5396914-JavaScript-PY-241.14494.241" />
|
|
||||||
<option value="bundled-python-sdk-0509580d9d50-28c9f5db9ffe-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-241.14494.241" />
|
|
||||||
</set>
|
|
||||||
</attachedChunks>
|
|
||||||
</component>
|
|
||||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
|
||||||
<component name="TaskManager">
|
|
||||||
<task active="true" id="Default" summary="Default task">
|
|
||||||
<changelist id="7a3ac8e1-7fbf-4aa7-9cf9-a51d7ade8503" name="Changes" comment="" />
|
|
||||||
<created>1713967494544</created>
|
|
||||||
<option name="number" value="Default" />
|
|
||||||
<option name="presentableId" value="Default" />
|
|
||||||
<updated>1713967494544</updated>
|
|
||||||
<workItem from="1713967495566" duration="6927000" />
|
|
||||||
<workItem from="1714554228183" duration="34000" />
|
|
||||||
<workItem from="1714554269789" duration="56478000" />
|
|
||||||
<workItem from="1714616237168" duration="6135000" />
|
|
||||||
<workItem from="1714850899817" duration="2659000" />
|
|
||||||
<workItem from="1714917763516" duration="9796000" />
|
|
||||||
<workItem from="1715078660090" duration="25014000" />
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00001" summary="Add code for capture testbed. This is a huge commit. End of day sync...">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1714615532115</created>
|
|
||||||
<option name="number" value="00001" />
|
|
||||||
<option name="presentableId" value="LOCAL-00001" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1714615532115</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00002" summary="Add some notes.">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1714615608142</created>
|
|
||||||
<option name="number" value="00002" />
|
|
||||||
<option name="presentableId" value="LOCAL-00002" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1714615608142</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00003" summary="Update gitignore">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1714616343905</created>
|
|
||||||
<option name="number" value="00003" />
|
|
||||||
<option name="presentableId" value="LOCAL-00003" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1714616343905</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00004" summary="Add test module.">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1714617162903</created>
|
|
||||||
<option name="number" value="00004" />
|
|
||||||
<option name="presentableId" value="LOCAL-00004" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1714617162903</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00005" summary="Update gitignore again.">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1714617231842</created>
|
|
||||||
<option name="number" value="00005" />
|
|
||||||
<option name="presentableId" value="LOCAL-00005" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1714617231842</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00006" summary="Start tracking development config files.">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1714617266799</created>
|
|
||||||
<option name="number" value="00006" />
|
|
||||||
<option name="presentableId" value="LOCAL-00006" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1714617266799</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00007" summary="SYNC">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1714823516954</created>
|
|
||||||
<option name="number" value="00007" />
|
|
||||||
<option name="presentableId" value="LOCAL-00007" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1714823516954</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00008" summary="Refactor various names.">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1714919098392</created>
|
|
||||||
<option name="number" value="00008" />
|
|
||||||
<option name="presentableId" value="LOCAL-00008" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1714919098392</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00009" summary="Refactor subcommands, config etc.">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1714924463148</created>
|
|
||||||
<option name="number" value="00009" />
|
|
||||||
<option name="presentableId" value="LOCAL-00009" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1714924463148</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00010" summary="UNTESTED REFACTORING: Move more functionality into Metadata Model classes to ensure data is available and better passable between functions.">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1715101138312</created>
|
|
||||||
<option name="number" value="00010" />
|
|
||||||
<option name="presentableId" value="LOCAL-00010" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1715101138312</updated>
|
|
||||||
</task>
|
|
||||||
<option name="localTasksCounter" value="11" />
|
|
||||||
<servers />
|
|
||||||
</component>
|
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
|
||||||
<option name="version" value="3" />
|
|
||||||
</component>
|
|
||||||
<component name="Vcs.Log.Tabs.Properties">
|
|
||||||
<option name="RECENT_FILTERS">
|
|
||||||
<map>
|
|
||||||
<entry key="Branch">
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<RecentGroup>
|
|
||||||
<option name="FILTER_VALUES">
|
|
||||||
<option value="HEAD" />
|
|
||||||
</option>
|
|
||||||
</RecentGroup>
|
|
||||||
<RecentGroup>
|
|
||||||
<option name="FILTER_VALUES">
|
|
||||||
<option value="devel" />
|
|
||||||
</option>
|
|
||||||
</RecentGroup>
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
<option name="TAB_STATES">
|
|
||||||
<map>
|
|
||||||
<entry key="MAIN">
|
|
||||||
<value>
|
|
||||||
<State>
|
|
||||||
<option name="FILTERS">
|
|
||||||
<map>
|
|
||||||
<entry key="branch">
|
|
||||||
<value>
|
|
||||||
<list>
|
|
||||||
<option value="HEAD" />
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</State>
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="VcsManagerConfiguration">
|
|
||||||
<MESSAGE value="Add code for capture testbed. This is a huge commit. End of day sync..." />
|
|
||||||
<MESSAGE value="Add some notes." />
|
|
||||||
<MESSAGE value="Update gitignore" />
|
|
||||||
<MESSAGE value="Add test module." />
|
|
||||||
<MESSAGE value="Update gitignore again." />
|
|
||||||
<MESSAGE value="Start tracking development config files." />
|
|
||||||
<MESSAGE value="SYNC" />
|
|
||||||
<MESSAGE value="Refactor various names." />
|
|
||||||
<MESSAGE value="Refactor subcommands, config etc." />
|
|
||||||
<MESSAGE value="UNTESTED REFACTORING: Move more functionality into Metadata Model classes to ensure data is available and better passable between functions." />
|
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="UNTESTED REFACTORING: Move more functionality into Metadata Model classes to ensure data is available and better passable between functions." />
|
|
||||||
</component>
|
|
||||||
<component name="com.intellij.coverage.CoverageDataManagerImpl">
|
|
||||||
<SUITE FILE_PATH="coverage/2024_bsc_sebastian_lenzlinger$__main__.coverage" NAME="__main__ Coverage Results" MODIFIED="1715103831289" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/code/iottb" />
|
|
||||||
<SUITE FILE_PATH="coverage/2024_bsc_sebastian_lenzlinger$iotdb.coverage" NAME="iotdb Coverage Results" MODIFIED="1715103593519" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/code/iottb" />
|
|
||||||
<SUITE FILE_PATH="coverage/2024_bsc_sebastian_lenzlinger$__init__.coverage" NAME="__init__ Coverage Results" MODIFIED="1714619300966" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/code/kydcap" />
|
|
||||||
<SUITE FILE_PATH="coverage/2024_bsc_sebastian_lenzlinger$main.coverage" NAME="__main__ Coverage Results" MODIFIED="1714619560177" SOURCE_PROVIDER="com.intellij.coverage.DefaultCoverageFileProvider" RUNNER="coverage.py" COVERAGE_BY_TEST_ENABLED="false" COVERAGE_TRACING_ENABLED="false" WORKING_DIRECTORY="$PROJECT_DIR$/code/kydcap" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -9,12 +9,12 @@ def set_device_ip_address(ip_addr: str, file_path: Path):
|
|||||||
assert file_path.is_file()
|
assert file_path.is_file()
|
||||||
with file_path.open('r') as f:
|
with file_path.open('r') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
current_ip = data["device_ip_address"]
|
current_ip = data['device_ip_address']
|
||||||
if current_ip is not None:
|
if current_ip is not None:
|
||||||
print(f"Device IP Address is set to {current_ip}")
|
print(f'Device IP Address is set to {current_ip}')
|
||||||
response = input(f"Do you want to change the recorded IP address to {ip_addr}? [Y/N] ")
|
response = input(f'Do you want to change the recorded IP address to {ip_addr}? [Y/N] ')
|
||||||
if response.upper() == "N":
|
if response.upper() == 'N':
|
||||||
print("Aborting change to device IP address")
|
print('Aborting change to device IP address')
|
||||||
return ReturnCodes.ABORTED
|
return ReturnCodes.ABORTED
|
||||||
with file_path.open('w') as f:
|
with file_path.open('w') as f:
|
||||||
json.dump(data, f)
|
json.dump(data, f)
|
||||||
@@ -26,12 +26,12 @@ def set_device_mac_address(mac_addr: str, file_path: Path):
|
|||||||
assert file_path.is_file()
|
assert file_path.is_file()
|
||||||
with file_path.open('r') as f:
|
with file_path.open('r') as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
current_mac = data["device_mac_address"]
|
current_mac = data['device_mac_address']
|
||||||
if current_mac is not None:
|
if current_mac is not None:
|
||||||
print(f"Device MAC Address is set to {current_mac}")
|
print(f'Device MAC Address is set to {current_mac}')
|
||||||
response = input(f"Do you want to change the recorded MAC address to {mac_addr}? [Y/N] ")
|
response = input(f'Do you want to change the recorded MAC address to {mac_addr}? [Y/N] ')
|
||||||
if response.upper() == "N":
|
if response.upper() == 'N':
|
||||||
print("Aborting change to device MAC address")
|
print('Aborting change to device MAC address')
|
||||||
return ReturnCodes.ABORTED
|
return ReturnCodes.ABORTED
|
||||||
with file_path.open('w') as f:
|
with file_path.open('w') as f:
|
||||||
json.dump(data, f)
|
json.dump(data, f)
|
||||||
@@ -2,31 +2,31 @@ def setup_sniff_tcpdump_parser(parser_sniff):
|
|||||||
# arguments which will be passed to tcpdump
|
# arguments which will be passed to tcpdump
|
||||||
parser_sniff_tcpdump = parser_sniff.add_argument_group('tcpdump arguments')
|
parser_sniff_tcpdump = parser_sniff.add_argument_group('tcpdump arguments')
|
||||||
# TODO: tcpdump_parser.add_argument('-c', '--count', re)
|
# TODO: tcpdump_parser.add_argument('-c', '--count', re)
|
||||||
parser_sniff_tcpdump.add_argument("-a", "--ip-address=", help="IP address of the device to sniff", dest="device_ip")
|
parser_sniff_tcpdump.add_argument('-a', '--ip-address=', help='IP address of the device to sniff', dest='device_ip')
|
||||||
parser_sniff_tcpdump.add_argument("-i", "--interface=", help="Interface of the capture device.", dest="capture_interface",default="")
|
parser_sniff_tcpdump.add_argument('-i', '--interface=', help='Interface of the capture device.', dest='capture_interface',default='')
|
||||||
parser_sniff_tcpdump.add_argument("-I", "--monitor-mode", help="Put interface into monitor mode",
|
parser_sniff_tcpdump.add_argument('-I', '--monitor-mode', help='Put interface into monitor mode',
|
||||||
action="store_true")
|
action='store_true')
|
||||||
parser_sniff_tcpdump.add_argument("-n", help="Deactivate name resolution. Option is set by default.",
|
parser_sniff_tcpdump.add_argument('-n', help='Deactivate name resolution. Option is set by default.',
|
||||||
action="store_true")
|
action='store_true')
|
||||||
parser_sniff_tcpdump.add_argument("-#", "--number",
|
parser_sniff_tcpdump.add_argument('-#', '--number',
|
||||||
help="Print packet number at beginning of line. Set by default.",
|
help='Print packet number at beginning of line. Set by default.',
|
||||||
action="store_true")
|
action='store_true')
|
||||||
parser_sniff_tcpdump.add_argument("-e", help="Print link layer headers. Option is set by default.",
|
parser_sniff_tcpdump.add_argument('-e', help='Print link layer headers. Option is set by default.',
|
||||||
action="store_true")
|
action='store_true')
|
||||||
parser_sniff_tcpdump.add_argument("-t", action="count", default=0,
|
parser_sniff_tcpdump.add_argument('-t', action='count', default=0,
|
||||||
help="Please see tcpdump manual for details. Unused by default.")
|
help='Please see tcpdump manual for details. Unused by default.')
|
||||||
|
|
||||||
|
|
||||||
def setup_sniff_parser(subparsers):
|
def setup_sniff_parser(subparsers):
|
||||||
# create parser for "sniff" command
|
# create parser for 'sniff' command
|
||||||
parser_sniff = subparsers.add_parser("sniff", help="Start tcpdump capture.")
|
parser_sniff = subparsers.add_parser('sniff', help='Start tcpdump capture.')
|
||||||
setup_sniff_tcpdump_parser(parser_sniff)
|
setup_sniff_tcpdump_parser(parser_sniff)
|
||||||
setup_pcap_filter_parser(parser_sniff)
|
setup_pcap_filter_parser(parser_sniff)
|
||||||
cap_size_group = parser_sniff.add_mutually_exclusive_group(required=True)
|
cap_size_group = parser_sniff.add_mutually_exclusive_group(required=True)
|
||||||
cap_size_group.add_argument("-c", "--count", type=int, help="Number of packets to capture.", default=0)
|
cap_size_group.add_argument('-c', '--count', type=int, help='Number of packets to capture.', default=0)
|
||||||
cap_size_group.add_argument("--mins", type=int, help="Time in minutes to capture.", default=60)
|
cap_size_group.add_argument('--mins', type=int, help='Time in minutes to capture.', default=60)
|
||||||
|
|
||||||
|
|
||||||
def setup_pcap_filter_parser(parser_sniff):
|
def setup_pcap_filter_parser(parser_sniff):
|
||||||
parser_pcap_filter = parser_sniff.add_argument_parser("pcap-filter expression")
|
parser_pcap_filter = parser_sniff.add_argument_parser('pcap-filter expression')
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ class Metadata:
|
|||||||
|
|
||||||
|
|
||||||
def create_metadata(filename, unique_id, device_details):
|
def create_metadata(filename, unique_id, device_details):
|
||||||
date_string = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
|
date_string = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
|
||||||
meta_filename = f"meta_{date_string}_{unique_id}.json"
|
meta_filename = f'meta_{date_string}_{unique_id}.json'
|
||||||
|
|||||||
@@ -8,33 +8,33 @@ from iottb.definitions import DEVICE_METADATA_FILE
|
|||||||
|
|
||||||
|
|
||||||
def write_device_metadata_to_file(metadata: DeviceMetadata, device_path: Path):
|
def write_device_metadata_to_file(metadata: DeviceMetadata, device_path: Path):
|
||||||
"""Write the device metadata to a JSON file in the specified directory."""
|
'''Write the device metadata to a JSON file in the specified directory.'''
|
||||||
meta_file_path = device_path / "meta.json"
|
meta_file_path = device_path / 'meta.json'
|
||||||
meta_file_path.write_text(metadata.json(indent=2))
|
meta_file_path.write_text(metadata.json(indent=2))
|
||||||
|
|
||||||
|
|
||||||
def confirm_device_metadata(metadata: DeviceMetadata) -> bool:
|
def confirm_device_metadata(metadata: DeviceMetadata) -> bool:
|
||||||
"""Display device metadata for user confirmation."""
|
'''Display device metadata for user confirmation.'''
|
||||||
print(metadata.json(indent=2))
|
print(metadata.json(indent=2))
|
||||||
return input("Confirm device metadata? (y/n): ").strip().lower() == 'y'
|
return input('Confirm device metadata? (y/n): ').strip().lower() == 'y'
|
||||||
|
|
||||||
|
|
||||||
def get_device_metadata_from_user() -> DeviceMetadata:
|
def get_device_metadata_from_user() -> DeviceMetadata:
|
||||||
"""Prompt the user to enter device details and return a populated DeviceMetadata object."""
|
'''Prompt the user to enter device details and return a populated DeviceMetadata object.'''
|
||||||
device_name = input("Device name: ")
|
device_name = input('Device name: ')
|
||||||
device_short_name = device_name.lower().replace(" ", "-")
|
device_short_name = device_name.lower().replace(' ', '-')
|
||||||
return DeviceMetadata(device_name=device_name, device_short_name=device_short_name)
|
return DeviceMetadata(device_name=device_name, device_short_name=device_short_name)
|
||||||
|
|
||||||
|
|
||||||
def initialize_device_root_dir(device_name: str) -> Path:
|
def initialize_device_root_dir(device_name: str) -> Path:
|
||||||
"""Create and return the path for the device directory."""
|
'''Create and return the path for the device directory.'''
|
||||||
device_path = Path.cwd() / device_name
|
device_path = Path.cwd() / device_name
|
||||||
device_path.mkdir(exist_ok=True)
|
device_path.mkdir(exist_ok=True)
|
||||||
return device_path
|
return device_path
|
||||||
|
|
||||||
|
|
||||||
def write_metadata(metadata: BaseModel, device_name: str):
|
def write_metadata(metadata: BaseModel, device_name: str):
|
||||||
"""Write device metadata to a JSON file."""
|
'''Write device metadata to a JSON file.'''
|
||||||
meta_path = Path.cwd() / device_name / DEVICE_METADATA_FILE
|
meta_path = Path.cwd() / device_name / DEVICE_METADATA_FILE
|
||||||
meta_path.parent.mkdir(parents=True, exist_ok=True)
|
meta_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
with meta_path.open('w') as f:
|
with meta_path.open('w') as f:
|
||||||
@@ -42,19 +42,19 @@ def write_metadata(metadata: BaseModel, device_name: str):
|
|||||||
|
|
||||||
|
|
||||||
def get_device_metadata(file_path: Path) -> DeviceMetadata | None:
|
def get_device_metadata(file_path: Path) -> DeviceMetadata | None:
|
||||||
"""Fetch device metadata from a JSON file."""
|
'''Fetch device metadata from a JSON file.'''
|
||||||
|
|
||||||
if dev_metadata_exists(file_path):
|
if dev_metadata_exists(file_path):
|
||||||
with file_path.open('r') as f:
|
with file_path.open('r') as f:
|
||||||
device_metadata_json = json.load(f)
|
device_metadata_json = json.load(f)
|
||||||
try:
|
try:
|
||||||
device_metadata = DeviceMetadata.model_validate_json(device_metadata_json)
|
device_metadata = DeviceMetadata.from_json(device_metadata_json)
|
||||||
return device_metadata
|
return device_metadata
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
print(f"Validation error for device metadata: {e}")
|
print(f'Validation error for device metadata: {e}')
|
||||||
else:
|
else:
|
||||||
# TODO Decide what to do (e.g. search for file etc)
|
# TODO Decide what to do (e.g. search for file etc)
|
||||||
print(f"No device metadata at {file_path}")
|
print(f'No device metadata at {file_path}')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
code/iottb-project
Submodule
1
code/iottb-project
Submodule
Submodule code/iottb-project added at a5d4390f37
@@ -1,8 +1,13 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
|
from os import environ
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from iottb.subcommands.capture import setup_capture_parser
|
from iottb.logger import logger
|
||||||
from iottb.subcommands.add_device import setup_init_device_root_parser
|
from iottb.subcommands.add_device import setup_init_device_root_parser
|
||||||
|
from iottb.subcommands.capture import setup_capture_parser
|
||||||
|
from iottb.utils.tcpdump_utils import list_interfaces
|
||||||
|
from definitions import IOTTB_HOME_ABS, ReturnCodes
|
||||||
|
|
||||||
|
|
||||||
######################
|
######################
|
||||||
@@ -10,16 +15,55 @@ from iottb.subcommands.add_device import setup_init_device_root_parser
|
|||||||
######################
|
######################
|
||||||
def setup_argparse():
|
def setup_argparse():
|
||||||
# create top level parser
|
# create top level parser
|
||||||
root_parser = argparse.ArgumentParser(prog="iottb")
|
root_parser = argparse.ArgumentParser(prog='iottb')
|
||||||
subparsers = root_parser.add_subparsers(title="subcommands", required=True, dest="command")
|
subparsers = root_parser.add_subparsers(title='subcommands', required=True, dest='command')
|
||||||
|
|
||||||
|
# shared options
|
||||||
|
root_parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||||
|
# configure subcommands
|
||||||
setup_capture_parser(subparsers)
|
setup_capture_parser(subparsers)
|
||||||
setup_init_device_root_parser(subparsers)
|
setup_init_device_root_parser(subparsers)
|
||||||
|
|
||||||
|
# Utility to list interfaces directly with iottb instead of relying on external tooling
|
||||||
|
|
||||||
|
interfaces_parser = subparsers.add_parser('list-interfaces', aliases=['li', 'if'],
|
||||||
|
help='List available network interfaces.')
|
||||||
|
interfaces_parser.set_defaults(func=list_interfaces)
|
||||||
|
|
||||||
return root_parser
|
return root_parser
|
||||||
|
|
||||||
|
|
||||||
|
def check_iottb_env():
|
||||||
|
# This makes the option '--root-dir' obsolescent # TODO How to streamline this?\
|
||||||
|
try:
|
||||||
|
iottb_home = environ['IOTTB_HOME'] # TODO WARN implicit declaration of env var name!
|
||||||
|
except KeyError:
|
||||||
|
logger.error(f"Environment variable 'IOTTB_HOME' is not set."
|
||||||
|
f"Setting environment variable 'IOTTB_HOME' to '~/{IOTTB_HOME_ABS}'")
|
||||||
|
environ['IOTTB_HOME'] = IOTTB_HOME_ABS
|
||||||
|
finally:
|
||||||
|
if not Path(IOTTB_HOME_ABS).exists():
|
||||||
|
print(f'"{IOTTB_HOME_ABS}" does not exist.')
|
||||||
|
response = input('Do you want to create it now? [y/N]')
|
||||||
|
logger.debug(f'response: {response}')
|
||||||
|
if response.lower() != 'y':
|
||||||
|
logger.debug(f'Not creating "{environ['IOTTB_HOME']}"')
|
||||||
|
print('TODO')
|
||||||
|
print("Aborting execution...")
|
||||||
|
return ReturnCodes.ABORTED
|
||||||
|
else:
|
||||||
|
print(f'Creating "{environ['IOTTB_HOME']}"')
|
||||||
|
Path(IOTTB_HOME_ABS).mkdir(parents=True,
|
||||||
|
exist_ok=False) # Should always work since in 'not exist' code path
|
||||||
|
return ReturnCodes.OK
|
||||||
|
logger.info(f'"{IOTTB_HOME_ABS}" exists.')
|
||||||
|
# TODO: Check that it is a valid iottb dir or can we say it is valid by definition if?
|
||||||
|
return ReturnCodes.OK
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
if check_iottb_env() != ReturnCodes.OK:
|
||||||
|
exit(ReturnCodes.ABORTED)
|
||||||
parser = setup_argparse()
|
parser = setup_argparse()
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
print(args)
|
print(args)
|
||||||
@@ -27,12 +71,12 @@ def main():
|
|||||||
try:
|
try:
|
||||||
args.func(args)
|
args.func(args)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("Received keyboard interrupt. Exiting...")
|
print('Received keyboard interrupt. Exiting...')
|
||||||
exit(1)
|
exit(1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error: {e}")
|
print(f'Error: {e}')
|
||||||
# create_capture_directory(args.device_name)
|
# create_capture_directory(args.device_name)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,16 +1,27 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Flag, unique, global_enum
|
from enum import Flag, unique, global_enum
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
DEVICE_METADATA_FILE = "device_metadata.json"
|
|
||||||
CAPTURE_METADATA_FILE = "capture_metadata.json"
|
|
||||||
TODAY_DATE_STRING = datetime.now().strftime("%d%b%Y").lower() # TODO convert to function in utils or so
|
|
||||||
|
|
||||||
CAPTURE_FOLDER_BASENAME = "capture_###"
|
'''
|
||||||
|
Defining IOTTB_HOME_ABS here implies that it be immutable.
|
||||||
|
It is used here so that one could configure it.
|
||||||
|
But after its used in __man__ this cannot be relied upon.
|
||||||
|
'''
|
||||||
|
IOTTB_HOME_ABS = Path().home() / 'IOTTB.db'
|
||||||
|
|
||||||
AFFIRMATIVE_USER_RESPONSE = {"yes", "y", "true", "Y", "Yes", "YES"}
|
# TODO maybe wrap this into class to make it easier to pass along to different objects
|
||||||
NEGATIVE_USER_RESPONSE = {"no", "n", "N", "No"}
|
# But will need more refactoring
|
||||||
YES_DEFAULT = AFFIRMATIVE_USER_RESPONSE.union({"", " "})
|
DEVICE_METADATA_FILE = 'device_metadata.json'
|
||||||
NO_DEFAULT = NEGATIVE_USER_RESPONSE.union({"", " "})
|
CAPTURE_METADATA_FILE = 'capture_metadata.json'
|
||||||
|
TODAY_DATE_STRING = datetime.now().strftime('%d%b%Y').lower() # TODO convert to function in utils or so
|
||||||
|
|
||||||
|
CAPTURE_FOLDER_BASENAME = 'capture_###'
|
||||||
|
|
||||||
|
AFFIRMATIVE_USER_RESPONSE = {'yes', 'y', 'true', 'Y', 'Yes', 'YES'}
|
||||||
|
NEGATIVE_USER_RESPONSE = {'no', 'n', 'N', 'No'}
|
||||||
|
YES_DEFAULT = AFFIRMATIVE_USER_RESPONSE.union({'', ' '})
|
||||||
|
NO_DEFAULT = NEGATIVE_USER_RESPONSE.union({'', ' '})
|
||||||
|
|
||||||
|
|
||||||
@unique
|
@unique
|
||||||
@@ -24,3 +35,7 @@ class ReturnCodes(Flag):
|
|||||||
FILE_ALREADY_EXISTS = 5
|
FILE_ALREADY_EXISTS = 5
|
||||||
INVALID_ARGUMENT = 6
|
INVALID_ARGUMENT = 6
|
||||||
INVALID_ARGUMENT_VALUE = 7
|
INVALID_ARGUMENT_VALUE = 7
|
||||||
|
|
||||||
|
|
||||||
|
def iottb_home_abs():
|
||||||
|
return None
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ from logging.handlers import RotatingFileHandler
|
|||||||
|
|
||||||
def setup_logging():
|
def setup_logging():
|
||||||
logger_obj = logging.getLogger('iottbLogger')
|
logger_obj = logging.getLogger('iottbLogger')
|
||||||
logger_obj.setLevel(logging.INFO)
|
logger_obj.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
file_handler = RotatingFileHandler('iottb.log')
|
file_handler = RotatingFileHandler('iottb.log')
|
||||||
console_handler = logging.StreamHandler(sys.stdout)
|
console_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
|
||||||
file_handler.setLevel(logging.DEBUG)
|
file_handler.setLevel(logging.INFO)
|
||||||
console_handler.setLevel(logging.INFO)
|
console_handler.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
file_fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
file_fmt = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||||
console_fmt = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
|
console_fmt = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s')
|
||||||
|
|
||||||
file_handler.setFormatter(file_fmt)
|
file_handler.setFormatter(file_fmt)
|
||||||
console_handler.setFormatter(console_fmt)
|
console_handler.setFormatter(console_fmt)
|
||||||
|
|||||||
@@ -2,22 +2,21 @@ import json
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Any
|
from typing import Optional
|
||||||
from uuid import UUID
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
from iottb.definitions import ReturnCodes, CAPTURE_METADATA_FILE
|
from iottb.definitions import ReturnCodes, CAPTURE_METADATA_FILE
|
||||||
from iottb.models.device_metadata_model import DeviceMetadata
|
from iottb.models.device_metadata_model import DeviceMetadata
|
||||||
|
from iottb.logger import logger
|
||||||
|
|
||||||
|
|
||||||
class CaptureMetadata(BaseModel):
|
class CaptureMetadata:
|
||||||
# Required Fields
|
# Required Fields
|
||||||
device_metadata: DeviceMetadata = Field(exclude=True)
|
device_metadata: DeviceMetadata
|
||||||
capture_id: uuid.UUID = Field(default_factory=lambda: str(uuid.uuid4()))
|
capture_id: str = lambda: str(uuid.uuid4())
|
||||||
|
device_id: str
|
||||||
capture_dir: Path
|
capture_dir: Path
|
||||||
capture_file: str
|
capture_file: str
|
||||||
capture_date: str = Field(default_factory=lambda: datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower())
|
capture_date: str = lambda: datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower()
|
||||||
|
|
||||||
# Statistics
|
# Statistics
|
||||||
start_time: str
|
start_time: str
|
||||||
@@ -25,133 +24,79 @@ class CaptureMetadata(BaseModel):
|
|||||||
|
|
||||||
# tcpdump
|
# tcpdump
|
||||||
packet_count: Optional[int]
|
packet_count: Optional[int]
|
||||||
pcap_filter: str = ""
|
pcap_filter: str = ''
|
||||||
tcpdump_command: str = ""
|
tcpdump_command: str = ''
|
||||||
interface: str = ""
|
interface: str = ''
|
||||||
|
|
||||||
# Optional Fields
|
# Optional Fields
|
||||||
device_ip_address: Optional[str] = "No IP Address set"
|
device_ip_address: str = 'No IP Address set'
|
||||||
device_mac_address: Optional[str] = None
|
device_mac_address: Optional[str] = None
|
||||||
|
|
||||||
app: Optional[str] = None
|
app: Optional[str] = None
|
||||||
app_version: Optional[str] = None
|
app_version: Optional[str] = None
|
||||||
firmware_version: Optional[str] = None
|
firmware_version: Optional[str] = None
|
||||||
|
|
||||||
def __init__(self, device_metadata: DeviceMetadata, capture_dir: Path, /, **data: Any):
|
def __init__(self, device_metadata: DeviceMetadata, capture_dir: Path):
|
||||||
super().__init__(**data) # Pycharms orders
|
logger.info(f'Creating CaptureMetadata model from DeviceMetadata: {device_metadata}')
|
||||||
self.device_metadata = device_metadata
|
self.device_metadata = device_metadata
|
||||||
|
|
||||||
self.capture_dir = capture_dir
|
self.capture_dir = capture_dir
|
||||||
assert capture_dir.is_dir()
|
assert capture_dir.is_dir(), f'Capture directory {capture_dir} does not exist'
|
||||||
|
|
||||||
# Getters
|
|
||||||
def get_device_id(self) -> str:
|
|
||||||
return self.device_id
|
|
||||||
|
|
||||||
def get_start_time(self) -> str:
|
|
||||||
return self.start_time
|
|
||||||
|
|
||||||
def get_stop_time(self) -> str:
|
|
||||||
return self.stop_time
|
|
||||||
|
|
||||||
def get_packet_count(self) -> int:
|
|
||||||
return self.packet_count
|
|
||||||
|
|
||||||
def get_pcap_filter(self) -> str:
|
|
||||||
return self.pcap_filter
|
|
||||||
|
|
||||||
def get_device_ip_address(self) -> str:
|
|
||||||
return self.device_ip_address
|
|
||||||
|
|
||||||
def get_device_mac_address(self) -> str:
|
|
||||||
return self.device_mac_address
|
|
||||||
|
|
||||||
def get_app(self) -> str:
|
|
||||||
return self.app
|
|
||||||
|
|
||||||
def get_app_version(self) -> str:
|
|
||||||
return self.app_version
|
|
||||||
|
|
||||||
def get_firmware_version(self) -> str:
|
|
||||||
return self.firmware_version
|
|
||||||
|
|
||||||
def get_capture_id(self) -> UUID:
|
|
||||||
return self.capture_id
|
|
||||||
|
|
||||||
def get_capture_date(self) -> str:
|
|
||||||
return self.capture_date
|
|
||||||
|
|
||||||
def get_capfile_name(self):
|
|
||||||
return self.capture_file
|
|
||||||
|
|
||||||
def get_device_metadata(self) -> DeviceMetadata:
|
|
||||||
return self.device_metadata
|
|
||||||
|
|
||||||
def get_interface(self):
|
|
||||||
return self.interface
|
|
||||||
|
|
||||||
# Setters
|
|
||||||
def set_capture_dir(self, capture_dir: Path):
|
|
||||||
self.capture_dir = capture_dir
|
|
||||||
|
|
||||||
def set_capture_file(self, capture_file: str):
|
|
||||||
self.capture_file = capture_file
|
|
||||||
|
|
||||||
def set_capture_date(self, capture_date: str):
|
|
||||||
self.capture_date = capture_date
|
|
||||||
|
|
||||||
def set_start_time(self, start_time: str):
|
|
||||||
self.start_time = start_time
|
|
||||||
|
|
||||||
def set_stop_time(self, stop_time: str):
|
|
||||||
self.stop_time = stop_time
|
|
||||||
|
|
||||||
def set_packet_count(self, packet_count: int):
|
|
||||||
self.packet_count = packet_count
|
|
||||||
|
|
||||||
def set_pcap_filter(self, pcap_filter: str):
|
|
||||||
self.pcap_filter = pcap_filter
|
|
||||||
|
|
||||||
def set_device_ip_address(self, device_ip_address: str):
|
|
||||||
self.device_ip_address = device_ip_address
|
|
||||||
|
|
||||||
def set_device_mac_address(self, device_mac_address: str):
|
|
||||||
self.device_mac_address = device_mac_address
|
|
||||||
|
|
||||||
def set_app(self, app: str):
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
def set_app_version(self, app_version: str):
|
|
||||||
self.app_version = app_version
|
|
||||||
|
|
||||||
def set_firmware_version(self, firmware_version: str):
|
|
||||||
self.firmware_version = firmware_version
|
|
||||||
self.device_metadata.set_device_firmware_version(firmware_version)
|
|
||||||
|
|
||||||
def set_interface(self, interface: str):
|
|
||||||
self.interface = interface
|
|
||||||
|
|
||||||
def set_tcpdump_command(self, tcpdump_command: str):
|
|
||||||
self.tcpdump_command = tcpdump_command
|
|
||||||
|
|
||||||
# Other
|
|
||||||
|
|
||||||
def build_capture_file_name(self):
|
def build_capture_file_name(self):
|
||||||
prefix = ""
|
logger.info(f'Building capture file name')
|
||||||
if self.app is None:
|
if self.app is None:
|
||||||
prefix = self.device_metadata.get_device_short_name()
|
logger.debug(f'No app specified')
|
||||||
|
prefix = self.device_metadata.device_short_name
|
||||||
else:
|
else:
|
||||||
assert str(self.app).strip() not in {"", " "}, f"app is not a valid name: {self.app}"
|
logger.debug(f'App specified: {self.app}')
|
||||||
prefix = self.get_app()
|
assert str(self.app).strip() not in {'', ' '}, f'app is not a valid name: {self.app}'
|
||||||
# assert self.capture_dir is not None, f"{self.capture_dir} does not exist"
|
prefix = self.app.lower().replace(' ', '_')
|
||||||
filename = f"{prefix}_{str(self.capture_id)}.pcap"
|
# assert self.capture_dir is not None, f'{self.capture_dir} does not exist'
|
||||||
self.set_capture_file(filename)
|
filename = f'{prefix}_{str(self.capture_id)}.pcap'
|
||||||
|
logger.debug(f'Capture file name: {filename}')
|
||||||
|
self.capture_file = filename
|
||||||
|
|
||||||
def save_capture_metadata_to_json(self, file_path: Path = Path(CAPTURE_METADATA_FILE)):
|
def save_capture_metadata_to_json(self, file_path: Path = Path(CAPTURE_METADATA_FILE)):
|
||||||
assert self.capture_dir.is_dir(), f"capture_dir is not a directory: {self.capture_dir}"
|
assert self.capture_dir.is_dir(), f'capture_dir is not a directory: {self.capture_dir}'
|
||||||
if file_path.is_file():
|
if file_path.is_file():
|
||||||
print(f"File {file_path} already exists, update instead.")
|
print(f'File {file_path} already exists, update instead.')
|
||||||
return ReturnCodes.FILE_ALREADY_EXISTS
|
return ReturnCodes.FILE_ALREADY_EXISTS
|
||||||
metadata = self.model_dump_json(indent=2, exclude_unset=True, exclude_none=True)
|
metadata = self.to_json(indent=2)
|
||||||
with file_path.open('w') as file:
|
with file_path.open('w') as file:
|
||||||
json.dump(metadata, file)
|
json.dump(metadata, file)
|
||||||
return ReturnCodes.SUCCESS
|
return ReturnCodes.SUCCESS
|
||||||
|
|
||||||
|
def to_json(self, indent=2):
|
||||||
|
# TODO: Where to validate data?
|
||||||
|
logger.info(f'Converting CaptureMetadata to JSON')
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
# List of fields from CaptureData class, if fields[key]==True, then it is a required field
|
||||||
|
fields = {
|
||||||
|
'capture_id': True, #
|
||||||
|
'device_id': True,
|
||||||
|
'capture_dir': True,
|
||||||
|
'capture_file': False,
|
||||||
|
'capture_date': False,
|
||||||
|
'start_time': True,
|
||||||
|
'stop_time': True,
|
||||||
|
'packet_count': False,
|
||||||
|
'pcap_filter': False,
|
||||||
|
'tcpdump_command': False,
|
||||||
|
'interface': False,
|
||||||
|
'device_ip_address': False,
|
||||||
|
'device_mac_address': False,
|
||||||
|
'app': False,
|
||||||
|
'app_version': False,
|
||||||
|
'firmware_version': False
|
||||||
|
}
|
||||||
|
|
||||||
|
for field, is_mandatory in fields.items():
|
||||||
|
value = getattr(self, field, None)
|
||||||
|
if value not in [None, ''] or is_mandatory:
|
||||||
|
if value in [None, ''] and is_mandatory:
|
||||||
|
raise ValueError(f'Field {field} is required and cannot be empty.')
|
||||||
|
data[field] = str(value) if not isinstance(value, str) else value
|
||||||
|
logger.debug(f'Capture metadata: {data}')
|
||||||
|
return json.dumps(data, indent=indent)
|
||||||
|
|||||||
@@ -2,26 +2,26 @@ import json
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, List, Any
|
from typing import Optional, List
|
||||||
|
|
||||||
# iottb modules
|
# iottb modules
|
||||||
from iottb.definitions import ReturnCodes, DEVICE_METADATA_FILE
|
from iottb.definitions import ReturnCodes, DEVICE_METADATA_FILE
|
||||||
|
from iottb.logger import logger
|
||||||
# 3rd party libs
|
# 3rd party libs
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
|
|
||||||
IMMUTABLE_FIELDS = {"device_name", "device_short_name", "device_id", "date_created"}
|
IMMUTABLE_FIELDS = {'device_name', 'device_short_name', 'device_id', 'date_created'}
|
||||||
|
|
||||||
|
|
||||||
class DeviceMetadata(BaseModel):
|
class DeviceMetadata:
|
||||||
# Required fields
|
# Required fields
|
||||||
device_name: str
|
device_name: str
|
||||||
device_short_name: str
|
device_short_name: str
|
||||||
device_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
device_id: str
|
||||||
date_created: str = Field(default_factory=lambda: datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower())
|
date_created: str
|
||||||
|
|
||||||
device_root_path: Path
|
device_root_path: Path
|
||||||
# Optional Fields
|
# Optional Fields
|
||||||
aliases: List[str] = Field(default_factory=lambda: [])
|
aliases: Optional[List[str]] = None
|
||||||
device_type: Optional[str] = None
|
device_type: Optional[str] = None
|
||||||
device_serial_number: Optional[str] = None
|
device_serial_number: Optional[str] = None
|
||||||
device_firmware_version: Optional[str] = None
|
device_firmware_version: Optional[str] = None
|
||||||
@@ -29,91 +29,74 @@ class DeviceMetadata(BaseModel):
|
|||||||
|
|
||||||
capture_files: Optional[List[str]] = []
|
capture_files: Optional[List[str]] = []
|
||||||
|
|
||||||
def __init__(self, device_name: str, device_root_dir: Path, /, **data: Any):
|
def __init__(self, device_name: str, device_root_path: Path):
|
||||||
super().__init__(**data)
|
|
||||||
self.device_name = device_name
|
self.device_name = device_name
|
||||||
self.device_short_name = device_name.lower().replace(" ", "_")
|
self.device_short_name = device_name.lower().replace(' ', '_')
|
||||||
# assert dir_contains_device_metadata(device_root_dir), \
|
self.device_id = str(uuid.uuid4())
|
||||||
# f"Directory {device_root_dir} is missing a {DEVICE_METADATA_FILE} file"
|
self.date_created = datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower()
|
||||||
self.device_root_dir = device_root_dir
|
self.device_root_path = device_root_path
|
||||||
|
if not self.device_root_path or not self.device_root_path.is_dir():
|
||||||
def get_device_id(self) -> str:
|
logger.error(f'Invalid device root path: {device_root_path}')
|
||||||
return self.device_id
|
raise ValueError(f'Invalid device root path: {device_root_path}')
|
||||||
|
logger.debug(f'Device name: {device_name}')
|
||||||
def get_device_name(self) -> str:
|
logger.debug(f'Device short_name: {self.device_short_name}')
|
||||||
return self.device_name
|
logger.debug(f'Device root dir: {device_root_path}')
|
||||||
|
logger.info(f'Initialized DeviceMetadata model: {device_name}')
|
||||||
def get_device_short_name(self) -> str:
|
|
||||||
return self.device_short_name
|
|
||||||
|
|
||||||
def get_device_type(self) -> str:
|
|
||||||
return self.device_type
|
|
||||||
|
|
||||||
def get_device_serial_number(self) -> str:
|
|
||||||
return self.device_serial_number
|
|
||||||
|
|
||||||
def get_device_firmware_version(self) -> str:
|
|
||||||
return self.device_firmware_version
|
|
||||||
|
|
||||||
def get_date_updated(self) -> str:
|
|
||||||
return self.date_updated
|
|
||||||
|
|
||||||
def get_capture_files(self) -> List[str]:
|
|
||||||
return self.capture_files
|
|
||||||
|
|
||||||
def get_aliases(self) -> List[str]:
|
|
||||||
return self.aliases
|
|
||||||
|
|
||||||
def set_device_type(self, device_type: str) -> None:
|
|
||||||
self.device_type = device_type
|
|
||||||
self.date_updated = datetime.now().strftime('%d-%m-%YT%H:%M:%S')
|
|
||||||
|
|
||||||
def set_device_serial_number(self, device_serial_number: str) -> None:
|
|
||||||
self.device_serial_number = device_serial_number
|
|
||||||
self.date_updated = datetime.now().strftime('%d-%m-%YT%H:%M:%S')
|
|
||||||
|
|
||||||
def set_device_firmware_version(self, device_firmware_version: str) -> None:
|
|
||||||
self.device_firmware_version = device_firmware_version
|
|
||||||
self.date_updated = datetime.now().strftime('%d-%m-%YT%H:%M:%S')
|
|
||||||
|
|
||||||
def set_device_name(self, device_name: str) -> None:
|
|
||||||
self.device_name = device_name
|
|
||||||
self.device_short_name = device_name.lower().replace(" ", "_")
|
|
||||||
self.date_updated = datetime.now().strftime('%d-%m-%YT%H:%M:%S')
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load_from_json(cls, device_file_path: Path):
|
def load_from_json(cls, device_file_path: Path):
|
||||||
assert device_file_path.is_file(), f"{device_file_path} is not a file"
|
logger.info(f'Loading DeviceMetadata from JSON file: {device_file_path}')
|
||||||
assert device_file_path.name == DEVICE_METADATA_FILE, f"{device_file_path} is not a {DEVICE_METADATA_FILE}"
|
assert device_file_path.is_file(), f'{device_file_path} is not a file'
|
||||||
|
assert device_file_path.name == DEVICE_METADATA_FILE, f'{device_file_path} is not a {DEVICE_METADATA_FILE}'
|
||||||
device_meta_filename = device_file_path
|
device_meta_filename = device_file_path
|
||||||
|
|
||||||
with device_meta_filename.open('r') as file:
|
with device_meta_filename.open('r') as file:
|
||||||
metadata_json = json.load(file)
|
metadata_json = json.load(file)
|
||||||
metadata_model_obj = cls.model_validate_json(metadata_json)
|
metadata_model_obj = cls.from_json(metadata_json)
|
||||||
return metadata_model_obj
|
return metadata_model_obj
|
||||||
|
|
||||||
def save_to_json(self, file_path: Path):
|
def save_to_json(self, file_path: Path):
|
||||||
|
logger.info(f'Saving DeviceMetadata to JSON file: {file_path}')
|
||||||
if file_path.is_file():
|
if file_path.is_file():
|
||||||
print(f"File {file_path} already exists, update instead.")
|
print(f'File {file_path} already exists, update instead.')
|
||||||
return ReturnCodes.FILE_ALREADY_EXISTS
|
return ReturnCodes.FILE_ALREADY_EXISTS
|
||||||
metadata = self.model_dump_json(indent=2)
|
metadata = self.to_json(indent=2)
|
||||||
with file_path.open('w') as file:
|
with file_path.open('w') as file:
|
||||||
json.dump(metadata, file)
|
json.dump(metadata, file)
|
||||||
return ReturnCodes.SUCCESS
|
return ReturnCodes.SUCCESS
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_metadata_in_json(cls, file_path: Path, **kwargs):
|
def from_json(cls, metadata_json):
|
||||||
# TODO Maybe not needed at all.
|
if isinstance(metadata_json, dict):
|
||||||
assert file_path.is_file()
|
return DeviceMetadata(**metadata_json)
|
||||||
for field in IMMUTABLE_FIELDS:
|
|
||||||
if field in kwargs:
|
def to_json(self, indent=2):
|
||||||
print(f"Field {field} is immutable")
|
# TODO: atm almost exact copy as in CaptureMetadata
|
||||||
return ReturnCodes.IMMUTABLE
|
data = {}
|
||||||
metadata = cls.load_from_json(file_path)
|
|
||||||
for field, value in kwargs.items():
|
fields = {
|
||||||
if field in metadata.model_fields_set:
|
'device_name': True,
|
||||||
setattr(metadata, field, value)
|
'device_short_name': True,
|
||||||
metadata.date_updated = datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower()
|
'device_id': True,
|
||||||
pass
|
'date_created': True,
|
||||||
|
'device_root_path': True,
|
||||||
|
'aliases': False,
|
||||||
|
'device_type': False,
|
||||||
|
'device_serial_number': False,
|
||||||
|
'device_firmware_version': False,
|
||||||
|
'date_updated': False,
|
||||||
|
'capture_files': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
for field, is_mandatory in fields.items():
|
||||||
|
value = getattr(self, field, None)
|
||||||
|
if value not in [None, ''] or is_mandatory:
|
||||||
|
if value in [None, ''] and is_mandatory:
|
||||||
|
logger.debug(f'Mandatory field {field}: {value}')
|
||||||
|
raise ValueError(f'Field {field} is required and cannot be empty.')
|
||||||
|
data[field] = str(value) if not isinstance(value, str) else value
|
||||||
|
logger.debug(f'Device metadata: {data}')
|
||||||
|
return json.dumps(data, indent=indent)
|
||||||
|
|
||||||
|
|
||||||
def dir_contains_device_metadata(dir_path: Path):
|
def dir_contains_device_metadata(dir_path: Path):
|
||||||
@@ -121,7 +104,7 @@ def dir_contains_device_metadata(dir_path: Path):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
meta_file_path = dir_path / DEVICE_METADATA_FILE
|
meta_file_path = dir_path / DEVICE_METADATA_FILE
|
||||||
print(f"Device metadata file path {str(meta_file_path)}")
|
print(f'Device metadata file path {str(meta_file_path)}')
|
||||||
if not meta_file_path.is_file():
|
if not meta_file_path.is_file():
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,41 +1,58 @@
|
|||||||
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
from iottb import definitions
|
from iottb import definitions
|
||||||
from iottb.definitions import DEVICE_METADATA_FILE, ReturnCodes
|
from iottb.definitions import DEVICE_METADATA_FILE, ReturnCodes
|
||||||
|
from iottb.logger import logger
|
||||||
from iottb.models.device_metadata_model import DeviceMetadata
|
from iottb.models.device_metadata_model import DeviceMetadata
|
||||||
from iottb.utils.device_metadata_utils import *
|
|
||||||
|
logger.setLevel(logging.INFO) # Since module currently passes all tests
|
||||||
|
|
||||||
|
|
||||||
def setup_init_device_root_parser(subparsers):
|
def setup_init_device_root_parser(subparsers):
|
||||||
parser = subparsers.add_parser("add-device", aliases=["add-device-root", "add"])
|
parser = subparsers.add_parser('add-device', aliases=['add-device-root', 'add'],
|
||||||
parser.add_argument("--root_dir", type=pathlib.Path, default=pathlib.Path.cwd())
|
help='Initialize a folder for a device.')
|
||||||
|
parser.add_argument('--root_dir', type=pathlib.Path, default=pathlib.Path.cwd())
|
||||||
group = parser.add_mutually_exclusive_group()
|
group = parser.add_mutually_exclusive_group()
|
||||||
group.add_argument("--guided", action="store_true", help="Guided setup", default=False)
|
group.add_argument('--guided', action='store_true', help='Guided setup', default=False)
|
||||||
group.add_argument("--name", action="store", type=str, help="name of device")
|
group.add_argument('--name', action='store', type=str, help='name of device')
|
||||||
parser.set_defaults(func=handle_add)
|
parser.set_defaults(func=handle_add)
|
||||||
|
|
||||||
|
|
||||||
def handle_add(args):
|
def handle_add(args):
|
||||||
print("Entered add-device-root")
|
logger.info(f'Add device handler called with args {args}')
|
||||||
|
|
||||||
|
args.root_dir.mkdir(parents=True,
|
||||||
|
exist_ok=True) # else metadata.save_to_file will fail TODO: unclear what to assume
|
||||||
|
|
||||||
if args.guided:
|
if args.guided:
|
||||||
|
logger.debug('begin guided setup')
|
||||||
metadata = guided_setup(args.root_dir)
|
metadata = guided_setup(args.root_dir)
|
||||||
|
logger.debug('guided setup complete')
|
||||||
else:
|
else:
|
||||||
device_name = args.name
|
logger.debug('Setup through passed args: setup')
|
||||||
args.root_dir.mkdir(parents=True, exist_ok=True)
|
if not args.name:
|
||||||
args.root_dir.chdir()
|
logger.error('No device name specified with unguided setup.')
|
||||||
metadata = DeviceMetadata(device_name, args.root_dir)
|
return ReturnCodes.ERROR
|
||||||
|
metadata = DeviceMetadata(args.name, args.root_dir)
|
||||||
|
|
||||||
file_path = args.root_dir / DEVICE_METADATA_FILE
|
file_path = args.root_dir / DEVICE_METADATA_FILE
|
||||||
response = input(f"Confirm device metadata: {metadata.model_dump()} [y/N]")
|
if file_path.exists():
|
||||||
if response.lower() not in definitions.AFFIRMATIVE_USER_RESPONSE.add(""):
|
print('Directory already contains a metadata file. Aborting.')
|
||||||
configure_metadata()
|
|
||||||
assert False, "TODO implement dynamic setup"
|
|
||||||
assert metadata.model_dump() != ""
|
|
||||||
if metadata.save_to_json(file_path) == ReturnCodes.FILE_ALREADY_EXISTS:
|
|
||||||
print("Directory already contains a device metadata file. Aborting operation.")
|
|
||||||
return ReturnCodes.ABORTED
|
return ReturnCodes.ABORTED
|
||||||
assert Path(file_path).exists(), f"{file_path} does not exist"
|
serialized_metadata = metadata.to_json()
|
||||||
|
response = input(f'Confirm device metadata: {serialized_metadata} [y/N]')
|
||||||
|
logger.debug(f'response: {response}')
|
||||||
|
if response not in definitions.AFFIRMATIVE_USER_RESPONSE:
|
||||||
|
print('Adding device aborted by user.')
|
||||||
|
return ReturnCodes.ABORTED
|
||||||
|
|
||||||
|
logger.debug(f'Device metadata file {file_path}')
|
||||||
|
if metadata.save_to_json(file_path) == ReturnCodes.FILE_ALREADY_EXISTS:
|
||||||
|
logger.error('File exists after checking, which should not happen.')
|
||||||
|
return ReturnCodes.ABORTED
|
||||||
|
|
||||||
|
print('Device metadata successfully created.')
|
||||||
return ReturnCodes.SUCCESS
|
return ReturnCodes.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
@@ -44,16 +61,16 @@ def configure_metadata():
|
|||||||
|
|
||||||
|
|
||||||
def guided_setup(device_root) -> DeviceMetadata:
|
def guided_setup(device_root) -> DeviceMetadata:
|
||||||
response = "N"
|
logger.info('Guided setup')
|
||||||
device_name = ""
|
response = 'N'
|
||||||
while response.upper() == "N":
|
device_name = ''
|
||||||
device_name = input("Please enter name of device: ")
|
while response.upper() == 'N':
|
||||||
if device_name == "" or device_name is None:
|
device_name = input('Please enter name of device: ')
|
||||||
print("Name cannot be empty")
|
response = input(f'Confirm device name: {device_name} [y/N] ')
|
||||||
response = input(f"Confirm device name: {device_name} [y/N] ")
|
if device_name == '' or device_name is None:
|
||||||
|
print('Name cannot be empty')
|
||||||
|
logger.warning('Name cannot be empty')
|
||||||
|
logger.debug(f'Response is {response}')
|
||||||
|
logger.debug(f'Device name is {device_name}')
|
||||||
|
|
||||||
assert response.lower() in definitions.AFFIRMATIVE_USER_RESPONSE.add(""), f"{response.upper()} not supported"
|
|
||||||
assert device_name != ""
|
|
||||||
assert device_name is not None
|
|
||||||
return DeviceMetadata(device_name, device_root)
|
return DeviceMetadata(device_name, device_root)
|
||||||
|
|
||||||
|
|||||||
@@ -5,36 +5,36 @@ from iottb.definitions import *
|
|||||||
from iottb.models.capture_metadata_model import CaptureMetadata
|
from iottb.models.capture_metadata_model import CaptureMetadata
|
||||||
from iottb.models.device_metadata_model import DeviceMetadata, dir_contains_device_metadata
|
from iottb.models.device_metadata_model import DeviceMetadata, dir_contains_device_metadata
|
||||||
from iottb.utils.capture_utils import get_capture_src_folder, make_capture_src_folder
|
from iottb.utils.capture_utils import get_capture_src_folder, make_capture_src_folder
|
||||||
|
from iottb.utils.tcpdump_utils import check_installed
|
||||||
|
|
||||||
def setup_capture_parser(subparsers):
|
def setup_capture_parser(subparsers):
|
||||||
parser = subparsers.add_parser('sniff', help='Sniff packets with tcpdump')
|
parser = subparsers.add_parser('sniff', help='Sniff packets with tcpdump')
|
||||||
# metadata args
|
# metadata args
|
||||||
parser.add_argument("-a", "--ip-address", help="IP address of the device to sniff", dest="device_ip")
|
parser.add_argument('-a', '--ip-address', help='IP address of the device to sniff', dest='device_ip')
|
||||||
# tcpdump args
|
# tcpdump args
|
||||||
parser.add_argument("device_root", help="Root folder for device to sniff",
|
parser.add_argument('device_root', help='Root folder for device to sniff',
|
||||||
type=Path, default=Path.cwd())
|
type=Path, default=Path.cwd())
|
||||||
parser.add_argument("-s", "--safe", help="Ensure correct device root folder before sniffing", action="store_true")
|
parser.add_argument('-s', '--safe', help='Ensure correct device root folder before sniffing', action='store_true')
|
||||||
parser.add_argument("--app", help="Application name to sniff", dest="app_name", default=None)
|
parser.add_argument('--app', help='Application name to sniff', dest='app_name', default=None)
|
||||||
|
|
||||||
parser_sniff_tcpdump = parser.add_argument_group('tcpdump arguments')
|
parser_sniff_tcpdump = parser.add_argument_group('tcpdump arguments')
|
||||||
parser_sniff_tcpdump.add_argument("-i", "--interface", help="Interface to capture on.", dest="capture_interface",
|
parser_sniff_tcpdump.add_argument('-i', '--interface', help='Interface to capture on.', dest='capture_interface',
|
||||||
required=True)
|
required=True)
|
||||||
parser_sniff_tcpdump.add_argument("-I", "--monitor-mode", help="Put interface into monitor mode",
|
parser_sniff_tcpdump.add_argument('-I', '--monitor-mode', help='Put interface into monitor mode',
|
||||||
action="store_true")
|
action='store_true')
|
||||||
parser_sniff_tcpdump.add_argument("-n", help="Deactivate name resolution. True by default.",
|
parser_sniff_tcpdump.add_argument('-n', help='Deactivate name resolution. True by default.',
|
||||||
action="store_true", dest="no_name_resolution")
|
action='store_true', dest='no_name_resolution')
|
||||||
parser_sniff_tcpdump.add_argument("-#", "--number",
|
parser_sniff_tcpdump.add_argument('-#', '--number',
|
||||||
help="Print packet number at beginning of line. True by default.",
|
help='Print packet number at beginning of line. True by default.',
|
||||||
action="store_true")
|
action='store_true')
|
||||||
parser_sniff_tcpdump.add_argument("-e", help="Print link layer headers. True by default.",
|
parser_sniff_tcpdump.add_argument('-e', help='Print link layer headers. True by default.',
|
||||||
action="store_true", dest="print_link_layer")
|
action='store_true', dest='print_link_layer')
|
||||||
parser_sniff_tcpdump.add_argument("-t", action="count", default=0,
|
parser_sniff_tcpdump.add_argument('-t', action='count', default=0,
|
||||||
help="Please see tcpdump manual for details. Unused by default.")
|
help='Please see tcpdump manual for details. Unused by default.')
|
||||||
|
|
||||||
cap_size_group = parser.add_mutually_exclusive_group(required=False)
|
cap_size_group = parser.add_mutually_exclusive_group(required=False)
|
||||||
cap_size_group.add_argument("-c", "--count", type=int, help="Number of packets to capture.", default=1000)
|
cap_size_group.add_argument('-c', '--count', type=int, help='Number of packets to capture.', default=1000)
|
||||||
cap_size_group.add_argument("--mins", type=int, help="Time in minutes to capture.", default=1)
|
cap_size_group.add_argument('--mins', type=int, help='Time in minutes to capture.', default=1)
|
||||||
|
|
||||||
parser.set_defaults(func=handle_capture)
|
parser.set_defaults(func=handle_capture)
|
||||||
|
|
||||||
@@ -45,25 +45,25 @@ def cwd_is_device_root_dir() -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def start_guided_device_root_dir_setup():
|
def start_guided_device_root_dir_setup():
|
||||||
assert False, "Not implemented"
|
assert False, 'Not implemented'
|
||||||
|
|
||||||
|
|
||||||
def handle_metadata():
|
def handle_metadata():
|
||||||
assert not cwd_is_device_root_dir()
|
assert not cwd_is_device_root_dir()
|
||||||
print(f"Unable to find {DEVICE_METADATA_FILE} in current working directory")
|
print(f'Unable to find {DEVICE_METADATA_FILE} in current working directory')
|
||||||
print("You need to setup a device root directory before using this command")
|
print('You need to setup a device root directory before using this command')
|
||||||
response = input("Would you like to be guided through the setup? [y/n]")
|
response = input('Would you like to be guided through the setup? [y/n]')
|
||||||
if response.lower() == "y":
|
if response.lower() == 'y':
|
||||||
start_guided_device_root_dir_setup()
|
start_guided_device_root_dir_setup()
|
||||||
else:
|
else:
|
||||||
print("'iottb init-device-root --help' for more information.")
|
print('\'iottb init-device-root --help\' for more information.')
|
||||||
exit(ReturnCodes.ABORTED)
|
exit(ReturnCodes.ABORTED)
|
||||||
# device_id = handle_capture_metadata()
|
# device_id = handle_capture_metadata()
|
||||||
return ReturnCodes.SUCCESS
|
return ReturnCodes.SUCCESS
|
||||||
|
|
||||||
|
|
||||||
def get_device_metadata_from_file(device_metadata_filename: Path) -> str:
|
def get_device_metadata_from_file(device_metadata_filename: Path) -> str:
|
||||||
assert device_metadata_filename.is_file(), f"Device metadata file '{device_metadata_filename} does not exist"
|
assert device_metadata_filename.is_file(), f'Device metadata file f"{device_metadata_filename}" does not exist'
|
||||||
device_metadata = DeviceMetadata.load_from_json(device_metadata_filename)
|
device_metadata = DeviceMetadata.load_from_json(device_metadata_filename)
|
||||||
return device_metadata
|
return device_metadata
|
||||||
|
|
||||||
@@ -73,26 +73,30 @@ def run_tcpdump(cmd):
|
|||||||
try:
|
try:
|
||||||
p = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
p = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
||||||
if p.returncode != 0:
|
if p.returncode != 0:
|
||||||
print(f"Error running tcpdump {p.stderr}")
|
print(f'Error running tcpdump {p.stderr}')
|
||||||
|
# TODO add logging
|
||||||
else:
|
else:
|
||||||
print(f"tcpdump run successfully\n: {p.stdout}")
|
print(f'tcpdump run successfully\n: {p.stdout}')
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def handle_capture(args):
|
def handle_capture(args):
|
||||||
assert args.device_root is not None, f"Device root directory is required"
|
if not check_installed():
|
||||||
assert dir_contains_device_metadata(args.device_root), f"Device metadata file '{args.device_root}' does not exist"
|
print('Please install tcpdump first')
|
||||||
|
exit(ReturnCodes.ABORTED)
|
||||||
|
assert args.device_root is not None, f'Device root directory is required'
|
||||||
|
assert dir_contains_device_metadata(args.device_root), f'Device metadata file \'{args.device_root}\' does not exist'
|
||||||
# get device metadata
|
# get device metadata
|
||||||
if args.safe and not dir_contains_device_metadata(args.device_root):
|
if args.safe and not dir_contains_device_metadata(args.device_root):
|
||||||
print(f"Supplied folder contains no device metadata. "
|
print(f'Supplied folder contains no device metadata. '
|
||||||
f"Please setup a device root directory before using this command")
|
f'Please setup a device root directory before using this command')
|
||||||
exit(ReturnCodes.ABORTED)
|
exit(ReturnCodes.ABORTED)
|
||||||
elif dir_contains_device_metadata(args.device_root):
|
elif dir_contains_device_metadata(args.device_root):
|
||||||
device_metadata_filename = args.device_root / DEVICE_METADATA_FILE
|
device_metadata_filename = args.device_root / DEVICE_METADATA_FILE
|
||||||
device_data = DeviceMetadata.load_from_json(device_metadata_filename)
|
device_data = DeviceMetadata.load_from_json(device_metadata_filename)
|
||||||
else:
|
else:
|
||||||
name = input("Please enter a device name: ")
|
name = input('Please enter a device name: ')
|
||||||
args.device_root.mkdir(parents=True, exist_ok=True)
|
args.device_root.mkdir(parents=True, exist_ok=True)
|
||||||
device_data = DeviceMetadata(name, args.device_root)
|
device_data = DeviceMetadata(name, args.device_root)
|
||||||
# start constructing environment for capture
|
# start constructing environment for capture
|
||||||
@@ -100,10 +104,10 @@ def handle_capture(args):
|
|||||||
make_capture_src_folder(capture_dir)
|
make_capture_src_folder(capture_dir)
|
||||||
capture_metadata = CaptureMetadata(device_data, capture_dir)
|
capture_metadata = CaptureMetadata(device_data, capture_dir)
|
||||||
|
|
||||||
capture_metadata.set_interface(args.capture_interface)
|
capture_metadata.interface = args.capture_interface
|
||||||
cmd = ['sudo', 'tcpdump', '-i', args.capture_interface]
|
cmd = ['sudo', 'tcpdump', '-i', args.capture_interface]
|
||||||
cmd = build_tcpdump_args(args, cmd, capture_metadata)
|
cmd = build_tcpdump_args(args, cmd, capture_metadata)
|
||||||
capture_metadata.set_tcpdump_command(cmd)
|
capture_metadata.tcpdump_command = cmd
|
||||||
|
|
||||||
print('Executing: ' + ' '.join(cmd))
|
print('Executing: ' + ' '.join(cmd))
|
||||||
|
|
||||||
@@ -112,16 +116,16 @@ def handle_capture(args):
|
|||||||
start_time = datetime.now().strftime('%H:%M:%S')
|
start_time = datetime.now().strftime('%H:%M:%S')
|
||||||
run_tcpdump(cmd)
|
run_tcpdump(cmd)
|
||||||
stop_time = datetime.now().strftime('%H:%M:%S')
|
stop_time = datetime.now().strftime('%H:%M:%S')
|
||||||
capture_metadata.set_start_time(start_time)
|
capture_metadata.start_time = start_time
|
||||||
capture_metadata.set_stop_time(stop_time)
|
capture_metadata.stop_time = stop_time
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("Received keyboard interrupt.")
|
print('Received keyboard interrupt.')
|
||||||
exit(ReturnCodes.ABORTED)
|
exit(ReturnCodes.ABORTED)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"Failed to capture packet: {e}")
|
print(f'Failed to capture packet: {e}')
|
||||||
exit(ReturnCodes.FAILURE)
|
exit(ReturnCodes.FAILURE)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Failed to capture packet: {e}")
|
print(f'Failed to capture packet: {e}')
|
||||||
exit(ReturnCodes.FAILURE)
|
exit(ReturnCodes.FAILURE)
|
||||||
|
|
||||||
return ReturnCodes.SUCCESS
|
return ReturnCodes.SUCCESS
|
||||||
@@ -141,18 +145,18 @@ def build_tcpdump_args(args, cmd, capture_metadata: CaptureMetadata):
|
|||||||
cmd.append('-c')
|
cmd.append('-c')
|
||||||
cmd.append(str(args.count))
|
cmd.append(str(args.count))
|
||||||
elif args.mins:
|
elif args.mins:
|
||||||
assert False, "Unimplemented option"
|
assert False, 'Unimplemented option'
|
||||||
|
|
||||||
if args.app_name is not None:
|
if args.app_name is not None:
|
||||||
capture_metadata.set_app_name(args.app_name)
|
capture_metadata.app = args.app_name
|
||||||
|
|
||||||
capture_metadata.build_capture_file_name()
|
capture_metadata.build_capture_file_name()
|
||||||
cmd.append('-w')
|
cmd.append('-w')
|
||||||
cmd.append(capture_metadata.get_capfile_name())
|
cmd.append(capture_metadata.capture_file)
|
||||||
|
|
||||||
if args.safe:
|
if args.safe:
|
||||||
cmd.append(f'host {args.device_ip}') # if not specified, filter 'any' implied by tcpdump
|
cmd.append(f'host {args.device_ip}') # if not specified, filter 'any' implied by tcpdump
|
||||||
capture_metadata.set_device_ip_address(args.device_ip)
|
capture_metadata.device_id = args.device_ip
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
@@ -162,7 +166,7 @@ def build_tcpdump_args(args, cmd, capture_metadata: CaptureMetadata):
|
|||||||
# if args.app_name is not None:
|
# if args.app_name is not None:
|
||||||
# capture_file_prefix = args.app_name
|
# capture_file_prefix = args.app_name
|
||||||
# capture_metadata.set_app(args.app_name)
|
# capture_metadata.set_app(args.app_name)
|
||||||
# capfile_name = capture_file_prefix + "_" + str(capture_metadata.get_capture_id()) + ".pcap"
|
# capfile_name = capture_file_prefix + '_' + str(capture_metadata.get_capture_id()) + '.pcap'
|
||||||
# capture_metadata.set_capture_file(capfile_name)
|
# capture_metadata.set_capture_file(capfile_name)
|
||||||
# capfile_abs_path = capture_dir / capfile_name
|
# capfile_abs_path = capture_dir / capfile_name
|
||||||
# capture_metadata.set_capture_file(capfile_name)
|
# capture_metadata.set_capture_file(capfile_name)
|
||||||
|
|||||||
@@ -16,29 +16,29 @@ def get_capture_date_folder(device_root: Path):
|
|||||||
try:
|
try:
|
||||||
today_folder.mkdir()
|
today_folder.mkdir()
|
||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
print(f"Folder {today_folder} already exists")
|
print(f'Folder {today_folder} already exists')
|
||||||
return today_folder
|
return today_folder
|
||||||
raise FileNotFoundError(f"Given path {device_root} is not a device root directory")
|
raise FileNotFoundError(f'Given path {device_root} is not a device root directory')
|
||||||
|
|
||||||
|
|
||||||
def get_capture_src_folder(device_folder: Path):
|
def get_capture_src_folder(device_folder: Path):
|
||||||
assert device_folder.is_dir(), f"Given path {device_folder} is not a folder"
|
assert device_folder.is_dir(), f'Given path {device_folder} is not a folder'
|
||||||
today_iso = get_iso_date()
|
today_iso = get_iso_date()
|
||||||
max_sequence_number = 1
|
max_sequence_number = 1
|
||||||
for d in device_folder.iterdir():
|
for d in device_folder.iterdir():
|
||||||
if d.is_dir() and d.name.startswith(f'{today_iso}_capture_'):
|
if d.is_dir() and d.name.startswith(f'{today_iso}_capture_'):
|
||||||
name = d.name
|
name = d.name
|
||||||
num = int(name.split("_")[2])
|
num = int(name.split('_')[2])
|
||||||
max_sequence_number = max(max_sequence_number, num)
|
max_sequence_number = max(max_sequence_number, num)
|
||||||
|
|
||||||
next_sequence_number = max_sequence_number + 1
|
next_sequence_number = max_sequence_number + 1
|
||||||
return device_folder.joinpath(f"{today_iso}_capture_{next_sequence_number:03}")
|
return device_folder.joinpath(f'{today_iso}_capture_{next_sequence_number:03}')
|
||||||
|
|
||||||
|
|
||||||
def make_capture_src_folder(capture_src_folder: Path):
|
def make_capture_src_folder(capture_src_folder: Path):
|
||||||
try:
|
try:
|
||||||
capture_src_folder.mkdir()
|
capture_src_folder.mkdir()
|
||||||
except FileExistsError:
|
except FileExistsError:
|
||||||
print(f"Folder {capture_src_folder} already exists")
|
print(f'Folder {capture_src_folder} already exists')
|
||||||
finally:
|
finally:
|
||||||
return capture_src_folder
|
return capture_src_folder
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ def check_installed() -> bool:
|
|||||||
def ensure_installed():
|
def ensure_installed():
|
||||||
"""Ensure that tcpdump is installed, raise an error if not."""
|
"""Ensure that tcpdump is installed, raise an error if not."""
|
||||||
if not check_installed():
|
if not check_installed():
|
||||||
raise RuntimeError("tcpdump is not installed. Please install it to continue.")
|
raise RuntimeError('tcpdump is not installed. Please install it to continue.')
|
||||||
|
|
||||||
|
|
||||||
def list_interfaces() -> str:
|
def list_interfaces() -> str:
|
||||||
@@ -22,8 +22,8 @@ def list_interfaces() -> str:
|
|||||||
result = subprocess.run(['tcpdump', '--list-interfaces'], capture_output=True, text=True, check=True)
|
result = subprocess.run(['tcpdump', '--list-interfaces'], capture_output=True, text=True, check=True)
|
||||||
return result.stdout
|
return result.stdout
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"Failed to list interfaces: {e}")
|
print(f'Failed to list interfaces: {e}')
|
||||||
return ""
|
return ''
|
||||||
|
|
||||||
|
|
||||||
def is_valid_ipv4(ip: str) -> bool:
|
def is_valid_ipv4(ip: str) -> bool:
|
||||||
|
|||||||
@@ -13,6 +13,6 @@ def subfolder_exists(parent: Path, child: str):
|
|||||||
|
|
||||||
|
|
||||||
def generate_unique_string_with_prefix(prefix: str):
|
def generate_unique_string_with_prefix(prefix: str):
|
||||||
return prefix + "_" + str(uuid.uuid4())
|
return prefix + '_' + str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
47
code/tests/subcommands/test_add_device.py
Normal file
47
code/tests/subcommands/test_add_device.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
from io import StringIO
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
from pathlib import Path
|
||||||
|
from iottb.definitions import DEVICE_METADATA_FILE
|
||||||
|
import shutil
|
||||||
|
from iottb.__main__ import main
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeviceMetadataFileCreation(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.test_dir = Path('/tmp/iottbtest/test_add_device')
|
||||||
|
self.test_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
# self.captured_output = StringIO()
|
||||||
|
# sys.stdout = self.captured_output
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# shutil.rmtree(str(self.test_dir))
|
||||||
|
for item in self.test_dir.iterdir():
|
||||||
|
if item.is_dir():
|
||||||
|
item.rmdir()
|
||||||
|
else:
|
||||||
|
item.unlink()
|
||||||
|
self.test_dir.rmdir()
|
||||||
|
# sys.stdout = sys.__stdout__
|
||||||
|
|
||||||
|
@patch('builtins.input', side_effect=['iPhone 14', 'y', 'y'])
|
||||||
|
def test_guided_device_setup(self, mock_input):
|
||||||
|
sys.argv = ['__main__.py', 'add', '--root_dir', str(self.test_dir), '--guided']
|
||||||
|
main()
|
||||||
|
expected_file = self.test_dir / DEVICE_METADATA_FILE
|
||||||
|
self.assertTrue(expected_file.exists()), f'Expected file not created: {expected_file}'
|
||||||
|
|
||||||
|
@patch('builtins.input', side_effect=['y']) # need mock_input else wont work
|
||||||
|
def test_device_setup(self, mock_input):
|
||||||
|
sys.argv = ['__main__.py', 'add', '--root_dir', str(self.test_dir), '--name', 'iPhone 14']
|
||||||
|
main()
|
||||||
|
expected_file = self.test_dir / DEVICE_METADATA_FILE
|
||||||
|
self.assertTrue(expected_file.exists()), f'Expected file not created: {expected_file}'
|
||||||
|
|
||||||
|
def test_add_when_file_exists(self):
|
||||||
|
# TODO
|
||||||
|
pass
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||
@@ -1,6 +1,2 @@
|
|||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
from unittest.mock import mock_open, patch
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from iottb.utils.capture_metadata_utils import set_device_ip_address
|
|
||||||
|
|||||||
177
notes/journal/2024-05-08-sun.md
Normal file
177
notes/journal/2024-05-08-sun.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Commands to remember + sample output
|
||||||
|
Used commands: [[nmcli]], [[iw]], [[grep]], [[sed]]
|
||||||
|
Resources: [Capturing Wireless LAN Packets in Monitor Mode with iw](https://sandilands.info/sgordon/capturing-wifi-in-monitor-mode-with-iw)
|
||||||
|
Foreign BSSIDs have been made anonymous by replacing with `XX:XX:XX:XX:XX:XX`.
|
||||||
|
## [[nmcli]]
|
||||||
|
Useful for getting channel needed to setup monitor mode properly.
|
||||||
|
### `nmcli dev wifi`
|
||||||
|
```
|
||||||
|
IN-USE BSSID SSID MODE CHAN RATE SIGNAL BARS SECURITY
|
||||||
|
XX:XX:XX:XX:XX:XX FRITZ!Box 5490 PB Infra 6 195 Mbit/s 75 ▂▄▆_ WPA2
|
||||||
|
* 4C:1B:86:D1:06:7B LenbrO Infra 100 540 Mbit/s 67 ▂▄▆_ WPA2
|
||||||
|
4C:1B:86:D1:06:7C LenbrO Infra 6 260 Mbit/s 64 ▂▄▆_ WPA2
|
||||||
|
B8:BE:F4:4D:48:17 LenbrO Infra 1 130 Mbit/s 62 ▂▄▆_ WPA
|
||||||
|
XX:XX:XX:XX:XX:XX -- Infra 6 260 Mbit/s 60 ▂▄▆_ WPA2
|
||||||
|
XX:XX:XX:XX:XX:XX FRITZ!Box 5490 PB Infra 60 405 Mbit/s 37 ▂▄__ WPA2
|
||||||
|
XX:XX:XX:XX:XX:XX FRITZ!Box Fon WLAN 7360 BP Infra 1 130 Mbit/s 34 ▂▄__ WPA1 WPA2
|
||||||
|
XX:XX:XX:XX:XX:XX FRITZ!Box 5490 PB Infra 6 195 Mbit/s 34 ▂▄__ WPA2
|
||||||
|
XX:XX:XX:XX:XX:XX Sunrise_Wi-Fi_09FB29 Infra 7 540 Mbit/s 34 ▂▄__ WPA2 WPA3
|
||||||
|
XX:XX:XX:XX:XX:XX Madchenband Infra 11 260 Mbit/s 34 ▂▄__ WPA2
|
||||||
|
XX:XX:XX:XX:XX:XX LenbrO Infra 36 270 Mbit/s 34 ▂▄__ WPA2
|
||||||
|
XX:XX:XX:XX:XX:XX FibreBox_X6-01EF47 Infra 1 260 Mbit/s 32 ▂▄__ WPA2
|
||||||
|
XX:XX:XX:XX:XX:XX -- Infra 11 260 Mbit/s 32 ▂▄__ WPA2
|
||||||
|
XX:XX:XX:XX:XX:XX EEG-04666 Infra 1 405 Mbit/s 30 ▂___ WPA2
|
||||||
|
XX:XX:XX:XX:XX:XX Salt_2GHz_8A9170 Infra 11 260 Mbit/s 29 ▂___ WPA2
|
||||||
|
XX:XX:XX:XX:XX:XX -- Infra 11 260 Mbit/s 24 ▂___ WPA2
|
||||||
|
XX:XX:XX:XX:XX:XX FRITZ!Box 5490 PB Infra 60 405 Mbit/s 19 ▂___ WPA2
|
||||||
|
```
|
||||||
|
### `nmcli -t dev wifi`
|
||||||
|
```
|
||||||
|
XX\:XX\:XX\:XX\:XX\:XX:FRITZ!Box 5490 PB:Infra:6:195 Mbit/s:79:▂▄▆_:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX::Infra:6:260 Mbit/s:75:▂▄▆_:WPA2
|
||||||
|
:4C\:1B\:86\:D1\:06\:7C:LenbrO:Infra:6:260 Mbit/s:74:▂▄▆_:WPA2
|
||||||
|
*:4C\:1B\:86\:D1\:06\:7B:LenbrO:Infra:100:540 Mbit/s:72:▂▄▆_:WPA2
|
||||||
|
:B8\:BE\:F4\:4D\:48\:17:LenbrO:Infra:1:130 Mbit/s:65:▂▄▆_:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:Sunrise_Wi-Fi_09FB29:Infra:7:540 Mbit/s:52:▂▄__:WPA2 WPA3
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:FRITZ!Box 5490 PB:Infra:60:405 Mbit/s:50:▂▄__:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:FRITZ!Box Fon WLAN 7360 BP:Infra:1:130 Mbit/s:47:▂▄__:WPA1 WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:FRITZ!Box 5490 PB:Infra:6:195 Mbit/s:45:▂▄__:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:Zentrum der Macht:Infra:1:195 Mbit/s:44:▂▄__:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:FibreBox_X6-01EF47:Infra:1:260 Mbit/s:42:▂▄__:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:Madchenband:Infra:11:260 Mbit/s:40:▂▄__:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:LenbrO:Infra:36:270 Mbit/s:37:▂▄__:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX::Infra:11:260 Mbit/s:34:▂▄__:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:EEG-04666:Infra:1:405 Mbit/s:30:▂___:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:Salt_2GHz_8A9170:Infra:11:260 Mbit/s:29:▂___:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:FRITZ!Box 5490 PB:Infra:60:405 Mbit/s:27:▂___:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:Madchenband2.0:Infra:100:540 Mbit/s:25:▂___:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX::Infra:11:260 Mbit/s:24:▂___:WPA2
|
||||||
|
:XX\:XX\:XX\:XX\:XX\:XX:FibreBox_X6-01EF47:Infra:44:540 Mbit/s:20:▂___:WPA2
|
||||||
|
```
|
||||||
|
## [[iw]]
|
||||||
|
### `iw dev`
|
||||||
|
Useful to list interfaces and see which hardware they correspond to.
|
||||||
|
Can use that to create a monitor interface with an easier to remember name.
|
||||||
|
```
|
||||||
|
phy#1
|
||||||
|
Unnamed/non-netdev interface
|
||||||
|
wdev 0x100000002
|
||||||
|
addr 3c:21:9c:f2:e4:00
|
||||||
|
type P2P-device
|
||||||
|
Interface wlp44s0
|
||||||
|
ifindex 5
|
||||||
|
wdev 0x100000001
|
||||||
|
addr e6:bf:0c:3c:47:ba
|
||||||
|
ssid LenbrO
|
||||||
|
type managed
|
||||||
|
channel 100 (5500 MHz), width: 80 MHz, center1: 5530 MHz
|
||||||
|
txpower 22.00 dBm
|
||||||
|
multicast TXQ:
|
||||||
|
qsz-byt qsz-pkt flows drops marks overlmt hashcol tx-bytes tx-packets
|
||||||
|
0 0 0 0 0 0 0 0 0
|
||||||
|
phy#0
|
||||||
|
Interface mon0
|
||||||
|
ifindex 7
|
||||||
|
wdev 0x2
|
||||||
|
addr a8:42:a1:8b:f4:e3
|
||||||
|
type monitor
|
||||||
|
channel 6 (2437 MHz), width: 20 MHz (no HT), center1: 2437 MHz
|
||||||
|
txpower 20.00 dBm
|
||||||
|
Interface wlp0s20f0u6
|
||||||
|
ifindex 4
|
||||||
|
wdev 0x1
|
||||||
|
addr a8:42:a1:8b:f4:e3
|
||||||
|
type monitor
|
||||||
|
channel 6 (2437 MHz), width: 20 MHz (no HT), center1: 2437 MHz
|
||||||
|
txpower 20.00 dBm
|
||||||
|
multicast TXQ:
|
||||||
|
qsz-byt qsz-pkt flows drops marks overlmt hashcol tx-bytes tx-packets
|
||||||
|
0 0 0 0 0 0 0 0 0
|
||||||
|
|
||||||
|
```
|
||||||
|
Here, `phy#1` is my laptops built-in WiFi card, and `phy#0` is a WiFi USB adapter.
|
||||||
|
### `iw [phy phy<index> | phy#<index>] info | grep -f monitor -B 10`
|
||||||
|
```
|
||||||
|
➜ iw phy phy0 info | fgrep monitor -B 10
|
||||||
|
* CMAC-256 (00-0f-ac:13)
|
||||||
|
* GMAC-128 (00-0f-ac:11)
|
||||||
|
* GMAC-256 (00-0f-ac:12)
|
||||||
|
Available Antennas: TX 0x3 RX 0x3
|
||||||
|
Configured Antennas: TX 0x3 RX 0x3
|
||||||
|
Supported interface modes:
|
||||||
|
* IBSS
|
||||||
|
* managed
|
||||||
|
* AP
|
||||||
|
* AP/VLAN
|
||||||
|
* monitor
|
||||||
|
--
|
||||||
|
* register_beacons
|
||||||
|
* start_p2p_device
|
||||||
|
* set_mcast_rate
|
||||||
|
* connect
|
||||||
|
* disconnect
|
||||||
|
* set_qos_map
|
||||||
|
* set_multicast_to_unicast
|
||||||
|
* set_sar_specs
|
||||||
|
software interface modes (can always be added):
|
||||||
|
* AP/VLAN
|
||||||
|
* monitor
|
||||||
|
```
|
||||||
|
Can do better
|
||||||
|
### `iw phy#0 info | grep monitor`
|
||||||
|
```
|
||||||
|
* monitor
|
||||||
|
* monitor
|
||||||
|
```
|
||||||
|
Concise but possible need more context to be sure?
|
||||||
|
### `iw phy phy0 info | sed -n '/software interface modes/,/monitor/p'`
|
||||||
|
More concise but with good context. Assuming only sw interfaces need to support monitor mode
|
||||||
|
```
|
||||||
|
software interface modes (can always be added):
|
||||||
|
* AP/VLAN
|
||||||
|
* monitor
|
||||||
|
```
|
||||||
|
### Getting a monitor interface
|
||||||
|
```
|
||||||
|
iw phy#0 interface add mon0 type monitor
|
||||||
|
```
|
||||||
|
Add a easy interface to wifi hw and make it a monitor. Can check again with 'iw dev' to make sure it is really in monitor mode. If there is an other interface it must be taken down or deleted e.g with
|
||||||
|
```
|
||||||
|
iw dev <phy#0 other interface> del # or
|
||||||
|
ip link set <phy#0 other interface> down
|
||||||
|
```
|
||||||
|
Then to enable `mon0` interface,
|
||||||
|
```
|
||||||
|
ip link set mon0 up
|
||||||
|
```
|
||||||
|
To effectively capture packets, we should set the interface to the correct frequency. For this we get the channel e.g. via the above mentioned `nmcli dev wifi`. We can see that, e.g. the BSSID I am connected to (marked with `*`) is on channel 100. We can also see that it there is also a BSSID belonging to the same SSID with the interface on channel 6. I.e., it is running one interface in 2.4 GHz (802.11b/g/n/ax/be) and one in 5 GHz (802.11a/h/n/ac/ax/be). We chose which which channel to tune our `mon0` interface to, then we can lookup what the center frequency is on [wikipedia(List of Wifi Channels)](https://en.wikipedia.org/wiki/List_of_WLAN_channels). E.g. for channel 6 (i.e. 2.4 GHz radio) we see that the center frequency is 2437. We set our interface to that frequency:
|
||||||
|
```
|
||||||
|
iw dev mon0 set freq 2437
|
||||||
|
```
|
||||||
|
Now double check that the interface is in monitor mode and tunedto the correct frequency:
|
||||||
|
```
|
||||||
|
iw dev mon0 info
|
||||||
|
```
|
||||||
|
Should give an output like
|
||||||
|
```
|
||||||
|
Interface mon0
|
||||||
|
ifindex 7
|
||||||
|
wdev 0x2
|
||||||
|
addr a8:42:a1:8b:f4:e3
|
||||||
|
type monitor
|
||||||
|
wiphy 0
|
||||||
|
channel 6 (2437 MHz), width: 20 MHz (no HT), center1: 2437 MHz
|
||||||
|
txpower 20.00 dBm
|
||||||
|
```
|
||||||
|
This concludes preparing the wifi card for packet capture in monitor mode.
|
||||||
|
### [remarks]
|
||||||
|
- `sudo` is probably required for these commands
|
||||||
|
- These network tools are what is available on fedora 40, on $(uname -r)= 6.8.8 Linux Kernel. It might be that other OSs still use older tools, which are being phased out. But other operating systems might still be using older versions of these commands. For a table on how they match up, see [this](https://www.tecmint.com/deprecated-linux-networking-commands-and-their-replacements/) recent article (July 2023), according to which the old commands are even deprecated in recent Debian and Ubuntu releases.
|
||||||
|
- If smth is not working run `rfkill list` to check device is blocked. If it is, `rfkill unblock 0`, where `0` is the same index used above and represents `phy0` /`phy#0`.
|
||||||
|
- To ensure that [[NetworkManager]] not managing you card, `nmcli device set wlp0s20f0u6 managed no` if the interface is called `wlp0s20f0u6`. Check with `nmcli dev`, the STATE should be "unmanaged".
|
||||||
|
- See resources on how to put interface/wifi hardware back into managed mode, if you need the card for personal use.
|
||||||
|
|
||||||
|
# Important
|
||||||
|
Monitor mode is actually completely useless, unless we can observe the EAPOL handshake. That means the Wifi AP should be using WPA/WPA2 with psk. Also we need to know the SSID and passphrase. So it is still better if we can setup an environment where we can just do port mirroring from the wifi router, or setup ourselves in AP mode, but then we need to be able to bridge to the internet somehow, which I haven't managed reliably. Have done some testing on raspberry pi seemed to work. But raspberry pi sometimes goes to sleep so the AP goes down which means the IoT device loses connection.
|
||||||
|
|
||||||
|
If we happen to know the MAC address we need, then in wireshark we can filter `wlan.addr == [MAC]`. In tcpdump we can use the filter
|
||||||
14
notes/journal/2024-05-15-wed.md
Normal file
14
notes/journal/2024-05-15-wed.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# `IOTTB_HOME`
|
||||||
|
I introduced the environment variable `IOTTB_HOME` into the code. It is used to configure where the root of a iottb database is. #TODO this means that some code needs refactoring. But, I think it will streamline the code. The path in `IOTTB_HOME` shall be used to define the database root. Then, all the code handling adding devices and running captures can rely on the fact that a canonical home exists. Unfortunately I've hard coded quite a bit of ad-hoc configuration to use `Path.cwd()`, i.e. the current working directory, by default. So there will be some refactoring involved in switching over to using `IOTTB_HOME`s value as the default path.
|
||||||
|
|
||||||
|
# Adding Functionality
|
||||||
|
## Quick and dirty capture
|
||||||
|
I want to have a mode which just takes a command and runs it directly with its arguments.
|
||||||
|
The question is weather to only allow a preconfigured list of commands or in principle allow any command to be passed and write the output. I tend toward providing a subcommand for each utility we want to support. The question is what to do about the syntax errors of those commands. Maybe the thing to do is only write a file into the db if the command runs successfully.
|
||||||
|
### Refactoring the tcpdump capture
|
||||||
|
With the above idea it would be possible to also refactor or rewrite how tcpdump is called completely. But, the command has a lot of options and maybe its better also offer some guidance to users via `-h`, e.g. to only input the needed and correct filters for example. Choosing the wrong filter could make the capture potentially useless and one might only see that after the capture has completed.
|
||||||
|
## Converting pcap to csv
|
||||||
|
I want an option such that one can automatically convert a captures resulting file into a csv. Probably will focus on tcpdump for now, since other tools like [[mitmproxy]] have different output files.
|
||||||
|
|
||||||
|
## Defining Experiment
|
||||||
|
I want a pair of commands that 1. provide a guided cli interface to define an experiment and 2. to run that experiment -> Here [Collective Knowledge Framework](https://github.com/mlcommons/ck) might actually come in handy. The already have tooling for setting up and defining aspects of experiments so that they become reproducible. So maybe one part of the `iottb` as a tool would be to write the correct json files into the directory which contain the informatin on how the command was run. Caveat: All all option values are the same, basically only, if it was used or not (flagging options) or that it was used (e.g. an ip address was used in the filter but the specific value of the ip is of no use for reproducing). Also, Collective Minds tooling relies very common ML algos/framework and static data. So maybe this only comes into play after a capture has been done. So maybe a feature extraction tool (see [[further considerations#Usage paths/ Workflows]]) should create the data and built the database separately.
|
||||||
1
thesis/overleaf
Submodule
1
thesis/overleaf
Submodule
Submodule thesis/overleaf added at 4412cf1b41
Reference in New Issue
Block a user