Validation and Parsing¶
The script validates all user input before applying any configuration.
Input Validators¶
| Function | Validates | Rules |
|---|---|---|
validate_port() | Port number | Integer 1–65535 |
validate_mtu() | MTU size | Integer 576–65535 |
validate_subnet_ipv4() | IPv4 VPN subnet | Format x.x.x.0, must be RFC1918 (10.x, 172.16-31.x, 192.168.x) |
validate_subnet_ipv6() | IPv6 VPN subnet | Must be ULA (fd00::/8), minimum /48 prefix |
validate_client_name() | Client name | Alphanumeric, underscore, hyphen, dot. Max 64 chars. No leading/trailing dots |
validate_positive_int() | Numeric values | Must be a positive integer |
is_valid_client_name() | Client name | Same rules, returns boolean (no fatal exit) |
is_valid_ipv4_cidr() | IPv4 CIDR | Valid a.b.c.d/n (1–32), network-aligned (host bits must be zero) |
is_valid_ipv6_cidr() | IPv6 CIDR | Valid address/prefix (1–128), network-aligned (via expand_ipv6_address()) |
is_valid_local_network() | --local-network value | is_valid_ipv4_cidr() or is_valid_ipv6_cidr() |
is_private_ipv4_network() | IPv4 CIDR | RFC1918 range (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) with a prefix at least as specific as the block |
is_private_ipv6_network() | IPv6 CIDR | ULA range (fc00::/7) with prefix >= 7 |
Local Network Helpers¶
These functions parse and manage the comma-separated LOCAL_NETWORKS list built from one or more --local-network flags. See Network & DNS — Server-Side Network Access.
| Function | Purpose |
|---|---|
add_local_network() | Validates a CIDR and appends it to LOCAL_NETWORKS if not already present |
normalize_local_networks() | Rebuilds LOCAL_NETWORKS from a raw comma-separated string, de-duplicating via add_local_network() |
local_networks_for_family() | Yields only the IPv4 (4) or IPv6 (6) entries from LOCAL_NETWORKS |
has_local_network_family() | Returns true if at least one local network of the given family is configured |
ipv4_prefix_to_netmask() | Converts a prefix length (e.g. 24) to a dotted netmask (e.g. 255.255.255.0), for push "route ..." lines |
ipv4_cidrs_overlap() / ipv6_cidrs_overlap() | Checks whether two CIDRs share the same network at the shorter of their two prefixes — used to reject local networks that overlap the VPN subnets |
expand_ipv6_address() | Expands a (possibly ::-compressed) IPv6 address into 8 hextets; used by the IPv6 CIDR/overlap helpers |
Option Parsers¶
These functions validate a value against the allowed set and store it:
| Function | Purpose | Valid Values |
|---|---|---|
parse_dns_provider() | DNS provider | system, unbound, cloudflare, quad9, google, etc. (13 total) |
parse_cipher() | Data cipher | AES-128-GCM, AES-256-GCM, CHACHA20-POLY1305, etc. (7 total) |
parse_curve() | ECDSA curve | prime256v1, secp384r1, secp521r1 |
Configuration Validation¶
validate_configuration() runs a comprehensive check of the entire configuration before installation. It validates:
- Protocol (
udp/tcp) - DNS provider
- Certificate type (
ecdsa/rsa) - TLS signature mode (
crypt-v2/crypt/auth) - Authentication mode (
pki/fingerprint) - Port range
- IPv4/IPv6 stack settings (at least one must be enabled)
- Cipher compatibility
- Curve or RSA key size (depending on cert type)
- TLS version
- HMAC algorithm
- MTU range
- Custom DNS addresses
- Subnet formats
- OpenVPN 2.6+ requirement for fingerprint mode
ROUTE_INTERNET/CLIENT_TO_CLIENTvalues (must beyorn)- DNS provider is required unless
ROUTE_INTERNET=n(emptyDNSis only valid in split-tunnel mode) - Each local network requires IPv4/IPv6 clients to be enabled for its family, and must not overlap the IPv4 or IPv6 VPN subnet (via
ipv4_cidrs_overlap()/ipv6_cidrs_overlap())
Version Comparison¶
| Function | Purpose |
|---|---|
version_ge() | Compare two semantic versions using sort -V |
get_openvpn_version() | Extract version string from openvpn --version |
openvpnVersionAtLeast() | Check if installed OpenVPN meets a minimum version |
kernelVersionAtLeast() | Check if running kernel meets a minimum version |
Supported Options¶
All valid values are defined as arrays at the top of the script:
PROTOCOLS=("udp" "tcp")
DNS_PROVIDERS=("system" "unbound" "cloudflare" "quad9" "quad9-uncensored"
"fdn" "dnswatch" "opendns" "google" "yandex" "adguard"
"nextdns" "custom")
CIPHERS=("AES-128-GCM" "AES-192-GCM" "AES-256-GCM"
"AES-128-CBC" "AES-192-CBC" "AES-256-CBC"
"CHACHA20-POLY1305")
CERT_TYPES=("ecdsa" "rsa")
CERT_CURVES=("prime256v1" "secp384r1" "secp521r1")
RSA_KEY_SIZES=("2048" "3072" "4096")
TLS_VERSIONS=("1.2" "1.3")
TLS_SIG_MODES=("crypt-v2" "crypt" "auth")
AUTH_MODES=("pki" "fingerprint")
HMAC_ALGS=("SHA256" "SHA384" "SHA512")
PROTECTED_IPV4_NETWORKS=("10.0.0.0/8" "100.64.0.0/10" "127.0.0.0/8" "169.254.0.0/16" "172.16.0.0/12" "192.168.0.0/16")
PROTECTED_IPV6_NETWORKS=("::1/128" "fc00::/7" "fe80::/10")
PROTECTED_IPV4_NETWORKS/PROTECTED_IPV6_NETWORKS are not user-selectable options — they are the networks the firewall explicitly blocks from implicit internet-routing access unless also passed via --local-network. See Firewall — Protected Networks.