# 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 ## 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 DHCP client starts reading tunnel frames **before** sending DISCOVER, so responses are not missed. Non-DHCP frames received during the exchange are dropped (the TAP bridge is not yet active). ### 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. ## Reconnection On disconnect (TCP error, server timeout, etc.), the client: 1. Flushes IP addresses from the TAP interface 2. Restores `/etc/resolv.conf` if DNS was modified 3. Waits `-reconnect-delay` (default 5s) 4. Establishes a new TLS connection and repeats the full handshake 5. Runs a fresh DHCP exchange through the new tunnel 6. Reconfigures the TAP interface with the new lease The TAP device itself persists across reconnections — only its IP configuration is reset. The host route to the VPN server also persists. ## 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. ## 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.