Add lost net_mapper code
This commit is contained in:
parent
35779abe5a
commit
cc495cf834
|
@ -0,0 +1,2 @@
|
||||||
|
version 1.
|
||||||
|
RoundTripTime = <rtt @address string @minimum float @average float @maximum float>.
|
|
@ -0,0 +1,169 @@
|
||||||
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||||
|
# SPDX-License-Identifier: Unlicense
|
||||||
|
|
||||||
|
## A ping utility for Syndicate.
|
||||||
|
|
||||||
|
import std/[asyncdispatch, asyncnet, math, monotimes, nativesockets, net, os, strutils, tables, times]
|
||||||
|
import preserves
|
||||||
|
import syndicate, syndicate/patterns
|
||||||
|
|
||||||
|
import ./schema/net_mapper
|
||||||
|
|
||||||
|
type ListenOn* {.preservesRecord: "listen-on".} = ref object
|
||||||
|
dataspace: Preserve[Ref]
|
||||||
|
|
||||||
|
#[
|
||||||
|
var
|
||||||
|
SOL_IP {.importc, nodecl, header: "<sys/socket.h>".}: int
|
||||||
|
IP_TTL {.importc, nodecl, header: "<netinet/in.h>".}: int
|
||||||
|
]#
|
||||||
|
|
||||||
|
proc toPreserveHook(address: IpAddress; E: typedesc): Preserve[E] = toPreserve($address, E)
|
||||||
|
|
||||||
|
proc fromPreserveHook[E](address: var IpAddress; pr: Preserve[E]): bool =
|
||||||
|
try:
|
||||||
|
if pr.isString:
|
||||||
|
address = parseIpAddress(pr.string)
|
||||||
|
result = true
|
||||||
|
except ValueError: discard
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
# verify that the hook catches
|
||||||
|
var ip: IpAddress
|
||||||
|
assert fromPreserveHook(ip, toPreserveHook(ip, void))
|
||||||
|
|
||||||
|
type
|
||||||
|
IcmpHeader {.packed.} = object
|
||||||
|
`type`: uint8
|
||||||
|
code: uint8
|
||||||
|
checksum: uint16
|
||||||
|
|
||||||
|
IcmpEchoFields {.packed.} = object
|
||||||
|
header: IcmpHeader
|
||||||
|
identifier: array[2, byte]
|
||||||
|
sequenceNumber: uint16
|
||||||
|
|
||||||
|
IcmpEcho {.union.} = object
|
||||||
|
fields: IcmpEchoFields
|
||||||
|
buffer: array[8, uint8]
|
||||||
|
|
||||||
|
IcmpTypes = enum
|
||||||
|
icmpEchoReply = 0,
|
||||||
|
icmpEcho = 8,
|
||||||
|
|
||||||
|
proc initIcmpEcho(): IcmpEcho =
|
||||||
|
result.fields.header.`type` = uint8 icmpEcho
|
||||||
|
# doAssert urandom(result.fields.identifier) # Linux does this?
|
||||||
|
|
||||||
|
proc updateChecksum(msg: var IcmpEcho) =
|
||||||
|
var sum: uint32
|
||||||
|
msg.fields.header.checksum = 0
|
||||||
|
for n in cast[array[4, uint16]](msg.buffer): sum = sum + uint32(n)
|
||||||
|
while (sum and 0xffff0000'u32) != 0:
|
||||||
|
sum = (sum and 0xffff) + (sum shr 16)
|
||||||
|
msg.fields.header.checksum = not uint16(sum)
|
||||||
|
|
||||||
|
proc match(a, b: IcmpEchoFields): bool =
|
||||||
|
({a.header.type, b.header.type} == {uint8 icmpEcho, uint8 icmpEchoReply}) and
|
||||||
|
(a.header.code == b.header.code) and
|
||||||
|
(a.sequenceNumber == b.sequenceNumber)
|
||||||
|
|
||||||
|
type
|
||||||
|
Pinger = ref object
|
||||||
|
facet: Facet
|
||||||
|
ds: Ref
|
||||||
|
rtt: RoundTripTime
|
||||||
|
rttHandle: Handle
|
||||||
|
sum: Duration
|
||||||
|
count: int64
|
||||||
|
msg: IcmpEcho
|
||||||
|
socket: AsyncSocket
|
||||||
|
sad: Sockaddr_storage
|
||||||
|
sadLen: SockLen
|
||||||
|
interval: Duration
|
||||||
|
|
||||||
|
proc newPinger(address: IpAddress; facet: Facet; ds: Ref): Pinger =
|
||||||
|
result = Pinger(
|
||||||
|
facet: facet,
|
||||||
|
ds: ds,
|
||||||
|
rtt: RoundTripTime(address: $address),
|
||||||
|
msg: initIcmpEcho(),
|
||||||
|
socket: newAsyncSocket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP, false, true),
|
||||||
|
interval: initDuration(milliseconds = 500))
|
||||||
|
toSockAddr(address, Port 0, result.sad, result.sadLen)
|
||||||
|
# setSockOptInt(getFd socket, SOL_IP, IP_TTL, _)
|
||||||
|
|
||||||
|
proc close(ping: Pinger) = close(ping.socket)
|
||||||
|
|
||||||
|
proc sqr(dur: Duration): Duration =
|
||||||
|
let us = dur.inMicroseconds
|
||||||
|
initDuration(microseconds = us * us)
|
||||||
|
|
||||||
|
proc update(ping: Pinger; dur: Duration) {.inline.} =
|
||||||
|
let secs = dur.inMicroseconds.float / 1_000_000.0
|
||||||
|
if ping.count == 0: (ping.rtt.minimum, ping.rtt.maximum) = (secs, secs)
|
||||||
|
elif secs < ping.rtt.minimum: ping.rtt.minimum = secs
|
||||||
|
elif secs > ping.rtt.maximum: ping.rtt.maximum = secs
|
||||||
|
ping.sum = ping.sum + dur
|
||||||
|
inc ping.count
|
||||||
|
ping.rtt.average = inMicroseconds(ping.sum div ping.count).float / 1_000_000.0
|
||||||
|
|
||||||
|
proc exchangeEcho(ping: Pinger) {.async.} =
|
||||||
|
inc ping.msg.fields.sequenceNumber
|
||||||
|
# updateChecksum(ping.msg) # Linux does this?
|
||||||
|
let
|
||||||
|
a = getMonoTime()
|
||||||
|
r = sendto(ping.socket.getFd,
|
||||||
|
unsafeAddr ping.msg.buffer[0], ping.msg.buffer.len, 0,
|
||||||
|
cast[ptr SockAddr](unsafeAddr ping.sad), # neckbeard loser API
|
||||||
|
ping.sadLen)
|
||||||
|
if r == -1'i32:
|
||||||
|
let osError = osLastError()
|
||||||
|
raiseOSError(osError)
|
||||||
|
while true:
|
||||||
|
var
|
||||||
|
(data, address, _) = await recvFrom(ping.socket, 128)
|
||||||
|
b = getMonoTime()
|
||||||
|
if address != $ping.rtt.address:
|
||||||
|
stderr.writeLine "want ICMP from ", ping.rtt.address, " but received from ", address, " instead"
|
||||||
|
elif data.len >= ping.msg.buffer.len:
|
||||||
|
let
|
||||||
|
period = b - a
|
||||||
|
resp = cast[ptr IcmpEcho](unsafeAddr data[0])
|
||||||
|
if match(ping.msg.fields, resp.fields):
|
||||||
|
update(ping, period)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
stderr.writeLine "ICMP mismatch"
|
||||||
|
else:
|
||||||
|
stderr.writeLine "reply data has a bad length ", data.len
|
||||||
|
|
||||||
|
proc kick(ping: Pinger) =
|
||||||
|
if not ping.socket.isClosed:
|
||||||
|
addTimer(ping.interval.inMilliseconds.int, oneshot = true) do (fd: AsyncFD) -> bool:
|
||||||
|
exchangeEcho(ping).addCallback do (fut: Future[void]):
|
||||||
|
if fut.failed and ping.rttHandle != Handle(0):
|
||||||
|
ping.facet.run do (turn: var Turn):
|
||||||
|
retract(turn, ping.rttHandle)
|
||||||
|
reset ping.rttHandle
|
||||||
|
else:
|
||||||
|
ping.facet.run do (turn: var Turn):
|
||||||
|
replace(turn, ping.ds, ping.rttHandle, ping.rtt)
|
||||||
|
if ping.interval < initDuration(seconds = 20):
|
||||||
|
ping.interval = ping.interval * 2
|
||||||
|
kick(ping)
|
||||||
|
|
||||||
|
type RefArgs {.preservesDictionary.} = object
|
||||||
|
dataspace: Ref
|
||||||
|
|
||||||
|
runActor("main") do (root: Ref; turn: var Turn):
|
||||||
|
connectStdio(root, turn)
|
||||||
|
let rttObserver = ?Observe(pattern: !RoundTripTime) ?? {0: grabLit()}
|
||||||
|
during(turn, root, ?RefArgs) do (ds: Ref):
|
||||||
|
during(turn, ds, rttObserver) do (address: IpAddress):
|
||||||
|
var ping: Pinger
|
||||||
|
if address.family == IpAddressFamily.IPv4:
|
||||||
|
ping = newPinger(address, turn.facet, ds)
|
||||||
|
kick(ping)
|
||||||
|
do:
|
||||||
|
if not ping.isNil: close(ping)
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
import
|
||||||
|
preserves
|
||||||
|
|
||||||
|
type
|
||||||
|
RoundTripTime* {.preservesRecord: "rtt".} = object
|
||||||
|
`address`*: string
|
||||||
|
`minimum`*: float32
|
||||||
|
`average`*: float32
|
||||||
|
`maximum`*: float32
|
||||||
|
|
||||||
|
proc `$`*(x: RoundTripTime): string =
|
||||||
|
`$`(toPreserve(x))
|
||||||
|
|
||||||
|
proc encode*(x: RoundTripTime): seq[byte] =
|
||||||
|
encode(toPreserve(x))
|
|
@ -1,11 +1,11 @@
|
||||||
# Package
|
# Package
|
||||||
|
|
||||||
version = "20230518"
|
version = "20230608"
|
||||||
author = "Emery Hemingway"
|
author = "Emery Hemingway"
|
||||||
description = "Utilites for Syndicated Actors and Synit"
|
description = "Utilites for Syndicated Actors and Synit"
|
||||||
license = "unlicense"
|
license = "unlicense"
|
||||||
srcDir = "src"
|
srcDir = "src"
|
||||||
bin = @["json_socket_translator", "json_translator", "msg", "preserve_process_environment"]
|
bin = @["json_socket_translator", "json_translator", "msg", "net_mapper", "preserve_process_environment"]
|
||||||
|
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
Loading…
Reference in New Issue