From 6cd41dff1a19f1501e3262818f5ea4e4f4a1ed35 Mon Sep 17 00:00:00 2001 From: Matthew Grotke Date: Sun, 31 May 2026 23:17:30 -0400 Subject: [PATCH] Development --- .../routlin-dash/app/pages/ddns/content.json | 6 ++- .../app/pages/intervlan/content.json | 40 ++++++++++-------- docker/routlin-dash/app/view_page.py | 2 +- routlin/core.py | 41 ++++++++++++------- 4 files changed, 56 insertions(+), 33 deletions(-) diff --git a/docker/routlin-dash/app/pages/ddns/content.json b/docker/routlin-dash/app/pages/ddns/content.json index 38ff3d1..2b229f3 100644 --- a/docker/routlin-dash/app/pages/ddns/content.json +++ b/docker/routlin-dash/app/pages/ddns/content.json @@ -261,7 +261,11 @@ "input_type": "number", "layout": "inline", "value": "%DDNS_GEN_LOG_MAX_KB%", - "min": "64" + "min": "64", + "hint": "Log will automatically be cleared when it reaches this size." + }, + { + "type": "hr" }, { "type": "field", diff --git a/docker/routlin-dash/app/pages/intervlan/content.json b/docker/routlin-dash/app/pages/intervlan/content.json index 526dd18..00df5ad 100644 --- a/docker/routlin-dash/app/pages/intervlan/content.json +++ b/docker/routlin-dash/app/pages/intervlan/content.json @@ -143,29 +143,35 @@ "type": "field_row", "cols": 3, "items": [ + { + "type": "field_row", + "cols": 2, + "items": [ + { + "type": "field", + "label": "Dest Port Range", + "name": "dst_port_min", + "input_type": "number", + "min": 1, + "max": 65535, + "hint": "This exception only applies to traffic over this port range and protocol." + }, + { + "type": "field", + "label": "", + "name": "dst_port_max", + "input_type": "number", + "min": 1, + "max": 65535 + } + ] + }, { "type": "field", "label": "Protocol", "name": "protocol", "input_type": "select", "options": "%PROTOCOL_OPTIONS%" - }, - { - "type": "field", - "label": "Port Min", - "name": "dst_port_min", - "input_type": "number", - "min": 1, - "max": 65535, - "hint": "This exception only applies to traffic over this port range and protocol." - }, - { - "type": "field", - "label": "Port Max", - "name": "dst_port_max", - "input_type": "number", - "min": 1, - "max": 65535 } ] }, diff --git a/docker/routlin-dash/app/view_page.py b/docker/routlin-dash/app/view_page.py index d985282..110da10 100644 --- a/docker/routlin-dash/app/view_page.py +++ b/docker/routlin-dash/app/view_page.py @@ -760,7 +760,7 @@ def collect_tokens(): 'Applied' 'Change' 'Fields' - 'Group' + 'Change ID' 'User' '' f'{hist_rows}' diff --git a/routlin/core.py b/routlin/core.py index cd8544a..fa7528d 100644 --- a/routlin/core.py +++ b/routlin/core.py @@ -67,10 +67,9 @@ Validation: must be valid. Protocol must be tcp, udp, or both. 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 must be valid IPv4 addresses declared in the vlans array. - inter_vlan_exceptions -- src_ip_or_subnet and dst_ip_or_subnet must be valid IPv4 addresses - or networks. dst_port must be valid (1-65535). Protocol - must be tcp, udp, or both. + 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). + Protocol must be tcp, udp, or both. Usage: sudo python3 core.py --apply Apply config fast: restart running services only @@ -1559,15 +1558,22 @@ def build_nft_config(data, dry_run=False): line(" # -- Inter-VLAN exceptions ------------------------------------------") line() for r in all_except: - src = r["src_ip_or_subnet"] - dst = r.get("dst_ip_or_subnet") or r.get("dst_ip", "") - port = r.get("dst_port") + 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") + if min_p and max_p and str(min_p) != str(max_p): + port_spec = f"{min_p}-{max_p}" + elif min_p: + port_spec = str(min_p) + else: + port_spec = None for proto, _, suffix in expand_protocols(r): line(f" # {r['description']}{suffix}") - if port is not None: - line(f" ip saddr {src} ip daddr {dst} {proto} dport {port} ct state new accept") + if port_spec is not None: + line(f" ip saddr {src} ip daddr {dst} {proto} dport {port_spec} ct state new accept") else: - line(f" ip saddr {src} ip daddr {dst} ct state new accept") + line(f" ip saddr {src} ip daddr {dst} {proto} ct state new accept") line() if all_fwd: @@ -1731,10 +1737,17 @@ def apply_nftables(data, dry_run=False): print() print("Active inter-VLAN exceptions:") for r in active_except: - src = r["src_ip_or_subnet"] - dst = r.get("dst_ip_or_subnet") or r.get("dst_ip", "") - port = r.get("dst_port") - dst_str = f"{dst}:{port}" if port is not None else dst + 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") + if min_p and max_p and str(min_p) != str(max_p): + port_str = f":{min_p}-{max_p}" + elif min_p: + port_str = f":{min_p}" + else: + port_str = "" + dst_str = f"{dst}{port_str}" print(f" [{r['protocol'].upper():<4}] {src} -> {dst_str} ({r['description']})") def show_rules():