Skip to content
Merged

Xdp #44

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 56 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,40 @@ env:
NODE_VERSION: '20'

jobs:
# ─── Build eBPF program (nightly, bpf target, architecture-independent) ───
build-ebpf:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4

- name: Install nightly Rust + BPF target
uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src

- name: Cache eBPF build
uses: Swatinem/rust-cache@v2
with:
workspaces: rust/xdp-ebpf

- name: Build eBPF program
working-directory: rust/xdp-ebpf
run: |
cargo build \
--target bpfel-unknown-none \
-Z build-std=core \
--release

- name: Upload eBPF binary
uses: actions/upload-artifact@v4
with:
name: xdp-ebpf
path: rust/xdp-ebpf/target/bpfel-unknown-none/release/xdp-ebpf
retention-days: 1

# ─── Test Rust ───
test-rust:
needs: build-ebpf
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
Expand All @@ -28,13 +60,22 @@ jobs:
with:
workspaces: rust

- name: Install libelf-dev
run: sudo apt-get update && sudo apt-get install -y libelf-dev

- name: Download eBPF binary
uses: actions/download-artifact@v4
with:
name: xdp-ebpf
path: rust/xdp-ebpf/target/bpfel-unknown-none/release/

- name: Rust test
working-directory: rust
run: cargo test

# ─── Build spoof-tunnel (CGO_ENABLED=1, Rust + Go, Linux only) ───
build-tunnel:
needs: test-rust
needs: [test-rust, build-ebpf]
runs-on: ubuntu-22.04
strategy:
fail-fast: false
Expand All @@ -50,7 +91,7 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Install Rust
- name: Install Rust (stable)
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.rust_target }}
Expand All @@ -60,9 +101,19 @@ jobs:
with:
workspaces: rust

- name: Install cross-compilation tools
if: matrix.goarch == 'arm64'
run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu
- name: Install cross-compilation tools & libelf-dev
run: |
sudo apt-get update
sudo apt-get install -y libelf-dev
if [ "${{ matrix.goarch }}" = "arm64" ]; then
sudo apt-get install -y gcc-aarch64-linux-gnu
fi

- name: Download eBPF binary
uses: actions/download-artifact@v4
with:
name: xdp-ebpf
path: rust/xdp-ebpf/target/bpfel-unknown-none/release/

- name: Build Rust library
working-directory: rust
Expand Down
63 changes: 59 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,98 @@ on:
- 'panel/**'

jobs:
# ─── Build eBPF program (nightly, bpf target) ───
build-ebpf:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install nightly Rust + BPF target
uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src

- name: Cache eBPF build
uses: Swatinem/rust-cache@v2
with:
workspaces: rust/xdp-ebpf

- name: Install bpf-linker
run: cargo install bpf-linker

- name: Build eBPF program
working-directory: rust/xdp-ebpf
run: |
cargo build \
--target bpfel-unknown-none \
-Z build-std=core \
--release

- name: Upload eBPF binary
uses: actions/upload-artifact@v4
with:
name: xdp-ebpf
path: rust/xdp-ebpf/target/bpfel-unknown-none/release/xdp-ebpf
retention-days: 1

# ─── Test Rust workspace ───
test-rust:
needs: build-ebpf
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Rust
- name: Install stable Rust
uses: dtolnay/rust-toolchain@stable

- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: rust

- name: Install libelf-dev (required by aya)
run: sudo apt-get update && sudo apt-get install -y libelf-dev

- name: Download eBPF binary
uses: actions/download-artifact@v4
with:
name: xdp-ebpf
path: rust/xdp-ebpf/target/bpfel-unknown-none/release/

- name: Test
working-directory: rust
run: cargo test

# ─── Test Go (depends on Rust workspace build) ───
test-go:
needs: test-rust
needs: build-ebpf
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ['1.22', '1.23']
steps:
- uses: actions/checkout@v4

- name: Install Rust
- name: Install stable Rust
uses: dtolnay/rust-toolchain@stable

- name: Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: rust

- name: Install libelf-dev (required by aya)
run: sudo apt-get update && sudo apt-get install -y libelf-dev

- name: Download eBPF binary
uses: actions/download-artifact@v4
with:
name: xdp-ebpf
path: rust/xdp-ebpf/target/bpfel-unknown-none/release/

- name: Build Rust library
working-directory: rust
run: cargo build --release
run: cargo build --release -p spoof-transport

- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v5
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,7 @@ next-env.d.ts
panel/backend/panel
spoof-linux-amd64
spoof-panel-linux-amd64

# XDP eBPF build artifacts
rust/xdp-ebpf/target/
rust/xdp-loader/target/
26 changes: 21 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
.PHONY: all core panel frontend backend clean test rust
.PHONY: all core panel frontend backend clean test rust xdp-ebpf xdp-loader

# ── Build All ──
all: core panel

# ── Core (spoof binary with Rust FFI) ──
# ── XDP/eBPF (kernel-side program, needs nightly) ──
xdp-ebpf:
cd rust/xdp-ebpf && cargo +nightly build \
--target bpfel-unknown-none \
-Z build-std=core \
--release

# ── XDP Loader (userspace, links into Go binary) ──
xdp-loader: xdp-ebpf
cd rust/xdp-loader && cargo build --release

# ── Core transport (existing Rust FFI) ──
rust:
cd rust && cargo build --release

core: rust
CGO_ENABLED=1 go build -o spoof ./cmd/spoof/
# ── Core binary (Go + Rust FFI + XDP) ──
core: rust xdp-loader
CGO_ENABLED=1 go build \
-ldflags "-r $(PWD)/rust/xdp-loader/target/release" \
-o spoof ./cmd/spoof/

# ── Panel (frontend + backend) ──
panel: frontend backend
Expand All @@ -29,14 +43,16 @@ dev-backend:
cd panel/backend && CGO_ENABLED=0 go build -o ../../spoof-panel ./cmd/panel/

# ── Test ──
test: rust
test: rust xdp-ebpf
cd rust && cargo test
CGO_ENABLED=1 go test ./internal/...
cd panel/backend && go vet ./...

# ── Clean ──
clean:
cd rust && cargo clean
cd rust/xdp-ebpf && cargo clean 2>/dev/null || true
cd rust/xdp-loader && cargo clean 2>/dev/null || true
rm -f spoof spoof-panel
rm -rf panel/frontend/.next panel/frontend/out
rm -rf panel/backend/cmd/panel/web
18 changes: 12 additions & 6 deletions cmd/spoof/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ func runCmd() *cobra.Command {
case "local":
runLocal(cfg.Listen, cfg.Remote, cfg.RemotePort, cfg.RecvPort,
cfg.SpoofIP, cfg.SpoofPort, cfg.PeerSpoofIP, cfg.SpoofIPFile,
cfg.SendTransport, cfg.RecvTransport)
cfg.SendTransport, cfg.RecvTransport, cfg.XDPInterface)
case "remote":
runRemote(cfg.ListenPort, cfg.Forward, cfg.ClientIP, cfg.ClientPort,
cfg.SpoofIP, cfg.SpoofPort, cfg.PeerSpoofIP,
cfg.SpoofIPFile, cfg.SendTransport, cfg.RecvTransport)
cfg.SpoofIPFile, cfg.SendTransport, cfg.RecvTransport, cfg.XDPInterface)
default:
log.Fatalf("unknown mode in config: %q", cfg.Mode)
}
Expand All @@ -81,6 +81,7 @@ func localCmd() *cobra.Command {
spoofIPFile string
sendTransport string
recvTransport string
xdpInterface string
configFile string
)

Expand Down Expand Up @@ -113,7 +114,7 @@ func localCmd() *cobra.Command {
log.Fatal("--spoof-ip or --spoof-ip-file is required")
}

runLocal(listen, remoteAddr, remotePort, recvPort, spoofIP, spoofPort, peerSpoofIP, spoofIPFile, sendTransport, recvTransport)
runLocal(listen, remoteAddr, remotePort, recvPort, spoofIP, spoofPort, peerSpoofIP, spoofIPFile, sendTransport, recvTransport, xdpInterface)
},
}

Expand All @@ -127,6 +128,7 @@ func localCmd() *cobra.Command {
cmd.Flags().StringVar(&spoofIPFile, "spoof-ip-file", "", "file with spoof IPs (one per line, round-robin)")
cmd.Flags().StringVar(&sendTransport, "send-transport", "", "send transport: tcp, udp, icmp, icmpv6 (default: tcp)")
cmd.Flags().StringVar(&recvTransport, "recv-transport", "", "recv transport: tcp, udp, icmp, icmpv6 (default: udp)")
cmd.Flags().StringVar(&xdpInterface, "xdp-interface", "", "network interface for XDP acceleration (e.g. eth0)")
cmd.Flags().StringVarP(&configFile, "config", "c", "", "path to config file (CLI flags override)")

return cmd
Expand All @@ -144,6 +146,7 @@ func remoteCmd() *cobra.Command {
spoofIPFile string
sendTransport string
recvTransport string
xdpInterface string
configFile string
)

Expand Down Expand Up @@ -176,7 +179,7 @@ func remoteCmd() *cobra.Command {
log.Fatal("--spoof-ip or --spoof-ip-file is required")
}

runRemote(listenPort, forward, clientIP, clientPort, spoofIP, spoofPort, peerSpoofIP, spoofIPFile, sendTransport, recvTransport)
runRemote(listenPort, forward, clientIP, clientPort, spoofIP, spoofPort, peerSpoofIP, spoofIPFile, sendTransport, recvTransport, xdpInterface)
},
}

Expand All @@ -190,6 +193,7 @@ func remoteCmd() *cobra.Command {
cmd.Flags().StringVar(&spoofIPFile, "spoof-ip-file", "", "file with spoof IPs (one per line, round-robin)")
cmd.Flags().StringVar(&sendTransport, "send-transport", "", "send transport: tcp, udp, icmp, icmpv6 (default: udp)")
cmd.Flags().StringVar(&recvTransport, "recv-transport", "", "recv transport: tcp, udp, icmp, icmpv6 (default: tcp)")
cmd.Flags().StringVar(&xdpInterface, "xdp-interface", "", "network interface for XDP acceleration (e.g. eth0)")
cmd.Flags().StringVarP(&configFile, "config", "c", "", "path to config file (CLI flags override)")

return cmd
Expand All @@ -211,7 +215,7 @@ func loadSpoofIPs(spoofIP, spoofIPFile string) ([]net.IP, error) {
return []net.IP{ip}, nil
}

func runLocal(listen, remoteAddr string, remotePort, recvPort int, spoofIP string, spoofPort int, peerSpoofIP, spoofIPFile, sendTransport, recvTransport string) {
func runLocal(listen, remoteAddr string, remotePort, recvPort int, spoofIP string, spoofPort int, peerSpoofIP, spoofIPFile, sendTransport, recvTransport, xdpInterface string) {
rIP := net.ParseIP(remoteAddr)
if rIP == nil {
log.Fatalf("invalid remote IP: %s", remoteAddr)
Expand Down Expand Up @@ -252,6 +256,7 @@ func runLocal(listen, remoteAddr string, remotePort, recvPort int, spoofIP strin
PeerSpoofIP: psIP,
SendTransport: sendTransport,
RecvTransport: recvTransport,
XDPInterface: xdpInterface,
}

l, err := relay.NewLocal(cfg)
Expand Down Expand Up @@ -284,7 +289,7 @@ func runLocal(listen, remoteAddr string, remotePort, recvPort int, spoofIP strin
l.Run()
}

func runRemote(listenPort int, forward, clientIP string, clientPort int, spoofIP string, spoofPort int, peerSpoofIP string, spoofIPFile, sendTransport, recvTransport string) {
func runRemote(listenPort int, forward, clientIP string, clientPort int, spoofIP string, spoofPort int, peerSpoofIP string, spoofIPFile, sendTransport, recvTransport, xdpInterface string) {
cIP := net.ParseIP(clientIP)
if cIP == nil {
log.Fatalf("invalid client-ip: %s", clientIP)
Expand Down Expand Up @@ -322,6 +327,7 @@ func runRemote(listenPort int, forward, clientIP string, clientPort int, spoofIP
PeerSpoofIP: psIP,
SendTransport: sendTransport,
RecvTransport: recvTransport,
XDPInterface: xdpInterface,
}

r, err := relay.NewRemote(cfg)
Expand Down
3 changes: 3 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ type Config struct {
// Transport selection per direction
SendTransport string `json:"send_transport,omitempty"` // "tcp", "udp", "icmp", "icmpv6"
RecvTransport string `json:"recv_transport,omitempty"` // "tcp", "udp", "icmp", "icmpv6"

// XDP/eBPF acceleration (receive path)
XDPInterface string `json:"xdp_interface,omitempty"` // NIC to attach XDP to (e.g. "eth0")
}

func Load(path string) (*Config, error) {
Expand Down
9 changes: 6 additions & 3 deletions internal/relay/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type LocalConfig struct {
PeerSpoofIP net.IP
SendTransport string // "tcp", "udp", "icmp", "icmpv6"
RecvTransport string // "tcp", "udp", "icmp", "icmpv6"
XDPInterface string // network interface for XDP (empty = disabled)
}

type Local struct {
Expand Down Expand Up @@ -89,9 +90,11 @@ func NewLocal(cfg LocalConfig) (*Local, error) {
}

recver, err := transport.NewReceiver(cfg.RecvTransport, transport.ReceiverConfig{
ListenPort: cfg.RecvPort,
PeerSpoofIP: cfg.PeerSpoofIP,
BufferSize: 4 * 1024 * 1024,
ListenPort: cfg.RecvPort,
PeerSpoofIP: cfg.PeerSpoofIP,
BufferSize: 4 * 1024 * 1024,
UseXDP: cfg.XDPInterface != "",
XDPInterface: cfg.XDPInterface,
})
if err != nil {
udpConn.Close()
Expand Down
Loading
Loading