libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit a8413e5315b078a1833beb597de400ad1dbfe2ee
parent bb485263833445d93ae2d5bba71b55bfdc18367a
Author: MS <ms@taler.net>
Date:   Wed, 18 Jan 2023 22:42:57 +0100

Falling back to IPv4, when IPv6 isn't supported.

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 11+++++++++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt | 23-----------------------
Msandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt | 52+++++++++++++++++-----------------------------------
Mutil/build.gradle | 2++
Autil/src/main/kotlin/startServer.kt | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autil/src/test/kotlin/StartServerTest.kt | 32++++++++++++++++++++++++++++++++
6 files changed, 142 insertions(+), 60 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -24,7 +24,6 @@ import com.github.ajalt.clikt.parameters.arguments.argument import org.jetbrains.exposed.sql.transactions.transaction import org.slf4j.Logger import org.slf4j.LoggerFactory -import tech.libeufin.nexus.server.serverMain import tech.libeufin.util.CryptoUtil.hashpw import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.github.ajalt.clikt.parameters.types.int @@ -82,7 +81,15 @@ class Serve : CliktCommand("Run nexus HTTP server") { ) exitProcess(0) } - serverMain(port, localhostOnly, ipv4Only) + logger.info("Starting Nexus on port ${this.port}") + startServerWithIPv4Fallback( + options = StartServerOptions( + ipv4OnlyOpt = this.ipv4Only, + localhostOnlyOpt = this.localhostOnly, + portOpt = this.port + ), + app = nexusApp + ) } } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/server/NexusServer.kt @@ -1048,26 +1048,3 @@ val nexusApp: Application.() -> Unit = { } } } -fun serverMain(port: Int, localhostOnly: Boolean, ipv4Only: Boolean) { - val server = embeddedServer( - Netty, - environment = applicationEngineEnvironment { - connector { - this.port = port - this.host = if (localhostOnly) "127.0.0.1" else "0.0.0.0" - } - if (!ipv4Only) connector { - this.port = port - this.host = if (localhostOnly) "[::1]" else "[::]" - } - module(nexusApp) - } - ) - logger.info("LibEuFin Nexus running on port $port") - try { - server.start(wait = true) - } catch (e: BindException) { - logger.error(e.message) - exitProcess(1) - } -} diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Main.kt @@ -60,7 +60,6 @@ import org.w3c.dom.Document import startServer import tech.libeufin.util.* import java.math.BigDecimal -import java.net.BindException import java.net.URL import java.security.interfaces.RSAPublicKey import javax.xml.bind.JAXBContext @@ -355,8 +354,11 @@ class Serve : CliktCommand("Run sandbox HTTP server") { WITH_AUTH = auth setLogLevel(logLevel) if (WITH_AUTH && adminPassword == null) { - System.err.println("Error: auth is enabled, but env LIBEUFIN_SANDBOX_ADMIN_PASSWORD is not." - + " (Option --no-auth exists for tests)") + System.err.println( + "Error: auth is enabled, but env " + + "LIBEUFIN_SANDBOX_ADMIN_PASSWORD is not." + + " (Option --no-auth exists for tests)" + ) exitProcess(1) } execThrowableOrTerminate { dbCreateTables(getDbConnFromEnv(SANDBOX_DB_ENV_VAR_NAME)) } @@ -376,7 +378,16 @@ class Serve : CliktCommand("Run sandbox HTTP server") { } SMS_TAN_CMD = smsTan EMAIL_TAN_CMD = emailTan - serverMain(port, localhostOnly, ipv4Only) + + logger.info("Starting Sandbox on port ${this.port}") + startServerWithIPv4Fallback( + options = StartServerOptions( + ipv4OnlyOpt = this.ipv4Only, + localhostOnlyOpt = this.localhostOnly, + portOpt = this.port + ), + app = sandboxApp + ) } } @@ -1606,34 +1617,4 @@ val sandboxApp: Application.() -> Unit = { } } } -} - -fun serverMain(port: Int, localhostOnly: Boolean, ipv4Only: Boolean) { - val server = embeddedServer( - Netty, - environment = applicationEngineEnvironment{ - connector { - this.port = port - this.host = if (localhostOnly) "127.0.0.1" else "0.0.0.0" - } - if (!ipv4Only) connector { - this.port = port - this.host = if (localhostOnly) "[::1]" else "[::]" - } - // parentCoroutineContext = Dispatchers.Main - module(sandboxApp) - }, - configure = { - connectionGroupSize = 1 - workerGroupSize = 1 - callGroupSize = 1 - } - ) - logger.info("LibEuFin Sandbox running on port $port") - try { - server.start(wait = true) - } catch (e: BindException) { - logger.error(e.message) - exitProcess(1) - } -} +} +\ No newline at end of file diff --git a/util/build.gradle b/util/build.gradle @@ -56,6 +56,8 @@ dependencies { testImplementation group: 'junit', name: 'junit', version: '4.13.2' testImplementation 'org.jetbrains.kotlin:kotlin-test-junit:1.5.21' testImplementation 'org.jetbrains.kotlin:kotlin-test:1.5.21' + testImplementation project(":sandbox") + testImplementation project(":nexus") } diff --git a/util/src/main/kotlin/startServer.kt b/util/src/main/kotlin/startServer.kt @@ -0,0 +1,81 @@ +package tech.libeufin.util + +import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.netty.* +import io.netty.channel.unix.Errors +import logger +import java.net.BindException +import kotlin.system.exitProcess + +const val EAFNOSUPPORT = -97 // Netty defines errors negatively. +class StartServerOptions( + var ipv4OnlyOpt: Boolean, + val localhostOnlyOpt: Boolean, + val portOpt: Int +) + +// Core function starting the server. +private fun serverMain(options: StartServerOptions, app: Application.() -> Unit) { + val server = embeddedServer( + Netty, + environment = applicationEngineEnvironment { + connector { + this.port = options.portOpt + this.host = if (options.localhostOnlyOpt) "127.0.0.1" else "0.0.0.0" + } + if (!options.ipv4OnlyOpt) connector { + this.port = options.portOpt + this.host = if (options.localhostOnlyOpt) "[::1]" else "[::]" + } + module(app) + }, + configure = { + connectionGroupSize = 1 + workerGroupSize = 1 + callGroupSize = 1 + } + ) + /** + * NOTE: excepted server still need the stop(), otherwise + * it leaves the port locked and prevents the IPv4 retry. + */ + try { + server.start(wait = true) + } catch (e: Exception) { + server.stop() + logger.debug("Rethrowing: ${e.message}") + throw e // Rethrowing for retry policies. + } +} + +// Wrapper function that retries when IPv6 fails. +fun startServerWithIPv4Fallback( + options: StartServerOptions, + app: Application.() -> Unit +) { + var maybeRetry = false + try { + serverMain(options, app) + } catch (e: Exception) { + logger.warn(e.message) + // Find reasons to retry. + if (e is Errors.NativeIoException) { + logger.debug("errno: ${e.expectedErr()}") + if ((e.expectedErr() == EAFNOSUPPORT) && (!options.ipv4OnlyOpt)) + maybeRetry = true + } + } + // Fail, if no retry policy applies. The catch block above logged the error. + if (!maybeRetry) { + exitProcess(1) + } + logger.info("Retrying to start the server on IPv4") + options.ipv4OnlyOpt = true + try { + serverMain(options, app) + } catch (e: Exception) { + logger.error(e.message) + exitProcess(1) + } +} +\ No newline at end of file diff --git a/util/src/test/kotlin/StartServerTest.kt b/util/src/test/kotlin/StartServerTest.kt @@ -0,0 +1,31 @@ +import org.junit.Ignore +import org.junit.Test +import tech.libeufin.nexus.server.nexusApp +import tech.libeufin.sandbox.sandboxApp +import tech.libeufin.util.StartServerOptions +import tech.libeufin.util.startServerWithIPv4Fallback + +class StartServerTest { + @Test + fun sandboxStart() { + startServerWithIPv4Fallback( + options = StartServerOptions( + ipv4OnlyOpt = false, + localhostOnlyOpt = false, + portOpt = 5000 + ), + app = sandboxApp + ) + } + @Test + fun nexusStart() { + startServerWithIPv4Fallback( + options = StartServerOptions( + ipv4OnlyOpt = false, + localhostOnlyOpt = true, + portOpt = 5000 + ), + app = nexusApp + ) + } +} +\ No newline at end of file