punchd/puncher.nim

88 lines
2.9 KiB
Nim

import asyncdispatch, strformat
from net import IpAddress, Port, `$`, toSockAddr
from nativesockets import SockAddr, Sockaddr_storage, SockLen
from sequtils import any
import asyncutils
import ip_packet
type
Attempt = tuple | object
Puncher*[T: Attempt] = ref object
attempts*: seq[T]
PunchHoleError* = object of ValueError
const Timeout* = 3000
proc findAttempt*(puncher: Puncher, srcIp: IpAddress, srcPort: Port,
dstIp: IpAddress, dstPorts: seq[Port]): int =
for (index, attempt) in puncher.attempts.pairs():
if attempt.srcIp == srcIp and attempt.srcPort == srcPort and
attempt.dstIp == dstIp and
attempt.dstPorts.any(proc (p: Port): bool = p in dstPorts):
return index
return -1
proc findAttemptsByLocalAddr*(puncher: Puncher[Attempt], address: IpAddress,
port: Port): seq[Attempt] =
for attempt in puncher.attempts:
if attempt.srcIp == address and attempt.srcPort == port:
result.add(attempt)
proc injectTcpPacket*(rawFd: AsyncFD, ipPacket: IpPacket) {.async.} =
assert(ipPacket.protocol == tcp)
try:
let packet = serialize(ipPacket)
var sockaddr: Sockaddr_storage
var sockaddrLen: SockLen
toSockAddr(ipPacket.ipAddrDst, ipPacket.tcpPortDst, sockaddr, sockaddrLen)
await rawFd.sendTo(packet.cstring, packet.len,
cast[ptr SockAddr](addr sockaddr), sockaddrLen)
except OSError as e:
raise newException(PunchHoleError, e.msg)
proc makeFirewallRule(srcIp: IpAddress, srcPort: Port,
dstIp: IpAddress, dstPort: Port): string =
# FIXME: use & instead of fmt?
result = fmt"""-w \
-d {srcIp} \
-p icmp \
--icmp-type time-exceeded \
-m conntrack \
--ctstate RELATED \
--ctproto tcp \
--ctorigsrc {srcIp} \
--ctorigsrcport {srcPort.int} \
--ctorigdst {dstIp} \
--ctorigdstport {dstPort.int} \
-j DROP"""
proc iptablesInsert(chain: string, rule: string) {.async.} =
let firewall_cmd = fmt"iptables -I {chain} {rule}"
discard await asyncExecCmd(firewall_cmd)
proc iptablesDelete(chain: string, rule: string) {.async.} =
let firewall_cmd = fmt"iptables -D {chain} {rule}"
discard await asyncExecCmd(firewall_cmd)
proc addFirewallRules*(attempt: Attempt) {.async.} =
for dstPort in attempt.dstPorts:
let rule = makeFirewallRule(attempt.srcIp, attempt.srcPort,
attempt.dstIp, dstPort)
try:
await iptablesInsert("INPUT", rule)
except OSError as e:
echo "cannot add firewall rule: ", e.msg
raise newException(PunchHoleError, e.msg)
proc deleteFirewallRules*(attempt: Attempt) {.async.} =
for dstPort in attempt.dstPorts:
let rule = makeFirewallRule(attempt.srcIp, attempt.srcPort,
attempt.dstIp, dstPort)
try:
await iptablesDelete("INPUT", rule)
except OSError:
# At least we tried
discard