]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[neighbour] Generalise concept of neighbour discovery
authorMichael Brown <mcb30@ipxe.org>
Sun, 1 Sep 2013 15:13:58 +0000 (16:13 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 3 Sep 2013 01:02:58 +0000 (02:02 +0100)
Split the protocol-independent portions of arp.c into a separate file
neighbour.c, to allow for sharing of functionality between IPv4+ARP
and IPv6+NDP.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/arp.h
src/include/ipxe/errfile.h
src/include/ipxe/neighbour.h [new file with mode: 0644]
src/net/arp.c
src/net/neighbour.c [new file with mode: 0644]

index 00396d8214dfa4395adc3785f8f8c50e0e821bc8..e30ae6b76f761e877f3cb55899dedd6a4f11c5e7 100644 (file)
@@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 
 #include <ipxe/tables.h>
 #include <ipxe/netdevice.h>
+#include <ipxe/neighbour.h>
 
 /** A network-layer protocol that relies upon ARP */
 struct arp_net_protocol {
@@ -34,9 +35,26 @@ struct arp_net_protocol {
 #define __arp_net_protocol __table_entry ( ARP_NET_PROTOCOLS, 01 )
 
 extern struct net_protocol arp_protocol __net_protocol;
+extern struct neighbour_discovery arp_discovery;
 
-extern int arp_tx ( struct io_buffer *iobuf, struct net_device *netdev,
-                   struct net_protocol *net_protocol, const void *net_dest,
-                   const void *net_source, const void *ll_source );
+/**
+ * Transmit packet, determining link-layer address via ARP
+ *
+ * @v iobuf            I/O buffer
+ * @v netdev           Network device
+ * @v net_protocol     Network-layer protocol
+ * @v net_dest         Destination network-layer address
+ * @v net_source       Source network-layer address
+ * @v ll_source                Source link-layer address
+ * @ret rc             Return status code
+ */
+static inline int arp_tx ( struct io_buffer *iobuf, struct net_device *netdev,
+                          struct net_protocol *net_protocol,
+                          const void *net_dest, const void *net_source,
+                          const void *ll_source ) {
+
+       return neighbour_tx ( iobuf, netdev, net_protocol, net_dest,
+                             &arp_discovery, net_source, ll_source );
+}
 
 #endif /* _IPXE_ARP_H */
index 801d579b60d657422202c3cb455f6f40a453bdb8..83675796c206b9b94d22851fafffd161aaeac6a9 100644 (file)
@@ -214,6 +214,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #define ERRFILE_nfs_open               ( ERRFILE_NET | 0x00340000 )
 #define ERRFILE_mount                  ( ERRFILE_NET | 0x00350000 )
 #define ERRFILE_oncrpc_iob             ( ERRFILE_NET | 0x00360000 )
+#define ERRFILE_neighbour              ( ERRFILE_NET | 0x00370000 )
 
 #define ERRFILE_image                ( ERRFILE_IMAGE | 0x00000000 )
 #define ERRFILE_elf                  ( ERRFILE_IMAGE | 0x00010000 )
diff --git a/src/include/ipxe/neighbour.h b/src/include/ipxe/neighbour.h
new file mode 100644 (file)
index 0000000..5720e8b
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef _IPXE_NEIGHBOUR_H
+#define _IPXE_NEIGHBOUR_H
+
+/** @file
+ *
+ * Neighbour discovery
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <ipxe/netdevice.h>
+
+/** A neighbour discovery protocol */
+struct neighbour_discovery {
+       /** Name */
+       const char *name;
+       /**
+        * Transmit neighbour discovery request
+        *
+        * @v netdev            Network device
+        * @v net_protocol      Network-layer protocol
+        * @v net_dest          Destination network-layer address
+        * @v net_source        Source network-layer address
+        * @ret rc              Return status code
+        */
+       int ( * tx_request ) ( struct net_device *netdev,
+                              struct net_protocol *net_protocol,
+                              const void *net_dest, const void *net_source );
+};
+
+extern int neighbour_tx ( struct io_buffer *iobuf, struct net_device *netdev,
+                         struct net_protocol *net_protocol,
+                         const void *net_dest,
+                         struct neighbour_discovery *discovery,
+                         const void *net_source, const void *ll_source );
+extern int neighbour_update ( struct net_device *netdev,
+                             struct net_protocol *net_protocol,
+                             const void *net_dest, const void *ll_dest );
+extern int neighbour_define ( struct net_device *netdev,
+                             struct net_protocol *net_protocol,
+                             const void *net_dest, const void *ll_dest );
+
+#endif /* _IPXE_NEIGHBOUR_H */
index b94eb9064b25b7965f84d158ddc418d3935dad1f..261e681e15c4c668b167dbb1901d7fb4090be521 100644 (file)
@@ -28,11 +28,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <ipxe/if_arp.h>
 #include <ipxe/iobuf.h>
 #include <ipxe/netdevice.h>
-#include <ipxe/list.h>
-#include <ipxe/retry.h>
-#include <ipxe/timer.h>
-#include <ipxe/malloc.h>
-#include <ipxe/refcnt.h>
+#include <ipxe/neighbour.h>
 #include <ipxe/arp.h>
 
 /** @file
@@ -45,291 +41,30 @@ FILE_LICENCE ( GPL2_OR_LATER );
  *
  */
 
-/** ARP minimum timeout */
-#define ARP_MIN_TIMEOUT ( TICKS_PER_SEC / 8 )
-
-/** ARP maximum timeout */
-#define ARP_MAX_TIMEOUT ( TICKS_PER_SEC * 3 )
-
-/** An ARP cache entry */
-struct arp_entry {
-       /** Reference count */
-       struct refcnt refcnt;
-       /** List of ARP cache entries */
-       struct list_head list;
-       /** Network device */
-       struct net_device *netdev;
-       /** Network-layer protocol */
-       struct net_protocol *net_protocol;
-       /** Network-layer destination address */
-       uint8_t net_dest[MAX_NET_ADDR_LEN];
-       /** Network-layer source address */
-       uint8_t net_source[MAX_NET_ADDR_LEN];
-       /** Link-layer destination address */
-       uint8_t ll_dest[MAX_LL_ADDR_LEN];
-       /** Retransmission timer */
-       struct retry_timer timer;
-       /** Pending I/O buffers */
-       struct list_head tx_queue;
-};
-
-/** The ARP cache */
-static LIST_HEAD ( arp_entries );
-
 struct net_protocol arp_protocol __net_protocol;
 
-static void arp_expired ( struct retry_timer *timer, int over );
-
-/**
- * Free ARP cache entry
- *
- * @v refcnt           Reference count
- */
-static void arp_free ( struct refcnt *refcnt ) {
-       struct arp_entry *arp =
-               container_of ( refcnt, struct arp_entry, refcnt );
-
-       /* Sanity check */
-       assert ( list_empty ( &arp->tx_queue ) );
-
-       /* Drop reference to network device */
-       netdev_put ( arp->netdev );
-
-       /* Free entry */
-       free ( arp );
-}
-
-/**
- * Create ARP cache entry
- *
- * @v netdev           Network device
- * @v net_protocol     Network-layer protocol
- * @v net_dest         Destination network-layer address
- * @v net_source       Source network-layer address
- * @ret arp            ARP cache entry, or NULL if allocation failed
- */
-static struct arp_entry * arp_create ( struct net_device *netdev,
-                                      struct net_protocol *net_protocol,
-                                      const void *net_dest,
-                                      const void *net_source ) {
-       struct arp_entry *arp;
-
-       /* Allocate and initialise entry */
-       arp = zalloc ( sizeof ( *arp ) );
-       if ( ! arp )
-               return NULL;
-       ref_init ( &arp->refcnt, arp_free );
-       arp->netdev = netdev_get ( netdev );
-       arp->net_protocol = net_protocol;
-       memcpy ( arp->net_dest, net_dest,
-                net_protocol->net_addr_len );
-       memcpy ( arp->net_source, net_source,
-                net_protocol->net_addr_len );
-       timer_init ( &arp->timer, arp_expired, &arp->refcnt );
-       arp->timer.min_timeout = ARP_MIN_TIMEOUT;
-       arp->timer.max_timeout = ARP_MAX_TIMEOUT;
-       INIT_LIST_HEAD ( &arp->tx_queue );
-
-       /* Start timer running to trigger initial transmission */
-       start_timer_nodelay ( &arp->timer );
-
-       /* Transfer ownership to cache */
-       list_add ( &arp->list, &arp_entries );
-
-       DBGC ( arp, "ARP %p %s %s %s created\n", arp, netdev->name,
-              net_protocol->name, net_protocol->ntoa ( net_dest ) );
-       return arp;
-}
-
-/**
- * Find entry in the ARP cache
- *
- * @v netdev           Network device
- * @v net_protocol     Network-layer protocol
- * @v net_dest         Destination network-layer address
- * @ret arp            ARP cache entry, or NULL if not found
- */
-static struct arp_entry * arp_find ( struct net_device *netdev,
-                                    struct net_protocol *net_protocol,
-                                    const void *net_dest ) {
-       struct arp_entry *arp;
-
-       list_for_each_entry ( arp, &arp_entries, list ) {
-               if ( ( arp->netdev == netdev ) &&
-                    ( arp->net_protocol == net_protocol ) &&
-                    ( memcmp ( arp->net_dest, net_dest,
-                               net_protocol->net_addr_len ) == 0 ) ) {
-
-                       /* Move to start of cache */
-                       list_del ( &arp->list );
-                       list_add ( &arp->list, &arp_entries );
-
-                       return arp;
-               }
-       }
-       return NULL;
-}
-
-/**
- * Destroy ARP cache entry
- *
- * @v arp              ARP cache entry
- * @v rc               Reason for destruction
- */
-static void arp_destroy ( struct arp_entry *arp, int rc ) {
-       struct net_device *netdev = arp->netdev;
-       struct net_protocol *net_protocol = arp->net_protocol;
-       struct io_buffer *iobuf;
-
-       /* Take ownership from cache */
-       list_del ( &arp->list );
-
-       /* Stop timer */
-       stop_timer ( &arp->timer );
-
-       /* Discard any outstanding I/O buffers */
-       while ( ( iobuf = list_first_entry ( &arp->tx_queue, struct io_buffer,
-                                            list ) ) != NULL ) {
-               DBGC2 ( arp, "ARP %p %s %s %s discarding deferred packet: "
-                       "%s\n", arp, netdev->name, net_protocol->name,
-                       net_protocol->ntoa ( arp->net_dest ), strerror ( rc ) );
-               list_del ( &iobuf->list );
-               netdev_tx_err ( arp->netdev, iobuf, rc );
-       }
-
-       DBGC ( arp, "ARP %p %s %s %s destroyed: %s\n", arp, netdev->name,
-              net_protocol->name, net_protocol->ntoa ( arp->net_dest ),
-              strerror ( rc ) );
-
-       /* Drop remaining reference */
-       ref_put ( &arp->refcnt );
-}
-
 /**
- * Test if ARP cache entry has a valid link-layer address
+ * Transmit ARP request
  *
- * @v arp              ARP cache entry
- * @ret resolved       ARP cache entry is resolved
- */
-static inline int arp_resolved ( struct arp_entry *arp ) {
-       return ( ! timer_running ( &arp->timer ) );
-}
-
-/**
- * Transmit packet, determining link-layer address via ARP
- *
- * @v iobuf            I/O buffer
  * @v netdev           Network device
  * @v net_protocol     Network-layer protocol
  * @v net_dest         Destination network-layer address
  * @v net_source       Source network-layer address
- * @v ll_source                Source link-layer address
  * @ret rc             Return status code
  */
-int arp_tx ( struct io_buffer *iobuf, struct net_device *netdev,
-            struct net_protocol *net_protocol, const void *net_dest,
-            const void *net_source, const void *ll_source ) {
-       struct arp_entry *arp;
-
-       /* Find or create ARP cache entry */
-       arp = arp_find ( netdev, net_protocol, net_dest );
-       if ( ! arp ) {
-               arp = arp_create ( netdev, net_protocol, net_dest,
-                                  net_source );
-               if ( ! arp )
-                       return -ENOMEM;
-       }
-
-       /* If a link-layer address is available then transmit
-        * immediately, otherwise queue for later transmission.
-        */
-       if ( arp_resolved ( arp ) ) {
-               return net_tx ( iobuf, netdev, net_protocol, arp->ll_dest,
-                               ll_source );
-       } else {
-               DBGC2 ( arp, "ARP %p %s %s %s deferring packet\n",
-                       arp, netdev->name, net_protocol->name,
-                       net_protocol->ntoa ( net_dest ) );
-               list_add_tail ( &iobuf->list, &arp->tx_queue );
-               return -EAGAIN;
-       }
-}
-
-/**
- * Update ARP cache entry
- *
- * @v arp              ARP cache entry
- * @v ll_dest          Destination link-layer address
- */
-static void arp_update ( struct arp_entry *arp, const void *ll_dest ) {
-       struct net_device *netdev = arp->netdev;
-       struct ll_protocol *ll_protocol = netdev->ll_protocol;
-       struct net_protocol *net_protocol = arp->net_protocol;
-       struct io_buffer *iobuf;
-       int rc;
-
-       DBGC ( arp, "ARP %p %s %s %s updated => %s\n", arp, netdev->name,
-              net_protocol->name, net_protocol->ntoa ( arp->net_dest ),
-              ll_protocol->ntoa ( ll_dest ) );
-
-       /* Fill in link-layer address */
-       memcpy ( arp->ll_dest, ll_dest, ll_protocol->ll_addr_len );
-
-       /* Stop retransmission timer */
-       stop_timer ( &arp->timer );
-
-       /* Transmit any packets in queue.  Take out a temporary
-        * reference on the entry to prevent it from going out of
-        * scope during the call to net_tx().
-        */
-       ref_get ( &arp->refcnt );
-       while ( ( iobuf = list_first_entry ( &arp->tx_queue, struct io_buffer,
-                                            list ) ) != NULL ) {
-               DBGC2 ( arp, "ARP %p %s %s %s transmitting deferred packet\n",
-                       arp, netdev->name, net_protocol->name,
-                       net_protocol->ntoa ( arp->net_dest ) );
-               list_del ( &iobuf->list );
-               if ( ( rc = net_tx ( iobuf, netdev, net_protocol, ll_dest,
-                                    netdev->ll_addr ) ) != 0 ) {
-                       DBGC ( arp, "ARP %p could not transmit deferred "
-                              "packet: %s\n", arp, strerror ( rc ) );
-                       /* Ignore error and continue */
-               }
-       }
-       ref_put ( &arp->refcnt );
-}
-
-/**
- * Handle ARP timer expiry
- *
- * @v timer            Retry timer
- * @v fail             Failure indicator
- */
-static void arp_expired ( struct retry_timer *timer, int fail ) {
-       struct arp_entry *arp = container_of ( timer, struct arp_entry, timer );
-       struct net_device *netdev = arp->netdev;
+static int arp_tx_request ( struct net_device *netdev,
+                           struct net_protocol *net_protocol,
+                           const void *net_dest, const void *net_source ) {
        struct ll_protocol *ll_protocol = netdev->ll_protocol;
-       struct net_protocol *net_protocol = arp->net_protocol;
        struct io_buffer *iobuf;
        struct arphdr *arphdr;
        int rc;
 
-       /* If we have failed, destroy the cache entry */
-       if ( fail ) {
-               arp_destroy ( arp, -ETIMEDOUT );
-               return;
-       }
-
-       /* Restart the timer */
-       start_timer ( &arp->timer );
-
        /* Allocate ARP packet */
        iobuf = alloc_iob ( MAX_LL_HEADER_LEN + sizeof ( *arphdr ) +
                            ( 2 * ( MAX_LL_ADDR_LEN + MAX_NET_ADDR_LEN ) ) );
-       if ( ! iobuf ) {
-               /* Leave timer running and try again later */
-               return;
-       }
+       if ( ! iobuf )
+               return -ENOMEM;
        iob_reserve ( iobuf, MAX_LL_HEADER_LEN );
 
        /* Build up ARP request */
@@ -342,21 +77,30 @@ static void arp_expired ( struct retry_timer *timer, int fail ) {
        memcpy ( iob_put ( iobuf, ll_protocol->ll_addr_len ),
                 netdev->ll_addr, ll_protocol->ll_addr_len );
        memcpy ( iob_put ( iobuf, net_protocol->net_addr_len ),
-                arp->net_source, net_protocol->net_addr_len );
+                net_source, net_protocol->net_addr_len );
        memset ( iob_put ( iobuf, ll_protocol->ll_addr_len ),
                 0, ll_protocol->ll_addr_len );
        memcpy ( iob_put ( iobuf, net_protocol->net_addr_len ),
-                arp->net_dest, net_protocol->net_addr_len );
+                net_dest, net_protocol->net_addr_len );
 
        /* Transmit ARP request */
        if ( ( rc = net_tx ( iobuf, netdev, &arp_protocol,
                             netdev->ll_broadcast, netdev->ll_addr ) ) != 0 ) {
-               DBGC ( arp, "ARP %p could not transmit request: %s\n",
-                      arp, strerror ( rc ) );
-               return;
+               DBGC ( netdev, "ARP %s %s %s could not transmit request: %s\n",
+                      netdev->name, net_protocol->name,
+                      net_protocol->ntoa ( net_dest ), strerror ( rc ) );
+               return rc;
        }
+
+       return 0;
 }
 
+/** ARP neighbour discovery protocol */
+struct neighbour_discovery arp_discovery = {
+       .name = "ARP",
+       .tx_request = arp_tx_request,
+};
+
 /**
  * Identify ARP protocol
  *
@@ -368,9 +112,8 @@ static struct arp_net_protocol * arp_find_protocol ( uint16_t net_proto ) {
        struct arp_net_protocol *arp_net_protocol;
 
        for_each_table_entry ( arp_net_protocol, ARP_NET_PROTOCOLS ) {
-               if ( arp_net_protocol->net_protocol->net_proto == net_proto ) {
+               if ( arp_net_protocol->net_protocol->net_proto == net_proto )
                        return arp_net_protocol;
-               }
        }
        return NULL;
 }
@@ -392,7 +135,6 @@ static int arp_rx ( struct io_buffer *iobuf, struct net_device *netdev,
        struct arp_net_protocol *arp_net_protocol;
        struct net_protocol *net_protocol;
        struct ll_protocol *ll_protocol;
-       struct arp_entry *arp;
        int rc;
 
        /* Identify network-layer and link-layer protocols */
@@ -412,11 +154,9 @@ static int arp_rx ( struct io_buffer *iobuf, struct net_device *netdev,
                goto done;
        }
 
-       /* See if we have an entry for this sender, and update it if so */
-       arp = arp_find ( netdev, net_protocol, arp_sender_pa ( arphdr ) );
-       if ( arp ) {
-               arp_update ( arp, arp_sender_ha ( arphdr ) );
-       }
+       /* Update neighbour cache entry for this sender, if any */
+       neighbour_update ( netdev, net_protocol, arp_sender_pa ( arphdr ),
+                          arp_sender_ha ( arphdr ) );
 
        /* If it's not a request, there's nothing more to do */
        if ( arphdr->ar_op != htons ( ARPOP_REQUEST ) ) {
@@ -431,10 +171,10 @@ static int arp_rx ( struct io_buffer *iobuf, struct net_device *netdev,
        }
 
        /* Change request to a reply */
-       DBGC ( netdev, "ARP reply %s %s %s => %s %s\n",
-              netdev->name, net_protocol->name,
-              net_protocol->ntoa ( arp_target_pa ( arphdr ) ),
-              ll_protocol->name, ll_protocol->ntoa ( netdev->ll_addr ) );
+       DBGC2 ( netdev, "ARP %s %s %s reply => %s %s\n",
+               netdev->name, net_protocol->name,
+               net_protocol->ntoa ( arp_target_pa ( arphdr ) ),
+               ll_protocol->name, ll_protocol->ntoa ( netdev->ll_addr ) );
        arphdr->ar_op = htons ( ARPOP_REPLY );
        memswap ( arp_sender_ha ( arphdr ), arp_target_ha ( arphdr ),
                 arphdr->ar_hln + arphdr->ar_pln );
@@ -444,8 +184,10 @@ static int arp_rx ( struct io_buffer *iobuf, struct net_device *netdev,
        if ( ( rc = net_tx ( iob_disown ( iobuf ), netdev, &arp_protocol,
                             arp_target_ha ( arphdr ),
                             netdev->ll_addr ) ) != 0 ) {
-               DBGC ( netdev, "ARP could not transmit reply via %s: %s\n",
-                      netdev->name, strerror ( rc ) );
+               DBGC ( netdev, "ARP %s %s %s could not transmit reply: %s\n",
+                      netdev->name, net_protocol->name,
+                      net_protocol->ntoa ( arp_target_pa ( arphdr ) ),
+                      strerror ( rc ) );
                goto done;
        }
 
@@ -469,72 +211,10 @@ static const char * arp_ntoa ( const void *net_addr __unused ) {
        return "<ARP>";
 }
 
-/** ARP protocol */
+/** ARP network protocol */
 struct net_protocol arp_protocol __net_protocol = {
        .name = "ARP",
        .net_proto = htons ( ETH_P_ARP ),
        .rx = arp_rx,
        .ntoa = arp_ntoa,
 };
-
-/**
- * Update ARP cache on network device creation
- *
- * @v netdev           Network device
- */
-static int arp_probe ( struct net_device *netdev __unused ) {
-       /* Nothing to do */
-       return 0;
-}
-
-/**
- * Update ARP cache on network device state change or removal
- *
- * @v netdev           Network device
- */
-static void arp_flush ( struct net_device *netdev ) {
-       struct arp_entry *arp;
-       struct arp_entry *tmp;
-
-       /* Remove all ARP cache entries when a network device is closed */
-       if ( ! netdev_is_open ( netdev ) ) {
-               list_for_each_entry_safe ( arp, tmp, &arp_entries, list )
-                       arp_destroy ( arp, -ENODEV );
-       }
-}
-
-/** ARP driver (for net device notifications) */
-struct net_driver arp_net_driver __net_driver = {
-       .name = "ARP",
-       .probe = arp_probe,
-       .notify = arp_flush,
-       .remove = arp_flush,
-};
-
-/**
- * Discard some cached ARP entries
- *
- * @ret discarded      Number of cached items discarded
- */
-static unsigned int arp_discard ( void ) {
-       struct arp_entry *arp;
-
-       /* Drop oldest cache entry, if any */
-       arp = list_last_entry ( &arp_entries, struct arp_entry, list );
-       if ( arp ) {
-               arp_destroy ( arp, -ENOBUFS );
-               return 1;
-       } else {
-               return 0;
-       }
-}
-
-/** ARP cache discarder
- *
- * ARP cache entries are deemed to have a high replacement cost, since
- * flushing an active ARP cache entry midway through a TCP transfer
- * will cause substantial disruption.
- */
-struct cache_discarder arp_discarder __cache_discarder ( CACHE_EXPENSIVE ) = {
-       .discard = arp_discard,
-};
diff --git a/src/net/neighbour.c b/src/net/neighbour.c
new file mode 100644 (file)
index 0000000..210b713
--- /dev/null
@@ -0,0 +1,479 @@
+/*
+ * Copyright (C) 2013 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ipxe/refcnt.h>
+#include <ipxe/list.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/retry.h>
+#include <ipxe/timer.h>
+#include <ipxe/malloc.h>
+#include <ipxe/neighbour.h>
+
+/** @file
+ *
+ * Neighbour discovery
+ *
+ * This file implements the abstract functions of neighbour discovery,
+ * independent of the underlying network protocol (e.g. ARP or NDP).
+ *
+ */
+
+/** A neighbour cache entry */
+struct neighbour {
+       /** Reference count */
+       struct refcnt refcnt;
+       /** List of neighbour cache entries */
+       struct list_head list;
+
+       /** Network device */
+       struct net_device *netdev;
+       /** Network-layer protocol */
+       struct net_protocol *net_protocol;
+       /** Network-layer destination address */
+       uint8_t net_dest[MAX_NET_ADDR_LEN];
+       /** Link-layer destination address */
+       uint8_t ll_dest[MAX_LL_ADDR_LEN];
+
+       /** Neighbour discovery protocol (if any) */
+       struct neighbour_discovery *discovery;
+       /** Network-layer source address (if any) */
+       uint8_t net_source[MAX_NET_ADDR_LEN];
+       /** Retransmission timer */
+       struct retry_timer timer;
+
+       /** Pending I/O buffers */
+       struct list_head tx_queue;
+};
+
+/** Neighbour discovery minimum timeout */
+#define NEIGHBOUR_MIN_TIMEOUT ( TICKS_PER_SEC / 8 )
+
+/** Neighbour discovery maximum timeout */
+#define NEIGHBOUR_MAX_TIMEOUT ( TICKS_PER_SEC * 3 )
+
+/** The neighbour cache */
+static LIST_HEAD ( neighbours );
+
+static void neighbour_expired ( struct retry_timer *timer, int over );
+
+/**
+ * Free neighbour cache entry
+ *
+ * @v refcnt           Reference count
+ */
+static void neighbour_free ( struct refcnt *refcnt ) {
+       struct neighbour *neighbour =
+               container_of ( refcnt, struct neighbour, refcnt );
+
+       /* Sanity check */
+       assert ( list_empty ( &neighbour->tx_queue ) );
+
+       /* Drop reference to network device */
+       netdev_put ( neighbour->netdev );
+
+       /* Free neighbour */
+       free ( neighbour );
+}
+
+/**
+ * Test if neighbour cache entry has a valid link-layer address
+ *
+ * @v neighbour                Neighbour cache entry
+ * @ret has_ll_dest    Neighbour cache entry has a valid link-layer address
+ */
+static inline __attribute__ (( always_inline )) int
+neighbour_has_ll_dest ( struct neighbour *neighbour ) {
+       return ( ! timer_running ( &neighbour->timer ) );
+}
+
+/**
+ * Create neighbour cache entry
+ *
+ * @v netdev           Network device
+ * @v net_protocol     Network-layer protocol
+ * @v net_dest         Destination network-layer address
+ * @ret neighbour      Neighbour cache entry, or NULL if allocation failed
+ */
+static struct neighbour * neighbour_create ( struct net_device *netdev,
+                                            struct net_protocol *net_protocol,
+                                            const void *net_dest ) {
+       struct neighbour *neighbour;
+
+       /* Allocate and initialise entry */
+       neighbour = zalloc ( sizeof ( *neighbour ) );
+       if ( ! neighbour )
+               return NULL;
+       ref_init ( &neighbour->refcnt, neighbour_free );
+       neighbour->netdev = netdev_get ( netdev );
+       neighbour->net_protocol = net_protocol;
+       memcpy ( neighbour->net_dest, net_dest,
+                net_protocol->net_addr_len );
+       timer_init ( &neighbour->timer, neighbour_expired, &neighbour->refcnt );
+       neighbour->timer.min_timeout = NEIGHBOUR_MIN_TIMEOUT;
+       neighbour->timer.max_timeout = NEIGHBOUR_MAX_TIMEOUT;
+       INIT_LIST_HEAD ( &neighbour->tx_queue );
+
+       /* Transfer ownership to cache */
+       list_add ( &neighbour->list, &neighbours );
+
+       DBGC ( neighbour, "NEIGHBOUR %s %s %s created\n", netdev->name,
+              net_protocol->name, net_protocol->ntoa ( net_dest ) );
+       return neighbour;
+}
+
+/**
+ * Find neighbour cache entry
+ *
+ * @v netdev           Network device
+ * @v net_protocol     Network-layer protocol
+ * @v net_dest         Destination network-layer address
+ * @ret neighbour      Neighbour cache entry, or NULL if not found
+ */
+static struct neighbour * neighbour_find ( struct net_device *netdev,
+                                          struct net_protocol *net_protocol,
+                                          const void *net_dest ) {
+       struct neighbour *neighbour;
+
+       list_for_each_entry ( neighbour, &neighbours, list ) {
+               if ( ( neighbour->netdev == netdev ) &&
+                    ( neighbour->net_protocol == net_protocol ) &&
+                    ( memcmp ( neighbour->net_dest, net_dest,
+                               net_protocol->net_addr_len ) == 0 ) ) {
+
+                       /* Move to start of cache */
+                       list_del ( &neighbour->list );
+                       list_add ( &neighbour->list, &neighbours );
+
+                       return neighbour;
+               }
+       }
+       return NULL;
+}
+
+/**
+ * Start neighbour discovery
+ *
+ * @v neighbour                Neighbour cache entry
+ * @v discovery                Neighbour discovery protocol
+ * @v net_source       Source network-layer address
+ */
+static void neighbour_discover ( struct neighbour *neighbour,
+                                struct neighbour_discovery *discovery,
+                                const void *net_source ) {
+       struct net_device *netdev = neighbour->netdev;
+       struct net_protocol *net_protocol = neighbour->net_protocol;
+
+       /* Record discovery protocol and source network-layer address */
+       neighbour->discovery = discovery;
+       memcpy ( neighbour->net_source, net_source,
+                net_protocol->net_addr_len );
+
+       /* Start timer to trigger neighbour discovery */
+       start_timer_nodelay ( &neighbour->timer );
+
+       DBGC ( neighbour, "NEIGHBOUR %s %s %s discovering via %s\n",
+              netdev->name, net_protocol->name,
+              net_protocol->ntoa ( neighbour->net_dest ),
+              neighbour->discovery->name );
+}
+
+/**
+ * Complete neighbour discovery
+ *
+ * @v neighbour                Neighbour cache entry
+ * @v ll_dest          Destination link-layer address
+ */
+static void neighbour_discovered ( struct neighbour *neighbour,
+                                  const void *ll_dest ) {
+       struct net_device *netdev = neighbour->netdev;
+       struct ll_protocol *ll_protocol = netdev->ll_protocol;
+       struct net_protocol *net_protocol = neighbour->net_protocol;
+       struct io_buffer *iobuf;
+       int rc;
+
+       /* Fill in link-layer address */
+       memcpy ( neighbour->ll_dest, ll_dest, ll_protocol->ll_addr_len );
+       DBGC ( neighbour, "NEIGHBOUR %s %s %s is %s %s\n", netdev->name,
+              net_protocol->name, net_protocol->ntoa ( neighbour->net_dest ),
+              ll_protocol->name, ll_protocol->ntoa ( neighbour->ll_dest ) );
+
+       /* Stop retransmission timer */
+       stop_timer ( &neighbour->timer );
+
+       /* Transmit any packets in queue.  Take out a temporary
+        * reference on the entry to prevent it from going out of
+        * scope during the call to net_tx().
+        */
+       ref_get ( &neighbour->refcnt );
+       while ( ( iobuf = list_first_entry ( &neighbour->tx_queue,
+                                            struct io_buffer, list )) != NULL){
+               DBGC2 ( neighbour, "NEIGHBOUR %s %s %s transmitting deferred "
+                       "packet\n", netdev->name, net_protocol->name,
+                       net_protocol->ntoa ( neighbour->net_dest ) );
+               list_del ( &iobuf->list );
+               if ( ( rc = net_tx ( iobuf, netdev, net_protocol, ll_dest,
+                                    netdev->ll_addr ) ) != 0 ) {
+                       DBGC ( neighbour, "NEIGHBOUR %s %s %s could not "
+                              "transmit deferred packet: %s\n",
+                              netdev->name, net_protocol->name,
+                              net_protocol->ntoa ( neighbour->net_dest ),
+                              strerror ( rc ) );
+                       /* Ignore error and continue */
+               }
+       }
+       ref_put ( &neighbour->refcnt );
+}
+
+/**
+ * Destroy neighbour cache entry
+ *
+ * @v neighbour                Neighbour cache entry
+ * @v rc               Reason for destruction
+ */
+static void neighbour_destroy ( struct neighbour *neighbour, int rc ) {
+       struct net_device *netdev = neighbour->netdev;
+       struct net_protocol *net_protocol = neighbour->net_protocol;
+       struct io_buffer *iobuf;
+
+       /* Take ownership from cache */
+       list_del ( &neighbour->list );
+
+       /* Stop timer */
+       stop_timer ( &neighbour->timer );
+
+       /* Discard any outstanding I/O buffers */
+       while ( ( iobuf = list_first_entry ( &neighbour->tx_queue,
+                                            struct io_buffer, list )) != NULL){
+               DBGC2 ( neighbour, "NEIGHBOUR %s %s %s discarding deferred "
+                       "packet: %s\n", netdev->name, net_protocol->name,
+                       net_protocol->ntoa ( neighbour->net_dest ),
+                       strerror ( rc ) );
+               list_del ( &iobuf->list );
+               netdev_tx_err ( neighbour->netdev, iobuf, rc );
+       }
+
+       DBGC ( neighbour, "NEIGHBOUR %s %s %s destroyed: %s\n", netdev->name,
+              net_protocol->name, net_protocol->ntoa ( neighbour->net_dest ),
+              strerror ( rc ) );
+
+       /* Drop remaining reference */
+       ref_put ( &neighbour->refcnt );
+}
+
+/**
+ * Handle neighbour timer expiry
+ *
+ * @v timer            Retry timer
+ * @v fail             Failure indicator
+ */
+static void neighbour_expired ( struct retry_timer *timer, int fail ) {
+       struct neighbour *neighbour =
+               container_of ( timer, struct neighbour, timer );
+       struct net_device *netdev = neighbour->netdev;
+       struct net_protocol *net_protocol = neighbour->net_protocol;
+       struct neighbour_discovery *discovery =
+               neighbour->discovery;
+       const void *net_dest = neighbour->net_dest;
+       const void *net_source = neighbour->net_source;
+       int rc;
+
+       /* If we have failed, destroy the cache entry */
+       if ( fail ) {
+               neighbour_destroy ( neighbour, -ETIMEDOUT );
+               return;
+       }
+
+       /* Restart the timer */
+       start_timer ( &neighbour->timer );
+
+       /* Transmit neighbour request */
+       if ( ( rc = discovery->tx_request ( netdev, net_protocol, net_dest,
+                                           net_source ) ) != 0 ) {
+               DBGC ( neighbour, "NEIGHBOUR %s %s %s could not transmit %s "
+                      "request: %s\n", netdev->name, net_protocol->name,
+                      net_protocol->ntoa ( neighbour->net_dest ),
+                      neighbour->discovery->name, strerror ( rc ) );
+               /* Retransmit when timer expires */
+               return;
+       }
+}
+
+/**
+ * Transmit packet, determining link-layer address via neighbour discovery
+ *
+ * @v iobuf            I/O buffer
+ * @v netdev           Network device
+ * @v discovery                Neighbour discovery protocol
+ * @v net_protocol     Network-layer protocol
+ * @v net_dest         Destination network-layer address
+ * @v net_source       Source network-layer address
+ * @v ll_source                Source link-layer address
+ * @ret rc             Return status code
+ */
+int neighbour_tx ( struct io_buffer *iobuf, struct net_device *netdev,
+                  struct net_protocol *net_protocol, const void *net_dest,
+                  struct neighbour_discovery *discovery,
+                  const void *net_source, const void *ll_source ) {
+       struct neighbour *neighbour;
+
+       /* Find or create neighbour cache entry */
+       neighbour = neighbour_find ( netdev, net_protocol, net_dest );
+       if ( ! neighbour ) {
+               neighbour = neighbour_create ( netdev, net_protocol, net_dest );
+               if ( ! neighbour )
+                       return -ENOMEM;
+               neighbour_discover ( neighbour, discovery, net_source );
+       }
+
+       /* If a link-layer address is available then transmit
+        * immediately, otherwise queue for later transmission.
+        */
+       if ( neighbour_has_ll_dest ( neighbour ) ) {
+               return net_tx ( iobuf, netdev, net_protocol, neighbour->ll_dest,
+                               ll_source );
+       } else {
+               DBGC2 ( neighbour, "NEIGHBOUR %s %s %s deferring packet\n",
+                       netdev->name, net_protocol->name,
+                       net_protocol->ntoa ( net_dest ) );
+               list_add_tail ( &iobuf->list, &neighbour->tx_queue );
+               return -EAGAIN;
+       }
+}
+
+/**
+ * Update existing neighbour cache entry
+ *
+ * @v netdev           Network device
+ * @v net_protocol     Network-layer protocol
+ * @v net_dest         Destination network-layer address
+ * @v ll_dest          Destination link-layer address
+ * @ret rc             Return status code
+ */
+int neighbour_update ( struct net_device *netdev,
+                      struct net_protocol *net_protocol,
+                      const void *net_dest, const void *ll_dest ) {
+       struct neighbour *neighbour;
+
+       /* Find neighbour cache entry */
+       neighbour = neighbour_find ( netdev, net_protocol, net_dest );
+       if ( ! neighbour )
+               return -ENOENT;
+
+       /* Set destination address */
+       neighbour_discovered ( neighbour, ll_dest );
+
+       return 0;
+}
+
+/**
+ * Define neighbour cache entry
+ *
+ * @v netdev           Network device
+ * @v net_protocol     Network-layer protocol
+ * @v net_dest         Destination network-layer address
+ * @v ll_dest          Destination link-layer address, if known
+ * @ret rc             Return status code
+ */
+int neighbour_define ( struct net_device *netdev,
+                      struct net_protocol *net_protocol,
+                      const void *net_dest, const void *ll_dest ) {
+       struct neighbour *neighbour;
+
+       /* Find or create neighbour cache entry */
+       neighbour = neighbour_find ( netdev, net_protocol, net_dest );
+       if ( ! neighbour ) {
+               neighbour = neighbour_create ( netdev, net_protocol, net_dest );
+               if ( ! neighbour )
+                       return -ENOMEM;
+       }
+
+       /* Set destination address */
+       neighbour_discovered ( neighbour, ll_dest );
+
+       return 0;
+}
+
+/**
+ * Update neighbour cache on network device creation
+ *
+ * @v netdev           Network device
+ */
+static int neighbour_probe ( struct net_device *netdev __unused ) {
+       /* Nothing to do */
+       return 0;
+}
+
+/**
+ * Update neighbour cache on network device state change or removal
+ *
+ * @v netdev           Network device
+ */
+static void neighbour_flush ( struct net_device *netdev ) {
+       struct neighbour *neighbour;
+       struct neighbour *tmp;
+
+       /* Remove all neighbour cache entries when a network device is closed */
+       if ( ! netdev_is_open ( netdev ) ) {
+               list_for_each_entry_safe ( neighbour, tmp, &neighbours, list )
+                       neighbour_destroy ( neighbour, -ENODEV );
+       }
+}
+
+/** Neighbour driver (for net device notifications) */
+struct net_driver neighbour_net_driver __net_driver = {
+       .name = "Neighbour",
+       .probe = neighbour_probe,
+       .notify = neighbour_flush,
+       .remove = neighbour_flush,
+};
+
+/**
+ * Discard some cached neighbour entries
+ *
+ * @ret discarded      Number of cached items discarded
+ */
+static unsigned int neighbour_discard ( void ) {
+       struct neighbour *neighbour;
+
+       /* Drop oldest cache entry, if any */
+       neighbour = list_last_entry ( &neighbours, struct neighbour, list );
+       if ( neighbour ) {
+               neighbour_destroy ( neighbour, -ENOBUFS );
+               return 1;
+       } else {
+               return 0;
+       }
+}
+
+/**
+ * Neighbour cache discarder
+ *
+ * Neighbour cache entries are deemed to have a high replacement cost,
+ * since flushing an active neighbour cache entry midway through a TCP
+ * transfer will cause substantial disruption.
+ */
+struct cache_discarder neighbour_discarder __cache_discarder (CACHE_EXPENSIVE)={
+       .discard = neighbour_discard,
+};