# openvpngate -- sample configuration # # Every key below is optional and shown at its default unless noted. Durations # take a unit suffix ("250ms", "30s", "5m", "2h"); a bare number means seconds. # Comments run from '#' to end of line. # # openvpngate -c etc/openvpngate.conf --check validate and print, no start # openvpngate -c etc/openvpngate.conf run # # Nothing here needs root. The tunnel is terminated in userspace (lwIP), so # there is no tun device, no routing table change, and no capability to grant. # --------------------------------------------------------------------------- [socks5] # --------------------------------------------------------------------------- listen_address = 127.0.0.1 listen_port = 1080 # Leave this on. With it off the proxy is an open relay to anyone who can reach # the listen address. require_auth = true # Credentials. Two ways, and they merge: # * the [users] section at the bottom of this file (plaintext, dev only) # * auth_file, which also accepts pre-hashed lines and is reloaded on SIGHUP # auth_file = etc/socks5.auth # SOCKS5 UDP ASSOCIATE. Supported, with the caveats in docs/FEASIBILITY.md §5: # fragmented datagrams (FRAG != 0) are dropped and counted, never reassembled. udp_associate = true # The address handed back in the UDP ASSOCIATE reply. It has to be reachable # *by the client*, which is why it cannot be derived from listen_address when # that is 0.0.0.0. Empty = use the control connection's local address. # advertise_address = # Admission control. Refused at accept, before a session object exists: this is # the only thing standing between a burst and lwIP's allocator. max_sessions = 1200 handshake_timeout = 10s connect_timeout = 20s idle_timeout = 5m udp_idle_timeout = 60s relay_buffer_size = 16384 # 0 = min(hardware_concurrency, 4). More than four buys little: every tunnelled # byte passes through lwIP's single strand regardless. io_threads = 0 # --------------------------------------------------------------------------- [vpngate] # --------------------------------------------------------------------------- # The public node directory. Lines in this CSV can be ~10 KB each (the whole # .ovpn profile is base64 in the last column), which the parser handles by # streaming rather than by splitting the response into lines. api_urls = http://www.vpngate.net/api/iphone/ refresh_interval = 30m http_timeout = 30s # A stale cache still beats no nodes at all when the API is down. cache_path = var/vpngate_cache.csv cache_max_age = 6h max_response_bytes = 33554432 # --------------------------------------------------------------------------- [selector] # --------------------------------------------------------------------------- # Two-phase: rank everything cheaply from the API metrics, then actively probe # only the survivors. VPNGate's own numbers are measured from their # infrastructure, not from yours, so they are a prior and nothing more. # country_allow = JP, KR, SG # country_deny = RU prefer_udp = true probe_candidates = 12 probe_samples = 3 probe_timeout = 3s probe_concurrency = 8 # Prior weights (API-derived). w_score = 0.35 w_speed = 0.30 w_sessions = 0.20 w_uptime = 0.15 # Final blend. Our own measured RTT outweighs anything the directory claims. w_rtt = 0.45 w_prior = 0.30 w_history = 0.25 # Per-node outcome history. This is what stops the selector walking into the # same broken node every time it appears with a flattering score. history_path = var/node_history.tsv failure_backoff_initial = 60s failure_backoff_max = 1h # --------------------------------------------------------------------------- [switch] # --------------------------------------------------------------------------- # graceful = make-before-break (new tunnel is fully up before the old one is # replaced); hard = promote and close everything at once. mode = graceful # How long a replaced egress keeps serving the sessions still on it. drain_grace = 2m max_draining = 2 # Anti-flap: no switch within this window of the last one, and a candidate must # beat the incumbent by improvement_margin to be worth the disruption. min_interval = 60s improvement_margin = 0.20 backoff_initial = 30s backoff_max = 8m # What survives a switch. A session that has moved no bytes carries no TCP # state, so it is re-dialled on the new egress transparently; a UDP association # has no sequence state at all, so only its egress-side socket is replaced and # the client never sees the port change. Everything else drains and is closed # when the grace window expires -- see docs/ARCHITECTURE.md §5.3. retry_zero_progress = true rehome_udp = true # Go looking for a *better* node while the current one is healthy. Off by # default: a scan probes a dozen volunteer-run servers and a switch costs every # session that has moved bytes. Degradation-driven switching covers the # requirement; this is the optional upgrade path. opportunistic_interval = 0 # --------------------------------------------------------------------------- [health] # --------------------------------------------------------------------------- interval = 15s # Consecutive bad windows before a switch is requested. One bad sample on a # volunteer tunnel in another country is weather, not a failure. unhealthy_windows = 3 # The probe dials this through the egress and drops the stream immediately. A # TCP handshake rather than a DNS lookup on purpose: a lookup can be answered # from cache without a byte crossing the tunnel, which would report a dead # tunnel as the healthiest node in the fleet. probe_domain = www.google.com probe_port = 80 probe_timeout = 5s min_score = 0.40 max_connect_failure_rate = 0.50 stall_threshold = 45s # --------------------------------------------------------------------------- [ovpn] # --------------------------------------------------------------------------- # VPNGate nodes are overwhelmingly AES-128-CBC + SHA1. Turning this off is # principled and will fail to connect to most of the directory. allow_legacy_algorithms = true # Most VPNGate nodes ignore credentials; the ones that ask want "vpn"/"vpn". username = vpn password = vpn connect_timeout = 30 tunnel_up_timeout = 45 compression = true # Socket buffers for the tun-side socketpair. Too small drops IP packets under # burst -- recoverable, but it costs throughput. packet_socket_buffer = 2097152 # --------------------------------------------------------------------------- [dns] # --------------------------------------------------------------------------- # Used inside the tunnel when the server pushes no resolver of its own. Must be # literals: resolving a resolver needs a resolver. fallback_servers = 1.1.1.1, 8.8.8.8 timeout = 5s cache_entries = 4096 min_ttl = 5s max_ttl = 1h prefer_ipv4 = true # --------------------------------------------------------------------------- [admin] # --------------------------------------------------------------------------- # GET /status /nodes /sessions /health /metrics /healthz, POST /switch. # There is NO authentication here. Keep it on loopback. enabled = true listen_address = 127.0.0.1 listen_port = 9080 # --------------------------------------------------------------------------- [log] # --------------------------------------------------------------------------- level = info file = - # "-" is stderr [log.modules] # Per-module overrides; the tag is the one in square brackets in each log line. # socks5 = debug # netstack = warn # health = debug # --------------------------------------------------------------------------- [egress] # --------------------------------------------------------------------------- # Which way out to build: # # tunnel OpenVPN + lwIP -- the real thing # direct host sockets, NO VPN -- for testing the proxy in isolation. A build # configured with -DOVG_WITH_TUNNEL=OFF refuses to start in any other # mode rather than silently proxying in the clear. mode = tunnel # --------------------------------------------------------------------------- [users] # --------------------------------------------------------------------------- # user = password, hashed with a random salt at load time. Fine for a laptop; # for anything shared use socks5.auth_file, which takes pre-hashed lines and # reloads on SIGHUP without dropping a single live session. alice = changeme