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 *
ndp_find_entry(struct in6_addr * in6)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
add_ndp_entry(struct net_device * netdev,struct in6_addr * in6,void * ll_addr,int state)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 */
ndp_resolve(struct net_device * netdev,struct in6_addr * dest,struct in6_addr * src,void * dest_ll_addr)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 */
ndp_process_advert(struct io_buffer * iobuf,struct sockaddr_tcpip * st_src __unused,struct sockaddr_tcpip * st_dest __unused)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 }
181