[gPXE-devel] [PATCH 14/31] [ipv6] implement handling of neighbour solicit messages, and improve handling of neighbour advert messages.
matthew at theiselins.net
matthew at theiselins.net
Fri Jul 8 10:28:23 EDT 2011
From: Matthew Iselin <matthew at theiselins.net>
Also added handling of ICMPv6 echoes, mostly for debugging.
Signed-off-by: Matthew Iselin <matthew at 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
More information about the gPXE-devel
mailing list