[ipv6] Enable router solicitations to timeout
[gpxe.git] / src / net / ndp.c
blobbf78f021fdc4f576681ca91077c601a8dd770ae7
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 int rc = 0;
273 iob_reserve ( iobuf, MAX_HDR_LEN );
274 solicit = iob_put ( iobuf, sizeof ( *solicit ) );
276 /* Fill up the headers */
277 memset ( solicit, 0, sizeof ( *solicit ) );
278 solicit->type = ICMP6_ROUTER_SOLICIT;
279 solicit->code = 0;
281 /* Partial checksum */
282 solicit->csum = 0;
283 solicit->csum = tcpip_chksum ( solicit, sizeof ( *solicit ) );
285 /* Solicited multicast address - FF02::2 (all routers on local network) */
286 memset(&st_dest.sin6, 0, sizeof(st_dest.sin6));
287 st_dest.sin6.sin_family = AF_INET6;
288 st_dest.sin6.sin6_addr.in6_u.u6_addr8[0] = 0xff;
289 st_dest.sin6.sin6_addr.in6_u.u6_addr8[1] = 0x2;
290 st_dest.sin6.sin6_addr.in6_u.u6_addr8[15] = 0x2;
292 /* Add an entry for this solicitation. */
293 entry = add_solicit_entry ( netdev, RSOLICIT_STATE_ALMOST );
294 entry->meta = meta;
296 /* Set up a job for the solicit. */
297 job_init ( &entry->job, &rsolicit_job_operations, &entry->refcnt );
298 timer_init ( &entry->timer, rsolicit_timer_expired );
300 /* Set up the retry timer. */
301 stop_timer ( &entry->timer );
302 entry->timer.min_timeout = 0;
303 entry->timer.max_timeout = 5;
304 start_timer ( &entry->timer );
306 /* Send packet over IP6 */
307 rc = ipv6_tx ( iobuf, &icmp6_protocol, NULL, &st_dest.st,
308 netdev, &solicit->csum );
310 /* Return. */
311 if ( rc == 0 ) {
312 entry->state = RSOLICIT_STATE_PENDING;
314 job_plug_plug ( &entry->job, job );
315 ref_put ( &entry->refcnt );
316 return 0;
317 } else {
318 entry->state = RSOLICIT_STATE_INVALID;
320 rsolicit_job_kill ( &entry->job );
321 ref_put ( &entry->refcnt );
322 return rc;
327 * Process Router Advertisement
329 * @v iobuf I/O buffer containing the data.
330 * @v st_src Address of the source station.
331 * @v st_dest Address of the destination station. Typically FF02::1.
333 int ndp_process_radvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
334 struct sockaddr_tcpip *st_dest __unused, struct net_device *netdev,
335 struct icmp6_net_protocol *net_protocol __unused ) {
336 struct router_advert *radvert = iobuf->data;
337 struct ndp_option *options = iobuf->data + sizeof(struct router_advert);
338 struct in6_addr router_addr = ( ( struct sockaddr_in6 * ) st_src )->sin6_addr;
339 struct in6_addr host_addr, prefix;
340 int rc = -ENOENT;
341 uint8_t prefix_len = 0;
342 size_t offset = sizeof ( struct router_advert ), ll_size;
343 int can_autoconf = 0; /* Can we autoconfigure from the prefix? */
345 /* Verify that there's a pending solicit */
346 struct pending_rsolicit *pending = solicit_find_entry ( netdev );
347 if ( ! pending ) {
348 DBG ( "ndp: unsolicited router advertisement, ignoring\n" );
349 return rc;
352 /* Stop retry timer - we'll complete the job no matter what happens. */
353 stop_timer ( &pending->timer );
355 memset ( &host_addr, 0, sizeof ( host_addr ) );
357 /* Router advertisement flags */
358 if ( ntohs ( radvert->hops_flags ) & RADVERT_MANAGED ) {
359 DBG ( "ndp: router advertisement suggests DHCPv6\n" );
360 pending->code |= RSOLICIT_CODE_MANAGED;
362 if ( ntohs ( radvert->hops_flags ) & RADVERT_OTHERCONF ) {
363 DBG ( "ndp: router advertisement suggests DHCPv6 for additional information\n" );
364 pending->code |= RSOLICIT_CODE_OTHERCONF;
367 /* Parse options. */
368 while ( offset < iob_len( iobuf ) ) {
370 switch ( options->type ) {
371 case NDP_OPTION_PREFIX_INFO:
373 struct prefix_option *opt = (struct prefix_option *) options;
375 prefix_len = opt->prefix_len;
377 if ( prefix_len % 8 ) {
378 /* Copy one extra prefix byte. */
379 prefix_len += 8;
382 if ( prefix_len > 64 ) {
383 /* > 64-bit prefix shouldn't happen. */
384 DBG ( "ndp: prefix length is quite long, connectivity may suffer.\n" );
387 /* Copy the prefix first and then add the EUI-64 */
388 memcpy ( &prefix.s6_addr, opt->prefix, prefix_len / 8 );
389 memcpy ( &host_addr.s6_addr, &prefix.s6_addr, prefix_len / 8 );
391 /* Create an IPv6 address for this station based on the prefix. */
392 ll_size = netdev->ll_protocol->ll_addr_len;
393 if ( ll_size < 6 ) {
394 memcpy ( host_addr.s6_addr + (8 - ll_size), netdev->ll_addr, ll_size );
395 } else {
396 ipv6_generate_eui64 ( host_addr.s6_addr + 8, netdev->ll_addr );
399 /* Get flags. */
400 can_autoconf = opt->flags_rsvd & ( 1 << 6 );
401 if ( ! can_autoconf )
402 DBG ( "ndp: got a prefix, but can't use it for SLAAC\n" );
403 else
404 DBG ( "ndp: can use prefix for SLAAC\n" );
406 rc = 0;
408 break;
410 case NDP_OPTION_SOURCE_LL:
412 struct ll_option *opt = (struct ll_option *) options;
414 /* Add entry in the neighbour cache for the router */
415 if ( ! ndp_find_entry ( &router_addr ) ) {
416 add_ndp_entry ( netdev, &router_addr, opt->address, NDP_STATE_REACHABLE );
420 break;
423 offset += options->length * 8;
424 options = (struct ndp_option *) (iobuf->data + offset);
427 if ( rc ) {
428 DBG ( "ndp: couldn't generate a prefix from a router advertisement\n" );
429 pending->code = RSOLICIT_CODE_NONE; /* Clear flags. */
431 job_done ( &pending->job, -ENOENT );
433 return 0;
436 /* Fill in information if we need to. */
437 if ( pending->meta != NULL ) {
438 DBG ( "ndp: filling meta information\n" );
439 pending->meta->router = router_addr;
440 pending->meta->prefix = prefix;
441 pending->meta->prefix_length = prefix_len;
442 pending->meta->no_address = ! can_autoconf;
445 /* Configure a route based on this router if none exists. */
446 if ( can_autoconf && net_protocol->check ( netdev, &host_addr ) ) {
447 DBG ( "ndp: autoconfigured %s/%d via a router advertisement\n", inet6_ntoa( host_addr ), prefix_len);
449 add_ipv6_address ( netdev, prefix, prefix_len, host_addr, router_addr );
452 /* Completed without error. */
453 job_done ( &pending->job, pending->code );
454 pending->state = RSOLICIT_STATE_INVALID;
456 return 0;
460 * Process neighbour advertisement
462 * @v iobuf I/O buffer
463 * @v st_src Source address
464 * @v st_dest Destination address
466 int ndp_process_nadvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused,
467 struct sockaddr_tcpip *st_dest __unused,
468 struct icmp6_net_protocol *net_protocol __unused ) {
469 struct neighbour_advert *nadvert = iobuf->data;
470 struct ll_option *ll_opt = iobuf->data + sizeof ( *nadvert );
471 struct ndp_entry *ndp;
473 /* Sanity check */
474 if ( iob_len ( iobuf ) < sizeof ( *nadvert ) ) {
475 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
476 return -EINVAL;
479 /* FIXME: assumes link-layer option is first. */
481 assert ( nadvert->code == 0 );
482 assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED );
483 assert ( ll_opt->type == 2 );
485 /* Update the neighbour cache, if entry is present */
486 ndp = ndp_find_entry ( &nadvert->target );
487 if ( ndp ) {
489 assert ( ll_opt->length ==
490 ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) );
492 if ( IP6_EQUAL ( ndp->in6, nadvert->target ) ) {
493 memcpy ( ndp->ll_addr, ll_opt->address,
494 ndp->ll_protocol->ll_addr_len );
495 ndp->state = NDP_STATE_REACHABLE;
496 return 0;
499 DBG ( "Unsolicited advertisement (dropping packet)\n" );
500 return 0;
504 * Process neighbour solicitation
506 * @v iobuf I/O buffer
507 * @v st_src Source address
508 * @v st_dest Destination address
509 * @v netdev Network device the packet was received on.
511 int ndp_process_nsolicit ( struct io_buffer *iobuf __unused, struct sockaddr_tcpip *st_src,
512 struct sockaddr_tcpip *st_dest __unused, struct net_device *netdev,
513 struct icmp6_net_protocol *net_protocol ) {
514 struct neighbour_solicit *nsolicit = iobuf->data;
515 struct in6_addr *src = &( ( struct sockaddr_in6 * ) st_src )->sin6_addr;
517 /* Does this match any addresses on the interface? */
518 if ( ! net_protocol->check ( netdev, &nsolicit->target ) ) {
519 /* Send an advertisement to the host. */
520 DBG ( "ndp: neighbour solicit received for us\n" );
521 return icmp6_send_advert ( netdev, &nsolicit->target, src );
522 } else {
523 DBG ( "ndp: neighbour solicit received but it's not for us\n" );
526 return 0;