4c0b537d788f9ef9d8317120c1de7ae649443360
[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         case NDP_OPTION_SOURCE_LL:
210                 {
211                 struct ll_option *opt = (struct ll_option *) options;
212                 
213                 /* Add entry in the neighbour cache for the router */
214                 if ( ! ndp_find_entry ( &router_addr ) ) {
215                         add_ndp_entry ( netdev, &router_addr, opt->address, NDP_STATE_REACHABLE );
216                 }
217                 
218                 }
219                 break;
220             }
221
222             offset += options->length * 8;
223             options = (struct ndp_option *) (iobuf->data + offset);
224         }
225
226         if ( rc ) {
227                 DBG ( "ndp: couldn't generate a prefix from a router advertisement\n" );
228                 return 0;
229         }
230
231         /* Configure a route based on this router if none exists. */
232         if ( net_protocol->check ( netdev, &host_addr ) ) {
233                 DBG ( "ndp: autoconfigured %s/%d via a router advertisement\n", inet6_ntoa( host_addr ), prefix_len);
234
235                 add_ipv6_address ( netdev, host_addr, prefix_len, host_addr, router_addr );
236         }
237
238         return 0;
239 }
240
241 /**
242  * Process neighbour advertisement
243  *
244  * @v iobuf     I/O buffer
245  * @v st_src    Source address
246  * @v st_dest   Destination address
247  */
248 int ndp_process_nadvert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused,
249                            struct sockaddr_tcpip *st_dest __unused,
250                            struct icmp6_net_protocol *net_protocol __unused ) {
251         struct neighbour_advert *nadvert = iobuf->data;
252         struct ll_option *ll_opt = iobuf->data + sizeof ( *nadvert );
253         struct ndp_entry *ndp;
254
255         /* Sanity check */
256         if ( iob_len ( iobuf ) < sizeof ( *nadvert ) ) {
257                 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
258                 return -EINVAL;
259         }
260
261         /* FIXME: assumes link-layer option is first. */
262
263         assert ( nadvert->code == 0 );
264         assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED );
265         assert ( ll_opt->type == 2 );
266
267         /* Update the neighbour cache, if entry is present */
268         ndp = ndp_find_entry ( &nadvert->target );
269         if ( ndp ) {
270
271         assert ( ll_opt->length ==
272                         ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) );
273
274                 if ( IP6_EQUAL ( ndp->in6, nadvert->target ) ) {
275                         memcpy ( ndp->ll_addr, ll_opt->address,
276                                  ndp->ll_protocol->ll_addr_len );
277                         ndp->state = NDP_STATE_REACHABLE;
278                         return 0;
279                 }
280         }
281         DBG ( "Unsolicited advertisement (dropping packet)\n" );
282         return 0;
283 }
284
285 /**
286  * Process neighbour solicitation
287  *
288  * @v iobuf     I/O buffer
289  * @v st_src    Source address
290  * @v st_dest   Destination address
291  * @v netdev    Network device the packet was received on.
292  */
293 int ndp_process_nsolicit ( struct io_buffer *iobuf __unused, struct sockaddr_tcpip *st_src,
294                            struct sockaddr_tcpip *st_dest __unused, struct net_device *netdev,
295                            struct icmp6_net_protocol *net_protocol ) {
296         struct neighbour_solicit *nsolicit = iobuf->data;
297         struct in6_addr *src =  &( ( struct sockaddr_in6 * ) st_src )->sin6_addr;
298
299         /* Does this match any addresses on the interface? */
300         if ( ! net_protocol->check ( netdev, &nsolicit->target ) ) {
301                 /* Send an advertisement to the host. */
302                 DBG ( "ndp: neighbour solicit received for us\n" );
303                 return icmp6_send_advert ( netdev, &nsolicit->target, src );
304         } else {
305                 DBG ( "ndp: neighbour solicit received but it's not for us\n" );
306         }
307
308         return 0;
309 }
310