Development

This commit is contained in:
Matthew Grotke 2026-06-01 00:54:59 -04:00
parent 6d8be4845e
commit bc623b14fc
5 changed files with 64 additions and 49 deletions

View file

@ -33,8 +33,8 @@ def _parse_entry():
protocol = sanitize.filtervalue(request.form.get('protocol', ''), validate.VALID_PROTOCOLS)
src_raw = request.form.get('src_ip_or_subnet', '').strip()
dst_raw = request.form.get('dst_ip_or_subnet', '').strip()
dst_port_min_raw = request.form.get('dst_port_min', '').strip()
dst_port_max_raw = request.form.get('dst_port_max', '').strip()
dest_port_start_raw = request.form.get('dest_port_start', '').strip()
dest_port_end_raw = request.form.get('dest_port_end', '').strip()
if not protocol:
flash(f'The configuration has not been saved because the protocol is invalid. '
@ -57,21 +57,21 @@ def _parse_entry():
flash(f'The configuration has not been saved because "{dst_raw}" is not a valid IP address or subnet.', 'error')
return None, True
dst_port_min = ''
if dst_port_min_raw:
dst_port_min = validate.port(dst_port_min_raw)
if not dst_port_min:
flash(f'The configuration has not been saved because "{dst_port_min_raw}" is not a valid port number (1-65535).', 'error')
dest_port_start = ''
if dest_port_start_raw:
dest_port_start = validate.port(dest_port_start_raw)
if not dest_port_start:
flash(f'The configuration has not been saved because "{dest_port_start_raw}" is not a valid port number (1-65535).', 'error')
return None, True
dst_port_max = ''
if dst_port_max_raw:
dst_port_max = validate.port(dst_port_max_raw)
if not dst_port_max:
flash(f'The configuration has not been saved because "{dst_port_max_raw}" is not a valid port number (1-65535).', 'error')
dest_port_end = ''
if dest_port_end_raw:
dest_port_end = validate.port(dest_port_end_raw)
if not dest_port_end:
flash(f'The configuration has not been saved because "{dest_port_end_raw}" is not a valid port number (1-65535).', 'error')
return None, True
if dst_port_min and dst_port_max and int(dst_port_min) > int(dst_port_max):
if dest_port_start and dest_port_end and int(dest_port_start) > int(dest_port_end):
flash('Port range min must not be greater than max.', 'error')
return None, True
@ -80,8 +80,8 @@ def _parse_entry():
'protocol': protocol,
'src_ip_or_subnet': src,
'dst_ip_or_subnet': dst,
'dst_port_min': dst_port_min,
'dst_port_max': dst_port_max,
'dest_port_start': dest_port_start,
'dest_port_end': dest_port_end,
'enabled': True,
}, None

View file

@ -39,13 +39,13 @@
"class": "col-mono"
},
{
"label": "Port Min",
"field": "dst_port_min",
"label": "Port Start",
"field": "dest_port_start",
"class": "col-mono col-narrow"
},
{
"label": "Port Max",
"field": "dst_port_max",
"label": "Port End",
"field": "dest_port_end",
"class": "col-mono col-narrow"
},
{
@ -80,11 +80,11 @@
"input_type": "text"
},
{
"col": "dst_port_min",
"col": "dest_port_start",
"input_type": "number"
},
{
"col": "dst_port_max",
"col": "dest_port_end",
"input_type": "number"
},
{
@ -159,7 +159,7 @@
{
"type": "field",
"label": "Dest Port Range Start",
"name": "dst_port_min",
"name": "dest_port_start",
"input_type": "number",
"min": 1,
"max": 65535
@ -167,7 +167,7 @@
{
"type": "field",
"label": "Dest Port Range End",
"name": "dst_port_max",
"name": "dest_port_end",
"input_type": "number",
"min": 1,
"max": 65535

View file

@ -75,7 +75,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.10.3",
"dst_ip_or_subnet": "192.168.1.20",
"dst_port": 32400
"dest_port_start": 32400,
"dest_port_end": 32400
},
{
"description": "IoT Streaming Box -> Plex",
@ -83,7 +84,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.10.4",
"dst_ip_or_subnet": "192.168.1.20",
"dst_port": 32400
"dest_port_start": 32400,
"dest_port_end": 32400
},
{
"description": "Kids -> Plex",
@ -91,7 +93,8 @@
"protocol": "both",
"src_ip_or_subnet": "192.168.30.0/24",
"dst_ip_or_subnet": "192.168.1.20",
"dst_port": 32400
"dest_port_start": 32400,
"dest_port_end": 32400
},
{
"description": "Kids -> SMB",
@ -99,7 +102,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.30.0/24",
"dst_ip_or_subnet": "192.168.1.20",
"dst_port": 445
"dest_port_start": 445,
"dest_port_end": 445
},
{
"description": "Kids -> Game Server",
@ -107,7 +111,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.30.0/24",
"dst_ip_or_subnet": "192.168.1.20",
"dst_port": 25565
"dest_port_start": 25565,
"dest_port_end": 25565
},
{
"description": "Kids -> Web Server HTTP",
@ -115,7 +120,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.30.0/24",
"dst_ip_or_subnet": "192.168.1.20",
"dst_port": 80
"dest_port_start": 80,
"dest_port_end": 80
},
{
"description": "Kids -> Web Server HTTPS",
@ -123,7 +129,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.30.0/24",
"dst_ip_or_subnet": "192.168.1.20",
"dst_port": 443
"dest_port_start": 443,
"dest_port_end": 443
},
{
"description": "Trusted -> Printer (RAW)",
@ -131,7 +138,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.1.0/24",
"dst_ip_or_subnet": "192.168.10.2",
"dst_port": 9100
"dest_port_start": 9100,
"dest_port_end": 9100
},
{
"description": "Trusted -> Printer (IPP)",
@ -139,7 +147,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.1.0/24",
"dst_ip_or_subnet": "192.168.10.2",
"dst_port": 631
"dest_port_start": 631,
"dest_port_end": 631
},
{
"description": "Kids -> Printer (RAW)",
@ -147,7 +156,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.30.0/24",
"dst_ip_or_subnet": "192.168.10.2",
"dst_port": 9100
"dest_port_start": 9100,
"dest_port_end": 9100
},
{
"description": "Kids -> Printer (IPP)",
@ -155,7 +165,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.30.0/24",
"dst_ip_or_subnet": "192.168.10.2",
"dst_port": 631
"dest_port_start": 631,
"dest_port_end": 631
},
{
"description": "Guest -> Printer (RAW)",
@ -163,7 +174,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.20.0/24",
"dst_ip_or_subnet": "192.168.10.2",
"dst_port": 9100
"dest_port_start": 9100,
"dest_port_end": 9100
},
{
"description": "Guest -> Printer (IPP)",
@ -171,7 +183,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.20.0/24",
"dst_ip_or_subnet": "192.168.10.2",
"dst_port": 631
"dest_port_start": 631,
"dest_port_end": 631
},
{
"description": "VPN -> SSH + Rsync",
@ -179,7 +192,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.40.0/24",
"dst_ip_or_subnet": "192.168.1.20",
"dst_port": 22
"dest_port_start": 22,
"dest_port_end": 22
},
{
"description": "VPN -> SMB",
@ -187,7 +201,8 @@
"protocol": "tcp",
"src_ip_or_subnet": "192.168.40.0/24",
"dst_ip_or_subnet": "192.168.1.20",
"dst_port": 445
"dest_port_start": 445,
"dest_port_end": 445
},
{
"description": "Trusted -> Kids (LAN Gaming)",

View file

@ -68,7 +68,7 @@ Validation:
Generates DNAT rules only; no forward chain rules needed
since redirect_to is always a local IP (INPUT handles it).
inter_vlan_exceptions -- src_ip_or_subnet and dst_ip_or_subnet may be a single IPv4 address
or a CIDR network. dst_port_min/dst_port_max are optional (1-65535).
or a CIDR network. dest_port_start/dest_port_end are optional (1-65535).
Protocol must be tcp, udp, or both.
Usage:
@ -1560,8 +1560,8 @@ def build_nft_config(data, dry_run=False):
for r in all_except:
src = r["src_ip_or_subnet"]
dst = r.get("dst_ip_or_subnet") or r.get("dst_ip", "")
min_p = r.get("dst_port_min") or r.get("dst_port")
max_p = r.get("dst_port_max")
min_p = r.get("dest_port_start") or r.get("dst_port")
max_p = r.get("dest_port_end")
if min_p and max_p and str(min_p) != str(max_p):
port_spec = f"{min_p}-{max_p}"
elif min_p:
@ -1739,8 +1739,8 @@ def apply_nftables(data, dry_run=False):
for r in active_except:
src = r["src_ip_or_subnet"]
dst = r.get("dst_ip_or_subnet") or r.get("dst_ip", "")
min_p = r.get("dst_port_min") or r.get("dst_port")
max_p = r.get("dst_port_max")
min_p = r.get("dest_port_start") or r.get("dst_port")
max_p = r.get("dest_port_end")
if min_p and max_p and str(min_p) != str(max_p):
port_str = f":{min_p}-{max_p}"
elif min_p:

View file

@ -851,15 +851,15 @@ def validate_config(data):
if not ipv4_or_cidr(dst):
errors.append(f"{label}: dst_ip_or_subnet '{dst}' is not a valid "
f"IPv4 address or network.")
if r.get("dst_port_min"):
nat_check_port(f"{label} dst_port_min", r.get("dst_port_min"))
if r.get("dst_port_max"):
nat_check_port(f"{label} dst_port_max", r.get("dst_port_max"))
min_p, max_p = r.get("dst_port_min", ""), r.get("dst_port_max", "")
if r.get("dest_port_start"):
nat_check_port(f"{label} dest_port_start", r.get("dest_port_start"))
if r.get("dest_port_end"):
nat_check_port(f"{label} dest_port_end", r.get("dest_port_end"))
min_p, max_p = r.get("dest_port_start", ""), r.get("dest_port_end", "")
if min_p and max_p:
try:
if int(min_p) > int(max_p):
errors.append(f"{label}: dst_port_min {min_p} is greater than dst_port_max {max_p}.")
errors.append(f"{label}: dest_port_start {min_p} is greater than dest_port_end {max_p}.")
except (ValueError, TypeError):
pass