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