diff --git a/DOCS/embedded/vhost-manager.md b/DOCS/embedded/vhost-manager.md index ee9db6d2..b72f1300 100644 --- a/DOCS/embedded/vhost-manager.md +++ b/DOCS/embedded/vhost-manager.md @@ -83,6 +83,19 @@ The LuCI backend writes to the same `/etc/config/vhosts` file, so changes from ` --- +## DMZ Mode + VHost Workflow + +When enabling the new **Router + DMZ** network mode (admin → SecuBox → Network → Modes → DMZ): + +1. Assign `eth2` (or another physical port) as the DMZ interface and give it a subnet such as `192.168.50.1/24`. +2. Apply the mode; the backend creates a dedicated firewall zone (`dmz`) that only forwards to WAN. +3. Connect servers (e.g., Lyrion, Zigbee2MQTT UI) to the DMZ port so they can reach the internet but cannot reach the LAN. +4. Use `scripts/vhostctl.sh add ... --upstream http://192.168.50.10:32400` to expose the DMZ service through nginx with TLS. + +Rollback is one click away: use the Network Modes “Confirm / Rollback” dialog within the 2‑minute window to restore the previous configs automatically. + +--- + ## Troubleshooting | Issue | Fix | diff --git a/docs/embedded/vhost-manager.md b/docs/embedded/vhost-manager.md index ee9db6d2..4117bbc5 100644 --- a/docs/embedded/vhost-manager.md +++ b/docs/embedded/vhost-manager.md @@ -83,6 +83,19 @@ The LuCI backend writes to the same `/etc/config/vhosts` file, so changes from ` --- +## DMZ Mode + VHost Workflow + +When enabling the new **Router + DMZ** network mode (admin → SecuBox → Network → Modes → DMZ): + +1. Assign `eth2` (or another physical port) as the DMZ interface and give it a subnet such as `192.168.50.1/24`. +2. Apply the mode; the backend creates a dedicated firewall zone (`dmz`) that only forwards to WAN. +3. Connect servers (e.g., Lyrion, Zigbee2MQTT UI) to the DMZ port so they can reach the internet but cannot reach the LAN. +4. Use `scripts/vhostctl.sh add ... --upstream http://192.168.50.10:32400` to expose the DMZ service through nginx with TLS. + +If something goes wrong, simply click **Rollback** in the Network Modes UI—the previous `/etc/config/network`, `/etc/config/firewall`, and `/etc/config/dhcp` are restored automatically thanks to the built-in backups. + +--- + ## Troubleshooting | Issue | Fix | diff --git a/luci-app-network-modes/README.md b/luci-app-network-modes/README.md index 197bf972..9193c9ee 100644 --- a/luci-app-network-modes/README.md +++ b/luci-app-network-modes/README.md @@ -173,6 +173,13 @@ Full router with WAN, proxy and HTTPS frontends. - **HTTPS Reverse Proxy**: Nginx, HAProxy, Caddy - **Multiple virtual hosts** with Let's Encrypt +### 🛡️ Router + DMZ Mode +Create a dedicated DMZ segment for exposed servers while keeping LAN traffic segregated. +- Separate DMZ interface with its own subnet/DHCP scope +- Firewall zone isolation (DMZ → WAN forwarding, no DMZ → LAN unless toggled) +- Fast rollback using the existing backup/confirm workflow +- Designed for hosting apps (Zigbee2MQTT, Lyrion, etc.) when combined with the VHost manager + ## ✨ Features - 🎛️ One-click mode switching with backup diff --git a/luci-app-network-modes/htdocs/luci-static/resources/network-modes/api.js b/luci-app-network-modes/htdocs/luci-static/resources/network-modes/api.js index 9f75eedb..fc3ecbd0 100644 --- a/luci-app-network-modes/htdocs/luci-static/resources/network-modes/api.js +++ b/luci-app-network-modes/htdocs/luci-static/resources/network-modes/api.js @@ -95,6 +95,12 @@ var callRouterConfig = rpc.declare({ expect: { } }); +var callDmzConfig = rpc.declare({ + object: 'luci.network-modes', + method: 'dmz_config', + expect: { } +}); + var callTravelConfig = rpc.declare({ object: 'luci.network-modes', method: 'travel_config', @@ -172,6 +178,7 @@ return baseclass.extend({ getAvailableModes: callGetAvailableModes, setMode: callSetMode, getInterfaces: callGetInterfaces, + getDmzConfig: callDmzConfig, validateConfig: callValidateConfig, previewChanges: callPreviewChanges, confirmMode: callConfirmMode, @@ -337,6 +344,7 @@ return baseclass.extend({ features: [] }; }, + getDmzConfig: callDmzConfig, // Format uptime seconds to human readable formatUptime: function(seconds) { @@ -388,3 +396,15 @@ return baseclass.extend({ return callEnableTcpBbr(); } }); + dmz: { + id: 'dmz', + name: 'Router + DMZ', + icon: '🛡️', + description: 'Traditional router with an additional DMZ interface isolated from LAN but allowed to reach WAN.', + features: [ + 'Dedicated DMZ subnet', + 'Separate firewall zone', + 'Optional DHCP for DMZ clients', + 'Quick rollback timer' + ] + }, diff --git a/luci-app-network-modes/htdocs/luci-static/resources/network-modes/helpers.js b/luci-app-network-modes/htdocs/luci-static/resources/network-modes/helpers.js index f3bcdc12..ffef0047 100644 --- a/luci-app-network-modes/htdocs/luci-static/resources/network-modes/helpers.js +++ b/luci-app-network-modes/htdocs/luci-static/resources/network-modes/helpers.js @@ -7,6 +7,7 @@ var NAV_BLUEPRINT = [ { id: 'overview', icon: '📊', labelKey: 'Overview' }, { id: 'wizard', icon: '🧭', labelKey: 'Wizard' }, { id: 'router', icon: '🌐', labelKey: 'Router' }, + { id: 'dmz', icon: '🛡️', labelKey: 'DMZ' }, { id: 'multiwan', icon: '🔀', labelKey: 'Multi-WAN' }, { id: 'doublenat', icon: '🧱', labelKey: 'Double NAT' }, { id: 'accesspoint', icon: '📡', labelKey: 'Access Point' }, diff --git a/luci-app-network-modes/htdocs/luci-static/resources/view/network-modes/dmz.js b/luci-app-network-modes/htdocs/luci-static/resources/view/network-modes/dmz.js new file mode 100644 index 00000000..3f79f85d --- /dev/null +++ b/luci-app-network-modes/htdocs/luci-static/resources/view/network-modes/dmz.js @@ -0,0 +1,212 @@ +'use strict'; +'require view'; +'require dom'; +'require ui'; +'require network-modes.api as api'; +'require network-modes.helpers as helpers'; +'require secubox-theme/theme as Theme'; + +var lang = (typeof L !== 'undefined' && L.env && L.env.lang) || + (document.documentElement && document.documentElement.getAttribute('lang')) || + (navigator.language ? navigator.language.split('-')[0] : 'en'); +Theme.init({ language: lang }); + +function buildToggle(id, icon, title, desc, enabled) { + return E('div', { 'class': 'nm-toggle' }, [ + E('div', { 'class': 'nm-toggle-info' }, [ + E('span', { 'class': 'nm-toggle-icon' }, icon), + E('div', {}, [ + E('div', { 'class': 'nm-toggle-label' }, title), + E('div', { 'class': 'nm-toggle-desc' }, desc) + ]) + ]), + E('div', { + 'class': 'nm-toggle-switch' + (enabled ? ' active' : ''), + 'id': id + }) + ]); +} + +return view.extend({ + title: _('DMZ Mode'), + + load: function() { + return Promise.all([ + api.getDmzConfig(), + api.getStatus() + ]); + }, + + render: function(payload) { + var config = (payload && payload[0]) || {}; + var status = (payload && payload[1]) || {}; + var wan = config.wan || {}; + var lan = config.lan || {}; + var dmz = config.dmz || {}; + + var hero = helpers.createHero({ + icon: '🛡️', + title: _('Router + DMZ Mode'), + subtitle: _('Create an isolated DMZ network that only reaches the WAN while keeping your LAN protected. Perfect for exposing servers without risking the internal network.'), + gradient: 'linear-gradient(135deg,#1e293b,#0ea5e9)', + meta: [ + { label: _('WAN Interface'), value: wan.interface || 'eth1' }, + { label: _('LAN Gateway'), value: lan.ip_address || '192.168.1.1' }, + { label: _('DMZ Gateway'), value: dmz.ip_address || '192.168.50.1' } + ], + actions: [ + E('button', { 'class': 'nm-btn nm-btn-primary', 'type': 'button', 'data-action': 'dmz-save' }, ['💾 ', _('Save Template')]), + E('button', { 'class': 'nm-btn', 'type': 'button', 'data-action': 'dmz-apply' }, ['🚀 ', _('Apply DMZ Mode')]), + E('button', { 'class': 'nm-btn', 'type': 'button', 'data-action': 'dmz-preview' }, ['📝 ', _('Preview Config')]) + ] + }); + + var wanSection = helpers.createSection({ + title: _('WAN Settings'), + icon: '🌍', + badge: (wan.interface || 'eth1').toUpperCase(), + body: [ + E('div', { 'class': 'nm-form-grid' }, [ + E('div', { 'class': 'nm-form-group' }, [ + E('label', { 'class': 'nm-form-label' }, _('WAN Interface')), + E('input', { 'class': 'nm-input', 'id': 'dmz-wan-if', 'value': wan.interface || 'eth1' }) + ]), + E('div', { 'class': 'nm-form-group' }, [ + E('label', { 'class': 'nm-form-label' }, _('WAN Protocol')), + E('select', { 'class': 'nm-select', 'id': 'dmz-wan-proto' }, + (wan.protocols || ['dhcp', 'static', 'pppoe']).map(function(proto) { + return E('option', { + 'value': proto, + 'selected': (proto === wan.protocol) + }, proto.toUpperCase()); + }) + ) + ]) + ]) + ] + }); + + var lanSection = helpers.createSection({ + title: _('LAN Network'), + icon: '🏠', + badge: lan.interface || 'br-lan', + body: [ + E('div', { 'class': 'nm-form-grid' }, [ + E('div', { 'class': 'nm-form-group' }, [ + E('label', { 'class': 'nm-form-label' }, _('LAN Interface')), + E('input', { 'class': 'nm-input', 'id': 'dmz-lan-if', 'value': lan.interface || 'br-lan' }) + ]), + E('div', { 'class': 'nm-form-group' }, [ + E('label', { 'class': 'nm-form-label' }, _('LAN IP Address')), + E('input', { 'class': 'nm-input', 'id': 'dmz-lan-ip', 'value': lan.ip_address || '192.168.1.1' }) + ]), + E('div', { 'class': 'nm-form-group' }, [ + E('label', { 'class': 'nm-form-label' }, _('Netmask')), + E('input', { 'class': 'nm-input', 'id': 'dmz-lan-mask', 'value': lan.netmask || '255.255.255.0' }) + ]) + ]) + ] + }); + + var dmzSection = helpers.createSection({ + title: _('DMZ Segment'), + icon: '🧱', + badge: dmz.interface || 'eth2', + body: [ + E('div', { 'class': 'nm-form-grid' }, [ + E('div', { 'class': 'nm-form-group' }, [ + E('label', { 'class': 'nm-form-label' }, _('DMZ Interface')), + E('input', { 'class': 'nm-input', 'id': 'dmz-if', 'value': dmz.interface || 'eth2' }) + ]), + E('div', { 'class': 'nm-form-group' }, [ + E('label', { 'class': 'nm-form-label' }, _('DMZ IP Address')), + E('input', { 'class': 'nm-input', 'id': 'dmz-ip', 'value': dmz.ip_address || '192.168.50.1' }) + ]), + E('div', { 'class': 'nm-form-group' }, [ + E('label', { 'class': 'nm-form-label' }, _('Netmask')), + E('input', { 'class': 'nm-input', 'id': 'dmz-mask', 'value': dmz.netmask || '255.255.255.0' }) + ]), + E('div', { 'class': 'nm-form-group' }, [ + E('label', { 'class': 'nm-form-label' }, _('DHCP Start')), + E('input', { 'class': 'nm-input', 'type': 'number', 'id': 'dmz-dhcp-start', 'value': dmz.dhcp_start || 50 }) + ]), + E('div', { 'class': 'nm-form-group' }, [ + E('label', { 'class': 'nm-form-label' }, _('DHCP Pool Size')), + E('input', { 'class': 'nm-input', 'type': 'number', 'id': 'dmz-dhcp-limit', 'value': dmz.dhcp_limit || 80 }) + ]) + ]), + E('div', { 'style': 'margin-top:12px;' }, [ + buildToggle('dmz-isolated-toggle', '🚧', _('Isolate DMZ from LAN'), _('Block DMZ clients from reaching the LAN'), dmz.isolated !== false) + ]), + E('p', { 'class': 'nm-alert nm-alert-info', 'style': 'margin-top:12px;' }, _('DMZ clients will reach the WAN by default. Toggle isolation to prevent DMZ → LAN access.')) + ] + }); + + var container = E('div', { 'class': 'nm-container' }, [ + helpers.createNavigationTabs('dmz'), + hero, + wanSection, + lanSection, + dmzSection + ]); + + container.querySelectorAll('.nm-toggle-switch').forEach(function(toggle) { + toggle.addEventListener('click', function() { + this.classList.toggle('active'); + }); + }); + + this.bindActions(container); + return container; + }, + + bindActions: function(container) { + var saveBtn = container.querySelector('[data-action="dmz-save"]'); + var applyBtn = container.querySelector('[data-action="dmz-apply"]'); + var previewBtn = container.querySelector('[data-action="dmz-preview"]'); + + if (saveBtn) + saveBtn.addEventListener('click', ui.createHandlerFn(this, 'saveSettings', container)); + if (applyBtn) + applyBtn.addEventListener('click', ui.createHandlerFn(this, 'applyMode')); + if (previewBtn) + previewBtn.addEventListener('click', ui.createHandlerFn(helpers, helpers.showGeneratedConfig, 'dmz')); + }, + + saveSettings: function(container) { + var payload = { + wan_interface: (container.querySelector('#dmz-wan-if') || {}).value || 'eth1', + wan_protocol: (container.querySelector('#dmz-wan-proto') || {}).value || 'dhcp', + lan_interface: (container.querySelector('#dmz-lan-if') || {}).value || 'br-lan', + lan_ip: (container.querySelector('#dmz-lan-ip') || {}).value || '192.168.1.1', + lan_netmask: (container.querySelector('#dmz-lan-mask') || {}).value || '255.255.255.0', + dmz_interface: (container.querySelector('#dmz-if') || {}).value || 'eth2', + dmz_ip: (container.querySelector('#dmz-ip') || {}).value || '192.168.50.1', + dmz_netmask: (container.querySelector('#dmz-mask') || {}).value || '255.255.255.0', + dmz_dhcp_start: (container.querySelector('#dmz-dhcp-start') || {}).value || '50', + dmz_dhcp_limit: (container.querySelector('#dmz-dhcp-limit') || {}).value || '80', + dmz_isolated: helpers.isToggleActive(container.querySelector('#dmz-isolated-toggle')) ? 1 : 0 + }; + + return helpers.persistSettings('dmz', payload); + }, + + applyMode: function() { + ui.showModal(_('Applying DMZ mode'), [ + E('p', { 'class': 'spinning' }, _('Backing up current config and switching to DMZ mode...')) + ]); + + return api.applyMode('dmz').then(function(result) { + ui.hideModal(); + if (result && result.success) { + ui.addNotification(null, E('p', {}, _('DMZ mode applied. Confirm within rollback window.')), 'info'); + window.location.reload(); + } else { + ui.addNotification(null, E('p', {}, (result && result.error) || _('Unable to apply DMZ mode')), 'error'); + } + }).catch(function(err) { + ui.hideModal(); + ui.addNotification(null, E('p', {}, err.message || err), 'error'); + }); + } +}); diff --git a/luci-app-network-modes/root/etc/config/network-modes b/luci-app-network-modes/root/etc/config/network-modes index 984668f1..4fb2aa14 100644 --- a/luci-app-network-modes/root/etc/config/network-modes +++ b/luci-app-network-modes/root/etc/config/network-modes @@ -50,6 +50,22 @@ config mode 'router' option frontend_type 'nginx' list frontend_domains '' +config mode 'dmz' + option name 'Router + DMZ' + option description 'Router mode with isolated DMZ segment and firewall segmentation' + option enabled '0' + option wan_interface 'eth1' + option wan_protocol 'dhcp' + option lan_interface 'br-lan' + option lan_ip '192.168.1.1' + option lan_netmask '255.255.255.0' + option dmz_interface 'eth2' + option dmz_ip '192.168.50.1' + option dmz_netmask '255.255.255.0' + option dmz_dhcp_start '50' + option dmz_dhcp_limit '80' + option dmz_isolated '1' + config mode 'doublenat' option name 'Double NAT' option description 'Behind ISP box with isolated LAN' diff --git a/luci-app-network-modes/root/usr/libexec/rpcd/luci.network-modes b/luci-app-network-modes/root/usr/libexec/rpcd/luci.network-modes index 71cf3d26..a2e2472c 100755 --- a/luci-app-network-modes/root/usr/libexec/rpcd/luci.network-modes +++ b/luci-app-network-modes/root/usr/libexec/rpcd/luci.network-modes @@ -393,6 +393,59 @@ get_router_config() { json_dump } +get_dmz_config() { + json_init + + json_add_string "mode" "dmz" + json_add_string "name" "Router + DMZ" + json_add_string "description" "LAN avec NAT + segment DMZ isolé" + + local wan_iface=$(uci -q get network-modes.dmz.wan_interface || echo "eth1") + local wan_proto=$(uci -q get network-modes.dmz.wan_protocol || echo "dhcp") + local lan_iface=$(uci -q get network-modes.dmz.lan_interface || echo "br-lan") + local lan_ip=$(uci -q get network-modes.dmz.lan_ip || echo "192.168.1.1") + local lan_netmask=$(uci -q get network-modes.dmz.lan_netmask || echo "255.255.255.0") + local dmz_iface=$(uci -q get network-modes.dmz.dmz_interface || echo "eth2") + local dmz_ip=$(uci -q get network-modes.dmz.dmz_ip || echo "192.168.50.1") + local dmz_netmask=$(uci -q get network-modes.dmz.dmz_netmask || echo "255.255.255.0") + local dmz_start=$(uci -q get network-modes.dmz.dmz_dhcp_start || echo "50") + local dmz_limit=$(uci -q get network-modes.dmz.dmz_dhcp_limit || echo "80") + local dmz_isolated=$(uci -q get network-modes.dmz.dmz_isolated || echo "1") + + json_add_object "wan" + json_add_string "interface" "$wan_iface" + json_add_string "protocol" "$wan_proto" + json_add_array "protocols" + for proto in dhcp static pppoe; do + json_add_string "" "$proto" + done + json_close_array + json_close_object + + json_add_object "lan" + json_add_string "interface" "$lan_iface" + json_add_string "ip_address" "$lan_ip" + json_add_string "netmask" "$lan_netmask" + json_close_object + + json_add_object "dmz" + json_add_string "interface" "$dmz_iface" + json_add_string "ip_address" "$dmz_ip" + json_add_string "netmask" "$dmz_netmask" + json_add_int "dhcp_start" "$dmz_start" + json_add_int "dhcp_limit" "$dmz_limit" + json_add_boolean "isolated" "$([ "$dmz_isolated" = "1" ] && echo 1 || echo 0)" + json_close_object + + json_add_array "available_interfaces" + for iface in $(ls /sys/class/net/ | grep -v "^lo$"); do + json_add_string "" "$iface" + done + json_close_array + + json_dump +} + # Get travel router configuration get_travel_config() { json_init @@ -599,6 +652,88 @@ apply_mode() { apply_transparent_proxy_rules deploy_nginx_vhosts ;; + dmz) + local dmz_wan=$(uci -q get network-modes.dmz.wan_interface || echo "eth1") + local dmz_wan_proto=$(uci -q get network-modes.dmz.wan_protocol || echo "dhcp") + local dmz_lan_iface=$(uci -q get network-modes.dmz.lan_interface || echo "br-lan") + local dmz_lan_ip=$(uci -q get network-modes.dmz.lan_ip || echo "192.168.1.1") + local dmz_lan_netmask=$(uci -q get network-modes.dmz.lan_netmask || echo "255.255.255.0") + local dmz_iface=$(uci -q get network-modes.dmz.dmz_interface || echo "eth2") + local dmz_ip=$(uci -q get network-modes.dmz.dmz_ip || echo "192.168.50.1") + local dmz_netmask=$(uci -q get network-modes.dmz.dmz_netmask || echo "255.255.255.0") + local dmz_start=$(uci -q get network-modes.dmz.dmz_dhcp_start || echo "50") + local dmz_limit=$(uci -q get network-modes.dmz.dmz_dhcp_limit || echo "80") + local dmz_isolated_flag=$(uci -q get network-modes.dmz.dmz_isolated || echo "1") + + uci delete network.wan 2>/dev/null + uci set network.wan=interface + uci set network.wan.device="$dmz_wan" + uci set network.wan.proto="$dmz_wan_proto" + + uci set network.lan=interface + uci set network.lan.proto='static' + uci set network.lan.device="$dmz_lan_iface" + uci set network.lan.ipaddr="$dmz_lan_ip" + uci set network.lan.netmask="$dmz_lan_netmask" + + uci delete network.dmz 2>/dev/null + uci set network.dmz=interface + uci set network.dmz.proto='static' + uci set network.dmz.device="$dmz_iface" + uci set network.dmz.ipaddr="$dmz_ip" + uci set network.dmz.netmask="$dmz_netmask" + + uci set dhcp.lan=dhcp + uci set dhcp.lan.interface='lan' + uci set dhcp.lan.start='100' + uci set dhcp.lan.limit='150' + uci set dhcp.lan.leasetime='12h' + + uci delete dhcp.dmz 2>/dev/null + uci set dhcp.dmz=dhcp + uci set dhcp.dmz.interface='dmz' + uci set dhcp.dmz.start="$dmz_start" + uci set dhcp.dmz.limit="$dmz_limit" + uci set dhcp.dmz.leasetime='12h' + + uci -q delete firewall.lan + uci set firewall.lan=zone + uci set firewall.lan.name='lan' + uci set firewall.lan.network='lan' + uci set firewall.lan.input='ACCEPT' + uci set firewall.lan.output='ACCEPT' + uci set firewall.lan.forward='ACCEPT' + + uci -q delete firewall.wan + uci set firewall.wan=zone + uci set firewall.wan.name='wan' + uci set firewall.wan.network='wan' + uci set firewall.wan.input='REJECT' + uci set firewall.wan.output='ACCEPT' + uci set firewall.wan.forward='REJECT' + uci set firewall.wan.masq='1' + uci set firewall.wan.mtu_fix='1' + + uci -q delete firewall.dmz + uci set firewall.dmz=zone + uci set firewall.dmz.name='dmz' + uci set firewall.dmz.network='dmz' + uci set firewall.dmz.input='REJECT' + uci set firewall.dmz.output='ACCEPT' + uci set firewall.dmz.forward='REJECT' + + uci -q delete firewall.dmz_wan + uci set firewall.dmz_wan=forwarding + uci set firewall.dmz_wan.src='dmz' + uci set firewall.dmz_wan.dest='wan' + + uci -q delete firewall.dmz_lan 2>/dev/null + if [ "$dmz_isolated_flag" != "1" ]; then + uci set firewall.dmz_lan=forwarding + uci set firewall.dmz_lan.src='dmz' + uci set firewall.dmz_lan.dest='lan' + fi + ;; travel) local client_iface=$(uci -q get network-modes.travel.client_interface || echo "wlan1") local client_radio=$(uci -q get network-modes.travel.client_radio || echo "radio1") @@ -915,6 +1050,31 @@ update_settings() { [ -n "$dns_over_https" ] && uci set network-modes.router.dns_over_https="$dns_over_https" [ -n "$letsencrypt" ] && uci set network-modes.router.letsencrypt="$letsencrypt" ;; + dmz) + json_get_var wan_interface wan_interface + json_get_var wan_protocol wan_protocol + json_get_var lan_interface lan_interface + json_get_var lan_ip lan_ip + json_get_var lan_netmask lan_netmask + json_get_var dmz_interface dmz_interface + json_get_var dmz_ip dmz_ip + json_get_var dmz_netmask dmz_netmask + json_get_var dmz_dhcp_start dmz_dhcp_start + json_get_var dmz_dhcp_limit dmz_dhcp_limit + json_get_var dmz_isolated dmz_isolated + + [ -n "$wan_interface" ] && uci set network-modes.dmz.wan_interface="$wan_interface" + [ -n "$wan_protocol" ] && uci set network-modes.dmz.wan_protocol="$wan_protocol" + [ -n "$lan_interface" ] && uci set network-modes.dmz.lan_interface="$lan_interface" + [ -n "$lan_ip" ] && uci set network-modes.dmz.lan_ip="$lan_ip" + [ -n "$lan_netmask" ] && uci set network-modes.dmz.lan_netmask="$lan_netmask" + [ -n "$dmz_interface" ] && uci set network-modes.dmz.dmz_interface="$dmz_interface" + [ -n "$dmz_ip" ] && uci set network-modes.dmz.dmz_ip="$dmz_ip" + [ -n "$dmz_netmask" ] && uci set network-modes.dmz.dmz_netmask="$dmz_netmask" + [ -n "$dmz_dhcp_start" ] && uci set network-modes.dmz.dmz_dhcp_start="$dmz_dhcp_start" + [ -n "$dmz_dhcp_limit" ] && uci set network-modes.dmz.dmz_dhcp_limit="$dmz_dhcp_limit" + [ -n "$dmz_isolated" ] && uci set network-modes.dmz.dmz_isolated="$dmz_isolated" + ;; travel) json_get_var client_interface client_interface json_get_var client_radio client_radio @@ -2016,6 +2176,9 @@ get_available_modes() { add_mode "router" "Router" "🌐" "Mode routeur complet avec NAT, DHCP et firewall" \ "NAT activé" "Serveur DHCP" "Firewall (zones WAN/LAN)" "Proxy optionnel" + add_mode "dmz" "Router + DMZ" "🛡️" "Mode routeur avec réseau DMZ isolé et firewall segmenté" \ + "Réseau DMZ séparé" "DHCP optionnel pour DMZ" "Isolation LAN/DMZ" "Forwarding WAN contrôlé" + add_mode "doublenat" "Double NAT" "🔁" "Derrière box opérateur avec réseau isolé" \ "WAN DHCP client" "Deuxième LAN privé" "Isolation invitée" "UPnP désactivé" @@ -2051,7 +2214,7 @@ set_mode() { # Validate mode case "$target_mode" in - router|multiwan|doublenat|accesspoint|relay|vpnrelay|bridge|sniffer|travel) + router|dmz|multiwan|doublenat|accesspoint|relay|vpnrelay|bridge|sniffer|travel) ;; *) json_add_boolean "success" 0 @@ -2104,6 +2267,9 @@ preview_changes() { # Changes array json_add_array "changes" + local dmz_ip_preview + dmz_ip_preview=$(uci -q get network-modes.dmz.dmz_ip || echo "192.168.50.1") + case "$pending_mode" in router) json_add_object @@ -2119,6 +2285,24 @@ preview_changes() { json_add_string "change" "Zones: WAN, LAN, forwarding rules" json_close_object ;; + dmz) + json_add_object + json_add_string "file" "/etc/config/network" + json_add_string "change" "WAN interface + LAN bridge reconfiguré" + json_close_object + json_add_object + json_add_string "file" "/etc/config/network" + json_add_string "change" "Nouveau segment DMZ (${dmz_ip_preview}/24)" + json_close_object + json_add_object + json_add_string "file" "/etc/config/dhcp" + json_add_string "change" "Serveur DHCP dédié pour DMZ" + json_close_object + json_add_object + json_add_string "file" "/etc/config/firewall" + json_add_string "change" "Zone DMZ isolée (forwarding uniquement vers WAN)" + json_close_object + ;; accesspoint) json_add_object json_add_string "file" "/etc/config/network" @@ -2376,7 +2560,7 @@ rollback() { # Main dispatcher case "$1" in list) - echo '{"status":{},"modes":{},"get_current_mode":{},"get_available_modes":{},"set_mode":{"mode":"str"},"preview_changes":{},"apply_mode":{},"confirm_mode":{},"rollback":{},"sniffer_config":{},"ap_config":{},"relay_config":{},"router_config":{},"travel_config":{},"doublenat_config":{},"multiwan_config":{},"vpnrelay_config":{},"travel_scan_networks":{},"update_settings":{"mode":"str"},"generate_wireguard_keys":{},"apply_wireguard_config":{},"apply_mtu_clamping":{},"enable_tcp_bbr":{},"add_vhost":{"domain":"str","backend":"str","port":"int","ssl":"bool"},"generate_config":{"mode":"str"},"validate_pcap_filter":{"filter":"str"},"cleanup_old_pcaps":{}}' + echo '{"status":{},"modes":{},"get_current_mode":{},"get_available_modes":{},"set_mode":{"mode":"str"},"preview_changes":{},"apply_mode":{},"confirm_mode":{},"rollback":{},"sniffer_config":{},"ap_config":{},"relay_config":{},"router_config":{},"dmz_config":{},"travel_config":{},"doublenat_config":{},"multiwan_config":{},"vpnrelay_config":{},"travel_scan_networks":{},"update_settings":{"mode":"str"},"generate_wireguard_keys":{},"apply_wireguard_config":{},"apply_mtu_clamping":{},"enable_tcp_bbr":{},"add_vhost":{"domain":"str","backend":"str","port":"int","ssl":"bool"},"generate_config":{"mode":"str"},"validate_pcap_filter":{"filter":"str"},"cleanup_old_pcaps":{}}' ;; call) case "$2" in @@ -2419,6 +2603,9 @@ case "$1" in router_config) get_router_config ;; + dmz_config) + get_dmz_config + ;; travel_config) get_travel_config ;; @@ -2479,3 +2666,63 @@ case "$1" in esac ;; esac + dmz) + local wan_if=$(uci -q get network-modes.dmz.wan_interface || echo "eth1") + local wan_proto=$(uci -q get network-modes.dmz.wan_protocol || echo "dhcp") + local lan_iface=$(uci -q get network-modes.dmz.lan_interface || echo "br-lan") + local lan_ip=$(uci -q get network-modes.dmz.lan_ip || echo "192.168.1.1") + local lan_mask=$(uci -q get network-modes.dmz.lan_netmask || echo "255.255.255.0") + local dmz_iface=$(uci -q get network-modes.dmz.dmz_interface || echo "eth2") + local dmz_ip=$(uci -q get network-modes.dmz.dmz_ip || echo "192.168.50.1") + local dmz_mask=$(uci -q get network-modes.dmz.dmz_netmask || echo "255.255.255.0") + config="# DMZ Mode Configuration +# /etc/config/network + +config interface 'wan' + option device '$wan_if' + option proto '$wan_proto' + +config interface 'lan' + option device '$lan_iface' + option proto 'static' + option ipaddr '$lan_ip' + option netmask '$lan_mask' + +config interface 'dmz' + option device '$dmz_iface' + option proto 'static' + option ipaddr '$dmz_ip' + option netmask '$dmz_mask' + +# /etc/config/firewall +config zone + option name 'lan' + option input 'ACCEPT' + option output 'ACCEPT' + option forward 'ACCEPT' + list network 'lan' + +config zone + option name 'dmz' + option input 'REJECT' + option output 'ACCEPT' + option forward 'REJECT' + list network 'dmz' + +config zone + option name 'wan' + option input 'REJECT' + option output 'ACCEPT' + option forward 'REJECT' + option masq '1' + option mtu_fix '1' + list network 'wan' + +config forwarding + option src 'lan' + option dest 'wan' + +config forwarding + option src 'dmz' + option dest 'wan'" + ;; diff --git a/luci-app-network-modes/root/usr/share/luci/menu.d/luci-app-network-modes.json b/luci-app-network-modes/root/usr/share/luci/menu.d/luci-app-network-modes.json index 707c8b60..f5fa9349 100644 --- a/luci-app-network-modes/root/usr/share/luci/menu.d/luci-app-network-modes.json +++ b/luci-app-network-modes/root/usr/share/luci/menu.d/luci-app-network-modes.json @@ -20,6 +20,11 @@ "order": 30, "action": {"type": "view", "path": "network-modes/router"} }, + "admin/secubox/network/modes/dmz": { + "title": "DMZ Mode", + "order": 33, + "action": {"type": "view", "path": "network-modes/dmz"} + }, "admin/secubox/network/modes/multiwan": { "title": "Multi-WAN Mode", "order": 35,