diff --git a/asyncreadline.nim b/asyncutils.nim similarity index 50% rename from asyncreadline.nim rename to asyncutils.nim index 7990262..36d23a8 100644 --- a/asyncreadline.nim +++ b/asyncutils.nim @@ -1,7 +1,6 @@ +import asyncdispatch, threadpool, osproc + ## asyncReadline as discussed at https://github.com/nim-lang/Nim/issues/11564 - -import asyncdispatch, threadpool - proc asyncReadline*(): Future[string] = let event = newAsyncEvent() let future = newFuture[string]("asyncReadline") @@ -15,4 +14,15 @@ proc asyncReadline*(): Future[string] = addEvent(event, callback) return future - +proc asyncExecCmd*(command: string): Future[int] = + let event = newAsyncEvent() + let future = newFuture[int]("asyncExecCmd") + proc execCmdBackground(event: AsyncEvent, command: string): int = + result = execCmd(command) + event.trigger() + let flowVar = spawn execCmdBackground(event, command) + proc callback(fd: AsyncFD): bool = + future.complete(^flowVar) + true + addEvent(event, callback) + return future diff --git a/punch.nim b/punch.nim index 16c966c..7231146 100644 --- a/punch.nim +++ b/punch.nim @@ -1,7 +1,9 @@ import asyncdispatch, + asyncutils, network_interface, - packet_info + packet_info, + strformat from nativesockets import SOCK_RAW, bindAddr, htons from posix import ioctl, setsockopt, SockAddr, SockLen, SocketHandle @@ -35,38 +37,38 @@ var PACKET_ADD_MEMBERSHIP {.importc: "PACKET_ADD_MEMBERSHIP", header: "".}: cushort PACKET_MR_PROMISC {.importc: "PACKET_MR_PROMISC", header: "".}: cushort -proc punchHoleAsClient*(clientAddress: string, - clientPort: Port, - serverAddress: string, - serverPort: Port) {.async.} = - echo "punchHoleAsClient" +proc setupRawSocket(iface: NetworkInterface): AsyncFD = # Create a raw packet socket. For accessing outgoing packets we need to use # ETH_P_ALL which is needed in network byte order, see packet(7) man page. - let rawFd = createAsyncNativeSocket(AF_PACKET.cint, - SOCK_RAW.cint, - htons(ETH_P_ALL).cint) + result = createAsyncNativeSocket(AF_PACKET.cint, + SOCK_RAW.cint, + htons(ETH_P_ALL).cint) # Limit capturing of packets to the desired network interface, see # netdevice(7) man page - let iface = fromIpAddress(clientAddress) echo "interface: ", iface.name, ", index: ", iface.index var sa = Sockaddr_ll(sll_family: AF_PACKET, sll_protocol: htons(ETH_P_ALL), sll_ifindex: iface.index) - if bindAddr(rawFd.SocketHandle, + if bindAddr(result.SocketHandle, cast[ptr SockAddr](addr sa), sizeof(Sockaddr_ll).SockLen) != 0: raise newException(PunchHoleError, "cannot bind to interface") # Enable promiscuous mode, see netdevice(7) man page var req = Packet_mreq(mr_ifindex: iface.index, mr_type: PACKET_MR_PROMISC) - if setsockopt(rawFd.SocketHandle, + if setsockopt(result.SocketHandle, SOL_PACKET.cint, PACKET_ADD_MEMBERSHIP.cint, addr req, sizeof(req).SockLen) != 0: raise newException(PunchHoleError, "cannot enable promiscuous mode") +proc captureSequenceNumber(rawFd: AsyncFD, + clientAddress: string, + clientPort: Port, + serverAddress: string, + serverPort: Port): Future[uint32] {.async.} = while true: let packet = await rawFd.recv(4000) if packet == "": @@ -78,8 +80,61 @@ proc punchHoleAsClient*(clientAddress: string, packetInfo.tcpPortSrc.int == clientPort.int and packetInfo.tcpIpDst == serverAddress and packetInfo.tcpPortDst.int == serverPort.int: - echo "captured packet, sequence number: ", packetInfo.tcpSeqNumber - break + return packetInfo.tcpSeqNumber +proc addFirewallRule(clientAddress: string, + clientPort: Port, + serverAddress: string, + serverPort: Port) {.async.} = + let firewall_cmd = fmt"""iptables -A INPUT \ + -d {clientAddress} \ + -p icmp \ + --icmp-type time-exceeded \ + -m conntrack \ + --ctstate RELATED \ + --ctproto tcp \ + --ctorigsrc {clientAddress} \ + --ctorigsrcport {clientPort.int} \ + --ctorigdst {serverAddress} \ + --ctorigdstport {serverPort.int} \ + -j DROP""" + let exitcode = await asyncExecCmd(firewall_cmd) + if exitcode != 0: + raise newException(PunchHoleError, "cannot add firewall rule") + +proc deleteFirewallRule(clientAddress: string, + clientPort: Port, + serverAddress: string, + serverPort: Port) {.async.} = + let firewall_cmd = fmt"""iptables -D INPUT \ + -d {clientAddress} \ + -p icmp \ + --icmp-type time-exceeded \ + -m conntrack \ + --ctstate RELATED \ + --ctproto tcp \ + --ctorigsrc {clientAddress} \ + --ctorigsrcport {clientPort.int} \ + --ctorigdst {serverAddress} \ + --ctorigdstport {serverPort.int} \ + -j DROP""" + let exitcode = await asyncExecCmd(firewall_cmd) + if exitcode != 0: + raise newException(PunchHoleError, "cannot delete firewall rule") + +proc punchHoleAsClient*(clientAddress: string, + clientPort: Port, + serverAddress: string, + serverPort: Port) {.async.} = + let iface = fromIpAddress(clientAddress) + let rawFd = setupRawSocket(iface) + let seqNumber = await captureSequenceNumber(rawFd, + clientAddress, + clientPort, + serverAddress, + serverPort) + echo "captured sequence number: ", seqNumber + await addFirewallRule(clientAddress, clientPort, serverAddress, serverPort) + await deleteFirewallRule(clientAddress, clientPort, serverAddress, serverPort) closeSocket(rawFd) diff --git a/syni_prototype.nim b/syni_prototype.nim index 33319e6..b050d89 100644 --- a/syni_prototype.nim +++ b/syni_prototype.nim @@ -2,7 +2,7 @@ import net, asyncnet, asyncdispatch, - asyncreadline, + asyncutils, punch from strutils import format, join