Add holepunch helper and a bunch of other stuff

This commit is contained in:
ProtoByter 2022-12-06 08:27:30 +00:00
parent 55587fa9a5
commit 20b3b43394
7 changed files with 113 additions and 11 deletions

View File

@ -43,6 +43,7 @@ dependencies {
implementation("io.ktor:ktor-server-auth-jvm:$ktor_version")
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
implementation("io.ktor:ktor-server-config-yaml:$ktor_version")
implementation("io.ktor:ktor-network:$ktor_version")
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
// Logging

View File

@ -8,6 +8,7 @@ import org.h2.security.SHA3
import org.jetbrains.exposed.dao.id.UUIDTable
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.less
import org.jetbrains.exposed.sql.javatime.timestamp
import org.jetbrains.exposed.sql.transactions.transaction
import java.time.Instant
@ -20,6 +21,7 @@ class Client(
var name: String,
var timeout: Instant,
var ip: String,
var port: Int?,
var passwordHash: String,
var sampledClients: List<UUID>
)
@ -27,6 +29,7 @@ class Client(
object Clients: UUIDTable() {
var name = varchar("name", 16)
var ip = varchar("ip", 21)
var port = integer("port").nullable()
var timeout = timestamp("timeout")
var passwordHash = varchar("passwordHash", 96)
var sampledClient1 = uuid("sampledClient1")
@ -72,6 +75,7 @@ object DatabaseHandler {
it[Clients.id] = id
it[Clients.name] = name
it[Clients.ip] = ip
it[port] = null
it[passwordHash] = pwHash
it[timeout] = java.time.LocalDateTime.now().toInstant(java.time.ZoneOffset.UTC) + java.time.Duration.ofMinutes(5)
it[sampledClient1] = sampledClients[0]
@ -100,7 +104,7 @@ object DatabaseHandler {
fun getClient(name: String): Client? {
val client = transaction {
Clients.select { Clients.name eq name }.map {
Client(it[Clients.id].value, it[Clients.name], it[Clients.timeout], it[Clients.ip], it[Clients.passwordHash], listOf(
Client(it[Clients.id].value, it[Clients.name], it[Clients.timeout], it[Clients.ip], it[Clients.port], it[Clients.passwordHash], listOf(
it[Clients.sampledClient1],it[Clients.sampledClient2],it[Clients.sampledClient3],it[Clients.sampledClient4],
it[Clients.sampledClient5],it[Clients.sampledClient6],it[Clients.sampledClient7],it[Clients.sampledClient8]
))
@ -113,7 +117,7 @@ object DatabaseHandler {
fun getClient(id: UUID): Client? {
val client = transaction {
Clients.select { Clients.id eq id }.map {
Client(it[Clients.id].value, it[Clients.name], it[Clients.timeout], it[Clients.ip], it[Clients.passwordHash], listOf(
Client(it[Clients.id].value, it[Clients.name], it[Clients.timeout], it[Clients.ip], it[Clients.port], it[Clients.passwordHash], listOf(
it[Clients.sampledClient1],it[Clients.sampledClient2],it[Clients.sampledClient3],it[Clients.sampledClient4],
it[Clients.sampledClient5],it[Clients.sampledClient6],it[Clients.sampledClient7],it[Clients.sampledClient8]
))
@ -153,6 +157,12 @@ object DatabaseHandler {
}
}
fun cullOutOfDateEntries() {
transaction {
Clients.deleteWhere { timeout less java.time.LocalDateTime.now().toInstant(java.time.ZoneOffset.UTC) }
}
}
internal fun clear() {
transaction {
Clients.deleteAll()

View File

@ -0,0 +1,56 @@
package org.muellerssoftware.openproximitychat.tracker
import io.ktor.network.selector.*
import io.ktor.network.sockets.*
import io.ktor.server.application.*
import io.ktor.utils.io.core.*
import kotlinx.coroutines.*
import kotlinx.coroutines.selects.select
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import org.muellerssoftware.openproximitychat.common.Logging
import java.util.*
@OptIn(ExperimentalCoroutinesApi::class, DelicateCoroutinesApi::class)
fun Application.puncher() {
val job = GlobalScope.launch {
val selectorManager = SelectorManager(Dispatchers.IO)
val serverSocket = aSocket(selectorManager).udp().bind()
Logging.info("HolePunch listening on ${serverSocket.localAddress}")
select<Unit> {
serverSocket.incoming.onReceive {
val address = it.address
val packet = it.packet
val packetBytes = packet.readByteBuffer()
val uuid: UUID?
try {
uuid = UUID.fromString(packetBytes.array().decodeToString())
} catch (e: Exception) {
Logging.error("Received invalid UUID from $address")
return@onReceive
}
transaction {
Clients.update({ Clients.id eq uuid }) {
it[ip] = address.toJavaAddress().toString()
}.run {
if (this == 0) {
Logging.info("Received packet from unknown client $uuid")
}
}
}
return@onReceive
}
}
}
environment.monitor.subscribe(ApplicationStopping) {
Logging.info("Closing HolePunch")
job.cancel()
}
}

View File

@ -0,0 +1,29 @@
package org.muellerssoftware.openproximitychat.tracker
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
class Scheduler(private val task: Runnable) {
private val executor = Executors.newScheduledThreadPool(1)
fun scheduleExecution(every: Every) {
val taskWrapper = Runnable {
task.run()
}
executor.scheduleWithFixedDelay(taskWrapper, every.n, every.n, every.unit)
}
fun stop() {
executor.shutdown()
try {
executor.awaitTermination(1, TimeUnit.MINUTES)
} catch (_: InterruptedException) {}
}
}
data class Every(val n: Long, val unit: TimeUnit)

View File

@ -10,21 +10,28 @@ import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.muellerssoftware.openproximitychat.common.Logging
import org.muellerssoftware.openproximitychat.common.UPnPManager
import java.util.*
import java.util.concurrent.TimeUnit
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
fun Application.module() {
fun Application.tracker() {
Logging.logger = log
val cullScheduler = Scheduler {
log.info("Culling clients")
DatabaseHandler.cullOutOfDateEntries()
}
environment.monitor.subscribe(ApplicationStarted) {
log.info("Application started")
UPnPManager.mapPort(this.environment.config.port, "TCP")
cullScheduler.scheduleExecution(Every(1, TimeUnit.MINUTES))
}
environment.monitor.subscribe(ApplicationStopping) {
log.info("Application stopping")
UPnPManager.unMapAll()
cullScheduler.stop()
}
authentication {
@ -93,7 +100,7 @@ fun Application.module() {
val id = DatabaseHandler.addClient(
request.id,
request.name,
this.call.request.origin.remoteHost + ":${this.call.request.origin.port}",
call.request.origin.remoteHost,
request.sampledClients
)
call.respond(HttpStatusCode.OK, RegisterResponse(id))

View File

@ -3,4 +3,5 @@ ktor:
port: 0
application:
modules:
- org.muellerssoftware.openproximitychat.tracker.ApplicationKt.module
- org.muellerssoftware.openproximitychat.tracker.TrackerApplicationKt.tracker
- org.muellerssoftware.openproximitychat.tracker.HolePunchApplicationKt.puncher

View File

@ -11,7 +11,7 @@ import java.util.*
import kotlin.test.Test
import kotlin.test.assertEquals
class ApplicationKtTest {
class TrackerApplicationKtTest {
private val sampledClientsTestVal = listOf(UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID())
@Before
@ -213,9 +213,7 @@ class ApplicationKtTest {
)
basicAuth("00000000-0000-0000-0000-000000000001", pw_hash)
}.apply {
val clientCorrect = DatabaseHandler.getClient(UUID.fromString("00000000-0000-0000-0000-000000000001"))
assertEquals(200, this.status.value)
assertEquals((this.body() as SearchResponse).ip, clientCorrect!!.ip)
}
}
}