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 javax.net.SocketFactory;
38 
39 import com.android.okhttp.ConnectionPool;
40 import com.android.okhttp.HttpHandler;
41 import com.android.okhttp.HttpsHandler;
42 import com.android.okhttp.OkHttpClient;
43 import com.android.okhttp.OkUrlFactory;
44 import 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  */
55 public 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      */
Network(int netId)85     public Network(int netId) {
86         this.netId = netId;
87     }
88 
89     /**
90      * @hide
91      */
Network(Network that)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      */
getAllByName(String host)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      */
getByName(String host)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 
NetworkBoundSocketFactory(int netId)128         public NetworkBoundSocketFactory(int netId) {
129             super();
130             mNetId = netId;
131         }
132 
connectToHost(String host, int port, SocketAddress localAddress)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
createSocket(String host, int port, InetAddress localHost, int localPort)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
createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)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
createSocket(InetAddress host, int port)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
createSocket(String host, int port)173         public Socket createSocket(String host, int port) throws IOException {
174             return connectToHost(host, port, null);
175         }
176 
177         @Override
createSocket()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      */
getSocketFactory()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.
maybeInitHttpClient()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      */
openConnection(URL url)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      */
openConnection(URL url, java.net.Proxy proxy)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      */
bindSocket(DatagramSocket socket)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      */
bindSocket(Socket socket)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      */
bindSocket(FileDescriptor fd)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      */
getNetworkHandle()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
describeContents()389     public int describeContents() {
390         return 0;
391     }
writeToParcel(Parcel dest, int flags)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
equals(Object obj)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
hashCode()417     public int hashCode() {
418         return netId * 11;
419     }
420 
421     @Override
toString()422     public String toString() {
423         return Integer.toString(netId);
424     }
425 }
426