aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/nft-event-listener.nu72
-rw-r--r--api/update-netbird-nftables.nu (renamed from api/netbird-nft-sets.nu)28
-rw-r--r--test-data/mock-nft-mon.nu4
-rw-r--r--test-data/nft-mon-output.json3
4 files changed, 97 insertions, 10 deletions
diff --git a/api/nft-event-listener.nu b/api/nft-event-listener.nu
new file mode 100644
index 0000000..0554c6f
--- /dev/null
+++ b/api/nft-event-listener.nu
@@ -0,0 +1,72 @@
+def "job recv pending" []: nothing -> list {
+ "marker" | job send (job id) # send a marker to self
+ mut events = []
+ mut ev = job recv # marker will prevent wait here
+ while $ev != "marker" {
+ $events = $events | append $ev
+ $ev = job recv
+ }
+ $events
+}
+
+# may wait up to two intervals, because event timestamps are not accounted
+def create-debounce-queue [interval: duration, handler: closure] {
+ job spawn -d "debounce queue" {
+ mut event = job recv
+ loop {
+ sleep $interval
+ let new_events = job recv pending
+ if ($new_events | is-empty) {
+ do $handler $event
+ $event = job recv
+ } else {
+ $event = ($new_events | last)
+ }
+ }
+ }
+}
+
+def single [] {
+ let $data = $in
+ if ($data | length) != 1 {
+ make error "Expected single element"
+ }
+ $data | first
+}
+
+def parse-nft-mon-event [] {
+ mut rest = $in
+ mut result = {}
+ for key_name in ['action', 'type'] {
+ let key = $rest | items {|k, v| $k} | single
+ $rest = $rest | get $key
+ $result = $result | insert $key_name $key
+ }
+ $result | merge $rest
+}
+
+def main [--test] {
+ let debounce_interval = 2sec
+ let debounce_queue = create-debounce-queue $debounce_interval {|ev|
+ print "Triggered" $ev
+ # nu update-netbird-nftables.nu --apply
+ }
+ try {
+ if $test {
+ nu test-data/mock-nft-mon.nu
+ } else {
+ ^nft --json monitor rules
+ } | lines
+ | each { from json | parse-nft-mon-event }
+ | where action == add and type == rule and table == netbird # TODO filter update evenets
+ | each { job send $debounce_queue }
+ | ignore
+ print "Exiting gracefully..."
+ sleep $debounce_interval
+ sleep $debounce_interval
+ } catch { |e|
+ print $e.rendered
+ } finally {
+ job kill $debounce_queue
+ }
+} \ No newline at end of file
diff --git a/api/netbird-nft-sets.nu b/api/update-netbird-nftables.nu
index 2187aa6..95b9e02 100644
--- a/api/netbird-nft-sets.nu
+++ b/api/update-netbird-nftables.nu
@@ -1,7 +1,3 @@
-# Usage:
-# ^nft --json list sets | nu --stdin api/netbird-nft-sets.nu
-# open --raw test-data/nft-sets.json | nu --stdin api/netbird-nft-sets.nu
-
use netbird-api.nu
def mangle-name [prefix: string, name: string] {
@@ -32,15 +28,27 @@ def generate-nft-commands [] {
# nft add talbe inet <name> # ignore if table already exists
# nft destroy table inet <name> # ignore if table already removed
let set_changes = $in
- for $s in $set_changes {
+ $set_changes | each {|s|
let path = [$s.family $s.table $s.nft_name] | str join ' '
- print $"flush set ($path)"
- print $"add element ($path) { ($s.new_elem | str join ', ') }"
- }
+ [
+ $"flush set ($path)",
+ $"add element ($path) { ($s.new_elem | str join ', ') }"
+ ]
+ } | flatten
}
-def main [] {
- $in | from json | get nftables | skip 1 | get set
+def main [--test, --apply] {
+ let commands = if $test {
+ open --raw test-data/nft-sets.json
+ } else {
+ ^nft --json list sets
+ } | from json | get nftables | skip 1 | get set
| find-changed-sets
| generate-nft-commands
+ | str join "\n"
+ if $apply {
+ $commands | ^nft -f -
+ } else {
+ $commands
+ }
}
diff --git a/test-data/mock-nft-mon.nu b/test-data/mock-nft-mon.nu
new file mode 100644
index 0000000..9048dc3
--- /dev/null
+++ b/test-data/mock-nft-mon.nu
@@ -0,0 +1,4 @@
+const events = 'test-data/nft-mon-output.json'
+open --raw $events | lines | each { sleep 1sec; print $in } | ignore
+sleep 5sec
+open --raw $events | lines | each { sleep 1sec; print $in } | ignore \ No newline at end of file
diff --git a/test-data/nft-mon-output.json b/test-data/nft-mon-output.json
new file mode 100644
index 0000000..1080082
--- /dev/null
+++ b/test-data/nft-mon-output.json
@@ -0,0 +1,3 @@
+{"delete": {"rule": {"family": "ip", "table": "netbird", "chain": "netbird-rt-fwd", "handle": 68, "expr": [{"match": {"op": "==", "left": {"payload": {"protocol": "ip", "field": "saddr"}}, "right": "100.66.181.207"}}, {"match": {"op": "==", "left": {"payload": {"protocol": "ip", "field": "daddr"}}, "right": "172.30.0.5"}}, {"counter": {"packets": 0, "bytes": 0}}, {"accept": null}]}}}
+{"add": {"rule": {"family": "ip", "table": "netbird", "chain": "netbird-rt-fwd", "handle": 69, "expr": [{"match": {"op": "==", "left": {"payload": {"protocol": "ip", "field": "saddr"}}, "right": "100.66.181.207"}}, {"match": {"op": "==", "left": {"payload": {"protocol": "ip", "field": "daddr"}}, "right": "172.30.0.5"}}, {"counter": {"packets": 0, "bytes": 0}}, {"accept": null}]}}}
+{"add": {"rule": {"family": "ip", "table": "netbird", "chain": "netbird-rt-fwd", "handle": 69, "expr": [{"match": {"op": "==", "left": {"payload": {"protocol": "ip", "field": "saddr"}}, "right": "100.66.181.207"}}, {"match": {"op": "==", "left": {"payload": {"protocol": "ip", "field": "daddr"}}, "right": "172.30.0.5"}}, {"counter": {"packets": 0, "bytes": 0}}, {"accept": null}]}}} \ No newline at end of file