我可以使用 OkHttp 将本地 IP 地址绑定到我的 SSLSocketFactory 吗?

Can I bind a local IP address to my SSLSocketFactory with OkHttp?

我正在努力让我的 OkHttpClient 在 Android 上使用自定义证书发出 HTTPS 请求,同时绑定到特定网络接口的本地地址。我目前的尝试使用以下 OkHttpClient:

val client = OkHttpClient.Builder()
        .readTimeout(2, TimeUnit.SECONDS)
        .connectTimeout(2, TimeUnit.SECONDS)
        .sslSocketFactory(
            MySocketFactory(
                getSslContext().socketFactory,
                localAddress
            ),
            MyTrustManager()
        )
        .build()

MySocketFactoryclass实现为:

class MySocketFactory(
    private val delegate: SSLSocketFactory, 
    private val localAddress: InetAddress
) : SSLSocketFactory() {
    override fun createSocket(): Socket {
        Timber.i("createSocket has been called!")
        val socket = delegate.createSocket()
        socket.bind(InetSocketAddress(localAddress, 0))
        return socket
    }

[other overrides of the various abstract createSocket methods, each of which throws an UnsupportedOperationException]

    override fun getDefaultCipherSuites() = delegate.defaultCipherSuites

    override fun getSupportedCipherSuites() = delegate.supportedCipherSuites
}

这主要是基于 documentation from the OkHttp GitHub site which describes how passing a value to Builder.socketFactory() allows us to bind each socket to a specific address by overriding the parameter-less createSocket method. However, the docs for sslSocketFactory 没有提及任何关于能够绑定套接字的内容。当我使用上面的代码 运行 我的应用程序时, createSocket 日志条目从未生成,表明工厂被完全忽略。因此,永远无法到达 HTTP 端点。

如果我在没有包装器 MySocketFactory class 的情况下尝试相同的设置 - 而不是直接将 getSslContext().socketFactory 传递到 Builder.sslSocketFactory - 那么我可以很好地联系端点,假设当时我的机器上只有一个本地地址。

所以我的问题是:这可能与自定义 SSLSocketFactory 相关吗?

是的!但不是自定义 SSLSocketFactory,而是自定义常规 SocketFactory。当 OkHttp 创建任何套接字时,它总是首先使用常规的 SocketFactory,然后可能用 SSL 包装它。这对于 TLS 隧道是必需的,但无处不在。

https://github.com/yschimke/okurl/blob/0abaa8510dd5466d5e9a08ebe33a009c491749bf/src/main/kotlin/com/baulsupp/okurl/network/InterfaceSocketFactory.kt

使用builder.socketFactory(getSocketFactory())。此示例代码应 select 基于 IP 或名称。

class InterfaceSocketFactory(private val localAddress: InetAddress) : SocketFactory() {
  private val systemFactory = getDefault()

  override fun createSocket(): Socket {
    val s = systemFactory.createSocket()
    s.bind(InetSocketAddress(localAddress, 0))
    return s
  }

  override fun createSocket(host: String, port: Int): Socket {
    return systemFactory.createSocket(host, port, localAddress, 0)
  }

  override fun createSocket(address: InetAddress, port: Int): Socket {
    return systemFactory.createSocket(address, port, localAddress, 0)
  }

  override fun createSocket(
    host: String,
    port: Int,
    localAddr: InetAddress,
    localPort: Int
  ): Socket {
    return systemFactory.createSocket(host, port, localAddr, localPort)
  }

  override fun createSocket(
    address: InetAddress,
    port: Int,
    localAddr: InetAddress,
    localPort: Int
  ): Socket {
    return systemFactory.createSocket(address, port, localAddr, localPort)
  }

  companion object {

    fun byName(ipOrInterface: String): SocketFactory? {
      val localAddress = try {
        // example 192.168.0.51
        InetAddress.getByName(ipOrInterface)
      } catch (uhe: UnknownHostException) {
        // example en0
        val networkInterface = NetworkInterface.getByName(ipOrInterface) ?: return null

        networkInterface.inetAddresses.nextElement()
      }

      return InterfaceSocketFactory(localAddress)
    }
  }
}