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

470 lines
12 KiB
C

/*++
Copyright (c) 2000 Microsoft Corporation
Module Name:
arp.c
Abstract:
Handles the ARP protocol
Revision History:
05/04/2000 davidx
Created it.
--*/
#include "precomp.h"
//
// Global ARP configuration info:
// ARP cache entry timeout value (in seconds)
// # of ARP cache entries per-interface
// # of retries for ARP requests
//
UINT cfgPositiveArpCacheTimeout = POSITIVE_ARP_CACHE_TIMEOUT;
UINT cfgNegativeArpCacheTimeout = NEGATIVE_ARP_CACHE_TIMEOUT;
UINT cfgArpRequestRetries = ARP_REQUEST_RETRIES;
NTSTATUS
ArpSendPacket(
IfEnet* ifp,
WORD op,
IPADDR tpa,
IPADDR spa,
const BYTE* tha
)
/*++
Routine Description:
Send out an ARP packet
Arguments:
ifp - Points to the interface structure
op - opcode: ARP_REQUEST or ARP_REPLY
tpa - Specifies the target IP address
spa - Specifies the source IP address
tha - Sppecifies the target hardware address
NULL when opcode is ARP_REQUEST
Return Value:
Status code
--*/
{
Packet* pkt;
EnetFrameHeader* enethdr;
ArpPacket* arppkt;
UINT size = ENETHDRLEN + ARPPKTLEN;
// Allocate memory for the packet
pkt = XnetAllocPacket(size, PKTFLAG_NETPOOL);
if (!pkt) return NETERR_MEMORY;
pkt->datalen = size;
// Fill out Ethernet header
// assume pkt->data is DWORD-aligned initially
enethdr = GETPKTDATA(pkt, EnetFrameHeader);
CopyMem(enethdr->dstaddr, tha ? tha : EnetBroadcastAddr, ENETADDRLEN);
CopyMem(enethdr->srcaddr, ifp->hwaddr, ENETADDRLEN);
enethdr->etherType = HTONS(ENETTYPE_ARP);
// Fill out the ARP request packet
arppkt = (ArpPacket*) (enethdr+1);
arppkt->hrd = HTONS(ARP_HWTYPE_ENET);
arppkt->pro = HTONS(ENETTYPE_IP);
arppkt->hln = ENETADDRLEN;
arppkt->pln = IPADDRLEN;
arppkt->op = HTONS(op);
CopyMem(arppkt->sha, ifp->hwaddr, ENETADDRLEN);
arppkt->spa = spa;
arppkt->tpa = tpa;
if (tha) {
CopyMem(arppkt->tha, tha, ENETADDRLEN);
} else {
ZeroMem(arppkt->tha, ENETADDRLEN);
}
NicTransmitFrame(ifp, pkt);
return NETERR_OK;
}
ArpCacheEntry*
ArpFindCacheEntry(
IfEnet* ifp,
IPADDR ipaddr,
INT resolve
)
/*++
Routine Description:
Resolve the specified target IP address to an ARP cache entry.
If no existing entry is found, then:
- if resolve is RESOLVE_NONE, we'll simple return NULL;
- if resolve is RESOLVE_CREATE_ENTRY, we'll try to make a new entry
for the specified address and return it to the caller;
- if resolve is RESOLVE_SEND_REQUEST, we'll create the entry
as well as send out a ARP request packet.
Arguments:
ifp - Points to the interface structure
ipaddr - Specifies the target address
resolve - What to do if no existing entry is found
Return Value:
ARP cache entry for the specified IP address
NULL if there is an error
--*/
// Not a terribly fancy hash function
// but it should serve our purpose.
#define ARPHASH(_addr) ((_addr) % ARP_HASH_MODULO)
{
ArpCacheEntry* arpEntry;
ArpCacheEntry* retryEnd;
ArpCacheEntry* hashEntry;
UINT hash;
RUNS_AT_DISPATCH_LEVEL
ASSERT(XnetIsValidUnicastAddr(ipaddr));
// Fast check
if (ipaddr == ifp->lastArpAddr)
return ifp->lastArpEntry;
// Get the hash bucket for the specified address
hash = ARPHASH(ipaddr);
arpEntry = &ifp->arpCache[hash];
// Found the target address in the cache via a direct hash hit
if (arpEntry->ipaddr == ipaddr) {
found:
ifp->lastArpAddr = ipaddr;
return (ifp->lastArpEntry = arpEntry);
}
// No direct hash hit, try linear search
hashEntry = arpEntry;
retryEnd = arpEntry + ARP_HASH_RETRY;
while (++arpEntry < retryEnd) {
if (arpEntry->ipaddr == ipaddr) goto found;
}
if (resolve == RESOLVE_NONE)
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 hack bucket is free
arpEntry = hashEntry;
if (!IsArpEntryFree(arpEntry)) {
while (++arpEntry < retryEnd) {
if (IsArpEntryFree(arpEntry)) break;
}
// Couldn't find a free entry
// fall back and try to find a non-busy entry
if (arpEntry == retryEnd) {
arpEntry = hashEntry;
while (++arpEntry < retryEnd) {
if (!IsArpEntryBusy(arpEntry)) break;
}
if (arpEntry == retryEnd) {
// Too bad: couldn't find either a free or non-busy entry
// emit a warning and give up
WARNING_("ARP cache collision error");
return NULL;
}
// This entry is currently used for another address:
// we'll just bump it off here.
VERBOSE_("Bumped ARP entry for %s", IPADDRSTR(arpEntry->ipaddr));
ifp->arpCacheCount--;
}
}
TRACE_("Add ARP entry for %s", IPADDRSTR(ipaddr));
arpEntry->ipaddr = ipaddr;
ASSERT(PktQIsEmpty(&arpEntry->waitq));
if (resolve == RESOLVE_SEND_REQUEST) {
arpEntry->state = ARPST_BUSY;
arpEntry->timer = cfgArpRequestRetries;
ArpSendRequest(ifp, ipaddr, ifp->ipaddr);
} else {
// NOTE: we mark the entry free here
// It'll be immediately marked as OK in ArpReceivePacket
// after this function returns.
arpEntry->state = ARPST_FREE;
}
ifp->arpCacheCount++;
goto found;
}
VOID
ArpTimerProc(
IfEnet* ifp
)
/*++
Routine Description:
ARP timer routine
Arguments:
ifp - Points to the interface structure
Return Value:
NONE
--*/
{
ArpCacheEntry* arpEntry;
INT index;
// Quick check: do nothing if all entries are free
if (ifp->arpCacheCount == 0) return;
ASSERT(ifp->arpCacheCount > 0);
// Loop through all cache entries
for (index=0; index < ARP_CACHE_SIZE; index++) {
arpEntry = &ifp->arpCache[index];
if (arpEntry->state == ARPST_FREE) continue;
switch (arpEntry->state) {
case ARPST_BUSY:
//
// The entry is currently being resolved
//
if (--arpEntry->timer == 0) {
//
// And the entry has expired:
// mark the entry as bad and complete any waiting packet
// with error status
//
arpEntry->state = ARPST_BAD;
arpEntry->timer = cfgNegativeArpCacheTimeout;
while (!PktQIsEmpty(&arpEntry->waitq)) {
Packet* pkt = PktQRemoveHead(&arpEntry->waitq);
COMPLETEPACKET(pkt, NETERR_UNREACHABLE);
}
} else {
//
// Timeout while waiting for ARP reply:
// retransmit the ARP request frame
//
ArpSendRequest(ifp, arpEntry->ipaddr, ifp->ipaddr);
}
break;
case ARPST_OK:
case ARPST_BAD:
if (--arpEntry->timer == 0) {
//
// If the entry has expired, remove it from the cache
//
TRACE_("ARP entry for %s timed out", IPADDRSTR(arpEntry->ipaddr));
// Reset the last ARP lookup info if applicable.
if (arpEntry == ifp->lastArpEntry) {
ifp->lastArpAddr = 0;
ifp->lastArpEntry = NULL;
}
ZeroMem(arpEntry, sizeof(*arpEntry));
ifp->arpCacheCount--;
}
break;
}
}
}
VOID
ArpReceivePacket(
IfEnet* ifp,
Packet* pkt
)
/*++
Routine Description:
Process an incoming ARP packet
Arguments:
ifp - Points to the interface structure
pkt - The received ARP packet
Return Value:
NONE
--*/
{
ArpPacket* arppkt = GETPKTDATA(pkt, ArpPacket);
WORD op;
IPADDR spa, tpa;
ArpCacheEntry* arpEntry;
INT resolve;
BYTE sha[ENETADDRLEN];
ASSERT(pkt->datalen >= ARPPKTLEN);
op = NTOHS(arppkt->op);
spa = arppkt->spa;
tpa = arppkt->tpa;
// Simple sanity check
if (arppkt->hrd != HTONS(ARP_HWTYPE_ENET) &&
arppkt->hrd != HTONS(ARP_HWTYPE_802) ||
arppkt->pro != HTONS(ENETTYPE_IP) ||
arppkt->hln != ENETADDRLEN ||
arppkt->pln != IPADDRLEN ||
op != ARP_REQUEST && op != ARP_REPLY ||
!XnetIsValidUnicastAddr(spa) ||
!XnetIsValidUnicastAddr(tpa)) {
goto exit;
}
// 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 (spa == ifp->ipaddr || spa == ifp->checkConflictAddr) {
if (IfDhcpEnabled(ifp)) {
ifp->checkConflictAddr = 0;
DhcpNotifyAddressConflict((IfInfo*) ifp);
}
// In case the other case is checking for address conflict,
// send out a reply to indicate we already have the address.
if (tpa == ifp->ipaddr && op == ARP_REQUEST) {
ArpSendReply(ifp, spa, arppkt->sha);
}
goto exit;
} else if (!IfRunning(ifp)) {
goto exit;
}
CopyMem(sha, arppkt->sha, ENETADDRLEN);
resolve = (tpa == ifp->ipaddr) ? RESOLVE_CREATE_ENTRY : RESOLVE_NONE;
// We should return the incoming packet structure
// back to the Enet driver as soon as possible.
COMPLETEPACKET(pkt, NETERR_OK);
arpEntry = ArpFindCacheEntry(ifp, spa, resolve);
if (arpEntry) {
arpEntry->state = ARPST_OK;
arpEntry->timer = cfgPositiveArpCacheTimeout;
CopyMem(arpEntry->enetaddr, sha, ENETADDRLEN);
// If there were packets waiting on this entry,
// now that we have the address resolve, it's
// time to start the transmission.
//
// NOTE: We insert the waiting packets at the head of
// the interface's send queue. We're not calling
// EnetTransmitPacket directly because we want to
// limit the depth of the command queue (which is
// done inside EnetStartOutput).
if (!PktQIsEmpty(&arpEntry->waitq)) {
PktQJoinBefore(&ifp->sendq, &arpEntry->waitq);
PktQInit(&arpEntry->waitq);
EnetStartOutput(ifp);
}
}
// If we're the target and the packet is an ARP request,
// then send out an ARP reply. Notice that we allocate a
// new Packet here instead reusing the incoming packet.
if (tpa == ifp->ipaddr && op == ARP_REQUEST) {
ArpSendReply(ifp, spa, sha);
}
return;
exit:
// Return the packet to the caller
COMPLETEPACKET(pkt, NETERR_DISCARDED);
}
VOID
ArpCleanup(
IfEnet* ifp
)
/*++
Routine Description:
Clean up ARP related data
Arguments:
ifp - Points to the interface structure
Return Value:
NONE
--*/
{
ArpCacheEntry* arpEntry;
Packet* pkt;
INT index;
// Loop thru all cache entries
for (index=0; index < ARP_CACHE_SIZE; index++) {
arpEntry = &ifp->arpCache[index];
arpEntry->state = ARPST_BAD;
// Complete any packets waiting on the entry
while (!PktQIsEmpty(&arpEntry->waitq)) {
pkt = PktQRemoveHead(&arpEntry->waitq);
COMPLETEPACKET(pkt, NETERR_CANCELLED);
}
}
ifp->lastArpAddr = 0;
ifp->lastArpEntry = NULL;
ifp->arpCacheCount = 0;
ZeroMem(&ifp->arpCache, sizeof(ifp->arpCache));
}