Development

This commit is contained in:
Matthew Grotke 2026-05-31 02:17:25 -04:00
parent 84118a4c2b
commit 916d238602
9 changed files with 51 additions and 51 deletions

View file

@ -112,9 +112,9 @@ def addvlan_add():
inferred_gw = (min(identity_ips, key=lambda ip: int(ip.split('.')[-1])) if identity_ips else '') inferred_gw = (min(identity_ips, key=lambda ip: int(ip.split('.')[-1])) if identity_ips else '')
new_stored_gw = gateway_raw if (gateway_raw and gateway_raw != inferred_gw) else '' new_stored_gw = gateway_raw if (gateway_raw and gateway_raw != inferred_gw) else ''
dns_override = 'dns_server_override' in request.form dns_override = 'dns_servers_override' in request.form
dns_ips = [] dns_ips = []
for _line in request.form.get('dns_server', '').splitlines(): for _line in request.form.get('dns_servers', '').splitlines():
_line = _line.strip() _line = _line.strip()
if not _line: if not _line:
continue continue
@ -130,9 +130,9 @@ def addvlan_add():
return redirect(f'/{_PAGE}') return redirect(f'/{_PAGE}')
new_stored_dns = dns_ips if dns_override else [] new_stored_dns = dns_ips if dns_override else []
ntp_override = 'ntp_server_override' in request.form ntp_override = 'ntp_servers_override' in request.form
ntp_ips = [] ntp_ips = []
for _line in request.form.get('ntp_server', '').splitlines(): for _line in request.form.get('ntp_servers', '').splitlines():
_line = _line.strip() _line = _line.strip()
if not _line: if not _line:
continue continue
@ -207,9 +207,9 @@ def addvlan_add():
if new_stored_gw: if new_stored_gw:
dhcp_overrides['gateway'] = new_stored_gw dhcp_overrides['gateway'] = new_stored_gw
if new_stored_dns: if new_stored_dns:
dhcp_overrides['dns_server'] = new_stored_dns dhcp_overrides['dns_servers'] = new_stored_dns
if new_stored_ntp: if new_stored_ntp:
dhcp_overrides['ntp_server'] = new_stored_ntp dhcp_overrides['ntp_servers'] = new_stored_ntp
if dhcp_overrides: if dhcp_overrides:
dhcp_info['explicit_overrides'] = dhcp_overrides dhcp_info['explicit_overrides'] = dhcp_overrides
@ -361,9 +361,9 @@ def vlans_edit():
new_stored_gw = gateway_raw if (gateway_raw and gateway_raw != inferred_gw) else '' new_stored_gw = gateway_raw if (gateway_raw and gateway_raw != inferred_gw) else ''
existing_gw = existing.get('dhcp_information', {}).get('explicit_overrides', {}).get('gateway', '') existing_gw = existing.get('dhcp_information', {}).get('explicit_overrides', {}).get('gateway', '')
dns_override = 'dns_server_override' in request.form dns_override = 'dns_servers_override' in request.form
dns_ips = [] dns_ips = []
for _line in request.form.get('dns_server', '').splitlines(): for _line in request.form.get('dns_servers', '').splitlines():
_line = _line.strip() _line = _line.strip()
if not _line: if not _line:
continue continue
@ -382,12 +382,12 @@ def vlans_edit():
flash(f"DNS server '{_ip}' is not in the VLAN subnet ({subnet}/{final_mask}).", 'error') flash(f"DNS server '{_ip}' is not in the VLAN subnet ({subnet}/{final_mask}).", 'error')
return redirect(f'/{_PAGE}') return redirect(f'/{_PAGE}')
new_stored_dns = dns_ips if dns_override else [] new_stored_dns = dns_ips if dns_override else []
_existing_dns = existing.get('dhcp_information', {}).get('explicit_overrides', {}).get('dns_server', []) _existing_dns = existing.get('dhcp_information', {}).get('explicit_overrides', {}).get('dns_servers', [])
existing_dns = _existing_dns if isinstance(_existing_dns, list) else ([_existing_dns] if _existing_dns else []) existing_dns = _existing_dns if isinstance(_existing_dns, list) else ([_existing_dns] if _existing_dns else [])
ntp_override = 'ntp_server_override' in request.form ntp_override = 'ntp_server_override' in request.form
ntp_ips = [] ntp_ips = []
for _line in request.form.get('ntp_server', '').splitlines(): for _line in request.form.get('ntp_servers', '').splitlines():
_line = _line.strip() _line = _line.strip()
if not _line: if not _line:
continue continue
@ -406,7 +406,7 @@ def vlans_edit():
flash(f"NTP server '{_ip}' is not in the VLAN subnet ({subnet}/{final_mask}).", 'error') flash(f"NTP server '{_ip}' is not in the VLAN subnet ({subnet}/{final_mask}).", 'error')
return redirect(f'/{_PAGE}') return redirect(f'/{_PAGE}')
new_stored_ntp = ntp_ips if ntp_override else [] new_stored_ntp = ntp_ips if ntp_override else []
_existing_ntp = existing.get('dhcp_information', {}).get('explicit_overrides', {}).get('ntp_server', []) _existing_ntp = existing.get('dhcp_information', {}).get('explicit_overrides', {}).get('ntp_servers', [])
existing_ntp = _existing_ntp if isinstance(_existing_ntp, list) else ([_existing_ntp] if _existing_ntp else []) existing_ntp = _existing_ntp if isinstance(_existing_ntp, list) else ([_existing_ntp] if _existing_ntp else [])
_ids_unchanged = ( _ids_unchanged = (
@ -452,13 +452,13 @@ def vlans_edit():
else: else:
dhcp_overrides.pop('gateway', None) dhcp_overrides.pop('gateway', None)
if new_stored_dns: if new_stored_dns:
dhcp_overrides['dns_server'] = new_stored_dns dhcp_overrides['dns_servers'] = new_stored_dns
else: else:
dhcp_overrides.pop('dns_server', None) dhcp_overrides.pop('dns_servers', None)
if new_stored_ntp: if new_stored_ntp:
dhcp_overrides['ntp_server'] = new_stored_ntp dhcp_overrides['ntp_servers'] = new_stored_ntp
else: else:
dhcp_overrides.pop('ntp_server', None) dhcp_overrides.pop('ntp_servers', None)
if not dhcp_overrides: if not dhcp_overrides:
existing.get('dhcp_information', {}).pop('explicit_overrides', None) existing.get('dhcp_information', {}).pop('explicit_overrides', None)
errors = validate.validate_config(cfg) errors = validate.validate_config(cfg)

View file

@ -221,16 +221,16 @@
{ {
"type": "overridable_textarea", "type": "overridable_textarea",
"label": "DNS Server(s)", "label": "DNS Server(s)",
"name": "dns_server", "name": "dns_servers",
"override_name": "dns_server_override", "override_name": "dns_servers_override",
"validate": "VALIDATION_ADDRESS", "validate": "VALIDATION_ADDRESS",
"hint": "DNS server(s) advertised to clients via DHCP." "hint": "DNS server(s) advertised to clients via DHCP."
}, },
{ {
"type": "overridable_textarea", "type": "overridable_textarea",
"label": "NTP Server(s)", "label": "NTP Server(s)",
"name": "ntp_server", "name": "ntp_servers",
"override_name": "ntp_server_override", "override_name": "ntp_servers_override",
"validate": "VALIDATION_ADDRESS", "validate": "VALIDATION_ADDRESS",
"hint": "NTP server(s) advertised to clients via DHCP." "hint": "NTP server(s) advertised to clients via DHCP."
}, },

View file

@ -88,7 +88,7 @@ def _build_client_conf(vlan, peer_name, peer_ip, private_key, server_pubkey):
default = str(min((ipaddress.IPv4Address(ip) for ip in ident_ips), default = str(min((ipaddress.IPv4Address(ip) for ip in ident_ips),
key=lambda x: x.packed[-1])) if ident_ips else str(next(network.hosts())) key=lambda x: x.packed[-1])) if ident_ips else str(next(network.hosts()))
gateway = overrides.get('gateway') or default gateway = overrides.get('gateway') or default
dns = overrides.get('dns_server') or gateway dns = overrides.get('dns_servers') or gateway
prefix = network.prefixlen prefix = network.prefixlen
mtu = overrides.get('mtu', '') mtu = overrides.get('mtu', '')
endpoint = info.get('server_endpoint', '') endpoint = info.get('server_endpoint', '')
@ -138,7 +138,7 @@ def wireguard_apply():
listen_port_raw = request.form.get('vpn_listen_port', '').strip() listen_port_raw = request.form.get('vpn_listen_port', '').strip()
server_endpoint = validate.domainname(request.form.get('vpn_server_endpoint', '')) server_endpoint = validate.domainname(request.form.get('vpn_server_endpoint', ''))
domain = validate.domainname(request.form.get('vpn_domain', '')) domain = validate.domainname(request.form.get('vpn_domain', ''))
dns_raw = request.form.get('vpn_dns_server', '').strip() dns_raw = request.form.get('vpn_dns_servers', '').strip()
mtu_raw = request.form.get('vpn_mtu', '').strip() mtu_raw = request.form.get('vpn_mtu', '').strip()
if not listen_port_raw: if not listen_port_raw:
@ -185,9 +185,9 @@ def wireguard_apply():
overrides = info.setdefault('explicit_overrides', {}) overrides = info.setdefault('explicit_overrides', {})
if dns_server: if dns_server:
overrides['dns_server'] = dns_server overrides['dns_servers'] = dns_server
else: else:
overrides.pop('dns_server', None) overrides.pop('dns_servers', None)
if mtu is not None: if mtu is not None:
overrides['mtu'] = mtu overrides['mtu'] = mtu
else: else:

View file

@ -302,10 +302,10 @@ def config_datasource(name):
row['server_identity_gateway'] = ( row['server_identity_gateway'] = (
v.get('dhcp_information', {}).get('explicit_overrides', {}).get('gateway', '') v.get('dhcp_information', {}).get('explicit_overrides', {}).get('gateway', '')
) )
_dns = v.get('dhcp_information', {}).get('explicit_overrides', {}).get('dns_server', []) _dns = v.get('dhcp_information', {}).get('explicit_overrides', {}).get('dns_servers', [])
row['server_identity_dns_server'] = '\n'.join(_dns) if isinstance(_dns, list) else str(_dns or '') row['server_identity_dns_servers'] = '\n'.join(_dns) if isinstance(_dns, list) else str(_dns or '')
_ntp = v.get('dhcp_information', {}).get('explicit_overrides', {}).get('ntp_server', []) _ntp = v.get('dhcp_information', {}).get('explicit_overrides', {}).get('ntp_servers', [])
row['server_identity_ntp_server'] = '\n'.join(_ntp) if isinstance(_ntp, list) else str(_ntp or '') row['server_identity_ntp_servers'] = '\n'.join(_ntp) if isinstance(_ntp, list) else str(_ntp or '')
rows.append(row) rows.append(row)
return rows return rows
@ -819,7 +819,7 @@ def collect_tokens():
tokens['VPN_LISTEN_PORT'] = str(vpn.get('listen_port', '')) tokens['VPN_LISTEN_PORT'] = str(vpn.get('listen_port', ''))
tokens['VPN_SERVER_ENDPOINT'] = str(vpn.get('server_endpoint', '')) tokens['VPN_SERVER_ENDPOINT'] = str(vpn.get('server_endpoint', ''))
tokens['VPN_DOMAIN'] = str(vpn.get('domain', '')) tokens['VPN_DOMAIN'] = str(vpn.get('domain', ''))
tokens['VPN_DNS_SERVER'] = str(overrides.get('dns_server', '')) tokens['VPN_DNS_SERVER'] = str(overrides.get('dns_servers', ''))
tokens['VPN_MTU'] = str(overrides.get('mtu', '')) tokens['VPN_MTU'] = str(overrides.get('mtu', ''))
# Compute gateway from server_identities (lowest last-octet), fallback to first subnet host # Compute gateway from server_identities (lowest last-octet), fallback to first subnet host

View file

@ -77,7 +77,7 @@ Edit the `vlans` array to match your network topology. For each VLAN:
"listen_port": 51820, "listen_port": 51820,
"server_endpoint": "vpn.example.com", "server_endpoint": "vpn.example.com",
"domain": "local", "domain": "local",
"explicit_overrides": { "gateway": "", "dns_server": "", "mtu": "" } "explicit_overrides": { "gateway": "", "dns_servers": "", "mtu": "" }
}, },
"peers": [], "peers": [],
"port_wrangling": [] "port_wrangling": []

View file

@ -298,8 +298,8 @@
"domain": "lan", "domain": "lan",
"explicit_overrides": { "explicit_overrides": {
"gateway": "", "gateway": "",
"dns_server": "", "dns_servers": "",
"ntp_server": "" "ntp_servers": ""
} }
}, },
"reservations": [ "reservations": [
@ -393,8 +393,8 @@
"domain": "lan", "domain": "lan",
"explicit_overrides": { "explicit_overrides": {
"gateway": "", "gateway": "",
"dns_server": "", "dns_servers": "",
"ntp_server": "" "ntp_servers": ""
} }
}, },
"reservations": [ "reservations": [
@ -498,8 +498,8 @@
"domain": "lan", "domain": "lan",
"explicit_overrides": { "explicit_overrides": {
"gateway": "", "gateway": "",
"dns_server": "", "dns_servers": "",
"ntp_server": "" "ntp_servers": ""
} }
}, },
"reservations": [ "reservations": [
@ -562,8 +562,8 @@
"domain": "lan", "domain": "lan",
"explicit_overrides": { "explicit_overrides": {
"gateway": "", "gateway": "",
"dns_server": "", "dns_servers": "",
"ntp_server": "" "ntp_servers": ""
} }
}, },
"reservations": [ "reservations": [
@ -638,7 +638,7 @@
"domain": "lan", "domain": "lan",
"explicit_overrides": { "explicit_overrides": {
"gateway": "", "gateway": "",
"dns_server": "", "dns_servers": "",
"mtu": "" "mtu": ""
} }
}, },

View file

@ -195,11 +195,11 @@ def resolve_vlan_options(vlan):
overrides = vpi.get("explicit_overrides", {}) overrides = vpi.get("explicit_overrides", {})
default = lowest_quartet_ip(vlan) or str(next(network_for(vlan).hosts())) default = lowest_quartet_ip(vlan) or str(next(network_for(vlan).hosts()))
gateway = overrides.get("gateway", "") or default gateway = overrides.get("gateway", "") or default
dns = overrides.get("dns_server", "") or gateway dns = overrides.get("dns_servers", "") or gateway
return { return {
"gateway": gateway, "gateway": gateway,
"dns_server": dns, "dns_servers": dns,
"ntp_server": None, "ntp_servers": None,
} }
overrides = vlan.get("dhcp_information", {}).get("explicit_overrides", {}) overrides = vlan.get("dhcp_information", {}).get("explicit_overrides", {})
default = lowest_quartet_ip(vlan) default = lowest_quartet_ip(vlan)
@ -210,8 +210,8 @@ def resolve_vlan_options(vlan):
return v or default return v or default
return { return {
"gateway": overrides.get("gateway", "") or default, "gateway": overrides.get("gateway", "") or default,
"dns_server": _resolve("dns_server"), "dns_servers": _resolve("dns_servers"),
"ntp_server": _resolve("ntp_server"), "ntp_servers": _resolve("ntp_servers"),
} }
def is_physical(vlan): def is_physical(vlan):
@ -481,8 +481,8 @@ def build_vlan_dnsmasq_conf(vlan, data, iface):
line(f"domain={d.get('domain', 'local')}") line(f"domain={d.get('domain', 'local')}")
line() line()
line(f"dhcp-option=tag:{name},option:router,{gateway}") line(f"dhcp-option=tag:{name},option:router,{gateway}")
line(f"dhcp-option=tag:{name},option:dns-server,{opts['dns_server']}") line(f"dhcp-option=tag:{name},option:dns-server,{opts['dns_servers']}")
line(f"dhcp-option=tag:{name},option:ntp-server,{opts['ntp_server']}") line(f"dhcp-option=tag:{name},option:ntp-server,{opts['ntp_servers']}")
line() line()
identity_hosts = [s for s in vlan.get("server_identities", []) if s.get("hostname")] identity_hosts = [s for s in vlan.get("server_identities", []) if s.get("hostname")]

View file

@ -122,7 +122,7 @@ def build_client_conf(vlan, peer_ip, private_key, server_pub, split_tunnel):
default = str(min((ipaddress.IPv4Address(ip) for ip in ident_ips), default = str(min((ipaddress.IPv4Address(ip) for ip in ident_ips),
key=lambda x: x.packed[-1])) if ident_ips else str(next(network.hosts())) key=lambda x: x.packed[-1])) if ident_ips else str(next(network.hosts()))
gateway = overrides.get("gateway") or default gateway = overrides.get("gateway") or default
dns = overrides.get("dns_server") or gateway dns = overrides.get("dns_servers") or gateway
prefix = network.prefixlen prefix = network.prefixlen
mtu = overrides.get("mtu", "") mtu = overrides.get("mtu", "")
endpoint = info.get("server_endpoint", "") endpoint = info.get("server_endpoint", "")

View file

@ -460,7 +460,7 @@ def validate_config(data):
f"any server_identity IP. Must be one of: " f"any server_identity IP. Must be one of: "
f"{[str(ip) for ip in identity_ips]}." f"{[str(ip) for ip in identity_ips]}."
) )
dns = eo.get("dns_server", "") dns = eo.get("dns_servers", "")
if dns and not ipv4(dns): if dns and not ipv4(dns):
errors.append(f"{label}: vpn_information.explicit_overrides.dns_server '{dns}' is not a valid IPv4 address.") errors.append(f"{label}: vpn_information.explicit_overrides.dns_server '{dns}' is not a valid IPv4 address.")
mtu = eo.get("mtu", "") mtu = eo.get("mtu", "")
@ -564,14 +564,14 @@ def validate_config(data):
f"any server_identity IP. Must be one of: " f"any server_identity IP. Must be one of: "
f"{[str(ip) for ip in identity_ips]}." f"{[str(ip) for ip in identity_ips]}."
) )
dns = eo.get("dns_server", "") dns = eo.get("dns_servers", "")
if dns: if dns:
for _ip in (dns if isinstance(dns, list) else [dns]): for _ip in (dns if isinstance(dns, list) else [dns]):
check_ip("explicit_overrides.dns_server", _ip) check_ip("explicit_overrides.dns_servers", _ip)
ntp = eo.get("ntp_server", "") ntp = eo.get("ntp_servers", "")
if ntp: if ntp:
for _ip in (ntp if isinstance(ntp, list) else [ntp]): for _ip in (ntp if isinstance(ntp, list) else [ntp]):
check_ip("explicit_overrides.ntp_server", _ip) check_ip("explicit_overrides.ntp_servers", _ip)
pool_start = check_ip("dynamic_pool_start", d["dynamic_pool_start"]) pool_start = check_ip("dynamic_pool_start", d["dynamic_pool_start"])
pool_end = check_ip("dynamic_pool_end", d["dynamic_pool_end"]) pool_end = check_ip("dynamic_pool_end", d["dynamic_pool_end"])