[ipv6] Fix router solicitation struct and flag checking
[gpxe.git] / src / net / ndp.c
blob4ac4a5b98f1ea2847ebe9c1364a4c0540beace25
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>
12 #include <gpxe/retry.h>
13 #include <gpxe/timer.h>
14 #include <gpxe/job.h>
16 /** @file
18 * Neighbour Discovery Protocol
20 * This file implements address resolution as specified by the neighbour
21 * discovery protocol in RFC2461. This protocol is part of the IPv6 protocol
22 * family.
25 /* A neighbour entry */
26 struct ndp_entry {
27 /** Target IP6 address */
28 struct in6_addr in6;
29 /** Link layer protocol */
30 struct ll_protocol *ll_protocol;
31 /** Link-layer address */
32 uint8_t ll_addr[MAX_LL_ADDR_LEN];
33 /** State of the neighbour entry */
34 int state;
37 /** A pending router solicitation. */
38 struct pending_rsolicit {
39 /** Network device for the solicit. */
40 struct net_device *netdev;
41 /** State of the solicitation. */
42 int state;
43 /** Status code after handling the solicit. */
44 int code;
45 /** Job control interface */
46 struct job_interface job;
47 /** Reference counter */
48 struct refcnt refcnt;
49 /** Metadata to fill when we receive an advertisement. */
50 struct rsolicit_info *meta;
51 /** Timer for timeout handling. */
52 struct retry_timer timer;
55 /** Number of entries in the neighbour cache table */
56 #define NUM_NDP_ENTRIES 4
58 /** The neighbour cache table */
59 static struct ndp_entry ndp_table[NUM_NDP_ENTRIES];
60 #define ndp_table_end &ndp_table[NUM_NDP_ENTRIES]
62 static unsigned int next_new_ndp_entry = 0;
64 /** The pending solicit table */
65 static struct pending_rsolicit solicit_table[NUM_NDP_ENTRIES];
66 #define solicit_table_end &solicit_table[NUM_NDP_ENTRIES]
68 static unsigned int next_new_solicit_entry = 0;
70 /**
71 * Handle kill() event received via job control interface
73 * @v job Router solicit job control interface
75 static void rsolicit_job_kill ( struct job_interface *job ) {
76 struct pending_rsolicit *entry =
77 container_of ( job, struct pending_rsolicit, job );
79 /* Terminate. */
80 entry->code = 0;
81 entry->state = RSOLICIT_STATE_INVALID;
83 /* Stop retry timer */
84 stop_timer ( &entry->timer );
86 /* Clean up. */
87 job_nullify ( &entry->job );
88 job_done ( &entry->job, -ECANCELED );
91 /** Router solicit job control interface operations */
92 static struct job_interface_operations rsolicit_job_operations = {
93 .done = ignore_job_done,
94 .kill = rsolicit_job_kill,
95 .progress = ignore_job_progress,
98 /**
99 * Handle router solicitation timeout.
100 * @v timer Solicitation retry timer.
101 * @v fail Failure indicator.
103 static void rsolicit_timer_expired ( struct retry_timer *timer, int fail __unused ) {
104 struct pending_rsolicit *entry =
105 container_of ( timer, struct pending_rsolicit, timer );
107 /* Don't bother retrying. */
108 rsolicit_job_kill ( &entry->job );
112 * Find entry in the neighbour cache
114 * @v in6 IP6 address
116 static struct ndp_entry *
117 ndp_find_entry ( struct in6_addr *in6 ) {
118 struct ndp_entry *ndp;
120 for ( ndp = ndp_table ; ndp < ndp_table_end ; ndp++ ) {
121 if ( IP6_EQUAL ( ( *in6 ), ndp->in6 ) &&
122 ( ndp->state != NDP_STATE_INVALID ) ) {
123 return ndp;
126 return NULL;
130 * Find a pending router solicitation for an interface.
132 * @v netdev Interface for the solicitation.
134 static struct pending_rsolicit *
135 solicit_find_entry ( struct net_device *netdev ) {
136 struct pending_rsolicit *entry;
138 for ( entry = solicit_table ; entry < solicit_table_end ; entry++ ) {
139 if ( ( entry->netdev == netdev ) &&
140 ( entry->state == RSOLICIT_STATE_PENDING ) ) {
141 return entry;
144 return NULL;
148 * Add NDP entry
150 * @v netdev Network device
151 * @v in6 IP6 address
152 * @v ll_addr Link-layer address
153 * @v state State of the entry - one of the NDP_STATE_XXX values
155 static void
156 add_ndp_entry ( struct net_device *netdev, struct in6_addr *in6,
157 void *ll_addr, int state ) {
158 struct ndp_entry *ndp;
159 ndp = &ndp_table[next_new_ndp_entry++ % NUM_NDP_ENTRIES];
161 /* Fill up entry */
162 ndp->ll_protocol = netdev->ll_protocol;
163 memcpy ( &ndp->in6, &( *in6 ), sizeof ( *in6 ) );
164 if ( ll_addr ) {
165 memcpy ( ndp->ll_addr, ll_addr, netdev->ll_protocol->ll_addr_len );
166 } else {
167 memset ( ndp->ll_addr, 0, netdev->ll_protocol->ll_addr_len );
169 ndp->state = state;
170 DBG ( "New neighbour cache entry: IP6 %s => %s %s\n",
171 inet6_ntoa ( ndp->in6 ), netdev->ll_protocol->name,
172 netdev->ll_protocol->ntoa ( ndp->ll_addr ) );
176 * Add pending solicit entry
178 * @v netdev Network device
179 * @v state State of the entry - one of the RSOLICIT_STATE_XXX values
181 static struct pending_rsolicit *
182 add_solicit_entry ( struct net_device *netdev, int state ) {
183 struct pending_rsolicit *entry;
184 entry = &solicit_table[next_new_solicit_entry++ % NUM_NDP_ENTRIES];
186 /* Fill up entry */
187 entry->netdev = netdev;
188 entry->state = state;
189 entry->code = RSOLICIT_CODE_NONE;
190 entry->meta = NULL;
192 return entry;
196 * Resolve the link-layer address
198 * @v netdev Network device
199 * @v dest Destination address
200 * @v src Source address
201 * @ret dest_ll_addr Destination link-layer address or NULL
202 * @ret rc Status
204 * This function looks up the neighbour cache for an entry corresponding to the
205 * destination address. If it finds a valid entry, it fills up dest_ll_addr and
206 * returns 0. Otherwise it sends a neighbour solicitation to the solicited
207 * multicast address.
209 int ndp_resolve ( struct net_device *netdev, struct in6_addr *dest,
210 struct in6_addr *src, void *dest_ll_addr ) {
211 struct ll_protocol *ll_protocol = netdev->ll_protocol;
212 struct ndp_entry *ndp;
213 int rc;
215 ndp = ndp_find_entry ( dest );
216 /* Check if the entry is valid */
217 if ( ndp && ndp->state == NDP_STATE_REACHABLE ) {
218 DBG ( "Neighbour cache hit: IP6 %s => %s %s\n",
219 inet6_ntoa ( *dest ), ll_protocol->name,
220 ll_protocol->ntoa ( ndp->ll_addr ) );
221 memcpy ( dest_ll_addr, ndp->ll_addr, ll_protocol->ll_addr_len );
222 return 0;
225 /* Check if the entry was already created */
226 if ( ndp ) {
227 DBG ( "Awaiting neighbour advertisement\n" );
228 /* For test */
229 // ndp->state = NDP_STATE_REACHABLE;
230 // memcpy ( ndp->ll_addr, netdev->ll_addr, 6 );
231 // assert ( ndp->ll_protocol->ll_addr_len == 6 );
232 // icmp6_test_nadvert ( netdev, dest, ndp->ll_addr );
233 // assert ( ndp->state == NDP_STATE_REACHABLE );
234 /* Take it out till here */
235 return -ENOENT;
237 DBG ( "Neighbour cache miss: IP6 %s\n", inet6_ntoa ( *dest ) );
239 /* Add entry in the neighbour cache */
240 add_ndp_entry ( netdev, dest, NULL, NDP_STATE_INCOMPLETE );
242 /* Send neighbour solicitation */
243 if ( ( rc = icmp6_send_solicit ( netdev, src, dest ) ) != 0 ) {
244 return rc;
246 return -ENOENT;
250 * Send router solicitation packet
252 * @v netdev Network device
253 * @v src Source address
254 * @v meta (optional) Pointer to struct to fill with information
255 * when an advertisement arrives.
256 * @v dest Destination address
258 * This function prepares a neighbour solicitation packet and sends it to the
259 * network layer.
261 int ndp_send_rsolicit ( struct net_device *netdev,
262 struct job_interface *job,
263 struct rsolicit_info *meta ) {
264 union {
265 struct sockaddr_in6 sin6;
266 struct sockaddr_tcpip st;
267 } st_dest;
268 struct router_solicit *solicit;
269 struct io_buffer *iobuf = alloc_iob ( sizeof ( *solicit ) + MIN_IOB_LEN );
270 struct pending_rsolicit *entry;
271 struct ll_option *ll;
272 int rc = 0;
274 iob_reserve ( iobuf, MAX_HDR_LEN );
275 solicit = iob_put ( iobuf, sizeof ( *solicit ) );
276 ll = iob_put ( iobuf, sizeof ( *ll ) );
278 /* Fill up the headers */
279 memset ( solicit, 0, sizeof ( *solicit ) );
280 solicit->type = ICMP6_ROUTER_SOLICIT;
281 solicit->code = 0;
283 /* Add our link-local address. */
284 ll->type = NDP_OPTION_SOURCE_LL;
285 ll->length = ( netdev->ll_protocol->ll_addr_len + 2 ) / 8;
286 memcpy ( ll->address, netdev->ll_addr, ll->length );
288 /* Partial checksum */
289 solicit->csum = 0;
290 solicit->csum = tcpip_chksum ( iobuf->data, iob_len ( iobuf ) );
292 /* Solicited multicast address - FF02::2 (all routers on local network) */
293 memset(&st_dest.sin6, 0, sizeof(st_dest.sin6));
294 st_dest.sin6.sin_family = AF_INET6;
295 st_dest.sin6.sin6_addr.in6_u.u6_addr8[0] = 0xff;
296 st_dest.sin6.sin6_addr.in6_u.u6_addr8[1] = 0x2;
297 st_dest.sin6.sin6_addr.in6_u.u6_addr8[15] = 0x2;
299 /* Add an entry for this solicitation. */
300 entry = add_solicit_entry ( netdev, RSOLICIT_STATE_ALMOST );
301 entry->meta = meta;
303 /* Set up a job for the solicit. */
304 job_init ( &entry->job, &rsolicit_job_operations, &entry->refcnt );
305 timer_init ( &entry->timer, rsolicit_timer_expired );
307 /* Set up the retry timer. */
308 stop_timer ( &entry->timer );
309 entry->timer.max_timeout = entry->timer.min_timeout = TICKS_PER_SEC * 6;
310 start_timer ( &entry->timer );
312 /* Send packet over IP6 */
313 rc = ipv6_tx ( iobuf, &icmp6_protocol, NULL, &st_dest.st,
314 netdev, &solicit->csum );
316 /* Return. */
317 if ( rc == 0 ) {
318 entry->state = RSOLICIT_STATE_PENDING;
320 job_plug_plug ( &entry->job, job );
321 ref_put ( &entry->refcnt );
322 return 0;
323 } else {
324 entry->state = RSOLICIT_STATE_INVALID;
326 rsolicit_job_kill ( &entry->job );
327 ref_put ( &entry->refcnt );
328 return rc;
333 * Process Router Advertisement
335 * @v iobuf I/O buffer containing the data.
336 * @v st_src Address of the source station.
337 * @v st_dest Address of the destination station. Typically FF02::1.
339 int ndp_process_radvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
340 struct sockaddr_tcpip *st_dest __unused, struct net_device *netdev,
341 struct icmp6_net_protocol *net_protocol __unused ) {
342 struct router_advert *radvert = iobuf->data;
343 struct ndp_option *options = iobuf->data + sizeof(struct router_advert);
344 struct in6_addr router_addr = ( ( struct sockaddr_in6 * ) st_src )->sin6_addr;
345 struct in6_addr host_addr, prefix;
346 int rc = -ENOENT;
347 uint8_t prefix_len = 0;
348 size_t offset = sizeof ( struct router_advert ), ll_size;
349 int can_autoconf = 0; /* Can we autoconfigure from the prefix? */
351 /* Verify that there's a pending solicit */
352 struct pending_rsolicit *pending = solicit_find_entry ( netdev );
353 if ( ! pending ) {
354 DBG ( "ndp: unsolicited router advertisement, ignoring\n" );
355 return rc;
358 /* Stop retry timer - we'll complete the job no matter what happens. */
359 stop_timer ( &pending->timer );
361 memset ( &host_addr, 0, sizeof ( host_addr ) );
363 /* Router advertisement flags */
364 if ( radvert->rsvd_flags & RADVERT_MANAGED ) {
365 DBG ( "ndp: router advertisement suggests DHCPv6\n" );
366 pending->code |= RSOLICIT_CODE_MANAGED;
368 if ( radvert->rsvd_flags & RADVERT_OTHERCONF ) {
369 DBG ( "ndp: router advertisement suggests DHCPv6 for additional information\n" );
370 pending->code |= RSOLICIT_CODE_OTHERCONF;
373 /* Parse options. */
374 while ( offset < iob_len( iobuf ) ) {
376 switch ( options->type ) {
377 case NDP_OPTION_PREFIX_INFO:
379 struct prefix_option *opt = (struct prefix_option *) options;
381 prefix_len = opt->prefix_len;
383 if ( prefix_len % 8 ) {
384 /* Copy one extra prefix byte. */
385 prefix_len += 8;
388 if ( prefix_len > 64 ) {
389 /* > 64-bit prefix shouldn't happen. */
390 DBG ( "ndp: prefix length is quite long, connectivity may suffer.\n" );
393 /* Copy the prefix first and then add the EUI-64 */
394 memcpy ( &prefix.s6_addr, opt->prefix, prefix_len / 8 );
395 memcpy ( &host_addr.s6_addr, &prefix.s6_addr, prefix_len / 8 );
397 /* Create an IPv6 address for this station based on the prefix. */
398 ll_size = netdev->ll_protocol->ll_addr_len;
399 if ( ll_size < 6 ) {
400 memcpy ( host_addr.s6_addr + (8 - ll_size), netdev->ll_addr, ll_size );
401 } else {
402 ipv6_generate_eui64 ( host_addr.s6_addr + 8, netdev->ll_addr );
405 /* Get flags. */
406 can_autoconf = opt->flags_rsvd & ( 1 << 6 );
407 if ( ! can_autoconf )
408 DBG ( "ndp: got a prefix, but can't use it for SLAAC\n" );
409 else
410 DBG ( "ndp: can use prefix for SLAAC\n" );
412 rc = 0;
414 break;
416 case NDP_OPTION_SOURCE_LL:
418 struct ll_option *opt = (struct ll_option *) options;
420 /* Add entry in the neighbour cache for the router */
421 if ( ! ndp_find_entry ( &router_addr ) ) {
422 add_ndp_entry ( netdev, &router_addr, opt->address, NDP_STATE_REACHABLE );
426 break;
429 offset += options->length * 8;
430 options = (struct ndp_option *) (iobuf->data + offset);
433 if ( rc ) {
434 DBG ( "ndp: couldn't generate a prefix from a router advertisement\n" );
435 pending->code = RSOLICIT_CODE_NONE; /* Clear flags. */
437 job_done ( &pending->job, -ENOENT );
439 return 0;
442 /* Fill in information if we need to. */
443 if ( pending->meta != NULL ) {
444 DBG ( "ndp: filling meta information\n" );
445 pending->meta->router = router_addr;
446 pending->meta->prefix = prefix;
447 pending->meta->prefix_length = prefix_len;
448 pending->meta->no_address = ! can_autoconf;
451 /* Configure a route based on this router if none exists. */
452 if ( can_autoconf && net_protocol->check ( netdev, &host_addr ) ) {
453 DBG ( "ndp: autoconfigured %s/%d via a router advertisement\n", inet6_ntoa( host_addr ), prefix_len);
455 add_ipv6_address ( netdev, prefix, prefix_len, host_addr, router_addr );
458 /* Completed without error. */
459 job_done ( &pending->job, pending->code );
460 pending->state = RSOLICIT_STATE_INVALID;
462 return 0;
466 * Process neighbour advertisement
468 * @v iobuf I/O buffer
469 * @v st_src Source address
470 * @v st_dest Destination address
472 int ndp_process_nadvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused,
473 struct sockaddr_tcpip *st_dest __unused,
474 struct icmp6_net_protocol *net_protocol __unused ) {
475 struct neighbour_advert *nadvert = iobuf->data;
476 struct ll_option *ll_opt = iobuf->data + sizeof ( *nadvert );
477 struct ndp_entry *ndp;
479 /* Sanity check */
480 if ( iob_len ( iobuf ) < sizeof ( *nadvert ) ) {
481 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
482 return -EINVAL;
485 /* FIXME: assumes link-layer option is first. */
487 assert ( nadvert->code == 0 );
488 assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED );
489 assert ( ll_opt->type == 2 );
491 /* Update the neighbour cache, if entry is present */
492 ndp = ndp_find_entry ( &nadvert->target );
493 if ( ndp ) {
495 assert ( ll_opt->length ==
496 ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) );
498 if ( IP6_EQUAL ( ndp->in6, nadvert->target ) ) {
499 memcpy ( ndp->ll_addr, ll_opt->address,
500 ndp->ll_protocol->ll_addr_len );
501 ndp->state = NDP_STATE_REACHABLE;
502 return 0;
505 DBG ( "Unsolicited advertisement (dropping packet)\n" );
506 return 0;
510 * Process neighbour solicitation
512 * @v iobuf I/O buffer
513 * @v st_src Source address
514 * @v st_dest Destination address
515 * @v netdev Network device the packet was received on.
517 int ndp_process_nsolicit ( struct io_buffer *iobuf __unused, struct sockaddr_tcpip *st_src,
518 struct sockaddr_tcpip *st_dest __unused, struct net_device *netdev,
519 struct icmp6_net_protocol *net_protocol ) {
520 struct neighbour_solicit *nsolicit = iobuf->data;
521 struct in6_addr *src = &( ( struct sockaddr_in6 * ) st_src )->sin6_addr;
523 /* Does this match any addresses on the interface? */
524 if ( ! net_protocol->check ( netdev, &nsolicit->target ) ) {
525 /* Send an advertisement to the host. */
526 DBG ( "ndp: neighbour solicit received for us\n" );
527 return icmp6_send_advert ( netdev, &nsolicit->target, src );
528 } else {
529 DBG ( "ndp: neighbour solicit received but it's not for us\n" );
532 return 0;