blob: 7d6262405a16d40757a32573e0d4c7607034c20d [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>
11#include <unistd.h>
12
13#include <arpa/inet.h>
14#include <linux/if_packet.h>
15#include <linux/in6.h>
16#include <net/ethernet.h>
17#include <net/if.h>
18#include <sys/ioctl.h>
19#include <sys/socket.h>
20
21#include <string>
22
23namespace arc_networkd {
24namespace {
25const unsigned char ether_addr_broadcast[] = {0xff, 0xff, 0xff,
26 0xff, 0xff, 0xff};
27} // namespace
28
29const ssize_t NDProxy::kTranslateErrorNotICMPv6Frame = -1;
30const ssize_t NDProxy::kTranslateErrorNotNDFrame = -2;
31const ssize_t NDProxy::kTranslateErrorInsufficientLength = -3;
32
33// RFC 1071 and RFC 8200 Section 8.1
34// We are doing calculation directly in network order. Note this algorithm works
35// regardless of the endianess of the host.
36uint16_t NDProxy::Icmpv6Checksum(const ip6_hdr* ip6, const icmp6_hdr* icmp6) {
37 uint32_t sum = 0;
38 // Src and Dst IP
39 for (size_t i = 0; i < (sizeof(struct in6_addr) >> 1); ++i)
40 sum += ip6->ip6_src.s6_addr16[i];
41 for (size_t i = 0; i < (sizeof(struct in6_addr) >> 1); ++i)
42 sum += ip6->ip6_dst.s6_addr16[i];
43
44 // Upper-Layer Packet Length
45 sum += ip6->ip6_plen;
46 // Next Header
47 sum += IPPROTO_ICMPV6 << 8;
48
49 // ICMP
50 const uint16_t* word = reinterpret_cast<const uint16_t*>(icmp6);
51 uint16_t len;
52 for (len = ntohs(ip6->ip6_plen); len > 1; len -= 2)
53 sum += *word++;
54 if (len)
55 sum += *word & htons(0x0000ffff);
56
57 // Fold 32-bit into 16 bits
58 while (sum >> 16)
59 sum = (sum & 0xffff) + (sum >> 16);
60 return ~sum;
61}
62
63// In an ICMPv6 Ethernet Frame *frame with length frame_len, replace the mac
64// address in option opt_type into *target_mac. nd_hdr_len indicates the length
65// of ICMPv6 ND message headers (so the first option starts after nd_hdr_len.)
66void NDProxy::ReplaceMacInIcmpOption(uint8_t* frame,
67 ssize_t frame_len,
68 size_t nd_hdr_len,
69 uint8_t opt_type,
70 const uint8_t* target_mac) {
71 nd_opt_hdr* opt;
72 nd_opt_hdr* end = reinterpret_cast<nd_opt_hdr*>(&frame[frame_len]);
73 for (opt = reinterpret_cast<nd_opt_hdr*>(frame + ETHER_HDR_LEN +
74 sizeof(ip6_hdr) + nd_hdr_len);
75 opt < end && opt->nd_opt_len > 0;
76 opt = reinterpret_cast<nd_opt_hdr*>(reinterpret_cast<uint64_t*>(opt) +
77 opt->nd_opt_len)) {
78 if (opt->nd_opt_type == opt_type) {
79 uint8_t* mac_in_opt =
80 reinterpret_cast<uint8_t*>(opt) + sizeof(nd_opt_hdr);
81 memcpy(mac_in_opt, target_mac, ETHER_ADDR_LEN);
82 }
83 }
84}
85
86// RFC 4389
87// Read the input ICMPv6 frame and determine whether it should be proxied. If
88// so, fill out_frame buffer with proxied frame and return the length of proxied
89// frame (usually same with input frame length). Return a negative value if
90// proxy is not needed or error occured.
91// in_frame: buffer containing input ethernet frame;
92// frame_len: the length of input frame;
93// local_mac_addr: MAC address of interface that will be used to send frame;
94// out_frame: buffer for output frame; should have at least space of frame_len.
95ssize_t NDProxy::TranslateNDFrame(const uint8_t* in_frame,
96 ssize_t frame_len,
97 const uint8_t* local_mac_addr,
98 uint8_t* out_frame) {
99 if (frame_len < ETHER_HDR_LEN + sizeof(ip6_hdr)) {
100 return kTranslateErrorInsufficientLength;
101 }
102 if (reinterpret_cast<const ethhdr*>(in_frame)->h_proto != htons(ETH_P_IPV6) ||
103 reinterpret_cast<const ip6_hdr*>(in_frame + ETHER_HDR_LEN)->ip6_nxt !=
104 IPPROTO_ICMPV6) {
105 return kTranslateErrorNotICMPv6Frame;
106 }
107
108 memcpy(out_frame, in_frame, frame_len);
109 ethhdr* eth = reinterpret_cast<ethhdr*>(out_frame);
110 ip6_hdr* ip6 = reinterpret_cast<ip6_hdr*>(out_frame + ETHER_HDR_LEN);
111 icmp6_hdr* icmp6 =
112 reinterpret_cast<icmp6_hdr*>(out_frame + ETHER_HDR_LEN + sizeof(ip6_hdr));
113
114 // If destination MAC is unicast (Individual/Group bit in MAC address == 0),
115 // it needs to be modified into broadcast so guest OS L3 stack can see it.
116 if (!(eth->h_dest[0] & 0x1)) {
117 memcpy(eth->h_dest, ether_addr_broadcast, ETHER_ADDR_LEN);
118 }
119
120 switch (icmp6->icmp6_type) {
121 case ND_ROUTER_SOLICIT:
122 ReplaceMacInIcmpOption(out_frame, frame_len, sizeof(nd_router_solicit),
123 ND_OPT_SOURCE_LINKADDR, local_mac_addr);
124 break;
125 case ND_ROUTER_ADVERT: {
126 // RFC 4389 Section 4.1.3.3 - Set Proxy bit
127 nd_router_advert* ra = reinterpret_cast<nd_router_advert*>(icmp6);
128 if (ra->nd_ra_flags_reserved & 0x04) {
129 // According to RFC 4389, an RA packet with 'Proxy' bit set already
130 // should not be proxied again, in order to avoid loop. However, we'll
131 // need this form of proxy cascading in Crostini (Host->VM->Container)
132 // so we are ignoring the check here. Note that we know we are doing RA
133 // proxy in only one direction so there should be no loop.
134 }
135 ra->nd_ra_flags_reserved |= 0x04;
136
137 ReplaceMacInIcmpOption(out_frame, frame_len, sizeof(nd_router_advert),
138 ND_OPT_SOURCE_LINKADDR, local_mac_addr);
139 break;
140 }
141 case ND_NEIGHBOR_SOLICIT:
142 ReplaceMacInIcmpOption(out_frame, frame_len, sizeof(nd_neighbor_solicit),
143 ND_OPT_SOURCE_LINKADDR, local_mac_addr);
144 break;
145 case ND_NEIGHBOR_ADVERT:
146 ReplaceMacInIcmpOption(out_frame, frame_len, sizeof(nd_neighbor_advert),
147 ND_OPT_TARGET_LINKADDR, local_mac_addr);
148 break;
149 default:
150 return kTranslateErrorNotNDFrame;
151 }
152
153 // We need to clear the old checksum first so checksum calculation does not
154 // wrongly take old checksum into account.
155 icmp6->icmp6_cksum = 0;
156 icmp6->icmp6_cksum = Icmpv6Checksum(ip6, icmp6);
157
158 memcpy(eth->h_source, local_mac_addr, ETHER_ADDR_LEN);
159 return frame_len;
160}
161
162NDProxy::interface_mapping* NDProxy::MapForType(uint8_t type) {
163 switch (type) {
164 case ND_ROUTER_SOLICIT:
165 return &if_map_rs_;
166 case ND_ROUTER_ADVERT:
167 return &if_map_ra_;
168 case ND_NEIGHBOR_SOLICIT:
169 return &if_map_ns_na_;
170 case ND_NEIGHBOR_ADVERT:
171 return &if_map_ns_na_;
172 default:
173 LOG(DFATAL) << "Attempt to get interface map on illegal icmpv6 type "
174 << static_cast<int>(type);
175 return nullptr;
176 }
177}
178
179void NDProxy::ProxyNDFrame(int target_if, ssize_t frame_len) {
180 ifreq ifr = {
181 .ifr_ifindex = target_if,
182 };
183 if (ioctl(fd_.get(), SIOCGIFNAME, &ifr) < 0) {
184 PLOG(ERROR) << "ioctl() failed to get interface name on interface "
185 << target_if;
186 return;
187 }
188 if (ioctl(fd_.get(), SIOCGIFHWADDR, &ifr) < 0) {
189 PLOG(ERROR) << "ioctl() failed to get MAC address on interface "
190 << target_if;
191 return;
192 }
193
194 int result =
195 TranslateNDFrame(in_frame_buffer_, frame_len,
196 reinterpret_cast<const uint8_t*>(ifr.ifr_addr.sa_data),
197 out_frame_buffer_);
198 if (result < 0) {
199 switch (result) {
200 case kTranslateErrorNotICMPv6Frame:
201 LOG(DFATAL) << "Attempt to TranslateNDFrame on a non-ICMPv6 frame";
202 return;
203 case kTranslateErrorNotNDFrame:
204 LOG(DFATAL)
205 << "Attempt to TranslateNDFrame on a non-NDP frame, icmpv6 type = "
206 << static_cast<int>(reinterpret_cast<icmp6_hdr*>(in_frame_buffer_ +
207 ETHER_HDR_LEN +
208 sizeof(ip6_hdr))
209 ->icmp6_type);
210 return;
211 case kTranslateErrorInsufficientLength:
212 LOG(DFATAL) << "TranslateNDFrame failed: frame_len = " << frame_len
213 << " is too small";
214 return;
215 default:
216 LOG(DFATAL) << "Unknown error in TranslateNDFrame";
217 return;
218 }
219 }
220
221 struct iovec iov = {
222 .iov_base = out_frame_buffer_,
223 .iov_len = static_cast<size_t>(frame_len),
224 };
225 sockaddr_ll addr = {
226 .sll_family = AF_PACKET,
227 .sll_ifindex = target_if,
228 .sll_halen = ETHER_ADDR_LEN,
229 .sll_protocol = htons(ETH_P_IPV6),
230 };
231 memcpy(addr.sll_addr, reinterpret_cast<ethhdr*>(out_frame_buffer_)->h_dest,
232 ETHER_ADDR_LEN);
233 msghdr hdr = {
234 .msg_name = &addr,
235 .msg_namelen = sizeof(addr),
236 .msg_iov = &iov,
237 .msg_iovlen = 1,
238 .msg_control = nullptr,
239 .msg_controllen = 0,
240 };
241
242 if (sendmsg(fd_.get(), &hdr, 0) < 0) {
243 PLOG(ERROR) << "sendmsg() failed on interface " << target_if;
244 }
245}
246
247void NDProxy::Start() {
248 fd_ = base::ScopedFD(socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6)));
249 if (!fd_.is_valid()) {
250 PLOG(ERROR) << "socket() failed";
251 return;
252 }
253
254 sockaddr_ll dst_addr;
255 struct iovec iov = {
256 .iov_base = in_frame_buffer_,
257 .iov_len = IP_MAXPACKET,
258 };
259 msghdr hdr = {
260 .msg_name = &dst_addr,
261 .msg_namelen = sizeof(dst_addr),
262 .msg_iov = &iov,
263 .msg_iovlen = 1,
264 .msg_control = nullptr,
265 .msg_controllen = 0,
266 .msg_flags = 0,
267 };
268
269 ssize_t len;
270 while (true) {
271 if ((len = recvmsg(fd_.get(), &hdr, 0)) < 0) {
272 PLOG(ERROR) << "recvmsg() failed";
273 return;
274 }
275 ip6_hdr* ip6 = reinterpret_cast<ip6_hdr*>(in_frame_buffer_ + ETH_HLEN);
276 icmp6_hdr* icmp6 = reinterpret_cast<icmp6_hdr*>(
277 in_frame_buffer_ + ETHER_HDR_LEN + sizeof(ip6_hdr));
278 if (ip6->ip6_nxt == IPPROTO_ICMPV6 &&
279 icmp6->icmp6_type >= ND_ROUTER_SOLICIT &&
280 icmp6->icmp6_type <= ND_NEIGHBOR_ADVERT) {
281 auto map_entry =
282 MapForType(icmp6->icmp6_type)->find(dst_addr.sll_ifindex);
283 if (map_entry != MapForType(icmp6->icmp6_type)->end()) {
284 const auto& target_ifs = map_entry->second;
285 for (auto target_if : target_ifs) {
286 ProxyNDFrame(target_if, len);
287 }
288 }
289 }
290 }
291}
292
293bool NDProxy::AddRouterInterfacePair(const std::string& ifname_physical,
294 const std::string& ifname_guest) {
295 return AddInterfacePairInternal(ifname_physical, ifname_guest, true);
296}
297
298bool NDProxy::AddPeeringInterfacePair(const std::string& ifname1,
299 const std::string& ifname2) {
300 return AddInterfacePairInternal(ifname1, ifname2, false);
301}
302
303bool NDProxy::AddInterfacePairInternal(const std::string& ifname1,
304 const std::string& ifname2,
305 bool proxy_rs_ra) {
306 int ifindex1 = if_nametoindex(ifname1.c_str());
307 if (ifindex1 == 0) {
308 PLOG(ERROR) << "Get interface index failed on " << ifname1;
309 return false;
310 }
311 int ifindex2 = if_nametoindex(ifname2.c_str());
312 if (ifindex2 == 0) {
313 PLOG(ERROR) << "Get interface index failed on " << ifname2;
314 return false;
315 }
316 if (ifindex1 == ifindex2) {
317 LOG(ERROR) << "Rejected attempt to forward between same interface "
318 << ifname1 << " and " << ifname2;
319 return false;
320 }
321 if (proxy_rs_ra) {
322 if_map_rs_[ifindex2].insert(ifindex1);
323 if_map_ra_[ifindex1].insert(ifindex2);
324 }
325 if_map_ns_na_[ifindex1].insert(ifindex2);
326 if_map_ns_na_[ifindex2].insert(ifindex1);
327 return true;
328}
329
330bool NDProxy::RemoveInterface(const std::string& ifname) {
331 int ifindex = if_nametoindex(ifname.c_str());
332 if (ifindex == 0) {
333 PLOG(ERROR) << "Get interface index failed on " << ifname;
334 return false;
335 }
336 if_map_rs_.erase(ifindex);
337 for (auto& kv : if_map_rs_)
338 kv.second.erase(ifindex);
339 if_map_ra_.erase(ifindex);
340 for (auto& kv : if_map_ra_)
341 kv.second.erase(ifindex);
342 if_map_ns_na_.erase(ifindex);
343 for (auto& kv : if_map_ns_na_)
344 kv.second.erase(ifindex);
345 return true;
346}
347
348} // namespace arc_networkd