Merge branch 'experiment2' into 'main'
Merge experiment2 into main See merge request dmi-pet/bsc-msc/2024-bsc-sebastian-lenzlinger!9
This commit is contained in:
commit
de30d7a4af
10
.idea/2024-bsc-sebastian-lenzlinger.iml
generated
10
.idea/2024-bsc-sebastian-lenzlinger.iml
generated
@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module version="4">
|
|
||||||
<component name="PyDocumentationSettings">
|
|
||||||
<option name="format" value="PLAIN" />
|
|
||||||
<option name="myDocStringFormat" value="Plain" />
|
|
||||||
</component>
|
|
||||||
<component name="TestRunnerService">
|
|
||||||
<option name="PROJECT_TEST_RUNNER" value="py.test" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
7
.idea/misc.xml
generated
7
.idea/misc.xml
generated
@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Black">
|
|
||||||
<option name="sdkName" value="Python 3.12 (pythonProject)" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (2024-bsc-sebastian-lenzlinger)" project-jdk-type="Python SDK" />
|
|
||||||
</project>
|
|
||||||
14
.idea/webResources.xml
generated
14
.idea/webResources.xml
generated
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="WebResourcesPaths">
|
|
||||||
<contentEntries>
|
|
||||||
<entry url="file://$PROJECT_DIR$">
|
|
||||||
<entryData>
|
|
||||||
<resourceRoots>
|
|
||||||
<path value="file://$PROJECT_DIR$/data" />
|
|
||||||
</resourceRoots>
|
|
||||||
</entryData>
|
|
||||||
</entry>
|
|
||||||
</contentEntries>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@ -30,3 +30,30 @@ def setup_sniff_parser(subparsers):
|
|||||||
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
|
||||||
|
|
||||||
|
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 setting "IOTTB_HOME"')
|
||||||
|
print('TODO')
|
||||||
|
print("Aborting execution...")
|
||||||
|
return ReturnCodes.ABORTED
|
||||||
|
else:
|
||||||
|
print(f'Setting environment variable IOTTB_HOME""')
|
||||||
|
Path(IOTTB_HOME_ABS).mkdir(parents=True,
|
||||||
|
exist_ok=False) # Should always work since in 'not exist' code path
|
||||||
|
return ReturnCodes.SUCCESS
|
||||||
|
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.SUCCESS
|
||||||
|
|||||||
107
archive/iottb/__main__.py
Normal file
107
archive/iottb/__main__.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import argparse
|
||||||
|
from os import environ
|
||||||
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
from archive.iottb.subcommands.add_device import setup_init_device_root_parser
|
||||||
|
# from iottb.subcommands.capture import setup_capture_parser
|
||||||
|
from iottb.subcommands.sniff import setup_sniff_parser
|
||||||
|
from iottb.utils.tcpdump_utils import list_interfaces
|
||||||
|
from iottb.logger import setup_logging
|
||||||
|
|
||||||
|
logger = logging.getLogger('iottbLogger.__main__')
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
######################
|
||||||
|
# Argparse setup
|
||||||
|
######################
|
||||||
|
def setup_argparse():
|
||||||
|
# create top level parser
|
||||||
|
root_parser = argparse.ArgumentParser(prog='iottb')
|
||||||
|
# shared options
|
||||||
|
root_parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||||
|
root_parser.add_argument('--script-mode', action='store_true', help='Run in script mode (non-interactive)')
|
||||||
|
# Group of args w.r.t iottb.db creation
|
||||||
|
group = root_parser.add_argument_group('database options')
|
||||||
|
group.add_argument('--db-home', default=Path.home() / 'IoTtb.db')
|
||||||
|
group.add_argument('--config-home', default=Path.home() / '.config' / 'iottb.conf', type=Path, )
|
||||||
|
group.add_argument('--user', default=Path.home().stem, type=Path, )
|
||||||
|
|
||||||
|
# configure subcommands
|
||||||
|
subparsers = root_parser.add_subparsers(title='subcommands', required=True, dest='command')
|
||||||
|
# setup_capture_parser(subparsers)
|
||||||
|
setup_init_device_root_parser(subparsers)
|
||||||
|
setup_sniff_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
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Where put ?!
|
||||||
|
###
|
||||||
|
class IoTdb:
|
||||||
|
def __init__(self, db_home=Path.home() / 'IoTtb.db', iottb_config=Path.home() / '.conf' / 'iottb.conf',
|
||||||
|
user=Path.home().stem):
|
||||||
|
self.db_home = db_home
|
||||||
|
self.config_home = iottb_config
|
||||||
|
self.default_filters_home = db_home / 'default_filters'
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
def create_db(self, mode=0o777, parents=False, exist_ok=False):
|
||||||
|
logger.info(f'Creating db at {self.db_home}')
|
||||||
|
try:
|
||||||
|
self.db_home.mkdir(mode=mode, parents=parents, exist_ok=exist_ok)
|
||||||
|
except FileExistsError:
|
||||||
|
logger.error(f'Database path already at {self.db_home} exists and is not a directory')
|
||||||
|
finally:
|
||||||
|
logger.debug(f'Leaving finally clause in create_db')
|
||||||
|
|
||||||
|
def create_device_tree(self, mode=0o777, parents=False, exist_ok=False):
|
||||||
|
logger.info(f'Creating device tree at {self.db_home / 'devices'}')
|
||||||
|
#TODO
|
||||||
|
|
||||||
|
def parse_db_config(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def parse_iottb_config(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_known_devices(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def iottb_db_exists(db_home=Path.home() / 'IoTtb.db'):
|
||||||
|
res = db_home.is_dir()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
logger.debug(f'Pre setup_argparse()')
|
||||||
|
parser = setup_argparse()
|
||||||
|
logger.debug('Post setup_argparse().')
|
||||||
|
args = parser.parse_args()
|
||||||
|
logger.debug(f'Args parsed: {args}')
|
||||||
|
if args.command:
|
||||||
|
try:
|
||||||
|
args.func(args)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('Received keyboard interrupt. Exiting...')
|
||||||
|
exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f'Error in main: {e}')
|
||||||
|
print(f'Error: {e}')
|
||||||
|
# create_capture_directory(args.device_name)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
setup_logging()
|
||||||
|
logger.debug("Debug level is working")
|
||||||
|
logger.info("Info level is working")
|
||||||
|
logger.warning("Warning level is working")
|
||||||
|
|
||||||
|
main()
|
||||||
35
archive/iottb/logger.py
Normal file
35
archive/iottb/logger.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging():
|
||||||
|
# Ensure the logs directory exists
|
||||||
|
log_directory = 'logs'
|
||||||
|
if not os.path.exists(log_directory):
|
||||||
|
os.makedirs(log_directory)
|
||||||
|
|
||||||
|
# Create handlers
|
||||||
|
file_handler = RotatingFileHandler(os.path.join(log_directory, 'iottb.log'), maxBytes=1048576, backupCount=5)
|
||||||
|
console_handler = logging.StreamHandler(sys.stdout)
|
||||||
|
|
||||||
|
# Create formatters and add it to handlers
|
||||||
|
file_fmt = logging.Formatter('%(asctime)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)
|
||||||
|
console_handler.setFormatter(console_fmt)
|
||||||
|
|
||||||
|
# Get the root logger and add handlers
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
root_logger.setLevel(logging.DEBUG)
|
||||||
|
root_logger.addHandler(file_handler)
|
||||||
|
root_logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
# Prevent propagation to the root logger to avoid duplicate logs
|
||||||
|
root_logger.propagate = False
|
||||||
|
|
||||||
|
|
||||||
|
setup_logging()
|
||||||
@ -6,17 +6,20 @@ from typing import Optional
|
|||||||
|
|
||||||
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
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger('iottbLogger.capture_metadata_model')
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
class CaptureMetadata:
|
class CaptureMetadata:
|
||||||
# Required Fields
|
# Required Fields
|
||||||
device_metadata: DeviceMetadata
|
device_metadata: DeviceMetadata
|
||||||
capture_id: str = lambda: str(uuid.uuid4())
|
|
||||||
device_id: str
|
device_id: str
|
||||||
capture_dir: Path
|
capture_dir: Path
|
||||||
capture_file: str
|
capture_file: str
|
||||||
capture_date: str = lambda: datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower()
|
|
||||||
|
|
||||||
# Statistics
|
# Statistics
|
||||||
start_time: str
|
start_time: str
|
||||||
@ -39,7 +42,8 @@ class CaptureMetadata:
|
|||||||
def __init__(self, device_metadata: DeviceMetadata, capture_dir: Path):
|
def __init__(self, device_metadata: DeviceMetadata, capture_dir: Path):
|
||||||
logger.info(f'Creating CaptureMetadata model from DeviceMetadata: {device_metadata}')
|
logger.info(f'Creating CaptureMetadata model from DeviceMetadata: {device_metadata}')
|
||||||
self.device_metadata = device_metadata
|
self.device_metadata = device_metadata
|
||||||
|
self.capture_id = str(uuid.uuid4())
|
||||||
|
self.capture_date = datetime.now().strftime('%d-%m-%YT%H:%M:%S').lower()
|
||||||
self.capture_dir = capture_dir
|
self.capture_dir = capture_dir
|
||||||
assert capture_dir.is_dir(), f'Capture directory {capture_dir} does not exist'
|
assert capture_dir.is_dir(), f'Capture directory {capture_dir} does not exist'
|
||||||
|
|
||||||
@ -47,7 +51,7 @@ class CaptureMetadata:
|
|||||||
logger.info(f'Building capture file name')
|
logger.info(f'Building capture file name')
|
||||||
if self.app is None:
|
if self.app is None:
|
||||||
logger.debug(f'No app specified')
|
logger.debug(f'No app specified')
|
||||||
prefix = self.device_metadata.device_short_name
|
prefix = "iphone-14" #self.device_metadata.device_short_name
|
||||||
else:
|
else:
|
||||||
logger.debug(f'App specified: {self.app}')
|
logger.debug(f'App specified: {self.app}')
|
||||||
assert str(self.app).strip() not in {'', ' '}, f'app is not a valid name: {self.app}'
|
assert str(self.app).strip() not in {'', ' '}, f'app is not a valid name: {self.app}'
|
||||||
@ -6,7 +6,10 @@ 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
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger('iottbLogger.device_metadata_model')
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
# 3rd party libs
|
# 3rd party libs
|
||||||
|
|
||||||
IMMUTABLE_FIELDS = {'device_name', 'device_short_name', 'device_id', 'date_created'}
|
IMMUTABLE_FIELDS = {'device_name', 'device_short_name', 'device_id', 'date_created'}
|
||||||
@ -1,18 +1,21 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
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
|
||||||
|
|
||||||
logger.setLevel(logging.INFO) # Since module currently passes all tests
|
# logger.setLevel(logging.INFO) # Since module currently passes all tests
|
||||||
|
logger = logging.getLogger('iottbLogger.add_device')
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
def setup_init_device_root_parser(subparsers):
|
def setup_init_device_root_parser(subparsers):
|
||||||
|
#assert os.environ['IOTTB_HOME'] is not None, "IOTTB_HOME environment variable is not set"
|
||||||
parser = subparsers.add_parser('add-device', aliases=['add-device-root', 'add'],
|
parser = subparsers.add_parser('add-device', aliases=['add-device-root', 'add'],
|
||||||
help='Initialize a folder for a device.')
|
help='Initialize a folder for a device.')
|
||||||
parser.add_argument('--root_dir', type=pathlib.Path, default=pathlib.Path.cwd())
|
parser.add_argument('--root_dir', type=pathlib.Path,
|
||||||
|
default=definitions.IOTTB_HOME_ABS) # TODO: Refactor code to not use this or handle iottb here
|
||||||
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')
|
||||||
@ -20,14 +23,12 @@ def setup_init_device_root_parser(subparsers):
|
|||||||
|
|
||||||
|
|
||||||
def handle_add(args):
|
def handle_add(args):
|
||||||
|
# TODO: This whole function should be refactored into using the fact that IOTTB_HOME is set, and the dir exists
|
||||||
logger.info(f'Add device handler called with args {args}')
|
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')
|
logger.debug('begin guided setup')
|
||||||
metadata = guided_setup(args.root_dir)
|
metadata = guided_setup(args.root_dir) # TODO refactor to use IOTTB_HOME
|
||||||
logger.debug('guided setup complete')
|
logger.debug('guided setup complete')
|
||||||
else:
|
else:
|
||||||
logger.debug('Setup through passed args: setup')
|
logger.debug('Setup through passed args: setup')
|
||||||
@ -36,7 +37,7 @@ def handle_add(args):
|
|||||||
return ReturnCodes.ERROR
|
return ReturnCodes.ERROR
|
||||||
metadata = DeviceMetadata(args.name, args.root_dir)
|
metadata = DeviceMetadata(args.name, args.root_dir)
|
||||||
|
|
||||||
file_path = args.root_dir / DEVICE_METADATA_FILE
|
file_path = args.root_dir / DEVICE_METADATA_FILE # TODO IOTTB_HOME REFACTOR
|
||||||
if file_path.exists():
|
if file_path.exists():
|
||||||
print('Directory already contains a metadata file. Aborting.')
|
print('Directory already contains a metadata file. Aborting.')
|
||||||
return ReturnCodes.ABORTED
|
return ReturnCodes.ABORTED
|
||||||
@ -2,11 +2,15 @@ import subprocess
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from iottb.definitions import *
|
from iottb.definitions import *
|
||||||
|
import logging
|
||||||
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
|
from iottb.utils.tcpdump_utils import check_installed
|
||||||
|
|
||||||
|
logger = logging.getLogger('iottbLogger.capture')
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
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
|
||||||
@ -33,7 +37,7 @@ def setup_capture_parser(subparsers):
|
|||||||
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=10)
|
||||||
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)
|
||||||
@ -88,6 +92,7 @@ def handle_capture(args):
|
|||||||
assert args.device_root is not None, f'Device root directory is required'
|
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'
|
assert dir_contains_device_metadata(args.device_root), f'Device metadata file \'{args.device_root}\' does not exist'
|
||||||
# get device metadata
|
# get device metadata
|
||||||
|
logger.info(f'Device root directory: {args.device_root}')
|
||||||
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')
|
||||||
@ -98,6 +103,7 @@ def handle_capture(args):
|
|||||||
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
|
||||||
capture_dir = get_capture_src_folder(args.device_root)
|
capture_dir = get_capture_src_folder(args.device_root)
|
||||||
@ -152,7 +158,7 @@ def build_tcpdump_args(args, cmd, capture_metadata: CaptureMetadata):
|
|||||||
|
|
||||||
capture_metadata.build_capture_file_name()
|
capture_metadata.build_capture_file_name()
|
||||||
cmd.append('-w')
|
cmd.append('-w')
|
||||||
cmd.append(capture_metadata.capture_file)
|
cmd.append(str(capture_metadata.capture_dir) + "/" + 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
|
||||||
@ -160,7 +166,6 @@ def build_tcpdump_args(args, cmd, capture_metadata: CaptureMetadata):
|
|||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
# def capture_file_cmd(args, cmd, capture_dir, capture_metadata: CaptureMetadata):
|
# def capture_file_cmd(args, cmd, capture_dir, capture_metadata: CaptureMetadata):
|
||||||
# capture_file_prefix = capture_metadata.get_device_metadata().get_device_short_name()
|
# capture_file_prefix = capture_metadata.get_device_metadata().get_device_short_name()
|
||||||
# if args.app_name is not None:
|
# if args.app_name is not None:
|
||||||
63
archive/iottb/subcommands/sniff.py
Normal file
63
archive/iottb/subcommands/sniff.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import subprocess
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger('iottbLogger.capture')
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
class Sniffer:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def setup_sniff_parser(subparsers):
|
||||||
|
parser = subparsers.add_parser('sniff', help='Sniff packets with tcpdump')
|
||||||
|
# metadata args
|
||||||
|
parser.add_argument('-a', '--addr', help='IP or MAC address of IoT device')
|
||||||
|
# tcpdump args
|
||||||
|
parser.add_argument('--app', help='Application name to sniff', 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=10)
|
||||||
|
cap_size_group.add_argument('--mins', type=int, help='Time in minutes to capture.', default=1)
|
||||||
|
|
||||||
|
parser.set_defaults(func=sniff)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_addr(addr):
|
||||||
|
#TODO Implement
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def sniff(args):
|
||||||
|
if args.addr is None:
|
||||||
|
print('You must supply either a MAC or IP(v4) address to use this tool!')
|
||||||
|
logger.info("Exiting on account of missing MAC/IP.")
|
||||||
|
exit(1)
|
||||||
|
else:
|
||||||
|
(type, value) = parse_addr(args.addr)
|
||||||
|
#TODO Get this party started
|
||||||
|
|
||||||
|
def sniff_tcpdump(args, filter):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sniff_mitmproxy(args, filter):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sniff_raw(cmd,args):
|
||||||
|
pass
|
||||||
44
archive/iottb/utils/capture_utils.py
Normal file
44
archive/iottb/utils/capture_utils.py
Normal 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
|
||||||
41
archive/iottb/utils/tcpdump_utils.py
Normal file
41
archive/iottb/utils/tcpdump_utils.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import ipaddress
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
def check_installed() -> bool:
|
||||||
|
"""Check if tcpdump is installed and available on the system path."""
|
||||||
|
return shutil.which('tcpdump') is not None
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_installed():
|
||||||
|
"""Ensure that tcpdump is installed, raise an error if not."""
|
||||||
|
if not check_installed():
|
||||||
|
raise RuntimeError('tcpdump is not installed. Please install it to continue.')
|
||||||
|
|
||||||
|
|
||||||
|
def list_interfaces(args) -> str:
|
||||||
|
"""List available network interfaces using tcpdump."""
|
||||||
|
ensure_installed()
|
||||||
|
try:
|
||||||
|
result = subprocess.run(['tcpdump', '--list-interfaces'], capture_output=True, text=True, check=True)
|
||||||
|
print(result.stdout)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f'Failed to list interfaces: {e}')
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_ipv4(ip: str) -> bool:
|
||||||
|
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
|
||||||
16
archive/pyproject.toml
Normal file
16
archive/pyproject.toml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=42", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = 'iottb'
|
||||||
|
version = '0.1.0'
|
||||||
|
authors = [{name = "Sebastian Lenzlinger", email = "sebastian.lenzlinger@unibas.ch"}]
|
||||||
|
description = "Automation Tool for Capturing Network packets of IoT devices."
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
packages = ["iottb"]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
iottb = "iottb.__main__:main"
|
||||||
0
archive/tests/models/test_device_metadata_model.py
Normal file
0
archive/tests/models/test_device_metadata_model.py
Normal file
0
archive/tests/test_main.py
Normal file
0
archive/tests/test_main.py
Normal file
0
archive/tests/utils/test_device_metadata_utils.py
Normal file
0
archive/tests/utils/test_device_metadata_utils.py
Normal file
0
archive/tests/utils/test_tcpdump_utils.py
Normal file
0
archive/tests/utils/test_tcpdump_utils.py
Normal file
36
code/iottb-project/.gitignore
vendored
Normal file
36
code/iottb-project/.gitignore
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
__pycache__
|
||||||
|
.venv
|
||||||
|
iottb.egg-info
|
||||||
|
.idea
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
*.pyc
|
||||||
|
.obsidian
|
||||||
|
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
.private/
|
||||||
|
*.pcap
|
||||||
@ -23,7 +23,7 @@ def add_device_guided(ctx, cn, db):
|
|||||||
@click.option('--dev', '--device-name', type=str, required=True,
|
@click.option('--dev', '--device-name', type=str, required=True,
|
||||||
help='The name of the device to be added. If this string contains spaces or other special characters \
|
help='The name of the device to be added. If this string contains spaces or other special characters \
|
||||||
normalization is performed to derive a canonical name')
|
normalization is performed to derive a canonical name')
|
||||||
@click.option('--db', '--database', type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
|
@click.option('--db', '--database', type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
||||||
envvar='IOTTB_DB', show_envvar=True,
|
envvar='IOTTB_DB', show_envvar=True,
|
||||||
help='Database in which to add this device. If not specified use default from config.')
|
help='Database in which to add this device. If not specified use default from config.')
|
||||||
@click.option('--guided', is_flag=True, default=False, show_default=True, envvar='IOTTB_GUIDED_ADD', show_envvar=True,
|
@click.option('--guided', is_flag=True, default=False, show_default=True, envvar='IOTTB_GUIDED_ADD', show_envvar=True,
|
||||||
@ -44,7 +44,7 @@ def add_device(dev, db, guided):
|
|||||||
# dependency: Database folder must exist
|
# dependency: Database folder must exist
|
||||||
if db:
|
if db:
|
||||||
database = db
|
database = db
|
||||||
path = config.db_path_dict
|
path = config.db_path_dict[database]
|
||||||
logger.debug(f'Resolved (path, db) {path}, {database}')
|
logger.debug(f'Resolved (path, db) {path}, {database}')
|
||||||
else:
|
else:
|
||||||
path = config.default_db_location
|
path = config.default_db_location
|
||||||
|
|||||||
@ -2,6 +2,7 @@ from pathlib import Path
|
|||||||
import logging
|
import logging
|
||||||
import click
|
import click
|
||||||
|
|
||||||
|
from iottb import tb_echo
|
||||||
from iottb.definitions import DB_NAME, CFG_FILE_PATH
|
from iottb.definitions import DB_NAME, CFG_FILE_PATH
|
||||||
from iottb.models.iottb_config import IottbConfig
|
from iottb.models.iottb_config import IottbConfig
|
||||||
|
|
||||||
@ -94,12 +95,17 @@ def show_everything(ctx):
|
|||||||
click.echo(f"Default Database: {config.default_database}")
|
click.echo(f"Default Database: {config.default_database}")
|
||||||
click.echo(f"Default Database Path: {config.default_db_location}")
|
click.echo(f"Default Database Path: {config.default_db_location}")
|
||||||
click.echo("Database Locations:")
|
click.echo("Database Locations:")
|
||||||
|
everything_dict = {}
|
||||||
|
for db_name, db_path in config.db_path_dict.items():
|
||||||
|
|
||||||
|
click.echo(f" - {db_name}: {db_path}")
|
||||||
for db_name, db_path in config.db_path_dict.items():
|
for db_name, db_path in config.db_path_dict.items():
|
||||||
full_db_path = Path(db_path) / db_name
|
full_db_path = Path(db_path) / db_name
|
||||||
click.echo(f" - {db_name}: {full_db_path}")
|
|
||||||
if full_db_path.is_dir():
|
if full_db_path.is_dir():
|
||||||
click.echo(f"Contents of {db_name} at {full_db_path}:")
|
click.echo(f"\nContents of {full_db_path}:")
|
||||||
|
flag = True
|
||||||
for item in full_db_path.iterdir():
|
for item in full_db_path.iterdir():
|
||||||
|
flag = False
|
||||||
if item.is_file():
|
if item.is_file():
|
||||||
click.echo(f" - {item.name}")
|
click.echo(f" - {item.name}")
|
||||||
try:
|
try:
|
||||||
@ -115,9 +121,10 @@ def show_everything(ctx):
|
|||||||
click.echo(f" - {subitem.name}")
|
click.echo(f" - {subitem.name}")
|
||||||
elif subitem.is_dir():
|
elif subitem.is_dir():
|
||||||
click.echo(f" - {subitem.name}/")
|
click.echo(f" - {subitem.name}/")
|
||||||
|
if flag:
|
||||||
|
tb_echo(f'\t EMPTY')
|
||||||
else:
|
else:
|
||||||
click.echo(f" {full_db_path} is not a directory")
|
click.echo(f"{full_db_path} is not a directory")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
warnstyle = {'fg': 'red', 'bold': True}
|
|
||||||
click.secho('Developer command used', **warnstyle)
|
|
||||||
|
|||||||
@ -1,3 +1,8 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import uuid
|
||||||
|
from time import time
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
@ -5,9 +10,11 @@ from pathlib import Path
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from click_option_group import optgroup
|
||||||
from iottb.definitions import APP_NAME, CFG_FILE_PATH
|
from iottb.definitions import APP_NAME, CFG_FILE_PATH
|
||||||
from iottb.models.iottb_config import IottbConfig
|
from iottb.models.iottb_config import IottbConfig
|
||||||
from iottb.utils.string_processing import make_canonical_name
|
from iottb.utils.string_processing import make_canonical_name
|
||||||
|
|
||||||
# Setup logger
|
# Setup logger
|
||||||
logger = logging.getLogger('iottb.sniff')
|
logger = logging.getLogger('iottb.sniff')
|
||||||
|
|
||||||
@ -40,29 +47,51 @@ def validate_sniff(ctx, param, value):
|
|||||||
|
|
||||||
|
|
||||||
@click.command('sniff', help='Sniff packets with tcpdump')
|
@click.command('sniff', help='Sniff packets with tcpdump')
|
||||||
@click.argument('device')
|
@optgroup.group('Testbed sources')
|
||||||
@click.option('-i', '--interface', callback=validate_sniff, help='Network interface to capture on',
|
@optgroup.option('--db', '--database', type=str, envvar='IOTTB_DB', show_envvar=True,
|
||||||
envvar='IOTTB_CAPTURE_INTERFACE')
|
help='Database of device. Only needed if not current default.')
|
||||||
@click.option('-a', '--address', callback=validate_sniff, help='IP or MAC address to filter packets by',
|
@optgroup.option('--app', type=str, help='Companion app being used during capture', required=False)
|
||||||
envvar='IOTTB_CAPTURE_ADDRESS')
|
@optgroup.group('Runtime behaviour')
|
||||||
@click.option('--db', '--database', type=click.Path(exists=True, file_okay=False), envvar='IOTTB_DB',
|
@optgroup.option('--unsafe', is_flag=True, default=False, envvar='IOTTB_UNSAFE', is_eager=True,
|
||||||
help='Database of device. Only needed if not current default.')
|
help='Disable checks for otherwise required options.\n', show_envvar=True)
|
||||||
@click.option('--unsafe', is_flag=True, default=False, envvar='IOTTB_UNSAFE', is_eager=True,
|
@optgroup.option('--guided', is_flag=True, default=False, envvar='IOTTB_GUIDED', show_envvar=True)
|
||||||
help='Disable checks for otherwise required options')
|
@optgroup.option('--pre', type=click.Path(exists=True, executable=True), help='Script to be executed before main '
|
||||||
@click.option('--guided', is_flag=True, default=False)
|
'command'
|
||||||
def sniff(device, interface, address, db, unsafe, guided):
|
'is started.')
|
||||||
|
@optgroup.group('Tcpdump options')
|
||||||
|
@optgroup.option('-i', '--interface',
|
||||||
|
help='Network interface to capture on.' +
|
||||||
|
'If not specified tcpdump tries to find and appropriate one.\n', show_envvar=True,
|
||||||
|
envvar='IOTTB_CAPTURE_INTERFACE')
|
||||||
|
@optgroup.option('-a', '--address', callback=validate_sniff,
|
||||||
|
help='IP or MAC address to filter packets by.\n', show_envvar=True,
|
||||||
|
envvar='IOTTB_CAPTURE_ADDRESS')
|
||||||
|
@optgroup.option('-I', '--monitor-mode', help='Put interface into monitor mode.', is_flag=True)
|
||||||
|
@optgroup.option('--ff', type=str, envvar='IOTTB_CAPTURE_FILTER', show_envvar=True,
|
||||||
|
help='tcpdump filter as string or file path.')
|
||||||
|
@optgroup.option('-#', '--print-pacno', is_flag=True, default=True,
|
||||||
|
help='Print packet number at beginning of line. True by default.')
|
||||||
|
@optgroup.option('-e', '--print-ll', is_flag=True, default=False,
|
||||||
|
help='Print link layer headers. True by default.')
|
||||||
|
@optgroup.option('-c', '--count', type=int, help='Number of packets to capture.', default=1000)
|
||||||
|
# @optgroup.option('--mins', type=int, help='Time in minutes to capture.', default=1)
|
||||||
|
@click.argument('tcpdump-args', nargs=-1, required=False, metavar='[TCPDUMP-ARGS]')
|
||||||
|
@click.argument('device', required=False)
|
||||||
|
@click.pass_context
|
||||||
|
def sniff(ctx, device, interface, print_pacno, ff, count, monitor_mode, print_ll, address, db, unsafe, guided,
|
||||||
|
app, tcpdump_args, **params):
|
||||||
""" Sniff packets from a device """
|
""" Sniff packets from a device """
|
||||||
logger.info('sniff command invoked')
|
logger.info('sniff command invoked')
|
||||||
|
|
||||||
# Step1: Load Config
|
# Step1: Load Config
|
||||||
config = IottbConfig(Path(CFG_FILE_PATH))
|
config = ctx.obj['CONFIG']
|
||||||
logger.debug(f'Config loaded: {config}')
|
logger.debug(f'Config loaded: {config}')
|
||||||
|
|
||||||
# Step2: determine relevant database
|
# Step2: determine relevant database
|
||||||
database = db if db else config.default_database
|
database = db if db else config.default_database
|
||||||
path = config.default_db_location[database]
|
path = config.db_path_dict[database]
|
||||||
full_db_path = Path(path) / database
|
full_db_path = Path(path) / database
|
||||||
logger.debug(f'Full db path is {str(path)}')
|
logger.debug(f'Full db path is {str(full_db_path)}')
|
||||||
|
|
||||||
# 2.2: Check if it exists
|
# 2.2: Check if it exists
|
||||||
if not full_db_path.is_dir():
|
if not full_db_path.is_dir():
|
||||||
@ -84,35 +113,215 @@ def sniff(device, interface, address, db, unsafe, guided):
|
|||||||
device_path.mkdir(parents=True, exist_ok=True)
|
device_path.mkdir(parents=True, exist_ok=True)
|
||||||
logger.info(f'Device path {device_path} created')
|
logger.info(f'Device path {device_path} created')
|
||||||
|
|
||||||
# Generate filter
|
click.echo(f'Found device at path {device_path}')
|
||||||
if not unsafe:
|
# Step 4: Generate filter
|
||||||
if is_ip_address(address):
|
generic_filter = None
|
||||||
packet_filter = f"host {address}"
|
cap_filter = None
|
||||||
elif is_mac_address(address):
|
if ff:
|
||||||
packet_filter = f"ether host {address}"
|
logger.debug(f'ff: {ff}')
|
||||||
|
if Path(ff).is_file():
|
||||||
|
logger.info('Given filter option is a file')
|
||||||
|
with open(ff, 'r') as f:
|
||||||
|
cap_filter = f.read().strip()
|
||||||
else:
|
else:
|
||||||
logger.error('Invalid address format')
|
logger.info('Given filter option is an expression')
|
||||||
click.echo('Invalid address format')
|
cap_filter = ff
|
||||||
return
|
|
||||||
else:
|
else:
|
||||||
packet_filter = None
|
if address is not None:
|
||||||
|
if is_ip_address(address):
|
||||||
|
generic_filter = 'net'
|
||||||
|
cap_filter = f'{generic_filter} {address}'
|
||||||
|
elif is_mac_address(address):
|
||||||
|
generic_filter = 'ether net'
|
||||||
|
cap_filter = f'{generic_filter} {address}'
|
||||||
|
elif not unsafe:
|
||||||
|
logger.error('Invalid address format')
|
||||||
|
click.echo('Invalid address format')
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(f'Generic filter {generic_filter}')
|
||||||
|
click.echo(f'Using filter {cap_filter}')
|
||||||
|
|
||||||
|
# Step 5: prep capture directory
|
||||||
|
capture_date = datetime.now().strftime('%Y-%m-%d')
|
||||||
|
capture_base_dir = device_path / f'sniffs/{capture_date}'
|
||||||
|
capture_base_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
logger.debug(f'Previous captures {capture_base_dir.glob('cap*')}')
|
||||||
|
capture_count = sum(1 for _ in capture_base_dir.glob('cap*'))
|
||||||
|
logger.debug(f'Capture count is {capture_count}')
|
||||||
|
|
||||||
@click.command('sniff', help='Sniff packets with tcpdump')
|
capture_dir = f'cap{capture_count:04d}-{datetime.now().strftime('%H%M')}'
|
||||||
@click.argument('device')
|
logger.debug(f'capture_dir: {capture_dir}')
|
||||||
@click.option('-i', '--interface', required=False, help='Network interface to capture on', envvar='IOTTB_CAPTURE_INTERFACE')
|
|
||||||
@click.option('-a', '--address', required=True, help='IP or MAC address to filter packets by', envvar='IOTTB_CAPTURE_ADDRESS')
|
# Full path
|
||||||
@click.option('--db', '--database', type=click.Path(exists=True, file_okay=False), envvar='IOTTB_DB',
|
capture_dir_full_path = capture_base_dir / capture_dir
|
||||||
help='Database of device. Only needed if not current default.')
|
capture_dir_full_path.mkdir(parents=True, exist_ok=True)
|
||||||
@click.option('--unsafe', is_flag=True, default=False, envvar='IOTTB_UNSAFE',
|
|
||||||
help='Disable checks for otherwise required options')
|
click.echo(f'Files will be placed in {str(capture_dir_full_path)}')
|
||||||
@click.option('--guided', is_flag=True)
|
logger.debug(f'successfully created capture directory')
|
||||||
def sniff2(device, interface, address, cfg_file):
|
|
||||||
""" Sniff packets from a device """
|
# Step 6: Prepare capture file names
|
||||||
logger.info('sniff command invoked')
|
# Generate UUID for filenames
|
||||||
# Step 1: Load Config
|
capture_uuid = str(uuid.uuid4())
|
||||||
# Dependency: Config file must exist
|
click.echo(f'Capture has id {capture_uuid}')
|
||||||
config = IottbConfig(Path(CFG_FILE_PATH))
|
|
||||||
logger.debug(f'Config loaded: {config}')
|
pcap_file = f"{canonical_name}_{capture_uuid}.pcap"
|
||||||
|
pcap_file_full_path = capture_dir_full_path / pcap_file
|
||||||
|
stdout_log_file = f'stdout_{capture_uuid}.log'
|
||||||
|
stderr_log_file = f'stderr_{capture_uuid}.log'
|
||||||
|
|
||||||
|
logger.debug(f'Full pcap file path is {pcap_file_full_path}')
|
||||||
|
logger.info(f'pcap file name is {pcap_file}')
|
||||||
|
logger.info(f'stdout log file is {stdout_log_file}')
|
||||||
|
logger.info(f'stderr log file is {stderr_log_file}')
|
||||||
|
|
||||||
|
# Step 7: Build tcpdump command
|
||||||
|
logger.debug(f'pgid {os.getpgrp()}')
|
||||||
|
logger.debug(f'ppid {os.getppid()}')
|
||||||
|
logger.debug(f'(real, effective, saved) user id: {os.getresuid()}')
|
||||||
|
logger.debug(f'(real, effective, saved) group id: {os.getresgid()}')
|
||||||
|
|
||||||
|
cmd = ['sudo', 'tcpdump']
|
||||||
|
|
||||||
|
# 7.1 process flags
|
||||||
|
flags = []
|
||||||
|
if print_pacno:
|
||||||
|
flags.append('-#')
|
||||||
|
if print_ll:
|
||||||
|
flags.append('-e')
|
||||||
|
if monitor_mode:
|
||||||
|
flags.append('-I')
|
||||||
|
flags.append('-n') # TODO: Integrate, in case name resolution is wanted!
|
||||||
|
cmd.extend(flags)
|
||||||
|
flags_string = " ".join(flags)
|
||||||
|
logger.debug(f'Flags: {flags_string}')
|
||||||
|
|
||||||
|
# debug interlude
|
||||||
|
verbosity = ctx.obj['VERBOSITY']
|
||||||
|
if verbosity > 0:
|
||||||
|
verbosity_flag = '-'
|
||||||
|
for i in range(0, verbosity):
|
||||||
|
verbosity_flag = verbosity_flag + 'v'
|
||||||
|
logger.debug(f'verbosity string to pass to tcpdump: {verbosity_flag}')
|
||||||
|
cmd.append(verbosity_flag)
|
||||||
|
|
||||||
|
# 7.2 generic (i.e. reusable) kw args
|
||||||
|
generic_kw_args = []
|
||||||
|
if count:
|
||||||
|
generic_kw_args.extend(['-c', str(count)])
|
||||||
|
# if mins:
|
||||||
|
# generic_kw_args.extend(['-G', str(mins * 60)]) TODO: this currently loads to errors with sudo
|
||||||
|
cmd.extend(generic_kw_args)
|
||||||
|
generic_kw_args_string = " ".join(generic_kw_args)
|
||||||
|
logger.debug(f'KW args: {generic_kw_args_string}')
|
||||||
|
|
||||||
|
# 7.3 special kw args (not a priori reusable)
|
||||||
|
non_generic_kw_args = []
|
||||||
|
if interface:
|
||||||
|
non_generic_kw_args.extend(['-i', interface])
|
||||||
|
non_generic_kw_args.extend(['-w', str(pcap_file_full_path)])
|
||||||
|
cmd.extend(non_generic_kw_args)
|
||||||
|
non_generic_kw_args_string = " ".join(non_generic_kw_args)
|
||||||
|
logger.debug(f'Non transferable (special) kw args: {non_generic_kw_args_string}')
|
||||||
|
|
||||||
|
# 7.4 add filter expression
|
||||||
|
if cap_filter:
|
||||||
|
logger.debug(f'cap_filter (not generic): {cap_filter}')
|
||||||
|
cmd.append(cap_filter)
|
||||||
|
|
||||||
|
full_cmd_string = " ".join(cmd)
|
||||||
|
|
||||||
|
logger.info(f'tcpdump command: {"".join(full_cmd_string)}')
|
||||||
|
click.echo('Capture setup complete!')
|
||||||
|
# Step 8: Execute tcpdump command
|
||||||
|
start_time = datetime.now().strftime("%H:%M:%S")
|
||||||
|
start = time()
|
||||||
|
try:
|
||||||
|
if guided:
|
||||||
|
click.confirm(f'Execute following command: {full_cmd_string}')
|
||||||
|
stdout_log_file_abs_path = capture_dir_full_path / stdout_log_file
|
||||||
|
stderr_log_file_abs_path = capture_dir_full_path / stderr_log_file
|
||||||
|
stdout_log_file_abs_path.touch(mode=0o777)
|
||||||
|
stderr_log_file_abs_path.touch(mode=0o777)
|
||||||
|
with open(stdout_log_file_abs_path, 'w') as out, open(stderr_log_file_abs_path, 'w') as err:
|
||||||
|
logger.debug(f'\nstdout: {out}.\nstderr: {err}.\n')
|
||||||
|
|
||||||
|
tcp_complete = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
||||||
|
|
||||||
|
out.write(tcp_complete.stdout)
|
||||||
|
err.write(tcp_complete.stderr)
|
||||||
|
|
||||||
|
#click.echo(f'Mock sniff execution')
|
||||||
|
click.echo(f"Capture complete. Saved to {pcap_file}")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.error(f'Failed to capture packets: {e}')
|
||||||
|
click.echo(f'Failed to capture packets: {e}')
|
||||||
|
click.echo(f'Check {stderr_log_file} for more info.')
|
||||||
|
if ctx.obj['DEBUG']:
|
||||||
|
msg = [f'STDERR log {stderr_log_file} contents:\n']
|
||||||
|
with open(capture_dir_full_path / stderr_log_file) as log:
|
||||||
|
for line in log:
|
||||||
|
msg.append(line)
|
||||||
|
|
||||||
|
click.echo("\t".join(msg), lvl='e')
|
||||||
|
# print('DEBUG ACTIVE')
|
||||||
|
if guided:
|
||||||
|
click.prompt('Create metadata anyway?')
|
||||||
|
else:
|
||||||
|
click.echo('Aborting capture...')
|
||||||
|
exit()
|
||||||
|
end_time = datetime.now().strftime("%H:%M:%S")
|
||||||
|
end = time()
|
||||||
|
delta = end - start
|
||||||
|
click.echo(f'tcpdump took {delta:.2f} seconds.')
|
||||||
|
# Step 9: Register metadata
|
||||||
|
metadata = {
|
||||||
|
'device': canonical_name,
|
||||||
|
'device_id': device,
|
||||||
|
'capture_id': capture_uuid,
|
||||||
|
'capture_date_iso': datetime.now().isoformat(),
|
||||||
|
'invoked_command': " ".join(map(str, cmd)),
|
||||||
|
'capture_duration': delta,
|
||||||
|
'generic_parameters': {
|
||||||
|
'flags': flags_string,
|
||||||
|
'kwargs': generic_kw_args_string,
|
||||||
|
'filter': generic_filter
|
||||||
|
},
|
||||||
|
'non_generic_parameters': {
|
||||||
|
'kwargs': non_generic_kw_args_string,
|
||||||
|
'filter': cap_filter
|
||||||
|
},
|
||||||
|
'features': {
|
||||||
|
'interface': interface,
|
||||||
|
'address': address
|
||||||
|
},
|
||||||
|
'resources': {
|
||||||
|
'pcap_file': str(pcap_file),
|
||||||
|
'stdout_log': str(stdout_log_file),
|
||||||
|
'stderr_log': str(stderr_log_file)
|
||||||
|
},
|
||||||
|
'environment': {
|
||||||
|
'capture_dir': capture_dir,
|
||||||
|
'database': database,
|
||||||
|
'capture_base_dir': str(capture_base_dir),
|
||||||
|
'capture_dir_abs_path': str(capture_dir_full_path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
click.echo('Ensuring correct ownership of created files.')
|
||||||
|
username = os.getlogin()
|
||||||
|
gid = os.getgid()
|
||||||
|
|
||||||
|
# Else there are issues when running with sudo:
|
||||||
|
try:
|
||||||
|
subprocess.run(f'sudo chown -R {username}:{username} {device_path}', shell=True)
|
||||||
|
except OSError as e:
|
||||||
|
click.echo(f'Some error {e}')
|
||||||
|
|
||||||
|
click.echo(f'Saving metadata.')
|
||||||
|
metadata_abs_path = capture_dir_full_path / 'capture_metadata.json'
|
||||||
|
with open(metadata_abs_path, 'w') as f:
|
||||||
|
json.dump(metadata, f, indent=4)
|
||||||
|
|
||||||
|
click.echo(f'END SNIFF SUBCOMMAND')
|
||||||
|
|||||||
120
code/iottb-project/iottb/commands/testbed.py
Normal file
120
code/iottb-project/iottb/commands/testbed.py
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import click
|
||||||
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
import sys
|
||||||
|
from iottb.models.iottb_config import IottbConfig
|
||||||
|
from iottb.definitions import DB_NAME, CFG_FILE_PATH
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option('-d', '--dest', type=click.Path(), help='Location to put (new) iottb database')
|
||||||
|
@click.option('-n', '--name', default=DB_NAME, type=str, help='Name of new database.')
|
||||||
|
@click.option('--update-default/--no-update-default', default=True, help='If new db should be set as the new default')
|
||||||
|
@click.pass_context
|
||||||
|
def init_db(ctx, dest, name, update_default):
|
||||||
|
logger.info('init-db invoked')
|
||||||
|
config = ctx.obj['CONFIG']
|
||||||
|
logger.debug(f'str(config)')
|
||||||
|
# Use the default path from config if dest is not provided
|
||||||
|
known_dbs = config.get_known_databases()
|
||||||
|
logger.debug(f'Known databases: {known_dbs}')
|
||||||
|
if name in known_dbs:
|
||||||
|
dest = config.get_database_location(name)
|
||||||
|
if Path(dest).joinpath(name).is_dir():
|
||||||
|
click.echo(f'A database {name} already exists.')
|
||||||
|
logger.debug(f'DB {name} exists in {dest}')
|
||||||
|
click.echo(f'Exiting...')
|
||||||
|
exit()
|
||||||
|
logger.debug(f'DB name {name} registered but does not exist.')
|
||||||
|
if not dest:
|
||||||
|
logger.info('No dest set, choosing default destination.')
|
||||||
|
dest = Path(config.default_db_location).parent
|
||||||
|
|
||||||
|
db_path = Path(dest).joinpath(name)
|
||||||
|
logger.debug(f'Full path for db {str(db_path)}')
|
||||||
|
# Create the directory if it doesn't exist
|
||||||
|
db_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
logger.info(f"mkdir {db_path} successful")
|
||||||
|
click.echo(f'Created {db_path}')
|
||||||
|
|
||||||
|
# Update configuration
|
||||||
|
config.set_database_location(name, str(dest))
|
||||||
|
if update_default:
|
||||||
|
config.set_default_database(name, str(dest))
|
||||||
|
config.save_config()
|
||||||
|
logger.info(f"Updated configuration with database {name} at {db_path}")
|
||||||
|
|
||||||
|
|
||||||
|
# @click.group('config')
|
||||||
|
# @click.pass_context
|
||||||
|
# def cfg(ctx):
|
||||||
|
# pass
|
||||||
|
#
|
||||||
|
# @click.command('set', help='Set the location of a database.')
|
||||||
|
# @click.argument('database', help='Name of database')
|
||||||
|
# @click.argument('location', help='Where the database is located (i.e. its parent directory)')
|
||||||
|
# @click.pass_context
|
||||||
|
# def set(ctx, key, value):
|
||||||
|
# click.echo(f'Setting {key} to {value} in config')
|
||||||
|
# config = ctx.obj['CONFIG']
|
||||||
|
# logger.warning('No checks performed!')
|
||||||
|
# config.set_database_location(key, value)
|
||||||
|
# config.save_config()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option('-d', '--dest', type=click.Path(), help='Location to put (new) iottb database')
|
||||||
|
@click.option('-n', '--name', default=DB_NAME, type=str, help='Name of new database.')
|
||||||
|
@click.option('--update-default/--no-update-default', default=True, help='If new db should be set as the new default')
|
||||||
|
@click.pass_context
|
||||||
|
def init_db_inactive(ctx, dest, name, update_default):
|
||||||
|
logger.info('init-db invoked')
|
||||||
|
config = ctx.obj['CONFIG']
|
||||||
|
logger.debug(f'str(config)')
|
||||||
|
|
||||||
|
# Retrieve known databases
|
||||||
|
known_dbs = config.get_known_databases()
|
||||||
|
|
||||||
|
# Determine destination path
|
||||||
|
if name in known_dbs:
|
||||||
|
dest = Path(config.get_database_location(name))
|
||||||
|
if dest.joinpath(name).is_dir():
|
||||||
|
click.echo(f'A database {name} already exists.')
|
||||||
|
logger.debug(f'DB {name} exists in {dest}')
|
||||||
|
click.echo(f'Exiting...')
|
||||||
|
exit()
|
||||||
|
logger.debug(f'DB name {name} registered but does not exist.')
|
||||||
|
elif not dest:
|
||||||
|
logger.info('No destination set, using default path from config.')
|
||||||
|
dest = Path(config.default_db_location).parent
|
||||||
|
|
||||||
|
# Ensure destination path is absolute
|
||||||
|
dest = dest.resolve()
|
||||||
|
|
||||||
|
# Combine destination path with database name
|
||||||
|
db_path = dest / name
|
||||||
|
logger.debug(f'Full path for database: {str(db_path)}')
|
||||||
|
|
||||||
|
# Create the directory if it doesn't exist
|
||||||
|
try:
|
||||||
|
db_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
logger.info(f'Directory {db_path} created successfully.')
|
||||||
|
click.echo(f'Created {db_path}')
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'Failed to create directory {db_path}: {e}')
|
||||||
|
click.echo(f'Failed to create directory {db_path}: {e}', err=True)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
# Update configuration
|
||||||
|
config.set_database_location(name, str(db_path))
|
||||||
|
if update_default:
|
||||||
|
config.set_default_database(name, str(db_path))
|
||||||
|
config.save_config()
|
||||||
|
logger.info(f'Updated configuration with database {name} at {db_path}')
|
||||||
|
click.echo(f'Updated configuration with database {name} at {db_path}')
|
||||||
@ -44,3 +44,5 @@ TB_ECHO_STYLES = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NAME_OF_CAPTURE_DIR = 'sniffs'
|
NAME_OF_CAPTURE_DIR = 'sniffs'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,16 +1,20 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from iottb.commands.sniff import sniff
|
from iottb.commands.sniff import sniff
|
||||||
from iottb.commands.developer import set_key_in_table_to, rm_cfg, rm_dbs, show_cfg, show_everything
|
from iottb.commands.developer import set_key_in_table_to, rm_cfg, rm_dbs, show_cfg, show_everything
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
# Import package modules
|
# Import package modules
|
||||||
#################################################
|
#################################################
|
||||||
from iottb.utils.logger_config import setup_logging
|
from iottb.utils.logger_config import setup_logging
|
||||||
from iottb import definitions
|
from iottb import definitions
|
||||||
from iottb.models.iottb_config import IottbConfig
|
from iottb.models.iottb_config import IottbConfig
|
||||||
from iottb.commands.initialize_testbed import init_db
|
from iottb.commands.testbed import init_db
|
||||||
from iottb.commands.add_device import add_device
|
from iottb.commands.add_device import add_device
|
||||||
|
|
||||||
############################################################################
|
############################################################################
|
||||||
@ -28,26 +32,33 @@ loglevel = definitions.LOGLEVEL
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@click.group()
|
@click.group(context_settings=dict(auto_envvar_prefix='IOTTB', show_default=True))
|
||||||
@click.option('-v', '--verbosity', count=True, type=click.IntRange(0, 3), default=0,
|
@click.option('-v', '--verbosity', count=True, type=click.IntRange(0, 3), default=0, is_eager=True,
|
||||||
help='Set verbosity')
|
help='Set verbosity')
|
||||||
@click.option('-d', '--debug', is_flag=True, default=False,
|
@click.option('-d', '--debug', is_flag=True, default=False, is_eager=True,
|
||||||
help='Enable debug mode')
|
help='Enable debug mode')
|
||||||
|
@click.option('--dry-run', is_flag=True, default=True, is_eager=True)
|
||||||
@click.option('--cfg-file', type=click.Path(),
|
@click.option('--cfg-file', type=click.Path(),
|
||||||
default=Path(click.get_app_dir(APP_NAME)).joinpath('iottb.cfg'),
|
default=Path(click.get_app_dir(APP_NAME)).joinpath('iottb.cfg'),
|
||||||
envvar='IOTTB_CONF_HOME', help='Path to iottb config file')
|
envvar='IOTTB_CONF_HOME', help='Path to iottb config file')
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def cli(ctx, verbosity, debug, cfg_file):
|
def cli(ctx, verbosity, debug, dry_run, cfg_file):
|
||||||
setup_logging(verbosity, debug) # Setup logging based on the loaded configuration and other options
|
setup_logging(verbosity, debug) # Setup logging based on the loaded configuration and other options
|
||||||
ctx.ensure_object(dict) # Make sure context is ready for use
|
ctx.ensure_object(dict) # Make sure context is ready for use
|
||||||
logger.info("Starting execution.")
|
logger.info("Starting execution.")
|
||||||
ctx.obj['CONFIG'] = IottbConfig(cfg_file) # Load configuration directly
|
ctx.obj['CONFIG'] = IottbConfig(cfg_file) # Load configuration directly
|
||||||
ctx.meta['FULL_PATH_CONFIG_FILE'] = str(cfg_file)
|
ctx.meta['FULL_PATH_CONFIG_FILE'] = str(cfg_file)
|
||||||
|
ctx.meta['DRY_RUN'] = dry_run
|
||||||
|
logger.debug(f'Verbosity: {verbosity}')
|
||||||
|
ctx.obj['VERBOSITY'] = verbosity
|
||||||
|
logger.debug(f'Debug: {debug}')
|
||||||
|
ctx.obj['DEBUG'] = debug
|
||||||
|
|
||||||
|
|
||||||
##################################################################################
|
##################################################################################
|
||||||
# Add all subcommands to group here
|
# Add all subcommands to group here
|
||||||
#################################################################################
|
#################################################################################
|
||||||
|
# TODO: Is there a way to do this without pylint freaking out?
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
cli.add_command(init_db)
|
cli.add_command(init_db)
|
||||||
cli.add_command(rm_cfg)
|
cli.add_command(rm_cfg)
|
||||||
@ -58,5 +69,9 @@ cli.add_command(add_device)
|
|||||||
cli.add_command(show_cfg)
|
cli.add_command(show_cfg)
|
||||||
cli.add_command(sniff)
|
cli.add_command(sniff)
|
||||||
cli.add_command(show_everything)
|
cli.add_command(show_everything)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
cli(auto_envvar_prefix='IOTTB', show_default=True, show_envvars=True)
|
cli()
|
||||||
|
for log in Path.cwd().iterdir():
|
||||||
|
log.chmod(0o777)
|
||||||
|
|||||||
@ -1,4 +1,39 @@
|
|||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
logger = logging.getLogger('iottb.sniff') # Log with sniff subcommand
|
logger = logging.getLogger('iottb.sniff') # Log with sniff subcommand
|
||||||
|
|
||||||
|
class CaptureMetadata:
|
||||||
|
def __init__(self, device_id, capture_dir, interface, address, capture_file, tcpdump_command, tcpdump_stdout, tcpdump_stderr, packet_filter, alias):
|
||||||
|
self.base_data = {
|
||||||
|
'device_id': device_id,
|
||||||
|
'capture_id': str(uuid.uuid4()),
|
||||||
|
'capture_date': datetime.now().isoformat(),
|
||||||
|
'capture_dir': str(capture_dir),
|
||||||
|
'capture_file': capture_file,
|
||||||
|
'start_time': "",
|
||||||
|
'stop_time': "",
|
||||||
|
'alias': alias
|
||||||
|
}
|
||||||
|
self.features = {
|
||||||
|
'interface': interface,
|
||||||
|
'device_ip_address': address if address else "No IP Address set",
|
||||||
|
'tcpdump_stdout': str(tcpdump_stdout),
|
||||||
|
'tcpdump_stderr': str(tcpdump_stderr),
|
||||||
|
'packet_filter': packet_filter
|
||||||
|
}
|
||||||
|
self.command = tcpdump_command
|
||||||
|
|
||||||
|
def save_to_file(self):
|
||||||
|
metadata = {
|
||||||
|
'base_data': self.base_data,
|
||||||
|
'features': self.features,
|
||||||
|
'command': self.command
|
||||||
|
}
|
||||||
|
metadata_file_path = Path(self.base_data['capture_dir']) / 'metadata.json'
|
||||||
|
with open(metadata_file_path, 'w') as f:
|
||||||
|
json.dump(metadata, f, indent=4)
|
||||||
|
logger.info(f'Metadata saved to {metadata_file_path}')
|
||||||
|
|||||||
52
code/iottb-project/iottb/scripts/generate_help.py
Executable file
52
code/iottb-project/iottb/scripts/generate_help.py
Executable file
@ -0,0 +1,52 @@
|
|||||||
|
import click
|
||||||
|
from io import StringIO
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Import your CLI app here
|
||||||
|
from iottb.main import cli
|
||||||
|
|
||||||
|
"""Script to generate the help text and write to file.
|
||||||
|
|
||||||
|
Definitely needs better formatting.
|
||||||
|
Script is also not very flexible.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def get_help_text(command):
|
||||||
|
"""Get the help text for a given command."""
|
||||||
|
help_text = StringIO()
|
||||||
|
with click.Context(command) as ctx:
|
||||||
|
# chatgpt says this helps: was right
|
||||||
|
sys_stdout = sys.stdout
|
||||||
|
sys.stdout = help_text
|
||||||
|
try:
|
||||||
|
click.echo(command.get_help(ctx))
|
||||||
|
finally:
|
||||||
|
sys.stdout = sys_stdout
|
||||||
|
return help_text.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def write_help_to_file(cli, filename):
|
||||||
|
"""Write help messages of all commands and subcommands to a file."""
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
# main
|
||||||
|
f.write(f"Main Command: iottb\n")
|
||||||
|
f.write(get_help_text(cli))
|
||||||
|
f.write("\n\n")
|
||||||
|
|
||||||
|
# go through subcommands
|
||||||
|
for cmd_name, cmd in cli.commands.items():
|
||||||
|
f.write(f"Command: {cmd_name}\n")
|
||||||
|
f.write(get_help_text(cmd))
|
||||||
|
f.write("\n\n")
|
||||||
|
|
||||||
|
# subcommands of subcommands
|
||||||
|
if isinstance(cmd, click.Group):
|
||||||
|
for sub_cmd_name, sub_cmd in cmd.commands.items():
|
||||||
|
f.write(f"Subcommand: {cmd_name} {sub_cmd_name}\n")
|
||||||
|
f.write(get_help_text(sub_cmd))
|
||||||
|
f.write("\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
write_help_to_file(cli, "help_messages.md")
|
||||||
4
code/iottb-project/iottb/scripts/sudo_iottb
Normal file
4
code/iottb-project/iottb/scripts/sudo_iottb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#/bin/sh
|
||||||
|
echo 'Running iottb as sudo'
|
||||||
|
sudo $(which python) iottb $@
|
||||||
|
echo 'Finished executing iottb with sudo'
|
||||||
@ -34,7 +34,7 @@ def make_canonical_name(name):
|
|||||||
parts = norm_name.split('-')
|
parts = norm_name.split('-')
|
||||||
canonical_name = canonical_name = '-'.join(parts[:2])
|
canonical_name = canonical_name = '-'.join(parts[:2])
|
||||||
aliases.append(canonical_name)
|
aliases.append(canonical_name)
|
||||||
|
aliases = list(set(aliases))
|
||||||
logger.debug(f'Canonical name: {canonical_name}')
|
logger.debug(f'Canonical name: {canonical_name}')
|
||||||
logger.debug(f'Aliases: {aliases}')
|
logger.debug(f'Aliases: {aliases}')
|
||||||
return canonical_name, list(set(aliases))
|
return canonical_name, aliases
|
||||||
|
|||||||
@ -4,7 +4,6 @@ version = "0.1.0"
|
|||||||
description = "IoT Testbed"
|
description = "IoT Testbed"
|
||||||
authors = ["Sebastian Lenzlinger <sebastian.lenzlinger@unibas.ch>"]
|
authors = ["Sebastian Lenzlinger <sebastian.lenzlinger@unibas.ch>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
package-mode = false
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.12"
|
python = "^3.12"
|
||||||
|
|||||||
3
code/iottb/.gitignore
vendored
Normal file
3
code/iottb/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
__pycache__
|
||||||
|
.venv
|
||||||
|
iottb.egg-info
|
||||||
@ -1,82 +1,30 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import argparse
|
import argparse
|
||||||
from os import environ
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from iottb.logger import logger
|
from .commands.sniff import setup_sniff_parser
|
||||||
from iottb.subcommands.add_device import setup_init_device_root_parser
|
from .config import Config
|
||||||
from iottb.subcommands.capture import setup_capture_parser
|
from .utils.file_utils import ensure_directory_exists
|
||||||
from iottb.utils.tcpdump_utils import list_interfaces
|
|
||||||
from definitions import IOTTB_HOME_ABS, ReturnCodes
|
|
||||||
|
|
||||||
|
|
||||||
######################
|
def setup_logging():
|
||||||
# Argparse setup
|
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)s %(name)s: %(message)s')
|
||||||
######################
|
|
||||||
def setup_argparse():
|
|
||||||
# create top level parser
|
|
||||||
root_parser = argparse.ArgumentParser(prog='iottb')
|
|
||||||
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_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
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
setup_logging()
|
||||||
exit(ReturnCodes.ABORTED)
|
|
||||||
parser = setup_argparse()
|
parser = argparse.ArgumentParser(description='IoT Testbed')
|
||||||
|
subparsers = parser.add_subparsers()
|
||||||
|
|
||||||
|
setup_sniff_parser(subparsers)
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
print(args)
|
if hasattr(args, 'func'):
|
||||||
if args.command:
|
args.func(args)
|
||||||
try:
|
else:
|
||||||
args.func(args)
|
parser.print_help()
|
||||||
except KeyboardInterrupt:
|
|
||||||
print('Received keyboard interrupt. Exiting...')
|
|
||||||
exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f'Error: {e}')
|
|
||||||
# create_capture_directory(args.device_name)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
0
code/iottb/commands/__init__.py
Normal file
0
code/iottb/commands/__init__.py
Normal file
108
code/iottb/commands/sniff.py
Normal file
108
code/iottb/commands/sniff.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import subprocess
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
from models.capture_metadata import CaptureMetadata
|
||||||
|
from models.device_metadata import DeviceMetadata
|
||||||
|
from utils.capture_utils import get_capture_src_folder, make_capture_src_folder
|
||||||
|
from utils.tcpdump_utils import check_installed
|
||||||
|
from utils.file_utils import ensure_directory_exists
|
||||||
|
from config import Config
|
||||||
|
|
||||||
|
logger = logging.getLogger('iottb.sniff')
|
||||||
|
|
||||||
|
|
||||||
|
def is_ip_address(address):
|
||||||
|
ip_pattern = re.compile(r"^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$")
|
||||||
|
return ip_pattern.match(address) is not None
|
||||||
|
|
||||||
|
|
||||||
|
def is_mac_address(address):
|
||||||
|
mac_pattern = re.compile(r"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$")
|
||||||
|
return mac_pattern.match(address) is not None
|
||||||
|
|
||||||
|
|
||||||
|
class Sniffer:
|
||||||
|
def __init__(self, device_id, capture_interface, address=None, safe=True):
|
||||||
|
#TODO Decide if we want to use the device_id as the device_name (seems unhandy)
|
||||||
|
self.device_id = device_id
|
||||||
|
self.capture_interface = capture_interface
|
||||||
|
self.address = address
|
||||||
|
self.safe = safe
|
||||||
|
self.config = Config().load_config()
|
||||||
|
self.device_path = self.initialize_device(device_id)
|
||||||
|
self.filter = self.generate_filter()
|
||||||
|
|
||||||
|
def initialize_device(self, device_id):
|
||||||
|
db_path = Path(self.config.get('database_path', '~/iottb.db')).expanduser()
|
||||||
|
device_path = db_path / device_id
|
||||||
|
ensure_directory_exists(device_path)
|
||||||
|
|
||||||
|
metadata_file = device_path / 'device_metadata.json'
|
||||||
|
if not metadata_file.exists():
|
||||||
|
device_metadata = DeviceMetadata(device_name=device_id, device_root_path=device_path)
|
||||||
|
device_metadata.save_to_file()
|
||||||
|
return device_path
|
||||||
|
|
||||||
|
def get_capture_metadata(self, capture_dir):
|
||||||
|
metadata = CaptureMetadata(device_id=self.device_id, capture_dir=capture_dir)
|
||||||
|
metadata.build_capture_file_name()
|
||||||
|
metadata.interface = self.capture_interface
|
||||||
|
metadata.device_ip_address = self.address or "No IP Address set"
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
def generate_filter(self):
|
||||||
|
|
||||||
|
if not self.address and self.safe:
|
||||||
|
raise ValueError("Address must be provided in safe mode.")
|
||||||
|
|
||||||
|
if is_ip_address(self.address):
|
||||||
|
return f"host {self.address}"
|
||||||
|
elif is_mac_address(self.address):
|
||||||
|
return f"ether host {self.address}"
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid address format.")
|
||||||
|
|
||||||
|
def capture(self):
|
||||||
|
if not check_installed():
|
||||||
|
print('Please install tcpdump first')
|
||||||
|
return
|
||||||
|
|
||||||
|
capture_dir = make_capture_src_folder(get_capture_src_folder(self.device_path))
|
||||||
|
metadata = self.get_capture_metadata(capture_dir)
|
||||||
|
pcap_file = capture_dir / metadata.capture_file
|
||||||
|
cmd = ['sudo', 'tcpdump', '-i', self.capture_interface, '-w', str(pcap_file)]
|
||||||
|
|
||||||
|
if self.filter:
|
||||||
|
cmd.append(self.filter)
|
||||||
|
|
||||||
|
metadata.tcpdump_command = ' '.join(cmd)
|
||||||
|
print(f'Executing: {metadata.tcpdump_command}')
|
||||||
|
|
||||||
|
try:
|
||||||
|
metadata.start_time = datetime.now().isoformat()
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
metadata.stop_time = datetime.now().isoformat()
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
logger.error(f'Failed to capture packets: {e}')
|
||||||
|
return
|
||||||
|
|
||||||
|
metadata.save_to_file()
|
||||||
|
print(f"Capture complete. Metadata saved to {capture_dir / 'metadata.json'}")
|
||||||
|
|
||||||
|
|
||||||
|
def setup_sniff_parser(subparsers):
|
||||||
|
parser = subparsers.add_parser('sniff', help='Sniff packets with tcpdump')
|
||||||
|
parser.add_argument('device_id', help='ID of the device to sniff')
|
||||||
|
parser.add_argument('-i', '--interface', required=True, help='Network interface to capture on')
|
||||||
|
parser.add_argument('-a', '--address', help='IP or MAC address to filter packets by')
|
||||||
|
parser.add_argument('-u', '--unsafe', action='store_true', help='Run in unsafe mode without supplying an address. '
|
||||||
|
'Highly discouraged.')
|
||||||
|
parser.set_defaults(func=handle_sniff)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_sniff(args):
|
||||||
|
sniffer = Sniffer(device_id=args.device_id, capture_interface=args.interface, address=args.address,
|
||||||
|
safe=not args.unsafe)
|
||||||
|
sniffer.capture()
|
||||||
1
code/iottb/config.json
Normal file
1
code/iottb/config.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"database_path": "~/.iottb.db", "log_level": "INFO"}
|
||||||
45
code/iottb/config.py
Normal file
45
code/iottb/config.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger('iottb.config')
|
||||||
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
"database_path": "~/.iottb.db",
|
||||||
|
"log_level": "INFO"
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, config_file=None):
|
||||||
|
self.config_file = Path(config_file or "config.json")
|
||||||
|
if not self.config_file.exists():
|
||||||
|
self.create_default_config()
|
||||||
|
else:
|
||||||
|
self.config = self.load_config()
|
||||||
|
|
||||||
|
def create_default_config(self):
|
||||||
|
try:
|
||||||
|
self.save_config(self.DEFAULT_CONFIG)
|
||||||
|
except (IsADirectoryError, PermissionError) as e:
|
||||||
|
logger.error(f"Error creating default config: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def load_config(self):
|
||||||
|
try:
|
||||||
|
with open(self.config_file, "r") as file:
|
||||||
|
return json.load(file)
|
||||||
|
except IsADirectoryError as e:
|
||||||
|
logger.error(f"Error loading config: {e}")
|
||||||
|
raise
|
||||||
|
except PermissionError as e:
|
||||||
|
logger.error(f"Error loading config: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def save_config(self, config):
|
||||||
|
try:
|
||||||
|
with open(self.config_file, "w") as f:
|
||||||
|
json.dump(config, f, indent=2)
|
||||||
|
except (IsADirectoryError, PermissionError) as e:
|
||||||
|
logger.error(f"Error saving config: {e}")
|
||||||
|
raise
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import logging
|
|
||||||
import sys
|
|
||||||
from logging.handlers import RotatingFileHandler
|
|
||||||
|
|
||||||
|
|
||||||
def setup_logging():
|
|
||||||
logger_obj = logging.getLogger('iottbLogger')
|
|
||||||
logger_obj.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
file_handler = RotatingFileHandler('iottb.log')
|
|
||||||
console_handler = logging.StreamHandler(sys.stdout)
|
|
||||||
|
|
||||||
file_handler.setLevel(logging.INFO)
|
|
||||||
console_handler.setLevel(logging.DEBUG)
|
|
||||||
|
|
||||||
file_fmt = logging.Formatter('%(asctime)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)
|
|
||||||
console_handler.setFormatter(console_fmt)
|
|
||||||
|
|
||||||
logger_obj.addHandler(file_handler)
|
|
||||||
logger_obj.addHandler(console_handler)
|
|
||||||
|
|
||||||
return logger_obj
|
|
||||||
|
|
||||||
|
|
||||||
logger = setup_logging()
|
|
||||||
29
code/iottb/models/capture_metadata.py
Normal file
29
code/iottb/models/capture_metadata.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class CaptureMetadata:
|
||||||
|
def __init__(self, device_id, capture_dir):
|
||||||
|
self.device_id = device_id
|
||||||
|
self.capture_id = str(uuid.uuid4())
|
||||||
|
self.capture_date = datetime.now().isoformat()
|
||||||
|
self.capture_dir = Path(capture_dir)
|
||||||
|
self.capture_file = ""
|
||||||
|
self.start_time = ""
|
||||||
|
self.stop_time = ""
|
||||||
|
self.tcpdump_command = ""
|
||||||
|
self.interface = ""
|
||||||
|
self.device_ip_address = ""
|
||||||
|
|
||||||
|
def build_capture_file_name(self):
|
||||||
|
self.capture_file = f"{self.device_id}_{self.capture_id}.pcap"
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return self.__dict__
|
||||||
|
|
||||||
|
def save_to_file(self, file_path=None):
|
||||||
|
file_path = file_path or self.capture_dir / 'metadata.json'
|
||||||
|
with open(file_path, 'w') as f:
|
||||||
|
json.dump(self.to_dict(), f, indent=4)
|
||||||
27
code/iottb/models/device_metadata.py
Normal file
27
code/iottb/models/device_metadata.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class DeviceMetadata:
|
||||||
|
def __init__(self, device_name, device_root_path):
|
||||||
|
self.device_name = device_name
|
||||||
|
self.device_short_name = device_name.lower().replace(' ', '_')
|
||||||
|
self.device_id = str(uuid.uuid4())
|
||||||
|
self.date_created = datetime.now().isoformat()
|
||||||
|
self.device_root_path = Path(device_root_path)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return self.__dict__
|
||||||
|
|
||||||
|
def save_to_file(self):
|
||||||
|
file_path = self.device_root_path / 'device_metadata.json'
|
||||||
|
with open(file_path, 'w') as f:
|
||||||
|
json.dump(self.to_dict(), f, indent=4)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_from_file(cls, file_path):
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
return cls(**data)
|
||||||
18
code/iottb/pyproject.toml
Normal file
18
code/iottb/pyproject.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=42", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "iottb"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = [{name = "Sebastian Lenzlinger", email = "sebastian.lenzlinger@unibas.ch"}]
|
||||||
|
description = "Automation Tool for Capturing Network packets of IoT devices."
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
exclude = ["tests*", "docs*"]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
iottb = "iottb.__main__:main"
|
||||||
55
code/iottb/scripts/wifi_ctl.sh
Normal file
55
code/iottb/scripts/wifi_ctl.sh
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#!/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
|
||||||
15
code/iottb/templates/capture_metadata_template.json
Normal file
15
code/iottb/templates/capture_metadata_template.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"device_id": "",
|
||||||
|
"capture_id": "",
|
||||||
|
"capture_date": "",
|
||||||
|
"capture_file": "",
|
||||||
|
"start_time": "",
|
||||||
|
"stop_time": "",
|
||||||
|
"capture_duration": "",
|
||||||
|
"interfaces": "",
|
||||||
|
"device_ip_address": "",
|
||||||
|
"device_mac_address": "",
|
||||||
|
"contacted_ip_address": [],
|
||||||
|
"device_firmware_version": "",
|
||||||
|
"campanion_app": ""
|
||||||
|
}
|
||||||
4
code/iottb/templates/config_template.json
Normal file
4
code/iottb/templates/config_template.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"database_path": "~/.iottb.db",
|
||||||
|
"log_level": "INFO"
|
||||||
|
}
|
||||||
14
code/iottb/templates/device_metadata_template.json
Normal file
14
code/iottb/templates/device_metadata_template.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"device_id": "",
|
||||||
|
"device_name": "",
|
||||||
|
"device_short_name": "",
|
||||||
|
"date_created": "",
|
||||||
|
"description": "",
|
||||||
|
"model": "",
|
||||||
|
"manufacturer": "",
|
||||||
|
"firmware_version": "",
|
||||||
|
"device_type": "",
|
||||||
|
"supported_interfaces": "",
|
||||||
|
"companion_applications": "",
|
||||||
|
"last_metadata_update": ""
|
||||||
|
}
|
||||||
43
code/iottb/test/test_Config.py
Normal file
43
code/iottb/test/test_Config.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from config import Config
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestConfig(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_creates_new_config_file_if_not_exists(self):
|
||||||
|
config_path = Path("test_config.json")
|
||||||
|
if config_path.exists():
|
||||||
|
config_path.unlink()
|
||||||
|
config = Config(config_file=config_path)
|
||||||
|
self.assertTrue(config_path.exists())
|
||||||
|
config_path.unlink()
|
||||||
|
|
||||||
|
def test_writes_default_configuration_to_config_file(self):
|
||||||
|
config_path = Path("test_config.json")
|
||||||
|
if config_path.exists():
|
||||||
|
config_path.unlink()
|
||||||
|
config = Config(config_file=config_path)
|
||||||
|
with open(config_path, "r") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
self.assertEqual(data, {"database_path": "~/.iottb.db", "log_level": "INFO"})
|
||||||
|
config_path.unlink()
|
||||||
|
|
||||||
|
@unittest.mock.patch("builtins.open", side_effect=PermissionError)
|
||||||
|
def test_config_file_path_not_writable(self, mock_open):
|
||||||
|
config_path = Path("test_config.json")
|
||||||
|
with self.assertRaises(PermissionError):
|
||||||
|
config = Config(config_file=config_path)
|
||||||
|
config.create_default_config()
|
||||||
|
|
||||||
|
def test_config_file_path_is_directory(self):
|
||||||
|
config_dir = Path("test_config_dir")
|
||||||
|
config_dir.mkdir(exist_ok=True)
|
||||||
|
with self.assertRaises(IsADirectoryError):
|
||||||
|
config = Config(config_file=config_dir)
|
||||||
|
config.create_default_config()
|
||||||
|
config_dir.rmdir()
|
||||||
38
code/iottb/test/test_ensure_directory_exists.py
Normal file
38
code/iottb/test/test_ensure_directory_exists.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Generated by CodiumAI
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from utils.file_utils import ensure_directory_exists
|
||||||
|
|
||||||
|
|
||||||
|
class TestEnsureDirectoryExists(unittest.TestCase):
|
||||||
|
|
||||||
|
# creates directory if it does not exist
|
||||||
|
def test_creates_directory_if_not_exists(self):
|
||||||
|
path = Path('/tmp/testdir')
|
||||||
|
if path.exists():
|
||||||
|
path.rmdir()
|
||||||
|
ensure_directory_exists(path)
|
||||||
|
self.assertTrue(path.exists())
|
||||||
|
path.rmdir()
|
||||||
|
|
||||||
|
# does not create directory if it already exists
|
||||||
|
def test_does_not_create_directory_if_exists(self):
|
||||||
|
path = Path('/tmp/testdir')
|
||||||
|
path.mkdir(exist_ok=True)
|
||||||
|
ensure_directory_exists(path)
|
||||||
|
self.assertTrue(path.exists())
|
||||||
|
path.rmdir()
|
||||||
|
|
||||||
|
# path is a symbolic link
|
||||||
|
def test_path_is_a_symbolic_link(self):
|
||||||
|
target_dir = Path('/tmp/targetdir')
|
||||||
|
symlink_path = Path('/tmp/symlinkdir')
|
||||||
|
target_dir.mkdir(exist_ok=True)
|
||||||
|
symlink_path.symlink_to(target_dir)
|
||||||
|
ensure_directory_exists(symlink_path)
|
||||||
|
self.assertTrue(symlink_path.exists())
|
||||||
|
self.assertTrue(symlink_path.is_symlink())
|
||||||
|
symlink_path.unlink()
|
||||||
|
target_dir.rmdir()
|
||||||
62
code/iottb/test/test_is_ip_address.py
Normal file
62
code/iottb/test/test_is_ip_address.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
from commands.sniff import is_ip_address
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestIsIpAddress(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_valid_ipv4_address_all_octets_in_range(self):
|
||||||
|
self.assertTrue(is_ip_address("192.168.1.1"))
|
||||||
|
self.assertTrue(is_ip_address("0.0.0.0"))
|
||||||
|
self.assertTrue(is_ip_address("255.255.255.255"))
|
||||||
|
|
||||||
|
def test_ipv4_address_with_leading_zeros(self):
|
||||||
|
self.assertTrue(is_ip_address("192.168.001.001"))
|
||||||
|
self.assertTrue(is_ip_address("0.0.0.0"))
|
||||||
|
self.assertTrue(is_ip_address("255.255.255.255"))
|
||||||
|
|
||||||
|
def test_ipv4_address_mixed_single_double_digit_octets(self):
|
||||||
|
self.assertTrue(is_ip_address("192.168.1.01"))
|
||||||
|
self.assertTrue(is_ip_address("0.0.0.0"))
|
||||||
|
self.assertTrue(is_ip_address("255.255.255.255"))
|
||||||
|
|
||||||
|
def test_ipv4_address_maximum_values_in_octets(self):
|
||||||
|
self.assertTrue(is_ip_address("255.255.255.255"))
|
||||||
|
self.assertTrue(is_ip_address("0.0.0.0"))
|
||||||
|
self.assertTrue(is_ip_address("192.168.1.1"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv4_address_minimum_values_in_octets(self):
|
||||||
|
self.assertTrue(is_ip_address("0.0.0.0"))
|
||||||
|
self.assertTrue(is_ip_address("192.168.1.1"))
|
||||||
|
self.assertTrue(is_ip_address("255.255.255.255"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv4_address_more_than_four_octets_invalid(self):
|
||||||
|
self.assertFalse(is_ip_address("192.168.1.1.1"))
|
||||||
|
self.assertFalse(is_ip_address("0.0.0.0.0"))
|
||||||
|
self.assertFalse(is_ip_address("255.255.255.255.255"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv4_address_fewer_than_four_octets_invalid(self):
|
||||||
|
self.assertFalse(is_ip_address("192.168.1"))
|
||||||
|
self.assertFalse(is_ip_address("0.0"))
|
||||||
|
self.assertFalse(is_ip_address("255"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv4_address_non_numeric_characters_invalid(self):
|
||||||
|
self.assertFalse(is_ip_address("192.a.b.c"))
|
||||||
|
self.assertFalse(is_ip_address("0.x.y.z"))
|
||||||
|
self.assertFalse(is_ip_address("255.q.w.e"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv4_address_octets_out_of_range_invalid(self):
|
||||||
|
self.assertFalse(is_ip_address("256.256.256.256"))
|
||||||
|
self.assertFalse(is_ip_address("300.300.300.300"))
|
||||||
|
self.assertFalse(is_ip_address("999.999.999.999"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv4_address_empty_string_invalid(self):
|
||||||
|
self.assertFalse(is_ip_address(""))
|
||||||
|
self.assertFalse(is_ip_address(" "))
|
||||||
|
self.assertFalse(is_ip_address(None))
|
||||||
64
code/iottb/test/test_is_mac_address.py
Normal file
64
code/iottb/test/test_is_mac_address.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
from commands.sniff import is_mac_address
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
class TestIsMacAddress(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_mac_address_lowercase(self):
|
||||||
|
self.assertTrue(is_mac_address("aa:bb:cc:dd:ee:ff"))
|
||||||
|
self.assertFalse(is_mac_address("192.168.1.1"))
|
||||||
|
self.assertFalse(is_mac_address("aa:bb:cc:dd:ee:ff:gg"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_mac_address_uppercase(self):
|
||||||
|
self.assertTrue(is_mac_address("AA:BB:CC:DD:EE:FF"))
|
||||||
|
self.assertFalse(is_mac_address("10.0.0.1"))
|
||||||
|
self.assertFalse(is_mac_address("AA:BB:CC:DD:EE"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_mac_address_mixed_case(self):
|
||||||
|
self.assertTrue(is_mac_address("Aa:Bb:Cc:Dd:Ee:Ff"))
|
||||||
|
self.assertFalse(is_mac_address("172.16.0.1"))
|
||||||
|
self.assertFalse(is_mac_address("Aa:Bb:Cc:Dd:Ee:Ff:Gg"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_mac_address_digits(self):
|
||||||
|
self.assertTrue(is_mac_address("00:11:22:33:44:55"))
|
||||||
|
self.assertFalse(is_mac_address("8.8.8.8"))
|
||||||
|
self.assertFalse(is_mac_address("00:11:22:33:44"))
|
||||||
|
|
||||||
|
# returns False for an empty string
|
||||||
|
def test_empty_string(self):
|
||||||
|
self.assertFalse(is_mac_address(""))
|
||||||
|
self.assertFalse(is_mac_address(":"))
|
||||||
|
|
||||||
|
def test_invalid_characters(self):
|
||||||
|
self.assertFalse(is_mac_address("gh:ij:kl:mn:op:qr"))
|
||||||
|
self.assertFalse(is_mac_address("192.168.0.256"))
|
||||||
|
self.assertFalse(is_mac_address("ghij::klmn::opqr"))
|
||||||
|
|
||||||
|
# returns False for a MAC address with incorrect length
|
||||||
|
def test_incorrect_length(self):
|
||||||
|
self.assertFalse(is_mac_address("aa:bb:cc"))
|
||||||
|
self.assertFalse(is_mac_address("10.0.0.256"))
|
||||||
|
self.assertFalse(is_mac_address("aa::bb::cc::dd::ee::ff::gg"))
|
||||||
|
|
||||||
|
# returns False for a MAC address with missing colons
|
||||||
|
def test_missing_colons(self):
|
||||||
|
self.assertFalse(is_mac_address("aabbccddeeff"))
|
||||||
|
self.assertFalse(is_mac_address("127.0.0.1"))
|
||||||
|
self.assertFalse(is_mac_address("aabbccddeeffgg"))
|
||||||
|
|
||||||
|
# returns False for a MAC address with extra colons
|
||||||
|
def test_extra_colons(self):
|
||||||
|
self.assertFalse(is_mac_address("aa::bb::cc::dd::ee::ff"))
|
||||||
|
self.assertFalse(is_mac_address("192.168.1.256"))
|
||||||
|
self.assertFalse(is_mac_address("aa::bb::cc::dd::ee::ff::gg"))
|
||||||
|
|
||||||
|
# returns False for a MAC address with spaces
|
||||||
|
def test_spaces_in_mac(self):
|
||||||
|
self.assertFalse(is_mac_address("aa bb cc dd ee ff"))
|
||||||
|
self.assertFalse(is_mac_address("8.8.4.4"))
|
||||||
|
self.assertFalse(is_mac_address("aa bb cc dd ee ff gg"))
|
||||||
@ -1,44 +1,20 @@
|
|||||||
import uuid
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from iottb.models.device_metadata_model import dir_contains_device_metadata
|
from datetime import datetime
|
||||||
from iottb.utils.utils import get_iso_date
|
|
||||||
|
|
||||||
|
|
||||||
def get_capture_uuid():
|
def get_capture_src_folder(device_path):
|
||||||
return str(uuid.uuid4())
|
today_str = datetime.now().strftime('%Y-%m-%d')
|
||||||
|
capture_base_path = device_path / today_str
|
||||||
|
capture_base_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
existing_captures = [d for d in capture_base_path.iterdir() if d.is_dir()]
|
||||||
|
nth_capture = len(existing_captures) + 1
|
||||||
|
capture_dir = capture_base_path / f'capture_{nth_capture}'
|
||||||
|
capture_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
return capture_dir
|
||||||
|
|
||||||
|
|
||||||
def get_capture_date_folder(device_root: Path):
|
def make_capture_src_folder(capture_src_folder):
|
||||||
today_iso = get_iso_date()
|
capture_src_folder.mkdir(parents=True, exist_ok=True)
|
||||||
today_folder = device_root / today_iso
|
return capture_src_folder
|
||||||
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
|
|
||||||
|
|||||||
29
code/iottb/utils/diagram1.py
Normal file
29
code/iottb/utils/diagram1.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
# Create the graph
|
||||||
|
G1 = nx.DiGraph()
|
||||||
|
|
||||||
|
# Add nodes with positions
|
||||||
|
G1.add_node("IoT Device", pos=(1, 3))
|
||||||
|
G1.add_node("AP", pos=(3, 3))
|
||||||
|
G1.add_node("Switch (Port Mirroring Enabled)", pos=(5, 3))
|
||||||
|
G1.add_node("Gateway Router", pos=(7, 3))
|
||||||
|
G1.add_node("Internet", pos=(9, 3))
|
||||||
|
G1.add_node("Capture Device", pos=(5, 1))
|
||||||
|
|
||||||
|
# Add edges
|
||||||
|
G1.add_edge("IoT Device", "AP")
|
||||||
|
G1.add_edge("AP", "Switch (Port Mirroring Enabled)")
|
||||||
|
G1.add_edge("Switch (Port Mirroring Enabled)", "Gateway Router")
|
||||||
|
G1.add_edge("Gateway Router", "Internet")
|
||||||
|
G1.add_edge("Switch (Port Mirroring Enabled)", "Capture Device")
|
||||||
|
|
||||||
|
# Draw the graph
|
||||||
|
pos = nx.get_node_attributes(G1, 'pos')
|
||||||
|
plt.figure(figsize=(12, 8))
|
||||||
|
nx.draw(G1, pos, with_labels=True, node_size=3000, node_color='lightblue', font_size=10, font_weight='bold')
|
||||||
|
nx.draw_networkx_edge_labels(G1, pos, edge_labels={("Switch (Port Mirroring Enabled)", "Capture Device"): "Mirrored Traffic"}, font_color='red')
|
||||||
|
|
||||||
|
plt.title("IoT Device Connected via AP to Gateway Router via Switch with Port Mirroring Enabled")
|
||||||
|
plt.show()
|
||||||
27
code/iottb/utils/diagramm2.py
Normal file
27
code/iottb/utils/diagramm2.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import networkx as nx
|
||||||
|
|
||||||
|
# Create the graph
|
||||||
|
G2 = nx.DiGraph()
|
||||||
|
|
||||||
|
# Add nodes with positions
|
||||||
|
G2.add_node("IoT Device", pos=(1, 3))
|
||||||
|
G2.add_node("Capture Device (Hotspot)", pos=(3, 3))
|
||||||
|
G2.add_node("Ethernet Connection", pos=(5, 3))
|
||||||
|
G2.add_node("Gateway Router", pos=(7, 3))
|
||||||
|
G2.add_node("Internet", pos=(9, 3))
|
||||||
|
|
||||||
|
# Add edges
|
||||||
|
G2.add_edge("IoT Device", "Capture Device (Hotspot)")
|
||||||
|
G2.add_edge("Capture Device (Hotspot)", "Ethernet Connection")
|
||||||
|
G2.add_edge("Ethernet Connection", "Gateway Router")
|
||||||
|
G2.add_edge("Gateway Router", "Internet")
|
||||||
|
|
||||||
|
# Draw the graph
|
||||||
|
pos = nx.get_node_attributes(G2, 'pos')
|
||||||
|
plt.figure(figsize=(12, 8))
|
||||||
|
nx.draw(G2, pos, with_labels=True, node_size=3000, node_color='lightblue', font_size=10, font_weight='bold')
|
||||||
|
nx.draw_networkx_edge_labels(G2, pos, edge_labels={("Capture Device (Hotspot)", "Ethernet Connection"): "Bridged Traffic"}, font_color='red')
|
||||||
|
|
||||||
|
plt.title("Capture Device Provides Hotspot and Bridges to Ethernet for Internet")
|
||||||
|
plt.show()
|
||||||
19
code/iottb/utils/file_utils.py
Normal file
19
code/iottb/utils/file_utils.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def load_json_template(template_path):
|
||||||
|
with open(template_path, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
|
def save_json(data, file_path):
|
||||||
|
with open(file_path, 'w') as f:
|
||||||
|
json.dump(data, f, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_directory_exists(path):
|
||||||
|
path = Path(path)
|
||||||
|
if not path.exists():
|
||||||
|
path.mkdir(parents=True, exist_ok=True)
|
||||||
|
return path
|
||||||
@ -1,41 +1,9 @@
|
|||||||
import ipaddress
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
def check_installed() -> bool:
|
def check_installed():
|
||||||
"""Check if tcpdump is installed and available on the system path."""
|
|
||||||
return shutil.which('tcpdump') is not None
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_installed():
|
|
||||||
"""Ensure that tcpdump is installed, raise an error if not."""
|
|
||||||
if not check_installed():
|
|
||||||
raise RuntimeError('tcpdump is not installed. Please install it to continue.')
|
|
||||||
|
|
||||||
|
|
||||||
def list_interfaces() -> str:
|
|
||||||
"""List available network interfaces using tcpdump."""
|
|
||||||
ensure_installed()
|
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(['tcpdump', '--list-interfaces'], capture_output=True, text=True, check=True)
|
subprocess.run(['tcpdump', '--version'], check=True, capture_output=True)
|
||||||
return result.stdout
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f'Failed to list interfaces: {e}')
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
def is_valid_ipv4(ip: str) -> bool:
|
|
||||||
try:
|
|
||||||
ipaddress.IPv4Address(ip)
|
|
||||||
return True
|
return True
|
||||||
except ValueError:
|
except subprocess.CalledProcessError:
|
||||||
return False
|
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
|
|
||||||
|
|||||||
@ -11,4 +11,5 @@ With the above idea it would be possible to also refactor or rewrite how tcpdump
|
|||||||
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.
|
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
|
## 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.
|
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.
|
||||||
|
#remark TCP dump filter could also be exported into an environment variable? But then again what is the use of defining a conformance, then could use the raw capture idea for tcpdump, too.
|
||||||
0
notes/journal/Untitled.md
Normal file
0
notes/journal/Untitled.md
Normal file
13
notes/scrible
Normal file
13
notes/scrible
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
`iottb sniff`:
|
||||||
|
min: nothing
|
||||||
|
min meaningfull: interface
|
||||||
|
min usefull: ip/mac addr of dev
|
||||||
|
good: ip/mac, device type
|
||||||
|
better:
|
||||||
|
|
||||||
|
`iottb device`
|
||||||
|
`add`: add new device config
|
||||||
|
|
||||||
|
`iottb db`
|
||||||
|
`init` initialize device database
|
||||||
|
`add` add device
|
||||||
7
notes/scrible.py
Normal file
7
notes/scrible.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
class Config:
|
||||||
|
db_dir = Path.home()
|
||||||
|
app_config_dir = Path.home /.Config
|
||||||
|
db_name = 'IoTtb.db'
|
||||||
|
app_config_name = 'iottb.conf'
|
||||||
|
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user