diff --git a/src/include/gpxe/icmp6.h b/src/include/gpxe/icmp6.h new file mode 100644 index 00000000..35210364 --- /dev/null +++ b/src/include/gpxe/icmp6.h @@ -0,0 +1,58 @@ +#ifndef _GPXE_ICMP6_H +#define _GPXE_ICMP6_H + +/** @file + * + * ICMP6 protocol + * + */ + +#include +#include +#include + +#define ICMP6_NSOLICIT 135 +#define ICMP6_NADVERT 136 + +extern struct tcpip_protocol icmp6_protocol; + +struct icmp6_header { + uint8_t type; + uint8_t code; + uint16_t csum; + /* Message body */ +}; + +struct neighbour_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; +#warning hack alert + uint8_t opt_ll_addr[6]; +}; + +struct neighbour_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; +#warning hack alert + uint8_t opt_ll_addr[6]; +}; + +#define ICMP6_FLAGS_ROUTER 0x80 +#define ICMP6_FLAGS_SOLICITED 0x40 +#define ICMP6_FLAGS_OVERRIDE 0x20 + +int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src, struct in6_addr *dest ); + +#endif /* _GPXE_ICMP6_H */ diff --git a/src/include/gpxe/in.h b/src/include/gpxe/in.h index eed9c09b..d502c4c3 100644 --- a/src/include/gpxe/in.h +++ b/src/include/gpxe/in.h @@ -10,6 +10,7 @@ #define IP_IGMP 2 #define IP_TCP 6 #define IP_UDP 17 +#define IP_ICMP6 58 /* IP address constants */ @@ -36,7 +37,7 @@ struct in6_addr { uint8_t u6_addr8[16]; uint16_t u6_addr16[8]; uint32_t u6_addr32[4]; - } in16_u; + } in6_u; #define s6_addr in6_u.u6_addr8 #define s6_addr16 in6_u.u6_addr16 #define s6_addr32 in6_u.u6_addr32 @@ -67,7 +68,7 @@ struct sockaddr_in6 { */ sa_family_t sin_family; /** TCP/IP port (part of struct @c sockaddr_tcpip) */ - uint16_t sin_port; + uint16_t sin_port; uint32_t sin6_flowinfo; /* Flow number */ struct in6_addr sin6_addr; /* 128-bit destination address */ uint32_t sin6_scope_id; /* Scope ID */ diff --git a/src/include/gpxe/ip6.h b/src/include/gpxe/ip6.h index 69cbd81b..0589f8e9 100644 --- a/src/include/gpxe/ip6.h +++ b/src/include/gpxe/ip6.h @@ -8,18 +8,33 @@ */ #include +#include /* IP6 constants */ #define IP6_VERSION 0x6 -#define IP6_HOP_LIMIT 64 +#define IP6_HOP_LIMIT 255 +/** + * Packet buffer contents + * This is duplicated in tcp.h and here. Ideally it should go into pkbuff.h + */ +#define MAX_HDR_LEN 100 +#define MAX_PKB_LEN 1500 +#define MIN_PKB_LEN MAX_HDR_LEN + 100 /* To account for padding by LL */ + +#define IP6_EQUAL( in6_addr1, in6_addr2 ) \ + ( strncmp ( ( char* ) &( in6_addr1 ), ( char* ) &( in6_addr2 ),\ + sizeof ( struct in6_addr ) ) == 0 ) + +#define IS_UNSPECIFIED( addr ) \ + ( ( (addr).in6_u.u6_addr32[0] == 0x00000000 ) && \ + ( (addr).in6_u.u6_addr32[1] == 0x00000000 ) && \ + ( (addr).in6_u.u6_addr32[2] == 0x00000000 ) && \ + ( (addr).in6_u.u6_addr32[3] == 0x00000000 ) ) /* IP6 header */ - struct ip6_header { - uint32_t vers:4, - traffic_class:8, - flow_label:20; + uint32_t ver_traffic_class_flow_label; uint16_t payload_len; uint8_t nxt_hdr; uint8_t hop_limit; @@ -27,12 +42,32 @@ struct ip6_header { struct in6_addr dest; }; +/* IP6 pseudo header */ +struct ipv6_pseudo_header { + struct in6_addr src; + struct in6_addr dest; + uint8_t zero_padding; + uint8_t nxt_hdr; + uint16_t len; +}; + +/* Next header numbers */ +#define IP6_HOPBYHOP 0x00 +#define IP6_ROUTING 0x43 +#define IP6_FRAGMENT 0x44 +#define IP6_AUTHENTICATION 0x51 +#define IP6_DEST_OPTS 0x60 +#define IP6_ESP 0x50 +#define IP6_ICMP6 0x58 +#define IP6_NO_HEADER 0x59 + struct pk_buff; struct net_device; struct net_protocol; extern struct net_protocol ipv6_protocol; +extern struct tcpip_net_protocol ipv6_tcpip_protocol; +extern char * inet6_ntoa ( struct in6_addr in6 ); -extern int ipv6_tx ( struct pk_buff *pkb, uint16_t trans_proto, struct in6_addr *dest ); #endif /* _GPXE_IP6_H */ diff --git a/src/include/gpxe/ndp.h b/src/include/gpxe/ndp.h new file mode 100644 index 00000000..ed9411f9 --- /dev/null +++ b/src/include/gpxe/ndp.h @@ -0,0 +1,23 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NDP_STATE_INVALID 0 +#define NDP_STATE_INCOMPLETE 1 +#define NDP_STATE_REACHABLE 2 +#define NDP_STATE_DELAY 3 +#define NDP_STATE_PROBE 4 +#define NDP_STATE_STALE 5 + +static struct ndp_entry * ndp_find_entry ( struct in6_addr *in6 ); +int ndp_resolve ( struct net_device *netdev, struct in6_addr *src, + struct in6_addr *dest, void *dest_ll_addr ); +int ndp_process_advert ( struct pk_buff *pkb, struct sockaddr_tcpip *st_src, + struct sockaddr_tcpip *st_dest ); diff --git a/src/net/icmpv6.c b/src/net/icmpv6.c new file mode 100644 index 00000000..183fd06c --- /dev/null +++ b/src/net/icmpv6.c @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct tcpip_protocol icmp6_protocol; + +/** + * Send neighbour solicitation packet + * + * @v netdev Network device + * @v src Source address + * @v dest Destination address + * + * This function prepares a neighbour solicitation packet and sends it to the + * network layer. + */ +int icmp6_send_solicit ( 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_solicit *nsolicit; + struct pk_buff *pkb = alloc_pkb ( sizeof ( *nsolicit ) + MIN_PKB_LEN ); + pkb_reserve ( pkb, MAX_HDR_LEN ); + nsolicit = pkb_put ( pkb, sizeof ( *nsolicit ) ); + + /* 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 ); + /* Partial checksum */ + nsolicit->csum = 0; + nsolicit->csum = tcpip_chksum ( nsolicit, sizeof ( *nsolicit ) ); + + /* Solicited multicast address */ + st_dest.sin6.sin_family = AF_INET6; + st_dest.sin6.sin6_addr.in6_u.u6_addr8[0] = 0xff; + st_dest.sin6.sin6_addr.in6_u.u6_addr8[2] = 0x02; + st_dest.sin6.sin6_addr.in6_u.u6_addr16[1] = 0x0000; + st_dest.sin6.sin6_addr.in6_u.u6_addr32[1] = 0x00000000; + st_dest.sin6.sin6_addr.in6_u.u6_addr16[4] = 0x0000; + st_dest.sin6.sin6_addr.in6_u.u6_addr16[5] = 0x0001; + st_dest.sin6.sin6_addr.in6_u.u6_addr32[3] = dest->in6_u.u6_addr32[3]; + st_dest.sin6.sin6_addr.in6_u.u6_addr8[13] = 0xff; + + /* Send packet over IP6 */ + tcpip_tx ( pkb, &icmp6_protocol, &st_dest.st ); +} + +/** + * Process ICMP6 headers + * + * @v pkb Packet buffer + * @v st_src Source address + * @v st_dest Destination address + */ +static int icmp6_rx ( struct pk_buff *pkb, struct sockaddr_tcpip *st_src, + struct sockaddr_tcpip *st_dest ) { + struct icmp6_header *icmp6hdr = pkb->data; + + /* Sanity check */ + if ( pkb_len ( pkb ) < sizeof ( *icmp6hdr ) ) { + DBG ( "Packet too short (%d bytes)\n", pkb_len ( pkb ) ); + free_pkb ( pkb ); + return -EINVAL; + } + + /* TODO: Verify checksum */ + + /* Process the ICMP header */ + switch ( icmp6hdr->type ) { + case ICMP6_NADVERT: + return ndp_process_advert ( pkb, st_src, st_dest ); + } + return -ENOSYS; +} + +#if 0 +void icmp6_test_nadvert (struct net_device *netdev, struct sockaddr_in6 *server_p, char *ll_addr) { + + struct sockaddr_in6 server; + memcpy ( &server, server_p, sizeof ( server ) ); + struct pk_buff *rxpkb = alloc_pkb ( 500 ); + pkb_reserve ( rxpkb, MAX_HDR_LEN ); + struct neighbour_advert *nadvert = pkb_put ( rxpkb, sizeof ( *nadvert ) ); + nadvert->type = 136; + nadvert->code = 0; + nadvert->flags = ICMP6_FLAGS_SOLICITED; + nadvert->csum = 0xffff; + nadvert->target = server.sin6_addr; + nadvert->opt_type = 2; + nadvert->opt_len = 1; + memcpy ( nadvert->opt_ll_addr, ll_addr, 6 ); + struct ip6_header *ip6hdr = pkb_push ( rxpkb, sizeof ( *ip6hdr ) ); + ip6hdr->ver_traffic_class_flow_label = htonl ( 0x60000000 ); + ip6hdr->hop_limit = 255; + ip6hdr->nxt_hdr = 58; + ip6hdr->payload_len = htons ( sizeof ( *nadvert ) ); + ip6hdr->src = server.sin6_addr; + ip6hdr->dest = server.sin6_addr; + hex_dump ( rxpkb->data, pkb_len ( rxpkb ) ); + net_rx ( rxpkb, netdev, htons ( ETH_P_IPV6 ), ll_addr ); +} +#endif + +/** ICMP6 protocol */ +struct tcpip_protocol icmp6_protocol __tcpip_protocol = { + .name = "ICMP6", + .rx = icmp6_rx, + .tcpip_proto = IP_ICMP6, // 58 + .csum_offset = 2, +}; diff --git a/src/net/ipv6.c b/src/net/ipv6.c index 38705aa0..d00562a0 100644 --- a/src/net/ipv6.c +++ b/src/net/ipv6.c @@ -1,42 +1,337 @@ #include -#include #include #include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include #include #include -#include #include -#include + +struct net_protocol ipv6_protocol; + +/* Unspecified IP6 address */ +static struct in6_addr ip6_none = { + .in6_u.u6_addr32[0] = 0, + .in6_u.u6_addr32[1] = 0, + .in6_u.u6_addr32[2] = 0, + .in6_u.u6_addr32[3] = 0, +}; + +/** An IPv6 routing table entry */ +struct ipv6_miniroute { + /* List of miniroutes */ + struct list_head list; + /* Network device */ + struct net_device *netdev; + /* Destination prefix */ + struct in6_addr prefix; + /* Prefix length */ + int prefix_len; + /* IPv6 address of interface */ + struct in6_addr address; + /* Gateway address */ + struct in6_addr gateway; +}; + +/** List of IPv6 miniroutes */ +static LIST_HEAD ( miniroutes ); /** - * Transmit IP6 packets + * Add IPv6 interface + * + * @v netdev Network device + * @v prefix Destination prefix + * @v address Address of the interface + * @v gateway Gateway address (or ::0 for no gateway) + */ +int add_ipv6_address ( struct net_device *netdev, struct in6_addr prefix, + int prefix_len, struct in6_addr address, + struct in6_addr gateway ) { + struct ipv6_miniroute *miniroute; + + miniroute = malloc ( sizeof ( *miniroute ) ); + if ( !miniroute ) { + DBG ( "Not enough memory\n" ); + return -ENOMEM; + } + miniroute->netdev = netdev; + miniroute->prefix = prefix; + miniroute->prefix_len = prefix_len; + miniroute->address = address; + miniroute->gateway = gateway; + + /* Add miniroute to list of miniroutes */ + if ( !IP6_EQUAL ( gateway, ip6_none ) ) { + list_add_tail ( &miniroute->list, &miniroutes ); + } else { + list_add ( &miniroute->list, &miniroutes ); + } + return 0; +} + +/** + * Remove IPv6 interface + * + * @v netdev Network device + */ +void del_ipv6_address ( struct net_device *netdev ) { + struct ipv6_miniroute *miniroute; + + list_for_each_entry ( miniroute, &miniroutes, list ) { + if ( miniroute->netdev == netdev ) { + list_del ( &miniroute->list ); + break; + } + } +} + +/** + * Calculate TCPIP checksum + * + * @v pkb Packet buffer + * @v tcpip TCP/IP protocol + * + * This function constructs the pseudo header and completes the checksum in the + * upper layer header. + */ +static void ipv6_tx_csum ( struct pk_buff *pkb, struct tcpip_protocol *tcpip ) { + struct ip6_header *ip6hdr = pkb->data; + struct ipv6_pseudo_header pshdr; + uint16_t *csum = ( ( ( void * ) ip6hdr ) + sizeof ( *ip6hdr ) + + tcpip->csum_offset ); + + /* Calculate pseudo header */ + memset ( &pshdr, 0, sizeof ( pshdr ) ); + pshdr.src = ip6hdr->src; + pshdr.dest = ip6hdr->dest; + pshdr.len = htons ( pkb_len ( pkb ) - sizeof ( *ip6hdr ) ); + pshdr.nxt_hdr = ip6hdr->nxt_hdr; + + /* Update checksum value */ + *csum = tcpip_continue_chksum ( *csum, &pshdr, sizeof ( pshdr ) ); +} + +/** + * Dump IP6 header for debugging + * + * ip6hdr IPv6 header + */ +void ipv6_dump ( struct ip6_header *ip6hdr ) { + DBG ( "IP6 %p src %s dest %s nxt_hdr %d len %d\n", ip6hdr, + inet6_ntoa ( ip6hdr->src ), inet6_ntoa ( ip6hdr->dest ), + ip6hdr->nxt_hdr, ntohs ( ip6hdr->payload_len ) ); +} + +/** + * Transmit IP6 packet + * + * pkb Packet buffer + * tcpip TCP/IP protocol + * st_dest Destination socket address + * + * This function prepends the IPv6 headers to the payload an transmits it. */ static int ipv6_tx ( struct pk_buff *pkb, - struct tcpip_protocol *tcpip_protocol, + struct tcpip_protocol *tcpip, struct sockaddr_tcpip *st_dest ) { - return -ENOSYS; + struct sockaddr_in6 *dest = ( struct sockaddr_in6* ) st_dest; + struct in6_addr next_hop; + struct ipv6_miniroute *miniroute; + struct net_device *netdev = NULL; + uint8_t ll_dest_buf[MAX_LL_ADDR_LEN]; + const uint8_t *ll_dest = ll_dest_buf; + int rc; + + /* Construct the IPv6 packet */ + struct ip6_header *ip6hdr = pkb_push ( pkb, sizeof ( *ip6hdr ) ); + memset ( ip6hdr, 0, sizeof ( *ip6hdr) ); + ip6hdr->ver_traffic_class_flow_label = htonl ( 0x60000000 );//IP6_VERSION; + ip6hdr->payload_len = htons ( pkb_len ( pkb ) - sizeof ( *ip6hdr ) ); + ip6hdr->nxt_hdr = tcpip->tcpip_proto; + ip6hdr->hop_limit = IP6_HOP_LIMIT; // 255 + + /* Determine the next hop address and interface + * + * TODO: Implement the routing table. + */ + next_hop = dest->sin6_addr; + list_for_each_entry ( miniroute, &miniroutes, list ) { + if ( ( strncmp ( &ip6hdr->dest, &miniroute->prefix, + miniroute->prefix_len ) == 0 ) || + ( IP6_EQUAL ( miniroute->gateway, ip6_none ) ) ) { + netdev = miniroute->netdev; + ip6hdr->src = miniroute->address; + if ( ! ( IS_UNSPECIFIED ( miniroute->gateway ) ) ) { + next_hop = miniroute->gateway; + } + break; + } + } + /* No network interface identified */ + if ( !netdev ) { + DBG ( "No route to host %s\n", inet6_ntoa ( ip6hdr->dest ) ); + rc = -EHOSTUNREACH; + goto err; + } + + /* Complete the transport layer checksum */ + if ( tcpip->csum_offset > 0 ) { + ipv6_tx_csum ( pkb, tcpip ); + } + + /* Print IPv6 header */ + ipv6_dump ( ip6hdr ); + + /* Resolve link layer address */ + if ( next_hop.in6_u.u6_addr8[0] == 0xff ) { + ll_dest_buf[0] = 0x33; + ll_dest_buf[1] = 0x33; + ll_dest_buf[2] = next_hop.in6_u.u6_addr8[12]; + ll_dest_buf[3] = next_hop.in6_u.u6_addr8[13]; + ll_dest_buf[4] = next_hop.in6_u.u6_addr8[14]; + ll_dest_buf[5] = next_hop.in6_u.u6_addr8[15]; + } else { + /* Unicast address needs to be resolved by NDP */ + if ( ( rc = ndp_resolve ( netdev, &next_hop, &ip6hdr->src, + ll_dest_buf ) ) != 0 ) { + DBG ( "No entry for %s\n", inet6_ntoa ( next_hop ) ); + goto err; + } + } + + /* Transmit packet */ + return net_tx ( pkb, netdev, &ipv6_protocol, ll_dest ); + + err: + free_pkb ( pkb ); + return rc; +} + +/** + * Process next IP6 header + * + * @v pkb Packet buffer + * @v nxt_hdr Next header number + * @v src Source socket address + * @v dest Destination socket address + * + * Refer http://www.iana.org/assignments/ipv6-parameters for the numbers + */ +static int ipv6_process_nxt_hdr ( struct pk_buff *pkb, uint8_t nxt_hdr, + struct sockaddr_tcpip *src, struct sockaddr_tcpip *dest ) { + switch ( nxt_hdr ) { + case IP6_HOPBYHOP: + case IP6_ROUTING: + case IP6_FRAGMENT: + case IP6_AUTHENTICATION: + case IP6_DEST_OPTS: + case IP6_ESP: + DBG ( "Function not implemented for header %d\n", nxt_hdr ); + return -ENOSYS; + case IP6_ICMP6: + break; + case IP6_NO_HEADER: + DBG ( "No next header\n" ); + return 0; + } + /* Next header is not a IPv6 extension header */ + return tcpip_rx ( pkb, nxt_hdr, src, dest ); } /** * Process incoming IP6 packets * - * Placeholder function. Should rewrite in net/ipv6.c + * @v pkb Packet buffer + * @v netdev Network device + * @v ll_source Link-layer source address + * + * This function processes a IPv6 packet */ -static int ipv6_rx ( struct pk_buff *pkb __unused, - struct net_device *netdev __unused, - const void *ll_source __unused ) { - return -ENOSYS; +static int ipv6_rx ( struct pk_buff *pkb, + struct net_device *netdev, + const void *ll_source ) { + + struct ip6_header *ip6hdr = pkb->data; + union { + struct sockaddr_in6 sin6; + struct sockaddr_tcpip st; + } src, dest; + + /* Sanity check */ + if ( pkb_len ( pkb ) < sizeof ( *ip6hdr ) ) { + DBG ( "Packet too short (%d bytes)\n", pkb_len ( pkb ) ); + goto drop; + } + + /* TODO: Verify checksum */ + + /* Print IP6 header for debugging */ + ipv6_dump ( ip6hdr ); + + /* Check header version */ + if ( ip6hdr->ver_traffic_class_flow_label & 0xf0000000 != 0x60000000 ) { + DBG ( "Invalid protocol version\n" ); + goto drop; + } + + /* Check the payload length */ + if ( ntohs ( ip6hdr->payload_len ) > pkb_len ( pkb ) ) { + DBG ( "Inconsistent packet length (%d bytes)\n", + ip6hdr->payload_len ); + goto drop; + } + + /* Ignore the traffic class and flow control values */ + + /* Construct socket address */ + memset ( &src, 0, sizeof ( src ) ); + src.sin6.sin_family = AF_INET6; + src.sin6.sin6_addr = ip6hdr->src; + memset ( &dest, 0, sizeof ( dest ) ); + dest.sin6.sin_family = AF_INET6; + dest.sin6.sin6_addr = ip6hdr->dest; + + /* Strip header */ + pkb_unput ( pkb, pkb_len ( pkb ) - ntohs ( ip6hdr->payload_len ) - + sizeof ( *ip6hdr ) ); + pkb_pull ( pkb, sizeof ( *ip6hdr ) ); + + /* Send it to the transport layer */ + return ipv6_process_nxt_hdr ( pkb, ip6hdr->nxt_hdr, &src.st, &dest.st ); + + drop: + DBG ( "Packet dropped\n" ); + free_pkb ( pkb ); + return -1; +} + +/** + * Print a IP6 address as xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx + */ +char * inet6_ntoa ( struct in6_addr in6 ) { + static char buf[40]; + uint16_t *bytes = ( uint16_t* ) &in6; + sprintf ( buf, "%x:%x:%x:%x:%x:%x:%x:%x", bytes[0], bytes[1], bytes[2], + bytes[3], bytes[4], bytes[5], bytes[6], bytes[7] ); + return buf; } static const char * ipv6_ntoa ( const void *net_addr ) { -// return inet6_ntoa ( * ( ( struct in6_addr * ) net_addr ) ); - return "no support yet"; + return inet6_ntoa ( * ( ( struct in6_addr * ) net_addr ) ); } /** IPv6 protocol */ struct net_protocol ipv6_protocol __net_protocol = { - .name = "IP6", + .name = "IPv6", .net_proto = htons ( ETH_P_IPV6 ), .net_addr_len = sizeof ( struct in6_addr ), .rx = ipv6_rx, diff --git a/src/net/ndp.c b/src/net/ndp.c new file mode 100644 index 00000000..b72cf76f --- /dev/null +++ b/src/net/ndp.c @@ -0,0 +1,181 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @file + * + * 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 + * + * @v in6 IP6 address + */ +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; + } + } + return NULL; +} + +/** + * 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 + */ +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 (%d): IP6 %s => %s %s\n", + ( ndp - ndp_table ) / sizeof ( *ndp ), + inet6_ntoa ( ndp->in6 ), netdev->ll_protocol->name, + netdev->ll_protocol->ntoa ( ndp->ll_addr ) ); +} + +/** + * Resolve the link-layer address + * + * @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. + */ +int ndp_resolve ( struct net_device *netdev, struct in6_addr *dest, + struct in6_addr *src, void *dest_ll_addr ) { + 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 ); + return 0; + } + + /* Check if the entry was already created */ + if ( ndp ) { + DBG ( "Awaiting neighbour advertisement (cache entry %d)\n", + ( ( ndp - ndp_table ) / sizeof ( *ndp ) ) ); + /* 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; + } + 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 ); + + /* Send neighbour solicitation */ + if ( ( rc = icmp6_send_solicit ( netdev, src, dest ) ) != 0 ) { + return rc; + } + return -ENOENT; +} + +/** + * Process neighbour advertisement + * + * @v pkb Packet buffer + * @v st_src Source address + * @v st_dest Destination address + */ +int ndp_process_advert ( struct pk_buff *pkb, struct sockaddr_tcpip *st_src, + struct sockaddr_tcpip *st_dest ) { + struct neighbour_advert *nadvert = pkb->data; + struct ndp_entry *ndp; + + /* Sanity check */ + if ( pkb_len ( pkb ) < sizeof ( *nadvert ) ) { + DBG ( "Packet too short (%d bytes)\n", pkb_len ( pkb ) ); + return -EINVAL; + } + + assert ( nadvert->code == 0 ); + assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED ); + assert ( nadvert->opt_type == 2 ); + + /* Update the neighbour cache, if entry is present */ + ndp = ndp_find_entry ( &nadvert->target ); + if ( ndp ) { + + assert ( nadvert->opt_len == + ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) ); + + 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; + } + } + DBG ( "Unsolicited advertisement (dropping packet)\n" ); + return 0; +}