/*- * Copyright (c) 2015-2019 Alexandre Fenyo - http://www.fenyo.net * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ndpacket.h" #include "ndconf.h" #include "ndparse.h" // Reply to neighbor solicitations with a specific neighbor advertisement, in order // to let the uplink router send packets to a downlink router, that may or may not // be the current host that run ndproxy. // The current host, the uplink router and the downlink router must be attached // to a common layer-2 link with broadcast multi-access capability. // Neighbor solicitation messages are multicast to the solicited-node multicast // address of the target address. Since we do not know the target address, // we can not join the corresponding group. So, to capture the solicitation messages, // the uplink interface must be set in permanent promiscuous mode and MLD snooping // must be disabled on the switches that share the layer-2 link relative to // the uplink interface. Note that MLD snooping must not be disabled entirely on // each switch, but only on the corresponding vlan. // called by pfil_run_hooks() @ ip6_input.c:ip_input() #ifdef PFIL_VERSION pfil_return_t packet(struct mbuf **packet_mp, struct ifnet *packet_ifnet, const int packet_dir, void *packet_arg, struct inpcb *packet_inpcb) { #else int packet(void *packet_arg, struct mbuf **packet_mp, struct ifnet *packet_ifnet, const int packet_dir, struct inpcb *packet_inpcb) { #endif struct mbuf *m = NULL, *mreply = NULL; struct ip6_hdr *ip6, *ip6reply; struct icmp6_hdr *icmp6; struct in6_addr srcaddr, dstaddr; int output_flags = 0; int maxlen, ret, i; #ifdef DEBUG_NDPROXY // when debuging, increment counter of received packets from the uplink interface ndproxy_conf_count = ++ndproxy_conf_count < 0 ? 1 : ndproxy_conf_count; #endif if (packet_mp == NULL) { printf("NDPROXY ERROR: no mbuf\n"); return 0; } m = *packet_mp; // locate start of IPv6 header data ip6 = mtod(m, struct ip6_hdr *); struct in6_addr ip6_src = ip6->ip6_src; // handle only packets originating from the uplink interface if (strcmp(if_name(packet_ifnet), ndproxy_conf_str_uplink_interface)) { #ifdef DEBUG_NDPROXY printf("NDPROXY DEBUG: packets from uplink interface: %s - %d\n", if_name(packet_ifnet), ndproxy_conf_count); #endif return 0; } // Handle only packets originating from one of the uplink router addresses. // Note that different source addresses can be choosen from the same uplink router, depending on the packet // that triggered the address resolution process or depending on other external factors. // Here are some cases when it can happen: // - the uplink router may have multiple interfaces; // - there may be multiple uplink routers; // - many routers choose to use a link-local address when sending neighbor solicitations, // but when an administrator of such a router, also having a global address assigned on the same link, // tries to send packets (echo request, for instance) to an on-link destination global address, // the source address of the echo request packet prompting the solicitation may be global-scoped according // to the selection algorithm described in RFC-6724. Therefore, the source address of the Neighbor Solicitation // packet should also be selected in the same global scope, according to RFC-4861 (§7.2.2); // - when the uplink router does not yet know its own address, it must use the unspecified address, // according to RFC-4861. // So, it can not be assumed that an uplink router will always use the same IPv6 address to send // neighbor solicitations. Every assigned addresses to the downlink interface of the uplink router // should then be declared to ndproxy via sysctl (net.inet6.ndproxyconf_uplink_ipv6_addresses). // Since the unsolicited address can be used by many different nodes, another node than the uplink router could // make use of such a source IP. This is why if such a node exists, the unsolicited address should not be // declared in the net.inet6.ndproxyconf_uplink_ipv6_addresses sysctl configuration parameter. for (i = 0; i < ndproxy_conf_uplink_ipv6_naddresses; i++) { #ifdef DEBUG_NDPROXY printf("NDPROXY INFO: compare: "); printf_ip6addr(ndproxy_conf_uplink_ipv6_addresses + i, false); printf(" (uplink router address) with "); printf_ip6addr(&ip6_src, false); printf(" (source address)\n"); #endif if (IN6_ARE_ADDR_EQUAL(ndproxy_conf_uplink_ipv6_addresses + i, &ip6_src)) break; } if (i == ndproxy_conf_uplink_ipv6_naddresses) { #ifdef DEBUG_NDPROXY printf("NDPROXY INFO: not from uplink router - from: "); printf_ip6addr(&ip6_src, false); printf(" - %d\n", ndproxy_conf_count); #endif return 0; } #ifdef DEBUG_NDPROXY printf("NDPROXY DEBUG: got packet from uplink router - %d\n", ndproxy_conf_count); #endif // For security reasons, we explicitely reject neighbor solicitation packets containing any extension header: // such a packet is mainly unattended: // Fragmentation: // According to RFC-6980, IPv6 fragmentation header is forbidden in all neighbor discovery messages. // Hop-by-hop header: // commonly used for jumbograms or for MLD. Should not involve neighbor solicitation packets. // Destination mobility headers: // commonly used for mobility, we do not support these headers. // Routing header: // commonly used for mobility or source routing, we do not support these headers. // AH & ESP headers: // securing the neighbor discovery process is not done with IPsec but with the SEcure Neighbor // Discovery protocol (RFC-3971). We can not support RFC-3971, since proxifying ND packets is // some kind of a spoofing process. // look for an ICMPv6 payload and no IPv6 extension header if (ip6->ip6_nxt != IPPROTO_ICMPV6) return 0; #ifdef DEBUG_NDPROXY printf("NDPROXY DEBUG: got ICMPv6 from uplink router - %d\n", ndproxy_conf_count); #endif // locate start of ICMPv6 header data icmp6 = (struct icmp6_hdr *) ((caddr_t) ip6 + sizeof(struct ip6_hdr)); // check the checksum const u_int16_t sum = icmp6->icmp6_cksum; icmp6->icmp6_cksum = 0; if (sum != in6_cksum(m, IPPROTO_ICMPV6, sizeof(struct ip6_hdr), m->m_len - sizeof(struct ip6_hdr))) { icmp6->icmp6_cksum = sum; printf("NDPROXY ERROR: bad checksum\n"); return 0; } icmp6->icmp6_cksum = sum; if (icmp6->icmp6_type != ND_NEIGHBOR_SOLICIT || icmp6->icmp6_code) return 0; #ifdef DEBUG_NDPROXY printf("NDPROXY DEBUG: got neighbor solicitation from "); printf_ip6addr(&ip6_src, true); printf("\n"); #endif // create a new mbuf to send a neighbor advertisement // ICMPv6 options are rounded up to 8 bytes alignment maxlen = (sizeof(struct ip6_hdr) + sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr) + packet_ifnet->if_addrlen + 7) & ~7; if (max_linkhdr + maxlen > MCLBYTES) { printf("NDPROXY ERROR: reply length > MCLBYTES\n"); return 0; } if (max_linkhdr + maxlen > MHLEN) mreply = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR); else mreply = m_gethdr(M_NOWAIT, MT_DATA); if (mreply == NULL) { printf("NDPROXY ERROR: no more mbufs (ENOBUFS)\n"); return 0; } // this is a newly created packet mreply->m_pkthdr.rcvif = NULL; // packet content: // IPv6 header + ICMPv6 Neighbor Advertisement including target address + target link-layer ICMPv6 address option mreply->m_pkthdr.len = mreply->m_len = (sizeof(struct ip6_hdr) + sizeof(struct nd_neighbor_advert) + sizeof(struct nd_opt_hdr) + packet_ifnet->if_addrlen + 7) & ~7; // reserve space for the link-layer header mreply->m_data += max_linkhdr; // fill in the destination address we want to reply to struct sockaddr_in6 dst_sa; bzero(&dst_sa, sizeof(struct sockaddr_in6)); dst_sa.sin6_family = AF_INET6; dst_sa.sin6_len = sizeof(struct sockaddr_in6); dst_sa.sin6_addr = ip6->ip6_src; if ((ret = in6_setscope(&dst_sa.sin6_addr, packet_ifnet, NULL))) { printf("NDPROXY ERROR: can not set source scope id (err=%d)\n", ret); m_freem(mreply); return 0; } // According to RFC-4861 (§7.2.4), "The Target Address of the advertisement is copied from the Target Address // of the solicitation. [...] If the source of the solicitation is the unspecified address, the // node MUST [...] multicast the advertisement to the all-nodes address.". if (!IN6_IS_ADDR_UNSPECIFIED(&ip6_src)) dstaddr = ip6->ip6_src; else { // Check compliance to RFC-4861: "If the IP source address is the unspecified address, the IP // destination address is a solicited-node multicast address.". if (ip6->ip6_dst.s6_addr16[0] == IPV6_ADDR_INT16_MLL && ip6->ip6_dst.s6_addr32[1] == 0 && ip6->ip6_dst.s6_addr32[2] == IPV6_ADDR_INT32_ONE && ip6->ip6_dst.s6_addr8[12] == 0xff) { #ifdef DEBUG_NDPROXY printf("NDPROXY DEBUG: unspecified source address and solicited-node multicast destination address\n"); #endif } else { printf("NDPROXY ERROR: destination address should be a solicited-node multicast address\n"); m_freem(mreply); return 0; } output_flags |= M_MCAST; dstaddr = in6addr_linklocal_allnodes; } if ((ret = in6_setscope(&dstaddr, packet_ifnet, NULL))) { printf("NDPROXY ERROR: can not set destination scope id (err=%d)\n", ret); m_freem(mreply); return 0; } // first, apply the RFC-3484 default address selection algorithm to get a source address for the advertisement packet. #if (__FreeBSD_version < 1100000) ret = in6_selectsrc(&dst_sa, NULL, NULL, NULL, NULL, NULL, &srcaddr); #else uint32_t _dst_sa_scopeid; struct in6_addr _dst_sa; in6_splitscope(&dst_sa.sin6_addr, &_dst_sa, &_dst_sa_scopeid); ret = in6_selectsrc_addr(RT_DEFAULT_FIB, &_dst_sa, _dst_sa_scopeid, packet_ifnet, &srcaddr, NULL); #endif if (ret && (ret != EHOSTUNREACH || in6_addrscope(&ip6_src) == IPV6_ADDR_SCOPE_LINKLOCAL)) { printf("NDPROXY ERROR: can not select a source address to reply (err=%d), source scope is %x\n", ret, in6_addrscope(&ip6_src)); m_freem(mreply); return 0; } if (ret) { // secondly, try to reply with a link-local address attached to the receiving interface struct in6_ifaddr *llifaddr = in6ifa_ifpforlinklocal(packet_ifnet, 0); if (llifaddr == NULL) printf("NDPROXY WARNING: no link-local address attached to the receiving interface\n"); #ifdef DEBUG_NDPROXY printf("NDPROXY INFO: no address in requested scope, using a link-local address to reply\n"); #endif if (llifaddr != NULL) { // use the link-local address srcaddr = (llifaddr->ia_addr).sin6_addr; ifa_free((struct ifaddr *) llifaddr); } else // No link-local address, we may for instance currently be verifying that the link-local stateless // autoconfiguration address is unused. // Then, we temporary use the unspecified address (::). bzero(&srcaddr, sizeof srcaddr); // Since we have no source address in the same scope of the destination address of the request packet, // we can not simply reply to the source address of the request packet. // Then we reply to the link-local all nodes multicast address (ff02::1). output_flags |= M_MCAST; dstaddr = in6addr_linklocal_allnodes; if ((ret = in6_setscope(&dstaddr, packet_ifnet, NULL))) { printf("NDPROXY ERROR: can not set destination scope id (err=%d)\n", ret); m_freem(mreply); return 0; } } #ifdef DEBUG_NDPROXY printf("NDPROXY DEBUG: source address used to reply: "); printf_ip6addr(&srcaddr, true); printf("\n"); #endif struct nd_neighbor_solicit *nd_ns = (struct nd_neighbor_solicit *) (ip6 + 1); struct in6_addr nd_ns_target = nd_ns->nd_ns_target; // fill in the IPv6 header ip6reply = mtod(mreply, struct ip6_hdr *); ip6reply->ip6_flow = 0; ip6reply->ip6_vfc &= ~IPV6_VERSION_MASK; ip6reply->ip6_vfc |= IPV6_VERSION; ip6reply->ip6_plen = htons((u_short) (mreply->m_len - sizeof(struct ip6_hdr))); ip6reply->ip6_nxt = IPPROTO_ICMPV6; ip6reply->ip6_hlim = 255; ip6reply->ip6_dst = dstaddr; ip6reply->ip6_src = srcaddr; // fill in the ICMPv6 neighbor advertisement header struct nd_neighbor_advert *nd_na = (struct nd_neighbor_advert *) (ip6reply + 1); nd_na->nd_na_type = ND_NEIGHBOR_ADVERT; nd_na->nd_na_code = 0; nd_na->nd_na_flags_reserved = 0; // According to RFC-4861 (§7.2.4), "If the source of the solicitation is the unspecified address, the // node MUST set the Solicited flag to zero [...]" if (!IN6_IS_ADDR_UNSPECIFIED(&ip6_src)) nd_na->nd_na_flags_reserved = ND_NA_FLAG_SOLICITED; // According to RFC-4861 (§7.2.4), "If the Target Address is either an anycast address or a unicast // address for which the node is providing proxy service, [...] the Override flag SHOULD // be set to zero." // Thus, we do not set the ND_NA_FLAG_OVERRIDE flag in nd_na->nd_na_flags_reserved. nd_na->nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; // according to RFC-4861 (§7.2.3), the target address can not be a multicast address if (IN6_IS_ADDR_MULTICAST(&nd_ns_target)) { printf("NDPROXY WARNING: rejecting multicast target address\n"); m_freem(mreply); return 0; } // we send a solicited neighbor advertisement relative to the target contained in the received neighbor solicitation nd_na->nd_na_target = nd_ns->nd_ns_target; struct in6_addr nd_na_target = nd_na->nd_na_target; // do not manage packets relative to exception target addresses int addr_allowed = 0; for (i = 0; i < ndproxy_conf_exception_ipv6_naddresses; i++) { if (IN6_IS_ADDR_LINKLOCAL(&nd_na_target) && IN6_IS_ADDR_LINKLOCAL(ndproxy_conf_exception_ipv6_addresses + i)) { unsigned char *addr1 = (unsigned char *)(ndproxy_conf_exception_ipv6_addresses + i); unsigned char *addr2 = (unsigned char *)(&nd_na_target); if (memcmp(addr1+8, addr2+8, 64/8) == 0) { addr_allowed = 1; break; } } else { if (memcmp(ndproxy_conf_exception_ipv6_addresses + i, &nd_na_target, 48/8) == 0) { addr_allowed = 1; break; } } } // printf("ndproxy: "); // printf_ip6addr(&nd_na_target, false); // printf("\n"); if (addr_allowed != 1) { m_freem(mreply); return 0; } // proxy to the downlink router: fill in the target link-layer address option with the MAC downlink router address int optlen = sizeof(struct nd_opt_hdr) + ETHER_ADDR_LEN; struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *) (nd_na + 1); // roundup to 8 bytes alignment optlen = (optlen + 7) & ~7; bzero((caddr_t) nd_opt, optlen); nd_opt->nd_opt_type = ND_OPT_TARGET_LINKADDR; nd_opt->nd_opt_len = optlen >> 3; bcopy(&ndproxy_conf_downlink_mac_address, (caddr_t) (nd_opt + 1), ETHER_ADDR_LEN); #ifdef DEBUG_NDPROXY printf("NDPROXY INFO: mac option: "); printf_macaddr_network_format(&ndproxy_conf_downlink_mac_address); printf("\n"); #endif // compute outgoing packet checksum nd_na->nd_na_cksum = 0; nd_na->nd_na_cksum = in6_cksum(mreply, IPPROTO_ICMPV6, sizeof(struct ip6_hdr), mreply->m_len - sizeof(struct ip6_hdr)); #ifdef DEBUG_NDPROXY struct in6_addr ip6reply_ip6_src = ip6reply->ip6_src; struct in6_addr ip6reply_ip6_dst = ip6reply->ip6_dst; printf("NDPROXY DEBUG: src="); printf_ip6addr(&ip6reply_ip6_src, false); printf(" / "); printf("dst="); printf_ip6addr(&ip6reply_ip6_dst, false); printf("\n"); #endif struct ip6_moptions im6o; if (output_flags & M_MCAST) { bzero(&im6o, sizeof im6o); im6o.im6o_multicast_hlim = 255; im6o.im6o_multicast_loop = false; im6o.im6o_multicast_ifp = NULL; } // send router advertisement if ((ret = ip6_output(mreply, NULL, NULL, output_flags, output_flags & M_MCAST ? &im6o : NULL, NULL, NULL))) { printf("NDPROXY DEBUG: can not send packet (err=%d)\n", ret); #ifdef DEBUG_NDPROXY kdb_backtrace(); return 0; #endif } else { #ifdef DEBUG_NDPROXY printf("NDPROXY DEBUG: reply sent\n"); #endif } #ifndef DEBUG_NDPROXY // when NOT debuging, increment counter for each neighbor advertisement sent ndproxy_conf_count = ++ndproxy_conf_count < 0 ? 1 : ndproxy_conf_count; #endif // do not process this packet by upper layers to avoid sending another advertissement m_freem(m); *packet_mp = NULL; return 1; }