first try implementing the NUTSS (b) approach
This commit is contained in:
parent
9fac0cec0e
commit
3f27102e20
43
punchd.nim
43
punchd.nim
|
@ -5,6 +5,8 @@ import asyncutils
|
||||||
import message
|
import message
|
||||||
import tcp_syni_connect
|
import tcp_syni_connect
|
||||||
import tcp_syni_accept
|
import tcp_syni_accept
|
||||||
|
import tcp_nutss_initiator
|
||||||
|
import tcp_nutss_responder
|
||||||
|
|
||||||
from strutils import format, join
|
from strutils import format, join
|
||||||
from nativesockets import setSockOptInt
|
from nativesockets import setSockOptInt
|
||||||
|
@ -14,6 +16,8 @@ type
|
||||||
unixSocket: AsyncSocket
|
unixSocket: AsyncSocket
|
||||||
tcpSyniCP: TcpSyniConnectPuncher
|
tcpSyniCP: TcpSyniConnectPuncher
|
||||||
tcpSyniAP: TcpSyniAcceptPuncher
|
tcpSyniAP: TcpSyniAcceptPuncher
|
||||||
|
tcpNutssInitiator: TcpNutssInitiator
|
||||||
|
tcpNutssResponder: TcpNutssResponder
|
||||||
|
|
||||||
Sigint = object of CatchableError
|
Sigint = object of CatchableError
|
||||||
|
|
||||||
|
@ -31,6 +35,18 @@ type
|
||||||
srcPorts: seq[Port]
|
srcPorts: seq[Port]
|
||||||
seqNums: seq[uint32]
|
seqNums: seq[uint32]
|
||||||
|
|
||||||
|
TcpNutssInitiate = object
|
||||||
|
srcIp: IpAddress
|
||||||
|
srcPorts: seq[Port]
|
||||||
|
dstIp: IpAddress
|
||||||
|
dstPorts: seq[Port]
|
||||||
|
|
||||||
|
TcpNutssRespond = object
|
||||||
|
dstIp: IpAddress
|
||||||
|
dstPorts: seq[Port]
|
||||||
|
srcIp: IpAddress
|
||||||
|
srcPorts: seq[Port]
|
||||||
|
|
||||||
const PunchdSocket = "/tmp/punchd.socket"
|
const PunchdSocket = "/tmp/punchd.socket"
|
||||||
|
|
||||||
proc handleSigint() {.noconv.} =
|
proc handleSigint() {.noconv.} =
|
||||||
|
@ -47,19 +63,34 @@ proc handleRequest(punchd: Punchd, line: string,
|
||||||
case args[0]:
|
case args[0]:
|
||||||
of "tcp-syni-connect":
|
of "tcp-syni-connect":
|
||||||
let req = parseMessage[TcpSyniConnect](args[2])
|
let req = parseMessage[TcpSyniConnect](args[2])
|
||||||
proc handleSeqNumbers(seqNumbers: seq[uint32]) {.async.} =
|
proc progress(seqNumbers: seq[uint32]) {.async.} =
|
||||||
echo "progress! seqNumbers: ", seqNumbers
|
echo "progress! seqNumbers: ", seqNumbers
|
||||||
let content = @["tcp-syni-accept", $req.srcIp, req.srcPorts.join(","),
|
let content = @["tcp-syni-accept", $req.srcIp, req.srcPorts.join(","),
|
||||||
$req.dstIp, req.dstPorts.join(","),
|
$req.dstIp, req.dstPorts.join(","),
|
||||||
seqNumbers.join(",")].join("|")
|
seqNumbers.join(",")].join("|")
|
||||||
await unixSock.send(&"progress|{id}|{content}\n")
|
await unixSock.send(&"progress|{id}|{content}\n")
|
||||||
sock = await punchd.tcpSyniCP.connect(req.srcPorts[0], req.dstIp,
|
sock = await punchd.tcpSyniCP.connect(req.srcPorts[0], req.dstIp,
|
||||||
req.dstPorts, handleSeqNumbers)
|
req.dstPorts, progress)
|
||||||
|
|
||||||
of "tcp-syni-accept":
|
of "tcp-syni-accept":
|
||||||
let req = parseMessage[TcpSyniAccept](args[2])
|
let req = parseMessage[TcpSyniAccept](args[2])
|
||||||
sock = await punchd.tcpSyniAP.accept(req.srcPorts[0], req.dstIp,
|
sock = await punchd.tcpSyniAP.accept(req.srcPorts[0], req.dstIp,
|
||||||
req.dstPorts, req.seqNums)
|
req.dstPorts, req.seqNums)
|
||||||
|
|
||||||
|
of "tcp-nutss-initiate":
|
||||||
|
let req = parseMessage[TcpNutssInitiate](args[2])
|
||||||
|
proc progress() {.async.} =
|
||||||
|
echo "progress!"
|
||||||
|
let content = @["tcp-nutss-respond", $req.srcIp, req.srcPorts.join(","),
|
||||||
|
$req.dstIp, req.dstPorts.join(",")].join("|")
|
||||||
|
await unixSock.send(&"progress|{id}|{content}\n")
|
||||||
|
sock = await punchd.tcpNutssInitiator.initiate(req.srcPorts[0], req.dstIp,
|
||||||
|
req.dstPorts, progress)
|
||||||
|
|
||||||
|
of "tcp-nutss-respond":
|
||||||
|
let req = parseMessage[TcpNutssRespond](args[2])
|
||||||
|
sock = await punchd.tcpNutssResponder.respond(req.srcPorts[0], req.dstIp,
|
||||||
|
req.dstPorts)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "invalid request")
|
raise newException(ValueError, "invalid request")
|
||||||
|
@ -99,14 +130,18 @@ proc main() =
|
||||||
fpOthersRead, fpOthersWrite})
|
fpOthersRead, fpOthersWrite})
|
||||||
let punchd = Punchd(unixSocket: unixSocket,
|
let punchd = Punchd(unixSocket: unixSocket,
|
||||||
tcpSyniCP: initTcpSyniConnectPuncher(),
|
tcpSyniCP: initTcpSyniConnectPuncher(),
|
||||||
tcpSyniAP: initTcpSyniAcceptPuncher())
|
tcpSyniAP: initTcpSyniAcceptPuncher(),
|
||||||
|
tcpNutssInitiator: initTcpNutssInitiator(),
|
||||||
|
tcpNutssResponder: initTcpNutssResponder())
|
||||||
asyncCheck handleUsers(punchd)
|
asyncCheck handleUsers(punchd)
|
||||||
try:
|
try:
|
||||||
runForever()
|
runForever()
|
||||||
|
|
||||||
except Sigint:
|
except Sigint:
|
||||||
waitFor punchd.tcpSyniCP.cleanup()
|
waitFor punchd.tcpSyniCP.cleanup()
|
||||||
waitFor punchd.tcpSyniAP.cleanup
|
waitFor punchd.tcpSyniAP.cleanup()
|
||||||
|
waitFor punchd.tcpNutssInitiator.cleanup()
|
||||||
|
waitFor punchd.tcpNutssResponder.cleanup()
|
||||||
punchd.unixSocket.close()
|
punchd.unixSocket.close()
|
||||||
removeFile(PunchdSocket)
|
removeFile(PunchdSocket)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
import asyncdispatch, asyncnet, strformat
|
||||||
|
from net import IpAddress, Port, `$`, `==`, parseIpAddress
|
||||||
|
from random import randomize, rand
|
||||||
|
from sequtils import any
|
||||||
|
import ip_packet
|
||||||
|
import port_prediction
|
||||||
|
import puncher
|
||||||
|
import raw_socket
|
||||||
|
import utils
|
||||||
|
|
||||||
|
export PunchHoleError
|
||||||
|
|
||||||
|
type
|
||||||
|
Attempt = object
|
||||||
|
srcIp: IpAddress
|
||||||
|
srcPort: Port
|
||||||
|
dstIp: IpAddress
|
||||||
|
dstPorts: seq[Port]
|
||||||
|
future: Future[AsyncSocket]
|
||||||
|
|
||||||
|
TcpNutssInitiator* = Puncher[Attempt]
|
||||||
|
|
||||||
|
PunchProgressCb* = proc() {.async.}
|
||||||
|
|
||||||
|
proc cleanup*(puncher: TcpNutssInitiator) {.async.} =
|
||||||
|
while puncher.attempts.len() != 0:
|
||||||
|
await puncher.attempts.pop().deleteFirewallRules() # FIXME: needed?
|
||||||
|
|
||||||
|
proc initTcpNutssInitiator*(): TcpNutssInitiator =
|
||||||
|
randomize()
|
||||||
|
TcpNutssInitiator()
|
||||||
|
|
||||||
|
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 accept(puncher: TcpNutssInitiator, ip: IpAddress, port: Port) {.async.} =
|
||||||
|
let sock = newAsyncSocket()
|
||||||
|
#sock.setSockOpt(OptReuseAddr, true)
|
||||||
|
sock.bindAddr(port, $ip)
|
||||||
|
sock.listen()
|
||||||
|
while true:
|
||||||
|
let acceptFuture = sock.accept()
|
||||||
|
await acceptFuture or sleepAsync(Timeout)
|
||||||
|
if acceptFuture.finished():
|
||||||
|
let peer = acceptFuture.read()
|
||||||
|
let (peerAddr, peerPort) = peer.getPeerAddr()
|
||||||
|
let peerIp = parseIpAddress(peerAddr)
|
||||||
|
let i = puncher.findAttempt(ip, port, peerIp, @[peerPort])
|
||||||
|
if i == -1:
|
||||||
|
echo "Accepted connection, but no attempt found. Discarding."
|
||||||
|
peer.close()
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
let attempt = puncher.attempts[i]
|
||||||
|
attempt.future.complete(peer)
|
||||||
|
let attempts = puncher.findAttemptsByLocalAddr(ip, port)
|
||||||
|
if attempts.len() <= 1:
|
||||||
|
break
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
proc initiate*(puncher: TcpNutssInitiator, srcPort: Port, dstIp: IpAddress,
|
||||||
|
dstPorts: seq[Port], progressCb: PunchProgressCb):
|
||||||
|
Future[AsyncSocket] {.async.} =
|
||||||
|
let localIp = getPrimaryIPAddr(dstIp)
|
||||||
|
let existingAttempts = puncher.findAttemptsByLocalAddr(localIp, srcPort)
|
||||||
|
if existingAttempts.len() == 0:
|
||||||
|
echo &"initiating connection to {dstIp}:{dstPorts[0].int}"
|
||||||
|
asyncCheck puncher.accept(localIp, srcPort)
|
||||||
|
else:
|
||||||
|
for a in existingAttempts:
|
||||||
|
if a.dstIp == dstIp and
|
||||||
|
a.dstPorts.any(proc (p: Port): bool = p in dstPorts):
|
||||||
|
raise newException(PunchHoleError, "hole punching for given parameters already active")
|
||||||
|
try:
|
||||||
|
let attempt = Attempt(srcIp: localIp, srcPort: srcPort, dstIp: dstIp,
|
||||||
|
dstPorts: predictPortRange(dstPorts),
|
||||||
|
future: newFuture[AsyncSocket]("initiate"))
|
||||||
|
puncher.attempts.add(attempt)
|
||||||
|
#await attempt.addFirewallRules()
|
||||||
|
await attempt.injectSynPackets()
|
||||||
|
await progressCb()
|
||||||
|
await attempt.future or sleepAsync(Timeout)
|
||||||
|
#await attempt.deleteFirewallRules()
|
||||||
|
puncher.attempts.del(puncher.attempts.find(attempt))
|
||||||
|
if attempt.future.finished():
|
||||||
|
result = attempt.future.read()
|
||||||
|
else:
|
||||||
|
raise newException(PunchHoleError, "timeout")
|
||||||
|
except OSError as e:
|
||||||
|
raise newException(PunchHoleError, e.msg)
|
|
@ -0,0 +1,50 @@
|
||||||
|
import asyncdispatch, asyncnet, strformat
|
||||||
|
from net import IpAddress, Port, `$`, `==`
|
||||||
|
import puncher
|
||||||
|
import utils
|
||||||
|
|
||||||
|
export PunchHoleError
|
||||||
|
|
||||||
|
type
|
||||||
|
Attempt = object
|
||||||
|
srcIp: IpAddress
|
||||||
|
srcPort: Port
|
||||||
|
dstIp: IpAddress
|
||||||
|
dstPorts: seq[Port]
|
||||||
|
|
||||||
|
TcpNutssResponder* = Puncher[Attempt]
|
||||||
|
|
||||||
|
proc cleanup*(puncher: TcpNutssResponder) {.async.} =
|
||||||
|
while puncher.attempts.len() != 0:
|
||||||
|
await puncher.attempts.pop().deleteFirewallRules() # FIXME: needed?
|
||||||
|
|
||||||
|
proc initTcpNutssResponder*(): TcpNutssResponder =
|
||||||
|
TcpNutssResponder()
|
||||||
|
|
||||||
|
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 respond*(puncher: TcpNutssResponder, srcPort: Port, dstIp: IpAddress,
|
||||||
|
dstPorts: seq[Port]): Future[AsyncSocket] {.async.} =
|
||||||
|
let localIp = getPrimaryIPAddr(dstIp)
|
||||||
|
try:
|
||||||
|
let connectFuture = newFuture[AsyncSocket]("respond")
|
||||||
|
for dstPort in dstPorts:
|
||||||
|
asyncCheck connect(localIp, srcPort, 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)
|
Loading…
Reference in New Issue