Support for using multiple wifi bands.

Context: https://twitter.com/ZPostFacto/status/1445748202926342144

NOTE: This is *not* currently useful in the opensource code, so
STEAMNETWORKINGSOCKETS_ENABLE_DUALWIFI is never defined.  It depends
on the sender negotiating with the receiver that a secondary source
IP:port address will be associated with the same connection, and that
has not been implemented for ordinary UDP connections at this time.
(On Steam it is part of the SDR protocol.)  When I do that work, I
probably will do it in conjunction with some other planned work to
support doing all UDP connections on the same socket, and using the
connection ID for multiplexing instead of IP:port tuples.  This
strategy not only is more efficient, due to fewer sockets being
used, but it also makes it very easy to support the peer roaming to
a different IP:port without the connection dropping, which can happen
in mobile environments and is a characteristic of QUIC.
This commit is contained in:
Fletcher Dunn
2021-10-06 11:06:33 -07:00
parent e11d1ec491
commit 8ce2ea94ed
11 changed files with 614 additions and 20 deletions
@@ -101,6 +101,9 @@ message CMsgSteamDatagramLinkLifetimeStats
optional uint64 packets_recv_out_of_order = 9;
optional uint64 packets_recv_duplicate = 10;
optional uint64 packets_recv_lurch = 11;
repeated uint64 multipath_packets_recv_sequenced = 12; // If multipath send or receive was used, then the samples for how many we received on each path
repeated uint64 multipath_packets_recv_later = 13; // How often we received a sequenced packet only this path, but another path one the race and so we discarded this one
optional uint32 multipath_send_enabled = 14;
/// Histogram of connection quality. Here we count up the number
/// of connection quality measurement intervals (about 5 seconds)
@@ -84,6 +84,9 @@ DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, MTU_PacketSize, 1300, k_cbSteamNetwor
DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, Unencrypted, 0, 0, 3 );
DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, SymmetricConnect, 0, 0, 1 );
DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, LocalVirtualPort, -1, -1, INT32_MAX );
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_DUALWIFI
DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, DualWifi_Enable, 1, 0, k_nDualWifiEnable_MAX );
#endif
DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, LogLevel_AckRTT, k_ESteamNetworkingSocketsDebugOutputType_Warning, k_ESteamNetworkingSocketsDebugOutputType_Error, k_ESteamNetworkingSocketsDebugOutputType_Everything );
DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, LogLevel_PacketDecode, k_ESteamNetworkingSocketsDebugOutputType_Warning, k_ESteamNetworkingSocketsDebugOutputType_Error, k_ESteamNetworkingSocketsDebugOutputType_Everything );
DEFINE_CONNECTON_DEFAULT_CONFIGVAL( int32, LogLevel_Message, k_ESteamNetworkingSocketsDebugOutputType_Warning, k_ESteamNetworkingSocketsDebugOutputType_Error, k_ESteamNetworkingSocketsDebugOutputType_Everything );
@@ -1060,7 +1060,8 @@ void CSteamNetworkConnectionBase::RecvNonDataSequencedPacket( int64 nPktNum, Ste
SNP_RecordReceivedPktNum( nPktNum, usecNow, false );
// Update general sequence number/stats tracker for the end-to-end flow.
m_statsEndToEnd.TrackProcessSequencedPacket( nPktNum, usecNow, 0 );
int idxMultiPath = 0; // Assume for now
m_statsEndToEnd.TrackProcessSequencedPacket( nPktNum, usecNow, 0, idxMultiPath );
}
bool CSteamNetworkConnectionBase::BThinkCryptoReady( SteamNetworkingMicroseconds usecNow )
@@ -2062,7 +2063,7 @@ bool CSteamNetworkConnectionBase::DecryptDataChunk( uint16 nWireSeqNum, int cbPa
AssertMsg1( m_statsEndToEnd.m_nMaxRecvPktNum > 0 || m_statsEndToEnd.m_nPeerProtocolVersion < 10, "[%s] packet number not properly initialized!", GetDescription() );
// Get the full end-to-end packet number, check if we should process it
ctx.m_nPktNum = m_statsEndToEnd.ExpandWirePacketNumberAndCheck( nWireSeqNum );
ctx.m_nPktNum = m_statsEndToEnd.ExpandWirePacketNumberAndCheck( nWireSeqNum, ctx.m_idxMultiPath );
if ( ctx.m_nPktNum <= 0 )
{
@@ -2118,8 +2119,8 @@ bool CSteamNetworkConnectionBase::DecryptDataChunk( uint16 nWireSeqNum, int cbPa
// The assumption is that we either have a bug or some weird thing,
// or that somebody is spoofing / tampering. If it's the latter
// we don't want to magnify the impact of their efforts
SpewWarningRateLimited( ctx.m_usecNow, "[%s] Packet %lld (0x%x) decrypt failed (tampering/spoofing/bug)!",
GetDescription(), (long long)ctx.m_nPktNum, (unsigned)nWireSeqNum );
SpewWarningRateLimited( ctx.m_usecNow, "[%s] Packet %lld (0x%x) decrypt failed (tampering/spoofing/bug)! mpath%d",
GetDescription(), (long long)ctx.m_nPktNum, (unsigned)nWireSeqNum, ctx.m_idxMultiPath );
// Update raw packet counters numbers, but do not update any logical state such as reply timeouts, etc
m_statsEndToEnd.m_recv.ProcessPacket( cbPacketSize );
@@ -3985,8 +3986,9 @@ void CSteamNetworkConnectionPipe::FakeRecvStats( SteamNetworkingMicroseconds use
// And the peer receiving it immediately. And assume every packet represents
// a ping measurement.
int64 nPktNum = m_statsEndToEnd.ExpandWirePacketNumberAndCheck( nWirePktNum );
m_statsEndToEnd.TrackProcessSequencedPacket( nPktNum, usecNow, -1 );
int idxMultiPath = 0;
int64 nPktNum = m_statsEndToEnd.ExpandWirePacketNumberAndCheck( nWirePktNum, idxMultiPath );
m_statsEndToEnd.TrackProcessSequencedPacket( nPktNum, usecNow, -1, idxMultiPath );
m_statsEndToEnd.TrackRecvPacket( cbPktSize, usecNow );
m_statsEndToEnd.m_ping.ReceivedPing( 0, usecNow );
}
@@ -114,6 +114,9 @@ struct RecvPacketContext_t
/// Jitter measurement, if present
//int m_usecTimeSinceLast;
/// For dual path, is this the primary or secondary path?
int m_idxMultiPath = 0;
//
// Output of DecryptDataChunk
//
@@ -970,6 +970,10 @@ public:
nullptr // lpCompletionRoutine
);
bool bResult = ( r == 0 );
if ( !bResult )
{
SpewWarning( "WSASendTo %s failed, returned %d, last error=0x%x\n", CUtlNetAdrRender( adrTo ).String(), r, GetLastSocketError() );
}
#else
msghdr msg;
msg.msg_name = (sockaddr *)&destAddress;
@@ -1052,6 +1056,11 @@ public:
}
}
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_DUALWIFI
CRawUDPSocketImpl *m_pDualWifiPartner = nullptr;
virtual IRawUDPSocket *GetDualWifiSecondarySocket( int nEnableSetting ) override;
#endif
private:
void InternalAddToCleanupQueue();
@@ -1369,6 +1378,31 @@ void CRawUDPSocketImpl::Close()
// Mark the callback as detached, and put us in the queue for cleanup when it's safe.
InternalAddToCleanupQueue();
// Check for Dual Wifi
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_DUALWIFI
if ( m_pDualWifiPartner )
{
Assert( m_pDualWifiPartner->m_pDualWifiPartner == this );
if ( m_eDualWifiStatus == k_EDualWifi_Primary )
{
Assert( m_pDualWifiPartner->m_eDualWifiStatus == k_EDualWifi_Secondary );
m_pDualWifiPartner->InternalAddToCleanupQueue();
}
else
{
// People shouldn't do this, but if they do, let's not crash
AssertMsg( false, "Closed secondary dual Wifi socket directly?" );
}
m_pDualWifiPartner->m_eDualWifiStatus = k_EDualWifi_Done;
m_pDualWifiPartner->m_pDualWifiPartner = nullptr;
m_eDualWifiStatus = k_EDualWifi_Done;
m_pDualWifiPartner = nullptr;
}
#endif
// Make sure we don't delay doing this too long
if ( s_bManualPollMode || ( s_pThreadSteamDatagram && s_pThreadSteamDatagram->get_id() != std::this_thread::get_id() ) )
{
@@ -1382,7 +1416,7 @@ void CRawUDPSocketImpl::Close()
}
}
static SOCKET OpenUDPSocketBoundToSockAddr( const void *sockaddr, size_t len, SteamDatagramErrMsg &errMsg, int *pnIPv6AddressFamilies )
static SOCKET OpenUDPSocketBoundToSockAddr( const void *sockaddr, size_t len, SteamDatagramErrMsg &errMsg, int *pnIPv6AddressFamilies, int nBindInterface = -1 )
{
unsigned int opt;
@@ -1468,7 +1502,46 @@ static SOCKET OpenUDPSocketBoundToSockAddr( const void *sockaddr, size_t len, St
}
}
// Bind it to specific desired port and/or interfaces
// Bind to particular interface
if ( nBindInterface >= 0 )
{
#ifdef _WIN32
Assert( nBindInterface != 0 ); // 0 is reserved, invalid value in Windows.
// Bind to particular interface for IPv4
if ( inaddr->sin_family == AF_INET || ( pnIPv6AddressFamilies && ( *pnIPv6AddressFamilies & k_nAddressFamily_IPv4 ) ) )
{
// WARNING: interface index should be in network byte order for IPPROTO_IP
const DWORD value = htonl(nBindInterface);
const int length = sizeof(value);
const auto error = setsockopt( sock , IPPROTO_IP, IP_UNICAST_IF, reinterpret_cast<const char*>(&value), length);
if (ERROR_SUCCESS != error)
{
V_sprintf_safe( errMsg, "sockopt(IP_PROTO_IP, IP_UNICAST_IF, %d) failed with error code 0x%08X.", nBindInterface, GetLastSocketError() );
closesocket( sock );
return INVALID_SOCKET;
}
SpewVerbose( "sockopt(IP_PROTO_IP, IP_UNICAST_IF, %d) OK\n", nBindInterface );
}
// Bind to particular interface for IPv6
if ( inaddr->sin_family == AF_INET6 || ( pnIPv6AddressFamilies && ( *pnIPv6AddressFamilies & k_nAddressFamily_IPv6 ) ) )
{
// WARNING: interface index should be in host byte order for IPPROTO_IPV6
auto length = static_cast<int>(sizeof(nBindInterface));
const auto error = setsockopt( sock, IPPROTO_IPV6, IPV6_UNICAST_IF, reinterpret_cast<const char*>(&nBindInterface), length);
if (ERROR_SUCCESS != error)
{
V_sprintf_safe( errMsg, "sockopt(IPPROTO_IPV6, IPV6_UNICAST_IF, %d) failed with error code 0x%08X.", nBindInterface, GetLastSocketError() );
closesocket( sock );
return INVALID_SOCKET;
}
SpewVerbose( "sockopt(IPPROTO_IPV6, IPV6_UNICAST_IF, %d) OK\n", nBindInterface );
}
#endif
}
// Bind it to specific desired local port/IP
if ( bind( sock, (struct sockaddr *)sockaddr, (socklen_t)len ) == -1 )
{
V_sprintf_safe( errMsg, "Failed to bind socket. Error code 0x%08X.", GetLastSocketError() );
@@ -1480,7 +1553,7 @@ static SOCKET OpenUDPSocketBoundToSockAddr( const void *sockaddr, size_t len, St
return sock;
}
static CRawUDPSocketImpl *OpenRawUDPSocketInternal( CRecvPacketCallback callback, SteamDatagramErrMsg &errMsg, const SteamNetworkingIPAddr *pAddrLocal, int *pnAddressFamilies )
static CRawUDPSocketImpl *OpenRawUDPSocketInternal( CRecvPacketCallback callback, SteamDatagramErrMsg &errMsg, const SteamNetworkingIPAddr *pAddrLocal, int *pnAddressFamilies, int nBindInterface = -1 )
{
// Creating a socket *should* be fast, but sometimes the OS might need to do some work.
// We shouldn't do this too often, give it a little extra time.
@@ -1552,7 +1625,7 @@ static CRawUDPSocketImpl *OpenRawUDPSocketInternal( CRecvPacketCallback callback
// Try to get socket
int nIPv6AddressFamilies = nAddressFamilies;
sock = OpenUDPSocketBoundToSockAddr( &address6, sizeof(address6), errMsg, &nIPv6AddressFamilies );
sock = OpenUDPSocketBoundToSockAddr( &address6, sizeof(address6), errMsg, &nIPv6AddressFamilies, nBindInterface );
if ( sock == INVALID_SOCKET )
{
@@ -1580,7 +1653,7 @@ static CRawUDPSocketImpl *OpenRawUDPSocketInternal( CRecvPacketCallback callback
address4.sin_port = BigWord( addrLocal.m_port );
// Try to get socket
sock = OpenUDPSocketBoundToSockAddr( &address4, sizeof(address4), errMsg, nullptr );
sock = OpenUDPSocketBoundToSockAddr( &address4, sizeof(address4), errMsg, nullptr, nBindInterface );
// If we failed, well, we have no other options left to try.
if ( sock == INVALID_SOCKET )
@@ -2014,6 +2087,398 @@ static void ProcessDeferredOperations()
ProcessPendingDestroyClosedRawUDPSockets();
}
/////////////////////////////////////////////////////////////////////////////
//
// Dual Wifi band support
//
/////////////////////////////////////////////////////////////////////////////
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_DUALWIFI
#include <wlanapi.h>
static int s_ifaceDualWifiSecondary = -1; // -1 means we haven't tried yet. Any other negative value means, we tried and failed
static HANDLE wlanHandle = INVALID_HANDLE_VALUE;
static HMODULE hModuleWlanAPI = NULL;
static
DWORD
(WINAPI *pWlanOpenHandle)(
DWORD dwClientVersion,
PVOID pReserved,
PDWORD pdwNegotiatedVersion,
PHANDLE phClientHandle
);
static
DWORD
(WINAPI *pWlanCloseHandle)(
HANDLE hClientHandle,
PVOID pReserved
);
static
DWORD
(WINAPI *pWlanEnumInterfaces)(
HANDLE hClientHandle,
PVOID pReserved,
PWLAN_INTERFACE_INFO_LIST *ppInterfaceList
);
static
DWORD
( WINAPI *pWlanSetInterface)(
HANDLE hClientHandle,
CONST GUID *pInterfaceGuid,
WLAN_INTF_OPCODE OpCode,
DWORD dwDataSize,
CONST PVOID pData,
PVOID pReserved
);
static
DWORD
( WINAPI *pWlanQueryInterface)(
HANDLE hClientHandle,
CONST GUID *pInterfaceGuid,
WLAN_INTF_OPCODE OpCode,
PVOID pReserved,
PDWORD pdwDataSize,
PVOID *ppData,
PWLAN_OPCODE_VALUE_TYPE pWlanOpcodeValueType
);
void DualWifiShutdown()
{
if ( wlanHandle != INVALID_HANDLE_VALUE )
{
(*pWlanCloseHandle)( wlanHandle, nullptr );
wlanHandle = INVALID_HANDLE_VALUE;
}
s_ifaceDualWifiSecondary = -1;
}
// !KLUDGE! Pasted from an early version of wlan.h.
namespace wlan_new
{
#ifdef __midl
// use the 4-byte enum
typedef [v1_enum] enum _WLAN_INTF_OPCODE {
#else
typedef enum _WLAN_INTF_OPCODE {
#endif
wlan_intf_opcode_autoconf_start = 0x000000000,
wlan_intf_opcode_autoconf_enabled,
wlan_intf_opcode_background_scan_enabled,
wlan_intf_opcode_media_streaming_mode,
wlan_intf_opcode_radio_state,
wlan_intf_opcode_bss_type,
wlan_intf_opcode_interface_state,
wlan_intf_opcode_current_connection,
wlan_intf_opcode_channel_number,
wlan_intf_opcode_supported_infrastructure_auth_cipher_pairs,
wlan_intf_opcode_supported_adhoc_auth_cipher_pairs,
wlan_intf_opcode_supported_country_or_region_string_list,
wlan_intf_opcode_current_operation_mode,
wlan_intf_opcode_supported_safe_mode,
wlan_intf_opcode_certified_safe_mode,
wlan_intf_opcode_hosted_network_capable,
wlan_intf_opcode_management_frame_protection_capable,
wlan_intf_opcode_secondary_sta_interfaces,
wlan_intf_opcode_secondary_sta_synchronized_connections,
wlan_intf_opcode_autoconf_end = 0x0fffffff,
wlan_intf_opcode_msm_start = 0x10000100,
wlan_intf_opcode_statistics,
wlan_intf_opcode_rssi,
wlan_intf_opcode_msm_end = 0x1fffffff,
wlan_intf_opcode_security_start = 0x20010000,
wlan_intf_opcode_security_end = 0x2fffffff,
wlan_intf_opcode_ihv_start = 0x30000000,
wlan_intf_opcode_ihv_end = 0x3fffffff
} WLAN_INTF_OPCODE, *PWLAN_INTF_OPCODE;
}
const WLAN_INTF_OPCODE wlan_intf_opcode_secondary_sta_synchronized_connections = (WLAN_INTF_OPCODE)wlan_new::wlan_intf_opcode_secondary_sta_synchronized_connections;
const WLAN_INTF_OPCODE wlan_intf_opcode_secondary_sta_interfaces = (WLAN_INTF_OPCODE)wlan_new::wlan_intf_opcode_secondary_sta_interfaces;
static void DualWifiInitFailed( const char *fmt, ... )
{
va_list ap;
va_start( ap, fmt );
char buf[ 512 ];
V_vsprintf_safe( buf, fmt, ap );
SpewMsg( "DualWifi not detected. We won't try again. %s\n", buf );
DualWifiShutdown();
s_ifaceDualWifiSecondary = -2; // but remember that we failed
}
static int ConvertInterfaceGuidToIndex(const GUID& interfaceGuid)
{
//
// These functions were added with Vista, so load dynamically
// in case
//
typedef
NETIO_STATUS
(NETIOAPI_API_*FnConvertInterfaceGuidToLuid)(
_In_ CONST GUID *InterfaceGuid,
_Out_ PNET_LUID InterfaceLuid
);
typedef
NETIO_STATUS
(NETIOAPI_API_*FnConvertInterfaceLuidToIndex)(
_In_ CONST NET_LUID *InterfaceLuid,
_Out_ PNET_IFINDEX InterfaceIndex
);
static HMODULE hModule = LoadLibraryA( "Iphlpapi.dll" );
static FnConvertInterfaceGuidToLuid pConvertInterfaceGuidToLuid = hModule ? (FnConvertInterfaceGuidToLuid)GetProcAddress( hModule, "ConvertInterfaceGuidToLuid" ) : nullptr;
static FnConvertInterfaceLuidToIndex pConvertInterfaceLuidToIndex = hModule ? (FnConvertInterfaceLuidToIndex)GetProcAddress( hModule, "ConvertInterfaceLuidToIndex" ) : nullptr;;
if ( !pConvertInterfaceGuidToLuid || !pConvertInterfaceLuidToIndex )
{
AssertMsg( false, "How did I get here?" );
return -1;
}
NET_LUID interfaceLuid{};
auto error = (pConvertInterfaceGuidToLuid)(&interfaceGuid, &interfaceLuid);
if ( ERROR_SUCCESS != error )
{
AssertMsg( false, "ConvertInterfaceGuidToLuid failed 0x%x", error );
return -1;
}
NET_IFINDEX interfaceIndex = 0;
error = (*pConvertInterfaceLuidToIndex)(&interfaceLuid, &interfaceIndex);
if ( ERROR_SUCCESS != error )
{
AssertMsg( false, "ConvertInterfaceLuidToIndex failed 0x%x", error );
return -1;
}
return static_cast<int>(interfaceIndex);
}
class RenderGUID
{
char buf[64];
public:
RenderGUID( const GUID &guid )
{
// https://stackoverflow.com/a/18114061/8004137
V_sprintf_safe( buf, "{%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX}",
guid.Data1, guid.Data2, guid.Data3,
guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
}
const char *c_str() const { return buf; }
};
template <typename F>
bool MyGetProcAddress( F& fn, HMODULE hm, const char *pszName )
{
if ( hm == NULL )
fn = nullptr;
else
fn = (F)GetProcAddress( hm, pszName );
return fn != nullptr;
}
static int GetDualWifiSecondaryInterfaceIndex( int nSimulateMode )
{
// First time attempt?
// FIXME - it's not clear to me when I should retry
if ( s_ifaceDualWifiSecondary != -1 )
return s_ifaceDualWifiSecondary;
// Dynamically load wlanapi.dll the first time.
if ( hModuleWlanAPI == NULL )
{
hModuleWlanAPI = LoadLibraryA( "wlanapi.dll" );
if (
!MyGetProcAddress( pWlanOpenHandle, hModuleWlanAPI, "WlanOpenHandle" )
|| !MyGetProcAddress( pWlanCloseHandle, hModuleWlanAPI, "WlanCloseHandle" )
|| !MyGetProcAddress( pWlanEnumInterfaces, hModuleWlanAPI, "WlanEnumInterfaces" )
|| !MyGetProcAddress( pWlanSetInterface, hModuleWlanAPI, "WlanSetInterface" )
|| !MyGetProcAddress( pWlanQueryInterface, hModuleWlanAPI, "WlanQueryInterface" )
) {
DualWifiInitFailed( "Failed to load wlanAPI.DLL" );
return -1;
}
}
// First time we need to open Wlan session
if ( wlanHandle == INVALID_HANDLE_VALUE )
{
DWORD clientVersion = 2; // Vista+ APIs
DWORD curVersion = 0;
DWORD error = (*pWlanOpenHandle)(clientVersion, nullptr, &curVersion, &wlanHandle );
if ( ERROR_SUCCESS != error || wlanHandle == INVALID_HANDLE_VALUE )
{
DualWifiInitFailed( "WlanOpenHandle failed 0x%x.", error );
return -1;
}
}
PWLAN_INTERFACE_INFO_LIST primaryInterfaceList = nullptr;
DWORD error = (*pWlanEnumInterfaces)(wlanHandle, nullptr, &primaryInterfaceList);
if ( ERROR_SUCCESS != error || !primaryInterfaceList )
{
DualWifiInitFailed( "WlanEnumInterfaces failed 0x%x.", error );
return -1;
}
if ( nSimulateMode == k_nDualWifiEnable_DoNotEnumerate )
{
DualWifiInitFailed( "Not really checking for capable adapters as per DualWifi_Enable=%d", nSimulateMode );
s_ifaceDualWifiSecondary = -1;
return -1;
}
// Look for the first Wireless interface that we can enable DualSTA for
for ( DWORD idxPrimary = 0 ; idxPrimary < primaryInterfaceList->dwNumberOfItems ; ++idxPrimary )
{
const GUID primaryInterfaceGuid = primaryInterfaceList->InterfaceInfo[idxPrimary].InterfaceGuid;
// Get adapter name in UTF8
char szInterfaceDescription[ 256 ];
memset( szInterfaceDescription, 0, sizeof(szInterfaceDescription) );
WideCharToMultiByte(
CP_UTF8, // codepage
0, // flags
primaryInterfaceList->InterfaceInfo[idxPrimary].strInterfaceDescription, -1, // input string and length
szInterfaceDescription, sizeof(szInterfaceDescription)-1, // output buffer and SIZE in bytes
nullptr, nullptr // no default char, and we don't care if it was used
);
// Try to enable the feature on this adapter. This is where most adapters should fail.
BOOL enable = TRUE;
error = (*pWlanSetInterface)(
wlanHandle, &primaryInterfaceGuid, wlan_intf_opcode_secondary_sta_synchronized_connections, sizeof(BOOL), static_cast<PVOID>(&enable), nullptr);
if ( ERROR_SUCCESS != error )
{
SpewVerbose( "Dual Wifi support not detected on adapter '%s' (wlan_intf_opcode_secondary_sta_synchronized_connections returned 0x%x)\n", szInterfaceDescription, error );
continue;
}
// Feature is detected!
SpewMsg( "Dual Wifi support enabled successfully on adapter '%s'\n", szInterfaceDescription );
PWLAN_INTERFACE_INFO_LIST secondaryInterfaceList = nullptr;
DWORD dataSize = 0;
error = (*pWlanQueryInterface)(
wlanHandle,
&primaryInterfaceGuid,
wlan_intf_opcode_secondary_sta_interfaces,
nullptr,
&dataSize,
reinterpret_cast<PVOID*>(&secondaryInterfaceList),
nullptr);
if ( ERROR_SUCCESS != error || !secondaryInterfaceList )
{
AssertMsg( false, "wlan_intf_opcode_secondary_sta_synchronized_connections succeeded, but wlan_intf_opcode_secondary_sta_interfaces failed 0x%x?", error );
continue;
}
for ( DWORD idxSecondary = 0 ; idxSecondary < secondaryInterfaceList->dwNumberOfItems ; ++idxSecondary )
{
s_ifaceDualWifiSecondary = ConvertInterfaceGuidToIndex( secondaryInterfaceList->InterfaceInfo[ idxSecondary ].InterfaceGuid );
if ( s_ifaceDualWifiSecondary >= 0 )
{
SpewMsg( "Primary DualSTA interfaces %s matched to secondary interface %s, index %d\n",
RenderGUID( primaryInterfaceGuid ).c_str(), RenderGUID( secondaryInterfaceList->InterfaceInfo[ idxSecondary ].InterfaceGuid ).c_str(),
s_ifaceDualWifiSecondary );
return s_ifaceDualWifiSecondary;
}
}
AssertMsg( false, "Could not find secondary wifi adapter, even though wlan_intf_opcode_secondary_sta_synchronized_connections succeeded" );
}
// Failed. This should be common
DualWifiInitFailed( "Didn't find any Dual-Wifi-capable Wifi adapters" );
return -1;
}
IRawUDPSocket *CRawUDPSocketImpl::GetDualWifiSecondarySocket( int nEnableSetting )
{
SteamDatagramErrMsg errMsg;
switch ( m_eDualWifiStatus )
{
case k_EDualWifi_NotAttempted:
{
Assert( m_pDualWifiPartner == nullptr );
// Locate the secondary interface, if any.
int ifaceIndex = -1;
if ( nEnableSetting == k_nDualWifiEnable_ForceSimulate )
{
SpewMsg( "Not actually checking for Dual-wifi support, just creating simulating support, as per DualWifi_Enable=%d\n", nEnableSetting );
}
else
{
ifaceIndex = GetDualWifiSecondaryInterfaceIndex( nEnableSetting );
if ( ifaceIndex < 0 && nEnableSetting == k_nDualWifiEnable_Enable )
{
// not found, and we don't want to simulate support.
// This will be common! Don't retry.
m_eDualWifiStatus = k_EDualWifi_Done;
break;
}
}
if ( nEnableSetting == k_nDualWifiEnable_DoNotBind )
{
SpewMsg( "Not actually creating secondary socket as per DualWifi_Enable=%d\n", nEnableSetting );
m_eDualWifiStatus = k_EDualWifi_Done;
break;
}
// Create the second socket, binding it the interface as appropriate
int nTempAddressFamiles = m_nAddressFamilies;
m_pDualWifiPartner = OpenRawUDPSocketInternal( m_callback, errMsg, nullptr, &nTempAddressFamiles, ifaceIndex );
if ( !m_pDualWifiPartner )
{
SpewWarning( "Failed to create dual Wifi secondary socket. %s\n", errMsg );
m_eDualWifiStatus = k_EDualWifi_Done; // Don't retry
break;
}
m_eDualWifiStatus = k_EDualWifi_Primary;
m_pDualWifiPartner->m_eDualWifiStatus = k_EDualWifi_Secondary;
m_pDualWifiPartner->m_pDualWifiPartner = this;
SpewMsg( "Created %sdual Wifi secondary socket OK. Primary local address is %s, secondary is %s\n",
ifaceIndex < 0 ? "simulated " : "",
SteamNetworkingIPAddrRender( m_boundAddr ).c_str(), SteamNetworkingIPAddrRender( m_pDualWifiPartner->m_boundAddr ).c_str() );
return m_pDualWifiPartner;
}
case k_EDualWifi_Primary:
Assert( m_pDualWifiPartner );
return m_pDualWifiPartner;
default:
case k_EDualWifi_Secondary:
Assert( false );
break;
case k_EDualWifi_Done:
break;
}
return nullptr;
}
#else
void DualWifiShutdown() {}
#endif
/////////////////////////////////////////////////////////////////////////////
//
// Service thread
@@ -2748,6 +3213,9 @@ void SteamNetworkingSocketsLowLevelDecRef()
// Shutdown event tracing
ETW_Kill();
// Shutdown Dual wifi support
DualWifiShutdown();
// Nuke sockets and COM
#ifdef _WIN32
::timeEndPeriod( 1 );
@@ -121,10 +121,33 @@ public:
/// get any further callbacks.
virtual void Close() = 0;
/// Check if Dual Wifi support is available, and if so, return the secondary socket.
/// The first time this is called, we will check for support and attempt to open a socket.
/// Thereafter, we will merely return the result of the first attempt
enum EDualWifiStatus
{
k_EDualWifi_NotAttempted,
k_EDualWifi_Done, // Failed or closed; don't try again
k_EDualWifi_Primary, // We're the primary. m_pDualWifiPartner is the secondary
k_EDualWifi_Secondary, // We're the secondary. m_pDualWifiPartner is the primary
};
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_DUALWIFI
virtual IRawUDPSocket *GetDualWifiSecondarySocket( int nEnableSetting ) = 0;
inline bool IsDualWifiSecondary() const { return m_eDualWifiStatus == k_EDualWifi_Secondary; }
#else
inline IRawUDPSocket *GetDualWifiSecondarySocket( int nEnableSetting ) { return nullptr; }
inline bool IsDualWifiSecondary() const { return false; }
#endif
/// The local address we ended up binding to
SteamNetworkingIPAddr m_boundAddr;
protected:
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_DUALWIFI
EDualWifiStatus m_eDualWifiStatus = k_EDualWifi_NotAttempted;
#endif
IRawUDPSocket();
virtual ~IRawUDPSocket();
};
@@ -1215,7 +1215,7 @@ bool CSteamNetworkConnectionBase::ProcessPlainTextDataChunk( int usecTimeSinceLa
//
// Also, note that order of operations is important. This call must
// happen after the SNP_RecordReceivedPktNum call above
m_statsEndToEnd.TrackProcessSequencedPacket( nPktNum, usecNow, usecTimeSinceLast );
m_statsEndToEnd.TrackProcessSequencedPacket( nPktNum, usecNow, usecTimeSinceLast, ctx.m_idxMultiPath );
// Packet can be processed further
return true;
@@ -191,6 +191,11 @@ struct SteamDatagramLinkLifetimeStats
int64 m_nPktsRecvDuplicate;
int64 m_nPktsRecvSequenceNumberLurch;
// Multipath stats
bool m_bMultiPathSendEnabled;
int64 m_nMultiPathRecvSeq[2];
int64 m_nMultiPathRecvLater[2];
// SNP message counters
int64 m_nMessagesSentReliable;
int64 m_nMessagesSentUnreliable;
@@ -580,6 +580,12 @@ struct LinkStatsTrackerBase
m_usecWhenTimeoutStarted = 0;
}
// For multi-path, we track some extra stats
uint64 m_recvPktNumberMaskMultiPath[2][2]; // Bitmask that we have received on either path
int64 m_nMultiPathRecvLater[2];
int64 m_nMultiPathRecvSeq[2];
bool m_bMultiPathSendEnabled;
//
// Quality metrics stats
//
@@ -874,10 +880,11 @@ protected:
// Hooks that derived classes may override when we process a packet
// and it meets certain characteristics
inline void InternalProcessSequencedPacket_Count()
inline void InternalProcessSequencedPacket_Count( int idxMultiPath )
{
m_seqPktCounters.OnRecv();
++m_nPktsRecvSequenced;
++m_nMultiPathRecvSeq[ idxMultiPath ];
}
void InternalProcessSequencedPacket_OutOfOrder( int64 nPktNum );
inline void InternalProcessSequencedPacket_Duplicate()
@@ -1115,7 +1122,7 @@ struct LinkStatsTracker final : public TLinkStatsTracker
/// This expands the wire packet number to its full value,
/// and checks if it is a duplicate or out of range.
/// Stats are also updated
int64 ExpandWirePacketNumberAndCheck( uint16 nWireSeqNum )
int64 ExpandWirePacketNumberAndCheck( uint16 nWireSeqNum, int idxMultiPath )
{
int16 nGap = (int16)( nWireSeqNum - (uint16)TLinkStatsTracker::m_nMaxRecvPktNum );
int64 nPktNum = TLinkStatsTracker::m_nMaxRecvPktNum + nGap;
@@ -1125,7 +1132,7 @@ struct LinkStatsTracker final : public TLinkStatsTracker
constexpr int N = V_ARRAYSIZE(TLinkStatsTracker::m_arDebugHistoryRecvSeqNum);
COMPILE_TIME_ASSERT( ( N & (N-1) ) == 0 );
TLinkStatsTracker::m_arDebugHistoryRecvSeqNum[ TLinkStatsTracker::m_nPktsRecvSequenced & (N-1) ] = nPktNum;
TLinkStatsTracker::InternalProcessSequencedPacket_Count();
TLinkStatsTracker::InternalProcessSequencedPacket_Count( idxMultiPath );
// Packet number is increasing?
// (Maybe by a lot -- we don't handle that here.)
@@ -1145,8 +1152,23 @@ struct LinkStatsTracker final : public TLinkStatsTracker
uint64 bit = uint64{1} << ( nPktNum & 63 );
if ( TLinkStatsTracker::m_recvPktNumberMask[ idxRecvBitmask ] & bit )
{
// Duplicate
TLinkStatsTracker::InternalProcessSequencedPacket_Duplicate();
// Duplicate. But if we haven't received it through this path yet,
// it's just because the packet got to us through the other path first.
if ( TLinkStatsTracker::m_recvPktNumberMaskMultiPath[idxMultiPath][idxRecvBitmask] & bit )
{
// Yes a true duplicate. (This will be the typical case,
// when dual-path is not available.)
TLinkStatsTracker::InternalProcessSequencedPacket_Duplicate();
}
else
{
// The other path beat us
++TLinkStatsTracker::m_nMultiPathRecvLater[ idxMultiPath ];
// Mark that we got it on this path, too
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[idxMultiPath][idxRecvBitmask] |= bit;
}
return 0;
}
@@ -1158,18 +1180,18 @@ struct LinkStatsTracker final : public TLinkStatsTracker
/// Same as ExpandWirePacketNumberAndCheck, but if this is the first sequenced
/// packet we have ever received, initialize the packet number
int64 ExpandWirePacketNumberAndCheckMaybeInitialize( uint16 nWireSeqNum )
int64 ExpandWirePacketNumberAndCheckMaybeInitialize( uint16 nWireSeqNum, int idxMultiPath )
{
if ( unlikely( TLinkStatsTracker::m_nMaxRecvPktNum == 0 ) )
TLinkStatsTracker::ResetMaxRecvPktNumForIncomingWirePktNum( nWireSeqNum );
return ExpandWirePacketNumberAndCheck( nWireSeqNum );
return ExpandWirePacketNumberAndCheck( nWireSeqNum, idxMultiPath );
}
/// Called when we have processed a packet with a sequence number, to update estimated
/// number of dropped packets, etc. This MUST only be called after we have
/// called ExpandWirePacketNumberAndCheck, to ensure that the packet number is not a
/// duplicate or out of range.
inline void TrackProcessSequencedPacket( int64 nPktNum, SteamNetworkingMicroseconds usecNow, int usecSenderTimeSincePrev )
inline void TrackProcessSequencedPacket( int64 nPktNum, SteamNetworkingMicroseconds usecNow, int usecSenderTimeSincePrev, int idxMultiPath )
{
Assert( nPktNum > 0 );
@@ -1183,18 +1205,25 @@ struct LinkStatsTracker final : public TLinkStatsTracker
{
// Crossed to the next 64-packet block. Shift bitmasks forward by one.
TLinkStatsTracker::m_recvPktNumberMask[0] = TLinkStatsTracker::m_recvPktNumberMask[1];
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[0][0] = TLinkStatsTracker::m_recvPktNumberMaskMultiPath[0][1];
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[1][0] = TLinkStatsTracker::m_recvPktNumberMaskMultiPath[1][1];
}
else
{
// Large packet number jump, we skipped a whole block
TLinkStatsTracker::m_recvPktNumberMask[0] = 0;
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[0][0] = 0;
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[1][0] = 0;
}
TLinkStatsTracker::m_recvPktNumberMask[1] = 0;
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[0][1] = 0;
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[1][1] = 0;
idxRecvBitmask = 1;
}
uint64 bit = uint64{1} << ( nPktNum & 63 );
Assert( !( TLinkStatsTracker::m_recvPktNumberMask[ idxRecvBitmask ] & bit ) ); // Should not have already been marked! We should have already discarded duplicates
TLinkStatsTracker::m_recvPktNumberMask[ idxRecvBitmask ] |= bit;
TLinkStatsTracker::m_recvPktNumberMaskMultiPath[idxMultiPath][idxRecvBitmask] |= bit;
// Check for dropped packet. Since we hope that by far the most common
// case will be packets delivered in order, we optimize this logic
@@ -80,6 +80,23 @@
#endif
#endif
//// Don't enable DualSTA support, even on Windows. It's not useful
//// right now because it only works for relayed connections. We'll want
//// to enable this once it's working for plain IP connections, too.
//#ifdef _WINDOWS
// #define STEAMNETWORKINGSOCKETS_ENABLE_DUALWIFI
//#endif
enum EDualWifiEnable {
k_nDualWifiEnable_Disable = 0,
k_nDualWifiEnable_Enable = 1, //
k_nDualWifiEnable_DoNotEnumerate = 2, // Enumerate primary adapters, but don't actually try to enable any Dual Wifi support
k_nDualWifiEnable_DoNotBind = 3, // Try to turn on Dual Wifi and locate the secondary adapter, but don't actually bind
k_nDualWifiEnable_ForceSimulate = 4, // Don't really do any DualWifi work, just open up another "regular" socket
k_nDualWifiEnable_MAX = k_nDualWifiEnable_ForceSimulate // Maximum legal value
};
/// Enumerate different kinds of transport that can be used
enum ESteamNetTransportKind
{
@@ -809,6 +826,9 @@ struct ConnectionConfig
ConfigValue<int32> m_EnableDiagnosticsUI;
#endif
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_DUALWIFI
ConfigValue<int32> m_DualWifi_Enable;
#endif
ConfigValue<int32> m_LogLevel_AckRTT;
ConfigValue<int32> m_LogLevel_PacketDecode;
@@ -154,6 +154,9 @@ void LinkStatsTrackerBase::InitInternal( SteamNetworkingMicroseconds usecNow )
m_flInPacketsWeirdSequencePct = -1.0f;
m_usecMaxJitterPreviousInterval = -1;
m_nPktsRecvSequenced = 0;
m_nMultiPathRecvSeq[0] = m_nMultiPathRecvSeq[1] = 0;
m_nMultiPathRecvLater[0] = m_nMultiPathRecvLater[1] = 0;
m_bMultiPathSendEnabled = false;
m_nDebugPktsRecvInOrder = 0;
m_nPktsRecvDroppedAccumulator = 0;
m_nPktsRecvOutOfOrderAccumulator = 0;
@@ -314,6 +317,9 @@ void LinkStatsTrackerBase::InitMaxRecvPktNum( int64 nPktNum )
else
m_recvPktNumberMask[1] = ( (uint64)1 << nBitsToSet ) - 1;
m_recvPktNumberMaskMultiPath[0][0] = m_recvPktNumberMaskMultiPath[1][0] = m_recvPktNumberMask[0];
m_recvPktNumberMaskMultiPath[0][1] = m_recvPktNumberMaskMultiPath[1][1] = m_recvPktNumberMask[1];
m_nDebugLastInitMaxRecvPktNum = nPktNum;
}
@@ -555,6 +561,10 @@ void LinkStatsTrackerBase::GetLifetimeStats( SteamDatagramLinkLifetimeStats &s )
s.m_nPktsRecvDuplicate = PktsRecvDuplicate();
s.m_nPktsRecvSequenceNumberLurch = PktsRecvLurch();
s.m_bMultiPathSendEnabled = m_bMultiPathSendEnabled;
s.m_nMultiPathRecvSeq[0] = m_nMultiPathRecvSeq[0]; s.m_nMultiPathRecvSeq[1] = m_nMultiPathRecvSeq[1];
s.m_nMultiPathRecvLater[0] = m_nMultiPathRecvLater[0]; s.m_nMultiPathRecvLater[1] = m_nMultiPathRecvLater[1];
s.m_qualityHistogram = m_qualityHistogram;
s.m_nQualityNtile50th = m_qualitySample.NumSamples() < 2 ? -1 : m_qualitySample.GetPercentile( .50f );
@@ -823,6 +833,16 @@ void LinkStatsLifetimeStructToMsg( const SteamDatagramLinkLifetimeStats &s, CMsg
msg.set_packets_recv_duplicate( s.m_nPktsRecvDuplicate );
msg.set_packets_recv_lurch( s.m_nPktsRecvSequenceNumberLurch );
if ( s.m_bMultiPathSendEnabled )
msg.set_multipath_send_enabled( 1 );
if ( s.m_bMultiPathSendEnabled || s.m_nMultiPathRecvSeq[1] > 0 )
{
msg.add_multipath_packets_recv_sequenced( s.m_nMultiPathRecvSeq[0] );
msg.add_multipath_packets_recv_sequenced( s.m_nMultiPathRecvSeq[1] );
msg.add_multipath_packets_recv_later( s.m_nMultiPathRecvLater[0] );
msg.add_multipath_packets_recv_later( s.m_nMultiPathRecvLater[1] );
}
#define SET_HISTOGRAM( mbr, field ) if ( mbr > 0 ) msg.set_ ## field( mbr );
#define SET_NTILE( mbr, field ) if ( mbr >= 0 ) msg.set_ ## field( mbr );
@@ -917,6 +937,12 @@ void LinkStatsLifetimeMsgToStruct( const CMsgSteamDatagramLinkLifetimeStats &msg
s.m_nPktsRecvDuplicate = msg.packets_recv_duplicate();
s.m_nPktsRecvSequenceNumberLurch = msg.packets_recv_lurch();
s.m_bMultiPathSendEnabled = msg.multipath_send_enabled() > 0;
s.m_nMultiPathRecvSeq[0] = ( msg.multipath_packets_recv_sequenced_size() > 0 ) ? msg.multipath_packets_recv_sequenced(0) : 0;
s.m_nMultiPathRecvSeq[1] = ( msg.multipath_packets_recv_sequenced_size() > 1 ) ? msg.multipath_packets_recv_sequenced(1) : 0;
s.m_nMultiPathRecvLater[0] = ( msg.multipath_packets_recv_later_size() > 0 ) ? msg.multipath_packets_recv_later(0) : 0;
s.m_nMultiPathRecvLater[1] = ( msg.multipath_packets_recv_later_size() > 1 ) ? msg.multipath_packets_recv_later(1) : 0;
#define SET_HISTOGRAM( mbr, field ) mbr = msg.field();
#define SET_NTILE( mbr, field ) mbr = ( msg.has_ ## field() ? msg.field() : -1 );
@@ -1059,6 +1085,7 @@ void LinkStatsPrintLifetimeToBuf( const char *pszLeader, const SteamDatagramLink
buf.Printf( "%sTotals\n", pszLeader );
buf.Printf( "%s Sent:%11s pkts %15s bytes\n", pszLeader, NumberPrettyPrinter( stats.m_nPacketsSent ).String(), NumberPrettyPrinter( stats.m_nBytesSent ).String() );
buf.Printf( "%s Recv:%11s pkts %15s bytes\n", pszLeader, NumberPrettyPrinter( stats.m_nPacketsRecv ).String(), NumberPrettyPrinter( stats.m_nBytesRecv ).String() );
if ( stats.m_nPktsRecvSequenced > 0 )
{
buf.Printf( "%s Recv w seq:%11s pkts\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvSequenced ).String() );
@@ -1067,6 +1094,17 @@ void LinkStatsPrintLifetimeToBuf( const char *pszLeader, const SteamDatagramLink
buf.Printf( "%s OutOfOrder:%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvOutOfOrder ).String(), stats.m_nPktsRecvOutOfOrder * flToPct );
buf.Printf( "%s Duplicate :%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvDuplicate ).String(), stats.m_nPktsRecvDuplicate * flToPct );
buf.Printf( "%s SeqLurch :%11s pkts%7.2f%%\n", pszLeader, NumberPrettyPrinter( stats.m_nPktsRecvSequenceNumberLurch ).String(), stats.m_nPktsRecvSequenceNumberLurch * flToPct );
if ( stats.m_nMultiPathRecvSeq[1] > 0 )
{
AssertMsg( stats.m_nMultiPathRecvSeq[0] + stats.m_nMultiPathRecvSeq[1] == stats.m_nPktsRecvSequenced, "multipath seq bookkeeping error %lld + %lld != %lld",
(long long)stats.m_nMultiPathRecvSeq[0], (long long)stats.m_nMultiPathRecvSeq[1], (long long)stats.m_nPktsRecvSequenced );
buf.Printf( "%s Pth0 w seq:%11s pkts\n", pszLeader, NumberPrettyPrinter( stats.m_nMultiPathRecvSeq[0] ).String() );
buf.Printf( "%s Pth1 w seq:%11s pkts\n", pszLeader, NumberPrettyPrinter( stats.m_nMultiPathRecvSeq[1] ).String() );
buf.Printf( "%s Pth0 later:%11s pkts\n", pszLeader, NumberPrettyPrinter( stats.m_nMultiPathRecvLater[0] ).String() );
buf.Printf( "%s Pth1 later:%11s pkts\n", pszLeader, NumberPrettyPrinter( stats.m_nMultiPathRecvLater[1] ).String() );
}
}
// Do we have enough ping samples such that the distribution might be interesting