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

问题描述

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

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

MySocketFactory 类实现为:

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,它描述了如何将值传递给 Builder.socketFactory() 允许我们通过覆盖无参数的 createSocket 方法将每个套接字绑定到特定地址。但是,docs for sslSocketFactory 没有提到任何关于能够绑定套接字的内容。当我使用上述代码运行我的应用程序时,永远不会生成 createSocket 日志条目,表明完全忽略了工厂。因此,永远无法到达 HTTP 端点。

如果我在没有包装 MySocketFactory 类的情况下尝试相同的设置 - 而只是将 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())。此示例代码应根据 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,0)
  }

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

  override fun createSocket(
    address: InetAddress,localPort: Int
  ): Socket {
    return systemFactory.createSocket(address,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)
    }
  }
}