[ipv6] Use ipv6_tx instead of tcpip_tx in icmp6
[gpxe.git] / src / net / icmpv6.c
1 #include <stdint.h>
2 #include <string.h>
3 #include <byteswap.h>
4 #include <errno.h>
5 #include <gpxe/in.h>
6 #include <gpxe/ip6.h>
7 #include <gpxe/if_ether.h>
8 #include <gpxe/iobuf.h>
9 #include <gpxe/ndp.h>
10 #include <gpxe/icmp6.h>
11 #include <gpxe/tcpip.h>
12 #include <gpxe/netdevice.h>
13
14 #include <gpxe/ethernet.h>
15
16 struct tcpip_protocol icmp6_protocol;
17
18 /**
19  * Send neighbour solicitation packet
20  *
21  * @v netdev    Network device
22  * @v src       Source address
23  * @v dest      Destination address
24  *
25  * This function prepares a neighbour solicitation packet and sends it to the
26  * network layer.
27  */
28 int icmp6_send_solicit ( struct net_device *netdev, struct in6_addr *src __unused,
29                          struct in6_addr *dest ) {
30         union {
31                 struct sockaddr_in6 sin6;
32                 struct sockaddr_tcpip st;
33         } st_dest;
34         struct ll_protocol *ll_protocol = netdev->ll_protocol;
35         struct neighbour_solicit *nsolicit;
36         struct ll_option *llopt;
37         struct io_buffer *iobuf = alloc_iob ( sizeof ( struct ll_option ) + 
38                                               sizeof ( *nsolicit ) + MIN_IOB_LEN );
39         iob_reserve ( iobuf, MAX_HDR_LEN );
40         nsolicit = iob_put ( iobuf, sizeof ( *nsolicit ) );
41         llopt = iob_put ( iobuf, sizeof ( *llopt ) );
42
43         /* Fill up the headers */
44         memset ( nsolicit, 0, sizeof ( *nsolicit ) );
45         nsolicit->type = ICMP6_NSOLICIT;
46         nsolicit->code = 0;
47         nsolicit->target = *dest;
48         
49         /* Fill in the link-layer address. FIXME: ll_option assumes 6 bytes. */
50         llopt->type = 1;
51         llopt->length = ( 2 + ll_protocol->ll_addr_len ) / 8;
52         memcpy ( llopt->address, netdev->ll_addr, netdev->ll_protocol->ll_addr_len );
53         
54         /* Partial checksum */
55         nsolicit->csum = 0;
56         nsolicit->csum = tcpip_chksum ( nsolicit, sizeof ( *nsolicit ) + sizeof ( *llopt ) );
57
58         /* Solicited multicast address - FF02::1 (all stations on local network) */
59         memset(&st_dest.sin6, 0, sizeof(st_dest.sin6));
60         st_dest.sin6.sin_family = AF_INET6;
61         st_dest.sin6.sin6_addr.in6_u.u6_addr8[0] = 0xff;
62         st_dest.sin6.sin6_addr.in6_u.u6_addr8[1] = 0x2;
63         st_dest.sin6.sin6_addr.in6_u.u6_addr8[15] = 0x1;
64
65         /* Send packet over IP6 */
66         return ipv6_tx ( iobuf, &icmp6_protocol, NULL, &st_dest.st,
67                          netdev, &nsolicit->csum );
68 }
69
70 /**
71  * Send router solicitation packet
72  *
73  * @v netdev    Network device
74  * @v src       Source address
75  * @v dest      Destination address
76  *
77  * This function prepares a neighbour solicitation packet and sends it to the
78  * network layer.
79  */
80 int icmp6_send_rsolicit ( struct net_device *netdev ) {
81         union {
82                 struct sockaddr_in6 sin6;
83                 struct sockaddr_tcpip st;
84         } st_dest;
85         struct router_solicit *solicit;
86         struct io_buffer *iobuf = alloc_iob ( sizeof ( *solicit ) + MIN_IOB_LEN );
87         
88         iob_reserve ( iobuf, MAX_HDR_LEN );
89         solicit = iob_put ( iobuf, sizeof ( *solicit ) );
90
91         /* Fill up the headers */
92         memset ( solicit, 0, sizeof ( *solicit ) );
93         solicit->type = ICMP6_ROUTER_SOLICIT;
94         solicit->code = 0;
95         
96         /* Partial checksum */
97         solicit->csum = 0;
98         solicit->csum = tcpip_chksum ( solicit, sizeof ( *solicit ) );
99
100         /* Solicited multicast address - FF02::2 (all routers on local network) */
101         memset(&st_dest.sin6, 0, sizeof(st_dest.sin6));
102         st_dest.sin6.sin_family = AF_INET6;
103         st_dest.sin6.sin6_addr.in6_u.u6_addr8[0] = 0xff;
104         st_dest.sin6.sin6_addr.in6_u.u6_addr8[1] = 0x2;
105         st_dest.sin6.sin6_addr.in6_u.u6_addr8[15] = 0x2;
106
107         /* Send packet over IP6 */
108         return ipv6_tx ( iobuf, &icmp6_protocol, NULL, &st_dest.st,
109                          netdev, &solicit->csum );
110 }
111
112 /**
113  * Send neighbour advertisement packet
114  *
115  * @v netdev    Network device
116  * @v src       Source address
117  * @v dest      Destination address
118  *
119  * This function prepares a neighbour advertisement packet and sends it to the
120  * network layer.
121  */
122 int icmp6_send_advert ( struct net_device *netdev, struct in6_addr *src,
123                         struct in6_addr *dest ) {
124         union {
125                 struct sockaddr_in6 sin6;
126                 struct sockaddr_tcpip st;
127         } st_dest;
128         struct ll_protocol *ll_protocol = netdev->ll_protocol;
129         struct neighbour_advert *nadvert;
130         struct ll_option *llopt;
131         struct io_buffer *iobuf = alloc_iob ( sizeof ( struct ll_option ) + 
132                                               sizeof ( *nadvert ) + MIN_IOB_LEN );
133         iob_reserve ( iobuf, MAX_HDR_LEN );
134         nadvert = iob_put ( iobuf, sizeof ( *nadvert ) );
135         llopt = iob_put ( iobuf, sizeof ( *llopt ) );
136
137         /* Fill up the headers */
138         memset ( nadvert, 0, sizeof ( *nadvert ) );
139         nadvert->type = ICMP6_NADVERT;
140         nadvert->code = 0;
141         nadvert->target = *src;
142         nadvert->flags = ICMP6_FLAGS_SOLICITED | ICMP6_FLAGS_OVERRIDE;
143         
144         /* Fill in the link-layer address. FIXME: ll_option assumes 6 bytes. */
145         llopt->type = 2;
146         llopt->length = ( 2 + ll_protocol->ll_addr_len ) / 8;
147         memcpy ( llopt->address, netdev->ll_addr, netdev->ll_protocol->ll_addr_len );
148
149         /* Partial checksum */
150         nadvert->csum = 0;
151         nadvert->csum = tcpip_chksum ( nadvert, sizeof ( *nadvert ) + sizeof ( *llopt ) );
152
153         /* Target network address. */
154         st_dest.sin6.sin_family = AF_INET6;
155         st_dest.sin6.sin6_addr = *dest;
156
157         /* Send packet over IP6 */
158         return ipv6_tx ( iobuf, &icmp6_protocol, NULL, &st_dest.st,
159                          NULL, &nadvert->csum );
160 }
161
162 /**
163  * Process ICMP6 Echo Request
164  *
165  * @v iobuf I/O buffer containing the original ICMPv6 packet.
166  * @v st_src Address of the source station.
167  * @v st_dest Address of the destination station.
168  */
169 int icmp6_handle_echo ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
170                         struct sockaddr_tcpip *st_dest,
171                         struct icmp6_net_protocol *net_protocol __unused ) {
172         struct icmp6_header *icmp6hdr = iobuf->data;
173         size_t len = iob_len ( iobuf );
174         int rc;
175
176         /* Change type to response and recalculate checksum */
177         icmp6hdr->type = ICMP6_ECHO_RESPONSE;
178         icmp6hdr->csum = 0;
179         icmp6hdr->csum = tcpip_chksum ( icmp6hdr, len );
180
181         /* Transmit the response */
182         if ( ( rc = ipv6_tx ( iob_disown ( iobuf ), &icmp6_protocol, st_dest,
183                               st_src, NULL, &icmp6hdr->csum ) ) != 0 ) {
184                 DBG ( "ICMP could not transmit ping response: %s\n",
185                       strerror ( rc ) );
186         }
187
188         free_iob(iobuf);
189         return rc;
190 }
191
192 /**
193  * Identify ICMP6 network layer protocol
194  *
195  * @v net_proto                 Network-layer protocol, in network-endian order
196  * @ret arp_net_protocol        ARP protocol, or NULL
197  *
198  */
199 static struct icmp6_net_protocol * icmp6_find_protocol ( uint16_t net_proto ) {
200         struct icmp6_net_protocol *icmp6_net_protocol;
201
202         for_each_table_entry ( icmp6_net_protocol, ICMP6_NET_PROTOCOLS ) {
203                 if ( icmp6_net_protocol->net_protocol->net_proto == net_proto ) {
204                         return icmp6_net_protocol;
205                 }
206         }
207         return NULL;
208 }
209
210 /**
211  * Process ICMP6 headers
212  *
213  * @v iobuf     I/O buffer
214  * @v st_src    Source address
215  * @v st_dest   Destination address
216  */
217 int icmp6_rx ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
218                       struct sockaddr_tcpip *st_dest, struct net_device *netdev,
219                       uint16_t pshdr_csum ) {
220         struct icmp6_header *icmp6hdr = iobuf->data;
221         struct icmp6_net_protocol *icmp6_net_protocol;
222         size_t len = iob_len ( iobuf );
223         unsigned int csum;
224         int rc;
225
226         /* Sanity check */
227         if ( iob_len ( iobuf ) < sizeof ( *icmp6hdr ) ) {
228                 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
229                 free_iob ( iobuf );
230                 return -EINVAL;
231         }
232
233         /* Verify checksum */
234         csum = tcpip_continue_chksum ( pshdr_csum, icmp6hdr, len );
235         if ( csum != 0 ) {
236                 DBG ( "ICMPv6 checksum incorrect (is %04x, should be 0000)\n",
237                       csum );
238                 DBG_HD ( icmp6hdr, len );
239                 rc = -EINVAL;
240                 goto done;
241         }
242         
243         /* Get the net protocol for this packet. */
244         icmp6_net_protocol = icmp6_find_protocol ( htons ( ETH_P_IPV6 ) );
245         if ( ! icmp6_net_protocol ) {
246                 rc = 0;
247                 goto done;
248         }
249
250         DBG ( "ICMPv6: packet with type %d and code %x\n", icmp6hdr->type, icmp6hdr->code);
251
252         /* Process the ICMP header */
253         switch ( icmp6hdr->type ) {
254         case ICMP6_ROUTER_ADVERT:
255             return ndp_process_radvert ( iobuf, st_src, st_dest, netdev, icmp6_net_protocol );
256         case ICMP6_NSOLICIT:
257                 return ndp_process_nsolicit ( iobuf, st_src, st_dest, netdev, icmp6_net_protocol );
258         case ICMP6_NADVERT:
259                 return ndp_process_nadvert ( iobuf, st_src, st_dest, icmp6_net_protocol );
260         case ICMP6_ECHO_REQUEST:
261                 return icmp6_handle_echo ( iobuf, st_src, st_dest, icmp6_net_protocol );
262         }
263
264         rc = -ENOSYS;
265
266  done:
267         free_iob ( iobuf );
268         return rc;
269 }
270
271 #if 0
272 void icmp6_test_nadvert (struct net_device *netdev, struct sockaddr_in6 *server_p, char *ll_addr) {
273
274                 struct sockaddr_in6 server;
275                 memcpy ( &server, server_p, sizeof ( server ) );
276                 struct io_buffer *rxiobuf = alloc_iob ( 500 );
277                 iob_reserve ( rxiobuf, MAX_HDR_LEN );
278                 struct neighbour_advert *nadvert = iob_put ( rxiobuf, sizeof ( *nadvert ) );
279                 nadvert->type = 136;
280                 nadvert->code = 0;
281                 nadvert->flags = ICMP6_FLAGS_SOLICITED;
282                 nadvert->csum = 0xffff;
283                 nadvert->target = server.sin6_addr;
284                 nadvert->opt_type = 2;
285                 nadvert->opt_len = 1;
286                 memcpy ( nadvert->opt_ll_addr, ll_addr, 6 );
287                 struct ip6_header *ip6hdr = iob_push ( rxiobuf, sizeof ( *ip6hdr ) );
288                 ip6hdr->ver_traffic_class_flow_label = htonl ( 0x60000000 );
289                 ip6hdr->hop_limit = 255;
290                 ip6hdr->nxt_hdr = 58;
291                 ip6hdr->payload_len = htons ( sizeof ( *nadvert ) );
292                 ip6hdr->src = server.sin6_addr;
293                 ip6hdr->dest = server.sin6_addr;
294                 hex_dump ( rxiobuf->data, iob_len ( rxiobuf ) );
295                 net_rx ( rxiobuf, netdev, htons ( ETH_P_IPV6 ), ll_addr );
296 }
297 #endif
298
299 /** ICMP6 protocol (needed for ipv6_tx) */
300 struct tcpip_protocol icmp6_protocol __tcpip_protocol = {
301         .name = "ICMP6",
302         .rx = NULL, /* icmp6_rx if tcpip passes netdev in future */
303         .tcpip_proto = IP_ICMP6, // 58
304 };
305