1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.connectivity; 18 19 import static android.net.INetd.IF_STATE_UP; 20 import static android.net.INetd.PERMISSION_NETWORK; 21 import static android.net.INetd.PERMISSION_SYSTEM; 22 import static android.system.OsConstants.ETH_P_IP; 23 import static android.system.OsConstants.ETH_P_IPV6; 24 25 import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; 26 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.net.INetd; 30 import android.net.InetAddresses; 31 import android.net.InterfaceConfigurationParcel; 32 import android.net.IpPrefix; 33 import android.os.ParcelFileDescriptor; 34 import android.os.RemoteException; 35 import android.os.ServiceSpecificException; 36 import android.system.ErrnoException; 37 import android.util.Log; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.util.IndentingPrintWriter; 41 import com.android.modules.utils.build.SdkLevel; 42 import com.android.net.module.util.BpfMap; 43 import com.android.net.module.util.IBpfMap; 44 import com.android.net.module.util.InterfaceParams; 45 import com.android.net.module.util.TcUtils; 46 import com.android.net.module.util.bpf.ClatEgress4Key; 47 import com.android.net.module.util.bpf.ClatEgress4Value; 48 import com.android.net.module.util.bpf.ClatIngress6Key; 49 import com.android.net.module.util.bpf.ClatIngress6Value; 50 import com.android.net.module.util.bpf.CookieTagMapKey; 51 import com.android.net.module.util.bpf.CookieTagMapValue; 52 53 import java.io.FileDescriptor; 54 import java.io.IOException; 55 import java.net.Inet4Address; 56 import java.net.Inet6Address; 57 import java.net.InetAddress; 58 import java.nio.ByteBuffer; 59 import java.util.Objects; 60 61 /** 62 * This coordinator is responsible for providing clat relevant functionality. 63 * 64 * {@hide} 65 */ 66 public class ClatCoordinator { 67 private static final String TAG = ClatCoordinator.class.getSimpleName(); 68 69 // Sync from system/core/libcutils/include/private/android_filesystem_config.h 70 @VisibleForTesting 71 static final int AID_CLAT = 1029; 72 73 // Sync from external/android-clat/clatd.c 74 // 40 bytes IPv6 header - 20 bytes IPv4 header + 8 bytes fragment header. 75 @VisibleForTesting 76 static final int MTU_DELTA = 28; 77 @VisibleForTesting 78 static final int CLAT_MAX_MTU = 65536; 79 80 // This must match the interface prefix in clatd.c. 81 private static final String CLAT_PREFIX = "v4-"; 82 83 // For historical reasons, start with 192.0.0.4, and after that, use all subsequent addresses 84 // in 192.0.0.0/29 (RFC 7335). 85 @VisibleForTesting 86 static final String INIT_V4ADDR_STRING = "192.0.0.4"; 87 @VisibleForTesting 88 static final int INIT_V4ADDR_PREFIX_LEN = 29; 89 private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8"); 90 91 private static final int INVALID_IFINDEX = 0; 92 93 // For better code clarity when used for 'bool ingress' parameter. 94 @VisibleForTesting 95 static final boolean EGRESS = false; 96 @VisibleForTesting 97 static final boolean INGRESS = true; 98 99 // For better code clarity when used for 'bool ether' parameter. 100 static final boolean RAWIP = false; 101 static final boolean ETHER = true; 102 103 // The priority of clat hook - must be after tethering. 104 @VisibleForTesting 105 static final int PRIO_CLAT = 4; 106 107 private static final String COOKIE_TAG_MAP_PATH = 108 "/sys/fs/bpf/netd_shared/map_netd_cookie_tag_map"; 109 private static final String CLAT_EGRESS4_MAP_PATH = makeMapPath("egress4"); 110 private static final String CLAT_INGRESS6_MAP_PATH = makeMapPath("ingress6"); 111 makeMapPath(String which)112 private static String makeMapPath(String which) { 113 return "/sys/fs/bpf/net_shared/map_clatd_clat_" + which + "_map"; 114 } 115 116 private static final String CLAT_EGRESS4_RAWIP_PROG_PATH = 117 "/sys/fs/bpf/net_shared/prog_clatd_schedcls_egress4_clat_rawip"; 118 makeIngressProgPath(boolean ether)119 private static String makeIngressProgPath(boolean ether) { 120 return "/sys/fs/bpf/net_shared/prog_clatd_schedcls_ingress6_clat_" 121 + (ether ? "ether" : "rawip"); 122 } 123 124 @NonNull 125 private final INetd mNetd; 126 @NonNull 127 private final Dependencies mDeps; 128 @Nullable 129 private final IBpfMap<ClatIngress6Key, ClatIngress6Value> mIngressMap; 130 @Nullable 131 private final IBpfMap<ClatEgress4Key, ClatEgress4Value> mEgressMap; 132 @Nullable 133 private final IBpfMap<CookieTagMapKey, CookieTagMapValue> mCookieTagMap; 134 @Nullable 135 private ClatdTracker mClatdTracker = null; 136 137 /** 138 * Dependencies of ClatCoordinator which makes ConnectivityService injection 139 * in tests. 140 */ 141 @VisibleForTesting 142 public abstract static class Dependencies { 143 /** 144 * Get netd. 145 */ 146 @NonNull getNetd()147 public abstract INetd getNetd(); 148 149 /** 150 * @see ParcelFileDescriptor#adoptFd(int). 151 */ 152 @NonNull adoptFd(int fd)153 public ParcelFileDescriptor adoptFd(int fd) { 154 return ParcelFileDescriptor.adoptFd(fd); 155 } 156 157 /** 158 * Get interface index for a given interface. 159 */ getInterfaceIndex(String ifName)160 public int getInterfaceIndex(String ifName) { 161 final InterfaceParams params = InterfaceParams.getByName(ifName); 162 return params != null ? params.index : INVALID_IFINDEX; 163 } 164 165 /** 166 * Create tun interface for a given interface name. 167 */ createTunInterface(@onNull String tuniface)168 public int createTunInterface(@NonNull String tuniface) throws IOException { 169 return native_createTunInterface(tuniface); 170 } 171 172 /** 173 * Pick an IPv4 address for clat. 174 */ 175 @NonNull selectIpv4Address(@onNull String v4addr, int prefixlen)176 public String selectIpv4Address(@NonNull String v4addr, int prefixlen) 177 throws IOException { 178 return native_selectIpv4Address(v4addr, prefixlen); 179 } 180 181 /** 182 * Generate a checksum-neutral IID. 183 */ 184 @NonNull generateIpv6Address(@onNull String iface, @NonNull String v4, @NonNull String prefix64, int mark)185 public String generateIpv6Address(@NonNull String iface, @NonNull String v4, 186 @NonNull String prefix64, int mark) throws IOException { 187 return native_generateIpv6Address(iface, v4, prefix64, mark); 188 } 189 190 /** 191 * Detect MTU. 192 */ detectMtu(@onNull String platSubnet, int platSuffix, int mark)193 public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark) 194 throws IOException { 195 return native_detectMtu(platSubnet, platSuffix, mark); 196 } 197 198 /** 199 * Open packet socket. 200 */ openPacketSocket()201 public int openPacketSocket() throws IOException { 202 return native_openPacketSocket(); 203 } 204 205 /** 206 * Open IPv6 raw socket and set SO_MARK. 207 */ openRawSocket6(int mark)208 public int openRawSocket6(int mark) throws IOException { 209 return native_openRawSocket6(mark); 210 } 211 212 /** 213 * Add anycast setsockopt. 214 */ addAnycastSetsockopt(@onNull FileDescriptor sock, String v6, int ifindex)215 public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6, int ifindex) 216 throws IOException { 217 native_addAnycastSetsockopt(sock, v6, ifindex); 218 } 219 220 /** 221 * Configure packet socket. 222 */ configurePacketSocket(@onNull FileDescriptor sock, String v6, int ifindex)223 public void configurePacketSocket(@NonNull FileDescriptor sock, String v6, int ifindex) 224 throws IOException { 225 native_configurePacketSocket(sock, v6, ifindex); 226 } 227 228 /** 229 * Start clatd. 230 */ startClatd(@onNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6, @NonNull FileDescriptor writesock6, @NonNull String iface, @NonNull String pfx96, @NonNull String v4, @NonNull String v6)231 public int startClatd(@NonNull FileDescriptor tunfd, @NonNull FileDescriptor readsock6, 232 @NonNull FileDescriptor writesock6, @NonNull String iface, @NonNull String pfx96, 233 @NonNull String v4, @NonNull String v6) throws IOException { 234 return native_startClatd(tunfd, readsock6, writesock6, iface, pfx96, v4, v6); 235 } 236 237 /** 238 * Stop clatd. 239 */ stopClatd(String iface, String pfx96, String v4, String v6, int pid)240 public void stopClatd(String iface, String pfx96, String v4, String v6, int pid) 241 throws IOException { 242 native_stopClatd(iface, pfx96, v4, v6, pid); 243 } 244 245 /** 246 * Get socket cookie. 247 */ getSocketCookie(@onNull FileDescriptor sock)248 public long getSocketCookie(@NonNull FileDescriptor sock) throws IOException { 249 return native_getSocketCookie(sock); 250 } 251 252 /** Get ingress6 BPF map. */ 253 @Nullable getBpfIngress6Map()254 public IBpfMap<ClatIngress6Key, ClatIngress6Value> getBpfIngress6Map() { 255 // Pre-T devices don't use ClatCoordinator to access clat map. Since Nat464Xlat 256 // initializes a ClatCoordinator object to avoid redundant null pointer check 257 // while using, ignore the BPF map initialization on pre-T devices. 258 // TODO: probably don't initialize ClatCoordinator object on pre-T devices. 259 if (!SdkLevel.isAtLeastT()) return null; 260 try { 261 return new BpfMap<>(CLAT_INGRESS6_MAP_PATH, 262 BpfMap.BPF_F_RDWR, ClatIngress6Key.class, ClatIngress6Value.class); 263 } catch (ErrnoException e) { 264 Log.e(TAG, "Cannot create ingress6 map: " + e); 265 return null; 266 } 267 } 268 269 /** Get egress4 BPF map. */ 270 @Nullable getBpfEgress4Map()271 public IBpfMap<ClatEgress4Key, ClatEgress4Value> getBpfEgress4Map() { 272 // Pre-T devices don't use ClatCoordinator to access clat map. Since Nat464Xlat 273 // initializes a ClatCoordinator object to avoid redundant null pointer check 274 // while using, ignore the BPF map initialization on pre-T devices. 275 // TODO: probably don't initialize ClatCoordinator object on pre-T devices. 276 if (!SdkLevel.isAtLeastT()) return null; 277 try { 278 return new BpfMap<>(CLAT_EGRESS4_MAP_PATH, 279 BpfMap.BPF_F_RDWR, ClatEgress4Key.class, ClatEgress4Value.class); 280 } catch (ErrnoException e) { 281 Log.e(TAG, "Cannot create egress4 map: " + e); 282 return null; 283 } 284 } 285 286 /** Get cookie tag map */ 287 @Nullable getBpfCookieTagMap()288 public IBpfMap<CookieTagMapKey, CookieTagMapValue> getBpfCookieTagMap() { 289 // Pre-T devices don't use ClatCoordinator to access clat map. Since Nat464Xlat 290 // initializes a ClatCoordinator object to avoid redundant null pointer check 291 // while using, ignore the BPF map initialization on pre-T devices. 292 // TODO: probably don't initialize ClatCoordinator object on pre-T devices. 293 if (!SdkLevel.isAtLeastT()) return null; 294 try { 295 return new BpfMap<>(COOKIE_TAG_MAP_PATH, 296 BpfMap.BPF_F_RDWR, CookieTagMapKey.class, CookieTagMapValue.class); 297 } catch (ErrnoException e) { 298 Log.wtf(TAG, "Cannot open cookie tag map: " + e); 299 return null; 300 } 301 } 302 303 /** Checks if the network interface uses an ethernet L2 header. */ isEthernet(String iface)304 public boolean isEthernet(String iface) throws IOException { 305 return TcUtils.isEthernet(iface); 306 } 307 308 /** Add a clsact qdisc. */ tcQdiscAddDevClsact(int ifIndex)309 public void tcQdiscAddDevClsact(int ifIndex) throws IOException { 310 TcUtils.tcQdiscAddDevClsact(ifIndex); 311 } 312 313 /** Attach a tc bpf filter. */ tcFilterAddDevBpf(int ifIndex, boolean ingress, short prio, short proto, String bpfProgPath)314 public void tcFilterAddDevBpf(int ifIndex, boolean ingress, short prio, short proto, 315 String bpfProgPath) throws IOException { 316 TcUtils.tcFilterAddDevBpf(ifIndex, ingress, prio, proto, bpfProgPath); 317 } 318 319 /** Delete a tc filter. */ tcFilterDelDev(int ifIndex, boolean ingress, short prio, short proto)320 public void tcFilterDelDev(int ifIndex, boolean ingress, short prio, short proto) 321 throws IOException { 322 TcUtils.tcFilterDelDev(ifIndex, ingress, prio, proto); 323 } 324 } 325 326 @VisibleForTesting 327 static class ClatdTracker { 328 @NonNull 329 public final String iface; 330 public final int ifIndex; 331 @NonNull 332 public final String v4iface; 333 public final int v4ifIndex; 334 @NonNull 335 public final Inet4Address v4; 336 @NonNull 337 public final Inet6Address v6; 338 @NonNull 339 public final Inet6Address pfx96; 340 public final int pid; 341 public final long cookie; 342 ClatdTracker(@onNull String iface, int ifIndex, @NonNull String v4iface, int v4ifIndex, @NonNull Inet4Address v4, @NonNull Inet6Address v6, @NonNull Inet6Address pfx96, int pid, long cookie)343 ClatdTracker(@NonNull String iface, int ifIndex, @NonNull String v4iface, 344 int v4ifIndex, @NonNull Inet4Address v4, @NonNull Inet6Address v6, 345 @NonNull Inet6Address pfx96, int pid, long cookie) { 346 this.iface = iface; 347 this.ifIndex = ifIndex; 348 this.v4iface = v4iface; 349 this.v4ifIndex = v4ifIndex; 350 this.v4 = v4; 351 this.v6 = v6; 352 this.pfx96 = pfx96; 353 this.pid = pid; 354 this.cookie = cookie; 355 } 356 357 @Override equals(Object o)358 public boolean equals(Object o) { 359 if (!(o instanceof ClatdTracker)) return false; 360 ClatdTracker that = (ClatdTracker) o; 361 return Objects.equals(this.iface, that.iface) 362 && this.ifIndex == that.ifIndex 363 && Objects.equals(this.v4iface, that.v4iface) 364 && this.v4ifIndex == that.v4ifIndex 365 && Objects.equals(this.v4, that.v4) 366 && Objects.equals(this.v6, that.v6) 367 && Objects.equals(this.pfx96, that.pfx96) 368 && this.pid == that.pid 369 && this.cookie == that.cookie; 370 } 371 372 @Override toString()373 public String toString() { 374 return "iface: " + iface 375 + " (" + ifIndex + ")" 376 + ", v4iface: " + v4iface 377 + " (" + v4ifIndex + ")" 378 + ", v4: " + v4 379 + ", v6: " + v6 380 + ", pfx96: " + pfx96 381 + ", pid: " + pid 382 + ", cookie: " + cookie; 383 } 384 }; 385 386 @VisibleForTesting getFwmark(int netId)387 static int getFwmark(int netId) { 388 // See union Fwmark in system/netd/include/Fwmark.h 389 return (netId & 0xffff) 390 | 0x1 << 16 // explicitlySelected: true 391 | 0x1 << 17 // protectedFromVpn: true 392 | ((PERMISSION_NETWORK | PERMISSION_SYSTEM) & 0x3) << 18; // 2 permission bits = 3 393 } 394 395 @VisibleForTesting adjustMtu(int mtu)396 static int adjustMtu(int mtu) { 397 // clamp to minimum ipv6 mtu - this probably cannot ever trigger 398 if (mtu < IPV6_MIN_MTU) mtu = IPV6_MIN_MTU; 399 // clamp to buffer size 400 if (mtu > CLAT_MAX_MTU) mtu = CLAT_MAX_MTU; 401 // decrease by ipv6(40) + ipv6 fragmentation header(8) vs ipv4(20) overhead of 28 bytes 402 mtu -= MTU_DELTA; 403 404 return mtu; 405 } 406 ClatCoordinator(@onNull Dependencies deps)407 public ClatCoordinator(@NonNull Dependencies deps) { 408 mDeps = deps; 409 mNetd = mDeps.getNetd(); 410 mIngressMap = mDeps.getBpfIngress6Map(); 411 mEgressMap = mDeps.getBpfEgress4Map(); 412 mCookieTagMap = mDeps.getBpfCookieTagMap(); 413 } 414 maybeStartBpf(final ClatdTracker tracker)415 private void maybeStartBpf(final ClatdTracker tracker) { 416 if (mIngressMap == null || mEgressMap == null) return; 417 418 final boolean isEthernet; 419 try { 420 isEthernet = mDeps.isEthernet(tracker.iface); 421 } catch (IOException e) { 422 Log.e(TAG, "Fail to call isEthernet for interface " + tracker.iface); 423 return; 424 } 425 426 final ClatEgress4Key txKey = new ClatEgress4Key(tracker.v4ifIndex, tracker.v4); 427 final ClatEgress4Value txValue = new ClatEgress4Value(tracker.ifIndex, tracker.v6, 428 tracker.pfx96, (short) (isEthernet ? 1 /* ETHER */ : 0 /* RAWIP */)); 429 try { 430 mEgressMap.insertEntry(txKey, txValue); 431 } catch (ErrnoException | IllegalStateException e) { 432 Log.e(TAG, "Could not insert entry (" + txKey + ", " + txValue + ") on egress map: " 433 + e); 434 return; 435 } 436 437 final ClatIngress6Key rxKey = new ClatIngress6Key(tracker.ifIndex, tracker.pfx96, 438 tracker.v6); 439 final ClatIngress6Value rxValue = new ClatIngress6Value(tracker.v4ifIndex, 440 tracker.v4); 441 try { 442 mIngressMap.insertEntry(rxKey, rxValue); 443 } catch (ErrnoException | IllegalStateException e) { 444 Log.e(TAG, "Could not insert entry (" + rxKey + ", " + rxValue + ") ingress map: " 445 + e); 446 try { 447 mEgressMap.deleteEntry(txKey); 448 } catch (ErrnoException | IllegalStateException e2) { 449 Log.e(TAG, "Could not delete entry (" + txKey + ") from egress map: " + e2); 450 } 451 return; 452 } 453 454 // Usually the clsact will be added in netd RouteController::addInterfaceToPhysicalNetwork. 455 // But clat is started before the v4- interface is added to the network. The clat startup 456 // have to add clsact of v4- tun interface first for adding bpf filter in maybeStartBpf. 457 try { 458 // tc qdisc add dev .. clsact 459 mDeps.tcQdiscAddDevClsact(tracker.v4ifIndex); 460 } catch (IOException e) { 461 Log.e(TAG, "tc qdisc add dev (" + tracker.v4ifIndex + "[" + tracker.v4iface 462 + "]) failure: " + e); 463 try { 464 mEgressMap.deleteEntry(txKey); 465 } catch (ErrnoException | IllegalStateException e2) { 466 Log.e(TAG, "Could not delete entry (" + txKey + ") from egress map: " + e2); 467 } 468 try { 469 mIngressMap.deleteEntry(rxKey); 470 } catch (ErrnoException | IllegalStateException e3) { 471 Log.e(TAG, "Could not delete entry (" + rxKey + ") from ingress map: " + e3); 472 } 473 return; 474 } 475 476 // This program will be attached to the v4-* interface which is a TUN and thus always rawip. 477 try { 478 // tc filter add dev .. egress prio 4 protocol ip bpf object-pinned /sys/fs/bpf/... 479 // direct-action 480 mDeps.tcFilterAddDevBpf(tracker.v4ifIndex, EGRESS, (short) PRIO_CLAT, (short) ETH_P_IP, 481 CLAT_EGRESS4_RAWIP_PROG_PATH); 482 } catch (IOException e) { 483 Log.e(TAG, "tc filter add dev (" + tracker.v4ifIndex + "[" + tracker.v4iface 484 + "]) egress prio PRIO_CLAT protocol ip failure: " + e); 485 486 // The v4- interface clsact is not deleted for unwinding error because once it is 487 // created with interface addition, the lifetime is till interface deletion. Moreover, 488 // the clsact has no clat filter now. It should not break anything. 489 490 try { 491 mEgressMap.deleteEntry(txKey); 492 } catch (ErrnoException | IllegalStateException e2) { 493 Log.e(TAG, "Could not delete entry (" + txKey + ") from egress map: " + e2); 494 } 495 try { 496 mIngressMap.deleteEntry(rxKey); 497 } catch (ErrnoException | IllegalStateException e3) { 498 Log.e(TAG, "Could not delete entry (" + rxKey + ") from ingress map: " + e3); 499 } 500 return; 501 } 502 503 try { 504 // tc filter add dev .. ingress prio 4 protocol ipv6 bpf object-pinned /sys/fs/bpf/... 505 // direct-action 506 mDeps.tcFilterAddDevBpf(tracker.ifIndex, INGRESS, (short) PRIO_CLAT, 507 (short) ETH_P_IPV6, makeIngressProgPath(isEthernet)); 508 } catch (IOException e) { 509 Log.e(TAG, "tc filter add dev (" + tracker.ifIndex + "[" + tracker.iface 510 + "]) ingress prio PRIO_CLAT protocol ipv6 failure: " + e); 511 512 // The v4- interface clsact is not deleted. See the reason in the error unwinding code 513 // of the egress filter attaching of v4- tun interface. 514 515 try { 516 mDeps.tcFilterDelDev(tracker.v4ifIndex, EGRESS, (short) PRIO_CLAT, 517 (short) ETH_P_IP); 518 } catch (IOException e2) { 519 Log.e(TAG, "tc filter del dev (" + tracker.v4ifIndex + "[" + tracker.v4iface 520 + "]) egress prio PRIO_CLAT protocol ip failure: " + e2); 521 } 522 try { 523 mEgressMap.deleteEntry(txKey); 524 } catch (ErrnoException | IllegalStateException e3) { 525 Log.e(TAG, "Could not delete entry (" + txKey + ") from egress map: " + e3); 526 } 527 try { 528 mIngressMap.deleteEntry(rxKey); 529 } catch (ErrnoException | IllegalStateException e4) { 530 Log.e(TAG, "Could not delete entry (" + rxKey + ") from ingress map: " + e4); 531 } 532 return; 533 } 534 } 535 maybeCleanUp(ParcelFileDescriptor tunFd, ParcelFileDescriptor readSock6, ParcelFileDescriptor writeSock6)536 private void maybeCleanUp(ParcelFileDescriptor tunFd, ParcelFileDescriptor readSock6, 537 ParcelFileDescriptor writeSock6) { 538 if (tunFd != null) { 539 try { 540 tunFd.close(); 541 } catch (IOException e) { 542 Log.e(TAG, "Fail to close tun file descriptor " + e); 543 } 544 } 545 if (readSock6 != null) { 546 try { 547 readSock6.close(); 548 } catch (IOException e) { 549 Log.e(TAG, "Fail to close read socket " + e); 550 } 551 } 552 if (writeSock6 != null) { 553 try { 554 writeSock6.close(); 555 } catch (IOException e) { 556 Log.e(TAG, "Fail to close write socket " + e); 557 } 558 } 559 } 560 tagSocketAsClat(long cookie)561 private void tagSocketAsClat(long cookie) throws IOException { 562 if (mCookieTagMap == null) { 563 throw new IOException("Cookie tag map is not initialized"); 564 } 565 566 // Tag raw socket with uid AID_CLAT and set tag as zero because tag is unused in bpf 567 // program for counting data usage in netd.c. Tagging socket is used to avoid counting 568 // duplicated clat traffic in bpf stat. 569 final CookieTagMapKey key = new CookieTagMapKey(cookie); 570 final CookieTagMapValue value = new CookieTagMapValue(AID_CLAT, 0 /* tag, unused */); 571 try { 572 mCookieTagMap.insertEntry(key, value); 573 } catch (ErrnoException | IllegalStateException e) { 574 throw new IOException("Could not insert entry (" + key + ", " + value 575 + ") on cookie tag map: " + e); 576 } 577 Log.i(TAG, "tag socket cookie " + cookie); 578 } 579 untagSocket(long cookie)580 private void untagSocket(long cookie) throws IOException { 581 if (mCookieTagMap == null) { 582 throw new IOException("Cookie tag map is not initialized"); 583 } 584 585 // The reason that deleting entry from cookie tag map directly is that the tag socket 586 // destroy listener only monitors on group INET_TCP, INET_UDP, INET6_TCP, INET6_UDP. 587 // The other socket types, ex: raw, are not able to be removed automatically by the 588 // listener. See TrafficController::makeSkDestroyListener. 589 final CookieTagMapKey key = new CookieTagMapKey(cookie); 590 try { 591 mCookieTagMap.deleteEntry(key); 592 } catch (ErrnoException | IllegalStateException e) { 593 throw new IOException("Could not delete entry (" + key + ") on cookie tag map: " + e); 594 } 595 Log.i(TAG, "untag socket cookie " + cookie); 596 } 597 isStarted()598 private boolean isStarted() { 599 return mClatdTracker != null; 600 } 601 602 /** 603 * Start clatd for a given interface and NAT64 prefix. 604 */ clatStart(final String iface, final int netId, @NonNull final IpPrefix nat64Prefix)605 public String clatStart(final String iface, final int netId, 606 @NonNull final IpPrefix nat64Prefix) 607 throws IOException { 608 if (isStarted()) { 609 throw new IOException("Clatd is already running on " + mClatdTracker.iface 610 + " (pid " + mClatdTracker.pid + ")"); 611 } 612 if (nat64Prefix.getPrefixLength() != 96) { 613 throw new IOException("Prefix must be 96 bits long: " + nat64Prefix); 614 } 615 616 // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 .. 617 final String v4Str; 618 try { 619 v4Str = mDeps.selectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN); 620 } catch (IOException e) { 621 throw new IOException("no IPv4 addresses were available for clat: " + e); 622 } 623 624 final Inet4Address v4; 625 try { 626 v4 = (Inet4Address) InetAddresses.parseNumericAddress(v4Str); 627 } catch (ClassCastException | IllegalArgumentException | NullPointerException e) { 628 throw new IOException("Invalid IPv4 address " + v4Str); 629 } 630 631 // [2] Generate a checksum-neutral IID. 632 final Integer fwmark = getFwmark(netId); 633 final String pfx96Str = nat64Prefix.getAddress().getHostAddress(); 634 final String v6Str; 635 try { 636 v6Str = mDeps.generateIpv6Address(iface, v4Str, pfx96Str, fwmark); 637 } catch (IOException e) { 638 throw new IOException("no IPv6 addresses were available for clat: " + e); 639 } 640 641 final Inet6Address pfx96 = (Inet6Address) nat64Prefix.getAddress(); 642 final Inet6Address v6; 643 try { 644 v6 = (Inet6Address) InetAddresses.parseNumericAddress(v6Str); 645 } catch (ClassCastException | IllegalArgumentException | NullPointerException e) { 646 throw new IOException("Invalid IPv6 address " + v6Str); 647 } 648 649 // [3] Open, configure and bring up the tun interface. 650 // Create the v4-... tun interface. 651 652 // Initialize all required file descriptors with null pointer. This makes the following 653 // error handling easier. Simply always call #maybeCleanUp for closing file descriptors, 654 // if any valid ones, in error handling. 655 ParcelFileDescriptor tunFd = null; 656 ParcelFileDescriptor readSock6 = null; 657 ParcelFileDescriptor writeSock6 = null; 658 659 final String tunIface = CLAT_PREFIX + iface; 660 try { 661 tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface)); 662 } catch (IOException e) { 663 throw new IOException("Create tun interface " + tunIface + " failed: " + e); 664 } 665 666 final int tunIfIndex = mDeps.getInterfaceIndex(tunIface); 667 if (tunIfIndex == INVALID_IFINDEX) { 668 maybeCleanUp(tunFd, readSock6, writeSock6); 669 throw new IOException("Fail to get interface index for interface " + tunIface); 670 } 671 672 // disable IPv6 on it - failing to do so is not a critical error 673 try { 674 mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */); 675 } catch (RemoteException | ServiceSpecificException e) { 676 Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e); 677 } 678 679 // Detect ipv4 mtu. 680 final int detectedMtu; 681 try { 682 detectedMtu = mDeps.detectMtu(pfx96Str, 683 ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark); 684 } catch (IOException e) { 685 maybeCleanUp(tunFd, readSock6, writeSock6); 686 throw new IOException("Detect MTU on " + tunIface + " failed: " + e); 687 } 688 final int mtu = adjustMtu(detectedMtu); 689 Log.i(TAG, "ipv4 mtu is " + mtu); 690 691 // Config tun interface mtu, address and bring up. 692 try { 693 mNetd.interfaceSetMtu(tunIface, mtu); 694 } catch (RemoteException | ServiceSpecificException e) { 695 maybeCleanUp(tunFd, readSock6, writeSock6); 696 throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e); 697 } 698 final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel(); 699 ifConfig.ifName = tunIface; 700 ifConfig.ipv4Addr = v4Str; 701 ifConfig.prefixLength = 32; 702 ifConfig.hwAddr = ""; 703 ifConfig.flags = new String[] {IF_STATE_UP}; 704 try { 705 mNetd.interfaceSetCfg(ifConfig); 706 } catch (RemoteException | ServiceSpecificException e) { 707 maybeCleanUp(tunFd, readSock6, writeSock6); 708 throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/" 709 + ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e); 710 } 711 712 // [4] Open and configure local 464xlat read/write sockets. 713 // Opens a packet socket to receive IPv6 packets in clatd. 714 try { 715 // Use a JNI call to get native file descriptor instead of Os.socket() because we would 716 // like to use ParcelFileDescriptor to manage file descriptor. But ctor 717 // ParcelFileDescriptor(FileDescriptor fd) is a @hide function. Need to use native file 718 // descriptor to initialize ParcelFileDescriptor object instead. 719 readSock6 = mDeps.adoptFd(mDeps.openPacketSocket()); 720 } catch (IOException e) { 721 maybeCleanUp(tunFd, readSock6, writeSock6); 722 throw new IOException("Open packet socket failed: " + e); 723 } 724 725 // Opens a raw socket with a given fwmark to send IPv6 packets in clatd. 726 try { 727 // Use a JNI call to get native file descriptor instead of Os.socket(). See above 728 // reason why we use jniOpenPacketSocket6(). 729 writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark)); 730 } catch (IOException e) { 731 maybeCleanUp(tunFd, readSock6, writeSock6); 732 throw new IOException("Open raw socket failed: " + e); 733 } 734 735 final int ifIndex = mDeps.getInterfaceIndex(iface); 736 if (ifIndex == INVALID_IFINDEX) { 737 maybeCleanUp(tunFd, readSock6, writeSock6); 738 throw new IOException("Fail to get interface index for interface " + iface); 739 } 740 741 // Start translating packets to the new prefix. 742 try { 743 mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6Str, ifIndex); 744 } catch (IOException e) { 745 maybeCleanUp(tunFd, readSock6, writeSock6); 746 throw new IOException("add anycast sockopt failed: " + e); 747 } 748 749 // Tag socket as AID_CLAT to avoid duplicated CLAT data usage accounting. 750 final long cookie; 751 try { 752 cookie = mDeps.getSocketCookie(writeSock6.getFileDescriptor()); 753 tagSocketAsClat(cookie); 754 } catch (IOException e) { 755 maybeCleanUp(tunFd, readSock6, writeSock6); 756 throw new IOException("tag raw socket failed: " + e); 757 } 758 759 // Update our packet socket filter to reflect the new 464xlat IP address. 760 try { 761 mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6Str, ifIndex); 762 } catch (IOException e) { 763 try { 764 untagSocket(cookie); 765 } catch (IOException e2) { 766 Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2); 767 } 768 maybeCleanUp(tunFd, readSock6, writeSock6); 769 throw new IOException("configure packet socket failed: " + e); 770 } 771 772 // [5] Start clatd. 773 final int pid; 774 try { 775 pid = mDeps.startClatd(tunFd.getFileDescriptor(), readSock6.getFileDescriptor(), 776 writeSock6.getFileDescriptor(), iface, pfx96Str, v4Str, v6Str); 777 } catch (IOException e) { 778 try { 779 untagSocket(cookie); 780 } catch (IOException e2) { 781 Log.e(TAG, "untagSocket cookie " + cookie + " failed: " + e2); 782 } 783 throw new IOException("Error start clatd on " + iface + ": " + e); 784 } finally { 785 // The file descriptors have been duplicated (dup2) to clatd in native_startClatd(). 786 // Close these file descriptor stubs which are unused anymore. 787 maybeCleanUp(tunFd, readSock6, writeSock6); 788 } 789 790 // [6] Initialize and store clatd tracker object. 791 mClatdTracker = new ClatdTracker(iface, ifIndex, tunIface, tunIfIndex, v4, v6, pfx96, 792 pid, cookie); 793 794 // [7] Start BPF 795 maybeStartBpf(mClatdTracker); 796 797 return v6Str; 798 } 799 maybeStopBpf(final ClatdTracker tracker)800 private void maybeStopBpf(final ClatdTracker tracker) { 801 if (mIngressMap == null || mEgressMap == null) return; 802 803 try { 804 mDeps.tcFilterDelDev(tracker.ifIndex, INGRESS, (short) PRIO_CLAT, (short) ETH_P_IPV6); 805 } catch (IOException e) { 806 Log.e(TAG, "tc filter del dev (" + tracker.ifIndex + "[" + tracker.iface 807 + "]) ingress prio PRIO_CLAT protocol ipv6 failure: " + e); 808 } 809 810 try { 811 mDeps.tcFilterDelDev(tracker.v4ifIndex, EGRESS, (short) PRIO_CLAT, (short) ETH_P_IP); 812 } catch (IOException e) { 813 Log.e(TAG, "tc filter del dev (" + tracker.v4ifIndex + "[" + tracker.v4iface 814 + "]) egress prio PRIO_CLAT protocol ip failure: " + e); 815 } 816 817 // We cleanup the maps last, so scanning through them can be used to 818 // determine what still needs cleanup. 819 820 final ClatEgress4Key txKey = new ClatEgress4Key(tracker.v4ifIndex, tracker.v4); 821 try { 822 mEgressMap.deleteEntry(txKey); 823 } catch (ErrnoException | IllegalStateException e) { 824 Log.e(TAG, "Could not delete entry (" + txKey + "): " + e); 825 } 826 827 final ClatIngress6Key rxKey = new ClatIngress6Key(tracker.ifIndex, tracker.pfx96, 828 tracker.v6); 829 try { 830 mIngressMap.deleteEntry(rxKey); 831 } catch (ErrnoException | IllegalStateException e) { 832 Log.e(TAG, "Could not delete entry (" + rxKey + "): " + e); 833 } 834 } 835 836 /** 837 * Stop clatd 838 */ clatStop()839 public void clatStop() throws IOException { 840 if (!isStarted()) { 841 throw new IOException("Clatd has not started"); 842 } 843 Log.i(TAG, "Stopping clatd pid=" + mClatdTracker.pid + " on " + mClatdTracker.iface); 844 845 maybeStopBpf(mClatdTracker); 846 mDeps.stopClatd(mClatdTracker.iface, mClatdTracker.pfx96.getHostAddress(), 847 mClatdTracker.v4.getHostAddress(), mClatdTracker.v6.getHostAddress(), 848 mClatdTracker.pid); 849 untagSocket(mClatdTracker.cookie); 850 851 Log.i(TAG, "clatd on " + mClatdTracker.iface + " stopped"); 852 mClatdTracker = null; 853 } 854 dumpBpfIngress(@onNull IndentingPrintWriter pw)855 private void dumpBpfIngress(@NonNull IndentingPrintWriter pw) { 856 if (mIngressMap == null) { 857 pw.println("No BPF ingress6 map"); 858 return; 859 } 860 861 try { 862 if (mIngressMap.isEmpty()) { 863 pw.println("<empty>"); 864 } 865 pw.println("BPF ingress map: iif nat64Prefix v6Addr -> v4Addr oif"); 866 pw.increaseIndent(); 867 mIngressMap.forEach((k, v) -> { 868 // TODO: print interface name 869 pw.println(String.format("%d %s/96 %s -> %s %d", k.iif, k.pfx96, k.local6, 870 v.local4, v.oif)); 871 }); 872 pw.decreaseIndent(); 873 } catch (ErrnoException e) { 874 pw.println("Error dumping BPF ingress6 map: " + e); 875 } 876 } 877 dumpBpfEgress(@onNull IndentingPrintWriter pw)878 private void dumpBpfEgress(@NonNull IndentingPrintWriter pw) { 879 if (mEgressMap == null) { 880 pw.println("No BPF egress4 map"); 881 return; 882 } 883 884 try { 885 if (mEgressMap.isEmpty()) { 886 pw.println("<empty>"); 887 } 888 pw.println("BPF egress map: iif v4Addr -> v6Addr nat64Prefix oif"); 889 pw.increaseIndent(); 890 mEgressMap.forEach((k, v) -> { 891 // TODO: print interface name 892 pw.println(String.format("%d %s -> %s %s/96 %d %s", k.iif, k.local4, v.local6, 893 v.pfx96, v.oif, v.oifIsEthernet != 0 ? "ether" : "rawip")); 894 }); 895 pw.decreaseIndent(); 896 } catch (ErrnoException e) { 897 pw.println("Error dumping BPF egress4 map: " + e); 898 } 899 } 900 901 /** 902 * Dump the coordinator information. 903 * 904 * @param pw print writer. 905 */ dump(@onNull IndentingPrintWriter pw)906 public void dump(@NonNull IndentingPrintWriter pw) { 907 // TODO: move map dump to a global place to avoid duplicate dump while there are two or 908 // more IPv6 only networks. 909 if (isStarted()) { 910 pw.println("CLAT tracker: " + mClatdTracker); 911 pw.println("Forwarding rules:"); 912 pw.increaseIndent(); 913 dumpBpfIngress(pw); 914 dumpBpfEgress(pw); 915 pw.decreaseIndent(); 916 } else { 917 pw.println("<not started>"); 918 } 919 pw.println(); 920 } 921 922 /** 923 * Get clatd tracker. For test only. 924 */ 925 @VisibleForTesting 926 @Nullable getClatdTrackerForTesting()927 ClatdTracker getClatdTrackerForTesting() { 928 return mClatdTracker; 929 } 930 native_selectIpv4Address(String v4addr, int prefixlen)931 private static native String native_selectIpv4Address(String v4addr, int prefixlen) 932 throws IOException; native_generateIpv6Address(String iface, String v4, String prefix64, int mark)933 private static native String native_generateIpv6Address(String iface, String v4, 934 String prefix64, int mark) throws IOException; native_createTunInterface(String tuniface)935 private static native int native_createTunInterface(String tuniface) throws IOException; native_detectMtu(String platSubnet, int platSuffix, int mark)936 private static native int native_detectMtu(String platSubnet, int platSuffix, int mark) 937 throws IOException; native_openPacketSocket()938 private static native int native_openPacketSocket() throws IOException; native_openRawSocket6(int mark)939 private static native int native_openRawSocket6(int mark) throws IOException; native_addAnycastSetsockopt(FileDescriptor sock, String v6, int ifindex)940 private static native void native_addAnycastSetsockopt(FileDescriptor sock, String v6, 941 int ifindex) throws IOException; native_configurePacketSocket(FileDescriptor sock, String v6, int ifindex)942 private static native void native_configurePacketSocket(FileDescriptor sock, String v6, 943 int ifindex) throws IOException; native_startClatd(FileDescriptor tunfd, FileDescriptor readsock6, FileDescriptor writesock6, String iface, String pfx96, String v4, String v6)944 private static native int native_startClatd(FileDescriptor tunfd, FileDescriptor readsock6, 945 FileDescriptor writesock6, String iface, String pfx96, String v4, String v6) 946 throws IOException; native_stopClatd(String iface, String pfx96, String v4, String v6, int pid)947 private static native void native_stopClatd(String iface, String pfx96, String v4, String v6, 948 int pid) throws IOException; native_getSocketCookie(FileDescriptor sock)949 private static native long native_getSocketCookie(FileDescriptor sock) throws IOException; 950 } 951