Path: blob/a-new-beginning/SharedDependencies/Sources/libslirp/ip6_icmp.c
2 views
/* SPDX-License-Identifier: BSD-3-Clause */1/*2* Copyright (c) 20133* Guillaume Subiron, Yann Bordenave, Serigne Modou Wagne.4*/56#include "slirp.h"7#include "ip6_icmp.h"89#define NDP_Interval \10g_rand_int_range(slirp->grand, NDP_MinRtrAdvInterval, NDP_MaxRtrAdvInterval)1112/* The message sent when emulating PING */13/* Be nice and tell them it's just a pseudo-ping packet */14static const char icmp6_ping_msg[] =15"This is a pseudo-PING packet used by Slirp to emulate ICMPV6 ECHO-REQUEST "16"packets.\n";1718void icmp6_post_init(Slirp *slirp)19{20if (!slirp->in6_enabled) {21return;22}2324slirp->ra_timer =25slirp_timer_new(slirp, SLIRP_TIMER_RA, NULL);26slirp->cb->timer_mod(slirp->ra_timer,27slirp->cb->clock_get_ns(slirp->opaque) / SCALE_MS +28NDP_Interval,29slirp->opaque);30}3132void icmp6_cleanup(Slirp *slirp)33{34if (!slirp->in6_enabled) {35return;36}3738slirp->cb->timer_free(slirp->ra_timer, slirp->opaque);39}4041/* Send ICMP packet to the Internet, and save it to so_m */42static int icmp6_send(struct socket *so, struct mbuf *m, int hlen)43{44Slirp *slirp = m->slirp;4546struct sockaddr_in6 addr;4748/*49* The behavior of reading SOCK_DGRAM+IPPROTO_ICMP sockets is inconsistent50* between host OSes. On Linux, only the ICMP header and payload is51* included. On macOS/Darwin, the socket acts like a raw socket and52* includes the IP header as well. On other BSDs, SOCK_DGRAM+IPPROTO_ICMP53* sockets aren't supported at all, so we treat them like raw sockets. It54* isn't possible to detect this difference at runtime, so we must use an55* #ifdef to determine if we need to remove the IP header.56*/57#if defined(BSD) && !defined(__GNU__)58so->so_type = IPPROTO_IPV6;59#else60so->so_type = IPPROTO_ICMPV6;61#endif6263so->s = slirp_socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);64if (so->s == -1) {65if (errno == EAFNOSUPPORT66|| errno == EPROTONOSUPPORT67|| errno == EACCES) {68/* Kernel doesn't support or allow ping sockets. */69so->so_type = IPPROTO_IPV6;70so->s = slirp_socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);71}72}73if (so->s == -1) {74return -1;75}76so->slirp->cb->register_poll_fd(so->s, so->slirp->opaque);7778if (slirp_bind_outbound(so, AF_INET6) != 0) {79// bind failed - close socket80closesocket(so->s);81so->s = -1;82return -1;83}8485M_DUP_DEBUG(slirp, m, 0, 0);86struct ip6 *ip = mtod(m, struct ip6 *);8788so->so_m = m;89so->so_faddr6 = ip->ip_dst;90so->so_laddr6 = ip->ip_src;91so->so_state = SS_ISFCONNECTED;92so->so_expire = curtime + SO_EXPIRE;9394addr.sin6_family = AF_INET6;95addr.sin6_addr = so->so_faddr6;9697slirp_insque(so, &so->slirp->icmp);9899if (sendto(so->s, m->m_data + hlen, m->m_len - hlen, 0,100(struct sockaddr *)&addr, sizeof(addr)) == -1) {101DEBUG_MISC("icmp6_input icmp sendto tx errno = %d-%s", errno,102strerror(errno));103icmp6_send_error(m, ICMP6_UNREACH, ICMP6_UNREACH_NO_ROUTE);104icmp_detach(so);105}106107return 0;108}109110static void icmp6_send_echoreply(struct mbuf *m, Slirp *slirp, struct ip6 *ip,111struct icmp6 *icmp)112{113struct mbuf *t = m_get(slirp);114t->m_len = sizeof(struct ip6) + ntohs(ip->ip_pl);115memcpy(t->m_data, m->m_data, t->m_len);116117/* IPv6 Packet */118struct ip6 *rip = mtod(t, struct ip6 *);119rip->ip_dst = ip->ip_src;120rip->ip_src = ip->ip_dst;121122/* ICMPv6 packet */123t->m_data += sizeof(struct ip6);124struct icmp6 *ricmp = mtod(t, struct icmp6 *);125ricmp->icmp6_type = ICMP6_ECHO_REPLY;126ricmp->icmp6_cksum = 0;127128/* Checksum */129t->m_data -= sizeof(struct ip6);130ricmp->icmp6_cksum = ip6_cksum(t);131132ip6_output(NULL, t, 0);133}134135void icmp6_forward_error(struct mbuf *m, uint8_t type, uint8_t code, struct in6_addr *src)136{137Slirp *slirp = m->slirp;138struct mbuf *t;139struct ip6 *ip = mtod(m, struct ip6 *);140char addrstr[INET6_ADDRSTRLEN];141142DEBUG_CALL("icmp6_send_error");143DEBUG_ARG("type = %d, code = %d", type, code);144145if (IN6_IS_ADDR_MULTICAST(&ip->ip_src) || in6_zero(&ip->ip_src)) {146/* TODO icmp error? */147return;148}149150t = m_get(slirp);151152/* IPv6 packet */153struct ip6 *rip = mtod(t, struct ip6 *);154rip->ip_src = *src;155rip->ip_dst = ip->ip_src;156inet_ntop(AF_INET6, &rip->ip_dst, addrstr, INET6_ADDRSTRLEN);157DEBUG_ARG("target = %s", addrstr);158159rip->ip_nh = IPPROTO_ICMPV6;160const int error_data_len = MIN(161m->m_len, slirp->if_mtu - (sizeof(struct ip6) + ICMP6_ERROR_MINLEN));162rip->ip_pl = htons(ICMP6_ERROR_MINLEN + error_data_len);163t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl);164165/* ICMPv6 packet */166t->m_data += sizeof(struct ip6);167struct icmp6 *ricmp = mtod(t, struct icmp6 *);168ricmp->icmp6_type = type;169ricmp->icmp6_code = code;170ricmp->icmp6_cksum = 0;171172switch (type) {173case ICMP6_UNREACH:174case ICMP6_TIMXCEED:175ricmp->icmp6_err.unused = 0;176break;177case ICMP6_TOOBIG:178ricmp->icmp6_err.mtu = htonl(slirp->if_mtu);179break;180case ICMP6_PARAMPROB:181/* TODO: Handle this case */182break;183default:184g_assert_not_reached();185}186t->m_data += ICMP6_ERROR_MINLEN;187memcpy(t->m_data, m->m_data, error_data_len);188189/* Checksum */190t->m_data -= ICMP6_ERROR_MINLEN;191t->m_data -= sizeof(struct ip6);192ricmp->icmp6_cksum = ip6_cksum(t);193194ip6_output(NULL, t, 0);195}196197void icmp6_send_error(struct mbuf *m, uint8_t type, uint8_t code)198{199struct in6_addr src = LINKLOCAL_ADDR;200icmp6_forward_error(m, type, code, &src);201}202203/*204* Reflect the ip packet back to the source205*/206void icmp6_reflect(struct mbuf *m)207{208register struct ip6 *ip = mtod(m, struct ip6 *);209int hlen = sizeof(struct ip6);210register struct icmp6 *icp;211212/*213* Send an icmp packet back to the ip level,214* after supplying a checksum.215*/216m->m_data += hlen;217m->m_len -= hlen;218icp = mtod(m, struct icmp6 *);219220icp->icmp6_type = ICMP6_ECHO_REPLY;221222m->m_data -= hlen;223m->m_len += hlen;224225icp->icmp6_cksum = 0;226icp->icmp6_cksum = ip6_cksum(m);227228ip->ip_hl = MAXTTL;229{ /* swap */230struct in6_addr icmp_dst;231icmp_dst = ip->ip_dst;232ip->ip_dst = ip->ip_src;233ip->ip_src = icmp_dst;234}235236ip6_output((struct socket *)NULL, m, 0);237}238239void icmp6_receive(struct socket *so)240{241struct mbuf *m = so->so_m;242int hlen = sizeof(struct ip6);243uint8_t error_code;244struct icmp6 *icp;245int id, seq, len;246247m->m_data += hlen;248m->m_len -= hlen;249icp = mtod(m, struct icmp6 *);250251id = icp->icmp6_id;252seq = icp->icmp6_seq;253len = recv(so->s, icp, M_ROOM(m), 0);254255icp->icmp6_id = id;256icp->icmp6_seq = seq;257258m->m_data -= hlen;259m->m_len += hlen;260261if (len == -1 || len == 0) {262if (errno == ENETUNREACH) {263error_code = ICMP6_UNREACH_NO_ROUTE;264} else {265error_code = ICMP6_UNREACH_ADDRESS;266}267DEBUG_MISC(" udp icmp rx errno = %d-%s", errno, strerror(errno));268icmp6_send_error(so->so_m, ICMP_UNREACH, error_code);269} else {270icmp6_reflect(so->so_m);271so->so_m = NULL; /* Don't m_free() it again! */272}273icmp_detach(so);274}275276/*277* Send NDP Router Advertisement278*/279static void ndp_send_ra(Slirp *slirp)280{281DEBUG_CALL("ndp_send_ra");282283/* Build IPv6 packet */284struct mbuf *t = m_get(slirp);285struct ip6 *rip = mtod(t, struct ip6 *);286size_t pl_size = 0;287struct in6_addr addr;288uint32_t scope_id;289290rip->ip_src = (struct in6_addr)LINKLOCAL_ADDR;291rip->ip_dst = (struct in6_addr)ALLNODES_MULTICAST;292rip->ip_nh = IPPROTO_ICMPV6;293294/* Build ICMPv6 packet */295t->m_data += sizeof(struct ip6);296struct icmp6 *ricmp = mtod(t, struct icmp6 *);297ricmp->icmp6_type = ICMP6_NDP_RA;298ricmp->icmp6_code = 0;299ricmp->icmp6_cksum = 0;300301/* NDP */302ricmp->icmp6_nra.chl = NDP_AdvCurHopLimit;303ricmp->icmp6_nra.M = NDP_AdvManagedFlag;304ricmp->icmp6_nra.O = NDP_AdvOtherConfigFlag;305ricmp->icmp6_nra.reserved = 0;306ricmp->icmp6_nra.lifetime = htons(NDP_AdvDefaultLifetime);307ricmp->icmp6_nra.reach_time = htonl(NDP_AdvReachableTime);308ricmp->icmp6_nra.retrans_time = htonl(NDP_AdvRetransTime);309t->m_data += ICMP6_NDP_RA_MINLEN;310pl_size += ICMP6_NDP_RA_MINLEN;311312/* Source link-layer address (NDP option) */313struct ndpopt *opt = mtod(t, struct ndpopt *);314opt->ndpopt_type = NDPOPT_LINKLAYER_SOURCE;315opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8;316in6_compute_ethaddr(rip->ip_src, opt->ndpopt_linklayer);317t->m_data += NDPOPT_LINKLAYER_LEN;318pl_size += NDPOPT_LINKLAYER_LEN;319320/* Prefix information (NDP option) */321struct ndpopt *opt2 = mtod(t, struct ndpopt *);322opt2->ndpopt_type = NDPOPT_PREFIX_INFO;323opt2->ndpopt_len = NDPOPT_PREFIXINFO_LEN / 8;324opt2->ndpopt_prefixinfo.prefix_length = slirp->vprefix_len;325opt2->ndpopt_prefixinfo.L = 1;326opt2->ndpopt_prefixinfo.A = 1;327opt2->ndpopt_prefixinfo.reserved1 = 0;328opt2->ndpopt_prefixinfo.valid_lt = htonl(NDP_AdvValidLifetime);329opt2->ndpopt_prefixinfo.pref_lt = htonl(NDP_AdvPrefLifetime);330opt2->ndpopt_prefixinfo.reserved2 = 0;331opt2->ndpopt_prefixinfo.prefix = slirp->vprefix_addr6;332t->m_data += NDPOPT_PREFIXINFO_LEN;333pl_size += NDPOPT_PREFIXINFO_LEN;334335/* Prefix information (NDP option) */336if (get_dns6_addr(&addr, &scope_id) >= 0) {337/* Host system does have an IPv6 DNS server, announce our proxy. */338struct ndpopt *opt3 = mtod(t, struct ndpopt *);339opt3->ndpopt_type = NDPOPT_RDNSS;340opt3->ndpopt_len = NDPOPT_RDNSS_LEN / 8;341opt3->ndpopt_rdnss.reserved = 0;342opt3->ndpopt_rdnss.lifetime = htonl(2 * NDP_MaxRtrAdvInterval);343opt3->ndpopt_rdnss.addr = slirp->vnameserver_addr6;344t->m_data += NDPOPT_RDNSS_LEN;345pl_size += NDPOPT_RDNSS_LEN;346}347348rip->ip_pl = htons(pl_size);349t->m_data -= sizeof(struct ip6) + pl_size;350t->m_len = sizeof(struct ip6) + pl_size;351352/* ICMPv6 Checksum */353ricmp->icmp6_cksum = ip6_cksum(t);354355ip6_output(NULL, t, 0);356}357358void ra_timer_handler(Slirp *slirp, void *unused)359{360slirp->cb->timer_mod(slirp->ra_timer,361slirp->cb->clock_get_ns(slirp->opaque) / SCALE_MS +362NDP_Interval,363slirp->opaque);364ndp_send_ra(slirp);365}366367/*368* Send NDP Neighbor Solitication369*/370void ndp_send_ns(Slirp *slirp, struct in6_addr addr)371{372char addrstr[INET6_ADDRSTRLEN];373374inet_ntop(AF_INET6, &addr, addrstr, INET6_ADDRSTRLEN);375376DEBUG_CALL("ndp_send_ns");377DEBUG_ARG("target = %s", addrstr);378379/* Build IPv6 packet */380struct mbuf *t = m_get(slirp);381struct ip6 *rip = mtod(t, struct ip6 *);382rip->ip_src = slirp->vhost_addr6;383rip->ip_dst = (struct in6_addr)SOLICITED_NODE_PREFIX;384memcpy(&rip->ip_dst.s6_addr[13], &addr.s6_addr[13], 3);385rip->ip_nh = IPPROTO_ICMPV6;386rip->ip_pl = htons(ICMP6_NDP_NS_MINLEN + NDPOPT_LINKLAYER_LEN);387t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl);388389/* Build ICMPv6 packet */390t->m_data += sizeof(struct ip6);391struct icmp6 *ricmp = mtod(t, struct icmp6 *);392ricmp->icmp6_type = ICMP6_NDP_NS;393ricmp->icmp6_code = 0;394ricmp->icmp6_cksum = 0;395396/* NDP */397ricmp->icmp6_nns.reserved = 0;398ricmp->icmp6_nns.target = addr;399400/* Build NDP option */401t->m_data += ICMP6_NDP_NS_MINLEN;402struct ndpopt *opt = mtod(t, struct ndpopt *);403opt->ndpopt_type = NDPOPT_LINKLAYER_SOURCE;404opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8;405in6_compute_ethaddr(slirp->vhost_addr6, opt->ndpopt_linklayer);406407/* ICMPv6 Checksum */408t->m_data -= ICMP6_NDP_NA_MINLEN;409t->m_data -= sizeof(struct ip6);410ricmp->icmp6_cksum = ip6_cksum(t);411412ip6_output(NULL, t, 1);413}414415/*416* Send NDP Neighbor Advertisement417*/418static void ndp_send_na(Slirp *slirp, struct ip6 *ip, struct icmp6 *icmp)419{420/* Build IPv6 packet */421struct mbuf *t = m_get(slirp);422struct ip6 *rip = mtod(t, struct ip6 *);423rip->ip_src = icmp->icmp6_nns.target;424if (in6_zero(&ip->ip_src)) {425rip->ip_dst = (struct in6_addr)ALLNODES_MULTICAST;426} else {427rip->ip_dst = ip->ip_src;428}429rip->ip_nh = IPPROTO_ICMPV6;430rip->ip_pl = htons(ICMP6_NDP_NA_MINLEN + NDPOPT_LINKLAYER_LEN);431t->m_len = sizeof(struct ip6) + ntohs(rip->ip_pl);432433/* Build ICMPv6 packet */434t->m_data += sizeof(struct ip6);435struct icmp6 *ricmp = mtod(t, struct icmp6 *);436ricmp->icmp6_type = ICMP6_NDP_NA;437ricmp->icmp6_code = 0;438ricmp->icmp6_cksum = 0;439440/* NDP */441ricmp->icmp6_nna.R = NDP_IsRouter;442ricmp->icmp6_nna.S = !IN6_IS_ADDR_MULTICAST(&rip->ip_dst);443ricmp->icmp6_nna.O = 1;444ricmp->icmp6_nna.reserved_1 = 0;445ricmp->icmp6_nna.reserved_2 = 0;446ricmp->icmp6_nna.reserved_3 = 0;447ricmp->icmp6_nna.target = icmp->icmp6_nns.target;448449/* Build NDP option */450t->m_data += ICMP6_NDP_NA_MINLEN;451struct ndpopt *opt = mtod(t, struct ndpopt *);452opt->ndpopt_type = NDPOPT_LINKLAYER_TARGET;453opt->ndpopt_len = NDPOPT_LINKLAYER_LEN / 8;454in6_compute_ethaddr(ricmp->icmp6_nna.target, opt->ndpopt_linklayer);455456/* ICMPv6 Checksum */457t->m_data -= ICMP6_NDP_NA_MINLEN;458t->m_data -= sizeof(struct ip6);459ricmp->icmp6_cksum = ip6_cksum(t);460461ip6_output(NULL, t, 0);462}463464/*465* Process a NDP message466*/467static void ndp_input(struct mbuf *m, Slirp *slirp, struct ip6 *ip,468struct icmp6 *icmp)469{470g_assert(M_ROOMBEFORE(m) >= ETH_HLEN);471472m->m_len += ETH_HLEN;473m->m_data -= ETH_HLEN;474struct ethhdr *eth = mtod(m, struct ethhdr *);475m->m_len -= ETH_HLEN;476m->m_data += ETH_HLEN;477478switch (icmp->icmp6_type) {479case ICMP6_NDP_RS:480DEBUG_CALL(" type = Router Solicitation");481if (ip->ip_hl == 255 && icmp->icmp6_code == 0 &&482ntohs(ip->ip_pl) >= ICMP6_NDP_RS_MINLEN) {483/* Gratuitous NDP */484ndp_table_add(slirp, ip->ip_src, eth->h_source);485486ndp_send_ra(slirp);487}488break;489490case ICMP6_NDP_RA:491DEBUG_CALL(" type = Router Advertisement");492slirp->cb->guest_error("Warning: guest sent NDP RA, but shouldn't",493slirp->opaque);494break;495496case ICMP6_NDP_NS:497DEBUG_CALL(" type = Neighbor Solicitation");498if (ip->ip_hl == 255 && icmp->icmp6_code == 0 &&499!IN6_IS_ADDR_MULTICAST(&icmp->icmp6_nns.target) &&500ntohs(ip->ip_pl) >= ICMP6_NDP_NS_MINLEN &&501(!in6_zero(&ip->ip_src) ||502in6_solicitednode_multicast(&ip->ip_dst))) {503if (in6_equal_host(&icmp->icmp6_nns.target)) {504/* Gratuitous NDP */505ndp_table_add(slirp, ip->ip_src, eth->h_source);506ndp_send_na(slirp, ip, icmp);507}508}509break;510511case ICMP6_NDP_NA:512DEBUG_CALL(" type = Neighbor Advertisement");513if (ip->ip_hl == 255 && icmp->icmp6_code == 0 &&514ntohs(ip->ip_pl) >= ICMP6_NDP_NA_MINLEN &&515!IN6_IS_ADDR_MULTICAST(&icmp->icmp6_nna.target) &&516(!IN6_IS_ADDR_MULTICAST(&ip->ip_dst) || icmp->icmp6_nna.S == 0)) {517ndp_table_add(slirp, icmp->icmp6_nna.target, eth->h_source);518}519break;520521case ICMP6_NDP_REDIRECT:522DEBUG_CALL(" type = Redirect");523slirp->cb->guest_error(524"Warning: guest sent NDP REDIRECT, but shouldn't", slirp->opaque);525break;526}527}528529/*530* Process a received ICMPv6 message.531*/532void icmp6_input(struct mbuf *m)533{534Slirp *slirp = m->slirp;535/* NDP reads the ethernet header for gratuitous NDP */536M_DUP_DEBUG(slirp, m, 1, ETH_HLEN);537538struct icmp6 *icmp;539struct ip6 *ip = mtod(m, struct ip6 *);540int hlen = sizeof(struct ip6);541542DEBUG_CALL("icmp6_input");543DEBUG_ARG("m = %p", m);544DEBUG_ARG("m_len = %d", m->m_len);545546if (ntohs(ip->ip_pl) < ICMP6_MINLEN) {547freeit:548m_free(m);549goto end_error;550}551552if (ip6_cksum(m)) {553goto freeit;554}555556m->m_len -= hlen;557m->m_data += hlen;558icmp = mtod(m, struct icmp6 *);559m->m_len += hlen;560m->m_data -= hlen;561562DEBUG_ARG("icmp6_type = %d", icmp->icmp6_type);563switch (icmp->icmp6_type) {564case ICMP6_ECHO_REQUEST:565if (in6_equal_host(&ip->ip_dst)) {566icmp6_send_echoreply(m, slirp, ip, icmp);567} else if (slirp->restricted) {568goto freeit;569} else {570struct socket *so;571struct sockaddr_storage addr;572int ttl;573574so = socreate(slirp, IPPROTO_ICMPV6);575if (icmp6_send(so, m, hlen) == 0) {576/* We could send this as ICMP, good! */577return;578}579580/* We could not send this as ICMP, try to send it on UDP echo581* service (7), wishfully hoping that it is open there. */582583if (udp_attach(so, AF_INET6) == -1) {584DEBUG_MISC("icmp6_input udp_attach errno = %d-%s", errno,585strerror(errno));586sofree(so);587m_free(m);588goto end_error;589}590so->so_m = m;591so->so_ffamily = AF_INET6;592so->so_faddr6 = ip->ip_dst;593so->so_fport = htons(7);594so->so_lfamily = AF_INET6;595so->so_laddr6 = ip->ip_src;596so->so_lport = htons(9);597so->so_state = SS_ISFCONNECTED;598599/* Send the packet */600addr = so->fhost.ss;601if (sotranslate_out(so, &addr) < 0) {602icmp6_send_error(m, ICMP6_UNREACH, ICMP6_UNREACH_NO_ROUTE);603udp_detach(so);604return;605}606607/*608* Check for TTL609*/610ttl = ip->ip_hl-1;611if (ttl <= 0) {612DEBUG_MISC("udp ttl exceeded");613icmp6_send_error(m, ICMP6_TIMXCEED, ICMP6_TIMXCEED_INTRANS);614udp_detach(so);615break;616}617setsockopt(so->s, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl));618619if (sendto(so->s, icmp6_ping_msg, strlen(icmp6_ping_msg), 0,620(struct sockaddr *)&addr, sockaddr_size(&addr)) == -1) {621DEBUG_MISC("icmp6_input udp sendto tx errno = %d-%s", errno,622strerror(errno));623icmp6_send_error(m, ICMP6_UNREACH, ICMP6_UNREACH_NO_ROUTE);624udp_detach(so);625}626} /* if (in6_equal_host(&ip->ip_dst)) */627break;628629case ICMP6_NDP_RS:630case ICMP6_NDP_RA:631case ICMP6_NDP_NS:632case ICMP6_NDP_NA:633case ICMP6_NDP_REDIRECT:634ndp_input(m, slirp, ip, icmp);635m_free(m);636break;637638case ICMP6_UNREACH:639case ICMP6_TOOBIG:640case ICMP6_TIMXCEED:641case ICMP6_PARAMPROB:642/* XXX? report error? close socket? */643default:644m_free(m);645break;646}647648end_error:649/* m is m_free()'d xor put in a socket xor or given to ip_send */650return;651}652653654