6 #include <gpxe/if_ether.h>
7 #include <gpxe/iobuf.h>
9 #include <gpxe/icmp6.h>
11 #include <gpxe/netdevice.h>
12 #include <gpxe/retry.h>
13 #include <gpxe/timer.h>
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
25 /* A neighbour entry */
27 /** Target IP6 address */
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 */
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. */
43 /** Status code after handling the solicit. */
45 /** Job control interface */
46 struct job_interface job
;
47 /** Reference counter */
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;
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
);
81 entry
->state
= RSOLICIT_STATE_INVALID
;
83 /* Stop retry timer */
84 stop_timer ( &entry
->timer
);
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
,
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
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
) ) {
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
) ) {
150 * @v netdev Network device
152 * @v ll_addr Link-layer address
153 * @v state State of the entry - one of the NDP_STATE_XXX values
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
];
162 ndp
->ll_protocol
= netdev
->ll_protocol
;
163 memcpy ( &ndp
->in6
, &( *in6
), sizeof ( *in6
) );
165 memcpy ( ndp
->ll_addr
, ll_addr
, netdev
->ll_protocol
->ll_addr_len
);
167 memset ( ndp
->ll_addr
, 0, netdev
->ll_protocol
->ll_addr_len
);
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
];
187 entry
->netdev
= netdev
;
188 entry
->state
= state
;
189 entry
->code
= RSOLICIT_CODE_NONE
;
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
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
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
;
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
);
225 /* Check if the entry was already created */
227 DBG ( "Awaiting neighbour advertisement\n" );
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 */
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 ) {
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
261 int ndp_send_rsolicit ( struct net_device
*netdev
,
262 struct job_interface
*job
,
263 struct rsolicit_info
*meta
) {
265 struct sockaddr_in6 sin6
;
266 struct sockaddr_tcpip st
;
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
;
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
;
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 */
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
);
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
);
318 entry
->state
= RSOLICIT_STATE_PENDING
;
320 job_plug_plug ( &entry
->job
, job
);
321 ref_put ( &entry
->refcnt
);
324 entry
->state
= RSOLICIT_STATE_INVALID
;
326 rsolicit_job_kill ( &entry
->job
);
327 ref_put ( &entry
->refcnt
);
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
;
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
);
354 DBG ( "ndp: unsolicited router advertisement, ignoring\n" );
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
;
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. */
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
;
400 memcpy ( host_addr
.s6_addr
+ (8 - ll_size
), netdev
->ll_addr
, ll_size
);
402 ipv6_generate_eui64 ( host_addr
.s6_addr
+ 8, netdev
->ll_addr
);
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" );
410 DBG ( "ndp: can use prefix for SLAAC\n" );
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
);
429 offset
+= options
->length
* 8;
430 options
= (struct ndp_option
*) (iobuf
->data
+ offset
);
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
);
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
;
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
;
480 if ( iob_len ( iobuf
) < sizeof ( *nadvert
) ) {
481 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf
) );
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
);
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
;
505 DBG ( "Unsolicited advertisement (dropping packet)\n" );
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
);
529 DBG ( "ndp: neighbour solicit received but it's not for us\n" );