diff --git a/FUNDRAISING.md b/FUNDRAISING.md new file mode 100644 index 0000000..bef6752 --- /dev/null +++ b/FUNDRAISING.md @@ -0,0 +1,182 @@ +# Routlin Kickstarter Fundraising Goals + +Routlin is a home router management platform built to give home users and small offices the kind of network control typically reserved for enterprise equipment. This document outlines what a fundraising campaign would go toward and why each item matters for the project. + +--- + +## Goal 1: Dedicated Testing and Integration Server + +**Purpose:** Test changes against varying network environments. + +Currently, I use my own PC for 100% of development and testing. In order to do proper testing, I need a dedicated server. This would allow: + +- Trying out a wide variety of network configurations without affecting my home network. +- Running the software on different distros to test the install/uninstall capabilities on different platforms and package managers. +- Running changes in isolation before deploying to production. +- Reproducing bugs without affecting connected devices. +- Running unit and integration tests. + +Realistiaclly I'd also like to get a ARM-based device with two ethernet ports that would be suitible for running as a router because I know many people would want that setup due to low-power and low-noise. Example hardware I'd be interested in obtaining would be NanoPi R5S/R6S and Rasberry Pi with a 2nd ethernet expansion. + +--- + +## Goal 2: Second ISP Connection + +**Purpose:** This is network software. A real WAN connection is required to test DHCP client behavior, failover, DDNS, firewall rules against live traffic, and VPN tunnels. Every time I test features or try to repoduce bugs, I risk losing my home internet connection, which makes troubleshooting and resolving very annoying and difficult. Luckily, multiple ISPs are available in my neighborhood, so I could get a 2nd connection dedicated to testing! But I need funding to pay for a 2nd one. + +A second ISP provides: + +- A dedicated WAN for the test network, completely isolated from my home network. +- The ability to test dual-WAN and failover scenarios. +- Safe environment for testing a wide variety of configurations without disrupting my primary connection. + +--- + +## Goal 3: Assortment of Network Equipment (Switches and Access Points) + +**Purpose:** This is one of the most important goals. I currently use a Unifi switch and Unifi access points for 100% of testing and development. I've already noticed several "quirks" with Unifi, especially relating to RADIUS, 802.1X, and VLAN tagging. I expect different vendors will have subtly different quirks as well. + +Funding would go toward acquiring equipment from additional vendors, including: + +- **Managed switches:** Cisco (SG series), Netgear (Plus/Pro), TP-Link (Omada), MikroTik (in bridge mode), Aruba (Instant On). +- **Unmanaged switch:** A consumer-grade unmanaged switch to test on, which will not have certain capabilities such as VLAN tagging, 802.1X port authentication, or RADIUS-based dynamic VLAN assignment - ensuring Routlin degrades gracefully when advanced switching features are unavailable. +- **Wireless access points:** TP-Link Omada, Aruba Instant On, MikroTik, OpenWrt-compatible hardware +- **Budget/prosumer gear** that home users are likely to own + +Each vendor has its own implementation of WPA-Enterprise, MAC-based 802.1X, VLAN assignment via RADIUS attributes, and huntgroup behavior. Testing against a realistic cross-section of hardware is the only way to ensure Routlin works reliably for users who do not own Unifi equipment. + +--- + +## Goal 4: Router Hardware Targets + +**Purpose:** Routlin runs on the router itself. Different hardware platforms have different constraints, driver availability, and performance characteristics. + +Target hardware for testing: + +- **x86 mini PC** (e.g. Protectli, Topton N100) - most capable, common for dedicated router builds +- **Raspberry Pi 4/5** - popular ARM SBC, limited NIC options, different network stack behavior +- **Additional ARM SBCs** (e.g. Orange Pi, Banana Pi) - lower-cost targets common outside North America + +--- + +## Goal 5: Development Time + +**Purpose:** The largest real cost of advancing this project is sustained developer time to implement new features, test, fix bugs, and respond to user feedback. + +A successful campaign would allow meaningful blocks of development time to be dedicated to Routlin rather than worked around other obligations. + +--- + +## Routlin Pro: Paid License Features + +Routlin Pro is a paid license tier planned for future development. Early Kickstarter backers will receive a Routlin Pro license as an investment incentive. + +The core Routlin software will always remain free for individual use. Pro features are advanced capabilities that require ongoing maintenance, threat database subscriptions, and significant development investment to build and sustain. + +--- + +### Pro Feature 1: Deep Packet Inspection (DPI) and Device Identification + +Routlin Pro will analyze traffic at the packet level to identify: + +- **Device categories** - automatically classify connected devices (phones, laptops, smart TVs, IoT sensors, gaming consoles) based on traffic fingerprints +- **Traffic categories** - identify streaming, gaming, P2P, VoIP, cloud backup, and other traffic types in real time +- **Per-device usage breakdowns** - see what each device on the network is actually doing + +This data surfaces in a Security Insights dashboard and feeds into traffic rules, allowing administrators to block or rate-limit specific applications for specific devices or device categories. + +--- + +### Pro Feature 2: Intrusion Detection and Prevention (IDS/IPS) + +Routlin Pro will monitor all network traffic for known threat signatures using DPI across multiple network layers: + +- **IDS mode** - monitors and alerts on suspicious activity without blocking +- **IPS mode** - automatically blocks detected threats in real time +- Generates a log of alerts with details on the source, destination, and matched signature +- Signature database updated regularly; an optional extended subscription provides access to a broader commercial threat database + +--- + +### Pro Feature 3: SSL/TLS Traffic Inspection + +Routlin Pro will support intercepting and inspecting encrypted HTTPS traffic for security monitoring and content filtering: + +- Performs SSL/TLS decryption, analyzes packet contents, then re-encrypts using the gateway's own certificate +- Configurable by traffic category or specific domains - inspect everything or only targeted categories +- Supports a high number of concurrent sessions suitable for home and small office environments +- Requires the gateway certificate to be installed on client devices for transparent operation + +This enables security features (IDS/IPS, anomaly detection) to operate on traffic that would otherwise be opaque. + +--- + +### Pro Feature 4: Traffic Flows (Session Logging) + +Routlin Pro will provide detailed logs of every network session passing through the router: + +- Full connection records including source IP, destination IP, protocol, port, bytes transferred, and session timing +- Not limited to DNS queries - captures all TCP/UDP flows +- Filterable and sortable views; save custom filter presets for repeated analysis +- Useful for diagnosing bandwidth issues, identifying unexpected connections, and post-incident investigation + +--- + +### Pro Feature 5: Anomaly and Pattern Detection + +Building on DPI and session logging, Routlin Pro will surface unusual network patterns automatically: + +- Large or unexpected outbound data transfers +- TCP SYN flood indicators +- Unexpected VPN or tunneling activity +- P2P and torrent detection +- High usage outside configured hours (e.g. overnight activity on a device that should be idle) +- New device types appearing on the network + +Anomalies generate alerts in the dashboard and can optionally trigger automated responses such as device isolation or rate limiting. + +--- + +### Pro Feature 6: Restricted VLANs + +Routlin Pro will allow any VLAN to be designated as "restricted" - blocking all internet access for devices on that VLAN while still allowing local communication. + +Use cases include: +- **IoT and smart home devices** - devices that need to talk to each other locally but should never reach the internet +- **Security cameras** - local NVR access only, no cloud uploads +- **Guest networks** - complete WAN isolation +- **Kids' devices** - internet access blocked, local resources still reachable +- **Security and privacy** - isolate local LLMs or prevent untrusted software from dialing out + +Restricted VLANs work in combination with Routlin's existing inter-VLAN exception rules, so a restricted device can still be granted access to a specific device or subnet on another VLAN (e.g. a NAS or a print server) without opening internet access. + +--- + +### Pro Feature 7: Supplicant-Based 802.1X Authentication + +Routlin currently supports MAC Authentication Bypass (MAB), where the switch or AP sends a device's MAC address to RADIUS passively - the device itself does nothing. This is easy to deploy but MAC addresses can be spoofed. + +Routlin Pro will add full supplicant-based 802.1X, where the client device actively participates in authentication using: + +- **EAP-PEAP / EAP-TTLS** - username and password credentials, common for corporate WiFi where employees authenticate with domain credentials +- **EAP-TLS** - client certificates installed on each device, the most secure option +- Certificate management for issuing and revoking client credentials + +This allows individual device certificates to be revoked without changing network passwords, and prevents unauthorized devices from gaining access even if they spoof a known MAC address. + +--- + +## Summary Table + +| Goal | Priority | Estimated Cost | +|------|----------|---------------| +| Testing/integration server | High | $800 - $1,500 | +| Second ISP connection | High | $40 - $80/mo ongoing | +| Network equipment assortment | High | $1,500 - $3,000 | +| Router hardware targets | Medium | $300 - $600 | +| Development time | High | Variable | +| Routlin Pro development | High | Variable | + +--- + +*Routlin is open source software for people who want real control over their home network.* diff --git a/docker/routlin-dash/app/config_utils.py b/docker/routlin-dash/app/config_utils.py index e7571a7..b07cf87 100644 --- a/docker/routlin-dash/app/config_utils.py +++ b/docker/routlin-dash/app/config_utils.py @@ -839,8 +839,10 @@ def load_datasource(spec): def collect_layout_tokens(cfg): + import license net = cfg.get('network_interfaces', {}) return { 'GENERAL_LAN_INTERFACE': str(net.get('lan_interface', '-')), 'VPN_VLAN_COUNT': str(sum(1 for v in cfg.get('vlans', []) if v.get('is_vpn'))), + 'PRO_LICENSE_JS': 'true' if license.is_pro() else 'false', } diff --git a/docker/routlin-dash/app/license.py b/docker/routlin-dash/app/license.py new file mode 100644 index 0000000..9afbfde --- /dev/null +++ b/docker/routlin-dash/app/license.py @@ -0,0 +1,2 @@ +def is_pro(): + return False diff --git a/docker/routlin-dash/app/pages/networklayout/action.py b/docker/routlin-dash/app/pages/networklayout/action.py index 9ae8010..1e1a5aa 100644 --- a/docker/routlin-dash/app/pages/networklayout/action.py +++ b/docker/routlin-dash/app/pages/networklayout/action.py @@ -8,9 +8,12 @@ from auth import require_level from config_utils import load_config, record_group, diff_fields, verify_config_hash import sanitize import mod_validation as validate +import license _PAGE = Path(__file__).parent.name +PRO_LICENSE = license.is_pro() + bp = Blueprint(_PAGE, __name__) @@ -46,11 +49,16 @@ def vlans_addedit(): radius_default = 'radius_default' in request.form mdns_reflection = 'mdns_reflection' in request.form dnsmasq_log_queries = 'dnsmasq_log_queries' in request.form + restricted_vlan = 'restricted_vlan' in request.form use_blocklists = sanitize.filterlist( request.form.getlist('use_blocklists'), {b.get('name') for b in load_config().get('dns_blocking', {}).get('blocklists', [])}, ) + if restricted_vlan and not PRO_LICENSE: + flash('Restricted VLAN requires a Routlin Pro license.', 'error') + return redirect(f'/{_PAGE}') + if not name: flash('Name is required.', 'error') return redirect(f'/{_PAGE}') @@ -261,6 +269,8 @@ def vlans_addedit(): 'use_blocklists': use_blocklists, 'server_identities': new_identities, }) + if PRO_LICENSE: + existing['restricted_vlan'] = restricted_vlan if dhcp_info: existing['dhcp_information'] = dhcp_info else: @@ -318,6 +328,8 @@ def vlans_addedit(): 'mdns_reflection': mdns_reflection, 'server_identities': new_identities, } + if PRO_LICENSE: + entry['restricted_vlan'] = restricted_vlan if dhcp_info: entry['dhcp_information'] = dhcp_info if is_vpn: diff --git a/docker/routlin-dash/app/pages/networklayout/content.json b/docker/routlin-dash/app/pages/networklayout/content.json index 98f52e9..bda6ad8 100644 --- a/docker/routlin-dash/app/pages/networklayout/content.json +++ b/docker/routlin-dash/app/pages/networklayout/content.json @@ -14,6 +14,10 @@ } ] }, + { + "type": "raw_html", + "html": "" + }, { "type": "info_bar", "variant": "info", @@ -88,6 +92,16 @@ "title_true": "DNS Queries Recorded", "title_false": "DNS Queries Not Recorded" } + }, + { + "label": "Restricted", + "field": "restricted_vlan", + "class": "col-narrow", + "render": "badge_yes_no", + "render_options": { + "title_true": "Restricted VLAN", + "title_false": "Not Restricted" + } } ], "row_actions": [ @@ -334,6 +348,13 @@ "input_type": "checkbox", "hint": "Log every DNS query. High volume - enable for debugging only." }, + { + "type": "field", + "label": "%RESTRICTED_VLAN_LABEL%", + "name": "restricted_vlan", + "input_type": "checkbox", + "hint": "Block devices on this VLAN from communicating with the Internet. Block all LAN traffic as well (except where Inter-VLAN-Exception rules allow)." + }, { "type": "button_row", "items": [ diff --git a/docker/routlin-dash/app/pages/networklayout/view.py b/docker/routlin-dash/app/pages/networklayout/view.py index d0b647a..5642872 100644 --- a/docker/routlin-dash/app/pages/networklayout/view.py +++ b/docker/routlin-dash/app/pages/networklayout/view.py @@ -1,6 +1,9 @@ import json from config_utils import collect_layout_tokens, load_datasource from factory import load_json, build_table, table_token_key, iter_table_items, PAGES_DIR +import license + +PRO_LICENSE = license.is_pro() def collect_tokens(cfg): @@ -10,6 +13,7 @@ def collect_tokens(cfg): tokens['EXISTING_VLAN_IDS_JSON'] = json.dumps([v.get('vlan_id') for v in vlans]) tokens['EXISTING_VLAN_NAMES_JSON'] = json.dumps([v.get('name') for v in vlans]) tokens['RADIUS_DEFAULT_VLAN'] = f'"{dv["name"]}" (VLAN {dv["vlan_id"]})' if dv else 'none set' + tokens['RESTRICTED_VLAN_LABEL'] = 'Restricted VLAN' if PRO_LICENSE else 'Restricted VLAN (PRO FEATURE)' tokens['BLOCKLIST_NAME_OPTIONS'] = json.dumps([ {'value': bl.get('name', ''), 'label': bl.get('description', bl.get('name', ''))} for bl in cfg.get('dns_blocking', {}).get('blocklists', []) diff --git a/docker/routlin-dash/app/pages/radius/action.py b/docker/routlin-dash/app/pages/radius/action.py index a5012e5..688ece8 100644 --- a/docker/routlin-dash/app/pages/radius/action.py +++ b/docker/routlin-dash/app/pages/radius/action.py @@ -8,9 +8,12 @@ from flask import Blueprint, request, redirect, flash, send_file, abort, jsonify from auth import require_level from config_utils import CONFIGS_DIR, load_config, record_group, diff_fields import mod_validation as validate +import license _PAGE = Path(__file__).parent.name +PRO_LICENSE = license.is_pro() + bp = Blueprint(_PAGE, __name__) RADIUS_SECRET_FILE = Path(CONFIGS_DIR) / '.radius-secret' @@ -52,6 +55,27 @@ def options_save(): return redirect(f'/{_PAGE}') +@bp.route('/action/radius/auth_mode_save', methods=['POST']) +@require_level('administrator') +def auth_mode_save(): + auth_mode = request.form.get('auth_mode', 'mab') + if auth_mode not in ('mab', 'eap_password', 'eap_credential'): + flash('Invalid authentication mode.', 'error') + return redirect(f'/{_PAGE}') + if auth_mode in ('eap_password', 'eap_credential') and not PRO_LICENSE: + flash('This authentication mode requires a Routlin Pro license.', 'error') + return redirect(f'/{_PAGE}') + + cfg = load_config() + before = copy.deepcopy(cfg.get('radius', {}).get('options', {})) + after = {**before, 'auth_mode': auth_mode} + cfg.setdefault('radius', {})['options'] = after + + changes = diff_fields(before, after) + flash(record_group(cfg, 'radius.options', 'auth_mode', auth_mode, changes, 'core apply'), 'success') + return redirect(f'/{_PAGE}') + + @bp.route('/action/radius/default_rule_save', methods=['POST']) @require_level('administrator') def default_rule_save(): diff --git a/docker/routlin-dash/app/pages/radius/content.json b/docker/routlin-dash/app/pages/radius/content.json index a93fabf..e0b70d6 100644 --- a/docker/routlin-dash/app/pages/radius/content.json +++ b/docker/routlin-dash/app/pages/radius/content.json @@ -14,6 +14,10 @@ } ] }, + { + "type": "raw_html", + "html": "" + }, { "type": "info_bar", "variant": "info", @@ -195,6 +199,49 @@ } ] }, + { + "type": "card", + "label": "Extensible Authentication Protocol (EAP)", + "client_requirement": "client_is_administrator+", + "items": [ + { + "type": "p", + "text": "802.1X authentication modes require a Routlin Pro license." + }, + { + "type": "hr" + }, + { + "type": "form", + "action": "/action/radius/auth_mode_save", + "method": "post", + "items": [ + { + "type": "field", + "label": "Authentication Mode", + "name": "auth_mode", + "input_type": "select", + "value": "%RADIUS_AUTH_MODE%", + "options": "%RADIUS_AUTH_MODE_OPTIONS%", + "hint": "_" + }, + { + "type": "button_row", + "items": [ + { + "type": "button_primary", + "text": "Save" + }, + { + "type": "button_cancel", + "text": "Cancel" + } + ] + } + ] + } + ] + }, { "type": "card", "label": "EAP Settings", diff --git a/docker/routlin-dash/app/pages/radius/view.py b/docker/routlin-dash/app/pages/radius/view.py index 86390e3..a7357af 100644 --- a/docker/routlin-dash/app/pages/radius/view.py +++ b/docker/routlin-dash/app/pages/radius/view.py @@ -1,6 +1,9 @@ import json import os from config_utils import collect_layout_tokens, CONFIGS_DIR +import license + +PRO_LICENSE = license.is_pro() RADIUS_LOG_MAX = 50 RADIUS_LOG_FILE = '/var/log/freeradius/radius.log' @@ -65,6 +68,13 @@ def collect_tokens(cfg): fr_opts = fr.get('options', {}) fr_gen = fr.get('general', {}) tokens['RADIUS_MAC_FORMAT'] = fr_opts.get('mac_format', 'aabbccddeeff') + tokens['RADIUS_AUTH_MODE'] = fr_opts.get('auth_mode', 'mab') + pro_suffix = '' if PRO_LICENSE else ' (PRO REQUIRED)' + tokens['RADIUS_AUTH_MODE_OPTIONS'] = json.dumps([ + {'value': 'mab', 'label': 'MAC Authentication Bypass (MAB)'}, + {'value': 'eap_password', 'label': f'802.1X - Client Username/Password{pro_suffix}'}, + {'value': 'eap_credential', 'label': f'802.1X - Client Certificate{pro_suffix}'}, + ]) tokens['RADIUS_APPLY_TO'] = fr_opts.get('apply_to', 'all') tokens['RADIUS_AP_IPS'] = json.dumps(fr_opts.get('ap_ips', []))