8bea8b32b16f76aa4a5586a2eda6c999942c5b9d
[people/meteger/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: IP6 %s => %s %s\n",
84               inet6_ntoa ( ndp->in6 ), netdev->ll_protocol->name,
85               netdev->ll_protocol->ntoa ( ndp->ll_addr ) );
86 }
87
88 /**
89  * Resolve the link-layer address
90  *
91  * @v netdev            Network device
92  * @v dest              Destination address
93  * @v src               Source address
94  * @ret dest_ll_addr    Destination link-layer address or NULL
95  * @ret rc              Status
96  *
97  * This function looks up the neighbour cache for an entry corresponding to the
98  * destination address. If it finds a valid entry, it fills up dest_ll_addr and
99  * returns 0. Otherwise it sends a neighbour solicitation to the solicited
100  * multicast address.
101  */
102 int ndp_resolve ( struct net_device *netdev, struct in6_addr *dest,
103                   struct in6_addr *src, void *dest_ll_addr ) {
104         struct ll_protocol *ll_protocol = netdev->ll_protocol;
105         struct ndp_entry *ndp;
106         int rc;
107
108         ndp = ndp_find_entry ( dest );
109         /* Check if the entry is valid */
110         if ( ndp && ndp->state == NDP_STATE_REACHABLE ) {
111                 DBG ( "Neighbour cache hit: IP6 %s => %s %s\n",
112                       inet6_ntoa ( *dest ), ll_protocol->name,
113                       ll_protocol->ntoa ( ndp->ll_addr ) );
114                 memcpy ( dest_ll_addr, ndp->ll_addr, ll_protocol->ll_addr_len );
115                 return 0;
116         }
117
118         /* Check if the entry was already created */
119         if ( ndp ) {
120                 DBG ( "Awaiting neighbour advertisement\n" );
121                 /* For test */
122 //              ndp->state = NDP_STATE_REACHABLE;
123 //              memcpy ( ndp->ll_addr, netdev->ll_addr, 6 );
124 //              assert ( ndp->ll_protocol->ll_addr_len == 6 );
125 //              icmp6_test_nadvert ( netdev, dest, ndp->ll_addr );
126 //              assert ( ndp->state == NDP_STATE_REACHABLE );
127                 /* Take it out till here */
128                 return -ENOENT;
129         }
130         DBG ( "Neighbour cache miss: IP6 %s\n", inet6_ntoa ( *dest ) );
131
132         /* Add entry in the neighbour cache */
133         add_ndp_entry ( netdev, dest, NULL, NDP_STATE_INCOMPLETE );
134
135         /* Send neighbour solicitation */
136         if ( ( rc = icmp6_send_solicit ( netdev, src, dest ) ) != 0 ) {
137                 return rc;
138         }
139         return -ENOENT;
140 }
141
142 /**
143  * Process neighbour advertisement
144  *
145  * @v iobuf     I/O buffer
146  * @v st_src    Source address
147  * @v st_dest   Destination address 
148  */
149 int ndp_process_advert ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src __unused,
150                            struct sockaddr_tcpip *st_dest __unused ) {
151         struct neighbour_advert *nadvert = iobuf->data;
152         struct ndp_entry *ndp;
153
154         /* Sanity check */
155         if ( iob_len ( iobuf ) < sizeof ( *nadvert ) ) {
156                 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
157                 return -EINVAL;
158         }
159
160         assert ( nadvert->code == 0 );
161         assert ( nadvert->flags & ICMP6_FLAGS_SOLICITED );
162         assert ( nadvert->opt_type == 2 );
163
164         /* Update the neighbour cache, if entry is present */
165         ndp = ndp_find_entry ( &nadvert->target );
166         if ( ndp ) {
167
168         assert ( nadvert->opt_len ==
169                         ( ( 2 + ndp->ll_protocol->ll_addr_len ) / 8 ) );
170
171                 if ( IP6_EQUAL ( ndp->in6, nadvert->target ) ) {
172                         memcpy ( ndp->ll_addr, nadvert->opt_ll_addr,
173                                  ndp->ll_protocol->ll_addr_len );
174                         ndp->state = NDP_STATE_REACHABLE;
175                         return 0;
176                 }
177         }
178         DBG ( "Unsolicited advertisement (dropping packet)\n" );
179         return 0;
180 }