Allow more fine grained access: include host/port combinations
It would be useful if pytest-socket supported allowing connect() to specific host/port combinations, instead of a blanket guard on all socket creation. The reason for this is when using testcontainers that spawn docker images, those containers are typically only accessible via localhost socket communication. With more fine-grained abilities I could do something like
container = RedisContainer()
pytest_socket.enable_connect(("localhost", container.port))
This would imply that socket creation could be allowed, but other socket operations such as connect() and bind() could be disallowed.
For now I am working around this by enabling sockets before my code creates a Redis connection, and then disable sockets immediately after creating the connection.
Note that some modules are not impacted by a socket guard because they are implemented in C (psycopg driver for postgres for example).
After using pytest-socket for many years, just came around to needing this use case this week :-)
I wanted to allow access to localhost:5432(postgres) but disallow localhost:6379(redis). One reason being was i didn't realize some tests were accidentally connecting to redis for locks. So i want to blanket block redis connections and force them to use in-memory abstractions we already have for eg. fakeredis, django loc-memory backend etc.
@miketheman I'm planning to take a stab at this by next week if possible. Does the following sound ok?
- Extend the existing
--allow-hosts=127.0.0.1,127.0.1.1to something like ->--allow-hosts=192.168.1.1:5432,[::1]:5432,localhost. i.e. add an optional:port. The issue here is the ipv6 which forces me to add a[..].
I got the basic parse working... thought it would be trivial(go figure!); but it can get hairy. I tried to use urlparse stdlib instead of doing regex or some hairy parsing. Is there some other standard lib thing i'm missing? Idea would be to parse ipv4/ipv6 addresses into (host, port) tuples and go from there.
def parse_address_to_host_port(address: str) -> typing.Tuple[str, str]:
"""Parse an address string into host and port components."""
# Prepend a dummy scheme to ensure correct parsing of netloc
# urlparse requires a scheme to properly identify netloc, otherwise
# '127.0.0.1:8080' would be scheme='127.0.0.1', path='8080'
# For IPv6, it also correctly handles the brackets if a scheme is present.
# Check if this is an IPv6 address without brackets
if ':' in address and not address.startswith('['):
# Check if it's an IPv6 address (multiple colons)
if address.count(':') > 1:
# Wrap IPv6 address in brackets for urlparse
url_string = f"dummy://[{address}]"
else:
url_string = f"dummy://{address}"
else:
url_string = f"dummy://{address}"
parsed = urlparse(url_string)
host = parsed.hostname
port = parsed.port
# Handle special cases that urlparse might not process correctly
if host is None:
if address.startswith('[') and ']:' in address:
host = address[1:address.find(']')]
else:
host = address # Assume the whole thing is the host if no colon/brackets for port
return (host, port)
...
# test seems to pass.
def test_parse_address_to_host_port():
_parse = parse_address_to_host_port
assert _parse("127.0.0.1") == ("127.0.0.1", None)
assert _parse("127.0.0.1:1234") == ("127.0.0.1", 1234)
assert _parse("localhost") == ("localhost", None)
assert _parse("localhost:5432") == ("localhost", 5432)
assert _parse("::1") == ("::1", None)
assert _parse("[::1]:1234") == ("::1", 1234)
assert _parse("[::1]:1234") == ("::1", 1234)
assert _parse("[::1]:5432") == ("::1", 5432)
assert _parse("[::1]:1234") == ("::1", 1234)
assert _parse("2001:0DB8:85A3:0000:0000:8A2E:0370:7334") == ("2001:0db8:85a3:0000:0000:8a2e:0370:7334", None)
This issue is stale because it has been open for 90 days with no activity.
This issue was closed because it has been inactive for 30 days since being marked as stale.