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      */
Network(int netId)89     public Network(int netId) {
90         this.netId = netId;
91     }
92 
93     /**
94      * @hide
95      */
Network(Network that)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      */
getAllByName(String host)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      */
getByName(String host)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 
NetworkBoundSocketFactory(int netId)132         public NetworkBoundSocketFactory(int netId) {
133             super();
134             mNetId = netId;
135         }
136 
connectToHost(String host, int port, SocketAddress localAddress)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
createSocket(String host, int port, InetAddress localHost, int localPort)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
createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)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
createSocket(InetAddress host, int port)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
createSocket(String host, int port)177         public Socket createSocket(String host, int port) throws IOException {
178             return connectToHost(host, port, null);
179         }
180 
181         @Override
createSocket()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      */
getSocketFactory()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.
maybeInitHttpClient()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      */
openConnection(URL url)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      */
openConnection(URL url, java.net.Proxy proxy)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      */
bindSocket(DatagramSocket socket)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      */
bindSocket(Socket socket)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      */
bindSocket(FileDescriptor fd)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      */
getNetworkHandle()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
describeContents()392     public int describeContents() {
393         return 0;
394     }
writeToParcel(Parcel dest, int flags)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
equals(Object obj)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
hashCode()420     public int hashCode() {
421         return netId * 11;
422     }
423 
424     @Override
toString()425     public String toString() {
426         return Integer.toString(netId);
427     }
428 }
429