[ipv6] Fix router solicitation struct and flag checking
[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 = 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? */
350         
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;
356         }
357         
358         /* Stop retry timer - we'll complete the job no matter what happens. */
359         stop_timer ( &pending->timer );
360
361         memset ( &host_addr, 0, sizeof ( host_addr ) );
362         
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;
367         }
368         if ( radvert->rsvd_flags & RADVERT_OTHERCONF ) {
369                 DBG ( "ndp: router advertisement suggests DHCPv6 for additional information\n" );
370                 pending->code |= RSOLICIT_CODE_OTHERCONF;
371         }
372
373         /* Parse options. */
374         while ( offset < iob_len( iobuf ) ) {
375
376                 switch ( options->type ) {
377                 case NDP_OPTION_PREFIX_INFO:
378                         {
379                         struct prefix_option *opt = (struct prefix_option *) options;
380
381                         prefix_len = opt->prefix_len;
382
383                         if ( prefix_len % 8 ) {
384                                 /* Copy one extra prefix byte. */
385                                 prefix_len += 8;
386                         }
387
388                         if ( prefix_len > 64 ) {
389                                 /* > 64-bit prefix shouldn't happen. */
390                                 DBG ( "ndp: prefix length is quite long, connectivity may suffer.\n" );
391                         }
392
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 );
396
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 );
403                         }
404                         
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" );
411
412                         rc = 0;
413                         }
414                         break;
415
416                 case NDP_OPTION_SOURCE_LL:
417                         {
418                         struct ll_option *opt = (struct ll_option *) options;
419
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 );
423                         }
424
425                         }
426                         break;
427                 }
428
429                 offset += options->length * 8;
430                 options = (struct ndp_option *) (iobuf->data + offset);
431         }
432
433         if ( rc ) {
434                 DBG ( "ndp: couldn't generate a prefix from a router advertisement\n" );
435                 pending->code = RSOLICIT_CODE_NONE; /* Clear flags. */
436                 
437                 job_done ( &pending->job, -ENOENT );
438                 
439                 return 0;
440         }
441         
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;
449         }
450
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);
454
455                 add_ipv6_address ( netdev, prefix, prefix_len, host_addr, router_addr );
456         }
457         
458         /* Completed without error. */
459         job_done ( &pending->job, pending->code );
460         pending->state = RSOLICIT_STATE_INVALID;
461
462         return 0;
463 }
464
465 /**
466  * Process neighbour advertisement
467  *
468  * @v iobuf     I/O buffer
469  * @v st_src    Source address
470  * @v st_dest   Destination address
471  */
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;
478
479         /* Sanity check */
480         if ( iob_len ( iobuf ) < sizeof ( *nadvert ) ) {
481                 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
482                 return -EINVAL;
483         }
484
485         /* FIXME: assumes link-layer option is first. */
486
487         assert ( nadvert->code == 0 );
488         assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED );
489         assert ( ll_opt->type == 2 );
490
491         /* Update the neighbour cache, if entry is present */
492         ndp = ndp_find_entry ( &nadvert->target );
493         if ( ndp ) {
494
495         assert ( ll_opt->length ==
496                         ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) );
497
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;
503                 }
504         }
505         DBG ( "Unsolicited advertisement (dropping packet)\n" );
506         return 0;
507 }
508
509 /**
510  * Process neighbour solicitation
511  *
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.
516  */
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;
522
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" );
530         }
531
532         return 0;
533 }
534