1 // Copyright (c) 2019 The Chromium 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 "quiche/quic/qbone/bonnet/tun_device_packet_exchanger.h"
6 
7 #include <netinet/icmp6.h>
8 #include <netinet/ip6.h>
9 
10 #include <utility>
11 
12 #include "absl/strings/str_cat.h"
13 #include "quiche/quic/qbone/platform/icmp_packet.h"
14 #include "quiche/quic/qbone/platform/netlink_interface.h"
15 #include "quiche/quic/qbone/qbone_constants.h"
16 
17 namespace quic {
18 
TunDevicePacketExchanger(size_t mtu,KernelInterface * kernel,NetlinkInterface * netlink,QbonePacketExchanger::Visitor * visitor,size_t max_pending_packets,bool is_tap,StatsInterface * stats,absl::string_view ifname)19 TunDevicePacketExchanger::TunDevicePacketExchanger(
20     size_t mtu, KernelInterface* kernel, NetlinkInterface* netlink,
21     QbonePacketExchanger::Visitor* visitor, size_t max_pending_packets,
22     bool is_tap, StatsInterface* stats, absl::string_view ifname)
23     : QbonePacketExchanger(visitor, max_pending_packets),
24       mtu_(mtu),
25       kernel_(kernel),
26       netlink_(netlink),
27       ifname_(ifname),
28       is_tap_(is_tap),
29       stats_(stats) {
30   if (is_tap_) {
31     mtu_ += ETH_HLEN;
32   }
33 }
34 
WritePacket(const char * packet,size_t size,bool * blocked,std::string * error)35 bool TunDevicePacketExchanger::WritePacket(const char* packet, size_t size,
36                                            bool* blocked, std::string* error) {
37   *blocked = false;
38   if (fd_ < 0) {
39     *error = absl::StrCat("Invalid file descriptor of the TUN device: ", fd_);
40     stats_->OnWriteError(error);
41     return false;
42   }
43 
44   auto buffer = std::make_unique<QuicData>(packet, size);
45   if (is_tap_) {
46     buffer = ApplyL2Headers(*buffer);
47   }
48   int result = kernel_->write(fd_, buffer->data(), buffer->length());
49   if (result == -1) {
50     if (errno == EWOULDBLOCK || errno == EAGAIN) {
51       // The tunnel is blocked. Note that this does not mean the receive buffer
52       // of a TCP connection is filled. This simply means the TUN device itself
53       // is blocked on handing packets to the rest part of the kernel.
54       *error = absl::StrCat("Write to the TUN device was blocked: ", errno);
55       *blocked = true;
56       stats_->OnWriteError(error);
57     }
58     return false;
59   }
60   stats_->OnPacketWritten(result);
61 
62   return true;
63 }
64 
ReadPacket(bool * blocked,std::string * error)65 std::unique_ptr<QuicData> TunDevicePacketExchanger::ReadPacket(
66     bool* blocked, std::string* error) {
67   *blocked = false;
68   if (fd_ < 0) {
69     *error = absl::StrCat("Invalid file descriptor of the TUN device: ", fd_);
70     stats_->OnReadError(error);
71     return nullptr;
72   }
73   // Reading on a TUN device returns a packet at a time. If the packet is longer
74   // than the buffer, it's truncated.
75   auto read_buffer = std::make_unique<char[]>(mtu_);
76   int result = kernel_->read(fd_, read_buffer.get(), mtu_);
77   // Note that 0 means end of file, but we're talking about a TUN device - there
78   // is no end of file. Therefore 0 also indicates error.
79   if (result <= 0) {
80     if (errno == EAGAIN || errno == EWOULDBLOCK) {
81       *error = absl::StrCat("Read from the TUN device was blocked: ", errno);
82       *blocked = true;
83       stats_->OnReadError(error);
84     }
85     return nullptr;
86   }
87 
88   auto buffer = std::make_unique<QuicData>(read_buffer.release(), result, true);
89   if (is_tap_) {
90     buffer = ConsumeL2Headers(*buffer);
91   }
92   if (buffer) {
93     stats_->OnPacketRead(buffer->length());
94   }
95   return buffer;
96 }
97 
set_file_descriptor(int fd)98 void TunDevicePacketExchanger::set_file_descriptor(int fd) { fd_ = fd; }
99 
100 const TunDevicePacketExchanger::StatsInterface*
stats_interface() const101 TunDevicePacketExchanger::stats_interface() const {
102   return stats_;
103 }
104 
ApplyL2Headers(const QuicData & l3_packet)105 std::unique_ptr<QuicData> TunDevicePacketExchanger::ApplyL2Headers(
106     const QuicData& l3_packet) {
107   if (is_tap_ && !mac_initialized_) {
108     NetlinkInterface::LinkInfo link_info{};
109     if (netlink_->GetLinkInfo(ifname_, &link_info)) {
110       memcpy(tap_mac_, link_info.hardware_address, ETH_ALEN);
111       mac_initialized_ = true;
112     } else {
113       QUIC_LOG_EVERY_N_SEC(ERROR, 30)
114           << "Unable to get link info for: " << ifname_;
115     }
116   }
117 
118   const auto l2_packet_size = l3_packet.length() + ETH_HLEN;
119   auto l2_buffer = std::make_unique<char[]>(l2_packet_size);
120 
121   // Populate the Ethernet header
122   auto* hdr = reinterpret_cast<ethhdr*>(l2_buffer.get());
123   // Set src & dst to my own address
124   memcpy(hdr->h_dest, tap_mac_, ETH_ALEN);
125   memcpy(hdr->h_source, tap_mac_, ETH_ALEN);
126   // Assume ipv6 for now
127   // TODO(b/195113643): Support additional protocols.
128   hdr->h_proto = absl::ghtons(ETH_P_IPV6);
129 
130   // Copy the l3 packet into buffer, just after the ethernet header.
131   memcpy(l2_buffer.get() + ETH_HLEN, l3_packet.data(), l3_packet.length());
132 
133   return std::make_unique<QuicData>(l2_buffer.release(), l2_packet_size, true);
134 }
135 
ConsumeL2Headers(const QuicData & l2_packet)136 std::unique_ptr<QuicData> TunDevicePacketExchanger::ConsumeL2Headers(
137     const QuicData& l2_packet) {
138   if (l2_packet.length() < ETH_HLEN) {
139     // Packet is too short for ethernet headers. Drop it.
140     return nullptr;
141   }
142   auto* hdr = reinterpret_cast<const ethhdr*>(l2_packet.data());
143   if (hdr->h_proto != absl::ghtons(ETH_P_IPV6)) {
144     return nullptr;
145   }
146   constexpr auto kIp6PrefixLen = ETH_HLEN + sizeof(ip6_hdr);
147   constexpr auto kIcmp6PrefixLen = kIp6PrefixLen + sizeof(icmp6_hdr);
148   if (l2_packet.length() < kIp6PrefixLen) {
149     // Packet is too short to be ipv6. Drop it.
150     return nullptr;
151   }
152   auto* ip_hdr = reinterpret_cast<const ip6_hdr*>(l2_packet.data() + ETH_HLEN);
153   const bool is_icmp = ip_hdr->ip6_ctlun.ip6_un1.ip6_un1_nxt == IPPROTO_ICMPV6;
154 
155   bool is_neighbor_solicit = false;
156   if (is_icmp) {
157     if (l2_packet.length() < kIcmp6PrefixLen) {
158       // Packet is too short to be icmp6. Drop it.
159       return nullptr;
160     }
161     is_neighbor_solicit =
162         reinterpret_cast<const icmp6_hdr*>(l2_packet.data() + kIp6PrefixLen)
163             ->icmp6_type == ND_NEIGHBOR_SOLICIT;
164   }
165 
166   if (is_neighbor_solicit) {
167     // If we've received a neighbor solicitation, craft an advertisement to
168     // respond with and write it back to the local interface.
169     auto* icmp6_payload = l2_packet.data() + kIcmp6PrefixLen;
170 
171     QuicIpAddress target_address(
172         *reinterpret_cast<const in6_addr*>(icmp6_payload));
173     if (target_address != *QboneConstants::GatewayAddress()) {
174       // Only respond to solicitations for our gateway address
175       return nullptr;
176     }
177 
178     // Neighbor Advertisement crafted per:
179     // https://datatracker.ietf.org/doc/html/rfc4861#section-4.4
180     //
181     // Using the Target link-layer address option defined at:
182     // https://datatracker.ietf.org/doc/html/rfc4861#section-4.6.1
183     constexpr size_t kIcmpv6OptionSize = 8;
184     const int payload_size = sizeof(in6_addr) + kIcmpv6OptionSize;
185     auto payload = std::make_unique<char[]>(payload_size);
186     // Place the solicited IPv6 address at the beginning of the response payload
187     memcpy(payload.get(), icmp6_payload, sizeof(in6_addr));
188     // Setup the Target link-layer address option:
189     //      0                   1                   2                   3
190     //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
191     // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
192     // |     Type      |    Length     |    Link-Layer Address ...
193     // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
194     int pos = sizeof(in6_addr);
195     payload[pos++] = ND_OPT_TARGET_LINKADDR;    // Type
196     payload[pos++] = 1;                         // Length in units of 8 octets
197     memcpy(&payload[pos], tap_mac_, ETH_ALEN);  // This interfaces' MAC address
198 
199     // Populate the ICMPv6 header
200     icmp6_hdr response_hdr{};
201     response_hdr.icmp6_type = ND_NEIGHBOR_ADVERT;
202     // Set the solicited bit to true
203     response_hdr.icmp6_dataun.icmp6_un_data8[0] = 64;
204     // Craft the full ICMPv6 packet and then ship it off to WritePacket
205     // to have it frame it with L2 headers and send it back to the requesting
206     // neighbor.
207     CreateIcmpPacket(ip_hdr->ip6_src, ip_hdr->ip6_src, response_hdr,
208                      absl::string_view(payload.get(), payload_size),
209                      [this](absl::string_view packet) {
210                        bool blocked;
211                        std::string error;
212                        WritePacket(packet.data(), packet.size(), &blocked,
213                                    &error);
214                      });
215     // Do not forward the neighbor solicitation through the tunnel since it's
216     // link-local.
217     return nullptr;
218   }
219 
220   // If this isn't a Neighbor Solicitation, remove the L2 headers and forward
221   // it as though it were an L3 packet.
222   const auto l3_packet_size = l2_packet.length() - ETH_HLEN;
223   auto shift_buffer = std::make_unique<char[]>(l3_packet_size);
224   memcpy(shift_buffer.get(), l2_packet.data() + ETH_HLEN, l3_packet_size);
225 
226   return std::make_unique<QuicData>(shift_buffer.release(), l3_packet_size,
227                                     true);
228 }
229 
230 }  // namespace quic
231