enter punchd
This commit is contained in:
parent
9056b28627
commit
f5e07b79f4
140
punch.nim
140
punch.nim
|
@ -1,140 +0,0 @@
|
||||||
import
|
|
||||||
asyncdispatch,
|
|
||||||
asyncutils,
|
|
||||||
network_interface,
|
|
||||||
packet_info,
|
|
||||||
strformat
|
|
||||||
|
|
||||||
from nativesockets import SOCK_RAW, bindAddr, htons
|
|
||||||
from posix import ioctl, setsockopt, SockAddr, SockLen, SocketHandle
|
|
||||||
|
|
||||||
export NetworkInterfaceError
|
|
||||||
|
|
||||||
type
|
|
||||||
Sockaddr_ll {.importc: "struct sockaddr_ll", pure, final,
|
|
||||||
header: "<linux/if_packet.h>".} = object
|
|
||||||
sll_family: cushort # Always AF_PACKET
|
|
||||||
sll_protocol: cushort # Physical-layer protocol
|
|
||||||
sll_ifindex: cint # Interface number
|
|
||||||
sll_hatype: cushort # ARP hardware type
|
|
||||||
sll_pkttype: cuchar # Packet type
|
|
||||||
sll_halen: cuchar # Length of address
|
|
||||||
sll_addr: array[8, cuchar] # Physical-layer address
|
|
||||||
|
|
||||||
Packet_mreq {.importc: "struct packet_mreq", pure, final,
|
|
||||||
header: "<linux/if_packet.h>".} = object
|
|
||||||
mr_ifindex: cint
|
|
||||||
mr_type: cushort
|
|
||||||
mr_alen: cushort
|
|
||||||
mr_address: array[8, cuchar]
|
|
||||||
|
|
||||||
PunchHoleError* = object of ValueError
|
|
||||||
|
|
||||||
var
|
|
||||||
AF_PACKET {.importc: "AF_PACKET", header: "<sys/socket.h>".}: cushort
|
|
||||||
SOL_PACKET {.importc: "SOL_PACKET", header: "<sys/socket.h>".}: cushort
|
|
||||||
ETH_P_ALL {.importc: "ETH_P_ALL", header: "<linux/if_ether.h>".}: cushort
|
|
||||||
PACKET_ADD_MEMBERSHIP {.importc: "PACKET_ADD_MEMBERSHIP", header: "<linux/if_packet.h>".}: cushort
|
|
||||||
PACKET_MR_PROMISC {.importc: "PACKET_MR_PROMISC", header: "<linux/if_packet.h>".}: cushort
|
|
||||||
|
|
||||||
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.
|
|
||||||
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
|
|
||||||
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(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(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 == "":
|
|
||||||
break
|
|
||||||
echo "packet len: ", packet.len
|
|
||||||
let packetInfo = fromString(packet)
|
|
||||||
if packetInfo.protocol == tcp and
|
|
||||||
packetInfo.tcpIpSrc == clientAddress and
|
|
||||||
packetInfo.tcpPortSrc.int == clientPort.int and
|
|
||||||
packetInfo.tcpIpDst == serverAddress and
|
|
||||||
packetInfo.tcpPortDst.int == serverPort.int:
|
|
||||||
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)
|
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
import asyncdispatch, asyncnet, os, strformat, strutils
|
||||||
|
from nativesockets import Domain, SockType, Protocol
|
||||||
|
from net import IpAddress, Port, `$`
|
||||||
|
import asyncutils
|
||||||
|
import message
|
||||||
|
import tcp_syni
|
||||||
|
|
||||||
|
from strutils import format, join
|
||||||
|
from nativesockets import setSockOptInt
|
||||||
|
|
||||||
|
type
|
||||||
|
# Requests
|
||||||
|
TcpSyniConnect = object
|
||||||
|
srcIp: IpAddress
|
||||||
|
srcPorts: array[3, Port]
|
||||||
|
dstIp: IpAddress
|
||||||
|
dstPorts: array[3, Port]
|
||||||
|
|
||||||
|
TcpSyniAccept = object
|
||||||
|
dstIp: IpAddress
|
||||||
|
dstPorts: array[3, Port]
|
||||||
|
srcIp: IpAddress
|
||||||
|
srcPorts: array[3, Port]
|
||||||
|
seqNums: array[10, uint32]
|
||||||
|
|
||||||
|
proc handleRequest(line: string, unixSock: AsyncSocket) {.async.} =
|
||||||
|
var id: string
|
||||||
|
var sock: AsyncSocket
|
||||||
|
var puncher: TcpSyniPuncher
|
||||||
|
try:
|
||||||
|
let args = line.parseArgs(3)
|
||||||
|
id = args[1]
|
||||||
|
|
||||||
|
case args[0]:
|
||||||
|
of "tcp-syni-connect":
|
||||||
|
let req = parseMessage[TcpSyniConnect](args[2])
|
||||||
|
proc handleSeqNumbers(seqNumbers: seq[uint32]) {.async.} =
|
||||||
|
let content = @["tcp-syni-accept", $req.srcIp, req.srcPorts.join(","),
|
||||||
|
$req.dstIp, req.dstPorts.join(","),
|
||||||
|
seqNumbers.join(",")].join("|")
|
||||||
|
await unixSock.send(&"progress|{id}|{content}\n")
|
||||||
|
puncher = await initPuncher(req.srcPorts[0], req.dstIp, req.dstPorts)
|
||||||
|
sock = await puncher.connect(handleSeqNumbers)
|
||||||
|
|
||||||
|
of "tcp-syni-accept":
|
||||||
|
let req = parseMessage[TcpSyniAccept](args[2])
|
||||||
|
puncher = await initPuncher(req.srcPorts[0], req.dstIp, req.dstPorts,
|
||||||
|
@(req.seqNums))
|
||||||
|
sock = await puncher.accept()
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise newException(ValueError, "invalid request")
|
||||||
|
|
||||||
|
let unixFd = unixSock.getFd.AsyncFD
|
||||||
|
await unixFd.asyncSendMsg(&"ok|{id}\n", @[fromFd(sock.getFd.AsyncFD)])
|
||||||
|
await puncher.cleanup
|
||||||
|
|
||||||
|
except PunchHoleError as e:
|
||||||
|
await unixSock.send(&"error|{id}|{e.msg}\n")
|
||||||
|
await puncher.cleanup
|
||||||
|
except ValueError:
|
||||||
|
unixSock.close
|
||||||
|
|
||||||
|
proc handleRequests(userSock: AsyncSocket) {.async.} =
|
||||||
|
while true:
|
||||||
|
let line = await userSock.recvLine(maxLength = 400)
|
||||||
|
if line.len == 0:
|
||||||
|
break
|
||||||
|
asyncCheck handleRequest(line, userSock)
|
||||||
|
|
||||||
|
proc handleUsers(sock: AsyncSocket) {.async.} =
|
||||||
|
while true:
|
||||||
|
let user = await sock.accept()
|
||||||
|
asyncCheck handleRequests(user)
|
||||||
|
|
||||||
|
proc main() =
|
||||||
|
removeFile("/tmp/punchd.socket")
|
||||||
|
let unixSocket = newAsyncSocket(AF_UNIX, SOCK_STREAM, IPPROTO_IP)
|
||||||
|
unixSocket.bindUnix("/tmp/punchd.socket")
|
||||||
|
asyncCheck handleUsers(unixSocket)
|
||||||
|
runForever()
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
main()
|
|
@ -0,0 +1,60 @@
|
||||||
|
import asyncdispatch
|
||||||
|
from nativesockets import SOCK_RAW, bindAddr, htons
|
||||||
|
from posix import setsockopt, SockAddr, SockLen, SocketHandle
|
||||||
|
import network_interface
|
||||||
|
|
||||||
|
type
|
||||||
|
RawSocketError* = object of CatchableError
|
||||||
|
|
||||||
|
Sockaddr_ll {.importc: "struct sockaddr_ll", pure, final,
|
||||||
|
header: "<linux/if_packet.h>".} = object
|
||||||
|
sll_family: cushort # Always AF_PACKET
|
||||||
|
sll_protocol: cushort # Physical-layer protocol
|
||||||
|
sll_ifindex: cint # Interface number
|
||||||
|
sll_hatype: cushort # ARP hardware type
|
||||||
|
sll_pkttype: cuchar # Packet type
|
||||||
|
sll_halen: cuchar # Length of address
|
||||||
|
sll_addr: array[8, cuchar] # Physical-layer address
|
||||||
|
|
||||||
|
Packet_mreq {.importc: "struct packet_mreq", pure, final,
|
||||||
|
header: "<linux/if_packet.h>".} = object
|
||||||
|
mr_ifindex: cint
|
||||||
|
mr_type: cushort
|
||||||
|
mr_alen: cushort
|
||||||
|
mr_address: array[8, cuchar]
|
||||||
|
|
||||||
|
var
|
||||||
|
AF_PACKET {.importc: "AF_PACKET", header: "<sys/socket.h>".}: cushort
|
||||||
|
SOL_PACKET {.importc: "SOL_PACKET", header: "<sys/socket.h>".}: cushort
|
||||||
|
ETH_P_ALL {.importc: "ETH_P_ALL", header: "<linux/if_ether.h>".}: cushort
|
||||||
|
PACKET_ADD_MEMBERSHIP {.importc: "PACKET_ADD_MEMBERSHIP", header: "<linux/if_packet.h>".}: cushort
|
||||||
|
PACKET_MR_PROMISC {.importc: "PACKET_MR_PROMISC", header: "<linux/if_packet.h>".}: cushort
|
||||||
|
|
||||||
|
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.
|
||||||
|
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
|
||||||
|
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(result.SocketHandle,
|
||||||
|
cast[ptr SockAddr](addr sa),
|
||||||
|
sizeof(Sockaddr_ll).SockLen) != 0:
|
||||||
|
raise newException(RawSocketError, "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(result.SocketHandle,
|
||||||
|
SOL_PACKET.cint,
|
||||||
|
PACKET_ADD_MEMBERSHIP.cint,
|
||||||
|
addr req,
|
||||||
|
sizeof(req).SockLen) != 0:
|
||||||
|
raise newException(RawSocketError, "cannot enable promiscuous mode")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
import asyncdispatch, asyncnet, strformat
|
||||||
|
from net import IpAddress, Port, `$`, getPrimaryIPAddr
|
||||||
|
from nativesockets import setSockOptInt
|
||||||
|
import asyncutils
|
||||||
|
import network_interface
|
||||||
|
import packet_info
|
||||||
|
import raw_socket
|
||||||
|
|
||||||
|
var IPPROTO_IP {.importc: "IPPROTO_IP", header: "<netinet/in.h>".}: cint
|
||||||
|
var IP_TTL {.importc: "IP_TTL", header: "<netinet/in.h>".}: cint
|
||||||
|
|
||||||
|
type
|
||||||
|
TcpSyniPuncher* = object
|
||||||
|
srcIp: IpAddress
|
||||||
|
srcPort: Port
|
||||||
|
dstIp: IpAddress
|
||||||
|
dstPorts: array[10, Port]
|
||||||
|
seqNums: seq[uint32]
|
||||||
|
|
||||||
|
PunchProgressCb* = proc (seqNums: seq[uint32]) {.async.}
|
||||||
|
|
||||||
|
PunchHoleError* = object of ValueError
|
||||||
|
|
||||||
|
proc addFirewallRule(srcIp: IpAddress, srcPort: Port,
|
||||||
|
dstIp: IpAddress, dstPort: Port) {.async.} =
|
||||||
|
let firewall_cmd = fmt"""iptables -A INPUT \
|
||||||
|
-d {srcIp} \
|
||||||
|
-p icmp \
|
||||||
|
--icmp-type time-exceeded \
|
||||||
|
-m conntrack \
|
||||||
|
--ctstate RELATED \
|
||||||
|
--ctproto tcp \
|
||||||
|
--ctorigsrc {srcIp} \
|
||||||
|
--ctorigsrcport {srcPort.int} \
|
||||||
|
--ctorigdst {dstIp} \
|
||||||
|
--ctorigdstport {dstPort.int} \
|
||||||
|
-j DROP"""
|
||||||
|
let exitcode = await asyncExecCmd(firewall_cmd)
|
||||||
|
if exitcode != 0:
|
||||||
|
raise newException(PunchHoleError, "cannot add firewall rule")
|
||||||
|
|
||||||
|
proc delFirewallRule(srcIp: IpAddress, srcPort: Port,
|
||||||
|
dstIp: IpAddress, dstPort: Port) {.async.} =
|
||||||
|
let firewall_cmd = fmt"""iptables -D INPUT \
|
||||||
|
-d {srcIp} \
|
||||||
|
-p icmp \
|
||||||
|
--icmp-type time-exceeded \
|
||||||
|
-m conntrack \
|
||||||
|
--ctstate RELATED \
|
||||||
|
--ctproto tcp \
|
||||||
|
--ctorigsrc {srcIp} \
|
||||||
|
--ctorigsrcport {srcPort.int} \
|
||||||
|
--ctorigdst {dstIp} \
|
||||||
|
--ctorigdstport {dstPort.int} \
|
||||||
|
-j DROP"""
|
||||||
|
let exitcode = await asyncExecCmd(firewall_cmd)
|
||||||
|
if exitcode != 0:
|
||||||
|
raise newException(PunchHoleError, "cannot delete firewall rule")
|
||||||
|
|
||||||
|
proc captureSeqNumbers(puncher: TcpSyniPuncher, rawFd: AsyncFD,
|
||||||
|
cb: PunchProgressCb) {.async.} =
|
||||||
|
# FIXME: timeout?
|
||||||
|
var seqNums = newSeq[uint32]()
|
||||||
|
while seqNums.len < puncher.dstPorts.len:
|
||||||
|
let packet = await rawFd.recv(4000)
|
||||||
|
if packet == "":
|
||||||
|
break
|
||||||
|
echo "packet len: ", packet.len
|
||||||
|
let packetInfo = fromString(packet)
|
||||||
|
if packetInfo.protocol == tcp and
|
||||||
|
packetInfo.tcpIpSrc == $puncher.srcIp and
|
||||||
|
packetInfo.tcpPortSrc.int == puncher.srcPort.int and
|
||||||
|
packetInfo.tcpIpDst == $puncher.dstIp:
|
||||||
|
for i, port in puncher.dstPorts.pairs:
|
||||||
|
if packetInfo.tcpPortDst.int == port.int:
|
||||||
|
seqNums.add(packetInfo.tcpSeqNumber)
|
||||||
|
break
|
||||||
|
await cb(seqNums)
|
||||||
|
|
||||||
|
proc initPuncher*(srcPort: Port, dstIp: IpAddress, dstPorts: array[3, Port],
|
||||||
|
seqNums: seq[uint32] = @[]): Future[TcpSyniPuncher] {.async.} =
|
||||||
|
let localIp = getPrimaryIPAddr(dstIp)
|
||||||
|
# TODO: do real port prediction
|
||||||
|
var predictedDstPorts: array[10, Port]
|
||||||
|
let basePort = min(dstPorts[1].uint16, uint16.high - 9)
|
||||||
|
for i in 0.uint16 .. 9.uint16:
|
||||||
|
predictedDstPorts[i] = Port(basePort + i)
|
||||||
|
result = TcpSyniPuncher(srcIp: localIp, srcPort: srcPort, dstIp: dstIp,
|
||||||
|
dstPorts: predictedDstPorts, seqNums: seqNums)
|
||||||
|
for dstPort in result.dstPorts:
|
||||||
|
await addFirewallRule(result.srcIp, result.srcPort, result.dstIp, dstPort)
|
||||||
|
|
||||||
|
proc cleanup*(puncher: TcpSyniPuncher) {.async.} =
|
||||||
|
for dstPort in puncher.dstPorts:
|
||||||
|
await delFirewallRule(puncher.srcIp, puncher.srcPort, puncher.dstIp, dstPort)
|
||||||
|
|
||||||
|
proc doConnect(srcIp: IpAddress, srcPort: Port, dstIp: IpAddress,
|
||||||
|
dstPort: Port, future: Future[AsyncSocket]) {.async.} =
|
||||||
|
let sock = newAsyncSocket()
|
||||||
|
sock.setSockOpt(OptReuseAddr, true)
|
||||||
|
sock.getFd.setSockOptInt(IPPROTO_IP, IP_TTL, 2)
|
||||||
|
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
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc connect*(puncher: TcpSyniPuncher,
|
||||||
|
progressCb: PunchProgressCb): Future[AsyncSocket] =
|
||||||
|
result = newFuture[AsyncSocket]("tcp_syni.connect")
|
||||||
|
let iface = fromIpAddress($puncher.srcIp)
|
||||||
|
let rawFd = setupRawSocket(iface)
|
||||||
|
asyncCheck puncher.captureSeqNumbers(rawFd, progressCb)
|
||||||
|
for dstPort in puncher.dstPorts:
|
||||||
|
asyncCheck doConnect(puncher.srcIp, puncher.srcPort, puncher.dstIp,
|
||||||
|
dstPort, result)
|
||||||
|
|
||||||
|
proc accept*(puncher: TcpSyniPuncher): Future[AsyncSocket] =
|
||||||
|
result = newFuture[AsyncSocket]("tcp_syni.accept")
|
Loading…
Reference in New Issue