http: Fail initialization when any bind fails #14968

pull laanwj wants to merge 1 commits into bitcoin:master from laanwj:2018_12_http_bind_error changing 1 files +23 −3
  1. laanwj commented at 7:04 am on December 15, 2018: member

    Currently the HTTP server initialization (HTTPBindAddresses) fails only when all bindings fail. So if multiple binds are specified (127.0.0.1 and ::1 by defeault) and one succeeds and the other fails, the latter is essentially ignored.

    This commit changes the error behavior to fail if not all binds could be performed, which I think is more in line with how software normally handles this and what users expect.

    Needs mention in release notes.

  2. laanwj added the label RPC/REST/ZMQ on Dec 15, 2018
  3. luke-jr commented at 7:14 am on December 15, 2018: member
    Doesn’t binding 127.0.0.1 fail if ::1 is already bound, on some systems? (Because ::1 includes 127.0.0.1 in practice.)
  4. laanwj commented at 7:20 am on December 15, 2018: member

    Doesn’t binding 127.0.0.1 fail if ::1 is already bound, on some systems? (Because ::1 includes 127.0.0.1 in practice.)

    No, that’s definitely not true. No localhost address or subnet should include each other.

    0lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
    1        inet 127.0.0.1  netmask 255.0.0.0
    2        inet6 ::1  prefixlen 128  scopeid 0x10<host>
    

    so:

    0127.0.0.1/8 (0:0:0:0:0:0:7f00:0001/96 if embedded in IPv6)
    10:0:0:0:0:0:0:1/128
    

    None of these overlaps.

    You’re mixing it up with ::0 / INADDR_ANY, which can be all-inclusive in some systems (sometimes depending on dual-stack settings).

  5. meshcollider commented at 7:42 pm on December 15, 2018: contributor

    Concept ACK

    The error message here also needs updating for the new behaviour:

    https://github.com/bitcoin/bitcoin/blob/f128ecae3577bf9294efc8ff1dc9e0549ae91243/src/httpserver.cpp#L393-L396

  6. laanwj force-pushed on Dec 16, 2018
  7. laanwj commented at 8:08 am on December 16, 2018: member
    @MeshCollider thanks, updated
  8. sipa commented at 8:10 am on December 16, 2018: member
    utACK
  9. gmaxwell commented at 10:25 am on December 16, 2018: contributor
    utACK would also be nicer to get this in sooner rather than later, so we get more of a chance to find out that SpatulaBSD behaves like luke thought before this ends up in a release. :)
  10. Empact commented at 10:30 am on December 16, 2018: member
    Concept ACK
  11. MarcoFalke commented at 5:31 pm on December 16, 2018: member
    Travis fails when creating the cache. Could be because of the default value of bind_to_localhost_only? Not sure if self.bind_to_localhost_only even makes sense after the recent changes to how rpc binds.
  12. laanwj commented at 8:53 am on December 17, 2018: member
    bind_to_localhost_only has to do with P2P, not RPC. IIRC RPC was always already bound to localhost only for the tests, apart from the bind test.
  13. laanwj commented at 9:09 am on December 17, 2018: member

    I guess this has to do with Travis not supporting (even local!) IPv6 at all, so the IPv6 bind fails and it doesn’t start.

    I suppose not being able to auto-bind on either IPv4 or IPv6 is valid if there is no such interface, at least if -rpcbind was not explicitly given.

  14. laanwj force-pushed on Dec 17, 2018
  15. laanwj commented at 10:16 am on December 17, 2018: member

    OK, per suggestion by @gmaxwell I’ve changed the logic. By default, if no explicit bind configuration is given, the EADDRNOTAVAIL error while binding is ignored (but not, say, EADDRINUSE). This is user-friendly as it means the default setting will auto-detect based on available IPv4/IPv6. In all other cases all errors are fatal.

    This can be tested using linux network namespaces

    0ip netns add dumb
    1ip netns exec dumb ip addr add 127.0.0.1/8 dev lo # adds both IPv4 and IPv6
    2ip netns exec dumb ip addr del ::1/128 dev lo # remove IPv6
    3ip netns exec dumb ip link set lo up
    4ip netns exec dumb src/bitcoind -regtest
    
  16. laanwj force-pushed on Dec 17, 2018
  17. laanwj commented at 10:59 am on December 17, 2018: member
    Sigh, that still didn’t solve the Travis issue though. Locally running the tests with no IPv6 localhost worked fine for me now.
  18. laanwj added the label Needs release notes on Dec 17, 2018
  19. laanwj force-pushed on Dec 17, 2018
  20. in src/httpserver.cpp:348 in a846175b02 outdated
    343+                LogPrintf("Binding RPC on address %s port %i failed, error ignored because interface was unavailable.\n", i->first, i->second);
    344+            }
    345+        }
    346+    }
    347+    if (num_fail != 0) {
    348+        // In case of an error, clean up listening sockets that succeeded to
    


    laanwj commented at 3:26 pm on December 17, 2018:
    Needed this to prevent a leak and segfault in StopHTTPServer (because the owning evhttp is freed but the listeners are not).
  21. laanwj force-pushed on Dec 17, 2018
  22. http: Fail initialization when any bind fails
    Currently the HTTP server initialization (`HTTPBindAddresses`) fails
    only when *all* bindings fail. So if multiple binds are specified
    (`127.0.0.1` and `::1` by defeault) and one succeeds and the other
    fails, the latter is essentially ignored.
    
    This commit changes the error behavior to fail *if not all* binds could
    be performed, which I think is more in line with how software normally
    handles this and what users expect.
    7b5e4001f9
  23. laanwj force-pushed on Dec 17, 2018
  24. laanwj added this to the milestone 0.18.0 on Dec 17, 2018
  25. luke-jr commented at 4:46 am on December 28, 2018: member
    Unfortunately, evhttp_bind_socket_with_handle returns a 0/Success errno when listen fails. I’ve opened a report upstream to fix this, but obviously we can’t rely on it now: https://github.com/libevent/libevent/pull/738
  26. luke-jr commented at 6:29 am on December 28, 2018: member
    One possible solution is to clone the evhttp code. I got it down to 65 lines: https://github.com/bitcoin/bitcoin/compare/master...luke-jr:my_bind_socket_with_handle
  27. luke-jr commented at 7:40 am on December 28, 2018: member

    At least on Travis, the failure isn’t caused by listen()’s errno being lost.

    Instead, what’s happening is that getaddrinfo is returning EVUTIL_EAI_ADDRFAMILY. These errors are borrowed from the system’s EAI_* constants, which [can] overlap with errno’s constants, so it’s not practical to just stuff them into errno from evhttp_bind_socket_with_handle… Opened a libevent issue for this: https://github.com/libevent/libevent/issues/740

    Added a somewhat-ugly hack on top of my branch: https://github.com/bitcoin/bitcoin/commit/d2d51e7aedc85f50dcc3a94d220500cb3bfe10f9

  28. luke-jr commented at 8:58 am on December 28, 2018: member
    Just noticed Travis is passing on this PR… did we lose some test coverage between 0.17 (where I’m testing it) and master?
  29. laanwj commented at 9:17 am on December 28, 2018: member

    Unfortunately, evhttp_bind_socket_with_handle returns a 0/Success errno when listen fails.

    Strange, this worked for me, and it seems to work on Travis. As stated I’ve tested this (at least on Linux) in various ways.

    An alternative solution here (which I initially wanted to do, but this seemed like a workable suggestion at the time if of course C code didn’t mess up error handling everywhere) is to test which local interfaces are available, and only

    • try to bind to IPv4 if there’s IPv4 localhost
    • try to bind to IPv6 if there’s IPv6 localhost

    This avoids having to check error codes, and could be more robust. I might give this a try. Though I’m not sure there’s a good platform-independent way of doing this.

    I do not think this is worth adding a forest of hacks cloning parts of code from libevent for. Better to leave it as-is then, it wasn’t that bad.

  30. laanwj commented at 9:35 am on December 28, 2018: member

    WAIT: it’s the bind in evhttp_bind_socket_with_handle failing that we’re looking for here, not the listen. This seems to not touch the error code, so it can be retrieved succesfully:

    0	if ((fd = bind_socket(address, port, 1 /*reuse*/)) == -1)
    1		return (NULL);
    

    I don’t think we care about specific errno when listen fails, do we? This is always fatal.

  31. luke-jr commented at 11:24 am on December 28, 2018: member
    With this backported to 0.17, Travis was indeed failing on getaddrinfo; no idea why it’s passing on master. :/
  32. laanwj commented at 11:37 am on December 28, 2018: member

    Yes, tried in #15050, really strange. I think specific error code checking is a dead end, just too fragile.

    Looking for a platform independent way now to check if there is a local IPv4 or local IPv6 interface without actually binding to it. libevent internally checks for a nonlocal interface in evutil_found_ifaddr but this is not what we want to know in this case. Too bad the logic in evutil_check_ifaddrs is not exposed externally.

    Looks like we have vaguely analogous logic in void Discover() in net.cpp, though for windows it falls back to ’look up localhost'.

  33. laanwj commented at 11:43 am on December 28, 2018: member
    Closing this for now.
  34. laanwj closed this on Dec 28, 2018

  35. fanquake removed the label Needs release note on Mar 20, 2019
  36. vasild commented at 10:51 am on September 15, 2021: member

    Just reading the libevent2 code, I did not try this, but the following may be a way to pass IPV6_V6ONLY to libevent2:

    Instead of evhttp_bind_socket_with_handle() (called from src/httpserver.cpp) call evconnlistener_new_bind(flags|=LEV_OPT_BIND_IPV6ONLY), it returns evconnlistener. Pass that to evhttp_bind_listener().

  37. laanwj commented at 11:08 am on September 15, 2021: member

    Just reading the libevent2 code, I did not try this, but the following may be a way to pass IPV6_V6ONLY to libevent2:

    To provide context: being able to set IPV6_V6ONLY on the evhttp listening socket to a fixed value (either on or off will do) independent of operating system will allow to do consistent error checking on the binding to localhost, as the the uncertainty in behavior is gone.

  38. DrahtBot locked this on Oct 30, 2022

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: 2024-10-18 10:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me