• 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 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