consolidate docs into README.md, remove docs/
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
42f8333783
commit
61237283f5
6 changed files with 248 additions and 454 deletions
264
README.md
264
README.md
|
|
@ -2,29 +2,261 @@
|
|||
|
||||
Standalone SoftEther VPN client written in Go. Connects to SoftEther VPN servers using the native protocol over TLS, with built-in DHCP, automatic reconnection, and route management. Single static binary, Linux only, zero runtime dependencies beyond `ip` (iproute2).
|
||||
|
||||
## Quick start
|
||||
## Features
|
||||
|
||||
- Native SoftEther protocol (TLS + HTTP handshake + TCP block framing)
|
||||
- Built-in DHCP client (raw Ethernet frame construction through the VPN tunnel)
|
||||
- Automatic reconnection with fresh DHCP on each reconnect
|
||||
- Host route to VPN server via existing default gateway (prevents routing loops)
|
||||
- Classless static routes (DHCP option 121/249, RFC 3442)
|
||||
- Policy routing for asymmetric return paths (VPN port forwards)
|
||||
- DNS configuration from DHCP lease (backup/restore of `/etc/resolv.conf`)
|
||||
- Deterministic MAC address support for stable DHCP assignments
|
||||
- Hashed password (SHA-0) and plaintext password (RADIUS/external) authentication
|
||||
- ~4.6 MB RAM under load, single process
|
||||
|
||||
## Requirements
|
||||
|
||||
- Linux (uses `/dev/net/tun` for TAP devices)
|
||||
- `CAP_NET_ADMIN` or root (TAP device creation, route management)
|
||||
- `ip` command (iproute2) on `$PATH`
|
||||
|
||||
## Building
|
||||
|
||||
Requires Go 1.24 or later.
|
||||
|
||||
```bash
|
||||
# Build
|
||||
go build -o softether-go ./cmd/softether-go/
|
||||
|
||||
# Connect (minimal)
|
||||
softether-go -host vpn.example.com -user admin -pass secret -plain-password
|
||||
|
||||
# Connect with full network setup
|
||||
softether-go -host vpn.example.com -user admin -pass secret \
|
||||
-plain-password -tap vpn0 -mac 5E:3B:6F:63:A8:3E \
|
||||
-accept-default-gateway -accept-dns -policy-route-table 200
|
||||
```
|
||||
|
||||
## Documentation
|
||||
Static binary (for Alpine, scratch containers):
|
||||
|
||||
See [docs/main.md](docs/main.md) for full documentation:
|
||||
```bash
|
||||
CGO_ENABLED=0 go build -o softether-go ./cmd/softether-go/
|
||||
```
|
||||
|
||||
- [Usage & CLI reference](docs/usage.md)
|
||||
- [How it works](docs/architecture.md)
|
||||
- [Building](docs/building.md)
|
||||
- [Project structure](docs/structure.md)
|
||||
Cross-compilation:
|
||||
|
||||
```bash
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o softether-go ./cmd/softether-go/
|
||||
```
|
||||
|
||||
Nix:
|
||||
|
||||
```bash
|
||||
nix build # result in ./result/bin/softether-go
|
||||
nix develop # dev shell with Go tooling
|
||||
```
|
||||
|
||||
The only Go dependency is `golang.org/x/sys` (vendored in `vendor/`).
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
softether-go [flags]
|
||||
```
|
||||
|
||||
### Required flags
|
||||
|
||||
| Flag | Description |
|
||||
|---|---|
|
||||
| `-host` | SoftEther server hostname or IP |
|
||||
| `-user` | Authentication username |
|
||||
|
||||
### Optional flags
|
||||
|
||||
| Flag | Default | Description |
|
||||
|---|---|---|
|
||||
| `-pass` | `""` | Authentication password |
|
||||
| `-port` | `443` | Server port |
|
||||
| `-hub` | `DEFAULT` | Virtual hub name |
|
||||
| `-tap` | *(auto)* | TAP interface name (kernel-assigned if empty) |
|
||||
| `-mac` | *(auto)* | TAP interface MAC address (e.g. `5E:3B:6F:63:A8:3E`) |
|
||||
| `-plain-password` | `false` | Send password as plaintext (AuthType 2, for RADIUS/external auth) |
|
||||
| `-insecure` | `false` | Skip TLS certificate verification |
|
||||
| `-no-dhcp` | `false` | Disable built-in DHCP client |
|
||||
| `-accept-default-gateway` | `false` | Install DHCP-provided gateway as default route |
|
||||
| `-accept-static-routes` | `false` | Install DHCP classless static routes (option 121/249) |
|
||||
| `-accept-dns` | `false` | Set `/etc/resolv.conf` from DHCP-provided DNS servers |
|
||||
| `-policy-route-table` | `0` | Policy routing table number (0 = disabled) |
|
||||
| `-reconnect-delay` | `5s` | Delay between reconnection attempts |
|
||||
|
||||
### Authentication
|
||||
|
||||
**Hashed password (AuthType 1)** — the default. Password is hashed with SHA-0 and combined with the server's random challenge. Used for local user accounts.
|
||||
|
||||
```bash
|
||||
softether-go -host vpn.example.com -user admin -pass secret
|
||||
```
|
||||
|
||||
**Plaintext password (AuthType 2)** — enabled with `-plain-password`. Password sent as-is over TLS. Used for RADIUS/external auth.
|
||||
|
||||
```bash
|
||||
softether-go -host vpn.example.com -user admin -pass secret -plain-password
|
||||
```
|
||||
|
||||
### Network configuration
|
||||
|
||||
**`-mac`** — sets a specific MAC for deterministic DHCP assignments across reconnects.
|
||||
|
||||
**`-accept-default-gateway`** — installs DHCP gateway as default route (metric 50). A `/32` host route to the VPN server via the original gateway prevents routing loops.
|
||||
|
||||
**`-accept-static-routes`** — installs classless static routes from DHCP option 121/249. A `0.0.0.0/0` entry is only installed if `-accept-default-gateway` is also set.
|
||||
|
||||
**`-accept-dns`** — overwrites `/etc/resolv.conf` with DHCP DNS servers. Original is backed up and restored on disconnect.
|
||||
|
||||
**`-policy-route-table N`** — policy routing for asymmetric return paths. Adds `ip rule from <VPN_IP> table N` and `ip route replace default via <VPN_GW> dev <TAP> table N`. Needed when the VPN server forwards ports to the client.
|
||||
|
||||
### Examples
|
||||
|
||||
Minimal:
|
||||
```bash
|
||||
softether-go -host vpn.example.com -user admin -pass secret
|
||||
```
|
||||
|
||||
Full setup:
|
||||
```bash
|
||||
softether-go \
|
||||
-host vpn.example.com \
|
||||
-port 992 \
|
||||
-user admin \
|
||||
-pass secret \
|
||||
-plain-password \
|
||||
-tap vpn0 \
|
||||
-mac 5E:3B:6F:63:A8:3E \
|
||||
-insecure \
|
||||
-accept-default-gateway \
|
||||
-accept-static-routes \
|
||||
-accept-dns \
|
||||
-policy-route-table 200
|
||||
```
|
||||
|
||||
No DHCP (manual config):
|
||||
```bash
|
||||
softether-go -host vpn.example.com -user admin -pass secret -no-dhcp -tap vpn0
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
docker run --rm -it \
|
||||
--cap-add NET_ADMIN \
|
||||
--device /dev/net/tun \
|
||||
-v ./softether-go:/usr/bin/softether-go \
|
||||
alpine \
|
||||
softether-go -host vpn.example.com -user admin -pass secret \
|
||||
-plain-password -insecure -tap vpn0 \
|
||||
-accept-default-gateway -accept-dns
|
||||
```
|
||||
|
||||
Container needs `iproute2` (`apk add iproute2` on Alpine).
|
||||
|
||||
### Signals
|
||||
|
||||
- **SIGINT / SIGTERM** — clean shutdown: closes tunnel, flushes TAP, restores DNS, removes routes
|
||||
- During reconnect delay, signal triggers immediate shutdown
|
||||
|
||||
## Architecture
|
||||
|
||||
### Connection flow
|
||||
|
||||
```
|
||||
Client Server
|
||||
│ │
|
||||
├──── TLS handshake ───────────────────►│ Standard TLS, looks like HTTPS
|
||||
│ │
|
||||
├──── POST /vpnsvc/connect.cgi ────────►│ Upload signature ("VPNCONNECT")
|
||||
│ │
|
||||
│◄──── HTTP 200 (Pack: hello) ──────────┤ Server version + random challenge
|
||||
│ │
|
||||
├──── POST /vpnsvc/vpn.cgi ───────────►│ Upload auth (credentials + metadata)
|
||||
│ │
|
||||
│◄──── HTTP 200 (Pack: welcome) ────────┤ Session info (or error code)
|
||||
│ │
|
||||
│◄────── TCP block framing ────────────►│ Ethernet frames + keepalive
|
||||
│ │
|
||||
```
|
||||
|
||||
1. **TLS connect** — standard TLS to the server port (typically 443 or 992)
|
||||
2. **Upload signature** — `VPNCONNECT` via HTTP POST to `/vpnsvc/connect.cgi`
|
||||
3. **Download Hello** — server sends version info and 20-byte random challenge
|
||||
4. **Upload Auth** — credentials, client version, connection options, node info as a Pack
|
||||
5. **Download Welcome** — session info on success, error code on failure
|
||||
6. **Tunnel mode** — raw TCP block framing: `uint32(numBlocks) + [uint32(size) + data]...`. Keepalive: `uint32(0xFFFFFFFF) + uint32(randSize) + randData`
|
||||
|
||||
### Session lifecycle
|
||||
|
||||
1. **Start bridge** — bidirectional frame forwarding begins immediately
|
||||
2. **DHCP exchange** — DHCP frames sent through tunnel while bridge runs. DHCP client intercepts server frames via `FeedFrame` callback
|
||||
3. **Configure TAP** — IP, routes, DNS, policy routing applied from lease
|
||||
4. **Wait** — blocks until bridge errors or signal arrives
|
||||
5. **Cleanup** — flush addresses, restore DNS, remove policy routes
|
||||
6. **Reconnect** — wait, then repeat with fresh handshake and DHCP
|
||||
|
||||
### DHCP through the tunnel
|
||||
|
||||
SoftEther operates at Layer 2. The built-in DHCP client constructs complete Ethernet/IP/UDP/DHCP frames sent through the tunnel:
|
||||
|
||||
```
|
||||
DHCP Client VPN Tunnel DHCP Server (on VPN)
|
||||
│ │ │
|
||||
├── DISCOVER ──► WriteFrames ├────────────────────────────►│
|
||||
│ │ │
|
||||
│◄── FeedFrame ◄─ ReadFrames ◄──────── OFFER ─────────────┤
|
||||
│ │ │
|
||||
├── REQUEST ───► WriteFrames ├────────────────────────────►│
|
||||
│ │ │
|
||||
│◄── FeedFrame ◄─ ReadFrames ◄──────── ACK ───────────────┤
|
||||
│ │ │
|
||||
```
|
||||
|
||||
DHCP options requested: subnet mask (1), router (3), DNS (6), lease time (51), classless static routes (121/249).
|
||||
|
||||
**Why fresh DHCP on reconnect**: SoftEther's `DHCPForce` policy drops packets from IPs not in its `DhcpAllocated` table. On disconnect, the server clears all entries for the session.
|
||||
|
||||
### Routing
|
||||
|
||||
**Server host route** — `/32` route to VPN server via current default gateway, added before first connection. Prevents routing loop when VPN becomes default route.
|
||||
|
||||
**Default gateway** — DHCP gateway installed with metric 50, lower than existing default routes.
|
||||
|
||||
**Static routes** — classless routes from option 121/249. Default route entries (`0.0.0.0/0`) only installed with `-accept-default-gateway`.
|
||||
|
||||
**Policy routing** — `ip rule from <VPN_IP> table N` ensures reply packets for VPN port forwards go back through the tunnel, not the default route.
|
||||
|
||||
### Password hashing
|
||||
|
||||
SoftEther uses **SHA-0** (not SHA-1) — no left-rotate in message schedule. `HashedPassword = SHA0(password)`, `SecurePassword = SHA0(HashedPassword + ServerRandom)`. Plaintext auth (AuthType 2) sends password as-is over TLS.
|
||||
|
||||
### Keepalive
|
||||
|
||||
Sent every 3 seconds: `uint32(0xFFFFFFFF) + uint32(randSize) + randData`. Silently consumed, never forwarded to TAP.
|
||||
|
||||
## Project structure
|
||||
|
||||
```
|
||||
cmd/softether-go/
|
||||
main.go Flag parsing, TAP setup, reconnect loop
|
||||
session.go Session lifecycle, DHCP orchestration
|
||||
|
||||
pkg/client/
|
||||
client.go SoftEther handshake and session
|
||||
tunnel.go TCP block framing, keepalive, frame bridging
|
||||
crypto.go SHA-0 and password hashing
|
||||
|
||||
pkg/protocol/
|
||||
http.go TLS connection, HTTP transport layer
|
||||
pack.go Pack binary serialization
|
||||
|
||||
pkg/dhcp/
|
||||
dhcp.go DHCP client (raw Ethernet frames)
|
||||
|
||||
pkg/netcfg/
|
||||
netcfg.go TAP configuration, routing, DNS management
|
||||
|
||||
pkg/tap/
|
||||
tap.go Linux TAP device management
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue