pytest-socket icon indicating copy to clipboard operation
pytest-socket copied to clipboard

Allow more fine grained access: include host/port combinations

Open jrafkind opened this issue 8 months ago • 2 comments

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).

jrafkind avatar Apr 30 '25 21:04 jrafkind

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.

sidmitra avatar May 16 '25 14:05 sidmitra

@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.1 to 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)

sidmitra avatar Jun 06 '25 23:06 sidmitra

This issue is stale because it has been open for 90 days with no activity.

github-actions[bot] avatar Sep 05 '25 02:09 github-actions[bot]

This issue was closed because it has been inactive for 30 days since being marked as stale.

github-actions[bot] avatar Oct 05 '25 02:10 github-actions[bot]