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