| /* SPDX-License-Identifier: BSD-3-Clause */ | |
| /* | |
| * Copyright (c) 1982, 1986, 1988, 1993 | |
| * The Regents of the University of California. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * 3. Neither the name of the University nor the names of its contributors | |
| * may be used to endorse or promote products derived from this software | |
| * without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
| * SUCH DAMAGE. | |
| * | |
| * @(#)ip_icmp.c 8.2 (Berkeley) 1/4/94 | |
| * ip_icmp.c,v 1.7 1995/05/30 08:09:42 rgrimes Exp | |
| */ | |
| #include "slirp.h" | |
| #include "ip_icmp.h" | |
| #ifndef _WIN32 | |
| #include <sys/param.h> | |
| #endif | |
| #if defined(__linux__) | |
| /* VirtualBox/virtualbox#479: Needed to read Linux ping-socket ICMP router errors. */ | |
| #include <linux/errqueue.h> | |
| #endif | |
| #if defined(_WIN32) | |
| /* VirtualBox/virtualbox#479: Needed for the Windows non-raw ICMP traceroute fallback. */ | |
| #include <iphlpapi.h> | |
| #include <icmpapi.h> | |
| #endif | |
| #ifndef WITH_ICMP_ERROR_MSG | |
| #define WITH_ICMP_ERROR_MSG 0 | |
| #endif | |
| /* The message sent when emulating PING */ | |
| /* Be nice and tell them it's just a pseudo-ping packet */ | |
| static const char icmp_ping_msg[] = | |
| "This is a pseudo-PING packet used by Slirp to emulate ICMP ECHO-REQUEST " | |
| "packets.\n"; | |
| /* list of actions for icmp_send_error() on RX of an icmp message */ | |
| static const int icmp_flush[19] = { | |
| /* ECHO REPLY (0) */ 0, | |
| 1, | |
| 1, | |
| /* DEST UNREACH (3) */ 1, | |
| /* SOURCE QUENCH (4)*/ 1, | |
| /* REDIRECT (5) */ 1, | |
| 1, | |
| 1, | |
| /* ECHO (8) */ 0, | |
| /* ROUTERADVERT (9) */ 1, | |
| /* ROUTERSOLICIT (10) */ 1, | |
| /* TIME EXCEEDED (11) */ 1, | |
| /* PARAMETER PROBLEM (12) */ 1, | |
| /* TIMESTAMP (13) */ 0, | |
| /* TIMESTAMP REPLY (14) */ 0, | |
| /* INFO (15) */ 0, | |
| /* INFO REPLY (16) */ 0, | |
| /* ADDR MASK (17) */ 0, | |
| /* ADDR MASK REPLY (18) */ 0 | |
| }; | |
| void icmp_init(Slirp *slirp) | |
| { | |
| slirp->icmp.so_next = slirp->icmp.so_prev = &slirp->icmp; | |
| slirp->icmp_last_so = &slirp->icmp; | |
| } | |
| void icmp_cleanup(Slirp *slirp) | |
| { | |
| struct socket *so, *so_next; | |
| for (so = slirp->icmp.so_next; so != &slirp->icmp; so = so_next) { | |
| so_next = so->so_next; | |
| icmp_detach(so); | |
| } | |
| } | |
| #if defined(_WIN32) | |
| #define ICMP_WINHELPER_TIMEOUT_MS 1000 | |
| /* VirtualBox/virtualbox#479: Map Windows IP Helper Echo statuses to guest-visible ICMP errors. */ | |
| static bool icmp_winhelper_status_to_icmp(DWORD status, uint8_t *ptype, uint8_t *pcode) | |
| { | |
| if (status == IP_REQ_TIMED_OUT) | |
| return false; | |
| switch (status) { | |
| case IP_TTL_EXPIRED_TRANSIT: | |
| *ptype = ICMP_TIMXCEED; | |
| *pcode = ICMP_TIMXCEED_INTRANS; | |
| return true; | |
| case IP_TTL_EXPIRED_REASSEM: | |
| *ptype = ICMP_TIMXCEED; | |
| *pcode = ICMP_TIMXCEED_REASS; | |
| return true; | |
| case IP_DEST_NET_UNREACHABLE: | |
| *ptype = ICMP_UNREACH; | |
| *pcode = ICMP_UNREACH_NET; | |
| return true; | |
| case IP_DEST_PROT_UNREACHABLE: | |
| *ptype = ICMP_UNREACH; | |
| *pcode = ICMP_UNREACH_PROTOCOL; | |
| return true; | |
| case IP_DEST_PORT_UNREACHABLE: | |
| *ptype = ICMP_UNREACH; | |
| *pcode = ICMP_UNREACH_PORT; | |
| return true; | |
| case IP_PACKET_TOO_BIG: | |
| *ptype = ICMP_UNREACH; | |
| *pcode = ICMP_UNREACH_NEEDFRAG; | |
| return true; | |
| case IP_DEST_HOST_UNREACHABLE: | |
| default: | |
| *ptype = ICMP_UNREACH; | |
| *pcode = ICMP_UNREACH_HOST; | |
| return true; | |
| } | |
| } | |
| /* | |
| * VirtualBox/virtualbox#479: Use Windows IP Helper when low-TTL ping sockets cannot expose intermediate hops. | |
| * This fallback is synchronous and may block the NAT thread up to ICMP_WINHELPER_TIMEOUT_MS. | |
| * A production follow-up should use IcmpSendEcho2Ex() asynchronously and signal completion | |
| * through a pollable loopback socket, so icmp_receive() can forward the result without blocking. | |
| */ | |
| static bool icmp_send_winhelper(struct socket *so, struct mbuf *m, int hlen, int ttl) | |
| { | |
| uint8_t *pbIcmp = (uint8_t *)m->m_data + hlen; | |
| int cbIcmp = m->m_len - hlen; | |
| if (cbIcmp < ICMP_MINLEN) | |
| return false; | |
| int cbPayload = cbIcmp - ICMP_MINLEN; | |
| if (cbPayload > UINT16_MAX) | |
| return false; | |
| HANDLE hIcmp = IcmpCreateFile(); | |
| if (hIcmp == INVALID_HANDLE_VALUE) | |
| return false; | |
| IP_OPTION_INFORMATION OptInfo; | |
| memset(&OptInfo, 0, sizeof(OptInfo)); | |
| OptInfo.Ttl = (UCHAR)ttl; | |
| OptInfo.Tos = so->so_iptos; | |
| IPAddr src_ip = 0; | |
| if (so->slirp->outbound_addr) | |
| src_ip = so->slirp->outbound_addr->sin_addr.s_addr; | |
| DWORD cbReply = sizeof(ICMP_ECHO_REPLY) + cbPayload + 8; | |
| ICMP_ECHO_REPLY *pReply = g_malloc0(cbReply); | |
| /* VirtualBox/virtualbox#479: Synchronous stopgap; see helper comment for the async completion-socket design. */ | |
| DWORD cReplies = IcmpSendEcho2Ex(hIcmp, NULL, NULL, NULL, src_ip, | |
| so->so_faddr.s_addr, pbIcmp + ICMP_MINLEN, | |
| (WORD)cbPayload, &OptInfo, pReply, cbReply, | |
| ICMP_WINHELPER_TIMEOUT_MS); | |
| DWORD dwError = cReplies ? ERROR_SUCCESS : GetLastError(); | |
| IcmpCloseHandle(hIcmp); | |
| if (!cReplies) { | |
| g_free(pReply); | |
| if (dwError == IP_REQ_TIMED_OUT) { | |
| icmp_detach(so); | |
| return true; | |
| } | |
| return false; | |
| } | |
| if (pReply->Status == IP_REQ_TIMED_OUT) { | |
| g_free(pReply); | |
| icmp_detach(so); | |
| return true; | |
| } | |
| bool fHandled = true; | |
| if (pReply->Status == IP_SUCCESS) { | |
| struct ip *ip = mtod(so->so_m, struct ip *); | |
| ip->ip_ttl = pReply->Options.Ttl ? pReply->Options.Ttl : MAXTTL; | |
| icmp_reflect(so->so_m); | |
| so->so_m = NULL; | |
| } else { | |
| uint8_t type; | |
| uint8_t code; | |
| if (icmp_winhelper_status_to_icmp(pReply->Status, &type, &code)) { | |
| struct in_addr src_addr; | |
| src_addr.s_addr = pReply->Address ? pReply->Address : so->so_faddr.s_addr; | |
| icmp_forward_error(so->so_m, type, code, 0, NULL, &src_addr); | |
| } else | |
| fHandled = false; | |
| } | |
| g_free(pReply); | |
| if (fHandled) | |
| icmp_detach(so); | |
| return fHandled; | |
| } | |
| #undef ICMP_WINHELPER_TIMEOUT_MS | |
| #endif | |
| /* Send ICMP packet to the Internet, and save it to so_m */ | |
| static int icmp_send(struct socket *so, struct mbuf *m, int hlen) | |
| { | |
| Slirp *slirp = m->slirp; | |
| struct sockaddr_in addr = { 0 }; | |
| struct ip *ip = mtod(m, struct ip *); | |
| int ttl = ip->ip_ttl - IPTTLDEC; | |
| /* Expire at the NAT hop even when the host cannot open ICMP sockets. */ | |
| if (ttl <= 0) { | |
| icmp_send_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, NULL); | |
| m_free(m); | |
| sofree(so); | |
| return 0; | |
| } | |
| /* | |
| * The behavior of reading SOCK_DGRAM+IPPROTO_ICMP sockets is inconsistent | |
| * between host OSes. On Linux, only the ICMP header and payload is | |
| * included. On macOS/Darwin, the socket acts like a raw socket and | |
| * includes the IP header as well. On other BSDs, SOCK_DGRAM+IPPROTO_ICMP | |
| * sockets aren't supported at all, so we treat them like raw sockets. It | |
| * isn't possible to detect this difference at runtime, so we must use an | |
| * #ifdef to determine if we need to remove the IP header. | |
| */ | |
| #if (defined(BSD) && !defined(__GNU__)) || defined(__APPLE__) | |
| so->so_type = IPPROTO_IP; | |
| #else | |
| so->so_type = IPPROTO_ICMP; | |
| #endif | |
| #ifndef __linux__ | |
| /* Prefer raw replies for low-TTL probes where ping sockets cannot | |
| * expose router errors. Failure still falls back to SOCK_DGRAM. */ | |
| if (ttl < IPDEFTTL) { | |
| so->s = slirp_socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); | |
| if (have_valid_socket(so->s)) { | |
| so->so_type = IPPROTO_IP; | |
| } | |
| } | |
| #endif | |
| if (not_valid_socket(so->s)) { | |
| so->s = slirp_socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); | |
| if (not_valid_socket(so->s)) { | |
| so->s = slirp_socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); | |
| so->so_type = IPPROTO_IP; | |
| } | |
| } | |
| if (not_valid_socket(so->s)) { | |
| return -1; | |
| } | |
| #if defined(__APPLE__) && defined(IP_STRIPHDR) | |
| if (so->so_type == IPPROTO_IP) { | |
| /* VirtualBox/virtualbox#479: Darwin ICMP dgram replies must keep the IP header for router-error parsing. */ | |
| int fStripHdr = 0; | |
| setsockopt(so->s, IPPROTO_IP, IP_STRIPHDR, &fStripHdr, sizeof(fStripHdr)); | |
| } | |
| #endif | |
| if (slirp_bind_outbound(so, AF_INET) != 0) { | |
| // bind failed - close socket | |
| closesocket(so->s); | |
| so->s = SLIRP_INVALID_SOCKET; | |
| return -1; | |
| } | |
| slirp_register_poll_socket(so); | |
| M_DUP_DEBUG(slirp, m, 0, 0); | |
| ip = mtod(m, struct ip *); | |
| so->so_m = m; | |
| so->so_faddr = ip->ip_dst; | |
| so->so_laddr = ip->ip_src; | |
| so->so_iptos = ip->ip_tos; | |
| so->so_state = SS_ISFCONNECTED; | |
| so->so_expire = curtime + SO_EXPIRE; | |
| addr.sin_family = AF_INET; | |
| addr.sin_addr = so->so_faddr; | |
| #ifdef _WIN32 | |
| if (ttl < IPDEFTTL && so->so_type == IPPROTO_ICMP && icmp_send_winhelper(so, m, hlen, ttl)) | |
| return 0; | |
| #endif | |
| #ifdef __linux__ | |
| if (so->so_type == IPPROTO_ICMP) { | |
| /* VirtualBox/virtualbox#479: Ping sockets report router errors via MSG_ERRQUEUE. */ | |
| int fRecvErr = 1; | |
| setsockopt(so->s, IPPROTO_IP, IP_RECVERR, &fRecvErr, sizeof(fRecvErr)); | |
| setsockopt(so->s, IPPROTO_IP, IP_RECVTTL, &fRecvErr, sizeof(fRecvErr)); | |
| } | |
| #endif | |
| setsockopt(so->s, IPPROTO_IP, IP_TTL, (const void *)&ttl, sizeof(ttl)); | |
| slirp_insque(so, &so->slirp->icmp); | |
| if (sendto(so->s, m->m_data + hlen, m->m_len - hlen, 0, | |
| (struct sockaddr *)&addr, sizeof(addr)) == -1) { | |
| DEBUG_MISC("icmp_input icmp sendto tx errno = %d-%s", errno, | |
| g_strerror(errno)); | |
| icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, g_strerror(errno)); | |
| icmp_detach(so); | |
| } | |
| return 0; | |
| } | |
| void icmp_detach(struct socket *so) | |
| { | |
| slirp_unregister_poll_socket(so); | |
| closesocket(so->s); | |
| sofree(so); | |
| } | |
| /* | |
| * Process a received ICMP message. | |
| */ | |
| void icmp_input(struct mbuf *m, int hlen) | |
| { | |
| Slirp *slirp = m->slirp; | |
| M_DUP_DEBUG(slirp, m, 0, 0); | |
| register struct icmp *icp; | |
| register struct ip *ip = mtod(m, struct ip *); | |
| int icmplen = ip->ip_len; | |
| DEBUG_CALL("icmp_input"); | |
| DEBUG_ARG("m = %p", m); | |
| DEBUG_ARG("m_len = %d", m->m_len); | |
| /* | |
| * Locate icmp structure in mbuf, and check | |
| * that its not corrupted and of at least minimum length. | |
| */ | |
| if (icmplen < ICMP_MINLEN) { /* min 8 bytes payload */ | |
| freeit: | |
| m_free(m); | |
| goto end_error; | |
| } | |
| m->m_len -= hlen; | |
| m->m_data += hlen; | |
| icp = mtod(m, struct icmp *); | |
| if (cksum(m, icmplen)) { | |
| goto freeit; | |
| } | |
| m->m_len += hlen; | |
| m->m_data -= hlen; | |
| DEBUG_ARG("icmp_type = %d", icp->icmp_type); | |
| switch (icp->icmp_type) { | |
| case ICMP_ECHO: | |
| ip->ip_len += hlen; /* since ip_input subtracts this */ | |
| if (ip->ip_dst.s_addr == slirp->vhost_addr.s_addr || | |
| ip->ip_dst.s_addr == slirp->vnameserver_addr.s_addr) { | |
| ip->ip_ttl = MAXTTL; | |
| icmp_reflect(m); | |
| } else if (slirp->restricted) { | |
| goto freeit; | |
| } else { | |
| struct socket *so; | |
| struct sockaddr_storage addr; | |
| int ttl; | |
| so = socreate(slirp, IPPROTO_ICMP); | |
| if (icmp_send(so, m, hlen) == 0) { | |
| /* We could send this as ICMP, good! */ | |
| return; | |
| } | |
| /* We could not send this as ICMP, try to send it on UDP echo | |
| * service (7), wishfully hoping that it is open there. */ | |
| if (not_valid_socket(udp_attach(so, AF_INET))) { | |
| DEBUG_MISC("icmp_input udp_attach errno = %d-%s", errno, | |
| g_strerror(errno)); | |
| sofree(so); | |
| m_free(m); | |
| goto end_error; | |
| } | |
| so->so_m = m; | |
| so->so_ffamily = AF_INET; | |
| so->so_faddr = ip->ip_dst; | |
| so->so_fport = htons(7); | |
| so->so_lfamily = AF_INET; | |
| so->so_laddr = ip->ip_src; | |
| so->so_lport = htons(9); | |
| so->so_iptos = ip->ip_tos; | |
| so->so_state = SS_ISFCONNECTED; | |
| /* Send the packet */ | |
| addr = so->fhost.ss; | |
| if (sotranslate_out(so, &addr) < 0) { | |
| icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, | |
| g_strerror(errno)); | |
| udp_detach(so); | |
| return; | |
| } | |
| /* | |
| * Check for TTL | |
| */ | |
| ttl = ip->ip_ttl-1; | |
| if (ttl <= 0) { | |
| DEBUG_MISC("udp ttl exceeded"); | |
| icmp_send_error(m, ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS, 0, | |
| NULL); | |
| udp_detach(so); | |
| break; | |
| } | |
| setsockopt(so->s, IPPROTO_IP, IP_TTL, (const void *)&ttl, sizeof(ttl)); | |
| if (sendto(so->s, icmp_ping_msg, strlen(icmp_ping_msg), 0, | |
| (struct sockaddr *)&addr, sockaddr_size(&addr)) == -1) { | |
| DEBUG_MISC("icmp_input udp sendto tx errno = %d-%s", errno, | |
| g_strerror(errno)); | |
| icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_NET, 0, | |
| g_strerror(errno)); | |
| udp_detach(so); | |
| } | |
| } /* if ip->ip_dst.s_addr == alias_addr.s_addr */ | |
| break; | |
| case ICMP_UNREACH: | |
| /* XXX? report error? close socket? */ | |
| case ICMP_TIMXCEED: | |
| case ICMP_PARAMPROB: | |
| case ICMP_SOURCEQUENCH: | |
| case ICMP_TSTAMP: | |
| case ICMP_MASKREQ: | |
| case ICMP_REDIRECT: | |
| m_free(m); | |
| break; | |
| default: | |
| m_free(m); | |
| } /* switch */ | |
| end_error: | |
| /* m is m_free()'d xor put in a socket xor or given to ip_send */ | |
| return; | |
| } | |
| /* | |
| * Send an ICMP message in response to a situation | |
| * | |
| * RFC 1122: 3.2.2 MUST send at least the IP header and 8 bytes of header. | |
| *MAY send more (we do). MUST NOT change this header information. MUST NOT reply | |
| *to a multicast/broadcast IP address. MUST NOT reply to a multicast/broadcast | |
| *MAC address. MUST reply to only the first fragment. | |
| */ | |
| /* | |
| * Send ICMP_UNREACH back to the source regarding msrc. | |
| * mbuf *msrc is used as a template, but is NOT m_free()'d. | |
| * It is reported as the bad ip packet. The header should | |
| * be fully correct and in host byte order. | |
| * ICMP fragmentation is illegal. All machines must accept 576 bytes in one | |
| * packet. The maximum payload is 576-20(ip hdr)-8(icmp hdr)=548 | |
| */ | |
| #define ICMP_MAXDATALEN (IP_MSS - 28) | |
| void icmp_forward_error(struct mbuf *msrc, uint8_t type, uint8_t code, int minsize, | |
| const char *message, struct in_addr *src) | |
| { | |
| unsigned hlen, shlen, s_ip_len; | |
| register struct ip *ip; | |
| register struct icmp *icp; | |
| register struct mbuf *m; | |
| DEBUG_CALL("icmp_send_error"); | |
| DEBUG_ARG("msrc = %p", msrc); | |
| DEBUG_ARG("msrc_len = %d", msrc->m_len); | |
| if (type != ICMP_UNREACH && type != ICMP_TIMXCEED) | |
| goto end_error; | |
| /* check msrc */ | |
| if (!msrc) | |
| goto end_error; | |
| ip = mtod(msrc, struct ip *); | |
| if (slirp_debug & SLIRP_DBG_MISC) { | |
| char addr_src[INET_ADDRSTRLEN]; | |
| char addr_dst[INET_ADDRSTRLEN]; | |
| inet_ntop(AF_INET, &ip->ip_src, addr_src, sizeof(addr_src)); | |
| inet_ntop(AF_INET, &ip->ip_dst, addr_dst, sizeof(addr_dst)); | |
| DEBUG_MISC(" %.16s to %.16s", addr_src, addr_dst); | |
| } | |
| if (ip->ip_off & IP_OFFMASK) | |
| goto end_error; /* Only reply to fragment 0 */ | |
| /* Do not reply to source-only IPs */ | |
| if ((ip->ip_src.s_addr & htonl(~(0xfU << 28))) == 0) { | |
| goto end_error; | |
| } | |
| shlen = ip->ip_hl << 2; | |
| s_ip_len = ip->ip_len; | |
| if (ip->ip_p == IPPROTO_ICMP) { | |
| icp = (struct icmp *)((char *)ip + shlen); | |
| /* | |
| * Assume any unknown ICMP type is an error. This isn't | |
| * specified by the RFC, but think about it.. | |
| */ | |
| if (icp->icmp_type > 18 || icmp_flush[icp->icmp_type]) | |
| goto end_error; | |
| } | |
| /* make a copy */ | |
| m = m_get(msrc->slirp); | |
| if (!m) { | |
| goto end_error; | |
| } | |
| { | |
| int new_m_size; | |
| new_m_size = | |
| sizeof(struct ip) + ICMP_MINLEN + msrc->m_len + ICMP_MAXDATALEN; | |
| if (new_m_size > m->m_size) | |
| m_inc(m, new_m_size); | |
| } | |
| memcpy(m->m_data, msrc->m_data, msrc->m_len); | |
| m->m_len = msrc->m_len; /* copy msrc to m */ | |
| /* make the header of the reply packet */ | |
| ip = mtod(m, struct ip *); | |
| hlen = sizeof(struct ip); /* no options in reply */ | |
| /* fill in icmp */ | |
| m->m_data += hlen; | |
| m->m_len -= hlen; | |
| icp = mtod(m, struct icmp *); | |
| if (minsize) | |
| s_ip_len = shlen + ICMP_MINLEN; /* return header+8b only */ | |
| else if (s_ip_len > ICMP_MAXDATALEN) /* maximum size */ | |
| s_ip_len = ICMP_MAXDATALEN; | |
| if (s_ip_len > msrc->m_len) | |
| s_ip_len = msrc->m_len; | |
| m->m_len = ICMP_MINLEN + s_ip_len; /* 8 bytes ICMP header */ | |
| /* min. size = 8+sizeof(struct ip)+8 */ | |
| icp->icmp_type = type; | |
| icp->icmp_code = code; | |
| icp->icmp_id = 0; | |
| icp->icmp_seq = 0; | |
| memcpy(&icp->icmp_ip, msrc->m_data, s_ip_len); /* report the ip packet */ | |
| HTONS(icp->icmp_ip.ip_len); | |
| HTONS(icp->icmp_ip.ip_id); | |
| HTONS(icp->icmp_ip.ip_off); | |
| if (message && WITH_ICMP_ERROR_MSG) { /* append message to ICMP packet */ | |
| int message_len; | |
| char *cpnt; | |
| message_len = strlen(message); | |
| if (message_len > ICMP_MAXDATALEN) | |
| message_len = ICMP_MAXDATALEN; | |
| cpnt = (char *)m->m_data + m->m_len; | |
| memcpy(cpnt, message, message_len); | |
| m->m_len += message_len; | |
| } | |
| icp->icmp_cksum = 0; | |
| icp->icmp_cksum = cksum(m, m->m_len); | |
| m->m_data -= hlen; | |
| m->m_len += hlen; | |
| /* fill in ip */ | |
| ip->ip_hl = hlen >> 2; | |
| ip->ip_len = m->m_len; | |
| ip->ip_tos = ((ip->ip_tos & 0x1E) | 0xC0); /* high priority for errors */ | |
| ip->ip_ttl = MAXTTL; | |
| ip->ip_p = IPPROTO_ICMP; | |
| ip->ip_dst = ip->ip_src; /* ip addresses */ | |
| ip->ip_src = *src; | |
| ip_output((struct socket *)NULL, m); | |
| end_error: | |
| return; | |
| } | |
| #undef ICMP_MAXDATALEN | |
| void icmp_send_error(struct mbuf *msrc, uint8_t type, uint8_t code, int minsize, | |
| const char *message) | |
| { | |
| icmp_forward_error(msrc, type, code, minsize, message, &msrc->slirp->vhost_addr); | |
| } | |
| /* | |
| * Reflect the ip packet back to the source | |
| */ | |
| void icmp_reflect(struct mbuf *m) | |
| { | |
| register struct ip *ip = mtod(m, struct ip *); | |
| int hlen = ip->ip_hl << 2; | |
| int optlen = hlen - sizeof(struct ip); | |
| register struct icmp *icp; | |
| /* | |
| * Send an icmp packet back to the ip level, | |
| * after supplying a checksum. | |
| */ | |
| m->m_data += hlen; | |
| m->m_len -= hlen; | |
| icp = mtod(m, struct icmp *); | |
| icp->icmp_type = ICMP_ECHOREPLY; | |
| icp->icmp_cksum = 0; | |
| icp->icmp_cksum = cksum(m, ip->ip_len - hlen); | |
| m->m_data -= hlen; | |
| m->m_len += hlen; | |
| /* fill in ip */ | |
| if (optlen > 0) { | |
| /* | |
| * Strip out original options by copying rest of first | |
| * mbuf's data back, and adjust the IP length. | |
| */ | |
| memmove((char *)(ip + 1), (char *)ip + hlen, | |
| (unsigned)(m->m_len - hlen)); | |
| hlen -= optlen; | |
| ip->ip_hl = hlen >> 2; | |
| ip->ip_len -= optlen; | |
| m->m_len -= optlen; | |
| } | |
| /* VirtualBox/virtualbox#479: Keep real Echo Reply TTL instead of resetting it to MAXTTL. */ | |
| { /* swap */ | |
| struct in_addr icmp_dst; | |
| icmp_dst = ip->ip_dst; | |
| ip->ip_dst = ip->ip_src; | |
| ip->ip_src = icmp_dst; | |
| } | |
| ip_output((struct socket *)NULL, m); | |
| } | |
| #ifdef __linux__ | |
| /* VirtualBox/virtualbox#479: Return true after draining a Linux ping-socket error queue entry, forwarding any ICMP router error. */ | |
| static bool icmp_receive_error_queue(struct socket *so) | |
| { | |
| char buf[256]; | |
| char control[1024]; | |
| struct sockaddr_storage saddr; | |
| struct msghdr msg; | |
| struct iovec iov; | |
| ssize_t len; | |
| /* Prepare recvmsg() buffers for the queued ICMP error and its ancillary data. */ | |
| memset(&msg, 0, sizeof(msg)); | |
| msg.msg_name = &saddr; | |
| msg.msg_namelen = sizeof(saddr); | |
| msg.msg_control = control; | |
| msg.msg_controllen = sizeof(control); | |
| iov.iov_base = buf; | |
| iov.iov_len = sizeof(buf); | |
| msg.msg_iov = &iov; | |
| msg.msg_iovlen = 1; | |
| /* Drain at most one queued router error without blocking normal Echo Reply receive. */ | |
| len = recvmsg(so->s, &msg, MSG_ERRQUEUE | MSG_DONTWAIT); | |
| if (len < 0) | |
| return false; | |
| /* Linux reports ping-socket ICMP errors as IP_RECVERR control messages. */ | |
| struct cmsghdr *cmsg; | |
| for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { | |
| if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVERR) { | |
| struct sock_extended_err *ee = (struct sock_extended_err *)CMSG_DATA(cmsg); | |
| if (cmsg->cmsg_len >= CMSG_LEN(sizeof(*ee) + sizeof(struct sockaddr_in)) | |
| && ee->ee_origin == SO_EE_ORIGIN_ICMP) { | |
| /* The offender address is the intermediate router traceroute must show. */ | |
| struct sockaddr *sa = SO_EE_OFFENDER(ee); | |
| if (sa->sa_family == AF_INET) { | |
| struct sockaddr_in *sin; | |
| /* VirtualBox/virtualbox#479: Forward Linux ping-socket router errors to the guest. */ | |
| sin = (struct sockaddr_in *)sa; | |
| icmp_forward_error(so->so_m, ee->ee_type, ee->ee_code, | |
| 0, NULL, &sin->sin_addr); | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| #endif | |
| /* VirtualBox/virtualbox#479: Receive into a temporary buffer so router errors do not overwrite the quoted Echo Request. */ | |
| void icmp_receive(struct socket *so) | |
| { | |
| struct mbuf *m = so->so_m; | |
| struct ip *ip = mtod(m, struct ip *); | |
| int hlen = ip->ip_hl << 2; | |
| uint8_t error_code; | |
| struct in_addr icmp_src = ip->ip_dst; | |
| uint8_t icmp_ttl = MAXTTL; | |
| bool f_have_icmp_ttl = false; | |
| struct icmp *icp; | |
| struct icmp *icp_rx; | |
| unsigned char *buf; | |
| struct sockaddr_storage srcaddr; | |
| #ifndef __linux__ | |
| socklen_t srcaddrlen; | |
| #endif | |
| int id, len, room; | |
| #ifdef __linux__ | |
| if (so->so_type == IPPROTO_ICMP && icmp_receive_error_queue(so)) { | |
| icmp_detach(so); | |
| return; | |
| } | |
| #endif | |
| m->m_data += hlen; | |
| m->m_len -= hlen; | |
| icp = mtod(m, struct icmp *); | |
| id = icp->icmp_id; | |
| room = M_ROOM(m); | |
| buf = g_malloc(room); | |
| memset(&srcaddr, 0, sizeof(srcaddr)); | |
| #ifndef __linux__ | |
| srcaddrlen = sizeof(srcaddr); | |
| #endif | |
| /* VirtualBox/virtualbox#479: recvfrom keeps the hop source on hosts delivering in-band ICMP errors without IP headers. */ | |
| #ifdef __linux__ | |
| struct iovec iov = { .iov_base = buf, .iov_len = room }; | |
| union { | |
| struct cmsghdr align; | |
| char data[CMSG_SPACE(sizeof(int))]; | |
| } control; | |
| struct msghdr msg = { | |
| .msg_name = &srcaddr, | |
| .msg_namelen = sizeof(srcaddr), | |
| .msg_iov = &iov, | |
| .msg_iovlen = 1, | |
| .msg_control = control.data, | |
| .msg_controllen = sizeof(control.data), | |
| }; | |
| len = recvmsg(so->s, &msg, MSG_DONTWAIT); | |
| if (len > 0 && !(msg.msg_flags & MSG_CTRUNC)) { | |
| struct cmsghdr *cmsg; | |
| for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { | |
| if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TTL && | |
| cmsg->cmsg_len >= CMSG_LEN(sizeof(int))) { | |
| int ttl; | |
| memcpy(&ttl, CMSG_DATA(cmsg), sizeof(ttl)); | |
| if (ttl > 0 && ttl <= MAXTTL) { | |
| icmp_ttl = ttl; | |
| f_have_icmp_ttl = true; | |
| } | |
| } | |
| } | |
| } | |
| #else | |
| len = recvfrom(so->s, (char *)buf, room, 0, | |
| (struct sockaddr *)&srcaddr, &srcaddrlen); | |
| #endif | |
| if (len > 0 && srcaddr.ss_family == AF_INET) | |
| icmp_src = ((struct sockaddr_in *)&srcaddr)->sin_addr; | |
| icp_rx = (struct icmp *)buf; | |
| if (so->so_type == IPPROTO_IP && len > 0) { | |
| if (len >= sizeof(struct ip)) { | |
| struct ip *inner_ip = (struct ip *)buf; | |
| int inner_hlen = inner_ip->ip_hl << 2; | |
| if (inner_hlen < (int)sizeof(struct ip) || inner_hlen > len) { | |
| len = -1; | |
| errno = EINVAL; | |
| } else { | |
| /* VirtualBox/virtualbox#479: Raw ICMP sockets include an IP header; keep its source and Echo Reply TTL. */ | |
| icmp_ttl = inner_ip->ip_ttl; | |
| f_have_icmp_ttl = true; | |
| icmp_src = inner_ip->ip_src; | |
| len -= inner_hlen; | |
| icp_rx = (struct icmp *)(buf + inner_hlen); | |
| } | |
| } else { | |
| len = -1; | |
| errno = EINVAL; | |
| } | |
| } | |
| m->m_data -= hlen; | |
| m->m_len += hlen; | |
| if (len < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { | |
| g_free(buf); | |
| return; | |
| } | |
| if (len == -1 || len == 0) { | |
| if (errno == ENETUNREACH) { | |
| error_code = ICMP_UNREACH_NET; | |
| } else { | |
| error_code = ICMP_UNREACH_HOST; | |
| } | |
| DEBUG_MISC(" udp icmp rx errno = %d-%s", errno, g_strerror(errno)); | |
| icmp_send_error(m, ICMP_UNREACH, error_code, 0, g_strerror(errno)); | |
| } else { | |
| /* Do not reflect ICMP errors as echo replies. */ | |
| if (len < ICMP_MINLEN) { | |
| icmp_send_error(m, ICMP_UNREACH, ICMP_UNREACH_HOST, 0, "short icmp"); | |
| } else if (icp_rx->icmp_type == ICMP_TIMXCEED || icp_rx->icmp_type == ICMP_UNREACH) { | |
| /* Raw sockets also receive errors for other probes. Only forward | |
| * a quote that identifies this socket's original echo request. */ | |
| struct ip *quoted = (struct ip *)((char *)icp_rx + ICMP_MINLEN); | |
| int quoted_hlen; | |
| struct icmp *quoted_icmp; | |
| if (len < ICMP_MINLEN + sizeof(struct ip)) { | |
| g_free(buf); | |
| return; | |
| } | |
| quoted_hlen = quoted->ip_hl << 2; | |
| if (quoted->ip_v != IPVERSION || quoted->ip_p != IPPROTO_ICMP || | |
| quoted_hlen < sizeof(struct ip) || | |
| len < ICMP_MINLEN + quoted_hlen + ICMP_MINLEN || | |
| quoted->ip_dst.s_addr != ip->ip_dst.s_addr) { | |
| g_free(buf); | |
| return; | |
| } | |
| quoted_icmp = (struct icmp *)((char *)quoted + quoted_hlen); | |
| if (quoted_icmp->icmp_type != ICMP_ECHO || | |
| quoted_icmp->icmp_seq != icp->icmp_seq || | |
| (so->so_type == IPPROTO_IP && quoted_icmp->icmp_id != id)) { | |
| g_free(buf); | |
| return; | |
| } | |
| /* VirtualBox/virtualbox#479: Forward router errors using the untouched Echo Request as the quote. */ | |
| icmp_forward_error(so->so_m, icp_rx->icmp_type, icp_rx->icmp_code, | |
| 0, NULL, &icmp_src); | |
| } else if (icp_rx->icmp_type == ICMP_ECHOREPLY && | |
| icp_rx->icmp_seq == icp->icmp_seq && | |
| (so->so_type != IPPROTO_IP || icp_rx->icmp_id == id)) { | |
| /* VirtualBox/virtualbox#479: Copy real Echo Replies into so_m and preserve their received TTL. */ | |
| m->m_data += hlen; | |
| m->m_len -= hlen; | |
| icp = mtod(m, struct icmp *); | |
| memcpy(icp, icp_rx, len); | |
| icp->icmp_id = id; | |
| m->m_data -= hlen; | |
| m->m_len = hlen + len; | |
| ip->ip_len = m->m_len; | |
| ip->ip_ttl = f_have_icmp_ttl ? icmp_ttl : MAXTTL; | |
| icmp_reflect(so->so_m); | |
| so->so_m = NULL; /* Don't m_free() it again! */ | |
| } else { | |
| /* Raw sockets can deliver unrelated ICMP traffic. */ | |
| g_free(buf); | |
| return; | |
| } | |
| } | |
| g_free(buf); | |
| icmp_detach(so); | |
| } |