Allowed zero-cost enforced ordering of features in startup banner
[people/xl0/gpxe.git] / src / net / ndp.c
1 #include <stdint.h>
2 #include <string.h>
3 #include <byteswap.h>
4 #include <errno.h>
5 #include <gpxe/if_ether.h>
6 #include <gpxe/iobuf.h>
7 #include <gpxe/ndp.h>
8 #include <gpxe/icmp6.h>
9 #include <gpxe/ip6.h>
10 #include <gpxe/netdevice.h>
11
12 /** @file
13  *
14  * Neighbour Discovery Protocol
15  *
16  * This file implements address resolution as specified by the neighbour
17  * discovery protocol in RFC2461. This protocol is part of the IPv6 protocol
18  * family.
19  */
20
21 /* A neighbour entry */
22 struct ndp_entry {
23         /** Target IP6 address */
24         struct in6_addr in6;
25         /** Link layer protocol */
26         struct ll_protocol *ll_protocol;
27         /** Link-layer address */
28         uint8_t ll_addr[MAX_LL_ADDR_LEN];
29         /** State of the neighbour entry */
30         int state;
31 };
32
33 /** Number of entries in the neighbour cache table */
34 #define NUM_NDP_ENTRIES 4
35
36 /** The neighbour cache table */
37 static struct ndp_entry ndp_table[NUM_NDP_ENTRIES];
38 #define ndp_table_end &ndp_table[NUM_NDP_ENTRIES]
39
40 static unsigned int next_new_ndp_entry = 0;
41
42 /**
43  * Find entry in the neighbour cache
44  *
45  * @v in6       IP6 address
46  */
47 static struct ndp_entry *
48 ndp_find_entry ( struct in6_addr *in6 ) {
49         struct ndp_entry *ndp;
50
51         for ( ndp = ndp_table ; ndp < ndp_table_end ; ndp++ ) {
52                 if ( IP6_EQUAL ( ( *in6 ), ndp->in6 ) && 
53                      ( ndp->state != NDP_STATE_INVALID ) ) {
54                         return ndp;
55                 }
56         }
57         return NULL;
58 }
59
60 /**
61  * Add NDP entry
62  * 
63  * @v netdev    Network device
64  * @v in6       IP6 address
65  * @v ll_addr   Link-layer address
66  * @v state     State of the entry - one of the NDP_STATE_XXX values
67  */
68 static void 
69 add_ndp_entry ( struct net_device *netdev, struct in6_addr *in6,
70                 void *ll_addr, int state ) {
71         struct ndp_entry *ndp;
72         ndp = &ndp_table[next_new_ndp_entry++ % NUM_NDP_ENTRIES];
73
74         /* Fill up entry */
75         ndp->ll_protocol = netdev->ll_protocol;
76         memcpy ( &ndp->in6, &( *in6 ), sizeof ( *in6 ) );
77         if ( ll_addr ) {
78                 memcpy ( ndp->ll_addr, ll_addr, netdev->ll_protocol->ll_addr_len );
79         } else {
80                 memset ( ndp->ll_addr, 0, netdev->ll_protocol->ll_addr_len );
81         }
82         ndp->state = state;
83         DBG ( "New neighbour cache entry (%d): IP6 %s => %s %s\n",
84               ( ndp - ndp_table ) / sizeof ( *ndp ),
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 (cache entry %d)\n",
122                       ( ( ndp - ndp_table ) / sizeof ( *ndp ) ) );
123                 /* For test */
124 //              ndp->state = NDP_STATE_REACHABLE;
125 //              memcpy ( ndp->ll_addr, netdev->ll_addr, 6 );
126 //              assert ( ndp->ll_protocol->ll_addr_len == 6 );
127 //              icmp6_test_nadvert ( netdev, dest, ndp->ll_addr );
128 //              assert ( ndp->state == NDP_STATE_REACHABLE );
129                 /* Take it out till here */
130                 return -ENOENT;
131         }
132         DBG ( "Neighbour cache miss: IP6 %s\n", inet6_ntoa ( *dest ) );
133
134         /* Add entry in the neighbour cache */
135         add_ndp_entry ( netdev, dest, NULL, NDP_STATE_INCOMPLETE );
136
137         /* Send neighbour solicitation */
138         if ( ( rc = icmp6_send_solicit ( netdev, src, dest ) ) != 0 ) {
139                 return rc;
140         }
141         return -ENOENT;
142 }
143
144 /**
145  * Process neighbour advertisement
146  *
147  * @v iobuf     I/O buffer
148  * @v st_src    Source address
149  * @v st_dest   Destination address 
150  */
151 int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused,
152                            struct sockaddr_tcpip *st_dest __unused ) {
153         struct neighbour_advert *nadvert = iobuf->data;
154         struct ndp_entry *ndp;
155
156         /* Sanity check */
157         if ( iob_len ( iobuf ) < sizeof ( *nadvert ) ) {
158                 DBG ( "Packet too short (%d bytes)\n", iob_len ( iobuf ) );
159                 return -EINVAL;
160         }
161
162         assert ( nadvert->code == 0 );
163         assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED );
164         assert ( nadvert->opt_type == 2 );
165
166         /* Update the neighbour cache, if entry is present */
167         ndp = ndp_find_entry ( &nadvert->target );
168         if ( ndp ) {
169
170         assert ( nadvert->opt_len ==
171                         ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) );
172
173                 if ( IP6_EQUAL ( ndp->in6, nadvert->target ) ) {
174                         memcpy ( ndp->ll_addr, nadvert->opt_ll_addr,
175                                  ndp->ll_protocol->ll_addr_len );
176                         ndp->state = NDP_STATE_REACHABLE;
177                         return 0;
178                 }
179         }
180         DBG ( "Unsolicited advertisement (dropping packet)\n" );
181         return 0;
182 }