-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
229 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
# sfunnel: multi-flow K8s pod session affinity | ||
|
||
`sfunnel` is an [eBPF](https://ebpf.io/)-based tool desgined to [_funnel_](docs/funneling) | ||
multiple traffic flows through a single [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) | ||
_port_, ensuring - under [certain conditions](#requirements) - consistent `ClientIP` | ||
affinity across all _ports_ within the service. | ||
|
||
See the original use-case [here](docs/use-cases/network-telemetry-nfacctd.md). | ||
|
||
## At a glance | ||
|
||
Example where `TCP/8080` and `TCP/443` traffic is funneled through `TCP/80`. | ||
|
||
|
||
Remove _ports_ from the K8s service and e.g. deployment. Add the `sfunnel` | ||
container along with the [rules](docs/rules.md) in `SFUNNEL_RULESET`: | ||
|
||
```diff | ||
--- a/service.yaml | ||
+++ b/service.yaml | ||
@@ -1,18 +1,12 @@ | ||
apiVersion: v1 | ||
kind: Service | ||
metadata: | ||
name: my-loadbalancer-service | ||
spec: | ||
type: LoadBalancer | ||
selector: | ||
app: my-nginx-app | ||
ports: | ||
- protocol: TCP | ||
port: 80 | ||
targetPort: 80 | ||
- - protocol: TCP | ||
- port: 8080 | ||
- targetPort: 8080 | ||
- - protocol: TCP | ||
- port: 443 | ||
- targetPort: 443 | ||
``` | ||
|
||
```diff | ||
--- a/nginx.yaml | ||
+++ b/nginx.yaml | ||
@@ -1,21 +1,31 @@ | ||
apiVersion: apps/v1 | ||
kind: Deployment | ||
metadata: | ||
name: my-nginx-deployment | ||
spec: | ||
replicas: 4 | ||
selector: | ||
matchLabels: | ||
app: my-nginx-app | ||
template: | ||
metadata: | ||
labels: | ||
app: my-nginx-app | ||
spec: | ||
containers: | ||
+ - name: sfunnel-init | ||
+ env: | ||
+ - name: SFUNNEL_RULESET | ||
+ value: ip tcp dport 80 sport 540 actions unfunnel tcp | ||
+ image: ghcr.io/datahangar/sfunnel:0.0.3 | ||
+ securityContext: | ||
+ privileged: true | ||
+ capabilities: | ||
+ add: [BPF, NET_ADMIN] | ||
+ volumeMounts: | ||
+ - name: bpffs | ||
+ mountPath: /sys/fs/bpf | ||
- name: nginx | ||
image: nginx:latest | ||
ports: | ||
- containerPort: 80 | ||
- - containerPort: 8080 | ||
- - containerPort: 443 | ||
|
||
``` | ||
|
||
On the other end (e.g. a Linux host, server etc..), deploy it with the | ||
matching [rules](docs/rules.md): | ||
|
||
```shell | ||
SFUNNEL_RULESET="ip daddr <your LB IP1> tcp port 443 actions funnel tcp dport 80 sport 540;\ | ||
ip daddr <your LB IP1> tcp port 8080 actions funnel tcp dport 80 sport 540" | ||
docker run --network="host" --privileged -e SFUNNEL_RULESET="$SFUNNEL_RULESET" sfunnel | ||
``` | ||
|
||
The `sfunnel` container will run and load the eBPF code. | ||
|
||
##### More use-cases | ||
|
||
This is a simple example yet not very useful example. See [use-cases](docs/use-cases/) | ||
for real world examples. | ||
|
||
## Requirements | ||
|
||
* In Kubernetes: | ||
* Permissions to spawn containers with `BPF` and `NET_ADMIN` capabilities. | ||
* [eBPF](https://ebpf.io/)-enabled kernel, with support for `clsact` and `direct-action`. | ||
* Proper MTU configuration (20 bytes for TCP, 8 for UDP). | ||
* On the funneling side: | ||
* Permissions to spawn `sfunnel`. | ||
* Route or proxy traffic to be funneled. More on this [here](docs/funneling.md) | ||
|
||
Make sure stateful firewalls and IDS/IDPS are properly configured to allow this | ||
type of traffic. | ||
|
||
## More... | ||
|
||
* [Rule syntax](docs/rules.md) | ||
* [Container parameters](docs/container_params.md) | ||
* [Next steps](docs/next_steps.md) | ||
|
||
Contact | ||
------- | ||
|
||
Marc Sune < marcdevel (at) gmail (dot) com> |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Next steps | ||
|
||
TODO list: | ||
|
||
* IPv6 support | ||
* Support for fwmark with mask | ||
* [VPP](https://fd.io/docs/vpp/master) plugin? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Funneling rules | ||
|
||
Rules have the following format: | ||
|
||
``` | ||
<list of match conditions> actions <list of actions> | ||
``` | ||
|
||
Rules are processed top to bottom, in the strict order they are defined. | ||
Total ordering is therefore guaranteed. | ||
|
||
## Match conditions | ||
|
||
Match conditions use [nftables syntax](https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Matches) [1]. | ||
|
||
### `ip` | ||
|
||
Matches IPv4 packets. Required to match `saddr` and `daddr`. | ||
|
||
### `saddr [!=] <cidr|addr>`/`daddr [!=] <cidr|addr>` | ||
|
||
Matches IPv4 source/destination address of the packet against CIDR. It can | ||
optionally be negated `!=`. | ||
|
||
Examples: | ||
|
||
``` | ||
ip saddr 127.0.0.1 | ||
``` | ||
|
||
``` | ||
ip daddr 10.0.0.0/24 | ||
``` | ||
|
||
``` | ||
ip saddr != 127.0.0.1 daddr 10.0.0.0/8 | ||
``` | ||
|
||
### `tcp`/`udp` | ||
|
||
Matches TCP or UDP packets. Required to match `sport` and `dport`. | ||
|
||
### `sport [!=] <port>`/`dport [!=] <port>` | ||
|
||
Matches _a single_ L4 source or destination port (exact match). It can | ||
optionally be negated with `!=`. | ||
|
||
``` | ||
tcp dport 80 | ||
``` | ||
|
||
``` | ||
udp dport 1000 sport != 1000 | ||
``` | ||
|
||
## Actions | ||
|
||
Actions resemble [nftables ones](), but they aren't exact. For `accept` and | ||
`drop` no other action can be defined. | ||
|
||
For the rest, actions are accumulative. | ||
|
||
|
||
### `funnel <l4_funneling_proto> sport <port> dport <port>` | ||
|
||
[Funnels](funneling.md) traffic through `<l4_funneling_proto>`, and set | ||
funneling L4 header and sets `sport`/`dport` accordingly. | ||
|
||
Examples: | ||
|
||
``` | ||
funnel tcp dport 179 sport 540 | ||
``` | ||
|
||
> :sparkles: You can reuse the same `sport` for all UDP or TCP traffic. Original | ||
`sport` and `dport` are preserved. | ||
|
||
> :warning: Make sure there isn't real traffic using `l4_funneling_proto`+`sport`+`dport`. | ||
> | ||
> :warning: Make sure `l4_funneling_proto`+`sport`+`dport` is allowed in your firewall rules. | ||
### `unfunnel <l4_proto>` | ||
|
||
Undo the `funnel`ing. The mandatory paramter `<l4_proto>` is eThe mandatory It must have the L4 protocol it | ||
|
||
Example: | ||
|
||
``` | ||
unfunnel tcp | ||
``` | ||
|
||
### `accept` | ||
|
||
Don't touch the packet. | ||
|
||
### `drop` | ||
|
||
Drop the packet. | ||
|
||
--- | ||
|
||
[1] To the best of my ability. AFAIK, a library to parse nftable filters doesn't exist yet in Python3. |