diff options
Diffstat (limited to 'src/net/ndp.c')
-rw-r--r-- | src/net/ndp.c | 448 |
1 files changed, 319 insertions, 129 deletions
diff --git a/src/net/ndp.c b/src/net/ndp.c index 4d37133..48b16d0 100644 --- a/src/net/ndp.c +++ b/src/net/ndp.c @@ -1,180 +1,370 @@ -#include <stdint.h> +/* + * 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 <string.h> -#include <byteswap.h> #include <errno.h> -#include <ipxe/if_ether.h> +#include <byteswap.h> +#include <ipxe/in.h> #include <ipxe/iobuf.h> +#include <ipxe/tcpip.h> +#include <ipxe/ipv6.h> +#include <ipxe/icmpv6.h> +#include <ipxe/neighbour.h> #include <ipxe/ndp.h> -#include <ipxe/icmp6.h> -#include <ipxe/ip6.h> -#include <ipxe/netdevice.h> /** @file * - * Neighbour Discovery Protocol + * IPv6 neighbour discovery protocol * - * This file implements address resolution as specified by the neighbour - * discovery protocol in RFC2461. This protocol is part of the IPv6 protocol - * family. */ -/* A neighbour entry */ -struct ndp_entry { - /** Target IP6 address */ - struct in6_addr in6; - /** Link layer protocol */ - struct ll_protocol *ll_protocol; - /** Link-layer address */ - uint8_t ll_addr[MAX_LL_ADDR_LEN]; - /** State of the neighbour entry */ - int state; -}; - -/** Number of entries in the neighbour cache table */ -#define NUM_NDP_ENTRIES 4 - -/** The neighbour cache table */ -static struct ndp_entry ndp_table[NUM_NDP_ENTRIES]; -#define ndp_table_end &ndp_table[NUM_NDP_ENTRIES] - -static unsigned int next_new_ndp_entry = 0; - /** - * Find entry in the neighbour cache + * Transmit NDP neighbour solicitation/advertisement packet * - * @v in6 IP6 address + * @v netdev Network device + * @v sin6_src Source socket address + * @v sin6_dest Destination socket address + * @v target Neighbour target address + * @v icmp_type ICMPv6 type + * @v flags NDP flags + * @v option_type NDP option type + * @ret rc Return status code */ -static struct ndp_entry * -ndp_find_entry ( struct in6_addr *in6 ) { - struct ndp_entry *ndp; - - for ( ndp = ndp_table ; ndp < ndp_table_end ; ndp++ ) { - if ( IP6_EQUAL ( ( *in6 ), ndp->in6 ) && - ( ndp->state != NDP_STATE_INVALID ) ) { - return ndp; - } +static int ndp_tx_neighbour ( struct net_device *netdev, + struct sockaddr_in6 *sin6_src, + struct sockaddr_in6 *sin6_dest, + const struct in6_addr *target, + unsigned int icmp_type, + unsigned int flags, + unsigned int option_type ) { + struct sockaddr_tcpip *st_src = + ( ( struct sockaddr_tcpip * ) sin6_src ); + struct sockaddr_tcpip *st_dest = + ( ( struct sockaddr_tcpip * ) sin6_dest ); + struct ll_protocol *ll_protocol = netdev->ll_protocol; + struct io_buffer *iobuf; + struct ndp_header *ndp; + size_t option_len; + size_t len; + int rc; + + /* Allocate and populate buffer */ + option_len = ( ( sizeof ( ndp->option[0] ) + ll_protocol->ll_addr_len + + NDP_OPTION_BLKSZ - 1 ) & + ~( NDP_OPTION_BLKSZ - 1 ) ); + len = ( sizeof ( *ndp ) + option_len ); + iobuf = alloc_iob ( MAX_LL_NET_HEADER_LEN + len ); + if ( ! iobuf ) + return -ENOMEM; + iob_reserve ( iobuf, MAX_LL_NET_HEADER_LEN ); + ndp = iob_put ( iobuf, len ); + memset ( ndp, 0, len ); + ndp->icmp.type = icmp_type; + ndp->flags = flags; + memcpy ( &ndp->target, target, sizeof ( ndp->target ) ); + ndp->option[0].type = option_type; + ndp->option[0].blocks = ( option_len / NDP_OPTION_BLKSZ ); + memcpy ( ndp->option[0].value, netdev->ll_addr, + ll_protocol->ll_addr_len ); + ndp->icmp.chksum = tcpip_chksum ( ndp, len ); + + /* Transmit packet */ + if ( ( rc = tcpip_tx ( iobuf, &icmpv6_protocol, st_src, st_dest, + netdev, &ndp->icmp.chksum ) ) != 0 ) { + DBGC ( netdev, "NDP could not transmit packet: %s\n", + strerror ( rc ) ); + return rc; } - return NULL; + + return 0; } /** - * Add NDP entry - * - * @v netdev Network device - * @v in6 IP6 address - * @v ll_addr Link-layer address - * @v state State of the entry - one of the NDP_STATE_XXX values + * Transmit NDP 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 */ -static void -add_ndp_entry ( struct net_device *netdev, struct in6_addr *in6, - void *ll_addr, int state ) { - struct ndp_entry *ndp; - ndp = &ndp_table[next_new_ndp_entry++ % NUM_NDP_ENTRIES]; - - /* Fill up entry */ - ndp->ll_protocol = netdev->ll_protocol; - memcpy ( &ndp->in6, &( *in6 ), sizeof ( *in6 ) ); - if ( ll_addr ) { - memcpy ( ndp->ll_addr, ll_addr, netdev->ll_protocol->ll_addr_len ); - } else { - memset ( ndp->ll_addr, 0, netdev->ll_protocol->ll_addr_len ); - } - ndp->state = state; - DBG ( "New neighbour cache entry: IP6 %s => %s %s\n", - inet6_ntoa ( ndp->in6 ), netdev->ll_protocol->name, - netdev->ll_protocol->ntoa ( ndp->ll_addr ) ); +static int ndp_tx_request ( struct net_device *netdev, + struct net_protocol *net_protocol __unused, + const void *net_dest, const void *net_source ) { + struct sockaddr_in6 sin6_src; + struct sockaddr_in6 sin6_dest; + + /* Construct source address */ + memset ( &sin6_src, 0, sizeof ( sin6_src ) ); + sin6_src.sin6_family = AF_INET6; + memcpy ( &sin6_src.sin6_addr, net_source, + sizeof ( sin6_src.sin6_addr ) ); + sin6_src.sin6_scope_id = htons ( netdev->index ); + + /* Construct multicast destination address */ + memset ( &sin6_dest, 0, sizeof ( sin6_dest ) ); + sin6_dest.sin6_family = AF_INET6; + sin6_dest.sin6_scope_id = htons ( netdev->index ); + ipv6_solicited_node ( &sin6_dest.sin6_addr, net_dest ); + + /* Transmit neighbour discovery packet */ + return ndp_tx_neighbour ( netdev, &sin6_src, &sin6_dest, net_dest, + ICMPV6_NDP_NEIGHBOUR_SOLICITATION, 0, + NDP_OPT_LL_SOURCE ); } +/** NDP neighbour discovery protocol */ +struct neighbour_discovery ndp_discovery = { + .name = "NDP", + .tx_request = ndp_tx_request, +}; + /** - * Resolve the link-layer address + * Process NDP neighbour solicitation source link-layer address option * * @v netdev Network device - * @v dest Destination address - * @v src Source address - * @ret dest_ll_addr Destination link-layer address or NULL - * @ret rc Status - * - * This function looks up the neighbour cache for an entry corresponding to the - * destination address. If it finds a valid entry, it fills up dest_ll_addr and - * returns 0. Otherwise it sends a neighbour solicitation to the solicited - * multicast address. + * @v sin6_src Source socket address + * @v ndp NDP packet + * @v ll_addr Source link-layer address + * @v ll_addr_len Source link-layer address length + * @ret rc Return status code */ -int ndp_resolve ( struct net_device *netdev, struct in6_addr *dest, - struct in6_addr *src, void *dest_ll_addr ) { +static int ndp_rx_neighbour_solicitation ( struct net_device *netdev, + struct sockaddr_in6 *sin6_src, + struct ndp_header *ndp __unused, + const void *ll_addr, + size_t ll_addr_len ) { struct ll_protocol *ll_protocol = netdev->ll_protocol; - struct ndp_entry *ndp; int rc; - ndp = ndp_find_entry ( dest ); - /* Check if the entry is valid */ - if ( ndp && ndp->state == NDP_STATE_REACHABLE ) { - DBG ( "Neighbour cache hit: IP6 %s => %s %s\n", - inet6_ntoa ( *dest ), ll_protocol->name, - ll_protocol->ntoa ( ndp->ll_addr ) ); - memcpy ( dest_ll_addr, ndp->ll_addr, ll_protocol->ll_addr_len ); + /* Silently ignore neighbour solicitations for addresses we do + * not own. + */ + if ( ! ipv6_has_addr ( netdev, &ndp->target ) ) return 0; - } - /* Check if the entry was already created */ - if ( ndp ) { - DBG ( "Awaiting neighbour advertisement\n" ); - /* For test */ -// ndp->state = NDP_STATE_REACHABLE; -// memcpy ( ndp->ll_addr, netdev->ll_addr, 6 ); -// assert ( ndp->ll_protocol->ll_addr_len == 6 ); -// icmp6_test_nadvert ( netdev, dest, ndp->ll_addr ); -// assert ( ndp->state == NDP_STATE_REACHABLE ); - /* Take it out till here */ - return -ENOENT; + /* Sanity check */ + if ( ll_addr_len < ll_protocol->ll_addr_len ) { + DBGC ( netdev, "NDP neighbour solicitation link-layer address " + "too short at %zd bytes (min %d bytes)\n", + ll_addr_len, ll_protocol->ll_addr_len ); + return -EINVAL; } - DBG ( "Neighbour cache miss: IP6 %s\n", inet6_ntoa ( *dest ) ); - /* Add entry in the neighbour cache */ - add_ndp_entry ( netdev, dest, NULL, NDP_STATE_INCOMPLETE ); + /* Create or update neighbour cache entry */ + if ( ( rc = neighbour_define ( netdev, &ipv6_protocol, + &sin6_src->sin6_addr, + ll_addr ) ) != 0 ) { + DBGC ( netdev, "NDP could not define %s => %s: %s\n", + inet6_ntoa ( &sin6_src->sin6_addr ), + ll_protocol->ntoa ( ll_addr ), strerror ( rc ) ); + return rc; + } - /* Send neighbour solicitation */ - if ( ( rc = icmp6_send_solicit ( netdev, src, dest ) ) != 0 ) { + /* Send neighbour advertisement */ + if ( ( rc = ndp_tx_neighbour ( netdev, NULL, sin6_src, &ndp->target, + ICMPV6_NDP_NEIGHBOUR_ADVERTISEMENT, + ( NDP_SOLICITED | NDP_OVERRIDE ), + NDP_OPT_LL_TARGET ) ) != 0 ) { return rc; } - return -ENOENT; + + return 0; } /** - * Process neighbour advertisement + * Process NDP neighbour advertisement target link-layer address option * - * @v iobuf I/O buffer - * @v st_src Source address - * @v st_dest Destination address + * @v netdev Network device + * @v sin6_src Source socket address + * @v ndp NDP packet + * @v ll_addr Target link-layer address + * @v ll_addr_len Target link-layer address length + * @ret rc Return status code */ -int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused, - struct sockaddr_tcpip *st_dest __unused ) { - struct neighbour_advert *nadvert = iobuf->data; - struct ndp_entry *ndp; +static int +ndp_rx_neighbour_advertisement ( struct net_device *netdev, + struct sockaddr_in6 *sin6_src __unused, + struct ndp_header *ndp, const void *ll_addr, + size_t ll_addr_len ) { + struct ll_protocol *ll_protocol = netdev->ll_protocol; + int rc; /* Sanity check */ - if ( iob_len ( iobuf ) < sizeof ( *nadvert ) ) { - DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) ); + if ( ll_addr_len < ll_protocol->ll_addr_len ) { + DBGC ( netdev, "NDP neighbour advertisement link-layer address " + "too short at %zd bytes (min %d bytes)\n", + ll_addr_len, ll_protocol->ll_addr_len ); return -EINVAL; } - assert ( nadvert->code == 0 ); - assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED ); - assert ( nadvert->opt_type == 2 ); + /* Update neighbour cache entry, if any */ + if ( ( rc = neighbour_update ( netdev, &ipv6_protocol, &ndp->target, + ll_addr ) ) != 0 ) { + DBGC ( netdev, "NDP could not update %s => %s: %s\n", + inet6_ntoa ( &ndp->target ), + ll_protocol->ntoa ( ll_addr ), strerror ( rc ) ); + return rc; + } - /* Update the neighbour cache, if entry is present */ - ndp = ndp_find_entry ( &nadvert->target ); - if ( ndp ) { + return 0; +} - assert ( nadvert->opt_len == - ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) ); +/** An NDP option handler */ +struct ndp_option_handler { + /** ICMPv6 type */ + uint8_t icmp_type; + /** Option type */ + uint8_t option_type; + /** + * Handle received option + * + * @v netdev Network device + * @v sin6_src Source socket address + * @v ndp NDP packet + * @v value Option value + * @v len Option length + * @ret rc Return status code + */ + int ( * rx ) ( struct net_device *netdev, struct sockaddr_in6 *sin6_src, + struct ndp_header *ndp, const void *value, size_t len ); +}; - if ( IP6_EQUAL ( ndp->in6, nadvert->target ) ) { - memcpy ( ndp->ll_addr, nadvert->opt_ll_addr, - ndp->ll_protocol->ll_addr_len ); - ndp->state = NDP_STATE_REACHABLE; - return 0; +/** NDP option handlers */ +static struct ndp_option_handler ndp_option_handlers[] = { + { + .icmp_type = ICMPV6_NDP_NEIGHBOUR_SOLICITATION, + .option_type = NDP_OPT_LL_SOURCE, + .rx = ndp_rx_neighbour_solicitation, + }, + { + .icmp_type = ICMPV6_NDP_NEIGHBOUR_ADVERTISEMENT, + .option_type = NDP_OPT_LL_TARGET, + .rx = ndp_rx_neighbour_advertisement, + }, +}; + +/** + * Process received NDP option + * + * @v netdev Network device + * @v sin6_src Source socket address + * @v ndp NDP packet + * @v type Option type + * @v value Option value + * @v len Option length + * @ret rc Return status code + */ +static int ndp_rx_option ( struct net_device *netdev, + struct sockaddr_in6 *sin6_src, + struct ndp_header *ndp, unsigned int type, + const void *value, size_t len ) { + struct ndp_option_handler *handler; + unsigned int i; + + /* Locate a suitable option handler, if any */ + for ( i = 0 ; i < ( sizeof ( ndp_option_handlers ) / + sizeof ( ndp_option_handlers[0] ) ) ; i++ ) { + handler = &ndp_option_handlers[i]; + if ( ( handler->icmp_type == ndp->icmp.type ) && + ( handler->option_type == type ) ) { + return handler->rx ( netdev, sin6_src, ndp, + value, len ); } } - DBG ( "Unsolicited advertisement (dropping packet)\n" ); + + /* Silently ignore unknown options as per RFC 4861 */ return 0; } + +/** + * Process received NDP packet + * + * @v iobuf I/O buffer + * @v netdev Network device + * @v sin6_src Source socket address + * @v sin6_dest Destination socket address + * @ret rc Return status code + */ +static int ndp_rx ( struct io_buffer *iobuf, + struct net_device *netdev, + struct sockaddr_in6 *sin6_src, + struct sockaddr_in6 *sin6_dest __unused ) { + struct ndp_header *ndp = iobuf->data; + struct ndp_option *option; + size_t remaining; + size_t option_len; + size_t option_value_len; + int rc; + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *ndp ) ) { + DBGC ( netdev, "NDP packet too short at %zd bytes (min %zd " + "bytes)\n", iob_len ( iobuf ), sizeof ( *ndp ) ); + rc = -EINVAL; + goto done; + } + + /* Search for option */ + option = ndp->option; + remaining = ( iob_len ( iobuf ) - offsetof ( typeof ( *ndp ), option )); + while ( remaining ) { + + /* Sanity check */ + if ( ( remaining < sizeof ( *option ) ) || + ( option->blocks == 0 ) || + ( remaining < ( option->blocks * NDP_OPTION_BLKSZ ) ) ) { + DBGC ( netdev, "NDP bad option length:\n" ); + DBGC_HDA ( netdev, 0, option, remaining ); + rc = -EINVAL; + goto done; + } + option_len = ( option->blocks * NDP_OPTION_BLKSZ ); + option_value_len = ( option_len - sizeof ( *option ) ); + + /* Handle option */ + if ( ( rc = ndp_rx_option ( netdev, sin6_src, ndp, + option->type, option->value, + option_value_len ) ) != 0 ) { + goto done; + } + + /* Move to next option */ + option = ( ( ( void * ) option ) + option_len ); + remaining -= option_len; + } + + done: + free_iob ( iobuf ); + return rc; +} + +/** NDP ICMPv6 handlers */ +struct icmpv6_handler ndp_handlers[] __icmpv6_handler = { + { + .type = ICMPV6_NDP_NEIGHBOUR_SOLICITATION, + .rx = ndp_rx, + }, + { + .type = ICMPV6_NDP_NEIGHBOUR_ADVERTISEMENT, + .rx = ndp_rx, + }, +}; |