2020-10-25 21:57:09 +01:00
|
|
|
from nativesockets import Protocol, SockType
|
|
|
|
from net import IpAddress, Port, `$`, `==`
|
|
|
|
import asyncdispatch
|
|
|
|
import asyncnet
|
|
|
|
import message
|
|
|
|
import options
|
|
|
|
import port_prediction
|
|
|
|
import puncher
|
|
|
|
import strformat
|
|
|
|
import utils
|
|
|
|
|
|
|
|
export puncher
|
|
|
|
|
|
|
|
type
|
|
|
|
UdpPuncher* = 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 doInitiate(srcIp: IpAddress, srcPort: Port, dstIp: IpAddress,
|
|
|
|
dstPort: Port, initiateFuture: Future[AsyncSocket]) {.async.} =
|
|
|
|
let sock = newAsyncSocket(sockType = SOCK_DGRAM, protocol = IPPROTO_UDP)
|
|
|
|
sock.bindAddr(srcPort, $srcIp)
|
|
|
|
await sock.connect($dstIp, dstPort)
|
|
|
|
await sock.send("SYN")
|
|
|
|
let recvFuture = sock.recv(6) # "SYNACK"
|
|
|
|
await recvFuture or sleepAsync(Timeout)
|
|
|
|
if recvFuture.finished():
|
|
|
|
let msg = recvFuture.read()
|
|
|
|
echo &"connection {srcIp}:{srcPort.int} -> {dstIp}:{dstPort.int} succeeded: ", msg
|
|
|
|
await sock.send("ACK")
|
|
|
|
initiateFuture.complete(sock)
|
|
|
|
else:
|
2020-10-25 22:23:39 +01:00
|
|
|
sock.close()
|
2020-10-25 21:57:09 +01:00
|
|
|
echo &"connection {srcIp}:{srcPort.int} -> {dstIp}:{dstPort.int} timed out"
|
|
|
|
|
|
|
|
proc doRespond(srcIp: IpAddress, srcPort: Port, dstIp: IpAddress,
|
|
|
|
dstPort: Port, respondFuture: Future[AsyncSocket]) {.async.} =
|
|
|
|
let sock = newAsyncSocket(sockType = SOCK_DGRAM, protocol = IPPROTO_UDP)
|
|
|
|
sock.bindAddr(srcPort, $srcIp)
|
|
|
|
await sock.connect($dstIp, dstPort)
|
|
|
|
await sock.send("SYNACK")
|
|
|
|
let recvFuture = sock.recv(3) # "ACK"
|
|
|
|
await recvFuture or sleepAsync(Timeout)
|
|
|
|
if recvFuture.finished():
|
|
|
|
let msg = recvFuture.read()
|
|
|
|
echo &"connection {srcIp}:{srcPort.int} -> {dstIp}:{dstPort.int} succeeded: ", msg
|
|
|
|
respondFuture.complete(sock)
|
|
|
|
else:
|
|
|
|
echo &"connection {srcIp}:{srcPort.int} -> {dstIp}:{dstPort.int} timed out"
|
|
|
|
|
|
|
|
proc initUdpPuncher*(): UdpPuncher =
|
|
|
|
UdpPuncher()
|
|
|
|
|
|
|
|
method parseInitiateRequest*(puncher: UdpPuncher, args: string): Attempt =
|
|
|
|
let parsed = parseMessage[InitiateRequest](args)
|
|
|
|
let localIp = getPrimaryIPAddr(parsed.dstIp)
|
|
|
|
let predictedDstPorts = predictPortRange(parsed.dstPorts)
|
|
|
|
Attempt(protocol: IPPROTO_UDP, srcIp: localIp, srcPort: parsed.srcPorts[0],
|
|
|
|
dstIp: parsed.dstIp, dstPorts: predictedDstPorts,
|
|
|
|
acceptFuture: none(Future[AsyncSocket]))
|
|
|
|
|
|
|
|
method parseRespondRequest*(puncher: UdpPuncher, args: string): Attempt =
|
|
|
|
let parsed = parseMessage[RespondRequest](args)
|
|
|
|
let localIp = getPrimaryIPAddr(parsed.dstIp)
|
|
|
|
let predictedDstPorts = predictPortRange(parsed.dstPorts)
|
|
|
|
Attempt(protocol: IPPROTO_UDP, srcIp: localIp, srcPort: parsed.srcPorts[0],
|
|
|
|
dstIp: parsed.dstIp, dstPorts: predictedDstPorts,
|
|
|
|
acceptFuture: none(Future[AsyncSocket]))
|
|
|
|
|
|
|
|
method initiate*(puncher: UdpPuncher, attempt: Attempt,
|
|
|
|
progress: PunchProgressCb): Future[AsyncSocket] {.async.} =
|
|
|
|
assert(attempt.acceptFuture.isNone(), "expected attempt without acceptFuture")
|
|
|
|
try:
|
|
|
|
let initiateFuture = newFuture[AsyncSocket]("initiate")
|
|
|
|
for dstPort in attempt.dstPorts:
|
|
|
|
asyncCheck doInitiate(attempt.srcIp, attempt.srcPort, attempt.dstIp,
|
|
|
|
dstPort, initiateFuture)
|
|
|
|
await progress("")
|
|
|
|
await initiateFuture or sleepAsync(Timeout)
|
|
|
|
if initiateFuture.finished():
|
|
|
|
result = initiateFuture.read()
|
|
|
|
else:
|
|
|
|
raise newException(PunchHoleError, "timeout")
|
|
|
|
except OSError as e:
|
|
|
|
raise newException(PunchHoleError, e.msg)
|
|
|
|
|
|
|
|
method respond*(puncher: UdpPuncher, attempt: Attempt):
|
|
|
|
Future[AsyncSocket] {.async.} =
|
|
|
|
assert(attempt.acceptFuture.isNone(), "expected attempt without acceptFuture")
|
|
|
|
try:
|
|
|
|
let respondFuture = newFuture[AsyncSocket]("respond")
|
|
|
|
for dstPort in attempt.dstPorts:
|
|
|
|
asyncCheck doRespond(attempt.srcIp, attempt.srcPort, attempt.dstIp,
|
|
|
|
dstPort, respondFuture)
|
|
|
|
await respondFuture or sleepAsync(Timeout)
|
|
|
|
if respondFuture.finished():
|
|
|
|
result = respondFuture.read()
|
|
|
|
else:
|
|
|
|
raise newException(PunchHoleError, "timeout")
|
|
|
|
except OSError as e:
|
|
|
|
raise newException(PunchHoleError, e.msg)
|