diff --git a/docker_compose/.env b/docker_compose/.env index a37e6c01e..4ac820460 100644 --- a/docker_compose/.env +++ b/docker_compose/.env @@ -6,6 +6,7 @@ TRAPS_CONFIG_FILE_ABSOLUTE_PATH= INVENTORY_FILE_ABSOLUTE_PATH= COREFILE_ABS_PATH= COREDNS_ADDRESS=172.28.0.255 +COREDNS_ADDRESS_IPv6=fd02:0:0:0:7fff:ffff:ffff:ffff SC4SNMP_VERSION="1.12.1-beta.2" IPv6_ENABLED=false diff --git a/docker_compose/Corefile b/docker_compose/Corefile index 7ea43e1b2..23823b42d 100644 --- a/docker_compose/Corefile +++ b/docker_compose/Corefile @@ -3,5 +3,5 @@ errors auto reload - forward . 8.8.8.8 + forward . 8.8.8.8 2001:4860:4860::8888 } \ No newline at end of file diff --git a/docker_compose/docker-compose.yaml b/docker_compose/docker-compose.yaml index 7fd14e719..4b9727134 100644 --- a/docker_compose/docker-compose.yaml +++ b/docker_compose/docker-compose.yaml @@ -47,6 +47,7 @@ x-dns_and_networks: &dns_and_networks - sc4snmp_network dns: - ${COREDNS_ADDRESS} + - ${COREDNS_ADDRESS_IPv6} x-dependency_and_restart_policy: &dependency_and_restart_policy depends_on: @@ -86,6 +87,7 @@ services: networks: sc4snmp_network: ipv4_address: ${COREDNS_ADDRESS} + ipv6_address: ${COREDNS_ADDRESS_IPv6} snmp-mibserver: <<: [*dns_and_networks, *dependend_on_core_dns] image: ${MIBSERVER_IMAGE}:${MIBSERVER_TAG:-latest} diff --git a/docs/dockercompose/6-env-file-configuration.md b/docs/dockercompose/6-env-file-configuration.md index 9921db035..6dd5d3d76 100644 --- a/docs/dockercompose/6-env-file-configuration.md +++ b/docs/dockercompose/6-env-file-configuration.md @@ -13,6 +13,7 @@ Inside the directory with the docker compose files, there is a `.env`. Variables | `INVENTORY_FILE_ABSOLUTE_PATH` | Absolute path to [inventory.csv](./3-inventory-configuration.md) file | | `COREFILE_ABS_PATH` | Absolute path to Corefile used by coreDNS. Default Corefile can be found inside the `docker_compose` | | `COREDNS_ADDRESS` | IP address of the coredns inside docker network. Should not be changed | +| `COREDNS_ADDRESS_IPv6` | IPv6 address of the coredns inside docker network. Should not be changed | | `SC4SNMP_VERSION` | Version of SC4SNMP | | `IPv6_ENABLED` | Enable receiving traps and polling from IPv6 devices | diff --git a/docs/microk8s/configuration/poller-configuration.md b/docs/microk8s/configuration/poller-configuration.md index 376d191ba..bd98047c6 100644 --- a/docs/microk8s/configuration/poller-configuration.md +++ b/docs/microk8s/configuration/poller-configuration.md @@ -31,6 +31,9 @@ poller: !!! info The header's line (`address,port,version,community,secret,security_engine,walk_interval,profiles,smart_profiles,delete`) is necessary for the correct execution of SC4SNMP. Do not remove it. +### IPv6 hostname resolution +When IPv6 is enabled and device is dual stack, the hostname resolution will try to resolve the name to the IPv6 address first, then to the IPv4 address. + ### Define log level The log level for poller can be set by changing the value for the key `logLevel`. The allowed values are: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` or `FATAL`. The default value is `INFO`. diff --git a/integration_tests/.env b/integration_tests/.env index 14fe6d9b5..8cabcf4d9 100644 --- a/integration_tests/.env +++ b/integration_tests/.env @@ -6,8 +6,9 @@ TRAPS_CONFIG_FILE_ABSOLUTE_PATH= INVENTORY_FILE_ABSOLUTE_PATH= COREFILE_ABS_PATH= COREDNS_ADDRESS=172.28.0.255 -SC4SNMP_VERSION="1.11.0-beta.9" - +COREDNS_ADDRESS_IPv6=fd02:0:0:0:7fff:ffff:ffff:ffff +SC4SNMP_VERSION=latest +IPv6_ENABLED=false # Dependencies images COREDNS_IMAGE=coredns/coredns @@ -81,6 +82,7 @@ CHAIN_OF_TASKS_EXPIRY_TIME=500 # Traps configuration SNMP_V3_SECURITY_ENGINE_ID=80003a8c04 TRAPS_PORT=162 +IPv6_TRAPS_PORT=2163 TRAP_LOG_LEVEL=INFO # Scheduler configuration diff --git a/integration_tests/automatic_setup_compose.sh b/integration_tests/automatic_setup_compose.sh index 54463c5fe..6d6831630 100755 --- a/integration_tests/automatic_setup_compose.sh +++ b/integration_tests/automatic_setup_compose.sh @@ -48,6 +48,7 @@ deploy_poetry() { } wait_for_containers_to_be_up() { + echo $(sudo docker ps) while true; do CONTAINERS_SC4SNMP=$(sudo docker ps | grep "sc4snmp\|worker-poller\|worker-sender\|worker-trap" | grep -v "Name" | wc -l) if [ "$CONTAINERS_SC4SNMP" -gt 0 ]; then diff --git a/splunk_connect_for_snmp/common/inventory_record.py b/splunk_connect_for_snmp/common/inventory_record.py index 41bcdc47c..3757da153 100644 --- a/splunk_connect_for_snmp/common/inventory_record.py +++ b/splunk_connect_for_snmp/common/inventory_record.py @@ -33,8 +33,8 @@ class InventoryRecord(BaseModel): - address: InventoryStr port: InventoryInt = 161 + address: InventoryStr version: InventoryStr community: InventoryStr secret: InventoryStr @@ -53,7 +53,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @validator("address", pre=True) - def address_validator(cls, value): + def address_validator(cls, value, values): if value is None: raise ValueError("field address cannot be null") if value.startswith("#"): @@ -63,7 +63,7 @@ def address_validator(cls, value): ip_address(value) except ValueError: try: - socket.gethostbyname_ex(value) + socket.getaddrinfo(value, values["port"]) except socket.gaierror: raise ValueError( f"field address must be an IP or a resolvable hostname {value}" diff --git a/splunk_connect_for_snmp/snmp/auth.py b/splunk_connect_for_snmp/snmp/auth.py index bb762d4fb..96ee4d7f9 100644 --- a/splunk_connect_for_snmp/snmp/auth.py +++ b/splunk_connect_for_snmp/snmp/auth.py @@ -14,6 +14,8 @@ # limitations under the License. # import os +import socket +from ipaddress import ip_address from typing import Any, Dict, Union from pysnmp.hlapi import ( @@ -28,11 +30,13 @@ from pysnmp.proto.api.v2c import OctetString from pysnmp.smi.rfc1902 import ObjectIdentity, ObjectType +from splunk_connect_for_snmp.common.hummanbool import human_bool from splunk_connect_for_snmp.common.inventory_record import InventoryRecord from splunk_connect_for_snmp.snmp.const import AuthProtocolMap, PrivProtocolMap from splunk_connect_for_snmp.snmp.exceptions import SnmpActionError UDP_CONNECTION_TIMEOUT = int(os.getenv("UDP_CONNECTION_TIMEOUT", 1)) +IPv6_ENABLED = human_bool(os.getenv("IPv6_ENABLED", False)) def get_secret_value( @@ -87,7 +91,8 @@ def get_security_engine_id(logger, ir: InventoryRecord, snmp_engine: SnmpEngine) def setup_transport_target(ir): - if ":" in ir.address: + ip = get_ip_from_socket(ir) if IPv6_ENABLED else ir.address + if ip_address(ip).version == 6: transport = Udp6TransportTarget( (ir.address, ir.port), timeout=UDP_CONNECTION_TIMEOUT ) @@ -98,6 +103,13 @@ def setup_transport_target(ir): return transport +def get_ip_from_socket(ir): + # Example of response from getaddrinfo + # [(< AddressFamily.AF_INET6: 10 >, < SocketKind.SOCK_STREAM: 1 >, 6, '', ('2607:f8b0:4004:c09::64', 161, 0, 0)), + # (< AddressFamily.AF_INET: 2 >, < SocketKind.SOCK_STREAM: 1 >, 6, '', ('142.251.16.139', 161))] + return socket.getaddrinfo(ir.address, ir.port)[0][4][0] + + def fetch_security_engine_id(observer_context, error_indication, ipaddress): if "securityEngineId" in observer_context: return observer_context["securityEngineId"]