106 lines
4.0 KiB
Nim
106 lines
4.0 KiB
Nim
from nativesockets import Protocol
|
|
from net import IpAddress, Port, `$`, `==`
|
|
from random import randomize, rand
|
|
import asyncdispatch, asyncnet, strformat
|
|
import ip_packet
|
|
import message
|
|
import options
|
|
import port_prediction
|
|
import puncher
|
|
import raw_socket
|
|
import utils
|
|
|
|
export puncher
|
|
|
|
type
|
|
TcpNutssPuncher* = ref object of Puncher
|
|
|
|
InitiateRequest = object
|
|
srcIp: IpAddress
|
|
srcPorts: seq[Port]
|
|
dstIp: IpAddress
|
|
dstPorts: seq[Port]
|
|
|
|
RespondRequest = object
|
|
dstIp: IpAddress
|
|
dstPorts: seq[Port]
|
|
srcIp: IpAddress
|
|
srcPorts: seq[Port]
|
|
extraArgs: string
|
|
|
|
proc injectSynPackets(attempt: Attempt) {.async.} =
|
|
let injectFd = setupTcpInjectingSocket()
|
|
for dstPort in attempt.dstPorts:
|
|
let synOut = IpPacket(protocol: tcp, ipAddrSrc: attempt.srcIp,
|
|
ipAddrDst: attempt.dstIp, ipTTL: 2,
|
|
tcpPortSrc: attempt.srcPort, tcpPortDst: dstPort,
|
|
tcpSeqNumber: rand(uint32), tcpAckNumber: 0,
|
|
tcpFlags: {SYN}, tcpWindowSize: 1452 * 10)
|
|
echo &"[{synOut.ipAddrSrc}:{synOut.tcpPortSrc} -> {synOut.ipAddrDst}:{synOut.tcpPortDst}, SEQ {synOut.tcpSeqNumber}] injecting outgoing SYN"
|
|
await injectFd.injectTcpPacket(synOut)
|
|
|
|
proc connect(srcIp: IpAddress, srcPort: Port, dstIp: IpAddress, dstPort: Port,
|
|
future: Future[AsyncSocket]) {.async.} =
|
|
let sock = newAsyncSocket()
|
|
sock.setSockOpt(OptReuseAddr, true)
|
|
echo &"connect {srcIp}:{srcPort} -> {dstIp}:{dstPort}"
|
|
sock.bindAddr(srcPort, $srcIp)
|
|
try:
|
|
await sock.connect($dstIp, dstPort)
|
|
future.complete(sock)
|
|
except OSError as e:
|
|
echo &"connection {srcIP}:{srcPort.int} -> {dstIp}:{dstPort.int} failed: ", e.msg
|
|
sock.close()
|
|
|
|
proc initTcpNutssPuncher*(): TcpNutssPuncher =
|
|
randomize()
|
|
TcpNutssPuncher()
|
|
|
|
method parseInitiateRequest*(puncher: TcpNutssPuncher, args: string): Attempt =
|
|
let parsed = parseMessage[InitiateRequest](args)
|
|
let localIp = getPrimaryIPAddr(parsed.dstIp)
|
|
let predictedDstPorts = predictPortRange(parsed.dstPorts)
|
|
let acceptFuture = newFuture[AsyncSocket]("parseInitiateRequest")
|
|
Attempt(protocol: IPPROTO_TCP, srcIp: localIp, srcPort: parsed.srcPorts[0],
|
|
dstIp: parsed.dstIp, dstPorts: predictedDstPorts,
|
|
acceptFuture: some(acceptFuture))
|
|
|
|
method parseRespondRequest*(puncher: TcpNutssPuncher, args: string): Attempt =
|
|
let parsed = parseMessage[RespondRequest](args)
|
|
let localIp = getPrimaryIPAddr(parsed.dstIp)
|
|
let predictedDstPorts = predictPortRange(parsed.dstPorts)
|
|
Attempt(protocol: IPPROTO_TCP, srcIp: localIp, srcPort: parsed.srcPorts[0],
|
|
dstIp: parsed.dstIp, dstPorts: predictedDstPorts,
|
|
acceptFuture: none(Future[AsyncSocket]))
|
|
|
|
method initiate*(puncher: TcpNutssPuncher, attempt: Attempt,
|
|
progress: PunchProgressCb): Future[AsyncSocket] {.async.} =
|
|
assert(attempt.acceptFuture.isSome(), "expected attempt with acceptFuture")
|
|
try:
|
|
let acceptFuture = attempt.acceptFuture.get()
|
|
await injectSynPackets(attempt)
|
|
await progress("") # FIXME: await means we wait until the rendezvous server replied
|
|
await acceptFuture or sleepAsync(Timeout)
|
|
if acceptFuture.finished():
|
|
result = acceptFuture.read()
|
|
else:
|
|
raise newException(PunchHoleError, "timeout")
|
|
except OSError as e:
|
|
raise newException(PunchHoleError, e.msg)
|
|
|
|
method respond*(puncher: TcpNutssPuncher, attempt: Attempt):
|
|
Future[AsyncSocket] {.async.} =
|
|
assert(attempt.acceptFuture.isNone(), "expected attempt without acceptFuture")
|
|
try:
|
|
let connectFuture = newFuture[AsyncSocket]("respond")
|
|
for dstPort in attempt.dstPorts:
|
|
asyncCheck connect(attempt.srcIp, attempt.srcPort, attempt.dstIp, dstPort,
|
|
connectFuture)
|
|
await connectFuture or sleepAsync(Timeout)
|
|
if connectFuture.finished():
|
|
result = connectFuture.read()
|
|
else:
|
|
raise newException(PunchHoleError, "timeout")
|
|
except OSError as e:
|
|
raise newException(PunchHoleError, e.msg)
|