Kevin Cernekee | 95d4ae9 | 2016-06-19 10:26:29 -0700 | [diff] [blame] | 1 | // Copyright 2016 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "arc-networkd/multicast_forwarder.h" |
| 6 | |
| 7 | #include <arpa/inet.h> |
| 8 | #include <netinet/ip.h> |
| 9 | #include <string.h> |
| 10 | #include <sys/socket.h> |
| 11 | #include <sys/types.h> |
| 12 | |
Kevin Cernekee | b2c0c83 | 2016-12-06 11:47:57 -0800 | [diff] [blame] | 13 | #include <utility> |
| 14 | |
Kevin Cernekee | 95d4ae9 | 2016-06-19 10:26:29 -0700 | [diff] [blame] | 15 | #include <base/bind.h> |
| 16 | #include <base/logging.h> |
| 17 | #include <base/message_loop/message_loop.h> |
| 18 | #include <base/time/time.h> |
| 19 | |
Kevin Cernekee | 73e0920 | 2017-06-17 20:55:09 -0700 | [diff] [blame^] | 20 | #include "arc-networkd/dns/dns_protocol.h" |
| 21 | #include "arc-networkd/dns/dns_response.h" |
| 22 | |
Kevin Cernekee | 95d4ae9 | 2016-06-19 10:26:29 -0700 | [diff] [blame] | 23 | namespace { |
| 24 | |
| 25 | const int kNumTempSockets = 4; |
| 26 | const int kBufSize = 1536; |
| 27 | const int kCleanupIntervalMs = 5000; |
| 28 | const int kCleanupTimeSeconds = 30; |
| 29 | |
| 30 | } // namespace |
| 31 | |
| 32 | namespace arc_networkd { |
| 33 | |
| 34 | bool MulticastForwarder::Start(const std::string& int_ifname, |
| 35 | const std::string& lan_ifname, |
Kevin Cernekee | 73e0920 | 2017-06-17 20:55:09 -0700 | [diff] [blame^] | 36 | const std::string& mdns_ipaddr, |
Kevin Cernekee | 95d4ae9 | 2016-06-19 10:26:29 -0700 | [diff] [blame] | 37 | const std::string& mcast_addr, |
| 38 | unsigned short port, |
| 39 | bool allow_stateless) { |
| 40 | int_ifname_ = int_ifname; |
| 41 | lan_ifname_ = lan_ifname; |
| 42 | port_ = port; |
| 43 | allow_stateless_ = allow_stateless; |
| 44 | |
| 45 | if (!inet_aton(mcast_addr.c_str(), &mcast_addr_)) { |
| 46 | LOG(ERROR) << "invalid multicast address " << mcast_addr; |
| 47 | return false; |
| 48 | } |
| 49 | |
Kevin Cernekee | 73e0920 | 2017-06-17 20:55:09 -0700 | [diff] [blame^] | 50 | mdns_ip_.s_addr = INADDR_ANY; |
| 51 | if (!mdns_ipaddr.empty() && !inet_aton(mdns_ipaddr.c_str(), &mdns_ip_)) { |
| 52 | LOG(WARNING) << "invalid internal IP address " << mdns_ipaddr; |
| 53 | } |
| 54 | |
Kevin Cernekee | 95d4ae9 | 2016-06-19 10:26:29 -0700 | [diff] [blame] | 55 | int_socket_.reset(new MulticastSocket()); |
| 56 | int_socket_->Bind(int_ifname, mcast_addr_, port, this); |
| 57 | |
| 58 | if (allow_stateless_) { |
| 59 | lan_socket_.reset(new MulticastSocket()); |
| 60 | lan_socket_->Bind(lan_ifname, mcast_addr_, port, this); |
Kevin Cernekee | 73e0920 | 2017-06-17 20:55:09 -0700 | [diff] [blame^] | 61 | lan_ip_ = lan_socket_->interface_ip(); |
Kevin Cernekee | 95d4ae9 | 2016-06-19 10:26:29 -0700 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | CleanupTask(); |
| 65 | return true; |
| 66 | } |
| 67 | |
| 68 | // This callback is registered as part of MulticastSocket::Bind(). |
| 69 | // All of our sockets use this function as a common callback. |
| 70 | void MulticastForwarder::OnFileCanReadWithoutBlocking(int fd) { |
| 71 | char data[kBufSize]; |
| 72 | struct sockaddr_in fromaddr; |
| 73 | |
| 74 | ssize_t bytes = MulticastSocket::RecvFromFd(fd, data, kBufSize, &fromaddr); |
| 75 | if (bytes < 0) |
| 76 | return; |
| 77 | |
| 78 | unsigned short port = ntohs(fromaddr.sin_port); |
| 79 | |
| 80 | struct sockaddr_in dst = {0}; |
| 81 | dst.sin_family = AF_INET; |
| 82 | dst.sin_port = htons(port_); |
| 83 | dst.sin_addr = mcast_addr_; |
| 84 | |
| 85 | // Forward traffic that is part of an existing connection. |
| 86 | for (auto& temp : temp_sockets_) { |
| 87 | if (fd == temp->fd()) { |
| 88 | int_socket_->SendTo(data, bytes, temp->int_addr); |
| 89 | return; |
| 90 | } else if (fd == int_socket_->fd() && |
| 91 | fromaddr.sin_port == temp->int_addr.sin_port) { |
Kevin Cernekee | 73e0920 | 2017-06-17 20:55:09 -0700 | [diff] [blame^] | 92 | TranslateMdnsIp(data, bytes); |
Kevin Cernekee | 95d4ae9 | 2016-06-19 10:26:29 -0700 | [diff] [blame] | 93 | temp->SendTo(data, bytes, dst); |
| 94 | return; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | // Forward stateless traffic. |
| 99 | if (allow_stateless_ && port == port_) { |
| 100 | if (fd == int_socket_->fd()) { |
Kevin Cernekee | 73e0920 | 2017-06-17 20:55:09 -0700 | [diff] [blame^] | 101 | TranslateMdnsIp(data, bytes); |
Kevin Cernekee | 95d4ae9 | 2016-06-19 10:26:29 -0700 | [diff] [blame] | 102 | lan_socket_->SendTo(data, bytes, dst); |
| 103 | return; |
| 104 | } else if (fd == lan_socket_->fd()) { |
| 105 | int_socket_->SendTo(data, bytes, dst); |
| 106 | return; |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | // New connection. |
| 111 | if (fd != int_socket_->fd()) |
| 112 | return; |
| 113 | |
| 114 | std::unique_ptr<MulticastSocket> new_sock(new MulticastSocket()); |
| 115 | if (!new_sock->Bind(lan_ifname_, mcast_addr_, port, this) && |
| 116 | !new_sock->Bind(lan_ifname_, mcast_addr_, 0, this)) |
| 117 | return; |
| 118 | memcpy(&new_sock->int_addr, &fromaddr, sizeof(new_sock->int_addr)); |
| 119 | |
| 120 | new_sock->SendTo(data, bytes, dst); |
| 121 | |
| 122 | // This should ideally delete the LRU entry, but since idle entries are |
| 123 | // purged by CleanupTask, the limit will only really be reached if |
| 124 | // the daemon is flooded with requests. |
| 125 | while (temp_sockets_.size() > kNumTempSockets) |
| 126 | temp_sockets_.pop_back(); |
| 127 | temp_sockets_.push_front(std::move(new_sock)); |
| 128 | } |
| 129 | |
Kevin Cernekee | 73e0920 | 2017-06-17 20:55:09 -0700 | [diff] [blame^] | 130 | void MulticastForwarder::TranslateMdnsIp(char* data, ssize_t bytes) { |
| 131 | if (mdns_ip_.s_addr == INADDR_ANY) { |
| 132 | return; |
| 133 | } |
| 134 | |
| 135 | // Make sure this is a valid, successful DNS response from the Android host. |
| 136 | if (bytes > net::dns_protocol::kMaxUDPSize || bytes <= 0) { |
| 137 | return; |
| 138 | } |
| 139 | net::DnsResponse resp; |
| 140 | memcpy(resp.io_buffer()->data(), data, bytes); |
| 141 | if (!resp.InitParseWithoutQuery(bytes) || |
| 142 | !(resp.flags() & net::dns_protocol::kFlagResponse) || |
| 143 | resp.rcode() != net::dns_protocol::kRcodeNOERROR) { |
| 144 | return; |
| 145 | } |
| 146 | |
| 147 | // Check all A records for the internal IP, and replace it with |lan_ip_| |
| 148 | // if it is found. |
| 149 | net::DnsRecordParser parser = resp.Parser(); |
| 150 | while (!parser.AtEnd()) { |
| 151 | const size_t ipv4_addr_len = sizeof(lan_ip_.s_addr); |
| 152 | |
| 153 | net::DnsResourceRecord record; |
| 154 | DCHECK(parser.ReadRecord(&record)); |
| 155 | if (record.type == net::dns_protocol::kTypeA && |
| 156 | record.rdata.size() == ipv4_addr_len) { |
| 157 | const char* rr_ip = record.rdata.data(); |
| 158 | if (mdns_ip_.s_addr == |
| 159 | reinterpret_cast<const struct in_addr*>(rr_ip)->s_addr) { |
| 160 | // HACK: This is able to calculate the (variable) offset of the IPv4 |
| 161 | // address inside the resource record by assuming that the StringPiece |
| 162 | // returns a pointer inside the io_buffer. It works today, but |
| 163 | // future libchrome changes might break it. |
| 164 | size_t ip_offset = rr_ip - resp.io_buffer()->data(); |
| 165 | CHECK(ip_offset <= bytes - ipv4_addr_len); |
| 166 | memcpy(&data[ip_offset], &lan_ip_.s_addr, ipv4_addr_len); |
| 167 | } |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | |
Kevin Cernekee | 95d4ae9 | 2016-06-19 10:26:29 -0700 | [diff] [blame] | 172 | void MulticastForwarder::CleanupTask() { |
| 173 | time_t exp = time(NULL) - kCleanupTimeSeconds; |
| 174 | for (auto it = temp_sockets_.begin(); it != temp_sockets_.end(); ) { |
| 175 | if ((*it)->last_used() < exp) |
| 176 | it = temp_sockets_.erase(it); |
| 177 | else |
| 178 | it++; |
| 179 | } |
| 180 | |
| 181 | base::MessageLoopForIO::current()->PostDelayedTask( |
| 182 | FROM_HERE, |
| 183 | base::Bind(&MulticastForwarder::CleanupTask, |
| 184 | weak_factory_.GetWeakPtr()), |
| 185 | base::TimeDelta::FromMilliseconds(kCleanupIntervalMs)); |
| 186 | } |
| 187 | |
| 188 | } // namespace arc_networkd |