Skip to content

Commit

Permalink
Create base class for the network plugin (#810)
Browse files Browse the repository at this point in the history
  • Loading branch information
Miauwkeru authored Aug 12, 2024
1 parent ba9bf7b commit 789009d
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 0 deletions.
37 changes: 37 additions & 0 deletions dissect/target/helpers/record.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,40 @@ def DynamicDescriptor(types): # noqa
"empty",
[],
)

COMMON_INTERFACE_ELEMENTS = [
("string", "name"),
("string", "type"),
("boolean", "enabled"),
("string", "mac"),
("net.ipaddress[]", "dns"),
("net.ipaddress[]", "ip"),
("net.ipaddress[]", "gateway"),
("string", "source"),
]


UnixInterfaceRecord = TargetRecordDescriptor(
"unix/network/interface",
COMMON_INTERFACE_ELEMENTS,
)

WindowsInterfaceRecord = TargetRecordDescriptor(
"windows/network/interface",
[
*COMMON_INTERFACE_ELEMENTS,
("varint", "vlan"),
("string", "metric"),
("datetime", "last_connected"),
],
)

MacInterfaceRecord = TargetRecordDescriptor(
"macos/network/interface",
[
*COMMON_INTERFACE_ELEMENTS,
("varint", "vlan"),
("string", "proxy"),
("varint", "interface_service_order"),
],
)
82 changes: 82 additions & 0 deletions dissect/target/plugins/general/network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from __future__ import annotations

from typing import Any, Iterator, Union

from flow.record.fieldtypes.net import IPAddress, IPNetwork

from dissect.target.helpers.record import (
MacInterfaceRecord,
UnixInterfaceRecord,
WindowsInterfaceRecord,
)
from dissect.target.plugin import Plugin, export, internal
from dissect.target.target import Target

InterfaceRecord = Union[UnixInterfaceRecord, WindowsInterfaceRecord, MacInterfaceRecord]


class NetworkPlugin(Plugin):
__namespace__ = "network"

def __init__(self, target: Target):
super().__init__(target)
self._interface_list: list[InterfaceRecord] | None = None

def check_compatible(self) -> None:
pass

def _interfaces(self) -> Iterator[InterfaceRecord]:
yield from ()

def _get_record_type(self, field_name: str) -> Iterator[Any]:
for record in self.interfaces():
if (output := getattr(record, field_name, None)) is None:
continue

if isinstance(output, list):
yield from output
else:
yield output

@export(record=InterfaceRecord)
def interfaces(self) -> Iterator[InterfaceRecord]:
# Only search for the interfaces once
if self._interface_list is None:
self._interface_list = list(self._interfaces())

yield from self._interface_list

@export
def ips(self) -> list[IPAddress]:
return list(self._get_record_type("ip"))

@export
def gateways(self) -> list[IPAddress]:
return list(self._get_record_type("gateway"))

@export
def macs(self) -> list[str]:
return list(self._get_record_type("mac"))

@export
def dns(self) -> list[str]:
return list(self._get_record_type("dns"))

@internal
def with_ip(self, ip_addr: str) -> Iterator[InterfaceRecord]:
for interface in self.interfaces():
if ip_addr in interface.ip:
yield interface

@internal
def with_mac(self, mac: str) -> Iterator[InterfaceRecord]:
for interface in self.interfaces():
if interface.mac == mac:
yield interface

@internal
def in_cidr(self, cidr: str) -> Iterator[InterfaceRecord]:
cidr = IPNetwork(cidr)
for interface in self.interfaces():
if any(ip_addr in cidr for ip_addr in interface.ip):
yield interface
46 changes: 46 additions & 0 deletions tests/plugins/general/test_network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from unittest.mock import patch

import pytest

from dissect.target.helpers.record import (
MacInterfaceRecord,
UnixInterfaceRecord,
WindowsInterfaceRecord,
)
from dissect.target.plugins.general.network import InterfaceRecord, NetworkPlugin
from dissect.target.target import Target


@pytest.fixture(params=[MacInterfaceRecord, WindowsInterfaceRecord, UnixInterfaceRecord])
def network_record(request: pytest.FixtureRequest) -> InterfaceRecord:
return request.param(
name="interface_name",
type="physical",
enabled=True,
mac="DE:AD:BE:EF:00:00",
ip=["10.42.42.10"],
gateway=["10.42.42.1"],
dns=["8.8.8.8", "1.1.1.1"],
source="some_file",
)


def test_base_network_plugin(target_bare: Target, network_record: InterfaceRecord) -> None:
with patch.object(NetworkPlugin, "_interfaces", return_value=[network_record]):
network = NetworkPlugin(target_bare)
interfaces = list(network.interfaces())
assert len(interfaces) == 1

assert network.ips() == ["10.42.42.10"]
assert network.gateways() == ["10.42.42.1"]
assert network.macs() == ["DE:AD:BE:EF:00:00"]
assert network.dns() == ["8.8.8.8", "1.1.1.1"]

assert len(list(network.in_cidr("10.42.42.0/24"))) == 1
assert len(list(network.in_cidr("10.43.42.0/24"))) == 0

assert len(list(network.with_mac("DE:AD:BE:EF:00:00"))) == 1
assert len(list(network.with_mac("DE:AD:BE:EF:00:01"))) == 0

assert len(list(network.with_ip("10.42.42.10"))) == 1
assert len(list(network.with_ip("10.42.42.42"))) == 0
1 change: 1 addition & 0 deletions tests/tools/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,5 +241,6 @@ def test_target_query_dry_run(capsys: pytest.CaptureFixture, monkeypatch: pytest
assert out == (
f"Dry run on: <Target {target_file}>\n"
" execute: users (general.default.users)\n"
" execute: network.interfaces (general.network.interfaces)\n"
" execute: osinfo (general.osinfo.osinfo)\n"
)

0 comments on commit 789009d

Please sign in to comment.