RealConnection#connect()疑似无限重试问题
connectionRetryEnabled
通过okhttpClient.retryOnConnectionFailure设置为true
ConnectionSpecSelector#isFallbackPossible
- 初始为false
- 调用configureSecureSocket()方法对isFallbackPossible赋值,可能为true或者false。
establishProtocol()
这个方法有如下调用路径:
establishProtocol() -> connectTls() -> |configureSecureSocket()
|...
|sslSocket.startHandshake()
可以看出,当establishProtocol()调用时,会对isFallbackPossible进行赋值。
connectionFailed(e)
fun connectionFailed(e: IOException): Boolean {
// Any future attempt to connect using this strategy will be a fallback attempt.
isFallback = true
return when {
!isFallbackPossible -> false
// If there was a protocol problem, don't recover.
e is ProtocolException -> false
// If there was an interruption or timeout (SocketTimeoutException), don't recover.
// For the socket connect timeout case we do not try the same host with a different
// ConnectionSpec: we assume it is unreachable.
e is InterruptedIOException -> false
// If the problem was a CertificateException from the X509TrustManager, do not retry.
e is SSLHandshakeException && e.cause is CertificateException -> false
// e.g. a certificate pinning error.
e is SSLPeerUnverifiedException -> false
// Retry for all other SSL failures.
e is SSLException -> true
else -> false
}
}
当isFallbackPossible == true 且e is SSLException == true时,connectionFailed()返回true
connection()方法疑似无限循环的流程
fun connect(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean,
call: Call,
eventListener: EventListener
) {
//..
while (true) {
try {
if (route.requiresTunnel()) {
// A
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)
if (rawSocket == null) {
// We were unable to connect the tunnel but properly closed down our resources.
break
}
} else {
connectSocket(connectTimeout, readTimeout, call, eventListener)
}
// B
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
eventListener.connectEnd(call, route.socketAddress, route.proxy, protocol)
// C
break
} catch (e: IOException) {
//...
if (routeException == null) {
routeException = RouteException(e)
} else {
routeException.addConnectException(e)
}
// D
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException
}
}
}
//...
}
前置:
下面讨论抛出SSLExcetpion时,指的是当isFallbackPossible == true时,这个SSLException需要能让connectionFailed()进入返回true的分支。
首次循环:
- 执行到establishProtocol(),isFallbackPossible被赋值为true
- 在isFallbackPossible赋值以后到// C之前抛出SSLException;
- 进入connectionFailed(e),根据isFallbackPossible和异常的值,这里返回true
- 于是进入第二次循环
第二次循环:
- 执行到connectTunnel()抛出SSLExcetpion
- 跳过establishProtocol()执行,于是isFallbackPossible当值保持不变,为true
- 进入connectionFailed(e),根据isFallbackPossible和异常的值,这里返回true
问题出现:
如果第二次循环的场景重复出现,connect()方法就会进入无限循环。
connectTunnel()抛出SSLException的可能性
connectTunnel()方法内部调用中,可能抛出SSLException的点大多在库的调用者实现的代码中。例如:
//RealConnection#connectSocket()
/** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
@Throws(IOException::class)
private fun connectSocket(
connectTimeout: Int,
readTimeout: Int,
call: Call,
eventListener: EventListener
) {
val proxy = route.proxy
val address = route.address
val rawSocket = when (proxy.type()) {
Proxy.Type.DIRECT, Proxy.Type.HTTP -> address.socketFactory.createSocket()!!// 可能的抛出点
else -> Socket(proxy)
}
this.rawSocket = rawSocket
// 可能的抛出点
eventListener.connectStart(call, route.socketAddress, proxy)
rawSocket.soTimeout = readTimeout
//....
}
在isFallbackPossible赋值以后到// C之前抛出SSLException的可能性
疑似点,ai说可能但是不确定:
例如:
//RealConnection#connectTls()
@Throws(IOException::class)
private fun connectTls(connectionSpecSelector: ConnectionSpecSelector) {
val address = route.address
val sslSocketFactory = address.sslSocketFactory
var success = false
var sslSocket: SSLSocket? = null
try {
// Create the wrapper over the connected socket.
sslSocket = sslSocketFactory!!.createSocket(
rawSocket, address.url.host, address.url.port, true /* autoClose */) as SSLSocket
// Configure the socket's ciphers, TLS versions, and extensions.
val connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket)
if (connectionSpec.supportsTlsExtensions) {
Platform.get().configureTlsExtensions(sslSocket, address.url.host, address.protocols)
}
// 疑似
// Force handshake. This can throw!
sslSocket.startHandshake()
// block for session establishment
val sslSocketSession = sslSocket.session
val unverifiedHandshake = sslSocketSession.handshake()
//..
val certificatePinner = address.certificatePinner!!
handshake = Handshake(unverifiedHandshake.tlsVersion, unverifiedHandshake.cipherSuite,
unverifiedHandshake.localCertificates) {
certificatePinner.certificateChainCleaner!!.clean(unverifiedHandshake.peerCertificates,
address.url.host)
}
//..
// Success! Save the handshake and the ALPN protocol.
val maybeProtocol = if (connectionSpec.supportsTlsExtensions) {
// 疑似
Platform.get().getSelectedProtocol(sslSocket)
} else {
null
}
//..
} finally {
if (sslSocket != null) {
// 疑似
Platform.get().afterHandshake(sslSocket)
}
//..
}
}
库的调用者实现的代码:
例如:
// RealConnection#connect()
eventListener.connectEnd(call, route.socketAddress, route.proxy, protocol)