content: /*
   * Copyright (C) 2014 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.net;
  
  import android.annotation.NonNull;
  import android.annotation.SystemApi;
  import android.annotation.TestApi;
  import android.annotation.UnsupportedAppUsage;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.system.ErrnoException;
  import android.system.Os;
  import android.system.OsConstants;
  import android.util.proto.ProtoOutputStream;
  
  import com.android.okhttp.internalandroidapi.Dns;
  import com.android.okhttp.internalandroidapi.HttpURLConnectionFactory;
  
  import libcore.io.IoUtils;
  
  import java.io.FileDescriptor;
  import java.io.IOException;
  import java.net.DatagramSocket;
  import java.net.InetAddress;
  import java.net.InetSocketAddress;
  import java.net.MalformedURLException;
  import java.net.Socket;
  import java.net.SocketAddress;
  import java.net.SocketException;
  import java.net.URL;
  import java.net.URLConnection;
  import java.net.UnknownHostException;
  import java.util.Arrays;
  import java.util.concurrent.TimeUnit;
  
  import javax.net.SocketFactory;
  
  /**
   * Identifies a {@code Network}.  This is supplied to applications via
   * {@link ConnectivityManager.NetworkCallback} in response to the active
   * {@link ConnectivityManager#requestNetwork} or passive
   * {@link ConnectivityManager#registerNetworkCallback} calls.
   * It is used to direct traffic to the given {@code Network}, either on a {@link Socket} basis
   * through a targeted {@link SocketFactory} or process-wide via
   * {@link ConnectivityManager#bindProcessToNetwork}.
   */
  public class Network implements Parcelable {
  
      /**
       * @hide
       */
      @UnsupportedAppUsage
      public final int netId;
  
      // Objects used to perform per-network operations such as getSocketFactory
      // and openConnection, and a lock to protect access to them.
      private volatile NetworkBoundSocketFactory mNetworkBoundSocketFactory = null;
      // mLock should be used to control write access to mUrlConnectionFactory.
      // maybeInitUrlConnectionFactory() must be called prior to reading this field.
      private volatile HttpURLConnectionFactory mUrlConnectionFactory;
      private final Object mLock = new Object();
  
      // Default connection pool values. These are evaluated at startup, just
      // like the OkHttp code. Also like the OkHttp code, we will throw parse
      // exceptions at class loading time if the properties are set but are not
      // valid integers.
      private static final boolean httpKeepAlive =
              Boolean.parseBoolean(System.getProperty("http.keepAlive", "true"));
      private static final int httpMaxConnections =
              httpKeepAlive ? Integer.parseInt(System.getProperty("http.maxConnections", "5")) : 0;
      private static final long httpKeepAliveDurationMs =
              Long.parseLong(System.getProperty("http.keepAliveDuration", "300000"));  // 5 minutes.
      // Value used to obfuscate network handle longs.
      // The HANDLE_MAGIC value MUST be kept in sync with the corresponding
      // value in the native/android/net.c NDK implementation.
      private static final long HANDLE_MAGIC = 0xcafed00dL;
      private static final int HANDLE_MAGIC_SIZE = 32;
  
      // A boolean to control how getAllByName()/getByName() behaves in the face
      // of Private DNS.
      //
      // When true, these calls will request that DNS resolution bypass any
      // Private DNS that might otherwise apply. Use of this feature is restricted
      // and permission checks are made by netd (attempts to bypass Private DNS
      // without appropriate permission are silently turned into vanilla DNS
      // requests). This only affects DNS queries made using this network object.
      //
      // It it not parceled to receivers because (a) it can be set or cleared at
      // anytime and (b) receivers should be explicit about attempts to bypass
      // Private DNS so that the intent of the code is easily determined and
      // code search audits are possible.
      private final transient boolean mPrivateDnsBypass;
  
      /**
       * @hide
       */
      @UnsupportedAppUsage
      public Network(int netId) {
          this(netId, false);
      }
  
      /**
       * @hide
       */
      public Network(int netId, boolean privateDnsBypass) {
          this.netId = netId;
          this.mPrivateDnsBypass = privateDnsBypass;
      }
  
      /**
       * @hide
       */
      @SystemApi
      @TestApi
      public Network(@NonNull Network that) {
          this(that.netId, that.mPrivateDnsBypass);
      }
  
      /**
       * Operates the same as {@code InetAddress.getAllByName} except that host
       * resolution is done on this network.
       *
       * @param host the hostname or literal IP string to be resolved.
       * @return the array of addresses associated with the specified host.
       * @throws UnknownHostException if the address lookup fails.
       */
      public InetAddress[] getAllByName(String host) throws UnknownHostException {
          return InetAddress.getAllByNameOnNet(host, getNetIdForResolv());
      }
  
      /**
       * Operates the same as {@code InetAddress.getByName} except that host
       * resolution is done on this network.
       *
       * @param host the hostname to be resolved to an address or {@code null}.
       * @return the {@code InetAddress} instance representing the host.
       * @throws UnknownHostException
       *             if the address lookup fails.
       */
      public InetAddress getByName(String host) throws UnknownHostException {
          return InetAddress.getByNameOnNet(host, getNetIdForResolv());
      }
  
      /**
       * Obtain a Network object for which Private DNS is to be bypassed when attempting
       * to use {@link #getAllByName(String)}/{@link #getByName(String)} methods on the given
       * instance for hostname resolution.
       *
       * @hide
       */
      @TestApi
      @SystemApi
      public @NonNull Network getPrivateDnsBypassingCopy() {
          return new Network(netId, true);
      }
  
      /**
       * Returns a netid marked with the Private DNS bypass flag.
       *
       * This flag must be kept in sync with the NETID_USE_LOCAL_NAMESERVERS flag
       * in system/netd/include/NetdClient.h.
       *
       * @hide
       */
      public int getNetIdForResolv() {
          return mPrivateDnsBypass
                  ? (int) (0x80000000L | (long) netId)  // Non-portable DNS resolution flag.
                  : netId;
      }
  
      /**
       * A {@code SocketFactory} that produces {@code Socket}'s bound to this network.
       */
      private class NetworkBoundSocketFactory extends SocketFactory {
          private Socket connectToHost(String host, int port, SocketAddress localAddress)
                  throws IOException {
              // Lookup addresses only on this Network.
              InetAddress[] hostAddresses = getAllByName(host);
              // Try all addresses.
              for (int i = 0; i < hostAddresses.length; i++) {
                  try {
                      Socket socket = createSocket();
                      boolean failed = true;
                      try {
                          if (localAddress != null) socket.bind(localAddress);
                          socket.connect(new InetSocketAddress(hostAddresses[i], port));
                          failed = false;
                          return socket;
                      } finally {
                          if (failed) IoUtils.closeQuietly(socket);
                      }
                  } catch (IOException e) {
                      if (i == (hostAddresses.length - 1)) throw e;
                  }
              }
              throw new UnknownHostException(host);
          }
  
          @Override
          public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
                  throws IOException {
              return connectToHost(host, port, new InetSocketAddress(localHost, localPort));
          }
  
          @Override
          public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
                  int localPort) throws IOException {
              Socket socket = createSocket();
              boolean failed = true;
              try {
                  socket.bind(new InetSocketAddress(localAddress, localPort));
                  socket.connect(new InetSocketAddress(address, port));
                  failed = false;
              } finally {
                  if (failed) IoUtils.closeQuietly(socket);
              }
              return socket;
          }
  
          @Override
          public Socket createSocket(InetAddress host, int port) throws IOException {
              Socket socket = createSocket();
              boolean failed = true;
              try {
                  socket.connect(new InetSocketAddress(host, port));
                  failed = false;
              } finally {
                  if (failed) IoUtils.closeQuietly(socket);
              }
              return socket;
          }
  
          @Override
          public Socket createSocket(String host, int port) throws IOException {
              return connectToHost(host, port, null);
          }
  
          @Override
          public Socket createSocket() throws IOException {
              Socket socket = new Socket();
              boolean failed = true;
              try {
                  bindSocket(socket);
                  failed = false;
              } finally {
                  if (failed) IoUtils.closeQuietly(socket);
              }
              return socket;
          }
      }
  
      /**
       * Returns a {@link SocketFactory} bound to this network.  Any {@link Socket} created by
       * this factory will have its traffic sent over this {@code Network}.  Note that if this
       * {@code Network} ever disconnects, this factory and any {@link Socket} it produced in the
       * past or future will cease to work.
       *
       * @return a {@link SocketFactory} which produces {@link Socket} instances bound to this
       *         {@code Network}.
       */
      public SocketFactory getSocketFactory() {
          if (mNetworkBoundSocketFactory == null) {
              synchronized (mLock) {
                  if (mNetworkBoundSocketFactory == null) {
                      mNetworkBoundSocketFactory = new NetworkBoundSocketFactory();
                  }
              }
          }
          return mNetworkBoundSocketFactory;
      }
  
      // TODO: This creates a connection pool and host resolver for
      // every Network object, instead of one for every NetId. This is
      // suboptimal, because an app could potentially have more than one
      // Network object for the same NetId, causing increased memory footprint
      // and performance penalties due to lack of connection reuse (connection
      // setup time, congestion window growth time, etc.).
      //
      // Instead, investigate only having one connection pool and host resolver
      // for every NetId, perhaps by using a static HashMap of NetIds to
      // connection pools and host resolvers. The tricky part is deciding when
      // to remove a map entry; a WeakHashMap shouldn't be used because whether
      // a Network is referenced doesn't correlate with whether a new Network
      // will be instantiated in the near future with the same NetID. A good
      // solution would involve purging empty (or when all connections are timed
      // out) ConnectionPools.
      private void maybeInitUrlConnectionFactory() {
          synchronized (mLock) {
              if (mUrlConnectionFactory == null) {
                  // Set configuration on the HttpURLConnectionFactory that will be good for all
                  // connections created by this Network. Configuration that might vary is left
                  // until openConnection() and passed as arguments.
                  Dns dnsLookup = hostname -> Arrays.asList(Network.this.getAllByName(hostname));
                  HttpURLConnectionFactory urlConnectionFactory = new HttpURLConnectionFactory();
                  urlConnectionFactory.setDns(dnsLookup); // Let traffic go via dnsLookup
                  // A private connection pool just for this Network.
                  urlConnectionFactory.setNewConnectionPool(httpMaxConnections,
                          httpKeepAliveDurationMs, TimeUnit.MILLISECONDS);
                  mUrlConnectionFactory = urlConnectionFactory;
              }
          }
      }
  
      /**
       * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent
       * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}.
       *
       * @return a {@code URLConnection} to the resource referred to by this URL.
       * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS.
       * @throws IOException if an error occurs while opening the connection.
       * @see java.net.URL#openConnection()
       */
      public URLConnection openConnection(URL url) throws IOException {
          final ConnectivityManager cm = ConnectivityManager.getInstanceOrNull();
          if (cm == null) {
              throw new IOException("No ConnectivityManager yet constructed, please construct one");
          }
          // TODO: Should this be optimized to avoid fetching the global proxy for every request?
          final ProxyInfo proxyInfo = cm.getProxyForNetwork(this);
          final java.net.Proxy proxy;
          if (proxyInfo != null) {
              proxy = proxyInfo.makeProxy();
          } else {
              proxy = java.net.Proxy.NO_PROXY;
          }
          return openConnection(url, proxy);
      }
  
      /**
       * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent
       * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}.
       *
       * @param proxy the proxy through which the connection will be established.
       * @return a {@code URLConnection} to the resource referred to by this URL.
       * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS.
       * @throws IllegalArgumentException if the argument proxy is null.
       * @throws IOException if an error occurs while opening the connection.
       * @see java.net.URL#openConnection()
       */
      public URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException {
          if (proxy == null) throw new IllegalArgumentException("proxy is null");
          maybeInitUrlConnectionFactory();
          SocketFactory socketFactory = getSocketFactory();
          return mUrlConnectionFactory.openConnection(url, socketFactory, proxy);
      }
  
      /**
       * Binds the specified {@link DatagramSocket} to this {@code Network}. All data traffic on the
       * socket will be sent on this {@code Network}, irrespective of any process-wide network binding
       * set by {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be
       * connected.
       */
      public void bindSocket(DatagramSocket socket) throws IOException {
          // Query a property of the underlying socket to ensure that the socket's file descriptor
          // exists, is available to bind to a network and is not closed.
          socket.getReuseAddress();
          bindSocket(socket.getFileDescriptor$());
      }
  
      /**
       * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket
       * will be sent on this {@code Network}, irrespective of any process-wide network binding set by
       * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
       */
      public void bindSocket(Socket socket) throws IOException {
          // Query a property of the underlying socket to ensure that the socket's file descriptor
          // exists, is available to bind to a network and is not closed.
          socket.getReuseAddress();
          bindSocket(socket.getFileDescriptor$());
      }
  
      /**
       * Binds the specified {@link FileDescriptor} to this {@code Network}. All data traffic on the
       * socket represented by this file descriptor will be sent on this {@code Network},
       * irrespective of any process-wide network binding set by
       * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
       */
      public void bindSocket(FileDescriptor fd) throws IOException {
          try {
              final SocketAddress peer = Os.getpeername(fd);
              final InetAddress inetPeer = ((InetSocketAddress) peer).getAddress();
              if (!inetPeer.isAnyLocalAddress()) {
                  // Apparently, the kernel doesn't update a connected UDP socket's
                  // routing upon mark changes.
                  throw new SocketException("Socket is connected");
              }
          } catch (ErrnoException e) {
              // getpeername() failed.
              if (e.errno != OsConstants.ENOTCONN) {
                  throw e.rethrowAsSocketException();
              }
          } catch (ClassCastException e) {
              // Wasn't an InetSocketAddress.
              throw new SocketException("Only AF_INET/AF_INET6 sockets supported");
          }
  
          final int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId);
          if (err != 0) {
              // bindSocketToNetwork returns negative errno.
              throw new ErrnoException("Binding socket to network " + netId, -err)
                      .rethrowAsSocketException();
          }
      }
  
      /**
       * Returns a {@link Network} object given a handle returned from {@link #getNetworkHandle}.
       *
       * @param networkHandle a handle returned from {@link #getNetworkHandle}.
       * @return A {@link Network} object derived from {@code networkHandle}.
       */
      public static Network fromNetworkHandle(long networkHandle) {
          if (networkHandle == 0) {
              throw new IllegalArgumentException(
                      "Network.fromNetworkHandle refusing to instantiate NETID_UNSET Network.");
          }
          if ((networkHandle & ((1L << HANDLE_MAGIC_SIZE) - 1)) != HANDLE_MAGIC
                  || networkHandle < 0) {
              throw new IllegalArgumentException(
                      "Value passed to fromNetworkHandle() is not a network handle.");
          }
          return new Network((int) (networkHandle >> HANDLE_MAGIC_SIZE));
      }
  
      /**
       * Returns a handle representing this {@code Network}, for use with the NDK API.
       */
      public long getNetworkHandle() {
          // The network handle is explicitly not the same as the netId.
          //
          // The netId is an implementation detail which might be changed in the
          // future, or which alone (i.e. in the absence of some additional
          // context) might not be sufficient to fully identify a Network.
          //
          // As such, the intention is to prevent accidental misuse of the API
          // that might result if a developer assumed that handles and netIds
          // were identical and passing a netId to a call expecting a handle
          // "just worked".  Such accidental misuse, if widely deployed, might
          // prevent future changes to the semantics of the netId field or
          // inhibit the expansion of state required for Network objects.
          //
          // This extra layer of indirection might be seen as paranoia, and might
          // never end up being necessary, but the added complexity is trivial.
          // At some future date it may be desirable to realign the handle with
          // Multiple Provisioning Domains API recommendations, as made by the
          // IETF mif working group.
          if (netId == 0) {
              return 0L;  // make this zero condition obvious for debugging
          }
          return (((long) netId) << HANDLE_MAGIC_SIZE) | HANDLE_MAGIC;
      }
  
      // implement the Parcelable interface
      public int describeContents() {
          return 0;
      }
      public void writeToParcel(Parcel dest, int flags) {
          dest.writeInt(netId);
      }
  
      public static final @android.annotation.NonNull Creator<Network> CREATOR =
          new Creator<Network>() {
              public Network createFromParcel(Parcel in) {
                  int netId = in.readInt();
  
                  return new Network(netId);
              }
  
              public Network[] newArray(int size) {
                  return new Network[size];
              }
      };
  
      @Override
      public boolean equals(Object obj) {
          if (!(obj instanceof Network)) return false;
          Network other = (Network)obj;
          return this.netId == other.netId;
      }
  
      @Override
      public int hashCode() {
          return netId * 11;
      }
  
      @Override
      public String toString() {
          return Integer.toString(netId);
      }
  
      /** @hide */
      public void writeToProto(ProtoOutputStream proto, long fieldId) {
          final long token = proto.start(fieldId);
          proto.write(NetworkProto.NET_ID, netId);
          proto.end(token);
      }
  }
  