From 8db35de529055394d9ff8bf4753328b38244dd2c Mon Sep 17 00:00:00 2001 From: Sektor van Skijlen Date: Thu, 2 Feb 2023 13:29:10 +0100 Subject: [PATCH] [core] Refaxed and fixed multiplexer reusage (#2608). Fixed ReuseAddr tests. --- .travis.yml | 2 +- docs/API/API-functions.md | 98 ++++++- docs/API/API-socket-options.md | 15 +- srtcore/api.cpp | 270 +++++++++++++++---- srtcore/api.h | 2 + srtcore/channel.cpp | 5 + srtcore/channel.h | 15 ++ srtcore/socketconfig.h | 14 +- test/test_reuseaddr.cpp | 461 ++++++++++++++++++++++++++++----- 9 files changed, 740 insertions(+), 142 deletions(-) diff --git a/.travis.yml b/.travis.yml index c3e8e586d..eaca3a571 100644 --- a/.travis.yml +++ b/.travis.yml @@ -71,7 +71,7 @@ matrix: - BUILD_TYPE=Release - BUILD_OPTS='-DENABLE_MONOTONIC_CLOCK=ON' script: - - TESTS_IPv6="TestMuxer.IPv4_and_IPv6:TestIPv6.v6_calls_v6*:ReuseAddr.ProtocolVersion" ; # Tests to skip due to lack of IPv6 support + - TESTS_IPv6="TestMuxer.IPv4_and_IPv6:TestIPv6.v6_calls_v6*:ReuseAddr.ProtocolVersion:ReuseAddr.*6" ; # Tests to skip due to lack of IPv6 support - if [ "$TRAVIS_COMPILER" == "x86_64-w64-mingw32-g++" ]; then export CC="x86_64-w64-mingw32-gcc"; export CXX="x86_64-w64-mingw32-g++"; diff --git a/docs/API/API-functions.md b/docs/API/API-functions.md index 11c9d4ca1..3eb442898 100644 --- a/docs/API/API-functions.md +++ b/docs/API/API-functions.md @@ -358,27 +358,96 @@ int srt_bind(SRTSOCKET u, const struct sockaddr* name, int namelen); Binds a socket to a local address and port. Binding specifies the local network interface and the UDP port number to be used for the socket. When the local -address is a form of `INADDR_ANY`, then it's bound to all interfaces. When the -port number is 0, then the port number will be system-allocated if necessary. +address is a wildcard (`INADDR_ANY` for IPv4 or `in6addr_any` for IPv6), then +it's bound to all interfaces (although see `SRTO_IPV6ONLY` and additional +information below for details about the wildcard address in IPv6). + +Binding is necessary for every socket to be used for communication. If the socket +is to be used to initiate a connection to a listener socket, which can be done, +for example, by the [`srt_connect`](#srt_connect) function, the socket is bound +implicitly to the wildcard address according to the IP family (`INADDR_ANY` for +`AF_INET` or `in6addr_any` for `AF_INET6`) and port number 0. In all other cases, +a socket must be bound explicitly by using the functionality of this function first. + +When the port number parameter is 0, then the effective port number will be +system-allocated. To obtain this effective port number you can use +[`srt_getsockname`](#srt_getsockname). This call is obligatory for a listening socket before calling [`srt_listen`](#srt_listen) and for rendezvous mode before calling [`srt_connect`](#srt_connect); otherwise it's optional. For a listening socket it defines the network interface and the port where -the listener should expect a call request. In the case of rendezvous mode (when the -socket has set [`SRTO_RENDEZVOUS`](API-socket-options.md#SRTO_RENDEZVOUS) to -true both parties connect to one another) it defines the network interface and port -from which packets will be sent to the peer, and the port to which the peer is -expected to send packets. - -For a connecting socket this call can set up the outgoing port to be used in the -communication. It is allowed that multiple SRT sockets share one local outgoing -port, as long as [`SRTO_REUSEADDR`](API-socket-options.md#SRTO_REUSEADDRS) -is set to *true* (default). Without this call the port will be automatically -selected by the system. +the listener should expect a call request. + +In the case of rendezvous mode there are two parties that connect to one another. +For every party there must be chosen a local binding endpoint (local address and port) +to which they expect connection from the peer. Let's say, we have a Party 1 +that selects an endpoint A and a Party 2 that selects an endpoint B. In this case the Party 1 +binds the socket to the endpoint A and then connects to the endpoint B, and the Party 2 +the other way around. Both sockets must be set +[`SRTO_RENDEZVOUS`](API-socket-options.md#SRTO_RENDEZVOUS) to *true* to make +this connection possible. + +For a connecting socket the call to `srt_bind` is optional, but can be used to set up the +outgoing port for communication as well as the local interface through which +it should reach out to the remote endpoint, should that be necessary. + +Whether binding is possible depends on some runtime conditions, in particular: + +* No socket in the system has been bound to this port ("free binding"), or + +* A socket bound to this port is bound to a certain address, and this binding is + using a different non-wildcard address ("side binding"), or + +* A socket bound to this port is bound to a wildcard address for a different IP + version than the version requested for this binding ("side wildcard binding", + see also `SRTO_IPV6ONLY` socket option). + +It is also possible to bind to the already busy port as long as the existing +binding ("shared binding") is possessed by an SRT socket created in the same +application, and: + +* Its binding address and UDP-related socket options match the socket to be bound. +* Its [`SRTO_REUSEADDR`](API-socket-options.md#SRTO_REUSEADDRS) is set to *true* (default). + +If none of the free, side and shared binding options is currently possible, this function +will fail. If the socket blocking the requested endpoint is an SRT +socket in the current application, it will report the `SRT_EBINDCONFLICT` error, +while if it was another socket in the system, or the problem was in the system +in general, it will report `SRT_ESOCKFAIL`. Here is the table that shows possible situations: + +| Requested binding | vs. Existing bindings... | | | | | +|---------------------|------------------------------|-----------|-----------------------------|---------------|---------------| +| | A.B.C.D | 0.0.0.0 | ::X | :: / V6ONLY=1 | :: / V6ONLY=0 | +| 1.2.3.4 | 1.2.3.4 shareable, else free | blocked | free | free | blocked | +| 0.0.0.0 | blocked | shareable | free | free | blocked | +| 8080::1 | free | free | 8080::1 sharable, else free | blocked | blocked | +| :: / V6ONLY=1 | free | free | blocked | sharable | blocked | +| :: / V6ONLY=0 | blocked | blocked | blocked | blocked | sharable | +Where: +* free: This binding can coexist with the requested binding. +* blocked: This binding conflicts with the requested binding. +* shareable: This binding can be shared with the requested binding if it's compatible. +* (ADDRESS) shareable, else free: this binding is shareable if the requested binding address is +equal to the given one. Otherwise it's free. +If the binding is shareable, then the operation will succeed if the socket that currently +occupies the binding has the `SRTO_REUSEADDR` option set to true (default) and all UDP +settings are the same as in the current socket. Otherwise it will fail. Shared binding means +sharing the underlying UDP socket and communication queues between SRT sockets. If +all existing bindings on the same port are "free" then the requested binding will +allocate a distinct UDP socket for this SRT socket ("side binding"). + **NOTE**: This function cannot be called on a socket group. If you need to have the group-member socket bound to the specified source address before -connecting, use [`srt_connect_bind`](#srt_connect_bind) for that purpose. +connecting, use [`srt_connect_bind`](#srt_connect_bind) for that purpose +or set the appropriate source address using +[`srt_prepare_endpoint`](#srt_prepare_endpoint). + +**IMPORTANT information about IPv6**: If you are going to bind to the +`in6addr_any` IPv6 wildcard address (known as `::`), the `SRTO_IPV6ONLY` +option must be first set explicitly to 0 or 1, otherwise the binding +will fail. In all other cases this option is meaningless. See `SRTO_IPV6ONLY` +option for more information. | Returns | | |:----------------------------- |:--------------------------------------------------------- | @@ -389,6 +458,7 @@ connecting, use [`srt_connect_bind`](#srt_connect_bind) for that purpose. |:---------------------------------------- |:-------------------------------------------------------------------- | | [`SRT_EINVSOCK`](#srt_einvsock) | Socket passed as [`u`](#u) designates no valid socket | | [`SRT_EINVOP`](#srt_einvop) | Socket already bound | +| [`SRT_EINVPARAM`](#srt_einvparam) | Invalid `name`/`namelen` or invalid `SRTO_IPV6ONLY` flag in `u` | | [`SRT_ECONNSETUP`](#srt_econnsetup) | Internal creation of a UDP socket failed | | [`SRT_ESOCKFAIL`](#srt_esockfail) | Internal configuration of a UDP socket (`bind`, `setsockopt`) failed | | [`SRT_EBINDCONFLICT`](#srt_ebindconflict)| Binding specification conflicts with existing one | diff --git a/docs/API/API-socket-options.md b/docs/API/API-socket-options.md index f587fba67..5188b0639 100644 --- a/docs/API/API-socket-options.md +++ b/docs/API/API-socket-options.md @@ -645,10 +645,17 @@ and the actual value for connected sockets. | ---------------- | ----- | -------- | ---------- | ------ | -------- | ------ | --- | ------ | | `SRTO_IPV6ONLY` | 1.4.0 | pre-bind | `int32_t` | | (system) | -1..1 | RW | GSD | -Set system socket flag `IPV6_V6ONLY`. When set to 0 a listening socket binding an -IPv6 address accepts also IPv4 clients (their addresses will be formatted as -IPv4-mapped IPv6 addresses). By default (-1) this option is not set and the -platform default value is used. +Set system socket option level `IPPROTO_IPV6` named `IPV6_V6ONLY`. This is meaningful +only when the socket is going to be bound to the IPv6 wildcard address `in6addr_any` +(known also as `::`). In this case this option must be also set explicitly to 0 or 1 +before binding, otherwise binding will fail (this is because it is not possible to +determine the default value of this above-mentioned system option in any portable or +reliable way). Possible values are: + +* -1: (default) use system-default value (can be used when not binding to IPv6 wildcard `::`) +* 0: The binding to `in6addr_any` will bind to both IPv6 and IPv4 wildcard address +* 1: The binding to `in6addr_any` will bind only to IPv6 and not IPv4 wildcard address + [Return to list](#list-of-options) diff --git a/srtcore/api.cpp b/srtcore/api.cpp index 97236fdbc..f052c9606 100644 --- a/srtcore/api.cpp +++ b/srtcore/api.cpp @@ -619,7 +619,14 @@ int srt::CUDTUnited::newConnection(const SRTSOCKET listen, // bind to the same addr of listening socket ns->core().open(); - updateListenerMux(ns, ls); + if (!updateListenerMux(ns, ls)) + { + // This is highly unlikely if not impossible, but there's + // a theoretical runtime chance of failure so it should be + // handled + ns->core().m_RejectReason = SRT_REJ_IPE; + throw false; // let it jump directly into the omni exception handler + } ns->core().acceptAndRespond(ls->m_SelfAddr, peer, hspkt, (w_hs)); } @@ -912,6 +919,15 @@ int srt::CUDTUnited::bind(CUDTSocket* s, const sockaddr_any& name) if (s->m_Status != SRTS_INIT) throw CUDTException(MJ_NOTSUP, MN_NONE, 0); + if (s->core().m_config.iIpV6Only == -1 && name.family() == AF_INET6 && name.isany()) + { + // V6ONLY option must be set explicitly if you want to bind to a wildcard address in IPv6 + HLOGP(smlog.Error, + "bind: when binding to :: (IPv6 wildcard), SRTO_IPV6ONLY option must be set explicitly to 0 or 1"); + + throw CUDTException(MJ_NOTSUP, MN_INVAL, 0); + } + s->core().open(); updateMux(s, name); s->m_Status = SRTS_OPENED; @@ -2823,12 +2839,41 @@ uint16_t srt::CUDTUnited::installMuxer(CUDTSocket* w_s, CMultiplexer& fw_sm) return sa.hport(); } +bool srt::CUDTUnited::inet6SettingsCompat(const sockaddr_any& muxaddr, const CSrtMuxerConfig& cfgMuxer, + const sockaddr_any& reqaddr, const CSrtMuxerConfig& cfgSocket) +{ + if (muxaddr.family() != AF_INET6) + return true; // Don't check - the family has been checked already + + if (reqaddr.isany()) + { + if (cfgSocket.iIpV6Only == -1) // Treat as "adaptive" + return true; + + // If set explicitly, then it must be equal to the one of found muxer. + return cfgSocket.iIpV6Only == cfgMuxer.iIpV6Only; + } + + // If binding to the certain IPv6 address, then this setting doesn't matter. + return true; +} + bool srt::CUDTUnited::channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, const CSrtConfig& cfgSocket) { - return cfgMuxer.bReuseAddr && cfgMuxer == cfgSocket; + if (!cfgMuxer.bReuseAddr) + { + HLOGP(smlog.Debug, "channelSettingsMatch: fail: the multiplexer is not reusable"); + return false; + } + + if (cfgMuxer.isCompatWith(cfgSocket)) + return true; + + HLOGP(smlog.Debug, "channelSettingsMatch: fail: some options have different values"); + return false; } -void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& addr, const UDPSOCKET* udpsock /*[[nullable]]*/) +void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& reqaddr, const UDPSOCKET* udpsock /*[[nullable]]*/) { ScopedLock cg(m_GlobControlLock); @@ -2841,9 +2886,23 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& addr, const U { // If not, we need to see if there exist already a multiplexer bound // to the same endpoint. - const int port = addr.hport(); + const int port = reqaddr.hport(); const CSrtConfig& cfgSocket = s->core().m_config; + // This loop is going to check the attempted binding of + // address:port and socket settings against every existing + // multiplexer. Possible results of the check are: + + // 1. MATCH: identical address - reuse it and quit. + // 2. CONFLICT: report error: the binding partially overlaps + // so it neither can be reused nor is free to bind. + // 3. PASS: different and not overlapping - continue searching. + + // In this function the convention is: + // MATCH: do nothing and proceed with binding reusage, THEN break. + // CONFLICT: throw an exception. + // PASS: use 'continue' to pass to the next element. + bool reuse_attempt = false; for (map::iterator i = m_mMultiplexer.begin(); i != m_mMultiplexer.end(); ++i) { @@ -2859,74 +2918,166 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& addr, const U } // If this is bound to the wildcard address, it can be reused if: - // - addr is also a wildcard + // - reqaddr is also a wildcard // - channel settings match // Otherwise it's a conflict. - sockaddr_any sa; - m.m_pChannel->getSockAddr((sa)); + sockaddr_any mux_addr; + m.m_pChannel->getSockAddr((mux_addr)); HLOGC(smlog.Debug, - log << "bind: Found existing muxer @" << m.m_iID << " : " << sa.str() << " - check against " - << addr.str()); + log << "bind: Found existing muxer @" << m.m_iID << " : " << mux_addr.str() << " - check against " + << reqaddr.str()); - if (sa.isany()) + if (mux_addr.isany()) { - if (!addr.isany()) + if (mux_addr.family() == AF_INET6) { - LOGC(smlog.Error, - log << "bind: Address: " << addr.str() - << " conflicts with existing wildcard binding: " << sa.str()); - throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + // With IPv6 we need to research two possibilities: + // iIpV6Only == 1 -> This means that it binds only :: wildcard, but not 0.0.0.0 + // iIpV6Only == 0 -> This means that it binds both :: and 0.0.0.0. + // iIpV6Only == -1 -> Hard to say what to do, but treat it as a potential conflict in any doubtful case. + + if (m.m_mcfg.iIpV6Only == 1) + { + // PASS IF: candidate is IPv4, no matter the address + // MATCH IF: candidate is IPv6 with only=1 + // CONFLICT IF: candidate is IPv6 with only != 1 or IPv6 non-wildcard. + + if (reqaddr.family() == AF_INET) + { + HLOGC(smlog.Debug, log << "bind: muxer @" << m.m_iID + << " is :: v6only - requested IPv4 ANY is NOT IN THE WAY. Searching on."); + continue; + } + + // Candidate is AF_INET6 + + if (cfgSocket.iIpV6Only != 1 || !reqaddr.isany()) + { + // CONFLICT: + // 1. attempting to make a wildcard IPv4 + IPv6 + // while the multiplexer for wildcard IPv6 exists. + // 2. If binding to a given address, it conflicts with the wildcard + LOGC(smlog.Error, + log << "bind: Address: " << reqaddr.str() + << " conflicts with existing IPv6 wildcard binding: " << mux_addr.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + + // Otherwise, MATCH. + } + else if (m.m_mcfg.iIpV6Only == 0) + { + // Muxer's address is a wildcard for :: and 0.0.0.0 at once. + // This way only IPv6 wildcard with v6only=0 is a perfect match and everything + // else is a conflict. + + if (reqaddr.family() == AF_INET6 && reqaddr.isany() && cfgSocket.iIpV6Only == 0) + { + // MATCH + } + else + { + // CONFLICT: attempting to make a wildcard IPv4 + IPv6 while + // the multiplexer for wildcard IPv6 exists. + LOGC(smlog.Error, + log << "bind: Address: " << reqaddr.str() << " v6only=" << cfgSocket.iIpV6Only + << " conflicts with existing IPv6 + IPv4 wildcard binding: " << mux_addr.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + } + else // Case -1, by unknown reason. Accept only with -1 setting, others are conflict. + { + if (reqaddr.family() == AF_INET6 && reqaddr.isany() && cfgSocket.iIpV6Only == -1) + { + // MATCH + } + else + { + LOGC(smlog.Error, + log << "bind: Address: " << reqaddr.str() << " v6only=" << cfgSocket.iIpV6Only + << " conflicts with existing IPv6 v6only=unknown wildcard binding: " << mux_addr.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + } } - - // Still, for ANY you need either the same family, or open - // for families. - if (m.m_mcfg.iIpV6Only != -1 && m.m_mcfg.iIpV6Only != cfgSocket.iIpV6Only) + else // muxer is IPv4 wildcard { - LOGC(smlog.Error, - log << "bind: Address: " << addr.str() - << " conflicts with existing IPv6 wildcard binding: " << sa.str()); - throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + // Then only IPv4 wildcard is a match and: + // - IPv6 with only=true is PASS (not a conflict) + // - IPv6 with only=false is CONFLICT + // - IPv6 with only=undefined is CONFLICT + // REASON: we need to make a potential conflict a conflict as there will be + // no bind() call to check if this wouldn't be a conflict in result. If you want + // to have a binding to IPv6 that should avoid conflict with IPv4 wildcard binding, + // then SRTO_IPV6ONLY option must be explicitly set before binding. + // Also: + if (reqaddr.family() == AF_INET) + { + if (reqaddr.isany()) + { + // MATCH + } + else + { + LOGC(smlog.Error, + log << "bind: Address: " << reqaddr.str() + << " conflicts with existing IPv4 wildcard binding: " << mux_addr.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + } + else // AF_INET6 + { + if (cfgSocket.iIpV6Only == 1 || !reqaddr.isany()) + { + // PASS + HLOGC(smlog.Debug, log << "bind: muxer @" << m.m_iID + << " is IPv4 wildcard - requested " << reqaddr.str() << " v6only=" << cfgSocket.iIpV6Only + << " is NOT IN THE WAY. Searching on."); + continue; + } + else + { + LOGC(smlog.Error, + log << "bind: Address: " << reqaddr.str() << " v6only=" << cfgSocket.iIpV6Only + << " conflicts with existing IPv4 wildcard binding: " << mux_addr.str()); + throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); + } + } } - if ((m.m_mcfg.iIpV6Only == 0 || cfgSocket.iIpV6Only == 0) && m.m_iIPversion != addr.family()) - { - LOGC(smlog.Error, - log << "bind: Address: " << addr.str() << " conflicts with IPv6 wildcard binding: " << sa.str() - << " : family " << (m.m_iIPversion == AF_INET ? "IPv4" : "IPv6") << " vs. " - << (addr.family() == AF_INET ? "IPv4" : "IPv6")); - throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); - } reuse_attempt = true; HLOGC(smlog.Debug, log << "bind: wildcard address - multiplexer reusable"); } - else if (addr.isany() && addr.family() == sa.family()) + // Muxer address is NOT a wildcard, so conflicts only with WILDCARD of the same type + else if (reqaddr.isany() && reqaddr.family() == mux_addr.family()) { LOGC(smlog.Error, - log << "bind: Wildcard address: " << addr.str() - << " conflicts with existting IP binding: " << sa.str()); + log << "bind: Wildcard address: " << reqaddr.str() + << " conflicts with existting IP binding: " << mux_addr.str()); throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); } // If this is bound to a certain address, AND: - else if (sa.equal_address(addr)) + else if (mux_addr.equal_address(reqaddr)) { - // - the address is the same as addr + // - the address is the same as reqaddr reuse_attempt = true; HLOGC(smlog.Debug, log << "bind: same IP address - multiplexer reusable"); } else { HLOGC(smlog.Debug, log << "bind: IP addresses differ - ALLOWED to create a new multiplexer"); + continue; } // Otherwise: - // - the address is different than addr + // - the address is different than reqaddr // - the address can't be reused, but this can go on with new one. // If this is a reusage attempt: if (reuse_attempt) { // - if the channel settings match, it can be reused - if (channelSettingsMatch(m.m_mcfg, cfgSocket)) + if (channelSettingsMatch(m.m_mcfg, cfgSocket) && inet6SettingsCompat(mux_addr, m.m_mcfg, reqaddr, cfgSocket)) { HLOGC(smlog.Debug, log << "bind: reusing multiplexer for port " << port); // reuse the existing multiplexer @@ -2938,7 +3089,7 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& addr, const U { // - if not, it's a conflict LOGC(smlog.Error, - log << "bind: Address: " << addr.str() << " conflicts with binding: " << sa.str() + log << "bind: Address: " << reqaddr.str() << " conflicts with binding: " << mux_addr.str() << " due to channel settings"); throw CUDTException(MJ_NOTSUP, MN_BUSYPORT, 0); } @@ -2948,12 +3099,14 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& addr, const U // Note that a binding to a different IP address is not treated // as a candidate for either reusage or conflict. + LOGC(smlog.Fatal, log << "SHOULD NOT GET HERE!!!"); + SRT_ASSERT(false); } } // a new multiplexer is needed CMultiplexer m; - configureMuxer((m), s, addr.family()); + configureMuxer((m), s, reqaddr.family()); try { @@ -2962,25 +3115,39 @@ void srt::CUDTUnited::updateMux(CUDTSocket* s, const sockaddr_any& addr, const U if (udpsock) { - // In this case, addr contains the address + // In this case, reqaddr contains the address // that has been extracted already from the // given socket - m.m_pChannel->attach(*udpsock, addr); + m.m_pChannel->attach(*udpsock, reqaddr); } - else if (addr.empty()) + else if (reqaddr.empty()) { // The case of previously used case of a NULL address. // This here is used to pass family only, in this case // just automatically bind to the "0" address to autoselect // everything. - m.m_pChannel->open(addr.family()); + m.m_pChannel->open(reqaddr.family()); } else { // If at least the IP address is specified, then bind to that // address, but still possibly autoselect the outgoing port, if the // port was specified as 0. - m.m_pChannel->open(addr); + m.m_pChannel->open(reqaddr); + } + + // AFTER OPENING, check the matter of IPV6_V6ONLY option, + // as it decides about the fact that the occupied binding address + // in case of wildcard is both :: and 0.0.0.0, or only ::. + if (reqaddr.family() == AF_INET6 && m.m_mcfg.iIpV6Only == -1) + { + // XXX We don't know how probable it is to get the error here + // and resulting -1 value. As a fallback for that case, the value -1 + // is honored here, just all side-bindings for other sockes will be + // rejected as a potential conflict, even if binding would be accepted + // in these circumstances. Only a perfect match in case of potential + // overlapping will be accepted on the same port. + m.m_mcfg.iIpV6Only = m.m_pChannel->sockopt(IPPROTO_IPV6, IPV6_V6ONLY, -1); } m.m_pTimer = new CTimer; @@ -3029,8 +3196,14 @@ bool srt::CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) CMultiplexer* mux = map_getp(m_mMultiplexer, ls->m_iMuxID); // NOTE: - // THIS BELOW CODE is only for a highly unlikely, and probably buggy, - // situation when the Multiplexer wasn't found by ID recorded in the listener. + // THIS BELOW CODE is only for a highly unlikely situation when the listener + // socket has been closed in the meantime when the accepted socket is being + // processed. This procedure is different than updateMux because this time we + // only want to have a multiplexer socket to be assigned to the accepted socket. + // It is also unlikely that the listener socket is garbage-collected so fast, so + // this procedure will most likely find the multiplexer of the zombie listener socket, + // which no longer accepts new connections (the listener is withdrawn immediately from + // the port) that wasn't yet completely deleted. CMultiplexer* fallback = NULL; if (!mux) { @@ -3057,8 +3230,9 @@ bool srt::CUDTUnited::updateListenerMux(CUDTSocket* s, const CUDTSocket* ls) mux = &m; // best match break; } - else + else if (m.m_iIPversion == AF_INET6) { + // Allowed fallback case when we only need an accepted socket. fallback = &m; } } diff --git a/srtcore/api.h b/srtcore/api.h index 551590e3e..9ba77d23a 100644 --- a/srtcore/api.h +++ b/srtcore/api.h @@ -456,6 +456,8 @@ class CUDTUnited /// @param cfgSocket socket configuration. /// @return tru if configurations match, false otherwise. static bool channelSettingsMatch(const CSrtMuxerConfig& cfgMuxer, const CSrtConfig& cfgSocket); + static bool inet6SettingsCompat(const sockaddr_any& muxaddr, const CSrtMuxerConfig& cfgMuxer, + const sockaddr_any& reqaddr, const CSrtMuxerConfig& cfgSocket); private: std::map m_mMultiplexer; // UDP multiplexer diff --git a/srtcore/channel.cpp b/srtcore/channel.cpp index 700759d0b..5b0e05e94 100644 --- a/srtcore/channel.cpp +++ b/srtcore/channel.cpp @@ -548,6 +548,11 @@ void srt::CChannel::setConfig(const CSrtMuxerConfig& config) m_mcfg = config; } +void srt::CChannel::getSocketOption(int level, int option, char* pw_dataptr, socklen_t& w_len, int& w_status) +{ + w_status = ::getsockopt(m_iSocket, level, option, (pw_dataptr), (&w_len)); +} + int srt::CChannel::getIpTTL() const { if (m_iSocket == INVALID_SOCKET) diff --git a/srtcore/channel.h b/srtcore/channel.h index bcc92431b..1bfcc47c8 100644 --- a/srtcore/channel.h +++ b/srtcore/channel.h @@ -129,6 +129,21 @@ class CChannel void setConfig(const CSrtMuxerConfig& config); + void getSocketOption(int level, int sockoptname, char* pw_dataptr, socklen_t& w_len, int& w_status); + + template + Type sockopt(int level, int sockoptname, Type deflt) + { + Type retval; + socklen_t socklen = sizeof retval; + int status; + getSocketOption(level, sockoptname, ((char*)&retval), (socklen), (status)); + if (status == -1) + return deflt; + + return retval; + } + /// Get the IP TTL. /// @param [in] ttl IP Time To Live. /// @return TTL. diff --git a/srtcore/socketconfig.h b/srtcore/socketconfig.h index a887c3ffd..488d12fb1 100644 --- a/srtcore/socketconfig.h +++ b/srtcore/socketconfig.h @@ -91,19 +91,27 @@ struct CSrtMuxerConfig int iUDPSndBufSize; // UDP sending buffer size int iUDPRcvBufSize; // UDP receiving buffer size - bool operator==(const CSrtMuxerConfig& other) const + // NOTE: this operator is not reversable. The syntax must use: + // muxer_entry == socket_entry + bool isCompatWith(const CSrtMuxerConfig& other) const { #define CEQUAL(field) (field == other.field) return CEQUAL(iIpTTL) && CEQUAL(iIpToS) - && CEQUAL(iIpV6Only) && CEQUAL(bReuseAddr) #ifdef SRT_ENABLE_BINDTODEVICE && CEQUAL(sBindToDevice) #endif && CEQUAL(iUDPSndBufSize) - && CEQUAL(iUDPRcvBufSize); + && CEQUAL(iUDPRcvBufSize) + && (other.iIpV6Only == -1 || CEQUAL(iIpV6Only)) + // NOTE: iIpV6Only is not regarded because + // this matches only in case of IPv6 with "any" address. + // And this aspect must be checked separately because here + // this procedure has no access to neither the address, + // nor the IP version (family). #undef CEQUAL + && true; } CSrtMuxerConfig() diff --git a/test/test_reuseaddr.cpp b/test/test_reuseaddr.cpp index 4f9dfe726..67cf33719 100644 --- a/test/test_reuseaddr.cpp +++ b/test/test_reuseaddr.cpp @@ -1,5 +1,6 @@ #include "gtest/gtest.h" #include +#include #ifndef _WIN32 #include #endif @@ -10,6 +11,18 @@ using srt::sockaddr_any; +template +struct AtReturnJoin +{ + Future& which_future; + + ~AtReturnJoin() + { + if (which_future.valid()) + which_future.wait(); + } +}; + // Copied from ../apps/apputil.cpp, can't really link this file here. sockaddr_any CreateAddr(const std::string& name, unsigned short port, int pref_family = AF_INET) { @@ -77,7 +90,7 @@ sockaddr_any CreateAddr(const std::string& name, unsigned short port, int pref_f // iphlp library to be attached to the executable, which is kinda // problematic. Temporarily block tests using this function on Windows. -std::string GetLocalIP() +std::string GetLocalIP(int af = AF_UNSPEC) { std::cout << "!!!WARNING!!!: GetLocalIP not supported, test FORCEFULLY passed\n"; return ""; @@ -97,7 +110,7 @@ struct IfAddr } }; -std::string GetLocalIP() +std::string GetLocalIP(int af = AF_UNSPEC) { struct ifaddrs * ifa=NULL; void * tmpAddrPtr=NULL; @@ -113,6 +126,10 @@ std::string GetLocalIP() if (ifa->ifa_addr->sa_family == AF_INET) { + // Ignore IPv4 address if not wanted. + if (af == AF_INET6) + continue; + // is a valid IP4 Address sockaddr_in* psin = (struct sockaddr_in *)ifa->ifa_addr; tmpAddrPtr=&psin->sin_addr; @@ -124,7 +141,14 @@ std::string GetLocalIP() inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN); return addressBuffer; printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer); - } else if (ifa->ifa_addr->sa_family == AF_INET6) { // check it is IP6 + } + else if (ifa->ifa_addr->sa_family == AF_INET6) + { // check it is IP6 + + // Ignore IPv6 address if not wanted. + if (af == AF_INET) + continue; + // is a valid IP6 Address tmpAddrPtr=&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; char addressBuffer[INET6_ADDRSTRLEN] = ""; @@ -138,7 +162,7 @@ std::string GetLocalIP() #endif int client_pollid = SRT_ERROR; -SRTSOCKET m_client_sock = SRT_ERROR; +SRTSOCKET g_client_sock = SRT_ERROR; void clientSocket(std::string ip, int port, bool expect_success) { @@ -146,28 +170,32 @@ void clientSocket(std::string ip, int port, bool expect_success) int no = 0; int family = AF_INET; + std::string famname = "IPv4"; if (ip.substr(0, 2) == "6.") { family = AF_INET6; ip = ip.substr(2); + famname = "IPv6"; } - m_client_sock = srt_create_socket(); - ASSERT_NE(m_client_sock, SRT_ERROR); + std::cout << "[T/C] Creating client socket\n"; + + g_client_sock = srt_create_socket(); + ASSERT_NE(g_client_sock, SRT_ERROR); - ASSERT_NE(srt_setsockopt(m_client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect - ASSERT_NE(srt_setsockflag(m_client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); + ASSERT_NE(srt_setsockopt(g_client_sock, 0, SRTO_SNDSYN, &no, sizeof no), SRT_ERROR); // for async connect + ASSERT_NE(srt_setsockflag(g_client_sock, SRTO_SENDER, &yes, sizeof yes), SRT_ERROR); - ASSERT_NE(srt_setsockopt(m_client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); + ASSERT_NE(srt_setsockopt(g_client_sock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); int epoll_out = SRT_EPOLL_OUT; - srt_epoll_add_usock(client_pollid, m_client_sock, &epoll_out); + srt_epoll_add_usock(client_pollid, g_client_sock, &epoll_out); sockaddr_any sa = CreateAddr(ip, port, family); - std::cout << "Connecting to: " << sa.str() << std::endl; + std::cout << "[T/C] Connecting to: " << sa.str() << " (" << famname << ")" << std::endl; - int connect_res = srt_connect(m_client_sock, sa.get(), sa.size()); + int connect_res = srt_connect(g_client_sock, sa.get(), sa.size()); if (connect_res == -1) { @@ -177,8 +205,12 @@ void clientSocket(std::string ip, int port, bool expect_success) if (expect_success) { EXPECT_NE(connect_res, -1); + if (connect_res == -1) + return; + // Socket readiness for connection is checked by polling on WRITE allowed sockets. + if (connect_res != -1) { int rlen = 2; SRTSOCKET read[2]; @@ -186,6 +218,8 @@ void clientSocket(std::string ip, int port, bool expect_success) int wlen = 2; SRTSOCKET write[2]; + std::cout << "[T/C] Waiting for connection readiness...\n"; + EXPECT_NE(srt_epoll_wait(client_pollid, read, &rlen, write, &wlen, -1, // -1 is set for debuging purpose. @@ -195,62 +229,118 @@ void clientSocket(std::string ip, int port, bool expect_success) EXPECT_EQ(rlen, 0); // get exactly one write event without reads EXPECT_EQ(wlen, 1); // get exactly one write event without reads - EXPECT_EQ(write[0], m_client_sock); // for our client socket + EXPECT_EQ(write[0], g_client_sock); // for our client socket + + char buffer[1316] = {1, 2, 3, 4}; + EXPECT_NE(srt_sendmsg(g_client_sock, buffer, sizeof buffer, + -1, // infinit ttl + true // in order must be set to true + ), + SRT_ERROR); + } + else + { + std::cout << "[T/C] (NOT TESTING TRANSMISSION - CONNECTION FAILED ALREADY)\n"; } - - char buffer[1316] = {1, 2, 3, 4}; - EXPECT_NE(srt_sendmsg(m_client_sock, buffer, sizeof buffer, - -1, // infinit ttl - true // in order must be set to true - ), - SRT_ERROR); } else { EXPECT_EQ(connect_res, -1); } - std::cout << "Client exit\n"; + std::cout << "[T/C] Client exit\n"; } int server_pollid = SRT_ERROR; -void serverSocket(std::string ip, int port, bool expect_success) +SRTSOCKET prepareSocket() { + SRTSOCKET bindsock = srt_create_socket(); + EXPECT_NE(bindsock, SRT_ERROR); + int yes = 1; int no = 0; - SRTSOCKET m_bindsock = srt_create_socket(); - ASSERT_NE(m_bindsock, SRT_ERROR); - - ASSERT_NE(srt_setsockopt(m_bindsock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect - ASSERT_NE(srt_setsockopt(m_bindsock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); + EXPECT_NE(srt_setsockopt(bindsock, 0, SRTO_RCVSYN, &no, sizeof no), SRT_ERROR); // for async connect + EXPECT_NE(srt_setsockopt(bindsock, 0, SRTO_TSBPDMODE, &yes, sizeof yes), SRT_ERROR); int epoll_in = SRT_EPOLL_IN; - srt_epoll_add_usock(server_pollid, m_bindsock, &epoll_in); + std::cout << "[T/S] Listener/binder sock @" << bindsock << " added to server_pollid\n"; + srt_epoll_add_usock(server_pollid, bindsock, &epoll_in); + + return bindsock; +} + +bool bindSocket(SRTSOCKET bindsock, std::string ip, int port, bool expect_success) +{ sockaddr_any sa = CreateAddr(ip, port); - std::cout << "Bind to: " << sa.str() << std::endl; + std::string fam = (sa.family() == AF_INET) ? "IPv4" : "IPv6"; + + std::cout << "[T/S] Bind @" << bindsock << " to: " << sa.str() << " (" << fam << ")" << std::endl; + + int bind_res = srt_bind(bindsock, sa.get(), sa.size()); + + std::cout << "[T/S] ... result " << bind_res << " (expected to " + << (expect_success ? "succeed" : "fail") << ")\n"; - int bind_res = srt_bind(m_bindsock, sa.get(), sa.size()); if (!expect_success) { - std::cout << "Binding should fail: " << srt_getlasterror_str() << std::endl; - ASSERT_EQ(bind_res, SRT_ERROR); - return; + std::cout << "[T/S] Binding should fail: " << srt_getlasterror_str() << std::endl; + EXPECT_EQ(bind_res, SRT_ERROR); + return false; + } + + EXPECT_NE(bind_res, SRT_ERROR); + return true; +} + +bool bindListener(SRTSOCKET bindsock, std::string ip, int port, bool expect_success) +{ + if (!bindSocket(bindsock, ip, port, expect_success)) + return false; + + EXPECT_NE(srt_listen(bindsock, SOMAXCONN), SRT_ERROR); + + return true; +} + +SRTSOCKET createListener(std::string ip, int port, bool expect_success) +{ + std::cout << "[T/S] serverSocket: creating listener socket\n"; + + SRTSOCKET bindsock = prepareSocket(); + + if (!bindListener(bindsock, ip, port, expect_success)) + return SRT_INVALID_SOCK; + + return bindsock; +} + +SRTSOCKET createBinder(std::string ip, int port, bool expect_success) +{ + std::cout << "[T/S] serverSocket: creating binder socket\n"; + + SRTSOCKET bindsock = prepareSocket(); + + if (!bindSocket(bindsock, ip, port, expect_success)) + { + srt_close(bindsock); + return SRT_INVALID_SOCK; } - ASSERT_NE(bind_res, SRT_ERROR); + return bindsock; +} - ASSERT_NE(srt_listen(m_bindsock, SOMAXCONN), SRT_ERROR); +void testAccept(SRTSOCKET bindsock, std::string ip, int port, bool expect_success) +{ - if (ip == "0.0.0.0") - ip = "127.0.0.1"; // override wildcard - else if ( ip == "::") - ip = "::1"; + auto run = [ip, port, expect_success]() { clientSocket(ip, port, expect_success); }; - std::thread client(clientSocket, ip, port, expect_success); + auto launched = std::async(std::launch::async, run); + + AtReturnJoin atreturn_join {launched}; { // wait for connection from client int rlen = 2; @@ -259,32 +349,35 @@ void serverSocket(std::string ip, int port, bool expect_success) int wlen = 2; SRTSOCKET write[2]; + std::cout << "[T/S] Wait 10s for acceptance on @" << bindsock << " ...\n"; + ASSERT_NE(srt_epoll_wait(server_pollid, read, &rlen, write, &wlen, - 3000, // -1 is set for debuging purpose. + 10000, // -1 is set for debuging purpose. // in case of production we need to set appropriate value 0, 0, 0, 0), SRT_ERROR ); ASSERT_EQ(rlen, 1); // get exactly one read event without writes ASSERT_EQ(wlen, 0); // get exactly one read event without writes - ASSERT_EQ(read[0], m_bindsock); // read event is for bind socket + ASSERT_EQ(read[0], bindsock); // read event is for bind socket } sockaddr_any scl; - SRTSOCKET m_sock = srt_accept(m_bindsock, scl.get(), &scl.len); - if (m_sock == -1) + SRTSOCKET accepted_sock = srt_accept(bindsock, scl.get(), &scl.len); + if (accepted_sock == -1) { std::cout << "srt_accept: " << srt_getlasterror_str() << std::endl; } - ASSERT_NE(m_sock, SRT_INVALID_SOCK); + ASSERT_NE(accepted_sock, SRT_INVALID_SOCK); sockaddr_any showacp = (sockaddr*)&scl; - std::cout << "Accepted from: " << showacp.str() << std::endl; + std::cout << "[T/S] Accepted from: " << showacp.str() << std::endl; - srt_epoll_add_usock(server_pollid, m_sock, &epoll_in); // wait for input + int epoll_in = SRT_EPOLL_IN; + srt_epoll_add_usock(server_pollid, accepted_sock, &epoll_in); // wait for input char buffer[1316]; { // wait for 1316 packet from client @@ -294,6 +387,8 @@ void serverSocket(std::string ip, int port, bool expect_success) int wlen = 2; SRTSOCKET write[2]; + std::cout << "[T/S] Wait for data reception...\n"; + ASSERT_NE(srt_epoll_wait(server_pollid, read, &rlen, write, &wlen, @@ -304,22 +399,51 @@ void serverSocket(std::string ip, int port, bool expect_success) ASSERT_EQ(rlen, 1); // get exactly one read event without writes ASSERT_EQ(wlen, 0); // get exactly one read event without writes - ASSERT_EQ(read[0], m_sock); // read event is for bind socket + ASSERT_EQ(read[0], accepted_sock); // read event is for bind socket } char pattern[4] = {1, 2, 3, 4}; - ASSERT_EQ(srt_recvmsg(m_sock, buffer, sizeof buffer), + ASSERT_EQ(srt_recvmsg(accepted_sock, buffer, sizeof buffer), 1316); EXPECT_EQ(memcmp(pattern, buffer, sizeof pattern), 0); - client.join(); - srt_close(m_sock); - srt_close(m_bindsock); - srt_close(m_client_sock); // cannot close m_client_sock after srt_sendmsg because of issue in api.c:2346 + std::cout << "[T/S] closing sockets: ACP:@" << accepted_sock << " LSN:@" << bindsock << " CLR:@" << g_client_sock << " ...\n"; + ASSERT_NE(srt_close(accepted_sock), SRT_ERROR); + ASSERT_NE(srt_close(g_client_sock), SRT_ERROR); // cannot close g_client_sock after srt_sendmsg because of issue in api.c:2346 + + std::cout << "[T/S] joining client async...\n"; + launched.get(); +} + +void shutdownListener(SRTSOCKET bindsock) +{ + // Silently ignore. Usually it should have been checked earlier, + // and an invalid sock might be expected in particular tests. + if (bindsock == SRT_INVALID_SOCK) + return; + + int yes = 1; + EXPECT_NE(srt_setsockopt(bindsock, 0, SRTO_RCVSYN, &yes, sizeof yes), SRT_ERROR); // for async connect + EXPECT_NE(srt_close(bindsock), SRT_ERROR); + + std::chrono::milliseconds check_period (250); + int credit = 400; // 10 seconds + + std::cout << "[T/S] waiting for cleanup of @" << bindsock << " up to 10s" << std::endl; + while (srt_getsockstate(bindsock) != SRTS_NONEXIST) + { + std::this_thread::sleep_for(check_period); + --credit; + if (!credit) + break; + + //std::cerr << "."; + } + //std::cerr << std::endl; - std::cout << "Server exit\n"; + EXPECT_NE(credit, 0); } TEST(ReuseAddr, SameAddr1) @@ -332,11 +456,16 @@ TEST(ReuseAddr, SameAddr1) server_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, server_pollid); - std::thread server_1(serverSocket, "127.0.0.1", 5000, true); - server_1.join(); + SRTSOCKET bindsock_1 = createBinder("127.0.0.1", 5000, true); + SRTSOCKET bindsock_2 = createListener("127.0.0.1", 5000, true); - std::thread server_2(serverSocket, "127.0.0.1", 5000, true); - server_2.join(); + testAccept(bindsock_2, "127.0.0.1", 5000, true); + + std::thread s1(shutdownListener, bindsock_1); + std::thread s2(shutdownListener, bindsock_2); + + s1.join(); + s2.join(); (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); @@ -345,7 +474,7 @@ TEST(ReuseAddr, SameAddr1) TEST(ReuseAddr, SameAddr2) { - std::string localip = GetLocalIP(); + std::string localip = GetLocalIP(AF_INET); if (localip == "") return; // DISABLE TEST if this doesn't work. @@ -357,20 +486,60 @@ TEST(ReuseAddr, SameAddr2) server_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, server_pollid); - std::thread server_1(serverSocket, localip, 5000, true); - server_1.join(); + SRTSOCKET bindsock_1 = createBinder(localip, 5000, true); + SRTSOCKET bindsock_2 = createListener(localip, 5000, true); + + testAccept(bindsock_2, localip, 5000, true); + + shutdownListener(bindsock_1); + + // Test simple close and reuse the multiplexer + ASSERT_NE(srt_close(bindsock_2), SRT_ERROR); + + SRTSOCKET bindsock_3 = createListener(localip, 5000, true); + testAccept(bindsock_3, localip, 5000, true); - std::thread server_2(serverSocket, localip, 5000, true); - server_2.join(); + shutdownListener(bindsock_3); (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); srt_cleanup(); } +TEST(ReuseAddr, SameAddrV6) +{ + ASSERT_EQ(srt_startup(), 0); + + client_pollid = srt_epoll_create(); + ASSERT_NE(SRT_ERROR, client_pollid); + + server_pollid = srt_epoll_create(); + ASSERT_NE(SRT_ERROR, server_pollid); + + SRTSOCKET bindsock_1 = createBinder("::1", 5000, true); + SRTSOCKET bindsock_2 = createListener("::1", 5000, true); + + testAccept(bindsock_2, "::1", 5000, true); + + shutdownListener(bindsock_1); + + // Test simple close and reuse the multiplexer + ASSERT_NE(srt_close(bindsock_2), SRT_ERROR); + + SRTSOCKET bindsock_3 = createListener("::1", 5000, true); + testAccept(bindsock_3, "::1", 5000, true); + + shutdownListener(bindsock_3); + + (void)srt_epoll_release(client_pollid); + (void)srt_epoll_release(server_pollid); + srt_cleanup(); +} + + TEST(ReuseAddr, DiffAddr) { - std::string localip = GetLocalIP(); + std::string localip = GetLocalIP(AF_INET); if (localip == "") return; // DISABLE TEST if this doesn't work. @@ -382,8 +551,16 @@ TEST(ReuseAddr, DiffAddr) server_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, server_pollid); - serverSocket("127.0.0.1", 5000, true); - serverSocket(localip, 5000, true); + SRTSOCKET bindsock_1 = createBinder("127.0.0.1", 5000, true); + SRTSOCKET bindsock_2 = createListener(localip, 5000, true); + + //std::thread server_1(testAccept, bindsock_1, "127.0.0.1", 5000, true); + //server_1.join(); + + testAccept(bindsock_2, localip, 5000, true); + + shutdownListener(bindsock_1); + shutdownListener(bindsock_2); (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); @@ -397,7 +574,10 @@ TEST(ReuseAddr, Wildcard) "Forcing test to pass, PLEASE FIX.\n"; return; #endif - std::string localip = GetLocalIP(); + + // This time exceptionally require IPv4 because we'll be + // checking it against 0.0.0.0 + std::string localip = GetLocalIP(AF_INET); if (localip == "") return; // DISABLE TEST if this doesn't work. @@ -409,16 +589,139 @@ TEST(ReuseAddr, Wildcard) server_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, server_pollid); - serverSocket("0.0.0.0", 5000, true); - serverSocket(localip, 5000, false); + SRTSOCKET bindsock_1 = createListener("0.0.0.0", 5000, true); + + // Binding a certain address when wildcard is already bound should fail. + SRTSOCKET bindsock_2 = createBinder(localip, 5000, false); + + testAccept(bindsock_1, "127.0.0.1", 5000, true); + + shutdownListener(bindsock_1); + shutdownListener(bindsock_2); (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid); srt_cleanup(); } +TEST(ReuseAddr, Wildcard6) +{ +#if defined(_WIN32) || defined(CYGWIN) + std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" + "Forcing test to pass, PLEASE FIX.\n"; + return; +#endif + + // This time exceptionally require IPv6 because we'll be + // checking it against :: + std::string localip = GetLocalIP(AF_INET6); + if (localip == "") + return; // DISABLE TEST if this doesn't work. + + // This "should work", but there can also be platforms + // that do not have IPv4, in which case this test can't be + // performed there. + std::string localip_v4 = GetLocalIP(AF_INET); + + ASSERT_EQ(srt_startup(), 0); + + client_pollid = srt_epoll_create(); + ASSERT_NE(SRT_ERROR, client_pollid); + + server_pollid = srt_epoll_create(); + ASSERT_NE(SRT_ERROR, server_pollid); + + // This must be obligatory set before binding a socket to "::" + int strict_ipv6 = 1; + + SRTSOCKET bindsock_1 = prepareSocket(); + srt_setsockflag(bindsock_1, SRTO_IPV6ONLY, &strict_ipv6, sizeof strict_ipv6); + bindListener(bindsock_1, "::", 5000, true); + + // Binding a certain address when wildcard is already bound should fail. + SRTSOCKET bindsock_2 = createBinder(localip, 5000, false); + + SRTSOCKET bindsock_3 = SRT_INVALID_SOCK; + if (localip_v4 != "") + { + // V6ONLY = 1, which means that binding to IPv4 should be possible. + bindsock_3 = createBinder(localip_v4, 5000, true); + } + + testAccept(bindsock_1, "::1", 5000, true); + + shutdownListener(bindsock_1); + shutdownListener(bindsock_2); + shutdownListener(bindsock_3); + + // Now the same thing, except that we bind to both IPv4 and IPv6. + + strict_ipv6 = 0; + + bindsock_1 = prepareSocket(); + srt_setsockflag(bindsock_1, SRTO_IPV6ONLY, &strict_ipv6, sizeof strict_ipv6); + bindListener(bindsock_1, "::", 5000, true); + + // Binding a certain address when wildcard is already bound should fail. + bindsock_2 = createBinder(localip, 5000, false); + bindsock_3 = SRT_INVALID_SOCK; -TEST(ReuseAddr, ProtocolVersion) + if (localip_v4 != "") + { + // V6ONLY = 0, which means that binding to IPv4 should not be possible. + bindsock_3 = createBinder(localip_v4, 5000, false); + } + + testAccept(bindsock_1, "::1", 5000, true); + + shutdownListener(bindsock_1); + shutdownListener(bindsock_2); + shutdownListener(bindsock_3); + + (void)srt_epoll_release(client_pollid); + (void)srt_epoll_release(server_pollid); + srt_cleanup(); +} + +TEST(ReuseAddr, ProtocolVersion6) +{ +#if defined(_WIN32) || defined(CYGWIN) + std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" + "Forcing test to pass, PLEASE FIX.\n"; + return; +#endif + ASSERT_EQ(srt_startup(), 0); + + client_pollid = srt_epoll_create(); + ASSERT_NE(SRT_ERROR, client_pollid); + + server_pollid = srt_epoll_create(); + ASSERT_NE(SRT_ERROR, server_pollid); + + SRTSOCKET bindsock_1 = createListener("0.0.0.0", 5000, true); + + // We need a small interception in this one. + // createListener = prepareSocket | bindListener + SRTSOCKET bindsock_2 = prepareSocket(); + { + int yes = 1; + + ASSERT_NE(srt_setsockflag(bindsock_2, SRTO_IPV6ONLY, &yes, sizeof yes), SRT_ERROR); + ASSERT_TRUE(bindListener(bindsock_2, "::", 5000, true)); + } + + testAccept(bindsock_1, "127.0.0.1", 5000, true); + testAccept(bindsock_2, "::1", 5000, true); + + shutdownListener(bindsock_1); + shutdownListener(bindsock_2); + + (void)srt_epoll_release(client_pollid); + (void)srt_epoll_release(server_pollid); + srt_cleanup(); +} + +TEST(ReuseAddr, ProtocolVersionFaux6) { #if defined(_WIN32) || defined(CYGWIN) std::cout << "!!!WARNING!!!: On Windows connection to localhost this way isn't possible.\n" @@ -433,8 +736,22 @@ TEST(ReuseAddr, ProtocolVersion) server_pollid = srt_epoll_create(); ASSERT_NE(SRT_ERROR, server_pollid); - serverSocket("::", 5000, true); - serverSocket("0.0.0.0", 5000, true); + SRTSOCKET bindsock_1 = createListener("0.0.0.0", 5000, true); + + // We need a small interception in this one. + // createListener = prepareSocket | bindListener + SRTSOCKET bindsock_2 = prepareSocket(); + { + int no = 0; + + ASSERT_NE(srt_setsockflag(bindsock_2, SRTO_IPV6ONLY, &no, sizeof no), SRT_ERROR); + ASSERT_FALSE(bindListener(bindsock_2, "::", 5000, false)); + } + + testAccept(bindsock_1, "127.0.0.1", 5000, true); + + shutdownListener(bindsock_1); + shutdownListener(bindsock_2); (void)srt_epoll_release(client_pollid); (void)srt_epoll_release(server_pollid);