This change adds all the kernel parts of a network stack. The network stack is partial but implements many of the important parts. Add if(4) network interface abstraction. Network interfaces are registered in a global list that can be iterated and each assigned an unique integer identifier. Add reference counted packets with a cache that recycles recent packets. Add support for lo(4) loopback and ether(4) ethernet network interfaces. The /dev/lo0 loopback device is created automatically on boot. Add arp(4) address resolution protocol driver for translation of inet(4) network layer addresses into ether(4) link layer addresses. arp(4) entries are cached and evicted from the cache when needed or when the entry has not been used for a while. The cache is limited to 256 entries for now. Add ip(4) internet protocol version 4 support. IP fragmentation and options are not implemented yet. Add tcp(4) transmission control protocol sockets for a reliable transport layer protocol that provides a reliable byte stream connection between two hosts. The implementation is incomplete and does not yet implement out of band data, options, and high performance extensions. Add udp(4) user datagram protocol sockets for a connectionless transport layer that provides best-effort delivery of datagrams. Add ping(4) sockets for a best-effort delivery of echo datagrams. Change type of sa_family_t from unsigned short to uint16_t. Add --disable-network-drivers to the kernel(7) options and expose it with a bootloader menu. tix-iso-bootconfig can set this option by default. Import CRC32 code from libz for the Ethernet checksum. This is a compatible ABI change that adds features to socket(2) (AF_INET, IPPROTO_TCP, IPPROTO_UDP, IPPROTO_PING), the ioctls for if(4), socket options, and the lo0 loopback interface. This commit is based on work by Meisaka Yukara contributed as the commit bbf7f1e8a5238a2bd1fe8eb1d2cc5c9c2421e2c4. Almost no lines of this work remains in this final commit as it has been rewritten or refactored away over the years, see the individual file headers for which files contain remnants of this work. Co-authored-by: Meisaka Yukara <Meisaka.Yukara@gmail.com>
741 lines
25 KiB
C++
741 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
|
|
// 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<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 )
|
|
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<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
|