745 lines
25 KiB
C++
745 lines
25 KiB
C++
|
/*
|
||
|
* 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 <assert.h>
|
||
|
#include <errno.h>
|
||
|
#include <netinet/if_ether.h>
|
||
|
#include <netinet/in.h>
|
||
|
#include <stdint.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <time.h>
|
||
|
#include <timespec.h>
|
||
|
|
||
|
#include <sortix/kernel/clock.h>
|
||
|
#include <sortix/kernel/kernel.h>
|
||
|
#include <sortix/kernel/kthread.h>
|
||
|
#include <sortix/kernel/if.h>
|
||
|
#include <sortix/kernel/packet.h>
|
||
|
#include <sortix/kernel/refcount.h>
|
||
|
#include <sortix/kernel/time.h>
|
||
|
#include <sortix/kernel/timer.h>
|
||
|
|
||
|
#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
|
||
|
// 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
|
||
|
// expiraton 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<Packet> pending_first;
|
||
|
Ref<Packet> 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<Packet> 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<Packet> 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 )
|
||
|
{
|
||
|
NetworkInterface* netif = table->netif;
|
||
|
Resolve(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;
|
||
|
NetworkInterface* netif = table->netif;
|
||
|
Resolve(netif, entry);
|
||
|
}
|
||
|
else
|
||
|
EvictEntry(table, entry);
|
||
|
}
|
||
|
expiration_timer_armed = false;
|
||
|
}
|
||
|
|
||
|
bool RouteIPv4Ethernet(NetworkInterface* netif,
|
||
|
Ref<Packet> pkt,
|
||
|
const struct in_addr* dst)
|
||
|
{
|
||
|
struct ether_addr local_ether;
|
||
|
struct in_addr local_in;
|
||
|
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_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;
|
||
|
// 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<Packet> 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<Packet> 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> 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
|