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

I'm working to get my OkHttpClient on Android to make its HTTPS requests using a custom certificate whilst bound to a specific network interface's local address. My current attempt at this uses the following OkHttpClient:
val client = OkHttpClient.Builder()
.readTimeout(2, TimeUnit.SECONDS)
.connectTimeout(2, TimeUnit.SECONDS)
.sslSocketFactory(
MySocketFactory(
getSslContext().socketFactory,
localAddress
),
MyTrustManager()
)
.build()
The MySocketFactory class is implemented as:
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
}
This is mostly based on the 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 do not mention anything about being able to bind sockets. When I run my app with the above code, the createSocket log entry is never generated, indicating that the factory is completely ignored. As a result, the HTTP endpoint is never reached.
If I try this same setup without the wrapper MySocketFactory class - instead just passing getSslContext().socketFactory directly into Builder.sslSocketFactory - then I can contact the endpoint perfectly well, assuming I only have the single local address on my machine at the time.
So my question: is this possible to do with a custom SSLSocketFactory?

Yes! But rather than customizing the SSLSocketFactory, customize the regular SocketFactory. When OkHttp creates any socket it always uses the regular SocketFactory first, and then potentially wraps that with SSL. This is necessary for TLS tunnels but used everywhere.

https://github.com/yschimke/okurl/blob/0abaa8510dd5466d5e9a08ebe33a009c491749bf/src/main/kotlin/com/baulsupp/okurl/network/InterfaceSocketFactory.kt
Use builder.socketFactory(getSocketFactory()). This example code should select based on IP or name.
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)
}
}
}

Related

Okio Throttler integration with OkHttp

My team is suffering from this issue with slack integration to upload files, so following the comments in that issue I would like to throttle the requests in our Kotlin implementation.
I am trying to integrate Okio Throttler within an OkHttp interceptor, so I have the setup:
val client = OkHttpClient.Builder()
.retryOnConnectionFailure(false)
.addInterceptor { chain ->
val request = chain.request()
val originalRequestBody = request.body
val newRequest = if (originalRequestBody != null) {
val wrappedRequestBody = ThrottledRequestBody(originalRequestBody)
request.newBuilder()
.method(request.method, wrappedRequestBody)
.build()
} else {
request
}
chain.proceed(newRequest)
}
.build()
class ThrottledRequestBody(private val delegate: RequestBody) : RequestBody() {
private val throttler = Throttler().apply {
bytesPerSecond(1024, 1024 * 4, 1024 * 8)
}
override fun contentType(): MediaType? {
return delegate.contentType()
}
override fun writeTo(sink: BufferedSink) {
delegate.writeTo(throttler.sink(sink).buffer())
}
}
It seems throttler.sink returns a Sink, but a BufferedSink is required to the method delegate.writeTo, so I called buffer() to get that BufferedSink.
Am I doing it wrong ? Is the call for .buffer() breaking the integration?
It's almost perfect. You just need to flush the buffer when you're done otherwise it'll finish with a few bytes inside.
override fun writeTo(sink: BufferedSink) {
throttler.sink(sink).buffer().use {
delegate.writeTo(it)
}
}

Capturing ElasticsearchSink Exceptions in Flink

I've recently been encountering some issues that I've noticed in the logs
of my Flink job that handles writing to an Elasticsearch index. I was
hoping to leverage some of the metrics that Flink exposes (or piggyback on
them) to update metric counters when I encounter specific kinds of errors.
val builder = ElasticsearchSink.Builder(...)
builder.setFailureHandler { actionRequest, throwable, _, _ ->
// Log error here (and update metrics via metricGroup.counter(...)
}
return builder.build()
Currently, I don't have any "context" when the callback for the setFailureHandler occurs, and while I can log it, ideally I'd like to expose a metric to track how frequently this is occurring:
builder.setFailureHandler ( actionRequest, throwable, _, _ ->
elasticExceptionsCounter.inc()
}
One additional wrinkle here is that my specific scenario relies on dynamically creating and handling these sinks via a router like the following:
class DynamicElasticsearchSink<ElementT, RouteT, SinkT : ElasticsearchSinkBase<ElementT, out AutoCloseable>>(
private val sinkRouter: ElasticsearchSinkRouter<ElementT, RouteT, SinkT>
) : RichSinkFunction<ElementT>(), CheckpointedFunction {
// Store a reference to all of the current routes
private val sinkRoutes: MutableMap<RouteT, SinkT> = ConcurrentHashMap()
private lateinit var configuration: Configuration
override fun open(parameters: Configuration) {
configuration = parameters
}
override fun invoke(value: ElementT, context: SinkFunction.Context) {
val route = sinkRouter.getRoute(value)
var sink = sinkRoutes[route]
if (sink == null) {
// Build a new sink for this key and cache it for later use based on incoming records
sink = sinkRouter.createSink(route, value)
sink.runtimeContext = runtimeContext
sink.open(configuration)
sinkRoutes[route] = sink
}
sink.invoke(value, context)
}
// Omitted for brevity
}
and the sinkRouter.createSink() looks like the following:
override fun createSink(cacheKey: String, element: JsonObject): ElasticsearchSink<JsonObject> {
return buildSinkFromRoute(element)
}
private fun buildSinkFromRoute(element: JsonObject): ElasticsearchSink<JsonObject> {
val builder = ElasticsearchSink.Builder(
buildHostsFromElement(element),
ElasticsearchRoutingFunction()
)
// Various configuration omitted for brevity
builder.setFailureHandler { actionRequest, throwable, _, _ ->
// Here's where I'd like to capture the failures and record them as metrics
}
return builder.build()
}
Is there a way to support this currently or what options are available for handing this?

Proper way to call a suspended function in ktor

i would like to start a process is active while my ktor server is up.
suspend fun doWork(){
while(true){
delay(2000L)
printit("I did work")
}
}
I have read that best practice is to avoid runBlocking and GlobalScope.
so i think i should create a custom scope for the coroutine to run in.
(the job is using some db calls so i did this)
val scope = CoroutineScope(Job() + Dispatchers.IO)
i then call it from my main function
fun main() {
val runningJob = scope.launch{
doWork()
}
embeddedServer(Netty, port = 8080, host = "127.0.0.1") {
routing {
get("/") {
call.respondHtml(HttpStatusCode.OK, HTML::index)
}
static("/static") {
resources()
}
}
}.start(wait = true)
}
Is this the proper way to do this? Most of the coroutine examples kind of leave out calling from non suspend functions.
Thank you in advance :)

How to combine CircuitBreaker with TimeLimiter and Bulkhead?

I have a service that calls a dependency via REST. Service and dependency are part of a microservice architecture, so I'd like to use resilience patterns. My goals are:
Have a circuit-breaker to protect the dependency when it's struggling
Limit the time the call can run. The service has an SLA and has to answer in a certain time. On timeout we use the fallback value.
Limit the number of concurrent calls to the dependency. Usually the rate of calls is low and the responses are fast, but we want to protect the dependency against bursts and queue requests inside the service.
Below is my current code. It works, but ideally I'd like to use the TimeLimiter and Bulkhead classes as they seem to be built to work together.
How can I write this better?
#Component
class FooService(#Autowired val circuitBreakerRegistry: CircuitBreakerRegistry)
{
...
// State machine to take load off the dependency when slow or unresponsive
private val circuitBreaker = circuitBreakerRegistry
.circuitBreaker("fooService")
// Limit parallel requests to dependency
private var semaphore = Semaphore(maxParallelRequests)
// The protected function
private suspend fun makeHttpCall(customerId: String): Boolean {
val client = webClientProvider.getCachedWebClient(baseUrl)
val response = client
.head()
.uri("/the/request/url")
.awaitExchange()
return when (val status = response.rawStatusCode()) {
200 -> true
204 -> false
else -> throw Exception(
"Foo service responded with invalid status code: $status"
)
}
}
// Main function
suspend fun isFoo(someId: String): Boolean {
try {
return circuitBreaker.executeSuspendFunction {
semaphore.withPermit {
try {
withTimeout(timeoutMs) {
makeHttpCall(someId)
}
} catch (e: TimeoutCancellationException) {
// This exception has to be converted because
// the circuit-breaker ignores CancellationException
throw Exception("Call to foo service timed out")
}
}
}
} catch (e: CallNotPermittedException) {
logger.error { "Call to foo blocked by circuit breaker" }
} catch (e: Exception) {
logger.error { "Exception while calling foo service: ${e.message}" }
}
// Fallback
return true
}
}
Ideally I'd like to write something like the docs describe for Flows:
// Main function
suspend fun isFoo(someId: String): Boolean {
return monoOf(makeHttpCall(someId))
.bulkhead(bulkhead)
.timeLimiter(timeLimiter)
.circuitBreaker(circuitBreaker)
}
You could also use Resilience4j's Bulkhead instead of your own Semaphore and Resilience4j's TimeLimiter.
You can stack you CircuitBreaker with bulkhead.executeSuspendFunction and timelimiter.executeSuspendFunction.

Events not firing? Using java socket.io client & netty-socketio on server

I know the client and server are connecting because my connect/disconnect events are firing. However, my custom events are not. I am using socket.io java client, and netty-socketio on the server. I usually use the socket.io javascript library which works seamlessly, so I am a bit lost as to why this is happening. I am writing this in Kotlin.
Client-Side
fun connectToServer(ipAddress : String)
{
socket = IO.socket("$ipAddress")
socket!!.on(Socket.EVENT_CONNECT) { obj ->
println("Connected To Server!!!")
}.on(EventNames.signOn) { obj ->
println(EventNames.signOn)
//cast value to string from server, hope for encrypted password
val encryptedPassword = obj[0] as String
when(encryptedPassword)
{
"no user" -> {
}
else -> {
val result = encryptedPassword!!.split("OR")
val isMatch = passwordTextField.text == dataProcessing.Encryption3().decryptValue("decrypt", result[0],result[1])
if(isMatch)
{
}
}
}
println("Encrypted Password: "+encryptedPassword)
}
// socket!!.on(Socket.EVENT_DISCONNECT, object : Emitter.Listener {
//
// override fun call(vararg args: Any) {}
//
// })
socket!!.connect()
// socket!!.open()
// socket!!.emit(Socket.EVENT_CONNECT, "Hello!")
socket!!.send("hey")
socket!!.emit(EventNames.requestClientSignOn, usernameTextField.text)
}
Server-Side
#Throws(InterruptedException::class, UnsupportedEncodingException::class)
fun server()
{
val config = Configuration()
config.setHostname("localhost")
config.setPort(PORT)
server = SocketIOServer(config)
server!!.addConnectListener {
println("Hello World!")
}
server!!.addEventListener(EventNames.requestClientSignOn, String::class.java) { client, data, ackRequest ->
println("Hello from requestClientSignOn..")
}
server!!.addDisconnectListener {
println("Client Disconnecting...")
}
server!!.addConnectListener {
println("client connected!! client: $it")
}
server!!.start()
You cannot use lambda expression in your event listeners, using netty-socketio on the sever.
Using the traditional EventListener solves this problem. I also converted the server to Kotlin, as it was easier to use the demo project as a reference.
server.addEventListener(EventNames.requestClientSignOn, String.class, new DataListener<String>() {
#Override
public void onData(SocketIOClient client, String username, AckRequest ackRequest) {
String isEncryptedPassword = new KOTS_EmployeeManager().getKOTS_User(KOTS_EmployeeManager.kotsUserType.CLIENT, username)
if(isEncryptedPassword != null)
{
//send back ack with encrypted password
ackRequest.sendAckData(isEncryptedPassword);
}else{
//send back ack with no user string
ackRequest.sendAckData("no user");
}
}
});

Resources