• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17 package com.squareup.okhttp.internal.io;
18 
19 import com.squareup.okhttp.Address;
20 import com.squareup.okhttp.CertificatePinner;
21 import com.squareup.okhttp.Connection;
22 import com.squareup.okhttp.ConnectionSpec;
23 import com.squareup.okhttp.Handshake;
24 import com.squareup.okhttp.HttpUrl;
25 import com.squareup.okhttp.Protocol;
26 import com.squareup.okhttp.Request;
27 import com.squareup.okhttp.Response;
28 import com.squareup.okhttp.Route;
29 import com.squareup.okhttp.internal.ConnectionSpecSelector;
30 import com.squareup.okhttp.internal.Platform;
31 import com.squareup.okhttp.internal.Util;
32 import com.squareup.okhttp.internal.Version;
33 import com.squareup.okhttp.internal.framed.FramedConnection;
34 import com.squareup.okhttp.internal.http.Http1xStream;
35 import com.squareup.okhttp.internal.http.OkHeaders;
36 import com.squareup.okhttp.internal.http.RouteException;
37 import com.squareup.okhttp.internal.http.StreamAllocation;
38 import com.squareup.okhttp.internal.tls.CertificateChainCleaner;
39 import com.squareup.okhttp.internal.tls.OkHostnameVerifier;
40 import com.squareup.okhttp.internal.tls.TrustRootIndex;
41 import java.io.IOException;
42 import java.lang.ref.Reference;
43 import java.net.ConnectException;
44 import java.net.Proxy;
45 import java.net.Socket;
46 import java.net.SocketTimeoutException;
47 import java.net.UnknownServiceException;
48 import java.security.cert.Certificate;
49 import java.security.cert.X509Certificate;
50 import java.util.ArrayList;
51 import java.util.List;
52 import java.util.concurrent.TimeUnit;
53 import javax.net.ssl.SSLPeerUnverifiedException;
54 import javax.net.ssl.SSLSocket;
55 import javax.net.ssl.SSLSocketFactory;
56 import javax.net.ssl.X509TrustManager;
57 import okio.BufferedSink;
58 import okio.BufferedSource;
59 import okio.Okio;
60 import okio.Source;
61 
62 import static com.squareup.okhttp.internal.Util.closeQuietly;
63 import static java.net.HttpURLConnection.HTTP_OK;
64 import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
65 import static java.util.concurrent.TimeUnit.MILLISECONDS;
66 
67 public final class RealConnection implements Connection {
68   private final Route route;
69 
70   /** The low-level TCP socket. */
71   private Socket rawSocket;
72 
73   /**
74    * The application layer socket. Either an {@link SSLSocket} layered over {@link #rawSocket}, or
75    * {@link #rawSocket} itself if this connection does not use SSL.
76    */
77   public Socket socket;
78   private Handshake handshake;
79   private Protocol protocol;
80   public volatile FramedConnection framedConnection;
81   public int streamCount;
82   public BufferedSource source;
83   public BufferedSink sink;
84   public final List<Reference<StreamAllocation>> allocations = new ArrayList<>();
85   public boolean noNewStreams;
86   public long idleAtNanos = Long.MAX_VALUE;
87 
RealConnection(Route route)88   public RealConnection(Route route) {
89     this.route = route;
90   }
91 
connect(int connectTimeout, int readTimeout, int writeTimeout, List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled)92   public void connect(int connectTimeout, int readTimeout, int writeTimeout,
93       List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
94     if (protocol != null) throw new IllegalStateException("already connected");
95 
96     RouteException routeException = null;
97     ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
98     Proxy proxy = route.getProxy();
99     Address address = route.getAddress();
100 
101     if (route.getAddress().getSslSocketFactory() == null
102         && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
103       throw new RouteException(new UnknownServiceException(
104           "CLEARTEXT communication not supported: " + connectionSpecs));
105     }
106 
107     while (protocol == null) {
108       try {
109         rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
110             ? address.getSocketFactory().createSocket()
111             : new Socket(proxy);
112         connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
113       } catch (IOException e) {
114         Util.closeQuietly(socket);
115         Util.closeQuietly(rawSocket);
116         socket = null;
117         rawSocket = null;
118         source = null;
119         sink = null;
120         handshake = null;
121         protocol = null;
122 
123         if (routeException == null) {
124           routeException = new RouteException(e);
125         } else {
126           routeException.addConnectException(e);
127         }
128 
129         if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
130           throw routeException;
131         }
132       }
133     }
134   }
135 
136   /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
connectSocket(int connectTimeout, int readTimeout, int writeTimeout, ConnectionSpecSelector connectionSpecSelector)137   private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
138       ConnectionSpecSelector connectionSpecSelector) throws IOException {
139     rawSocket.setSoTimeout(readTimeout);
140     try {
141       Platform.get().connectSocket(rawSocket, route.getSocketAddress(), connectTimeout);
142     } catch (ConnectException e) {
143       throw new ConnectException("Failed to connect to " + route.getSocketAddress());
144     }
145     source = Okio.buffer(Okio.source(rawSocket));
146     sink = Okio.buffer(Okio.sink(rawSocket));
147 
148     if (route.getAddress().getSslSocketFactory() != null) {
149       connectTls(readTimeout, writeTimeout, connectionSpecSelector);
150     } else {
151       protocol = Protocol.HTTP_1_1;
152       socket = rawSocket;
153     }
154 
155     if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
156       socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.
157 
158       FramedConnection framedConnection = new FramedConnection.Builder(true)
159           .socket(socket, route.getAddress().url().host(), source, sink)
160           .protocol(protocol)
161           .build();
162       framedConnection.sendConnectionPreface();
163 
164       // Only assign the framed connection once the preface has been sent successfully.
165       this.framedConnection = framedConnection;
166     }
167   }
168 
connectTls(int readTimeout, int writeTimeout, ConnectionSpecSelector connectionSpecSelector)169   private void connectTls(int readTimeout, int writeTimeout,
170       ConnectionSpecSelector connectionSpecSelector) throws IOException {
171     if (route.requiresTunnel()) {
172       createTunnel(readTimeout, writeTimeout);
173     }
174 
175     Address address = route.getAddress();
176     SSLSocketFactory sslSocketFactory = address.getSslSocketFactory();
177     boolean success = false;
178     SSLSocket sslSocket = null;
179     try {
180       // Create the wrapper over the connected socket.
181       sslSocket = (SSLSocket) sslSocketFactory.createSocket(
182           rawSocket, address.getUriHost(), address.getUriPort(), true /* autoClose */);
183 
184       // Configure the socket's ciphers, TLS versions, and extensions.
185       ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
186       if (connectionSpec.supportsTlsExtensions()) {
187         Platform.get().configureTlsExtensions(
188             sslSocket, address.getUriHost(), address.getProtocols());
189       }
190 
191       // Force handshake. This can throw!
192       sslSocket.startHandshake();
193       Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());
194 
195       // Verify that the socket's certificates are acceptable for the target host.
196       if (!address.getHostnameVerifier().verify(address.getUriHost(), sslSocket.getSession())) {
197         X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
198         throw new SSLPeerUnverifiedException("Hostname " + address.getUriHost() + " not verified:"
199             + "\n    certificate: " + CertificatePinner.pin(cert)
200             + "\n    DN: " + cert.getSubjectDN().getName()
201             + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
202       }
203 
204       // Check that the certificate pinner is satisfied by the certificates presented.
205       if (address.getCertificatePinner() != CertificatePinner.DEFAULT) {
206         TrustRootIndex trustRootIndex = trustRootIndex(address.getSslSocketFactory());
207         List<Certificate> certificates = new CertificateChainCleaner(trustRootIndex)
208             .clean(unverifiedHandshake.peerCertificates());
209         address.getCertificatePinner().check(address.getUriHost(), certificates);
210       }
211 
212       // Success! Save the handshake and the ALPN protocol.
213       String maybeProtocol = connectionSpec.supportsTlsExtensions()
214           ? Platform.get().getSelectedProtocol(sslSocket)
215           : null;
216       socket = sslSocket;
217       source = Okio.buffer(Okio.source(socket));
218       sink = Okio.buffer(Okio.sink(socket));
219       handshake = unverifiedHandshake;
220       protocol = maybeProtocol != null
221           ? Protocol.get(maybeProtocol)
222           : Protocol.HTTP_1_1;
223       success = true;
224     } catch (AssertionError e) {
225       if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
226       throw e;
227     } finally {
228       if (sslSocket != null) {
229         Platform.get().afterHandshake(sslSocket);
230       }
231       if (!success) {
232         closeQuietly(sslSocket);
233       }
234     }
235   }
236 
237   private static SSLSocketFactory lastSslSocketFactory;
238   private static TrustRootIndex lastTrustRootIndex;
239 
240   /**
241    * Returns a trust root index for {@code sslSocketFactory}. This uses a static, single-element
242    * cache to avoid redoing reflection and SSL indexing in the common case where most SSL
243    * connections use the same SSL socket factory.
244    */
trustRootIndex(SSLSocketFactory sslSocketFactory)245   private static synchronized TrustRootIndex trustRootIndex(SSLSocketFactory sslSocketFactory) {
246     if (sslSocketFactory != lastSslSocketFactory) {
247       X509TrustManager trustManager = Platform.get().trustManager(sslSocketFactory);
248       lastTrustRootIndex = Platform.get().trustRootIndex(trustManager);
249       lastSslSocketFactory = sslSocketFactory;
250     }
251     return lastTrustRootIndex;
252   }
253 
254   /**
255    * To make an HTTPS connection over an HTTP proxy, send an unencrypted
256    * CONNECT request to create the proxy connection. This may need to be
257    * retried if the proxy requires authorization.
258    */
createTunnel(int readTimeout, int writeTimeout)259   private void createTunnel(int readTimeout, int writeTimeout) throws IOException {
260     // Make an SSL Tunnel on the first message pair of each SSL + proxy connection.
261     Request tunnelRequest = createTunnelRequest();
262     HttpUrl url = tunnelRequest.httpUrl();
263     String requestLine = "CONNECT " + Util.hostHeader(url, true) + " HTTP/1.1";
264     while (true) {
265       Http1xStream tunnelConnection = new Http1xStream(null, source, sink);
266       source.timeout().timeout(readTimeout, MILLISECONDS);
267       sink.timeout().timeout(writeTimeout, MILLISECONDS);
268       tunnelConnection.writeRequest(tunnelRequest.headers(), requestLine);
269       tunnelConnection.finishRequest();
270       Response response = tunnelConnection.readResponse().request(tunnelRequest).build();
271       // The response body from a CONNECT should be empty, but if it is not then we should consume
272       // it before proceeding.
273       long contentLength = OkHeaders.contentLength(response);
274       if (contentLength == -1L) {
275         contentLength = 0L;
276       }
277       Source body = tunnelConnection.newFixedLengthSource(contentLength);
278       Util.skipAll(body, Integer.MAX_VALUE, TimeUnit.MILLISECONDS);
279       body.close();
280 
281       switch (response.code()) {
282         case HTTP_OK:
283           // Assume the server won't send a TLS ServerHello until we send a TLS ClientHello. If
284           // that happens, then we will have buffered bytes that are needed by the SSLSocket!
285           // This check is imperfect: it doesn't tell us whether a handshake will succeed, just
286           // that it will almost certainly fail because the proxy has sent unexpected data.
287           if (!source.buffer().exhausted() || !sink.buffer().exhausted()) {
288             throw new IOException("TLS tunnel buffered too many bytes!");
289           }
290           return;
291 
292         case HTTP_PROXY_AUTH:
293           tunnelRequest = OkHeaders.processAuthHeader(
294               route.getAddress().getAuthenticator(), response, route.getProxy());
295           if (tunnelRequest != null) continue;
296           throw new IOException("Failed to authenticate with proxy");
297 
298         default:
299           throw new IOException(
300               "Unexpected response code for CONNECT: " + response.code());
301       }
302     }
303   }
304 
305   /**
306    * Returns a request that creates a TLS tunnel via an HTTP proxy, or null if
307    * no tunnel is necessary. Everything in the tunnel request is sent
308    * unencrypted to the proxy server, so tunnels include only the minimum set of
309    * headers. This avoids sending potentially sensitive data like HTTP cookies
310    * to the proxy unencrypted.
311    */
createTunnelRequest()312   private Request createTunnelRequest() throws IOException {
313     return new Request.Builder()
314         .url(route.getAddress().url())
315         .header("Host", Util.hostHeader(route.getAddress().url(), true))
316         .header("Proxy-Connection", "Keep-Alive")
317         .header("User-Agent", Version.userAgent()) // For HTTP/1.0 proxies like Squid.
318         .build();
319   }
320 
321   /** Returns true if {@link #connect} has been attempted on this connection. */
isConnected()322   boolean isConnected() {
323     return protocol != null;
324   }
325 
getRoute()326   @Override public Route getRoute() {
327     return route;
328   }
329 
cancel()330   public void cancel() {
331     // Close the raw socket so we don't end up doing synchronous I/O.
332     Util.closeQuietly(rawSocket);
333   }
334 
getSocket()335   @Override public Socket getSocket() {
336     return socket;
337   }
338 
allocationLimit()339   public int allocationLimit() {
340     FramedConnection framedConnection = this.framedConnection;
341     return framedConnection != null
342         ? framedConnection.maxConcurrentStreams()
343         : 1;
344   }
345 
346   /** Returns true if this connection is ready to host new streams. */
isHealthy(boolean doExtensiveChecks)347   public boolean isHealthy(boolean doExtensiveChecks) {
348     if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) {
349       return false;
350     }
351 
352     if (framedConnection != null) {
353       return true; // TODO: check framedConnection.shutdown.
354     }
355 
356     if (doExtensiveChecks) {
357       try {
358         int readTimeout = socket.getSoTimeout();
359         try {
360           socket.setSoTimeout(1);
361           if (source.exhausted()) {
362             return false; // Stream is exhausted; socket is closed.
363           }
364           return true;
365         } finally {
366           socket.setSoTimeout(readTimeout);
367         }
368       } catch (SocketTimeoutException ignored) {
369         // Read timed out; socket is good.
370       } catch (IOException e) {
371         return false; // Couldn't read; socket is closed.
372       }
373     }
374 
375     return true;
376   }
377 
getHandshake()378   @Override public Handshake getHandshake() {
379     return handshake;
380   }
381 
382   /**
383    * Returns true if this is a SPDY connection. Such connections can be used
384    * in multiple HTTP requests simultaneously.
385    */
isMultiplexed()386   public boolean isMultiplexed() {
387     return framedConnection != null;
388   }
389 
getProtocol()390   @Override public Protocol getProtocol() {
391     return protocol != null ? protocol : Protocol.HTTP_1_1;
392   }
393 
toString()394   @Override public String toString() {
395     return "Connection{"
396         + route.getAddress().url().host() + ":" + route.getAddress().url().port()
397         + ", proxy="
398         + route.getProxy()
399         + " hostAddress="
400         + route.getSocketAddress()
401         + " cipherSuite="
402         + (handshake != null ? handshake.cipherSuite() : "none")
403         + " protocol="
404         + protocol
405         + '}';
406   }
407 }
408