[ipv6] Implement EUI-64 generation as a function
[gpxe.git] / src / net / ndp.c
blob6f98b3b117e6d444c1c2044770735d530462e572
1 #include <stdint.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <byteswap.h>
5 #include <errno.h>
6 #include <gpxe/if_ether.h>
7 #include <gpxe/iobuf.h>
8 #include <gpxe/ndp.h>
9 #include <gpxe/icmp6.h>
10 #include <gpxe/ip6.h>
11 #include <gpxe/netdevice.h>
13 /** @file
15 * Neighbour Discovery Protocol
17 * This file implements address resolution as specified by the neighbour
18 * discovery protocol in RFC2461. This protocol is part of the IPv6 protocol
19 * family.
22 /* A neighbour entry */
23 struct ndp_entry {
24 /** Target IP6 address */
25 struct in6_addr in6;
26 /** Link layer protocol */
27 struct ll_protocol *ll_protocol;
28 /** Link-layer address */
29 uint8_t ll_addr[MAX_LL_ADDR_LEN];
30 /** State of the neighbour entry */
31 int state;
34 /** Number of entries in the neighbour cache table */
35 #define NUM_NDP_ENTRIES 4
37 /** The neighbour cache table */
38 static struct ndp_entry ndp_table[NUM_NDP_ENTRIES];
39 #define ndp_table_end &ndp_table[NUM_NDP_ENTRIES]
41 static unsigned int next_new_ndp_entry = 0;
43 /**
44 * Find entry in the neighbour cache
46 * @v in6 IP6 address
48 static struct ndp_entry *
49 ndp_find_entry ( struct in6_addr *in6 ) {
50 struct ndp_entry *ndp;
52 for ( ndp = ndp_table ; ndp < ndp_table_end ; ndp++ ) {
53 if ( IP6_EQUAL ( ( *in6 ), ndp->in6 ) &&
54 ( ndp->state != NDP_STATE_INVALID ) ) {
55 return ndp;
58 return NULL;
61 /**
62 * Add NDP entry
64 * @v netdev Network device
65 * @v in6 IP6 address
66 * @v ll_addr Link-layer address
67 * @v state State of the entry - one of the NDP_STATE_XXX values
69 static void
70 add_ndp_entry ( struct net_device *netdev, struct in6_addr *in6,
71 void *ll_addr, int state ) {
72 struct ndp_entry *ndp;
73 ndp = &ndp_table[next_new_ndp_entry++ % NUM_NDP_ENTRIES];
75 /* Fill up entry */
76 ndp->ll_protocol = netdev->ll_protocol;
77 memcpy ( &ndp->in6, &( *in6 ), sizeof ( *in6 ) );
78 if ( ll_addr ) {
79 memcpy ( ndp->ll_addr, ll_addr, netdev->ll_protocol->ll_addr_len );
80 } else {
81 memset ( ndp->ll_addr, 0, netdev->ll_protocol->ll_addr_len );
83 ndp->state = state;
84 DBG ( "New neighbour cache entry: IP6 %s => %s %s\n",
85 inet6_ntoa ( ndp->in6 ), netdev->ll_protocol->name,
86 netdev->ll_protocol->ntoa ( ndp->ll_addr ) );
89 /**
90 * Resolve the link-layer address
92 * @v netdev Network device
93 * @v dest Destination address
94 * @v src Source address
95 * @ret dest_ll_addr Destination link-layer address or NULL
96 * @ret rc Status
98 * This function looks up the neighbour cache for an entry corresponding to the
99 * destination address. If it finds a valid entry, it fills up dest_ll_addr and
100 * returns 0. Otherwise it sends a neighbour solicitation to the solicited
101 * multicast address.
103 int ndp_resolve ( struct net_device *netdev, struct in6_addr *dest,
104 struct in6_addr *src, void *dest_ll_addr ) {
105 struct ll_protocol *ll_protocol = netdev->ll_protocol;
106 struct ndp_entry *ndp;
107 int rc;
109 ndp = ndp_find_entry ( dest );
110 /* Check if the entry is valid */
111 if ( ndp && ndp->state == NDP_STATE_REACHABLE ) {
112 DBG ( "Neighbour cache hit: IP6 %s => %s %s\n",
113 inet6_ntoa ( *dest ), ll_protocol->name,
114 ll_protocol->ntoa ( ndp->ll_addr ) );
115 memcpy ( dest_ll_addr, ndp->ll_addr, ll_protocol->ll_addr_len );
116 return 0;
119 /* Check if the entry was already created */
120 if ( ndp ) {
121 DBG ( "Awaiting neighbour advertisement\n" );
122 /* For test */
123 // ndp->state = NDP_STATE_REACHABLE;
124 // memcpy ( ndp->ll_addr, netdev->ll_addr, 6 );
125 // assert ( ndp->ll_protocol->ll_addr_len == 6 );
126 // icmp6_test_nadvert ( netdev, dest, ndp->ll_addr );
127 // assert ( ndp->state == NDP_STATE_REACHABLE );
128 /* Take it out till here */
129 return -ENOENT;
131 DBG ( "Neighbour cache miss: IP6 %s\n", inet6_ntoa ( *dest ) );
133 /* Add entry in the neighbour cache */
134 add_ndp_entry ( netdev, dest, NULL, NDP_STATE_INCOMPLETE );
136 /* Send neighbour solicitation */
137 if ( ( rc = icmp6_send_solicit ( netdev, src, dest ) ) != 0 ) {
138 return rc;
140 return -ENOENT;
144 * Process Router Advertisement
146 * @v iobuf I/O buffer containing the data.
147 * @v st_src Address of the source station.
148 * @v st_dest Address of the destination station. Typically FF02::1.
150 int ndp_process_radvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
151 struct sockaddr_tcpip *st_dest __unused, struct net_device *netdev,
152 struct icmp6_net_protocol *net_protocol __unused ) {
153 struct router_advert *radvert = iobuf->data;
154 struct ndp_option *options = iobuf->data + sizeof(struct router_advert);
155 struct in6_addr router_addr = ( ( struct sockaddr_in6 * ) st_src )->sin6_addr;
156 struct in6_addr host_addr;
157 int rc = -ENOENT;
158 uint8_t prefix_len = 0;
159 size_t offset = sizeof ( struct router_advert ), ll_size;
161 memset ( &host_addr, 0, sizeof ( host_addr ) );
163 /* Verify that we shouldn't be trying DHCPv6 instead. */
164 if ( ntohs ( radvert->hops_flags ) & RADVERT_MANAGED ) {
165 DBG ( "ndp: router advertisement suggests DHCPv6\n" );
166 return 0;
169 /* Parse options. */
170 while ( offset < iob_len( iobuf ) ) {
172 switch ( options->type ) {
173 case NDP_OPTION_PREFIX_INFO:
175 struct prefix_option *opt = (struct prefix_option *) options;
177 prefix_len = opt->prefix_len;
179 if ( prefix_len % 8 ) {
180 /* FIXME: non-aligned prefixes unhandled */
181 DBG ( "ndp: prefix length is unaligned, connectivity may suffer.\n" );
184 if ( prefix_len > 64 ) {
185 /* > 64-bit prefix shouldn't happen. */
186 DBG ( "ndp: prefix length is quite long, connectivity may suffer.\n" );
189 /* Create an IPv6 address for this station based on the prefix. */
190 ll_size = netdev->ll_protocol->ll_addr_len;
191 if ( ll_size < 6 ) {
192 memcpy ( host_addr.s6_addr + (8 - ll_size), netdev->ll_addr, ll_size );
193 } else {
194 ipv6_generate_eui64 ( host_addr.s6_addr + 8, netdev->ll_addr );
197 memcpy( &host_addr.s6_addr, opt->prefix, prefix_len / 8 );
199 rc = 0;
201 break;
203 case NDP_OPTION_SOURCE_LL:
205 struct ll_option *opt = (struct ll_option *) options;
207 /* Add entry in the neighbour cache for the router */
208 if ( ! ndp_find_entry ( &router_addr ) ) {
209 add_ndp_entry ( netdev, &router_addr, opt->address, NDP_STATE_REACHABLE );
213 break;
216 offset += options->length * 8;
217 options = (struct ndp_option *) (iobuf->data + offset);
220 if ( rc ) {
221 DBG ( "ndp: couldn't generate a prefix from a router advertisement\n" );
222 return 0;
225 /* Configure a route based on this router if none exists. */
226 if ( net_protocol->check ( netdev, &host_addr ) ) {
227 DBG ( "ndp: autoconfigured %s/%d via a router advertisement\n", inet6_ntoa( host_addr ), prefix_len);
229 add_ipv6_address ( netdev, host_addr, prefix_len, host_addr, router_addr );
232 return 0;
236 * Process neighbour advertisement
238 * @v iobuf I/O buffer
239 * @v st_src Source address
240 * @v st_dest Destination address
242 int ndp_process_nadvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused,
243 struct sockaddr_tcpip *st_dest __unused,
244 struct icmp6_net_protocol *net_protocol __unused ) {
245 struct neighbour_advert *nadvert = iobuf->data;
246 struct ll_option *ll_opt = iobuf->data + sizeof ( *nadvert );
247 struct ndp_entry *ndp;
249 /* Sanity check */
250 if ( iob_len ( iobuf ) < sizeof ( *nadvert ) ) {
251 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
252 return -EINVAL;
255 /* FIXME: assumes link-layer option is first. */
257 assert ( nadvert->code == 0 );
258 assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED );
259 assert ( ll_opt->type == 2 );
261 /* Update the neighbour cache, if entry is present */
262 ndp = ndp_find_entry ( &nadvert->target );
263 if ( ndp ) {
265 assert ( ll_opt->length ==
266 ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) );
268 if ( IP6_EQUAL ( ndp->in6, nadvert->target ) ) {
269 memcpy ( ndp->ll_addr, ll_opt->address,
270 ndp->ll_protocol->ll_addr_len );
271 ndp->state = NDP_STATE_REACHABLE;
272 return 0;
275 DBG ( "Unsolicited advertisement (dropping packet)\n" );
276 return 0;
280 * Process neighbour solicitation
282 * @v iobuf I/O buffer
283 * @v st_src Source address
284 * @v st_dest Destination address
285 * @v netdev Network device the packet was received on.
287 int ndp_process_nsolicit ( struct io_buffer *iobuf __unused, struct sockaddr_tcpip *st_src,
288 struct sockaddr_tcpip *st_dest __unused, struct net_device *netdev,
289 struct icmp6_net_protocol *net_protocol ) {
290 struct neighbour_solicit *nsolicit = iobuf->data;
291 struct in6_addr *src = &( ( struct sockaddr_in6 * ) st_src )->sin6_addr;
293 /* Does this match any addresses on the interface? */
294 if ( ! net_protocol->check ( netdev, &nsolicit->target ) ) {
295 /* Send an advertisement to the host. */
296 DBG ( "ndp: neighbour solicit received for us\n" );
297 return icmp6_send_advert ( netdev, &nsolicit->target, src );
298 } else {
299 DBG ( "ndp: neighbour solicit received but it's not for us\n" );
302 return 0;