punchd/tcp_syni_initiator.nim

121 lines
4.5 KiB
Nim
Raw Normal View History

import asyncdispatch, asyncnet, strformat
from net import IpAddress, Port, `$`, `==`
from nativesockets import setSockOptInt
from strutils import join
import ip_packet
import message
import network_interface
import port_prediction
import puncher
import raw_socket
import utils
export Puncher, Initiator, PunchProgressCb, PunchHoleError, cleanup, initiate
type
TcpSyniInitiator* = ref object of Initiator
Request = object
srcIp: IpAddress
srcPorts: seq[Port]
dstIp: IpAddress
dstPorts: seq[Port]
var IPPROTO_IP {.importc: "IPPROTO_IP", header: "<netinet/in.h>".}: cint
var IP_TTL {.importc: "IP_TTL", header: "<netinet/in.h>".}: cint
method cleanup*(puncher: TcpSyniInitiator): Future[void] {.async.} =
while puncher.attempts.len() != 0:
await puncher.attempts.pop().deleteFirewallRules()
2020-10-22 00:22:11 +02:00
proc initTcpSyniInitiator*(): TcpSyniInitiator =
TcpSyniInitiator()
proc captureSeqNumbers(attempt: Attempt, cb: PunchProgressCb) {.async.} =
# FIXME: timeout?
let iface = getNetworkInterface(attempt.srcIp)
let captureFd = setupEthernetCapturingSocket(iface)
var seqNums = newSeq[uint32]()
while seqNums.len < attempt.dstPorts.len:
let packet = await captureFd.recv(4000)
if packet == "":
break
let parsed = parseEthernetPacket(packet)
if parsed.protocol == tcp and
parsed.ipAddrSrc == attempt.srcIp and
parsed.tcpPortSrc.int == attempt.srcPort.int and
parsed.ipAddrDst == attempt.dstIp and
parsed.tcpFlags == {SYN}:
for port in attempt.dstPorts:
if parsed.tcpPortDst.int == port.int:
seqNums.add(parsed.tcpSeqNumber)
break
closeSocket(captureFd)
await cb(seqNums.join(","))
proc captureAndResendAck(attempt: Attempt) {.async.} =
let iface = getNetworkInterface(attempt.srcIp)
let captureFd = setupEthernetCapturingSocket(iface)
let injectFd = setupTcpInjectingSocket()
block loops:
while true:
let packet = await captureFd.recv(4000)
if packet == "":
break
var parsed = parseEthernetPacket(packet)
if parsed.protocol == tcp and
parsed.ipAddrSrc == attempt.srcIp and
parsed.tcpPortSrc.int == attempt.srcPort.int and
parsed.ipAddrDst == attempt.dstIp and
parsed.tcpFlags == {ACK}:
for port in attempt.dstPorts:
if parsed.tcpPortDst.int == port.int:
parsed.ipTTL = 64
echo &"[{parsed.ipAddrSrc}:{parsed.tcpPortSrc.int} -> {parsed.ipAddrDst}:{parsed.tcpPortDst}, SEQ {parsed.tcpSeqNumber}] resending ACK with TTL {parsed.ipTTL}"
await injectFd.injectTcpPacket(parsed)
break loops
closeSocket(captureFd)
closeSocket(injectFd)
2020-10-22 00:22:11 +02:00
proc connect(srcIp: IpAddress, srcPort: Port, dstIp: IpAddress, dstPort: Port,
future: Future[AsyncSocket]) {.async.} =
let sock = newAsyncSocket()
sock.setSockOpt(OptReuseAddr, true)
sock.getFd.setSockOptInt(IPPROTO_IP, IP_TTL, 2)
2020-10-22 00:22:11 +02:00
echo &"connect {srcIp}:{srcPort} -> {dstIp}:{dstPort}"
sock.bindAddr(srcPort, $srcIp)
try:
await sock.connect($dstIp, dstPort)
sock.getFd.setSockOptInt(IPPROTO_IP, IP_TTL, 64)
future.complete(sock)
except OSError as e:
echo &"connection {srcIP}:{srcPort.int} -> {dstIp}:{dstPort.int} failed: ", e.msg
sock.close()
method initiate*(puncher: TcpSyniInitiator, args: string,
progressCb: PunchProgressCb): Future[AsyncSocket] {.async.} =
let req = parseMessage[Request](args)
let localIp = getPrimaryIPAddr(req.dstIp)
if puncher.findAttempt(localIp, req.srcPorts[0], req.dstIp, req.dstPorts) != -1:
raise newException(PunchHoleError, "hole punching for given parameters already active")
let attempt = Attempt(srcIp: localIp, srcPort: req.srcPorts[0], dstIp: req.dstIp,
dstPorts: predictPortRange(req.dstPorts))
puncher.attempts.add(attempt)
await attempt.addFirewallRules()
asyncCheck attempt.captureSeqNumbers(progressCb)
asyncCheck attempt.captureAndResendAck()
try:
let connectFuture = newFuture[AsyncSocket]("connect")
for dstPort in attempt.dstPorts:
2020-10-22 00:22:11 +02:00
asyncCheck connect(attempt.srcIp, attempt.srcPort, attempt.dstIp,
2020-10-22 00:40:14 +02:00
dstPort, connectFuture)
await connectFuture or sleepAsync(Timeout)
await attempt.deleteFirewallRules()
puncher.attempts.del(puncher.attempts.find(attempt))
if connectFuture.finished():
result = connectFuture.read()
else:
raise newException(PunchHoleError, "timeout")
except OSError as e:
raise newException(PunchHoleError, e.msg)