Home | History | Annotate | Download | only in net
      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.net.NetworkUtils;
     20 import android.os.Parcelable;
     21 import android.os.Parcel;
     22 import android.system.ErrnoException;
     23 
     24 import java.io.FileDescriptor;
     25 import java.io.IOException;
     26 import java.net.DatagramSocket;
     27 import java.net.InetAddress;
     28 import java.net.InetSocketAddress;
     29 import java.net.MalformedURLException;
     30 import java.net.ProxySelector;
     31 import java.net.Socket;
     32 import java.net.SocketAddress;
     33 import java.net.SocketException;
     34 import java.net.UnknownHostException;
     35 import java.net.URL;
     36 import java.net.URLConnection;
     37 import java.net.URLStreamHandler;
     38 import java.util.concurrent.atomic.AtomicReference;
     39 import javax.net.SocketFactory;
     40 
     41 import com.android.okhttp.ConnectionPool;
     42 import com.android.okhttp.HostResolver;
     43 import com.android.okhttp.HttpHandler;
     44 import com.android.okhttp.HttpsHandler;
     45 import com.android.okhttp.OkHttpClient;
     46 
     47 /**
     48  * Identifies a {@code Network}.  This is supplied to applications via
     49  * {@link ConnectivityManager.NetworkCallback} in response to the active
     50  * {@link ConnectivityManager#requestNetwork} or passive
     51  * {@link ConnectivityManager#registerNetworkCallback} calls.
     52  * It is used to direct traffic to the given {@code Network}, either on a {@link Socket} basis
     53  * through a targeted {@link SocketFactory} or process-wide via
     54  * {@link ConnectivityManager#setProcessDefaultNetwork}.
     55  */
     56 public class Network implements Parcelable {
     57 
     58     /**
     59      * @hide
     60      */
     61     public final int netId;
     62 
     63     // Objects used to perform per-network operations such as getSocketFactory
     64     // and openConnection, and a lock to protect access to them.
     65     private volatile NetworkBoundSocketFactory mNetworkBoundSocketFactory = null;
     66     // mLock should be used to control write access to mConnectionPool and mHostResolver.
     67     // maybeInitHttpClient() must be called prior to reading either variable.
     68     private volatile ConnectionPool mConnectionPool = null;
     69     private volatile HostResolver mHostResolver = null;
     70     private Object mLock = new Object();
     71 
     72     // Default connection pool values. These are evaluated at startup, just
     73     // like the OkHttp code. Also like the OkHttp code, we will throw parse
     74     // exceptions at class loading time if the properties are set but are not
     75     // valid integers.
     76     private static final boolean httpKeepAlive =
     77             Boolean.parseBoolean(System.getProperty("http.keepAlive", "true"));
     78     private static final int httpMaxConnections =
     79             httpKeepAlive ? Integer.parseInt(System.getProperty("http.maxConnections", "5")) : 0;
     80     private static final long httpKeepAliveDurationMs =
     81             Long.parseLong(System.getProperty("http.keepAliveDuration", "300000"));  // 5 minutes.
     82 
     83     /**
     84      * @hide
     85      */
     86     public Network(int netId) {
     87         this.netId = netId;
     88     }
     89 
     90     /**
     91      * @hide
     92      */
     93     public Network(Network that) {
     94         this.netId = that.netId;
     95     }
     96 
     97     /**
     98      * Operates the same as {@code InetAddress.getAllByName} except that host
     99      * resolution is done on this network.
    100      *
    101      * @param host the hostname or literal IP string to be resolved.
    102      * @return the array of addresses associated with the specified host.
    103      * @throws UnknownHostException if the address lookup fails.
    104      */
    105     public InetAddress[] getAllByName(String host) throws UnknownHostException {
    106         return InetAddress.getAllByNameOnNet(host, netId);
    107     }
    108 
    109     /**
    110      * Operates the same as {@code InetAddress.getByName} except that host
    111      * resolution is done on this network.
    112      *
    113      * @param host
    114      *            the hostName to be resolved to an address or {@code null}.
    115      * @return the {@code InetAddress} instance representing the host.
    116      * @throws UnknownHostException
    117      *             if the address lookup fails.
    118      */
    119     public InetAddress getByName(String host) throws UnknownHostException {
    120         return InetAddress.getByNameOnNet(host, netId);
    121     }
    122 
    123     /**
    124      * A {@code SocketFactory} that produces {@code Socket}'s bound to this network.
    125      */
    126     private class NetworkBoundSocketFactory extends SocketFactory {
    127         private final int mNetId;
    128 
    129         public NetworkBoundSocketFactory(int netId) {
    130             super();
    131             mNetId = netId;
    132         }
    133 
    134         private Socket connectToHost(String host, int port, SocketAddress localAddress)
    135                 throws IOException {
    136             // Lookup addresses only on this Network.
    137             InetAddress[] hostAddresses = getAllByName(host);
    138             // Try all addresses.
    139             for (int i = 0; i < hostAddresses.length; i++) {
    140                 try {
    141                     Socket socket = createSocket();
    142                     if (localAddress != null) socket.bind(localAddress);
    143                     socket.connect(new InetSocketAddress(hostAddresses[i], port));
    144                     return socket;
    145                 } catch (IOException e) {
    146                     if (i == (hostAddresses.length - 1)) throw e;
    147                 }
    148             }
    149             throw new UnknownHostException(host);
    150         }
    151 
    152         @Override
    153         public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
    154             return connectToHost(host, port, new InetSocketAddress(localHost, localPort));
    155         }
    156 
    157         @Override
    158         public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
    159                 int localPort) throws IOException {
    160             Socket socket = createSocket();
    161             socket.bind(new InetSocketAddress(localAddress, localPort));
    162             socket.connect(new InetSocketAddress(address, port));
    163             return socket;
    164         }
    165 
    166         @Override
    167         public Socket createSocket(InetAddress host, int port) throws IOException {
    168             Socket socket = createSocket();
    169             socket.connect(new InetSocketAddress(host, port));
    170             return socket;
    171         }
    172 
    173         @Override
    174         public Socket createSocket(String host, int port) throws IOException {
    175             return connectToHost(host, port, null);
    176         }
    177 
    178         @Override
    179         public Socket createSocket() throws IOException {
    180             Socket socket = new Socket();
    181             bindSocket(socket);
    182             return socket;
    183         }
    184     }
    185 
    186     /**
    187      * Returns a {@link SocketFactory} bound to this network.  Any {@link Socket} created by
    188      * this factory will have its traffic sent over this {@code Network}.  Note that if this
    189      * {@code Network} ever disconnects, this factory and any {@link Socket} it produced in the
    190      * past or future will cease to work.
    191      *
    192      * @return a {@link SocketFactory} which produces {@link Socket} instances bound to this
    193      *         {@code Network}.
    194      */
    195     public SocketFactory getSocketFactory() {
    196         if (mNetworkBoundSocketFactory == null) {
    197             synchronized (mLock) {
    198                 if (mNetworkBoundSocketFactory == null) {
    199                     mNetworkBoundSocketFactory = new NetworkBoundSocketFactory(netId);
    200                 }
    201             }
    202         }
    203         return mNetworkBoundSocketFactory;
    204     }
    205 
    206     // TODO: This creates a connection pool and host resolver for
    207     // every Network object, instead of one for every NetId. This is
    208     // suboptimal, because an app could potentially have more than one
    209     // Network object for the same NetId, causing increased memory footprint
    210     // and performance penalties due to lack of connection reuse (connection
    211     // setup time, congestion window growth time, etc.).
    212     //
    213     // Instead, investigate only having one connection pool and host resolver
    214     // for every NetId, perhaps by using a static HashMap of NetIds to
    215     // connection pools and host resolvers. The tricky part is deciding when
    216     // to remove a map entry; a WeakHashMap shouldn't be used because whether
    217     // a Network is referenced doesn't correlate with whether a new Network
    218     // will be instantiated in the near future with the same NetID. A good
    219     // solution would involve purging empty (or when all connections are timed
    220     // out) ConnectionPools.
    221     private void maybeInitHttpClient() {
    222         synchronized (mLock) {
    223             if (mHostResolver == null) {
    224                 mHostResolver = new HostResolver() {
    225                     @Override
    226                     public InetAddress[] getAllByName(String host) throws UnknownHostException {
    227                         return Network.this.getAllByName(host);
    228                     }
    229                 };
    230             }
    231             if (mConnectionPool == null) {
    232                 mConnectionPool = new ConnectionPool(httpMaxConnections,
    233                         httpKeepAliveDurationMs);
    234             }
    235         }
    236     }
    237 
    238     /**
    239      * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent
    240      * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}.
    241      *
    242      * @return a {@code URLConnection} to the resource referred to by this URL.
    243      * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS.
    244      * @throws IOException if an error occurs while opening the connection.
    245      * @see java.net.URL#openConnection()
    246      */
    247     public URLConnection openConnection(URL url) throws IOException {
    248         final ConnectivityManager cm = ConnectivityManager.getInstance();
    249         // TODO: Should this be optimized to avoid fetching the global proxy for every request?
    250         ProxyInfo proxyInfo = cm.getGlobalProxy();
    251         if (proxyInfo == null) {
    252             // TODO: Should this be optimized to avoid fetching LinkProperties for every request?
    253             final LinkProperties lp = cm.getLinkProperties(this);
    254             if (lp != null) proxyInfo = lp.getHttpProxy();
    255         }
    256         java.net.Proxy proxy = null;
    257         if (proxyInfo != null) {
    258             proxy = proxyInfo.makeProxy();
    259         } else {
    260             proxy = java.net.Proxy.NO_PROXY;
    261         }
    262         return openConnection(url, proxy);
    263     }
    264 
    265     /**
    266      * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent
    267      * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}.
    268      *
    269      * @param proxy the proxy through which the connection will be established.
    270      * @return a {@code URLConnection} to the resource referred to by this URL.
    271      * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS.
    272      * @throws IllegalArgumentException if the argument proxy is null.
    273      * @throws IOException if an error occurs while opening the connection.
    274      * @see java.net.URL#openConnection()
    275      * @hide
    276      */
    277     public URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException {
    278         if (proxy == null) throw new IllegalArgumentException("proxy is null");
    279         maybeInitHttpClient();
    280         String protocol = url.getProtocol();
    281         OkHttpClient client;
    282         // TODO: HttpHandler creates OkHttpClients that share the default ResponseCache.
    283         // Could this cause unexpected behavior?
    284         if (protocol.equals("http")) {
    285             client = HttpHandler.createHttpOkHttpClient(proxy);
    286         } else if (protocol.equals("https")) {
    287             client = HttpsHandler.createHttpsOkHttpClient(proxy);
    288         } else {
    289             // OkHttpClient only supports HTTP and HTTPS and returns a null URLStreamHandler if
    290             // passed another protocol.
    291             throw new MalformedURLException("Invalid URL or unrecognized protocol " + protocol);
    292         }
    293         return client.setSocketFactory(getSocketFactory())
    294                 .setHostResolver(mHostResolver)
    295                 .setConnectionPool(mConnectionPool)
    296                 .open(url);
    297     }
    298 
    299     /**
    300      * Binds the specified {@link DatagramSocket} to this {@code Network}. All data traffic on the
    301      * socket will be sent on this {@code Network}, irrespective of any process-wide network binding
    302      * set by {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be
    303      * connected.
    304      */
    305     public void bindSocket(DatagramSocket socket) throws IOException {
    306         // Apparently, the kernel doesn't update a connected UDP socket's routing upon mark changes.
    307         if (socket.isConnected()) {
    308             throw new SocketException("Socket is connected");
    309         }
    310         // Query a property of the underlying socket to ensure that the socket's file descriptor
    311         // exists, is available to bind to a network and is not closed.
    312         socket.getReuseAddress();
    313         bindSocketFd(socket.getFileDescriptor$());
    314     }
    315 
    316     /**
    317      * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket
    318      * will be sent on this {@code Network}, irrespective of any process-wide network binding set by
    319      * {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be connected.
    320      */
    321     public void bindSocket(Socket socket) throws IOException {
    322         // Apparently, the kernel doesn't update a connected TCP socket's routing upon mark changes.
    323         if (socket.isConnected()) {
    324             throw new SocketException("Socket is connected");
    325         }
    326         // Query a property of the underlying socket to ensure that the socket's file descriptor
    327         // exists, is available to bind to a network and is not closed.
    328         socket.getReuseAddress();
    329         bindSocketFd(socket.getFileDescriptor$());
    330     }
    331 
    332     private void bindSocketFd(FileDescriptor fd) throws IOException {
    333         int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId);
    334         if (err != 0) {
    335             // bindSocketToNetwork returns negative errno.
    336             throw new ErrnoException("Binding socket to network " + netId, -err)
    337                     .rethrowAsSocketException();
    338         }
    339     }
    340 
    341     // implement the Parcelable interface
    342     public int describeContents() {
    343         return 0;
    344     }
    345     public void writeToParcel(Parcel dest, int flags) {
    346         dest.writeInt(netId);
    347     }
    348 
    349     public static final Creator<Network> CREATOR =
    350         new Creator<Network>() {
    351             public Network createFromParcel(Parcel in) {
    352                 int netId = in.readInt();
    353 
    354                 return new Network(netId);
    355             }
    356 
    357             public Network[] newArray(int size) {
    358                 return new Network[size];
    359             }
    360     };
    361 
    362     @Override
    363     public boolean equals(Object obj) {
    364         if (obj instanceof Network == false) return false;
    365         Network other = (Network)obj;
    366         return this.netId == other.netId;
    367     }
    368 
    369     @Override
    370     public int hashCode() {
    371         return netId * 11;
    372     }
    373 
    374     @Override
    375     public String toString() {
    376         return Integer.toString(netId);
    377     }
    378 }
    379