// --------------------------------------------------------------------------------------- // enet.cpp // // Copyright (C) Microsoft Corporation // --------------------------------------------------------------------------------------- #include "xnp.h" #include "xnver.h" // --------------------------------------------------------------------------------------- // Trace Tags // --------------------------------------------------------------------------------------- DefineTag(Arp, 0); DefineTag(ArpWarn, TAG_ENABLE); // --------------------------------------------------------------------------------------- // CXnEnet - External Functions // --------------------------------------------------------------------------------------- NTSTATUS CXnEnet::EnetInit(XNetInitParams * pxnip) { TCHECK(USER); NTSTATUS status = NicInit(pxnip); if (!NT_SUCCESS(status)) return(status); SetInitFlag(INITF_ENET); KeInitializeDpc(&_dpcEnet, &EnetDpc, this); #ifdef XNET_FEATURE_ARP _paeLast = _aae; _timerArp.Init((PFNTIMER)ArpTimer); #endif return(NETERR_OK); } void CXnEnet::EnetTerm() { TCHECK(UDPC); EnetStop(); if (TestInitFlag(INITF_ENET)) { KeRemoveQueueDpc(&_dpcEnet); if (!_pqXmit.IsEmpty()) { TraceSz1(Warning, "Enet shutdown with %d packet(s) queued for transmit", _pqXmit.Count()); _pqXmit.Discard(this); } #ifdef XNET_FEATURE_ARP CArpEntry * pae = _aae; UINT cae = dimensionof(_aae); for (; cae > 0; --cae, ++pae) { if (!pae->_pqWait.IsEmpty()) { TraceSz2(Warning, "Enet shutdown with %d packet(s) queued for ARP %s", pae->_pqWait.Count(), pae->_ipa.Str()); pae->_pqWait.Discard(this); } } TimerSet(&_timerArp, TIMER_INFINITE); #endif } SetInitFlag(INITF_ENET_TERM); NicTerm(); } void CXnEnet::EnetXmit(CPacket * ppkt, CIpAddr ipaNext) { ICHECK(ENET, UDPC|SDPC); if (!ppkt->TestFlags(PKTF_XMIT_FRAME)) { Assert(ppkt->IsIp()); *((CIpAddr *)ppkt->GetPv() - 1) = ipaNext; } if (ppkt->TestFlags(PKTF_XMIT_PRIORITY)) _pqXmit.InsertHead(ppkt); else _pqXmit.InsertTail(ppkt); ppkt->ClearFlags(PKTF_XMIT_PRIORITY); EnetQueuePush(); } #ifdef XNET_FEATURE_ARP void CXnEnet::EnetXmitArp(CIpAddr ipa) { ICHECK(ENET, UDPC|SDPC); _ipaCheck = ipa; if (_ipaCheck) { ArpXmit(ARP_OP_REQUEST, _ipaCheck, _ipaCheck, NULL); } } #endif void CXnEnet::EnetStop() { TCHECK(UDPC); if (TestInitFlag(INITF_ENET) && !TestInitFlag(INITF_ENET_STOP)) { SetInitFlag(INITF_ENET_STOP); } NicStop(); } // --------------------------------------------------------------------------------------- // Virtual callbacks // --------------------------------------------------------------------------------------- void CXnEnet::EnetRecv(CPacket * ppkt, UINT uiType) { ICHECK(ENET, UDPC|SDPC); Assert(ppkt->IsEnet()); if (uiType == ENET_TYPE_IP) { ppkt->SetType(PKTF_TYPE_IP); IpRecv(ppkt); } #ifdef XNET_FEATURE_ARP else if (uiType == ENET_TYPE_ARP) { ArpRecv(ppkt); } #endif else { TraceSz1(pktRecv, "[DISCARD] No support for Ethernet type %04X", NTOHS((WORD)uiType)); } } // --------------------------------------------------------------------------------------- // CXnEnet - Internal Functions // --------------------------------------------------------------------------------------- #ifdef XNET_FEATURE_ARP void CXnEnet::ArpXmit(WORD wOp, CIpAddr ipaTarget, CIpAddr ipaSender, CEnetAddr * peaTarget) { ICHECK(ENET, UDPC|SDPC); CPacket * ppkt; CEnetHdr * pEnetHdr; CArpMsg * pArpMsg; ppkt = PacketAlloc(PTAG_CArpPacket, PKTF_POOLALLOC|PKTF_TYPE_ENET|PKTF_XMIT_FRAME|PKTF_XMIT_PRIORITY, sizeof(CArpMsg)); if (ppkt == NULL) { TraceSz(Warning, "Out of memory allocating ARP packet"); return; } pEnetHdr = ppkt->GetEnetHdr(); pArpMsg = (CArpMsg *)ppkt->GetPv(); if (peaTarget) { pEnetHdr->_eaDst = *peaTarget; pArpMsg->_eaTarget = *peaTarget; } else { pEnetHdr->_eaDst.SetBroadcast(); pArpMsg->_eaTarget.SetZero(); } pEnetHdr->_eaSrc = _ea; pEnetHdr->_wType = ENET_TYPE_ARP; pArpMsg->_wHrd = ARP_HWTYPE_ENET; pArpMsg->_wPro = ENET_TYPE_IP; pArpMsg->_bHln = sizeof(CEnetAddr); pArpMsg->_bPln = sizeof(CIpAddr); pArpMsg->_wOp = wOp; pArpMsg->_eaSender = _ea; pArpMsg->_ipaSender = ipaSender; pArpMsg->_ipaTarget = ipaTarget; TraceSz5(Arp, "%s (%s) is %s %s (%s)", ipaSender.Str(), _ea.Str(), peaTarget ? "replying to" : "broadcasting request for", ipaTarget.Str(), pArpMsg->_eaTarget.Str()); EnetXmit(ppkt); } CXnEnet::CArpEntry * CXnEnet::ArpLookup(CIpAddr ipa, ArpResolve eResolve) { ICHECK(ENET, UDPC|SDPC); Assert(ipa.IsValidUnicast()); CArpEntry * pae; CArpEntry * paeRetryEnd; CArpEntry * paeHash; UINT uiHash; Assert(ipa != 0); if (_paeLast->_ipa == ipa) { Assert(!_paeLast->IsFree()); return(_paeLast); } // Get the hash bucket for the specified address uiHash = ARP_HASH(ipa); pae = &_aae[uiHash]; // Found the target address in the cache via a direct hash hit if (pae->_ipa == ipa) { goto found; } // No direct hash hit, try linear search paeHash = pae; paeRetryEnd = pae + ARP_HASH_RETRY; while (++pae < paeRetryEnd) { if (pae->_ipa == ipa) goto found; } if (eResolve == eNone) { return(NULL); } // The target IP address is not in the cache: // send out an ARP request if specified; // and make a new cache entry for the target // Check to see if the hash bucket is free pae = paeHash; if (!pae->IsFree()) { while (++pae < paeRetryEnd) { if (pae->IsFree()) break; } // Couldn't find a free entry // fall back and try to find a non-busy entry if (pae == paeRetryEnd) { pae = paeHash; while (++pae < paeRetryEnd) { if (!pae->IsBusy()) break; } if (pae == paeRetryEnd) { // Too bad: couldn't find either a free or non-busy entry // emit a warning and give up return(NULL); } // This entry is currently used for another address: // we'll just bump it off here. TraceSz1(Arp, "Bumped entry for %s", pae->_ipa.Str()); } } TraceSz1(Arp, "Adding entry for %s", ipa.Str()); pae->_ipa = ipa; Assert(pae->_pqWait.IsEmpty()); if (eResolve == eSendRequest) { pae->_wState = ARP_STATE_BUSY + cfgEnetArpReqRetries; pae->_dwTick = TimerSetRelative(&_timerArp, cfgEnetArpRexmitTimeoutInSeconds * TICKS_PER_SECOND); ArpXmit(ARP_OP_REQUEST, ipa, _ipa, NULL); } else { // Remaining initialization of this entry happens in the caller which // changes the state to ARP_STATE_IDLE pae->_wState = ARP_STATE_BUSY; } found: Assert(pae && !pae->IsFree()); _paeLast = pae; return(pae); } void CXnEnet::ArpTimer(CTimer * pt) { ICHECK(ENET, UDPC|SDPC); CArpEntry * pae; UINT iae; DWORD dwTickNow = TimerTick(); DWORD dwTickArp = TIMER_INFINITE; for (iae = 0, pae = _aae; iae < ARP_CACHE_SIZE; ++iae, ++pae) { if (pae->IsFree()) continue; if (dwTickNow >= pae->_dwTick) { if (pae->_wState > ARP_STATE_BUSY) { TraceSz1(ArpWarn, "ArpTimer: %s didn't respond to request. Trying again.", pae->_ipa.Str()); pae->_dwTick = dwTickNow + cfgEnetArpRexmitTimeoutInSeconds * TICKS_PER_SECOND; pae->_wState -= 1; ArpXmit(ARP_OP_REQUEST, pae->_ipa, _ipa, NULL); } else if (pae->_wState == ARP_STATE_BUSY) { TraceSz3(ArpWarn, "ArpTimer: %s is unreachable. Discarding %d waiting packet%s.", pae->_ipa.Str(), pae->_pqWait.Count(), pae->_pqWait.Count() == 1 ? "" : "s"); pae->_pqWait.Complete(this); pae->_dwTick = dwTickNow + (cfgEnetArpNegCacheTimeoutInMinutes * 60 * TICKS_PER_SECOND); pae->_wState = ARP_STATE_BAD; } else { TraceSz1(Arp, "%s has timed out", pae->_ipa.Str()); pae->_dwTick = TIMER_INFINITE; pae->_wState = ARP_STATE_FREE; pae->_ipa = 0; } } if (dwTickArp > pae->_dwTick) dwTickArp = pae->_dwTick; } Assert(pt == &_timerArp); TimerSet(pt, dwTickArp); } void CXnEnet::ArpRecv(CPacket * ppkt) { ICHECK(ENET, UDPC|SDPC); Assert(ppkt->IsEnet()); CEnetHdr * pEnetHdr = ppkt->GetEnetHdr(); CArpMsg * pArpMsg = (CArpMsg *)ppkt->GetPv(); UINT cbMsg = ppkt->GetCb(); WORD wOp; CIpAddr ipaSender; CEnetAddr eaSender; CIpAddr ipaTarget; CArpEntry * pae; ArpResolve eResolve; TraceSz5(pktRecv, "[ARP %s %s %s %s %s]", pArpMsg->_wOp == ARP_OP_REQUEST ? "Request" : pArpMsg->_wOp == ARP_OP_REPLY ? "Reply" : "???", pArpMsg->_ipaSender.Str(), pArpMsg->_eaSender.Str(), pArpMsg->_ipaTarget.Str(), pArpMsg->_eaTarget.Str()); if (_ea.IsEqual(pEnetHdr->_eaSrc)) { // We received an ARP packet from someone who has the same Ethernet address // as us. Issue a warning and discard the packet. There's not a lot we // can do at this point. TraceSz(pktWarn, "[DISCARD] ARP sender has conflicting Ethernet address!"); return; } if (cbMsg < sizeof(CArpMsg)) { TraceSz1(pktRecv, "[DISCARD] ARP packet is too small (%d)", cbMsg); return; } wOp = pArpMsg->_wOp; ipaSender = pArpMsg->_ipaSender; eaSender = pArpMsg->_eaSender; ipaTarget = pArpMsg->_ipaTarget; if ( pArpMsg->_wHrd != ARP_HWTYPE_ENET && pArpMsg->_wHrd != ARP_HWTYPE_802 || pArpMsg->_wPro != ENET_TYPE_IP || pArpMsg->_bHln != sizeof(CEnetAddr) || pArpMsg->_bPln != sizeof(CIpAddr) || wOp != ARP_OP_REQUEST && wOp != ARP_OP_REPLY || !ipaSender.IsValidUnicast() || !ipaTarget.IsValidUnicast() || eaSender.IsMulticast()) { TraceSz9(pktRecv, "[DISCARD] ARP message is invalid (%d,%d,%d,%d,%d,%d,%d,%d,%d)", pArpMsg->_wHrd != ARP_HWTYPE_ENET && pArpMsg->_wHrd != ARP_HWTYPE_802, pArpMsg->_wPro != ENET_TYPE_IP, pArpMsg->_bHln != sizeof(CEnetAddr), pArpMsg->_bPln != sizeof(CIpAddr), wOp != ARP_OP_REQUEST && wOp != ARP_OP_REPLY, !ipaSender.IsValidUnicast(), !ipaTarget.IsValidUnicast(), eaSender.IsMulticast(), eaSender.IsBroadcast()); return; } // Check to see if we have an existing entry for the sender // in our ARP cache. If we're the target and there is no // existing entry, then we'll create a new entry. // This assumes that communication will likely be bidirectional. if (ipaSender == _ipaCheck) { TraceSz(Arp, "Auto-IP collision detected"); _ipaCheck = 0; IpRecvArp(&eaSender); } // If we don't have an IP address yet, there is nothing more to do if (_ipa == 0) return; if (ipaSender != _ipa) { eResolve = (ipaTarget == _ipa) ? eCreateEntry : eNone; pae = ArpLookup(ipaSender, eResolve); if (pae) { if (pae->_wState != ARP_STATE_IDLE) { TraceSz2(Arp, "%s resolved to %s", pae->_ipa.Str(), eaSender.Str()); } pae->_wState = ARP_STATE_IDLE; pae->_dwTick = TimerSetRelative(&_timerArp, cfgEnetArpPosCacheTimeoutInMinutes * 60 * TICKS_PER_SECOND); pae->_eaNext = eaSender; if (!pae->_pqWait.IsEmpty()) { TraceSz3(Arp, "Sending %d packet%s waiting for %s", pae->_pqWait.Count(), pae->_pqWait.Count() == 1 ? "" : "s", pae->_ipa.Str()); _pqXmit.InsertHead(&pae->_pqWait); EnetQueuePush(); } } } // If we're the target and the packet is an ARP request, // then send out an ARP reply. if (ipaTarget == _ipa && wOp == ARP_OP_REQUEST) { TraceSz(pktRecv, "[REPLY] Replying to ARP request"); ArpXmit(ARP_OP_REPLY, ipaSender, _ipa, &eaSender); } else if (wOp == ARP_OP_REQUEST) { TraceSz(pktRecv, "[DISCARD] ARP request not for me"); return; } else { TraceSz(pktRecv, "[ARPDONE] ARP reply processed"); return; } } #endif // --------------------------------------------------------------------------------------- // EnetPush // --------------------------------------------------------------------------------------- void CXnEnet::EnetPush() { ICHECK(ENET, UDPC|SDPC); CPacket * ppkt; CIpAddr ipaNext; #ifdef XNET_FEATURE_ARP CArpEntry * pae; #endif while (!_pqXmit.IsEmpty() && NicXmitReady()) { ppkt = _pqXmit.RemoveHead(); if (TestInitFlag(INITF_ENET_STOP)) { TraceSz(pktWarn, "[DISCARD] Network is down"); goto complete; } if (ppkt->TestFlags(PKTF_XMIT_FRAME)) { ppkt->ClearFlags(PKTF_XMIT_FRAME); NicXmit(ppkt); continue; } Assert(ppkt->IsIp()); ipaNext = *((CIpAddr *)ppkt->GetPv() - 1); if (ipaNext == 0) { // ipaNext set to zero from the caller means that this packet should be discarded // silently. The purpose is to prevent calling back to the sender in the middle // of transmitting the packet to break potential recursion. goto complete; } if (ipaNext.IsBroadcast()) { CEnetAddr eaNext; eaNext.SetBroadcast(); EnetFillAndXmit(ppkt, &eaNext); continue; } if (ipaNext.IsLoopback() && ipaNext != IPADDR_LOOPBACK) { TraceSz1(pktWarn, "[DISCARD] Can't send to %s", ipaNext.Str()); goto complete; } if (ipaNext == IPADDR_LOOPBACK || ipaNext == _ipa) { TraceSz1(pktRecv, "\n[LOOPBACK][%d]", ppkt->GetCb()); ppkt->SetFlags(PKTF_RECV_LOOPBACK); IpRecv(ppkt); ppkt->ClearFlags(PKTF_RECV_LOOPBACK); ppkt->Complete(this); continue; } #ifdef XNET_FEATURE_ARP pae = ArpLookup(ipaNext, eSendRequest); if (pae == NULL) { TraceSz(pktWarn, "[DISCARD] ARP cache is full"); goto complete; } if (pae->IsIdle()) { EnetFillAndXmit(ppkt, &pae->_eaNext); continue; } if (pae->IsBad()) { TraceSz1(pktWarn, "[DISCARD] %s is unreachable", pae->_ipa.Str()); goto complete; } Assert(pae->IsBusy()); pae->_pqWait.InsertTail(ppkt); continue; #else TraceSz1(pktWarn, "[DISCARD] %s is unreachable", ipaNext); goto complete; #endif complete: ppkt->Complete(this); } } void CXnEnet::EnetFillAndXmit(CPacket * ppkt, CEnetAddr * peaNext) { ICHECK(ENET, UDPC|SDPC); Assert(ppkt->IsIp()); Assert(peaNext); CEnetHdr * pEnetHdr = ppkt->GetEnetHdr(); pEnetHdr->_eaDst = *peaNext; pEnetHdr->_eaSrc = _ea; pEnetHdr->_wType = ENET_TYPE_IP; NicXmit(ppkt); } void CXnEnet::EnetQueuePush() { ICHECK(ENET, UDPC|SDPC); KeInsertQueueDpc(&_dpcEnet, NULL, NULL); } void CXnEnet::EnetDpc(PKDPC, void * pEnet, void *, void *) { ((CXnEnet *)pEnet)->EnetPush(); }