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