Stub of ping object
This commit is contained in:
parent
bc9ba0ae54
commit
eb19b3e36a
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal 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
1
libs/int-serializers
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 1d4b797c31e0fa622c2fb411c3e202de85b3aadc
|
@ -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}"
|
||||
}
|
@ -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"
|
||||
}
|
27
modules/icmp/build.gradle.kts
Normal file
27
modules/icmp/build.gradle.kts
Normal 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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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))
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
@ -92,6 +92,7 @@ inline fun <R> safeAutoClose3e(
|
||||
if (!wasError) {
|
||||
if (crossReturned)
|
||||
onCrossReturn()
|
||||
else
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(project(":modules:low-level:multithreading"))
|
||||
implementation(project(":modules:low-level:sockets"))
|
||||
implementation(project(":modules:icmp"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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")
|
Loading…
Reference in New Issue
Block a user