mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-09-13 20:04:45 +08:00
468 lines
18 KiB
JavaScript
468 lines
18 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require rpc';
|
|
'require uci';
|
|
'require form';
|
|
'require network';
|
|
'require firewall';
|
|
'require tools.firewall as fwtool';
|
|
'require tools.widgets as widgets';
|
|
|
|
return view.extend({
|
|
callConntrackHelpers: rpc.declare({
|
|
object: 'luci',
|
|
method: 'getConntrackHelpers',
|
|
expect: { result: [] }
|
|
}),
|
|
|
|
load() {
|
|
return Promise.all([
|
|
this.callConntrackHelpers(),
|
|
firewall.getDefaults()
|
|
]);
|
|
},
|
|
|
|
render(data) {
|
|
if (fwtool.checkLegacySNAT())
|
|
return fwtool.renderMigration();
|
|
else
|
|
return this.renderZones(data);
|
|
},
|
|
|
|
renderZones([ctHelpers, fwDefaults]) {
|
|
let m, s, o, out;
|
|
const fw4 = L.hasSystemFeature('firewall4');
|
|
const addTCPCCAOption = () => {
|
|
const tcpcca = s.option(form.ListValue, 'tcpcca', _('TCP CCA'),
|
|
_('TCP congestion control algorithm.'));
|
|
tcpcca.value('bbr', 'bbr');
|
|
tcpcca.value('cubic', 'cubic');
|
|
tcpcca.value('reno', 'reno');
|
|
tcpcca.default = 'cubic';
|
|
tcpcca.rmempty = false;
|
|
};
|
|
|
|
m = new form.Map('firewall', _('Firewall - Zone Settings'),
|
|
_('The firewall creates zones over your network interfaces to control network traffic flow.'));
|
|
|
|
s = m.section(form.TypedSection, 'defaults', _('General Settings'));
|
|
s.anonymous = true;
|
|
s.addremove = false;
|
|
|
|
o = s.option(form.Flag, 'synflood_protect', _('Enable SYN-flood protection'));
|
|
o.cfgvalue = function(section_id) {
|
|
const val = uci.get('firewall', section_id, 'synflood_protect');
|
|
return (val != null) ? val : uci.get('firewall', section_id, 'syn_flood');
|
|
};
|
|
o.write = function(section_id, value) {
|
|
uci.unset('firewall', section_id, 'syn_flood');
|
|
uci.set('firewall', section_id, 'synflood_protect', value);
|
|
};
|
|
o.remove = function(section_id) {
|
|
uci.unset('firewall', section_id, 'syn_flood');
|
|
uci.unset('firewall', section_id, 'synflood_protect');
|
|
};
|
|
|
|
o = s.option(form.Flag, 'drop_invalid', _('Drop invalid packets'));
|
|
if (L.hasSystemFeature('fullcone')) {
|
|
o = s.option(form.ListValue, 'fullcone', _('Enable FullCone NAT'));
|
|
o.value('0', _('Disable'));
|
|
o.value('1', _('FULLCONENAT'));
|
|
o.value('2', _('Broadcom Fullcone NAT1'));
|
|
addTCPCCAOption();
|
|
if (fw4)
|
|
o = s.option(form.Flag, 'fullcone6', _('Enable FullCone NAT6'));
|
|
o.depends('fullcone', '1');
|
|
}
|
|
else {
|
|
addTCPCCAOption();
|
|
}
|
|
|
|
o = s.option(form.Flag, 'expose_wan', _('Expose WAN'), _('Danger! Proceed at your own risk.'));
|
|
|
|
o = s.option(form.Value, 'export', _('Ports to Expose'), _('Multiple ports can be, separated by spaces, format: 80 81 82'));
|
|
o.depends('expose_wan', '1');
|
|
o.validate = function(section_id, value) {
|
|
if (value.match(/^(\d+\s*)+$/)) {
|
|
return true;
|
|
}
|
|
return _('Please enter valid format.');
|
|
};
|
|
|
|
o = s.option(form.ListValue, 'family', _('Restrict to address family'));
|
|
o.modalonly = true;
|
|
o.rmempty = true;
|
|
o.depends('expose_wan', '1');
|
|
o.value('', _('IPv4 and IPv6'));
|
|
o.value('ipv4', _('IPv4 only'));
|
|
o.value('ipv6', _('IPv6 only'));
|
|
|
|
o = s.option(form.ListValue, 'proto', _('Protocol'));
|
|
o.modalonly = true;
|
|
o.rmempty = true;
|
|
o.default = 'tcp';
|
|
o.depends('expose_wan', '1');
|
|
o.value('tcp', _('TCP'));
|
|
o.value('udp', _('UDP'));
|
|
o.value('tudp', _('TCP+UDP'));
|
|
|
|
o = s.option(form.Flag, 'ex_ssh', _('Expose SSH'));
|
|
o.depends('expose_wan', '1');
|
|
o = s.option(form.Flag, 'ex_backend', _('Expose Backend'));
|
|
o.depends('expose_wan', '1');
|
|
o = s.option(form.Value, 'backend_port', _('Backend Port'), _('国内请使用除80,443外的端口'));
|
|
o.depends('ex_backend', '1');
|
|
o.rmempty = false;
|
|
o.datatype = 'integer';
|
|
|
|
let p = [
|
|
s.option(form.ListValue, 'input', _('Input')),
|
|
s.option(form.ListValue, 'output', _('Output')),
|
|
s.option(form.ListValue, 'forward', _('Forward'))
|
|
];
|
|
|
|
for (let popt of p) {
|
|
popt.value('REJECT', _('reject'));
|
|
popt.value('DROP', _('drop'));
|
|
popt.value('ACCEPT', _('accept'));
|
|
}
|
|
|
|
/* Netfilter flow offload support */
|
|
|
|
if (L.hasSystemFeature('offloading')) {
|
|
s = m.section(form.TypedSection, 'defaults', _('Routing/NAT Offloading'),
|
|
_('Not fully compatible with QoS/SQM.'));
|
|
|
|
s.anonymous = true;
|
|
s.addremove = false;
|
|
|
|
o = s.option(form.RichListValue, "offloading_type", _("Flow offloading type"));
|
|
o.value('0', _("None"));
|
|
o.value('1', _("Software flow offloading"), _('Software based offloading for routing/NAT.'));
|
|
if (L.hasSystemFeature('offloading_hw'))
|
|
o.value('2', _("Hardware flow offloading"), _('Hardware based offloading for routing with/without NAT.') + ' ' + _(' Requires hardware NAT support.'));
|
|
o.optional = false;
|
|
o.load = function (section_id) {
|
|
const flow_offloading = uci.get('firewall', section_id, 'flow_offloading');
|
|
const flow_offloading_hw = uci.get('firewall', section_id, 'flow_offloading_hw');
|
|
const has_hw_offloading = L.hasSystemFeature('offloading_hw');
|
|
return (flow_offloading === '1')
|
|
? ((flow_offloading_hw === '1' && has_hw_offloading) ? '2' : '1')
|
|
: '0';
|
|
};
|
|
o.write = function(section_id, value) {
|
|
uci.set('firewall', section_id, 'flow_offloading', value === '0' ? null : '1');
|
|
uci.set('firewall', section_id, 'flow_offloading_hw',
|
|
(value === '2' && L.hasSystemFeature('offloading_hw')) ? '1' : null);
|
|
};
|
|
}
|
|
|
|
|
|
s = m.section(form.GridSection, 'zone', _('Zones'));
|
|
s.addremove = true;
|
|
s.anonymous = true;
|
|
s.sortable = true;
|
|
s.filterrow = true;
|
|
s.nodescriptions = true;
|
|
|
|
s.handleRemove = function(section_id, ev) {
|
|
return firewall.deleteZone(section_id).then(L.bind(function() {
|
|
return this.super('handleRemove', [section_id, ev]);
|
|
}, this));
|
|
};
|
|
|
|
s.tab('general', _('General Settings'));
|
|
s.tab('advanced', _('Advanced Settings'));
|
|
s.tab('conntrack', _('Conntrack Settings'));
|
|
s.tab('extra', _('Extra iptables arguments'));
|
|
|
|
o = s.taboption('general', form.DummyValue, '_generalinfo');
|
|
o.rawhtml = true;
|
|
o.modalonly = true;
|
|
o.cfgvalue = function(section_id) {
|
|
let name = uci.get('firewall', section_id, 'name');
|
|
if (name == null)
|
|
name = _("this new zone");
|
|
return _('This section defines common properties of %q. The <em>input</em> and <em>output</em> options set the default policies for traffic entering and leaving this zone while the <em>forward</em> option describes the policy for forwarded traffic between different networks within the zone. <em>Covered networks</em> specifies which available networks are members of this zone.')
|
|
.replace(/%s/g, name).replace(/%q/g, '"' + name + '"');
|
|
};
|
|
|
|
o = s.taboption('general', form.Value, 'name', _('Name'));
|
|
o.placeholder = _('Unnamed zone');
|
|
o.modalonly = true;
|
|
o.rmempty = false;
|
|
o.datatype = fw4 ? 'ucifw4zonename' : 'and(ucifw4zonename,maxlength(11))';
|
|
o.write = function(section_id, formvalue) {
|
|
const cfgvalue = this.cfgvalue(section_id);
|
|
|
|
if (cfgvalue == null || cfgvalue == '')
|
|
return uci.set('firewall', section_id, 'name', formvalue);
|
|
else if (cfgvalue != formvalue)
|
|
return firewall.renameZone(cfgvalue, formvalue);
|
|
};
|
|
|
|
o = s.option(widgets.ZoneForwards, '_info', _('Zone ⇒ Forwards'));
|
|
o.editable = true;
|
|
o.modalonly = false;
|
|
o.cfgvalue = function(section_id) {
|
|
return uci.get('firewall', section_id, 'name');
|
|
};
|
|
|
|
p = [
|
|
s.taboption('general', form.ListValue, 'input', _('Input')),
|
|
s.taboption('general', form.ListValue, 'output', _('Output')),
|
|
s.taboption('general', form.ListValue, 'forward', _('Intra zone forward'))
|
|
];
|
|
|
|
for (let popt of p) {
|
|
popt.value('REJECT', _('reject'));
|
|
popt.value('DROP', _('drop'));
|
|
popt.value('ACCEPT', _('accept'));
|
|
popt.editable = true;
|
|
}
|
|
|
|
p[0].default = fwDefaults.getInput();
|
|
p[1].default = fwDefaults.getOutput();
|
|
p[2].default = fwDefaults.getForward();
|
|
|
|
o = s.taboption('general', form.Flag, 'masq', _('IPv4 Masquerading'),
|
|
_('Enable network address and port translation IPv4 (NAT4 or NAPT4) for outbound traffic on this zone. This is typically enabled on the <em>wan</em> zone.'));
|
|
o.editable = true;
|
|
o.tooltip = function(section_id) {
|
|
var family = uci.get('firewall', section_id, 'family')
|
|
var masq_src = uci.get('firewall', section_id, 'masq_src')
|
|
var masq_dest = uci.get('firewall', section_id, 'masq_dest')
|
|
if ((!family || family.indexOf('6') == -1) && (masq_src || masq_dest))
|
|
return _('Limited masquerading enabled');
|
|
|
|
return null;
|
|
};
|
|
|
|
o = s.taboption('general', form.Flag, 'mtu_fix', _('MSS clamping'));
|
|
o.modalonly = true;
|
|
|
|
o = s.taboption('general', widgets.NetworkSelect, 'network', _('Covered networks'));
|
|
o.modalonly = true;
|
|
o.multiple = true;
|
|
o.cfgvalue = function(section_id) {
|
|
return uci.get('firewall', section_id, 'network');
|
|
};
|
|
o.write = function(section_id, formvalue) {
|
|
const name = uci.get('firewall', section_id, 'name');
|
|
const cfgvalue = this.cfgvalue(section_id);
|
|
const oldNetworks = L.toArray(cfgvalue);
|
|
const newNetworks = L.toArray(formvalue);
|
|
|
|
oldNetworks.sort();
|
|
newNetworks.sort();
|
|
|
|
if (oldNetworks.join(' ') == newNetworks.join(' '))
|
|
return;
|
|
|
|
const tasks = [ firewall.getZone(name) ];
|
|
|
|
if (Array.isArray(formvalue))
|
|
for (let netname of newNetworks) {
|
|
tasks.push(network.getNetwork(netname).then(L.bind(function(netname, net) {
|
|
return net || network.addNetwork(netname, { 'proto': 'none' });
|
|
}, this, netname)));
|
|
}
|
|
|
|
return Promise.all(tasks).then(function(zone_networks) {
|
|
if (zone_networks[0]) {
|
|
zone_networks[0].clearNetworks();
|
|
for (var i = 1; i < zone_networks.length; i++)
|
|
zone_networks[0].addNetwork(zone_networks[i].getName());
|
|
}
|
|
});
|
|
};
|
|
|
|
o = s.taboption('advanced', form.DummyValue, '_advancedinfo');
|
|
o.rawhtml = true;
|
|
o.modalonly = true;
|
|
o.cfgvalue = function(section_id) {
|
|
let name = uci.get('firewall', section_id, 'name');
|
|
if (name == null)
|
|
name = _("this new zone");
|
|
return _('The options below control the forwarding policies between this zone (%s) and other zones. <em>Destination zones</em> cover forwarded traffic <strong>originating from %q</strong>. <em>Source zones</em> match forwarded traffic from other zones <strong>targeted at %q</strong>. The forwarding rule is <em>unidirectional</em>, e.g. a forward from lan to wan does <em>not</em> imply a permission to forward from wan to lan as well.')
|
|
.format(name);
|
|
};
|
|
|
|
o = s.taboption('advanced', widgets.DeviceSelect, 'device', _('Covered devices'), _('Use this option to classify zone traffic by raw, non-<em>uci</em> managed network devices.'));
|
|
o.modalonly = true;
|
|
o.noaliases = true;
|
|
o.multiple = true;
|
|
|
|
o = s.taboption('advanced', form.DynamicList, 'subnet', _('Covered subnets'), _('Use this option to classify zone traffic by source or destination subnet instead of networks or devices.'));
|
|
o.datatype = 'neg(cidr("true"))';
|
|
o.modalonly = true;
|
|
o.multiple = true;
|
|
|
|
if (fw4) {
|
|
o = s.taboption('advanced', form.Flag, 'masq6', _('IPv6 Masquerading'),
|
|
_('Enable network address and port translation IPv6 (NAT6 or NAPT6) for outbound traffic on this zone.'));
|
|
o.modalonly = true;
|
|
o.tooltip = function(section_id) {
|
|
const family = uci.get('firewall', section_id, 'family')
|
|
const masq_src = uci.get('firewall', section_id, 'masq_src')
|
|
const masq_dest = uci.get('firewall', section_id, 'masq_dest')
|
|
if ((!family || family.indexOf('6') >= 0) && (masq_src || masq_dest))
|
|
return _('Limited masquerading enabled');
|
|
return null;
|
|
};
|
|
}
|
|
|
|
o = s.taboption('advanced', form.ListValue, 'family', _('Restrict to address family'));
|
|
o.value('', _('IPv4 and IPv6'));
|
|
o.value('ipv4', _('IPv4 only'));
|
|
o.value('ipv6', _('IPv6 only'));
|
|
o.modalonly = true;
|
|
|
|
o = s.taboption('advanced', form.DynamicList, 'masq_src', _('Restrict Masquerading to given source subnets'));
|
|
if (fw4) {
|
|
o.datatype = 'list(neg(or(uciname,hostname,ipmask)))';
|
|
} else {
|
|
o.depends('family', '');
|
|
o.depends('family', 'ipv4');
|
|
o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
|
|
}
|
|
o.placeholder = '0.0.0.0/0';
|
|
o.modalonly = true;
|
|
|
|
o = s.taboption('advanced', form.DynamicList, 'masq_dest', _('Restrict Masquerading to given destination subnets'));
|
|
if (fw4) {
|
|
o.datatype = 'list(neg(or(uciname,hostname,ipmask)))';
|
|
} else {
|
|
o.depends('family', '');
|
|
o.depends('family', 'ipv4');
|
|
o.datatype = 'list(neg(or(uciname,hostname,ipmask4)))';
|
|
}
|
|
o.placeholder = '0.0.0.0/0';
|
|
o.modalonly = true;
|
|
|
|
o = s.taboption('conntrack', form.Flag, 'masq_allow_invalid', _('Allow "invalid" traffic'), _('Do not install extra rules to reject forwarded traffic with conntrack state <em>invalid</em>. This may be required for complex asymmetric route setups.'));
|
|
o.modalonly = true;
|
|
|
|
o = s.taboption('conntrack', form.Flag, 'auto_helper', _('Automatic helper assignment'), _('Automatically assign conntrack helpers based on traffic protocol and port'));
|
|
o.default = o.enabled;
|
|
o.modalonly = true;
|
|
|
|
o = s.taboption('conntrack', form.MultiValue, 'helper', _('Conntrack helpers'), _('Explicitly choses allowed connection tracking helpers for zone traffic'));
|
|
o.depends('auto_helper', '0');
|
|
o.modalonly = true;
|
|
for (let cth of ctHelpers)
|
|
o.value(cth.name, E('<span><span class="hide-close">%s (%s)</span><span class="hide-open">%s</span></span>'.format(cth.description, cth.name.toUpperCase(), cth.name.toUpperCase())));
|
|
|
|
o = s.taboption('advanced', form.MultiValue, 'log', _('Enable logging'), _('Log matched packets on the selected tables to syslog.'));
|
|
o.modalonly = true;
|
|
o.value('filter');
|
|
o.value('mangle');
|
|
o.placeholder = 'No table selected';
|
|
const TABLES = { filter: 1, mangle: 2 };
|
|
o.cfgvalue = function (section_id) {
|
|
let bitfield = this.super('load', [section_id]) || this.default;
|
|
return Object.keys(TABLES).filter(table => bitfield & TABLES[table]);
|
|
};
|
|
o.write = function (section_id, value) {
|
|
let bitfield = L.toArray(value).reduce((acc, table) => acc | (TABLES[table] || 0), 0);
|
|
return this.super('write', [section_id, bitfield]);
|
|
};
|
|
|
|
o = s.taboption('advanced', form.Value, 'log_limit', _('Limit log messages'));
|
|
o.depends({log: [], "!reverse": true});
|
|
o.placeholder = '10/minute';
|
|
o.modalonly = true;
|
|
|
|
if (!fw4) {
|
|
o = s.taboption('extra', form.DummyValue, '_extrainfo');
|
|
o.rawhtml = true;
|
|
o.modalonly = true;
|
|
o.cfgvalue = function(section_id) {
|
|
return _('Passing raw iptables arguments to source and destination traffic classification rules allows to match packets based on other criteria than interfaces or subnets. These options should be used with extreme care as invalid values could render the firewall ruleset broken, completely exposing all services.');
|
|
};
|
|
|
|
o = s.taboption('extra', form.Value, 'extra_src', _('Extra source arguments'), _('Additional raw <em>iptables</em> arguments to classify zone source traffic, e.g. <code>-p tcp --sport 443</code> to only match inbound HTTPS traffic.'));
|
|
o.modalonly = true;
|
|
o.cfgvalue = function(section_id) {
|
|
return uci.get('firewall', section_id, 'extra_src') || uci.get('firewall', section_id, 'extra');
|
|
};
|
|
o.write = function(section_id, value) {
|
|
uci.unset('firewall', section_id, 'extra');
|
|
uci.set('firewall', section_id, 'extra_src', value);
|
|
};
|
|
|
|
o = s.taboption('extra', form.Value, 'extra_dest', _('Extra destination arguments'), _('Additional raw <em>iptables</em> arguments to classify zone destination traffic, e.g. <code>-p tcp --dport 443</code> to only match outbound HTTPS traffic.'));
|
|
o.modalonly = true;
|
|
o.cfgvalue = function(section_id) {
|
|
return uci.get('firewall', section_id, 'extra_dest') || uci.get('firewall', section_id, 'extra_src') || uci.get('firewall', section_id, 'extra');
|
|
};
|
|
o.write = function(section_id, value) {
|
|
uci.unset('firewall', section_id, 'extra');
|
|
uci.set('firewall', section_id, 'extra_dest', value);
|
|
};
|
|
}
|
|
|
|
o = s.taboption('general', form.DummyValue, '_forwardinfo');
|
|
o.rawhtml = true;
|
|
o.modalonly = true;
|
|
o.cfgvalue = function(section_id) {
|
|
let name = uci.get('firewall', section_id, 'name');
|
|
if (name == null)
|
|
name = _("this new zone");
|
|
return _('The options below control the forwarding policies between this zone (%s) and other zones. <em>Destination zones</em> cover forwarded traffic <strong>originating from %q</strong>. <em>Source zones</em> match forwarded traffic from other zones <strong>targeted at %q</strong>. The forwarding rule is <em>unidirectional</em>, e.g. a forward from lan to wan does <em>not</em> imply a permission to forward from wan to lan as well.')
|
|
.format(name);
|
|
};
|
|
|
|
out = o = s.taboption('general', widgets.ZoneSelect, 'out', _('Allow forward to <em>destination zones</em>:'));
|
|
o.nocreate = true;
|
|
o.multiple = true;
|
|
o.modalonly = true;
|
|
o.filter = function(section_id, value) {
|
|
return (uci.get('firewall', section_id, 'name') != value);
|
|
};
|
|
o.cfgvalue = function(section_id) {
|
|
const out = (this.option == 'out');
|
|
const zone = this.lookupZone(uci.get('firewall', section_id, 'name'));
|
|
const fwds = zone ? zone.getForwardingsBy(out ? 'src' : 'dest') : [];
|
|
const value = [];
|
|
|
|
for (let fwd of fwds)
|
|
value.push(out ? fwd.getDestination() : fwd.getSource());
|
|
|
|
return value;
|
|
};
|
|
o.write = o.remove = function(section_id, formvalue) {
|
|
const out = (this.option == 'out');
|
|
const zone = this.lookupZone(uci.get('firewall', section_id, 'name'));
|
|
const fwds = zone ? zone.getForwardingsBy(out ? 'src' : 'dest') : [];
|
|
|
|
if (formvalue == null)
|
|
formvalue = [];
|
|
|
|
if (Array.isArray(formvalue)) {
|
|
for (let fwd of fwds) {
|
|
var cmp = out ? fwd.getDestination() : fwd.getSource();
|
|
if (!formvalue.filter(function(d) { return d == cmp }).length)
|
|
zone.deleteForwarding(fwd);
|
|
}
|
|
|
|
for (let fv of formvalue)
|
|
if (out)
|
|
zone.addForwardingTo(fv);
|
|
else
|
|
zone.addForwardingFrom(fv);
|
|
}
|
|
};
|
|
|
|
o = s.taboption('general', widgets.ZoneSelect, 'in', _('Allow forward from <em>source zones</em>:'));
|
|
o.nocreate = true;
|
|
o.multiple = true;
|
|
o.modalonly = true;
|
|
o.write = o.remove = out.write;
|
|
o.filter = out.filter;
|
|
o.cfgvalue = out.cfgvalue;
|
|
|
|
return m.render();
|
|
}
|
|
});
|