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