# 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.
