1 /* 2 * Copyright (C) 2014 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 android.net; 18 19 import android.annotation.NonNull; 20 import android.annotation.SystemApi; 21 import android.annotation.TestApi; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.system.ErrnoException; 26 import android.system.Os; 27 import android.system.OsConstants; 28 import android.util.proto.ProtoOutputStream; 29 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.okhttp.internalandroidapi.Dns; 32 import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory; 33 34 import libcore.io.IoUtils; 35 36 import java.io.FileDescriptor; 37 import java.io.IOException; 38 import java.net.DatagramSocket; 39 import java.net.InetAddress; 40 import java.net.InetSocketAddress; 41 import java.net.MalformedURLException; 42 import java.net.Socket; 43 import java.net.SocketAddress; 44 import java.net.SocketException; 45 import java.net.URL; 46 import java.net.URLConnection; 47 import java.net.UnknownHostException; 48 import java.util.Arrays; 49 import java.util.concurrent.TimeUnit; 50 51 import javax.net.SocketFactory; 52 53 /** 54 * Identifies a {@code Network}. This is supplied to applications via 55 * {@link ConnectivityManager.NetworkCallback} in response to the active 56 * {@link ConnectivityManager#requestNetwork} or passive 57 * {@link ConnectivityManager#registerNetworkCallback} calls. 58 * It is used to direct traffic to the given {@code Network}, either on a {@link Socket} basis 59 * through a targeted {@link SocketFactory} or process-wide via 60 * {@link ConnectivityManager#bindProcessToNetwork}. 61 */ 62 public class Network implements Parcelable { 63 64 /** 65 * The unique id of the network. 66 * @hide 67 */ 68 @UnsupportedAppUsage 69 public final int netId; 70 71 // Objects used to perform per-network operations such as getSocketFactory 72 // and openConnection, and a lock to protect access to them. 73 private volatile NetworkBoundSocketFactory mNetworkBoundSocketFactory = null; 74 // mUrlConnectionFactory is initialized lazily when it is first needed. 75 @GuardedBy("mLock") 76 private HttpURLConnectionFactory mUrlConnectionFactory; 77 private final Object mLock = new Object(); 78 79 // Default connection pool values. These are evaluated at startup, just 80 // like the OkHttp code. Also like the OkHttp code, we will throw parse 81 // exceptions at class loading time if the properties are set but are not 82 // valid integers. 83 private static final boolean httpKeepAlive = 84 Boolean.parseBoolean(System.getProperty("http.keepAlive", "true")); 85 private static final int httpMaxConnections = 86 httpKeepAlive ? Integer.parseInt(System.getProperty("http.maxConnections", "5")) : 0; 87 private static final long httpKeepAliveDurationMs = 88 Long.parseLong(System.getProperty("http.keepAliveDuration", "300000")); // 5 minutes. 89 // Value used to obfuscate network handle longs. 90 // The HANDLE_MAGIC value MUST be kept in sync with the corresponding 91 // value in the native/android/net.c NDK implementation. 92 private static final long HANDLE_MAGIC = 0xcafed00dL; 93 private static final int HANDLE_MAGIC_SIZE = 32; 94 95 // A boolean to control how getAllByName()/getByName() behaves in the face 96 // of Private DNS. 97 // 98 // When true, these calls will request that DNS resolution bypass any 99 // Private DNS that might otherwise apply. Use of this feature is restricted 100 // and permission checks are made by netd (attempts to bypass Private DNS 101 // without appropriate permission are silently turned into vanilla DNS 102 // requests). This only affects DNS queries made using this network object. 103 // 104 // It it not parceled to receivers because (a) it can be set or cleared at 105 // anytime and (b) receivers should be explicit about attempts to bypass 106 // Private DNS so that the intent of the code is easily determined and 107 // code search audits are possible. 108 private final transient boolean mPrivateDnsBypass; 109 110 /** 111 * @hide 112 */ 113 @UnsupportedAppUsage Network(int netId)114 public Network(int netId) { 115 this(netId, false); 116 } 117 118 /** 119 * @hide 120 */ Network(int netId, boolean privateDnsBypass)121 public Network(int netId, boolean privateDnsBypass) { 122 this.netId = netId; 123 this.mPrivateDnsBypass = privateDnsBypass; 124 } 125 126 /** 127 * @hide 128 */ 129 @SystemApi 130 @TestApi Network(@onNull Network that)131 public Network(@NonNull Network that) { 132 this(that.netId, that.mPrivateDnsBypass); 133 } 134 135 /** 136 * Operates the same as {@code InetAddress.getAllByName} except that host 137 * resolution is done on this network. 138 * 139 * @param host the hostname or literal IP string to be resolved. 140 * @return the array of addresses associated with the specified host. 141 * @throws UnknownHostException if the address lookup fails. 142 */ getAllByName(String host)143 public InetAddress[] getAllByName(String host) throws UnknownHostException { 144 return InetAddress.getAllByNameOnNet(host, getNetIdForResolv()); 145 } 146 147 /** 148 * Operates the same as {@code InetAddress.getByName} except that host 149 * resolution is done on this network. 150 * 151 * @param host the hostname to be resolved to an address or {@code null}. 152 * @return the {@code InetAddress} instance representing the host. 153 * @throws UnknownHostException 154 * if the address lookup fails. 155 */ getByName(String host)156 public InetAddress getByName(String host) throws UnknownHostException { 157 return InetAddress.getByNameOnNet(host, getNetIdForResolv()); 158 } 159 160 /** 161 * Obtain a Network object for which Private DNS is to be bypassed when attempting 162 * to use {@link #getAllByName(String)}/{@link #getByName(String)} methods on the given 163 * instance for hostname resolution. 164 * 165 * @hide 166 */ 167 @TestApi 168 @SystemApi getPrivateDnsBypassingCopy()169 public @NonNull Network getPrivateDnsBypassingCopy() { 170 return new Network(netId, true); 171 } 172 173 /** 174 * Get the unique id of the network. 175 * 176 * @hide 177 */ 178 @TestApi 179 @SystemApi getNetId()180 public int getNetId() { 181 return netId; 182 } 183 184 /** 185 * Returns a netid marked with the Private DNS bypass flag. 186 * 187 * This flag must be kept in sync with the NETID_USE_LOCAL_NAMESERVERS flag 188 * in system/netd/include/NetdClient.h. 189 * 190 * @hide 191 */ getNetIdForResolv()192 public int getNetIdForResolv() { 193 return mPrivateDnsBypass 194 ? (int) (0x80000000L | (long) netId) // Non-portable DNS resolution flag. 195 : netId; 196 } 197 198 /** 199 * A {@code SocketFactory} that produces {@code Socket}'s bound to this network. 200 */ 201 private class NetworkBoundSocketFactory extends SocketFactory { connectToHost(String host, int port, SocketAddress localAddress)202 private Socket connectToHost(String host, int port, SocketAddress localAddress) 203 throws IOException { 204 // Lookup addresses only on this Network. 205 InetAddress[] hostAddresses = getAllByName(host); 206 // Try all addresses. 207 for (int i = 0; i < hostAddresses.length; i++) { 208 try { 209 Socket socket = createSocket(); 210 boolean failed = true; 211 try { 212 if (localAddress != null) socket.bind(localAddress); 213 socket.connect(new InetSocketAddress(hostAddresses[i], port)); 214 failed = false; 215 return socket; 216 } finally { 217 if (failed) IoUtils.closeQuietly(socket); 218 } 219 } catch (IOException e) { 220 if (i == (hostAddresses.length - 1)) throw e; 221 } 222 } 223 throw new UnknownHostException(host); 224 } 225 226 @Override createSocket(String host, int port, InetAddress localHost, int localPort)227 public Socket createSocket(String host, int port, InetAddress localHost, int localPort) 228 throws IOException { 229 return connectToHost(host, port, new InetSocketAddress(localHost, localPort)); 230 } 231 232 @Override createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)233 public Socket createSocket(InetAddress address, int port, InetAddress localAddress, 234 int localPort) throws IOException { 235 Socket socket = createSocket(); 236 boolean failed = true; 237 try { 238 socket.bind(new InetSocketAddress(localAddress, localPort)); 239 socket.connect(new InetSocketAddress(address, port)); 240 failed = false; 241 } finally { 242 if (failed) IoUtils.closeQuietly(socket); 243 } 244 return socket; 245 } 246 247 @Override createSocket(InetAddress host, int port)248 public Socket createSocket(InetAddress host, int port) throws IOException { 249 Socket socket = createSocket(); 250 boolean failed = true; 251 try { 252 socket.connect(new InetSocketAddress(host, port)); 253 failed = false; 254 } finally { 255 if (failed) IoUtils.closeQuietly(socket); 256 } 257 return socket; 258 } 259 260 @Override createSocket(String host, int port)261 public Socket createSocket(String host, int port) throws IOException { 262 return connectToHost(host, port, null); 263 } 264 265 @Override createSocket()266 public Socket createSocket() throws IOException { 267 Socket socket = new Socket(); 268 boolean failed = true; 269 try { 270 bindSocket(socket); 271 failed = false; 272 } finally { 273 if (failed) IoUtils.closeQuietly(socket); 274 } 275 return socket; 276 } 277 } 278 279 /** 280 * Returns a {@link SocketFactory} bound to this network. Any {@link Socket} created by 281 * this factory will have its traffic sent over this {@code Network}. Note that if this 282 * {@code Network} ever disconnects, this factory and any {@link Socket} it produced in the 283 * past or future will cease to work. 284 * 285 * @return a {@link SocketFactory} which produces {@link Socket} instances bound to this 286 * {@code Network}. 287 */ getSocketFactory()288 public SocketFactory getSocketFactory() { 289 if (mNetworkBoundSocketFactory == null) { 290 synchronized (mLock) { 291 if (mNetworkBoundSocketFactory == null) { 292 mNetworkBoundSocketFactory = new NetworkBoundSocketFactory(); 293 } 294 } 295 } 296 return mNetworkBoundSocketFactory; 297 } 298 createUrlConnectionFactory(Dns dnsLookup)299 private static HttpURLConnectionFactory createUrlConnectionFactory(Dns dnsLookup) { 300 // Set configuration on the HttpURLConnectionFactory that will be good for all 301 // connections created by this Network. Configuration that might vary is left 302 // until openConnection() and passed as arguments. 303 HttpURLConnectionFactory urlConnectionFactory = new HttpURLConnectionFactory(); 304 urlConnectionFactory.setDns(dnsLookup); // Let traffic go via dnsLookup 305 // A private connection pool just for this Network. 306 urlConnectionFactory.setNewConnectionPool(httpMaxConnections, 307 httpKeepAliveDurationMs, TimeUnit.MILLISECONDS); 308 return urlConnectionFactory; 309 } 310 311 /** 312 * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent 313 * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}. 314 * 315 * @return a {@code URLConnection} to the resource referred to by this URL. 316 * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS. 317 * @throws IOException if an error occurs while opening the connection. 318 * @see java.net.URL#openConnection() 319 */ openConnection(URL url)320 public URLConnection openConnection(URL url) throws IOException { 321 final ConnectivityManager cm = ConnectivityManager.getInstanceOrNull(); 322 if (cm == null) { 323 throw new IOException("No ConnectivityManager yet constructed, please construct one"); 324 } 325 // TODO: Should this be optimized to avoid fetching the global proxy for every request? 326 final ProxyInfo proxyInfo = cm.getProxyForNetwork(this); 327 final java.net.Proxy proxy; 328 if (proxyInfo != null) { 329 proxy = proxyInfo.makeProxy(); 330 } else { 331 proxy = java.net.Proxy.NO_PROXY; 332 } 333 return openConnection(url, proxy); 334 } 335 336 /** 337 * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent 338 * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}. 339 * 340 * @param proxy the proxy through which the connection will be established. 341 * @return a {@code URLConnection} to the resource referred to by this URL. 342 * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS. 343 * @throws IllegalArgumentException if the argument proxy is null. 344 * @throws IOException if an error occurs while opening the connection. 345 * @see java.net.URL#openConnection() 346 */ openConnection(URL url, java.net.Proxy proxy)347 public URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException { 348 if (proxy == null) throw new IllegalArgumentException("proxy is null"); 349 // TODO: This creates a connection pool and host resolver for 350 // every Network object, instead of one for every NetId. This is 351 // suboptimal, because an app could potentially have more than one 352 // Network object for the same NetId, causing increased memory footprint 353 // and performance penalties due to lack of connection reuse (connection 354 // setup time, congestion window growth time, etc.). 355 // 356 // Instead, investigate only having one connection pool and host resolver 357 // for every NetId, perhaps by using a static HashMap of NetIds to 358 // connection pools and host resolvers. The tricky part is deciding when 359 // to remove a map entry; a WeakHashMap shouldn't be used because whether 360 // a Network is referenced doesn't correlate with whether a new Network 361 // will be instantiated in the near future with the same NetID. A good 362 // solution would involve purging empty (or when all connections are timed 363 // out) ConnectionPools. 364 final HttpURLConnectionFactory urlConnectionFactory; 365 synchronized (mLock) { 366 if (mUrlConnectionFactory == null) { 367 Dns dnsLookup = hostname -> Arrays.asList(getAllByName(hostname)); 368 mUrlConnectionFactory = createUrlConnectionFactory(dnsLookup); 369 } 370 urlConnectionFactory = mUrlConnectionFactory; 371 } 372 SocketFactory socketFactory = getSocketFactory(); 373 return urlConnectionFactory.openConnection(url, socketFactory, proxy); 374 } 375 376 /** 377 * Binds the specified {@link DatagramSocket} to this {@code Network}. All data traffic on the 378 * socket will be sent on this {@code Network}, irrespective of any process-wide network binding 379 * set by {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be 380 * connected. 381 */ bindSocket(DatagramSocket socket)382 public void bindSocket(DatagramSocket socket) throws IOException { 383 // Query a property of the underlying socket to ensure that the socket's file descriptor 384 // exists, is available to bind to a network and is not closed. 385 socket.getReuseAddress(); 386 bindSocket(socket.getFileDescriptor$()); 387 } 388 389 /** 390 * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket 391 * will be sent on this {@code Network}, irrespective of any process-wide network binding set by 392 * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected. 393 */ bindSocket(Socket socket)394 public void bindSocket(Socket socket) throws IOException { 395 // Query a property of the underlying socket to ensure that the socket's file descriptor 396 // exists, is available to bind to a network and is not closed. 397 socket.getReuseAddress(); 398 bindSocket(socket.getFileDescriptor$()); 399 } 400 401 /** 402 * Binds the specified {@link FileDescriptor} to this {@code Network}. All data traffic on the 403 * socket represented by this file descriptor will be sent on this {@code Network}, 404 * irrespective of any process-wide network binding set by 405 * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected. 406 */ bindSocket(FileDescriptor fd)407 public void bindSocket(FileDescriptor fd) throws IOException { 408 try { 409 final SocketAddress peer = Os.getpeername(fd); 410 final InetAddress inetPeer = ((InetSocketAddress) peer).getAddress(); 411 if (!inetPeer.isAnyLocalAddress()) { 412 // Apparently, the kernel doesn't update a connected UDP socket's 413 // routing upon mark changes. 414 throw new SocketException("Socket is connected"); 415 } 416 } catch (ErrnoException e) { 417 // getpeername() failed. 418 if (e.errno != OsConstants.ENOTCONN) { 419 throw e.rethrowAsSocketException(); 420 } 421 } catch (ClassCastException e) { 422 // Wasn't an InetSocketAddress. 423 throw new SocketException("Only AF_INET/AF_INET6 sockets supported"); 424 } 425 426 final int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId); 427 if (err != 0) { 428 // bindSocketToNetwork returns negative errno. 429 throw new ErrnoException("Binding socket to network " + netId, -err) 430 .rethrowAsSocketException(); 431 } 432 } 433 434 /** 435 * Returns a {@link Network} object given a handle returned from {@link #getNetworkHandle}. 436 * 437 * @param networkHandle a handle returned from {@link #getNetworkHandle}. 438 * @return A {@link Network} object derived from {@code networkHandle}. 439 */ fromNetworkHandle(long networkHandle)440 public static Network fromNetworkHandle(long networkHandle) { 441 if (networkHandle == 0) { 442 throw new IllegalArgumentException( 443 "Network.fromNetworkHandle refusing to instantiate NETID_UNSET Network."); 444 } 445 if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC 446 || networkHandle < 0) { 447 throw new IllegalArgumentException( 448 "Value passed to fromNetworkHandle() is not a network handle."); 449 } 450 return new Network((int) (networkHandle >> HANDLE_MAGIC_SIZE)); 451 } 452 453 /** 454 * Returns a handle representing this {@code Network}, for use with the NDK API. 455 */ getNetworkHandle()456 public long getNetworkHandle() { 457 // The network handle is explicitly not the same as the netId. 458 // 459 // The netId is an implementation detail which might be changed in the 460 // future, or which alone (i.e. in the absence of some additional 461 // context) might not be sufficient to fully identify a Network. 462 // 463 // As such, the intention is to prevent accidental misuse of the API 464 // that might result if a developer assumed that handles and netIds 465 // were identical and passing a netId to a call expecting a handle 466 // "just worked". Such accidental misuse, if widely deployed, might 467 // prevent future changes to the semantics of the netId field or 468 // inhibit the expansion of state required for Network objects. 469 // 470 // This extra layer of indirection might be seen as paranoia, and might 471 // never end up being necessary, but the added complexity is trivial. 472 // At some future date it may be desirable to realign the handle with 473 // Multiple Provisioning Domains API recommendations, as made by the 474 // IETF mif working group. 475 if (netId == 0) { 476 return 0L; // make this zero condition obvious for debugging 477 } 478 return (((long) netId) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC; 479 } 480 481 // implement the Parcelable interface describeContents()482 public int describeContents() { 483 return 0; 484 } writeToParcel(Parcel dest, int flags)485 public void writeToParcel(Parcel dest, int flags) { 486 dest.writeInt(netId); 487 } 488 489 public static final @android.annotation.NonNull Creator<Network> CREATOR = 490 new Creator<Network>() { 491 public Network createFromParcel(Parcel in) { 492 int netId = in.readInt(); 493 494 return new Network(netId); 495 } 496 497 public Network[] newArray(int size) { 498 return new Network[size]; 499 } 500 }; 501 502 @Override equals(Object obj)503 public boolean equals(Object obj) { 504 if (!(obj instanceof Network)) return false; 505 Network other = (Network)obj; 506 return this.netId == other.netId; 507 } 508 509 @Override hashCode()510 public int hashCode() { 511 return netId * 11; 512 } 513 514 @Override toString()515 public String toString() { 516 return Integer.toString(netId); 517 } 518 519 /** @hide */ writeToProto(ProtoOutputStream proto, long fieldId)520 public void writeToProto(ProtoOutputStream proto, long fieldId) { 521 final long token = proto.start(fieldId); 522 proto.write(NetworkProto.NET_ID, netId); 523 proto.end(token); 524 } 525 } 526