Stub of ping object

This commit is contained in:
Andrew Golovashevich 2025-03-20 02:41:44 +03:00
parent bc9ba0ae54
commit eb19b3e36a
16 changed files with 275 additions and 47 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "libs/int-serializers"]
path = libs/int-serializers
url = https://git.landgrafhomyak.ru/xomrk/int-serializers.kt

1
libs/int-serializers Submodule

@ -0,0 +1 @@
Subproject commit 1d4b797c31e0fa622c2fb411c3e202de85b3aadc

View File

@ -3,4 +3,6 @@ package ru.landgrafhomyak.bgtu.networks0.build_script
object Dependencies { object Dependencies {
val kotlin_atomicfu = "org.jetbrains.kotlinx:atomicfu:${Versions.kotlin_atomicfu}" val kotlin_atomicfu = "org.jetbrains.kotlinx:atomicfu:${Versions.kotlin_atomicfu}"
val kotlin_coroutines_core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlin_coroutines_core}" val kotlin_coroutines_core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.kotlin_coroutines_core}"
val int_serializers = "ru.landgrafhomyak.utility:int-serializers:${Versions.int_serializers}"
val kotlin_datetime = "org.jetbrains.kotlinx:kotlinx-datetime:${Versions.kotlin_datetime}"
} }

View File

@ -4,4 +4,6 @@ package ru.landgrafhomyak.bgtu.networks0.build_script
object Versions { object Versions {
val kotlin_atomicfu = "0.27.0" val kotlin_atomicfu = "0.27.0"
val kotlin_coroutines_core = "1.10.1" val kotlin_coroutines_core = "1.10.1"
val int_serializers = "1.1"
val kotlin_datetime = "0.6.2"
} }

View File

@ -0,0 +1,27 @@
import ru.landgrafhomyak.bgtu.networks0.build_script.Dependencies
import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.defineAllMultiplatformTargets
plugins {
kotlin("multiplatform")
}
repositories {
mavenCentral()
maven("https://maven.landgrafhomyak.ru/")
}
kotlin {
defineAllMultiplatformTargets()
sourceSets {
commonMain {
dependencies {
implementation(Dependencies.kotlin_coroutines_core)
implementation(Dependencies.kotlin_datetime)
implementation(Dependencies.int_serializers)
implementation(project(":modules:low-level:sockets"))
}
}
}
}

View File

@ -0,0 +1,10 @@
package ru.landgrafhomyak.bgtu.networks0.icmp
@OptIn(ExperimentalUnsignedTypes::class)
interface IcmpHeader {
val type: UByte
val code: UByte
fun checksum(withData: UByteArray): UShort
fun serialize(withData: UByteArray): UByteArray
}

View File

@ -0,0 +1,31 @@
package ru.landgrafhomyak.bgtu.networks0.icmp
import ru.landgrafhomyak.utility.IntSerializers
@OptIn(ExperimentalUnsignedTypes::class)
class IcmpPingHeader(
val identifier_be: UShort,
val seqNo_be: UShort,
) : IcmpHeader {
override val type: UByte get() = 8u
override val code: UByte get() = 0u
override fun checksum(withData: UByteArray): UShort = Ipv4Checksum.calculate { c ->
c.addFirstByte(this.type)
c.addSecondByte(this.code)
c.addWord(this.identifier_be)
c.addWord(this.seqNo_be)
c.addTrailingData(withData)
}
override fun serialize(withData: UByteArray): UByteArray {
val serialized = UByteArray(withData.size + 8)
IntSerializers.encode8Bu(serialized, 0, this.type)
IntSerializers.encode8Bu(serialized, 1, this.code)
IntSerializers.encode16beHu(serialized, 2, this.checksum(withData))
IntSerializers.encode16beHu(serialized, 4, this.identifier_be)
IntSerializers.encode16beHu(serialized, 6, this.seqNo_be)
withData.copyInto(serialized, 8)
return serialized
}
}

View File

@ -0,0 +1,56 @@
package ru.landgrafhomyak.bgtu.networks0.icmp
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@OptIn(ExperimentalUnsignedTypes::class)
internal class Ipv4Checksum() {
private var _accumulator: UInt = 0u
fun addFirstByte(b: UByte) {
this._accumulator += (b.toUInt() shl 8)
}
fun addSecondByte(b: UByte) {
this._accumulator += b
}
fun addWord(b: UShort) {
this._accumulator += b
}
fun addLastByte(b: UByte) {
this._accumulator += b
}
fun addTrailingData(data: UByteArray) {
for (i in data.indices step 2) {
this.addFirstByte(data[i])
this.addSecondByte(data[i + 1])
}
if (data.size % 2 != 0)
this.addLastByte(data.last())
}
fun export(): UShort {
var out: UInt = this._accumulator
while (out > 0xFFFFu) {
out = (out and 0xFFFFu) + (out shr 16);
}
return out.toUShort().inv()
}
companion object {
@OptIn(ExperimentalContracts::class)
inline fun calculate(builder: (Ipv4Checksum) -> Unit): UShort {
contract {
callsInPlace(builder, InvocationKind.EXACTLY_ONCE)
}
val b = Ipv4Checksum()
builder(b)
return b.export()
}
}
}

View File

@ -0,0 +1,112 @@
package ru.landgrafhomyak.bgtu.networks0.icmp
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeout
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.coroutines.sync.Mutex as CMutex
import ru.landgrafhomyak.bgtu.networks0.low_level.sockets.IcmpSocket
import ru.landgrafhomyak.bgtu.networks0.utilities.safeAutoClose1
import ru.landgrafhomyak.bgtu.networks0.utilities.safeAutoClose2
import ru.landgrafhomyak.utility.IntSerializers
@OptIn(ExperimentalUnsignedTypes::class)
class PingSocket : AutoCloseable {
private val _rawSocket: IcmpSocket
private val _mapSync: CMutex
private val _resumeMap: HashMap<ShortKeyPair, Continuation<Unit>>
private val _recvCoro: Job
internal constructor(rawSocket: IcmpSocket) {
safeAutoClose2(onAbort = rawSocket::close) {
this._rawSocket = rawSocket
this._resumeMap = HashMap()
this._mapSync = CMutex()
this._recvCoro = CoroutineScope(Dispatchers.Default).launch { this@PingSocket._recvRoutine() }
}
}
private class ShortKeyPair(val identifier: UShort, val seqNo: UShort) {
override fun equals(other: Any?): Boolean {
if (other !is ShortKeyPair) return false
return this.identifier == other.identifier && this.seqNo == other.seqNo
}
override fun hashCode(): Int {
return ((this.identifier.toUInt() shl 16) or (this.seqNo).toUInt()).toInt()
}
override fun toString(): String = "<icmp ping key pair identifier=${this.identifier} seq_no=${this.seqNo}>"
}
private fun _generateKeyPair(): ShortKeyPair {
val timestamp = Clock.System.now()
return ShortKeyPair(
identifier = (timestamp.epochSeconds ushr 13).toULong().toUShort(),
seqNo = ((timestamp.epochSeconds shl 3) or ((((timestamp.nanosecondsOfSecond ushr 27) and 7).toLong())) and 0xFFFF).toULong().toUShort(),
)
}
suspend fun ping(timeoutMillis: UInt): UInt? {
var isLocked = true
val start: Instant
this._mapSync.lock()
safeAutoClose1(finally = { if (isLocked) this._mapSync.unlock() }) {
val key = this._generateKeyPair()
val header = IcmpPingHeader(identifier_be = key.identifier, seqNo_be = key.seqNo)
val serialized = header.serialize(PingSocket.randomData)
start = Clock.System.now()
this._rawSocket.sendRaw(serialized)
try {
withTimeout(timeoutMillis.toULong().toLong()) {
suspendCancellableCoroutine<Unit> { continuation ->
if (key in this@PingSocket._resumeMap)
throw RuntimeException("Key duplication (shouldn't happen)")
this@PingSocket._resumeMap[key] = continuation
this@PingSocket._mapSync.unlock()
isLocked = false
}
}
} catch (_: TimeoutCancellationException) {
return null
}
}
val td = Clock.System.now() - start
return td.inWholeMilliseconds.toUInt()
}
private suspend fun _recvRoutine() {
try {
// todo handle errors
while (true) {
val raw = this._rawSocket.recvRaw()
if (raw.size < 8) continue
val key = ShortKeyPair(identifier = IntSerializers.decode16beHu(raw, 4), seqNo = IntSerializers.decode16beHu(raw, 6))
val continuation = this._mapSync.withLock { this._resumeMap.remove(key) }
if (continuation == null) continue
continuation.resume(Unit)
}
} catch (e: Throwable) {
e.printStackTrace()
}
}
override fun close() {
TODO("Not yet implemented")
}
companion object {
@Suppress("SpellCheckingInspection")
private val randomData = "abcdefghijklmnopqrstuvwabcdefghi".encodeToByteArray().asUByteArray()
}
}

View File

@ -0,0 +1,6 @@
package ru.landgrafhomyak.bgtu.networks0.icmp
import ru.landgrafhomyak.bgtu.networks0.low_level.sockets.SocketEventLoopScope
fun SocketEventLoopScope.pingSocket_IPv4(addr: String): PingSocket = PingSocket(this.icmp_IPv4(addr))
fun SocketEventLoopScope.pingSocket_IPv6(addr: String): PingSocket = PingSocket(this.icmp_IPv6(addr))

View File

@ -12,6 +12,9 @@ import kotlinx.cinterop.cValue
import kotlinx.cinterop.get import kotlinx.cinterop.get
import kotlinx.cinterop.memScoped import kotlinx.cinterop.memScoped
import kotlinx.cinterop.ptr import kotlinx.cinterop.ptr
import kotlinx.coroutines.suspendCancellableCoroutine
import platform.linux.EPOLLERR
import platform.linux.EPOLLHUP
import platform.linux.EPOLLIN import platform.linux.EPOLLIN
import platform.linux.EPOLLONESHOT import platform.linux.EPOLLONESHOT
import platform.linux.EPOLLOUT import platform.linux.EPOLLOUT
@ -32,6 +35,7 @@ import ru.landgrafhomyak.bgtu.networks0.low_level.multithreading.withLock
import ru.landgrafhomyak.bgtu.networks0.utilities.CloseableRefCounter import ru.landgrafhomyak.bgtu.networks0.utilities.CloseableRefCounter
import ru.landgrafhomyak.bgtu.networks0.utilities.safeAutoClose1 import ru.landgrafhomyak.bgtu.networks0.utilities.safeAutoClose1
import ru.landgrafhomyak.bgtu.networks0.utilities.safeAutoClose2 import ru.landgrafhomyak.bgtu.networks0.utilities.safeAutoClose2
import ru.landgrafhomyak.bgtu.networks0.utilities.safeAutoClose2e
@OptIn(ExperimentalForeignApi::class) @OptIn(ExperimentalForeignApi::class)
@Suppress("JoinDeclarationAndAssignment", "ConvertSecondaryConstructorToPrimary", "FunctionName") @Suppress("JoinDeclarationAndAssignment", "ConvertSecondaryConstructorToPrimary", "FunctionName")
@ -66,7 +70,7 @@ class EpollSocketEventLoop : SocketEventLoopScope.Closeable {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
private fun _waitFor(metadata: _SocketMetadata) { private fun _schedule(metadata: _SocketMetadata) {
val ee = cValue<epoll_event> { val ee = cValue<epoll_event> {
this.events = ((if (metadata.read == null) 0 else EPOLLIN) or (if (metadata.write == null) 0 else EPOLLOUT) or EPOLLONESHOT).toUInt() this.events = ((if (metadata.read == null) 0 else EPOLLIN) or (if (metadata.write == null) 0 else EPOLLOUT) or EPOLLONESHOT).toUInt()
this.data.fd = metadata.fd this.data.fd = metadata.fd
@ -96,7 +100,7 @@ class EpollSocketEventLoop : SocketEventLoopScope.Closeable {
if (metadata.read != null) if (metadata.read != null)
throw IllegalStateException("Socket already waiting for read") throw IllegalStateException("Socket already waiting for read")
metadata.read = continuation metadata.read = continuation
this._waitFor(metadata) this._schedule(metadata)
} }
} }
@ -106,7 +110,7 @@ class EpollSocketEventLoop : SocketEventLoopScope.Closeable {
if (metadata.write != null) if (metadata.write != null)
throw IllegalStateException("Socket already waiting for write") throw IllegalStateException("Socket already waiting for write")
metadata.write = continuation metadata.write = continuation
this._waitFor(metadata) this._schedule(metadata)
} }
} }
@ -118,11 +122,11 @@ class EpollSocketEventLoop : SocketEventLoopScope.Closeable {
metadata.read = null metadata.read = null
} }
if ((ev.events and EPOLLOUT.toUInt()) != 0u) { if ((ev.events and EPOLLOUT.toUInt()) != 0u) {
(metadata.write ?: throw RuntimeException("Socket ready for read, but nobody requests")).resume(Unit) (metadata.write ?: throw RuntimeException("Socket ready for write, but nobody requests")).resume(Unit)
metadata.write = null metadata.write = null
} }
if (metadata.read != null || metadata.write != null) if (metadata.read != null || metadata.write != null)
this._waitFor(metadata) this._schedule(metadata)
} }
} }

View File

@ -121,6 +121,7 @@ internal abstract class NonblockingIcmpSocketImpl : IcmpSocket {
this._socketFd, null, 0u, this._socketFd, null, 0u,
MSG_NOSIGNAL or MSG_PEEK or MSG_TRUNC MSG_NOSIGNAL or MSG_PEEK or MSG_TRUNC
) )
if (bytesAvailable == 0L) continue@polling if (bytesAvailable == 0L) continue@polling
if (bytesAvailable < 0) { if (bytesAvailable < 0) {
when (errno) { when (errno) {
@ -130,16 +131,16 @@ internal abstract class NonblockingIcmpSocketImpl : IcmpSocket {
} }
val data = UByteArray(bytesAvailable.toInt()) val data = UByteArray(bytesAvailable.toInt())
var sentCount = data.usePinned { pinnedData -> var receivedCount = data.usePinned { pinnedData ->
return@usePinned recv_lowlevel(this._socketFd, pinnedData.addressOf(0), data.size.toULong(), MSG_NOSIGNAL) return@usePinned recv_lowlevel(this._socketFd, pinnedData.addressOf(0), data.size.toULong(), MSG_NOSIGNAL)
} }
if (sentCount < 0) { if (receivedCount < 0) {
when (errno) { when (errno) {
EAGAIN, EWOULDBLOCK -> continue@polling EAGAIN, EWOULDBLOCK -> continue@polling
else -> PosixUtilities.throwErrno { d -> RuntimeException("Failed to send message over ICMP socket: $d") } else -> PosixUtilities.throwErrno { d -> RuntimeException("Failed to send message over ICMP socket: $d") }
} }
} }
return data return data.sliceArray(((data[0] and 0xFu).toUInt().toInt() + 15)..<data.size)
} }
@Suppress("KotlinUnreachableCode") @Suppress("KotlinUnreachableCode")
throw RuntimeException("unreachable") throw RuntimeException("unreachable")

View File

@ -92,7 +92,8 @@ inline fun <R> safeAutoClose3e(
if (!wasError) { if (!wasError) {
if (crossReturned) if (crossReturned)
onCrossReturn() onCrossReturn()
onSuccess() else
onSuccess()
} }
} }
return ret return ret

View File

@ -20,6 +20,7 @@ kotlin {
dependencies { dependencies {
implementation(project(":modules:low-level:multithreading")) implementation(project(":modules:low-level:multithreading"))
implementation(project(":modules:low-level:sockets")) implementation(project(":modules:low-level:sockets"))
implementation(project(":modules:icmp"))
} }
} }
} }

View File

@ -1,6 +1,7 @@
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import ru.landgrafhomyak.bgtu.networks0.icmp.pingSocket_IPv4
import ru.landgrafhomyak.bgtu.networks0.low_level.multithreading.Thread import ru.landgrafhomyak.bgtu.networks0.low_level.multithreading.Thread
import ru.landgrafhomyak.bgtu.networks0.low_level.sockets.EpollSocketEventLoop import ru.landgrafhomyak.bgtu.networks0.low_level.sockets.EpollSocketEventLoop
@ -24,48 +25,14 @@ fun main() {
val loopThread = Thread(EventLoopRoutine(loop)) val loopThread = Thread(EventLoopRoutine(loop))
loopThread.start() loopThread.start()
val googleSock = loop.icmp_IPv4("8.8.8.8") val googleSock = loop.pingSocket_IPv4("8.8.8.8")
val yandexSock = loop.icmp_IPv4("5.255.255.70")
val phoneSock = loop.icmp_IPv4("192.168.43.1")
val pingData = ubyteArrayOf(
0x08u, 0x00u, 0x4Cu, 0xE4u, 0x00u, 0x01u, 0x00u, 0x77u,
0x61u, 0x62u, 0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u,
0x69u, 0x6Au, 0x6Bu, 0x6Cu, 0x6Du, 0x6Eu, 0x6Fu, 0x70u,
0x71u, 0x72u, 0x73u, 0x74u, 0x75u, 0x76u, 0x77u, 0x61u,
0x62u, 0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u, 0x69u
)
runBlocking { runBlocking {
launch { while (true) {
while (true) { print("google: ${googleSock.ping(1000u)}ms\n")
googleSock.sendRaw(pingData) delay(1000)
print("sent to google\n")
googleSock.recvRaw()
print("got from google\n")
delay(1000)
}
}
launch {
while (true) {
yandexSock.sendRaw(pingData)
print("sent to yandex\n")
yandexSock.recvRaw()
print("got from yandex\n")
delay(2500)
}
}
launch {
while (true) {
phoneSock.sendRaw(pingData)
print("sent to phone\n")
phoneSock.recvRaw()
print("got from phone\n")
delay(5500)
}
} }
} }
} catch (e: Throwable) { } catch (e: Throwable) {
e.printStackTrace() e.printStackTrace()
} }

View File

@ -21,8 +21,12 @@ pluginManagement {
} }
} }
} }
includeBuild("./libs/int-serializers")
include(":modules:utilities") include(":modules:utilities")
include(":modules:low-level:c-interop-utilities") include(":modules:low-level:c-interop-utilities")
include(":modules:low-level:multithreading") include(":modules:low-level:multithreading")
include(":modules:low-level:sockets") include(":modules:low-level:sockets")
include(":modules:icmp")
include(":programs:test") include(":programs:test")