Skip to content

Commit f46c831

Browse files
committed
More TOS fixes
- Windows behaves similarly to apply wrt IPv6 dual stack. - Linux is different. On dual stack, we need to set *both* socket options, and we will get the TOS returned in different cmsg data, depending on what type of packet is being processed. - Only assert about not getting any TOS data if we were actually to set the appropriate socket options - Also added a CMSG_DATA #define for windows. Unfortunately, this is some sort of crypto constant in windows so we cannot define it everywhere.
1 parent 903161c commit f46c831

1 file changed

Lines changed: 92 additions & 33 deletions

File tree

src/steamnetworkingsockets/clientlib/steamnetworkingsockets_lowlevel.cpp

Lines changed: 92 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ constexpr int k_cbETWEventUDPPacketDataSize = 16;
8383
#define CMSG_NXTHDR WSA_CMSG_NXTHDR
8484
#endif
8585

86+
#ifdef _WIN32
87+
// wincrypt.h defines CMSG_DATA as a CryptoAPI message-type constant (value 1),
88+
// completely unrelated to sockets. Stomp it with the socket cmsg accessor so
89+
// we can use CMSG_DATA uniformly in this file without #ifdef _WIN32 everywhere.
90+
#undef CMSG_DATA
91+
#define CMSG_DATA WSA_CMSG_DATA
92+
#endif
93+
8694
namespace SteamNetworkingSocketsLib {
8795

8896
inline void ETW_LongOp( const char *opName, SteamNetworkingMicroseconds usec, const char *pszInfo )
@@ -1062,6 +1070,10 @@ class CRawUDPSocketImpl final : public IRawUDPSocket
10621070
LPFN_WSARECVMSG m_pfnWSARecvMsg = nullptr;
10631071
#endif
10641072

1073+
#if PlatformSupportsRecvTOS()
1074+
bool m_bTOSEnabled = false;
1075+
#endif
1076+
10651077
// Implements IRawUDPSocket
10661078
virtual bool BSendRawPacketGather( int nChunks, const iovec *pChunks, const netadr_t &adrTo, int ecn = -1 ) const override;
10671079
virtual void Close() override;
@@ -2146,30 +2158,6 @@ static SOCKET OpenUDPSocketBoundToSockAddr( const void *pSockaddr, size_t len, S
21462158
#endif
21472159
}
21482160

2149-
// Enable receiving of the ToS field in the ancillary data
2150-
#if PlatformSupportsRecvTOS()
2151-
{
2152-
opt = 1;
2153-
2154-
// Apple uses a different field to receive this for IPv6
2155-
#if defined(__APPLE__)
2156-
if ( inaddr->sin_family == AF_INET6 )
2157-
{
2158-
if ( setsockopt( sock, IPPROTO_IPV6, IPV6_RECVTCLASS, (char *)&opt, sizeof(opt) ) == -1 )
2159-
{
2160-
SpewWarning( "sockopt(IPPROTO_IPV6, IPV6_RECVTCLASS, 1) failed (0x%x), will not be able to read TOS\n", GetLastSocketError() );
2161-
}
2162-
} else
2163-
#endif
2164-
{
2165-
if ( setsockopt( sock, IPPROTO_IP, IP_RECVTOS, (char *)&opt, sizeof(opt) ) == -1 )
2166-
{
2167-
SpewWarning( "sockopt(IPPROTO_IP, IP_RECVTOS, 1) failed (0x%x), will not be able to read TOS\n", GetLastSocketError() );
2168-
}
2169-
}
2170-
}
2171-
#endif
2172-
21732161
// Bind it to specific desired local port/IP
21742162
if ( bind( sock, (struct sockaddr *)pSockaddr, (socklen_t)len ) == -1 )
21752163
{
@@ -2369,6 +2357,63 @@ static CRawUDPSocketImpl *OpenRawUDPSocketInternal( CRecvPacketCallback callback
23692357
}
23702358
#endif
23712359

2360+
// Enable receiving TOS/traffic class in ancillary data, and record whether it succeeded.
2361+
//
2362+
// Platform/family matrix:
2363+
//
2364+
// Windows AF_INET6 (incl. dual-stack): IPPROTO_IP options are rejected with WSAEINVAL.
2365+
// Use IPV6_RECVTCLASS only — Windows maps the IPv4 TOS byte into the IPv6 traffic-class
2366+
// field in the cmsg for IPv4-mapped packets, so one sockopt covers both families.
2367+
//
2368+
// Apple AF_INET6: same as Windows — IPV6_RECVTCLASS covers both families.
2369+
//
2370+
// Linux AF_INET6 dual-stack: both sockopts must be set. IP_RECVTOS covers IPv4-mapped
2371+
// packets (returns IPPROTO_IP/IP_TOS); IPV6_RECVTCLASS covers pure IPv6 (returns
2372+
// IPPROTO_IPV6/IPV6_TCLASS). Unlike Windows, Linux does NOT map one to the other.
2373+
//
2374+
// Linux AF_INET6 IPv6-only: only IPV6_RECVTCLASS is needed (no IPv4-mapped packets).
2375+
//
2376+
// All platforms AF_INET: IP_RECVTOS only.
2377+
#if PlatformSupportsRecvTOS()
2378+
{
2379+
unsigned int opt = 1;
2380+
bool bTOSEnabled = false;
2381+
2382+
bool use_IPv4_RECVTOS = true;
2383+
if ( addrBound.ss_family == AF_INET6 )
2384+
{
2385+
#if defined( _WIN32 ) || defined( __APPLE__ )
2386+
if ( setsockopt( sock, IPPROTO_IPV6, IPV6_RECVTCLASS, (char *)&opt, sizeof(opt) ) == -1 )
2387+
SpewWarning( "sockopt(IPPROTO_IPV6, IPV6_RECVTCLASS, 1) failed (0x%x), will not be able to read TOS\n", GetLastSocketError() );
2388+
else
2389+
bTOSEnabled = true;
2390+
2391+
// One sockopt covers both IPv4-mapped and pure IPv6 on these platforms.
2392+
use_IPv4_RECVTOS = false;
2393+
#else
2394+
if ( setsockopt( sock, IPPROTO_IPV6, IPV6_RECVTCLASS, (char *)&opt, sizeof(opt) ) == -1 )
2395+
SpewWarning( "sockopt(IPPROTO_IPV6, IPV6_RECVTCLASS, 1) failed (0x%x), will not be able to read TOS for IPv6 packets\n", GetLastSocketError() );
2396+
else
2397+
bTOSEnabled = true;
2398+
2399+
// Linux: dual-stack sockets can receive IPv4-mapped packets; but skip IP_RECVTOS for IPv6-only.
2400+
if ( !( pSock->m_nAddressFamilies & k_nAddressFamily_IPv4 ) )
2401+
use_IPv4_RECVTOS = false;
2402+
#endif
2403+
}
2404+
2405+
if ( use_IPv4_RECVTOS )
2406+
{
2407+
if ( setsockopt( sock, IPPROTO_IP, IP_RECVTOS, (char *)&opt, sizeof(opt) ) == -1 )
2408+
SpewWarning( "sockopt(IPPROTO_IP, IP_RECVTOS, 1) failed (0x%x), will not be able to read TOS\n", GetLastSocketError() );
2409+
else
2410+
bTOSEnabled = true;
2411+
}
2412+
2413+
pSock->m_bTOSEnabled = bTOSEnabled;
2414+
}
2415+
#endif
2416+
23722417
// Add to master list. (Hopefully we usually won't have that many.)
23732418
s_vecRawSockets.AddToTail( pSock );
23742419

@@ -2568,11 +2613,14 @@ static bool DrainSocket( CRawUDPSocketImpl *pSock )
25682613
goto tos_done;
25692614
}
25702615

2571-
// The naming of this field is apparently inconsistent
2616+
// IPv6 tclass
25722617
if (
25732618
cmsg->cmsg_level == IPPROTO_IPV6
25742619
&& (
25752620
cmsg->cmsg_type == IPV6_TCLASS
2621+
2622+
// Older versions of MacOS return the socket
2623+
// option ID instead of the cmsg ID.
25762624
|| cmsg->cmsg_type == IPV6_RECVTCLASS
25772625
#ifdef IP_RECVTCLASS
25782626
|| cmsg->cmsg_type == IP_RECVTCLASS
@@ -2586,26 +2634,37 @@ static bool DrainSocket( CRawUDPSocketImpl *pSock )
25862634
}
25872635

25882636
#else
2637+
// IPv4 TOS: returned for AF_INET sockets, and for IPv4-mapped packets on
2638+
// Linux dual-stack AF_INET6 sockets.
25892639
if ( cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TOS )
25902640
{
25912641
#ifdef _WIN32
2592-
AssertMsgOnce( cmsg->cmsg_len == sizeof(cmsghdr) + sizeof(int), "Unexpected IP_TOS cmsg_len %lld", (long long)cmsg->cmsg_len );
2593-
info.m_tos = (uint8)*((int *) WSA_CMSG_DATA(cmsg));
2642+
// Windows returns TOS as int
2643+
AssertMsgOnce( cmsg->cmsg_len >= CMSG_LEN( sizeof(int) ), "Unexpected IP_TOS cmsg_len %lld", (long long)cmsg->cmsg_len );
2644+
info.m_tos = (uint8)*((int *) CMSG_DATA(cmsg));
25942645
#else
2595-
AssertMsgOnce( cmsg->cmsg_len == sizeof(cmsghdr) + sizeof(uint8), "Unexpected IP_TOS cmsg_len %lld", (long long)cmsg->cmsg_len );
2646+
// POSIX returns TOS as uint8
2647+
AssertMsgOnce( cmsg->cmsg_len >= CMSG_LEN( sizeof(uint8) ), "Unexpected IP_TOS cmsg_len %lld", (long long)cmsg->cmsg_len );
25962648
info.m_tos = *((uint8 *) CMSG_DATA(cmsg));
25972649
#endif
25982650
goto tos_done;
25992651
}
2652+
2653+
// IPv6 traffic class: returned for pure IPv6 packets on Linux AF_INET6
2654+
// sockets, and for all packets (incl. IPv4-mapped) on Windows/Apple AF_INET6.
2655+
if ( cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_TCLASS )
2656+
{
2657+
AssertMsgOnce( cmsg->cmsg_len >= CMSG_LEN( sizeof(int) ), "Unexpected IPV6_TCLASS cmsg_len %lld", (long long)cmsg->cmsg_len );
2658+
info.m_tos = (uint8)*((int *) CMSG_DATA(cmsg));
2659+
goto tos_done;
2660+
}
26002661
#endif
26012662
}
26022663

26032664
// If we get here, we scanned all control messages but didn't get the TOS data.
2604-
// If the platform supports it, we always ask for TOS, so it's bad that we didn't get it back.
2605-
// But only assert once, because all of the current the code that consumes this field
2606-
// is tolerant of the fact that it might not be available. (Since we have to
2607-
// support platforms that don't support it at all.)
2608-
AssertMsgOnce( false, "No control data returned even though we asked for TOS?" );
2665+
// Only assert if we successfully enabled TOS on this socket — if the setsockopt
2666+
// failed we already warned at startup and shouldn't fire repeatedly here.
2667+
AssertMsgOnce( !pSock->m_bTOSEnabled, "No control data returned even though we asked for TOS?" );
26092668
}
26102669
tos_done:
26112670
#endif

0 commit comments

Comments
 (0)