[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