Network.java revision e15db7c23d9e99e6f788a69d07a8436c6039bb08
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.net.NetworkUtils;
20import android.os.Parcelable;
21import android.os.Parcel;
22import android.system.ErrnoException;
23
24import java.io.FileDescriptor;
25import java.io.IOException;
26import java.net.DatagramSocket;
27import java.net.InetAddress;
28import java.net.InetSocketAddress;
29import java.net.MalformedURLException;
30import java.net.Socket;
31import java.net.SocketAddress;
32import java.net.SocketException;
33import java.net.UnknownHostException;
34import java.net.URL;
35import java.net.URLConnection;
36import java.net.URLStreamHandler;
37import java.util.concurrent.atomic.AtomicReference;
38import javax.net.SocketFactory;
39
40import com.android.okhttp.ConnectionPool;
41import com.android.okhttp.HostResolver;
42import com.android.okhttp.HttpHandler;
43import com.android.okhttp.HttpsHandler;
44import com.android.okhttp.OkHttpClient;
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#setProcessDefaultNetwork}.
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 mHostResolver.
66    // maybeInitHttpClient() must be called prior to reading either variable.
67    private volatile ConnectionPool mConnectionPool = null;
68    private volatile HostResolver mHostResolver = null;
69    private 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 (mHostResolver == null) {
223                mHostResolver = new HostResolver() {
224                    @Override
225                    public InetAddress[] getAllByName(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        maybeInitHttpClient();
248        String protocol = url.getProtocol();
249        OkHttpClient client;
250        // TODO: HttpHandler creates OkHttpClients that share the default ResponseCache.
251        // Could this cause unexpected behavior?
252        // TODO: Should the network's proxy be specified?
253        if (protocol.equals("http")) {
254            client = HttpHandler.createHttpOkHttpClient(null /* proxy */);
255        } else if (protocol.equals("https")) {
256            client = HttpsHandler.createHttpsOkHttpClient(null /* proxy */);
257        } else {
258            // OkHttpClient only supports HTTP and HTTPS and returns a null URLStreamHandler if
259            // passed another protocol.
260            throw new MalformedURLException("Invalid URL or unrecognized protocol " + protocol);
261        }
262        return client.setSocketFactory(getSocketFactory())
263                .setHostResolver(mHostResolver)
264                .setConnectionPool(mConnectionPool)
265                .open(url);
266    }
267
268    /**
269     * Binds the specified {@link DatagramSocket} to this {@code Network}. All data traffic on the
270     * socket will be sent on this {@code Network}, irrespective of any process-wide network binding
271     * set by {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be
272     * connected.
273     */
274    public void bindSocket(DatagramSocket socket) throws IOException {
275        // Apparently, the kernel doesn't update a connected UDP socket's routing upon mark changes.
276        if (socket.isConnected()) {
277            throw new SocketException("Socket is connected");
278        }
279        // Query a property of the underlying socket to ensure that the socket's file descriptor
280        // exists, is available to bind to a network and is not closed.
281        socket.getReuseAddress();
282        bindSocketFd(socket.getFileDescriptor$());
283    }
284
285    /**
286     * Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket
287     * will be sent on this {@code Network}, irrespective of any process-wide network binding set by
288     * {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be connected.
289     */
290    public void bindSocket(Socket socket) throws IOException {
291        // Apparently, the kernel doesn't update a connected TCP socket's routing upon mark changes.
292        if (socket.isConnected()) {
293            throw new SocketException("Socket is connected");
294        }
295        // Query a property of the underlying socket to ensure that the socket's file descriptor
296        // exists, is available to bind to a network and is not closed.
297        socket.getReuseAddress();
298        bindSocketFd(socket.getFileDescriptor$());
299    }
300
301    private void bindSocketFd(FileDescriptor fd) throws IOException {
302        int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId);
303        if (err != 0) {
304            // bindSocketToNetwork returns negative errno.
305            throw new ErrnoException("Binding socket to network " + netId, -err)
306                    .rethrowAsSocketException();
307        }
308    }
309
310    // implement the Parcelable interface
311    public int describeContents() {
312        return 0;
313    }
314    public void writeToParcel(Parcel dest, int flags) {
315        dest.writeInt(netId);
316    }
317
318    public static final Creator<Network> CREATOR =
319        new Creator<Network>() {
320            public Network createFromParcel(Parcel in) {
321                int netId = in.readInt();
322
323                return new Network(netId);
324            }
325
326            public Network[] newArray(int size) {
327                return new Network[size];
328            }
329    };
330
331    @Override
332    public boolean equals(Object obj) {
333        if (obj instanceof Network == false) return false;
334        Network other = (Network)obj;
335        return this.netId == other.netId;
336    }
337
338    @Override
339    public int hashCode() {
340        return netId * 11;
341    }
342
343    @Override
344    public String toString() {
345        return Integer.toString(netId);
346    }
347}
348