[ipv6] Change return code handling from router solicits
[people/meteger/gpxe.git] / src / net / ndp.c
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>
15
16 /** @file
17  *
18  * Neighbour Discovery Protocol
19  *
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.
23  */
24
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;
35 };
36
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;
53 };
54
55 /** Number of entries in the neighbour cache table */
56 #define NUM_NDP_ENTRIES 4
57
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]
61
62 static unsigned int next_new_ndp_entry = 0;
63
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]
67
68 static unsigned int next_new_solicit_entry = 0;
69
70 /**
71  * Handle kill() event received via job control interface
72  *
73  * @v job               Router solicit job control interface
74  */
75 static void rsolicit_job_kill ( struct job_interface *job ) {
76         struct pending_rsolicit *entry =
77                 container_of ( job, struct pending_rsolicit, job );
78
79         /* Terminate. */
80         entry->code = 0;
81         entry->state = RSOLICIT_STATE_INVALID;
82
83         /* Stop retry timer */
84         stop_timer ( &entry->timer );
85
86         /* Clean up. */
87         job_nullify ( &entry->job );
88         job_done ( &entry->job, -ECANCELED );
89 }
90
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,
96 };
97
98 /**
99  * Handle router solicitation timeout.
100  * @v timer     Solicitation retry timer.
101  * @v fail      Failure indicator.
102  */
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 );
106
107         /* Don't bother retrying. */
108         rsolicit_job_kill ( &entry->job );
109 }
110
111 /**
112  * Find entry in the neighbour cache
113  *
114  * @v in6       IP6 address
115  */
116 static struct ndp_entry *
117 ndp_find_entry ( struct in6_addr *in6 ) {
118         struct ndp_entry *ndp;
119
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;
124                 }
125         }
126         return NULL;
127 }
128
129 /**
130  * Find a pending router solicitation for an interface.
131  *
132  * @v netdev    Interface for the solicitation.
133  */
134 static struct pending_rsolicit *
135 solicit_find_entry ( struct net_device *netdev ) {
136         struct pending_rsolicit *entry;
137
138         for ( entry = solicit_table ; entry < solicit_table_end ; entry++ ) {
139                 if ( ( entry->netdev == netdev ) &&
140                      ( entry->state == RSOLICIT_STATE_PENDING ) ) {
141                         return entry;
142                 }
143         }
144         return NULL;
145 }
146
147 /**
148  * Add NDP entry
149  *
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
154  */
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];
160
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 );
168         }
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 ) );
173 }
174
175 /**
176  * Add pending solicit entry
177  *
178  * @v netdev    Network device
179  * @v state     State of the entry - one of the RSOLICIT_STATE_XXX values
180  */
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];
185
186         /* Fill up entry */
187         entry->netdev = netdev;
188         entry->state = state;
189         entry->code = RSOLICIT_CODE_NONE;
190         entry->meta = NULL;
191
192         return entry;
193 }
194
195 /**
196  * Resolve the link-layer address
197  *
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
203  *
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.
208  */
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;
214
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;
223         }
224
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;
236         }
237         DBG ( "Neighbour cache miss: IP6 %s\n", inet6_ntoa ( *dest ) );
238
239         /* Add entry in the neighbour cache */
240         add_ndp_entry ( netdev, dest, NULL, NDP_STATE_INCOMPLETE );
241
242         /* Send neighbour solicitation */
243         if ( ( rc = icmp6_send_solicit ( netdev, src, dest ) ) != 0 ) {
244                 return rc;
245         }
246         return -ENOENT;
247 }
248
249 /**
250  * Send router solicitation packet
251  *
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
257  *
258  * This function prepares a neighbour solicitation packet and sends it to the
259  * network layer.
260  */
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;
273
274         iob_reserve ( iobuf, MAX_HDR_LEN );
275         solicit = iob_put ( iobuf, sizeof ( *solicit ) );
276         ll = iob_put ( iobuf, sizeof ( *ll ) );
277
278         /* Fill up the headers */
279         memset ( solicit, 0, sizeof ( *solicit ) );
280         solicit->type = ICMP6_ROUTER_SOLICIT;
281         solicit->code = 0;
282
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 );
287
288         /* Partial checksum */
289         solicit->csum = 0;
290         solicit->csum = tcpip_chksum ( iobuf->data, iob_len ( iobuf ) );
291
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;
298
299         /* Add an entry for this solicitation. */
300         entry = add_solicit_entry ( netdev, RSOLICIT_STATE_ALMOST );
301         entry->meta = meta;
302
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 );
306
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 );
311
312         /* Send packet over IP6 */
313         rc = ipv6_tx ( iobuf, &icmp6_protocol, NULL, &st_dest.st,
314                        netdev, &solicit->csum );
315
316         /* Return. */
317         if ( rc == 0 ) {
318                 entry->state = RSOLICIT_STATE_PENDING;
319
320                 job_plug_plug ( &entry->job, job );
321                 ref_put ( &entry->refcnt );
322                 return 0;
323         } else {
324                 entry->state = RSOLICIT_STATE_INVALID;
325
326                 rsolicit_job_kill ( &entry->job );
327                 ref_put ( &entry->refcnt );
328                 return rc;
329         }
330 }
331
332 /**
333  * Process Router Advertisement
334  *
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.
338  */
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;
343         struct ndp_option *opt;
344         struct prefix_option *prefix_opt;
345         struct ll_option *ll_opt;
346         struct in6_addr router_addr = ( ( struct sockaddr_in6 * ) st_src )->sin6_addr;
347         struct in6_addr host_addr, prefix;
348         uint8_t prefix_len = 0;
349         size_t ll_size;
350         int can_autoconf = 0; /* Can we autoconfigure from the prefix? */
351         int rc = -ENOENT;
352
353         /* Verify that there's a pending solicit */
354         struct pending_rsolicit *pending = solicit_find_entry ( netdev );
355         if ( ! pending ) {
356                 DBG ( "ndp: unsolicited router advertisement, ignoring\n" );
357                 return rc;
358         }
359
360         /* Stop retry timer - we'll complete the job no matter what happens. */
361         stop_timer ( &pending->timer );
362
363         /* Grab the header. */
364         radvert = iobuf->data;
365         opt = iob_pull ( iobuf, sizeof ( *radvert ) );
366
367         memset ( &host_addr, 0, sizeof ( host_addr ) );
368
369         /* Router advertisement flags */
370         if ( radvert->rsvd_flags & RADVERT_MANAGED ) {
371                 DBG ( "ndp: router advertisement suggests DHCPv6\n" );
372                 pending->code |= RSOLICIT_CODE_MANAGED;
373         }
374         if ( radvert->rsvd_flags & RADVERT_OTHERCONF ) {
375                 DBG ( "ndp: router advertisement suggests DHCPv6 for additional information\n" );
376                 pending->code |= RSOLICIT_CODE_OTHERCONF;
377         }
378
379         /* Parse options. */
380         while ( iob_len( iobuf ) ) {
381
382                 switch ( opt->type ) {
383                 case NDP_OPTION_PREFIX_INFO:
384                         {
385                         prefix_opt = (struct prefix_option *) opt;
386
387                         prefix_len = prefix_opt->prefix_len;
388
389                         if ( prefix_len % 8 ) {
390                                 /* Copy one extra prefix byte. */
391                                 prefix_len += 8;
392                         }
393
394                         if ( prefix_len > 64 ) {
395                                 /* > 64-bit prefix shouldn't happen. */
396                                 DBG ( "ndp: prefix length is quite long, connectivity may suffer.\n" );
397                         }
398
399                         /* Copy the prefix first and then add the EUI-64 */
400                         memcpy ( &prefix.s6_addr, prefix_opt->prefix, prefix_len / 8 );
401                         memcpy ( &host_addr.s6_addr, &prefix.s6_addr, prefix_len / 8 );
402
403                         /* Create an IPv6 address for this station based on the prefix. */
404                         ll_size = netdev->ll_protocol->ll_addr_len;
405                         if ( ll_size < 6 ) {
406                                 memcpy ( host_addr.s6_addr + (8 - ll_size), netdev->ll_addr, ll_size );
407                         } else {
408                                 ipv6_generate_eui64 ( host_addr.s6_addr + 8, netdev->ll_addr );
409                         }
410
411                         /* Get flags. */
412                         can_autoconf = prefix_opt->flags_rsvd & ( 1 << 6 );
413                         if ( ! can_autoconf )
414                                 DBG ( "ndp: got a prefix, but can't use it for SLAAC\n" );
415                         else
416                                 DBG ( "ndp: can use prefix for SLAAC\n" );
417
418                         rc = 0;
419                         }
420                         break;
421
422                 case NDP_OPTION_SOURCE_LL:
423                         {
424                         ll_opt = (struct ll_option *) opt;
425
426                         /* Add entry in the neighbour cache for the router */
427                         if ( ! ndp_find_entry ( &router_addr ) ) {
428                                 add_ndp_entry ( netdev, &router_addr, ll_opt->address, NDP_STATE_REACHABLE );
429                         }
430
431                         }
432                         break;
433                 }
434
435                 opt = iob_pull ( iobuf, opt->length * 8 );
436         }
437
438         if ( rc ) {
439                 DBG ( "ndp: couldn't generate a prefix from a router advertisement\n" );
440                 pending->code = RSOLICIT_CODE_NONE; /* Clear flags. */
441
442                 job_done ( &pending->job, -ENOENT );
443
444                 return 0;
445         }
446
447         /* Fill in information if we need to. */
448         if ( pending->meta != NULL ) {
449                 DBG ( "ndp: filling meta information\n" );
450                 pending->meta->router = router_addr;
451                 pending->meta->prefix = prefix;
452                 pending->meta->prefix_length = prefix_len;
453                 pending->meta->no_address = ! can_autoconf;
454                 pending->meta->flags = pending->code;
455         }
456
457         /* Configure a route based on this router if none exists. */
458         if ( can_autoconf && net_protocol->check ( netdev, &host_addr ) ) {
459                 DBG ( "ndp: autoconfigured %s/%d via a router advertisement\n", inet6_ntoa( host_addr ), prefix_len);
460
461                 add_ipv6_address ( netdev, prefix, prefix_len, host_addr, router_addr );
462         }
463
464         /* Completed without error. */
465         job_done ( &pending->job, 0 );
466         pending->state = RSOLICIT_STATE_INVALID;
467
468         return 0;
469 }
470
471 /**
472  * Process neighbour advertisement
473  *
474  * @v iobuf     I/O buffer
475  * @v st_src    Source address
476  * @v st_dest   Destination address
477  */
478 int ndp_process_nadvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused,
479                            struct sockaddr_tcpip *st_dest __unused,
480                            struct icmp6_net_protocol *net_protocol __unused ) {
481         struct neighbour_advert *nadvert = iobuf->data;
482         struct ll_option *ll_opt;
483         struct ndp_entry *ndp;
484
485         /* Sanity check */
486         if ( iob_len ( iobuf ) < ( sizeof ( *nadvert ) + sizeof ( *ll_opt ) ) ) {
487                 DBG ( "ndp: neighbour advert packet too short (%zd bytes)\n", iob_len ( iobuf ) );
488                 return -EINVAL;
489         }
490
491         /* Grab the first option, ready for option parsing. */
492         ll_opt = iob_pull ( iobuf, sizeof ( *nadvert ) );
493
494         /* Check for the solicited flag - we don't want generic announcements. */
495         if ( ! ( nadvert->flags & ICMP6_FLAGS_SOLICITED ) ) {
496                 DBG ( "ndp: unsolicited neighbour advertisement, ignoring\n" );
497                 return 0;
498         }
499
500         /* Check for an existing entry in the cache that we can update. */
501         ndp = ndp_find_entry ( &nadvert->target );
502         if ( ndp ) {
503                 /* Verify the packet is actually a response to this entry. */
504                 if ( ! IP6_EQUAL ( ndp->in6, nadvert->target ) ) {
505                         DBG ( "ndp: advert for unknown target\n" );
506                         return -EINVAL;
507                 }
508
509                 /* Parse options, looking for "target link-layer address". */
510                 while ( iob_len ( iobuf ) ) {
511                         if ( ll_opt->type == NDP_OPTION_TARGET_LL ) {
512                                 /* Check the length for validity. */
513                                 if ( ll_opt->length ==
514                                         ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) {
515                                         memcpy ( ndp->ll_addr, ll_opt->address,
516                                                  ndp->ll_protocol->ll_addr_len );
517                                         ndp->state = NDP_STATE_REACHABLE;
518                                         break;
519                                 }
520                         }
521
522                         ll_opt = iob_pull ( iobuf, ll_opt->length * 8 );
523                 }
524         } else {
525                 DBG ( "Unsolicited advertisement (dropping packet)\n" );
526         }
527         return 0;
528 }
529
530 /**
531  * Process neighbour solicitation
532  *
533  * @v iobuf     I/O buffer
534  * @v st_src    Source address
535  * @v st_dest   Destination address
536  * @v netdev    Network device the packet was received on.
537  */
538 int ndp_process_nsolicit ( struct io_buffer *iobuf __unused, struct sockaddr_tcpip *st_src,
539                            struct sockaddr_tcpip *st_dest __unused, struct net_device *netdev,
540                            struct icmp6_net_protocol *net_protocol ) {
541         struct neighbour_solicit *nsolicit = iobuf->data;
542         struct in6_addr *src =  &( ( struct sockaddr_in6 * ) st_src )->sin6_addr;
543
544         /* Does this match any addresses on the interface? */
545         if ( ! net_protocol->check ( netdev, &nsolicit->target ) ) {
546                 /* Send an advertisement to the host. */
547                 DBG ( "ndp: neighbour solicit received for us\n" );
548                 return icmp6_send_advert ( netdev, &nsolicit->target, src );
549         } else {
550                 DBG ( "ndp: neighbour solicit received but it's not for us\n" );
551         }
552
553         return 0;
554 }
555