// --------------------------------------------------------------------------------------- // ip.cpp // // Copyright (C) Microsoft Corporation // --------------------------------------------------------------------------------------- #include "xnp.h" #include "xnver.h" // --------------------------------------------------------------------------------------- // Trace Tags // --------------------------------------------------------------------------------------- DefineTag(secStat, 0); DefineTag(tcpRetrans, 0); DefineTag(pktPreAuth, 0); DefineTag(keyExDrop, 0); // --------------------------------------------------------------------------------------- // Definitions // --------------------------------------------------------------------------------------- #define ESPUDP_REPLAY_WINDOW 1024 // How far ahead sender can get // --------------------------------------------------------------------------------------- // CXnIp External // --------------------------------------------------------------------------------------- NTSTATUS CXnIp::IpInit(XNetInitParams * pxnip) { TCHECK(USER); NTSTATUS status = EnetInit(pxnip); if (!NT_SUCCESS(status)) return(status); SetInitFlag(INITF_IP); status = RouteInit(); if (!NT_SUCCESS(status)) return(status); status = DhcpInit(); if (!NT_SUCCESS(status)) return(status); _pKeyReg = (CKeyReg *)SysAllocZ(cfgKeyRegMax * sizeof(CKeyReg), PTAG_CKeyReg); if (_pKeyReg == NULL) { TraceSz(Warning, "IpInit - Out of memory allocating CKeyReg vector"); return(WSAENOBUFS); } _pSecReg = (CSecReg *)SysAllocZ(cfgSecRegMax * sizeof(CSecReg), PTAG_CSecReg); if (_pSecReg == NULL) { TraceSz(Warning, "IpInit - Out of memory allocating CSecReg vector"); return(WSAENOBUFS); } #if defined(XNET_FEATURE_XBOX) && !defined(XNET_FEATURE_XBDM_SERVER) // Generate the SHA and 3DES keys for LAN broadcast. We do this by concatenating two digests: // // HMAC(HMAC(ROM-LAN-KEY, CERT-LAN-KEY), 0 | CERT-LAN-KEY) | // HMAC(HMAC(ROM-LAN-KEY, CERT-LAN-KEY), 1 | CERT-LAN-KEY) // // This produces 40 bytes of digest. The first 16 bytes are used as a SHA key, and the // remaining 24 bytes as the 3DES key. BYTE abCert[1 + XBEIMAGE_CERTIFICATE_KEY_LENGTH]; BYTE abHash[XC_SERVICE_DIGEST_SIZE * 2]; memcpy(&abCert[1], XeImageHeader()->Certificate->LANKey, XBEIMAGE_CERTIFICATE_KEY_LENGTH); abCert[0] = 0; XcHMAC((BYTE *)*XboxLANKey, XBOX_KEY_LENGTH, abCert, sizeof(abCert), NULL, 0, &abHash[0]); abCert[0] = 1; XcHMAC((BYTE *)*XboxLANKey, XBOX_KEY_LENGTH, abCert, sizeof(abCert), NULL, 0, &abHash[XC_SERVICE_DIGEST_SIZE]); Assert(sizeof(abHash) == sizeof(_abKeyShaLan) + sizeof(_abKeyDesLan)); memcpy(_abKeyShaLan, &abHash[0], sizeof(_abKeyShaLan)); memcpy(_abKeyDesLan, &abHash[sizeof(_abKeyShaLan)], sizeof(_abKeyDesLan)); XcDESKeyParity(_abKeyDesLan, sizeof(_abKeyDesLan)); #endif Rand((BYTE *)&_lNextDgramId, sizeof(_lNextDgramId)); Rand((BYTE *)&_wSecRegUniq, sizeof(_wSecRegUniq)); KeQuerySystemTime(&_liTime); _cSecRegProbeDenom = cfgSecRegVisitInSeconds * TICKS_PER_SECOND; #if defined(XNET_FEATURE_SG) && defined(XNET_FEATURE_INSECURE) Rand(_abDhXNull, sizeof(_abDhXNull)); XcModExp((DWORD *)_abDhGXNull, (DWORD *)g_abOakleyGroup1Base, (DWORD *)_abDhXNull, (DWORD *)g_abOakleyGroup1Mod, CBDHG1 / sizeof(DWORD)); #endif return(NETERR_OK); } void CXnIp::IpStop() { TCHECK(UDPC); if (TestInitFlag(INITF_IP) && !TestInitFlag(INITF_IP_STOP)) { SecRegShutdown(FALSE); NicFlush(); SetInitFlag(INITF_IP_STOP); } EnetStop(); } void CXnIp::IpTerm() { TCHECK(UDPC); IpStop(); if (TestInitFlag(INITF_IP)) { DhcpTerm(); RouteTerm(); FragTerm(); if (_pKeyReg) { CKeyReg * pKeyReg = &_pKeyReg[_cKeyReg - 1]; UINT cKeyReg = _cKeyReg; for (; cKeyReg > 0; --pKeyReg, --cKeyReg) { TraceSz1(Warning, "IpTerm - XNKID %s was not unregistered before shutdown", HexStr(pKeyReg->_xnkid.ab, sizeof(pKeyReg->_xnkid.ab))); IpUnregisterKey(&pKeyReg->_xnkid); } SysFree(_pKeyReg); } if (_pSecReg) { CSecReg * pSecReg = _pSecReg; UINT cSecReg = cfgSecRegMax; for (; cSecReg > 0; ++pSecReg, --cSecReg) { if (pSecReg->_dwSpiRecv) { SecRegFree(pSecReg); } } SysFree(_pSecReg); } } SetInitFlag(INITF_IP_TERM); EnetTerm(); } // --------------------------------------------------------------------------------------- // IpConfig // --------------------------------------------------------------------------------------- INT CXnIp::IpConfig(const XNetConfigParams * pxncp, DWORD dwFlags) { ICHECK(IP, USER|UDPC); RaiseToDpc(); INT err = NicConfig(pxncp); #ifdef XNET_FEATURE_DHCP if (err == 0) { err = DhcpConfig(pxncp); } #endif return(err); } INT CXnIp::IpGetConfigStatus(XNetConfigStatus * pxncs) { ICHECK(IP, USER); TraceSz(Warning, "IpGetConfigStatus not yet implemented"); memset(pxncs, 0, sizeof(XNetConfigStatus)); pxncs->dwFlags = XNET_STATUS_PENDING; return(0); } // --------------------------------------------------------------------------------------- // Utilities // --------------------------------------------------------------------------------------- void CXnIp::IpSetAddress(CIpAddr ipa, CIpAddr ipaMask) { ICHECK(IP, USER|UDPC|SDPC); RaiseToDpc(); if (ipa == 0) { ipaMask = 0; } else if (!ipa.IsValidUnicast()) { TraceSz4(Warning, "IpSetAddress - Cannot set IP address to %s (reason %d/%d/%d)", ipa.Str, ipa.IsBroadcast(), ipa.IsMulticast(), ipa.IsLoopback()); ipa = 0; ipaMask = 0; } else if (ipaMask == 0 || !ipaMask.IsValidMask()) { ipaMask = ipa.DefaultMask(); } _ipa = ipa; _ipaMask = ipaMask; _ipaSubnet = (ipa & ipaMask); if (ipa) { #if DBG TraceSz(Warning, "+\n-------------------------------------------------------------------------"); #ifdef XNET_FEATURE_XBDM_SERVER TraceSz3(Warning, "+XBOX DEBUG IP: %s / %s [%s]", ipa.Str(), ipaMask.Str(), _ea.Str()); #else TraceSz3(Warning, "+XBOX TITLE IP: %s / %s [%s]", ipa.Str(), ipaMask.Str(), _ea.Str()); #endif TraceSz(Warning, "+-------------------------------------------------------------------------\n"); #endif // Add a route for the local subnet RouteAdd(_ipaSubnet, _ipaMask, _ipa, RTEF_LOCAL, RTE_DEFAULT_METRIC); } else { // We've lost our subnet so clear the route list RouteListOrphan(); } } // --------------------------------------------------------------------------------------- // CXnIp::IpRecv // --------------------------------------------------------------------------------------- void CXnIp::IpRecv(CPacket * ppkt) { ICHECK(IP, UDPC|SDPC); Assert(ppkt->IsIp()); CIpHdr * pIpHdr; CIpAddr ipaDst; CIpAddr ipaSrc; UINT cbHdrLen; UINT cbLen; if (ppkt->GetCb() < sizeof(CIpHdr)) { TraceSz(pktWarn, "[DISCARD] Ethernet frame smaller than IP header"); return; } pIpHdr = ppkt->GetIpHdr(); cbHdrLen = pIpHdr->_bVerHdr; cbLen = pIpHdr->GetLen(); if ((cbHdrLen & 0xF0) != 0x40) { TraceSz1(pktWarn, "[DISCARD] IP version (%d) is not IPv4", cbHdrLen >> 4); return; } cbHdrLen = (cbHdrLen & 0x0F) << 2; if (cbHdrLen < sizeof(CIpHdr) || cbHdrLen > cbLen || cbLen > ppkt->GetCb()) { TraceSz3(pktWarn, "[DISCARD] IP header length is bad (%d,%d,%d)", cbHdrLen < sizeof(CIpHdr), cbHdrLen > cbLen, cbLen > ppkt->GetCb()); return; } // Change the size of the packet to match the size specified in the IP header. An // ethernet frame, especially a small one, can sometimes be longer than the IP // packet length. We've already checked above that the ethernet frame is at least // as big as cbLen. ppkt->SetCb(cbLen); ipaDst = pIpHdr->_ipaDst; ipaSrc = pIpHdr->_ipaSrc; if (cbHdrLen > sizeof(CIpHdr)) { TraceSz9(pktRecv, "[IP %s %s (%d %d %04X %04X) %d]{%d}[%d]", ipaDst.Str(), ipaSrc.Str(), pIpHdr->_bTos, pIpHdr->_bTtl, NTOHS(pIpHdr->_wId), NTOHS(pIpHdr->_wFragOff), pIpHdr->_bProtocol, cbHdrLen - sizeof(CIpHdr), cbLen - cbHdrLen); TraceSz(pktRecv, "IP header options are not supported. Continuing as if no options."); ppkt->SetHdrOptLen(cbHdrLen - sizeof(CIpHdr)); } else { TraceSz8(pktRecv, "[IP %s %s (%d %d %04X %04X) %d][%d]", ipaDst.Str(), ipaSrc.Str(), pIpHdr->_bTos, pIpHdr->_bTtl, NTOHS(pIpHdr->_wId), NTOHS(pIpHdr->_wFragOff), pIpHdr->_bProtocol, cbLen - cbHdrLen); } if (tcpipxsum(0, pIpHdr, cbHdrLen) != 0xffff) { TraceSz(pktWarn, "[DISCARD] IP header checksum failed"); return; } Assert(!_ipa.IsBroadcast()); Assert(!_ipa.IsMulticast()); Assert(!_ipa.IsLoopback()); if (ipaDst == 0) { TraceSz(pktWarn, "[DISCARD] Destination address is zero"); return; } if (ppkt->TestFlags(PKTF_RECV_BROADCAST) && !ipaDst.IsBroadcast()) { TraceSz(pktRecv, "[DISCARD] IP-level unicast via link-level broadcast"); return; } if (ipaSrc.IsBroadcast() || (ipaSrc == 0 && !IsGateway(0))) { TraceSz1(pktRecv, "[DISCARD] Source address is %s", ipaSrc == 0 ? "zero" : "broadcast"); return; } if (!ppkt->TestFlags(PKTF_RECV_LOOPBACK)) { if (ipaDst.IsLoopback() || ipaSrc.IsLoopback() || ipaSrc == _ipa) { TraceSz3(pktRecv, "[DISCARD] IP loopback addresses received via link-level (%d,%d,%d)", ipaDst.IsLoopback(), ipaSrc.IsLoopback(), ipaSrc == _ipa); return; } if (!ppkt->TestFlags(PKTF_RECV_BROADCAST) && !ipaDst.IsBroadcast() && !IsGateway(0)) { if (_ipa != ipaDst && (ipaDst != IPADDR_SECURE_DEFAULT || pIpHdr->_bProtocol != IPPROTOCOL_UDP)) { #if DBG BYTE bProtocol = pIpHdr->_bProtocol; CUdpHdr * pUdpHdr = (bProtocol == IPPROTOCOL_UDP || bProtocol == IPPROTOCOL_TCP) ? (CUdpHdr *)(pIpHdr + 1) : NULL; TraceSz6(pktWarn, "[DISCARD] %s packet from %s:%d to %s:%d but my IP is %s", bProtocol == IPPROTOCOL_UDP ? "UDP" : bProtocol == IPPROTOCOL_TCP ? "TCP " : "IP", ipaSrc.Str(), pUdpHdr ? NTOHS(pUdpHdr->_ipportSrc) : bProtocol, ipaDst.Str(), pUdpHdr ? NTOHS(pUdpHdr->_ipportDst) : bProtocol, _ipa ? _ipa.Str() : "not yet acquired"); #endif return; } } } if (pIpHdr->_wFragOff & HTONS(MORE_FRAGMENTS|FRAGOFFSET_MASK)) { #ifdef XNET_FEATURE_FRAG #ifdef XNET_FEATURE_FRAG_LOOPBACK if (ppkt->TestFlags(PKTF_RECV_BROADCAST)) #else if (ppkt->TestFlags(PKTF_RECV_BROADCAST|PKTF_RECV_LOOPBACK)) #endif { TraceSz1(pktWarn, "[DISCARD] Fragmented packet received via %s", ppkt->TestFlags(PKTF_RECV_BROADCAST) ? "broadcast" : "loopback"); return; } else { FragRecv(ppkt, pIpHdr, cbHdrLen, cbLen); return; } #else TraceSz(pktWarn, "[DISCARD] No support for fragmented packets"); return; #endif } BYTE * pb = (BYTE *)pIpHdr + cbHdrLen; cbLen -= cbHdrLen; if (pIpHdr->_bProtocol == IPPROTOCOL_UDP) { ppkt->SetType(PKTF_TYPE_UDP); IpRecvUdp(ppkt, pIpHdr, (CUdpHdr *)pb, cbLen); return; } if (pIpHdr->_bProtocol == IPPROTOCOL_TCP) { ppkt->SetType(PKTF_TYPE_TCP); IpRecvTcp(ppkt, pIpHdr, (CTcpHdr *)pb, cbLen); return; } #ifdef XNET_FEATURE_ICMP if (pIpHdr->_bProtocol == IPPROTOCOL_ICMP) { IcmpRecv(ppkt, pIpHdr, pb, cbLen); return; } #endif TraceSz1(pktWarn, "[DISCARD] No support for protocol %d", pIpHdr->_bProtocol); return; } void CXnIp::IpRecvUdp(CPacket * ppkt, CIpHdr * pIpHdr, CUdpHdr * pUdpHdr, UINT cbLen) { ICHECK(IP, UDPC|SDPC); Assert(ppkt->IsUdp()); if ( cbLen < sizeof(CUdpHdr) || pUdpHdr->GetLen() != cbLen || pUdpHdr->_ipportDst == 0 || pUdpHdr->_ipportSrc == 0) { TraceSz4(pktWarn, "[DISCARD] UDP header is invalid (%d,%d,%d,%d)", cbLen < sizeof(CUdpHdr), pUdpHdr->GetLen() != cbLen, pUdpHdr->_ipportDst == 0, pUdpHdr->_ipportSrc == 0); return; } if (!ppkt->IsEsp()) { if (pUdpHdr->_ipportDst == ESPUDP_CLIENT_PORT && !IsGateway(pUdpHdr->_ipportDst)) { IpRecvEsp(ppkt, pIpHdr, (CEspHdr *)pUdpHdr, cbLen); return; } if (pIpHdr->_ipaDst == IPADDR_SECURE_DEFAULT) { TraceSz5(pktWarn, "[DISCARD] UDP packet from %s:%d to %s:%d but my IP is %s", pIpHdr->_ipaSrc.Str(), NTOHS(pUdpHdr->_ipportSrc), pIpHdr->_ipaDst.Str(), NTOHS(pUdpHdr->_ipportDst), _ipa ? _ipa.Str() : "not yet acquired"); return; } if (!ppkt->TestFlags(PKTF_RECV_LOOPBACK)) { if (pUdpHdr->_wChecksum) { CPseudoHeader ph; ph._ipaSrc = pIpHdr->_ipaSrc; ph._ipaDst = pIpHdr->_ipaDst; ph._bZero = 0; ph._bProtocol = IPPROTOCOL_UDP; ph._wLen = pUdpHdr->_wLen; Assert(cbLen == NTOHS(ph._wLen)); if (tcpipxsum(tcpipxsum(0, &ph, sizeof(CPseudoHeader)), pUdpHdr, cbLen) != 0xFFFF) { TraceSz(pktWarn, "[DISCARD] UDP header checksum failed"); return; } } #ifdef XNET_FEATURE_DHCP if (pUdpHdr->_ipportDst == DHCP_CLIENT_PORT) { TraceSz(pktRecv, "[DHCPRECV]"); DhcpRecv(ppkt, pUdpHdr, cbLen - sizeof(CUdpHdr)); return; } #endif #ifdef XNET_FEATURE_DNS if (pUdpHdr->_ipportDst == DNS_CLIENT_PORT && !IsGateway(pUdpHdr->_ipportDst)) { TraceSz(pktRecv, "[DNSRECV]"); IpRecvDns(ppkt, pUdpHdr, cbLen - sizeof(CUdpHdr)); return; } #endif // This is an insecure packet. If we are compiled for online, some sockets // can accept insecure packets, so we'll pass them up to UpdRecv and let it // decide. Otherwise, unless we are compiled insecure and the client has // requested bypassing security, the insecure UDP packet stops right here. #if !defined(XNET_FEATURE_ONLINE) #ifdef XNET_FEATURE_INSECURE if (!(cfgFlags & XNET_STARTUP_BYPASS_SECURITY)) #endif { TraceSz3(pktWarn, "[DISCARD] Insecure UDP packet on port %d from %s:%d", NTOHS(pUdpHdr->_ipportDst), pIpHdr->_ipaSrc.Str(), NTOHS(pUdpHdr->_ipportSrc)); return; } #endif } } UdpRecv(ppkt, pIpHdr, pUdpHdr, cbLen - sizeof(CUdpHdr)); } void CXnIp::IpRecvTcp(CPacket * ppkt, CIpHdr * pIpHdr, CTcpHdr * pTcpHdr, UINT cbLen) { ICHECK(IP, UDPC|SDPC); Assert(ppkt->IsTcp()); if (ppkt->TestFlags(PKTF_RECV_BROADCAST) || pIpHdr->_ipaDst.IsBroadcast()) { TraceSz(pktRecv, "[DISCARD] TCP packet with broadcast destination address"); return; } UINT cbHdrLen; if ( cbLen < sizeof(CTcpHdr) || (cbHdrLen = pTcpHdr->GetHdrLen()) < sizeof(CTcpHdr) || cbHdrLen > cbLen || pTcpHdr->_ipportDst == 0 || pTcpHdr->_ipportSrc == 0) { TraceSz5(pktWarn, "[DISCARD] TCP header is invalid (%d,%d,%d,%d,%d)", cbLen < sizeof(CTcpHdr), cbHdrLen < sizeof(CTcpHdr), cbHdrLen > cbLen, pTcpHdr->_ipportDst == 0, pTcpHdr->_ipportSrc == 0); return; } if (!ppkt->TestFlags(PKTF_TYPE_ESP|PKTF_RECV_LOOPBACK)) { CPseudoHeader ph; ph._ipaSrc = pIpHdr->_ipaSrc; ph._ipaDst = pIpHdr->_ipaDst; ph._bZero = 0; ph._bProtocol = IPPROTOCOL_TCP; ph._wLen = HTONS((WORD)cbLen); if (tcpipxsum(tcpipxsum(0, &ph, sizeof(CPseudoHeader)), pTcpHdr, cbLen) != 0xFFFF) { TraceSz(pktWarn, "[DISCARD] TCP header checksum failed"); return; } // This is an insecure packet. If we are compiled for online, some sockets // can accept insecure packets, so we'll pass them up to TcpRecv and let it // decide. Otherwise, unless we are compiled insecure and the client has // requested bypassing security, the insecure TCP packet stops right here. #if !defined(XNET_FEATURE_ONLINE) #ifdef XNET_FEATURE_INSECURE if (!(cfgFlags & XNET_STARTUP_BYPASS_SECURITY)) #endif { TraceSz3(pktWarn, "[DISCARD] Insecure TCP packet on port %d from %s:%d", NTOHS(pTcpHdr->_ipportDst), pIpHdr->_ipaSrc.Str(), NTOHS(pTcpHdr->_ipportSrc)); return; } #endif } TcpRecv(ppkt, pIpHdr, pTcpHdr, cbHdrLen, cbLen - cbHdrLen); } void CXnIp::IpRecvEsp(CPacket * ppkt, CIpHdr * pIpHdr, CEspHdr * pEspHdr, UINT cbLen) { ICHECK(IP, UDPC|SDPC); Assert(ppkt->IsUdp()); Assert(pEspHdr->_ipportDst == ESPUDP_CLIENT_PORT); if (cbLen < sizeof(CEspHdr)) { TraceSz(pktWarn, "[DISCARD] ESPUDP header length exceeds packet length"); return; } if (pEspHdr->_dwSpi == 0) { if (ppkt->TestFlags(PKTF_RECV_BROADCAST|PKTF_RECV_LOOPBACK)) { TraceSz1(pktWarn, "[DISCARD] KeyEx packet received via %s", ppkt->TestFlags(PKTF_RECV_BROADCAST) ? "broadcast" : "loopback"); return; } IpRecvKeyEx(ppkt, pIpHdr->_ipaSrc, pEspHdr->_ipportSrc, (CKeyExHdr *)&pEspHdr->_dwSeq, cbLen - offsetof(CEspHdr, _dwSeq)); return; } if ((cbLen & 3) != 0) { TraceSz1(pktWarn, "[DISCARD] ESPUDP packet is not four-byte aligned (%d)", cbLen); return; } ppkt->SetType(PKTF_TYPE_IP|PKTF_TYPE_ESP); CSecReg * pSecReg = NULL; CIpAddr ipa = pEspHdr->_dwSpi; BOOL fBroadcast = ipa.IsBroadcast(); DWORD dwSeq = NTOHL(pEspHdr->_dwSeq); DWORD dwBit = 0; CEspTail * pEspTail = ppkt->GetEspTail(); BYTE * pb = (BYTE *)(pEspHdr + 1); BYTE * pbKeySha; UINT cbKeySha; BYTE * pbKeyDes; UINT cbKeyDes; if (!!fBroadcast != !!pIpHdr->_ipaDst.IsBroadcast()) { TraceSz2(pktWarn, "[DISCARD] ESPUDP packet has dwSpi %08X but ipaDst %s", pEspHdr->_dwSpi, pIpHdr->_ipaDst.Str()); return; } if (fBroadcast) { pbKeySha = _abKeyShaLan; cbKeySha = sizeof(_abKeyShaLan); pbKeyDes = _abKeyDesLan; cbKeyDes = sizeof(_abKeyDesLan); if (dwSeq != 0xFFFFFFFF) { TraceSz1(pktWarn, "[DISCARD] ESPUDP broadcast packet has invalid dwSeq (%08lX)", dwSeq); return; } } else { pSecReg = SecRegLookup(ipa); if (pSecReg == NULL) { TraceSz1(pktWarn, "[DISCARD] Secure packet sent to unregistered address (%s)", ipa.Str()); return; } if (!pSecReg->IsRecvReady()) { TraceSz1(pktWarn, "[DISCARD] Secure packet to %s before key exchange is complete", ipa.Str()); return; } pbKeySha = pSecReg->_abKeyShaRecv; cbKeySha = sizeof(pSecReg->_abKeyShaRecv); pbKeyDes = pSecReg->_abKeyDesRecv; cbKeyDes = pSecReg->_cbKeyDesRecv; if (dwSeq < pSecReg->_dwSeqRecv) { TraceSz3(pktWarn, "[DISCARD] Secure packet to %s has dwSeq %d less than window base %d", ipa.Str(), dwSeq, pSecReg->_dwSeqRecv); return; } dwBit = dwSeq - pSecReg->_dwSeqRecv; if (dwBit > ESPUDP_REPLAY_WINDOW) { TraceSz4(pktWarn, "[DISCARD] Secure packet to %s has dwSeq %d outside window (%d to %d)", ipa.Str(), dwSeq, pSecReg->_dwSeqRecv, pSecReg->_dwSeqRecv + ESPUDP_REPLAY_WINDOW); return; } if (dwBit < 32 && (pSecReg->_dwSeqMask & (1 << dwBit))) { TraceSz2(pktWarn, "[DISCARD] Secure packet to %s has dwSeq %d which is replayed", ipa.Str(), dwSeq); return; } } if (cbKeyDes) { ppkt->SetFlags(PKTF_CRYPT); if (cbLen < sizeof(CEspHdr) + ROUNDUP8(XC_SERVICE_DES_BLOCKLEN + offsetof(CEspTail, _abHash)) + sizeof(pEspTail->_abHash)) { TraceSz1(pktWarn, "[DISCARD] ESPUDP crypt packet is too small (%d)", cbLen); return; } } else { if (cbLen < sizeof(CEspHdr) + ROUNDUP4(sizeof(CEspTail))) { TraceSz1(pktWarn, "[DISCARD] ESPUDP auth packet is too small (%d)", cbLen); return; } } cbLen -= sizeof(CEspHdr) + sizeof(pEspTail->_abHash); TraceSz4(pktRecv, "[ESP %s #%d]%s[%d][ESPT]", ipa.Str(), dwSeq, ppkt->TestFlags(PKTF_CRYPT) ? "[IV]" : "", cbLen - (ppkt->TestFlags(PKTF_CRYPT) ? XC_SERVICE_DES_BLOCKLEN : 0) - offsetof(CEspTail, _abHash)); // Authenicate the packet from the [ESP] header to just before the _abHash in [ESPT] BYTE abHash[XC_SERVICE_DIGEST_SIZE]; Assert(sizeof(pEspTail->_abHash) <= sizeof(abHash)); XcHMAC(pbKeySha, cbKeySha, (BYTE *)&pEspHdr->_dwSpi, (sizeof(CEspHdr) - sizeof(CUdpHdr)) + cbLen, NULL, 0, abHash); if (memcmp(pEspTail->_abHash, abHash, sizeof(pEspTail->_abHash)) != 0) { TraceSz1(pktWarn, "[DISCARD] Secure packet to %s failed to authenticate", ipa.Str()); return; } if (cbKeyDes) { // Decrypt the packet from just after the [ESP] header to just before the _abHash in [ESPT] if (cbLen != ROUNDUP8(cbLen)) { TraceSz1(pktWarn, "[DISCARD] Secure crypt packet has invalid payload size (%d)", cbLen); return; } CryptDes(XC_SERVICE_DECRYPT, pbKeyDes, cbKeyDes, pb, pb + XC_SERVICE_DES_BLOCKLEN, cbLen); pb += XC_SERVICE_DES_BLOCKLEN; cbLen -= XC_SERVICE_DES_BLOCKLEN; } cbLen -= offsetof(CEspTail, _abHash); if ( pEspTail->_bNextHeader != IPPROTOCOL_UDP && pEspTail->_bNextHeader != IPPROTOCOL_TCP && pEspTail->_bNextHeader != IPPROTOCOL_SECMSG) { TraceSz2(pktWarn, "[DISCARD] Secure packet to %s failed bNextHeader test (%d)", ipa.Str(), pEspTail->_bNextHeader); return; } if (cbLen < pEspTail->_bPadLen) { TraceSz3(pktWarn, "[DISCARD] Secure packet to %s failed bPadLen test (%d/%d)", ipa.Str(), cbLen, pEspTail->_bPadLen); return; } if (pEspTail->_bPadLen > 0) { UINT cbPad = pEspTail->_bPadLen; BYTE * pbPad = (BYTE *)pEspTail; // Verify that the padding is the series of bytes 1, 2, 3, ... while (cbPad > 0 && *--pbPad == (BYTE)cbPad) cbPad--; if (cbPad) { TraceSz1(pktWarn, "[DISCARD] Secure packet to %s failed padding test", ipa.Str()); return; } cbLen -= pEspTail->_bPadLen; } if (!fBroadcast) { if (dwBit < 32) { // Sequence number is within the current window. Just set the bit. Assert((pSecReg->_dwSeqMask & (1 << dwBit)) == 0); pSecReg->_dwSeqMask |= (1 << dwBit); } else { // Sequence number is beyond the edge of the window. Slide window so that the // edge is at the sequence number. if (dwBit >= 63) pSecReg->_dwSeqMask = 0x80000000; else pSecReg->_dwSeqMask = 0x80000000 | (pSecReg->_dwSeqMask >> (dwBit - 31)); pSecReg->_dwSeqRecv += dwBit - 31; } if (pSecReg->_bState != SR_STATE_READY) { Assert(pSecReg->_bState == SR_STATE_INITWAIT || pSecReg->_bState == SR_STATE_RESPSENT); // We have successfully authenticated a packet while in the INITWAIT or RESPSENT // state. That means the other side definitely knows the keys. Go into the // ready state and release any pending packets. #ifdef XNET_FEATURE_SG if (pSecReg->TestFlags(SRF_ONLINEPEER)) { // Capture the address of the sender of this packet. This will be used // as the return address for packets sent on this security association. Assert(pIpHdr->_ipaSrc != 0 && pEspHdr->_ipportSrc != 0); pSecReg->_ipaDst = pIpHdr->_ipaSrc; pSecReg->_ipportDst = pEspHdr->_ipportSrc; } #endif pSecReg->_bState = SR_STATE_READY; TimerSet(&pSecReg->_timer, TIMER_INFINITE); SecRegXmitQueue(pSecReg); } // Change the return address in the IP header to the secure address of the sender pIpHdr->_ipaSrc = CIpAddr(pSecReg->_dwSpiRecv); // Remember the last time a packet was received on this security association pSecReg->_dwTickRecv = TimerTick(); } pIpHdr->_bProtocol = pEspTail->_bNextHeader; if (pIpHdr->_bProtocol == IPPROTOCOL_UDP) { ppkt->SetType(PKTF_TYPE_ESP|PKTF_TYPE_UDP); IpRecvUdp(ppkt, pIpHdr, (CUdpHdr *)pb, cbLen); } else if (pIpHdr->_bProtocol == IPPROTOCOL_TCP) { ppkt->SetType(PKTF_TYPE_ESP|PKTF_TYPE_TCP); IpRecvTcp(ppkt, pIpHdr, (CTcpHdr *)pb, cbLen); } else { Assert(pIpHdr->_bProtocol == IPPROTOCOL_SECMSG); ppkt->SetType(PKTF_TYPE_ESP); IpRecvSecMsg(ppkt, pSecReg, dwSeq, (CSecMsgHdr *)pb, cbLen); } } void CXnIp::IpRecvKeyEx(CPacket * ppkt, CIpAddr ipaSrc, CIpPort ipportSrc, CKeyExHdr * pKeyExHdr, UINT cbKeyEx) { ICHECK(IP, UDPC|SDPC); if (cbKeyEx < sizeof(CKeyExHdr) || pKeyExHdr->_cbEnt < sizeof(CKeyExHdr) || pKeyExHdr->_cbEnt > cbKeyEx) { TraceSz5(pktWarn, "[DISCARD] KeyEx from %s:%d header entry is invalid (%d,%d,%d)", ipaSrc.Str(), NTOHS(ipportSrc), cbKeyEx < sizeof(CKeyExHdr), pKeyExHdr->_cbEnt < sizeof(CKeyExHdr), pKeyExHdr->_cbEnt > cbKeyEx); return; } if (pKeyExHdr->_wType == KEYEX_TYPE_XBTOXB_INIT || pKeyExHdr->_wType == KEYEX_TYPE_XBTOXB_RESP) { IpRecvKeyExXbToXb(ppkt, ipaSrc, ipportSrc, (CKeyExXbToXb *)pKeyExHdr, cbKeyEx); return; } #ifdef XNET_FEATURE_SG if (pKeyExHdr->_wType == KEYEX_TYPE_SGTOXB_RESP && ipaSrc != 0 && ipportSrc != 0) { IpRecvKeyExSgToXb(ppkt, ipaSrc, ipportSrc, (CKeyExSgToXbResp *)pKeyExHdr, cbKeyEx); return; } if ( (pKeyExHdr->_wType == KEYEX_TYPE_NATOPEN_INIT && ipaSrc == 0 && ipportSrc == 0) || (pKeyExHdr->_wType == KEYEX_TYPE_NATOPEN_RESP && ipaSrc != 0 && ipportSrc != 0)) { IpRecvKeyExNatOpen(ppkt, ipaSrc, ipportSrc, (CKeyExNatOpen *)pKeyExHdr, cbKeyEx); return; } #endif TraceSz3(pktWarn, "[DISCARD] KeyEx from %s:%d header type (%04X) is invalid", ipaSrc.Str(), NTOHS(ipportSrc), pKeyExHdr->_wType); return; } void CXnIp::IpRecvKeyExXbToXb(CPacket * ppkt, CIpAddr ipaSrc, CIpPort ipportSrc, CKeyExXbToXb * pKeyExXbToXb, UINT cbKeyEx) { ICHECK(IP, UDPC|SDPC); Assert(cbKeyEx >= pKeyExXbToXb->_cbEnt); if (pKeyExXbToXb->_cbEnt != sizeof(CKeyExXbToXb)) { TraceSz4(pktWarn, "[DISCARD] KeyExXbToXb from %s:%d entry has an incorrect size (%d,%d)", ipaSrc.Str(), NTOHS(ipportSrc), pKeyExXbToXb->_cbEnt, sizeof(CKeyExXbToXb)); return; } #ifdef XNET_FEATURE_SG if (XNetXnKidIsOnlinePeer(&pKeyExXbToXb->_xnkid) && _pSecRegLogon == NULL) { TraceSz2(pktWarn, "[DISCARD] KeyExXbToXb from %s:%d ignoring request from online-peer while offline", ipaSrc.Str(), NTOHS(ipportSrc)); return; } #endif CKeyReg * pKeyReg = NULL; CSecReg * pSecReg = NULL; if (pKeyExXbToXb->_wType == KEYEX_TYPE_XBTOXB_INIT) { pKeyReg = KeyRegLookup(&pKeyExXbToXb->_xnkid); if (pKeyReg == NULL) { TraceSz3(pktWarn, "[DISCARD] KeyExXbToXbInit from %s:%d to unregistered XNKID %s", ipaSrc.Str(), NTOHS(ipportSrc), HexStr(pKeyExXbToXb->_xnkid.ab, sizeof(pKeyExXbToXb->_xnkid.ab))); return; } } else { pSecReg = SecRegLookup(pKeyExXbToXb->_dwSpiInit); if (pSecReg == NULL) { TraceSz3(pktWarn, "[DISCARD] KeyExXbToXbResp from %s:%d to unregistered address %s", ipaSrc.Str(), NTOHS(ipportSrc), CIpAddr(pKeyExXbToXb->_dwSpiInit).Str()); return; } #ifdef XNET_FEATURE_SG if (ipaSrc == 0 || ipportSrc == 0) { TraceSz2(pktWarn, "[DISCARD] KeyExXbToXbResp received via SG forwarding (%d,%d)", ipaSrc == 0, ipportSrc == 0); return; } if (!pSecReg->TestFlags(SRF_SYSTEMLINK|SRF_ONLINEPEER)) { TraceSz3(pktWarn, "[DISCARD] KeyExXbToXbResp from %s:%d to non peer-to-peer address %s", ipaSrc.Str(), NTOHS(ipportSrc), CIpAddr(pSecReg->_dwSpiRecv).Str()); return; } #endif if (pKeyExXbToXb->_liTime.QuadPart <= pSecReg->_liTime.QuadPart) { TraceSz3(pktWarn, "[DISCARD] KeyExXbToXbResp from %s:%d to %s was replayed", ipaSrc.Str(), NTOHS(ipportSrc), pSecReg->Str()); return; } if (pSecReg->_bState != SR_STATE_INITSENT && pSecReg->_bState != SR_STATE_INITWAIT) { TraceSz3(pktWarn, "[DISCARD] KeyExXbToXbResp from %s:%d to %s while not in ISENT or IWAIT state", ipaSrc.Str(), NTOHS(ipportSrc), pSecReg->Str()); return; } Assert(sizeof(pSecReg->_abNonceInit) == sizeof(pKeyExXbToXb->_abNonceInit)); if (memcmp(pSecReg->_abNonceInit, pKeyExXbToXb->_abNonceInit, sizeof(pSecReg->_abNonceInit)) != 0) { TraceSz3(pktWarn, "[DISCARD] KeyExXbToXbResp from %s:%d to %s has incorrect nonce", ipaSrc.Str(), NTOHS(ipportSrc), pSecReg->Str()); return; } pKeyReg = pSecReg->_pKeyReg; if (memcmp(pKeyReg->_xnkid.ab, pKeyExXbToXb->_xnkid.ab, sizeof(pKeyReg->_xnkid.ab)) != 0) { TraceSz3(pktWarn, "[DISCARD] KeyExXbToXbResp from %s:%d to %s has incorrect xnkid", ipaSrc.Str(), NTOHS(ipportSrc), pSecReg->Str()); return; } } // Advance to the next entry. It must contain the diffie-hellman g^X value. cbKeyEx -= pKeyExXbToXb->_cbEnt; CKeyExHdr * pKeyExDh = (CKeyExHdr *)((BYTE *)pKeyExXbToXb + pKeyExXbToXb->_cbEnt); if ( cbKeyEx < sizeof(CKeyExHdr) || pKeyExDh->_wType != KEYEX_TYPE_DH_GX || pKeyExDh->_cbEnt != sizeof(CKeyExHdr) + CBDHG1 || pKeyExDh->_cbEnt > cbKeyEx) { TraceSz6(pktWarn, "[DISCARD] KeyExXbToXb from %s:%d DH entry is invalid (%d,%d,%d,%d)", ipaSrc.Str(), NTOHS(ipportSrc), cbKeyEx < sizeof(CKeyExHdr), cbKeyEx >= sizeof(CKeyExHdr) && pKeyExDh->_wType != KEYEX_TYPE_DH_GX, cbKeyEx >= sizeof(CKeyExHdr) && pKeyExDh->_cbEnt != sizeof(CKeyExHdr) + CBDHG1, cbKeyEx >= sizeof(CKeyExHdr) && pKeyExDh->_cbEnt > cbKeyEx); return; } // Advance to the next entry. It must contain the HMAC-SHA digest of the previous entries, // and it must also be the last entry. cbKeyEx -= pKeyExDh->_cbEnt; CKeyExHdr * pKeyExSha = (CKeyExHdr *)((BYTE *)pKeyExDh + pKeyExDh->_cbEnt); if ( cbKeyEx < sizeof(CKeyExHdr) || pKeyExSha->_wType != KEYEX_TYPE_HMAC_SHA || pKeyExSha->_cbEnt != sizeof(CKeyExHdr) + XC_SERVICE_DIGEST_SIZE || pKeyExSha->_cbEnt != cbKeyEx) { TraceSz6(pktWarn, "[DISCARD] KeyExXbToXb from %s:%d SHA entry is invalid (%d,%d,%d,%d)", ipaSrc.Str(), NTOHS(ipportSrc), cbKeyEx < sizeof(CKeyExHdr), cbKeyEx >= sizeof(CKeyExHdr) && pKeyExSha->_wType != KEYEX_TYPE_HMAC_SHA, cbKeyEx >= sizeof(CKeyExHdr) && pKeyExSha->_cbEnt != sizeof(CKeyExHdr) + XC_SERVICE_DIGEST_SIZE, cbKeyEx >= sizeof(CKeyExHdr) && pKeyExSha->_cbEnt != cbKeyEx); return; } // Authenticate the key exchange message (all entries except the last) using the key-exchange-key // corresponding to the XNKID in the message. BYTE abHash[XC_SERVICE_DIGEST_SIZE]; XcHMAC(pKeyReg->_abKeySha, sizeof(pKeyReg->_abKeySha), (BYTE *)pKeyExXbToXb, (BYTE *)pKeyExSha - (BYTE *)pKeyExXbToXb, NULL, 0, abHash); Assert(pKeyExSha->_cbEnt == sizeof(CKeyExHdr) + sizeof(abHash)); if (memcmp((BYTE *)(pKeyExSha + 1), abHash, sizeof(abHash)) != 0) { TraceSz2(pktWarn, "[DISCARD] KeyExXbToXb from %s:%d failed to authenticate", ipaSrc.Str(), NTOHS(ipportSrc)); return; } // Decrypt the portion of the first entry which contains the XNADDR structures CryptDes(XC_SERVICE_DECRYPT, pKeyReg->_abKeyDes, sizeof(pKeyReg->_abKeyDes), pKeyExXbToXb->_abIv, pKeyExXbToXb->_abIv + XC_SERVICE_DES_BLOCKLEN, sizeof(CKeyExXbToXb) - offsetof(CKeyExXbToXb, _abIv) - XC_SERVICE_DES_BLOCKLEN); // Verify that the sender knows the current XNADDR of this stack XNADDR xnaddr, * pxnaddr; IpGetXnAddr(&xnaddr); pxnaddr = (pKeyExXbToXb->_wType == KEYEX_TYPE_XBTOXB_INIT) ? &pKeyExXbToXb->_xnaddrResp : &pKeyExXbToXb->_xnaddrInit; if (memcmp(&xnaddr, pxnaddr, sizeof(XNADDR)) != 0) { TraceSz4(pktWarn, "[DISCARD] KeyExXbToXb from %s:%d target XNADDR mismatch; got %s expected %s", ipaSrc.Str(), NTOHS(ipportSrc), XnAddrStr(pxnaddr), XnAddrStr(&xnaddr)); return; } // Verify that the sender's XNADDR is valid pxnaddr = (pKeyExXbToXb->_wType == KEYEX_TYPE_XBTOXB_INIT) ? &pKeyExXbToXb->_xnaddrInit : &pKeyExXbToXb->_xnaddrResp; if (XNetXnKidIsSystemLink(&pKeyExXbToXb->_xnkid)) { CEnetHdr * pEnetHdr = ppkt->GetEnetHdr(); if (!pEnetHdr->_eaSrc.IsEqual(pxnaddr->abEnet)) { TraceSz3(pktWarn, "[DISCARD] KeyExXbToXb from %s:%d source XNADDR failed system-link test (%d)", ipaSrc.Str(), NTOHS(ipportSrc), !pEnetHdr->_eaSrc.IsEqual(pxnaddr->abEnet)); return; } } #ifdef XNET_FEATURE_SG if (XNetXnKidIsOnlinePeer(&pKeyExXbToXb->_xnkid)) { if ( !CIpAddr(pxnaddr->ina.s_addr).IsValidUnicast() || !CIpAddr(pxnaddr->inaOnline.s_addr).IsValidUnicast() || pxnaddr->wPortOnline == 0) { TraceSz5(pktWarn, "[DISCARD] KeyExXbToXb from %s:%d source XNADDR failed online-peer tests (%d,%d,%d)", ipaSrc.Str(), NTOHS(ipportSrc), !CIpAddr(pxnaddr->ina.s_addr).IsValidUnicast(), !CIpAddr(pxnaddr->inaOnline.s_addr).IsValidUnicast(), pxnaddr->wPortOnline == 0); return; } } #endif if (pKeyExXbToXb->_wType == KEYEX_TYPE_XBTOXB_INIT) { // Try to find an existing CSecReg given the XNADDR of the initiator and the XNKID pSecReg = SecRegLookup(&pKeyExXbToXb->_xnaddrInit, &pKeyExXbToXb->_xnkid); if (pSecReg == NULL) { // Not found. Allocate a new CSecReg. pSecReg = SecRegAlloc(&pKeyExXbToXb->_xnaddrInit, pKeyReg); if (pSecReg == NULL) { TraceSz2(pktWarn, "[DISCARD] KeyExXbToXbInit from %s:%d reached CSecReg limit", ipaSrc.Str(), NTOHS(ipportSrc)); return; } } else if (pKeyExXbToXb->_liTime.QuadPart <= pSecReg->_liTime.QuadPart) { TraceSz2(pktWarn, "[DISCARD] KeyExXbToXbInit from %s:%d is obsolete or replayed", ipaSrc.Str(), NTOHS(ipportSrc)); return; } #ifdef XNET_FEATURE_SG else if (pSecReg->TestFlags(SRF_ONLINEPEER)) { // Update the XNADDR for this CSecReg. Is is possible that the online information // has changed since the last time the peer contacted us. pSecReg->_xnaddr = pKeyExXbToXb->_xnaddrInit; } #endif pSecReg->_liTime = pKeyExXbToXb->_liTime; // If we are in the INITSENT state, then we have simultaneous initiator packets // being sent. To resolve this, the side which has the highest ethernet address // is the initiator and the other side becomes the responder. if (pSecReg->_bState == SR_STATE_INITSENT) { if (memcmp(_ea._ab, pKeyExXbToXb->_xnaddrInit.abEnet, sizeof(CEnetAddr)) > 0) { // This side wins. The other side will run the same computation and will // respond to our initiator packet. We just discard theirs. TraceSz2(pktRecv, "[DISCARD] KeyExXbToXbInit from %s:%d simultaneous initiators", ipaSrc.Str(), NTOHS(ipportSrc)); return; } } // If we are in the RESPSENT state, this could be a retransmit of the initiator packet. // We verify this by checking the nonce of the initiator that we remembered from before. if ( pSecReg->_bState != SR_STATE_RESPSENT || memcmp(pSecReg->_abNonceInit, pKeyExXbToXb->_abNonceInit, sizeof(pSecReg->_abNonceInit)) != 0) { // This is a new key exchange initiation sequence. Reset the security association. SecRegSetIdle(pSecReg); pSecReg->_bState = SR_STATE_RESPSENT; pSecReg->_bRetry = SecRegRexmitRetries(pSecReg); pSecReg->_dwSpiXmit = pKeyExXbToXb->_dwSpiInit; // Remember the nonce of the initiator in case we need to retransmit this reponse. // Generate a random nonce for the responder. Assert(sizeof(pSecReg->_abNonceInit) == sizeof(pKeyExXbToXb->_abNonceInit)); memcpy(pSecReg->_abNonceInit, pKeyExXbToXb->_abNonceInit, sizeof(pSecReg->_abNonceInit)); Rand(pSecReg->_abNonceResp, sizeof(pSecReg->_abNonceResp)); // Generate the SHA and DES keys for this security association SecRegSetKey(pSecReg, pKeyReg->_abKeySha, sizeof(pKeyReg->_abKeySha), pKeyReg->_abDhX, sizeof(pKeyReg->_abDhX), (BYTE *)(pKeyExDh + 1), pKeyExDh->_cbEnt - sizeof(CKeyExHdr), FALSE); // Set the retransmission timer. This side is responsible for retransmitting // the key exchange response until it gets a secure packet from the other side. TimerSet(&pSecReg->_timer, TimerTick() + SecRegRexmitTimeoutInSeconds(pSecReg) * TICKS_PER_SECOND); } #ifdef XNET_FEATURE_SG if (pSecReg->TestFlags(SRF_ONLINEPEER) && ipaSrc != 0 && ipportSrc != 0) { // Capture the return address information from the initiator packet if the packet // arrived directly (versus being forwarded through the security gateways). pSecReg->_ipaDst = ipaSrc; pSecReg->_ipportDst = ipportSrc; } #endif // Send a KeyEx XbToXb response packet back to the initiator IpXmitKeyExXbToXb(pSecReg); } else { #if DBG if (Tag(keyExDrop) && pSecReg->_bRetry == SecRegRexmitRetries(pSecReg)) { // Drop the first key-exchange response for testing purposes TraceSz(pktWarn, "[DISCARD] Dropping first KeyExXbToXbResp (debug only)"); return; } #endif pSecReg->_liTime = pKeyExXbToXb->_liTime; #ifdef XNET_FEATURE_SG if (pSecReg->TestFlags(SRF_ONLINEPEER)) { // Capture the return address information from the responder packet. Responses are // always sent directly (as opposed to being forwarded through the security gateways). Assert(ipaSrc != 0 && ipportSrc != 0); pSecReg->_ipaDst = ipaSrc; pSecReg->_ipportDst = ipportSrc; } #endif // If we are in the INITWAIT state, this could be a retransmit of the responder packet. // We verify this by checking the nonce of the responder that we remembered from before. if ( pSecReg->_bState != SR_STATE_INITWAIT || memcmp(pSecReg->_abNonceResp, pKeyExXbToXb->_abNonceResp, sizeof(pSecReg->_abNonceResp)) != 0) { // This is the first time we've gotten a response from the other side to this // key exchange initiation. Remember the nonce of the responder and the dwSpiXmit. Assert(sizeof(pSecReg->_abNonceResp) == sizeof(pKeyExXbToXb->_abNonceResp)); memcpy(pSecReg->_abNonceResp, pKeyExXbToXb->_abNonceResp, sizeof(pSecReg->_abNonceResp)); pSecReg->_dwSpiXmit = pKeyExXbToXb->_dwSpiResp; // Generate the SHA and DES keys for this security association SecRegSetKey(pSecReg, pKeyReg->_abKeySha, sizeof(pKeyReg->_abKeySha), pKeyReg->_abDhX, sizeof(pKeyReg->_abDhX), (BYTE *)(pKeyExDh + 1), pKeyExDh->_cbEnt - sizeof(CKeyExHdr), TRUE); // Switch to the INITWAIT state. In this state, we know that the other side has computed // the same keys that we have, so we stop retransmitting the initiator packet. pSecReg->_bState = SR_STATE_INITWAIT; TimerSet(&pSecReg->_timer, TIMER_INFINITE); } // We must send a response. If there are waiting packets, send those now. // Otherwise, send a SECMSG_TYPE_PULSE to let the other side know we're alive. if (pSecReg->_pqWait.IsEmpty()) IpXmitSecMsg(pSecReg, SECMSG_TYPE_PULSE); else SecRegXmitQueue(pSecReg); } } #ifdef XNET_FEATURE_SG void CXnIp::IpRecvKeyExNatOpen(CPacket * ppkt, CIpAddr ipaSrc, CIpPort ipportSrc, CKeyExNatOpen * pKeyExNatOpen, UINT cbKeyEx) { ICHECK(IP, UDPC|SDPC); Assert(pKeyExNatOpen->_wType == KEYEX_TYPE_NATOPEN_INIT || pKeyExNatOpen->_wType == KEYEX_TYPE_NATOPEN_RESP); Assert(cbKeyEx >= pKeyExNatOpen->_cbEnt); if (pKeyExNatOpen->_cbEnt != sizeof(CKeyExNatOpen)) { TraceSz4(pktWarn, "[DISCARD] KeyExNatOepn from %s:%d entry has an incorrect size (%d,%d)", ipaSrc.Str(), NTOHS(ipportSrc), pKeyExNatOpen->_cbEnt, sizeof(CKeyExNatOpen)); return; } CKeyReg * pKeyReg = KeyRegLookup(&pKeyExNatOpen->_xnkid); if (pKeyReg == NULL) { TraceSz3(pktWarn, "[DISCARD] KeyExNatOpen from %s:%d to unregistered XNKID %s", ipaSrc.Str(), NTOHS(ipportSrc), HexStr(pKeyExNatOpen->_xnkid.ab, sizeof(pKeyExNatOpen->_xnkid.ab))); return; } // Authenticate the message including the header using the key-exchange-key corresponding to // the XNKID in the message. BYTE abHash[XC_SERVICE_DIGEST_SIZE]; XcHMAC(pKeyReg->_abKeySha, sizeof(pKeyReg->_abKeySha), (BYTE *)pKeyExNatOpen, offsetof(CKeyExNatOpen, _abHash), NULL, 0, abHash); Assert(sizeof(pKeyExNatOpen->_abHash) == sizeof(abHash)); if (memcmp(abHash, pKeyExNatOpen->_abHash, sizeof(abHash)) != 0) { TraceSz2(pktWarn, "[DISCARD] KeyExNatOpen from %s:%d failed to authenticate", ipaSrc.Str(), NTOHS(ipportSrc)); return; } if (pKeyExNatOpen->_wType == KEYEX_TYPE_NATOPEN_INIT) { Assert(ipaSrc == 0 && ipportSrc == 0); // Reply to this request by sending a CKeyExNatOpen packet to the destination address // specified. We change the type to KEYEX_TYPE_NATOPEN_RESP and recompute the abHash // to prove to the requester that we know the key-exhcange-key associated with the xnkid. CPacket * ppktXmit = PacketAlloc(PTAG_CKeyExPacket, PKTF_TYPE_UDP|PKTF_POOLALLOC, sizeof(DWORD) + sizeof(CKeyExNatOpen)); if (ppktXmit == NULL) { TraceSz(Warning, "IpRecvKeyExNatOpen - Out of memory allocating packet"); return; } CEnetHdr * pEnetHdr = ppktXmit->GetEnetHdr(); CIpHdr * pIpHdr = ppktXmit->GetIpHdr(); CEspHdr * pEspHdr = (CEspHdr *)(pIpHdr + 1); CKeyExNatOpen * pKeyExNatOpenXmit = (CKeyExNatOpen *)&pEspHdr->_dwSeq; IpFillHdr(ppktXmit, pKeyExNatOpen->_ipaDst, IPPROTOCOL_UDP); pIpHdr->_ipaSrc = _ipa; pEspHdr->_wLen = NTOHS(sizeof(CUdpHdr) + sizeof(DWORD) + sizeof(CKeyExNatOpen)); pEspHdr->_ipportSrc = ESPUDP_CLIENT_PORT; pEspHdr->_ipportDst = pKeyExNatOpen->_ipportDst; pEspHdr->_wChecksum = 0; pEspHdr->_dwSpi = 0; memcpy(pKeyExNatOpenXmit, pKeyExNatOpen, offsetof(CKeyExNatOpen, _abHash)); pKeyExNatOpenXmit->_wType = KEYEX_TYPE_NATOPEN_RESP; Assert(sizeof(pKeyExNatOpenXmit->_abHash) == XC_SERVICE_DIGEST_SIZE); XcHMAC(pKeyReg->_abKeySha, sizeof(pKeyReg->_abKeySha), (BYTE *)pKeyExNatOpenXmit, offsetof(CKeyExNatOpen, _abHash), NULL, 0, pKeyExNatOpenXmit->_abHash); TraceSz3(secStat, "Sending KeyExNatOpen RESP/%04X to %s:%d", pKeyExNatOpenXmit->_wFlags, pIpHdr->_ipaDst.Str(), NTOHS(pEspHdr->_ipportDst)); IpXmitIp(ppktXmit, NULL); return; } if (pKeyExNatOpen->_wType == KEYEX_TYPE_NATOPEN_RESP) { Assert(ipaSrc != 0 && ipportSrc != 0); if (pKeyExNatOpen->_wFlags & KNOF_XBTOXB_KEYEX) { // This message is the response from a NatOpen request we sent via the SG forwarding // mechanism. Lookup the CSecReg associated from the _dwCtx. CSecReg * pSecReg = SecRegLookup(pKeyExNatOpen->_dwCtx); Assert(sizeof(pSecReg->_abNonceInit) == sizeof(pKeyExNatOpen->_abNonce)); if ( pSecReg == NULL || pSecReg->_bState != SR_STATE_INITSENT || !pSecReg->TestFlags(SRF_ONLINEPEER) || memcmp(pSecReg->_abNonceInit, pKeyExNatOpen->_abNonce, sizeof(pSecReg->_abNonceInit)) != 0) { TraceSz6(pktWarn, "[DISCARD] KeyExNatOpen from %s:%d is obsolete or invalid (%d,%d,%d,%d)", ipaSrc.Str(), NTOHS(ipportSrc), pSecReg == NULL, pSecReg && pSecReg->_bState != SR_STATE_INITSENT, pSecReg && !pSecReg->TestFlags(SRF_ONLINEPEER), pSecReg && memcmp(pSecReg->_abNonceInit, pKeyExNatOpen->_abNonce, sizeof(pSecReg->_abNonceInit)) != 0); return; } // Capture the return address information from the sender, and retransmit the key exchange // initiator packet. The hope is that the sender will have programmed his NAT to allow // this packet to get through. It is still not guaranteed to get through, however, if our // NAT is agressively assigning ports and the other side is port filtering. pSecReg->_ipaDst = ipaSrc; pSecReg->_ipportDst = ipportSrc; // Retransmit the key exchange initiator packet right now (instead of waiting for the // timeout to expire). The second argument tells IpXmitKeyExXbToXb to not send a // CKeyExNatOpen message this time. Sending it now could cause a storm of packets // flowing if communication is one-sided (i.e. this side can receive but not // transmit). IpXmitKeyExXbToXb(pSecReg, TRUE); return; } //@@@ Handle KNOF_XBTOXB_PROBE here } TraceSz4(pktWarn, "[DISCARD] KeyExNatOpen from %s:%d type %04X is invalid (wFlags %04X)", ipaSrc.Str(), NTOHS(ipportSrc), pKeyExNatOpen->_wType, pKeyExNatOpen->_wFlags); return; } void CXnIp::IpRecvKeyExSgToXb(CPacket * ppkt, CIpAddr ipaSrc, CIpPort ipportSrc, CKeyExSgToXbResp * pKeyExSgToXbResp, UINT cbKeyEx) { ICHECK(IP, UDPC|SDPC); Assert(pKeyExSgToXbResp->_wType == KEYEX_TYPE_SGTOXB_RESP); Assert(cbKeyEx >= pKeyExSgToXbResp->_cbEnt); if (pKeyExSgToXbResp->_cbEnt != sizeof(CKeyExSgToXbResp)) { TraceSz4(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d entry has an incorrect size (%d,%d)", ipaSrc.Str(), NTOHS(ipportSrc), pKeyExSgToXbResp->_cbEnt, sizeof(CKeyExSgToXbResp)); return; } CSecReg * pSecReg = SecRegLookup(pKeyExSgToXbResp->_dwSpiInit); if (pSecReg == NULL) { TraceSz3(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d to unregistered address %s", ipaSrc.Str(), NTOHS(ipportSrc), CIpAddr(pKeyExSgToXbResp->_dwSpiInit).Str()); return; } if (!pSecReg->TestFlags(SRF_ONLINESERVER)) { TraceSz3(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d to non online-server address %s", ipaSrc.Str(), NTOHS(ipportSrc), CIpAddr(pKeyExSgToXbResp->_dwSpiInit).Str()); return; } if (pSecReg->_bState != SR_STATE_INITSENT) { TraceSz3(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d to %s while not in ISENT state", ipaSrc.Str(), NTOHS(ipportSrc), pSecReg->Str()); return; } Assert(sizeof(pSecReg->_abNonceInit) == sizeof(pKeyExSgToXbResp->_abNonceInit)); if (memcmp(pSecReg->_abNonceInit, pKeyExSgToXbResp->_abNonceInit, sizeof(pSecReg->_abNonceInit)) != 0) { TraceSz2(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d has incorrect nonce", ipaSrc.Str(), NTOHS(ipportSrc)); return; } // Advance to the next entry. It must contain the diffie-hellman g^X value. cbKeyEx -= pKeyExSgToXbResp->_cbEnt; CKeyExHdr * pKeyExDh = (CKeyExHdr *)((BYTE *)pKeyExSgToXbResp + pKeyExSgToXbResp->_cbEnt); if ( cbKeyEx < sizeof(CKeyExHdr) || pKeyExDh->_wType != KEYEX_TYPE_DH_GX || pKeyExDh->_cbEnt != sizeof(CKeyExHdr) + CBDHG1 || pKeyExDh->_cbEnt > cbKeyEx) { TraceSz6(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d DH entry is invalid (%d,%d,%d,%d)", ipaSrc.Str(), NTOHS(ipportSrc), cbKeyEx < sizeof(CKeyExHdr), cbKeyEx >= sizeof(CKeyExHdr) && pKeyExDh->_wType != KEYEX_TYPE_DH_GX, cbKeyEx >= sizeof(CKeyExHdr) && pKeyExDh->_cbEnt != sizeof(CKeyExHdr) + CBDHG1, cbKeyEx >= sizeof(CKeyExHdr) && pKeyExDh->_cbEnt > cbKeyEx); return; } // Advance to the next entry. It must contain the KERB_APREP and it must also be the last entry. cbKeyEx -= pKeyExDh->_cbEnt; CKeyExHdr * pKeyExApRep = (CKeyExHdr *)((BYTE *)pKeyExDh + pKeyExDh->_cbEnt); if (cbKeyEx < sizeof(CKeyExHdr) || pKeyExApRep->_cbEnt != cbKeyEx) { TraceSz4(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d ApRep entry is invalid (%d,%d)", ipaSrc.Str(), NTOHS(ipportSrc), cbKeyEx < sizeof(CKeyExHdr), cbKeyEx >= sizeof(CKeyExHdr) && pKeyExApRep->_cbEnt != cbKeyEx); return; } // Crack the ApRep BYTE * pbKeyHmac; // Key to use to compute HMAC-SHA of key-exchange message UINT cbKeyHmac; // Size of pbKeyHmac BYTE * pbShaApRep; // HMAC-SHA from the AP reply UINT cbShaApRep; // Size of pbShaApRep BYTE * pbDhX; // Diffie-hellman X UINT cbDhX; // Size of pbDhX #ifdef XNET_FEATURE_ONLINE XOKERBINFO * pxokerbinfo; BYTE abShaApRep[XC_SERVICE_DIGEST_SIZE]; if (pKeyExApRep->_wType == KEYEX_TYPE_KERB_APREP) { pxokerbinfo = _pXoBase ? _pXoBase->XoKerbGetInfo(pSecReg->_dwServiceId) : NULL; if (pxokerbinfo == NULL) { TraceSz3(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d cannot find XOKERBINFO for service %08lX", ipaSrc.Str(), NTOHS(ipportSrc), pSecReg->_dwServiceId); return; } if (!_pXoBase->XoKerbCrackApRep(pSecReg->_dwServiceId, &pSecReg->_liTime, abShaApRep, sizeof(abShaApRep), (BYTE *)(pKeyExApRep + 1), pKeyExApRep->_cbEnt - sizeof(CKeyExHdr))) { TraceSz2(Warning, "[DISCARD] KeyExSgToXbResp from %s:%d XoKerbCrackApRep failed", ipaSrc.Str(), NTOHS(ipportSrc)); return; } pbKeyHmac = pxokerbinfo->_abKey; cbKeyHmac = sizeof(pxokerbinfo->_abKey); pbShaApRep = abShaApRep; cbShaApRep = sizeof(abShaApRep); pbDhX = pxokerbinfo->_abDhX; cbDhX = sizeof(pxokerbinfo->_abDhX); goto ApRepDone; } #endif #ifdef XNET_FEATURE_INSECURE if (pKeyExApRep->_wType == KEYEX_TYPE_NULL_APREP) { CKeyExNullApRep * pKeyExNullApRep = (CKeyExNullApRep *)pKeyExApRep; if (pKeyExNullApRep->_cbEnt != sizeof(CKeyExNullApRep)) { TraceSz3(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d NULL_APREP has invalid size (%d bytes)", ipaSrc.Str(), NTOHS(ipportSrc), pKeyExApRep->_cbEnt); return; } if (pSecReg->_dwServiceId != 0) { TraceSz3(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d NULL_APREP unexpected for service %08lX", ipaSrc.Str(), NTOHS(ipportSrc), pSecReg->_dwServiceId); return; } pbKeyHmac = _abKeyNull; cbKeyHmac = sizeof(_abKeyNull); pbShaApRep = pKeyExNullApRep->_abSha; cbShaApRep = sizeof(pKeyExNullApRep->_abSha); pbDhX = _abDhXNull; cbDhX = sizeof(_abDhXNull); goto ApRepDone; } #endif TraceSz3(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d APREP type (%d) not supported", ipaSrc.Str(), NTOHS(ipportSrc), pKeyExApRep->_wType); return; ApRepDone: // Authenticate the key exchange message (all entries except the last). BYTE abSha[XC_SERVICE_DIGEST_SIZE]; XcHMAC(pbKeyHmac, cbKeyHmac, (BYTE *)pKeyExSgToXbResp, (BYTE *)pKeyExApRep - (BYTE *)pKeyExSgToXbResp, NULL, 0, abSha); Assert(cbShaApRep <= sizeof(abSha)); if (memcmp(abSha, pbShaApRep, cbShaApRep) != 0) { TraceSz2(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d failed to authenticate", ipaSrc.Str(), NTOHS(ipportSrc)); return; } if (pKeyExSgToXbResp->_dwFlags & SXRF_ENCRYPT_DES) pSecReg->_cbKeyDesRecv = pSecReg->_cbKeyDesXmit = XC_SERVICE_DES_KEYSIZE; else if (pKeyExSgToXbResp->_dwFlags & SXRF_ENCRYPT_3DES) pSecReg->_cbKeyDesRecv = pSecReg->_cbKeyDesXmit = XC_SERVICE_DES3_KEYSIZE; else { TraceSz2(pktWarn, "[DISCARD] KeyExSgToXbResp from %s:%d unrecognized encryption", ipaSrc.Str(), NTOHS(ipportSrc)); return; } #if DBG if (Tag(keyExDrop) && pSecReg->_bRetry == SecRegRexmitRetries(pSecReg)) { // Drop the first key-exchange response for testing purposes TraceSz(pktWarn, "[DISCARD] Dropping first KeyExSgToXbResp (debug only)"); return; } #endif pSecReg->_dwSpiXmit = pKeyExSgToXbResp->_dwSpiResp; pSecReg->_dwTickPulseTimeout = pKeyExSgToXbResp->_wXbToSgPulseTimeoutInSecs * TICKS_PER_SECOND; pSecReg->_dwTickTimeout = pKeyExSgToXbResp->_wXbToSgTimeoutInSecs * TICKS_PER_SECOND; pSecReg->_sgaddr = pKeyExSgToXbResp->_sgaddrInit; pSecReg->_ipaNat = pKeyExSgToXbResp->_inaInit.s_addr; pSecReg->_ipportNat = pKeyExSgToXbResp->_wPortInit; Assert(sizeof(pSecReg->_abNonceResp) == sizeof(pKeyExSgToXbResp->_abNonceResp)); memcpy(pSecReg->_abNonceResp, pKeyExSgToXbResp->_abNonceResp, sizeof(pSecReg->_abNonceResp)); SecRegSetKey(pSecReg, pbKeyHmac, cbKeyHmac, pbDhX, cbDhX, (BYTE *)(pKeyExDh + 1), pKeyExDh->_cbEnt - sizeof(CKeyExHdr), TRUE); pSecReg->_bState = SR_STATE_INITWAIT; TimerSet(&pSecReg->_timer, TIMER_INFINITE); if (pSecReg == _pSecRegLogon) { Assert(_uiLogonState == XN_LOGON_STATE_PENDING); _uiLogonState = XN_LOGON_STATE_ONLINE; if (_pEventLogon) { EvtSet(_pEventLogon, EVENT_INCREMENT); } } // We must respond with to the initiator. If there are waiting packets, send those now. // Otherwise, send a SECMSG_TYPE_PULSE to let the other side know we're alive. if (pSecReg->_pqWait.IsEmpty()) IpXmitSecMsg(pSecReg, SECMSG_TYPE_PULSE); else SecRegXmitQueue(pSecReg); } #endif void CXnIp::IpRecvSecMsg(CPacket * ppkt, CSecReg * pSecReg, DWORD dwSeq, CSecMsgHdr * pSecMsgHdr, UINT cb) { ICHECK(IP, UDPC|SDPC); if (cb < sizeof(CSecMsgHdr) || cb != pSecMsgHdr->_cbEnt) { TraceSz3(pktWarn, "[DISCARD] SecMsg to %s has wrong size (%d,%d)", pSecReg->Str(), cb < sizeof(CSecMsgHdr), cb >= sizeof(CSecMsgHdr) && cb != pSecMsgHdr->_cbEnt); return; } TraceSz3(pktRecv, "[SecMsg %s %04X]{+%d}", pSecReg->Str(), pSecMsgHdr->_wType, pSecMsgHdr->_cbEnt - sizeof(CSecMsgHdr)); if (pSecMsgHdr->_wType == SECMSG_TYPE_DELETE) { if (cb != sizeof(CSecMsgDelete)) { TraceSz2(pktWarn, "[DISCARD] SecMsg SECMSG_TYPE_DELETE to %s has incorrect payload size (%d)", pSecReg->Str(), cb); return; } // The other side has disconnected from us. Reset the CSecReg so that KeyEx will // be reinitiated next time a packet is transmitted. if (!pSecReg->TestFlags(SRF_OWNED)) SecRegFree(pSecReg); else SecRegSetIdle(pSecReg); return; } if (pSecMsgHdr->_wType == SECMSG_TYPE_PULSE) { if (cb != sizeof(CSecMsgPulse)) { TraceSz2(pktWarn, "[DISCARD] SecMsg SECMSG_TYPE_PULSE to %s has incorrect payload size (%d)", pSecReg->Str(), cb); return; } // Nothing else to do with this message. It was only sent to let us know that the // other side is still alive. return; } #ifdef XNET_FEATURE_SG if (pSecMsgHdr->_wType == SECMSG_TYPE_SGTOXB_PULSE) { if (cb < sizeof(CSecMsgSgToXbPulse)) { TraceSz2(pktWarn, "[DISCARD] SecMsg SECMSG_TYPE_SGTOXB_PULSE to %s has incorrect payload size (%d)", pSecReg->Str(), cb); return; } if (pSecReg == _pSecRegLogon) { CSecMsgSgToXbPulse * pSecMsgSgToXbPulse = (CSecMsgSgToXbPulse *)pSecMsgHdr; if (pSecMsgSgToXbPulse->_dwSeqAck && pSecMsgSgToXbPulse->_dwSeqAck >= _dwSeqXbToSg) { _dwSeqXbToSg = 0; *(DWORD *)_abXbToSgPulse = 0; } cb -= sizeof(CSecMsgSgToXbPulse); if (cb > 0) { BYTE * pb = (BYTE *)(pSecMsgSgToXbPulse + 1); while (cb > 0) { BYTE b = *pb++; cb -= 1; if ((b & ~(SGPULSE_USER_INDEX_MASK|SGPULSE_QFLAGS_CHANGE)) != 0) { TraceSz2(pktWarn, "SecMsg SECMSG_TYPE_SGTOXB_PULSE to %s has bad control byte (%02X)", pSecReg->Str(), b); break; } if (b & SGPULSE_QFLAGS_CHANGE) { if (cb < 2*sizeof(DWORD)) { TraceSz3(pktWarn, "SecMsg SECMSG_TYPE_SGTOXB_PULSE to %s ends prematurely (cb=%d,cbReq=%d)", pSecReg->Str(), cb, 2*sizeof(DWORD)); break; } DWORD dwQFlags = *(DWORD *)pb; pb += sizeof(DWORD); cb -= sizeof(DWORD); DWORD dwSeqQFlags = *(DWORD *)pb; pb += sizeof(DWORD); cb -= sizeof(DWORD); XOUSERINFO * pxouserinfo = &_axouserinfo[b & SGPULSE_USER_INDEX_MASK]; if (pxouserinfo->_dwSeqQFlags < dwSeqQFlags) { pxouserinfo->_dwSeqQFlags = dwSeqQFlags; pxouserinfo->_dwQFlags = dwQFlags; if (_pEventLogon) { EvtSet(_pEventLogon, EVENT_INCREMENT); } } } } _dwSeqSgToXb = dwSeq; } } return; } if (pSecMsgHdr->_wType == SECMSG_TYPE_XBTOXB_FORWARD) { if (cb < sizeof(CSecMsgXbToXbForward)) { TraceSz2(pktWarn, "[DISCARD] SecMsg SECMSG_TYPE_XBTOXB_FORWARD to %s has incorrect payload size (%d)", pSecReg->Str(), cb); return; } if (pSecReg != _pSecRegLogon) { TraceSz4(pktWarn, "[DISCARD] SecMsg SECMSG_TYPE_XBTOXB_FORWARD to %s received %s%s", pSecReg->Str(), _pSecRegLogon ? "from wrong SG (expected " : "while offline", _pSecRegLogon ? _pSecRegLogon->Str() : "", _pSecRegLogon ? ")" : ""); return; } CSecMsgXbToXbForward * pSecMsgXbToXbForward = (CSecMsgXbToXbForward *)pSecMsgHdr; Assert(pSecReg->TestFlags(SRF_ONLINESERVER)); Assert(memcmp(&pSecMsgXbToXbForward->_sgaddr, &pSecReg->_sgaddr, sizeof(SGADDR)) == 0); IpRecvKeyEx(ppkt, 0, 0, (CKeyExHdr *)(pSecMsgXbToXbForward + 1), cb - sizeof(CSecMsgXbToXbForward)); return; } #endif TraceSz3(pktWarn, "[DISCARD] SecMsg to %s has unrecognized type (%d, cb=%d)", pSecReg->Str(), pSecMsgHdr->_wType, pSecMsgHdr->_cbEnt - sizeof(CSecMsgHdr)); return; } // --------------------------------------------------------------------------------------- // CXnIp (Xmit) // --------------------------------------------------------------------------------------- void CXnIp::IpXmit(CPacket * ppkt, CRouteEntry ** pprte) { ICHECK(IP, UDPC|SDPC); Assert(ppkt->IsIp()); ppkt->Validate(); CIpHdr * pIpHdr = ppkt->GetIpHdr(); CIpAddr ipaDst = pIpHdr->_ipaDst; if (ipaDst.IsLoopback() || ipaDst == _ipa) { Assert(!ppkt->IsEsp()); pIpHdr->_ipaSrc = ipaDst; IpXmitIp(ppkt, pprte); return; } if (ppkt->IsEsp()) { if (ipaDst.IsBroadcast()) { IpXmitEsp(ppkt, NULL, NULL); return; } CSecReg * pSecReg = SecRegLookup(ipaDst); if (pSecReg == NULL) { TraceSz1(pktWarn, "[DISCARD] %s is not a registered secure address", ipaDst.Str()); EnetXmit(ppkt, 0); return; } if (pSecReg->IsXmitReady()) { IpXmitEsp(ppkt, pSecReg, pprte); return; } #ifdef XNET_FEATURE_SG if (pSecReg->TestFlags(SRF_ONLINESERVER) && _uiLogonState != XN_LOGON_STATE_ONLINE) { TraceSz2(pktWarn, "[DISCARD] Secure packet to %s %s", ipaDst.Str(), _uiLogonState == XN_LOGON_STATE_IDLE ? "before XOnlineLogon" : _uiLogonState == XN_LOGON_STATE_PENDING ? "before XOnlineLogon has connected" : "after XOnlineLogon connection lost"); EnetXmit(ppkt, 0); return; } #endif SecRegEnqueue(pSecReg, ppkt); return; } #ifdef XNET_FEATURE_INSECURE if (cfgFlags & XNET_STARTUP_BYPASS_SECURITY) { ppkt->SetFlags(PKTF_XMIT_INSECURE); } #endif #if defined(XNET_FEATURE_INSECURE) || defined(XNET_FEATURE_ICMP) || defined(XNET_FEATURE_DHCP) || defined(XNET_FEATURE_DNS) || defined(XNET_FEATURE_ONLINE) if (ppkt->TestFlags(PKTF_XMIT_INSECURE)) { pIpHdr->_ipaSrc = _ipa; IpXmitIp(ppkt, pprte); return; } #endif TraceSz1(pktWarn, "[DISCARD] %s is not a registered secure address", ipaDst.Str()); EnetXmit(ppkt, 0); } void CXnIp::IpXmitEsp(CPacket * ppkt, CSecReg * pSecReg, CRouteEntry ** pprte) { ICHECK(IP, UDPC|SDPC); Assert(ppkt->IsIp()); ppkt->Validate(); CIpHdr * pIpHdr = ppkt->GetIpHdr(); CEspHdr * pEspHdr = ppkt->GetEspHdr(); CEspTail * pEspTail = ppkt->GetEspTail(); CEnetHdr * pEnetHdr = ppkt->GetEnetHdr(); BYTE * pbKeySha; UINT cbKeySha; BYTE * pbKeyDes; UINT cbKeyDes; BYTE * pb; UINT cb; if (pSecReg == NULL) { ppkt->SetFlags(PKTF_XMIT_FRAME); pEnetHdr->_eaSrc = _ea; pEnetHdr->_eaDst.SetBroadcast(); pEnetHdr->_wType = ENET_TYPE_IP; pIpHdr->_ipaSrc = IPADDR_SECURE_DEFAULT; Assert(pIpHdr->_ipaDst.IsBroadcast()); pEspHdr->_ipportDst = ESPUDP_CLIENT_PORT; pEspHdr->_dwSpi = 0xFFFFFFFF; pEspHdr->_dwSeq = 0xFFFFFFFF; pbKeySha = _abKeyShaLan; cbKeySha = sizeof(_abKeyShaLan); pbKeyDes = _abKeyDesLan; cbKeyDes = sizeof(_abKeyDesLan); } else { #ifdef XNET_FEATURE_SG if (pSecReg->TestFlags(SRF_ONLINESERVER|SRF_ONLINEPEER)) { pIpHdr->_ipaSrc = _ipa; } else #endif { pIpHdr->_ipaSrc = IPADDR_SECURE_DEFAULT; ppkt->SetFlags(PKTF_XMIT_FRAME); pEnetHdr->_eaDst = *(CEnetAddr *)pSecReg->_xnaddr.abEnet; pEnetHdr->_eaSrc = _ea; pEnetHdr->_wType = ENET_TYPE_IP; } Assert(pSecReg->_ipaDst != 0 && pSecReg->_ipportDst != 0); pIpHdr->_ipaDst = pSecReg->_ipaDst; pEspHdr->_ipportDst = pSecReg->_ipportDst; pEspHdr->_dwSpi = pSecReg->_dwSpiXmit; pEspHdr->_dwSeq = HTONL(++pSecReg->_dwSeqXmit); pbKeySha = pSecReg->_abKeyShaXmit; cbKeySha = sizeof(pSecReg->_abKeyShaXmit); pbKeyDes = pSecReg->_abKeyDesXmit; cbKeyDes = pSecReg->_cbKeyDesXmit; pSecReg->_dwTickXmit = TimerTick(); } Assert(!!(cbKeyDes != 0) == !!ppkt->IsCrypt()); Assert(pIpHdr->GetOptLen() == 0); pb = (BYTE *)(pEspHdr + 1); cb = ppkt->GetCb() - sizeof(CIpHdr) - sizeof(CEspHdr) - sizeof(pEspTail->_abHash); Assert(cb == ROUNDUP4(cb)); ppkt->Validate(); pEspTail->_bNextHeader = pIpHdr->_bProtocol; pIpHdr->_bProtocol = IPPROTOCOL_UDP; pEspHdr->_ipportSrc = ESPUDP_CLIENT_PORT; pEspHdr->_wLen = HTONS(ppkt->GetCb() - sizeof(CIpHdr)); if (ppkt->IsCrypt()) { // Encrypt the packet from just after the [ESP] header to just before the _abHash in [ESPT] if (pSecReg == NULL) Rand(pb, XC_SERVICE_DES_BLOCKLEN); else memcpy(pb, pSecReg->_abIv, XC_SERVICE_DES_BLOCKLEN); Assert(cb == ROUNDUP8(cb)); CryptDes(XC_SERVICE_ENCRYPT, pbKeyDes, cbKeyDes, pb, pb + XC_SERVICE_DES_BLOCKLEN, cb - XC_SERVICE_DES_BLOCKLEN); if (pSecReg) { // Remember the last DES block to initialize the next packet's IV. This logically // extends the cipher-block-chaining and prevents us from having to call Rand // for each packet. memcpy(pSecReg->_abIv, pb + cb - XC_SERVICE_DES_BLOCKLEN, XC_SERVICE_DES_BLOCKLEN); } } // Authenicate the packet from the [ESP] header to just before the _abHash in [ESPT] BYTE abHash[XC_SERVICE_DIGEST_SIZE]; Assert(sizeof(pEspTail->_abHash) <= sizeof(abHash)); XcHMAC(pbKeySha, cbKeySha, (BYTE *)&pEspHdr->_dwSpi, (sizeof(CEspHdr) - sizeof(CUdpHdr)) + cb, NULL, 0, abHash); memcpy(pEspTail->_abHash, abHash, sizeof(pEspTail->_abHash)); TraceSz4(pktXmit, "[ESP %s #%d]%s[%d][ESPT]", CIpAddr(pEspHdr->_dwSpi).Str(), NTOHL(pEspHdr->_dwSeq), ppkt->TestFlags(PKTF_CRYPT) ? "[IV]" : "", cb - offsetof(CEspTail, _abHash) - (ppkt->TestFlags(PKTF_CRYPT) ? XC_SERVICE_DES_BLOCKLEN : 0)); IpXmitIp(ppkt, pprte); } void CXnIp::IpXmitKeyEx(CSecReg * pSecReg) { if (pSecReg->_bState == SR_STATE_IDLE) { pSecReg->_bState = SR_STATE_INITSENT; pSecReg->_bRetry = SecRegRexmitRetries(pSecReg); SecRegSetTicks(pSecReg); Rand(pSecReg->_abNonceInit, sizeof(pSecReg->_abNonceInit)); TimerSet(&pSecReg->_timer, TimerTick() + SecRegRexmitTimeoutInSeconds(pSecReg) * TICKS_PER_SECOND); } #ifdef XNET_FEATURE_SG if (pSecReg->TestFlags(SRF_ONLINESERVER)) { IpXmitKeyExXbToSg(pSecReg); return; } #endif IpXmitKeyExXbToXb(pSecReg); } void CXnIp::IpXmitKeyExXbToXb(CSecReg * pSecReg, BOOL fInhibitNatOpen) { ICHECK(IP, UDPC|SDPC); Assert(pSecReg->_bState == SR_STATE_INITSENT || pSecReg->_bState == SR_STATE_RESPSENT); Assert(pSecReg->_bState != SR_STATE_INITSENT || pSecReg->TestFlags(SRF_OWNED)); #ifdef XNET_FEATURE_SG if (pSecReg->TestFlags(SRF_ONLINEPEER) && _uiLogonState != XN_LOGON_STATE_ONLINE) { TraceSz2(pktWarn, "[DISCARD] KeyExXbToXb%s to online-peer %s while offline", pSecReg->_bState == SR_STATE_INITSENT ? "Init" : "Resp", pSecReg->Str()); return; } #endif UINT cbKeyEx = sizeof(CKeyExXbToXb) + sizeof(CKeyExHdr) + CBDHG1 + sizeof(CKeyExHdr) + XC_SERVICE_DIGEST_SIZE; CPacket * ppkt = PacketAlloc(PTAG_CKeyExPacket, PKTF_TYPE_UDP|PKTF_POOLALLOC, sizeof(DWORD) + cbKeyEx); if (ppkt == NULL) { TraceSz(Warning, "IpXmitKeyExXbToXb - Out of memory allocating packet"); return; } CEnetHdr * pEnetHdr = ppkt->GetEnetHdr(); CIpHdr * pIpHdr = ppkt->GetIpHdr(); CEspHdr * pEspHdr = (CEspHdr *)(pIpHdr + 1); IpFillHdr(ppkt, 0, IPPROTOCOL_UDP); #ifdef XNET_FEATURE_SG if (pSecReg->TestFlags(SRF_ONLINEPEER)) { pIpHdr->_ipaSrc = _ipa; if (pSecReg->_ipaDst != 0) { // We've received a packet directly from the other side at least once, so we'll // send the response back there. Assert(pSecReg->_ipaDst != 0 && pSecReg->_ipportDst != 0); pIpHdr->_ipaDst = pSecReg->_ipaDst; pEspHdr->_ipportDst = pSecReg->_ipportDst; } else if (pSecReg->_xnaddr.inaOnline.s_addr == _pSecRegLogon->_ipaNat) { // The other side has the same IP address as this side from the perspective // of the security gateways. This implies that the two sides are behind the same // NAT, so we should address packets to the inner IP address behind the NAT (i.e. // the actual IP address of the xbox). Assert(pSecReg->_xnaddr.ina.s_addr != 0); pIpHdr->_ipaDst = pSecReg->_xnaddr.ina.s_addr; pEspHdr->_ipportDst = ESPUDP_CLIENT_PORT; } else { // Our best guess is to send a packet to the return IP address and port that the // security gateway the other side is connected to is reporting as its return address. // Depending on how strict a NAT device is in front of the xbox, if any, this // packet may or may not make it through. Assert(pSecReg->_xnaddr.inaOnline.s_addr != 0 && pSecReg->_xnaddr.wPortOnline != 0); pIpHdr->_ipaDst = pSecReg->_xnaddr.inaOnline.s_addr; pEspHdr->_ipportDst = pSecReg->_xnaddr.wPortOnline; } if ( pSecReg->_bState == SR_STATE_INITSENT && pSecReg->_bRetry < cfgKeyExXbToXbRexmitRetries && pSecReg->_xnaddr.inaOnline.s_addr != _pSecRegLogon->_ipaNat && !fInhibitNatOpen) { // This is a retransmission of a key exchange initiator. In case the other side // is behind a NAT (other than ours) that is not letting our packet through, // we'll transmit a CKeyExNatOpen message via the SG forwarding mechanism. This // is guaranteed to arrive because every xbox that is online is maintaining a // bidirectional connection to a security gateway, and all security gateways have // direct access to each other. CKeyReg * pKeyReg = pSecReg->_pKeyReg; CKeyExNatOpen KeyExNatOpen; KeyExNatOpen._wType = KEYEX_TYPE_NATOPEN_INIT; KeyExNatOpen._cbEnt = sizeof(CKeyExNatOpen); KeyExNatOpen._xnkid = pSecReg->_pKeyReg->_xnkid; KeyExNatOpen._dwCtx = pSecReg->_dwSpiRecv; Assert(sizeof(KeyExNatOpen._abNonce) == sizeof(pSecReg->_abNonceInit)); memcpy(KeyExNatOpen._abNonce, pSecReg->_abNonceInit, sizeof(KeyExNatOpen._abNonce)); KeyExNatOpen._ipaDst = _pSecRegLogon->_ipaNat; KeyExNatOpen._ipportDst = _pSecRegLogon->_ipportNat; KeyExNatOpen._wFlags = KNOF_XBTOXB_KEYEX; Assert(sizeof(KeyExNatOpen._abHash) == XC_SERVICE_DIGEST_SIZE); XcHMAC(pKeyReg->_abKeySha, sizeof(pKeyReg->_abKeySha), (BYTE *)&KeyExNatOpen, offsetof(CKeyExNatOpen, _abHash), NULL, 0, KeyExNatOpen._abHash); TraceSz3(secStat, "Sending KeyExNatOpen INIT/%04X packet to %s via SGADDR %s", KeyExNatOpen._wFlags, pSecReg->Str(), HexStr(pSecReg->_xnaddr.abOnline, sizeof(pSecReg->_xnaddr.abOnline))); IpXmitSecMsg(_pSecRegLogon, SECMSG_TYPE_XBTOXB_FORWARD, pSecReg->_xnaddr.abOnline, sizeof(pSecReg->_xnaddr.abOnline), &KeyExNatOpen, sizeof(CKeyExNatOpen)); } } else #endif { ppkt->SetFlags(PKTF_XMIT_FRAME); pEnetHdr->_eaDst = *(CEnetAddr *)pSecReg->_xnaddr.abEnet; pEnetHdr->_eaSrc = _ea; pEnetHdr->_wType = ENET_TYPE_IP; pIpHdr->_ipaSrc = IPADDR_SECURE_DEFAULT; pIpHdr->_ipaDst = IPADDR_SECURE_DEFAULT; pEspHdr->_ipportDst = ESPUDP_CLIENT_PORT; } // Fill in the UDP header plus the SPI = 0 indicator of the ESP header pEspHdr->_wLen = NTOHS(sizeof(CUdpHdr) + sizeof(DWORD) + cbKeyEx); pEspHdr->_ipportSrc = ESPUDP_CLIENT_PORT; pEspHdr->_wChecksum = 0; pEspHdr->_dwSpi = 0; IpFillKeyExXbToXb(pSecReg, (CKeyExXbToXb *)&pEspHdr->_dwSeq); TraceSz4(secStat, "Sending KeyExXbToXb%s packet to %s via %s:%d", pSecReg->_bState == SR_STATE_INITSENT ? "Init" : "Resp", pSecReg->Str(), pIpHdr->_ipaDst.Str(), NTOHS(pEspHdr->_ipportDst)); IpXmitIp(ppkt, NULL); } void CXnIp::IpFillKeyExXbToXb(CSecReg * pSecReg, CKeyExXbToXb * pKeyExXbToXb) { memset(pKeyExXbToXb, 0, sizeof(CKeyExXbToXb)); CKeyReg * pKeyReg = pSecReg->_pKeyReg; pKeyExXbToXb->_cbEnt = sizeof(CKeyExXbToXb); pKeyExXbToXb->_xnkid = pKeyReg->_xnkid; pKeyExXbToXb->_liTime = _liTime; _liTime.QuadPart += 1; if (pSecReg->_bState == SR_STATE_INITSENT) { pKeyExXbToXb->_wType = KEYEX_TYPE_XBTOXB_INIT; pKeyExXbToXb->_dwSpiInit = pSecReg->_dwSpiRecv; Assert(sizeof(pKeyExXbToXb->_abNonceInit) == sizeof(pSecReg->_abNonceInit)); memcpy(pKeyExXbToXb->_abNonceInit, pSecReg->_abNonceInit, sizeof(pKeyExXbToXb->_abNonceInit)); IpGetXnAddr(&pKeyExXbToXb->_xnaddrInit); pKeyExXbToXb->_xnaddrResp = pSecReg->_xnaddr; } else { pKeyExXbToXb->_wType = KEYEX_TYPE_XBTOXB_RESP; pKeyExXbToXb->_dwSpiInit = pSecReg->_dwSpiXmit; pKeyExXbToXb->_dwSpiResp = pSecReg->_dwSpiRecv; Assert(sizeof(pKeyExXbToXb->_abNonceInit) == sizeof(pSecReg->_abNonceInit)); memcpy(pKeyExXbToXb->_abNonceInit, pSecReg->_abNonceInit, sizeof(pKeyExXbToXb->_abNonceInit)); Assert(sizeof(pKeyExXbToXb->_abNonceResp) == sizeof(pSecReg->_abNonceResp)); memcpy(pKeyExXbToXb->_abNonceResp, pSecReg->_abNonceResp, sizeof(pKeyExXbToXb->_abNonceResp)); pKeyExXbToXb->_xnaddrInit = pSecReg->_xnaddr; IpGetXnAddr(&pKeyExXbToXb->_xnaddrResp); } // Encrypt the portion of this entry which contains the XNADDR structures to prevent // observers from seeing any addressing information Rand(pKeyExXbToXb->_abIv, sizeof(pKeyExXbToXb->_abIv)); CryptDes(XC_SERVICE_ENCRYPT, pKeyReg->_abKeyDes, sizeof(pKeyReg->_abKeyDes), pKeyExXbToXb->_abIv, pKeyExXbToXb->_abIv + XC_SERVICE_DES_BLOCKLEN, sizeof(CKeyExXbToXb) - offsetof(CKeyExXbToXb, _abIv) - XC_SERVICE_DES_BLOCKLEN); // Fill in the DH key exchange entry next CKeyExHdr * pKeyExDh = (CKeyExHdr *)((BYTE *)pKeyExXbToXb + pKeyExXbToXb->_cbEnt); pKeyExDh->_wType = KEYEX_TYPE_DH_GX; pKeyExDh->_cbEnt = sizeof(CKeyExHdr) + CBDHG1; Assert(sizeof(pKeyReg->_abDhGX) == CBDHG1); memcpy(pKeyExDh + 1, pKeyReg->_abDhGX, CBDHG1); // Fill in the HMAC_SHA key exchange entry CKeyExHdr * pKeyExSha = (CKeyExHdr *)((BYTE *)pKeyExDh + pKeyExDh->_cbEnt); pKeyExSha->_wType = KEYEX_TYPE_HMAC_SHA; pKeyExSha->_cbEnt = sizeof(CKeyExHdr) + XC_SERVICE_DIGEST_SIZE; XcHMAC(pKeyReg->_abKeySha, sizeof(pKeyReg->_abKeySha), (BYTE *)pKeyExXbToXb, (BYTE *)pKeyExSha - (BYTE *)pKeyExXbToXb, NULL, 0, (BYTE *)(pKeyExSha + 1)); } #ifdef XNET_FEATURE_SG void CXnIp::IpXmitKeyExXbToSg(CSecReg * pSecReg) { ICHECK(IP, UDPC|SDPC); Assert(pSecReg->_bState == SR_STATE_INITSENT); BYTE * pbKeyHmac; UINT cbKeyHmac; BYTE * pbDhGX; UINT cbDhGX; UINT cbApReqMax; DWORD dwUserPerm; #ifdef XNET_FEATURE_INSECURE if (pSecReg->_dwServiceId == 0) { pbKeyHmac = _abKeyNull; cbKeyHmac = sizeof(_abKeyNull); pbDhGX = _abDhGXNull; cbDhGX = sizeof(_abDhGXNull); cbApReqMax = sizeof(CKeyExNullApReq); dwUserPerm = 0xE0A06020; goto ServiceDone; } #endif #ifdef XNET_FEATURE_ONLINE { XOKERBINFO * pxokerbinfo = _pXoBase ? _pXoBase->XoKerbGetInfo(pSecReg->_dwServiceId) : NULL; if (pxokerbinfo != NULL) { pbKeyHmac = pxokerbinfo->_abKey; cbKeyHmac = sizeof(pxokerbinfo->_abKey); pbDhGX = pxokerbinfo->_abDhGX; cbDhGX = sizeof(pxokerbinfo->_abDhGX); cbApReqMax = sizeof(CKeyExHdr) + pxokerbinfo->_cbApReqMax; dwUserPerm = pxokerbinfo->_dwUserPerm; goto ServiceDone; } } #endif TraceSz2(Warning, "IpXmitKeyExXbToSgInit to %s service %08lX is invalid", pSecReg->Str(), pSecReg->_dwServiceId); return; ServiceDone: UINT cbKeyEx = sizeof(CKeyExXbToSgInit) + sizeof(CKeyExHdr) + CBDHG1 + cbApReqMax; CPacket * ppkt = PacketAlloc(PTAG_CKeyExPacket, PKTF_TYPE_UDP|PKTF_POOLALLOC, sizeof(DWORD) + cbKeyEx); if (ppkt == NULL) { TraceSz1(Warning, "IpXmitKeyExXbToSgInit to %s - Out of memory allocating packet", pSecReg->Str()); return; } CIpHdr * pIpHdr = ppkt->GetIpHdr(); CEspHdr * pEspHdr = (CEspHdr *)(pIpHdr + 1); CKeyExHdr * pKeyExHdr = (CKeyExHdr *)&pEspHdr->_dwSeq; // Fill in the IP header IpFillHdr(ppkt, pSecReg->_ipaDst, IPPROTOCOL_UDP); pIpHdr->_ipaSrc = _ipa; // Fill in the UDP header plus the SPI = 0 indicator of the ESP header pEspHdr->_ipportDst = ESPUDP_CLIENT_PORT; pEspHdr->_ipportSrc = ESPUDP_CLIENT_PORT; pEspHdr->_wChecksum = 0; pEspHdr->_dwSpi = 0; // Fill in the first key exchange entry CKeyExXbToSgInit * pKeyExXbToSgInit = (CKeyExXbToSgInit *)pKeyExHdr; pKeyExXbToSgInit->_wType = KEYEX_TYPE_XBTOSG_INIT; pKeyExXbToSgInit->_cbEnt = sizeof(CKeyExXbToSgInit); pKeyExXbToSgInit->_dwFlags = 0; #ifdef XNET_FEATURE_SG if (_pSecRegLogon == pSecReg) { pKeyExXbToSgInit->_dwFlags |= XSIF_CONNECTION_SERVICE; } #endif pKeyExXbToSgInit->_dwSpiInit = pSecReg->_dwSpiRecv; pKeyExXbToSgInit->_dwUserPerm = dwUserPerm; Assert(sizeof(pKeyExXbToSgInit->_abNonceInit) == sizeof(pSecReg->_abNonceInit)); memcpy(pKeyExXbToSgInit->_abNonceInit, pSecReg->_abNonceInit, sizeof(pKeyExXbToSgInit->_abNonceInit)); // Fill in the DH key exchange entry next CKeyExHdr * pKeyExDh = (CKeyExHdr *)((BYTE *)pKeyExHdr + pKeyExHdr->_cbEnt); pKeyExDh->_wType = KEYEX_TYPE_DH_GX; pKeyExDh->_cbEnt = sizeof(CKeyExHdr) + CBDHG1; Assert(cbDhGX == CBDHG1); memcpy(pKeyExDh + 1, pbDhGX, CBDHG1); // Fill in the APREQ key exchange entry CKeyExHdr * pKeyExApReq = (CKeyExHdr *)((BYTE *)pKeyExDh + pKeyExDh->_cbEnt); BYTE abSha[XC_SERVICE_DIGEST_SIZE]; XcHMAC(pbKeyHmac, cbKeyHmac, (BYTE *)pKeyExXbToSgInit, (BYTE *)pKeyExApReq - (BYTE *)pKeyExXbToSgInit, NULL, 0, abSha); #ifdef XNET_FEATURE_INSECURE if (pSecReg->_dwServiceId == 0) { CKeyExNullApReq * pKeyExNullApReq = (CKeyExNullApReq *)pKeyExApReq; pKeyExNullApReq->_wType = KEYEX_TYPE_NULL_APREQ; pKeyExNullApReq->_cbEnt = sizeof(CKeyExNullApReq); pKeyExNullApReq->_liTime = _liTime; _liTime.QuadPart += 1; Assert(sizeof(pKeyExNullApReq->_abSha) == sizeof(abSha)); memcpy(pKeyExNullApReq->_abSha, abSha, sizeof(pKeyExNullApReq->_abSha)); memset(&pKeyExNullApReq->_AuthData, 0, sizeof(pKeyExNullApReq->_AuthData)); pKeyExNullApReq->_AuthData.SetCb(sizeof(pKeyExNullApReq->_AuthData)); pKeyExNullApReq->_AuthData.wAuthDataVersion = XONLINE_AUTHDATA_VERSION; pKeyExNullApReq->_AuthData.dwAuthDataSize = sizeof(XKERB_AD_XBOX); #ifdef XNET_FEATURE_ONLINE if (_pXoBase) { _pXoBase->XoKerbGetAuthData(&pKeyExNullApReq->_AuthData); } else #endif { memset(&pKeyExNullApReq->_AuthData.qwXboxID, 0xAA, sizeof(pKeyExNullApReq->_AuthData.qwXboxID)); memcpy(&pKeyExNullApReq->_AuthData.qwXboxID, _ea._ab, sizeof(_ea)); XOUSERINFO * pxouserinfo = _axouserinfo; XOUSERINFO * pxouserinfoEnd = _axouserinfo + dimensionof(_axouserinfo); XUID * pxuid = pKeyExNullApReq->_AuthData.users; Assert(dimensionof(_axouserinfo) == dimensionof(pKeyExNullApReq->_AuthData.users)); for (; pxouserinfo < pxouserinfoEnd; ++pxouserinfo, ++pxuid) { pxuid->qwUserID = pxouserinfo->_qwUserId; } } goto ApReqDone; } #endif #ifdef XNET_FEATURE_ONLINE { UINT cbApReq = cbApReqMax - sizeof(CKeyExHdr); if (!_pXoBase->XoKerbBuildApReq(pSecReg->_dwServiceId, &pSecReg->_liTime, abSha, sizeof(abSha), (BYTE *)(pKeyExApReq + 1), &cbApReq)) { TraceSz1(Warning, "IpXmitKeyExXbToSgInit to %s - XoKerbBuildApReq failed", pSecReg->Str()); return; } pKeyExApReq->_wType = KEYEX_TYPE_KERB_APREQ; pKeyExApReq->_cbEnt = sizeof(CKeyExHdr) + cbApReq; goto ApReqDone; } #endif ApReqDone: // Shrink the size of the packet if the ApReq is smaller than the space allocated Assert(cbApReqMax >= pKeyExApReq->_cbEnt); UINT cbExtra = cbApReqMax - pKeyExApReq->_cbEnt; ppkt->SetCb(ppkt->GetCb() - cbExtra); pEspHdr->_wLen = HTONS(sizeof(CUdpHdr) + sizeof(DWORD) + cbKeyEx - cbExtra); pIpHdr->_wLen = HTONS(pIpHdr->GetLen() - cbExtra); TraceSz3(secStat, "Sending KeyExXbToSgInit packet to %s via %s:%d", pSecReg->Str(), pIpHdr->_ipaDst.Str(), NTOHS(pEspHdr->_ipportDst)); IpXmitIp(ppkt, NULL); } #endif void CXnIp::IpXmitSecMsg(CSecReg * pSecReg, WORD wType, void * pv1, UINT cb1, void * pv2, UINT cb2) { ICHECK(IP, UDPC|SDPC); TraceSz3(secStat, "Sending SecMsg packet to %s (%04X, cb=%d)", pSecReg->Str(), wType, cb1+cb2); CPacket * ppkt = PacketAlloc(PTAG_CSecMsgPacket, PKTF_POOLALLOC|PKTF_TYPE_ESP|PKTF_CRYPT, sizeof(CSecMsgHdr) + cb1 + cb2); if (ppkt == NULL) { TraceSz(Warning, "IpXmitSecMsg - Out of memory allocating packet"); return; } // Fill in the IP header IpFillHdr(ppkt, pSecReg->_dwSpiRecv, IPPROTOCOL_SECMSG); // Fill in the payload CSecMsgHdr * pSecMsgHdr = (CSecMsgHdr *)((BYTE *)ppkt->GetEspHdr() + sizeof(CEspHdr) + XC_SERVICE_DES_BLOCKLEN); pSecMsgHdr->_wType = wType; pSecMsgHdr->_cbEnt = sizeof(CSecMsgHdr) + cb1 + cb2; if (cb1 > 0) { memcpy(pSecMsgHdr + 1, pv1, cb1); } if (cb2 > 0) { memcpy((BYTE *)(pSecMsgHdr + 1) + cb1, pv2, cb2); } TraceSz2(pktXmit, "[SecMsg %04X]{+%d}", wType, cb1 + cb2); IpXmitEsp(ppkt, pSecReg, NULL); } void CXnIp::IpXmitSecMsgDelete(CSecReg * pSecReg, DWORD dwReason) { ICHECK(IP, UDPC|SDPC); IpXmitSecMsg(pSecReg, SECMSG_TYPE_DELETE, &dwReason, sizeof(dwReason)); } #ifdef XNET_FEATURE_SG void CXnIp::IpXmitSecMsgXbToSgPulse(CSecReg * pSecReg) { ICHECK(IP, UDPC|SDPC); BYTE ab[sizeof(CSecMsgXbToSgPulse) + dimensionof(_axouserinfo) * (1 + sizeof(DWORD) + sizeof(XNKID) + 1 + sizeof(_axouserinfo[0]._abData))]; DWORD dwSeqXmit = pSecReg->_dwSeqXmit; CSecMsgXbToSgPulse * pSecMsgXbToSgPulse = (CSecMsgXbToSgPulse *)ab; BYTE * pb = &ab[sizeof(CSecMsgXbToSgPulse)]; if (pSecReg == _pSecRegLogon) { pSecMsgXbToSgPulse->_dwSeqAck = _dwSeqSgToXb; if (*(DWORD *)_abXbToSgPulse != 0) { XOUSERINFO * pxouserinfo = _axouserinfo; XOUSERINFO * pxouserinfoEnd = _axouserinfo + dimensionof(_axouserinfo); BYTE * pbXbToSgPulse = _abXbToSgPulse; for (; pxouserinfo < pxouserinfoEnd; ++pxouserinfo, ++pbXbToSgPulse) { BYTE b = *pbXbToSgPulse; if (b != 0) { Assert((b & (XBPULSE_USER_INDEX_MASK)) == pxouserinfo - _axouserinfo); Assert((b & ~(XBPULSE_USER_INDEX_MASK|XBPULSE_STATE_CHANGE|XBPULSE_XNKID_CHANGE|XBPULSE_TDATA_CHANGE)) == 0); Assert((b & (XBPULSE_STATE_CHANGE|XBPULSE_XNKID_CHANGE|XBPULSE_TDATA_CHANGE)) != 0); *pb++ = b; if (b & XBPULSE_STATE_CHANGE) { *(DWORD *)pb = pxouserinfo->_dwPState; pb += sizeof(DWORD); } if (b & XBPULSE_XNKID_CHANGE) { *(XNKID *)pb = pxouserinfo->_xnkid; pb += sizeof(XNKID); } if (b & XBPULSE_TDATA_CHANGE) { Assert(pxouserinfo->_cbData <= sizeof(pxouserinfo->_abData)); *pb++ = (BYTE)pxouserinfo->_cbData; if (pxouserinfo->_cbData > 0) { memcpy(pb, pxouserinfo->_abData, pxouserinfo->_cbData); pb += pxouserinfo->_cbData; } } } } } } else { pSecMsgXbToSgPulse->_dwSeqAck = 0; } Assert(pb - ab <= sizeof(ab)); IpXmitSecMsg(pSecReg, SECMSG_TYPE_XBTOSG_PULSE, &ab[sizeof(CSecMsgHdr)], pb - ab - sizeof(CSecMsgHdr)); if (pSecReg->_dwSeqXmit > dwSeqXmit) { Assert(pSecReg->_dwSeqXmit == dwSeqXmit + 1); pSecReg->_dwTickPulse = TimerTick(); if (pSecReg == _pSecRegLogon) { _dwSeqSgToXb = 0; if (*(DWORD *)_abXbToSgPulse != 0) { _dwSeqXbToSg = pSecReg->_dwSeqXmit; } } } } #endif void CXnIp::IpXmitIp(CPacket * ppkt, CRouteEntry ** pprte) { ICHECK(IP, UDPC|SDPC); Assert(ppkt->IsIp()); CIpHdr * pIpHdr = ppkt->GetIpHdr(); CIpAddr ipaDst = pIpHdr->_ipaDst; UINT cbHdrLen = pIpHdr->GetHdrLen(); UINT cbLen = ppkt->GetCb(); UINT uiChecksum; CPseudoHeader ph; Assert(pIpHdr->_ipaSrc != 0 || _ipa == 0); Assert(pIpHdr->_ipaDst != 0); TraceIpHdr(pktXmit, pIpHdr, ppkt->GetCb() - pIpHdr->GetHdrLen()); pIpHdr->_wChecksum = 0; pIpHdr->_wChecksum = (WORD)~tcpipxsum(0, pIpHdr, cbHdrLen); if (ppkt->IsUdp() || ppkt->IsEsp()) { CUdpHdr * pUdpHdr = (CUdpHdr *)((BYTE *)pIpHdr + cbHdrLen); ph._ipaSrc = pIpHdr->_ipaSrc; ph._ipaDst = ipaDst; ph._bZero = 0; ph._bProtocol = IPPROTOCOL_UDP; ph._wLen = HTONS(cbLen - cbHdrLen); pUdpHdr->_wChecksum = 0; uiChecksum = ~tcpipxsum(tcpipxsum(0, &ph, sizeof(ph)), pUdpHdr, cbLen - cbHdrLen); pUdpHdr->_wChecksum = uiChecksum - (uiChecksum == 0); } #ifdef XNET_FEATURE_INSECURE else if (ppkt->IsTcp()) { CTcpHdr * pTcpHdr = (CTcpHdr *)((BYTE *)pIpHdr + cbHdrLen); ph._ipaSrc = pIpHdr->_ipaSrc; ph._ipaDst = ipaDst; ph._bZero = 0; ph._bProtocol = IPPROTOCOL_TCP; ph._wLen = HTONS(cbLen - cbHdrLen); pTcpHdr->_wChecksum = 0; pTcpHdr->_wChecksum = ~tcpipxsum(tcpipxsum(0, &ph, sizeof(ph)), pTcpHdr, cbLen - cbHdrLen); } #endif #ifdef XNET_FEATURE_ROUTE if ( !ppkt->TestFlags(PKTF_XMIT_FRAME) && ipaDst != _ipa && !ipaDst.IsLoopback() && !ipaDst.IsBroadcast()) { CRouteEntry * prte = pprte ? *pprte : NULL; Assert(!prte || prte->_ipaDst == (ipaDst & prte->_ipaMask)); if (prte == NULL || prte->IsOrphan()) { if (prte) { RouteRelease(prte); } prte = RouteLookup(ipaDst); if (pprte) { *pprte = prte; } if (prte == NULL) { TraceSz1(pktWarn, "[DISCARD] No route found to %s", ipaDst.Str()); EnetXmit(ppkt, 0); return; } } if (!prte->IsLocal()) { ipaDst = prte->_ipaNext; } if (prte && pprte == NULL) { RouteRelease(prte); } } #endif #ifdef XNET_FEATURE_TRACE if (Tag(tcpRetrans) && ppkt->IsTcp() && ppkt->IsEsp() && ppkt->IsCrypt() && !ppkt->TestFlags(PKTF_XMIT_PRIORITY)) { // This causes encrypted TCP packets to fail on first transmit, and it used to // test the decrypt-before-retransmit code path. ppkt->ClearFlags(PKTF_XMIT_FRAME); ipaDst = 0; } #endif EnetXmit(ppkt, ipaDst); return; } // -------------------------------------------------------------------------------------- // CXnIp (Xmit Helpers) // -------------------------------------------------------------------------------------- void CXnIp::IpFillAndXmit(CPacket * ppkt, CIpAddr ipaDst, BYTE bProtocol, CRouteEntry ** pprte) { ICHECK(IP, USER|UDPC|SDPC); IpFillHdr(ppkt, ipaDst, bProtocol); RaiseToDpc(); IpXmit(ppkt, pprte); } void CXnIp::IpFillHdr(CPacket * ppkt, CIpAddr ipaDst, BYTE bProtocol) { ICHECK(IP, USER|UDPC|SDPC); Assert(ppkt->IsIp()); Assert(ppkt->GetHdrOptLen() == 0); CIpHdr * pIpHdr = ppkt->GetIpHdr(); pIpHdr->SetHdrLen(sizeof(CIpHdr)); pIpHdr->_bTos = cfgIpDefaultTos; pIpHdr->_wLen = HTONS((WORD)ppkt->GetCb()); pIpHdr->_wId = HTONS(GetNextDgramId()); pIpHdr->_wFragOff = 0; pIpHdr->_bTtl = cfgIpDefaultTtl; pIpHdr->_bProtocol = bProtocol; pIpHdr->_ipaSrc = 0; pIpHdr->_ipaDst = ipaDst; } // --------------------------------------------------------------------------------------- // CXnIp (Frag) // --------------------------------------------------------------------------------------- #ifdef XNET_FEATURE_FRAG class CFragPacket : public CPacket { friend class CXnIp; CTimer _timer; // reassembly timeout timer CIpAddr _ipaSrc; // source IP address CIpAddr _ipaDst; // destination IP address DWORD _dwProtoId; // protocol and datagram ID UINT _cbHdrLen; // length of reassembled datagram header UINT _cbLen; // length of reassembled datagram payload UINT _cBitRecv; // number of chunks of payload received so far UINT _cBitTotal; // total number of chunks of payload expected UINT _iBitEnd; // highest bit position filled so far BYTE _abBits[1]; // vector of bit flags for which payload fragments have arrived INLINE void SetBit(UINT iBit) { BYTE * pb = &_abBits[iBit / 8]; UINT iMask = (1 << (iBit % 8)); _cBitRecv += !(*pb & iMask); *pb |= iMask; } }; void CXnIp::FragTerm() { TCHECK(UDPC); while (_cFrag > 0) { FragFree(_pqFrag.GetHead()); } } void CXnIp::FragFree(CPacket * ppkt) { ICHECK(IP, UDPC|SDPC); Assert(ppkt != NULL); Assert(_cFrag > 0); Assert(_cFrag == _pqFrag.Count()); _pqFrag.Dequeue(ppkt); _cFrag -= 1; Assert(_cFrag == _pqFrag.Count()); TimerSet(&((CFragPacket *)ppkt)->_timer, TIMER_INFINITE); #ifdef XNET_FEATURE_FRAG_LOOPBACK ppkt->ClearFlags(PKTF_RECV_LOOPBACK); #endif PacketFree(ppkt); } void CXnIp::FragTimer(CTimer * pt) { ICHECK(IP, UDPC|SDPC); CFragPacket * ppktFrag = (CFragPacket *)((BYTE *)pt - offsetof(CFragPacket, _timer)); TraceSz3(Warning, "FragTimer - Packet reassembly timeout [IP %s %s (%08lX)]", ppktFrag->_ipaDst.Str(), ppktFrag->_ipaSrc.Str(), ppktFrag->_dwProtoId); FragFree(ppktFrag); } void CXnIp::FragRecv(CPacket * ppkt, CIpHdr * pIpHdr, UINT cbHdrLen, UINT cbLen) { ICHECK(IP, UDPC|SDPC); CFragPacket * ppktFrag; DWORD dwProtoId; UINT uiFragOff; BOOL fMoreFrag; UINT cbPkt; UINT ibEnd; UINT iBit, iBitEnd; uiFragOff = NTOHS(pIpHdr->_wFragOff); fMoreFrag = !!(uiFragOff & MORE_FRAGMENTS); uiFragOff &= (uiFragOff & FRAGOFFSET_MASK); dwProtoId = (NTOHS(pIpHdr->_wId) << 16) | pIpHdr->_bProtocol; ppktFrag = (CFragPacket *)_pqFrag.GetHead(); for (; ppktFrag; ppktFrag = (CFragPacket *)ppktFrag->GetNextPkt()) { if ( ppktFrag->_dwProtoId == dwProtoId && ppktFrag->_ipaSrc == pIpHdr->_ipaSrc && ppktFrag->_ipaDst == pIpHdr->_ipaDst) break; } if (ppktFrag == NULL) { if (_cFrag >= cfgIpFragMaxSimultaneous) { TraceSz(pktWarn, "[DISCARD] Too many fragmented IP datagrams needing reassembly"); return; } cbPkt = offsetof(CFragPacket, _abBits); cbPkt += ((cfgIpFragMaxPacketDiv256 * 256) / 8 + 7) / 8; cbPkt = ROUNDUP4(cbPkt); ppktFrag = (CFragPacket *)PacketAlloc(PTAG_CFragPacket, PKTF_TYPE_ENET|PKTF_POOLALLOC, cfgIpFragMaxPacketDiv256 * 256, cbPkt, (PFNPKTFREE)FragFree); if (ppktFrag == NULL) { TraceSz(pktWarn, "[DISCARD] Out of memory allocating CFragPacket"); return; } #ifdef XNET_FEATURE_FRAG_LOOPBACK if (ppkt->TestFlags(PKTF_RECV_LOOPBACK)) { ppktFrag->SetFlags(PKTF_RECV_LOOPBACK); } #endif ppktFrag->_timer.Init((PFNTIMER)FragTimer); ppktFrag->_ipaSrc = pIpHdr->_ipaSrc; ppktFrag->_ipaDst = pIpHdr->_ipaDst; ppktFrag->_dwProtoId = dwProtoId; ppktFrag->_cbHdrLen = 0; ppktFrag->_cbLen = 0; ppktFrag->_cBitRecv = 0; ppktFrag->_cBitTotal = 0; ppktFrag->_iBitEnd = 0; memset(ppktFrag->_abBits, 0, cbPkt - offsetof(CFragPacket, _abBits)); TimerSet(&ppktFrag->_timer, TimerTick() + cfgIpFragTimeoutInSeconds * TICKS_PER_SECOND); Assert(_cFrag == _pqFrag.Count()); _pqFrag.InsertTail(ppktFrag); _cFrag += 1; Assert(_cFrag == _pqFrag.Count()); } if (uiFragOff == 0) { if (ppktFrag->_cbHdrLen > 0) { if (ppktFrag->_cbHdrLen != cbHdrLen) { TraceSz2(pktWarn, "[DISCARD] Duplicate first fragment and header sizes don't match (%d vs. %d)", ppktFrag->_cbHdrLen, cbHdrLen); goto fragfree; } TraceSz(pktWarn, "[DISCARD] Ignoring duplicate first fragment"); return; } memcpy((BYTE *)ppktFrag->GetPv() + MAXIPHDRLEN - cbHdrLen, pIpHdr, cbHdrLen); ppktFrag->_cbHdrLen = cbHdrLen; } cbLen -= cbHdrLen; if (cbLen > 0) { ibEnd = (uiFragOff * 8) + cbLen; iBit = uiFragOff; iBitEnd = iBit + (cbLen + 7) / 8; if (MAXIPHDRLEN + ibEnd > ppktFrag->GetCb()) { TraceSz1(pktWarn, "[DISCARD] Maximum reassembly size exceeded (%d)", ibEnd); goto fragfree; } if (fMoreFrag) { if ((cbLen % 8) != 0) { TraceSz1(pktWarn, "[DISCARD] Fragment with MF flag has non 8-byte multiple payload (%d)", cbLen); goto fragfree; } } else { if (ppktFrag->_cbLen == 0) { if (ppktFrag->_iBitEnd >= iBitEnd) { TraceSz(pktWarn, "[DISCARD] Fragments received past end of last fragment"); goto fragfree; } ppktFrag->_cbLen = ibEnd; ppktFrag->_cBitTotal = iBitEnd; } else if (ppktFrag->_cbLen != ibEnd) { TraceSz2(pktWarn, "[DISCARD] Last fragment was duplicated and has a different size (%d vs. %d)", ppktFrag->_cbLen, ibEnd); goto fragfree; } } memcpy((BYTE *)ppktFrag->GetPv() + MAXIPHDRLEN + (uiFragOff * 8), (BYTE *)pIpHdr + cbHdrLen, cbLen); if (ppktFrag->_cBitTotal && iBitEnd > ppktFrag->_cBitTotal) { TraceSz(pktWarn, "[DISCARD] Fragment received past end of last fragment"); goto fragfree; } if (ppktFrag->_iBitEnd < iBitEnd) ppktFrag->_iBitEnd = iBitEnd; for (; iBit < iBitEnd; ++iBit) { ppktFrag->SetBit(iBit); Assert(ppktFrag->_cBitTotal == 0 || ppktFrag->_cBitRecv <= ppktFrag->_cBitTotal); } } if (ppktFrag->_cbHdrLen && ppktFrag->_cbLen && ppktFrag->_cBitRecv == ppktFrag->_cBitTotal) { ppktFrag->SetPv((BYTE *)ppktFrag->GetPv() + MAXIPHDRLEN - ppktFrag->_cbHdrLen); ppktFrag->SetCb(ppktFrag->_cbHdrLen + ppktFrag->_cbLen); ppktFrag->SetType(PKTF_TYPE_IP); pIpHdr = ppktFrag->GetIpHdr(); pIpHdr->_wLen = HTONS((WORD)ppktFrag->GetCb()); pIpHdr->_wFragOff = 0; pIpHdr->_wChecksum = 0; pIpHdr->_wChecksum = (WORD)~tcpipxsum(0, pIpHdr, ppktFrag->_cbHdrLen); TraceSz(pktRecv, "Fragment processing complete. Processing reassembled packet."); IpRecv(ppktFrag); goto fragfree; } TraceSz4(pktRecv, "[FRAGHOLD] Waiting for more fragments (cbHdrLen=%ld,cBitRecv=%ld,cBitTotal=%ld,cbLen=%ld)", ppktFrag->_cbHdrLen, ppktFrag->_cBitRecv, ppktFrag->_cBitTotal, ppktFrag->_cbLen); return; fragfree: FragFree(ppktFrag); return; } #endif // XNET_FEATURE_FRAG // --------------------------------------------------------------------------------------- // CXnIp (Route) // --------------------------------------------------------------------------------------- #ifdef XNET_FEATURE_ROUTE NTSTATUS CXnIp::RouteInit() { TCHECK(USER); InitializeListHead(&_leRouteList); Assert(_ipaDstLast == 0); Assert(_prteLast == NULL); return(NETERR_OK); } void CXnIp::RouteTerm() { TCHECK(UDPC); RouteListOrphan(); } void CXnIp::RouteInvalidateCache() { ICHECK(IP, UDPC|SDPC); _ipaDstLast = 0; _prteLast = NULL; } void CXnIp::RouteListOrphan() { ICHECK(IP, UDPC|SDPC); if (!IsListNull(&_leRouteList)) { CRouteEntry * prte = (CRouteEntry *)_leRouteList.Flink; CRouteEntry * prteNext; while (prte != (CRouteEntry *)&_leRouteList) { prteNext = (CRouteEntry *)prte->_le.Flink; RouteEntryOrphan(prte); prte = prteNext; } } } void CXnIp::RouteEntryOrphan(CRouteEntry * prte) { ICHECK(IP, UDPC|SDPC); RouteInvalidateCache(); AssertListEntry(&_leRouteList, &prte->_le); RemoveEntryList(&prte->_le); prte->SetFlags(RTEF_ORPHAN); RouteRelease(prte); } CRouteEntry * CXnIp::RouteLookup(CIpAddr ipaDst) { ICHECK(IP, UDPC|SDPC); CRouteEntry * prte; CRouteEntry * prteNext; CRouteEntry * prteFound; if (ipaDst == 0) { return(NULL); } if (ipaDst == _ipaDstLast) { // _prteLast may be NULL if the last successful lookup for this IP address // failed to find any route for it. RouteAdd will invalidate the cache // and force a slow lookup. Until then, quickly keep responding that // there is no route entry for this IP address. if (_prteLast) _prteLast->AddRef(); return(_prteLast); } prte = (CRouteEntry *)_leRouteList.Flink; prteFound = NULL; while (prte != (CRouteEntry *)&_leRouteList) { prteNext = (CRouteEntry *)prte->_le.Flink; if ((ipaDst & prte->_ipaMask) == prte->_ipaDst) { prteFound = prte; prte->AddRef(); break; } prte = prteNext; } _ipaDstLast = ipaDst; _prteLast = prteFound; return(prteFound); } void CXnIp::RouteAdd(CIpAddr ipaDst, CIpAddr ipaMask, CIpAddr ipaNext, WORD wFlags, WORD wMetric) { ICHECK(IP, UDPC|SDPC); CRouteEntry * prte; CRouteEntry * prteNext; CRouteEntry * prteNew; // Validate input parameters: // - address mask must be of the form 111...000 // be careful about the byte order // - next hop address cannot be a broadcast address ipaDst = ipaDst & ipaMask; if ( (!ipaDst.IsValidUnicast() && !(wFlags & RTEF_DEFAULT)) || !ipaNext.IsValidUnicast() || !ipaMask.IsValidMask()) { TraceSz1(Warning, "Bad route to %s:", ipaDst.Str()); TraceSz1(Warning, " mask = %s", ipaMask.Str()); TraceSz1(Warning, " nexthop = %s", ipaNext.Str()); return; } // Find out if the specified route is already in the table AssertList(&_leRouteList); prte = (CRouteEntry *)_leRouteList.Flink; prteNew = NULL; while (prte != (CRouteEntry *)&_leRouteList) { prteNext = (CRouteEntry *)prte->_le.Flink; if ( prte->_ipaDst == ipaDst && prte->_ipaMask == ipaMask && (ipaMask != 0 || prte->_ipaNext == ipaNext)) { // Update the existing route information prteNew = prte; RemoveEntryList(&prte->_le); break; } prte = prteNext; } if (prteNew == NULL) { prteNew = (CRouteEntry *)PoolAllocZ(sizeof(CRouteEntry), PTAG_CRouteEntry); if (prteNew == NULL) { TraceSz(Warning, "CXnIp::RouteAdd - Out of memory allocating route entry"); return; } prteNew->_cRefs = 1; } RouteInvalidateCache(); prteNew->_wFlags = wFlags; prteNew->_wMetric = wMetric; prteNew->_ipaDst = ipaDst; prteNew->_ipaMask = ipaMask; prteNew->_ipaNext = ipaNext; // Insert the new route into the table // sorted by the mask length and route metric AssertList(&_leRouteList); prte = (CRouteEntry *)_leRouteList.Flink; while (prte != (CRouteEntry *)&_leRouteList) { prteNext = (CRouteEntry *)prte->_le.Flink; if ( ipaMask > prte->_ipaMask || ( ipaMask == prte->_ipaMask && wMetric < prte->_wMetric)) { break; } prte = prteNext; } prteNew->_le.Flink = (LIST_ENTRY *)prte; prteNew->_le.Blink = prte->_le.Blink; prte->_le.Blink->Flink = (LIST_ENTRY *)prteNew; prte->_le.Blink = (LIST_ENTRY *)prteNew; AssertList(&_leRouteList); } void CXnIp::RouteDelete(CIpAddr ipaDst, CIpAddr ipaMask, CIpAddr ipaNext) { ICHECK(IP, UDPC|SDPC); CRouteEntry * prte; CRouteEntry * prteNext; RaiseToDpc(); AssertList(&_leRouteList); prte = (CRouteEntry *)_leRouteList.Flink; while (prte != (CRouteEntry *)&_leRouteList) { prteNext = (CRouteEntry *)prte->_le.Flink; if ( prte->_ipaDst == ipaDst && prte->_ipaMask == ipaMask && prte->_ipaNext == ipaNext) { RouteEntryOrphan(prte); break; } prte = prteNext; } } void CXnIp::RouteRedirect(CIpAddr ipaDst, CIpAddr ipaOldGateway, CIpAddr ipaNewGateway) { ICHECK(IP, SDPC); CRouteEntry * prte; // The redirected destination must be a unicast address if (!ipaDst.IsValidUnicast()) return; // Validate the new gateway address: // must be a unicast address on the same subnet // through which the redirect message arrived if ( !ipaNewGateway.IsValidUnicast() || _ipaSubnet != (ipaNewGateway & _ipaMask)) return; // Find the current route to the destination // and see if the sender of the redirect message // is the current next-hop gateway prte = RouteLookup(ipaDst); if (prte == NULL) { return; } else if (prte->_ipaNext != ipaOldGateway) { RouteRelease(prte); return; } // Add a new host route RouteAdd(ipaDst, 0xFFFFFFFF, ipaNewGateway, RTEF_HOST, RTE_DEFAULT_METRIC); } void CXnIp::RouteRelease(CRouteEntry * prte) { ICHECK(IP, USER|UDPC|SDPC); if (InterlockedDecrement(&prte->_cRefs) == 0) { Assert(prte->IsOrphan()); PoolFree(prte); } } #if DBG void CXnIp::RouteListDump() { ICHECK(IP, USER|UDPC|SDPC); RaiseToDpc(); TraceSz(Verbose, "\n*** Route table:\n"); CRouteEntry * prte = (CRouteEntry *)_leRouteList.Flink; while (prte != (CRouteEntry *)&_leRouteList) { TraceSz6(Verbose, "%s/%s/%s %02X %d %d", prte->_ipaDst.Str(), prte->_ipaMask.Str(), prte->_ipaNext.Str(), prte->_wFlags, prte->_wMetric, prte->_cRefs); prte = (CRouteEntry *)prte->_le.Flink; } } #endif #endif // --------------------------------------------------------------------------------------- // CXnIp (Register / Unregister Key) // --------------------------------------------------------------------------------------- INT CXnIp::IpRegisterKey(const XNKID * pxnkid, const XNKEY * pxnkey) { ICHECK(IP, USER); #ifdef XNET_FEATURE_SG if (!XNetXnKidIsSystemLink(pxnkid) && !XNetXnKidIsOnlinePeer(pxnkid)) #else if (!XNetXnKidIsSystemLink(pxnkid)) #endif { TraceSz1(Warning, "IpRegisterKey - XNKID %s is not peer-to-peer", HexStr(pxnkid->ab, sizeof(pxnkid->ab))); return(WSAEINVAL); } BYTE abHash[XC_SERVICE_DIGEST_SIZE]; BYTE abDhX[CBDHG1]; BYTE abDhGX[CBDHG1]; // HMAC the key with the LAN keys as a precaution against the title not keeping // its key private (by sending it in cleartext over the network, for example). // This means that even if you intercept an XNKEY you still need to figure // out the LAN key to make use of it. XcHMAC(_abKeyShaLan, sizeof(_abKeyShaLan), (BYTE *)pxnkid->ab, sizeof(pxnkid->ab), (BYTE *)pxnkey->ab, sizeof(pxnkey->ab), abHash); // Generate a diffie-hellman X and g^X for use in key-exchange Rand(abDhX, sizeof(abDhX)); XcModExp((DWORD *)abDhGX, (DWORD *)g_abOakleyGroup1Base, (DWORD *)abDhX, (DWORD *)g_abOakleyGroup1Mod, CBDHG1 / sizeof(DWORD)); RaiseToDpc(); CKeyReg * pKeyReg = KeyRegLookup(pxnkid); if (pKeyReg != NULL) { TraceSz1(Warning, "IpRegisterKey - XNKID %s is already registered", HexStr(pxnkid->ab, sizeof(pxnkid->ab))); return(WSAEALREADY); } if (_cKeyReg == cfgKeyRegMax) { TraceSz1(Warning, "IpRegisterKey - Reached config limit for key registrations (%d)", cfgKeyRegMax); return(WSAENOMORE); } pKeyReg = &_pKeyReg[_cKeyReg++]; pKeyReg->_xnkid = *pxnkid; memcpy(pKeyReg->_abKeySha, abHash, sizeof(pKeyReg->_abKeySha)); memcpy(pKeyReg->_abKeyDes, &abHash[sizeof(abHash) - sizeof(pKeyReg->_abKeyDes)], sizeof(pKeyReg->_abKeyDes)); memcpy(pKeyReg->_abDhX, abDhX, sizeof(pKeyReg->_abDhX)); memcpy(pKeyReg->_abDhGX, abDhGX, sizeof(pKeyReg->_abDhGX)); XcDESKeyParity(pKeyReg->_abKeyDes, sizeof(pKeyReg->_abKeyDes)); pKeyReg->_pQosReg = NULL; TraceSz1(secStat, "XNKID %s: Registered", HexStr(pxnkid->ab, sizeof(pxnkid->ab))); return(0); } INT CXnIp::IpCreateKey(XNKID * pxnkid, XNKEY * pxnkey) { ICHECK(IP, USER|UDPC); Rand((BYTE *)pxnkid, sizeof(XNKID)); Rand((BYTE *)pxnkey, sizeof(XNKEY)); pxnkid->ab[0] &= ~XNET_XNKID_MASK; pxnkid->ab[0] |= XNET_XNKID_SYSTEM_LINK; return(0); } INT CXnIp::IpUnregisterKey(const XNKID * pxnkid) { ICHECK(IP, USER|UDPC); RaiseToDpc(); CKeyReg * pKeyReg = KeyRegLookup(pxnkid); if (pKeyReg == NULL) { TraceSz1(Warning, "IpUnregisterKey - XNKID %s is not registered", HexStr(pxnkid->ab, sizeof(pxnkid->ab))); return(WSAEINVAL); } #if 0 //@@@ if (pKeyReg->_pbQos) { SysFree(pKeyReg->_pbQos); pKeyReg->_pbQos = NULL; pKeyReg->_cbQos = 0; } #endif TraceSz1(secStat, "XNKID %s: Unregistered", HexStr(pxnkid->ab, sizeof(pxnkid->ab))); CKeyReg * pKeyRegLast = &_pKeyReg[--_cKeyReg]; UINT cSecReg = cfgSecRegMax; CSecReg * pSecReg = _pSecReg; // Move the last entry to this spot to keep the table contiguous *pKeyReg = *pKeyRegLast; // Free any CSecReg that points at the deleted key entry. Update any CSecReg that // pointed to the last CKeyReg that was just moved. for (; cSecReg > 0; ++pSecReg, --cSecReg) { if (pSecReg->_dwSpiRecv) { if (pSecReg->_pKeyReg == pKeyReg) { if (pSecReg->IsXmitReady() && !pSecReg->TestFlags(SRF_SECMSG_DELETE)) { pSecReg->SetFlags(SRF_SECMSG_DELETE); IpXmitSecMsgDelete(pSecReg, SECMSG_DELETE_UNREGISTER); } SecRegFree(pSecReg); } else if (pSecReg->_pKeyReg == pKeyRegLast) { pSecReg->_pKeyReg = pKeyReg; } } } return(0); } // --------------------------------------------------------------------------------------- // CXnIp (KeyRegLookup / KeyRegKey) // --------------------------------------------------------------------------------------- CXnIp::CKeyReg * CXnIp::KeyRegLookup(const XNKID * pxnkid) { ICHECK(IP, UDPC|SDPC); CKeyReg * pKeyReg = _pKeyReg; UINT cKeyReg = _cKeyReg; for (; cKeyReg-- > 0; ++pKeyReg) { if (memcmp(pKeyReg->_xnkid.ab, pxnkid->ab, sizeof(XNKID)) == 0) { return(pKeyReg); } } return(NULL); } // --------------------------------------------------------------------------------------- // CXnIp (DES Encrypt / Decrypt) // --------------------------------------------------------------------------------------- void CXnIp::CryptDes(DWORD dwOp, BYTE * pbKey, UINT cbKey, BYTE * pbIv, BYTE * pb, UINT cb) { Assert(cbKey == XC_SERVICE_DES_KEYSIZE || cbKey == XC_SERVICE_DES3_KEYSIZE); BYTE abDesTable[XC_SERVICE_DES3_TABLESIZE]; BYTE abFeedback[XC_SERVICE_DES_BLOCKLEN]; XcKeyTable(cbKey == XC_SERVICE_DES_KEYSIZE ? XC_SERVICE_DES_CIPHER : XC_SERVICE_DES3_CIPHER, abDesTable, pbKey); memcpy(abFeedback, pbIv, XC_SERVICE_DES_BLOCKLEN); XcBlockCryptCBC(cbKey == XC_SERVICE_DES_KEYSIZE ? XC_SERVICE_DES_CIPHER : XC_SERVICE_DES3_CIPHER, cb, pb, pb, abDesTable, dwOp, abFeedback); } // --------------------------------------------------------------------------------------- // CXnIp (InAddr) // --------------------------------------------------------------------------------------- INT CXnIp::IpXnAddrToInAddr(const XNADDR * pxna, const XNKID * pxnkid, CIpAddr * pipa) { ICHECK(IP, USER); // The InAddr for the local host is the loopback address if (_ea.IsEqual(pxna->abEnet)) { *pipa = IPADDR_LOOPBACK; return(0); } #ifdef XNET_FEATURE_SG if (XNetXnKidIsOnlinePeer(pxnkid) && pxna->inaOnline.s_addr == 0) { TraceSz2(Warning, "IpXnAddrToInAddr - XNKID %s is online-peer but XNADDR %s is system-link", HexStr(pxnkid->ab, sizeof(pxnkid->ab)), XnAddrStr(pxna)); return(WSAEINVAL); } #endif RaiseToDpc(); // See if we have a CSecReg already for this XNADDR and XNKID CSecReg * pSecReg = SecRegLookup(pxna, pxnkid); if (pSecReg == NULL) { CKeyReg * pKeyReg = KeyRegLookup(pxnkid); if (pKeyReg == NULL) { TraceSz1(Warning, "IpXnAddrToInAddr - XNKID %s is not registered", HexStr(pxnkid->ab, sizeof(pxnkid->ab))); return(WSAEINVAL); } pSecReg = SecRegAlloc(pxna, pKeyReg); if (pSecReg == NULL) { return(WSAENOMORE); } } else { // Update the XNADDR of the CSecReg in case it has been updated with new // online information. pSecReg->_xnaddr = *pxna; } pSecReg->SetFlags(SRF_OWNED); *pipa = CIpAddr(pSecReg->_dwSpiRecv); return(0); } INT CXnIp::IpServerToInAddr(const CIpAddr ipa, DWORD dwServiceId, CIpAddr * pipa) { ICHECK(IP, USER); if (!ipa.IsValidUnicast()) { TraceSz1(Warning, "IpServerToInAddr - %s is not a valid unicast IP address", ipa.Str()); return(WSAEINVAL); } RaiseToDpc(); #ifdef XNET_FEATURE_SG XOKERBINFO * pxokerbinfo; #ifdef XNET_FEATURE_INSECURE if (dwServiceId == 0) { pxokerbinfo = NULL; goto ServiceDone; } #endif #ifdef XNET_FEATURE_ONLINE pxokerbinfo = _pXoBase ? _pXoBase->XoKerbGetInfo(dwServiceId) : NULL; if (pxokerbinfo != NULL) goto ServiceDone; #endif #endif TraceSz1(Warning, "IpServerToInAddr - dwServiceId %08lX is not available", dwServiceId); return(WSAEINVAL); #ifdef XNET_FEATURE_SG ServiceDone: // See if we have a CSecReg already for this XNADDR and XOKERBINFO CSecReg * pSecReg = SecRegLookup(ipa, dwServiceId, pxokerbinfo); if (pSecReg == NULL) { pSecReg = SecRegAlloc(ipa, dwServiceId); if (pSecReg == NULL) { return(WSAENOMORE); } } *pipa = CIpAddr(pSecReg->_dwSpiRecv); return(0); #endif } INT CXnIp::IpInAddrToXnAddr(const CIpAddr ipa, XNADDR * pxna, XNKID * pxnkid) { ICHECK(IP, USER|UDPC|SDPC); if (pxna) { memset(pxna, 0, sizeof(XNADDR)); } if (pxnkid) { memset(pxnkid, 0, sizeof(XNKID)); } if (ipa == IPADDR_LOOPBACK) { if (pxna) { IpGetXnAddr(pxna); } return(0); } RaiseToDpc(); CSecReg * pSecReg = ipa.IsSecure() ? SecRegLookup(ipa) : NULL; if (pSecReg == NULL) { TraceSz1(Warning, "IpInAddrToXnAddr - %s is not a registered secure address", ipa.Str()); return(WSAEINVAL); } #ifdef XNET_FEATURE_SG if (pSecReg->TestFlags(SRF_ONLINESERVER)) { TraceSz1(Warning, "IpInAddrToXnAddr - %s is a secure address to a server. Cannot convert to XNADDR.", ipa.Str()); return(WSAEINVAL); } #endif if (pxna) { *pxna = pSecReg->_xnaddr; } if (pxnkid) { *pxnkid = pSecReg->_pKeyReg->_xnkid; } return(0); } INT CXnIp::IpUnregisterInAddr(const CIpAddr ipa) { ICHECK(IP, USER|UDPC|SDPC); RaiseToDpc(); CSecReg * pSecReg = ipa.IsSecure() ? SecRegLookup(ipa) : NULL; if (pSecReg == NULL) { TraceSz1(Warning, "IpUnregisterInAddr - %s is not a registered secure address", ipa.Str()); return(WSAEINVAL); } #ifdef XNET_FEATURE_SG if (pSecReg == _pSecRegLogon) { TraceSz1(Warning, "IpUnregisterInAddr - %s cannot be manually unregistered", ipa.Str()); return(WSAEINVAL); } #endif if (pSecReg->_bState == SR_STATE_RESPSENT) { // The other side is in initiating key-exchange with us, so just forget that we have // seen this secure address. pSecReg->ClearFlags(SRF_OWNED); } else { // Shutdown the security association and let the other side know if necessary. if (pSecReg->IsXmitReady() && !pSecReg->TestFlags(SRF_SECMSG_DELETE)) { IpXmitSecMsgDelete(pSecReg, SECMSG_DELETE_SHUTDOWN); } SecRegFree(pSecReg); } return(0); } // --------------------------------------------------------------------------------------- // CXnIp (SecRegLookup / SecRegAlloc / SecRegFree) // --------------------------------------------------------------------------------------- CXnIp::CSecReg * CXnIp::SecRegLookup(const XNADDR * pxna, const XNKID * pxnkid) { ICHECK(IP, UDPC|SDPC); CSecReg * pSecReg = _pSecReg; UINT cSecReg = cfgSecRegMax; for (; cSecReg > 0; ++pSecReg, --cSecReg) { if (pSecReg->_dwSpiRecv == 0 || pSecReg->TestFlags(SRF_ONLINESERVER)) continue; if (memcmp(pSecReg->_xnaddr.abEnet, pxna->abEnet, sizeof(CEnetAddr)) != 0) continue; if (memcmp(&pSecReg->_pKeyReg->_xnkid, pxnkid, sizeof(XNKID)) != 0) continue; return(pSecReg); } return(NULL); } #ifdef XNET_FEATURE_SG CXnIp::CSecReg * CXnIp::SecRegLookup(const CIpAddr ipa, DWORD dwServiceId, const XOKERBINFO * pxokerbinfo) { ICHECK(IP, UDPC|SDPC); CSecReg * pSecReg = _pSecReg; UINT cSecReg = cfgSecRegMax; for (; cSecReg > 0; ++pSecReg, --cSecReg) { if (pSecReg->_dwSpiRecv == 0 || !pSecReg->TestFlags(SRF_ONLINESERVER)) continue; if (pSecReg->_ipaDst != ipa) continue; if (pSecReg->_dwServiceId == dwServiceId) return(pSecReg); #ifdef XNET_FEATURE_ONLINE XOKERBINFO * pxokerbinfoReg = _pXoBase ? _pXoBase->XoKerbGetInfo(pSecReg->_dwServiceId) : NULL; if (pxokerbinfoReg && pxokerbinfo->_dwTicketId == pxokerbinfoReg->_dwTicketId) return(pSecReg); #endif } return(NULL); } #endif CXnIp::CSecReg * CXnIp::SecRegLookup(DWORD dwSpiRecv) { ICHECK(IP, UDPC|SDPC); CSecReg * pSecReg = NULL; if (_pSecReg && CIpAddr(dwSpiRecv).IsSecure()) { UINT uiSlot = CIpAddr(dwSpiRecv).SecureSlot(); if (uiSlot < cfgSecRegMax) { pSecReg = &_pSecReg[uiSlot]; if (pSecReg->_dwSpiRecv != dwSpiRecv) { pSecReg = NULL; } } } return(pSecReg); } CXnIp::CSecReg * CXnIp::SecRegAlloc() { ICHECK(IP, UDPC|SDPC); CSecReg * pSecReg = _pSecReg; UINT cSecReg = cfgSecRegMax; for (; cSecReg > 0; ++pSecReg, --cSecReg) { if (pSecReg->_dwSpiRecv == 0) break; } if (cSecReg == 0) { TraceSz1(Warning, "Reached config limit for secure address registrations (%d)", cfgSecRegMax); return(NULL); } pSecReg->_timer.Init((PFNTIMER)SecRegTimer); if (++_wSecRegUniq == 0) ++_wSecRegUniq; CIpAddr ipa(_wSecRegUniq, (BYTE)(pSecReg - _pSecReg)); pSecReg->_dwSpiRecv = ipa; TraceSz1(secStat, "Allocated %s", pSecReg->Str()); return(pSecReg); } CXnIp::CSecReg * CXnIp::SecRegAlloc(const XNADDR * pxna, CKeyReg * pKeyReg) { ICHECK(IP, UDPC|SDPC); CSecReg * pSecReg = SecRegAlloc(); if (pSecReg != NULL) { pSecReg->_wFlags = SRF_SYSTEMLINK; pSecReg->_xnaddr = *pxna; pSecReg->_pKeyReg = pKeyReg; pSecReg->_cbKeyDesRecv = XC_SERVICE_DES_KEYSIZE; pSecReg->_cbKeyDesXmit = XC_SERVICE_DES_KEYSIZE; pSecReg->_ipaDst = IPADDR_SECURE_DEFAULT; pSecReg->_ipportDst = ESPUDP_CLIENT_PORT; #ifdef XNET_FEATURE_SG // For an online peer we don't know for sure the address to send packets until the // key exchange process is complete. During key exchange, the _ipaDst, _ipportDst, // and _ipportSrc fields of the CSecReg are updated as return address information // becomes available. if (XNetXnKidIsOnlinePeer(&pKeyReg->_xnkid)) { pSecReg->_wFlags = SRF_ONLINEPEER; pSecReg->_ipaDst = 0; pSecReg->_ipportDst = 0; } #endif } return(pSecReg); } #ifdef XNET_FEATURE_SG CXnIp::CSecReg * CXnIp::SecRegAlloc(const CIpAddr ipa, DWORD dwServiceId) { ICHECK(IP, UDPC|SDPC); CSecReg * pSecReg = SecRegAlloc(); if (pSecReg != NULL) { pSecReg->_wFlags = SRF_ONLINESERVER|SRF_OWNED; pSecReg->_dwServiceId = dwServiceId; pSecReg->_cbKeyDesRecv = XC_SERVICE_DES3_KEYSIZE; pSecReg->_cbKeyDesXmit = XC_SERVICE_DES3_KEYSIZE; pSecReg->_ipaDst = ipa; pSecReg->_ipportDst = ESPUDP_CLIENT_PORT; } return(pSecReg); } #endif void CXnIp::SecRegFree(CSecReg * pSecReg) { ICHECK(IP, UDPC|SDPC); if (pSecReg->_dwSpiRecv) { TraceSz1(secStat, "Deallocated %s", pSecReg->Str()); SecRegSetIdle(pSecReg); memset(pSecReg, 0, sizeof(CSecReg)); } } // --------------------------------------------------------------------------------------- // CXnIp (SecReg state machine) // --------------------------------------------------------------------------------------- void CXnIp::SecRegEnqueue(CSecReg * pSecReg, CPacket * ppkt) { ICHECK(IP, UDPC|SDPC); Assert( pSecReg->_bState == SR_STATE_IDLE || pSecReg->_bState == SR_STATE_INITSENT || pSecReg->_bState == SR_STATE_RESPSENT); if (pSecReg->_bState == SR_STATE_IDLE) { IpXmitKeyEx(pSecReg); } // Insert the packet into the wait queue until we make it into the ready state pSecReg->_pqWait.InsertTail(ppkt); TraceSz2(secStat, "Packet enqueued to %s (%d waiting)", pSecReg->Str(), pSecReg->_pqWait.Count()); } void CXnIp::SecRegXmitQueue(CSecReg * pSecReg) { if (!pSecReg->_pqWait.IsEmpty()) { TraceSz3(secStat, "Sending %d waiting packet%s to %s", pSecReg->_pqWait.Count(), pSecReg->_pqWait.Count() == 1 ? "" : "s", pSecReg->Str()); do { CPacket * ppkt = pSecReg->_pqWait.RemoveHead(); IpXmit(ppkt, NULL); } while (!pSecReg->_pqWait.IsEmpty()); } } void CXnIp::SecRegShutdown(BOOL fOnlineOnly) { ICHECK(IP, UDPC|SDPC); CSecReg * pSecReg = _pSecReg; UINT cSecReg = cfgSecRegMax; for (; cSecReg > 0; ++pSecReg, --cSecReg) { if (pSecReg->_dwSpiRecv == 0) continue; if (fOnlineOnly && !pSecReg->TestFlags(SRF_ONLINEPEER|SRF_ONLINESERVER)) continue; if (pSecReg->IsXmitReady() && !pSecReg->TestFlags(SRF_SECMSG_DELETE)) { pSecReg->SetFlags(SRF_SECMSG_DELETE); IpXmitSecMsgDelete(pSecReg, SECMSG_DELETE_SHUTDOWN); } if (fOnlineOnly) { SecRegFree(pSecReg); } } } void CXnIp::SecRegSetIdle(CSecReg * pSecReg) { ICHECK(IP, UDPC|SDPC); // Tell the sockets layer that this secure IP address has being disconnected. // Any TCP socket connected to this address will be reset. SockReset(CIpAddr(pSecReg->_dwSpiRecv)); pSecReg->_wFlags &= SRF_SYSTEMLINK|SRF_ONLINEPEER|SRF_ONLINESERVER|SRF_OWNED; pSecReg->_bState = SR_STATE_IDLE; pSecReg->_bRetry = 0; pSecReg->_dwSeqXmit = 0; pSecReg->_dwSeqRecv = 0; pSecReg->_dwSeqMask = 0; memset(pSecReg->_abNonceInit, 0, sizeof(pSecReg->_abNonceInit)); memset(pSecReg->_abNonceResp, 0, sizeof(pSecReg->_abNonceResp)); Rand(pSecReg->_abIv, sizeof(pSecReg->_abIv)); TimerSet(&pSecReg->_timer, TIMER_INFINITE); pSecReg->_pqWait.Complete(this); SecRegSetTicks(pSecReg); #ifdef XNET_FEATURE_SG if (pSecReg->_wFlags & SRF_ONLINEPEER) { pSecReg->_ipaDst = 0; pSecReg->_ipportDst = 0; } if (_pSecRegLogon == pSecReg && _uiLogonState != XN_LOGON_STATE_IDLE) { _uiLogonState = XN_LOGON_STATE_OFFLINE; if (_pEventLogon) { EvtSet(_pEventLogon, EVENT_INCREMENT); } } #endif } void CXnIp::SecRegSetTicks(CSecReg * pSecReg) { ICHECK(IP, UDPC|SDPC); pSecReg->_dwTickRecv = TimerTick(); pSecReg->_dwTickXmit = TimerTick(); pSecReg->_dwTickPulse = TimerTick(); pSecReg->_dwTickPulseTimeout = cfgSecRegPulseTimeoutInSeconds * TICKS_PER_SECOND; pSecReg->_dwTickTimeout = cfgSecRegTimeoutInSecondsDiv10 * 10 * TICKS_PER_SECOND; } void CXnIp::SecRegSetOwned(CIpAddr ipa) { ICHECK(IP, USER|UDPC|SDPC); if (ipa.IsSecure()) { RaiseToDpc(); CSecReg * pSecReg = SecRegLookup(ipa); if (pSecReg != NULL) { pSecReg->SetFlags(SRF_OWNED); } } } void CXnIp::SecRegSetKey(CSecReg * pSecReg, BYTE * pbKeyHmac, UINT cbKeyHmac, BYTE * pbDhX, UINT cbDhX, BYTE * pbDhGY, UINT cbDhGY, BOOL fInitiator) { ICHECK(IP, UDPC|SDPC); BYTE abDhGXY[CBDHG1]; // Generate the diffie-hellman g^XY mod p = (g^Y)^X mod p Assert(cbDhX == CBDHG1); Assert(cbDhGY == CBDHG1); XcModExp((DWORD *)abDhGXY, (DWORD *)pbDhGY, (DWORD *)pbDhX, (DWORD *)g_abOakleyGroup1Mod, CBDHG1 / sizeof(DWORD)); BYTE * pbKeyOut = fInitiator ? pSecReg->_abKeyShaXmit : pSecReg->_abKeyShaRecv; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { XcHMAC(pbKeyHmac, cbKeyHmac, abDhGXY, sizeof(abDhGXY), pSecReg->_abNonceInit, sizeof(pSecReg->_abNonceInit) + sizeof(pSecReg->_abNonceResp), pbKeyOut); pbKeyOut += XC_SERVICE_DIGEST_SIZE; abDhGXY[0] += 1; } pbKeyOut = fInitiator ? pSecReg->_abKeyShaRecv : pSecReg->_abKeyShaXmit; } XcDESKeyParity(pSecReg->_abKeyDesXmit, sizeof(pSecReg->_abKeyDesXmit)); XcDESKeyParity(pSecReg->_abKeyDesRecv, sizeof(pSecReg->_abKeyDesRecv)); } void CXnIp::SecRegTimer(CTimer * pt) { ICHECK(IP, SDPC); CSecReg * pSecReg = (CSecReg *)((BYTE *)pt - offsetof(CSecReg, _timer)); Assert(pSecReg->_bState == SR_STATE_INITSENT || pSecReg->_bState == SR_STATE_RESPSENT); TraceSz2(secStat, "KeyEx timeout to %s (bRetry %d)", pSecReg->Str(), pSecReg->_bRetry); if (pSecReg->_bRetry == 0) { TraceSz3(secStat, "Failed KeyEx to %s (%d packet%s flushed)", pSecReg->Str(), pSecReg->_pqWait.Count(), pSecReg->_pqWait.Count() == 1 ? "" : "s"); if (!pSecReg->TestFlags(SRF_OWNED)) SecRegFree(pSecReg); else SecRegSetIdle(pSecReg); } else { pSecReg->_bRetry -= 1; TimerSet(&pSecReg->_timer, TimerTick() + SecRegRexmitTimeoutInSeconds(pSecReg) * TICKS_PER_SECOND); IpXmitKeyEx(pSecReg); } } void CXnIp::SecRegProbe() { ICHECK(IP, SDPC); _cSecRegProbeNumer += cfgSecRegMax; if (_cSecRegProbeNumer < _cSecRegProbeDenom) return; DWORD dwTickNow = TimerTick(); UINT cSecReg = _cSecRegProbeNumer / _cSecRegProbeDenom; _cSecRegProbeNumer -= cSecReg * _cSecRegProbeDenom; Assert(_cSecRegProbeNumer < _cSecRegProbeDenom); CSecReg * pSecReg = _pSecRegProbe ? _pSecRegProbe : _pSecReg; CSecReg * pSecRegLast = _pSecReg + cfgSecRegMax; for (; cSecReg > 0; --cSecReg, ++pSecReg) { if (pSecReg == pSecRegLast) { // We've run off the end of the CSecReg vector. Start back at the beginning. pSecReg = _pSecReg; } if (pSecReg->_dwSpiRecv == 0) { // This CSecReg is not in use. Go on to the next one. continue; } if (pSecReg->_bState < SR_STATE_INITWAIT) { // This CSecReg is in the middle of key exchange. That process handles its // own timeouts. Go on to the next one. continue; } if (pSecReg->_dwTickRecv <= dwTickNow - pSecReg->_dwTickTimeout) { // This CSecReg hasn't received a packet in a long enough time that the other // side should be considered down. TraceSz2(secStat, "Timeout after %d secs of no incoming packets from %s", (dwTickNow - pSecReg->_dwTickRecv) / TICKS_PER_SECOND, pSecReg->Str()); if (!pSecReg->TestFlags(SRF_OWNED)) SecRegFree(pSecReg); else SecRegSetIdle(pSecReg); continue; } DWORD dwTickPulse = dwTickNow - pSecReg->_dwTickPulseTimeout; if (pSecReg->_dwTickPulse <= dwTickPulse) { // This CSecReg hasn't sent a pulse in a long enough time that now might be // a good time to send it. We only need to send it if there have been no // packets transmitted recently, or if we have something to say. #ifdef XNET_FEATURE_SG if (pSecReg->TestFlags(SRF_ONLINESERVER)) { if ( pSecReg->_dwTickXmit <= dwTickPulse || (pSecReg == _pSecRegLogon && (_dwSeqSgToXb || *(DWORD *)_abXbToSgPulse))) { TraceSz3(secStat, "Sending pulse to %s (last xmit/pulse %d/%d secs ago)", pSecReg->Str(), (dwTickNow - pSecReg->_dwTickXmit) / TICKS_PER_SECOND, (dwTickNow - pSecReg->_dwTickPulse) / TICKS_PER_SECOND); IpXmitSecMsgXbToSgPulse(pSecReg); } continue; } #endif if (pSecReg->_dwTickXmit <= dwTickPulse) { TraceSz2(secStat, "Sending pulse to %s (last xmit/pulse %d/%d secs ago)", pSecReg->Str(), (dwTickNow - pSecReg->_dwTickXmit) / TICKS_PER_SECOND); IpXmitSecMsg(pSecReg, SECMSG_TYPE_PULSE); pSecReg->_dwTickPulse = dwTickNow; continue; } } } _pSecRegProbe = pSecReg; } #ifdef XNET_FEATURE_TRACE char * CXnIp::CSecReg::Str() { #define SECREG_STR_BUFFS 32 #define SECREG_BUF_SIZE 24 static char g_chBufSecReg[SECREG_STR_BUFFS * SECREG_BUF_SIZE]; static LONG g_lBufIndexSecReg = 0; char * pch = &g_chBufSecReg[(InterlockedIncrement(&g_lBufIndexSecReg) % SECREG_STR_BUFFS) * SECREG_BUF_SIZE]; XnInAddrToString(*(IN_ADDR *)&_dwSpiRecv, pch, SECREG_BUF_SIZE); strcat(pch, _bState == SR_STATE_IDLE ? "/IDLE" : _bState == SR_STATE_INITSENT ? "/ISENT" : _bState == SR_STATE_RESPSENT ? "/RSENT" : "/READY"); return(pch); } #endif // --------------------------------------------------------------------------------------- // CXnIp (IpDecrypt) // --------------------------------------------------------------------------------------- BOOL CXnIp::IpDecrypt(CPacket * ppkt, CIpAddr ipaDst) { ICHECK(IP, USER|UDPC|SDPC); Assert(ppkt->IsEsp() && ppkt->IsCrypt()); CSecReg * pSecReg = SecRegLookup(ipaDst); if (pSecReg == NULL) { TraceSz1(pktWarn, "IpDecrypt: %s is not a valid secure address", ipaDst.Str()); return(FALSE); } if (!pSecReg->IsXmitReady()) { TraceSz1(pktWarn, "IpDecrypt: %s is not ready for transmit", ipaDst.Str()); return(FALSE); } // Check that the packet authenticates with the transmit SHA key CEspHdr * pEspHdr = ppkt->GetEspHdr(); CEspTail * pEspTail = ppkt->GetEspTail(); BYTE abHash[XC_SERVICE_DIGEST_SIZE]; Assert(sizeof(pEspTail->_abHash) <= sizeof(abHash)); XcHMAC(pSecReg->_abKeyShaXmit, sizeof(pSecReg->_abKeyShaXmit), (BYTE *)&pEspHdr->_dwSpi, (BYTE *)&pEspTail->_abHash - (BYTE *)&pEspHdr->_dwSpi, NULL, 0, abHash); if (memcmp(pEspTail->_abHash, abHash, sizeof(pEspTail->_abHash)) != 0) { TraceSz(pktWarn, "IpDecrypt: packet failed to authenticate"); return(FALSE); } // Decrypt the packet with the transmit DES key BYTE * pb = (BYTE *)(pEspHdr + 1); CryptDes(XC_SERVICE_DECRYPT, pSecReg->_abKeyDesXmit, pSecReg->_cbKeyDesXmit, pb, pb + XC_SERVICE_DES_BLOCKLEN, (BYTE *)&pEspTail->_abHash - pb); return(TRUE); } // --------------------------------------------------------------------------------------- // CXnIp (GetXnAddr) // --------------------------------------------------------------------------------------- DWORD CXnIp::IpGetXnAddr(XNADDR * pxna) { ICHECK(IP, USER|UDPC|SDPC); RaiseToDpc(); memset(pxna, 0, sizeof(XNADDR)); pxna->ina.s_addr = _ipa; memcpy(pxna->abEnet, _ea._ab, sizeof(_ea)); DWORD dwFlags = 0; #ifdef XNET_FEATURE_SG if (_uiLogonState == XN_LOGON_STATE_ONLINE && _pSecRegLogon != NULL) { pxna->inaOnline.s_addr = _pSecRegLogon->_ipaNat; pxna->wPortOnline = _pSecRegLogon->_ipportNat; Assert(sizeof(pxna->abOnline) == sizeof(SGADDR)); memcpy(pxna->abOnline, &_pSecRegLogon->_sgaddr, sizeof(pxna->abOnline)); dwFlags |= XNET_GET_XNADDR_ONLINE; } #endif #ifdef XNET_FEATURE_DHCP if (_options._gatewayCount > 0) dwFlags |= XNET_GET_XNADDR_GATEWAY; if (_options._dnsServerCount > 0) dwFlags |= XNET_GET_XNADDR_DNS; if (ActiveDhcpAddr()) dwFlags |= XNET_GET_XNADDR_DHCP; else if (ActiveAutonetAddr()) dwFlags |= XNET_GET_XNADDR_AUTO; else if (ActiveStaticAddr()) dwFlags |= XNET_GET_XNADDR_STATIC; else if (ActiveNoAddr()) dwFlags |= XNET_GET_XNADDR_ETHERNET; #else // For now on the XBOX if we are not configured for using DHCP there is no // way to acquire an IP address. This is the common codepath for the secure // xnets.lib to take for the XTL 1.0 release. Assert(_ipa == 0); dwFlags |= XNET_GET_XNADDR_ETHERNET; #endif return(dwFlags); } // --------------------------------------------------------------------------------------- // Online Support // --------------------------------------------------------------------------------------- #ifdef XNET_FEATURE_ONLINE void CXnIp::IpSetXoBase(CXoBase * pXoBase) { ICHECK(IP, USER); RaiseToDpc(); if (pXoBase == NULL) { IpLogoff(); } _pXoBase = pXoBase; } #endif #ifdef XNET_FEATURE_SG void CXnIp::IpLogon(CIpAddr ipaLogon, ULONGLONG * pqwUserId, WSAEVENT hEventLogon) { ICHECK(IP, USER); RaiseToDpc(); Assert(_uiLogonState == XN_LOGON_STATE_IDLE); _pEventLogon = hEventLogon ? EvtFromHandle(hEventLogon) : NULL; if (_pEventLogon == NULL && hEventLogon != NULL) { TraceSz1(Warning, "IpLogon - invalid hEvent %08lX", hEventLogon); } if (ipaLogon != 0) { _pSecRegLogon = SecRegLookup(ipaLogon); if (_pSecRegLogon == NULL || !_pSecRegLogon->TestFlags(SRF_ONLINESERVER)) { TraceSz1(Warning, "IpLogon - %s is not a valid secure address to an SG", ipaLogon.Str()); _pSecRegLogon = NULL; } } if (_pSecRegLogon == NULL) { // This logon is not connecting to an SG providing connection services. Just mark // the state machine as online. _uiLogonState = XN_LOGON_STATE_ONLINE; if (_pEventLogon) { EvtSet(_pEventLogon, EVENT_INCREMENT); } } else { _uiLogonState = XN_LOGON_STATE_PENDING; if (pqwUserId != NULL) { XOUSERINFO * pxouserinfo = _axouserinfo; XOUSERINFO * pxouserinfoEnd = pxouserinfo + dimensionof(_axouserinfo); for (; pxouserinfo < pxouserinfoEnd; ++pxouserinfo, ++pqwUserId) { pxouserinfo->_qwUserId = *pqwUserId; } } // Kick off key exchange to the SG providing connection services. Assert(_pSecRegLogon->_bState == SR_STATE_IDLE); IpXmitKeyEx(_pSecRegLogon); } } DWORD CXnIp::IpLogonGetStatus(SGADDR * psgaddr) { ICHECK(IP, USER|UDPC); RaiseToDpc(); if (psgaddr != NULL) { if (_pSecRegLogon != NULL && _uiLogonState == XN_LOGON_STATE_ONLINE) *psgaddr = _pSecRegLogon->_sgaddr; else memset(psgaddr, 0, sizeof(SGADDR)); } return(_uiLogonState); } BOOL CXnIp::IpLogonGetQFlags(UINT iUserId, ULONGLONG * pqwUserId, DWORD * pdwQFlags, DWORD * pdwSeqQFlags) { ICHECK(IP, USER|UDPC); RaiseToDpc(); if (iUserId < dimensionof(_axouserinfo)) { XOUSERINFO * pxouserinfo = &_axouserinfo[iUserId]; if (pqwUserId != NULL) { *pqwUserId = pxouserinfo->_qwUserId; } if (pdwQFlags != NULL) { *pdwQFlags = pxouserinfo->_dwQFlags; } if (pdwSeqQFlags != NULL) { *pdwSeqQFlags = pxouserinfo->_dwSeqQFlags; } return(TRUE); } return(FALSE); } BOOL CXnIp::IpLogonSetQFlags(UINT iUserId, DWORD dwQFlags, DWORD dwSeqQFlags) { ICHECK(IP, USER|UDPC); RaiseToDpc(); if (iUserId < dimensionof(_axouserinfo)) { XOUSERINFO * pxouserinfo = &_axouserinfo[iUserId]; if (pxouserinfo->_dwSeqQFlags < dwSeqQFlags) { pxouserinfo->_dwSeqQFlags = dwSeqQFlags; pxouserinfo->_dwQFlags = dwQFlags; if (_pEventLogon) { EvtSet(_pEventLogon, EVENT_INCREMENT); } } return(TRUE); } return(FALSE); } BOOL CXnIp::IpLogonSetPState(UINT iUserId, DWORD dwPState, const XNKID * pxnkid, UINT cbData, BYTE * pbData) { ICHECK(IP, USER|UDPC); RaiseToDpc(); if (iUserId < dimensionof(_axouserinfo) && cbData <= sizeof(_axouserinfo[0]._abData)) { XOUSERINFO * pxouserinfo = &_axouserinfo[iUserId]; BYTE * pb = &_abXbToSgPulse[iUserId]; if (pxouserinfo->_dwPState != dwPState) { pxouserinfo->_dwPState = dwPState; *pb |= (BYTE)iUserId | XBPULSE_STATE_CHANGE; } if (memcmp(&pxouserinfo->_xnkid, pxnkid, sizeof(XNKID)) != 0) { pxouserinfo->_xnkid = *pxnkid; *pb |= (BYTE)iUserId | XBPULSE_XNKID_CHANGE; } if (cbData != pxouserinfo->_cbData || memcmp(pxouserinfo->_abData, pbData, cbData) != 0) { pxouserinfo->_cbData = cbData; if (cbData > 0) { memcpy(pxouserinfo->_abData, pbData, cbData); } *pb |= (BYTE)iUserId | XBPULSE_TDATA_CHANGE; } return(TRUE); } return(FALSE); } void CXnIp::IpLogoff() { ICHECK(IP, USER|UDPC); RaiseToDpc(); _uiLogonState = XN_LOGON_STATE_IDLE; _pSecRegLogon = NULL; if (_pEventLogon) { EvtDereference(_pEventLogon); _pEventLogon = NULL; } memset(&_axouserinfo, 0, sizeof(_axouserinfo)); *(DWORD *)_abXbToSgPulse = 0; _dwSeqXbToSg = 0; _dwSeqSgToXb = 0; SecRegShutdown(TRUE); } #endif // --------------------------------------------------------------------------------------- // CXnIp::IpSetEventTimer / CXnIp::EventTimer // --------------------------------------------------------------------------------------- void CXnIp::IpSetEventTimer(CEventTimer * pEventTimer, WSAEVENT hEvent, DWORD dwTimeout) { ICHECK(IP, USER); RaiseToDpc(); if (pEventTimer->IsNull()) { pEventTimer->Init((PFNTIMER)EventTimer); } // Release the reference to the existing event. if (pEventTimer->_pEvent != NULL) { EvtDereference(pEventTimer->_pEvent); pEventTimer->_pEvent = NULL; } if (dwTimeout != TIMER_INFINITE) { // Compute the number of milliseconds until our timer next fires. DWORD dwTickKe = KeQueryTickCount() - _dwTickKe; dwTickKe = MSEC_PER_TICK - min(MSEC_PER_TICK, dwTickKe); // In order to compensate for edge effects between the kernel timer and our timer // we add 25 ms to the requested timeout. This will guarantee that we don't set // the event prematurely with respect to the kernel millisecond timer. dwTimeout += 25; // Now compute the number of whole timer ticks we need to wait beyond the partial // tick we are in right now. Note that we round up to the next timer tick. if (dwTimeout < dwTickKe) { dwTimeout = 0; } else { dwTimeout -= dwTickKe; dwTimeout += MSEC_PER_TICK - 1; dwTimeout = (dwTimeout * TICKS_PER_SECOND) / 1000; } // Finally, add in the current timer tick plus one, which is the tick of the next // firing of our timer. dwTimeout += TimerTick() + 1; // Add a reference to the event handle pEventTimer->_pEvent = EvtFromHandle(hEvent); if (pEventTimer->_pEvent == NULL) { TraceSz1(Warning, "IpSetEventTimer - invalid hEvent %08lX", hEvent); } } // Set or cancel the timer TimerSet(pEventTimer, dwTimeout); } void CXnIp::EventTimer(CTimer * pt) { ICHECK(IP, SDPC); CEventTimer * pEventTimer = (CEventTimer *)pt; if (pEventTimer->_pEvent != NULL) { EvtSet(pEventTimer->_pEvent, EVENT_INCREMENT); EvtDereference(pEventTimer->_pEvent); pEventTimer->_pEvent = NULL; } } // --------------------------------------------------------------------------------------- // CXnIp::IpRaiseToDpc // --------------------------------------------------------------------------------------- #ifdef XNET_FEATURE_ONLINE void CXnIp::IpRaiseToDpc(BOOL fRaise) { ICHECK(IP, USER|UDPC); Assert(KeGetCurrentIrql() == (fRaise ? PASSIVE_LEVEL : DISPATCH_LEVEL)); if (fRaise) KeRaiseIrqlToDpcLevel(); else KeLowerIrql(PASSIVE_LEVEL); } #endif