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