2020-07-07 19:39:28 +02:00
|
|
|
from nativesockets import Domain, SockType, Protocol
|
2020-10-24 18:44:37 +02:00
|
|
|
from net import IpAddress, Port, `$`, `==`, parseIpAddress
|
|
|
|
from sequtils import filter
|
|
|
|
import asyncdispatch, asyncnet, os, strformat, strutils
|
2020-07-07 19:39:28 +02:00
|
|
|
import asyncutils
|
|
|
|
import message
|
2020-10-24 18:44:37 +02:00
|
|
|
import options
|
2020-10-23 01:15:37 +02:00
|
|
|
import tables
|
2020-10-24 18:44:37 +02:00
|
|
|
import tcp_syni
|
|
|
|
import tcp_nutss
|
2020-07-07 19:39:28 +02:00
|
|
|
|
|
|
|
from strutils import format, join
|
|
|
|
from nativesockets import setSockOptInt
|
|
|
|
|
|
|
|
type
|
2020-08-22 12:34:12 +02:00
|
|
|
Punchd = ref object
|
|
|
|
unixSocket: AsyncSocket
|
2020-10-24 18:44:37 +02:00
|
|
|
punchers: Table[string, Puncher]
|
|
|
|
attempts: seq[Attempt]
|
2020-08-22 12:34:12 +02:00
|
|
|
|
|
|
|
Sigint = object of CatchableError
|
|
|
|
|
2020-10-12 21:31:55 +02:00
|
|
|
const PunchdSocket = "/tmp/punchd.socket"
|
|
|
|
|
2020-07-28 00:17:45 +02:00
|
|
|
proc handleSigint() {.noconv.} =
|
|
|
|
raise newException(Sigint, "received SIGINT")
|
|
|
|
|
2020-10-16 00:18:11 +02:00
|
|
|
proc sendToClient(unixSock: AsyncSocket, msg: string,
|
|
|
|
cmsgs: seq[ControlMessage] = @[]) {.async.} =
|
|
|
|
if not unixSock.isClosed():
|
|
|
|
let unixFd = unixSock.getFd.AsyncFD
|
|
|
|
await unixFd.asyncSendMsg(msg, cmsgs)
|
|
|
|
|
2020-10-24 18:44:37 +02:00
|
|
|
proc findAttemptsByLocalAddr(punchd: Punchd, srcIp: IpAddress,
|
|
|
|
srcPort: Port): seq[Attempt] =
|
|
|
|
proc matchesLocalAddr(a: Attempt): bool =
|
|
|
|
a.srcIp == srcIp and a.srcPort == srcPort
|
|
|
|
punchd.attempts.filter(matchesLocalAddr)
|
|
|
|
|
|
|
|
proc acceptConnections(punchd: Punchd, ip: IpAddress, port: Port,
|
|
|
|
protocol: Protocol) {.async.} =
|
|
|
|
var sockType: SockType
|
|
|
|
case protocol:
|
|
|
|
of IPPROTO_TCP:
|
|
|
|
sockType = SOCK_STREAM
|
|
|
|
of IPPROTO_UDP:
|
|
|
|
sockType = SOCK_DGRAM
|
|
|
|
else:
|
|
|
|
assert(false, "can only accept TCP or UDP connections")
|
|
|
|
let sock = newAsyncSocket(sockType = sockType, protocol = protocol)
|
|
|
|
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 query = Attempt(srcIp: ip, srcPort: port, dstIp: peerIp,
|
|
|
|
dstPorts: @[peerPort])
|
|
|
|
let i = punchd.attempts.find(query)
|
|
|
|
if i == -1:
|
|
|
|
echo "Accepted connection, but no attempt found. Discarding."
|
|
|
|
peer.close()
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
let acceptFuture = punchd.attempts[i].acceptFuture.get()
|
|
|
|
acceptFuture.complete(peer)
|
|
|
|
let localAddrMatches = punchd.findAttemptsByLocalAddr(ip, port)
|
|
|
|
if localAddrMatches.len() <= 1:
|
|
|
|
break
|
|
|
|
sock.close()
|
|
|
|
|
|
|
|
proc addAttempt(punchd: Punchd, attempt: Attempt, puncher: Puncher) =
|
|
|
|
let localAddrMatches = punchd.findAttemptsByLocalAddr(attempt.srcIp,
|
|
|
|
attempt.srcPort)
|
|
|
|
punchd.attempts.add(attempt)
|
|
|
|
if localAddrMatches.len() == 0:
|
|
|
|
if attempt.acceptFuture.isSome():
|
|
|
|
asyncCheck punchd.acceptConnections(attempt.srcIp, attempt.srcPort,
|
|
|
|
puncher.getProtocol())
|
|
|
|
elif localAddrMatches.contains(attempt):
|
|
|
|
raise newException(PunchHoleError,
|
|
|
|
"hole punching for given parameters already active")
|
|
|
|
|
|
|
|
proc removeAttempt(punchd: Punchd, attempt: Attempt) =
|
|
|
|
punchd.attempts.del(punchd.attempts.find(attempt))
|
|
|
|
|
2020-08-22 12:34:12 +02:00
|
|
|
proc handleRequest(punchd: Punchd, line: string,
|
|
|
|
unixSock: AsyncSocket) {.async.} =
|
2020-07-07 19:39:28 +02:00
|
|
|
var id: string
|
|
|
|
var sock: AsyncSocket
|
2020-10-24 18:44:37 +02:00
|
|
|
var attempt: Attempt
|
2020-07-07 19:39:28 +02:00
|
|
|
try:
|
2020-10-22 00:22:11 +02:00
|
|
|
let args = line.parseArgs(4)
|
2020-07-07 19:39:28 +02:00
|
|
|
id = args[1]
|
2020-10-24 18:44:37 +02:00
|
|
|
let puncher = punchd.punchers[args[2]]
|
2020-10-22 00:22:11 +02:00
|
|
|
|
2020-07-07 19:39:28 +02:00
|
|
|
case args[0]:
|
2020-10-22 00:22:11 +02:00
|
|
|
of "initiate":
|
2020-10-24 18:44:37 +02:00
|
|
|
attempt = puncher.parseInitiateRequest(args[3])
|
|
|
|
punchd.addAttempt(attempt, puncher)
|
2020-10-23 01:15:37 +02:00
|
|
|
proc progress(extraArgs: string) {.async.} =
|
|
|
|
let msg = &"progress|{id}|{args[2]}|{args[3]}|{extraArgs}\n"
|
|
|
|
await sendToClient(unixSock, msg)
|
2020-10-24 18:44:37 +02:00
|
|
|
sock = await puncher.initiate(attempt, progress)
|
|
|
|
punchd.removeAttempt(attempt)
|
2020-10-22 00:22:11 +02:00
|
|
|
|
|
|
|
of "respond":
|
2020-10-24 18:44:37 +02:00
|
|
|
attempt = puncher.parseRespondRequest(args[3])
|
|
|
|
punchd.addAttempt(attempt, puncher)
|
|
|
|
sock = await puncher.respond(attempt)
|
|
|
|
punchd.removeAttempt(attempt)
|
2020-10-22 00:22:11 +02:00
|
|
|
|
2020-07-07 19:39:28 +02:00
|
|
|
else:
|
|
|
|
raise newException(ValueError, "invalid request")
|
2020-08-21 23:12:45 +02:00
|
|
|
|
2020-10-16 00:18:11 +02:00
|
|
|
await sendToClient(unixSock, &"ok|{id}\n", @[fromFd(sock.getFd.AsyncFD)])
|
2020-10-07 00:31:04 +02:00
|
|
|
sock.close()
|
2020-07-07 19:39:28 +02:00
|
|
|
|
|
|
|
except PunchHoleError as e:
|
2020-10-24 18:44:37 +02:00
|
|
|
punchd.removeAttempt(attempt)
|
2020-10-16 00:18:11 +02:00
|
|
|
await sendToClient(unixSock, &"error|{id}|{e.msg}\n")
|
2020-10-23 01:15:37 +02:00
|
|
|
except KeyError, ValueError:
|
2020-07-07 19:39:28 +02:00
|
|
|
unixSock.close
|
|
|
|
|
2020-08-22 12:34:12 +02:00
|
|
|
proc handleRequests(punchd: Punchd, userSock: AsyncSocket) {.async.} =
|
2020-07-07 19:39:28 +02:00
|
|
|
while true:
|
2020-07-27 23:51:16 +02:00
|
|
|
if userSock.isClosed:
|
|
|
|
break
|
2020-07-07 19:39:28 +02:00
|
|
|
let line = await userSock.recvLine(maxLength = 400)
|
|
|
|
if line.len == 0:
|
2020-10-10 11:14:31 +02:00
|
|
|
userSock.close()
|
2020-07-07 19:39:28 +02:00
|
|
|
break
|
2020-08-22 12:34:12 +02:00
|
|
|
asyncCheck punchd.handleRequest(line, userSock)
|
2020-07-07 19:39:28 +02:00
|
|
|
|
2020-08-22 12:34:12 +02:00
|
|
|
proc handleUsers(punchd: Punchd) {.async.} =
|
2020-07-07 19:39:28 +02:00
|
|
|
while true:
|
2020-08-22 12:34:12 +02:00
|
|
|
let user = await punchd.unixSocket.accept()
|
|
|
|
asyncCheck punchd.handleRequests(user)
|
2020-07-07 19:39:28 +02:00
|
|
|
|
|
|
|
proc main() =
|
2020-07-28 00:17:45 +02:00
|
|
|
setControlCHook(handleSigint)
|
|
|
|
removeFile(PunchdSocket)
|
2020-10-02 17:12:29 +02:00
|
|
|
let unixSocket = newAsyncSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP)
|
|
|
|
unixSocket.bindUnix(PunchdSocket)
|
|
|
|
unixSocket.listen()
|
2020-07-28 00:18:27 +02:00
|
|
|
setFilePermissions(PunchdSocket,
|
|
|
|
{fpUserRead, fpUserWrite, fpGroupRead, fpGroupWrite,
|
|
|
|
fpOthersRead, fpOthersWrite})
|
2020-10-23 01:15:37 +02:00
|
|
|
let punchd = Punchd(unixSocket: unixSocket)
|
2020-10-24 18:44:37 +02:00
|
|
|
punchd.punchers["tcp-syni"] = initTcpSyniPuncher()
|
|
|
|
punchd.punchers["tcp-nutss"] = initTcpNutssPuncher()
|
2020-08-22 12:34:12 +02:00
|
|
|
asyncCheck handleUsers(punchd)
|
2020-07-28 00:17:45 +02:00
|
|
|
try:
|
|
|
|
runForever()
|
|
|
|
|
|
|
|
except Sigint:
|
2020-10-24 18:44:37 +02:00
|
|
|
while punchd.attempts.len() != 0:
|
|
|
|
waitFor punchd.attempts.pop().cleanup()
|
2020-08-22 13:11:55 +02:00
|
|
|
punchd.unixSocket.close()
|
2020-07-28 00:17:45 +02:00
|
|
|
removeFile(PunchdSocket)
|
2020-07-07 19:39:28 +02:00
|
|
|
|
|
|
|
when isMainModule:
|
|
|
|
main()
|