[ipv6] Handle unaligned prefixes in router advertisements
[people/pcmattman/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
13 /** @file
14  *
15  * Neighbour Discovery Protocol
16  *
17  * This file implements address resolution as specified by the neighbour
18  * discovery protocol in RFC2461. This protocol is part of the IPv6 protocol
19  * family.
20  */
21
22 /* A neighbour entry */
23 struct ndp_entry {
24         /** Target IP6 address */
25         struct in6_addr in6;
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 */
31         int state;
32 };
33
34 /** Number of entries in the neighbour cache table */
35 #define NUM_NDP_ENTRIES 4
36
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]
40
41 static unsigned int next_new_ndp_entry = 0;
42
43 /**
44  * Find entry in the neighbour cache
45  *
46  * @v in6       IP6 address
47  */
48 static struct ndp_entry *
49 ndp_find_entry ( struct in6_addr *in6 ) {
50         struct ndp_entry *ndp;
51
52         for ( ndp = ndp_table ; ndp < ndp_table_end ; ndp++ ) {
53                 if ( IP6_EQUAL ( ( *in6 ), ndp->in6 ) &&
54                      ( ndp->state != NDP_STATE_INVALID ) ) {
55                         return ndp;
56                 }
57         }
58         return NULL;
59 }
60
61 /**
62  * Add NDP entry
63  *
64  * @v netdev    Network device
65  * @v in6       IP6 address
66  * @v ll_addr   Link-layer address
67  * @v state     State of the entry - one of the NDP_STATE_XXX values
68  */
69 static void
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];
74
75         /* Fill up entry */
76         ndp->ll_protocol = netdev->ll_protocol;
77         memcpy ( &ndp->in6, &( *in6 ), sizeof ( *in6 ) );
78         if ( ll_addr ) {
79                 memcpy ( ndp->ll_addr, ll_addr, netdev->ll_protocol->ll_addr_len );
80         } else {
81                 memset ( ndp->ll_addr, 0, netdev->ll_protocol->ll_addr_len );
82         }
83         ndp->state = state;
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 ) );
87 }
88
89 /**
90  * Resolve the link-layer address
91  *
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
96  * @ret rc              Status
97  *
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
101  * multicast address.
102  */
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;
107         int rc;
108
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 );
116                 return 0;
117         }
118
119         /* Check if the entry was already created */
120         if ( ndp ) {
121                 DBG ( "Awaiting neighbour advertisement\n" );
122                 /* For test */
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 */
129                 return -ENOENT;
130         }
131         DBG ( "Neighbour cache miss: IP6 %s\n", inet6_ntoa ( *dest ) );
132
133         /* Add entry in the neighbour cache */
134         add_ndp_entry ( netdev, dest, NULL, NDP_STATE_INCOMPLETE );
135
136         /* Send neighbour solicitation */
137         if ( ( rc = icmp6_send_solicit ( netdev, src, dest ) ) != 0 ) {
138                 return rc;
139         }
140         return -ENOENT;
141 }
142
143 /**
144  * Process Router Advertisement
145  *
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.
149  */
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;
157         int rc = -ENOENT;
158         uint8_t prefix_len = 0;
159         size_t offset = sizeof ( struct router_advert ), ll_size;
160
161         memset ( &host_addr, 0, sizeof ( host_addr ) );
162
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" );
166                 return 0;
167         }
168
169         /* Parse options. */
170         while ( offset < iob_len( iobuf ) ) {
171
172                 switch ( options->type ) {
173                 case NDP_OPTION_PREFIX_INFO:
174                         {
175                         struct prefix_option *opt = (struct prefix_option *) options;
176
177                         prefix_len = opt->prefix_len;
178
179                         if ( prefix_len % 8 ) {
180                                 /* Copy one extra prefix byte. */
181                                 prefix_len += 8;
182                         }
183
184                         if ( prefix_len > 64 ) {
185                                 /* > 64-bit prefix shouldn't happen. */
186                                 DBG ( "ndp: prefix length is quite long, connectivity may suffer.\n" );
187                         }
188
189                         /* Copy the prefix first and then add the EUI-64 */
190                         memcpy( &host_addr.s6_addr, opt->prefix, prefix_len / 8 );
191
192                         /* Create an IPv6 address for this station based on the prefix. */
193                         ll_size = netdev->ll_protocol->ll_addr_len;
194                         if ( ll_size < 6 ) {
195                                 memcpy ( host_addr.s6_addr + (8 - ll_size), netdev->ll_addr, ll_size );
196                         } else {
197                                 ipv6_generate_eui64 ( host_addr.s6_addr + 8, netdev->ll_addr );
198                         }
199
200                         rc = 0;
201                         }
202                         break;
203
204                 case NDP_OPTION_SOURCE_LL:
205                         {
206                         struct ll_option *opt = (struct ll_option *) options;
207
208                         /* Add entry in the neighbour cache for the router */
209                         if ( ! ndp_find_entry ( &router_addr ) ) {
210                                 add_ndp_entry ( netdev, &router_addr, opt->address, NDP_STATE_REACHABLE );
211                         }
212
213                         }
214                         break;
215                 }
216
217                 offset += options->length * 8;
218                 options = (struct ndp_option *) (iobuf->data + offset);
219         }
220
221         if ( rc ) {
222                 DBG ( "ndp: couldn't generate a prefix from a router advertisement\n" );
223                 return 0;
224         }
225
226         /* Configure a route based on this router if none exists. */
227         if ( net_protocol->check ( netdev, &host_addr ) ) {
228                 DBG ( "ndp: autoconfigured %s/%d via a router advertisement\n", inet6_ntoa( host_addr ), prefix_len);
229
230                 add_ipv6_address ( netdev, host_addr, prefix_len, host_addr, router_addr );
231         }
232
233         return 0;
234 }
235
236 /**
237  * Process neighbour advertisement
238  *
239  * @v iobuf     I/O buffer
240  * @v st_src    Source address
241  * @v st_dest   Destination address
242  */
243 int ndp_process_nadvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused,
244                            struct sockaddr_tcpip *st_dest __unused,
245                            struct icmp6_net_protocol *net_protocol __unused ) {
246         struct neighbour_advert *nadvert = iobuf->data;
247         struct ll_option *ll_opt = iobuf->data + sizeof ( *nadvert );
248         struct ndp_entry *ndp;
249
250         /* Sanity check */
251         if ( iob_len ( iobuf ) < sizeof ( *nadvert ) ) {
252                 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
253                 return -EINVAL;
254         }
255
256         /* FIXME: assumes link-layer option is first. */
257
258         assert ( nadvert->code == 0 );
259         assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED );
260         assert ( ll_opt->type == 2 );
261
262         /* Update the neighbour cache, if entry is present */
263         ndp = ndp_find_entry ( &nadvert->target );
264         if ( ndp ) {
265
266         assert ( ll_opt->length ==
267                         ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) );
268
269                 if ( IP6_EQUAL ( ndp->in6, nadvert->target ) ) {
270                         memcpy ( ndp->ll_addr, ll_opt->address,
271                                  ndp->ll_protocol->ll_addr_len );
272                         ndp->state = NDP_STATE_REACHABLE;
273                         return 0;
274                 }
275         }
276         DBG ( "Unsolicited advertisement (dropping packet)\n" );
277         return 0;
278 }
279
280 /**
281  * Process neighbour solicitation
282  *
283  * @v iobuf     I/O buffer
284  * @v st_src    Source address
285  * @v st_dest   Destination address
286  * @v netdev    Network device the packet was received on.
287  */
288 int ndp_process_nsolicit ( struct io_buffer *iobuf __unused, struct sockaddr_tcpip *st_src,
289                            struct sockaddr_tcpip *st_dest __unused, struct net_device *netdev,
290                            struct icmp6_net_protocol *net_protocol ) {
291         struct neighbour_solicit *nsolicit = iobuf->data;
292         struct in6_addr *src =  &( ( struct sockaddr_in6 * ) st_src )->sin6_addr;
293
294         /* Does this match any addresses on the interface? */
295         if ( ! net_protocol->check ( netdev, &nsolicit->target ) ) {
296                 /* Send an advertisement to the host. */
297                 DBG ( "ndp: neighbour solicit received for us\n" );
298                 return icmp6_send_advert ( netdev, &nsolicit->target, src );
299         } else {
300                 DBG ( "ndp: neighbour solicit received but it's not for us\n" );
301         }
302
303         return 0;
304 }
305