/* * Copyright (c) 2016, 2017 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * net/arp.cpp * Address resolution protocol. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "arp.h" #include "ether.h" // Every network interface has its own ARP table of cached entries. The table // is a hash map of IP address to an ARP entry. The hash function is the // bytewise xor of each byte in the IP address. The table can contain up to 256 // entries, which all start out in a linked list of unused entries. // // The used entries of an table are in a linked list sorted in order of last // use. The unused entries are in a linked list in no particular order. The // entries currently being resolved are in the deadline linked list sorted in // order of the request deadline. The entries currently resolved and valid are // in a linked list sorted in order of their expiration. // // To evict an entry from the cache, remove the entry from the appropriate // linked lists, discard the entry's transmission queue, clear it, and add it to // the table's list of unused entries. // // To allocate an entry for an IP address, the hash table is searched for an // existing entry to return. If an existing entry is found, it is moved to the // front of the hash table in case of a collision. Otherwise, the first unused // entry is used. If the table was full, the least recently used entry is // evicted and then used. The new entry is assigned the IP address and added to // the hash table. // // When a packet is sent to an IP address, an ARP table is made for the network // interface if it doesn't already have one. If the IP address is outside the // network interface's IP subnet, or if the network interface has no IP address // configured, the request fails. An ARP entry for the destination IP address is // searched for, or if none exists, then a new one is allocated. The entry is // marked as USED and is moved to the front of the table's list of entries in // order of last use. If the entry is marked as RESOLVED, the packet is just // sent to the entry's Ethernet address. Otherwise if the entry has not been // marked as RESOLVING, an initial request for the IP address is broadcast on // the local network, the entry is added to the end of the deadline linked list, // and the deadline timer is set to fire when the request times out. The packet // is added to the entry's transmission queue unless it is already full. // // If the deadline timer fires, the entry is removed from the deadline linked // list. If too many attempts failed, the entry is evicted. Otherwise, the IP // address resolution is attempted again and the entry's request attempt counter // is incremented. // // When an ARP message is received, the message is discarded if the source or // destination IP is outside the network interface's subnet, or if the network // interface did not have an IP address configured. The entry for the source // IP address is located in the network interface's table, or if none exists and // the table is not currently full, an entry is allocated. The entry is removed // from the deadline linked list if it is RESOLVING. The entry is removed from // the expiration linked list if it is EXPIRING. The entry is marked as RESOLVED // and the source Ethernet address is assigned to the entry. The entry is marked // as EXPIRING and is added to the end of the expiring linked list and the // expiration is set to fire when the entry expires. Every packet in the entry's // transmission queue is sent to the source Ethernet address. // // If the message is a request, and the destination IP address is that of the // network interface. Otherwise, an ARP reply message is sent back with the // Ethernet address of the network interface. // // When the expiration timer fires, the entry is removed from the expiration // linked list. If the entry was not marked as USED, it is evicted. Otherwise // the entry is marked as RESOLVING, the attempt request attempt counter is // reset, and the address resolution is attempted again. Until the renewal // succeeds or times out, the entry remains marked RESOLVED and is used to route // traffic from its IP address to its Ethernet address. #define ETHERTYPE_ETHER 1 #define ARP_REQUEST 1 #define ARP_REPLY 2 // The entry contains a valid Ethernet address that has been resolved. #define ARP_STATUS_RESOLVED (1 << 0) // The entry is currently being resolved, the deadline timeout has been set and // the deadline timer will fire when the resolution times out. This status is // mutually exclusive with the EXPIRING status. #define ARP_STATUS_RESOLVING (1 << 1) // The entry has been resolved and is currently waiting until it expires, the // expiration timeout has been set and the expiration timer will fire when the // entry expires. This status is mutually exclusive with the RESOLVING status. #define ARP_STATUS_EXPIRING (1 << 2) // The entry has been used to route a packet and should be renewed on expiry. #define ARP_STATUS_USED (1 << 3) // The number of entries in an ARP table, this value is documented in arp(4). #define ARP_TABLE_LENGTH 256 // The number of entries in the ARP table hashmap, this value is documented // in arp(4). #define ARP_HASHTABLE_LENGTH 256 // Attempt to resolve an address this many times before giving up, this value is // documented in arp(4). #define ARP_MAX_ATTEMPTS 3 // The maximum number of packets in an ARP entry's transmission queue, this // value is documented in arp(4). #define ARP_MAX_PENDING 16 namespace Sortix { namespace ARP { // The duration to wait before giving up on an attempt to resolve an address, // this value is documented in arp(4). static const struct timespec REQUEST_TIMEOUT = { .tv_sec = 1, .tv_nsec = 0 }; // The duration before the entry expires and renewal begins, this value is // documented in arp(4). static const struct timespec ENTRY_TIMEOUT = { .tv_sec = 60, .tv_nsec = 0 }; struct arp { uint16_t hrd; /* Hardware address space */ uint16_t pro; /* Protocol address space */ uint8_t hln; /* Byte length of each hardware address */ uint8_t pln; /* Byte length of each protocol address */ uint16_t op; /* opcode */ uint8_t sha[6]; /* Hardware address of sender */ uint8_t spa[4]; /* Protocol address of sender */ uint8_t tha[6]; /* Hardware address of target */ uint8_t tpa[4]; /* Protocol address of target */ }; struct arp_entry { struct arp_table* table; struct arp_entry* prev_by_table; struct arp_entry* next_by_table; struct arp_entry* prev_by_hash; struct arp_entry* next_by_hash; struct arp_entry* prev_by_timer; struct arp_entry* next_by_timer; struct timespec timeout; struct in_addr addr; struct ether_addr ether; uint16_t status; uint16_t attempts; uint16_t pending; Ref pending_first; Ref pending_last; }; struct arp_table { NetworkInterface* netif; struct arp_entry* first_unused; struct arp_entry* first_used; struct arp_entry* last_unused; struct arp_entry* last_used; struct arp_entry* hashtable[ARP_HASHTABLE_LENGTH]; struct arp_entry entries[ARP_TABLE_LENGTH]; }; static void OnDeadline(Clock* clock, Timer* timer, void* context); static void OnExpiration(Clock* clock, Timer* timer, void* context); kthread_mutex_t arp_lock = KTHREAD_MUTEX_INITIALIZER; static struct arp_entry* first_by_deadline; static struct arp_entry* last_by_deadline; static struct arp_entry* first_by_expiration; static struct arp_entry* last_by_expiration; static Timer* deadline_timer; static Timer* expiration_timer; static bool deadline_timer_armed; static bool expiration_timer_armed; // This hash function is perfect if the subnet is at least /24, with no more // than remaining bits for the address of the machine on the subnet. static inline uint8_t HashAddress(const struct in_addr* addr) { uint32_t value = be32toh(addr->s_addr); return (value << 0 & 0xFF) ^ (value << 8 & 0xFF) ^ (value << 16 & 0xFF) ^ (value << 24 & 0xFF); } // arp_lock locked static struct arp_table* GetTable(NetworkInterface* netif) { if ( netif->arp_table ) return netif->arp_table; struct arp_table* table = new struct arp_table; if ( !table ) return NULL; memset(table, 0, sizeof(*table)); netif->arp_table = table; table->netif = netif; // Enter every entry into the table's unused linked list. table->first_unused = &table->entries[0]; for ( size_t i = 0; i < ARP_TABLE_LENGTH; i++ ) { table->entries[i].table = table; if ( i ) table->entries[i].prev_by_table = &table->entries[i-1]; if ( i + 1 < ARP_TABLE_LENGTH ) table->entries[i].next_by_table = &table->entries[i+1]; } table->last_unused = &table->entries[ARP_TABLE_LENGTH-1]; return table; } // arp_lock locked static void EvictEntry(struct arp_table* table, struct arp_entry* entry) { unsigned char hash = HashAddress(&entry->addr); // Remove from the table's used linked list. (entry->next_by_table ? entry->next_by_table->prev_by_table : table->last_used) = entry->prev_by_table; (entry->prev_by_table ? entry->prev_by_table->next_by_table : table->first_used) = entry->next_by_table; entry->prev_by_table = NULL; entry->next_by_table = NULL; // Remove from the hash table. if ( entry->next_by_hash ) entry->next_by_hash->prev_by_hash = entry->prev_by_hash; (entry->prev_by_hash ? entry->prev_by_hash->next_by_hash : table->hashtable[hash]) = entry->next_by_hash; entry->prev_by_hash = NULL; entry->next_by_hash = NULL; // Remove from deadline linked list. if ( entry->status & ARP_STATUS_RESOLVING ) { (entry->next_by_timer ? entry->next_by_timer->prev_by_timer : last_by_deadline) = entry->prev_by_timer; (entry->prev_by_timer ? entry->prev_by_timer->next_by_timer : first_by_deadline) = entry->next_by_timer; entry->prev_by_timer = NULL; entry->next_by_timer = NULL; } // Remove from expiration linked list. else if ( entry->status & ARP_STATUS_EXPIRING ) { (entry->next_by_timer ? entry->next_by_timer->prev_by_timer : last_by_expiration) = entry->prev_by_timer; (entry->prev_by_timer ? entry->prev_by_timer->next_by_timer : first_by_expiration) = entry->next_by_timer; entry->prev_by_timer = NULL; entry->next_by_timer = NULL; } // Drain the transmission queue while avoiding a stack overflow in packet // recursive destructor. while ( entry->pending_first ) { Ref next = entry->pending_first->next; entry->pending_first->next.Reset(); entry->pending_first = next; entry->pending--; if ( !entry->pending_first ) entry->pending_last.Reset(); } // Clear the entry. memset(entry, 0, sizeof(*entry)); entry->table = table; // Insert the entry into the table's unused linked list. (table->first_unused ? table->first_unused->prev_by_table : table->last_unused) = entry; entry->prev_by_table = NULL; entry->next_by_table = table->first_unused; table->first_unused = entry; } // arp_lock locked static struct arp_entry* AllocateEntry(struct arp_table* table, const struct in_addr* addr, bool evict) { // Search for an existing entry. unsigned char hash = HashAddress(addr); for ( struct arp_entry* entry = table->hashtable[hash]; entry; entry = entry->next_by_hash ) { if ( be32toh(addr->s_addr) == be32toh(entry->addr.s_addr) ) { // Move to the front of the hash table if not already. if ( entry->prev_by_hash ) { if ( entry->next_by_hash ) entry->next_by_hash->prev_by_hash = entry->prev_by_hash; entry->prev_by_hash->next_by_hash = entry->next_by_hash; entry->prev_by_hash = NULL; entry->next_by_hash = table->hashtable[hash]; table->hashtable[hash]->prev_by_hash = entry; table->hashtable[hash] = entry; } return entry; } } // Allocate a new entry, potentially evicting the least recently used entry. struct arp_entry* entry = table->first_unused; if ( !table->first_unused ) { if ( !evict ) return NULL; EvictEntry(table, table->last_used); assert(table->first_unused); entry = table->first_unused; } // Remove from the table's unused list. table->first_unused = entry->next_by_table; (table->first_unused ? table->first_unused->prev_by_table : table->last_unused) = NULL; // Initialize the entry. entry->addr.s_addr = addr->s_addr; // Insert into the table's used list. (table->last_used ? table->last_used->next_by_table : table->first_used) = entry; entry->prev_by_table = table->last_used; entry->next_by_table = NULL; table->last_used = entry; // Add to the front of the hash table. if ( table->hashtable[hash] ) table->hashtable[hash]->prev_by_hash = entry; entry->prev_by_hash = NULL; entry->next_by_hash = table->hashtable[hash]; table->hashtable[hash] = entry; return entry; } // arp_lock locked static bool Resolve(NetworkInterface* netif, struct arp_entry* entry) { if ( !deadline_timer ) { if ( !(deadline_timer = new Timer()) ) return false; deadline_timer->Attach(Time::GetClock(CLOCK_MONOTONIC)); } struct ether_addr src_ether_addr; struct in_addr src_in_addr; kthread_mutex_lock(&netif->cfg_lock); struct if_config* cfg = &netif->cfg; memcpy(&src_ether_addr, &cfg->ether.address, sizeof(struct ether_addr)); memcpy(&src_in_addr, &cfg->inet.address, sizeof(struct in_addr)); kthread_mutex_unlock(&netif->cfg_lock); struct arp arp; arp.hrd = htobe16(ETHERTYPE_ETHER); arp.pro = htobe16(ETHERTYPE_IP); arp.hln = sizeof(struct ether_addr); arp.pln = sizeof(struct in_addr); arp.op = htobe16(ARP_REQUEST); memcpy(arp.sha, &src_ether_addr, sizeof(struct ether_addr)); memcpy(arp.spa, &src_in_addr, sizeof(struct in_addr)); memcpy(arp.tha, ðeraddr_broadcast, sizeof(struct ether_addr)); memcpy(arp.tpa, &entry->addr, sizeof(struct in_addr)); Ref pkt = GetPacket(); if ( !pkt ) return false; if ( pkt->pmap.size < sizeof(arp) ) return errno = EMSGSIZE, false; pkt->length = sizeof(arp); memcpy(pkt->from, &arp, sizeof(arp)); if ( !Ether::Send(pkt, &src_ether_addr, ðeraddr_broadcast, ETHERTYPE_ARP, netif) ) return false; entry->status |= ARP_STATUS_RESOLVING; entry->attempts++; struct timespec now = Time::Get(CLOCK_MONOTONIC); entry->timeout = timespec_add(now, REQUEST_TIMEOUT); // Add entry to end of deadline linked list. (last_by_deadline ? last_by_deadline->next_by_timer : first_by_deadline) = entry; entry->prev_by_timer = last_by_deadline; entry->next_by_timer = NULL; last_by_deadline = entry; if ( !deadline_timer_armed ) { struct itimerspec its; its.it_value = REQUEST_TIMEOUT; its.it_interval = timespec_nul(); deadline_timer->Set(&its, NULL, 0, OnDeadline, NULL); deadline_timer_armed = true; } return true; } static void OnDeadline(Clock* clock, Timer* timer, void* /*context*/) { ScopedLock lock(&arp_lock); struct timespec now; clock->Get(&now, NULL); struct arp_entry* entry; while ( (entry = first_by_deadline) ) { if ( timespec_lt(now, entry->timeout) ) { struct itimerspec its; its.it_value = timespec_sub(entry->timeout, now); its.it_interval = timespec_nul(); timer->Set(&its, NULL, 0, OnDeadline, NULL); return; } struct arp_table* table = entry->table; // Remove from the deadline linked list. entry->status &= ~ARP_STATUS_RESOLVING; first_by_deadline = entry->next_by_timer; (first_by_deadline ? first_by_deadline->prev_by_timer : last_by_deadline) = NULL; entry->prev_by_timer = NULL; entry->next_by_timer = NULL; if ( entry->attempts < ARP_MAX_ATTEMPTS ) Resolve(table->netif, entry); else EvictEntry(table, entry); } deadline_timer_armed = false; } static void OnExpiration(Clock* clock, Timer* timer, void* /*context*/) { ScopedLock lock(&arp_lock); struct timespec now; clock->Get(&now, NULL); struct arp_entry* entry; while ( (entry = first_by_expiration) ) { if ( timespec_lt(now, entry->timeout) ) { struct itimerspec its; its.it_value = timespec_sub(entry->timeout, now); its.it_interval = timespec_nul(); timer->Set(&its, NULL, 0, OnExpiration, NULL); return; } struct arp_table* table = entry->table; // Remove the entry from the expiration linked list. entry->status &= ~ARP_STATUS_EXPIRING; first_by_expiration = entry->next_by_timer; (first_by_expiration ? first_by_expiration->prev_by_timer : last_by_expiration) = NULL; entry->prev_by_timer = NULL; entry->next_by_timer = NULL; if ( entry->status & ARP_STATUS_USED ) { entry->status &= ~ARP_STATUS_USED; entry->attempts = 0; Resolve(table->netif, entry); } else EvictEntry(table, entry); } expiration_timer_armed = false; } bool RouteIPEthernet(NetworkInterface* netif, Ref pkt, const struct in_addr* dst) { struct ether_addr local_ether; struct in_addr local_in; struct in_addr local_router; struct in_addr local_subnet; kthread_mutex_lock(&netif->cfg_lock); memcpy(&local_ether, &netif->cfg.ether.address, sizeof(struct ether_addr)); memcpy(&local_in, &netif->cfg.inet.address, sizeof(struct in_addr)); memcpy(&local_router, &netif->cfg.inet.router, sizeof(struct in_addr)); memcpy(&local_subnet, &netif->cfg.inet.subnet, sizeof(struct in_addr)); kthread_mutex_unlock(&netif->cfg_lock); if ( be32toh(local_in.s_addr) == INADDR_ANY ) return errno = ENETUNREACH, false; if ( (local_in.s_addr & local_subnet.s_addr) != (dst->s_addr &local_subnet.s_addr) ) return errno = ENETUNREACH, false; ScopedLock lock(&arp_lock); struct arp_table* table = GetTable(netif); if ( !table ) return false; struct arp_entry* entry = AllocateEntry(table, dst, true); assert(entry); // Mark as USED and move the entry to the front of table's used linked list. entry->status |= ARP_STATUS_USED; if ( entry->prev_by_table ) { (entry->next_by_table ? entry->next_by_table->prev_by_table : table->last_used) = entry->prev_by_table; entry->prev_by_table->next_by_table = entry->next_by_table; entry->prev_by_table = NULL; entry->next_by_table = table->first_used; table->first_used->prev_by_table = entry; table->first_used = entry; } if ( entry->status & ARP_STATUS_RESOLVED ) { struct ether_addr dst_ether = entry->ether; lock.Reset(); return Ether::Send(pkt, &local_ether, &dst_ether, ETHERTYPE_IP, netif); } assert(!pkt->next); if ( !(entry->status & ARP_STATUS_RESOLVING) && !Resolve(netif, entry) ) return false; // If the address isn't resolved, try send to the router instead. if ( dst->s_addr != local_router.s_addr && local_router.s_addr != INADDR_ANY ) { lock.Reset(); return RouteIPEthernet(netif, pkt, &local_router); } // Drop the packet if the transmission queue is full. if ( ARP_MAX_PENDING <= entry->pending ) return true; (entry->pending_last ? entry->pending_last->next : entry->pending_first) = pkt; entry->pending_last = pkt; entry->pending++; return true; } void Handle(Ref pkt, const struct ether_addr* src_ether_of_packet, const struct ether_addr* /*dst_ether*/, bool /*dst_ether*/) { const unsigned char* in = pkt->from + pkt->offset; size_t inlen = pkt->length - pkt->offset; NetworkInterface* netif = pkt->netif; struct arp hdr; if ( inlen < sizeof(hdr) ) return; memcpy(&hdr, in, sizeof(hdr)); hdr.hrd = be16toh(hdr.hrd); hdr.pro = be16toh(hdr.pro); hdr.op = be16toh(hdr.op); // Drop unsupported or invalid packets. if ( !(hdr.hrd == ETHERTYPE_ETHER && hdr.hln == 6) ) return; if ( !(hdr.pro == ETHERTYPE_IP && hdr.pln == 4) ) return; if ( !(hdr.op == ARP_REQUEST || hdr.op == ARP_REPLY) ) return; struct in_addr src; struct in_addr dst; memcpy(&src, hdr.spa, sizeof(src)); memcpy(&dst, hdr.tpa, sizeof(dst)); struct ether_addr src_ether; memcpy(&src_ether, hdr.sha, sizeof(src_ether)); struct ether_addr local_eth; struct in_addr local_in; struct in_addr local_subnet; kthread_mutex_lock(&netif->cfg_lock); memcpy(&local_eth, &netif->cfg.ether.address, sizeof(struct ether_addr)); memcpy(&local_in, &netif->cfg.inet.address, sizeof(struct in_addr)); memcpy(&local_subnet, &netif->cfg.inet.subnet, sizeof(struct in_addr)); kthread_mutex_unlock(&netif->cfg_lock); // Drop packets if the network interface does not have an IP address // configured, or if the source or destination IP address are outside of the // network interface's IP subnet. if ( be32toh(local_in.s_addr) == INADDR_ANY ) return; if ( (local_in.s_addr & local_subnet.s_addr) != (src.s_addr & local_subnet.s_addr)) return; if ( (local_in.s_addr & local_subnet.s_addr) != (dst.s_addr & local_subnet.s_addr) ) return; ScopedLock lock(&arp_lock); if ( !expiration_timer ) { if ( !(expiration_timer = new Timer()) ) return; expiration_timer->Attach(Time::GetClock(CLOCK_MONOTONIC)); } struct arp_table* table = GetTable(netif); if ( !table ) return; struct arp_entry* entry = AllocateEntry(table, &src, false); if ( entry ) { // Remove from pending request linked list. if ( entry->status & ARP_STATUS_RESOLVING ) { entry->status &= ~ARP_STATUS_RESOLVING; (entry->next_by_timer ? entry->next_by_timer->prev_by_timer : last_by_deadline) = entry->prev_by_timer; (entry->prev_by_timer ? entry->prev_by_timer->next_by_timer : first_by_deadline) = entry->next_by_timer; entry->prev_by_timer = NULL; entry->next_by_timer = NULL; } // Remove from expiration linked list. else if ( entry->status & ARP_STATUS_EXPIRING ) { entry->status &= ~ARP_STATUS_EXPIRING; (entry->next_by_timer ? entry->next_by_timer->prev_by_timer : last_by_expiration) = entry->prev_by_timer; (entry->prev_by_timer ? entry->prev_by_timer->next_by_timer : first_by_expiration) = entry->next_by_timer; entry->prev_by_timer = NULL; entry->next_by_timer = NULL; } // Mark entry as RESOLVED. entry->status |= ARP_STATUS_RESOLVED; memcpy(&entry->ether, &src_ether, sizeof(src_ether)); // Mark entry as EXPIRING and add to end of the expiration linked list. entry->status |= ARP_STATUS_EXPIRING; (last_by_expiration ? last_by_expiration->next_by_timer : first_by_expiration) = entry; entry->prev_by_timer = last_by_expiration; entry->next_by_timer = NULL; last_by_expiration = entry; struct timespec now = Time::Get(CLOCK_MONOTONIC); entry->timeout = timespec_add(now, ENTRY_TIMEOUT); if ( !expiration_timer_armed ) { struct itimerspec its; its.it_value = ENTRY_TIMEOUT; its.it_interval = timespec_nul(); expiration_timer->Set(&its, NULL, 0, OnExpiration, NULL); expiration_timer_armed = true; } // Transmit the transission queue. while ( entry->pending_first ) { Ref pending = entry->pending_first; entry->pending_first = pending->next; pending->next.Reset(); Ether::Send(pending, &local_eth, &src_ether, ETHERTYPE_IP, netif); if ( !entry->pending_first ) entry->pending_last.Reset(); } } // Send an ARP reply if our local address was requested. if ( hdr.op == ARP_REQUEST && !memcmp(&local_in, &dst, sizeof(struct in_addr)) ) { Ref packet = GetPacket(); if ( !packet ) return; struct arp arp; arp.hrd = htobe16(ETHERTYPE_ETHER); arp.pro = htobe16(ETHERTYPE_IP); arp.hln = sizeof(struct ether_addr); arp.pln = sizeof(struct in_addr); arp.op = htobe16(ARP_REPLY); memcpy(arp.sha, &local_eth, sizeof(struct ether_addr)); memcpy(arp.spa, &local_in, sizeof(struct in_addr)); memcpy(arp.tha, &src_ether, sizeof(struct ether_addr)); memcpy(arp.tpa, &src, sizeof(struct in_addr)); if ( packet->pmap.size < sizeof(arp) ) return; packet->length = sizeof(arp); unsigned char* out = packet->from; memcpy(out, &arp, sizeof(arp)); Ether::Send(packet, &local_eth, src_ether_of_packet, ETHERTYPE_ARP, netif); } } // arp_lock locked, netif->cfg_lock locked. void OnConfiguration(NetworkInterface* netif, const struct if_config* old_cfg, const struct if_config* new_cfg) { // Purge the ARP cache if the ether or inet configuration changed. if ( !memcmp(&old_cfg->ether, &new_cfg->ether, sizeof(new_cfg->ether)) && !memcmp(&old_cfg->inet, &new_cfg->inet, sizeof(new_cfg->inet)) ) return; struct arp_table* table = GetTable(netif); if ( !table ) return; while ( table->first_used ) EvictEntry(table, table->first_used); } } // namespace ARP } // namespace Sortix