Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some suggestions for better IPv6 support #31

Merged
merged 11 commits into from
Sep 18, 2023
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ __pycache__
build
dist
zeek_client.egg-info
.DS_Store
68 changes: 68 additions & 0 deletions tests/test_config_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,74 @@ def test_config_addl_key(self):
"warning: ignoring unexpected keys: also_not_a_key, not_a_key",
)

def test_config_ipv4_ipv6_instances(self):
# This test creates a Configuration from an INI file with various IP addresses
# and ports specified for the agents.

ini_input = """[instances]
agent = 127.0.0.1:2151
agent2 = ::1:2151
agent3 = [::2]:2151

[manager]
instance = agent
role = MANAGER
port = 5000
"""
ini_expected = """[instances]
agent = 127.0.0.1:2151
agent2 = ::1:2151
agent3 = ::2:2151

[manager]
instance = agent
role = MANAGER
port = 5000
"""
cfp = self.parser_from_string(ini_input)
config = zeekclient.types.Configuration.from_config_parser(cfp)
self.assertTrue(config is not None)

# Turning that back into a config parser should have expected content:
cfp = config.to_config_parser()
with io.StringIO() as buf:
cfp.write(buf)
self.assertEqualStripped(buf.getvalue(), ini_expected)

def test_config_invalid_ipv4_instance(self):
# This test creates a Configuration with an invalid IPv4 address

ini_input = """[instances]
agent = 127.0.0.1.1:2151

[manager]
instance = agent
role = MANAGER
port = 5000
"""
cfp = self.parser_from_string(ini_input)
with self.assertRaisesRegex(
ValueError, "'127.0.0.1.1' does not appear to be an IPv4 or IPv6 address"
):
zeekclient.types.Configuration.from_config_parser(cfp)

def test_config_invalid_ipv6_instance(self):
# This test creates a Configuration with an invalid IPv6 address

ini_input = """[instances]
agent = ::2/128:2151

[manager]
instance = agent
role = MANAGER
port = 5000
"""
cfp = self.parser_from_string(ini_input)
with self.assertRaisesRegex(
ValueError, "'::2/128' does not appear to be an IPv4 or IPv6 address"
):
zeekclient.types.Configuration.from_config_parser(cfp)

def test_config_invalid_instances(self):
ini_input = """
[instances]
Expand Down
6 changes: 5 additions & 1 deletion zeekclient/brokertypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,11 @@ def __lt__(self, other):
res = super().__lt__(other)
if res != NotImplemented:
return res
return self._addr < other._addr
if self._addr.version == other._addr.version:
return self._addr < other._addr

# Make a blanket assumption that an IPv4 address is "less than" an IPv6 address
dopheide-esnet marked this conversation as resolved.
Show resolved Hide resolved
return self._addr.version < other._addr.version

def __hash__(self):
return hash(self._value)
Expand Down
16 changes: 10 additions & 6 deletions zeekclient/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import enum
import shlex
import socket
from ipaddress import ip_address

from . import brokertypes as bt
from .utils import make_uuid
Expand Down Expand Up @@ -238,7 +239,9 @@ def __init__(self, name, addr=None, port=None):
self.name = name
# This is a workaround until we've resolved addresses in instances
self.host = "0.0.0.0" # XXX needs proper optionality
if addr is not None:

# If addr isn't a valid address, the ipaddress module will raise a ValueError
if addr is not None and ip_address(addr):
self.host = str(addr)
self.port = port # None or integer value; we always mean TCP

Expand Down Expand Up @@ -650,17 +653,18 @@ def from_config_parser(cls, cfp, _section=None):
config.instances.append(Instance(key))
else:
hostport = val
parts = hostport.split(":", 1)
if len(parts) != 2 or not parts[0] or not parts[1]:
host, _, port = hostport.rpartition(":")
if host == "" or port == "":
LOG.error(
'invalid spec for instance "%s": "%s" should be <host>:<port>',
key,
val,
)
return None
config.instances.append(
Instance(key, parts[0].strip(), parts[1].strip())
)
# remove brackets to support [ipv6]:port formats
host = host.strip("[] ")
port = port.strip()
config.instances.append(Instance(key, host, port))
continue

# All keys for sections other than "instances" need to have a value.
Expand Down