| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "patchpanel/ndproxy.h" |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sysexits.h> |
| #include <unistd.h> |
| |
| #include <arpa/inet.h> |
| #include <linux/filter.h> |
| #include <linux/if_packet.h> |
| #include <linux/in6.h> |
| #include <linux/netlink.h> |
| #include <linux/rtnetlink.h> |
| #include <net/ethernet.h> |
| #include <net/if.h> |
| #include <sys/ioctl.h> |
| #include <sys/socket.h> |
| |
| #include <fstream> |
| #include <string> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_split.h> |
| |
| #include "patchpanel/minijailed_process_runner.h" |
| #include "patchpanel/net_util.h" |
| |
| namespace patchpanel { |
| namespace { |
| const unsigned char kBroadcastMacAddress[] = {0xff, 0xff, 0xff, |
| 0xff, 0xff, 0xff}; |
| |
| sock_filter kNDFrameBpfInstructions[] = { |
| // Load ethernet type. |
| BPF_STMT(BPF_LD | BPF_H | BPF_ABS, offsetof(ether_header, ether_type)), |
| // Check if it equals IPv6, if not, then goto return 0. |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 9), |
| // Move index to start of IPv6 header. |
| BPF_STMT(BPF_LDX | BPF_IMM, sizeof(ether_header)), |
| // Load IPv6 next header. |
| BPF_STMT(BPF_LD | BPF_B | BPF_IND, offsetof(ip6_hdr, ip6_nxt)), |
| // Check if equals ICMPv6, if not, then goto return 0. |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 6), |
| // Move index to start of ICMPv6 header. |
| BPF_STMT(BPF_LDX | BPF_IMM, sizeof(ether_header) + sizeof(ip6_hdr)), |
| // Load ICMPv6 type. |
| BPF_STMT(BPF_LD | BPF_B | BPF_IND, offsetof(icmp6_hdr, icmp6_type)), |
| // Check if is ND ICMPv6 message. |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_SOLICIT, 4, 0), |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 3, 0), |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 2, 0), |
| BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0), |
| // Return 0. |
| BPF_STMT(BPF_RET | BPF_K, 0), |
| // Return MAX. |
| BPF_STMT(BPF_RET | BPF_K, IP_MAXPACKET), |
| }; |
| const sock_fprog kNDFrameBpfProgram = { |
| .len = sizeof(kNDFrameBpfInstructions) / sizeof(sock_filter), |
| .filter = kNDFrameBpfInstructions}; |
| |
| } // namespace |
| |
| constexpr ssize_t NDProxy::kTranslateErrorNotICMPv6Frame; |
| constexpr ssize_t NDProxy::kTranslateErrorNotNDFrame; |
| constexpr ssize_t NDProxy::kTranslateErrorInsufficientLength; |
| constexpr ssize_t NDProxy::kTranslateErrorBufferMisaligned; |
| |
| NDProxy::NDProxy() |
| : in_frame_buffer_(AlignFrameBuffer(in_frame_buffer_extended_)), |
| out_frame_buffer_(AlignFrameBuffer(out_frame_buffer_extended_)) {} |
| |
| base::ScopedFD NDProxy::PreparePacketSocket() { |
| base::ScopedFD fd( |
| socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, htons(ETH_P_IPV6))); |
| if (!fd.is_valid()) { |
| PLOG(ERROR) << "socket() failed"; |
| return base::ScopedFD(); |
| } |
| if (setsockopt(fd.get(), SOL_SOCKET, SO_ATTACH_FILTER, &kNDFrameBpfProgram, |
| sizeof(kNDFrameBpfProgram))) { |
| PLOG(ERROR) << "setsockopt(SO_ATTACH_FILTER) failed"; |
| return base::ScopedFD(); |
| } |
| return fd; |
| } |
| |
| bool NDProxy::Init() { |
| rtnl_fd_ = base::ScopedFD( |
| socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE)); |
| if (!rtnl_fd_.is_valid()) { |
| PLOG(ERROR) << "socket() failed for rtnetlink socket"; |
| return false; |
| } |
| sockaddr_nl local = { |
| .nl_family = AF_NETLINK, |
| .nl_groups = 0, |
| }; |
| if (bind(rtnl_fd_.get(), reinterpret_cast<sockaddr*>(&local), sizeof(local)) < |
| 0) { |
| PLOG(ERROR) << "bind() failed on rtnetlink socket"; |
| return false; |
| } |
| |
| dummy_fd_ = base::ScopedFD(socket(AF_INET6, SOCK_DGRAM, 0)); |
| if (!dummy_fd_.is_valid()) { |
| PLOG(ERROR) << "socket() failed for dummy socket"; |
| return false; |
| } |
| return true; |
| } |
| |
| // In an ICMPv6 Ethernet Frame *frame with length frame_len, replace the mac |
| // address in option opt_type into *target_mac. nd_hdr_len indicates the length |
| // of ICMPv6 ND message headers (so the first option starts after nd_hdr_len.) |
| void NDProxy::ReplaceMacInIcmpOption(uint8_t* frame, |
| ssize_t frame_len, |
| size_t nd_hdr_len, |
| uint8_t opt_type, |
| const MacAddress& target_mac) { |
| nd_opt_hdr* opt; |
| nd_opt_hdr* end = reinterpret_cast<nd_opt_hdr*>(&frame[frame_len]); |
| for (opt = reinterpret_cast<nd_opt_hdr*>(frame + ETHER_HDR_LEN + |
| sizeof(ip6_hdr) + nd_hdr_len); |
| opt < end && opt->nd_opt_len > 0; |
| opt = reinterpret_cast<nd_opt_hdr*>(reinterpret_cast<uint64_t*>(opt) + |
| opt->nd_opt_len)) { |
| if (opt->nd_opt_type == opt_type) { |
| uint8_t* mac_in_opt = |
| reinterpret_cast<uint8_t*>(opt) + sizeof(nd_opt_hdr); |
| memcpy(mac_in_opt, target_mac.data(), ETHER_ADDR_LEN); |
| } |
| } |
| } |
| |
| // RFC 4389 |
| // Read the input ICMPv6 frame and determine whether it should be proxied. If |
| // so, fill out_frame buffer with proxied frame and return the length of proxied |
| // frame (usually same with input frame length). Return a negative value if |
| // proxy is not needed or error occured. |
| // in_frame: buffer containing input ethernet frame; needs special alignment |
| // so that IP header is 4-bytes aligned; |
| // frame_len: the length of input frame; |
| // local_mac_addr: MAC address of interface that will be used to send frame; |
| // out_frame: buffer for output frame; should have at least space of frame_len; |
| // needs special alignment so that IP header is 4-bytes aligned. |
| ssize_t NDProxy::TranslateNDFrame(const uint8_t* in_frame, |
| ssize_t frame_len, |
| const MacAddress& local_mac_addr, |
| uint8_t* out_frame) { |
| if ((reinterpret_cast<uintptr_t>(in_frame + ETHER_HDR_LEN) & 0x3) != 0 || |
| (reinterpret_cast<uintptr_t>(out_frame + ETHER_HDR_LEN) & 0x3) != 0) { |
| return kTranslateErrorBufferMisaligned; |
| } |
| if (frame_len < ETHER_HDR_LEN + sizeof(ip6_hdr) + sizeof(icmp6_hdr)) { |
| return kTranslateErrorInsufficientLength; |
| } |
| if (reinterpret_cast<const ethhdr*>(in_frame)->h_proto != htons(ETH_P_IPV6) || |
| reinterpret_cast<const ip6_hdr*>(in_frame + ETHER_HDR_LEN)->ip6_nxt != |
| IPPROTO_ICMPV6) { |
| return kTranslateErrorNotICMPv6Frame; |
| } |
| |
| memcpy(out_frame, in_frame, frame_len); |
| ethhdr* eth = reinterpret_cast<ethhdr*>(out_frame); |
| ip6_hdr* ip6 = reinterpret_cast<ip6_hdr*>(out_frame + ETHER_HDR_LEN); |
| icmp6_hdr* icmp6 = |
| reinterpret_cast<icmp6_hdr*>(out_frame + ETHER_HDR_LEN + sizeof(ip6_hdr)); |
| |
| // If destination MAC is unicast (Individual/Group bit in MAC address == 0), |
| // it needs to be modified so guest OS L3 stack can see it. |
| // For proxy cascading case, we also need to recheck if destination MAC is |
| // ff:ff:ff:ff:ff:ff (which must have been filled by an upstream proxy). |
| if (!(eth->h_dest[0] & 0x1) || |
| memcmp(eth->h_dest, kBroadcastMacAddress, ETHER_ADDR_LEN) == 0) { |
| MacAddress neighbor_mac; |
| if (GetNeighborMac(ip6->ip6_dst, &neighbor_mac)) { |
| memcpy(eth->h_dest, neighbor_mac.data(), ETHER_ADDR_LEN); |
| } else { |
| // If we can't resolve the destination IP into MAC from kernel neighbor |
| // table, fill destination MAC with broadcast MAC instead. |
| memcpy(eth->h_dest, kBroadcastMacAddress, ETHER_ADDR_LEN); |
| } |
| } |
| |
| switch (icmp6->icmp6_type) { |
| case ND_ROUTER_SOLICIT: |
| ReplaceMacInIcmpOption(out_frame, frame_len, sizeof(nd_router_solicit), |
| ND_OPT_SOURCE_LINKADDR, local_mac_addr); |
| break; |
| case ND_ROUTER_ADVERT: { |
| // RFC 4389 Section 4.1.3.3 - Set Proxy bit |
| nd_router_advert* ra = reinterpret_cast<nd_router_advert*>(icmp6); |
| if (ra->nd_ra_flags_reserved & 0x04) { |
| // According to RFC 4389, an RA packet with 'Proxy' bit set already |
| // should not be proxied again, in order to avoid loop. However, we'll |
| // need this form of proxy cascading in Crostini (Host->VM->Container) |
| // so we are ignoring the check here. Note that we know we are doing RA |
| // proxy in only one direction so there should be no loop. |
| } |
| ra->nd_ra_flags_reserved |= 0x04; |
| |
| ReplaceMacInIcmpOption(out_frame, frame_len, sizeof(nd_router_advert), |
| ND_OPT_SOURCE_LINKADDR, local_mac_addr); |
| break; |
| } |
| case ND_NEIGHBOR_SOLICIT: |
| ReplaceMacInIcmpOption(out_frame, frame_len, sizeof(nd_neighbor_solicit), |
| ND_OPT_SOURCE_LINKADDR, local_mac_addr); |
| break; |
| case ND_NEIGHBOR_ADVERT: |
| ReplaceMacInIcmpOption(out_frame, frame_len, sizeof(nd_neighbor_advert), |
| ND_OPT_TARGET_LINKADDR, local_mac_addr); |
| break; |
| default: |
| return kTranslateErrorNotNDFrame; |
| } |
| |
| // We need to clear the old checksum first so checksum calculation does not |
| // wrongly take old checksum into account. |
| icmp6->icmp6_cksum = 0; |
| icmp6->icmp6_cksum = Icmpv6Checksum(ip6, icmp6); |
| |
| memcpy(eth->h_source, local_mac_addr.data(), ETHER_ADDR_LEN); |
| return frame_len; |
| } |
| |
| void NDProxy::ReplaceSourceIP(uint8_t* frame, const in6_addr& src_ip) { |
| ip6_hdr* ip6 = reinterpret_cast<ip6_hdr*>(frame + ETHER_HDR_LEN); |
| icmp6_hdr* icmp6 = |
| reinterpret_cast<icmp6_hdr*>(frame + ETHER_HDR_LEN + sizeof(ip6_hdr)); |
| |
| memcpy(&ip6->ip6_src, &src_ip, sizeof(in6_addr)); |
| |
| // Recalculate checksum. |
| icmp6->icmp6_cksum = 0; |
| icmp6->icmp6_cksum = Icmpv6Checksum(ip6, icmp6); |
| } |
| |
| void NDProxy::ReadAndProcessOneFrame(int fd) { |
| sockaddr_ll dst_addr; |
| struct iovec iov = { |
| .iov_base = in_frame_buffer_, |
| .iov_len = IP_MAXPACKET, |
| }; |
| msghdr hdr = { |
| .msg_name = &dst_addr, |
| .msg_namelen = sizeof(dst_addr), |
| .msg_iov = &iov, |
| .msg_iovlen = 1, |
| .msg_control = nullptr, |
| .msg_controllen = 0, |
| .msg_flags = 0, |
| }; |
| |
| ssize_t len; |
| if ((len = recvmsg(fd, &hdr, 0)) < 0) { |
| // Ignore ENETDOWN: this can happen if the interface is not yet configured |
| if (errno != ENETDOWN) { |
| PLOG(WARNING) << "recvmsg() failed"; |
| } |
| return; |
| } |
| ip6_hdr* ip6 = reinterpret_cast<ip6_hdr*>(in_frame_buffer_ + ETH_HLEN); |
| icmp6_hdr* icmp6 = reinterpret_cast<icmp6_hdr*>( |
| in_frame_buffer_ + ETHER_HDR_LEN + sizeof(ip6_hdr)); |
| |
| if (ip6->ip6_nxt != IPPROTO_ICMPV6 || icmp6->icmp6_type < ND_ROUTER_SOLICIT || |
| icmp6->icmp6_type > ND_NEIGHBOR_ADVERT) |
| return; |
| |
| // Notify DeviceManager on receiving NA from guest, so a /128 route to the |
| // guest can be added on the host. |
| if (icmp6->icmp6_type == ND_NEIGHBOR_ADVERT && |
| IsGuestInterface(dst_addr.sll_ifindex) && |
| !guest_discovery_handler_.is_null()) { |
| nd_neighbor_advert* na = reinterpret_cast<nd_neighbor_advert*>(icmp6); |
| if (((na->nd_na_target.s6_addr[0] & 0xe0) == 0x20) // Global Unicast |
| || ((na->nd_na_target.s6_addr[0] & 0xfe) == 0xfc)) { // Unique Local |
| char ifname[IFNAMSIZ]; |
| if_indextoname(dst_addr.sll_ifindex, ifname); |
| char ipv6_addr_str[INET6_ADDRSTRLEN]; |
| inet_ntop(AF_INET6, &(na->nd_na_target.s6_addr), ipv6_addr_str, |
| INET6_ADDRSTRLEN); |
| guest_discovery_handler_.Run(std::string(ifname), |
| std::string(ipv6_addr_str)); |
| } |
| } |
| |
| // On receiving RA from router, generate an address for each guest-facing |
| // interface, and sent it to DeviceManager so it can be assigned. This address |
| // will be used when directly communicating with guest OS through IPv6. |
| if (icmp6->icmp6_type == ND_ROUTER_ADVERT && |
| IsRouterInterface(dst_addr.sll_ifindex) && |
| !router_discovery_handler_.is_null()) { |
| const nd_opt_prefix_info* prefix_info = |
| GetPrefixInfoOption(in_frame_buffer_, len); |
| if (prefix_info != nullptr && prefix_info->nd_opt_pi_prefix_len <= 64) { |
| // Generate an EUI-64 address from virtual interface MAC. A prefix |
| // larger that /64 is required. |
| for (int target_if : if_map_ra_[dst_addr.sll_ifindex]) { |
| MacAddress local_mac; |
| if (!GetLocalMac(target_if, &local_mac)) |
| continue; |
| in6_addr eui64_ip; |
| GenerateEUI64Address(&eui64_ip, prefix_info->nd_opt_pi_prefix, |
| local_mac); |
| char eui64_addr_str[INET6_ADDRSTRLEN]; |
| inet_ntop(AF_INET6, &eui64_ip, eui64_addr_str, INET6_ADDRSTRLEN); |
| char target_ifname[IFNAMSIZ]; |
| if_indextoname(target_if, target_ifname); |
| router_discovery_handler_.Run(std::string(target_ifname), |
| std::string(eui64_addr_str)); |
| } |
| } |
| } |
| |
| // b/187918638: with Fibocom cellular modem we are observing irregular RAs |
| // coming from a src IP that either cannot map to a hardware address in |
| // neighbor table, or is mapped to the local MAC address on the cellular |
| // interface. Directly proxying these RAs will cause guest OS to set up a |
| // default route to a next hop that's not reachable. |
| // A workaround is taken to overwrite that router IP with the host link local |
| // IP, so that the guest OS set up the default route with the host as next hop |
| // instead. |
| // This piece of code detect this irregular case and the actual translation |
| // code happens later below. |
| bool unusual_ra_src = false; |
| if (icmp6->icmp6_type == ND_ROUTER_ADVERT && |
| IsRouterInterface(dst_addr.sll_ifindex)) { |
| MacAddress router_mac; |
| MacAddress inbound_local_mac; |
| if (!GetNeighborMac(ip6->ip6_src, &router_mac)) { |
| // The router ip is not in neighbor table |
| unusual_ra_src = true; |
| } else { |
| // Detect if the router ip get resolved to a local MAC |
| unusual_ra_src = (GetLocalMac(dst_addr.sll_ifindex, &inbound_local_mac) && |
| router_mac == inbound_local_mac); |
| } |
| if (unusual_ra_src) |
| irregular_router_ifs.insert(dst_addr.sll_ifindex); |
| } |
| |
| // b/187918638(cont.): since these cell upstream never send proper NS and NA, |
| // there is no chance that we get the guest IP as normally from NA. Instead, |
| // we have to monitor DAD NS frames and use it as judgement. Notice that since |
| // upstream never reply NA, this DAD never fails. |
| if (icmp6->icmp6_type == ND_NEIGHBOR_SOLICIT && |
| IsGuestToIrregularRouter(dst_addr.sll_ifindex) && |
| !guest_discovery_handler_.is_null()) { |
| uint8_t zerobuf[sizeof(in6_addr)] = {0}; |
| nd_neighbor_solicit* ns = reinterpret_cast<nd_neighbor_solicit*>(icmp6); |
| if (memcmp(&ip6->ip6_src, zerobuf, sizeof(in6_addr)) == |
| 0 // Empty source IP indicates DAD |
| && |
| (((ns->nd_ns_target.s6_addr[0] & 0xe0) == 0x20) // Global Unicast |
| || ((ns->nd_ns_target.s6_addr[0] & 0xfe) == 0xfc))) { // Unique Local |
| char ifname[IFNAMSIZ]; |
| if_indextoname(dst_addr.sll_ifindex, ifname); |
| char ipv6_addr_str[INET6_ADDRSTRLEN]; |
| inet_ntop(AF_INET6, &(ns->nd_ns_target.s6_addr), ipv6_addr_str, |
| INET6_ADDRSTRLEN); |
| guest_discovery_handler_.Run(std::string(ifname), |
| std::string(ipv6_addr_str)); |
| } |
| } |
| |
| // Translate the NDP frame and send it through proxy interface |
| auto map_entry = MapForType(icmp6->icmp6_type)->find(dst_addr.sll_ifindex); |
| if (map_entry == MapForType(icmp6->icmp6_type)->end()) |
| return; |
| const auto& target_ifs = map_entry->second; |
| for (int target_if : target_ifs) { |
| MacAddress local_mac; |
| if (!GetLocalMac(target_if, &local_mac)) |
| continue; |
| int result = |
| TranslateNDFrame(in_frame_buffer_, len, local_mac, out_frame_buffer_); |
| if (result < 0) { |
| switch (result) { |
| case kTranslateErrorNotICMPv6Frame: |
| LOG(DFATAL) << "Attempt to TranslateNDFrame on a non-ICMPv6 frame"; |
| return; |
| case kTranslateErrorNotNDFrame: |
| LOG(DFATAL) << "Attempt to TranslateNDFrame on a non-NDP frame, " |
| "icmpv6 type = " |
| << static_cast<int>(reinterpret_cast<icmp6_hdr*>( |
| in_frame_buffer_ + ETHER_HDR_LEN + |
| sizeof(ip6_hdr)) |
| ->icmp6_type); |
| return; |
| case kTranslateErrorInsufficientLength: |
| LOG(DFATAL) << "TranslateNDFrame failed: frame_len = " << len |
| << " is too small"; |
| return; |
| default: |
| LOG(DFATAL) << "Unknown error in TranslateNDFrame"; |
| return; |
| } |
| } |
| |
| // b/187918638: Overwrite source IP address with host address to workaround |
| // irregular router address on Fibocom cell modem, as described above. |
| if (unusual_ra_src) { |
| in6_addr new_src; |
| if (!GetLinkLocalAddress(target_if, &new_src)) { |
| LOG(WARNING) << "Cannot find a local address for L3 relay, skipping " |
| "proxy RA to interface " |
| << target_if; |
| return; |
| } |
| ReplaceSourceIP(out_frame_buffer_, new_src); |
| } |
| |
| struct iovec iov_out = { |
| .iov_base = out_frame_buffer_, |
| .iov_len = static_cast<size_t>(len), |
| }; |
| sockaddr_ll addr = { |
| .sll_family = AF_PACKET, |
| .sll_protocol = htons(ETH_P_IPV6), |
| .sll_ifindex = target_if, |
| .sll_halen = ETHER_ADDR_LEN, |
| }; |
| memcpy(addr.sll_addr, reinterpret_cast<ethhdr*>(out_frame_buffer_)->h_dest, |
| ETHER_ADDR_LEN); |
| msghdr hdr = { |
| .msg_name = &addr, |
| .msg_namelen = sizeof(addr), |
| .msg_iov = &iov_out, |
| .msg_iovlen = 1, |
| .msg_control = nullptr, |
| .msg_controllen = 0, |
| }; |
| if (sendmsg(fd, &hdr, 0) < 0) { |
| // Ignore ENETDOWN: this can happen if the interface is not yet configured |
| if (if_map_ra_.find(target_if) != if_map_ra_.end() && errno != ENETDOWN) { |
| PLOG(WARNING) << "sendmsg() failed on interface " << target_if; |
| } |
| } |
| } |
| } |
| |
| const nd_opt_prefix_info* NDProxy::GetPrefixInfoOption(const uint8_t* in_frame, |
| ssize_t frame_len) { |
| const uint8_t* ptr = |
| in_frame + ETH_HLEN + sizeof(ip6_hdr) + sizeof(nd_router_advert); |
| while (ptr + offsetof(nd_opt_hdr, nd_opt_len) < in_frame + frame_len) { |
| const nd_opt_hdr* opt = reinterpret_cast<const nd_opt_hdr*>(ptr); |
| if (opt->nd_opt_len == 0) |
| return nullptr; |
| ptr += opt->nd_opt_len << 3; // nd_opt_len is in 8 bytes |
| if (ptr > in_frame + frame_len) |
| return nullptr; |
| if (opt->nd_opt_type == ND_OPT_PREFIX_INFORMATION && |
| opt->nd_opt_len << 3 == sizeof(nd_opt_prefix_info)) { |
| return reinterpret_cast<const nd_opt_prefix_info*>(opt); |
| } |
| } |
| return nullptr; |
| } |
| |
| bool NDProxy::GetLinkLocalAddress(int ifindex, in6_addr* link_local) { |
| DCHECK(link_local != nullptr); |
| std::ifstream proc_file("/proc/net/if_inet6"); |
| std::string line; |
| while (std::getline(proc_file, line)) { |
| // Line format in /proc/net/if_inet6: |
| // address ifindex prefix_len scope flags ifname |
| auto tokens = base::SplitString(line, " \t", base::TRIM_WHITESPACE, |
| base::SPLIT_WANT_NONEMPTY); |
| if (tokens[3] != "20") { |
| // We are only looking for link local address (scope value == "20") |
| continue; |
| } |
| int line_if_id; |
| if (!base::HexStringToInt(tokens[1], &line_if_id) || |
| line_if_id != ifindex) { |
| continue; |
| } |
| std::vector<uint8_t> line_address; |
| if (!base::HexStringToBytes(tokens[0], &line_address) || |
| line_address.size() != sizeof(in6_addr)) { |
| continue; |
| } |
| memcpy(link_local, line_address.data(), sizeof(in6_addr)); |
| return true; |
| } |
| return false; |
| } |
| |
| bool NDProxy::GetLocalMac(int if_id, MacAddress* mac_addr) { |
| ifreq ifr = { |
| .ifr_ifindex = if_id, |
| }; |
| if (ioctl(dummy_fd_.get(), SIOCGIFNAME, &ifr) < 0) { |
| PLOG(ERROR) << "ioctl() failed to get interface name on interface " |
| << if_id; |
| return false; |
| } |
| if (ioctl(dummy_fd_.get(), SIOCGIFHWADDR, &ifr) < 0) { |
| PLOG(ERROR) << "ioctl() failed to get MAC address on interface " << if_id; |
| return false; |
| } |
| memcpy(mac_addr->data(), ifr.ifr_addr.sa_data, ETHER_ADDR_LEN); |
| return true; |
| } |
| |
| bool NDProxy::GetNeighborMac(const in6_addr& ipv6_addr, MacAddress* mac_addr) { |
| sockaddr_nl kernel = { |
| .nl_family = AF_NETLINK, |
| .nl_groups = 0, |
| }; |
| struct nl_req { |
| nlmsghdr hdr; |
| rtgenmsg gen; |
| } req = { |
| .hdr = |
| { |
| .nlmsg_len = NLMSG_LENGTH(sizeof(rtgenmsg)), |
| .nlmsg_type = RTM_GETNEIGH, |
| .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP, |
| .nlmsg_seq = 1, |
| }, |
| .gen = |
| { |
| .rtgen_family = AF_INET6, |
| }, |
| }; |
| iovec io_req = { |
| .iov_base = &req, |
| .iov_len = req.hdr.nlmsg_len, |
| }; |
| msghdr rtnl_req = { |
| .msg_name = &kernel, |
| .msg_namelen = sizeof(kernel), |
| .msg_iov = &io_req, |
| .msg_iovlen = 1, |
| }; |
| if (sendmsg(rtnl_fd_.get(), &rtnl_req, 0) < 0) { |
| PLOG(ERROR) << "sendmsg() failed on rtnetlink socket"; |
| return false; |
| } |
| |
| static constexpr size_t kRtnlReplyBufferSize = 32768; |
| char reply_buffer[kRtnlReplyBufferSize]; |
| iovec io_reply = { |
| .iov_base = reply_buffer, |
| .iov_len = kRtnlReplyBufferSize, |
| }; |
| msghdr rtnl_reply = { |
| .msg_name = &kernel, |
| .msg_namelen = sizeof(kernel), |
| .msg_iov = &io_reply, |
| .msg_iovlen = 1, |
| }; |
| |
| bool any_entry_matched = false; |
| bool done = false; |
| while (!done) { |
| ssize_t len; |
| if ((len = recvmsg(rtnl_fd_.get(), &rtnl_reply, 0)) < 0) { |
| PLOG(ERROR) << "recvmsg() failed on rtnetlink socket"; |
| return false; |
| } |
| for (nlmsghdr* msg_ptr = reinterpret_cast<nlmsghdr*>(reply_buffer); |
| NLMSG_OK(msg_ptr, len); msg_ptr = NLMSG_NEXT(msg_ptr, len)) { |
| switch (msg_ptr->nlmsg_type) { |
| case NLMSG_DONE: { |
| done = true; |
| break; |
| } |
| case RTM_NEWNEIGH: { |
| // Bitmap - 0x1: Found IP match; 0x2: found MAC address; |
| uint8_t current_entry_status = 0x0; |
| uint8_t current_mac[ETHER_ADDR_LEN]; |
| ndmsg* nd_msg = reinterpret_cast<ndmsg*>(NLMSG_DATA(msg_ptr)); |
| rtattr* rt_attr = reinterpret_cast<rtattr*>(RTM_RTA(nd_msg)); |
| size_t rt_attr_len = RTM_PAYLOAD(msg_ptr); |
| for (; RTA_OK(rt_attr, rt_attr_len); |
| rt_attr = RTA_NEXT(rt_attr, rt_attr_len)) { |
| if (rt_attr->rta_type == NDA_DST && |
| memcmp(&ipv6_addr, RTA_DATA(rt_attr), sizeof(in6_addr)) == 0) { |
| current_entry_status |= 0x1; |
| } else if (rt_attr->rta_type == NDA_LLADDR) { |
| current_entry_status |= 0x2; |
| memcpy(current_mac, RTA_DATA(rt_attr), ETHER_ADDR_LEN); |
| } |
| } |
| if (current_entry_status == 0x3) { |
| memcpy(mac_addr->data(), current_mac, ETHER_ADDR_LEN); |
| any_entry_matched = true; |
| } |
| break; |
| } |
| default: { |
| LOG(WARNING) << "received unexpected rtnetlink message type " |
| << msg_ptr->nlmsg_type << ", length " |
| << msg_ptr->nlmsg_len; |
| break; |
| } |
| } |
| } |
| } |
| return any_entry_matched; |
| } |
| |
| void NDProxy::RegisterOnGuestIpDiscoveryHandler( |
| const base::Callback<void(const std::string&, const std::string&)>& |
| handler) { |
| guest_discovery_handler_ = handler; |
| } |
| |
| void NDProxy::RegisterOnRouterDiscoveryHandler( |
| const base::Callback<void(const std::string&, const std::string&)>& |
| handler) { |
| router_discovery_handler_ = handler; |
| } |
| |
| NDProxy::interface_mapping* NDProxy::MapForType(uint8_t type) { |
| switch (type) { |
| case ND_ROUTER_SOLICIT: |
| return &if_map_rs_; |
| case ND_ROUTER_ADVERT: |
| return &if_map_ra_; |
| case ND_NEIGHBOR_SOLICIT: |
| return &if_map_ns_na_; |
| case ND_NEIGHBOR_ADVERT: |
| return &if_map_ns_na_; |
| default: |
| LOG(DFATAL) << "Attempt to get interface map on illegal icmpv6 type " |
| << static_cast<int>(type); |
| return nullptr; |
| } |
| } |
| |
| bool NDProxy::AddInterfacePair(const std::string& ifname_physical, |
| const std::string& ifname_guest) { |
| LOG(INFO) << "Adding interface pair between physical: " << ifname_physical |
| << ", guest: " << ifname_guest; |
| int ifid_physical = if_nametoindex(ifname_physical.c_str()); |
| if (ifid_physical == 0) { |
| PLOG(ERROR) << "Get interface index failed on " << ifname_physical; |
| return false; |
| } |
| int ifid_guest = if_nametoindex(ifname_guest.c_str()); |
| if (ifid_guest == 0) { |
| PLOG(ERROR) << "Get interface index failed on " << ifname_guest; |
| return false; |
| } |
| if (ifid_physical == ifid_guest) { |
| LOG(ERROR) << "Rejected attempt to forward between same interface " |
| << ifname_physical << " and " << ifname_guest; |
| return false; |
| } |
| if_map_rs_[ifid_guest].insert(ifid_physical); |
| if_map_ra_[ifid_physical].insert(ifid_guest); |
| if_map_ns_na_[ifid_physical].insert(ifid_guest); |
| if_map_ns_na_[ifid_guest].insert(ifid_physical); |
| for (int ifid_other_guest : if_map_ra_[ifid_physical]) { |
| if (ifid_other_guest != ifid_guest) { |
| if_map_ns_na_[ifid_other_guest].insert(ifid_guest); |
| if_map_ns_na_[ifid_guest].insert(ifid_other_guest); |
| } |
| } |
| return true; |
| } |
| |
| bool NDProxy::RemoveInterfacePair(const std::string& ifname_physical, |
| const std::string& ifname_guest) { |
| LOG(INFO) << "Removing interface pair between physical: " << ifname_physical |
| << ", guest: " << ifname_guest; |
| int ifid_physical = if_nametoindex(ifname_physical.c_str()); |
| if (ifid_physical == 0) { |
| PLOG(ERROR) << "Get interface index failed on " << ifname_physical; |
| return false; |
| } |
| int ifid_guest = if_nametoindex(ifname_guest.c_str()); |
| if (ifid_guest == 0) { |
| PLOG(ERROR) << "Get interface index failed on " << ifname_guest; |
| return false; |
| } |
| if (ifid_physical == ifid_guest) { |
| LOG(ERROR) << "Rejected attempt to forward between same interface " |
| << ifname_physical << " and " << ifname_guest; |
| return false; |
| } |
| if_map_rs_.erase(ifid_guest); |
| if_map_ra_[ifid_physical].erase(ifid_guest); |
| if_map_ns_na_.erase(ifid_guest); |
| if_map_ns_na_[ifid_physical].erase(ifid_guest); |
| for (int ifid_other_guest : if_map_ra_[ifid_physical]) { |
| if_map_ns_na_[ifid_other_guest].erase(ifid_guest); |
| } |
| return true; |
| } |
| |
| bool NDProxy::RemoveInterface(const std::string& ifname) { |
| LOG(INFO) << "Removing physical interface " << ifname; |
| int ifindex = if_nametoindex(ifname.c_str()); |
| if (ifindex == 0) { |
| PLOG(ERROR) << "Get interface index failed on " << ifname; |
| return false; |
| } |
| for (int ifid_guest : if_map_ra_[ifindex]) { |
| if_map_rs_.erase(ifid_guest); |
| if_map_ns_na_.erase(ifid_guest); |
| } |
| if_map_ra_.erase(ifindex); |
| if_map_ns_na_.erase(ifindex); |
| return true; |
| } |
| |
| bool NDProxy::IsGuestInterface(int ifindex) { |
| return if_map_rs_.find(ifindex) != if_map_rs_.end(); |
| } |
| |
| bool NDProxy::IsRouterInterface(int ifindex) { |
| return if_map_ra_.find(ifindex) != if_map_ra_.end(); |
| } |
| |
| bool NDProxy::IsGuestToIrregularRouter(int ifindex) { |
| if (!IsGuestInterface(ifindex)) |
| return false; |
| for (int target_if : if_map_rs_[ifindex]) { |
| if (irregular_router_ifs.count(target_if) > 0) |
| return true; |
| } |
| return false; |
| } |
| |
| std::vector<std::string> NDProxy::GetGuestInterfaces( |
| const std::string& ifname_physical) { |
| std::vector<std::string> result; |
| int ifid_physical = if_nametoindex(ifname_physical.c_str()); |
| if (ifid_physical == 0) |
| return result; |
| for (int ifid_guest : if_map_ra_[ifid_physical]) { |
| char ifname[IFNAMSIZ]; |
| if_indextoname(ifid_guest, ifname); |
| result.push_back(ifname); |
| } |
| return result; |
| } |
| |
| NDProxyDaemon::NDProxyDaemon(base::ScopedFD control_fd) |
| : msg_dispatcher_( |
| std::make_unique<MessageDispatcher>(std::move(control_fd))) {} |
| |
| NDProxyDaemon::~NDProxyDaemon() {} |
| |
| int NDProxyDaemon::OnInit() { |
| // Prevent the main process from sending us any signals. |
| if (setsid() < 0) { |
| PLOG(ERROR) << "Failed to created a new session with setsid: exiting"; |
| return EX_OSERR; |
| } |
| |
| EnterChildProcessJail(); |
| |
| // Register control fd callbacks |
| if (msg_dispatcher_) { |
| msg_dispatcher_->RegisterFailureHandler(base::Bind( |
| &NDProxyDaemon::OnParentProcessExit, weak_factory_.GetWeakPtr())); |
| msg_dispatcher_->RegisterDeviceMessageHandler(base::Bind( |
| &NDProxyDaemon::OnDeviceMessage, weak_factory_.GetWeakPtr())); |
| } |
| |
| // Initialize NDProxy and register guest IP discovery callback |
| if (!proxy_.Init()) { |
| PLOG(ERROR) << "Failed to initialize NDProxy internal state"; |
| return EX_OSERR; |
| } |
| proxy_.RegisterOnGuestIpDiscoveryHandler(base::Bind( |
| &NDProxyDaemon::OnGuestIpDiscovery, weak_factory_.GetWeakPtr())); |
| proxy_.RegisterOnRouterDiscoveryHandler(base::Bind( |
| &NDProxyDaemon::OnRouterDiscovery, weak_factory_.GetWeakPtr())); |
| |
| // Initialize data fd |
| fd_ = NDProxy::PreparePacketSocket(); |
| if (!fd_.is_valid()) { |
| return EX_OSERR; |
| } |
| |
| // Start watching on data fd |
| watcher_ = base::FileDescriptorWatcher::WatchReadable( |
| fd_.get(), base::Bind(&NDProxyDaemon::OnDataSocketReadReady, |
| weak_factory_.GetWeakPtr())); |
| LOG(INFO) << "Started watching on packet fd..."; |
| |
| return Daemon::OnInit(); |
| } |
| |
| void NDProxyDaemon::OnDataSocketReadReady() { |
| proxy_.ReadAndProcessOneFrame(fd_.get()); |
| } |
| |
| void NDProxyDaemon::OnParentProcessExit() { |
| LOG(ERROR) << "Quitting because the parent process died"; |
| Quit(); |
| } |
| |
| void NDProxyDaemon::OnDeviceMessage(const DeviceMessage& msg) { |
| const std::string& dev_ifname = msg.dev_ifname(); |
| LOG_IF(DFATAL, dev_ifname.empty()) |
| << "Received DeviceMessage w/ empty dev_ifname"; |
| if (msg.has_teardown()) { |
| if (msg.has_br_ifname()) { |
| proxy_.RemoveInterfacePair(dev_ifname, msg.br_ifname()); |
| if (guest_if_addrs_.find(msg.br_ifname()) != guest_if_addrs_.end()) { |
| SendMessage(NDProxyMessage::DEL_ADDR, msg.br_ifname(), |
| guest_if_addrs_[msg.br_ifname()]); |
| guest_if_addrs_.erase(msg.br_ifname()); |
| } |
| |
| } else { |
| auto guest_ifs = proxy_.GetGuestInterfaces(dev_ifname); |
| proxy_.RemoveInterface(dev_ifname); |
| for (const auto& guest_if : guest_ifs) { |
| if (guest_if_addrs_.find(guest_if) != guest_if_addrs_.end()) { |
| SendMessage(NDProxyMessage::DEL_ADDR, guest_if, |
| guest_if_addrs_[guest_if]); |
| guest_if_addrs_.erase(guest_if); |
| } |
| } |
| } |
| } else if (msg.has_br_ifname()) { |
| proxy_.AddInterfacePair(dev_ifname, msg.br_ifname()); |
| } |
| } |
| |
| void NDProxyDaemon::OnGuestIpDiscovery(const std::string& ifname, |
| const std::string& ip6addr) { |
| SendMessage(NDProxyMessage::ADD_ROUTE, ifname, ip6addr); |
| } |
| |
| void NDProxyDaemon::OnRouterDiscovery(const std::string& ifname, |
| const std::string& ip6addr) { |
| std::string current_addr = guest_if_addrs_[ifname]; |
| if (current_addr == ip6addr) |
| return; |
| if (!current_addr.empty()) { |
| SendMessage(NDProxyMessage::DEL_ADDR, ifname, current_addr); |
| } |
| SendMessage(NDProxyMessage::ADD_ADDR, ifname, ip6addr); |
| guest_if_addrs_[ifname] = ip6addr; |
| } |
| |
| void NDProxyDaemon::SendMessage(NDProxyMessage::NDProxyEventType type, |
| const std::string& ifname, |
| const std::string& ip6addr) { |
| if (!msg_dispatcher_) |
| return; |
| NDProxyMessage msg; |
| msg.set_type(type); |
| msg.set_ifname(ifname); |
| msg.set_ip6addr(ip6addr); |
| IpHelperMessage ipm; |
| *ipm.mutable_ndproxy_message() = msg; |
| msg_dispatcher_->SendMessage(ipm); |
| } |
| |
| } // namespace patchpanel |