Skip to content

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_CLIENT values (must be y or n)
  • DNS provider is required unless ROUTE_INTERNET=n (empty DNS is 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.