[ipv6] Fix formatting in ndp_process_radvert
[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                                 /* FIXME: non-aligned prefixes unhandled */
181                                 DBG ( "ndp: prefix length is unaligned, connectivity may suffer.\n" );
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                         /* Create an IPv6 address for this station based on the prefix. */
190                         ll_size = netdev->ll_protocol->ll_addr_len;
191                         if ( ll_size < 6 ) {
192                                 memcpy ( host_addr.s6_addr + (8 - ll_size), netdev->ll_addr, ll_size );
193                         } else {
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;
199
200                                 /* Designate that this is in fact an EUI-64. */
201                                 host_addr.s6_addr[8] |= 0x2;
202                         }
203
204                         memcpy( &host_addr.s6_addr, opt->prefix, prefix_len / 8 );
205
206                         rc = 0;
207                         }
208                         break;
209
210                 case NDP_OPTION_SOURCE_LL:
211                         {
212                         struct ll_option *opt = (struct ll_option *) options;
213
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 );
217                         }
218
219                         }
220                         break;
221                 }
222
223                 offset += options->length * 8;
224                 options = (struct ndp_option *) (iobuf->data + offset);
225         }
226
227         if ( rc ) {
228                 DBG ( "ndp: couldn't generate a prefix from a router advertisement\n" );
229                 return 0;
230         }
231
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);
235
236                 add_ipv6_address ( netdev, host_addr, prefix_len, host_addr, router_addr );
237         }
238
239         return 0;
240 }
241
242 /**
243  * Process neighbour advertisement
244  *
245  * @v iobuf     I/O buffer
246  * @v st_src    Source address
247  * @v st_dest   Destination address
248  */
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;
255
256         /* Sanity check */
257         if ( iob_len ( iobuf ) < sizeof ( *nadvert ) ) {
258                 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
259                 return -EINVAL;
260         }
261
262         /* FIXME: assumes link-layer option is first. */
263
264         assert ( nadvert->code == 0 );
265         assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED );
266         assert ( ll_opt->type == 2 );
267
268         /* Update the neighbour cache, if entry is present */
269         ndp = ndp_find_entry ( &nadvert->target );
270         if ( ndp ) {
271
272         assert ( ll_opt->length ==
273                         ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) );
274
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;
279                         return 0;
280                 }
281         }
282         DBG ( "Unsolicited advertisement (dropping packet)\n" );
283         return 0;
284 }
285
286 /**
287  * Process neighbour solicitation
288  *
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.
293  */
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;
299
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 );
305         } else {
306                 DBG ( "ndp: neighbour solicit received but it's not for us\n" );
307         }
308
309         return 0;
310 }
311