@@ -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+
8694namespace SteamNetworkingSocketsLib {
8795
8896inline 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