Refactor and add Logger

This commit is contained in:
Sebastian Lenzlinger 2024-05-07 19:24:56 +00:00
parent 822b49ed8b
commit 347d43dcef
27 changed files with 735 additions and 402 deletions

111
.idea/workspace.xml generated
View File

@ -4,13 +4,13 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="7a3ac8e1-7fbf-4aa7-9cf9-a51d7ade8503" name="Changes" comment="Setup testing structure"> <list default="true" id="7a3ac8e1-7fbf-4aa7-9cf9-a51d7ade8503" name="Changes" comment="UNTESTED REFACTORING:&#10;Move more functionality into Metadata Model classes to ensure data is available and better passable between functions.">
<change afterPath="$PROJECT_DIR$/code/kydcap/scripts/wifi_ctl.sh" afterDir="false" /> <change afterPath="$PROJECT_DIR$/code/iottb/logger.py" afterDir="false" />
<change afterPath="$PROJECT_DIR$/code/kydcap/utils/wifi_ctrl_utils.py" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/2024-bsc-sebastian-lenzlinger.iml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/2024-bsc-sebastian-lenzlinger.iml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/code/kydcap/subcommands/sniff.py" beforeDir="false" afterPath="$PROJECT_DIR$/code/kydcap/subcommands/sniff.py" 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/kydcap/utils/tcpdump_utils.py" beforeDir="false" afterPath="$PROJECT_DIR$/code/kydcap/utils/utils.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> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -28,7 +28,7 @@
<option name="PUSH_AUTO_UPDATE" value="true" /> <option name="PUSH_AUTO_UPDATE" value="true" />
<option name="RECENT_BRANCH_BY_REPOSITORY"> <option name="RECENT_BRANCH_BY_REPOSITORY">
<map> <map>
<entry key="$PROJECT_DIR$" value="devel" /> <entry key="$PROJECT_DIR$" value="sync" />
</map> </map>
</option> </option>
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
@ -44,27 +44,28 @@
<option name="hideEmptyMiddlePackages" value="true" /> <option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent">{ <component name="PropertiesComponent"><![CDATA[{
&quot;keyToString&quot;: { "keyToString": {
&quot;ASKED_ADD_EXTERNAL_FILES&quot;: &quot;true&quot;, "ASKED_ADD_EXTERNAL_FILES": "true",
&quot;ASKED_MARK_IGNORED_FILES_AS_EXCLUDED&quot;: &quot;true&quot;, "ASKED_MARK_IGNORED_FILES_AS_EXCLUDED": "true",
&quot;ASKED_SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;, "ASKED_SHARE_PROJECT_CONFIGURATION_FILES": "true",
&quot;Python.__init__.executor&quot;: &quot;Run&quot;, "Python.__init__.executor": "Run",
&quot;Python.__main__.executor&quot;: &quot;Run&quot;, "Python.__main__.executor": "Debug",
&quot;Python.main.executor&quot;: &quot;Run&quot;, "Python.iotdb.executor": "Debug",
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, "Python.main.executor": "Run",
&quot;SHARE_PROJECT_CONFIGURATION_FILES&quot;: &quot;true&quot;, "RunOnceActivity.ShowReadmeOnStart": "true",
&quot;git-widget-placeholder&quot;: &quot;main&quot;, "SHARE_PROJECT_CONFIGURATION_FILES": "true",
&quot;last_opened_file_path&quot;: &quot;/home/slnopriv/projects/2024-bsc-sebastian-lenzlinger/code/kydcap/utils/device_metadata_utils.py&quot;, "git-widget-placeholder": "cli-dev",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "last_opened_file_path": "/home/slnopriv/projects/2024-bsc-sebastian-lenzlinger/code/iottb/logger.py",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "node.js.detected.package.eslint": "true",
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, "node.js.detected.package.tslint": "true",
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.eslint": "(autodetect)",
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, "node.js.selected.package.tslint": "(autodetect)",
&quot;settings.editor.selected.configurable&quot;: &quot;com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable&quot;, "nodejs_package_manager_path": "npm",
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; "settings.editor.selected.configurable": "com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable",
"vue.rearranger.settings.migration": "true"
} }
}</component> }]]></component>
<component name="RecentsManager"> <component name="RecentsManager">
<key name="MoveFile.RECENT_KEYS"> <key name="MoveFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/archive" /> <recent name="$PROJECT_DIR$/archive" />
@ -98,7 +99,7 @@
<option name="INPUT_FILE" value="" /> <option name="INPUT_FILE" value="" />
<method v="2" /> <method v="2" />
</configuration> </configuration>
<configuration name="__main__" type="PythonConfigurationType" factoryName="Python" nameIsGenerated="true"> <configuration name="__main__" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
<module name="2024-bsc-sebastian-lenzlinger" /> <module name="2024-bsc-sebastian-lenzlinger" />
<option name="ENV_FILES" value="" /> <option name="ENV_FILES" value="" />
<option name="INTERPRETER_OPTIONS" value="" /> <option name="INTERPRETER_OPTIONS" value="" />
@ -107,13 +108,13 @@
<env name="PYTHONUNBUFFERED" value="1" /> <env name="PYTHONUNBUFFERED" value="1" />
</envs> </envs>
<option name="SDK_HOME" value="" /> <option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/code/kydcap" /> <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/code/iottb" />
<option name="IS_MODULE_SDK" value="true" /> <option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" /> <option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" /> <option name="ADD_SOURCE_ROOTS" value="true" />
<EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" /> <EXTENSION ID="PythonCoverageRunConfigurationExtension" runner="coverage.py" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/code/kydcap/__main__.py" /> <option name="SCRIPT_NAME" value="$PROJECT_DIR$/code/iottb/__main__.py" />
<option name="PARAMETERS" value="init-device-root --dynamic" /> <option name="PARAMETERS" value="add --root /tmp/test --guided" />
<option name="SHOW_COMMAND_LINE" value="false" /> <option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" /> <option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" /> <option name="MODULE_MODE" value="false" />
@ -127,6 +128,7 @@
</list> </list>
<recent_temporary> <recent_temporary>
<list> <list>
<item itemvalue="Python.__main__" />
<item itemvalue="Python.__init__" /> <item itemvalue="Python.__init__" />
</list> </list>
</recent_temporary> </recent_temporary>
@ -150,8 +152,10 @@
<workItem from="1713967495566" duration="6927000" /> <workItem from="1713967495566" duration="6927000" />
<workItem from="1714554228183" duration="34000" /> <workItem from="1714554228183" duration="34000" />
<workItem from="1714554269789" duration="56478000" /> <workItem from="1714554269789" duration="56478000" />
<workItem from="1714616237168" duration="9071000" /> <workItem from="1714616237168" duration="6135000" />
<workItem from="1714631778143" duration="1000000" /> <workItem from="1714850899817" duration="2659000" />
<workItem from="1714917763516" duration="9796000" />
<workItem from="1715078660090" duration="25014000" />
</task> </task>
<task id="LOCAL-00001" summary="Add code for capture testbed. This is a huge commit. End of day sync..."> <task id="LOCAL-00001" summary="Add code for capture testbed. This is a huge commit. End of day sync...">
<option name="closed" value="true" /> <option name="closed" value="true" />
@ -201,15 +205,39 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1714617266799</updated> <updated>1714617266799</updated>
</task> </task>
<task id="LOCAL-00007" summary="Setup testing structure"> <task id="LOCAL-00007" summary="SYNC">
<option name="closed" value="true" /> <option name="closed" value="true" />
<created>1714622469786</created> <created>1714823516954</created>
<option name="number" value="00007" /> <option name="number" value="00007" />
<option name="presentableId" value="LOCAL-00007" /> <option name="presentableId" value="LOCAL-00007" />
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1714622469786</updated> <updated>1714823516954</updated>
</task> </task>
<option name="localTasksCounter" value="8" /> <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:&#10;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 /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@ -265,10 +293,15 @@
<MESSAGE value="Add test module." /> <MESSAGE value="Add test module." />
<MESSAGE value="Update gitignore again." /> <MESSAGE value="Update gitignore again." />
<MESSAGE value="Start tracking development config files." /> <MESSAGE value="Start tracking development config files." />
<MESSAGE value="Setup testing structure" /> <MESSAGE value="SYNC" />
<option name="LAST_COMMIT_MESSAGE" value="Setup testing structure" /> <MESSAGE value="Refactor various names." />
<MESSAGE value="Refactor subcommands, config etc." />
<MESSAGE value="UNTESTED REFACTORING:&#10;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:&#10;Move more functionality into Metadata Model classes to ensure data is available and better passable between functions." />
</component> </component>
<component name="com.intellij.coverage.CoverageDataManagerImpl"> <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$__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" /> <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> </component>

View File

@ -3,8 +3,8 @@ from pathlib import Path
from pydantic import BaseModel from pydantic import BaseModel
from kydcap.models.device_metadata_model import DeviceMetadata from iottb.models.device_metadata_model import DeviceMetadata
from kydcap.config import DEVICE_METADATA_FILE 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):

View File

@ -1,10 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
from kydcap.subcommands.sniff import setup_sniff_parser from iottb.subcommands.capture import setup_capture_parser
from kydcap.subcommands.initialize_device_root_dir import setup_init_root_dir_parser from iottb.subcommands.add_device import setup_init_device_root_parser
CAP_DIR_PREFIX = ...
###################### ######################
@ -12,11 +10,11 @@ CAP_DIR_PREFIX = ...
###################### ######################
def setup_argparse(): def setup_argparse():
# create top level parser # create top level parser
root_parser = argparse.ArgumentParser(prog="kydcap") 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")
setup_sniff_parser(subparsers) setup_capture_parser(subparsers)
setup_init_root_dir_parser(subparsers) setup_init_device_root_parser(subparsers)
return root_parser return root_parser

26
code/iottb/definitions.py Normal file
View File

@ -0,0 +1,26 @@
from datetime import datetime
from enum import Flag, unique, global_enum
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_###"
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
@global_enum
class ReturnCodes(Flag):
SUCCESS = 0
ABORTED = 1
FAILURE = 2
UNKNOWN = 3
FILE_NOT_FOUND = 4
FILE_ALREADY_EXISTS = 5
INVALID_ARGUMENT = 6
INVALID_ARGUMENT_VALUE = 7

28
code/iottb/logger.py Normal file
View File

@ -0,0 +1,28 @@
import logging
import sys
from logging.handlers import RotatingFileHandler
def setup_logging():
logger_obj = logging.getLogger('iottbLogger')
logger_obj.setLevel(logging.INFO)
file_handler = RotatingFileHandler('iottb.log')
console_handler = logging.StreamHandler(sys.stdout)
file_handler.setLevel(logging.DEBUG)
console_handler.setLevel(logging.INFO)
file_fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_fmt = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(file_fmt)
console_handler.setFormatter(console_fmt)
logger_obj.addHandler(file_handler)
logger_obj.addHandler(console_handler)
return logger_obj
logger = setup_logging()

View File

@ -0,0 +1,157 @@
import json
import uuid
from datetime import datetime
from pathlib import Path
from typing import Optional, Any
from uuid import UUID
from pydantic import BaseModel, Field
from iottb.definitions import ReturnCodes, CAPTURE_METADATA_FILE
from iottb.models.device_metadata_model import DeviceMetadata
class CaptureMetadata(BaseModel):
# Required Fields
device_metadata: DeviceMetadata = Field(exclude=True)
capture_id: uuid.UUID = Field(default_factory=lambda: str(uuid.uuid4()))
capture_dir: Path
capture_file: str
capture_date: str = Field(default_factory=lambda: datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower())
# Statistics
start_time: str
stop_time: str
# tcpdump
packet_count: Optional[int]
pcap_filter: str = ""
tcpdump_command: str = ""
interface: str = ""
# Optional Fields
device_ip_address: Optional[str] = "No IP Address set"
device_mac_address: Optional[str] = None
app: Optional[str] = None
app_version: Optional[str] = None
firmware_version: Optional[str] = None
def __init__(self, device_metadata: DeviceMetadata, capture_dir: Path, /, **data: Any):
super().__init__(**data) # Pycharms orders
self.device_metadata = device_metadata
self.capture_dir = capture_dir
assert capture_dir.is_dir()
# 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):
prefix = ""
if self.app is None:
prefix = self.device_metadata.get_device_short_name()
else:
assert str(self.app).strip() not in {"", " "}, f"app is not a valid name: {self.app}"
prefix = self.get_app()
# assert self.capture_dir is not None, f"{self.capture_dir} does not exist"
filename = f"{prefix}_{str(self.capture_id)}.pcap"
self.set_capture_file(filename)
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}"
if file_path.is_file():
print(f"File {file_path} already exists, update instead.")
return ReturnCodes.FILE_ALREADY_EXISTS
metadata = self.model_dump_json(indent=2, exclude_unset=True, exclude_none=True)
with file_path.open('w') as file:
json.dump(metadata, file)
return ReturnCodes.SUCCESS

View File

@ -0,0 +1,128 @@
import json
import uuid
from datetime import datetime
from pathlib import Path
from typing import Optional, List, Any
# iottb modules
from iottb.definitions import ReturnCodes, DEVICE_METADATA_FILE
# 3rd party libs
from pydantic import BaseModel, Field
IMMUTABLE_FIELDS = {"device_name", "device_short_name", "device_id", "date_created"}
class DeviceMetadata(BaseModel):
# Required fields
device_name: str
device_short_name: str
device_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
date_created: str = Field(default_factory=lambda: datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower())
device_root_path: Path
# Optional Fields
aliases: List[str] = Field(default_factory=lambda: [])
device_type: Optional[str] = None
device_serial_number: Optional[str] = None
device_firmware_version: Optional[str] = None
date_updated: Optional[str] = None
capture_files: Optional[List[str]] = []
def __init__(self, device_name: str, device_root_dir: Path, /, **data: Any):
super().__init__(**data)
self.device_name = device_name
self.device_short_name = device_name.lower().replace(" ", "_")
# assert dir_contains_device_metadata(device_root_dir), \
# f"Directory {device_root_dir} is missing a {DEVICE_METADATA_FILE} file"
self.device_root_dir = device_root_dir
def get_device_id(self) -> str:
return self.device_id
def get_device_name(self) -> str:
return self.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
def load_from_json(cls, device_file_path: Path):
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
with device_meta_filename.open('r') as file:
metadata_json = json.load(file)
metadata_model_obj = cls.model_validate_json(metadata_json)
return metadata_model_obj
def save_to_json(self, file_path: Path):
if file_path.is_file():
print(f"File {file_path} already exists, update instead.")
return ReturnCodes.FILE_ALREADY_EXISTS
metadata = self.model_dump_json(indent=2)
with file_path.open('w') as file:
json.dump(metadata, file)
return ReturnCodes.SUCCESS
@classmethod
def update_metadata_in_json(cls, file_path: Path, **kwargs):
# TODO Maybe not needed at all.
assert file_path.is_file()
for field in IMMUTABLE_FIELDS:
if field in kwargs:
print(f"Field {field} is immutable")
return ReturnCodes.IMMUTABLE
metadata = cls.load_from_json(file_path)
for field, value in kwargs.items():
if field in metadata.model_fields_set:
setattr(metadata, field, value)
metadata.date_updated = datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower()
pass
def dir_contains_device_metadata(dir_path: Path):
if not dir_path.is_dir():
return False
else:
meta_file_path = dir_path / DEVICE_METADATA_FILE
print(f"Device metadata file path {str(meta_file_path)}")
if not meta_file_path.is_file():
return False
else:
return True

View File

@ -0,0 +1,59 @@
import pathlib
from iottb import definitions
from iottb.definitions import DEVICE_METADATA_FILE, ReturnCodes
from iottb.models.device_metadata_model import DeviceMetadata
from iottb.utils.device_metadata_utils import *
def setup_init_device_root_parser(subparsers):
parser = subparsers.add_parser("add-device", aliases=["add-device-root", "add"])
parser.add_argument("--root_dir", type=pathlib.Path, default=pathlib.Path.cwd())
group = parser.add_mutually_exclusive_group()
group.add_argument("--guided", action="store_true", help="Guided setup", default=False)
group.add_argument("--name", action="store", type=str, help="name of device")
parser.set_defaults(func=handle_add)
def handle_add(args):
print("Entered add-device-root")
if args.guided:
metadata = guided_setup(args.root_dir)
else:
device_name = args.name
args.root_dir.mkdir(parents=True, exist_ok=True)
args.root_dir.chdir()
metadata = DeviceMetadata(device_name, args.root_dir)
file_path = args.root_dir / DEVICE_METADATA_FILE
response = input(f"Confirm device metadata: {metadata.model_dump()} [y/N]")
if response.lower() not in definitions.AFFIRMATIVE_USER_RESPONSE.add(""):
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
assert Path(file_path).exists(), f"{file_path} does not exist"
return ReturnCodes.SUCCESS
def configure_metadata():
pass
def guided_setup(device_root) -> DeviceMetadata:
response = "N"
device_name = ""
while response.upper() == "N":
device_name = input("Please enter name of device: ")
if device_name == "" or device_name is None:
print("Name cannot be empty")
response = input(f"Confirm device name: {device_name} [y/N] ")
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)

View File

@ -0,0 +1,170 @@
import subprocess
from pathlib import Path
from iottb.definitions import *
from iottb.models.capture_metadata_model import CaptureMetadata
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
def setup_capture_parser(subparsers):
parser = subparsers.add_parser('sniff', help='Sniff packets with tcpdump')
# metadata args
parser.add_argument("-a", "--ip-address", help="IP address of the device to sniff", dest="device_ip")
# tcpdump args
parser.add_argument("device_root", help="Root folder for device to sniff",
type=Path, default=Path.cwd())
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_sniff_tcpdump = parser.add_argument_group('tcpdump arguments')
parser_sniff_tcpdump.add_argument("-i", "--interface", help="Interface to capture on.", dest="capture_interface",
required=True)
parser_sniff_tcpdump.add_argument("-I", "--monitor-mode", help="Put interface into monitor mode",
action="store_true")
parser_sniff_tcpdump.add_argument("-n", help="Deactivate name resolution. True by default.",
action="store_true", dest="no_name_resolution")
parser_sniff_tcpdump.add_argument("-#", "--number",
help="Print packet number at beginning of line. True by default.",
action="store_true")
parser_sniff_tcpdump.add_argument("-e", help="Print link layer headers. True by default.",
action="store_true", dest="print_link_layer")
parser_sniff_tcpdump.add_argument("-t", action="count", default=0,
help="Please see tcpdump manual for details. Unused by default.")
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("--mins", type=int, help="Time in minutes to capture.", default=1)
parser.set_defaults(func=handle_capture)
def cwd_is_device_root_dir() -> bool:
device_metadata_file = Path.cwd() / DEVICE_METADATA_FILE
return device_metadata_file.is_file()
def start_guided_device_root_dir_setup():
assert False, "Not implemented"
def handle_metadata():
assert not cwd_is_device_root_dir()
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")
response = input("Would you like to be guided through the setup? [y/n]")
if response.lower() == "y":
start_guided_device_root_dir_setup()
else:
print("'iottb init-device-root --help' for more information.")
exit(ReturnCodes.ABORTED)
# device_id = handle_capture_metadata()
return ReturnCodes.SUCCESS
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"
device_metadata = DeviceMetadata.load_from_json(device_metadata_filename)
return device_metadata
def run_tcpdump(cmd):
# TODO: Maybe specify files for stout and stderr
try:
p = subprocess.run(cmd, capture_output=True, text=True, check=True)
if p.returncode != 0:
print(f"Error running tcpdump {p.stderr}")
else:
print(f"tcpdump run successfully\n: {p.stdout}")
except KeyboardInterrupt:
pass
def handle_capture(args):
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
if args.safe and not dir_contains_device_metadata(args.device_root):
print(f"Supplied folder contains no device metadata. "
f"Please setup a device root directory before using this command")
exit(ReturnCodes.ABORTED)
elif dir_contains_device_metadata(args.device_root):
device_metadata_filename = args.device_root / DEVICE_METADATA_FILE
device_data = DeviceMetadata.load_from_json(device_metadata_filename)
else:
name = input("Please enter a device name: ")
args.device_root.mkdir(parents=True, exist_ok=True)
device_data = DeviceMetadata(name, args.device_root)
# start constructing environment for capture
capture_dir = get_capture_src_folder(args.device_root)
make_capture_src_folder(capture_dir)
capture_metadata = CaptureMetadata(device_data, capture_dir)
capture_metadata.set_interface(args.capture_interface)
cmd = ['sudo', 'tcpdump', '-i', args.capture_interface]
cmd = build_tcpdump_args(args, cmd, capture_metadata)
capture_metadata.set_tcpdump_command(cmd)
print('Executing: ' + ' '.join(cmd))
# run capture
try:
start_time = datetime.now().strftime('%H:%M:%S')
run_tcpdump(cmd)
stop_time = datetime.now().strftime('%H:%M:%S')
capture_metadata.set_start_time(start_time)
capture_metadata.set_stop_time(stop_time)
except KeyboardInterrupt:
print("Received keyboard interrupt.")
exit(ReturnCodes.ABORTED)
except subprocess.CalledProcessError as e:
print(f"Failed to capture packet: {e}")
exit(ReturnCodes.FAILURE)
except Exception as e:
print(f"Failed to capture packet: {e}")
exit(ReturnCodes.FAILURE)
return ReturnCodes.SUCCESS
def build_tcpdump_args(args, cmd, capture_metadata: CaptureMetadata):
if args.monitor_mode:
cmd.append('-I')
if args.no_name_resolution:
cmd.append('-n')
if args.number:
cmd.append('-#')
if args.print_link_layer:
cmd.append('-e')
if args.count:
cmd.append('-c')
cmd.append(str(args.count))
elif args.mins:
assert False, "Unimplemented option"
if args.app_name is not None:
capture_metadata.set_app_name(args.app_name)
capture_metadata.build_capture_file_name()
cmd.append('-w')
cmd.append(capture_metadata.get_capfile_name())
if args.safe:
cmd.append(f'host {args.device_ip}') # if not specified, filter 'any' implied by tcpdump
capture_metadata.set_device_ip_address(args.device_ip)
return cmd
# def capture_file_cmd(args, cmd, capture_dir, capture_metadata: CaptureMetadata):
# capture_file_prefix = capture_metadata.get_device_metadata().get_device_short_name()
# if args.app_name is not None:
# capture_file_prefix = args.app_name
# capture_metadata.set_app(args.app_name)
# capfile_name = capture_file_prefix + "_" + str(capture_metadata.get_capture_id()) + ".pcap"
# capture_metadata.set_capture_file(capfile_name)
# capfile_abs_path = capture_dir / capfile_name
# capture_metadata.set_capture_file(capfile_name)
# cmd.append('-w')
# cmd.append(str(capfile_abs_path))

View File

@ -1,7 +1,7 @@
import json import json
from pathlib import Path from pathlib import Path
from kydcap.config import ReturnCodes from iottb.definitions import ReturnCodes
def set_device_ip_address(ip_addr: str, file_path: Path): def set_device_ip_address(ip_addr: str, file_path: Path):
@ -36,5 +36,3 @@ def set_device_mac_address(mac_addr: str, file_path: Path):
with file_path.open('w') as f: with file_path.open('w') as f:
json.dump(data, f) json.dump(data, f)
return ReturnCodes.SUCCESS return ReturnCodes.SUCCESS
# TODO finnish for other fields in capture metadata

View File

@ -0,0 +1,44 @@
import uuid
from pathlib import Path
from iottb.models.device_metadata_model import dir_contains_device_metadata
from iottb.utils.utils import get_iso_date
def get_capture_uuid():
return str(uuid.uuid4())
def get_capture_date_folder(device_root: Path):
today_iso = get_iso_date()
today_folder = device_root / today_iso
if dir_contains_device_metadata(device_root):
if not today_folder.is_dir():
try:
today_folder.mkdir()
except FileExistsError:
print(f"Folder {today_folder} already exists")
return today_folder
raise FileNotFoundError(f"Given path {device_root} is not a device root directory")
def get_capture_src_folder(device_folder: Path):
assert device_folder.is_dir(), f"Given path {device_folder} is not a folder"
today_iso = get_iso_date()
max_sequence_number = 1
for d in device_folder.iterdir():
if d.is_dir() and d.name.startswith(f'{today_iso}_capture_'):
name = d.name
num = int(name.split("_")[2])
max_sequence_number = max(max_sequence_number, num)
next_sequence_number = max_sequence_number + 1
return device_folder.joinpath(f"{today_iso}_capture_{next_sequence_number:03}")
def make_capture_src_folder(capture_src_folder: Path):
try:
capture_src_folder.mkdir()
except FileExistsError:
print(f"Folder {capture_src_folder} already exists")
finally:
return capture_src_folder

View File

@ -2,7 +2,7 @@ import json
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from kydcap.config import ReturnCodes from iottb.definitions import ReturnCodes
def update_firmware_version(version: str, file_path: Path): def update_firmware_version(version: str, file_path: Path):
@ -47,3 +47,5 @@ def update_device_type(device_type: str, file_path: Path):
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

View File

@ -1,16 +1,17 @@
import ipaddress
import shutil import shutil
import subprocess import subprocess
from typing import Optional
DEPENDENCIES =
def check_installed(tool) -> bool: def check_installed() -> bool:
"""Check if tcpdump is installed and available on the system path.""" """Check if tcpdump is installed and available on the system path."""
return shutil.which(f'{tool}') is not None return shutil.which('tcpdump') is not None
def ensure_installed(tool): 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(tool): 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.")
@ -25,5 +26,16 @@ def list_interfaces() -> str:
return "" return ""
def start_tcpdump(): def is_valid_ipv4(ip: str) -> bool:
return None try:
ipaddress.IPv4Address(ip)
return True
except ValueError:
return False
def str_to_ipv4(ip: str) -> (bool, Optional[ipaddress]):
try:
address = ipaddress.IPv4Address(ip)
return address == ipaddress.IPv4Address(ip), address
except ipaddress.AddressValueError:
return False, None

18
code/iottb/utils/utils.py Normal file
View File

@ -0,0 +1,18 @@
import uuid
from datetime import datetime
from iottb.definitions import TODAY_DATE_STRING, DEVICE_METADATA_FILE, CAPTURE_METADATA_FILE
from pathlib import Path
def get_iso_date():
return datetime.now().strftime('%Y-%m-%d')
def subfolder_exists(parent: Path, child: str):
return parent.joinpath(child).exists()
def generate_unique_string_with_prefix(prefix: str):
return prefix + "_" + str(uuid.uuid4())

View File

@ -1,20 +0,0 @@
from datetime import datetime
from enum import Flag, unique, global_enum
DEVICE_METADATA_FILE = "device-metadata.json"
CAPTURE_METADATA_FILE = "capture-metadata.json"
TODAY_DATE_STRING = datetime.now().strftime("%d%b%Y").lower()
@unique
@global_enum
class ReturnCodes(Flag):
SUCCESS = 0
ABORTED = 1
FAILURE = 2
UNKNOWN = 3
FILE_NOT_FOUND = 4
FILE_ALREADY_EXISTS = 5
INVALID_ARGUMENT = 6
INVALID_ARGUMENT_VALUE = 7

View File

@ -1,47 +0,0 @@
import json
import uuid
from datetime import datetime
from pathlib import Path
from typing import Optional, Any
from pydantic import BaseModel, Field
from kydcap.config import ReturnCodes
class KydcapCaptureMetadata(BaseModel):
# Required Fields
device_id: str
capture_id: uuid.UUID = Field(default_factory=lambda: str(uuid.uuid4()))
capture_date: str = Field(default_factory=lambda: datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower())
# Statistics
start_time: str
stop_time: str
packet_count: Optional[int]
# Optional Fields
device_ip_address: Optional[str] = None
device_mac_address: Optional[str] = None
app: Optional[str] = None
app_version: Optional[str] = None
firmware_version: Optional[str] = None
def __init__(self, device_id: str, start_time: str, stop_time: str, /, **data: Any):
super().__init__(**data) # Pycharms orders
assert isinstance(device_id, str)
assert isinstance(start_time, str)
assert isinstance(stop_time, str)
self.device_id = device_id
self.start_time = start_time
self.stop_time = stop_time
def save_to_json(self, file_path: Path):
if file_path.is_file():
print(f"File {file_path} already exists, update instead.")
return ReturnCodes.FILE_ALREADY_EXISTS
metadata = self.model_dump_json(indent=2)
with file_path.open('w') as file:
json.dump(metadata, file)
return ReturnCodes.SUCCESS

View File

@ -1,66 +0,0 @@
import json
import uuid
from datetime import datetime
from pathlib import Path
from typing import Optional, List, Any
# kydcap modules
from kydcap.config import ReturnCodes
# 3rd party libs
from pydantic import BaseModel, Field
IMMUTABLE_FIELDS = {"device_name", "device_short_name", "device_id", "date_created"}
class DeviceMetadata(BaseModel):
# Required fields
device_name: str
device_short_name: str
device_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
date_created: str = Field(default_factory=lambda: datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower())
# Optional Fields
device_type: Optional[str] = None
device_serial_number: Optional[str] = None
device_firmware_version: Optional[str] = None
date_updated: Optional[str] = None
capture_files: Optional[List[str]] = []
def __init__(self, device_name: str, /, **data: Any):
super().__init__(**data)
self.device_name = device_name
self.device_short_name = device_name.lower().replace(" ", "_")
@classmethod
def load_from_json(cls, file_path: Path):
assert file_path.is_file()
with file_path.open('r') as file:
metadata_json = json.load(file)
metadata_model_obj = cls.model_validate_json(metadata_json)
return metadata_model_obj
def save_to_json(self, file_path: Path):
if file_path.is_file():
print(f"File {file_path} already exists, update instead.")
return ReturnCodes.FILE_ALREADY_EXISTS
metadata = self.model_dump_json(indent=2)
with file_path.open('w') as file:
json.dump(metadata, file)
return ReturnCodes.SUCCESS
@classmethod
def update_metadata_in_json(cls, file_path: Path, **kwargs):
# TODO Maybe not needed at all.
assert file_path.is_file()
for field in IMMUTABLE_FIELDS:
if field in kwargs:
print(f"Field {field} is immutable")
return ReturnCodes.IMMUTABLE
metadata = cls.load_from_json(file_path)
for field, value in kwargs.items():
if field in metadata.model_fields_set:
setattr(metadata, field, value)
metadata.date_updated = datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower()
pass

View File

@ -1,55 +0,0 @@
#!/usr/bin/env bash
# Note, this is not my original work. Source: https://linuxtldr.com/changing-interface-mode/
function list_nic_info () {
ip addr show
}
function enable_monm_iw () {
interface=$1
sudo ip link set "$interface" down
sudo iw "$interface" set monitor control
sudo ip link set "$interface" up
}
function disable_monm_iw () {
interface=$1
sudo ip link set "$interface" down
sudo iw "$interface" set type managed
sudo ip link set "$interface" up
}
function enable_monm_iwconfig () {
interface=$1
sudo ifconfig "$interface" down
sudo iwconfig "$interface" mode monitor
sudo ifconfig "$interface" up
}
function disable_monm_iwconfig () {
interface=$1
sudo ifconfig "$interface" down
sudo iwconfig "$interface" mode managed
sudo ifconfig "$interface" up
}
function enable_monm_acng () {
interface=$1
sudo airmon-ng check
sudo airmon-ng check kill
sudo airmon-ng start "$interface"
}
function disable_monm_acng () {
interface="${1}mon"
sudo airmon-ng stop "$interface"
sudo systemctl restart NetworkManager
}
if declare -f "$1" > /dev/null
then
"$@"
else
echo "Unknown function '$1'" >&2
exit 1
fi

View File

@ -1,40 +0,0 @@
import pathlib
from kydcap.config import DEVICE_METADATA_FILE
from kydcap.models.device_metadata_model import DeviceMetadata
def setup_init_root_dir_parser(subparsers):
parser = subparsers.add_parser("init-device-root", aliases=["idr"])
parser.add_argument("--root_dir", type=pathlib.Path, default=pathlib.Path.cwd())
group = parser.add_mutually_exclusive_group()
group.add_argument("--dynamic", action="store_true", help="enable guided setup", default=False)
group.add_argument("-n", "--name", action="store", type=str, help="name of device")
parser.set_defaults(func=handle_idr)
def handle_idr(args):
print("Entered kydcap initialize-device-root")
root_dir = args.root_dir
device_name = None
if args.dynamic:
response = "N"
while response == "N":
name = input("Please enter name of device: ")
# TODO extended config for other fields like apps, firmware etc.
response = input(f"Confirm device name: {name} [y/N]")
device_name = name
else:
device_name = args.name
root_dir.mkdir(parents=True, exist_ok=True)
root_dir.chdir()
dev_metadata_model = DeviceMetadata(device_name)
file_path = root_dir / device_name / DEVICE_METADATA_FILE
assert not file_path.exists(), f"{file_path} already exists"
if args.dynamic:
response = input(f"Confirm device metadata: {dev_metadata_model.model_dump()} [y/N]")
if response.lower() != "y":
assert False, "TODO implement dynamic setup"
code = dev_metadata_model.save_to_json(file_path)
print(f"Device metadata saved to {file_path}")
return code

View File

@ -1,104 +0,0 @@
import subprocess
from pathlib import Path
from kydcap.config import *
from kydcap.models.device_metadata_model import DeviceMetadata
def setup_sniff_parser(subparsers):
parser = subparsers.add_parser('sniff', help='Sniff packets with tcpdump')
# metadata args
parser.add_argument("-a", "--ip-address=", help="IP address of the device to sniff", dest="device_ip")
# tcpdump args
parser_sniff_tcpdump = parser.add_argument_group('tcpdump arguments')
parser_sniff_tcpdump.add_argument("-i", "--interface=", help="Interface to capture on.", dest="capture_interface",
default="any")
parser_sniff_tcpdump.add_argument("-I", "--monitor-mode", help="Put interface into monitor mode",
action="store_true")
parser_sniff_tcpdump.add_argument("-n", help="Deactivate name resolution. Option is set by default.",
action="store_true", dest="no_name_resolution")
parser_sniff_tcpdump.add_argument("-#", "--number",
help="Print packet number at beginning of line. Set by default.",
action="store_true")
parser_sniff_tcpdump.add_argument("-e", help="Print link layer headers. Option is set by default.",
action="store_true", dest="print_link_layer")
parser_sniff_tcpdump.add_argument("-t", action="count", default=0,
help="Please see tcpdump manual for details. Unused by default.")
# parser_sniff_tcpdump.add_argument("--filter",type=str,default="ip help=f"pcap filter expression. \
# Defaults is '{default}'")
# shared args
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=0)
cap_size_group.add_argument("--mins", type=int, help="Time in minutes to capture.", default=60)
parser.set_defaults(func=handle_sniff)
# return parser
# parser.add_default(func=handle_sniff(args=sniff_args))
def cwd_is_device_root_dir() -> bool:
device_metadata_file = Path.cwd() / DEVICE_METADATA_FILE
return device_metadata_file.exists()
def start_guided_device_root_dir_setup():
assert False, "Not implemented"
def handle_metadata():
assert not cwd_is_device_root_dir()
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")
response = input("Would you like to be guided through the setup? [y/n]")
if response.lower() == "y":
start_guided_device_root_dir_setup()
else:
print("'kydcap init-device-root --help' for more information.")
exit(ReturnCodes.ABORTED)
# device_id = handle_capture_metadata()
return ReturnCodes.SUCCESS
def handle_capture_metadata():
device_metadata_json = Path.cwd() / DEVICE_METADATA_FILE
device_metadata = DeviceMetadata.load_from_json(device_metadata_json)
device_id = device_metadata.device_id
return device_id
def handle_sniff(args):
if not cwd_is_device_root_dir():
handle_metadata()
else:
cmd = ['sudo', 'tcpdump', '-i', args.capture_interface]
if args.monitor_mode:
cmd.append('-I')
if args.no_name_resolution:
cmd.append('-n')
if args.number:
cmd.append('-#')
if args.print_link_layer:
cmd.append('-e')
if args.count:
cmd.append('-c')
cmd.append(str(args.count))
elif args.mins:
pass
print('Complete command:' + ' '.join(cmd))
# TODO maybe dump this into file -> put into device metadata
# TODO generate pcap filename
# TODO construct capture metadata file
try:
start_time = datetime.now().strftime('%H:%M:%S')
subprocess.run(cmd)
stop_time = datetime.now().strftime('%H:%M:%S')
except KeyboardInterrupt:
print("Received keyboard interrupt.")
exit(ReturnCodes.ABORTED)
except subprocess.CalledProcessError as e:
print(f"Failed to capture packet: {e}")
exit(ReturnCodes.FAILURE)
except Exception as e:
print(f"Failed to capture packet: {e}")
exit(ReturnCodes.FAILURE)
return ReturnCodes.SUCCESS

View File

@ -1,10 +0,0 @@
import subprocess
def enable_monitor_mode(interface):
pass
def disable_monitor_mode(interface):
pass
def get_ap_channel(interface):
pass

View File

@ -0,0 +1,2 @@
def test_save_to_json():
assert False

View File

@ -3,4 +3,4 @@ from pathlib import Path
from unittest.mock import mock_open, patch from unittest.mock import mock_open, patch
import pytest import pytest
from kydcap.utils.capture_metadata_utils import set_device_ip_address from iottb.utils.capture_metadata_utils import set_device_ip_address