Skip to content

Commit

Permalink
Tc
Browse files Browse the repository at this point in the history
  • Loading branch information
msune committed Aug 27, 2024
1 parent 3a704ab commit 75f3133
Show file tree
Hide file tree
Showing 2 changed files with 175 additions and 0 deletions.
114 changes: 114 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# 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.

Contact
-------

Marc Sune < marcdevel (at) gmail (dot) com>
61 changes: 61 additions & 0 deletions docs/rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Funneling rules

Rules have the following format:

```
<list of match conditions> actions <list of actions>
```

Rules are processed in the strict order, top to bottom, as 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`/`daddr`

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`/`dport`

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



[^1] To the best of my ability, as there isn't a defacto-standard python library
to parse nftable filters.

0 comments on commit 75f3133

Please sign in to comment.