net: Use exclusive P2P listen sockets on Windows #36305

pull winterrdog wants to merge 2 commits into bitcoin:master from winterrdog:fix/p2p-exclusive-bind-win32 changing 2 files +31 −0
  1. winterrdog commented at 10:11 PM on September 20, 2026: contributor

    on Windows, SO_REUSEADDR does not carry over the same semantics as on POSIX systems. a second socket can set SO_REUSEADDR and bind to a port that is already in use by another process running under the same user account. this means another local process could bind to the P2P listen port and receive incoming connections intended for bitcoind.

    this matters because the P2P listener is expected to have exclusive ownership of its address and port. allowing another process to bind to it could let that process intercept incoming P2P connections. this is in a similar spirit to #36169, which made the RPC listener exclusive on Windows.

    to fix this, we use SO_EXCLUSIVEADDRUSE for the P2P listen socket on Windows instead of SO_REUSEADDR, while retaining SO_REUSEADDR on other platforms. the added test verifies the expected behaviour by attempting to bind a second socket to the same port. on Windows, this fails with WSAEACCES (10013), which Python exposes as PermissionError (an OSError subclass).

    references:

  2. DrahtBot added the label P2P on Sep 20, 2026
  3. DrahtBot commented at 10:12 PM on September 20, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    ACK jpk68, hodlinator
    Concept ACK hebasto

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  4. winterrdog commented at 10:12 PM on September 20, 2026: contributor

    indeed, this difference in SO_REUSEADDR behaviour across operating systems is somewhat surprising, and has sparked similar discussions in Tornado, CPython, and Trio.

  5. maflcko added the label Windows on Sep 21, 2026
  6. jpk68 commented at 1:58 AM on October 20, 2026: contributor

    ACK db31a3ad1f75538d07707d373adbfbe90ba8e328

  7. in src/net.cpp:3402 in c6971fd610
    3397 | +    // which could allow it to intercept incoming P2P connections. On Windows,
    3398 | +    // SO_REUSEADDR is NOT exclusive, so another process running under the same
    3399 | +    // user account could bind to the same port.
    3400 | +    if (sock->SetSockOpt(SOL_SOCKET, SO_EXCLUSIVEADDRUSE, &nOne, sizeof(int)) == SOCKET_ERROR) {
    3401 | +        strError = Untranslated(strprintf("Error setting SO_EXCLUSIVEADDRUSE on socket: %s, continuing anyway", NetworkErrorString(WSAGetLastError())));
    3402 | +        LogInfo("%s\n", strError.original);
    


    hodlinator commented at 11:19 AM on October 23, 2026:

    Not sure why setting the option would fail, but I think we should treat it as a fatal error, same as in httpserver.cpp.

            strError = Untranslated(strprintf("Error setting SO_EXCLUSIVEADDRUSE on socket: %s", NetworkErrorString(WSAGetLastError())));
            LogError("%s\n", strError.original);
            return false;
    

    https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse state:

    All server applications must set SO_EXCLUSIVEADDRUSE for a strong level of socket security. Not only does it prevent malicious software from hijacking the port, but it also indicates whether or not another application is bound to the requested port. For example, a call to bind on the wildcard address by a process with the SO_EXCLUSIVEADDRUSE socket option set will fail if another process is currently bound to the same port on a specific interface.

    Lastly, even though socket security has been improved in Windows Server 2003, an application should always set the SO_EXCLUSIVEADDRUSE socket option to ensure that it binds to all the specific interfaces the process has requested. The socket security in Windows Server 2003 adds an increased level of security for legacy applications, but application developers must still design their products with all aspects of security in mind.


    winterrdog commented at 1:56 PM on October 25, 2026:

    correct, this is much better. fixed and thanks a lot!

  8. in test/functional/feature_port.py:43 in c6971fd610 outdated
      38 | +        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as competing_listener:
      39 | +            competing_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      40 | +            # Misconfigured sockets allow port reuse unless the original
      41 | +            # listener requested for exclusive address use.
      42 | +            assert_raises(OSError, lambda: competing_listener.bind(("0.0.0.0", port1)))
      43 | +
    


    hodlinator commented at 11:22 AM on October 23, 2026:

    Could also add a test for when the socket is bound before bitcoind:

    --- a/test/functional/feature_port.py
    +++ b/test/functional/feature_port.py
    @@ -11,6 +11,7 @@ import socket
     from test_framework.test_framework import (
         BitcoinTestFramework,
     )
    +from test_framework.test_node import ErrorMatch
     from test_framework.util import (
         p2p_port,
         assert_raises,
    @@ -41,6 +42,17 @@ class PortTest(BitcoinTestFramework):
                 # listener requested for exclusive address use.
                 assert_raises(OSError, lambda: competing_listener.bind(("0.0.0.0", port1)))
    
    +        self.log.info("Verifying that we fail during startup if another process is bound to the port")
    +        self.stop_node(0)
    +        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as competing_listener:
    +            competing_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    +            self.log.info(f"Binding to {port1}, not {port2}")
    +            competing_listener.bind(("0.0.0.0", port1))
    +            node.assert_start_raises_init_error(
    +                extra_args=["-listen", f"-port={port1}"],
    +                expected_msg=fr"^Error\: Unable to bind to 0\.0\.0\.0\:{port1} on this computer\..*",
    +                match=ErrorMatch.PARTIAL_REGEX)
    +
    

    (Edit: Realized the setsockopt() on the python side is probably not necessary).


    winterrdog commented at 1:56 PM on October 25, 2026:

    i agreee, added the test

    (Edit: Realized the setsockopt() on the python side is probably not necessary).

    yes, we can leave out the Python-side setsockopt() because on Linux (and likely other POSIX systems), setting SO_REUSEADDR on both sockets would let bitcoind bind successfully (the Python socket is only bound but does not listen), making the test fail. leaving it out is fine on Windows, since the first check (where bitcoind binds first) already catches that regression

  9. hodlinator commented at 11:24 AM on October 23, 2026: contributor

    Concept ACK c6971fd61036cf738606afa62826af47e4315730

  10. winterrdog force-pushed on Oct 25, 2026
  11. DrahtBot requested review from hodlinator on Oct 25, 2026
  12. hodlinator approved
  13. hodlinator commented at 10:26 AM on October 26, 2026: contributor

    ACK 36505a3992f509510250b9701b617a35594dbff3

    Ran functional test on Windows. Thanks for taking my suggestions!

  14. hebasto commented at 12:56 PM on October 26, 2026: member

    Concept ACK.

  15. hebasto commented at 1:31 PM on October 27, 2026: member

    I think the commits should be swapped. Otherwise, the first commit fails the test on Windows.

  16. net: use exclusive P2P listen sockets on Windows
    On Windows, `SO_REUSEADDR` does not give exclusive use of an address and
    port, so another process under the same user account can bind the same
    port and intercept incoming P2P connections.
    
    On Windows, use `SO_EXCLUSIVEADDRUSE` instead. If the option cannot be
    set, fail the bind rather than listen on a port that could be hijacked.
    0dbd271eb4
  17. test: check P2P listen port exclusivity
    Check both orderings of a port conflict: another socket cannot bind the
    P2P port while `bitcoind` is listening on it, and `bitcoind` refuses to
    start if another socket already holds the port.
    
    Co-authored-by: hodlinator <172445034+hodlinator@users.noreply.github.com>
    db31a3ad1f
  18. winterrdog force-pushed on Oct 27, 2026
  19. winterrdog commented at 3:42 PM on October 27, 2026: contributor

    the commits should be swapped

    done

  20. DrahtBot requested review from hebasto on Oct 27, 2026
  21. DrahtBot requested review from hodlinator on Oct 27, 2026
  22. hodlinator approved
  23. hodlinator commented at 6:54 PM on October 27, 2026: contributor

    re-ACK db31a3ad1f75538d07707d373adbfbe90ba8e328

    I think the commits should be swapped. Otherwise, the first commit fails the test on Windows.

    Think I've seen this recently in another PR, but just thought "well, the ancestor commits CI doesn't run Windows anyway", but I agree it's more correct to swap the order.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-10-30 09:40 UTC