/* * Copyright (c) 2020, The OpenThread Authors. * 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 copyright holder 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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. */ /** * @file * The file implements the ND Proxy management. */ #define OTBR_LOG_TAG "NDPROXY" #include "backbone_router/nd_proxy.hpp" #if OTBR_ENABLE_DUA_ROUTING #include #include #include #include #include #include #include #if __linux__ #include #else #error "Platform not supported" #endif #include "backbone_router/constants.hpp" #include "common/code_utils.hpp" #include "common/logging.hpp" #include "common/types.hpp" #include "utils/system_utils.hpp" namespace otbr { namespace BackboneRouter { void NdProxyManager::Enable(const Ip6Prefix &aDomainPrefix) { otbrError error = OTBR_ERROR_NONE; VerifyOrExit(!IsEnabled()); assert(aDomainPrefix.IsValid()); mDomainPrefix = aDomainPrefix; SuccessOrExit(error = InitIcmp6RawSocket()); SuccessOrExit(error = UpdateMacAddress()); SuccessOrExit(error = InitNetfilterQueue()); // Add ip6tables rule for unicast ICMPv6 messages VerifyOrExit(SystemUtils::ExecuteCommand( "ip6tables -t raw -A PREROUTING -6 -d %s -p icmpv6 --icmpv6-type neighbor-solicitation -i %s -j " "NFQUEUE --queue-num 88", mDomainPrefix.ToString().c_str(), mBackboneInterfaceName.c_str()) == 0, error = OTBR_ERROR_ERRNO); exit: if (error != OTBR_ERROR_NONE) { FiniNetfilterQueue(); FiniIcmp6RawSocket(); } otbrLogResult(error, "NdProxyManager: %s", __FUNCTION__); } void NdProxyManager::Disable(void) { otbrError error = OTBR_ERROR_NONE; VerifyOrExit(IsEnabled()); FiniNetfilterQueue(); FiniIcmp6RawSocket(); // Remove ip6tables rule for unicast ICMPv6 messages VerifyOrExit(SystemUtils::ExecuteCommand( "ip6tables -t raw -D PREROUTING -6 -d %s -p icmpv6 --icmpv6-type neighbor-solicitation -i %s -j " "NFQUEUE --queue-num 88", mDomainPrefix.ToString().c_str(), mBackboneInterfaceName.c_str()) == 0, error = OTBR_ERROR_ERRNO); exit: otbrLogResult(error, "NdProxyManager: %s", __FUNCTION__); } void NdProxyManager::Init(void) { mBackboneIfIndex = if_nametoindex(mBackboneInterfaceName.c_str()); VerifyOrDie(mBackboneIfIndex > 0, "if_nametoindex failed"); } void NdProxyManager::Update(MainloopContext &aMainloop) { if (mIcmp6RawSock >= 0) { aMainloop.AddFdToReadSet(mIcmp6RawSock); } if (mUnicastNsQueueSock >= 0) { aMainloop.AddFdToReadSet(mUnicastNsQueueSock); } } void NdProxyManager::Process(const MainloopContext &aMainloop) { VerifyOrExit(IsEnabled()); if (FD_ISSET(mIcmp6RawSock, &aMainloop.mReadFdSet)) { ProcessMulticastNeighborSolicition(); } if (FD_ISSET(mUnicastNsQueueSock, &aMainloop.mReadFdSet)) { ProcessUnicastNeighborSolicition(); } exit: return; } void NdProxyManager::ProcessMulticastNeighborSolicition() { struct msghdr msghdr; sockaddr_in6 sin6; struct iovec iovec; ssize_t len; struct icmp6_hdr *icmp6header; struct cmsghdr *cmsghdr; unsigned char cbuf[2 * CMSG_SPACE(sizeof(struct in6_pktinfo))]; uint8_t packet[kMaxICMP6PacketSize]; otbrError error = OTBR_ERROR_NONE; bool found = false; iovec.iov_len = kMaxICMP6PacketSize; iovec.iov_base = packet; msghdr.msg_name = &sin6; msghdr.msg_namelen = sizeof(sin6); msghdr.msg_iov = &iovec; msghdr.msg_iovlen = 1; msghdr.msg_control = cbuf; msghdr.msg_controllen = sizeof(cbuf); len = recvmsg(mIcmp6RawSock, &msghdr, 0); VerifyOrExit(len >= static_cast(sizeof(struct icmp6_hdr)), error = OTBR_ERROR_ERRNO); { Ip6Address &src = *reinterpret_cast(&sin6.sin6_addr); icmp6header = reinterpret_cast(packet); // only process neighbor solicit VerifyOrExit(icmp6header->icmp6_type == ND_NEIGHBOR_SOLICIT, error = OTBR_ERROR_PARSE); otbrLogDebug("NdProxyManager: Received ND-NS from %s", src.ToString().c_str()); for (cmsghdr = CMSG_FIRSTHDR(&msghdr); cmsghdr; cmsghdr = CMSG_NXTHDR(&msghdr, cmsghdr)) { if (cmsghdr->cmsg_level != IPPROTO_IPV6) { continue; } switch (cmsghdr->cmsg_type) { case IPV6_PKTINFO: if (cmsghdr->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo))) { struct in6_pktinfo *pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsghdr); Ip6Address &dst = *reinterpret_cast(&pktinfo->ipi6_addr); uint32_t ifindex = pktinfo->ipi6_ifindex; for (const Ip6Address &ipaddr : mNdProxySet) { if (ipaddr.ToSolicitedNodeMulticastAddress() == dst) { found = true; break; } } otbrLogDebug("NdProxyManager: dst=%s, ifindex=%d, proxying=%s", dst.ToString().c_str(), ifindex, found ? "Y" : "N"); } break; case IPV6_HOPLIMIT: if (cmsghdr->cmsg_len == CMSG_LEN(sizeof(int))) { int hops = *(int *)CMSG_DATA(cmsghdr); otbrLogDebug("NdProxyManager: hops=%d (%s)", hops, hops == 255 ? "Good" : "Bad"); VerifyOrExit(hops == 255); } break; } } VerifyOrExit(found, error = OTBR_ERROR_NOT_FOUND); { struct nd_neighbor_solicit *ns = reinterpret_cast(packet); Ip6Address &target = *reinterpret_cast(&ns->nd_ns_target); otbrLogInfo("NdProxyManager: send solicited NA for multicast NS: src=%s, target=%s", src.ToString().c_str(), target.ToString().c_str()); SendNeighborAdvertisement(target, src); } } exit: otbrLogResult(error, "NdProxyManager: %s", __FUNCTION__); } void NdProxyManager::ProcessUnicastNeighborSolicition(void) { otbrError error = OTBR_ERROR_NONE; char packet[kMaxICMP6PacketSize]; ssize_t len; VerifyOrExit((len = recv(mUnicastNsQueueSock, packet, sizeof(packet), 0)) >= 0, error = OTBR_ERROR_ERRNO); VerifyOrExit(nfq_handle_packet(mNfqHandler, packet, len) == 0, error = OTBR_ERROR_ERRNO); error = OTBR_ERROR_NONE; exit: otbrLogResult(error, "NdProxyManager: %s", __FUNCTION__); } void NdProxyManager::HandleBackboneRouterNdProxyEvent(otBackboneRouterNdProxyEvent aEvent, const otIp6Address *aDua) { Ip6Address target; if (aEvent != OT_BACKBONE_ROUTER_NDPROXY_CLEARED) { assert(aDua != nullptr); target = Ip6Address(aDua->mFields.m8); } switch (aEvent) { case OT_BACKBONE_ROUTER_NDPROXY_ADDED: case OT_BACKBONE_ROUTER_NDPROXY_RENEWED: { bool isNewInsert = mNdProxySet.insert(target).second; if (isNewInsert) { JoinSolicitedNodeMulticastGroup(target); } SendNeighborAdvertisement(target, Ip6Address::GetLinkLocalAllNodesMulticastAddress()); break; } case OT_BACKBONE_ROUTER_NDPROXY_REMOVED: mNdProxySet.erase(target); LeaveSolicitedNodeMulticastGroup(target); break; case OT_BACKBONE_ROUTER_NDPROXY_CLEARED: for (const Ip6Address &proxingTarget : mNdProxySet) { LeaveSolicitedNodeMulticastGroup(proxingTarget); } mNdProxySet.clear(); break; } } void NdProxyManager::SendNeighborAdvertisement(const Ip6Address &aTarget, const Ip6Address &aDst) { uint8_t packet[kMaxICMP6PacketSize]; uint16_t len = 0; struct nd_neighbor_advert &na = *reinterpret_cast(packet); struct nd_opt_hdr &opt = *reinterpret_cast(packet + sizeof(struct nd_neighbor_advert)); bool isSolicited = !aDst.IsMulticast(); sockaddr_in6 dst; otbrError error = OTBR_ERROR_NONE; otBackboneRouterNdProxyInfo aNdProxyInfo; VerifyOrExit(otBackboneRouterGetNdProxyInfo(mHost.GetInstance(), reinterpret_cast(&aTarget), &aNdProxyInfo) == OT_ERROR_NONE, error = OTBR_ERROR_OPENTHREAD); memset(packet, 0, sizeof(packet)); na.nd_na_type = ND_NEIGHBOR_ADVERT; na.nd_na_code = 0; // set Solicited na.nd_na_flags_reserved = isSolicited ? ND_NA_FLAG_SOLICITED : 0; // set Router na.nd_na_flags_reserved |= ND_NA_FLAG_ROUTER; // set Override na.nd_na_flags_reserved |= aNdProxyInfo.mTimeSinceLastTransaction <= kDuaRecentTime ? ND_NA_FLAG_OVERRIDE : 0; memcpy(&na.nd_na_target, aTarget.m8, sizeof(Ip6Address)); len += sizeof(struct nd_neighbor_advert); opt.nd_opt_type = ND_OPT_TARGET_LINKADDR; opt.nd_opt_len = 1; memcpy(reinterpret_cast(&opt) + 2, mMacAddress.m8, sizeof(mMacAddress)); len += (opt.nd_opt_len * 8); aDst.CopyTo(dst); VerifyOrExit(sendto(mIcmp6RawSock, packet, len, 0, reinterpret_cast(&dst), sizeof(dst)) == len, error = OTBR_ERROR_ERRNO); exit: otbrLogResult(error, "NdProxyManager: %s", __FUNCTION__); } otbrError NdProxyManager::UpdateMacAddress(void) { otbrError error = OTBR_ERROR_NONE; #if !__APPLE__ struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, mBackboneInterfaceName.c_str(), sizeof(ifr.ifr_name) - 1); VerifyOrExit(ioctl(mIcmp6RawSock, SIOCGIFHWADDR, &ifr) != -1, error = OTBR_ERROR_ERRNO); memcpy(mMacAddress.m8, ifr.ifr_hwaddr.sa_data, sizeof(mMacAddress)); #else ExitNow(error = OTBR_ERROR_NOT_IMPLEMENTED); #endif exit: otbrLogResult(error, "NdProxyManager: UpdateMacAddress to %s", mMacAddress.ToString().c_str()); return error; } otbrError NdProxyManager::InitIcmp6RawSocket(void) { otbrError error = OTBR_ERROR_NONE; int on = 1; int hops = 255; struct icmp6_filter filter; mIcmp6RawSock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); VerifyOrExit(mIcmp6RawSock >= 0, error = OTBR_ERROR_ERRNO); #if __linux__ VerifyOrExit(setsockopt(mIcmp6RawSock, SOL_SOCKET, SO_BINDTODEVICE, mBackboneInterfaceName.c_str(), mBackboneInterfaceName.length()) == 0, error = OTBR_ERROR_ERRNO); #else // __NetBSD__ || __FreeBSD__ || __APPLE__ VerifyOrExit( setsockopt(mIcmp6RawSock, IPPROTO_IPV6, IPV6_BOUND_IF, mBackboneIfName.c_str(), mBackboneIfName.size()), error = OTBR_ERROR_ERRNO); #endif // __linux__ VerifyOrExit(setsockopt(mIcmp6RawSock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on)) == 0, error = OTBR_ERROR_ERRNO); VerifyOrExit(setsockopt(mIcmp6RawSock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)) == 0, error = OTBR_ERROR_ERRNO); VerifyOrExit(setsockopt(mIcmp6RawSock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof(hops)) == 0, error = OTBR_ERROR_ERRNO); VerifyOrExit(setsockopt(mIcmp6RawSock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hops, sizeof(hops)) == 0, error = OTBR_ERROR_ERRNO); ICMP6_FILTER_SETBLOCKALL(&filter); ICMP6_FILTER_SETPASS(ND_NEIGHBOR_SOLICIT, &filter); VerifyOrExit(setsockopt(mIcmp6RawSock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) == 0, error = OTBR_ERROR_ERRNO); exit: if (error != OTBR_ERROR_NONE) { FiniIcmp6RawSocket(); } return error; } void NdProxyManager::FiniIcmp6RawSocket(void) { if (mIcmp6RawSock != -1) { close(mIcmp6RawSock); mIcmp6RawSock = -1; } } otbrError NdProxyManager::InitNetfilterQueue(void) { otbrError error = OTBR_ERROR_ERRNO; VerifyOrExit((mNfqHandler = nfq_open()) != nullptr); VerifyOrExit(nfq_unbind_pf(mNfqHandler, AF_INET6) >= 0); VerifyOrExit(nfq_bind_pf(mNfqHandler, AF_INET6) >= 0); VerifyOrExit((mNfqQueueHandler = nfq_create_queue(mNfqHandler, 88, HandleNetfilterQueue, this)) != nullptr); VerifyOrExit(nfq_set_mode(mNfqQueueHandler, NFQNL_COPY_PACKET, 0xffff) >= 0); VerifyOrExit((mUnicastNsQueueSock = nfq_fd(mNfqHandler)) >= 0); error = OTBR_ERROR_NONE; exit: otbrLogResult(error, "NdProxyManager: %s", __FUNCTION__); if (error != OTBR_ERROR_NONE) { FiniNetfilterQueue(); } return error; } void NdProxyManager::FiniNetfilterQueue(void) { if (mUnicastNsQueueSock != -1) { close(mUnicastNsQueueSock); mUnicastNsQueueSock = -1; } if (mNfqQueueHandler != nullptr) { nfq_destroy_queue(mNfqQueueHandler); mNfqQueueHandler = nullptr; } if (mNfqHandler != nullptr) { nfq_close(mNfqHandler); mNfqHandler = nullptr; } } int NdProxyManager::HandleNetfilterQueue(struct nfq_q_handle *aNfQueueHandler, struct nfgenmsg *aNfMsg, struct nfq_data *aNfData, void *aContext) { return static_cast(aContext)->HandleNetfilterQueue(aNfQueueHandler, aNfMsg, aNfData); } int NdProxyManager::HandleNetfilterQueue(struct nfq_q_handle *aNfQueueHandler, struct nfgenmsg *aNfMsg, struct nfq_data *aNfData) { OTBR_UNUSED_VARIABLE(aNfMsg); struct nfqnl_msg_packet_hdr *ph; unsigned char *data; uint32_t id = 0; int ret = 0; int len = 0; int verdict = NF_ACCEPT; Ip6Address dst; Ip6Address src; struct icmp6_hdr *icmp6header = nullptr; struct ip6_hdr *ip6header = nullptr; otbrError error = OTBR_ERROR_NONE; if ((ph = nfq_get_msg_packet_hdr(aNfData)) != nullptr) { id = ntohl(ph->packet_id); otbrLogDebug("NdProxyManager: %s: id %d", __FUNCTION__, id); } VerifyOrExit((len = nfq_get_payload(aNfData, &data)) > 0, error = OTBR_ERROR_PARSE); ip6header = reinterpret_cast(data); src = *reinterpret_cast(&ip6header->ip6_src); dst = *reinterpret_cast(&ip6header->ip6_dst); VerifyOrExit(ip6header->ip6_nxt == IPPROTO_ICMPV6); otbrLogDebug("NdProxyManager: Handle Neighbor Solicitation: from %s to %s", src.ToString().c_str(), dst.ToString().c_str()); icmp6header = reinterpret_cast(data + sizeof(struct ip6_hdr)); VerifyOrExit(icmp6header->icmp6_type == ND_NEIGHBOR_SOLICIT); VerifyOrExit(mNdProxySet.find(dst) != mNdProxySet.end(), error = OTBR_ERROR_NOT_FOUND); { struct nd_neighbor_solicit &ns = *reinterpret_cast(data + sizeof(struct ip6_hdr)); Ip6Address &target = *reinterpret_cast(&ns.nd_ns_target); otbrLogDebug("NdProxyManager: %s: target: %s, hoplimit %d", __FUNCTION__, target.ToString().c_str(), ip6header->ip6_hlim); VerifyOrExit(ip6header->ip6_hlim == 255, error = OTBR_ERROR_PARSE); SendNeighborAdvertisement(target, src); verdict = NF_DROP; } exit: ret = nfq_set_verdict(aNfQueueHandler, id, verdict, len, data); otbrLogResult(error, "NdProxyManager: %s (nfq_set_verdict id %d, ret %d verdict %d)", __FUNCTION__, id, ret, verdict); return ret; } void NdProxyManager::JoinSolicitedNodeMulticastGroup(const Ip6Address &aTarget) const { ipv6_mreq mreq; otbrError error = OTBR_ERROR_NONE; Ip6Address solicitedMulticastAddress = aTarget.ToSolicitedNodeMulticastAddress(); mreq.ipv6mr_interface = mBackboneIfIndex; solicitedMulticastAddress.CopyTo(mreq.ipv6mr_multiaddr); VerifyOrExit(setsockopt(mIcmp6RawSock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) == 0, error = OTBR_ERROR_ERRNO); exit: otbrLogResult(error, "NdProxyManager: JoinSolicitedNodeMulticastGroup of %s: %s", aTarget.ToString().c_str(), solicitedMulticastAddress.ToString().c_str()); } void NdProxyManager::LeaveSolicitedNodeMulticastGroup(const Ip6Address &aTarget) const { ipv6_mreq mreq; otbrError error = OTBR_ERROR_NONE; Ip6Address solicitedMulticastAddress = aTarget.ToSolicitedNodeMulticastAddress(); mreq.ipv6mr_interface = mBackboneIfIndex; solicitedMulticastAddress.CopyTo(mreq.ipv6mr_multiaddr); VerifyOrExit(setsockopt(mIcmp6RawSock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq, sizeof(mreq)) == 0, error = OTBR_ERROR_ERRNO); exit: otbrLogResult(error, "NdProxyManager: LeaveSolicitedNodeMulticastGroup of %s: %s", aTarget.ToString().c_str(), solicitedMulticastAddress.ToString().c_str()); } } // namespace BackboneRouter } // namespace otbr #endif // OTBR_ENABLE_DUA_ROUTING