diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 16063ae..2a0a213 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -123,8 +123,9 @@ jobs: MAX_ITERATIONS: 15 timeout-minutes: 5 run: | - cd sfunnel/example/k8s - minikube kubectl -- apply -f client.yaml + cd sfunnel/.github/workflows/ + cp ../../example/k8s/client.yaml cni_overlay + minikube kubectl -- apply -k cni_overlay/ minikube kubectl -- get pods -o wide echo "Waiting for all pods to be running (filter spureous starts)..." while [[ "$(minikube kubectl -- get pods | grep client | grep -v Running)" != "" ]]; do @@ -223,7 +224,7 @@ jobs: - name: "[TEST] Run container with ruleset file..." run: | - RULE="ip saddr 127.0.0.1 udp dport 80 actions unfunnel udp" + RULE="ip saddr 127.0.0.1 udp dport 80 actions unfunnel decap udp" echo "$RULE" > ruleset set -o pipefail @@ -249,7 +250,7 @@ jobs: - name: "[TEST] Run container with ruleset via SFUNNEL_RULESET (no override)..." run: | - RULE="ip saddr 127.0.0.2 udp dport 80 actions unfunnel udp" + RULE="ip saddr 127.0.0.2 udp dport 80 actions unfunnel decap udp" set -o pipefail docker run -e SFUNNEL_RULESET="$RULE" --privileged sfunnel:latest 2>&1 | tee output @@ -264,7 +265,7 @@ jobs: - name: "[TEST] Run container with ruleset via SFUNNEL_RULESET (override)..." run: | - RULE="ip saddr 127.0.0.2 udp dport 80 actions unfunnel udp" #Should override ruleset file with 127.0.0.1 + RULE="ip saddr 127.0.0.2 udp dport 80 actions unfunnel decap udp" #Should override ruleset file with 127.0.0.1 set -o pipefail docker run -e SFUNNEL_RULESET="$RULE" --privileged -v `pwd`/ruleset:/etc/sfunnel/ruleset sfunnel:latest 2>&1 | tee output @@ -297,7 +298,7 @@ jobs: - name: "[TEST] Run container with DEBUG=1 ..." run: | - RULE="ip saddr 127.0.0.1 udp dport 80 actions unfunnel udp" + RULE="ip saddr 127.0.0.1 udp dport 80 actions unfunnel decap udp" echo "$RULE" > ruleset set -o pipefail @@ -421,7 +422,7 @@ jobs: (grep "\$DEBUG='1'" output) || (echo "ERROR: unable to validate env. variables are passed to the NETNS execution" && exit 1) #Successful run with SFUNNEL_RULESET - RULE="ip saddr 127.0.0.2 udp dport 80 actions unfunnel udp" + RULE="ip saddr 127.0.0.2 udp dport 80 actions unfunnel decap udp" docker run --privileged --network=host -v /var/run/netns:/var/run/netns -e NETNS=test_ns -e DEBUG=1 -e SFUNNEL_RULESET="$RULE" -e IFACES=lo sfunnel:latest 2>&1 | tee output if [ ${PIPESTATUS[0]} -ne 0 ]; then echo "ERROR: container execution FAILED!" diff --git a/.github/workflows/cni_overlay/client_patch.yaml b/.github/workflows/cni_overlay/client_patch.yaml new file mode 100644 index 0000000..f8a2799 --- /dev/null +++ b/.github/workflows/cni_overlay/client_patch.yaml @@ -0,0 +1,12 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: my-web-client +spec: + template: + spec: + initContainers: + - name: sfunnel-init + image: sfunnel + - name: sfunnel-init-egress + image: sfunnel diff --git a/.github/workflows/cni_overlay/kustomization.yaml b/.github/workflows/cni_overlay/kustomization.yaml new file mode 100644 index 0000000..bb8a586 --- /dev/null +++ b/.github/workflows/cni_overlay/kustomization.yaml @@ -0,0 +1,11 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: + - client.yaml + +patches: + - path: client_patch.yaml + target: + kind: Deployment + name: my-web-client diff --git a/src/ruleset.default b/src/ruleset.default index 633b833..fff0f9e 100644 --- a/src/ruleset.default +++ b/src/ruleset.default @@ -1,7 +1,7 @@ ##Funneling rules -ip udp dport 2055 actions funnel tcp dport 179 sport 540 #Netflow -ip udp dport 4739 actions funnel tcp dport 179 sport 540 #IPFIX -ip udp dport 6343 actions funnel tcp dport 179 sport 540 #SFLOW +ip udp dport 2055 actions funnel encap tcp dport 179 sport 540 #Netflow +ip udp dport 4739 actions funnel encap tcp dport 179 sport 540 #IPFIX +ip udp dport 6343 actions funnel encap tcp dport 179 sport 540 #SFLOW ##Unfunneling -ip tcp dport 179 sport 540 actions unfunnel udp +ip tcp dport 179 sport 540 actions unfunnel decap udp diff --git a/test/cni/Makefile b/test/cni/Makefile index ee18eac..0cf962e 100644 --- a/test/cni/Makefile +++ b/test/cni/Makefile @@ -20,8 +20,8 @@ endif CLANG ?= clang ITERATIONS ?= 10 -RULESET_EGRESS := "ip tcp dport 8080 actions funnel tcp dport 80 sport 540; ip udp dport 8080 actions funnel tcp dport 80 sport 541;" -RULESET_INGRESS := "ip tcp sport 80 dport 540 actions unfunnel tcp" +RULESET_EGRESS := "ip tcp dport 8080 actions funnel encap tcp dport 80 sport 540; ip udp dport 8080 actions funnel encap tcp dport 80 sport 541;" +RULESET_INGRESS := "ip tcp sport 80 dport 540 actions unfunnel decap tcp" SRC_IPS := 192.168.254.2 192.168.254.3 192.168.254.4 192.168.254.5 192.168.254.6 192.168.254.7 192.168.254.8 192.168.254.9 192.168.254.10 192.168.254.11 all: check diff --git a/test/ns/ruleset b/test/ns/ruleset index 46a3608..a14947f 100644 --- a/test/ns/ruleset +++ b/test/ns/ruleset @@ -1,12 +1,12 @@ ##Matching funneling and unfunneling rules 1:1 -ip udp dport 2055 actions funnel tcp dport 179 sport 540 -ip tcp dport 179 sport 540 actions unfunnel udp +ip udp dport 2055 actions funnel encap tcp dport 179 sport 540 +ip tcp dport 179 sport 540 actions unfunnel decap udp -ip udp dport 2056 actions funnel udp dport 179 sport 540 -ip udp dport 179 sport 540 actions unfunnel udp +ip udp dport 2056 actions funnel encap udp dport 179 sport 540 +ip udp dport 179 sport 540 actions unfunnel decap udp -ip tcp dport 2055 actions funnel tcp dport 179 sport 541 -ip tcp dport 179 sport 541 actions unfunnel tcp +ip tcp dport 2055 actions funnel encap tcp dport 179 sport 541 +ip tcp dport 179 sport 541 actions unfunnel decap tcp -ip tcp dport 2056 actions funnel udp dport 179 sport 541 -ip udp dport 179 sport 541 actions unfunnel tcp +ip tcp dport 2056 actions funnel encap udp dport 179 sport 541 +ip udp dport 179 sport 541 actions unfunnel decap tcp diff --git a/test/ns_perf/Makefile b/test/ns_perf/Makefile index 6a5a0b3..501ae23 100644 --- a/test/ns_perf/Makefile +++ b/test/ns_perf/Makefile @@ -42,11 +42,11 @@ check: _setup _load # ........... ............ # -RULES_SVC_EGRESS := "ip $(PROTO) sport 8080 actions funnel $(FUN_PROTO) sport 80 dport 540" -RULES_SVC_INGRESS := "ip $(FUN_PROTO) dport 80 sport 540 actions unfunnel $(PROTO)" +RULES_SVC_EGRESS := "ip $(PROTO) sport 8080 actions funnel encap $(FUN_PROTO) sport 80 dport 540" +RULES_SVC_INGRESS := "ip $(FUN_PROTO) dport 80 sport 540 actions unfunnel decap $(PROTO)" -RULES_INGRESS := "ip $(FUN_PROTO) sport 80 dport 540 actions unfunnel $(PROTO)" -RULES_EGRESS := "ip $(PROTO) dport 8080 actions funnel $(FUN_PROTO) sport 540 dport 80" +RULES_INGRESS := "ip $(FUN_PROTO) sport 80 dport 540 actions unfunnel decap $(PROTO)" +RULES_EGRESS := "ip $(PROTO) dport 8080 actions funnel encap $(FUN_PROTO) sport 540 dport 80" _setup: $(QUIET)echo -n "Creating ifaces..." diff --git a/tools/gen.py b/tools/gen.py index c0f483a..54c9a4a 100644 --- a/tools/gen.py +++ b/tools/gen.py @@ -60,8 +60,8 @@ ] action_patterns = [ - f"(funnel)\s*{param_re}\s*(sport|dport)\s*{param_re}\s*(sport|dport)\s*{param_re}", - f"(unfunnel)\s*{param_re}", + f"(funnel)\s*(encap)\s*{param_re}\s*(sport|dport)\s*{param_re}\s*(sport|dport)\s*{param_re}", + f"(unfunnel)\s*(decap)\s*{param_re}", f"(dnat)\s*{param_re}", f"(accept)", f"(drop)" @@ -96,6 +96,32 @@ def extract_matches(string: str): return matches +def extract_unfunnel(string, re_a): + a = {} + if re_a.groups()[1] == "decap": + if re_a.groups()[2] not in ["tcp", "udp"]: + raise ValueError(f"ERROR: protocol following unfunnel action must be 'tcp' or 'udp' in '{string}'") + a["value"] = re_a.groups()[2] + else: + raise ValueError(f"ERROR: unknown unfunnel mode {re_a.groups()[1]}") + return a + +def extract_funnel(string, re_a): + a = {} + if re_a.groups()[1] == "encap": + if re_a.groups()[2] not in ["tcp", "udp"]: + raise ValueError(f"ERROR: protocol following funnel action must be 'tcp' or 'udp' in '{string}'") + a["fun_proto"] = re_a.groups()[2] + if re_a.groups()[3] not in ["sport", "dport"]: + raise ValueError(f"ERROR: unknown header field '{re_a.groups()[3]}' in '{string}'") + a[re_a.groups()[3]] = re_a.groups()[4] + if re_a.groups()[5] not in ["sport", "dport"]: + raise ValueError(f"ERROR: unknown header field '{re_a.groups()[5]}' in '{string}'") + a[re_a.groups()[5]] = re_a.groups()[6] + else: + raise ValueError(f"ERROR: unknown unfunnel mode {re_a.groups()[1]}") + return a + def extract_actions(string: str): s = string actions={} @@ -105,25 +131,21 @@ def extract_actions(string: str): if not re_a: continue grp_len = len(re_a.groups()) - if grp_len not in [1,2,6]: + if grp_len not in [1,3,7]: raise Exception(f"ERROR: parsing '{a_it}' in '{string}'") a = {} - if grp_len == 2: + if grp_len == 3: + #Unfunnel/DNAT + if re_a.groups()[0] == "unfunnel": + a |= extract_unfunnel(string, re_a) + else: + raise ValueError(f"ERROR: unknown action {re_a.groups()[0]}") + elif grp_len == 7: #Unfunnel/DNAT - if re_a.groups()[0] == "unfunnel" and re_a.groups()[1] not in ["tcp", "udp"]: - raise ValueError(f"ERROR: protocol following unfunnel action must be 'tcp' or 'udp' in '{string}'") - a["value"] = re_a.groups()[1] - elif grp_len == 6: - #Funnel - if re_a.groups()[1] not in ["tcp", "udp"]: - raise ValueError(f"ERROR: protocol following funnel action must be 'tcp' or 'udp' in '{string}'") - a["fun_proto"] = re_a.groups()[1] - if re_a.groups()[2] not in ["sport", "dport"]: - raise ValueError(f"ERROR: unknown heade field '{re_a.groups()[3]}' in '{string}'") - a[re_a.groups()[2]] = re_a.groups()[3] - if re_a.groups()[4] not in ["sport", "dport"]: - raise ValueError(f"ERROR: unknown heade field '{re_a.groups()[3]}' in '{string}'") - a[re_a.groups()[4]] = re_a.groups()[5] + if re_a.groups()[0] == "funnel": + a |= extract_funnel(string, re_a) + else: + raise ValueError(f"ERROR: unknown action {re_a.groups()[0]}") actions[re_a.groups()[0]] = a s = re.sub(pattern, "", s)