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