diff --git a/monkey/common/network/network_utils.py b/monkey/common/network/network_utils.py index d211474a5..06d754502 100644 --- a/monkey/common/network/network_utils.py +++ b/monkey/common/network/network_utils.py @@ -1,4 +1,30 @@ -from typing import Optional, Tuple +import ipaddress +from ipaddress import IPv4Interface +from typing import List, Optional, Sequence, Tuple + +from netifaces import AF_INET, ifaddresses, interfaces + + +def get_local_ip_addresses() -> Sequence[str]: + ip_list = [] + for interface in interfaces(): + addresses = ifaddresses(interface).get(AF_INET, []) + ip_list.extend([link["addr"] for link in addresses if link["addr"] != "127.0.0.1"]) + return ip_list + + +def get_local_interfaces() -> List[IPv4Interface]: + local_interfaces = [] + for interface in interfaces(): + addresses = ifaddresses(interface).get(AF_INET, []) + local_interfaces.extend( + [ + ipaddress.IPv4Interface(link["addr"] + "/" + link["netmask"]) + for link in addresses + if link["addr"] != "127.0.0.1" + ] + ) + return local_interfaces # TODO: `address_to_port()` should return the port as an integer. diff --git a/monkey/infection_monkey/control.py b/monkey/infection_monkey/control.py index 7f161ecbd..406e7bfc4 100644 --- a/monkey/infection_monkey/control.py +++ b/monkey/infection_monkey/control.py @@ -7,8 +7,9 @@ import requests from urllib3 import disable_warnings from common.common_consts.timeouts import LONG_REQUEST_TIMEOUT, MEDIUM_REQUEST_TIMEOUT +from common.network.network_utils import get_local_ip_addresses from infection_monkey.config import GUID -from infection_monkey.network.info import get_host_subnets, local_ips +from infection_monkey.network.info import get_host_subnets from infection_monkey.utils import agent_process disable_warnings() # noqa DUO131 @@ -38,7 +39,7 @@ class ControlClient: monkey = { "guid": GUID, "hostname": hostname, - "ip_addresses": local_ips(), + "ip_addresses": get_local_ip_addresses(), "networks": get_host_subnets(), "description": " ".join(platform.uname()), "parent": parent, diff --git a/monkey/infection_monkey/master/control_channel.py b/monkey/infection_monkey/master/control_channel.py index b1b4bae81..18029b148 100644 --- a/monkey/infection_monkey/master/control_channel.py +++ b/monkey/infection_monkey/master/control_channel.py @@ -11,8 +11,8 @@ from common import AgentRegistrationData from common.agent_configuration import AgentConfiguration from common.common_consts.timeouts import SHORT_REQUEST_TIMEOUT from common.credentials import Credentials +from common.network.network_utils import get_local_interfaces from infection_monkey.i_control_channel import IControlChannel, IslandCommunicationError -from infection_monkey.network.info import get_network_interfaces from infection_monkey.utils import agent_process from infection_monkey.utils.ids import get_agent_id, get_machine_id @@ -34,7 +34,7 @@ class ControlChannel(IControlChannel): # parent_id=parent, parent_id=None, # None for now, until we change GUID to UUID cc_server=self._control_channel_server, - network_interfaces=get_network_interfaces(), + network_interfaces=get_local_interfaces(), ) try: diff --git a/monkey/infection_monkey/monkey.py b/monkey/infection_monkey/monkey.py index dfc34bec2..a115c0965 100644 --- a/monkey/infection_monkey/monkey.py +++ b/monkey/infection_monkey/monkey.py @@ -15,7 +15,11 @@ from common.event_serializers import ( register_common_agent_event_serializers, ) from common.events import CredentialsStolenEvent -from common.network.network_utils import address_to_ip_port +from common.network.network_utils import ( + address_to_ip_port, + get_local_interfaces, + get_local_ip_addresses, +) from common.utils.argparse_types import positive_int from common.utils.attack_utils import ScanStatus, UsageEnum from common.version import get_version @@ -45,7 +49,7 @@ from infection_monkey.master import AutomatedMaster from infection_monkey.master.control_channel import ControlChannel from infection_monkey.model import VictimHostFactory from infection_monkey.network.firewall import app as firewall -from infection_monkey.network.info import get_free_tcp_port, get_network_interfaces, local_ips +from infection_monkey.network.info import get_free_tcp_port from infection_monkey.network.relay import TCPRelay from infection_monkey.network.relay.utils import ( find_server, @@ -201,7 +205,7 @@ class InfectionMonkey: self._cmd_island_port, client_disconnect_timeout=config.keep_tunnel_open_time, ) - relay_servers = [f"{ip}:{relay_port}" for ip in local_ips()] + relay_servers = [f"{ip}:{relay_port}" for ip in get_local_ip_addresses()] if not maximum_depth_reached(config.propagation.maximum_depth, self._current_depth): self._relay.start() @@ -220,7 +224,7 @@ class InfectionMonkey: return agent_event_serializer_registry def _build_master(self, relay_servers: List[str]): - local_network_interfaces = InfectionMonkey._get_local_network_interfaces() + local_network_interfaces = get_local_interfaces() # TODO control_channel and control_client have same responsibilities, merge them propagation_credentials_repository = AggregatingPropagationCredentialsRepository( @@ -271,14 +275,6 @@ class InfectionMonkey: AgentEventForwarder(server_address, agent_event_serializer_registry).send_event ) - @staticmethod - def _get_local_network_interfaces() -> List[IPv4Interface]: - local_network_interfaces = get_network_interfaces() - for interface in local_network_interfaces: - logger.debug(f"Found local interface {str(interface)}") - - return local_network_interfaces - def _build_puppet( self, propagation_credentials_repository: IPropagationCredentialsRepository, diff --git a/monkey/infection_monkey/network/info.py b/monkey/infection_monkey/network/info.py index 4bd5b763e..8cc12038d 100644 --- a/monkey/infection_monkey/network/info.py +++ b/monkey/infection_monkey/network/info.py @@ -2,10 +2,9 @@ import itertools import socket import struct from dataclasses import dataclass -from ipaddress import IPv4Interface from random import shuffle # noqa: DUO102 from threading import Lock -from typing import Dict, List, Set +from typing import Dict, Set import netifaces import psutil @@ -29,10 +28,6 @@ class NetworkAddress: domain: str -def get_network_interfaces() -> List[IPv4Interface]: - return [IPv4Interface(f"{i['addr']}/{i['netmask']}") for i in get_host_subnets()] - - def get_host_subnets(): """ Returns a list of subnets visible to host (omitting loopback and auto conf networks) @@ -60,20 +55,12 @@ def get_host_subnets(): if is_windows_os(): - def local_ips(): - local_hostname = socket.gethostname() - return socket.gethostbyname_ex(local_hostname)[2] - def get_routes(): raise NotImplementedError() else: from fcntl import ioctl - def local_ips(): - valid_ips = [network["addr"] for network in get_host_subnets()] - return valid_ips - def get_routes(): # based on scapy implementation for route parsing try: f = open("/proc/net/route", "r") diff --git a/monkey/monkey_island/cc/models/monkey.py b/monkey/monkey_island/cc/models/monkey.py index cf15c3cd4..988454b1a 100644 --- a/monkey/monkey_island/cc/models/monkey.py +++ b/monkey/monkey_island/cc/models/monkey.py @@ -19,7 +19,7 @@ from mongoengine import ( from monkey_island.cc.models.command_control_channel import CommandControlChannel from monkey_island.cc.models.monkey_ttl import MonkeyTtl, create_monkey_ttl_document from monkey_island.cc.server_utils.consts import DEFAULT_MONKEY_TTL_EXPIRY_DURATION_IN_SECONDS -from monkey_island.cc.server_utils.network_utils import get_ip_addresses +from monkey_island.cc.server_utils.network_utils import get_cached_local_ip_addresses class ParentNotFoundError(Exception): @@ -123,7 +123,8 @@ class Monkey(Document): def get_label_by_id(object_id): current_monkey = Monkey.get_single_monkey_by_id(object_id) label = Monkey.get_hostname_by_id(object_id) + " : " + current_monkey.ip_addresses[0] - if len(set(current_monkey.ip_addresses).intersection(get_ip_addresses())) > 0: + local_ips = map(str, get_cached_local_ip_addresses()) + if len(set(current_monkey.ip_addresses).intersection(local_ips)) > 0: label = "MonkeyIsland - " + label return label diff --git a/monkey/monkey_island/cc/server_setup.py b/monkey/monkey_island/cc/server_setup.py index 4f6989e12..9d092da53 100644 --- a/monkey/monkey_island/cc/server_setup.py +++ b/monkey/monkey_island/cc/server_setup.py @@ -37,7 +37,7 @@ from monkey_island.cc.server_utils.consts import ( # noqa: E402 MONKEY_ISLAND_ABS_PATH, ) from monkey_island.cc.server_utils.island_logger import reset_logger, setup_logging # noqa: E402 -from monkey_island.cc.server_utils.network_utils import get_ip_addresses # noqa: E402 +from monkey_island.cc.server_utils.network_utils import get_cached_local_ip_addresses # noqa: E402 from monkey_island.cc.services.initialize import initialize_services # noqa: E402 from monkey_island.cc.setup import island_config_options_validator # noqa: E402 from monkey_island.cc.setup import ( # noqa: E402 @@ -106,7 +106,7 @@ def _configure_logging(config_options): def _collect_system_info() -> Tuple[Sequence[str], Deployment, Version]: deployment = _get_deployment() version = Version(get_version(), deployment) - return (get_ip_addresses(), deployment, version) + return (get_cached_local_ip_addresses(), deployment, version) def _get_deployment() -> Deployment: diff --git a/monkey/monkey_island/cc/server_utils/network_utils.py b/monkey/monkey_island/cc/server_utils/network_utils.py index 60c397c77..73abded95 100644 --- a/monkey/monkey_island/cc/server_utils/network_utils.py +++ b/monkey/monkey_island/cc/server_utils/network_utils.py @@ -1,23 +1,14 @@ -import ipaddress +from ipaddress import IPv4Address, IPv4Interface from typing import Sequence -from netifaces import AF_INET, ifaddresses, interfaces from ring import lru -# TODO: This functionality is duplicated in the agent. Unify them after 2216-tcp-relay is merged +from common.network.network_utils import get_local_interfaces, get_local_ip_addresses -# The local IP addresses list should not change often. Therefore, we can cache the result and never -# call this function more than once. This stopgap measure is here since this function is called a -# lot of times during the report generation. This means that if the interfaces of the Island machine -# change, the Island process needs to be restarted. @lru(maxsize=1) -def get_ip_addresses() -> Sequence[str]: - ip_list = [] - for interface in interfaces(): - addresses = ifaddresses(interface).get(AF_INET, []) - ip_list.extend([link["addr"] for link in addresses if link["addr"] != "127.0.0.1"]) - return ip_list +def get_cached_local_ip_addresses() -> Sequence[IPv4Address]: + return get_local_ip_addresses() # The subnets list should not change often. Therefore, we can cache the result and never call this @@ -25,15 +16,5 @@ def get_ip_addresses() -> Sequence[str]: # during the report generation. This means that if the interfaces or subnets of the Island machine # change, the Island process needs to be restarted. @lru(maxsize=1) -def get_subnets(): - subnets = [] - for interface in interfaces(): - addresses = ifaddresses(interface).get(AF_INET, []) - subnets.extend( - [ - ipaddress.ip_interface(link["addr"] + "/" + link["netmask"]).network - for link in addresses - if link["addr"] != "127.0.0.1" - ] - ) - return subnets +def get_cached_local_interfaces() -> Sequence[IPv4Interface]: + return get_local_interfaces() diff --git a/monkey/monkey_island/cc/services/node.py b/monkey/monkey_island/cc/services/node.py index 2a7f0f661..6797cc7a9 100644 --- a/monkey/monkey_island/cc/services/node.py +++ b/monkey/monkey_island/cc/services/node.py @@ -7,7 +7,7 @@ import monkey_island.cc.services.log from monkey_island.cc import models from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey -from monkey_island.cc.server_utils.network_utils import get_ip_addresses +from monkey_island.cc.server_utils.network_utils import get_cached_local_ip_addresses from monkey_island.cc.services.edge.displayed_edge import DisplayedEdgeService from monkey_island.cc.services.edge.edge import EdgeService from monkey_island.cc.services.utils.node_states import NodeStates @@ -110,7 +110,7 @@ class NodeService: def get_monkey_label(monkey): # todo label = monkey["hostname"] + " : " + monkey["ip_addresses"][0] - ip_addresses = get_ip_addresses() + ip_addresses = get_cached_local_ip_addresses() if len(set(monkey["ip_addresses"]).intersection(ip_addresses)) > 0: label = "MonkeyIsland - " + label return label @@ -118,7 +118,7 @@ class NodeService: @staticmethod def get_monkey_group(monkey): keywords = [] - if len(set(monkey["ip_addresses"]).intersection(get_ip_addresses())) != 0: + if len(set(monkey["ip_addresses"]).intersection(get_cached_local_ip_addresses())) != 0: keywords.extend(["island", "monkey"]) else: monkey_type = "manual" if NodeService.get_monkey_manual_run(monkey) else "monkey" @@ -275,7 +275,7 @@ class NodeService: # It's better to just initialize the island machine on reset I think @staticmethod def get_monkey_island_monkey(): - ip_addresses = get_ip_addresses() + ip_addresses = get_cached_local_ip_addresses() for ip_address in ip_addresses: monkey = NodeService.get_monkey_by_ip(ip_address) if monkey is not None: @@ -297,7 +297,7 @@ class NodeService: @staticmethod def get_monkey_island_node(): island_node = NodeService.get_monkey_island_pseudo_net_node() - island_node["ip_addresses"] = get_ip_addresses() + island_node["ip_addresses"] = get_cached_local_ip_addresses() island_node["domain_name"] = socket.gethostname() return island_node diff --git a/monkey/monkey_island/cc/services/reporting/report.py b/monkey/monkey_island/cc/services/reporting/report.py index 01ce711d1..59c40499a 100644 --- a/monkey/monkey_island/cc/services/reporting/report.py +++ b/monkey/monkey_island/cc/services/reporting/report.py @@ -10,7 +10,10 @@ from monkey_island.cc.database import mongo from monkey_island.cc.models import Monkey from monkey_island.cc.models.report import get_report, save_report from monkey_island.cc.repository import IAgentConfigurationRepository, ICredentialsRepository -from monkey_island.cc.server_utils.network_utils import get_ip_addresses, get_subnets +from monkey_island.cc.server_utils.network_utils import ( + get_cached_local_interfaces, + get_cached_local_ip_addresses, +) from monkey_island.cc.services.node import NodeService from monkey_island.cc.services.reporting.exploitations.manual_exploitation import get_manual_monkeys from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import ( @@ -175,7 +178,7 @@ class ReportService: @staticmethod def get_island_cross_segment_issues(): issues = [] - island_ips = get_ip_addresses() + island_ips = get_cached_local_ip_addresses() for monkey in mongo.db.monkey.find( {"tunnel": {"$exists": False}}, {"tunnel": 1, "guid": 1, "hostname": 1} ): @@ -194,7 +197,9 @@ class ReportService: "type": "island_cross_segment", "machine": monkey["hostname"], "networks": [str(subnet) for subnet in monkey_subnets], - "server_networks": [str(subnet) for subnet in get_subnets()], + "server_networks": [ + str(interface.network) for interface in get_cached_local_interfaces() + ], } )