From bc623b14fcf3ddd6589446f34f6037fa6ee5c774 Mon Sep 17 00:00:00 2001 From: Matthew Grotke Date: Mon, 1 Jun 2026 00:54:59 -0400 Subject: [PATCH] Development --- .../app/pages/intervlan/action.py | 30 ++++++------- .../app/pages/intervlan/content.json | 16 +++---- routlin/config.json | 45 ++++++++++++------- routlin/core.py | 10 ++--- routlin/validation.py | 12 ++--- 5 files changed, 64 insertions(+), 49 deletions(-) diff --git a/docker/routlin-dash/app/pages/intervlan/action.py b/docker/routlin-dash/app/pages/intervlan/action.py index 9d88748..dd1a10f 100644 --- a/docker/routlin-dash/app/pages/intervlan/action.py +++ b/docker/routlin-dash/app/pages/intervlan/action.py @@ -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 diff --git a/docker/routlin-dash/app/pages/intervlan/content.json b/docker/routlin-dash/app/pages/intervlan/content.json index 5b12dd1..8fd7247 100644 --- a/docker/routlin-dash/app/pages/intervlan/content.json +++ b/docker/routlin-dash/app/pages/intervlan/content.json @@ -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 diff --git a/routlin/config.json b/routlin/config.json index a8962fe..6fe9c33 100644 --- a/routlin/config.json +++ b/routlin/config.json @@ -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)", diff --git a/routlin/core.py b/routlin/core.py index 2b2f690..a753443 100644 --- a/routlin/core.py +++ b/routlin/core.py @@ -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: diff --git a/routlin/validation.py b/routlin/validation.py index e0e44f8..8e92a67 100644 --- a/routlin/validation.py +++ b/routlin/validation.py @@ -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