1 | /* SPDX-License-Identifier: BSD-3-Clause */ |
2 | /* |
3 | * Copyright (c) 1982, 1986, 1988, 1993 |
4 | * The Regents of the University of California. All rights reserved. |
5 | * |
6 | * Redistribution and use in source and binary forms, with or without |
7 | * modification, are permitted provided that the following conditions |
8 | * are met: |
9 | * 1. Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * 2. Redistributions in binary form must reproduce the above copyright |
12 | * notice, this list of conditions and the following disclaimer in the |
13 | * documentation and/or other materials provided with the distribution. |
14 | * 3. Neither the name of the University nor the names of its contributors |
15 | * may be used to endorse or promote products derived from this software |
16 | * without specific prior written permission. |
17 | * |
18 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
24 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
26 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
27 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
28 | * SUCH DAMAGE. |
29 | * |
30 | * @(#)ip_icmp.c 8.2 (Berkeley) 1/4/94 |
31 | * ip_icmp.c,v 1.7 1995/05/30 08:09:42 rgrimes Exp |
32 | */ |
33 | |
34 | #include "slirp.h" |
35 | #include "ip_icmp.h" |
36 | |
37 | #ifndef WITH_ICMP_ERROR_MSG |
38 | #define WITH_ICMP_ERROR_MSG 0 |
39 | #endif |
40 | |
41 | /* The message sent when emulating PING */ |
42 | /* Be nice and tell them it's just a pseudo-ping packet */ |
43 | static const char icmp_ping_msg[] = |
44 | "This is a pseudo-PING packet used by Slirp to emulate ICMP ECHO-REQUEST " |
45 | "packets.\n" ; |
46 | |
47 | /* list of actions for icmp_send_error() on RX of an icmp message */ |
48 | static const int icmp_flush[19] = { |
49 | /* ECHO REPLY (0) */ 0, |
50 | 1, |
51 | 1, |
52 | /* DEST UNREACH (3) */ 1, |
53 | /* SOURCE QUENCH (4)*/ 1, |
54 | /* REDIRECT (5) */ 1, |
55 | 1, |
56 | 1, |
57 | /* ECHO (8) */ 0, |
58 | /* ROUTERADVERT (9) */ 1, |
59 | /* ROUTERSOLICIT (10) */ 1, |
60 | /* TIME EXCEEDED (11) */ 1, |
61 | /* PARAMETER PROBLEM (12) */ 1, |
62 | /* TIMESTAMP (13) */ 0, |
63 | /* TIMESTAMP REPLY (14) */ 0, |
64 | /* INFO (15) */ 0, |
65 | /* INFO REPLY (16) */ 0, |
66 | /* ADDR MASK (17) */ 0, |
67 | /* ADDR MASK REPLY (18) */ 0 |
68 | }; |
69 | |
70 | void icmp_init(Slirp *slirp) |
71 | { |
72 | slirp->icmp.so_next = slirp->icmp.so_prev = &slirp->icmp; |
73 | slirp->icmp_last_so = &slirp->icmp; |
74 | } |
75 | |
76 | void icmp_cleanup(Slirp *slirp) |
77 | { |
78 | while (slirp->icmp.so_next != &slirp->icmp) { |
79 | icmp_detach(slirp->icmp.so_next); |
80 | } |
81 | } |
82 | |
83 | static int icmp_send(struct socket *so, struct mbuf *m, int hlen) |
84 | { |
85 | struct ip *ip = mtod(m, struct ip *); |
86 | struct sockaddr_in addr; |
87 | |
88 | so->s = slirp_socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); |
89 | if (so->s == -1) { |
90 | return -1; |
91 | } |
92 | |
93 | so->so_m = m; |
94 | so->so_faddr = ip->ip_dst; |
95 | so->so_laddr = ip->ip_src; |
96 | so->so_iptos = ip->ip_tos; |
97 | so->so_type = IPPROTO_ICMP; |
98 | so->so_state = SS_ISFCONNECTED; |
99 | so->so_expire = curtime + SO_EXPIRE; |
100 | |
101 | addr.sin_family = AF_INET; |
102 | addr.sin_addr = so->so_faddr; |
103 | |
104 | insque(so, &so->slirp->icmp); |
105 | |
106 | if (sendto(so->s, m->m_data + hlen, m->m_len - hlen, 0, |
107 | (struct sockaddr *)&addr, sizeof(addr)) == -1) { |
108 | DEBUG_MISC("icmp_input icmp sendto tx errno = %d-%s" , errno, |
109 | strerror(errno)); |
110 | icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, strerror(errno)); |
111 | icmp_detach(so); |
112 | } |
113 | |
114 | return 0; |
115 | } |
116 | |
117 | void icmp_detach(struct socket *so) |
118 | { |
119 | so->slirp->cb->unregister_poll_fd(so->s, so->slirp->opaque); |
120 | closesocket(so->s); |
121 | sofree(so); |
122 | } |
123 | |
124 | /* |
125 | * Process a received ICMP message. |
126 | */ |
127 | void icmp_input(struct mbuf *m, int hlen) |
128 | { |
129 | register struct icmp *icp; |
130 | register struct ip *ip = mtod(m, struct ip *); |
131 | int icmplen = ip->ip_len; |
132 | Slirp *slirp = m->slirp; |
133 | |
134 | DEBUG_CALL("icmp_input" ); |
135 | DEBUG_ARG("m = %p" , m); |
136 | DEBUG_ARG("m_len = %d" , m->m_len); |
137 | |
138 | /* |
139 | * Locate icmp structure in mbuf, and check |
140 | * that its not corrupted and of at least minimum length. |
141 | */ |
142 | if (icmplen < ICMP_MINLEN) { /* min 8 bytes payload */ |
143 | freeit: |
144 | m_free(m); |
145 | goto end_error; |
146 | } |
147 | |
148 | m->m_len -= hlen; |
149 | m->m_data += hlen; |
150 | icp = mtod(m, struct icmp *); |
151 | if (cksum(m, icmplen)) { |
152 | goto freeit; |
153 | } |
154 | m->m_len += hlen; |
155 | m->m_data -= hlen; |
156 | |
157 | DEBUG_ARG("icmp_type = %d" , icp->icmp_type); |
158 | switch (icp->icmp_type) { |
159 | case ICMP_ECHO: |
160 | ip->ip_len += hlen; /* since ip_input subtracts this */ |
161 | if (ip->ip_dst.s_addr == slirp->vhost_addr.s_addr || |
162 | ip->ip_dst.s_addr == slirp->vnameserver_addr.s_addr) { |
163 | icmp_reflect(m); |
164 | } else if (slirp->restricted) { |
165 | goto freeit; |
166 | } else { |
167 | struct socket *so; |
168 | struct sockaddr_storage addr; |
169 | so = socreate(slirp); |
170 | if (icmp_send(so, m, hlen) == 0) { |
171 | return; |
172 | } |
173 | if (udp_attach(so, AF_INET) == -1) { |
174 | DEBUG_MISC("icmp_input udp_attach errno = %d-%s" , errno, |
175 | strerror(errno)); |
176 | sofree(so); |
177 | m_free(m); |
178 | goto end_error; |
179 | } |
180 | so->so_m = m; |
181 | so->so_ffamily = AF_INET; |
182 | so->so_faddr = ip->ip_dst; |
183 | so->so_fport = htons(7); |
184 | so->so_lfamily = AF_INET; |
185 | so->so_laddr = ip->ip_src; |
186 | so->so_lport = htons(9); |
187 | so->so_iptos = ip->ip_tos; |
188 | so->so_type = IPPROTO_ICMP; |
189 | so->so_state = SS_ISFCONNECTED; |
190 | |
191 | /* Send the packet */ |
192 | addr = so->fhost.ss; |
193 | sotranslate_out(so, &addr); |
194 | |
195 | if (sendto(so->s, icmp_ping_msg, strlen(icmp_ping_msg), 0, |
196 | (struct sockaddr *)&addr, sockaddr_size(&addr)) == -1) { |
197 | DEBUG_MISC("icmp_input udp sendto tx errno = %d-%s" , errno, |
198 | strerror(errno)); |
199 | icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, |
200 | strerror(errno)); |
201 | udp_detach(so); |
202 | } |
203 | } /* if ip->ip_dst.s_addr == alias_addr.s_addr */ |
204 | break; |
205 | case ICMP_UNREACH: |
206 | /* XXX? report error? close socket? */ |
207 | case ICMP_TIMXCEED: |
208 | case ICMP_PARAMPROB: |
209 | case ICMP_SOURCEQUENCH: |
210 | case ICMP_TSTAMP: |
211 | case ICMP_MASKREQ: |
212 | case ICMP_REDIRECT: |
213 | m_free(m); |
214 | break; |
215 | |
216 | default: |
217 | m_free(m); |
218 | } /* swith */ |
219 | |
220 | end_error: |
221 | /* m is m_free()'d xor put in a socket xor or given to ip_send */ |
222 | return; |
223 | } |
224 | |
225 | |
226 | /* |
227 | * Send an ICMP message in response to a situation |
228 | * |
229 | * RFC 1122: 3.2.2 MUST send at least the IP header and 8 bytes of header. |
230 | *MAY send more (we do). MUST NOT change this header information. MUST NOT reply |
231 | *to a multicast/broadcast IP address. MUST NOT reply to a multicast/broadcast |
232 | *MAC address. MUST reply to only the first fragment. |
233 | */ |
234 | /* |
235 | * Send ICMP_UNREACH back to the source regarding msrc. |
236 | * mbuf *msrc is used as a template, but is NOT m_free()'d. |
237 | * It is reported as the bad ip packet. The header should |
238 | * be fully correct and in host byte order. |
239 | * ICMP fragmentation is illegal. All machines must accept 576 bytes in one |
240 | * packet. The maximum payload is 576-20(ip hdr)-8(icmp hdr)=548 |
241 | */ |
242 | |
243 | #define ICMP_MAXDATALEN (IP_MSS - 28) |
244 | void icmp_send_error(struct mbuf *msrc, uint8_t type, uint8_t code, int minsize, |
245 | const char *message) |
246 | { |
247 | unsigned hlen, shlen, s_ip_len; |
248 | register struct ip *ip; |
249 | register struct icmp *icp; |
250 | register struct mbuf *m; |
251 | |
252 | DEBUG_CALL("icmp_send_error" ); |
253 | DEBUG_ARG("msrc = %p" , msrc); |
254 | DEBUG_ARG("msrc_len = %d" , msrc->m_len); |
255 | |
256 | if (type != ICMP_UNREACH && type != ICMP_TIMXCEED) |
257 | goto end_error; |
258 | |
259 | /* check msrc */ |
260 | if (!msrc) |
261 | goto end_error; |
262 | ip = mtod(msrc, struct ip *); |
263 | if (slirp_debug & DBG_MISC) { |
264 | char bufa[20], bufb[20]; |
265 | strcpy(bufa, inet_ntoa(ip->ip_src)); |
266 | strcpy(bufb, inet_ntoa(ip->ip_dst)); |
267 | DEBUG_MISC(" %.16s to %.16s" , bufa, bufb); |
268 | } |
269 | if (ip->ip_off & IP_OFFMASK) |
270 | goto end_error; /* Only reply to fragment 0 */ |
271 | |
272 | /* Do not reply to source-only IPs */ |
273 | if ((ip->ip_src.s_addr & htonl(~(0xf << 28))) == 0) { |
274 | goto end_error; |
275 | } |
276 | |
277 | shlen = ip->ip_hl << 2; |
278 | s_ip_len = ip->ip_len; |
279 | if (ip->ip_p == IPPROTO_ICMP) { |
280 | icp = (struct icmp *)((char *)ip + shlen); |
281 | /* |
282 | * Assume any unknown ICMP type is an error. This isn't |
283 | * specified by the RFC, but think about it.. |
284 | */ |
285 | if (icp->icmp_type > 18 || icmp_flush[icp->icmp_type]) |
286 | goto end_error; |
287 | } |
288 | |
289 | /* make a copy */ |
290 | m = m_get(msrc->slirp); |
291 | if (!m) { |
292 | goto end_error; |
293 | } |
294 | |
295 | { |
296 | int new_m_size; |
297 | new_m_size = |
298 | sizeof(struct ip) + ICMP_MINLEN + msrc->m_len + ICMP_MAXDATALEN; |
299 | if (new_m_size > m->m_size) |
300 | m_inc(m, new_m_size); |
301 | } |
302 | memcpy(m->m_data, msrc->m_data, msrc->m_len); |
303 | m->m_len = msrc->m_len; /* copy msrc to m */ |
304 | |
305 | /* make the header of the reply packet */ |
306 | ip = mtod(m, struct ip *); |
307 | hlen = sizeof(struct ip); /* no options in reply */ |
308 | |
309 | /* fill in icmp */ |
310 | m->m_data += hlen; |
311 | m->m_len -= hlen; |
312 | |
313 | icp = mtod(m, struct icmp *); |
314 | |
315 | if (minsize) |
316 | s_ip_len = shlen + ICMP_MINLEN; /* return header+8b only */ |
317 | else if (s_ip_len > ICMP_MAXDATALEN) /* maximum size */ |
318 | s_ip_len = ICMP_MAXDATALEN; |
319 | |
320 | m->m_len = ICMP_MINLEN + s_ip_len; /* 8 bytes ICMP header */ |
321 | |
322 | /* min. size = 8+sizeof(struct ip)+8 */ |
323 | |
324 | icp->icmp_type = type; |
325 | icp->icmp_code = code; |
326 | icp->icmp_id = 0; |
327 | icp->icmp_seq = 0; |
328 | |
329 | memcpy(&icp->icmp_ip, msrc->m_data, s_ip_len); /* report the ip packet */ |
330 | HTONS(icp->icmp_ip.ip_len); |
331 | HTONS(icp->icmp_ip.ip_id); |
332 | HTONS(icp->icmp_ip.ip_off); |
333 | |
334 | if (message && WITH_ICMP_ERROR_MSG) { /* append message to ICMP packet */ |
335 | int message_len; |
336 | char *cpnt; |
337 | message_len = strlen(message); |
338 | if (message_len > ICMP_MAXDATALEN) |
339 | message_len = ICMP_MAXDATALEN; |
340 | cpnt = (char *)m->m_data + m->m_len; |
341 | memcpy(cpnt, message, message_len); |
342 | m->m_len += message_len; |
343 | } |
344 | |
345 | icp->icmp_cksum = 0; |
346 | icp->icmp_cksum = cksum(m, m->m_len); |
347 | |
348 | m->m_data -= hlen; |
349 | m->m_len += hlen; |
350 | |
351 | /* fill in ip */ |
352 | ip->ip_hl = hlen >> 2; |
353 | ip->ip_len = m->m_len; |
354 | |
355 | ip->ip_tos = ((ip->ip_tos & 0x1E) | 0xC0); /* high priority for errors */ |
356 | |
357 | ip->ip_ttl = MAXTTL; |
358 | ip->ip_p = IPPROTO_ICMP; |
359 | ip->ip_dst = ip->ip_src; /* ip addresses */ |
360 | ip->ip_src = m->slirp->vhost_addr; |
361 | |
362 | (void)ip_output((struct socket *)NULL, m); |
363 | |
364 | end_error: |
365 | return; |
366 | } |
367 | #undef ICMP_MAXDATALEN |
368 | |
369 | /* |
370 | * Reflect the ip packet back to the source |
371 | */ |
372 | void icmp_reflect(struct mbuf *m) |
373 | { |
374 | register struct ip *ip = mtod(m, struct ip *); |
375 | int hlen = ip->ip_hl << 2; |
376 | int optlen = hlen - sizeof(struct ip); |
377 | register struct icmp *icp; |
378 | |
379 | /* |
380 | * Send an icmp packet back to the ip level, |
381 | * after supplying a checksum. |
382 | */ |
383 | m->m_data += hlen; |
384 | m->m_len -= hlen; |
385 | icp = mtod(m, struct icmp *); |
386 | |
387 | icp->icmp_type = ICMP_ECHOREPLY; |
388 | icp->icmp_cksum = 0; |
389 | icp->icmp_cksum = cksum(m, ip->ip_len - hlen); |
390 | |
391 | m->m_data -= hlen; |
392 | m->m_len += hlen; |
393 | |
394 | /* fill in ip */ |
395 | if (optlen > 0) { |
396 | /* |
397 | * Strip out original options by copying rest of first |
398 | * mbuf's data back, and adjust the IP length. |
399 | */ |
400 | memmove((char *)(ip + 1), (char *)ip + hlen, |
401 | (unsigned)(m->m_len - hlen)); |
402 | hlen -= optlen; |
403 | ip->ip_hl = hlen >> 2; |
404 | ip->ip_len -= optlen; |
405 | m->m_len -= optlen; |
406 | } |
407 | |
408 | ip->ip_ttl = MAXTTL; |
409 | { /* swap */ |
410 | struct in_addr icmp_dst; |
411 | icmp_dst = ip->ip_dst; |
412 | ip->ip_dst = ip->ip_src; |
413 | ip->ip_src = icmp_dst; |
414 | } |
415 | |
416 | (void)ip_output((struct socket *)NULL, m); |
417 | } |
418 | |
419 | void icmp_receive(struct socket *so) |
420 | { |
421 | struct mbuf *m = so->so_m; |
422 | struct ip *ip = mtod(m, struct ip *); |
423 | int hlen = ip->ip_hl << 2; |
424 | uint8_t error_code; |
425 | struct icmp *icp; |
426 | int id, len; |
427 | |
428 | m->m_data += hlen; |
429 | m->m_len -= hlen; |
430 | icp = mtod(m, struct icmp *); |
431 | |
432 | id = icp->icmp_id; |
433 | len = recv(so->s, icp, M_ROOM(m), 0); |
434 | /* |
435 | * The behavior of reading SOCK_DGRAM+IPPROTO_ICMP sockets is inconsistent |
436 | * between host OSes. On Linux, only the ICMP header and payload is |
437 | * included. On macOS/Darwin, the socket acts like a raw socket and |
438 | * includes the IP header as well. On other BSDs, SOCK_DGRAM+IPPROTO_ICMP |
439 | * sockets aren't supported at all, so we treat them like raw sockets. It |
440 | * isn't possible to detect this difference at runtime, so we must use an |
441 | * #ifdef to determine if we need to remove the IP header. |
442 | */ |
443 | #ifdef CONFIG_BSD |
444 | if (len >= sizeof(struct ip)) { |
445 | struct ip *inner_ip = mtod(m, struct ip *); |
446 | int inner_hlen = inner_ip->ip_hl << 2; |
447 | if (inner_hlen > len) { |
448 | len = -1; |
449 | errno = -EINVAL; |
450 | } else { |
451 | len -= inner_hlen; |
452 | memmove(icp, (unsigned char *)icp + inner_hlen, len); |
453 | } |
454 | } else { |
455 | len = -1; |
456 | errno = -EINVAL; |
457 | } |
458 | #endif |
459 | icp->icmp_id = id; |
460 | |
461 | m->m_data -= hlen; |
462 | m->m_len += hlen; |
463 | |
464 | if (len == -1 || len == 0) { |
465 | if (errno == ENETUNREACH) { |
466 | error_code = ICMP_UNREACH_NET; |
467 | } else { |
468 | error_code = ICMP_UNREACH_HOST; |
469 | } |
470 | DEBUG_MISC(" udp icmp rx errno = %d-%s" , errno, strerror(errno)); |
471 | icmp_send_error(so->so_m, ICMP_UNREACH, error_code, 0, strerror(errno)); |
472 | } else { |
473 | icmp_reflect(so->so_m); |
474 | so->so_m = NULL; /* Don't m_free() it again! */ |
475 | } |
476 | icmp_detach(so); |
477 | } |
478 | |