From: Matthew Iselin <matt...@theiselins.net> Also added handling of ICMPv6 echoes, mostly for debugging.
Signed-off-by: Matthew Iselin <matt...@theiselins.net> --- src/include/gpxe/icmp6.h | 32 ++++----- src/include/gpxe/ndp.h | 68 +++++++++++++++++++- src/net/icmpv6.c | 159 ++++++++++++++++++++++++++++++++++++++++++--- src/net/ndp.c | 41 ++++++++++-- 4 files changed, 265 insertions(+), 35 deletions(-) diff --git a/src/include/gpxe/icmp6.h b/src/include/gpxe/icmp6.h index e6f6ccd..4a1f136 100644 --- a/src/include/gpxe/icmp6.h +++ b/src/include/gpxe/icmp6.h @@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include <gpxe/ip6.h> #include <gpxe/ndp.h> +#include <gpxe/tcpip.h> #include <gpxe/tables.h> @@ -38,8 +39,12 @@ struct icmp6_net_protocol { /** Declare an ICMPv6 protocol */ #define __icmp6_net_protocol __table_entry ( ICMP6_NET_PROTOCOLS, 01 ) -#define ICMP6_NSOLICIT 135 -#define ICMP6_NADVERT 136 +#define ICMP6_ECHO_REQUEST 128 +#define ICMP6_ECHO_RESPONSE 129 +#define ICMP6_ROUTER_SOLICIT 133 +#define ICMP6_ROUTER_ADVERT 134 +#define ICMP6_NSOLICIT 135 +#define ICMP6_NADVERT 136 extern struct tcpip_protocol icmp6_protocol; @@ -50,30 +55,21 @@ struct icmp6_header { /* Message body */ }; -struct neighbour_solicit { +struct router_solicit { uint8_t type; uint8_t code; uint16_t csum; uint32_t reserved; - struct in6_addr target; - /* "Compulsory" options */ - uint8_t opt_type; - uint8_t opt_len; - /* FIXME: hack alert */ - uint8_t opt_ll_addr[6]; }; -struct neighbour_advert { +struct router_advert { uint8_t type; uint8_t code; uint16_t csum; - uint8_t flags; - uint8_t reserved; - struct in6_addr target; - uint8_t opt_type; - uint8_t opt_len; - /* FIXME: hack alert */ - uint8_t opt_ll_addr[6]; + uint16_t lifetime; + uint16_t hops_flags; + uint32_t reachable_time; + uint32_t retrans_time; }; #define ICMP6_FLAGS_ROUTER 0x80 @@ -86,4 +82,6 @@ int icmp6_rx ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src, int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src, struct in6_addr *dest ); +int icmp6_send_advert ( struct net_device *netdev, struct in6_addr *src, struct in6_addr *dest ); + #endif /* _GPXE_ICMP6_H */ diff --git a/src/include/gpxe/ndp.h b/src/include/gpxe/ndp.h index db32b0c..88cdb8e 100644 --- a/src/include/gpxe/ndp.h +++ b/src/include/gpxe/ndp.h @@ -1,3 +1,6 @@ +#ifndef _GPXE_NDP_H +#define _GPXE_NDP_H + #include <stdint.h> #include <byteswap.h> #include <string.h> @@ -15,7 +18,68 @@ #define NDP_STATE_PROBE 4 #define NDP_STATE_STALE 5 +#define NDP_OPTION_SOURCE_LL 1 +#define NDP_OPTION_TARGET_LL 2 +#define NDP_OPTION_PREFIX_INFO 3 +#define NDP_OPTION_REDIRECT 4 +#define NDP_OPTION_MTU 5 + +struct neighbour_solicit { + uint8_t type; + uint8_t code; + uint16_t csum; + uint32_t reserved; + struct in6_addr target; +}; + +struct neighbour_advert { + uint8_t type; + uint8_t code; + uint16_t csum; + uint8_t flags; + uint8_t reserved; + struct in6_addr target; +}; + +struct ndp_option +{ + uint8_t type; + uint8_t length; +}; + +struct ll_option +{ + uint8_t type; + uint8_t length; + uint8_t address[6]; +}; + +struct prefix_option +{ + uint8_t type; + uint8_t length; + uint8_t prefix_len; + uint8_t flags_rsvd; + uint32_t lifetime; + uint32_t pref_lifetime; + uint32_t rsvd2; + uint8_t prefix[16]; +}; + int ndp_resolve ( struct net_device *netdev, struct in6_addr *src, struct in6_addr *dest, void *dest_ll_addr ); -int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src, - struct sockaddr_tcpip *st_dest ); + +int ndp_process_radvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src, + struct sockaddr_tcpip *st_dest, + struct icmp6_net_protocol *net_protocol ); + +int ndp_process_nadvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src, + struct sockaddr_tcpip *st_dest, + struct icmp6_net_protocol *net_protocol ); + +int ndp_process_nsolicit ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src, + struct sockaddr_tcpip *st_dest, struct net_device *netdev, + struct icmp6_net_protocol *net_protocol ); + +#endif + diff --git a/src/net/icmpv6.c b/src/net/icmpv6.c index 50a548b..cd52456 100644 --- a/src/net/icmpv6.c +++ b/src/net/icmpv6.c @@ -11,6 +11,8 @@ #include <gpxe/tcpip.h> #include <gpxe/netdevice.h> +#include <gpxe/ethernet.h> + struct tcpip_protocol icmp6_protocol; /** @@ -31,22 +33,27 @@ int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src __unuse } st_dest; struct ll_protocol *ll_protocol = netdev->ll_protocol; struct neighbour_solicit *nsolicit; - struct io_buffer *iobuf = alloc_iob ( sizeof ( *nsolicit ) + MIN_IOB_LEN ); + struct ll_option *llopt; + struct io_buffer *iobuf = alloc_iob ( sizeof ( struct ll_option ) + + sizeof ( *nsolicit ) + MIN_IOB_LEN ); iob_reserve ( iobuf, MAX_HDR_LEN ); nsolicit = iob_put ( iobuf, sizeof ( *nsolicit ) ); + llopt = iob_put ( iobuf, sizeof ( *llopt ) ); /* Fill up the headers */ memset ( nsolicit, 0, sizeof ( *nsolicit ) ); nsolicit->type = ICMP6_NSOLICIT; nsolicit->code = 0; nsolicit->target = *dest; - nsolicit->opt_type = 1; - nsolicit->opt_len = ( 2 + ll_protocol->ll_addr_len ) / 8; - memcpy ( nsolicit->opt_ll_addr, netdev->ll_addr, - netdev->ll_protocol->ll_addr_len ); + + /* Fill in the link-layer address. FIXME: ll_option assumes 6 bytes. */ + llopt->type = 1; + llopt->length = ( 2 + ll_protocol->ll_addr_len ) / 8; + memcpy ( llopt->address, netdev->ll_addr, netdev->ll_protocol->ll_addr_len ); + /* Partial checksum */ nsolicit->csum = 0; - nsolicit->csum = tcpip_chksum ( nsolicit, sizeof ( *nsolicit ) ); + nsolicit->csum = tcpip_chksum ( nsolicit, sizeof ( *nsolicit ) + sizeof ( *llopt ) ); /* Solicited multicast address - FF02::1 (all stations on local network) */ memset(&st_dest.sin6, 0, sizeof(st_dest.sin6)); @@ -61,6 +68,104 @@ int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src __unuse } /** + * Send neighbour advertisement packet + * + * @v netdev Network device + * @v src Source address + * @v dest Destination address + * + * This function prepares a neighbour advertisement packet and sends it to the + * network layer. + */ +int icmp6_send_advert ( struct net_device *netdev, struct in6_addr *src, + struct in6_addr *dest ) { + union { + struct sockaddr_in6 sin6; + struct sockaddr_tcpip st; + } st_dest; + struct ll_protocol *ll_protocol = netdev->ll_protocol; + struct neighbour_advert *nadvert; + struct ll_option *llopt; + struct io_buffer *iobuf = alloc_iob ( sizeof ( struct ll_option ) + + sizeof ( *nadvert ) + MIN_IOB_LEN ); + iob_reserve ( iobuf, MAX_HDR_LEN ); + nadvert = iob_put ( iobuf, sizeof ( *nadvert ) ); + llopt = iob_put ( iobuf, sizeof ( *llopt ) ); + + /* Fill up the headers */ + memset ( nadvert, 0, sizeof ( *nadvert ) ); + nadvert->type = ICMP6_NADVERT; + nadvert->code = 0; + nadvert->target = *src; + nadvert->flags = ICMP6_FLAGS_SOLICITED | ICMP6_FLAGS_OVERRIDE; + + /* Fill in the link-layer address. FIXME: ll_option assumes 6 bytes. */ + llopt->type = 2; + llopt->length = ( 2 + ll_protocol->ll_addr_len ) / 8; + memcpy ( llopt->address, netdev->ll_addr, netdev->ll_protocol->ll_addr_len ); + + /* Partial checksum */ + nadvert->csum = 0; + nadvert->csum = tcpip_chksum ( nadvert, sizeof ( *nadvert ) + sizeof ( *llopt ) ); + + /* Target network address. */ + st_dest.sin6.sin_family = AF_INET6; + st_dest.sin6.sin6_addr = *dest; + + /* Send packet over IP6 */ + return tcpip_tx ( iobuf, &icmp6_protocol, NULL, &st_dest.st, + NULL, &nadvert->csum ); +} + +/** + * Process ICMP6 Echo Request + * + * @v iobuf I/O buffer containing the original ICMPv6 packet. + * @v st_src Address of the source station. + * @v st_dest Address of the destination station. + */ +int icmp6_handle_echo ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src, + struct sockaddr_tcpip *st_dest, + struct icmp6_net_protocol *net_protocol __unused ) { + struct icmp6_header *icmp6hdr = iobuf->data; + size_t len = iob_len ( iobuf ); + int rc; + + /* Change type to response and recalculate checksum */ + icmp6hdr->type = ICMP6_ECHO_RESPONSE; + icmp6hdr->csum = 0; + icmp6hdr->csum = tcpip_chksum ( icmp6hdr, len ); + + /* Transmit the response */ + if ( ( rc = tcpip_tx ( iob_disown ( iobuf ), &icmp6_protocol, st_dest, + st_src, NULL, &icmp6hdr->csum ) ) != 0 ) { + DBG ( "ICMP could not transmit ping response: %s\n", + strerror ( rc ) ); + } + + free_iob(iobuf); + return rc; +} + +/** + * Identify ICMP6 network layer protocol + * + * @v net_proto Network-layer protocol, in network-endian order + * @ret arp_net_protocol ARP protocol, or NULL + * + */ +static struct icmp6_net_protocol * icmp6_find_protocol ( uint16_t net_proto ) { + struct icmp6_net_protocol *icmp6_net_protocol; + + for_each_table_entry ( icmp6_net_protocol, ICMP6_NET_PROTOCOLS ) { + if ( icmp6_net_protocol->net_protocol->net_proto == net_proto ) { + return icmp6_net_protocol; + } + } + return NULL; +} + +/** * Process ICMP6 headers * * @v iobuf I/O buffer @@ -68,9 +173,13 @@ int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src __unuse * @v st_dest Destination address */ int icmp6_rx ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src, - struct sockaddr_tcpip *st_dest, struct net_device *netdev __unused, - uint16_t pshdr_csum __unused ) { + struct sockaddr_tcpip *st_dest, struct net_device *netdev, + uint16_t pshdr_csum ) { struct icmp6_header *icmp6hdr = iobuf->data; + struct icmp6_net_protocol *icmp6_net_protocol; + size_t len = iob_len ( iobuf ); + unsigned int csum; + int rc; /* Sanity check */ if ( iob_len ( iobuf ) < sizeof ( *icmp6hdr ) ) { @@ -79,14 +188,42 @@ int icmp6_rx ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src, return -EINVAL; } - /* TODO: Verify checksum */ + /* Verify checksum */ + csum = tcpip_continue_chksum ( pshdr_csum, icmp6hdr, len ); + if ( csum != 0 ) { + DBG ( "ICMPv6 checksum incorrect (is %04x, should be 0000)\n", + csum ); + DBG_HD ( icmp6hdr, len ); + rc = -EINVAL; + goto done; + } + + /* Get the net protocol for this packet. */ + icmp6_net_protocol = icmp6_find_protocol ( htons ( ETH_P_IPV6 ) ); + if ( ! icmp6_net_protocol ) { + rc = 0; + goto done; + } + + DBG ( "ICMPv6: packet with type %d and code %x\n", icmp6hdr->type, icmp6hdr->code); /* Process the ICMP header */ switch ( icmp6hdr->type ) { + case ICMP6_ROUTER_ADVERT: + return ndp_process_radvert ( iobuf, st_src, st_dest, icmp6_net_protocol ); + case ICMP6_NSOLICIT: + return ndp_process_nsolicit ( iobuf, st_src, st_dest, netdev, icmp6_net_protocol ); case ICMP6_NADVERT: - return ndp_process_advert ( iobuf, st_src, st_dest ); + return ndp_process_nadvert ( iobuf, st_src, st_dest, icmp6_net_protocol ); + case ICMP6_ECHO_REQUEST: + return icmp6_handle_echo ( iobuf, st_src, st_dest, icmp6_net_protocol ); } - return -ENOSYS; + + rc = -ENOSYS; + + done: + free_iob ( iobuf ); + return rc; } #if 0 diff --git a/src/net/ndp.c b/src/net/ndp.c index 8bea8b3..0eb27ff 100644 --- a/src/net/ndp.c +++ b/src/net/ndp.c @@ -146,9 +146,11 @@ int ndp_resolve ( struct net_device *netdev, struct in6_addr *dest, * @v st_src Source address * @v st_dest Destination address */ -int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused, - struct sockaddr_tcpip *st_dest __unused ) { +int ndp_process_nadvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused, + struct sockaddr_tcpip *st_dest __unused, + struct icmp6_net_protocol *net_protocol __unused ) { struct neighbour_advert *nadvert = iobuf->data; + struct ll_option *ll_opt = iobuf->data + sizeof ( *nadvert ); struct ndp_entry *ndp; /* Sanity check */ @@ -157,19 +159,21 @@ int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src return -EINVAL; } + /* FIXME: assumes link-layer option is first. */ + assert ( nadvert->code == 0 ); assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED ); - assert ( nadvert->opt_type == 2 ); + assert ( ll_opt->type == 2 ); /* Update the neighbour cache, if entry is present */ ndp = ndp_find_entry ( &nadvert->target ); if ( ndp ) { - assert ( nadvert->opt_len == + assert ( ll_opt->length == ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) ); if ( IP6_EQUAL ( ndp->in6, nadvert->target ) ) { - memcpy ( ndp->ll_addr, nadvert->opt_ll_addr, + memcpy ( ndp->ll_addr, ll_opt->address, ndp->ll_protocol->ll_addr_len ); ndp->state = NDP_STATE_REACHABLE; return 0; @@ -178,3 +182,30 @@ int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src DBG ( "Unsolicited advertisement (dropping packet)\n" ); return 0; } + +/** + * Process neighbour solicitation + * + * @v iobuf I/O buffer + * @v st_src Source address + * @v st_dest Destination address + * @v netdev Network device the packet was received on. + */ +int ndp_process_nsolicit ( struct io_buffer *iobuf __unused, struct sockaddr_tcpip *st_src, + struct sockaddr_tcpip *st_dest __unused, struct net_device *netdev, + struct icmp6_net_protocol *net_protocol ) { + struct neighbour_solicit *nsolicit = iobuf->data; + struct in6_addr *src = &( ( struct sockaddr_in6 * ) st_src )->sin6_addr; + + /* Does this match any addresses on the interface? */ + if ( ! net_protocol->check ( netdev, &nsolicit->target ) ) { + /* Send an advertisement to the host. */ + DBG ( "ndp: neighbour solicit received for us\n" ); + return icmp6_send_advert ( netdev, &nsolicit->target, src ); + } else { + DBG ( "ndp: neighbour solicit received but it's not for us\n" ); + } + + return 0; +} + -- 1.7.2.5 _______________________________________________ gPXE-devel mailing list gPXE-devel@etherboot.org http://etherboot.org/mailman/listinfo/gpxe-devel