blob: 2ca216ef4ece6bb52151563aea18e351b71a1ed2 [file] [log] [blame]
Taoyu Liaa6238b2019-09-06 17:38:52 +09001// Copyright 2019 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/network/ndproxy.h"
6
7#include <errno.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
Taoyu Lif0370c92019-09-18 15:04:37 +090011#include <sysexits.h>
Taoyu Liaa6238b2019-09-06 17:38:52 +090012#include <unistd.h>
13
14#include <arpa/inet.h>
Taoyu Lifbcda452019-09-11 16:57:25 +090015#include <linux/filter.h>
Taoyu Liaa6238b2019-09-06 17:38:52 +090016#include <linux/if_packet.h>
17#include <linux/in6.h>
18#include <net/ethernet.h>
19#include <net/if.h>
20#include <sys/ioctl.h>
21#include <sys/socket.h>
22
23#include <string>
Taoyu Lif0370c92019-09-18 15:04:37 +090024#include <utility>
25
26#include <base/bind.h>
Taoyu Liaa6238b2019-09-06 17:38:52 +090027
28namespace arc_networkd {
29namespace {
Taoyu Lifbcda452019-09-11 16:57:25 +090030const unsigned char kBroadcastMacAddress[] = {0xff, 0xff, 0xff,
Taoyu Liaa6238b2019-09-06 17:38:52 +090031 0xff, 0xff, 0xff};
Taoyu Lifbcda452019-09-11 16:57:25 +090032
33sock_filter kNDFrameBpfInstructions[] = {
34 // Load ethernet type.
35 BPF_STMT(BPF_LD | BPF_H | BPF_ABS, offsetof(ether_header, ether_type)),
36 // Check if it equals IPv6, if not, then goto return 0.
37 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 9),
38 // Move index to start of IPv6 header.
39 BPF_STMT(BPF_LDX | BPF_IMM, sizeof(ether_header)),
40 // Load IPv6 next header.
41 BPF_STMT(BPF_LD | BPF_B | BPF_IND, offsetof(ip6_hdr, ip6_nxt)),
42 // Check if equals ICMPv6, if not, then goto return 0.
43 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 6),
44 // Move index to start of ICMPv6 header.
45 BPF_STMT(BPF_LDX | BPF_IMM, sizeof(ether_header) + sizeof(ip6_hdr)),
46 // Load ICMPv6 type.
47 BPF_STMT(BPF_LD | BPF_B | BPF_IND, offsetof(icmp6_hdr, icmp6_type)),
48 // Check if is ND ICMPv6 message.
49 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_SOLICIT, 4, 0),
50 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 3, 0),
51 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_SOLICIT, 2, 0),
52 BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0),
53 // Return 0.
54 BPF_STMT(BPF_RET | BPF_K, 0),
55 // Return MAX.
56 BPF_STMT(BPF_RET | BPF_K, IP_MAXPACKET),
57};
58const sock_fprog kNDFrameBpfProgram = {
59 .len = sizeof(kNDFrameBpfInstructions) / sizeof(sock_filter),
60 .filter = kNDFrameBpfInstructions};
61
Taoyu Liaa6238b2019-09-06 17:38:52 +090062} // namespace
63
64const ssize_t NDProxy::kTranslateErrorNotICMPv6Frame = -1;
65const ssize_t NDProxy::kTranslateErrorNotNDFrame = -2;
66const ssize_t NDProxy::kTranslateErrorInsufficientLength = -3;
67
68// RFC 1071 and RFC 8200 Section 8.1
69// We are doing calculation directly in network order. Note this algorithm works
70// regardless of the endianess of the host.
71uint16_t NDProxy::Icmpv6Checksum(const ip6_hdr* ip6, const icmp6_hdr* icmp6) {
72 uint32_t sum = 0;
73 // Src and Dst IP
74 for (size_t i = 0; i < (sizeof(struct in6_addr) >> 1); ++i)
75 sum += ip6->ip6_src.s6_addr16[i];
76 for (size_t i = 0; i < (sizeof(struct in6_addr) >> 1); ++i)
77 sum += ip6->ip6_dst.s6_addr16[i];
78
79 // Upper-Layer Packet Length
80 sum += ip6->ip6_plen;
81 // Next Header
82 sum += IPPROTO_ICMPV6 << 8;
83
84 // ICMP
85 const uint16_t* word = reinterpret_cast<const uint16_t*>(icmp6);
86 uint16_t len;
87 for (len = ntohs(ip6->ip6_plen); len > 1; len -= 2)
88 sum += *word++;
89 if (len)
90 sum += *word & htons(0x0000ffff);
91
92 // Fold 32-bit into 16 bits
93 while (sum >> 16)
94 sum = (sum & 0xffff) + (sum >> 16);
95 return ~sum;
96}
97
98// In an ICMPv6 Ethernet Frame *frame with length frame_len, replace the mac
99// address in option opt_type into *target_mac. nd_hdr_len indicates the length
100// of ICMPv6 ND message headers (so the first option starts after nd_hdr_len.)
101void NDProxy::ReplaceMacInIcmpOption(uint8_t* frame,
102 ssize_t frame_len,
103 size_t nd_hdr_len,
104 uint8_t opt_type,
105 const uint8_t* target_mac) {
106 nd_opt_hdr* opt;
107 nd_opt_hdr* end = reinterpret_cast<nd_opt_hdr*>(&frame[frame_len]);
108 for (opt = reinterpret_cast<nd_opt_hdr*>(frame + ETHER_HDR_LEN +
109 sizeof(ip6_hdr) + nd_hdr_len);
110 opt < end && opt->nd_opt_len > 0;
111 opt = reinterpret_cast<nd_opt_hdr*>(reinterpret_cast<uint64_t*>(opt) +
112 opt->nd_opt_len)) {
113 if (opt->nd_opt_type == opt_type) {
114 uint8_t* mac_in_opt =
115 reinterpret_cast<uint8_t*>(opt) + sizeof(nd_opt_hdr);
116 memcpy(mac_in_opt, target_mac, ETHER_ADDR_LEN);
117 }
118 }
119}
120
121// RFC 4389
122// Read the input ICMPv6 frame and determine whether it should be proxied. If
123// so, fill out_frame buffer with proxied frame and return the length of proxied
124// frame (usually same with input frame length). Return a negative value if
125// proxy is not needed or error occured.
126// in_frame: buffer containing input ethernet frame;
127// frame_len: the length of input frame;
128// local_mac_addr: MAC address of interface that will be used to send frame;
129// out_frame: buffer for output frame; should have at least space of frame_len.
130ssize_t NDProxy::TranslateNDFrame(const uint8_t* in_frame,
131 ssize_t frame_len,
132 const uint8_t* local_mac_addr,
133 uint8_t* out_frame) {
134 if (frame_len < ETHER_HDR_LEN + sizeof(ip6_hdr)) {
135 return kTranslateErrorInsufficientLength;
136 }
137 if (reinterpret_cast<const ethhdr*>(in_frame)->h_proto != htons(ETH_P_IPV6) ||
138 reinterpret_cast<const ip6_hdr*>(in_frame + ETHER_HDR_LEN)->ip6_nxt !=
139 IPPROTO_ICMPV6) {
140 return kTranslateErrorNotICMPv6Frame;
141 }
142
143 memcpy(out_frame, in_frame, frame_len);
144 ethhdr* eth = reinterpret_cast<ethhdr*>(out_frame);
145 ip6_hdr* ip6 = reinterpret_cast<ip6_hdr*>(out_frame + ETHER_HDR_LEN);
146 icmp6_hdr* icmp6 =
147 reinterpret_cast<icmp6_hdr*>(out_frame + ETHER_HDR_LEN + sizeof(ip6_hdr));
148
149 // If destination MAC is unicast (Individual/Group bit in MAC address == 0),
150 // it needs to be modified into broadcast so guest OS L3 stack can see it.
151 if (!(eth->h_dest[0] & 0x1)) {
Taoyu Lifbcda452019-09-11 16:57:25 +0900152 memcpy(eth->h_dest, kBroadcastMacAddress, ETHER_ADDR_LEN);
Taoyu Liaa6238b2019-09-06 17:38:52 +0900153 }
154
155 switch (icmp6->icmp6_type) {
156 case ND_ROUTER_SOLICIT:
157 ReplaceMacInIcmpOption(out_frame, frame_len, sizeof(nd_router_solicit),
158 ND_OPT_SOURCE_LINKADDR, local_mac_addr);
159 break;
160 case ND_ROUTER_ADVERT: {
161 // RFC 4389 Section 4.1.3.3 - Set Proxy bit
162 nd_router_advert* ra = reinterpret_cast<nd_router_advert*>(icmp6);
163 if (ra->nd_ra_flags_reserved & 0x04) {
164 // According to RFC 4389, an RA packet with 'Proxy' bit set already
165 // should not be proxied again, in order to avoid loop. However, we'll
166 // need this form of proxy cascading in Crostini (Host->VM->Container)
167 // so we are ignoring the check here. Note that we know we are doing RA
168 // proxy in only one direction so there should be no loop.
169 }
170 ra->nd_ra_flags_reserved |= 0x04;
171
172 ReplaceMacInIcmpOption(out_frame, frame_len, sizeof(nd_router_advert),
173 ND_OPT_SOURCE_LINKADDR, local_mac_addr);
174 break;
175 }
176 case ND_NEIGHBOR_SOLICIT:
177 ReplaceMacInIcmpOption(out_frame, frame_len, sizeof(nd_neighbor_solicit),
178 ND_OPT_SOURCE_LINKADDR, local_mac_addr);
179 break;
180 case ND_NEIGHBOR_ADVERT:
181 ReplaceMacInIcmpOption(out_frame, frame_len, sizeof(nd_neighbor_advert),
182 ND_OPT_TARGET_LINKADDR, local_mac_addr);
183 break;
184 default:
185 return kTranslateErrorNotNDFrame;
186 }
187
188 // We need to clear the old checksum first so checksum calculation does not
189 // wrongly take old checksum into account.
190 icmp6->icmp6_cksum = 0;
191 icmp6->icmp6_cksum = Icmpv6Checksum(ip6, icmp6);
192
193 memcpy(eth->h_source, local_mac_addr, ETHER_ADDR_LEN);
194 return frame_len;
195}
196
197NDProxy::interface_mapping* NDProxy::MapForType(uint8_t type) {
198 switch (type) {
199 case ND_ROUTER_SOLICIT:
200 return &if_map_rs_;
201 case ND_ROUTER_ADVERT:
202 return &if_map_ra_;
203 case ND_NEIGHBOR_SOLICIT:
204 return &if_map_ns_na_;
205 case ND_NEIGHBOR_ADVERT:
206 return &if_map_ns_na_;
207 default:
208 LOG(DFATAL) << "Attempt to get interface map on illegal icmpv6 type "
209 << static_cast<int>(type);
210 return nullptr;
211 }
212}
213
214void NDProxy::ProxyNDFrame(int target_if, ssize_t frame_len) {
215 ifreq ifr = {
216 .ifr_ifindex = target_if,
217 };
218 if (ioctl(fd_.get(), SIOCGIFNAME, &ifr) < 0) {
219 PLOG(ERROR) << "ioctl() failed to get interface name on interface "
220 << target_if;
221 return;
222 }
223 if (ioctl(fd_.get(), SIOCGIFHWADDR, &ifr) < 0) {
224 PLOG(ERROR) << "ioctl() failed to get MAC address on interface "
225 << target_if;
226 return;
227 }
228
229 int result =
230 TranslateNDFrame(in_frame_buffer_, frame_len,
231 reinterpret_cast<const uint8_t*>(ifr.ifr_addr.sa_data),
232 out_frame_buffer_);
233 if (result < 0) {
234 switch (result) {
235 case kTranslateErrorNotICMPv6Frame:
236 LOG(DFATAL) << "Attempt to TranslateNDFrame on a non-ICMPv6 frame";
237 return;
238 case kTranslateErrorNotNDFrame:
239 LOG(DFATAL)
240 << "Attempt to TranslateNDFrame on a non-NDP frame, icmpv6 type = "
241 << static_cast<int>(reinterpret_cast<icmp6_hdr*>(in_frame_buffer_ +
242 ETHER_HDR_LEN +
243 sizeof(ip6_hdr))
244 ->icmp6_type);
245 return;
246 case kTranslateErrorInsufficientLength:
247 LOG(DFATAL) << "TranslateNDFrame failed: frame_len = " << frame_len
248 << " is too small";
249 return;
250 default:
251 LOG(DFATAL) << "Unknown error in TranslateNDFrame";
252 return;
253 }
254 }
255
256 struct iovec iov = {
257 .iov_base = out_frame_buffer_,
258 .iov_len = static_cast<size_t>(frame_len),
259 };
260 sockaddr_ll addr = {
261 .sll_family = AF_PACKET,
Caroline Tice98512942019-09-17 11:04:00 -0700262 .sll_protocol = htons(ETH_P_IPV6),
Taoyu Liaa6238b2019-09-06 17:38:52 +0900263 .sll_ifindex = target_if,
264 .sll_halen = ETHER_ADDR_LEN,
Taoyu Liaa6238b2019-09-06 17:38:52 +0900265 };
266 memcpy(addr.sll_addr, reinterpret_cast<ethhdr*>(out_frame_buffer_)->h_dest,
267 ETHER_ADDR_LEN);
268 msghdr hdr = {
269 .msg_name = &addr,
270 .msg_namelen = sizeof(addr),
271 .msg_iov = &iov,
272 .msg_iovlen = 1,
273 .msg_control = nullptr,
274 .msg_controllen = 0,
275 };
276
277 if (sendmsg(fd_.get(), &hdr, 0) < 0) {
278 PLOG(ERROR) << "sendmsg() failed on interface " << target_if;
279 }
280}
281
Taoyu Lif0370c92019-09-18 15:04:37 +0900282NDProxy::NDProxy() {}
283
284NDProxy::NDProxy(base::ScopedFD control_fd)
285 : msg_dispatcher_(
286 std::make_unique<MessageDispatcher>(std::move(control_fd))) {}
287
288NDProxy::~NDProxy() {}
289
290int NDProxy::OnInit() {
291 // Prevent the main process from sending us any signals.
292 if (setsid() < 0) {
293 PLOG(ERROR) << "Failed to created a new session with setsid: exiting";
294 return EX_OSERR;
295 }
296
297 // Register control fd callbacks
298 if (msg_dispatcher_) {
299 msg_dispatcher_->RegisterFailureHandler(
300 base::Bind(&NDProxy::OnParentProcessExit, weak_factory_.GetWeakPtr()));
301 msg_dispatcher_->RegisterDeviceMessageHandler(
302 base::Bind(&NDProxy::OnDeviceMessage, weak_factory_.GetWeakPtr()));
303 }
304
305 // Initialize data fd
Taoyu Liaa6238b2019-09-06 17:38:52 +0900306 fd_ = base::ScopedFD(socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6)));
307 if (!fd_.is_valid()) {
308 PLOG(ERROR) << "socket() failed";
Taoyu Lif0370c92019-09-18 15:04:37 +0900309 return EX_OSERR;
Taoyu Liaa6238b2019-09-06 17:38:52 +0900310 }
Taoyu Lifbcda452019-09-11 16:57:25 +0900311 if (setsockopt(fd_.get(), SOL_SOCKET, SO_ATTACH_FILTER, &kNDFrameBpfProgram,
312 sizeof(kNDFrameBpfProgram))) {
313 PLOG(ERROR) << "setsockopt(SO_ATTACH_FILTER) failed";
Taoyu Lif0370c92019-09-18 15:04:37 +0900314 return EX_OSERR;
Taoyu Lifbcda452019-09-11 16:57:25 +0900315 }
Taoyu Liaa6238b2019-09-06 17:38:52 +0900316
Taoyu Lif0370c92019-09-18 15:04:37 +0900317 // Start watching on data fd
318 watcher_ = base::FileDescriptorWatcher::WatchReadable(
319 fd_.get(),
320 base::Bind(&NDProxy::OnDataSocketReadReady, weak_factory_.GetWeakPtr()));
321 LOG(INFO) << "Started watching on packet fd...";
322
323 return Daemon::OnInit();
324}
325
326void NDProxy::OnDataSocketReadReady() {
Taoyu Liaa6238b2019-09-06 17:38:52 +0900327 sockaddr_ll dst_addr;
328 struct iovec iov = {
329 .iov_base = in_frame_buffer_,
330 .iov_len = IP_MAXPACKET,
331 };
332 msghdr hdr = {
333 .msg_name = &dst_addr,
334 .msg_namelen = sizeof(dst_addr),
335 .msg_iov = &iov,
336 .msg_iovlen = 1,
337 .msg_control = nullptr,
338 .msg_controllen = 0,
339 .msg_flags = 0,
340 };
341
342 ssize_t len;
Taoyu Lif0370c92019-09-18 15:04:37 +0900343 if ((len = recvmsg(fd_.get(), &hdr, 0)) < 0) {
344 PLOG(ERROR) << "recvmsg() failed";
345 return;
346 }
347 ip6_hdr* ip6 = reinterpret_cast<ip6_hdr*>(in_frame_buffer_ + ETH_HLEN);
348 icmp6_hdr* icmp6 = reinterpret_cast<icmp6_hdr*>(
349 in_frame_buffer_ + ETHER_HDR_LEN + sizeof(ip6_hdr));
350 if (ip6->ip6_nxt != IPPROTO_ICMPV6 || icmp6->icmp6_type < ND_ROUTER_SOLICIT ||
351 icmp6->icmp6_type > ND_NEIGHBOR_ADVERT)
352 return;
353 auto map_entry = MapForType(icmp6->icmp6_type)->find(dst_addr.sll_ifindex);
354 if (map_entry == MapForType(icmp6->icmp6_type)->end())
355 return;
356 const auto& target_ifs = map_entry->second;
357 for (int target_if : target_ifs) {
358 ProxyNDFrame(target_if, len);
359 }
360}
361
362void NDProxy::OnParentProcessExit() {
363 LOG(ERROR) << "Quitting because the parent process died";
364 Quit();
365}
366
367void NDProxy::OnDeviceMessage(const DeviceMessage& msg) {
368 const std::string& dev_ifname = msg.dev_ifname();
369 LOG_IF(DFATAL, dev_ifname.empty())
370 << "Received DeviceMessage w/ empty dev_ifname";
371 if (msg.has_br_ifname()) {
372 AddRouterInterfacePair(dev_ifname, msg.br_ifname());
373 } else if (msg.has_teardown()) {
374 RemoveInterface(dev_ifname);
Taoyu Liaa6238b2019-09-06 17:38:52 +0900375 }
376}
377
378bool NDProxy::AddRouterInterfacePair(const std::string& ifname_physical,
379 const std::string& ifname_guest) {
Taoyu Lif0370c92019-09-18 15:04:37 +0900380 LOG(INFO) << "Adding interface pair between physical: " << ifname_physical
381 << ", guest: " << ifname_guest;
Taoyu Liaa6238b2019-09-06 17:38:52 +0900382 return AddInterfacePairInternal(ifname_physical, ifname_guest, true);
383}
384
385bool NDProxy::AddPeeringInterfacePair(const std::string& ifname1,
386 const std::string& ifname2) {
Taoyu Lif0370c92019-09-18 15:04:37 +0900387 LOG(INFO) << "Adding peering interface pair between " << ifname1 << " and "
388 << ifname2;
Taoyu Liaa6238b2019-09-06 17:38:52 +0900389 return AddInterfacePairInternal(ifname1, ifname2, false);
390}
391
392bool NDProxy::AddInterfacePairInternal(const std::string& ifname1,
393 const std::string& ifname2,
394 bool proxy_rs_ra) {
395 int ifindex1 = if_nametoindex(ifname1.c_str());
396 if (ifindex1 == 0) {
397 PLOG(ERROR) << "Get interface index failed on " << ifname1;
398 return false;
399 }
400 int ifindex2 = if_nametoindex(ifname2.c_str());
401 if (ifindex2 == 0) {
402 PLOG(ERROR) << "Get interface index failed on " << ifname2;
403 return false;
404 }
405 if (ifindex1 == ifindex2) {
406 LOG(ERROR) << "Rejected attempt to forward between same interface "
407 << ifname1 << " and " << ifname2;
408 return false;
409 }
410 if (proxy_rs_ra) {
411 if_map_rs_[ifindex2].insert(ifindex1);
412 if_map_ra_[ifindex1].insert(ifindex2);
413 }
414 if_map_ns_na_[ifindex1].insert(ifindex2);
415 if_map_ns_na_[ifindex2].insert(ifindex1);
416 return true;
417}
418
419bool NDProxy::RemoveInterface(const std::string& ifname) {
Taoyu Lif0370c92019-09-18 15:04:37 +0900420 LOG(INFO) << "Removing interface " << ifname;
Taoyu Liaa6238b2019-09-06 17:38:52 +0900421 int ifindex = if_nametoindex(ifname.c_str());
422 if (ifindex == 0) {
423 PLOG(ERROR) << "Get interface index failed on " << ifname;
424 return false;
425 }
426 if_map_rs_.erase(ifindex);
427 for (auto& kv : if_map_rs_)
428 kv.second.erase(ifindex);
429 if_map_ra_.erase(ifindex);
430 for (auto& kv : if_map_ra_)
431 kv.second.erase(ifindex);
432 if_map_ns_na_.erase(ifindex);
433 for (auto& kv : if_map_ns_na_)
434 kv.second.erase(ifindex);
435 return true;
436}
437
438} // namespace arc_networkd