Improve P2P mocking framework

There can be more than one gateway.  (E.g. cell connection and local
internet connection.)  Each interface is assigned to one gateway,
or none of the interface is public.

Add ability to tag an adapter as non-functioning.

Simulate network latency.

Add more test cases.
This commit is contained in:
Fletcher Dunn
2026-05-17 19:48:59 -07:00
parent 0e18b7dd57
commit d790bd66e2
4 changed files with 304 additions and 113 deletions
@@ -11,42 +11,73 @@
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_MOCK
struct TEST_mocknetwork_interface_t
{
SteamNetworkingIPAddr m_ip; // port not relevant and must be zero
int m_nSendLatencyMS = 0;
};
enum class TEST_mocknetwork_nat_type
{
// Internal IP:port mapped to public IP:port. Any peer can send to the public port.
FullCone,
// IPs are not translated at all, they are "public IPs"
None,
// Inbound traffic to public port from IP X will only be forwarded if
// an outbound packet was previously sent to X (but any port).
RestrictedCone,
// Internal IP:port mapped to public IP:port. Any peer can send to the
// public port
FullCone,
// Inbound traffic to public port from IP X and port P will only be
// forwarded if an outbound packet was previously sent to X *and* port P.
PortRestrictedCone,
// Inbound traffic to public port from IP X will only be forwarded if
// an inbound traffic was previously sent to X(but any port)
RestrictedCone,
// Every unique remote IP:port gets a separate external port.
Symmetric,
};
// Inbound traffic to public port from IP X and port P will only be
// forwarded if an inbound traffic was previously sent to X *and* port P
PortRestrictedCone,
struct TEST_mocknetwork_gateway_t
{
SteamNetworkingIPAddr m_ipv4_public; // NAT public IP (127.0.100.x); port must be zero
// Every internal IP:port <-> remote IP:port generates a new NAT port
Symmetric,
TEST_mocknetwork_nat_type m_natType = TEST_mocknetwork_nat_type::FullCone;
// Whether the gateway supports hairpinning: internally-originated packets addressed
// to another host on the same gateway's public IP are looped back internally.
// Many consumer routers do NOT support this. (Not yet implemented; reserved for future use.)
bool m_bHairpinSupported = true;
// Latency from the local host to this gateway's exit point.
// Models VPN-style scenarios where the exit node is geographically distant.
int m_nInternalLatencyMS = 0;
// Extra latency applied to all packets leaving this gateway to the public internet.
// Models WAN/ISP link quality; applies to all destinations equally (including STUN servers).
int m_nExternalLatencyMS = 0;
};
struct TEST_mocknetwork_interface_t
{
SteamNetworkingIPAddr m_ip; // local IP to bind on; port must be zero
// Index into TEST_mocknetwork_config_t::m_vecGateways, or -1 if this is a
// public-facing interface (no NAT). Public interfaces must use an IP in the
// 127.0.100.x range.
int m_iGateway = -1;
// Artificial one-way latency applied to every outbound packet on this interface.
// Models local link quality (e.g. WiFi jitter vs Ethernet).
int m_nSendLatencyMS = 0;
// If false, the interface is "down": no packets are sent or received.
bool m_bEnabled = true;
};
struct TEST_mocknetwork_config_t
{
std::vector<TEST_mocknetwork_interface_t> m_vecInterfaces;
SteamNetworkingIPAddr m_ipv4_gateway; // Port not relevant and must be zero
TEST_mocknetwork_nat_type m_natType = TEST_mocknetwork_nat_type::None;
// NAT gateways. Interfaces with m_iGateway >= 0 route outbound traffic through
// the corresponding entry here.
std::vector<TEST_mocknetwork_gateway_t> m_vecGateways;
// Network interfaces on this host. Public interfaces (m_iGateway == -1) must use
// addresses in the 127.0.100.x range. Private interfaces use 127.0.X.x (X != 100).
// The same 127.0.X subnet can be shared across interfaces to model hosts on the same LAN.
std::vector<TEST_mocknetwork_interface_t> m_vecInterfaces;
};
void TEST_mocknetwork_init( const TEST_mocknetwork_config_t &info );
void TEST_mocknetwork_init( const TEST_mocknetwork_config_t &config );
extern bool TEST_mocknetwork_active;
@@ -1754,6 +1754,10 @@ public:
if ( !m_pSockLocal )
return false;
// Drop all packets if this interface is disabled
if ( !m_ifaceConfig.m_bEnabled )
return true;
DbgAssert( m_boundAddr == m_pSockLocal->m_boundAddr );
if ( adrTo.GetType() == k_EIPTypeV4 )
@@ -1772,11 +1776,10 @@ public:
return const_cast<CUDPSocketMock *>(this)->BCreateNATAndSend( nChunks, pChunks, adrTo, ecn );
const uint32 net_local = m_boundAddr.GetIPv4() & 0xFF00;
if ( net_local == net_remote )
{
// Same 'LAN', send directly
return m_pSockLocal->BSendRawPacketGather( nChunks, pChunks, adrTo, ecn );
// Same LAN send directly with interface latency only (no gateway involved)
return SendDelayed( m_pSockLocal, nChunks, pChunks, adrTo, ecn, m_ifaceConfig.m_nSendLatencyMS );
}
}
@@ -1795,6 +1798,22 @@ public:
protected:
TEST_mocknetwork_interface_t m_ifaceConfig;
const TEST_mocknetwork_gateway_t *m_pGatewayConfig; // null for public interfaces (no NAT)
CUDPSocketMock( const TEST_mocknetwork_interface_t &ifaceConfig, const TEST_mocknetwork_gateway_t *pGatewayConfig )
: m_ifaceConfig( ifaceConfig ), m_pGatewayConfig( pGatewayConfig ) {}
// Total one-way latency for packets going to the public internet via this interface.
// Sums interface send latency + gateway internal latency (VPN tunnel) + gateway external latency (WAN).
int GetPublicSendLatencyMS() const
{
int ms = m_ifaceConfig.m_nSendLatencyMS;
if ( m_pGatewayConfig )
ms += m_pGatewayConfig->m_nInternalLatencyMS + m_pGatewayConfig->m_nExternalLatencyMS;
return ms;
}
// The internal (LAN-side) socket
CRawUDPSocketImpl *m_pSockLocal = nullptr;
@@ -1810,27 +1829,51 @@ protected:
{
if ( !m_userCallback.m_fnCallback )
return;
if ( m_pGatewayConfig && m_pGatewayConfig->m_nInternalLatencyMS > 0 )
{
iovec iov_buf;
iov_buf.iov_base = (void *)info.m_pPkt;
iov_buf.iov_len = info.m_cbPkt;
SteamNetworkingMicroseconds usecDeliverAt = SteamNetworkingSockets_GetLocalTimestamp() + (SteamNetworkingMicroseconds)m_pGatewayConfig->m_nInternalLatencyMS * 1000;
s_packetLagQueueRecv.LagPacket( m_pSockLocal, info.m_adrFrom, usecDeliverAt, 1, &iov_buf, info.m_tos );
return;
}
RecvPktInfo_t fixedInfo = info;
fixedInfo.m_pSock = this;
m_userCallback( fixedInfo );
}
// Allocate a port on the public gateway IP for a NAT entry
static CRawUDPSocketImpl *CreateExternalSock( CRecvPacketCallback callback )
// Allocate a port on this interface's gateway public IP for a NAT entry
CRawUDPSocketImpl *CreateExternalSock( CRecvPacketCallback callback )
{
Assert( s_mockNetworkConfig.m_ipv4_gateway.IsIPv4() );
Assert( s_mockNetworkConfig.m_ipv4_gateway.m_port == 0 );
SteamNetworkingIPAddr addrLocal = s_mockNetworkConfig.m_ipv4_gateway;
Assert( m_pGatewayConfig );
Assert( m_pGatewayConfig->m_ipv4_public.IsIPv4() );
Assert( m_pGatewayConfig->m_ipv4_public.m_port == 0 );
SteamNetworkingIPAddr addrGateway = m_pGatewayConfig->m_ipv4_public;
SteamNetworkingErrMsg errMsg;
CRawUDPSocketImpl *pSock = OpenRawUDPSocketInternal( callback, errMsg, &addrLocal, nullptr );
CRawUDPSocketImpl *pSock = OpenRawUDPSocketInternal( callback, errMsg, &addrGateway, nullptr );
if ( !pSock )
SpewError( "Failed to create external socket for NAT. %s\n", errMsg );
return pSock;
}
// Send a packet, optionally delaying delivery by nDelayMS milliseconds.
// NAT bookkeeping must be completed before calling this; only the wire send is delayed.
bool SendDelayed( CRawUDPSocketImpl *pSock, int nChunks, const iovec *pChunks, const netadr_t &adrTo, int ecn, int nDelayMS ) const
{
if ( nDelayMS <= 0 )
return pSock->BSendRawPacketGather( nChunks, pChunks, adrTo, ecn );
SteamNetworkingMicroseconds usecDeliverAt = SteamNetworkingSockets_GetLocalTimestamp() + (SteamNetworkingMicroseconds)nDelayMS * 1000;
s_packetLagQueueSend.LagPacket( pSock, adrTo, usecDeliverAt, nChunks, pChunks, ecn == -1 ? 0xff : (uint8)ecn );
return true;
}
private:
static void StaticRecvInternalPacketThunk( const RecvPktInfo_t &info, CUDPSocketMock *pSelf )
{
if ( !pSelf->m_ifaceConfig.m_bEnabled )
return;
// Fix up m_pSock to point to this mock socket, not the internal one, so replies route correctly
RecvPktInfo_t fixedInfo = info;
fixedInfo.m_pSock = pSelf;
@@ -1838,14 +1881,16 @@ private:
}
};
// No NAT -- local IP is already on the public network
class CUDPSocketMock_None final : public CUDPSocketMock
// Public interface — local IP is already on the public network, no NAT
class CUDPSocketMock_Public final : public CUDPSocketMock
{
public:
CUDPSocketMock_Public( const TEST_mocknetwork_interface_t &iface )
: CUDPSocketMock( iface, nullptr ) {}
virtual bool BCreateNATAndSend( int nChunks, const iovec *pChunks, const netadr_t &adrTo, int ecn ) override
{
// No NAT, send directly
return m_pSockLocal->BSendRawPacketGather( nChunks, pChunks, adrTo, ecn );
return SendDelayed( m_pSockLocal, nChunks, pChunks, adrTo, ecn, m_ifaceConfig.m_nSendLatencyMS );
}
};
@@ -1854,13 +1899,12 @@ public:
class CUDPSocketMock_OnePublicPort final : public CUDPSocketMock
{
public:
const TEST_mocknetwork_nat_type m_eNATType;
CUDPSocketMock_OnePublicPort( TEST_mocknetwork_nat_type eNATType ) : m_eNATType( eNATType )
CUDPSocketMock_OnePublicPort( const TEST_mocknetwork_interface_t &iface, const TEST_mocknetwork_gateway_t &gw )
: CUDPSocketMock( iface, &gw )
{
Assert( m_eNATType == TEST_mocknetwork_nat_type::FullCone
|| m_eNATType == TEST_mocknetwork_nat_type::RestrictedCone
|| m_eNATType == TEST_mocknetwork_nat_type::PortRestrictedCone );
Assert( gw.m_natType == TEST_mocknetwork_nat_type::FullCone
|| gw.m_natType == TEST_mocknetwork_nat_type::RestrictedCone
|| gw.m_natType == TEST_mocknetwork_nat_type::PortRestrictedCone );
}
virtual void Close() override
@@ -1898,25 +1942,27 @@ private:
}
// Track destinations for restricted NAT types
if ( m_eNATType != TEST_mocknetwork_nat_type::FullCone )
TEST_mocknetwork_nat_type eNATType = m_pGatewayConfig->m_natType;
if ( eNATType != TEST_mocknetwork_nat_type::FullCone )
{
netadr_t adrRecordSent = adrTo;
if ( m_eNATType == TEST_mocknetwork_nat_type::RestrictedCone )
if ( eNATType == TEST_mocknetwork_nat_type::RestrictedCone )
adrRecordSent.SetPort(0);
if ( std::find( m_vecAdrsSentTo.begin(), m_vecAdrsSentTo.end(), adrRecordSent ) == m_vecAdrsSentTo.end() )
m_vecAdrsSentTo.push_back( adrRecordSent );
}
return m_pSockExternal->BSendRawPacketGather( nChunks, pChunks, adrTo, ecn );
return SendDelayed( m_pSockExternal, nChunks, pChunks, adrTo, ecn, GetPublicSendLatencyMS() );
}
void RecvPacketExternal( const RecvPktInfo_t &info )
{
// For restricted NAT types, drop packets from addresses we haven't sent to
if ( m_eNATType != TEST_mocknetwork_nat_type::FullCone )
TEST_mocknetwork_nat_type eNATType = m_pGatewayConfig->m_natType;
if ( eNATType != TEST_mocknetwork_nat_type::FullCone )
{
netadr_t adrCheck = info.m_adrFrom;
if ( m_eNATType == TEST_mocknetwork_nat_type::RestrictedCone )
if ( eNATType == TEST_mocknetwork_nat_type::RestrictedCone )
adrCheck.SetPort(0);
if ( std::find( m_vecAdrsSentTo.begin(), m_vecAdrsSentTo.end(), adrCheck ) == m_vecAdrsSentTo.end() )
{
@@ -1934,6 +1980,10 @@ private:
// Symmetric NAT: every unique remote destination gets a separate external port
class CUDPSocketMock_Symmetric final : public CUDPSocketMock
{
public:
CUDPSocketMock_Symmetric( const TEST_mocknetwork_interface_t &iface, const TEST_mocknetwork_gateway_t &gw )
: CUDPSocketMock( iface, &gw ) {}
virtual void Close() override
{
m_vecNATEntries.clear(); // NATEntry_t destructor closes external sockets
@@ -2003,7 +2053,7 @@ private:
m_vecNATEntries.push_back( std::move( pNewEntry ) );
}
return pNatEntry->m_pSockExternal->BSendRawPacketGather( nChunks, pChunks, adrTo, ecn );
return SendDelayed( pNatEntry->m_pSockExternal, nChunks, pChunks, adrTo, ecn, GetPublicSendLatencyMS() );
}
};
@@ -2015,35 +2065,62 @@ IRawUDPSocket *OpenRawUDPSocket( CRecvPacketCallback callback, SteamNetworkingEr
#if STEAMNETWORKINGSOCKETS_ENABLE_MOCK
if ( TEST_mocknetwork_active )
{
// Determine the local address to bind to
SteamNetworkingIPAddr addrLocal;
// Find the matching interface config by address
const TEST_mocknetwork_interface_t *pIfaceConfig = nullptr;
if ( pAddrLocal && pAddrLocal->IsIPv4() && pAddrLocal->GetIPv4() != 0 )
addrLocal = *pAddrLocal;
{
uint32 nLookupIP = pAddrLocal->GetIPv4();
for ( const TEST_mocknetwork_interface_t &iface : s_mockNetworkConfig.m_vecInterfaces )
{
if ( iface.m_ip.IsIPv4() && iface.m_ip.GetIPv4() == nLookupIP )
{
pIfaceConfig = &iface;
break;
}
}
if ( !pIfaceConfig )
{
V_sprintf_safe( errMsg, "Mock: no interface configured for %s", SteamNetworkingIPAddrRender( *pAddrLocal ).c_str() );
return nullptr;
}
}
else
{
Assert( !s_mockNetworkConfig.m_vecInterfaces.empty() );
addrLocal = s_mockNetworkConfig.m_vecInterfaces[0].m_ip;
pIfaceConfig = &s_mockNetworkConfig.m_vecInterfaces[0];
}
// Create the appropriate mock socket type based on NAT config
CUDPSocketMock *pMock;
switch ( s_mockNetworkConfig.m_natType )
// Look up gateway config (null for public interfaces)
const TEST_mocknetwork_gateway_t *pGatewayConfig = nullptr;
if ( pIfaceConfig->m_iGateway >= 0 )
{
default:
case TEST_mocknetwork_nat_type::None:
pMock = new CUDPSocketMock_None();
break;
case TEST_mocknetwork_nat_type::FullCone:
case TEST_mocknetwork_nat_type::RestrictedCone:
case TEST_mocknetwork_nat_type::PortRestrictedCone:
pMock = new CUDPSocketMock_OnePublicPort( s_mockNetworkConfig.m_natType );
break;
case TEST_mocknetwork_nat_type::Symmetric:
pMock = new CUDPSocketMock_Symmetric();
break;
Assert( pIfaceConfig->m_iGateway < (int)s_mockNetworkConfig.m_vecGateways.size() );
pGatewayConfig = &s_mockNetworkConfig.m_vecGateways[ pIfaceConfig->m_iGateway ];
}
if ( !pMock->BindLocal( callback, errMsg, addrLocal ) )
// Create the appropriate mock socket type
CUDPSocketMock *pMock;
if ( !pGatewayConfig )
{
pMock = new CUDPSocketMock_Public( *pIfaceConfig );
}
else
{
switch ( pGatewayConfig->m_natType )
{
default:
case TEST_mocknetwork_nat_type::FullCone:
case TEST_mocknetwork_nat_type::RestrictedCone:
case TEST_mocknetwork_nat_type::PortRestrictedCone:
pMock = new CUDPSocketMock_OnePublicPort( *pIfaceConfig, *pGatewayConfig );
break;
case TEST_mocknetwork_nat_type::Symmetric:
pMock = new CUDPSocketMock_Symmetric( *pIfaceConfig, *pGatewayConfig );
break;
}
}
if ( !pMock->BindLocal( callback, errMsg, pIfaceConfig->m_ip ) )
{
delete pMock;
return nullptr;
@@ -3838,7 +3915,10 @@ bool GetLocalAddresses( CUtlVector< SteamNetworkingIPAddr >* pAddrs )
if ( TEST_mocknetwork_active )
{
for ( const TEST_mocknetwork_interface_t &iface : s_mockNetworkConfig.m_vecInterfaces )
pAddrs->AddToTail( iface.m_ip );
{
if ( iface.m_bEnabled )
pAddrs->AddToTail( iface.m_ip );
}
return true;
}
#endif
@@ -4038,21 +4118,41 @@ void TEST_mocknetwork_init( const TEST_mocknetwork_config_t &config )
s_mockNetworkConfig = config;
TEST_mocknetwork_active = true;
const char *pszNATType = "???";
switch ( config.m_natType )
SpewMsg( "Mock network active.\n" );
for ( int i = 0; i < (int)config.m_vecGateways.size(); ++i )
{
case TEST_mocknetwork_nat_type::None: pszNATType = "None (public IP)"; break;
case TEST_mocknetwork_nat_type::FullCone: pszNATType = "Full Cone"; break;
case TEST_mocknetwork_nat_type::RestrictedCone: pszNATType = "Restricted Cone"; break;
case TEST_mocknetwork_nat_type::PortRestrictedCone: pszNATType = "Port Restricted Cone"; break;
case TEST_mocknetwork_nat_type::Symmetric: pszNATType = "Symmetric"; break;
const TEST_mocknetwork_gateway_t &gw = config.m_vecGateways[i];
const char *pszNATType = "???";
switch ( gw.m_natType )
{
case TEST_mocknetwork_nat_type::FullCone: pszNATType = "full-cone"; break;
case TEST_mocknetwork_nat_type::RestrictedCone: pszNATType = "restricted-cone"; break;
case TEST_mocknetwork_nat_type::PortRestrictedCone: pszNATType = "port-restricted-cone"; break;
case TEST_mocknetwork_nat_type::Symmetric: pszNATType = "symmetric"; break;
}
SpewMsg( " Gateway[%d]: %s NAT=%s int=%dms ext=%dms\n",
i, SteamNetworkingIPAddrRender( gw.m_ipv4_public, false ).c_str(),
pszNATType, gw.m_nInternalLatencyMS, gw.m_nExternalLatencyMS );
}
SpewMsg( "Mock network active. NAT type: %s\n", pszNATType );
if ( config.m_ipv4_gateway.IsIPv4() )
SpewMsg( " Gateway: %s\n", SteamNetworkingIPAddrRender( config.m_ipv4_gateway, false ).c_str() );
for ( const TEST_mocknetwork_interface_t &iface : config.m_vecInterfaces )
SpewMsg( " Adapter: %s\n", SteamNetworkingIPAddrRender( iface.m_ip, false ).c_str() );
{
if ( iface.m_bEnabled )
{
if ( iface.m_iGateway >= 0 )
SpewMsg( " Adapter: %s gw[%d] latency=%dms\n",
SteamNetworkingIPAddrRender( iface.m_ip, false ).c_str(),
iface.m_iGateway, iface.m_nSendLatencyMS );
else
SpewMsg( " Adapter: %s (public) latency=%dms\n",
SteamNetworkingIPAddrRender( iface.m_ip, false ).c_str(),
iface.m_nSendLatencyMS );
}
else
{
SpewMsg( " Adapter: %s (DISABLED)\n",
SteamNetworkingIPAddrRender( iface.m_ip, false ).c_str() );
}
}
}
#endif // STEAMNETWORKINGSOCKETS_ENABLE_MOCK
+58 -27
View File
@@ -49,11 +49,17 @@ void PrintUsage()
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_MOCK
"\n"
"Mock network options:\n"
" --mock-adapter <ip> Add a mock network adapter IP (repeatable)\n"
" --mock-gateway <ip> Gateway/public IP for mock network\n"
" (required if --mock-nat is not 'none')\n"
" --mock-nat <type> NAT type: none (default), full-cone,\n"
" --mock-adapter <ip> Add a mock network adapter (repeatable).\n"
" Assigned to the most recently declared gateway,\n"
" or public (no NAT) if no gateway declared yet.\n"
" --mock-latency <ms> One-way send latency for the last --mock-adapter.\n"
" --mock-disabled Mark the last --mock-adapter as down.\n"
" --mock-gateway <ip> Declare a NAT gateway with this public IP.\n"
" Subsequent --mock-adapters are assigned to it.\n"
" --mock-nat <type> NAT type for last gateway: full-cone (default),\n"
" restricted-cone, port-restricted-cone, symmetric\n"
" --mock-internal-latency <ms> VPN-tunnel latency for last gateway (host->exit).\n"
" --mock-external-latency <ms> WAN latency for last gateway (exit->internet).\n"
#endif
);
}
@@ -267,6 +273,45 @@ int main( int argc, const char **argv )
g_eTestP2PRendezvousLogLevel = ParseLogLevelValue( pszArg, "--loglevel-p2prendezvous" );
}
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_MOCK
else if ( !strcmp( pszSwitch, "--mock-gateway" ) )
{
const char *pszArg = GetArg();
TEST_mocknetwork_gateway_t gw;
if ( !gw.m_ipv4_public.ParseString( pszArg ) || !gw.m_ipv4_public.IsIPv4() )
TEST_Fatal( "'%s' is not a valid IPv4 address for --mock-gateway", pszArg );
gw.m_ipv4_public.m_port = 0;
mockConfig.m_vecGateways.push_back( gw );
}
else if ( !strcmp( pszSwitch, "--mock-nat" ) )
{
if ( mockConfig.m_vecGateways.empty() )
TEST_Fatal( "--mock-nat must follow --mock-gateway" );
const char *pszArg = GetArg();
TEST_mocknetwork_nat_type eNATType;
if ( !strcmp( pszArg, "full-cone" ) )
eNATType = TEST_mocknetwork_nat_type::FullCone;
else if ( !strcmp( pszArg, "restricted-cone" ) )
eNATType = TEST_mocknetwork_nat_type::RestrictedCone;
else if ( !strcmp( pszArg, "port-restricted-cone" ) )
eNATType = TEST_mocknetwork_nat_type::PortRestrictedCone;
else if ( !strcmp( pszArg, "symmetric" ) )
eNATType = TEST_mocknetwork_nat_type::Symmetric;
else
TEST_Fatal( "Invalid --mock-nat '%s'. Expected: full-cone, restricted-cone, port-restricted-cone, symmetric", pszArg );
mockConfig.m_vecGateways.back().m_natType = eNATType;
}
else if ( !strcmp( pszSwitch, "--mock-internal-latency" ) )
{
if ( mockConfig.m_vecGateways.empty() )
TEST_Fatal( "--mock-internal-latency must follow --mock-gateway" );
mockConfig.m_vecGateways.back().m_nInternalLatencyMS = atoi( GetArg() );
}
else if ( !strcmp( pszSwitch, "--mock-external-latency" ) )
{
if ( mockConfig.m_vecGateways.empty() )
TEST_Fatal( "--mock-external-latency must follow --mock-gateway" );
mockConfig.m_vecGateways.back().m_nExternalLatencyMS = atoi( GetArg() );
}
else if ( !strcmp( pszSwitch, "--mock-adapter" ) )
{
const char *pszArg = GetArg();
@@ -274,30 +319,20 @@ int main( int argc, const char **argv )
if ( !iface.m_ip.ParseString( pszArg ) || !iface.m_ip.IsIPv4() )
TEST_Fatal( "'%s' is not a valid IPv4 address for --mock-adapter", pszArg );
iface.m_ip.m_port = 0;
iface.m_iGateway = mockConfig.m_vecGateways.empty() ? -1 : (int)mockConfig.m_vecGateways.size() - 1;
mockConfig.m_vecInterfaces.push_back( iface );
}
else if ( !strcmp( pszSwitch, "--mock-gateway" ) )
else if ( !strcmp( pszSwitch, "--mock-latency" ) )
{
const char *pszArg = GetArg();
if ( !mockConfig.m_ipv4_gateway.ParseString( pszArg ) || !mockConfig.m_ipv4_gateway.IsIPv4() )
TEST_Fatal( "'%s' is not a valid IPv4 address for --mock-gateway", pszArg );
mockConfig.m_ipv4_gateway.m_port = 0;
if ( mockConfig.m_vecInterfaces.empty() )
TEST_Fatal( "--mock-latency must follow --mock-adapter" );
mockConfig.m_vecInterfaces.back().m_nSendLatencyMS = atoi( GetArg() );
}
else if ( !strcmp( pszSwitch, "--mock-nat" ) )
else if ( !strcmp( pszSwitch, "--mock-disabled" ) )
{
const char *pszArg = GetArg();
if ( !strcmp( pszArg, "none" ) )
mockConfig.m_natType = TEST_mocknetwork_nat_type::None;
else if ( !strcmp( pszArg, "full-cone" ) )
mockConfig.m_natType = TEST_mocknetwork_nat_type::FullCone;
else if ( !strcmp( pszArg, "restricted-cone" ) )
mockConfig.m_natType = TEST_mocknetwork_nat_type::RestrictedCone;
else if ( !strcmp( pszArg, "port-restricted-cone" ) )
mockConfig.m_natType = TEST_mocknetwork_nat_type::PortRestrictedCone;
else if ( !strcmp( pszArg, "symmetric" ) )
mockConfig.m_natType = TEST_mocknetwork_nat_type::Symmetric;
else
TEST_Fatal( "Invalid --mock-nat '%s'. Expected: none, full-cone, restricted-cone, port-restricted-cone, symmetric", pszArg );
if ( mockConfig.m_vecInterfaces.empty() )
TEST_Fatal( "--mock-disabled must follow --mock-adapter" );
mockConfig.m_vecInterfaces.back().m_bEnabled = false;
}
#endif
else if ( !strcmp( pszSwitch, "--help" ) || !strcmp( pszSwitch, "-h" ) )
@@ -318,11 +353,7 @@ int main( int argc, const char **argv )
#ifdef STEAMNETWORKINGSOCKETS_ENABLE_MOCK
if ( !mockConfig.m_vecInterfaces.empty() )
{
if ( mockConfig.m_natType != TEST_mocknetwork_nat_type::None && !mockConfig.m_ipv4_gateway.IsIPv4() )
TEST_Fatal( "--mock-gateway is required when --mock-nat is not 'none'" );
TEST_mocknetwork_init( mockConfig );
}
#endif
// Initialize library, with the desired local identity
+34 -5
View File
@@ -153,13 +153,25 @@ def StartClientInThread( role, local, remote, extra_args=[] ):
return StartProcessInThread( local, cmdline, env );
# Mock network address constants
_SRV_GW = '127.0.100.2' # server-side NAT gateway (public)
_CLI_GW = '127.0.100.3' # client-side NAT gateway (public)
_SRV_INT = '127.0.1.2' # server internal address behind NAT
_CLI_INT = '127.0.2.2' # client internal address behind NAT
_SRV_GW = '127.0.100.2' # server-side NAT gateway (public)
_CLI_GW = '127.0.100.3' # client-side NAT gateway (public)
_SRV_GW2 = '127.0.100.4' # second server gateway (public)
_CLI_GW2 = '127.0.100.5' # second client gateway (public)
_SRV_INT = '127.0.1.2' # server internal address behind NAT
_CLI_INT = '127.0.2.2' # client internal address behind NAT
_SRV_INT2 = '127.0.3.2' # second server internal address
_CLI_INT2 = '127.0.4.2' # second client internal address
_DEAD_INT = '127.0.9.2' # address used for disabled adapters
def _nat( internal, gateway, nat_type ):
return [ '--mock-adapter', internal, '--mock-gateway', gateway, '--mock-nat', nat_type ]
# Gateway must be declared before the adapter that uses it
return [ '--mock-gateway', gateway, '--mock-nat', nat_type, '--mock-adapter', internal ]
def _disabled_adapter( ip ):
return [ '--mock-adapter', ip, '--mock-disabled' ]
def _slow_nat( internal, gateway, nat_type, latency_ms ):
return [ '--mock-gateway', gateway, '--mock-nat', nat_type, '--mock-adapter', internal, '--mock-latency', str(latency_ms) ]
# Each entry: ( description, server_extra_args, client_extra_args, expected_server_route, expected_client_route )
# Route types: 'local' = host-to-host (no NAT traversal needed)
@@ -194,6 +206,23 @@ CLIENT_SERVER_TEST_CASES = [
_nat( _SRV_INT, _SRV_GW, 'full-cone' ),
_nat( _CLI_INT, _CLI_GW, 'symmetric' ),
'udp', 'udp' ),
# Disabled adapter: verify connection still succeeds when one adapter is down
( 'server has disabled second adapter',
[ '--mock-adapter', _SRV_GW ] + _disabled_adapter( _DEAD_INT ),
_nat( _CLI_INT, _CLI_GW, 'full-cone' ),
'udp', 'local' ),
( 'client has disabled second adapter',
[ '--mock-adapter', _SRV_GW ],
_nat( _CLI_INT, _CLI_GW, 'full-cone' ) + _disabled_adapter( _DEAD_INT ),
'udp', 'local' ),
# Multi-adapter with latency: fast public adapter + slow NATd adapter.
# ICE should prefer the low-latency host-to-host path.
( 'both multi-adapter: fast public + slow NATd',
[ '--mock-adapter', _SRV_GW ] + _slow_nat( _SRV_INT2, _SRV_GW2, 'full-cone', 50 ),
[ '--mock-adapter', _CLI_GW ] + _slow_nat( _CLI_INT2, _CLI_GW2, 'full-cone', 50 ),
'local', 'local' ),
]
def ClientServerTest( server_extra_args=[], client_extra_args=[], expected_server_route=None, expected_client_route=None ):