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 {
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 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 {
val kotlin_atomicfu = "0.27.0"
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.memScoped
import kotlinx.cinterop.ptr
import kotlinx.coroutines.suspendCancellableCoroutine
import platform.linux.EPOLLERR
import platform.linux.EPOLLHUP
import platform.linux.EPOLLIN
import platform.linux.EPOLLONESHOT
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.safeAutoClose1
import ru.landgrafhomyak.bgtu.networks0.utilities.safeAutoClose2
import ru.landgrafhomyak.bgtu.networks0.utilities.safeAutoClose2e
@OptIn(ExperimentalForeignApi::class)
@Suppress("JoinDeclarationAndAssignment", "ConvertSecondaryConstructorToPrimary", "FunctionName")
@ -66,7 +70,7 @@ class EpollSocketEventLoop : SocketEventLoopScope.Closeable {
TODO("Not yet implemented")
}
private fun _waitFor(metadata: _SocketMetadata) {
private fun _schedule(metadata: _SocketMetadata) {
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.data.fd = metadata.fd
@ -96,7 +100,7 @@ class EpollSocketEventLoop : SocketEventLoopScope.Closeable {
if (metadata.read != null)
throw IllegalStateException("Socket already waiting for read")
metadata.read = continuation
this._waitFor(metadata)
this._schedule(metadata)
}
}
@ -106,7 +110,7 @@ class EpollSocketEventLoop : SocketEventLoopScope.Closeable {
if (metadata.write != null)
throw IllegalStateException("Socket already waiting for write")
metadata.write = continuation
this._waitFor(metadata)
this._schedule(metadata)
}
}
@ -118,11 +122,11 @@ class EpollSocketEventLoop : SocketEventLoopScope.Closeable {
metadata.read = null
}
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
}
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,
MSG_NOSIGNAL or MSG_PEEK or MSG_TRUNC
)
if (bytesAvailable == 0L) continue@polling
if (bytesAvailable < 0) {
when (errno) {
@ -130,16 +131,16 @@ internal abstract class NonblockingIcmpSocketImpl : IcmpSocket {
}
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)
}
if (sentCount < 0) {
if (receivedCount < 0) {
when (errno) {
EAGAIN, EWOULDBLOCK -> continue@polling
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")
throw RuntimeException("unreachable")

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
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.sockets.EpollSocketEventLoop
@ -24,48 +25,14 @@ fun main() {
val loopThread = Thread(EventLoopRoutine(loop))
loopThread.start()
val googleSock = loop.icmp_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
)
val googleSock = loop.pingSocket_IPv4("8.8.8.8")
runBlocking {
launch {
while (true) {
googleSock.sendRaw(pingData)
print("sent to google\n")
googleSock.recvRaw()
print("got from google\n")
print("google: ${googleSock.ping(1000u)}ms\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) {
e.printStackTrace()
}

View File

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