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.os.Parcelable;
     20 import android.os.Parcel;
     21 import android.system.ErrnoException;
     22 import android.system.Os;
     23 import android.system.OsConstants;
     24 
     25 import java.io.FileDescriptor;
     26 import java.io.IOException;
     27 import java.net.DatagramSocket;
     28 import java.net.InetAddress;
     29 import java.net.InetSocketAddress;
     30 import java.net.MalformedURLException;
     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.util.Arrays;
     38 import java.util.List;
     39 import java.util.concurrent.TimeUnit;
     40 import javax.net.SocketFactory;
     41 
     42 import com.android.okhttp.ConnectionPool;
     43 import com.android.okhttp.Dns;
     44 import com.android.okhttp.HttpHandler;
     45 import com.android.okhttp.HttpsHandler;
     46 import com.android.okhttp.OkHttpClient;
     47 import com.android.okhttp.OkUrlFactory;
     48 import com.android.okhttp.internal.Internal;
     49 
     50 /**
     51  * Identifies a {@code Network}.  This is supplied to applications via
     52  * {@link ConnectivityManager.NetworkCallback} in response to the active
     53  * {@link ConnectivityManager#requestNetwork} or passive
     54  * {@link ConnectivityManager#registerNetworkCallback} calls.
     55  * It is used to direct traffic to the given {@code Network}, either on a {@link Socket} basis
     56  * through a targeted {@link SocketFactory} or process-wide via
     57  * {@link ConnectivityManager#bindProcessToNetwork}.
     58  */
     59 public class Network implements Parcelable {
     60 
     61     /**
     62      * @hide
     63      */
     64     public final int netId;
     65 
     66     // Objects used to perform per-network operations such as getSocketFactory
     67     // and openConnection, and a lock to protect access to them.
     68     private volatile NetworkBoundSocketFactory mNetworkBoundSocketFactory = null;
     69     // mLock should be used to control write access to mConnectionPool and mDns.
     70     // maybeInitHttpClient() must be called prior to reading either variable.
     71     private volatile ConnectionPool mConnectionPool = null;
     72     private volatile Dns mDns = null;
     73     private final Object mLock = new Object();
     74 
     75     // Default connection pool values. These are evaluated at startup, just
     76     // like the OkHttp code. Also like the OkHttp code, we will throw parse
     77     // exceptions at class loading time if the properties are set but are not
     78     // valid integers.
     79     private static final boolean httpKeepAlive =
     80             Boolean.parseBoolean(System.getProperty("http.keepAlive", "true"));
     81     private static final int httpMaxConnections =
     82             httpKeepAlive ? Integer.parseInt(System.getProperty("http.maxConnections", "5")) : 0;
     83     private static final long httpKeepAliveDurationMs =
     84             Long.parseLong(System.getProperty("http.keepAliveDuration", "300000"));  // 5 minutes.
     85 
     86     /**
     87      * @hide
     88      */
     89     public Network(int netId) {
     90         this.netId = netId;
     91     }
     92 
     93     /**
     94      * @hide
     95      */
     96     public Network(Network that) {
     97         this.netId = that.netId;
     98     }
     99 
    100     /**
    101      * Operates the same as {@code InetAddress.getAllByName} except that host
    102      * resolution is done on this network.
    103      *
    104      * @param host the hostname or literal IP string to be resolved.
    105      * @return the array of addresses associated with the specified host.
    106      * @throws UnknownHostException if the address lookup fails.
    107      */
    108     public InetAddress[] getAllByName(String host) throws UnknownHostException {
    109         return InetAddress.getAllByNameOnNet(host, netId);
    110     }
    111 
    112     /**
    113      * Operates the same as {@code InetAddress.getByName} except that host
    114      * resolution is done on this network.
    115      *
    116      * @param host
    117      *            the hostName to be resolved to an address or {@code null}.
    118      * @return the {@code InetAddress} instance representing the host.
    119      * @throws UnknownHostException
    120      *             if the address lookup fails.
    121      */
    122     public InetAddress getByName(String host) throws UnknownHostException {
    123         return InetAddress.getByNameOnNet(host, netId);
    124     }
    125 
    126     /**
    127      * A {@code SocketFactory} that produces {@code Socket}'s bound to this network.
    128      */
    129     private class NetworkBoundSocketFactory extends SocketFactory {
    130         private final int mNetId;
    131 
    132         public NetworkBoundSocketFactory(int netId) {
    133             super();
    134             mNetId = netId;
    135         }
    136 
    137         private Socket connectToHost(String host, int port, SocketAddress localAddress)
    138                 throws IOException {
    139             // Lookup addresses only on this Network.
    140             InetAddress[] hostAddresses = getAllByName(host);
    141             // Try all addresses.
    142             for (int i = 0; i < hostAddresses.length; i++) {
    143                 try {
    144                     Socket socket = createSocket();
    145                     if (localAddress != null) socket.bind(localAddress);
    146                     socket.connect(new InetSocketAddress(hostAddresses[i], port));
    147                     return socket;
    148                 } catch (IOException e) {
    149                     if (i == (hostAddresses.length - 1)) throw e;
    150                 }
    151             }
    152             throw new UnknownHostException(host);
    153         }
    154 
    155         @Override
    156         public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
    157             return connectToHost(host, port, new InetSocketAddress(localHost, localPort));
    158         }
    159 
    160         @Override
    161         public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
    162                 int localPort) throws IOException {
    163             Socket socket = createSocket();
    164             socket.bind(new InetSocketAddress(localAddress, localPort));
    165             socket.connect(new InetSocketAddress(address, port));
    166             return socket;
    167         }
    168 
    169         @Override
    170         public Socket createSocket(InetAddress host, int port) throws IOException {
    171             Socket socket = createSocket();
    172             socket.connect(new InetSocketAddress(host, port));
    173             return socket;
    174         }
    175 
    176         @Override
    177         public Socket createSocket(String host, int port) throws IOException {
    178             return connectToHost(host, port, null);
    179         }
    180 
    181         @Override
    182         public Socket createSocket() throws IOException {
    183             Socket socket = new Socket();
    184             bindSocket(socket);
    185             return socket;
    186         }
    187     }
    188 
    189     /**
    190      * Returns a {@link SocketFactory} bound to this network.  Any {@link Socket} created by
    191      * this factory will have its traffic sent over this {@code Network}.  Note that if this
    192      * {@code Network} ever disconnects, this factory and any {@link Socket} it produced in the
    193      * past or future will cease to work.
    194      *
    195      * @return a {@link SocketFactory} which produces {@link Socket} instances bound to this
    196      *         {@code Network}.
    197      */
    198     public SocketFactory getSocketFactory() {
    199         if (mNetworkBoundSocketFactory == null) {
    200             synchronized (mLock) {
    201                 if (mNetworkBoundSocketFactory == null) {
    202                     mNetworkBoundSocketFactory = new NetworkBoundSocketFactory(netId);
    203                 }
    204             }
    205         }
    206         return mNetworkBoundSocketFactory;
    207     }
    208 
    209     // TODO: This creates a connection pool and host resolver for
    210     // every Network object, instead of one for every NetId. This is
    211     // suboptimal, because an app could potentially have more than one
    212     // Network object for the same NetId, causing increased memory footprint
    213     // and performance penalties due to lack of connection reuse (connection
    214     // setup time, congestion window growth time, etc.).
    215     //
    216     // Instead, investigate only having one connection pool and host resolver
    217     // for every NetId, perhaps by using a static HashMap of NetIds to
    218     // connection pools and host resolvers. The tricky part is deciding when
    219     // to remove a map entry; a WeakHashMap shouldn't be used because whether
    220     // a Network is referenced doesn't correlate with whether a new Network
    221     // will be instantiated in the near future with the same NetID. A good
    222     // solution would involve purging empty (or when all connections are timed
    223     // out) ConnectionPools.
    224     private void maybeInitHttpClient() {
    225         synchronized (mLock) {
    226             if (mDns == null) {
    227                 mDns = new Dns() {
    228                     @Override
    229                     public List<InetAddress> lookup(String hostname) throws UnknownHostException {
    230                         return Arrays.asList(Network.this.getAllByName(hostname));
    231                     }
    232                 };
    233             }
    234             if (mConnectionPool == null) {
    235                 mConnectionPool = new ConnectionPool(httpMaxConnections,
    236                         httpKeepAliveDurationMs, TimeUnit.MILLISECONDS);
    237             }
    238         }
    239     }
    240 
    241     /**
    242      * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent
    243      * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}.
    244      *
    245      * @return a {@code URLConnection} to the resource referred to by this URL.
    246      * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS.
    247      * @throws IOException if an error occurs while opening the connection.
    248      * @see java.net.URL#openConnection()
    249      */
    250     public URLConnection openConnection(URL url) throws IOException {
    251         final ConnectivityManager cm = ConnectivityManager.getInstanceOrNull();
    252         if (cm == null) {
    253             throw new IOException("No ConnectivityManager yet constructed, please construct one");
    254         }
    255         // TODO: Should this be optimized to avoid fetching the global proxy for every request?
    256         final ProxyInfo proxyInfo = cm.getProxyForNetwork(this);
    257         java.net.Proxy proxy = null;
    258         if (proxyInfo != null) {
    259             proxy = proxyInfo.makeProxy();
    260         } else {
    261             proxy = java.net.Proxy.NO_PROXY;
    262         }
    263         return openConnection(url, proxy);
    264     }
    265 
    266     /**
    267      * Opens the specified {@link URL} on this {@code Network}, such that all traffic will be sent
    268      * on this Network. The URL protocol must be {@code HTTP} or {@code HTTPS}.
    269      *
    270      * @param proxy the proxy through which the connection will be established.
    271      * @return a {@code URLConnection} to the resource referred to by this URL.
    272      * @throws MalformedURLException if the URL protocol is not HTTP or HTTPS.
    273      * @throws IllegalArgumentException if the argument proxy is null.
    274      * @throws IOException if an error occurs while opening the connection.
    275      * @see java.net.URL#openConnection()
    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         OkUrlFactory okUrlFactory;
    282         // TODO: HttpHandler creates OkUrlFactory instances that share the default ResponseCache.
    283         // Could this cause unexpected behavior?
    284         if (protocol.equals("http")) {
    285             okUrlFactory = HttpHandler.createHttpOkUrlFactory(proxy);
    286         } else if (protocol.equals("https")) {
    287             okUrlFactory = HttpsHandler.createHttpsOkUrlFactory(proxy);
    288         } else {
    289             // OkHttp 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         OkHttpClient client = okUrlFactory.client();
    294         client.setSocketFactory(getSocketFactory()).setConnectionPool(mConnectionPool);
    295         // Let network traffic go via mDns
    296         client.setDns(mDns);
    297 
    298         return okUrlFactory.open(url);
    299     }
    300 
    301     /**
    302      * Binds the specified {@link DatagramSocket} to this {@code Network}. All data traffic on the
    303      * socket will be sent on this {@code Network}, irrespective of any process-wide network binding
    304      * set by {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be
    305      * connected.
    306      */
    307     public void bindSocket(DatagramSocket socket) throws IOException {
    308         // Query a property of the underlying socket to ensure that the socket's file descriptor
    309         // exists, is available to bind to a network and is not closed.
    310         socket.getReuseAddress();
    311         bindSocket(socket.getFileDescriptor$());
    312     }
    313 
    314     /**
    315      * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket
    316      * will be sent on this {@code Network}, irrespective of any process-wide network binding set by
    317      * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
    318      */
    319     public void bindSocket(Socket socket) throws IOException {
    320         // Query a property of the underlying socket to ensure that the socket's file descriptor
    321         // exists, is available to bind to a network and is not closed.
    322         socket.getReuseAddress();
    323         bindSocket(socket.getFileDescriptor$());
    324     }
    325 
    326     /**
    327      * Binds the specified {@link FileDescriptor} to this {@code Network}. All data traffic on the
    328      * socket represented by this file descriptor will be sent on this {@code Network},
    329      * irrespective of any process-wide network binding set by
    330      * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
    331      */
    332     public void bindSocket(FileDescriptor fd) throws IOException {
    333         try {
    334             final SocketAddress peer = Os.getpeername(fd);
    335             final InetAddress inetPeer = ((InetSocketAddress) peer).getAddress();
    336             if (!inetPeer.isAnyLocalAddress()) {
    337                 // Apparently, the kernel doesn't update a connected UDP socket's
    338                 // routing upon mark changes.
    339                 throw new SocketException("Socket is connected");
    340             }
    341         } catch (ErrnoException e) {
    342             // getpeername() failed.
    343             if (e.errno != OsConstants.ENOTCONN) {
    344                 throw e.rethrowAsSocketException();
    345             }
    346         } catch (ClassCastException e) {
    347             // Wasn't an InetSocketAddress.
    348             throw new SocketException("Only AF_INET/AF_INET6 sockets supported");
    349         }
    350 
    351         final int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId);
    352         if (err != 0) {
    353             // bindSocketToNetwork returns negative errno.
    354             throw new ErrnoException("Binding socket to network " + netId, -err)
    355                     .rethrowAsSocketException();
    356         }
    357     }
    358 
    359     /**
    360      * Returns a handle representing this {@code Network}, for use with the NDK API.
    361      */
    362     public long getNetworkHandle() {
    363         // The network handle is explicitly not the same as the netId.
    364         //
    365         // The netId is an implementation detail which might be changed in the
    366         // future, or which alone (i.e. in the absence of some additional
    367         // context) might not be sufficient to fully identify a Network.
    368         //
    369         // As such, the intention is to prevent accidental misuse of the API
    370         // that might result if a developer assumed that handles and netIds
    371         // were identical and passing a netId to a call expecting a handle
    372         // "just worked".  Such accidental misuse, if widely deployed, might
    373         // prevent future changes to the semantics of the netId field or
    374         // inhibit the expansion of state required for Network objects.
    375         //
    376         // This extra layer of indirection might be seen as paranoia, and might
    377         // never end up being necessary, but the added complexity is trivial.
    378         // At some future date it may be desirable to realign the handle with
    379         // Multiple Provisioning Domains API recommendations, as made by the
    380         // IETF mif working group.
    381         //
    382         // The HANDLE_MAGIC value MUST be kept in sync with the corresponding
    383         // value in the native/android/net.c NDK implementation.
    384         if (netId == 0) {
    385             return 0L;  // make this zero condition obvious for debugging
    386         }
    387         final long HANDLE_MAGIC = 0xfacade;
    388         return (((long) netId) << 32) | HANDLE_MAGIC;
    389     }
    390 
    391     // implement the Parcelable interface
    392     public int describeContents() {
    393         return 0;
    394     }
    395     public void writeToParcel(Parcel dest, int flags) {
    396         dest.writeInt(netId);
    397     }
    398 
    399     public static final Creator<Network> CREATOR =
    400         new Creator<Network>() {
    401             public Network createFromParcel(Parcel in) {
    402                 int netId = in.readInt();
    403 
    404                 return new Network(netId);
    405             }
    406 
    407             public Network[] newArray(int size) {
    408                 return new Network[size];
    409             }
    410     };
    411 
    412     @Override
    413     public boolean equals(Object obj) {
    414         if (obj instanceof Network == false) return false;
    415         Network other = (Network)obj;
    416         return this.netId == other.netId;
    417     }
    418 
    419     @Override
    420     public int hashCode() {
    421         return netId * 11;
    422     }
    423 
    424     @Override
    425     public String toString() {
    426         return Integer.toString(netId);
    427     }
    428 }
    429