Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e7cef93
refactor(socks5): move rule matching to proxy, extract handlers
xvzc Dec 21, 2025
f211a10
refactor: extract Destination to netutil, update socks5 to use it, re…
xvzc Dec 21, 2025
d5363c9
refactor: extract TLS tunneling logic to handler.Bridge
xvzc Dec 21, 2025
87f4155
refactor: restructure proxy packages
xvzc Dec 21, 2025
919fbf6
chore: rename handler files for clarity
xvzc Dec 21, 2025
0f6b76f
refactor: restructure proxy packages to http, socks5, and tlsutil
xvzc Dec 21, 2025
92247c3
feat: support socks5 and tun
xvzc Jan 14, 2026
d23b69a
feat: support socks5 and tun for linux
xvzc Jan 16, 2026
3fa51d6
fix: build condition for tun mode
xvzc Jan 16, 2026
bfa53b0
refactor(netutil): consolidate OS-specific code into netutil_*.go
xvzc Jan 17, 2026
cbdb7a5
fix(tun): use -ifscope for proper interface-bound routing on macOS
xvzc Jan 17, 2026
2acfb16
refactor(netutil): rename TimeoutConn to IdleTimeoutConn
xvzc Jan 17, 2026
eaae972
refactor(config): rename UDPTimeout to UDPIdleTimeout
xvzc Jan 17, 2026
fae0abf
feat: add support for socks5, tun modes
xvzc Mar 18, 2026
b9d6b93
fix: unsafe casting for trace id
xvzc Mar 18, 2026
4793a14
style: reformat
xvzc Mar 18, 2026
c803a16
style: reformat
xvzc Mar 18, 2026
a657f7a
style: reformat
xvzc Mar 18, 2026
7af0e95
refactor: optimize cache with generics
xvzc Mar 18, 2026
5fc7fae
docs: update docs
xvzc Mar 18, 2026
934c519
refactor: rename network-config flag to auto-configure-network
xvzc Mar 18, 2026
ff8efc0
fix(socks5): close associated UDP connection on TCP disconnection
xvzc Mar 18, 2026
6dd4393
fix: validate source address for UDP associate
xvzc Mar 18, 2026
22785a5
fix(packet): skip ethernet layer if MAC addresses are missing
xvzc Mar 19, 2026
e0c1bdb
fix: ensure graceful cleanup using global context
xvzc Mar 19, 2026
4533f68
chore: remove unnecessary file
xvzc Mar 19, 2026
60affa6
refactor(tun): resolve gateway and interface prior to server startup
xvzc Mar 19, 2026
bcb42ab
fix(cache): trigger onInvalidate on duplicate key updates
xvzc Mar 19, 2026
c5b627d
refactor(socks5): improve variable naming and logging
xvzc Mar 19, 2026
d7a6a63
test: remove unused test files
xvzc Mar 19, 2026
0f9799f
refactor(netutil): rename SessionCache to ConnRegistry
xvzc Mar 19, 2026
25306ab
fix(tcp_writer): also check dstMAC to determine ethernet layer inclusion
xvzc Mar 21, 2026
804063c
refactor: rename lAddr to lUDPAddr
xvzc Mar 21, 2026
34928d0
refactor: rename network config cleanup function to unset
xvzc Mar 21, 2026
b71e149
style: remove trailing whitespace in network_darwin
xvzc Mar 21, 2026
c892f27
fix(socks5): send success response after upstream dial
xvzc Mar 21, 2026
9242e82
fix(netutil): fix goroutine leak by returning and closing http.Server…
xvzc Mar 21, 2026
5b59a8d
refactor(socks5): extract inbound udp relay logic into a method
xvzc Mar 21, 2026
73d362c
style(socks5): make relayInboundUDP inline
xvzc Mar 21, 2026
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
348 changes: 240 additions & 108 deletions cmd/spoofdpi/main.go

Large diffs are not rendered by default.

91 changes: 54 additions & 37 deletions cmd/spoofdpi/main_test.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
package main

import (
"context"
"net"
"testing"
"time"

"github.com/rs/zerolog"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/xvzc/SpoofDPI/internal/config"
"github.com/xvzc/SpoofDPI/internal/proto"
"github.com/xvzc/SpoofDPI/internal/ptr"
)

func TestCreateResolver(t *testing.T) {
cfg := config.NewConfig()
cfg.DNS = &config.DNSOptions{
Mode: ptr.FromValue(config.DNSModeUDP),
Mode: lo.ToPtr(config.DNSModeUDP),
Addr: &net.TCPAddr{IP: net.ParseIP("8.8.8.8"), Port: 53},
HTTPSURL: ptr.FromValue("https://dns.google/dns-query"),
QType: ptr.FromValue(config.DNSQueryIPv4),
Cache: ptr.FromValue(true),
HTTPSURL: lo.ToPtr("https://dns.google/dns-query"),
QType: lo.ToPtr(config.DNSQueryIPv4),
Cache: lo.ToPtr(true),
}
cfg.Conn = &config.ConnOptions{
DNSTimeout: lo.ToPtr(time.Duration(0)),
TCPTimeout: lo.ToPtr(time.Duration(0)),
UDPIdleTimeout: lo.ToPtr(time.Duration(0)),
}

logger := zerolog.Nop()
Expand All @@ -30,95 +36,106 @@ func TestCreateResolver(t *testing.T) {
}

func TestCreateProxy_NoPcap(t *testing.T) {
// Setup configuration that doesn't require PCAP (root privileges)
// Setup configuration that dAppModeHTTP PCAP (root privileges)
cfg := config.NewConfig()

// Server Config
cfg.Server = &config.ServerOptions{
// App Config
cfg.App = &config.AppOptions{
Mode: lo.ToPtr(config.AppModeHTTP),
ListenAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0},
DefaultTTL: ptr.FromValue(uint8(64)),
Timeout: ptr.FromValue(time.Duration(1 * time.Second)),
}

// Conn Config
cfg.Conn = &config.ConnOptions{
DefaultFakeTTL: lo.ToPtr(uint8(64)),
DNSTimeout: lo.ToPtr(time.Duration(0)),
TCPTimeout: lo.ToPtr(time.Duration(0)),
UDPIdleTimeout: lo.ToPtr(time.Duration(0)),
}

// HTTPS Config (Ensure FakeCount is 0 to disable PCAP)
cfg.HTTPS = &config.HTTPSOptions{
Disorder: ptr.FromValue(false),
FakeCount: ptr.FromValue(uint8(0)),
Disorder: lo.ToPtr(false),
FakeCount: lo.ToPtr(uint8(0)),
FakePacket: proto.NewFakeTLSMessage([]byte{}),
SplitMode: ptr.FromValue(config.HTTPSSplitModeChunk),
ChunkSize: ptr.FromValue(uint8(10)),
Skip: ptr.FromValue(false),
SplitMode: lo.ToPtr(config.HTTPSSplitModeChunk),
ChunkSize: lo.ToPtr(uint8(10)),
Skip: lo.ToPtr(false),
}

// Policy Config
cfg.Policy = &config.PolicyOptions{
Auto: ptr.FromValue(false),
}
cfg.Policy = &config.PolicyOptions{}

// DNS Config
cfg.DNS = &config.DNSOptions{
Mode: ptr.FromValue(config.DNSModeUDP),
Mode: lo.ToPtr(config.DNSModeUDP),
Addr: &net.TCPAddr{IP: net.ParseIP("8.8.8.8"), Port: 53},
HTTPSURL: ptr.FromValue("https://dns.google/dns-query"),
QType: ptr.FromValue(config.DNSQueryIPv4),
Cache: ptr.FromValue(false),
HTTPSURL: lo.ToPtr("https://dns.google/dns-query"),
QType: lo.ToPtr(config.DNSQueryIPv4),
Cache: lo.ToPtr(false),
}

logger := zerolog.Nop()
resolver := createResolver(logger, cfg)

p, err := createProxy(logger, cfg, resolver)
p, err := createServer(context.Background(), logger, cfg, resolver)
require.NoError(t, err)
assert.NotNil(t, p)
}

func TestCreateProxy_WithPolicy(t *testing.T) {
cfg := config.NewConfig()

// Server Config
cfg.Server = &config.ServerOptions{
// App Config
cfg.App = &config.AppOptions{
Mode: lo.ToPtr(config.AppModeHTTP),
ListenAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0},
DefaultTTL: ptr.FromValue(uint8(64)),
Timeout: ptr.FromValue(time.Duration(0)),
}

// Conn Config
cfg.Conn = &config.ConnOptions{
DefaultFakeTTL: lo.ToPtr(uint8(64)),
DNSTimeout: lo.ToPtr(time.Duration(0)),
TCPTimeout: lo.ToPtr(time.Duration(0)),
UDPIdleTimeout: lo.ToPtr(time.Duration(0)),
}

// HTTPS Config
cfg.HTTPS = &config.HTTPSOptions{
FakeCount: ptr.FromValue(uint8(0)),
FakeCount: lo.ToPtr(uint8(0)),
}

// Policy Config with one override
cfg.Policy = &config.PolicyOptions{
Auto: ptr.FromValue(false),
Overrides: []config.Rule{
{
Name: ptr.FromValue("test-rule"),
Name: lo.ToPtr("test-rule"),
Match: &config.MatchAttrs{
Domains: []string{"example.com"},
},
DNS: &config.DNSOptions{
Mode: ptr.FromValue(config.DNSModeSystem),
Mode: lo.ToPtr(config.DNSModeSystem),
},
HTTPS: &config.HTTPSOptions{
Skip: ptr.FromValue(true),
Skip: lo.ToPtr(true),
},
},
},
}

// DNS Config
cfg.DNS = &config.DNSOptions{
Mode: ptr.FromValue(config.DNSModeUDP),
Mode: lo.ToPtr(config.DNSModeUDP),
Addr: &net.TCPAddr{IP: net.ParseIP("8.8.8.8"), Port: 53},
HTTPSURL: ptr.FromValue("https://dns.google/dns-query"),
QType: ptr.FromValue(config.DNSQueryIPv4),
Cache: ptr.FromValue(false),
HTTPSURL: lo.ToPtr("https://dns.google/dns-query"),
QType: lo.ToPtr(config.DNSQueryIPv4),
Cache: lo.ToPtr(false),
}

logger := zerolog.Nop()
resolver := createResolver(logger, cfg)

p, err := createProxy(logger, cfg, resolver)
p, err := createServer(context.Background(), logger, cfg, resolver)
require.NoError(t, err)
assert.NotNil(t, p)
}
70 changes: 62 additions & 8 deletions docs/user-guide/general.md → docs/user-guide/app.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,60 @@
# General Configuration
# App Configuration

General settings for the application, including logging and system integration.
Application-level settings including mode, logging, and system integration.

## `app-mode`

`type: string`

### Description

Specifies the proxy mode. `(default: "http")`

### Allowed Values

- `http`: HTTP proxy mode
- `socks5`: SOCKS5 proxy mode
- `tun`: TUN interface mode (transparent proxy)

### Usage

**Command-Line Flag**
```console
$ spoofdpi --app-mode socks5
```

**TOML Config**
```toml
[app]
mode = "socks5"
```

---

## `listen-addr`

`type: <ip:port>`

### Description

Specifies the IP address and port to listen on. `(default: 127.0.0.1:8080 for http, 127.0.0.1:1080 for socks5)`

If you want to run SpoofDPI remotely (e.g., on a physically separated machine), set the IP part to `0.0.0.0`. Otherwise, it is recommended to leave this option as default for security.

### Usage

**Command-Line Flag**
```console
$ spoofdpi --listen-addr "0.0.0.0:8080"
```

**TOML Config**
```toml
[app]
listen-addr = "0.0.0.0:8080"
```

---

## `log-level`

Expand All @@ -21,7 +75,7 @@ $ spoofdpi --log-level trace

**TOML Config**
```toml
[general]
[app]
log-level = "trace"
```

Expand All @@ -44,13 +98,13 @@ $ spoofdpi --silent

**TOML Config**
```toml
[general]
[app]
silent = true
```

---

## `system-proxy`
## `auto-configure-network`

`type: boolean`

Expand All @@ -65,13 +119,13 @@ Specifies whether to automatically set up the system-wide proxy configuration. `

**Command-Line Flag**
```console
$ spoofdpi --system-proxy
$ spoofdpi --auto-configure-network
```

**TOML Config**
```toml
[general]
system-proxy = true
[app]
auto-configure-network = true
```

---
Expand Down
106 changes: 106 additions & 0 deletions docs/user-guide/connection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Connection Configuration

Settings for network connection timeouts and packet configuration.

## `dns-timeout`

`type: uint16`

### Description

Specifies the timeout (in milliseconds) for DNS connections. `(default: 5000, max: 65535)`

A value of `0` means no timeout.

### Usage

**Command-Line Flag**
```console
$ spoofdpi --dns-timeout 3000
```

**TOML Config**
```toml
[connection]
dns-timeout = 3000
```

---

## `tcp-timeout`

`type: uint16`

### Description

Specifies the timeout (in milliseconds) for TCP connections. `(default: 10000, max: 65535)`

A value of `0` means no timeout.

### Usage

**Command-Line Flag**
```console
$ spoofdpi --tcp-timeout 5000
```

**TOML Config**
```toml
[connection]
tcp-timeout = 5000
```

---

## `udp-idle-timeout`

`type: uint16`

### Description

Specifies the idle timeout (in milliseconds) for UDP connections. `(default: 25000, max: 65535)`

The connection will be closed if there is no read/write activity for this duration. Each read or write operation resets the timeout.

A value of `0` means no timeout.

### Usage

**Command-Line Flag**
```console
$ spoofdpi --udp-idle-timeout 30000
```

**TOML Config**
```toml
[connection]
udp-idle-timeout = 30000
```

---

## `default-fake-ttl`

`type: uint8`

### Description

Specifies the default [Time To Live (TTL)](https://en.wikipedia.org/wiki/Time_to_live) value for fake packets. `(default: 8)`

This value is used for fake packets sent during disorder strategies. A lower value ensures fake packets expire before reaching the destination, while the real packets arrive successfully.

!!! note
The fake TTL should be less than the number of hops to the destination.

### Usage

**Command-Line Flag**
```console
$ spoofdpi --default-fake-ttl 10
```

**TOML Config**
```toml
[connection]
default-fake-ttl = 10
```
Loading
Loading