try to implement clean firewall rule creation / destruction
This commit is contained in:
parent
f9f18ec3a4
commit
1dc7cf6676
|
@ -47,18 +47,18 @@ proc handleRequest(line: string, unixSock: AsyncSocket) {.async.} =
|
||||||
$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")
|
||||||
puncher = await initPuncher(req.srcPorts[0], req.dstIp, req.dstPorts)
|
puncher = initPuncher(req.srcPorts[0], req.dstIp, req.dstPorts)
|
||||||
sock = await puncher.connect(handleSeqNumbers)
|
sock = await puncher.connect(handleSeqNumbers)
|
||||||
|
|
||||||
of "tcp-syni-accept":
|
of "tcp-syni-accept":
|
||||||
let req = parseMessage[TcpSyniAccept](args[2])
|
let req = parseMessage[TcpSyniAccept](args[2])
|
||||||
puncher = await initPuncher(req.srcPorts[0], req.dstIp, req.dstPorts,
|
puncher = initPuncher(req.srcPorts[0], req.dstIp, req.dstPorts,
|
||||||
req.seqNums)
|
req.seqNums)
|
||||||
sock = await puncher.accept()
|
sock = await puncher.accept()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "invalid request")
|
raise newException(ValueError, "invalid request")
|
||||||
|
|
||||||
let unixFd = unixSock.getFd.AsyncFD
|
let unixFd = unixSock.getFd.AsyncFD
|
||||||
await unixFd.asyncSendMsg(&"ok|{id}\n", @[fromFd(sock.getFd.AsyncFD)])
|
await unixFd.asyncSendMsg(&"ok|{id}\n", @[fromFd(sock.getFd.AsyncFD)])
|
||||||
|
|
||||||
|
|
120
tcp_syni.nim
120
tcp_syni.nim
|
@ -10,20 +10,21 @@ var IPPROTO_IP {.importc: "IPPROTO_IP", header: "<netinet/in.h>".}: cint
|
||||||
var IP_TTL {.importc: "IP_TTL", header: "<netinet/in.h>".}: cint
|
var IP_TTL {.importc: "IP_TTL", header: "<netinet/in.h>".}: cint
|
||||||
|
|
||||||
type
|
type
|
||||||
TcpSyniPuncher* = object
|
TcpSyniPuncher* = ref object
|
||||||
srcIp: IpAddress
|
srcIp: IpAddress
|
||||||
srcPort: Port
|
srcPort: Port
|
||||||
dstIp: IpAddress
|
dstIp: IpAddress
|
||||||
dstPorts: seq[Port]
|
dstPorts: seq[Port]
|
||||||
seqNums: seq[uint32]
|
seqNums: seq[uint32]
|
||||||
|
firewallRules: seq[string]
|
||||||
|
|
||||||
PunchProgressCb* = proc (seqNums: seq[uint32]) {.async.}
|
PunchProgressCb* = proc (seqNums: seq[uint32]) {.async.}
|
||||||
|
|
||||||
PunchHoleError* = object of ValueError
|
PunchHoleError* = object of ValueError
|
||||||
|
|
||||||
proc addFirewallRule(srcIp: IpAddress, srcPort: Port,
|
proc makeFirewallRule(srcIp: IpAddress, srcPort: Port,
|
||||||
dstIp: IpAddress, dstPort: Port) {.async.} =
|
dstIp: IpAddress, dstPort: Port): string =
|
||||||
let firewall_cmd = fmt"""iptables -I INPUT \
|
result = fmt"""-w \
|
||||||
-d {srcIp} \
|
-d {srcIp} \
|
||||||
-p icmp \
|
-p icmp \
|
||||||
--icmp-type time-exceeded \
|
--icmp-type time-exceeded \
|
||||||
|
@ -35,22 +36,13 @@ proc addFirewallRule(srcIp: IpAddress, srcPort: Port,
|
||||||
--ctorigdst {dstIp} \
|
--ctorigdst {dstIp} \
|
||||||
--ctorigdstport {dstPort.int} \
|
--ctorigdstport {dstPort.int} \
|
||||||
-j DROP"""
|
-j DROP"""
|
||||||
|
|
||||||
|
proc iptablesInsert(chain: string, rule: string) {.async.} =
|
||||||
|
let firewall_cmd = fmt"iptables -I {chain} {rule}"
|
||||||
discard await asyncExecCmd(firewall_cmd)
|
discard await asyncExecCmd(firewall_cmd)
|
||||||
|
|
||||||
proc delFirewallRule(srcIp: IpAddress, srcPort: Port,
|
proc iptablesDelete(chain: string, rule: string) {.async.} =
|
||||||
dstIp: IpAddress, dstPort: Port) {.async.} =
|
let firewall_cmd = fmt"iptables -D {chain} {rule}"
|
||||||
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"""
|
|
||||||
discard await asyncExecCmd(firewall_cmd)
|
discard await asyncExecCmd(firewall_cmd)
|
||||||
|
|
||||||
proc captureSeqNumbers(puncher: TcpSyniPuncher, rawFd: AsyncFD,
|
proc captureSeqNumbers(puncher: TcpSyniPuncher, rawFd: AsyncFD,
|
||||||
|
@ -98,7 +90,7 @@ proc injectSyns(rawFd: AsyncFD, srcIp: IpAddress, srcPort: Port,
|
||||||
echo "cannot inject {srcIp}:{srcPort.int} -> {dstIp}:{dstPort.int} (seq {seqNum}): ", e.msg
|
echo "cannot inject {srcIp}:{srcPort.int} -> {dstIp}:{dstPort.int} (seq {seqNum}): ", e.msg
|
||||||
|
|
||||||
proc initPuncher*(srcPort: Port, dstIp: IpAddress, dstPorts: array[3, Port],
|
proc initPuncher*(srcPort: Port, dstIp: IpAddress, dstPorts: array[3, Port],
|
||||||
seqNums: seq[uint32] = @[]): Future[TcpSyniPuncher] {.async.} =
|
seqNums: seq[uint32] = @[]): TcpSyniPuncher =
|
||||||
let localIp = getPrimaryIPAddr(dstIp)
|
let localIp = getPrimaryIPAddr(dstIp)
|
||||||
# TODO: do real port prediction
|
# TODO: do real port prediction
|
||||||
var predictedDstPorts = newSeq[Port](3)
|
var predictedDstPorts = newSeq[Port](3)
|
||||||
|
@ -109,31 +101,58 @@ proc initPuncher*(srcPort: Port, dstIp: IpAddress, dstPorts: array[3, Port],
|
||||||
result = TcpSyniPuncher(srcIp: localIp, srcPort: srcPort, dstIp: dstIp,
|
result = TcpSyniPuncher(srcIp: localIp, srcPort: srcPort, dstIp: dstIp,
|
||||||
dstPorts: predictedDstPorts, seqNums: seqNums)
|
dstPorts: predictedDstPorts, seqNums: seqNums)
|
||||||
|
|
||||||
proc doConnect(srcIp: IpAddress, srcPort: Port, dstIp: IpAddress,
|
proc addFirewallRules(puncher: TcpSyniPuncher) {.async.} =
|
||||||
dstPort: Port, future: Future[AsyncSocket]) {.async.} =
|
for dstPort in puncher.dstPorts:
|
||||||
|
let rule = makeFirewallRule(puncher.srcIp, puncher.srcPort,
|
||||||
|
puncher.dstIp, dstPort)
|
||||||
|
try:
|
||||||
|
await iptablesInsert("INPUT", rule)
|
||||||
|
puncher.firewallRules.add(rule)
|
||||||
|
except OSError as e:
|
||||||
|
echo "cannot add firewall rule: ", e.msg
|
||||||
|
raise newException(PunchHoleError, e.msg)
|
||||||
|
|
||||||
|
proc cleanup*(puncher: TcpSyniPuncher) {.async.} =
|
||||||
|
for rule in puncher.firewallRules:
|
||||||
|
try:
|
||||||
|
await iptablesDelete("INPUT", rule)
|
||||||
|
except OSError:
|
||||||
|
# At least we tried
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc doConnect(srcIp: IpAddress, srcPort: Port, dstIp: IpAddress, dstPort: Port,
|
||||||
|
future: Future[AsyncSocket]) {.async.} =
|
||||||
let sock = newAsyncSocket()
|
let sock = newAsyncSocket()
|
||||||
sock.setSockOpt(OptReuseAddr, true)
|
sock.setSockOpt(OptReuseAddr, true)
|
||||||
sock.getFd.setSockOptInt(IPPROTO_IP, IP_TTL, 2)
|
sock.getFd.setSockOptInt(IPPROTO_IP, IP_TTL, 2)
|
||||||
sock.bindAddr(srcPort, $srcIp)
|
sock.bindAddr(srcPort, $srcIp)
|
||||||
try:
|
|
||||||
await addFirewallRule(srcIp, srcPort, dstIp, dstPort)
|
|
||||||
except OSError as e:
|
|
||||||
echo "cannot add firewall rule: ", e.msg
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
await sock.connect($dstIp, dstPort)
|
await sock.connect($dstIp, dstPort)
|
||||||
future.complete(sock)
|
future.complete(sock)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
echo &"connection {srcIP}:{srcPort.int} -> {dstIp}:{dstPort.int} failed: ", e.msg
|
echo &"connection {srcIP}:{srcPort.int} -> {dstIp}:{dstPort.int} failed: ", e.msg
|
||||||
discard
|
discard
|
||||||
try:
|
|
||||||
await delFirewallRule(srcIp, srcPort, dstIp, dstPort)
|
|
||||||
except OSError as e:
|
|
||||||
echo "cannot delete firewall rule: ", e.msg
|
|
||||||
|
|
||||||
proc doAccept(puncher: TcpSyniPuncher, future: Future[AsyncSocket]) {.async.} =
|
proc connectParallel(puncher: TcpSyniPuncher): Future[AsyncSocket] =
|
||||||
|
result = newFuture[AsyncSocket]("doConnect")
|
||||||
|
for dstPort in puncher.dstPorts:
|
||||||
|
asyncCheck doConnect(puncher.srcIp, puncher.srcPort, puncher.dstIp, dstPort, result)
|
||||||
|
|
||||||
|
proc connect*(puncher: TcpSyniPuncher,
|
||||||
|
progressCb: PunchProgressCb): Future[AsyncSocket] {.async.} =
|
||||||
|
let iface = fromIpAddress(puncher.srcIp)
|
||||||
|
let rawFd = setupEthernetCapturingSocket(iface)
|
||||||
|
asyncCheck puncher.captureSeqNumbers(rawFd, progressCb)
|
||||||
|
await puncher.addFirewallRules()
|
||||||
|
try:
|
||||||
|
result = await puncher.connectParallel()
|
||||||
|
await puncher.cleanup()
|
||||||
|
except OSError as e:
|
||||||
|
raise newException(PunchHoleError, e.msg)
|
||||||
|
|
||||||
|
proc prepareAccept(puncher: TcpSyniPuncher) {.async.} =
|
||||||
|
# FIXME: timeouts
|
||||||
for dstPort in puncher.dstPorts:
|
for dstPort in puncher.dstPorts:
|
||||||
# TODO: connect in parallel for better performance
|
|
||||||
try:
|
try:
|
||||||
let sock = newAsyncSocket()
|
let sock = newAsyncSocket()
|
||||||
sock.setSockOpt(OptReuseAddr, true)
|
sock.setSockOpt(OptReuseAddr, true)
|
||||||
|
@ -144,12 +163,10 @@ proc doAccept(puncher: TcpSyniPuncher, future: Future[AsyncSocket]) {.async.} =
|
||||||
sock.close()
|
sock.close()
|
||||||
except OSError:
|
except OSError:
|
||||||
discard
|
discard
|
||||||
try:
|
|
||||||
await addFirewallRule(puncher.srcIp, puncher.srcPort, puncher.dstIp,
|
proc accept*(puncher: TcpSyniPuncher): Future[AsyncSocket] {.async.} =
|
||||||
dstPort)
|
await puncher.prepareAccept()
|
||||||
except OSError as e:
|
await puncher.addFirewallRules()
|
||||||
echo "cannot add firewall rule: ", e.msg
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
# FIXME: timeout
|
# FIXME: timeout
|
||||||
let rawFd = setupTcpInjectingSocket()
|
let rawFd = setupTcpInjectingSocket()
|
||||||
|
@ -161,28 +178,9 @@ proc doAccept(puncher: TcpSyniPuncher, future: Future[AsyncSocket]) {.async.} =
|
||||||
sock.bindAddr(puncher.srcPort, $(puncher.srcIp))
|
sock.bindAddr(puncher.srcPort, $(puncher.srcIp))
|
||||||
sock.listen()
|
sock.listen()
|
||||||
echo &"accepting connections from {puncher.dstIp}:{puncher.dstPorts[0].int}"
|
echo &"accepting connections from {puncher.dstIp}:{puncher.dstPorts[0].int}"
|
||||||
let connectedSock = await sock.accept()
|
result = await sock.accept()
|
||||||
future.complete(connectedSock)
|
await puncher.cleanup()
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
echo &"accepting connections from {puncher.dstIP}:{puncher.dstPorts[0].int} failed: ", e.msg
|
echo &"accepting connections from {puncher.dstIP}:{puncher.dstPorts[0].int} failed: ", e.msg
|
||||||
discard
|
await puncher.cleanup()
|
||||||
for dstPort in puncher.dstPorts:
|
raise newException(PunchHoleError, e.msg)
|
||||||
try:
|
|
||||||
await delFirewallRule(puncher.srcIp, puncher.srcPort, puncher.dstIp,
|
|
||||||
dstPort)
|
|
||||||
except OSError as e:
|
|
||||||
echo "cannot delete firewall rule: ", e.msg
|
|
||||||
|
|
||||||
proc connect*(puncher: TcpSyniPuncher,
|
|
||||||
progressCb: PunchProgressCb): Future[AsyncSocket] =
|
|
||||||
result = newFuture[AsyncSocket]("tcp_syni.connect")
|
|
||||||
let iface = fromIpAddress(puncher.srcIp)
|
|
||||||
let rawFd = setupEthernetCapturingSocket(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")
|
|
||||||
asyncCheck puncher.doAccept(result)
|
|
||||||
|
|
Loading…
Reference in New Issue