Compare commits
1 commit
main
...
ebpf-xdp-t
Author | SHA1 | Date | |
---|---|---|---|
95918a26b2 |
36 changed files with 1285 additions and 415 deletions
|
@ -1,32 +0,0 @@
|
|||
name: Deploy pages
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22.x"
|
||||
- uses: https://code.icb4dc0.de/prskr/setup-hugo@main
|
||||
with:
|
||||
dart-sass: true
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: /home/runner/.cache/hugo_cache
|
||||
key: ${{ runner.os }}-hugomod-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-hugomod-
|
||||
- name: Build & deploy
|
||||
run: |
|
||||
hugo mod get
|
||||
hugo --minify --environment production
|
||||
hugo deploy
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.GARAGE_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.GARAGE_SECRET_KEY }}
|
32
.github/workflows/gh-pages.yml
vendored
Normal file
32
.github/workflows/gh-pages.yml
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
name: github pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main # Set a branch to deploy
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true # Fetch Hugo themes (true OR recursive)
|
||||
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod
|
||||
|
||||
- name: Setup Hugo
|
||||
uses: peaceiris/actions-hugo@v2
|
||||
with:
|
||||
hugo-version: 'latest'
|
||||
# extended: true
|
||||
|
||||
- name: Build
|
||||
run: hugo --minify
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
if: github.ref == 'refs/heads/main'
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./public
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -15,7 +15,5 @@ hugo.linux
|
|||
# IntelliJ project files
|
||||
.idea/
|
||||
|
||||
*.o
|
||||
cmake-build-debug
|
||||
out/
|
||||
CMakeLists.txt
|
||||
# Vagrant files
|
||||
.vagrant/
|
||||
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "themes/anubis"]
|
||||
path = themes/anubis
|
||||
url = https://github.com/mitrichius/hugo-theme-anubis.git
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"yaml.schemas": {
|
||||
"https://json.schemastore.org/github-workflow.json": "file:///Users/baez/sources/code.icb4dc0.de/prskr/blog/.forgejo/workflows/deploy.yml"
|
||||
}
|
||||
}
|
15
Dockerfile
15
Dockerfile
|
@ -1,15 +0,0 @@
|
|||
FROM docker.io/golang:1.19-alpine as builder
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
RUN apk add -U --no-cache hugo git
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY . /src/
|
||||
|
||||
RUN hugo --minify --environment production --config config.toml
|
||||
|
||||
FROM code.icb4dc0.de/prskr/ci-images/caddy:latest as final
|
||||
|
||||
COPY --from=builder /src/public /usr/share/caddy
|
5
code/ebpf-xdp-tc/.gitignore
vendored
Normal file
5
code/ebpf-xdp-tc/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
*.o
|
||||
CMakeLists.txt
|
||||
|
||||
out/
|
||||
cmake-build-debug/
|
7
code/ebpf-xdp-tc/Dockerfile
Normal file
7
code/ebpf-xdp-tc/Dockerfile
Normal file
|
@ -0,0 +1,7 @@
|
|||
FROM docker.io/alpine:3.15
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY out/ebpf-xdp-tc ./
|
||||
|
||||
ENTRYPOINT ["/app/ebpf-xdp-tc"]
|
58
code/ebpf-xdp-tc/Taskfile.yml
Normal file
58
code/ebpf-xdp-tc/Taskfile.yml
Normal file
|
@ -0,0 +1,58 @@
|
|||
# https://taskfile.dev
|
||||
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
|
||||
build-ebpf:
|
||||
cmds:
|
||||
- mkdir -p ebpf/bin
|
||||
- |
|
||||
clang \
|
||||
-Wno-unused-value \
|
||||
-Wno-pointer-sign \
|
||||
-Wno-compare-distinct-pointer-types \
|
||||
-Wunused \
|
||||
-Wall \
|
||||
-fno-stack-protector \
|
||||
-fno-ident \
|
||||
-g \
|
||||
-O2 -emit-llvm \
|
||||
ebpf/main.c \
|
||||
-c -o - | llc -march=bpf -mcpu=probe -filetype=obj -o ebpf/bin/probe.o
|
||||
|
||||
build-bin:
|
||||
deps:
|
||||
- build-ebpf
|
||||
env:
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
cmds:
|
||||
- mkdir -p out/
|
||||
- go build -o out/ebpf-xdp-tc -trimpath -a -installsuffix=cgo -ldflags "-w -s -linkmode external -extldflags -static" ./
|
||||
|
||||
build-docker:
|
||||
deps:
|
||||
- build-bin
|
||||
cmds:
|
||||
- buildah bud -t ebpf-xdp-tc .
|
||||
|
||||
run-in-container:
|
||||
deps:
|
||||
- build-docker
|
||||
cmds:
|
||||
- |
|
||||
podman run \
|
||||
--rm \
|
||||
-ti \
|
||||
-v /sys:/sys:ro \
|
||||
--security-opt=seccomp=unconfined \
|
||||
--network=libvirt \
|
||||
--ip "10.10.1.1" \
|
||||
--name ebpf-xdp-tc \
|
||||
--cap-add=CAP_SYS_ADMIN \
|
||||
--cap-add=CAP_NET_RAW \
|
||||
--cap-add=CAP_NET_BIND_SERVICE \
|
||||
--cap-add=CAP_NET_ADMIN \
|
||||
ebpf-xdp-tc
|
22
code/ebpf-xdp-tc/Vagrantfile
vendored
Normal file
22
code/ebpf-xdp-tc/Vagrantfile
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
Vagrant.configure("2") do |config|
|
||||
# The most common configuration options are documented and commented below.
|
||||
# For a complete reference, please see the online documentation at
|
||||
# https://docs.vagrantup.com.
|
||||
|
||||
# Every Vagrant development environment requires a box. You can search for
|
||||
# boxes at https://vagrantcloud.com/search.
|
||||
config.vm.box = "peru/windows-10-enterprise-x64-eval"
|
||||
config.vm.box_version = "20220202.01"
|
||||
|
||||
config.vm.provider "libvirt" do |libvirt|
|
||||
libvirt.management_network_mode = 'veryisoled'
|
||||
end
|
||||
|
||||
|
||||
config.vm.define :win_victim do |win_victim|
|
||||
win_victim.vm.network :private_network,
|
||||
:libvirt__network_name => "containers"
|
||||
end
|
||||
|
||||
config.vm.box_check_update = false
|
||||
end
|
53
code/ebpf-xdp-tc/ebpf/helpers.h
Normal file
53
code/ebpf-xdp-tc/ebpf/helpers.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
#include <linux/if_ether.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/tcp.h>
|
||||
#include <linux/udp.h>
|
||||
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
static inline unsigned short checksum(unsigned short *buf, int bufsz) {
|
||||
unsigned long sum = 0;
|
||||
|
||||
while (bufsz > 1) {
|
||||
sum += *buf;
|
||||
buf++;
|
||||
bufsz -= 2;
|
||||
}
|
||||
|
||||
if (bufsz == 1) {
|
||||
sum += *(unsigned char *) buf;
|
||||
}
|
||||
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
sum = (sum & 0xffff) + (sum >> 16);
|
||||
|
||||
return ~sum;
|
||||
}
|
||||
|
||||
static inline struct tcphdr *extract_tcp_meta(struct observed_packet *pkt, void *iph, __u64 off, void *data_end) {
|
||||
struct tcphdr *hdr = iph + off;
|
||||
if ((void *) hdr + sizeof(*hdr) > data_end) {
|
||||
return NULL;
|
||||
}
|
||||
pkt->transport_proto = TCP;
|
||||
pkt->sourcePort = bpf_ntohs(hdr->source);
|
||||
pkt->destPort = bpf_ntohs(hdr->dest);
|
||||
|
||||
return hdr;
|
||||
}
|
||||
|
||||
static inline struct udphdr *extract_udp_meta(struct observed_packet *pkt, void *iph, __u64 off, void *data_end) {
|
||||
struct udphdr *hdr = iph + off;
|
||||
if ((void *) hdr + sizeof(*hdr) > data_end) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pkt->transport_proto = UDP;
|
||||
pkt->sourcePort = bpf_ntohs(hdr->source);
|
||||
pkt->destPort = bpf_ntohs(hdr->dest);
|
||||
return hdr;
|
||||
}
|
222
code/ebpf-xdp-tc/ebpf/main.c
Normal file
222
code/ebpf-xdp-tc/ebpf/main.c
Normal file
|
@ -0,0 +1,222 @@
|
|||
#include <linux/bpf.h>
|
||||
#include <linux/pkt_cls.h>
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
#define IP_FRAGMENTED 65343
|
||||
|
||||
char LICENSE[] SEC("license") = "Dual MIT/GPL";
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
} perf_observed_packets SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||
__uint(max_entries, 1 << 24);
|
||||
} ring_observed_packets SEC(".maps");
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__type(key, sizeof(struct two_tuple));
|
||||
__type(value, sizeof(struct two_tuple));
|
||||
__uint(max_entries, 1024);
|
||||
} conn_track SEC(".maps");
|
||||
|
||||
SEC("classifier/egress")
|
||||
int egress(struct __sk_buff *skb) {
|
||||
bpf_printk("new packet captured on egress (TC)\n");
|
||||
void *data = (void *) (long) skb->data;
|
||||
void *data_end = (void *) (long) skb->data_end;
|
||||
|
||||
struct ethhdr *eth = data;
|
||||
|
||||
if ((void *) eth + sizeof(*eth) > data_end) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
if(eth->h_proto != ETH_P_IP && eth->h_proto != ETH_P_IPV6) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
struct iphdr *iph = data + sizeof(*eth);
|
||||
if ((void *) iph + sizeof(*iph) > data_end) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
/* do not support fragmented packets as L4 headers may be missing */
|
||||
if (iph->frag_off & IP_FRAGMENTED) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
if (iph->protocol != IPPROTO_TCP) {
|
||||
bpf_printk("Packet's not TCP - forwarding");
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
struct tcphdr *tcp = (void *) iph + sizeof(*iph);
|
||||
if ((void *) tcp + sizeof(*tcp) > data_end) {
|
||||
return TC_ACT_SHOT;
|
||||
}
|
||||
|
||||
struct two_tuple dst = {
|
||||
.ip = iph->daddr,
|
||||
.port = tcp->dest
|
||||
};
|
||||
|
||||
struct two_tuple *orig_src = bpf_map_lookup_elem(&conn_track, &dst);
|
||||
|
||||
if (orig_src == NULL) {
|
||||
bpf_printk("No translation found - pass it through");
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
bpf_printk("Restore original source IP");
|
||||
|
||||
iph->saddr = orig_src->ip;
|
||||
tcp->source = orig_src->port;
|
||||
|
||||
iph->tos = 7 << 2;
|
||||
iph->check = 0;
|
||||
iph->check = checksum((unsigned short *) iph, sizeof(struct iphdr));
|
||||
|
||||
return TC_ACT_OK;
|
||||
};
|
||||
|
||||
SEC("classifier/ingress")
|
||||
int ingress(struct __sk_buff *skb) {
|
||||
bpf_printk("new packet captured on ingress (TC)");
|
||||
void *data = (void *) (long) skb->data;
|
||||
void *data_end = (void *) (long) skb->data_end;
|
||||
|
||||
struct ethhdr *eth = data;
|
||||
|
||||
if ((void *) eth + sizeof(*eth) > data_end) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
struct iphdr *iph = data + sizeof(*eth);
|
||||
if ((void *) iph + sizeof(*iph) > data_end) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
/* do not support fragmented packets as L4 headers may be missing */
|
||||
if (iph->frag_off & IP_FRAGMENTED) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
if (iph->protocol != IPPROTO_TCP) {
|
||||
bpf_printk("Packet's not TCP - forwarding");
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
if (iph->daddr == 16845322) {
|
||||
bpf_printk("We're the destination - don't touch it");
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
struct tcphdr *tcp = (void *) iph + sizeof(*iph);
|
||||
if ((void *) tcp + sizeof(*tcp) > data_end) {
|
||||
return TC_ACT_SHOT;
|
||||
}
|
||||
|
||||
struct two_tuple src = {
|
||||
.ip = iph->saddr,
|
||||
.port = tcp->source
|
||||
};
|
||||
|
||||
struct two_tuple dst = {
|
||||
.ip = iph->daddr,
|
||||
.port = tcp->dest
|
||||
};
|
||||
|
||||
bpf_map_update_elem(&conn_track, &src, &dst, 0);
|
||||
|
||||
bpf_printk("Forward packet to localhost (TC)");
|
||||
iph->daddr = 16845322;
|
||||
iph->tos = 7 << 2;
|
||||
iph->check = 0;
|
||||
iph->check = checksum((unsigned short *) iph, sizeof(struct iphdr));
|
||||
return TC_ACT_OK;
|
||||
};
|
||||
|
||||
static inline enum xdp_action extract_meta(struct xdp_md *ctx, struct observed_packet *pkt) {
|
||||
void *data = (void *) (long) ctx->data;
|
||||
void *data_end = (void *) (long) ctx->data_end;
|
||||
struct ethhdr *eth = data;
|
||||
__u16 proto;
|
||||
|
||||
if (data + sizeof(struct ethhdr) > data_end) {
|
||||
bpf_printk("Packet apparently not ethernet");
|
||||
return XDP_DROP;
|
||||
}
|
||||
|
||||
proto = eth->h_proto;
|
||||
if (proto != bpf_htons(ETH_P_IP) && proto != bpf_htons(ETH_P_IPV6)) {
|
||||
bpf_printk("Not an IP packet");
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
struct iphdr *iph = data + sizeof(*eth);
|
||||
if ((void *) iph + sizeof(struct iphdr) > data_end) {
|
||||
return XDP_DROP;
|
||||
}
|
||||
|
||||
/* do not support fragmented packets as L4 headers may be missing */
|
||||
if (iph->frag_off & IP_FRAGMENTED) {
|
||||
return XDP_DROP;
|
||||
}
|
||||
|
||||
pkt->sourceIp = iph->saddr;
|
||||
pkt->destIp = iph->daddr;
|
||||
|
||||
__u8 ip_proto = iph->protocol;
|
||||
|
||||
if (ip_proto == IPPROTO_TCP) {
|
||||
struct tcphdr *tcph = extract_tcp_meta(pkt, (void *) iph, sizeof(struct iphdr), data_end);
|
||||
// if ACK flag is set we just pass it through because it belongs to an already established connection
|
||||
if (tcph == NULL || tcph->ack) {
|
||||
return XDP_PASS;
|
||||
}
|
||||
} else if (ip_proto == IPPROTO_UDP) {
|
||||
struct udphdr *udph = extract_udp_meta(pkt, (void *) iph, sizeof(struct iphdr), data_end);
|
||||
// could also check if we're the source
|
||||
if (udph == NULL) {
|
||||
return XDP_PASS;
|
||||
}
|
||||
}
|
||||
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
SEC("xdp/perf")
|
||||
int xdp_ingress_perf(struct xdp_md *ctx) {
|
||||
struct observed_packet pkt;
|
||||
|
||||
enum xdp_action action = extract_meta(ctx, &pkt);
|
||||
|
||||
if (pkt.destIp == 0 || pkt.sourceIp == 0) {
|
||||
return action;
|
||||
}
|
||||
|
||||
if (!bpf_perf_event_output(ctx, &perf_observed_packets, BPF_F_CURRENT_CPU, &pkt, sizeof(struct observed_packet))) {
|
||||
bpf_printk("Failed to submit observed packet");
|
||||
}
|
||||
|
||||
return XDP_PASS;
|
||||
}
|
||||
|
||||
SEC("xdp/ring")
|
||||
int xdp_ingress_ring(struct xdp_md *ctx) {
|
||||
struct observed_packet pkt = {};
|
||||
|
||||
enum xdp_action action = extract_meta(ctx, &pkt);
|
||||
|
||||
if (pkt.destIp == 0 || pkt.sourceIp == 0) {
|
||||
return action;
|
||||
}
|
||||
|
||||
bpf_ringbuf_output(&ring_observed_packets, &pkt, sizeof(pkt), 0);
|
||||
|
||||
return XDP_PASS;
|
||||
}
|
17
code/ebpf-xdp-tc/ebpf/types.h
Normal file
17
code/ebpf-xdp-tc/ebpf/types.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
struct observed_packet {
|
||||
__u32 sourceIp;
|
||||
__u32 destIp;
|
||||
__u16 sourcePort;
|
||||
__u16 destPort;
|
||||
enum {
|
||||
TCP,
|
||||
UDP
|
||||
} transport_proto;
|
||||
};
|
||||
|
||||
|
||||
struct two_tuple {
|
||||
__u32 ip;
|
||||
__u16 port;
|
||||
__u16 _pad;
|
||||
};
|
25
code/ebpf-xdp-tc/go.mod
Normal file
25
code/ebpf-xdp-tc/go.mod
Normal file
|
@ -0,0 +1,25 @@
|
|||
module ebpf-xdp-tc
|
||||
|
||||
go 1.17
|
||||
|
||||
require github.com/DataDog/ebpf-manager v1.0.3
|
||||
|
||||
require (
|
||||
github.com/DataDog/gopsutil v0.0.0-20200624212600-1b53412ef321 // indirect
|
||||
github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705 // indirect
|
||||
github.com/avast/retry-go v3.0.0+incompatible // indirect
|
||||
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect
|
||||
github.com/cilium/ebpf v0.6.3-0.20210917122031-fc2955d2ecee // indirect
|
||||
github.com/florianl/go-tc v0.3.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||
github.com/google/go-cmp v0.5.4 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||
github.com/mdlayher/netlink v1.4.0 // indirect
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
|
||||
github.com/vishvananda/netlink v1.1.0 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 // indirect
|
||||
golang.org/x/sys v0.0.0-20210921065528-437939a70204 // indirect
|
||||
)
|
114
code/ebpf-xdp-tc/go.sum
Normal file
114
code/ebpf-xdp-tc/go.sum
Normal file
|
@ -0,0 +1,114 @@
|
|||
github.com/DataDog/ebpf-manager v1.0.3 h1:zeuFyHmP4/m8uqx7LyLHkHKbmrDjKkk34bz324tBOlc=
|
||||
github.com/DataDog/ebpf-manager v1.0.3/go.mod h1:05Y9FhEyILUdCovBthi5y4KPY8AfUg5EbMNC6RMQXDY=
|
||||
github.com/DataDog/gopsutil v0.0.0-20200624212600-1b53412ef321 h1:OPAXA+r6yznoxWR5jQ2iTh5CvzIMrdw8AU0uFN2RwEw=
|
||||
github.com/DataDog/gopsutil v0.0.0-20200624212600-1b53412ef321/go.mod h1:tGQp6XG4XpOyy67WG/YWXVxzOY6LejK35e8KcQhtRIQ=
|
||||
github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705 h1:UUppSQnhf4Yc6xGxSkoQpPhb7RVzuv5Nb1mwJ5VId9s=
|
||||
github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 h1:kHaBemcxl8o/pQ5VM1c8PVE1PubbNx3mjUr09OqWGCs=
|
||||
github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575/go.mod h1:9d6lWj8KzO/fd/NrVaLscBKmPigpZpn5YawRPw+e3Yo=
|
||||
github.com/cilium/ebpf v0.6.3-0.20210917122031-fc2955d2ecee h1:eg3Xm5uBYJLRDVq750EFFx9CHTOEFIH/MjLNNpyTS3Y=
|
||||
github.com/cilium/ebpf v0.6.3-0.20210917122031-fc2955d2ecee/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/florianl/go-tc v0.3.0 h1:qeqQB5kp2lwJP1p/8krLQIuRfkHWpiPPcYr3rhRSaC8=
|
||||
github.com/florianl/go-tc v0.3.0/go.mod h1:Ni/GTSK8ymDnsRQfL2meJeGmcXy7RFIvchiVHizU76U=
|
||||
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b h1:c3NTyLNozICy8B4mlMXemD3z/gXgQzVXZS/HqT+i3do=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
|
||||
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
|
||||
github.com/mdlayher/netlink v1.4.0 h1:n3ARR+Fm0dDv37dj5wSWZXDKcy+U0zwcXS3zKMnSiT0=
|
||||
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
|
||||
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k=
|
||||
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210921065528-437939a70204 h1:JJhkWtBuTQKyz2bd5WG9H8iUsJRU3En/KRfN8B2RnDs=
|
||||
golang.org/x/sys v0.0.0-20210921065528-437939a70204/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
190
code/ebpf-xdp-tc/main.go
Normal file
190
code/ebpf-xdp-tc/main.go
Normal file
|
@ -0,0 +1,190 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
manager "github.com/DataDog/ebpf-manager"
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/features"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed ebpf/bin/probe.o
|
||||
ebpfTCProgram []byte
|
||||
|
||||
xdpMonitorPerf = &manager.Probe{
|
||||
ProbeIdentificationPair: manager.ProbeIdentificationPair{
|
||||
EBPFSection: "xdp/perf",
|
||||
EBPFFuncName: "xdp_ingress_perf",
|
||||
},
|
||||
Ifname: "eth0",
|
||||
NetworkDirection: manager.Ingress,
|
||||
}
|
||||
|
||||
xdpMonitorRing = &manager.Probe{
|
||||
ProbeIdentificationPair: manager.ProbeIdentificationPair{
|
||||
EBPFSection: "xdp/ring",
|
||||
EBPFFuncName: "xdp_ingress_ring",
|
||||
},
|
||||
Ifname: "eth0",
|
||||
NetworkDirection: manager.Ingress,
|
||||
}
|
||||
|
||||
mgr = &manager.Manager{
|
||||
Probes: []*manager.Probe{
|
||||
{
|
||||
ProbeIdentificationPair: manager.ProbeIdentificationPair{
|
||||
EBPFSection: "classifier/egress",
|
||||
EBPFFuncName: "egress",
|
||||
},
|
||||
Ifname: "eth0",
|
||||
NetworkDirection: manager.Egress,
|
||||
},
|
||||
{
|
||||
ProbeIdentificationPair: manager.ProbeIdentificationPair{
|
||||
EBPFSection: "classifier/ingress",
|
||||
EBPFFuncName: "ingress",
|
||||
},
|
||||
Ifname: "eth0",
|
||||
NetworkDirection: manager.Ingress,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
packetMonitorMode uint8
|
||||
packetReader interface {
|
||||
Read() (*Packet, error)
|
||||
Close() error
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
packetMonitorModeRing packetMonitorMode = iota
|
||||
packetMonitorModePerfEvent
|
||||
)
|
||||
|
||||
func main() {
|
||||
var monitorMode packetMonitorMode
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
|
||||
defer cancel()
|
||||
|
||||
mgrOpts := manager.Options{
|
||||
ExcludedFunctions: nil,
|
||||
RLimit: nil,
|
||||
}
|
||||
|
||||
if err := features.HaveMapType(ebpf.RingBuf); err != nil {
|
||||
if errors.Is(err, ebpf.ErrNotSupported) {
|
||||
log.Println("Falling back to perf event reader")
|
||||
mgr.Probes = append(mgr.Probes, xdpMonitorPerf)
|
||||
monitorMode = packetMonitorModePerfEvent
|
||||
mgrOpts.ExcludedFunctions = append(mgrOpts.ExcludedFunctions, xdpMonitorRing.EBPFFuncName)
|
||||
} else {
|
||||
log.Fatalf("God knows what happened: %v\n", err)
|
||||
}
|
||||
} else {
|
||||
log.Println("Using fancy new ringbuf reader")
|
||||
mgr.Probes = append(mgr.Probes, xdpMonitorRing)
|
||||
monitorMode = packetMonitorModeRing
|
||||
mgrOpts.ExcludedFunctions = append(mgrOpts.ExcludedFunctions, xdpMonitorPerf.EBPFFuncName)
|
||||
}
|
||||
|
||||
if err := mgr.InitWithOptions(bytes.NewReader(ebpfTCProgram), mgrOpts); err != nil {
|
||||
log.Fatalf("Failed to init manager: %v", err)
|
||||
}
|
||||
|
||||
if err := mgr.Start(); err != nil {
|
||||
log.Fatalf("Failed to start manager: %v", err)
|
||||
}
|
||||
|
||||
runHTTPServer()
|
||||
|
||||
var reader packetReader
|
||||
switch monitorMode {
|
||||
case packetMonitorModeRing:
|
||||
if r, err := createRingBufReader(mgr); err != nil {
|
||||
log.Fatalf("Failed to create rinbuf reader: %v\n", err)
|
||||
} else {
|
||||
reader = r
|
||||
}
|
||||
case packetMonitorModePerfEvent:
|
||||
if r, err := createPerfEventReader(mgr); err != nil {
|
||||
log.Fatalf("Failed to create perf_event reader: %v\n", err)
|
||||
} else {
|
||||
reader = r
|
||||
}
|
||||
}
|
||||
|
||||
go logEventsFromReader(ctx, reader)
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
if err := mgr.Stop(manager.CleanAll); err != nil {
|
||||
log.Fatalf("Failed to stop manager: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func logEventsFromReader(ctx context.Context, reader packetReader) {
|
||||
log.Println("Waiting for received packets")
|
||||
defer func() {
|
||||
if err := reader.Close(); err != nil {
|
||||
log.Fatalf("Failed to close reader: %v\n", err)
|
||||
}
|
||||
}()
|
||||
for ctx.Err() == nil {
|
||||
if pkt, err := reader.Read(); err != nil {
|
||||
log.Printf("Error occurred while reading packet: %v\n", err)
|
||||
} else {
|
||||
log.Println(pkt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createRingBufReader(mgr *manager.Manager) (packetReader, error) {
|
||||
if m, present, err := mgr.GetMap("ring_observed_packets"); err != nil {
|
||||
return nil, err
|
||||
} else if !present {
|
||||
return nil, fmt.Errorf("ring_observed_packets map not loaded")
|
||||
} else {
|
||||
return NewRingBufReader(m)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func createPerfEventReader(mgr *manager.Manager) (packetReader, error) {
|
||||
if m, present, err := mgr.GetMap("perf_observed_packets"); err != nil {
|
||||
return nil, err
|
||||
} else if !present {
|
||||
return nil, errors.New("perf_observed_packets map not loaded")
|
||||
} else {
|
||||
return NewPerfEventReader(m, 8)
|
||||
}
|
||||
}
|
||||
|
||||
func runHTTPServer() {
|
||||
log.Println("Listening on: 0.0.0.0:80")
|
||||
go func() {
|
||||
err := http.ListenAndServe("0.0.0.0:80", http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
log.Println("Handling request")
|
||||
writer.WriteHeader(200)
|
||||
_, _ = writer.Write([]byte("Hello, world!"))
|
||||
}))
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
return
|
||||
}
|
||||
log.Printf("Error serving HTTP: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
39
code/ebpf-xdp-tc/perf_event.go
Normal file
39
code/ebpf-xdp-tc/perf_event.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/perf"
|
||||
)
|
||||
|
||||
func NewPerfEventReader(m *ebpf.Map, perCPUBufferSize int) (reader *PerfEventReader, err error) {
|
||||
reader = new(PerfEventReader)
|
||||
if reader.reader, err = perf.NewReader(m, perCPUBufferSize); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return reader, nil
|
||||
}
|
||||
}
|
||||
|
||||
type PerfEventReader struct {
|
||||
reader *perf.Reader
|
||||
}
|
||||
|
||||
func (r *PerfEventReader) Read() (*Packet, error) {
|
||||
var pkt observedPacket
|
||||
if rec, err := r.reader.Read(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if err = binary.Read(bytes.NewReader(rec.RawSample), binary.LittleEndian, &pkt); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return pkt.ToPacket(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *PerfEventReader) Close() error {
|
||||
return r.reader.Close()
|
||||
}
|
39
code/ebpf-xdp-tc/ringbuf.go
Normal file
39
code/ebpf-xdp-tc/ringbuf.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/cilium/ebpf"
|
||||
"github.com/cilium/ebpf/ringbuf"
|
||||
)
|
||||
|
||||
func NewRingBufReader(m *ebpf.Map) (reader *RingBufReader, err error) {
|
||||
reader = new(RingBufReader)
|
||||
if reader.reader, err = ringbuf.NewReader(m); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return reader, nil
|
||||
}
|
||||
}
|
||||
|
||||
type RingBufReader struct {
|
||||
reader *ringbuf.Reader
|
||||
}
|
||||
|
||||
func (r *RingBufReader) Read() (*Packet, error) {
|
||||
var pkt observedPacket
|
||||
if rec, err := r.reader.Read(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if err = binary.Read(bytes.NewReader(rec.RawSample), binary.LittleEndian, &pkt); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return pkt.ToPacket(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RingBufReader) Close() error {
|
||||
return r.reader.Close()
|
||||
}
|
58
code/ebpf-xdp-tc/types.go
Normal file
58
code/ebpf-xdp-tc/types.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Protocol uint8
|
||||
|
||||
const (
|
||||
ProtocolTCP Protocol = iota
|
||||
ProtocolUDP
|
||||
)
|
||||
|
||||
type Packet struct {
|
||||
SourceIP net.IP
|
||||
DestIP net.IP
|
||||
SourcePort uint16
|
||||
DestPort uint16
|
||||
Transport Protocol
|
||||
}
|
||||
|
||||
type observedPacket struct {
|
||||
SourceIP uint32
|
||||
DestIP uint32
|
||||
SourcePort uint16
|
||||
DestPort uint16
|
||||
Transport Protocol
|
||||
}
|
||||
|
||||
func (p *observedPacket) ToPacket() *Packet {
|
||||
return &Packet{
|
||||
SourceIP: int2ip(p.SourceIP),
|
||||
DestIP: int2ip(p.DestIP),
|
||||
SourcePort: p.SourcePort,
|
||||
DestPort: p.DestPort,
|
||||
Transport: p.Transport,
|
||||
}
|
||||
}
|
||||
|
||||
func int2ip(nn uint32) net.IP {
|
||||
ip := make(net.IP, net.IPv4len)
|
||||
binary.LittleEndian.PutUint32(ip, nn)
|
||||
return ip
|
||||
}
|
||||
|
||||
func ip2int(ip net.IP) uint32 {
|
||||
b := make([]byte, net.IPv4len)
|
||||
copy(b, ip.To4())
|
||||
reverse(b)
|
||||
return binary.BigEndian.Uint32(b)
|
||||
}
|
||||
|
||||
func reverse(input []byte) {
|
||||
for i := 0; i < len(input)/2; i++ {
|
||||
input[i], input[len(input)-1-i] = input[len(input)-1-i], input[i]
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
<name>containers</name>
|
||||
<uuid>929b7b7d-bd82-452d-96b7-12f0cf1a4b17</uuid>
|
||||
<bridge name='conbr0' stp='on' delay='0'/>
|
||||
<mac address='af:af:13:ed:c6:41'/>
|
||||
<mac address='00:30:c4:86:c9:1c'/>
|
||||
<ip address='10.10.1.42' netmask='255.255.255.0'>
|
||||
<dhcp>
|
||||
<range start='10.10.1.100' end='10.10.1.150'/>
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"name": "libvirt",
|
||||
"id": "0489e6e643b97003c47b27a9ce0a6f6a8dce7d5f08329603e79a0ba48ad5285f",
|
||||
"driver": "bridge",
|
||||
"network_interface": "conbr0",
|
||||
"created": "2022-04-05T09:18:48.198960971+01:00",
|
||||
"subnets": [
|
||||
{
|
||||
"subnet": "10.10.1.0/24",
|
||||
"gateway": "10.10.1.42",
|
||||
"lease_range": {
|
||||
"start_ip": "10.10.1.1",
|
||||
"end_ip": "10.10.1.10"
|
||||
}
|
||||
}
|
||||
],
|
||||
"ipv6_enabled": false,
|
||||
"internal": false,
|
||||
"dns_enabled": false,
|
||||
"ipam_options": {
|
||||
"driver": "host-local"
|
||||
}
|
||||
}
|
92
config.toml
92
config.toml
|
@ -1,67 +1,59 @@
|
|||
baseURL = 'https://www.icb4dc0.de/'
|
||||
baseURL = 'https://www.1533b4dc0.de/'
|
||||
languageCode = 'en-us'
|
||||
title = 'icb4dc0.de'
|
||||
title = '1533B4dC0.de'
|
||||
theme = "anubis"
|
||||
paginate = 10
|
||||
enableEmoji = true
|
||||
|
||||
[module]
|
||||
[[module.imports]]
|
||||
path = 'github.com/LordMathis/hugo-theme-nightfall'
|
||||
[author]
|
||||
name = "Peter Kurfer"
|
||||
|
||||
[params]
|
||||
author = "Peter Kurfer"
|
||||
style= "auto"
|
||||
copyCodeButton = true
|
||||
rssAsSocialIcon = true
|
||||
readingTime = true
|
||||
published = true
|
||||
custom_css = ["css/custom.css"]
|
||||
|
||||
[params.author]
|
||||
name = "Peter Kurfer"
|
||||
email = "peter.kurfer@gmail.com"
|
||||
[[menu.main]]
|
||||
identifier = "about"
|
||||
name = "About"
|
||||
url = "/about/"
|
||||
weight = 1
|
||||
|
||||
[menu]
|
||||
[[menu.header]]
|
||||
name = "blog"
|
||||
weight = 0
|
||||
url = "blog"
|
||||
[[menu.header]]
|
||||
name = "about"
|
||||
weight = 1
|
||||
url = "about"
|
||||
[[menu.header]]
|
||||
name = "projects"
|
||||
weight = 2
|
||||
url = "projects"
|
||||
[[menu.header]]
|
||||
name = "tags"
|
||||
weight = 3
|
||||
url = "tags"
|
||||
[[menu.main]]
|
||||
identifier = "projects"
|
||||
name = "Projects"
|
||||
url = "/projects/"
|
||||
weight = 2
|
||||
|
||||
[[menu.main]]
|
||||
identifier = "tags"
|
||||
name = "Tags"
|
||||
url = "/tags/"
|
||||
weight = 3
|
||||
|
||||
[[menu.main]]
|
||||
identifier = "archive"
|
||||
name = "Archive"
|
||||
url = "/posts/"
|
||||
weight = 4
|
||||
|
||||
[[params.social]]
|
||||
name = "forgejob"
|
||||
url = "https://code.icb4dc0.de"
|
||||
icon = "fa-brands fa-git"
|
||||
target = "_blank"
|
||||
aria = "Forgejo Instance"
|
||||
id = "github"
|
||||
name = "baez90"
|
||||
|
||||
[[params.social]]
|
||||
name = "github"
|
||||
url = "https://github.com/prskr"
|
||||
icon = "fa-brands fa-github"
|
||||
target = "_blank"
|
||||
aria = "GitHub Profile"
|
||||
id = "linkedin"
|
||||
name = "peter-s-kurfer"
|
||||
|
||||
[[params.social]]
|
||||
name = "linkedin"
|
||||
url = "https://www.linkedin.com/in/peter-s-kurfer/"
|
||||
icon = "fa-brands fa-linkedin"
|
||||
target = "_blank"
|
||||
aria = "LinkedIn Profile"
|
||||
|
||||
[[params.social]]
|
||||
name = "xing"
|
||||
id = "xing"
|
||||
url = "https://www.xing.com/profile/Sebastian_Kurfer"
|
||||
icon = "fa-brands fa-xing"
|
||||
target = "_blank"
|
||||
aria = "Xing Profile"
|
||||
|
||||
[[deployment.targets]]
|
||||
URL = "s3://blog?endpoint=garage.garage.svc:3900&disableSSL=true&s3ForcePathStyle=true®ion=hel1"
|
||||
[taxonomies]
|
||||
category = "categories"
|
||||
tag = "tags"
|
||||
|
||||
[markup.goldmark.renderer]
|
||||
unsafe = true
|
|
@ -2,9 +2,8 @@
|
|||
title: 'About me'
|
||||
button: 'About me'
|
||||
weight: 1
|
||||
showMetadata: false
|
||||
---
|
||||
|
||||
My name's Peter. I'm a passionate software developer especially interested in all kind of networking stuff but also asynchronous data processing, software architecture, distributed computing, testing and automatic software quality analysis and many more.
|
||||
My name's Peter. I'm a passionate software developer especially interested in all kind of networking stuff but also asynchronous data processing, software architecture, testing and automatic software quality analysis and many more.
|
||||
|
||||
I'm the author of [InetMock](https://gitlab.com/inetmock/inetmock) and [Goveal](https://github.com/baez90/goveal) (more on [projects](/projects)) but I'm also trying to contribute to other open source projects like [testcontainers-go](https://github.com/testcontainers/testcontainers-go)
|
||||
I'm the author of [InetMock](https://gitlab.com/inetmock/inetmock) and [Goveal](https://github.com/baez90/goveal) (more on [projects](/projects)) but I'm also trying to contribute to other open source projects.
|
|
@ -1,110 +0,0 @@
|
|||
+++
|
||||
author = "Peter Kurfer"
|
||||
title = "Libvirt & Podman: follow up for Podman 4.0 and netavark"
|
||||
date = "2022-02-24"
|
||||
description = "Joining libvirt VMs and containers with Podman 4.0's new network stack netavark"
|
||||
tags = [
|
||||
"podman",
|
||||
"libvirt",
|
||||
"netavark"
|
||||
]
|
||||
+++
|
||||
|
||||
This is a follow up post to ["Joining libvirt {{<abbr short="VM" full="Virtual Machine" >}}s and Podman container in a common network"]({{<relref "2022-02-libvirt-podman-network-mesh.md" >}}).
|
||||
Therefore I won't cover all the basics again and how to configure libvirt because nothing's changed on that side.
|
||||
|
||||
## Podman 4.0
|
||||
|
||||
Podman 4.0 comes with a completely new network stack replacing the previous [{{<abbr short="CNI" full="Container Network Interface" >}}](https://www.cni.dev/) stack:
|
||||
|
||||
* [Netavark](https://github.com/containers/netavark)
|
||||
* [Aardvark](https://github.com/containers/aardvark-dns)
|
||||
|
||||
There are [great resources](https://www.redhat.com/sysadmin/podman-new-network-stack) that explain the backgrounds of both tools and I don't think I could describe it better than the folks implementing it :smile: so if you're interested have a look at the aforementioned article or the [release post](https://podman.io/releases/2022/02/22/podman-release-v4.0.0.html).
|
||||
|
||||
## Netavark and libvirt
|
||||
|
||||
After reading the announcement I was most curious if I would be able to configure an equivalent setup for Netavark like I described it with Podman 3.x and CNI.
|
||||
|
||||
__Short answer:__ yes, it is possible! :tada:
|
||||
|
||||
_"But how?!"_ do you ask?
|
||||
Well it's pretty much equivalent to the previous solution: you need to create a new Podman network I once more named it _'libvirt'_.
|
||||
To get an idea how the config should look like and where it should placed.
|
||||
I reused the CLI call from my previous article:
|
||||
|
||||
```bash
|
||||
podman network create \
|
||||
--disable-dns \
|
||||
--internal \
|
||||
--gateway 10.10.2.37 \
|
||||
--ip-range 10.10.2.160/29 \
|
||||
--subnet 10.10.2.0/24 \
|
||||
libvirt
|
||||
```
|
||||
|
||||
The configuration files are now obviously resided in `/etc/containers/networks/` and my (already modified) `libvirt.json` now looks like so:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "libvirt",
|
||||
"id": "0489e6e643b97003c47b27a9ce0a6f6a8dce7d5f08329603e79a0ba48ad5285f",
|
||||
"driver": "bridge",
|
||||
"network_interface": "conbr0",
|
||||
"created": "2022-04-05T09:18:48.198960971+01:00",
|
||||
"subnets": [
|
||||
{
|
||||
"subnet": "10.10.1.0/24",
|
||||
"gateway": "10.10.1.42",
|
||||
"lease_range": {
|
||||
"start_ip": "10.10.1.1",
|
||||
"end_ip": "10.10.1.10"
|
||||
}
|
||||
}
|
||||
],
|
||||
"ipv6_enabled": false,
|
||||
"internal": false,
|
||||
"dns_enabled": false,
|
||||
"ipam_options": {
|
||||
"driver": "host-local"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
_Side note: I'm really happy they dropped the `.conflist` extension because this way most editors offer really helpful syntax highlighting in the first place!_
|
||||
|
||||
Note that `"internal": false` is mandatory. Otherwise I wasn't able to establish communication between VM and container.
|
||||
I also disabled the Aardvark {{<abbr short="DNS" full="Domain Name System">}} server and IPv6 support because I don't need it and I also don't expect much benefit of it due to the fact that it can't be aware of the VMs present in the network same as `dnsmasq` won't be able to resolve containers in the libvirt network.
|
||||
|
||||
Having this in place I was again able to reuse the CLI command from my previous article:
|
||||
|
||||
```bash
|
||||
podman run \
|
||||
--rm \
|
||||
-d \
|
||||
--name nginx \
|
||||
--network libvirt \
|
||||
--ip 10.10.1.151 \
|
||||
docker.io/nginx:alpine
|
||||
```
|
||||
|
||||
to create a Nginx container that can be reached from a VM.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Sometimes the communication between container and VM fails - don't know if I restarted the libvirt network previously or somehow fucked up the container network configuration but a:
|
||||
|
||||
```bash
|
||||
podman network reload <container ID/container name>
|
||||
```
|
||||
|
||||
often resolved the problem.
|
||||
|
||||
## Final thoughts
|
||||
|
||||
I haven't used _Netavark_ and _Aardvark_ a lot, yet.
|
||||
But I already noticed a few **really awesome** things:
|
||||
|
||||
- the `docker-compose` support seems to be a lot better now because containers are actually able to talk to each other by _service name_, something I wasn't able to configure properly in Podman 3.x - at least not rootless.
|
||||
- with _Netavark_ all the Podman configuration is now unified within `/etc/containers` or `$HOME/.config/containers` respectively
|
||||
- the new configuration format is a little bit cleaner the the previous one due to the fact that _Netavark_ does not support plugins and with a `.json` extension editors do help a lot more without requiring extra "configuration"
|
|
@ -1,155 +0,0 @@
|
|||
+++
|
||||
author = "Peter Kurfer"
|
||||
title = "Build & deploy a Hugo site with Gitea/Forgejo actions"
|
||||
description = "How to host a Hugo site with Cloudflare pages and deploy it automatically with Forgejo actions"
|
||||
date = "2024-04-30"
|
||||
tags = [
|
||||
"hugo",
|
||||
"cloudflare",
|
||||
"CD/CD",
|
||||
"actions"
|
||||
]
|
||||
+++
|
||||
|
||||
I admit it. I like self-hosting.
|
||||
I like the idea of being able to control every aspect of my infrastructure.
|
||||
It was only consequent to also self-host my blog.
|
||||
This article describes my odyssey and why I ended up letting [Cloudflare](https://www.cloudflare.com/) do the hosting.
|
||||
|
||||
In the beginning - there was a repository.
|
||||
As we all know, the repository is the truth.
|
||||
When the time came for deploying the blog, I already had a Kubernetes (K8s) cluster at hand so the obvious choice was to containerize the web page and host it there.
|
||||
I wrote a simple Dockerfile with a multi-stage build, just like this:
|
||||
|
||||
```Dockerfile
|
||||
FROM docker.io/golang:1-alpine as builder
|
||||
|
||||
WORKDIR /tmp
|
||||
|
||||
RUN apk add -U --no-cache hugo git
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY . /src/
|
||||
|
||||
RUN hugo --minify --environment production --config config.toml
|
||||
|
||||
FROM caddy as runtime
|
||||
|
||||
COPY --from=builder /src/public /usr/share/caddy
|
||||
```
|
||||
|
||||
prepared my deployment manifests and setup a CI pipeline (back then with DroneCI) to deploy everything.
|
||||
|
||||
So far so good, the only complicacy was that I now had two 'truths'.
|
||||
One was the repository and the second one was the container registry - let alone that I also had to 💸 the storage for both.
|
||||
Of course, various container registries have cleanup options but being a software engineer, why using something existing when you can build the 11th solution to solve the same problem, right?
|
||||
|
||||
Yes...actually, no!
|
||||
|
||||
In the beginning I just accepted the fact and went on.
|
||||
Every now and then, when the amount of images became costlier, I manually deleted a few until I reached a reasonable count - say...five, I mean in the end there was no reason to keep any old version at all, but you know, I was lazy.
|
||||
At some point I had a similar problem at work with our SPAs and I couldn't help but wonder: is this really the best way?
|
||||
Not only because I'm duplicating the content every time, but also the web server needs patching, every now and then a breaking change in the configuration system happens and so on and so forth.
|
||||
I came across the possibility to serve a S3 bucket (or similar) directly from a [K8s ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/#resource-backend).
|
||||
That sounded awesome!
|
||||
No need to build a container image, no need to waste compute resources, simple copy to S3 bucket and be done with it!
|
||||
|
||||
So I came back to my blog and tried to migrate to this approach.
|
||||
I wasted a few hours of my spare time, only realizing that - apparently - Cloudflare R2 or some CLI or something else is ignoring the content type of my files, leaving me with `application/octet-stream` which is absolutely useless for web pages.
|
||||
It might be different when I would use [MinIO](https://min.io/) or AWS S3 but I didn't want to waste even more resources (and 💵) on hosting a MinIO instance in my cluster.
|
||||
Also, I am already using Hetzner Cloud and didn't feel like distributing my costs around multiple cloud providers, so I started looking for alternative solutions.
|
||||
|
||||
I then stumbled upon [Cloudflare Pages](https://pages.cloudflare.com/).
|
||||
After a 'quick' prototype I was happy and decided to migrate - actually not so quick, I spent a few evenings on migrating my whole DNS setup to [external-dns](https://github.com/kubernetes-sigs/external-dns) and experimented with Cloudflare DNS for DoS protection but that's a topic for another day.
|
||||
|
||||
The only other problem I had was: I also got rid of DroneCI in favor of [Forgejo Actions](https://forgejo.org/docs/latest/user/actions/).
|
||||
I know, if I would use GitHub, there would be perfect integration from Cloudflare to build my Hugo page and deploy it, but we don't want to make things too easy, right?
|
||||
|
||||
But using Forgejo Actions also seemed pretty straight forward:
|
||||
|
||||
```yaml
|
||||
name: Deploy pages
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Hugo
|
||||
uses: peaceiris/actions-hugo@v3
|
||||
- name: Build
|
||||
run: hugo --minify --environment production
|
||||
- name: Deploy
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
apiToken: ${{ secrets.CF_PAGES_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
command: pages deploy public --project-name=blog
|
||||
```
|
||||
|
||||
Well, not so fast kiddo!
|
||||
|
||||
At first I noticed, when using [Hugo modules](https://gohugo.io/hugo-modules/) you need to fetch those modules before being able to build anything, alright:
|
||||
|
||||
```yaml
|
||||
// ...
|
||||
- name: Build
|
||||
run: |
|
||||
hugo mod get
|
||||
hugo --minify --environment production
|
||||
// ...
|
||||
```
|
||||
|
||||
then, obviously, I realized, for being able to fetch those modules, you need a Go SDK, there you go (pun intended):
|
||||
|
||||
```yaml
|
||||
// ...
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22.x"
|
||||
- name: Setup Hugo
|
||||
uses: peaceiris/actions-hugo@v3
|
||||
// ...
|
||||
```
|
||||
|
||||
and now we're getting - finally - to the point when things got really annoying...
|
||||
I'm using the [github.com/LordMathis/hugo-theme-nightfall](https://github.com/LordMathis/hugo-theme-nightfall) theme.
|
||||
Although being a very minimalistic theme, it requires [dart-sass](https://gohugo.io/functions/resources/tocss/#dart-sass).
|
||||
Even though this also seem straight forward, especially because there's only [documentation for Github Actions](https://gohugo.io/functions/resources/tocss/#github-pages), with Forgejo Actions it isn't.
|
||||
The key difference between Github Actions and Forgejo Actions is, that Forgejo Actions are running in containers.
|
||||
The officially recommended way to install `dart-sass` in Github Actions is via `snap`, but snap doesn't really work in containers, so I had find another way.
|
||||
When doing some research, you might come across the official [`dart-sass` repository](https://github.com/sass/dart-sass) that mentions another installation method:
|
||||
|
||||
```bash
|
||||
npm install -g sass
|
||||
```
|
||||
|
||||
but:
|
||||
|
||||
> The `--embedded` command-line flag is not available when you install Dart Sass as an npm package.
|
||||
|
||||
(*see [here](https://github.com/sass/dart-sass?tab=readme-ov-file#embedded-dart-sass)*)
|
||||
|
||||
unfortunately, Hugo requires the `--embedded` flag, so also not an option.
|
||||
Eventually I came around this abomination:
|
||||
|
||||
```yaml
|
||||
- name: Install sass
|
||||
run: |
|
||||
export SASS_VERSION=$(curl https://api.github.com/repos/sass/dart-sass/releases | jq -r '. | first |.tag_name | capture("(?<version>[[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+)") | .version')
|
||||
curl -L "https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-linux-arm64.tar.gz" | tar xvz -C /opt/
|
||||
ln -s /opt/dart-sass/sass /usr/local/bin/
|
||||
```
|
||||
|
||||
*Don't get confused by the huge capture in the `jq` expression, I'm using this snippet whenever I have to use the version of a package in the filename and this way I don't have to think about, is there a `v` prefix or not, looking at you 'goreleaser' 👀*
|
||||
|
||||
That downloads the latest release of `dart-sass` and makes it available in the `$PATH`.
|
||||
So far I'm not considering the CPU architecture because whenever possible I'm running my CI jobs on ARM machines anyway, but if I find the time, I might try to implement a custom action similar to `peaceiris/actions-hugo@v3` but with `dart-sass` support.
|
||||
|
||||
You can imagine how happy I was realizing the `cloudflare/wrangler-action@v3` step 'just worked' ™.
|
334
content/post/go-ebpf-tc.md
Normal file
334
content/post/go-ebpf-tc.md
Normal file
|
@ -0,0 +1,334 @@
|
|||
+++
|
||||
author = "Peter Kurfer"
|
||||
title = "eBPF traffic control a.k.a tc with Go"
|
||||
date = "2022-02-24"
|
||||
description = "Build your own DNAT with eBPF traffic control and Go"
|
||||
tags = [
|
||||
"golang",
|
||||
"ebpf",
|
||||
"tc"
|
||||
]
|
||||
+++
|
||||
|
||||
While working on my 'pet project' [INetMock](https://gitlab.com/inetmock/inetmock) I realized early that it would be amazing to be able to monitor traffic that 'flows by' to see not only traffic I could actually handle but also to get some information which high ports were requested.
|
||||
Unfortunately back then I was only aware of PCAP capturing and it seemed rather complicated to implement what I wanted on top of PCAPs - or at least rather computation intense.
|
||||
|
||||
## eBPF for the rescue
|
||||
|
||||
At work I'm part of the platform team maintaining a bunch of Kubernetes clusters and this is where I first stumbled upon eBPF when we migrated from Calico to Cilium.
|
||||
So you might ask what is eBPF?!
|
||||
Very short answer: [RTFM](https://ebpf.io/what-is-ebpf) :nerd:
|
||||
|
||||
eBPF is the abbreviation for 'Extended Berkeley Packet Filter'.
|
||||
While originally intended to be used for network traffic analysis it got some major upgrades over the past years so that you can now:
|
||||
|
||||
* monitor/control syscalls like [Falco](https://sysdig.com/opensource/falco/) is doing it
|
||||
* implement a very mighty [keylogger](https://arighi.blogspot.com/2018/12/linux-easy-keylogger-with-ebpf.html)
|
||||
* [build DDoS attack prevention systems](https://blog.cloudflare.com/l4drop-xdp-ebpf-based-ddos-mitigations/)
|
||||
* [L4 load balancers](https://blog.cloudflare.com/unimog-cloudflares-edge-load-balancer/)
|
||||
* [Making a firewall using eBPFs and cgroups](https://nfil.dev/coding/security/ebpf-firewall-with-cgroups/)
|
||||
* ...
|
||||
|
||||
After some initial research I figured using XDP (eXpress Data Path) would be best to implement the kind of monitoring I had in mind.
|
||||
XDP is also used by Cloudflare for their DDoS prevention system and for their load balancer hence what's good enough for them could be good enough for me (irony intended).
|
||||
So I was looking for some 'examples' a.k.a. code I can copy and adopt to get started quickly.
|
||||
I stumbled upon a project monitoring that monitored the API server traffic in a Kubernetes cluster and this was a lot like what I wanted to do!
|
||||
At least it looked like it at this point.
|
||||
It took me a few evening to get my prototype working and I read a lot more about the topic whenever possible.
|
||||
|
||||
Coincidentally we had a firewall/network misconfiguration issue at work at the same time when I tried to get my first prototype live in the staging environment and it hit me:
|
||||
why not using ebpf/XDP not only for monitoring but also to get rid of `iptables` and build my own 'firewall'! :heart_eyes:
|
||||
Don't misunderstand me, `iptables` is a perfectly fine and perfectly working solution and it served me well!
|
||||
But it's always a bit annoying when I start the server and forgot to run the ominous `iptables` command I originally copied from the _INetSim_ docs that takes care of the DNATing and suddenly nothing works.
|
||||
|
||||
## 1st approach: XDP
|
||||
|
||||
To be honest: I used and configured both SNAT and DNAT often in the past and I had a basic idea how this all works but...this 'tiny bit of knowledge' rapidly crumbled into nothing as soon as I tried to build it myself.
|
||||
|
||||
The idea is simple.
|
||||
In general the communication of a client with an external server looks like this:
|
||||
|
||||
{{<mermaid>}}
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
flowchart LR
|
||||
client([Client]) --> |IP packet|router{{Router}}
|
||||
router --> |IP packet|srv>Server]
|
||||
srv -->|response|router
|
||||
router-->|esponse|client
|
||||
{{< /mermaid>}}
|
||||
|
||||
(Ignoring everything that is actually required like DNS, TCP handshakes,...)
|
||||
|
||||
What I wanted to achieve looks like this:
|
||||
|
||||
{{<mermaid>}}
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
flowchart LR
|
||||
client([Client]) --> |IP packet|router{{Router}}
|
||||
router --> |modifiedIP packet|router
|
||||
router-->|faked response|client
|
||||
{{< /mermaid>}}
|
||||
|
||||
I already knew it's possible and even intended to re-route packets with XDP because that's exactly what Cloudflare is doing with Unimog.
|
||||
I tried to find some more examples on how to do redirect traffic and at this point I realized how much I **did/do** not know:
|
||||
|
||||
* Do I need to update any checksums?
|
||||
* If I only modify the IP header, do I also need to fix TCP/UDP checksums?
|
||||
* Is modifying the IP enough or do I also need to modify the destination MAC address of the ethernet packet?
|
||||
|
||||
And I bet there's even more I still haven't even thought about...
|
||||
|
||||
It's kind of hart to admit but it took me actually a lot of time to realize why my XDP approach wasn't working and will not ever work in the setup I have in mind.
|
||||
One disadvantage of diving directly into the code and focusing on examples is: you've no idea what you're actually doing!
|
||||
XDP is so great because depending on your hardware it is even possible to execute the program directly on the NIC but one huge disadvantage (for me) is, that it only captures **_ingress_** traffic!
|
||||
|
||||
So I perfectly screwed up because my whole traffic was within a single network segment and looked like in the following diagram.
|
||||
I'm now using a sequence diagram to make things a bit more explicit.
|
||||
|
||||
Assuming the following network configuration:
|
||||
|
||||
{{< table "center" >}}
|
||||
| Actor | IP |
|
||||
| ------ | -------------- |
|
||||
| Client | 192.168.178.51 |
|
||||
| Router | 192.168.178.1 |
|
||||
{{</ table >}}
|
||||
|
||||
{{<mermaid>}}
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
sequenceDiagram
|
||||
Note over Client,Router: .51:34578 → 1.2.3.4:80
|
||||
Client->>Router: TCP SYN
|
||||
Note over Client,Router: .51:34578 → 192.168.178.1:80
|
||||
Router->>Router: Redirect to 192.168.178.1:80
|
||||
Note over Client,Router: .51:34578 ← .1:80
|
||||
Router->>Client: SYN-ACK
|
||||
{{< /mermaid>}}
|
||||
|
||||
But the `Client` didn't try to connect to `192.168.178.1` hence it won't accept the packet.
|
||||
While this is kind of obvious it took me quite some time to get my head around this.
|
||||
Not only but also because it's hard to observe this if you're using XDP because XDP forwarded/modified packets are not included in a PCAP.
|
||||
Fortunately there's [`xdp-dump`](https://github.com/xdp-project/xdp-tools) to get a better understanding what's going on.
|
||||
|
||||
Okay, so I've to maintain a mapping of the original 4-tuple to my modified one to be able to restore the original source after the network stack handled the packet.
|
||||
eBPF has a few different map types to store data between program invocations (I'll cover that later) so this wasn't a problem.
|
||||
But now it hit me, with XDP I cannot manipulate egress (outgoing) packets.
|
||||
So XDP's a dead end for this use case (although perfectly fine for the monitoring!).
|
||||
|
||||
## What if XDP is not enough?
|
||||
|
||||
Another round of 'research' revealed there are even more points in the network stack already where I could attach eBPF programs.
|
||||
Every trace point has slightly different options (and therefore make sense to be there).
|
||||
|
||||
There are:
|
||||
|
||||
* `BPF_PROG_TYPE_XDP`: earliest possible point to get your hands on ingress traffic, can be used to monitor (and pass) incoming traffic, drop or redirect traffic
|
||||
* `BPF_PROG_TYPE_SOCKET_FILTER`: drop or trim packets at socket level (much later than with XDP)
|
||||
* `BPF_PROG_TYPE_CGROUP_SOCK`: much like XDP but within network cgroups
|
||||
* ...
|
||||
|
||||
_A full list of program types can be found in [include/uapi/linux/bpf.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/bpf.h#L920) in the Linux kernel source.
|
||||
A good introduction into the different types of eBPF programs can be found [here](https://blogs.oracle.com/linux/post/bpf-a-tour-of-program-types) (can't believe I'm linking an Oracle document).
|
||||
|
||||
All of the aforementioned options have in common that they can be used to filter packets but what I needed was an option to _modify egress_ packets.
|
||||
AFAIK the only available option for this is `tc` (a.k.a. traffic control) which is Linux' QoS system.
|
||||
Even though you get a lot of 'high level' information about what it is, that it supports (e)BPF and that there's the `tc` CLI to interact with it - also lot's of examples how to use the CLI - I could barely find a library/documentation about the API to not requiring shell outs.
|
||||
Finally I found DataDog's [ebpf-manager](https://github.com/DataDog/ebpf-manager/) which uses Cilium's [ebpf](https://github.com/cilium/ebpf) and [go-tc](https://github.com/florianl/go-tc) to attach eBPF programs to the `tc` subsystem.
|
||||
Actually not only this it also comes with a pretty handy manager layer to make working with eBPF a charm.
|
||||
|
||||
## `tc` in action
|
||||
|
||||
From now I'd like to dig a bit into the source code - all sources can be found [in the repo](https://github.com/baez90/baez90.github.io) under `code/ebpf-xdp-tc`.
|
||||
The setup for experimenting looks like this:
|
||||
|
||||
{{<mermaid>}}
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
flowchart LR
|
||||
subgraph libvirtNet [isolated Libvirt network]
|
||||
vm["Windows VM"] --> container["Podman container"]
|
||||
end
|
||||
{{< /mermaid>}}
|
||||
|
||||
and is based on my post on how to [join a Podman container to a Libvirt network]({{< relref "libvirt-podman-network-mesh.md" >}}).
|
||||
|
||||
The resulting workflow should be looking like so:
|
||||
|
||||
{{<mermaid>}}
|
||||
%%{init: {'theme': 'neutral'}}%%
|
||||
sequenceDiagram
|
||||
Client->>Ingress: IP packet
|
||||
Ingress->>Ingress: Rewrite packet and store original destination
|
||||
Ingress->>Server: Forward packet
|
||||
Server->>Egress: Intercept response packet
|
||||
Egress->>Egress: Restore original destination as source
|
||||
Egress->>Client: Forward packet
|
||||
{{< /mermaid>}}
|
||||
|
||||
So in short words: the client sends an IP packet to an IP outside of the local network hence it will be sent to the gateway (which happens to be my Podman container).
|
||||
The eBPF program attached to the `tc` ingress hook takes the incoming packet, rewrites it's destination to the local IP of the Podman container and passes the modified packet on.
|
||||
In my experiment I'm using a simple HTTP server to respond to every HTTP request with a plain text message and a status code `200`.
|
||||
The network stack processes the packet and replies e.g. with a `SYN-ACK` to the client's IP address.
|
||||
The eBPF program attached to the `tc` egress hook intercepts the response, restores the original source source IP based on the client's IP address and TCP/UDP port and forwards the packet.
|
||||
|
||||
### Ingress traffic
|
||||
|
||||
Every eBPF program needs a section identifier and has to fulfill some constraints to pass the verifier.
|
||||
Currently this means for instance that loops are not allowed to ensure the program has a guaranteed end.
|
||||
|
||||
The simplest ingress hook would look like this:
|
||||
|
||||
```c
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/pkt_cls.h>
|
||||
|
||||
SEC("classifier/ingress")
|
||||
int ingress(struct __sk_buff *skb) {
|
||||
return TC_ACT_SHOT;
|
||||
}
|
||||
```
|
||||
|
||||
This program would simply drop every incoming packet but it's a valid program.
|
||||
The parameter - in this case `struct __sk_buff *skb` - depends on the trace point.
|
||||
The `skb` is the most powerful one and in theory it's even possible to extract HTTP parameters out of it.
|
||||
See for example [this article](http://vger.kernel.org/~davem/skb_data.html) for further details.
|
||||
XDP programs are receiving another parameter that is a bit more generic but also less expensive to initialize.
|
||||
Either way, you can easily (and rather cheap) extract the different parts of the IP packet just with a few lines of code.
|
||||
|
||||
A quick reminder how an IP packet looks like:
|
||||
|
||||
{{< figure
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/3/3b/UDP_encapsulation.svg"
|
||||
link="https://commons.wikimedia.org/wiki/File:UDP_encapsulation.svg"
|
||||
target="__blank"
|
||||
caption="en:User:Cburnett original work, colorization by en:User:Kbrose, [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/), via Wikimedia Commons"
|
||||
title="UDP encapsulation"
|
||||
>}}
|
||||
|
||||
In my simplified case I assume the `Frame header` will be a `struct ethhdr`, followed by the `struct iphdr` and then either a `struct udphdr` or a `struct tcphdr`.
|
||||
|
||||
To satisfy the verifier you've to validate after every cast that you haven't reached already the end of the current `skb` to ensure memory safety which is both: a bit annoying and a lot calming because memory issues are avoided right away.
|
||||
So assuming we just want to 'print' the source and destination address of every packet reaching our ingress hook we would do the following:
|
||||
|
||||
|
||||
```c
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/if_ether.h>
|
||||
#include <linux/in.h>
|
||||
#include <linux/ip.h>
|
||||
#include <linux/pkt_cls.h>
|
||||
|
||||
SEC("classifier/ingress")
|
||||
int ingress(struct __sk_buff *skb) {
|
||||
void *data = (void *) (long) skb->data;
|
||||
void *data_end = (void *) (long) skb->data_end;
|
||||
|
||||
struct ethhdr *eth = data;
|
||||
|
||||
// apparently not an ethernet packet
|
||||
if ((void *) eth + sizeof(*eth) > data_end) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
// ignore packet that are neither IPv4 nor IPv6
|
||||
if(eth->h_proto != ETH_P_IP && eth->h_proto != ETH_P_IPV6) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
struct iphdr *iph = data + sizeof(*eth);
|
||||
if ((void *) iph + sizeof(*iph) > data_end) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
bpf_printk("Packet from %d to %d\n", iph->saddr, iph->daddr);
|
||||
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
```
|
||||
|
||||
That's already a bit more code, isn't it?
|
||||
So we start by capturing the beginning and the end of the packet.
|
||||
As mentioned previously it's possible to 'extract' the individual parts of the packet just by casting the right offsets.
|
||||
Of course some additional sanity checks are necessary to make sure we don't misinterpret anything.
|
||||
For instance if the current packet is not an IP packet but probably an ARP packet we just let it pass.
|
||||
What's worth mentioned is the `bpf_printk` macro because it's particularly useful - although it's slower than other options but it's pretty easy to use for debugging.
|
||||
To get the message we're sending with it you can simply do
|
||||
|
||||
```sh
|
||||
sudo cat /sys/kernel/debug/tracing/trace_pipe
|
||||
```
|
||||
|
||||
and you're good to go!
|
||||
|
||||
#### eBPF maps
|
||||
|
||||
So remembering the sequence diagram above we are already almost finished regarding the parsing of the required information but how do we store the gather information?
|
||||
eBPF comes with a bunch of different maps.
|
||||
All types can be found in [include/uapi/linux/bpf.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/bpf.h#L878) or with more details [here](https://prototype-kernel.readthedocs.io/en/latest/bpf/ebpf_maps_types.html).
|
||||
|
||||
Some of them are equivalent to ordinary data structures most developers are used to like:
|
||||
|
||||
* `BPF_MAP_TYPE_ARRAY` behaves like an ordinary array
|
||||
* `BPF_MAP_TYPE_HASH` behaves like an ordinary map/dictionary
|
||||
|
||||
but others like `BPF_MAP_TYPE_PERF_EVENT_ARRAY` or `BPF_MAP_TYPE_RINGBUF` are rather special.
|
||||
Fow now we're focusing on `BPF_MAP_TYPE_HASH` because we can use it to store the `orig-src` → `orig-dest` mapping.
|
||||
To store data in a map and load it later on eBPF exposes some [bpf-helpers](https://www.man7.org/linux/man-pages/man7/bpf-helpers.7.html):
|
||||
|
||||
* `long bpf_map_update_elem(struct bpf_map *map, const void *key, const void *value, u64 flags)` to store data in a map
|
||||
* `void *bpf_map_lookup_elem(struct bpf_map *map, const void *key)` to load data
|
||||
|
||||
An extension of the previous program could look like so:
|
||||
|
||||
|
||||
```c
|
||||
/*
|
||||
... includes
|
||||
*/
|
||||
|
||||
#define IP_FRAGMENTED 65343
|
||||
|
||||
// source IP and port
|
||||
struct two_tuple {
|
||||
__u32 ip;
|
||||
__u16 port;
|
||||
__u16 _pad; // required to pad the size of the struct to a multiple of 4
|
||||
};
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_HASH);
|
||||
__type(key, sizeof(struct two_tuple));
|
||||
__type(value, sizeof(struct two_tuple));
|
||||
__uint(max_entries, 1024);
|
||||
} conn_track SEC(".maps");
|
||||
|
||||
SEC("classifier/ingress")
|
||||
int ingress(struct __sk_buff *skb) {
|
||||
// ... previous logic
|
||||
|
||||
// do not support fragmented packets as L4 headers may be missing
|
||||
if (iph->frag_off & IP_FRAGMENTED) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
if (iph->protocol != IPPROTO_TCP) {
|
||||
return TC_ACT_OK;
|
||||
}
|
||||
|
||||
struct tcphdr *tcp = (void *) iph + sizeof(*iph);
|
||||
if ((void *) tcp + sizeof(*tcp) > data_end) {
|
||||
return TC_ACT_SHOT;
|
||||
}
|
||||
|
||||
struct two_tuple src = {
|
||||
.ip = iph->saddr,
|
||||
.port = tcp->source
|
||||
};
|
||||
|
||||
struct two_tuple dst = {
|
||||
.ip = iph->daddr,
|
||||
.port = tcp->dest
|
||||
};
|
||||
|
||||
bpf_map_update_elem(&conn_track, &src, &dst, 0);
|
||||
}
|
||||
```
|
|
@ -9,9 +9,9 @@ tags = [
|
|||
]
|
||||
+++
|
||||
|
||||
_Disclaimer: I tested all this with Podman 3.x even though Podman 4.0 is already announced but the {{<abbr short="CNI" full="Container Network Interface" >}} driver is still available with Podman 4.0 and as soon as I get my hands on 4.0 I'll give **Netavark** a try, too!_
|
||||
_Disclaimer: I tested all this with Podman 3.x even though Podman 4.0 is already announced but the CNI driver is still available with Podman 4.0 and as soon as I get my hands on 4.0 I'll give **Netavark** a try, too!_
|
||||
|
||||
When playing around with containers and {{<abbr short="VM" full="Virtual Machine" >}}s one might ask if it's possible to bring VMs and containers into a common network segment.
|
||||
When playing around with containers and VMs one might ask if it's possible to bring VMs and containers into a common network segment.
|
||||
I see 'why the hell would I need a VM anyway when already having containers' or something similar I almost see on your face :stuck_out_tongue_winking_eye:
|
||||
|
||||
Well 1st of all, not everything can be solved with containers.
|
||||
|
@ -19,7 +19,7 @@ For instance windows applications can be run in Windows containers but I'm not a
|
|||
|
||||
But also in pure Linux environments there are cases where a VM is probably a better fit for the problem.
|
||||
As you might know I'm a bit of network :nerd: and I love playing around with 'weird' stuff almost no one else does even think about if not forced to.
|
||||
So if you try to implement for example your own DHCP server you might want to isolate your experiments (especially at the beginning) to avoid discussion about "why's Netflix on the TV not working?!" :smile: or also if you try to implement your own 'firewall' with {{<abbr short="DNAT" full="Destination network address translation" >}} support (stay tuned - post's following!).
|
||||
So if you try to implement for example your own DHCP server you might want to isolate your experiments (especially at the beginning) to avoid discussion about "why's Netflix on the TV not working?!" :smile: or also if you try to implement your own 'firewall' with DNAT support (stay tuned - post's following!).
|
||||
|
||||
## Part 1: Libvirt preparation
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
title: 'Projects'
|
||||
button: 'Projects'
|
||||
weight: 2
|
||||
showMetadata: false
|
||||
---
|
||||
|
||||
## INetMock
|
||||
|
|
5
go.mod
5
go.mod
|
@ -1,5 +0,0 @@
|
|||
module code.icb4dc0.de/prskr/blog
|
||||
|
||||
go 1.22
|
||||
|
||||
require github.com/LordMathis/hugo-theme-nightfall v0.7.1 // indirect
|
2
go.sum
2
go.sum
|
@ -1,2 +0,0 @@
|
|||
github.com/LordMathis/hugo-theme-nightfall v0.7.1 h1:n8T4Eg/5ZJLXGWmRHHAZ9DrbIv0yXBATeNo/A3p8EoM=
|
||||
github.com/LordMathis/hugo-theme-nightfall v0.7.1/go.mod h1:0tCPxAeg5+tWhv17517Q8Lti/TPh0KNyON/uferEU30=
|
|
@ -1,4 +0,0 @@
|
|||
{{ if hugo.IsProduction }}
|
||||
<script defer src="https://analytics.icb4dc0.de/script.js" data-website-id="fcc44d94-82fe-433a-bee1-46b586dc69f8"></script>
|
||||
{{ end }}
|
||||
<script src="https://kit.fontawesome.com/e6634cbc14.js" crossorigin="anonymous"></script>
|
|
@ -1 +1,4 @@
|
|||
<!--for overriding-->
|
||||
{{ range .Site.Params.custom_css -}}
|
||||
<link rel="stylesheet" href="{{ . | absURL }}">
|
||||
{{- end }}
|
|
@ -1 +0,0 @@
|
|||
<abbr title="{{ .Get "full" }}">{{ .Get "short" }}</abbr>
|
6
layouts/shortcodes/table.html
Normal file
6
layouts/shortcodes/table.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
{{ $htmlTable := .Inner | markdownify }}
|
||||
{{ $class := .Get 0 }}
|
||||
{{ $old := "<table>" }}
|
||||
{{ $new := printf "<table class=\"%s\">" $class }}
|
||||
{{ $htmlTable := replace $htmlTable $old $new }}
|
||||
{{ $htmlTable | safeHTML }}
|
6
static/css/custom.css
Normal file
6
static/css/custom.css
Normal file
|
@ -0,0 +1,6 @@
|
|||
.center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
1
themes/anubis
Submodule
1
themes/anubis
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 8e618548dad3553e196aeae334900d9afe76256e
|
Loading…
Reference in a new issue