consolidate docs into README.md, remove docs/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Git Sagar 2026-06-06 17:54:38 +05:30
parent 42f8333783
commit 61237283f5
6 changed files with 248 additions and 454 deletions

264
README.md
View file

@ -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

View file

@ -1,149 +0,0 @@
# Architecture
## Connection flow
The SoftEther protocol layers HTTP over TLS, then switches to raw TCP framing for the data tunnel.
```
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
│ │
```
### Step details
1. **TLS connect** — standard TLS to the server port (typically 443 or 992). The connection looks like normal HTTPS traffic to network observers.
2. **Upload signature** — the client sends `VPNCONNECT` as the body of an HTTP POST to `/vpnsvc/connect.cgi`. The server validates this to confirm the client speaks the SoftEther protocol.
3. **Download Hello** — the server responds with a binary Pack containing its version info and a 20-byte random challenge used for password hashing.
4. **Upload Auth** — the client sends credentials (hashed or plaintext password), client version, connection options, and node info as a Pack via HTTP POST to `/vpnsvc/vpn.cgi`.
5. **Download Welcome** — the server responds with session info (session name, connection name, session key, policies) on success, or an error code on failure.
6. **Tunnel mode** — the connection switches from HTTP to raw TCP block framing. Each message is `uint32(numBlocks) + [uint32(size) + data]...`. Keepalive packets use `0xFFFFFFFF` as the block count, followed by random padding.
## Pack format
SoftEther uses a custom binary serialization called "Pack" for all structured data exchange. A Pack contains named Elements, each holding typed Values (int, string, data, ip4).
Key details:
- Element names are **case-insensitive** (compared with `StrCmpi`)
- `AddElement` rejects duplicate names — the second add silently fails
- String values use BufStr encoding: `uint32(strlen+1)` followed by `strlen` bytes (no null terminator on wire)
- HTTP-transported Packs include a `pencore` dummy element with random padding
## Session lifecycle
After connecting, a session proceeds as:
1. **Start bridge** — bidirectional frame forwarding between the tunnel and TAP device begins immediately in the background
2. **DHCP exchange** — if enabled, DHCP frames are sent through the tunnel while the bridge is already running. The DHCP client intercepts server frames via a callback (`FeedFrame`) before they reach the TAP device
3. **Configure TAP** — IP address, routes, DNS, and policy routing are applied from the DHCP lease
4. **Wait** — the session blocks until the bridge errors (server disconnect, network failure) or a signal arrives
5. **Cleanup** — TAP addresses are flushed, DNS is restored, policy routes are removed
On disconnect, the reconnect loop waits and starts a new session with a fresh handshake and DHCP exchange.
## DHCP through the tunnel
SoftEther operates at Layer 2 — the tunnel carries raw Ethernet frames. The built-in DHCP client constructs complete Ethernet/IP/UDP/DHCP frames and sends them through the tunnel's frame transport.
```
DHCP Client VPN Tunnel DHCP Server (on VPN)
│ │ │
├── DISCOVER ──► WriteFrames ├────────────────────────────►│
│ │ │
│◄── FeedFrame ◄─ ReadFrames ◄──────── OFFER ─────────────┤
│ │ │
├── REQUEST ───► WriteFrames ├────────────────────────────►│
│ │ │
│◄── FeedFrame ◄─ ReadFrames ◄──────── ACK ───────────────┤
│ │ │
```
The frame bridge runs concurrently with the DHCP exchange. All server frames pass through the `FeedFrame` callback, which identifies DHCP responses by transaction ID. Non-DHCP frames are written to the TAP device as normal (though the TAP has no IP yet, so the OS drops them).
### Why raw frames?
The VPN tunnel transports Ethernet frames, and the DHCP exchange must happen *inside* the tunnel before the TAP interface has an IP address. Constructing frames directly avoids any external dependency (`dhcpcd`, `udhcpc`) and keeps the client self-contained.
### DHCP options requested
- Option 1: Subnet Mask
- Option 3: Router (gateway)
- Option 6: DNS Servers
- Option 51: Lease Time
- Option 121: Classless Static Routes (RFC 3442)
- Option 249: Microsoft Classless Static Routes
### Why DHCP is required on reconnect
SoftEther servers with `DHCPForce` policy discard any packet whose source IP is not in the server's IP table with `DhcpAllocated=true`. When a session disconnects, the server calls `HubPaFree` which deletes **all** MAC and IP table entries for that session. The new session has no entries, so all traffic is dropped until a fresh DHCP exchange creates a new `DhcpAllocated=true` entry.
## Routing
### Server host route
Before the first connection, the client resolves the server hostname and adds a `/32` host route via the current default gateway:
```
50.117.55.1/32 via 172.17.0.1 dev eth0
```
This ensures the VPN tunnel traffic itself always uses the original path, even after `-accept-default-gateway` installs a new default route via the VPN.
### Default gateway
With `-accept-default-gateway`, the DHCP-provided gateway is installed as a default route with metric 50:
```
default via 10.100.8.1 dev vpn0 metric 50
```
The metric ensures it takes precedence over any existing default route with a higher metric, while the server host route (no metric, most specific) keeps the tunnel traffic on the original path.
### Static routes
With `-accept-static-routes`, classless static routes from DHCP option 121 or 249 are installed. A `0.0.0.0/0` entry in static routes is treated as a default gateway and only installed if `-accept-default-gateway` is also set. Per RFC 3442, option 121 takes precedence over option 3 (Router) when present.
### Policy routing
With `-policy-route-table N`, the client sets up policy routing for asymmetric return paths:
```
ip route replace default via <VPN_GW> dev <TAP> table N
ip rule add from <VPN_IP> table N
```
This is needed when the VPN server has port forwards to the client. Without policy routing, inbound traffic arrives via the VPN tunnel but reply packets use the default route (home router) instead of going back through the tunnel. The remote host sees replies from a different IP and drops them.
The policy route is cleaned up on disconnect and re-applied with each new DHCP lease (since the VPN IP may change).
## Keepalive
The client sends keepalive packets every 3 seconds. A keepalive is `uint32(0xFFFFFFFF) + uint32(randSize) + randData`. The server sends keepalives in the same format. Keepalive packets are silently consumed and never forwarded to the TAP device.
## Password hashing
SoftEther uses **SHA-0** (not SHA-1) for password hashing. SHA-0 differs from SHA-1 only in the message schedule — there is no left-rotate of the W[t] values.
For hashed password auth (AuthType 1):
```
HashedPassword = SHA0(password)
SecurePassword = SHA0(HashedPassword + ServerRandom)
```
For plaintext auth (AuthType 2), the password is sent as-is over the TLS connection.

View file

@ -1,54 +0,0 @@
# Building
## Go
Requires Go 1.24 or later.
```bash
go build -o softether-go ./cmd/softether-go/
```
### Static binary
For deployment in minimal containers (Alpine, scratch) or systems without glibc:
```bash
CGO_ENABLED=0 go build -o softether-go ./cmd/softether-go/
```
### Cross-compilation
```bash
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o softether-go ./cmd/softether-go/
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o softether-go ./cmd/softether-go/
```
Only Linux targets are supported (the TAP device interface uses Linux-specific ioctls).
## Nix
Build with the flake:
```bash
nix build
```
The result is in `./result/bin/softether-go`.
Enter a development shell with Go tooling:
```bash
nix develop
```
Build within the dev shell:
```bash
nix develop -c bash -c 'go build -o softether-go ./cmd/softether-go/'
```
## Dependencies
The only Go dependency is `golang.org/x/sys` for Linux syscall constants (TUN/TAP ioctls). Dependencies are vendored in `vendor/`.
At runtime, the only external dependency is `ip` (iproute2) for route and address management.

View file

@ -1,29 +0,0 @@
# softether-go documentation
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.
## 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
- Single static binary, Linux only
## Contents
- [Usage & CLI reference](usage.md) — flags, examples, Docker usage
- [Architecture](architecture.md) — connection flow, DHCP, reconnection, routing
- [Building](building.md) — Go, Nix, static builds
- [Project structure](structure.md) — source layout and package descriptions
## Requirements
- Linux (uses `/dev/net/tun` for TAP devices)
- `CAP_NET_ADMIN` or root (TAP device creation, route management)
- `ip` command (iproute2) on `$PATH`

View file

@ -1,63 +0,0 @@
# Project structure
```
softether-go/
├── 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
│ ├── protocol/
│ │ ├── http.go TLS connection, HTTP transport layer
│ │ └── pack.go Pack binary serialization
│ ├── dhcp/
│ │ └── dhcp.go DHCP client (raw Ethernet frames)
│ ├── netcfg/
│ │ └── netcfg.go TAP configuration, routing, DNS management
│ └── tap/
│ └── tap.go Linux TAP device management
├── docs/ Documentation
├── vendor/ Vendored Go dependencies
├── flake.nix Nix build definition
├── go.mod
└── go.sum
```
## Package details
### `cmd/softether-go`
CLI entry point, split into two files:
**`main.go`** — flag parsing, TAP device creation, MAC configuration, signal handling, and the reconnect loop. Calls `runSession` for each connection attempt.
**`session.go`** — one VPN session lifecycle: connect to server, start bridge, run DHCP, configure TAP (IP/routes/DNS/policy routing), and wait for disconnect or signal. Also contains `runDHCP` which orchestrates the DHCP exchange through the tunnel.
### `pkg/client`
**`client.go`** — implements the SoftEther handshake: TLS connect, signature upload, hello/auth/welcome pack exchange. Exports `Connect(Config) (*Session, error)` and the `Config`/`Session` types.
**`tunnel.go`** — TCP block framing after the HTTP handshake completes. `ReadFrames()` reads batches of Ethernet frames from the server. `WriteFrames()` sends batches. `Bridge()` runs bidirectional frame forwarding between the tunnel and a TAP device, with an optional `FrameHandler` callback for intercepting frames (used by DHCP). `StartKeepalive()` sends periodic keepalive packets (every 3s).
**`crypto.go`** — SHA-0 implementation (differs from SHA-1 only in the message schedule — no left-rotate). `HashPassword()` produces `SHA0(password)`. `SecurePassword()` produces `SHA0(hashed + serverRandom)`.
### `pkg/protocol`
**`http.go`** — HTTP transport layer. `DialTLS()` establishes the TLS connection. `UploadSignature()` sends the protocol signature. `SendPack()` and `RecvPack()` exchange binary Packs as HTTP POST request/response bodies.
**`pack.go`** — SoftEther Pack binary serialization. A Pack is a list of named Elements, each containing typed Values (int, string, data, ip4). Handles the BufStr wire format (`uint32(strlen+1)` then `strlen` bytes) and the `pencore` random padding element.
### `pkg/dhcp`
**`dhcp.go`** — DHCP client that constructs complete Ethernet/IP/UDP/DHCP frames. The full DHCP exchange (DISCOVER → OFFER → REQUEST → ACK) runs through the VPN tunnel's frame transport. Parses lease information including classless static routes (option 121/249, RFC 3442).
### `pkg/netcfg`
**`netcfg.go`** — network configuration for the VPN tunnel. `ConfigureTAP()` sets IP address, routes, and DNS on the TAP interface from a DHCP lease. `ConfigurePolicyRoute()` sets up policy routing for asymmetric return paths. `AddServerRoute()` adds a host route to the VPN server via the current default gateway. `ResolveHost()` resolves hostnames to IPv4.
### `pkg/tap`
**`tap.go`** — Linux TAP (Layer 2) device management via `/dev/net/tun`. Opens TAP devices with `IFF_TAP | IFF_NO_PI`, reads/writes raw Ethernet frames. Provides `MAC()` and `SetMAC()` for hardware address management, and `SetUp()` to bring the interface up.

View file

@ -1,143 +0,0 @@
# 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
Two authentication modes are supported:
**Hashed password (AuthType 1)** — the default. Password is hashed with SHA-0 and combined with the server's random challenge to produce a `SecurePassword`. Used for local user accounts on the SoftEther server.
```bash
softether-go -host vpn.example.com -user admin -pass secret
```
**Plaintext password (AuthType 2)** — enabled with `-plain-password`. Password is sent as-is over TLS. Used when the server delegates authentication to an external system like RADIUS.
```bash
softether-go -host vpn.example.com -user admin -pass secret -plain-password
```
## Network configuration flags
These flags control what the client does with the DHCP lease it receives from the VPN server.
### `-mac`
Sets a specific MAC address on the TAP interface before connecting. Useful for deterministic DHCP assignments — the server sees the same MAC across reconnects and can assign the same IP.
```bash
softether-go -host vpn.example.com -user admin -mac 5E:3B:6F:63:A8:3E
```
### `-no-dhcp`
Disables the built-in DHCP client. Use this if the TAP interface will be configured manually or by an external DHCP client.
### `-accept-default-gateway`
Adds a default route via the DHCP-provided gateway on the TAP interface with metric 50. Before doing this, the client adds a `/32` host route to the VPN server via the current default gateway so the tunnel itself is not routed through the VPN.
Without this flag, only the subnet route (implicit from the assigned IP/mask) is added.
### `-accept-static-routes`
Installs classless static routes from DHCP option 121 (RFC 3442) or option 249 (Microsoft variant). These are non-default routes pushed by the DHCP server, such as routes to specific subnets via the VPN gateway.
If a static route entry has destination `0.0.0.0/0` (default route), it is only installed when `-accept-default-gateway` is also set. Per RFC 3442, when option 121 is present it takes precedence over option 3 (Router).
### `-accept-dns`
Overwrites `/etc/resolv.conf` with the DNS servers from the DHCP lease. The original file is backed up in memory and restored when the session ends (disconnect, reconnect, or shutdown).
### `-policy-route-table`
Enables policy routing for asymmetric return paths. Set to a routing table number (e.g. `200`). When enabled, the client adds:
```
ip rule add from <VPN_IP> table 200
ip route replace default via <VPN_GW> dev <TAP> table 200
```
This ensures reply packets from the VPN IP are routed back through the VPN tunnel, not the default route. Needed when the VPN server forwards ports to the client — without it, reply packets leave via the home router and get dropped.
Cleaned up on disconnect and shutdown.
## Examples
Minimal connection:
```bash
softether-go -host vpn.example.com -user admin -pass secret
```
Full setup with routing, DNS, and policy routing:
```bash
softether-go \
-host vpn.example.com \
-port 992 \
-hub DEFAULT \
-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 configuration):
```bash
softether-go -host vpn.example.com -user admin -pass secret -no-dhcp -tap vpn0
```
## Docker
The client works in containers with `NET_ADMIN` capability and the TUN device:
```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
```
The container needs `iproute2` installed (`apk add iproute2` on Alpine) for the `ip` command.
## Signals
- **SIGINT / SIGTERM** — clean shutdown: closes tunnel, flushes TAP addresses, restores DNS, removes server host route, cleans up policy routes
- During reconnect delay, a signal triggers immediate shutdown instead of waiting