ICE client refactor: Handling of received peer candidates

RFC5245CandidateAttr now store the address directly, nor address string
and port.
AddPeerCandidate will receive this directly, and return the type
This commit is contained in:
Fletcher Dunn
2026-05-25 15:58:22 -07:00
parent 390d19d1ff
commit bfdefb73af
2 changed files with 39 additions and 33 deletions
@@ -727,17 +727,6 @@ static uint32 CRC32( const unsigned char *buf, int len )
// Parse a candidate-attribute from https://datatracker.ietf.org/doc/html/rfc5245#section-15.1
// Ex: candidate:2442523459 0 udp 2122262784 2602:801:f001:1034:5078:221c:76b:a3d6 63368 typ host generation 0 ufrag WLM82 network-id 2
struct RFC5245CandidateAttr {
std::string sFoundation;
int nComponent;
std::string sTransport;
int nPriority;
std::string sAddress;
int nPort;
std::string sType;
CSteamNetworkingICESession::ICECandidateType nType;
CUtlVector< std::pair< std::string, std::string > > vAttrs;
};
bool ParseRFC5245CandidateAttribute( const char *pszAttr, RFC5245CandidateAttr *pAttr )
{
if ( pszAttr == nullptr || pAttr == nullptr )
@@ -878,9 +867,10 @@ bool ParseRFC5245CandidateAttribute( const char *pszAttr, RFC5245CandidateAttr *
pAttr->nPriority = atoi( pPriorityBegin );
{
std::string connectionAddr( pConnectionAddressBegin, pConnectionAddressEnd - pConnectionAddressBegin );
pAttr->sAddress.swap( connectionAddr );
if ( !pAttr->address.ParseString( connectionAddr.c_str() ) )
return false;
}
pAttr->nPort = atoi( pPortBegin );
pAttr->address.m_port = (uint16)atoi( pPortBegin );
{
std::string candidateType( pCandidateTypeBegin, pCandidateTypeEnd - pCandidateTypeBegin );
pAttr->sType.swap( candidateType );
@@ -1146,10 +1136,16 @@ void CSteamNetworkingICESession::SetRemotePassword( const char *pszPassword )
SetNextThinkTimeASAP();
}
void CSteamNetworkingICESession::AddPeerCandidate( const ICECandidateBase& candidate, const char* pszFoundation )
EICECandidateType CSteamNetworkingICESession::AddPeerCandidate( const RFC5245CandidateAttr& attr )
{
SteamNetworkingGlobalLock::AssertHeldByCurrentThread();
ICECandidateBase candidate( attr.nType, attr.address, attr.address );
candidate.m_nPriority = attr.nPriority;
const char *pszFoundation = attr.sFoundation.c_str();
EICECandidateType eCandidateType = candidate.CalcType();
// Do we already have a candidate for this peer? If so, just update the foundation and move on.
bool bNeedsNewEntry = true;
for ( ICEPeerCandidate& c : m_vecPeerCandidates )
@@ -1158,7 +1154,7 @@ void CSteamNetworkingICESession::AddPeerCandidate( const ICECandidateBase& candi
{
// If the foundation is the same, don't do anything - this is redundant.
if ( V_strcmp( c.m_sFoundation.c_str(), pszFoundation ) == 0 )
return;
return eCandidateType;
(ICECandidateBase&)c = candidate;
c.m_sFoundation = pszFoundation;
@@ -1182,6 +1178,7 @@ void CSteamNetworkingICESession::AddPeerCandidate( const ICECandidateBase& candi
}
m_bCandidatePairsNeedUpdate = true;
SetNextThinkTimeASAP();
return eCandidateType;
}
void CSteamNetworkingICESession::InvalidateInterfaceList()
@@ -2244,23 +2241,19 @@ void CConnectionTransportP2PICE_Valve::RecvRendezvous( const CMsgICERendezvous &
{
// candidate-attribute from https://datatracker.ietf.org/doc/html/rfc5245#section-15.1
const std::string& s = msg.add_candidate().candidate();
SpewMsg( "Got remote candidate \'%s\'\n", s.c_str() );
RFC5245CandidateAttr attr;
if ( ParseRFC5245CandidateAttribute( s.c_str(), &attr ) )
if ( !ParseRFC5245CandidateAttribute( s.c_str(), &attr ) )
{
SteamNetworkingIPAddr candidateAddr;
if ( !candidateAddr.ParseString( attr.sAddress.c_str() ) )
{
SpewMsg( "Failed to parse address \'%s\' as an IP address.", attr.sAddress.c_str() );
return;
}
candidateAddr.m_port = attr.nPort;
SpewMsg( "Got a rendezvous candidate at \"%s\"\n", SteamNetworkingIPAddrRender( candidateAddr ).c_str() );
CSteamNetworkingICESession::ICECandidateBase newCandidate( attr.nType, candidateAddr, candidateAddr );
newCandidate.m_nPriority = attr.nPriority;
m_pICESession->AddPeerCandidate( newCandidate, attr.sFoundation.c_str() );
Connection().m_msgICESessionSummary.set_remote_candidate_types( Connection().m_msgICESessionSummary.remote_candidate_types() | newCandidate.CalcType() );
SpewMsg( "[%s] Failed to parse remote candidate \'%s\'\n",
Connection().GetDescription(), s.c_str() );
}
else
{
SpewMsg( "[%s] Got remote candidate \'%s\'\n",
Connection().GetDescription(), s.c_str() );
EICECandidateType nType = m_pICESession->AddPeerCandidate( attr );
Connection().m_msgICESessionSummary.set_remote_candidate_types(
Connection().m_msgICESessionSummary.remote_candidate_types() | nType );
}
}
}
@@ -13,6 +13,8 @@
namespace SteamNetworkingSocketsLib {
class CSteamNetworkingSocketsSTUNRequest;
class CSteamNetworkingICESessionCallbacks;
struct RFC5245CandidateAttr;
/// Represents one local network interface used for ICE candidate gathering.
/// Owns its socket and tracks at most one in-flight server-reflexive STUN request.
@@ -169,8 +171,6 @@ namespace SteamNetworkingSocketsLib {
CSteamNetworkingSocketsSTUNRequest& operator=( const CSteamNetworkingSocketsSTUNRequest& );
};
class CSteamNetworkingICESessionCallbacks;
/// Main logic of establishing an ICE session with a peer. In real-world
/// uses cases this is always associated one-to-one with a CConnectionTransportP2PICE_Valve.
/// But breaking it out into a separate object helps with testing.
@@ -214,7 +214,7 @@ namespace SteamNetworkingSocketsLib {
void CalcCandidateAttribute( char *pszBuffer, size_t nBufferSize ) const;
};
EICERole GetRole() { return m_role; }
void AddPeerCandidate( const ICECandidateBase& peerCandidate, const char* pszFoundation );
EICECandidateType AddPeerCandidate( const RFC5245CandidateAttr& attr );
void SetRemoteUsername( const char *pszUsername );
void SetRemotePassword( const char *pszPassword );
@@ -389,6 +389,19 @@ namespace SteamNetworkingSocketsLib {
static void StaticPacketReceived( const RecvPktInfo_t &info, CSteamNetworkingICESession *pContext );
};
// Parsed representation of an RFC 5245 candidate-attribute line.
// https://datatracker.ietf.org/doc/html/rfc5245#section-15.1
struct RFC5245CandidateAttr {
std::string sFoundation;
int nComponent;
std::string sTransport;
int nPriority;
SteamNetworkingIPAddr address;
std::string sType;
CSteamNetworkingICESession::ICECandidateType nType;
CUtlVector< std::pair< std::string, std::string > > vAttrs;
};
class CSteamNetworkingICESessionCallbacks
{
public: