|
@@ -4173,6 +4173,183 @@ function toggleMaximizeFilterModal() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const FW_NATIVE_CHAINS = {
|
|
|
|
|
+ filter: [
|
|
|
|
|
+ { id: 'forward', label: 'FORWARD (routed through router)' },
|
|
|
|
|
+ { id: 'input', label: 'INPUT (destined to this router)' },
|
|
|
|
|
+ { id: 'output', label: 'OUTPUT (generated by this router)' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ nat: [
|
|
|
|
|
+ { id: 'postrouting', label: 'POSTROUTING (outgoing after routing — SNAT / MASQUERADE)' },
|
|
|
|
|
+ { id: 'prerouting', label: 'PREROUTING (incoming before routing — DNAT / REDIRECT)' },
|
|
|
|
|
+ { id: 'output', label: 'OUTPUT (locally generated — DNAT / REDIRECT)' },
|
|
|
|
|
+ { id: 'input', label: 'INPUT (destined to this machine — SNAT)' }
|
|
|
|
|
+ ],
|
|
|
|
|
+ mangle: [
|
|
|
|
|
+ { id: 'prerouting', label: 'PREROUTING (incoming before routing)' },
|
|
|
|
|
+ { id: 'input', label: 'INPUT (destined to this machine)' },
|
|
|
|
|
+ { id: 'forward', label: 'FORWARD (routed through router)' },
|
|
|
|
|
+ { id: 'output', label: 'OUTPUT (generated by this machine)' },
|
|
|
|
|
+ { id: 'postrouting', label: 'POSTROUTING (outgoing after routing)' }
|
|
|
|
|
+ ]
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+function populateChainDropdown(table, currentChain) {
|
|
|
|
|
+ const sel = document.getElementById(`${table}-modal-chain-select`);
|
|
|
|
|
+ if (!sel) return;
|
|
|
|
|
+ const cur = (currentChain || '').trim();
|
|
|
|
|
+ const lower = cur.toLowerCase();
|
|
|
|
|
+
|
|
|
|
|
+ const rulesKey = table === 'filter' ? 'filterRules' : table === 'nat' ? 'natRules' : 'mangleRules';
|
|
|
|
|
+ const rules = state[rulesKey] || [];
|
|
|
|
|
+ const nativeIds = FW_NATIVE_CHAINS[table].map(c => c.id);
|
|
|
|
|
+ const customChains = new Set();
|
|
|
|
|
+ rules.forEach(r => {
|
|
|
|
|
+ if (r.chain) {
|
|
|
|
|
+ const c = r.chain.trim();
|
|
|
|
|
+ const l = c.toLowerCase();
|
|
|
|
|
+ if (!nativeIds.includes(l) && l !== 'srcnat' && l !== 'dstnat' && !['forward', 'input', 'output', 'prerouting', 'postrouting'].includes(l)) {
|
|
|
|
|
+ customChains.add(c);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const isCurrentCustom = cur && !nativeIds.includes(lower) && lower !== 'srcnat' && lower !== 'dstnat' && !['forward', 'input', 'output', 'prerouting', 'postrouting'].includes(lower);
|
|
|
|
|
+ if (isCurrentCustom) {
|
|
|
|
|
+ customChains.add(cur);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let html = '';
|
|
|
|
|
+ FW_NATIVE_CHAINS[table].forEach(c => {
|
|
|
|
|
+ html += `<option value="${c.id}">${escapeHtml(c.label)}</option>`;
|
|
|
|
|
+ });
|
|
|
|
|
+ if (customChains.size > 0) {
|
|
|
|
|
+ html += `<optgroup label="Custom Chains">`;
|
|
|
|
|
+ customChains.forEach(c => {
|
|
|
|
|
+ html += `<option value="${escapeHtml(c)}">Chain: ${escapeHtml(c)} (custom)</option>`;
|
|
|
|
|
+ });
|
|
|
|
|
+ html += `</optgroup>`;
|
|
|
|
|
+ }
|
|
|
|
|
+ html += `<option value="__custom__">+ Custom Chain...</option>`;
|
|
|
|
|
+ sel.innerHTML = html;
|
|
|
|
|
+
|
|
|
|
|
+ if (isCurrentCustom) {
|
|
|
|
|
+ sel.value = cur;
|
|
|
|
|
+ } else if (nativeIds.includes(lower)) {
|
|
|
|
|
+ sel.value = lower;
|
|
|
|
|
+ } else if (table === 'nat' && (lower === 'srcnat' || lower === 'dstnat')) {
|
|
|
|
|
+ sel.value = lower === 'dstnat' ? 'prerouting' : 'postrouting';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ sel.value = nativeIds[0];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const customWrap = document.getElementById(`${table}-modal-custom-wrap`);
|
|
|
|
|
+ const customInput = document.getElementById(`${table}-modal-custom-input`);
|
|
|
|
|
+ if (isCurrentCustom || sel.value === '__custom__') {
|
|
|
|
|
+ if (customWrap) customWrap.classList.remove('hidden');
|
|
|
|
|
+ if (customInput) customInput.value = cur;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (customWrap) customWrap.classList.add('hidden');
|
|
|
|
|
+ if (customInput) customInput.value = '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const chainInput = document.getElementById(`${table}-modal-chain`);
|
|
|
|
|
+ if (chainInput) chainInput.value = cur || sel.value;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function syncFilterChainFromSelect() {
|
|
|
|
|
+ const sel = document.getElementById('filter-modal-chain-select');
|
|
|
|
|
+ const customWrap = document.getElementById('filter-modal-custom-wrap');
|
|
|
|
|
+ const customInput = document.getElementById('filter-modal-custom-input');
|
|
|
|
|
+ const chainInput = document.getElementById('filter-modal-chain');
|
|
|
|
|
+ if (!sel || !chainInput) return;
|
|
|
|
|
+
|
|
|
|
|
+ if (sel.value === '__custom__') {
|
|
|
|
|
+ if (customWrap) customWrap.classList.remove('hidden');
|
|
|
|
|
+ if (customInput) {
|
|
|
|
|
+ customInput.value = '';
|
|
|
|
|
+ customInput.focus();
|
|
|
|
|
+ }
|
|
|
|
|
+ chainInput.value = '';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (customWrap) customWrap.classList.add('hidden');
|
|
|
|
|
+ chainInput.value = sel.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ handleFilterChainChange();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function syncFilterCustomChainInput() {
|
|
|
|
|
+ const customInput = document.getElementById('filter-modal-custom-input');
|
|
|
|
|
+ const chainInput = document.getElementById('filter-modal-chain');
|
|
|
|
|
+ if (customInput && chainInput) {
|
|
|
|
|
+ chainInput.value = customInput.value.trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ handleFilterChainChange();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleFilterChainChange() {
|
|
|
|
|
+ const chainInput = document.getElementById('filter-modal-chain');
|
|
|
|
|
+ const chain = (chainInput?.value || 'forward').trim().toLowerCase();
|
|
|
|
|
+
|
|
|
|
|
+ const sel = document.getElementById('filter-modal-chain-select');
|
|
|
|
|
+ if (sel && sel.value !== chain && sel.value !== '__custom__') {
|
|
|
|
|
+ if (['forward', 'input', 'output'].includes(chain)) {
|
|
|
|
|
+ sel.value = chain;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const hintEl = document.getElementById('filter-chain-hint');
|
|
|
|
|
+ if (hintEl) {
|
|
|
|
|
+ if (chain === 'forward') {
|
|
|
|
|
+ hintEl.textContent = 'FORWARD: packets routed through this router (accepts both In and Out interfaces).';
|
|
|
|
|
+ } else if (chain === 'input') {
|
|
|
|
|
+ hintEl.textContent = 'INPUT: packets destined to this router (In-Interface only; Out-Interface is invalid in iptables).';
|
|
|
|
|
+ } else if (chain === 'output') {
|
|
|
|
|
+ hintEl.textContent = 'OUTPUT: packets generated by this router (Out-Interface only; In-Interface is invalid in iptables).';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ hintEl.textContent = `Custom chain "${chain}": reached via an explicit JUMP from a built-in chain.`;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const rowIn = document.getElementById('fw-row-iniface');
|
|
|
|
|
+ const rowInList = document.getElementById('fw-row-inifacelist');
|
|
|
|
|
+ const rowOut = document.getElementById('fw-row-outiface');
|
|
|
|
|
+ const rowOutList = document.getElementById('fw-row-outifacelist');
|
|
|
|
|
+
|
|
|
|
|
+ if (chain === 'input') {
|
|
|
|
|
+ if (rowIn) rowIn.classList.remove('hidden');
|
|
|
|
|
+ if (rowInList) rowInList.classList.remove('hidden');
|
|
|
|
|
+ if (rowOut) rowOut.classList.add('hidden');
|
|
|
|
|
+ if (rowOutList) rowOutList.classList.add('hidden');
|
|
|
|
|
+ } else if (chain === 'output') {
|
|
|
|
|
+ if (rowIn) rowIn.classList.add('hidden');
|
|
|
|
|
+ if (rowInList) rowInList.classList.add('hidden');
|
|
|
|
|
+ if (rowOut) rowOut.classList.remove('hidden');
|
|
|
|
|
+ if (rowOutList) rowOutList.classList.remove('hidden');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (rowIn) rowIn.classList.remove('hidden');
|
|
|
|
|
+ if (rowInList) rowInList.classList.remove('hidden');
|
|
|
|
|
+ if (rowOut) rowOut.classList.remove('hidden');
|
|
|
|
|
+ if (rowOutList) rowOutList.classList.remove('hidden');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ checkFilterActionChainCompatibility();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function checkFilterActionChainCompatibility() {
|
|
|
|
|
+ const chain = (document.getElementById('filter-modal-chain')?.value || 'forward').trim().toLowerCase();
|
|
|
|
|
+ const warnBox = document.getElementById('filter-action-incompatible-warn');
|
|
|
|
|
+ const warnMsg = document.getElementById('filter-action-incompatible-msg');
|
|
|
|
|
+ if (!warnBox || !warnMsg) return;
|
|
|
|
|
+
|
|
|
|
|
+ if (chain === 'prerouting' || chain === 'postrouting') {
|
|
|
|
|
+ warnMsg.textContent = `Chain "${chain.toUpperCase()}" is not available in the Filter table. Filter only supports INPUT, FORWARD, OUTPUT or custom chains.`;
|
|
|
|
|
+ warnBox.classList.remove('hidden');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ warnBox.classList.add('hidden');
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function openAddFilterModal() {
|
|
function openAddFilterModal() {
|
|
|
const tos = document.getElementById('fw-val-tos'); if (tos) tos.value = '';
|
|
const tos = document.getElementById('fw-val-tos'); if (tos) tos.value = '';
|
|
|
switchFwRuleTab('general');
|
|
switchFwRuleTab('general');
|
|
@@ -4184,6 +4361,8 @@ function openAddFilterModal() {
|
|
|
document.getElementById('filter-modal-enabled').checked = true;
|
|
document.getElementById('filter-modal-enabled').checked = true;
|
|
|
document.getElementById('filter-modal-comment').value = '';
|
|
document.getElementById('filter-modal-comment').value = '';
|
|
|
document.getElementById('filter-modal-chain').value = 'forward';
|
|
document.getElementById('filter-modal-chain').value = 'forward';
|
|
|
|
|
+ populateChainDropdown('filter', 'forward');
|
|
|
|
|
+ handleFilterChainChange();
|
|
|
document.getElementById('filter-modal-action').value = 'accept';
|
|
document.getElementById('filter-modal-action').value = 'accept';
|
|
|
document.getElementById('filter-modal-log').checked = false;
|
|
document.getElementById('filter-modal-log').checked = false;
|
|
|
|
|
|
|
@@ -4226,7 +4405,10 @@ function openEditFilterModal(id) {
|
|
|
document.getElementById('filter-modal-family').value = String(r.ip_version || 4);
|
|
document.getElementById('filter-modal-family').value = String(r.ip_version || 4);
|
|
|
document.getElementById('filter-modal-enabled').checked = !r.disabled;
|
|
document.getElementById('filter-modal-enabled').checked = !r.disabled;
|
|
|
document.getElementById('filter-modal-comment').value = r.comment || '';
|
|
document.getElementById('filter-modal-comment').value = r.comment || '';
|
|
|
- document.getElementById('filter-modal-chain').value = r.chain || 'forward';
|
|
|
|
|
|
|
+ const filterChain = r.chain || 'forward';
|
|
|
|
|
+ document.getElementById('filter-modal-chain').value = filterChain;
|
|
|
|
|
+ populateChainDropdown('filter', filterChain);
|
|
|
|
|
+ handleFilterChainChange();
|
|
|
setFirewallActionSelection('filter', r.action || 'accept');
|
|
setFirewallActionSelection('filter', r.action || 'accept');
|
|
|
document.getElementById('filter-modal-log').checked = !!r.log;
|
|
document.getElementById('filter-modal-log').checked = !!r.log;
|
|
|
|
|
|
|
@@ -4439,7 +4621,14 @@ async function applyFilterRule(closeAfter = false) {
|
|
|
const id = document.getElementById('filter-modal-id').value;
|
|
const id = document.getElementById('filter-modal-id').value;
|
|
|
if (!reviewLegacyFirewallSettings('filter', id)) return;
|
|
if (!reviewLegacyFirewallSettings('filter', id)) return;
|
|
|
let selectedDays; try { selectedDays = getFirewallDays('fw'); } catch(error) { showToast('Error', error.message, 'error'); return; }
|
|
let selectedDays; try { selectedDays = getFirewallDays('fw'); } catch(error) { showToast('Error', error.message, 'error'); return; }
|
|
|
- const chain = document.getElementById('filter-modal-chain').value;
|
|
|
|
|
|
|
+ const chain = (document.getElementById('filter-modal-chain').value || '').trim();
|
|
|
|
|
+ const chainLower = chain.toLowerCase();
|
|
|
|
|
+ if (!chain) { switchFwRuleTab('general'); showToast('Filter Rule', 'A valid chain is required.', 'error'); document.getElementById('filter-modal-chain').focus(); return; }
|
|
|
|
|
+ if (chainLower === 'prerouting' || chainLower === 'postrouting') {
|
|
|
|
|
+ switchFwRuleTab('general');
|
|
|
|
|
+ showToast('Invalid Chain', `Chain "${chain.toUpperCase()}" is not available in the Filter table in iptables.`, 'error');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
const action = document.getElementById('filter-modal-action').value;
|
|
const action = document.getElementById('filter-modal-action').value;
|
|
|
const comment = document.getElementById('filter-modal-comment').value.trim();
|
|
const comment = document.getElementById('filter-modal-comment').value.trim();
|
|
|
const enabled = document.getElementById('filter-modal-enabled').checked;
|
|
const enabled = document.getElementById('filter-modal-enabled').checked;
|
|
@@ -4458,10 +4647,17 @@ async function applyFilterRule(closeAfter = false) {
|
|
|
switchFwRuleTab('general'); toggleFwField('proto', true);
|
|
switchFwRuleTab('general'); toggleFwField('proto', true);
|
|
|
showToast('Select protocol', 'Port matches require TCP or UDP without protocol inversion.', 'error'); document.getElementById('fw-val-proto').focus(); return;
|
|
showToast('Select protocol', 'Port matches require TCP or UDP without protocol inversion.', 'error'); document.getElementById('fw-val-proto').focus(); return;
|
|
|
}
|
|
}
|
|
|
- const iniface = getFwFieldVal('iniface', true);
|
|
|
|
|
- const outiface = getFwFieldVal('outiface', true);
|
|
|
|
|
- const inifacelist = getFwFieldVal('inifacelist', true);
|
|
|
|
|
- const outifacelist = getFwFieldVal('outifacelist', true);
|
|
|
|
|
|
|
+ let iniface = getFwFieldVal('iniface', true);
|
|
|
|
|
+ let outiface = getFwFieldVal('outiface', true);
|
|
|
|
|
+ let inifacelist = getFwFieldVal('inifacelist', true);
|
|
|
|
|
+ let outifacelist = getFwFieldVal('outifacelist', true);
|
|
|
|
|
+ if (chainLower === 'input') {
|
|
|
|
|
+ outiface = '';
|
|
|
|
|
+ outifacelist = '';
|
|
|
|
|
+ } else if (chainLower === 'output') {
|
|
|
|
|
+ iniface = '';
|
|
|
|
|
+ inifacelist = '';
|
|
|
|
|
+ }
|
|
|
const pktmark = getFwFieldVal('pktmark', true);
|
|
const pktmark = getFwFieldVal('pktmark', true);
|
|
|
const connmark = getFwFieldVal('connmark', true);
|
|
const connmark = getFwFieldVal('connmark', true);
|
|
|
const routingmark = getFwFieldVal('routingmark', true);
|
|
const routingmark = getFwFieldVal('routingmark', true);
|
|
@@ -4913,10 +5109,130 @@ function toggleMaximizeNATModal() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function syncNATChainFromSelect() {
|
|
|
|
|
+ const sel = document.getElementById('nat-modal-chain-select');
|
|
|
|
|
+ const customWrap = document.getElementById('nat-modal-custom-wrap');
|
|
|
|
|
+ const customInput = document.getElementById('nat-modal-custom-input');
|
|
|
|
|
+ const chainInput = document.getElementById('nat-modal-chain');
|
|
|
|
|
+ if (!sel || !chainInput) return;
|
|
|
|
|
+
|
|
|
|
|
+ if (sel.value === '__custom__') {
|
|
|
|
|
+ if (customWrap) customWrap.classList.remove('hidden');
|
|
|
|
|
+ if (customInput) {
|
|
|
|
|
+ customInput.value = '';
|
|
|
|
|
+ customInput.focus();
|
|
|
|
|
+ }
|
|
|
|
|
+ chainInput.value = '';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (customWrap) customWrap.classList.add('hidden');
|
|
|
|
|
+ chainInput.value = sel.value;
|
|
|
|
|
+
|
|
|
|
|
+ const actEl = document.getElementById('nat-modal-action');
|
|
|
|
|
+ if (actEl) {
|
|
|
|
|
+ const curAct = actEl.value;
|
|
|
|
|
+ if (sel.value === 'prerouting' && ['masquerade', 'src-nat'].includes(curAct)) {
|
|
|
|
|
+ actEl.value = 'dst-nat';
|
|
|
|
|
+ } else if (sel.value === 'postrouting' && ['dst-nat', 'redirect'].includes(curAct)) {
|
|
|
|
|
+ actEl.value = 'masquerade';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ handleNATChainChange();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function syncNATCustomChainInput() {
|
|
|
|
|
+ const customInput = document.getElementById('nat-modal-custom-input');
|
|
|
|
|
+ const chainInput = document.getElementById('nat-modal-chain');
|
|
|
|
|
+ if (customInput && chainInput) {
|
|
|
|
|
+ chainInput.value = customInput.value.trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ handleNATChainChange();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function handleNATChainChange() {
|
|
function handleNATChainChange() {
|
|
|
|
|
+ const chainInput = document.getElementById('nat-modal-chain');
|
|
|
|
|
+ const rawChain = (chainInput?.value || 'postrouting').trim();
|
|
|
|
|
+ const chain = nativeNATChain(rawChain).toLowerCase();
|
|
|
|
|
+
|
|
|
|
|
+ const sel = document.getElementById('nat-modal-chain-select');
|
|
|
|
|
+ if (sel && sel.value !== chain && sel.value !== '__custom__') {
|
|
|
|
|
+ if (['postrouting', 'prerouting', 'output', 'input'].includes(chain)) {
|
|
|
|
|
+ sel.value = chain;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const hintEl = document.getElementById('nat-chain-hint');
|
|
|
|
|
+ if (hintEl) {
|
|
|
|
|
+ if (chain === 'prerouting') {
|
|
|
|
|
+ hintEl.textContent = 'PREROUTING: destination NAT (DNAT / REDIRECT) before routing decision (In-Interface only).';
|
|
|
|
|
+ } else if (chain === 'postrouting') {
|
|
|
|
|
+ hintEl.textContent = 'POSTROUTING: source NAT (SNAT / MASQUERADE) after routing decision (Out-Interface only).';
|
|
|
|
|
+ } else if (chain === 'output') {
|
|
|
|
|
+ hintEl.textContent = 'OUTPUT: destination NAT for locally generated packets (Out-Interface only).';
|
|
|
|
|
+ } else if (chain === 'input') {
|
|
|
|
|
+ hintEl.textContent = 'INPUT: source NAT for packets destined to local sockets (In-Interface only).';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ hintEl.textContent = `Custom chain "${rawChain}": user-defined NAT chain reached via JUMP.`;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const rowIn = document.getElementById('nat-row-iniface');
|
|
|
|
|
+ const rowInList = document.getElementById('nat-row-inifacelist');
|
|
|
|
|
+ const rowOut = document.getElementById('nat-row-outiface');
|
|
|
|
|
+ const rowOutList = document.getElementById('nat-row-outifacelist');
|
|
|
|
|
+
|
|
|
|
|
+ if (chain === 'prerouting' || chain === 'input') {
|
|
|
|
|
+ if (rowIn) rowIn.classList.remove('hidden');
|
|
|
|
|
+ if (rowInList) rowInList.classList.remove('hidden');
|
|
|
|
|
+ if (rowOut) rowOut.classList.add('hidden');
|
|
|
|
|
+ if (rowOutList) rowOutList.classList.add('hidden');
|
|
|
|
|
+ } else if (chain === 'postrouting' || chain === 'output') {
|
|
|
|
|
+ if (rowIn) rowIn.classList.add('hidden');
|
|
|
|
|
+ if (rowInList) rowInList.classList.add('hidden');
|
|
|
|
|
+ if (rowOut) rowOut.classList.remove('hidden');
|
|
|
|
|
+ if (rowOutList) rowOutList.classList.remove('hidden');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (rowIn) rowIn.classList.remove('hidden');
|
|
|
|
|
+ if (rowInList) rowInList.classList.remove('hidden');
|
|
|
|
|
+ if (rowOut) rowOut.classList.remove('hidden');
|
|
|
|
|
+ if (rowOutList) rowOutList.classList.remove('hidden');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ checkNATActionChainCompatibility();
|
|
|
toggleNATActionFields();
|
|
toggleNATActionFields();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function checkNATActionChainCompatibility() {
|
|
|
|
|
+ const rawChain = (document.getElementById('nat-modal-chain')?.value || 'postrouting').trim();
|
|
|
|
|
+ const chain = nativeNATChain(rawChain).toLowerCase();
|
|
|
|
|
+ const action = (document.getElementById('nat-modal-action')?.value || '').toLowerCase();
|
|
|
|
|
+ const warnBox = document.getElementById('nat-action-incompatible-warn');
|
|
|
|
|
+ const warnMsg = document.getElementById('nat-action-incompatible-msg');
|
|
|
|
|
+ if (!warnBox || !warnMsg) return;
|
|
|
|
|
+
|
|
|
|
|
+ let msg = '';
|
|
|
|
|
+ if (['dst-nat', 'redirect'].includes(action)) {
|
|
|
|
|
+ if (chain !== 'prerouting' && chain !== 'output') {
|
|
|
|
|
+ msg = `${action === 'dst-nat' ? 'DNAT' : 'REDIRECT'} is only valid in PREROUTING or OUTPUT chains in iptables (current chain: ${chain.toUpperCase()}).`;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (action === 'masquerade') {
|
|
|
|
|
+ if (chain !== 'postrouting') {
|
|
|
|
|
+ msg = `MASQUERADE is only valid in the POSTROUTING chain in iptables (current chain: ${chain.toUpperCase()}).`;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (action === 'src-nat') {
|
|
|
|
|
+ if (chain !== 'postrouting' && chain !== 'input') {
|
|
|
|
|
+ msg = `SNAT is only valid in POSTROUTING or INPUT chains in iptables (current chain: ${chain.toUpperCase()}).`;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (msg) {
|
|
|
|
|
+ warnMsg.textContent = msg;
|
|
|
|
|
+ warnBox.classList.remove('hidden');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ warnBox.classList.add('hidden');
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function toggleNATActionFields() {
|
|
function toggleNATActionFields() {
|
|
|
const act = document.getElementById('nat-modal-action')?.value || 'masquerade';
|
|
const act = document.getElementById('nat-modal-action')?.value || 'masquerade';
|
|
|
const rowAddr = document.getElementById('nat-row-toaddr');
|
|
const rowAddr = document.getElementById('nat-row-toaddr');
|
|
@@ -4950,6 +5266,7 @@ function toggleNATActionFields() {
|
|
|
|
|
|
|
|
function handleNATActionChange() {
|
|
function handleNATActionChange() {
|
|
|
toggleNATActionFields();
|
|
toggleNATActionFields();
|
|
|
|
|
+ checkNATActionChainCompatibility();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function toggleNATLogPrefix() {
|
|
function toggleNATLogPrefix() {
|
|
@@ -4968,6 +5285,8 @@ function openAddNATModal() {
|
|
|
document.getElementById('nat-modal-enabled').checked = true;
|
|
document.getElementById('nat-modal-enabled').checked = true;
|
|
|
document.getElementById('nat-modal-comment').value = '';
|
|
document.getElementById('nat-modal-comment').value = '';
|
|
|
document.getElementById('nat-modal-chain').value = 'postrouting';
|
|
document.getElementById('nat-modal-chain').value = 'postrouting';
|
|
|
|
|
+ populateChainDropdown('nat', 'postrouting');
|
|
|
|
|
+ handleNATChainChange();
|
|
|
document.getElementById('nat-modal-action').value = 'masquerade';
|
|
document.getElementById('nat-modal-action').value = 'masquerade';
|
|
|
document.getElementById('nat-modal-toaddr').value = '';
|
|
document.getElementById('nat-modal-toaddr').value = '';
|
|
|
document.getElementById('nat-modal-toport').value = '';
|
|
document.getElementById('nat-modal-toport').value = '';
|
|
@@ -5012,7 +5331,10 @@ function openEditNATModal(id) {
|
|
|
document.getElementById('nat-modal-family').value = String(r.ip_version || 4);
|
|
document.getElementById('nat-modal-family').value = String(r.ip_version || 4);
|
|
|
document.getElementById('nat-modal-enabled').checked = !r.disabled;
|
|
document.getElementById('nat-modal-enabled').checked = !r.disabled;
|
|
|
document.getElementById('nat-modal-comment').value = r.comment || '';
|
|
document.getElementById('nat-modal-comment').value = r.comment || '';
|
|
|
- document.getElementById('nat-modal-chain').value = nativeNATChain(r.chain || 'postrouting');
|
|
|
|
|
|
|
+ const natChain = nativeNATChain(r.chain || 'postrouting');
|
|
|
|
|
+ document.getElementById('nat-modal-chain').value = natChain;
|
|
|
|
|
+ populateChainDropdown('nat', natChain);
|
|
|
|
|
+ handleNATChainChange();
|
|
|
setFirewallActionSelection('nat', r.action || 'accept');
|
|
setFirewallActionSelection('nat', r.action || 'accept');
|
|
|
document.getElementById('nat-modal-toaddr').value = r.to_addresses || '';
|
|
document.getElementById('nat-modal-toaddr').value = r.to_addresses || '';
|
|
|
document.getElementById('nat-modal-toport').value = r.to_ports || '';
|
|
document.getElementById('nat-modal-toport').value = r.to_ports || '';
|
|
@@ -5153,7 +5475,8 @@ async function applyNATRule(closeAfter = false) {
|
|
|
const id = document.getElementById('nat-modal-id').value;
|
|
const id = document.getElementById('nat-modal-id').value;
|
|
|
if (!reviewLegacyFirewallSettings('nat', id)) return;
|
|
if (!reviewLegacyFirewallSettings('nat', id)) return;
|
|
|
let selectedDays; try { selectedDays = getFirewallDays('nat'); } catch(error) { showToast('Error', error.message, 'error'); return; }
|
|
let selectedDays; try { selectedDays = getFirewallDays('nat'); } catch(error) { showToast('Error', error.message, 'error'); return; }
|
|
|
- const chain = document.getElementById('nat-modal-chain').value;
|
|
|
|
|
|
|
+ const chain = (document.getElementById('nat-modal-chain').value || '').trim();
|
|
|
|
|
+ const chainLower = nativeNATChain(chain).toLowerCase();
|
|
|
const action = document.getElementById('nat-modal-action').value;
|
|
const action = document.getElementById('nat-modal-action').value;
|
|
|
const comment = document.getElementById('nat-modal-comment').value.trim();
|
|
const comment = document.getElementById('nat-modal-comment').value.trim();
|
|
|
const enabled = document.getElementById('nat-modal-enabled').checked;
|
|
const enabled = document.getElementById('nat-modal-enabled').checked;
|
|
@@ -5168,10 +5491,19 @@ async function applyNATRule(closeAfter = false) {
|
|
|
const sport = getNATFieldVal('sport', true);
|
|
const sport = getNATFieldVal('sport', true);
|
|
|
const dport = getNATFieldVal('dport', true);
|
|
const dport = getNATFieldVal('dport', true);
|
|
|
const anyport = getNATFieldVal('anyport', true);
|
|
const anyport = getNATFieldVal('anyport', true);
|
|
|
- const iniface = getNATFieldVal('iniface', true);
|
|
|
|
|
- const outiface = getNATFieldVal('outiface', true);
|
|
|
|
|
- const inifacelist = getNATFieldVal('inifacelist', true);
|
|
|
|
|
- const outifacelist = getNATFieldVal('outifacelist', true);
|
|
|
|
|
|
|
+ let iniface = getNATFieldVal('iniface', true);
|
|
|
|
|
+ let outiface = getNATFieldVal('outiface', true);
|
|
|
|
|
+ let inifacelist = getNATFieldVal('inifacelist', true);
|
|
|
|
|
+ let outifacelist = getNATFieldVal('outifacelist', true);
|
|
|
|
|
+
|
|
|
|
|
+ if (chainLower === 'prerouting' || chainLower === 'input') {
|
|
|
|
|
+ outiface = '';
|
|
|
|
|
+ outifacelist = '';
|
|
|
|
|
+ } else if (chainLower === 'postrouting' || chainLower === 'output') {
|
|
|
|
|
+ iniface = '';
|
|
|
|
|
+ inifacelist = '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
const pktmark = getNATFieldVal('pktmark', true);
|
|
const pktmark = getNATFieldVal('pktmark', true);
|
|
|
const connmark = getNATFieldVal('connmark', true);
|
|
const connmark = getNATFieldVal('connmark', true);
|
|
|
const routingmark = getNATFieldVal('routingmark', true);
|
|
const routingmark = getNATFieldVal('routingmark', true);
|
|
@@ -5179,7 +5511,23 @@ async function applyNATRule(closeAfter = false) {
|
|
|
|
|
|
|
|
const usesAddr = ['src-nat', 'dst-nat', 'netmap'].includes(action);
|
|
const usesAddr = ['src-nat', 'dst-nat', 'netmap'].includes(action);
|
|
|
const usesPort = ['src-nat', 'dst-nat', 'redirect', 'masquerade'].includes(action);
|
|
const usesPort = ['src-nat', 'dst-nat', 'redirect', 'masquerade'].includes(action);
|
|
|
- if (!chain.trim()) { switchNATRuleTab('general'); showToast('NAT rule', 'Enter a chain.', 'error'); document.getElementById('nat-modal-chain').focus(); return; }
|
|
|
|
|
|
|
+ if (!chain) { switchNATRuleTab('general'); showToast('NAT rule', 'Enter a chain.', 'error'); document.getElementById('nat-modal-chain').focus(); return; }
|
|
|
|
|
+ if (chainLower === 'forward') { switchNATRuleTab('general'); showToast('Invalid Chain', 'Chain FORWARD is not valid in the NAT table in iptables.', 'error'); return; }
|
|
|
|
|
+ if (['dst-nat', 'redirect'].includes(action) && chainLower !== 'prerouting' && chainLower !== 'output') {
|
|
|
|
|
+ switchNATRuleTab('general');
|
|
|
|
|
+ showToast('Invalid Target for Chain', `${action === 'dst-nat' ? 'DNAT' : 'REDIRECT'} requires PREROUTING or OUTPUT chain in iptables.`, 'error');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (action === 'masquerade' && chainLower !== 'postrouting') {
|
|
|
|
|
+ switchNATRuleTab('general');
|
|
|
|
|
+ showToast('Invalid Target for Chain', 'MASQUERADE requires POSTROUTING chain in iptables.', 'error');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (action === 'src-nat' && chainLower !== 'postrouting' && chainLower !== 'input') {
|
|
|
|
|
+ switchNATRuleTab('general');
|
|
|
|
|
+ showToast('Invalid Target for Chain', 'SNAT requires POSTROUTING or INPUT chain in iptables.', 'error');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
if (usesAddr && !document.getElementById('nat-modal-toaddr').value.trim()) { switchNATRuleTab('general'); showToast('NAT rule', 'Enter the destination IP / address for this action.', 'error'); document.getElementById('nat-modal-toaddr').focus(); return; }
|
|
if (usesAddr && !document.getElementById('nat-modal-toaddr').value.trim()) { switchNATRuleTab('general'); showToast('NAT rule', 'Enter the destination IP / address for this action.', 'error'); document.getElementById('nat-modal-toaddr').focus(); return; }
|
|
|
if ((sport || dport || anyport || (usesPort && document.getElementById('nat-modal-toport').value.trim())) && !['tcp', 'udp'].includes(proto)) {
|
|
if ((sport || dport || anyport || (usesPort && document.getElementById('nat-modal-toport').value.trim())) && !['tcp', 'udp'].includes(proto)) {
|
|
|
switchNATRuleTab('general'); toggleNATField('proto', true); document.getElementById('nat-val-proto').value = proto || 'all';
|
|
switchNATRuleTab('general'); toggleNATField('proto', true); document.getElementById('nat-val-proto').value = proto || 'all';
|
|
@@ -5561,6 +5909,112 @@ function toggleMaximizeMangleModal() {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function syncMangleChainFromSelect() {
|
|
|
|
|
+ const sel = document.getElementById('mangle-modal-chain-select');
|
|
|
|
|
+ const customWrap = document.getElementById('mangle-modal-custom-wrap');
|
|
|
|
|
+ const customInput = document.getElementById('mangle-modal-custom-input');
|
|
|
|
|
+ const chainInput = document.getElementById('mangle-modal-chain');
|
|
|
|
|
+ if (!sel || !chainInput) return;
|
|
|
|
|
+
|
|
|
|
|
+ if (sel.value === '__custom__') {
|
|
|
|
|
+ if (customWrap) customWrap.classList.remove('hidden');
|
|
|
|
|
+ if (customInput) {
|
|
|
|
|
+ customInput.value = '';
|
|
|
|
|
+ customInput.focus();
|
|
|
|
|
+ }
|
|
|
|
|
+ chainInput.value = '';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (customWrap) customWrap.classList.add('hidden');
|
|
|
|
|
+ chainInput.value = sel.value;
|
|
|
|
|
+ }
|
|
|
|
|
+ handleMangleChainChange();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function syncMangleCustomChainInput() {
|
|
|
|
|
+ const customInput = document.getElementById('mangle-modal-custom-input');
|
|
|
|
|
+ const chainInput = document.getElementById('mangle-modal-chain');
|
|
|
|
|
+ if (customInput && chainInput) {
|
|
|
|
|
+ chainInput.value = customInput.value.trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ handleMangleChainChange();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleMangleChainChange() {
|
|
|
|
|
+ const chainInput = document.getElementById('mangle-modal-chain');
|
|
|
|
|
+ const rawChain = (chainInput?.value || 'prerouting').trim();
|
|
|
|
|
+ const chain = rawChain.toLowerCase();
|
|
|
|
|
+
|
|
|
|
|
+ const sel = document.getElementById('mangle-modal-chain-select');
|
|
|
|
|
+ if (sel && sel.value !== chain && sel.value !== '__custom__') {
|
|
|
|
|
+ if (['prerouting', 'input', 'forward', 'output', 'postrouting'].includes(chain)) {
|
|
|
|
|
+ sel.value = chain;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const hintEl = document.getElementById('mangle-chain-hint');
|
|
|
|
|
+ if (hintEl) {
|
|
|
|
|
+ if (chain === 'prerouting') {
|
|
|
|
|
+ hintEl.textContent = 'PREROUTING: packets altered before routing decision (In-Interface only).';
|
|
|
|
|
+ } else if (chain === 'input') {
|
|
|
|
|
+ hintEl.textContent = 'INPUT: packets altered before local socket delivery (In-Interface only).';
|
|
|
|
|
+ } else if (chain === 'forward') {
|
|
|
|
|
+ hintEl.textContent = 'FORWARD: transit packets routed through this router (accepts both In and Out interfaces).';
|
|
|
|
|
+ } else if (chain === 'output') {
|
|
|
|
|
+ hintEl.textContent = 'OUTPUT: locally generated packets before routing decision (Out-Interface only).';
|
|
|
|
|
+ } else if (chain === 'postrouting') {
|
|
|
|
|
+ hintEl.textContent = 'POSTROUTING: packets altered after routing decision before transmission (Out-Interface only; CLASSIFY supported).';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ hintEl.textContent = `Custom chain "${rawChain}": user-defined Mangle chain reached via JUMP.`;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const rowIn = document.getElementById('mangle-row-iniface');
|
|
|
|
|
+ const rowInList = document.getElementById('mangle-row-inifacelist');
|
|
|
|
|
+ const rowOut = document.getElementById('mangle-row-outiface');
|
|
|
|
|
+ const rowOutList = document.getElementById('mangle-row-outifacelist');
|
|
|
|
|
+
|
|
|
|
|
+ if (chain === 'prerouting' || chain === 'input') {
|
|
|
|
|
+ if (rowIn) rowIn.classList.remove('hidden');
|
|
|
|
|
+ if (rowInList) rowInList.classList.remove('hidden');
|
|
|
|
|
+ if (rowOut) rowOut.classList.add('hidden');
|
|
|
|
|
+ if (rowOutList) rowOutList.classList.add('hidden');
|
|
|
|
|
+ } else if (chain === 'output' || chain === 'postrouting') {
|
|
|
|
|
+ if (rowIn) rowIn.classList.add('hidden');
|
|
|
|
|
+ if (rowInList) rowInList.classList.add('hidden');
|
|
|
|
|
+ if (rowOut) rowOut.classList.remove('hidden');
|
|
|
|
|
+ if (rowOutList) rowOutList.classList.remove('hidden');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (rowIn) rowIn.classList.remove('hidden');
|
|
|
|
|
+ if (rowInList) rowInList.classList.remove('hidden');
|
|
|
|
|
+ if (rowOut) rowOut.classList.remove('hidden');
|
|
|
|
|
+ if (rowOutList) rowOutList.classList.remove('hidden');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ checkMangleActionChainCompatibility();
|
|
|
|
|
+ toggleMangleActionFields();
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function checkMangleActionChainCompatibility() {
|
|
|
|
|
+ const rawChain = (document.getElementById('mangle-modal-chain')?.value || 'prerouting').trim();
|
|
|
|
|
+ const chain = rawChain.toLowerCase();
|
|
|
|
|
+ const action = (document.getElementById('mangle-modal-action')?.value || '').toLowerCase();
|
|
|
|
|
+ const warnBox = document.getElementById('mangle-action-incompatible-warn');
|
|
|
|
|
+ const warnMsg = document.getElementById('mangle-action-incompatible-msg');
|
|
|
|
|
+ if (!warnBox || !warnMsg) return;
|
|
|
|
|
+
|
|
|
|
|
+ let msg = '';
|
|
|
|
|
+ if (action === 'set-priority' && chain !== 'postrouting') {
|
|
|
|
|
+ msg = 'CLASSIFY (set-priority) requires the POSTROUTING chain in iptables.';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (msg) {
|
|
|
|
|
+ warnMsg.textContent = msg;
|
|
|
|
|
+ warnBox.classList.remove('hidden');
|
|
|
|
|
+ } else {
|
|
|
|
|
+ warnBox.classList.add('hidden');
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function toggleMangleActionFields() {
|
|
function toggleMangleActionFields() {
|
|
|
const act = document.getElementById('mangle-modal-action')?.value || 'mark-connection';
|
|
const act = document.getElementById('mangle-modal-action')?.value || 'mark-connection';
|
|
|
|
|
|
|
@@ -5615,6 +6069,7 @@ function toggleMangleActionFields() {
|
|
|
if (logEl) logEl.checked = true;
|
|
if (logEl) logEl.checked = true;
|
|
|
toggleMangleField('logprefix', true);
|
|
toggleMangleField('logprefix', true);
|
|
|
}
|
|
}
|
|
|
|
|
+ checkMangleActionChainCompatibility();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function toggleMangleLogPrefix() {
|
|
function toggleMangleLogPrefix() {
|
|
@@ -5644,6 +6099,8 @@ function openAddMangleModal() {
|
|
|
document.getElementById('mangle-modal-enabled').checked = true;
|
|
document.getElementById('mangle-modal-enabled').checked = true;
|
|
|
document.getElementById('mangle-modal-comment').value = '';
|
|
document.getElementById('mangle-modal-comment').value = '';
|
|
|
document.getElementById('mangle-modal-chain').value = 'prerouting';
|
|
document.getElementById('mangle-modal-chain').value = 'prerouting';
|
|
|
|
|
+ populateChainDropdown('mangle', 'prerouting');
|
|
|
|
|
+ handleMangleChainChange();
|
|
|
document.getElementById('mangle-modal-action').value = 'mark-connection';
|
|
document.getElementById('mangle-modal-action').value = 'mark-connection';
|
|
|
|
|
|
|
|
if (document.getElementById('mangle-modal-newconnmark')) document.getElementById('mangle-modal-newconnmark').value = '';
|
|
if (document.getElementById('mangle-modal-newconnmark')) document.getElementById('mangle-modal-newconnmark').value = '';
|
|
@@ -5689,7 +6146,10 @@ function openEditMangleModal(id) {
|
|
|
document.getElementById('mangle-modal-family').value = String(r.ip_version || 4);
|
|
document.getElementById('mangle-modal-family').value = String(r.ip_version || 4);
|
|
|
document.getElementById('mangle-modal-enabled').checked = !r.disabled;
|
|
document.getElementById('mangle-modal-enabled').checked = !r.disabled;
|
|
|
document.getElementById('mangle-modal-comment').value = r.comment || '';
|
|
document.getElementById('mangle-modal-comment').value = r.comment || '';
|
|
|
- document.getElementById('mangle-modal-chain').value = r.chain || 'prerouting';
|
|
|
|
|
|
|
+ const mangleChain = r.chain || 'prerouting';
|
|
|
|
|
+ document.getElementById('mangle-modal-chain').value = mangleChain;
|
|
|
|
|
+ populateChainDropdown('mangle', mangleChain);
|
|
|
|
|
+ handleMangleChainChange();
|
|
|
setFirewallActionSelection('mangle', r.action || 'accept');
|
|
setFirewallActionSelection('mangle', r.action || 'accept');
|
|
|
|
|
|
|
|
if (document.getElementById('mangle-modal-newconnmark')) document.getElementById('mangle-modal-newconnmark').value = r.new_connection_mark || '';
|
|
if (document.getElementById('mangle-modal-newconnmark')) document.getElementById('mangle-modal-newconnmark').value = r.new_connection_mark || '';
|
|
@@ -5864,7 +6324,8 @@ function resetMangleCounterDisplay() {
|
|
|
async function applyMangleRule(closeAfter = false) {
|
|
async function applyMangleRule(closeAfter = false) {
|
|
|
const id = document.getElementById('mangle-modal-id').value;
|
|
const id = document.getElementById('mangle-modal-id').value;
|
|
|
if (!reviewLegacyFirewallSettings('mangle', id)) return;
|
|
if (!reviewLegacyFirewallSettings('mangle', id)) return;
|
|
|
- const chain = document.getElementById('mangle-modal-chain').value;
|
|
|
|
|
|
|
+ const chain = (document.getElementById('mangle-modal-chain').value || '').trim();
|
|
|
|
|
+ const chainLower = chain.toLowerCase();
|
|
|
const action = document.getElementById('mangle-modal-action').value;
|
|
const action = document.getElementById('mangle-modal-action').value;
|
|
|
const comment = document.getElementById('mangle-modal-comment').value.trim();
|
|
const comment = document.getElementById('mangle-modal-comment').value.trim();
|
|
|
const enabled = document.getElementById('mangle-modal-enabled').checked;
|
|
const enabled = document.getElementById('mangle-modal-enabled').checked;
|
|
@@ -5883,10 +6344,25 @@ async function applyMangleRule(closeAfter = false) {
|
|
|
switchMangleRuleTab('general'); toggleMangleField('proto', true);
|
|
switchMangleRuleTab('general'); toggleMangleField('proto', true);
|
|
|
showToast('Select protocol', 'Port matches require TCP or UDP without protocol inversion.', 'error'); document.getElementById('mangle-val-proto').focus(); return;
|
|
showToast('Select protocol', 'Port matches require TCP or UDP without protocol inversion.', 'error'); document.getElementById('mangle-val-proto').focus(); return;
|
|
|
}
|
|
}
|
|
|
- const iniface = getMangleFieldVal('iniface', true);
|
|
|
|
|
- const outiface = getMangleFieldVal('outiface', true);
|
|
|
|
|
- const inifacelist = getMangleFieldVal('inifacelist', true);
|
|
|
|
|
- const outifacelist = getMangleFieldVal('outifacelist', true);
|
|
|
|
|
|
|
+ let iniface = getMangleFieldVal('iniface', true);
|
|
|
|
|
+ let outiface = getMangleFieldVal('outiface', true);
|
|
|
|
|
+ let inifacelist = getMangleFieldVal('inifacelist', true);
|
|
|
|
|
+ let outifacelist = getMangleFieldVal('outifacelist', true);
|
|
|
|
|
+
|
|
|
|
|
+ if (chainLower === 'prerouting' || chainLower === 'input') {
|
|
|
|
|
+ outiface = '';
|
|
|
|
|
+ outifacelist = '';
|
|
|
|
|
+ } else if (chainLower === 'output' || chainLower === 'postrouting') {
|
|
|
|
|
+ iniface = '';
|
|
|
|
|
+ inifacelist = '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!chain) { switchMangleRuleTab('general'); showToast('Mangle rule', 'Enter a chain.', 'error'); document.getElementById('mangle-modal-chain').focus(); return; }
|
|
|
|
|
+ if (action === 'set-priority' && chainLower !== 'postrouting') {
|
|
|
|
|
+ switchMangleRuleTab('general');
|
|
|
|
|
+ showToast('Invalid Target for Chain', 'CLASSIFY (set-priority) requires POSTROUTING chain in iptables.', 'error');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
const pktmark = getMangleFieldVal('pktmark', true);
|
|
const pktmark = getMangleFieldVal('pktmark', true);
|
|
|
const connmark = getMangleFieldVal('connmark', true);
|
|
const connmark = getMangleFieldVal('connmark', true);
|
|
|
const routingmark = getMangleFieldVal('routingmark', true);
|
|
const routingmark = getMangleFieldVal('routingmark', true);
|