1 /*
2  * Copyright (C) 2023 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.thread;
18 
19 import static android.system.OsConstants.AF_INET6;
20 import static android.system.OsConstants.EADDRINUSE;
21 import static android.system.OsConstants.IFF_MULTICAST;
22 import static android.system.OsConstants.IFF_NOARP;
23 import static android.system.OsConstants.NETLINK_ROUTE;
24 
25 import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK;
26 import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_AF_SPEC;
27 import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_INET6_ADDR_GEN_MODE;
28 import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IN6_ADDR_GEN_MODE_NONE;
29 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
30 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
31 
32 import android.annotation.Nullable;
33 import android.net.IpPrefix;
34 import android.net.LinkAddress;
35 import android.net.LinkProperties;
36 import android.net.RouteInfo;
37 import android.os.ParcelFileDescriptor;
38 import android.os.SystemClock;
39 import android.system.ErrnoException;
40 import android.system.Os;
41 import android.util.Log;
42 
43 import com.android.net.module.util.HexDump;
44 import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
45 import com.android.net.module.util.netlink.NetlinkUtils;
46 import com.android.net.module.util.netlink.StructIfinfoMsg;
47 import com.android.net.module.util.netlink.StructNlAttr;
48 import com.android.net.module.util.netlink.StructNlMsgHdr;
49 import com.android.server.thread.openthread.Ipv6AddressInfo;
50 import com.android.server.thread.openthread.OnMeshPrefixConfig;
51 
52 import java.io.IOException;
53 import java.net.Inet6Address;
54 import java.net.InetAddress;
55 import java.net.InetSocketAddress;
56 import java.net.MulticastSocket;
57 import java.net.NetworkInterface;
58 import java.net.SocketException;
59 import java.net.UnknownHostException;
60 import java.nio.ByteBuffer;
61 import java.nio.ByteOrder;
62 import java.util.ArrayList;
63 import java.util.List;
64 
65 /** Controller for virtual/tunnel network interfaces. */
66 public class TunInterfaceController {
67     private static final String TAG = "TunIfController";
68     private static final boolean DBG = false;
69     private static final long INFINITE_LIFETIME = 0xffffffffL;
70     static final int MTU = 1280;
71 
72     static {
73         System.loadLibrary("service-thread-jni");
74     }
75 
76     private final String mIfName;
77     private final LinkProperties mLinkProperties = new LinkProperties();
78     private final MulticastSocket mMulticastSocket; // For join group and leave group
79     private final List<InetAddress> mMulticastAddresses = new ArrayList<>();
80     private final List<RouteInfo> mNetDataPrefixes = new ArrayList<>();
81 
82     private ParcelFileDescriptor mParcelTunFd;
83     private NetworkInterface mNetworkInterface;
84 
85     /** Creates a new {@link TunInterfaceController} instance for given interface. */
TunInterfaceController(String interfaceName)86     public TunInterfaceController(String interfaceName) {
87         mIfName = interfaceName;
88         mLinkProperties.setInterfaceName(mIfName);
89         mLinkProperties.setMtu(MTU);
90         mMulticastSocket = createMulticastSocket();
91     }
92 
93     /** Returns link properties of the Thread TUN interface. */
getLinkProperties()94     public LinkProperties getLinkProperties() {
95         return mLinkProperties;
96     }
97 
98     /**
99      * Creates the tunnel interface.
100      *
101      * @throws IOException if failed to create the interface
102      */
createTunInterface()103     public void createTunInterface() throws IOException {
104         mParcelTunFd = ParcelFileDescriptor.adoptFd(nativeCreateTunInterface(mIfName, MTU));
105         try {
106             mNetworkInterface = NetworkInterface.getByName(mIfName);
107         } catch (SocketException e) {
108             throw new IOException("Failed to get NetworkInterface", e);
109         }
110 
111         setAddrGenModeToNone();
112     }
113 
destroyTunInterface()114     public void destroyTunInterface() {
115         try {
116             mParcelTunFd.close();
117         } catch (IOException e) {
118             // Should never fail
119         }
120         mParcelTunFd = null;
121         mNetworkInterface = null;
122     }
123 
124     /** Returns the FD of the tunnel interface. */
125     @Nullable
getTunFd()126     public ParcelFileDescriptor getTunFd() {
127         return mParcelTunFd;
128     }
129 
nativeCreateTunInterface(String interfaceName, int mtu)130     private native int nativeCreateTunInterface(String interfaceName, int mtu) throws IOException;
131 
132     /** Sets the interface up or down according to {@code isUp}. */
setInterfaceUp(boolean isUp)133     public void setInterfaceUp(boolean isUp) throws IOException {
134         if (!isUp) {
135             for (LinkAddress address : mLinkProperties.getAllLinkAddresses()) {
136                 removeAddress(address);
137             }
138             for (RouteInfo route : mLinkProperties.getAllRoutes()) {
139                 mLinkProperties.removeRoute(route);
140             }
141             mNetDataPrefixes.clear();
142         }
143         nativeSetInterfaceUp(mIfName, isUp);
144     }
145 
nativeSetInterfaceUp(String interfaceName, boolean isUp)146     private native void nativeSetInterfaceUp(String interfaceName, boolean isUp) throws IOException;
147 
148     /** Adds a new address to the interface. */
addAddress(LinkAddress address)149     public void addAddress(LinkAddress address) {
150         Log.d(TAG, "Adding address " + address + " with flags: " + address.getFlags());
151 
152         long preferredLifetimeSeconds;
153         long validLifetimeSeconds;
154 
155         if (address.getDeprecationTime() == LinkAddress.LIFETIME_PERMANENT
156                 || address.getDeprecationTime() == LinkAddress.LIFETIME_UNKNOWN) {
157             preferredLifetimeSeconds = INFINITE_LIFETIME;
158         } else {
159             preferredLifetimeSeconds =
160                     Math.max(
161                             (address.getDeprecationTime() - SystemClock.elapsedRealtime()) / 1000L,
162                             0L);
163         }
164 
165         if (address.getExpirationTime() == LinkAddress.LIFETIME_PERMANENT
166                 || address.getExpirationTime() == LinkAddress.LIFETIME_UNKNOWN) {
167             validLifetimeSeconds = INFINITE_LIFETIME;
168         } else {
169             validLifetimeSeconds =
170                     Math.max(
171                             (address.getExpirationTime() - SystemClock.elapsedRealtime()) / 1000L,
172                             0L);
173         }
174 
175         if (!NetlinkUtils.sendRtmNewAddressRequest(
176                 Os.if_nametoindex(mIfName),
177                 address.getAddress(),
178                 (short) address.getPrefixLength(),
179                 address.getFlags(),
180                 (byte) address.getScope(),
181                 preferredLifetimeSeconds,
182                 validLifetimeSeconds)) {
183             Log.w(TAG, "Failed to add address " + address.getAddress().getHostAddress());
184             return;
185         }
186         mLinkProperties.addLinkAddress(address);
187         mLinkProperties.addRoute(getRouteForAddress(address));
188     }
189 
190     /** Removes an address from the interface. */
removeAddress(LinkAddress address)191     public void removeAddress(LinkAddress address) {
192         Log.d(TAG, "Removing address " + address);
193 
194         // Intentionally update the mLinkProperties before send netlink message because the
195         // address is already removed from ot-daemon and apps can't reach to the address even
196         // when the netlink request below fails
197         mLinkProperties.removeLinkAddress(address);
198         mLinkProperties.removeRoute(getRouteForAddress(address));
199         if (!NetlinkUtils.sendRtmDelAddressRequest(
200                 Os.if_nametoindex(mIfName),
201                 (Inet6Address) address.getAddress(),
202                 (short) address.getPrefixLength())) {
203             Log.w(TAG, "Failed to remove address " + address.getAddress().getHostAddress());
204         }
205     }
206 
updateAddresses(List<Ipv6AddressInfo> addressInfoList)207     public void updateAddresses(List<Ipv6AddressInfo> addressInfoList) {
208         final List<LinkAddress> newLinkAddresses = new ArrayList<>();
209         final List<InetAddress> newMulticastAddresses = new ArrayList<>();
210         boolean hasActiveOmrAddress = false;
211 
212         for (Ipv6AddressInfo addressInfo : addressInfoList) {
213             if (addressInfo.isActiveOmr) {
214                 hasActiveOmrAddress = true;
215                 break;
216             }
217         }
218 
219         for (Ipv6AddressInfo addressInfo : addressInfoList) {
220             InetAddress address = addressInfoToInetAddress(addressInfo);
221             if (address.isMulticastAddress()) {
222                 newMulticastAddresses.add(address);
223             } else {
224                 newLinkAddresses.add(newLinkAddress(addressInfo, hasActiveOmrAddress));
225             }
226         }
227 
228         final CompareResult<LinkAddress> addressDiff =
229                 new CompareResult<>(mLinkProperties.getAllLinkAddresses(), newLinkAddresses);
230         for (LinkAddress linkAddress : addressDiff.removed) {
231             removeAddress(linkAddress);
232         }
233         for (LinkAddress linkAddress : addressDiff.added) {
234             addAddress(linkAddress);
235         }
236 
237         final CompareResult<InetAddress> multicastAddressDiff =
238                 new CompareResult<>(mMulticastAddresses, newMulticastAddresses);
239         for (InetAddress address : multicastAddressDiff.removed) {
240             leaveGroup(address);
241         }
242         for (InetAddress address : multicastAddressDiff.added) {
243             joinGroup(address);
244         }
245         mMulticastAddresses.clear();
246         mMulticastAddresses.addAll(newMulticastAddresses);
247     }
248 
updatePrefixes(List<OnMeshPrefixConfig> onMeshPrefixConfigList)249     public void updatePrefixes(List<OnMeshPrefixConfig> onMeshPrefixConfigList) {
250         final List<RouteInfo> newNetDataPrefixes = new ArrayList<>();
251 
252         for (OnMeshPrefixConfig onMeshPrefixConfig : onMeshPrefixConfigList) {
253             newNetDataPrefixes.add(getRouteForOnMeshPrefix(onMeshPrefixConfig));
254         }
255 
256         final CompareResult<RouteInfo> prefixDiff =
257                 new CompareResult<>(mNetDataPrefixes, newNetDataPrefixes);
258         for (RouteInfo routeRemoved : prefixDiff.removed) {
259             mLinkProperties.removeRoute(routeRemoved);
260         }
261         for (RouteInfo routeAdded : prefixDiff.added) {
262             mLinkProperties.addRoute(routeAdded);
263         }
264 
265         mNetDataPrefixes.clear();
266         mNetDataPrefixes.addAll(newNetDataPrefixes);
267     }
268 
getRouteForAddress(LinkAddress linkAddress)269     private RouteInfo getRouteForAddress(LinkAddress linkAddress) {
270         return getRouteForIpPrefix(
271                 new IpPrefix(linkAddress.getAddress(), linkAddress.getPrefixLength()));
272     }
273 
getRouteForOnMeshPrefix(OnMeshPrefixConfig onMeshPrefixConfig)274     private RouteInfo getRouteForOnMeshPrefix(OnMeshPrefixConfig onMeshPrefixConfig) {
275         return getRouteForIpPrefix(
276                 new IpPrefix(
277                         bytesToInet6Address(onMeshPrefixConfig.prefix),
278                         onMeshPrefixConfig.prefixLength));
279     }
280 
getRouteForIpPrefix(IpPrefix ipPrefix)281     private RouteInfo getRouteForIpPrefix(IpPrefix ipPrefix) {
282         return new RouteInfo(ipPrefix, null, mIfName, RouteInfo.RTN_UNICAST, MTU);
283     }
284 
285     /** Called by {@link ThreadNetworkControllerService} to do clean up when ot-daemon is dead. */
onOtDaemonDied()286     public void onOtDaemonDied() {
287         try {
288             setInterfaceUp(false);
289         } catch (IOException e) {
290             Log.e(TAG, "Failed to set Thread TUN interface down");
291         }
292     }
293 
addressInfoToInetAddress(Ipv6AddressInfo addressInfo)294     private static InetAddress addressInfoToInetAddress(Ipv6AddressInfo addressInfo) {
295         return bytesToInet6Address(addressInfo.address);
296     }
297 
bytesToInet6Address(byte[] addressBytes)298     private static Inet6Address bytesToInet6Address(byte[] addressBytes) {
299         try {
300             return (Inet6Address) Inet6Address.getByAddress(addressBytes);
301         } catch (UnknownHostException e) {
302             // This is unlikely to happen unless the Thread daemon is critically broken
303             return null;
304         }
305     }
306 
newLinkAddress( Ipv6AddressInfo addressInfo, boolean hasActiveOmrAddress)307     private static LinkAddress newLinkAddress(
308             Ipv6AddressInfo addressInfo, boolean hasActiveOmrAddress) {
309         // Mesh-local addresses and OMR address have the same scope, to distinguish them we set
310         // mesh-local addresses as deprecated when there is an active OMR address.
311         // For OMR address and link-local address we only use the value isPreferred set by
312         // ot-daemon.
313         boolean isPreferred = addressInfo.isPreferred;
314         if (addressInfo.isMeshLocal && hasActiveOmrAddress) {
315             isPreferred = false;
316         }
317 
318         final long deprecationTimeMillis =
319                 isPreferred ? LinkAddress.LIFETIME_PERMANENT : SystemClock.elapsedRealtime();
320 
321         final InetAddress address = addressInfoToInetAddress(addressInfo);
322 
323         // flags and scope will be adjusted automatically depending on the address and
324         // its lifetimes.
325         return new LinkAddress(
326                 address,
327                 addressInfo.prefixLength,
328                 0 /* flags */,
329                 0 /* scope */,
330                 deprecationTimeMillis,
331                 LinkAddress.LIFETIME_PERMANENT /* expirationTime */);
332     }
333 
createMulticastSocket()334     private MulticastSocket createMulticastSocket() {
335         try {
336             return new MulticastSocket();
337         } catch (IOException e) {
338             throw new IllegalStateException("Failed to create multicast socket ", e);
339         }
340     }
341 
joinGroup(InetAddress address)342     private void joinGroup(InetAddress address) {
343         InetSocketAddress socketAddress = new InetSocketAddress(address, 0);
344         try {
345             mMulticastSocket.joinGroup(socketAddress, mNetworkInterface);
346         } catch (IOException e) {
347             if (e.getCause() instanceof ErrnoException) {
348                 ErrnoException ee = (ErrnoException) e.getCause();
349                 if (ee.errno == EADDRINUSE) {
350                     Log.w(TAG, "Already joined group" + address.getHostAddress(), e);
351                     return;
352                 }
353             }
354             Log.e(TAG, "failed to join group " + address.getHostAddress(), e);
355         }
356     }
357 
leaveGroup(InetAddress address)358     private void leaveGroup(InetAddress address) {
359         InetSocketAddress socketAddress = new InetSocketAddress(address, 0);
360         try {
361             mMulticastSocket.leaveGroup(socketAddress, mNetworkInterface);
362         } catch (IOException e) {
363             Log.e(TAG, "failed to leave group " + address.getHostAddress(), e);
364         }
365     }
366 
367     /**
368      * Sets the address generation mode to {@code IN6_ADDR_GEN_MODE_NONE}.
369      *
370      * <p>So that the "thread-wpan" interface has only one IPv6 link local address which is
371      * generated by OpenThread.
372      */
setAddrGenModeToNone()373     private void setAddrGenModeToNone() {
374         StructNlMsgHdr header = new StructNlMsgHdr();
375         header.nlmsg_type = RTM_NEWLINK;
376         header.nlmsg_pid = 0;
377         header.nlmsg_seq = 0;
378         header.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
379 
380         StructIfinfoMsg ifInfo =
381                 new StructIfinfoMsg(
382                         (short) 0 /* family */,
383                         0 /* type */,
384                         Os.if_nametoindex(mIfName),
385                         (IFF_MULTICAST | IFF_NOARP) /* flags */,
386                         0xffffffff /* change */);
387 
388         // Nested attributes
389         // IFLA_AF_SPEC
390         //   AF_INET6
391         //     IFLA_INET6_ADDR_GEN_MODE
392         StructNlAttr addrGenMode =
393                 new StructNlAttr(IFLA_INET6_ADDR_GEN_MODE, (byte) IN6_ADDR_GEN_MODE_NONE);
394         StructNlAttr afInet6 = new StructNlAttr((short) AF_INET6, addrGenMode);
395         StructNlAttr afSpec = new StructNlAttr(IFLA_AF_SPEC, afInet6);
396 
397         final int msgLength =
398                 StructNlMsgHdr.STRUCT_SIZE
399                         + StructIfinfoMsg.STRUCT_SIZE
400                         + afSpec.getAlignedLength();
401         byte[] msg = new byte[msgLength];
402         ByteBuffer buf = ByteBuffer.wrap(msg);
403         buf.order(ByteOrder.nativeOrder());
404 
405         header.nlmsg_len = msgLength;
406         header.pack(buf);
407         ifInfo.pack(buf);
408         afSpec.pack(buf);
409 
410         if (buf.position() != msgLength) {
411             throw new AssertionError(
412                     String.format(
413                             "Unexpected netlink message size (actual = %d, expected = %d)",
414                             buf.position(), msgLength));
415         }
416 
417         if (DBG) {
418             Log.d(TAG, "ADDR_GEN_MODE message is:");
419             Log.d(TAG, HexDump.dumpHexString(msg));
420         }
421 
422         try {
423             NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
424         } catch (ErrnoException e) {
425             Log.e(TAG, "Failed to set ADDR_GEN_MODE to NONE", e);
426         }
427     }
428 }
429