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