2020-09-30 17:17:25 +02:00

635 lines
17 KiB
C++

// ---------------------------------------------------------------------------------------
// 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();
}