[dns] Handle AAAA records
[people/meteger/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 tcpip_tx ( iobuf, &icmp6_protocol, NULL, &st_dest.st,
67                           NULL, &nsolicit->csum );
68 }
69
70 /**
71  * Send neighbour advertisement packet
72  *
73  * @v netdev    Network device
74  * @v src       Source address
75  * @v dest      Destination address
76  *
77  * This function prepares a neighbour advertisement packet and sends it to the
78  * network layer.
79  */
80 int icmp6_send_advert ( struct net_device *netdev, struct in6_addr *src,
81                         struct in6_addr *dest ) {
82         union {
83                 struct sockaddr_in6 sin6;
84                 struct sockaddr_tcpip st;
85         } st_dest;
86         struct ll_protocol *ll_protocol = netdev->ll_protocol;
87         struct neighbour_advert *nadvert;
88         struct ll_option *llopt;
89         struct io_buffer *iobuf = alloc_iob ( sizeof ( struct ll_option ) + 
90                                               sizeof ( *nadvert ) + MIN_IOB_LEN );
91         iob_reserve ( iobuf, MAX_HDR_LEN );
92         nadvert = iob_put ( iobuf, sizeof ( *nadvert ) );
93         llopt = iob_put ( iobuf, sizeof ( *llopt ) );
94
95         /* Fill up the headers */
96         memset ( nadvert, 0, sizeof ( *nadvert ) );
97         nadvert->type = ICMP6_NADVERT;
98         nadvert->code = 0;
99         nadvert->target = *src;
100         nadvert->flags = ICMP6_FLAGS_SOLICITED | ICMP6_FLAGS_OVERRIDE;
101         
102         /* Fill in the link-layer address. FIXME: ll_option assumes 6 bytes. */
103         llopt->type = 2;
104         llopt->length = ( 2 + ll_protocol->ll_addr_len ) / 8;
105         memcpy ( llopt->address, netdev->ll_addr, netdev->ll_protocol->ll_addr_len );
106
107         /* Partial checksum */
108         nadvert->csum = 0;
109         nadvert->csum = tcpip_chksum ( nadvert, sizeof ( *nadvert ) + sizeof ( *llopt ) );
110
111         /* Target network address. */
112         st_dest.sin6.sin_family = AF_INET6;
113         st_dest.sin6.sin6_addr = *dest;
114
115         /* Send packet over IP6 */
116         return tcpip_tx ( iobuf, &icmp6_protocol, NULL, &st_dest.st,
117                           NULL, &nadvert->csum );
118 }
119
120 /**
121  * Process ICMP6 Echo Request
122  *
123  * @v iobuf I/O buffer containing the original ICMPv6 packet.
124  * @v st_src Address of the source station.
125  * @v st_dest Address of the destination station.
126  */
127 int icmp6_handle_echo ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
128                         struct sockaddr_tcpip *st_dest,
129                         struct icmp6_net_protocol *net_protocol __unused ) {
130         struct icmp6_header *icmp6hdr = iobuf->data;
131         size_t len = iob_len ( iobuf );
132         int rc;
133
134         /* Change type to response and recalculate checksum */
135         icmp6hdr->type = ICMP6_ECHO_RESPONSE;
136         icmp6hdr->csum = 0;
137         icmp6hdr->csum = tcpip_chksum ( icmp6hdr, len );
138
139         /* Transmit the response */
140         if ( ( rc = tcpip_tx ( iob_disown ( iobuf ), &icmp6_protocol, st_dest,
141                                st_src, NULL, &icmp6hdr->csum ) ) != 0 ) {
142                 DBG ( "ICMP could not transmit ping response: %s\n",
143                       strerror ( rc ) );
144         }
145
146         free_iob(iobuf);
147         return rc;
148 }
149
150 /**
151  * Identify ICMP6 network layer protocol
152  *
153  * @v net_proto                 Network-layer protocol, in network-endian order
154  * @ret arp_net_protocol        ARP protocol, or NULL
155  *
156  */
157 static struct icmp6_net_protocol * icmp6_find_protocol ( uint16_t net_proto ) {
158         struct icmp6_net_protocol *icmp6_net_protocol;
159
160         for_each_table_entry ( icmp6_net_protocol, ICMP6_NET_PROTOCOLS ) {
161                 if ( icmp6_net_protocol->net_protocol->net_proto == net_proto ) {
162                         return icmp6_net_protocol;
163                 }
164         }
165         return NULL;
166 }
167
168 /**
169  * Process ICMP6 headers
170  *
171  * @v iobuf     I/O buffer
172  * @v st_src    Source address
173  * @v st_dest   Destination address
174  */
175 int icmp6_rx ( struct io_buffer *iobuf, struct sockaddr_tcpip *st_src,
176                       struct sockaddr_tcpip *st_dest, struct net_device *netdev,
177                       uint16_t pshdr_csum ) {
178         struct icmp6_header *icmp6hdr = iobuf->data;
179         struct icmp6_net_protocol *icmp6_net_protocol;
180         size_t len = iob_len ( iobuf );
181         unsigned int csum;
182         int rc;
183
184         /* Sanity check */
185         if ( iob_len ( iobuf ) < sizeof ( *icmp6hdr ) ) {
186                 DBG ( "Packet too short (%zd bytes)\n", iob_len ( iobuf ) );
187                 free_iob ( iobuf );
188                 return -EINVAL;
189         }
190
191         /* Verify checksum */
192         csum = tcpip_continue_chksum ( pshdr_csum, icmp6hdr, len );
193         if ( csum != 0 ) {
194                 DBG ( "ICMPv6 checksum incorrect (is %04x, should be 0000)\n",
195                       csum );
196                 DBG_HD ( icmp6hdr, len );
197                 rc = -EINVAL;
198                 goto done;
199         }
200         
201         /* Get the net protocol for this packet. */
202         icmp6_net_protocol = icmp6_find_protocol ( htons ( ETH_P_IPV6 ) );
203         if ( ! icmp6_net_protocol ) {
204                 rc = 0;
205                 goto done;
206         }
207
208         DBG ( "ICMPv6: packet with type %d and code %x\n", icmp6hdr->type, icmp6hdr->code);
209
210         /* Process the ICMP header */
211         switch ( icmp6hdr->type ) {
212         case ICMP6_ROUTER_ADVERT:
213             return ndp_process_radvert ( iobuf, st_src, st_dest, netdev, icmp6_net_protocol );
214         case ICMP6_NSOLICIT:
215                 return ndp_process_nsolicit ( iobuf, st_src, st_dest, netdev, icmp6_net_protocol );
216         case ICMP6_NADVERT:
217                 return ndp_process_nadvert ( iobuf, st_src, st_dest, icmp6_net_protocol );
218         case ICMP6_ECHO_REQUEST:
219                 return icmp6_handle_echo ( iobuf, st_src, st_dest, icmp6_net_protocol );
220         }
221
222         rc = -ENOSYS;
223
224  done:
225         free_iob ( iobuf );
226         return rc;
227 }
228
229 #if 0
230 void icmp6_test_nadvert (struct net_device *netdev, struct sockaddr_in6 *server_p, char *ll_addr) {
231
232                 struct sockaddr_in6 server;
233                 memcpy ( &server, server_p, sizeof ( server ) );
234                 struct io_buffer *rxiobuf = alloc_iob ( 500 );
235                 iob_reserve ( rxiobuf, MAX_HDR_LEN );
236                 struct neighbour_advert *nadvert = iob_put ( rxiobuf, sizeof ( *nadvert ) );
237                 nadvert->type = 136;
238                 nadvert->code = 0;
239                 nadvert->flags = ICMP6_FLAGS_SOLICITED;
240                 nadvert->csum = 0xffff;
241                 nadvert->target = server.sin6_addr;
242                 nadvert->opt_type = 2;
243                 nadvert->opt_len = 1;
244                 memcpy ( nadvert->opt_ll_addr, ll_addr, 6 );
245                 struct ip6_header *ip6hdr = iob_push ( rxiobuf, sizeof ( *ip6hdr ) );
246                 ip6hdr->ver_traffic_class_flow_label = htonl ( 0x60000000 );
247                 ip6hdr->hop_limit = 255;
248                 ip6hdr->nxt_hdr = 58;
249                 ip6hdr->payload_len = htons ( sizeof ( *nadvert ) );
250                 ip6hdr->src = server.sin6_addr;
251                 ip6hdr->dest = server.sin6_addr;
252                 hex_dump ( rxiobuf->data, iob_len ( rxiobuf ) );
253                 net_rx ( rxiobuf, netdev, htons ( ETH_P_IPV6 ), ll_addr );
254 }
255 #endif
256
257 /** ICMP6 protocol (needed for ipv6_tx) */
258 struct tcpip_protocol icmp6_protocol __tcpip_protocol = {
259         .name = "ICMP6",
260         .rx = NULL, /* icmp6_rx if tcpip passes netdev in future */
261         .tcpip_proto = IP_ICMP6, // 58
262 };
263