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