6 #include <gpxe/if_ether.h>
7 #include <gpxe/iobuf.h>
9 #include <gpxe/icmp6.h>
11 #include <gpxe/netdevice.h>
15 * Neighbour Discovery Protocol
17 * This file implements address resolution as specified by the neighbour
18 * discovery protocol in RFC2461. This protocol is part of the IPv6 protocol
22 /* A neighbour entry */
24 /** Target IP6 address */
26 /** Link layer protocol */
27 struct ll_protocol
*ll_protocol
;
28 /** Link-layer address */
29 uint8_t ll_addr
[MAX_LL_ADDR_LEN
];
30 /** State of the neighbour entry */
34 /** Number of entries in the neighbour cache table */
35 #define NUM_NDP_ENTRIES 4
37 /** The neighbour cache table */
38 static struct ndp_entry ndp_table
[NUM_NDP_ENTRIES
];
39 #define ndp_table_end &ndp_table[NUM_NDP_ENTRIES]
41 static unsigned int next_new_ndp_entry
= 0;
44 * Find entry in the neighbour cache
48 static struct ndp_entry
*
49 ndp_find_entry ( struct in6_addr
*in6
) {
50 struct ndp_entry
*ndp
;
52 for ( ndp
= ndp_table
; ndp
< ndp_table_end
; ndp
++ ) {
53 if ( IP6_EQUAL ( ( *in6
), ndp
->in6
) &&
54 ( ndp
->state
!= NDP_STATE_INVALID
) ) {
64 * @v netdev Network device
66 * @v ll_addr Link-layer address
67 * @v state State of the entry - one of the NDP_STATE_XXX values
70 add_ndp_entry ( struct net_device
*netdev
, struct in6_addr
*in6
,
71 void *ll_addr
, int state
) {
72 struct ndp_entry
*ndp
;
73 ndp
= &ndp_table
[next_new_ndp_entry
++ % NUM_NDP_ENTRIES
];
76 ndp
->ll_protocol
= netdev
->ll_protocol
;
77 memcpy ( &ndp
->in6
, &( *in6
), sizeof ( *in6
) );
79 memcpy ( ndp
->ll_addr
, ll_addr
, netdev
->ll_protocol
->ll_addr_len
);
81 memset ( ndp
->ll_addr
, 0, netdev
->ll_protocol
->ll_addr_len
);
84 DBG ( "New neighbour cache entry: IP6 %s => %s %s\n",
85 inet6_ntoa ( ndp
->in6
), netdev
->ll_protocol
->name
,
86 netdev
->ll_protocol
->ntoa ( ndp
->ll_addr
) );
90 * Resolve the link-layer address
92 * @v netdev Network device
93 * @v dest Destination address
94 * @v src Source address
95 * @ret dest_ll_addr Destination link-layer address or NULL
98 * This function looks up the neighbour cache for an entry corresponding to the
99 * destination address. If it finds a valid entry, it fills up dest_ll_addr and
100 * returns 0. Otherwise it sends a neighbour solicitation to the solicited
103 int ndp_resolve ( struct net_device
*netdev
, struct in6_addr
*dest
,
104 struct in6_addr
*src
, void *dest_ll_addr
) {
105 struct ll_protocol
*ll_protocol
= netdev
->ll_protocol
;
106 struct ndp_entry
*ndp
;
109 ndp
= ndp_find_entry ( dest
);
110 /* Check if the entry is valid */
111 if ( ndp
&& ndp
->state
== NDP_STATE_REACHABLE
) {
112 DBG ( "Neighbour cache hit: IP6 %s => %s %s\n",
113 inet6_ntoa ( *dest
), ll_protocol
->name
,
114 ll_protocol
->ntoa ( ndp
->ll_addr
) );
115 memcpy ( dest_ll_addr
, ndp
->ll_addr
, ll_protocol
->ll_addr_len
);
119 /* Check if the entry was already created */
121 DBG ( "Awaiting neighbour advertisement\n" );
123 // ndp->state = NDP_STATE_REACHABLE;
124 // memcpy ( ndp->ll_addr, netdev->ll_addr, 6 );
125 // assert ( ndp->ll_protocol->ll_addr_len == 6 );
126 // icmp6_test_nadvert ( netdev, dest, ndp->ll_addr );
127 // assert ( ndp->state == NDP_STATE_REACHABLE );
128 /* Take it out till here */
131 DBG ( "Neighbour cache miss: IP6 %s\n", inet6_ntoa ( *dest
) );
133 /* Add entry in the neighbour cache */
134 add_ndp_entry ( netdev
, dest
, NULL
, NDP_STATE_INCOMPLETE
);
136 /* Send neighbour solicitation */
137 if ( ( rc
= icmp6_send_solicit ( netdev
, src
, dest
) ) != 0 ) {
144 * Process Router Advertisement
146 * @v iobuf I/O buffer containing the data.
147 * @v st_src Address of the source station.
148 * @v st_dest Address of the destination station. Typically FF02::1.
150 int ndp_process_radvert ( struct io_buffer
*iobuf
, struct sockaddr_tcpip
*st_src
,
151 struct sockaddr_tcpip
*st_dest __unused
, struct net_device
*netdev
,
152 struct icmp6_net_protocol
*net_protocol __unused
) {
153 struct router_advert
*radvert
= iobuf
->data
;
154 struct ndp_option
*options
= iobuf
->data
+ sizeof(struct router_advert
);
155 struct in6_addr router_addr
= ( ( struct sockaddr_in6
* ) st_src
)->sin6_addr
;
156 struct in6_addr host_addr
;
158 uint8_t prefix_len
= 0;
159 size_t offset
= sizeof ( struct router_advert
), ll_size
;
161 memset ( &host_addr
, 0, sizeof ( host_addr
) );
163 /* Verify that we shouldn't be trying DHCPv6 instead. */
164 if ( ntohs ( radvert
->hops_flags
) & RADVERT_MANAGED
) {
165 DBG ( "ndp: router advertisement suggests DHCPv6\n" );
170 while ( offset
< iob_len( iobuf
) ) {
172 switch ( options
->type
) {
173 case NDP_OPTION_PREFIX_INFO
:
175 struct prefix_option
*opt
= (struct prefix_option
*) options
;
177 prefix_len
= opt
->prefix_len
;
179 if ( prefix_len
% 8 ) {
180 /* FIXME: non-aligned prefixes unhandled */
181 DBG ( "ndp: prefix length is unaligned, connectivity may suffer.\n" );
184 if ( prefix_len
> 64 ) {
185 /* > 64-bit prefix shouldn't happen. */
186 DBG ( "ndp: prefix length is quite long, connectivity may suffer.\n" );
189 /* Create an IPv6 address for this station based on the prefix. */
190 ll_size
= netdev
->ll_protocol
->ll_addr_len
;
192 memcpy ( host_addr
.s6_addr
+ (8 - ll_size
), netdev
->ll_addr
, ll_size
);
194 /* Create an EUI-64 identifier. */
195 memcpy( host_addr
.s6_addr
+ 8, netdev
->ll_addr
, 3 );
196 memcpy( host_addr
.s6_addr
+ 8 + 5, netdev
->ll_addr
+ 3, 3 );
197 host_addr
.s6_addr
[11] = 0xFF;
198 host_addr
.s6_addr
[12] = 0xFE;
200 /* Designate that this is in fact an EUI-64. */
201 host_addr
.s6_addr
[8] |= 0x2;
204 memcpy( &host_addr
.s6_addr
, opt
->prefix
, prefix_len
/ 8 );
210 case NDP_OPTION_SOURCE_LL
:
212 struct ll_option
*opt
= (struct ll_option
*) options
;
214 /* Add entry in the neighbour cache for the router */
215 if ( ! ndp_find_entry ( &router_addr
) ) {
216 add_ndp_entry ( netdev
, &router_addr
, opt
->address
, NDP_STATE_REACHABLE
);
223 offset
+= options
->length
* 8;
224 options
= (struct ndp_option
*) (iobuf
->data
+ offset
);
228 DBG ( "ndp: couldn't generate a prefix from a router advertisement\n" );
232 /* Configure a route based on this router if none exists. */
233 if ( net_protocol
->check ( netdev
, &host_addr
) ) {
234 DBG ( "ndp: autoconfigured %s/%d via a router advertisement\n", inet6_ntoa( host_addr
), prefix_len
);
236 add_ipv6_address ( netdev
, host_addr
, prefix_len
, host_addr
, router_addr
);
243 * Process neighbour advertisement
245 * @v iobuf I/O buffer
246 * @v st_src Source address
247 * @v st_dest Destination address
249 int ndp_process_nadvert ( struct io_buffer
*iobuf
, struct sockaddr_tcpip
*st_src __unused
,
250 struct sockaddr_tcpip
*st_dest __unused
,
251 struct icmp6_net_protocol
*net_protocol __unused
) {
252 struct neighbour_advert
*nadvert
= iobuf
->data
;
253 struct ll_option
*ll_opt
= iobuf
->data
+ sizeof ( *nadvert
);
254 struct ndp_entry
*ndp
;
257 if ( iob_len ( iobuf
) < sizeof ( *nadvert
) ) {
258 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf
) );
262 /* FIXME: assumes link-layer option is first. */
264 assert ( nadvert
->code
== 0 );
265 assert ( nadvert
->flags
& ICMP6_FLAGS_SOLICITED
);
266 assert ( ll_opt
->type
== 2 );
268 /* Update the neighbour cache, if entry is present */
269 ndp
= ndp_find_entry ( &nadvert
->target
);
272 assert ( ll_opt
->length
==
273 ( ( 2 + ndp
->ll_protocol
->ll_addr_len
) / 8 ) );
275 if ( IP6_EQUAL ( ndp
->in6
, nadvert
->target
) ) {
276 memcpy ( ndp
->ll_addr
, ll_opt
->address
,
277 ndp
->ll_protocol
->ll_addr_len
);
278 ndp
->state
= NDP_STATE_REACHABLE
;
282 DBG ( "Unsolicited advertisement (dropping packet)\n" );
287 * Process neighbour solicitation
289 * @v iobuf I/O buffer
290 * @v st_src Source address
291 * @v st_dest Destination address
292 * @v netdev Network device the packet was received on.
294 int ndp_process_nsolicit ( struct io_buffer
*iobuf __unused
, struct sockaddr_tcpip
*st_src
,
295 struct sockaddr_tcpip
*st_dest __unused
, struct net_device
*netdev
,
296 struct icmp6_net_protocol
*net_protocol
) {
297 struct neighbour_solicit
*nsolicit
= iobuf
->data
;
298 struct in6_addr
*src
= &( ( struct sockaddr_in6
* ) st_src
)->sin6_addr
;
300 /* Does this match any addresses on the interface? */
301 if ( ! net_protocol
->check ( netdev
, &nsolicit
->target
) ) {
302 /* Send an advertisement to the host. */
303 DBG ( "ndp: neighbour solicit received for us\n" );
304 return icmp6_send_advert ( netdev
, &nsolicit
->target
, src
);
306 DBG ( "ndp: neighbour solicit received but it's not for us\n" );