'use strict';'require view';'require ui';'require rpc';'require uci';'require form';'require firewall as fwmodel';'require tools.firewall as fwtool';'require tools.widgets as widgets';function rule_proto_txt(s,ctHelpers){const f=(uci.get('firewall',s,'family')||'').toLowerCase().replace(/^(?:any|\*)$/,'');const proto=L.toArray(uci.get('firewall',s,'proto')).filter(function(p){return(p!='*'&&p!='any'&&p!='all');}).map(function(p){const pr=fwtool.lookupProto(p);return{num:pr[0],name:pr[1],types:(pr[0]==1||pr[0]==58)?L.toArray(uci.get('firewall',s,'icmp_type')):null};});let m=String(uci.get('firewall',s,'helper')||'').match(/^(!\s*)?(\S+)$/);const h=m?{val:m[0].toUpperCase(),inv:m[1],name:(ctHelpers.filter(function(ctH){return ctH.name.toLowerCase()==m[2].toLowerCase()})[0]||{}).description}:null;m=String(uci.get('firewall',s,'mark')).match(/^(!\s*)?(0x[0-9a-f]{1,8}|[0-9]{1,10})(?:\/(0x[0-9a-f]{1,8}|[0-9]{1,10}))?$/i);const w=m?{val:m[0].toUpperCase().replace(/X/g,'x'),inv:m[1],num:'0x%02X'.format(+m[2]),mask:m[3]?'0x%02X'.format(+m[3]):null}:null;m=String(uci.get('firewall',s,'dscp')).match(/^(!\s*)?(?:(CS[0-7]|BE|AF[1234][123]|EF)|(0x[0-9a-f]{1,2}|[0-9]{1,2}))$/);const d=m?{val:m[0],inv:m[1],name:m[2],num:m[3]?'0x%02X'.format(+m[3]):null}:null;return fwtool.fmt(_('%{src?%{dest?Forwarded:Incoming}:Outgoing} %{ipv6?%{ipv4?<var>IPv4</var> and <var>IPv6</var>:<var>IPv6</var>}:<var>IPv4</var>}%{proto?, protocol %{proto#%{next?, }%{item.types?<var class="cbi-tooltip-container">%{item.name}<span class="cbi-tooltip">ICMP with types %{item.types#%{next?, }<var>%{item}</var>}</span></var>:<var>%{item.name}</var>}}}%{mark?, mark <var%{mark.inv? data-tooltip="Match fwmarks except %{mark.num}%{mark.mask? with mask %{mark.mask}}.":%{mark.mask? data-tooltip="Mask fwmark value with %{mark.mask} before compare."}}>%{mark.val}</var>}%{dscp?, DSCP %{dscp.inv?<var data-tooltip="Match DSCP classifications except %{dscp.num?:%{dscp.name}}">%{dscp.val}</var>:<var>%{dscp.val}</var>}}%{helper?, helper %{helper.inv?<var data-tooltip="Match any helper except &quot;%{helper.name}&quot;">%{helper.val}</var>:<var data-tooltip="%{helper.name}">%{helper.val}</var>}}'),{ipv4:(!f||f=='ipv4'),ipv6:(!f||f=='ipv6'),src:uci.get('firewall',s,'src'),dest:uci.get('firewall',s,'dest'),proto:proto,helper:h,mark:w,dscp:d});}
function rule_src_txt(s,hosts){const z=uci.get('firewall',s,'src');const d=(uci.get('firewall',s,'direction')=='in')?uci.get('firewall',s,'device'):null;return fwtool.fmt(_('From %{src}%{src_device?, interface <var>%{src_device}</var>}%{src_ip?, IP %{src_ip#%{next?, }<var%{item.inv? data-tooltip="Match IP addresses except %{item.val}."}>%{item.ival}</var>}}%{src_port?, port %{src_port#%{next?, }<var%{item.inv? data-tooltip="Match ports except %{item.val}."}>%{item.ival}</var>}}%{src_mac?, MAC %{src_mac#%{next?, }<var%{item.inv? data-tooltip="Match MACs except %{item.val}%{item.hint.name? a.k.a. %{item.hint.name}}.":%{item.hint.name? data-tooltip="%{item.hint.name}"}}>%{item.ival}</var>}}'),{src:E('span',{'class':'zonebadge','style':fwmodel.getZoneColorStyle(z)},[(z=='*')?E('em',_('any zone')):(z?E('strong',z):E('em',_('this device')))]),src_ip:fwtool.map_invert(uci.get('firewall',s,'src_ip'),'toLowerCase'),src_mac:fwtool.map_invert(uci.get('firewall',s,'src_mac'),'toUpperCase').map(function(v){return Object.assign(v,{hint:hosts[v.val]})}),src_port:fwtool.map_invert(uci.get('firewall',s,'src_port')),src_device:d});}
function rule_dest_txt(s){const z=uci.get('firewall',s,'dest');const d=(uci.get('firewall',s,'direction')=='out')?uci.get('firewall',s,'device'):null;return fwtool.fmt(_('To %{dest}%{dest_device?, interface <var>%{dest_device}</var>}%{dest_ip?, IP %{dest_ip#%{next?, }<var%{item.inv? data-tooltip="Match IP addresses except %{item.val}."}>%{item.ival}</var>}}%{dest_port?, port %{dest_port#%{next?, }<var%{item.inv? data-tooltip="Match ports except %{item.val}."}>%{item.ival}</var>}}'),{dest:E('span',{'class':'zonebadge','style':fwmodel.getZoneColorStyle(z)},[(z=='*')?E('em',_('any zone')):(z?E('strong',z):E('em',_('this device')))]),dest_ip:fwtool.map_invert(uci.get('firewall',s,'dest_ip'),'toLowerCase'),dest_port:fwtool.map_invert(uci.get('firewall',s,'dest_port')),dest_device:d});}
function rule_limit_txt(s){const m=String(uci.get('firewall',s,'limit')).match(/^(\d+)\/([smhd])\w*$/i);const l=m?{num:+m[1],unit:({s:_('second'),m:_('minute'),h:_('hour'),d:_('day')})[m[2]],burst:uci.get('firewall',s,'limit_burst')}:null;if(!l)
return'';return fwtool.fmt(_('Limit matching to <var>%{limit.num}</var> packets per <var>%{limit.unit}</var>%{limit.burst? burst <var>%{limit.burst}</var>}'),{limit:l});}
function rule_target_txt(sid,ctHelpers){const t=uci.get('firewall',sid,'target');const h=(uci.get('firewall',sid,'set_helper')||'').toUpperCase();const s={target:t,src:uci.get('firewall',sid,'src'),dest:uci.get('firewall',sid,'dest'),set_helper:h,set_mark:uci.get('firewall',sid,'set_mark'),set_xmark:uci.get('firewall',sid,'set_xmark'),set_dscp:uci.get('firewall',sid,'set_dscp'),helper_name:(ctHelpers.filter(function(ctH){return ctH.name.toUpperCase()==h})[0]||{}).description};switch(t){case'DROP':return fwtool.fmt(_('<var data-tooltip="DROP">Drop</var> %{src?%{dest?forward:input}:output}'),s);case'ACCEPT':return fwtool.fmt(_('<var data-tooltip="ACCEPT">Accept</var> %{src?%{dest?forward:input}:output}'),s);case'REJECT':return fwtool.fmt(_('<var data-tooltip="REJECT">Reject</var> %{src?%{dest?forward:input}:output}'),s);case'NOTRACK':return fwtool.fmt(_('<var data-tooltip="NOTRACK">Do not track</var> %{src?%{dest?forward:input}:output}'),s);case'HELPER':return fwtool.fmt(_('<var data-tooltip="HELPER">Assign conntrack</var> helper <var%{helper_name? data-tooltip="%{helper_name}"}>%{set_helper}</var>'),s);case'MARK':return fwtool.fmt(_('<var data-tooltip="MARK">%{set_mark?Assign:XOR}</var> firewall mark <var>%{set_mark?:%{set_xmark}}</var>'),s);case'DSCP':return fwtool.fmt(_('<var data-tooltip="DSCP">Assign DSCP</var> classification <var>%{set_dscp}</var>'),s);default:return t;}}
return view.extend({callHostHints:rpc.declare({object:'luci-rpc',method:'getHostHints',expect:{'':{}}}),callConntrackHelpers:rpc.declare({object:'luci',method:'getConntrackHelpers',expect:{result:[]}}),load(){return Promise.all([this.callHostHints(),this.callConntrackHelpers(),uci.load('firewall')]);},render(data){if(fwtool.checkLegacySNAT())
return fwtool.renderMigration();else
return this.renderRules(data);},renderRules([hosts,ctHelpers]){let m,s,o;m=new form.Map('firewall',_('Firewall - Traffic Rules'),_('Traffic rules define policies for packets travelling between different zones, for example to reject traffic between certain hosts or to open WAN ports on the router.'));s=m.section(form.GridSection,'rule',_('Traffic Rules'));s.addremove=true;s.anonymous=true;s.sortable=true;s.cloneable=true;s.filterrow=true;s.tab('general',_('General Settings'));s.tab('advanced',_('Advanced Settings'));s.tab('timed',_('Time Restrictions'));s.filter=function(section_id){return(uci.get('firewall',section_id,'target')!='SNAT');};s.sectiontitle=function(section_id){return uci.get('firewall',section_id,'name')||_('Unnamed rule');};s.handleAdd=function(ev){const config_name=this.uciconfig||this.map.config;const section_id=uci.add(config_name,this.sectiontype);uci.set(config_name,section_id,'src','wan');uci.set(config_name,section_id,'dest','lan');m.addedSection=section_id;this.renderMoreOptionsModal(section_id);};o=s.taboption('general',form.Value,'name',_('Name'));o.placeholder=_('Unnamed rule');o.modalonly=true;o=s.option(form.DummyValue,'_match',_('Match'));o.modalonly=false;o.textvalue=function(s){return E('small',[rule_proto_txt(s,ctHelpers),E('br'),rule_src_txt(s,hosts),E('br'),rule_dest_txt(s),E('br'),rule_limit_txt(s)]);};o=s.option(form.ListValue,'_target',_('Action'));o.modalonly=false;o.textvalue=function(s){return rule_target_txt(s,ctHelpers);};o=s.option(form.Flag,'enabled',_('Enable'));o.modalonly=false;o.default=o.enabled;o.rmempty=false;o.editable=true;o.tooltip=function(section_id){const weekdays=uci.get('firewall',section_id,'weekdays');const monthdays=uci.get('firewall',section_id,'monthdays');const start_time=uci.get('firewall',section_id,'start_time');const stop_time=uci.get('firewall',section_id,'stop_time');const start_date=uci.get('firewall',section_id,'start_date');const stop_date=uci.get('firewall',section_id,'stop_date');if(weekdays||monthdays||start_time||stop_time||start_date||stop_date)
return _('Time restrictions are enabled for this rule');return null;};o=s.taboption('advanced',form.ListValue,'direction',_('Match device'));o.modalonly=true;o.value('',_('unspecified'));o.value('in',_('Inbound device'));o.value('out',_('Outbound device'));o.cfgvalue=function(section_id){const val=uci.get('firewall',section_id,'direction');switch(val){case'in':case'ingress':return'in';case'out':case'egress':return'out';}
return null;};o=s.taboption('advanced',widgets.DeviceSelect,'device',_('Device name'),_('Specifies whether to tie this traffic rule to a specific inbound or outbound network device.'));o.modalonly=true;o.noaliases=true;o.rmempty=false;o.depends('direction','in');o.depends('direction','out');o=s.taboption('advanced',form.ListValue,'family',_('Restrict to address family'));o.modalonly=true;o.rmempty=true;o.value('',_('IPv4 and IPv6'));o.value('ipv4',_('IPv4 only'));o.value('ipv6',_('IPv6 only'));o.validate=function(section_id,value){fwtool.updateHostHints(this.map,section_id,'src_ip',value,hosts);fwtool.updateHostHints(this.map,section_id,'dest_ip',value,hosts);return true;};o=s.taboption('general',fwtool.CBIProtocolSelect,'proto',_('Protocol'));o.modalonly=true;o.default='tcp udp';o=s.taboption('advanced',form.MultiValue,'icmp_type',_('Match ICMP type'));o.modalonly=true;o.multiple=true;o.custom=true;o.cast='table';o.placeholder=_('any/all');o.value('address-mask-reply');o.value('address-mask-request');o.value('address-unreachable');o.value('bad-header');o.value('certification-path-solicitation-message');o.value('certification-path-advertisement-message');o.value('communication-prohibited');o.value('destination-unreachable');o.value('duplicate-address-request');o.value('duplicate-address-confirmation');o.value('echo-reply');o.value('echo-request');o.value('extended-echo-request');o.value('extended-echo-reply');o.value('fmipv6-message');o.value('fragmentation-needed');o.value('home-agent-address-discovery-reply-message');o.value('home-agent-address-discovery-request-message');o.value('host-precedence-violation');o.value('host-prohibited');o.value('host-redirect');o.value('host-unknown');o.value('host-unreachable');o.value('ilnpv6-locator-update-message');o.value('inverse-neighbour-discovery-advertisement-message');o.value('inverse-neighbour-discovery-solicitation-message');o.value('ip-header-bad');o.value('mobile-prefix-advertisement');o.value('mobile-prefix-solicitation');o.value('mpl-control-message');o.value('multicast-listener-query');o.value('multicast-listener-report');o.value('multicast-listener-done');o.value('multicast-router-advertisement');o.value('multicast-router-solicitation');o.value('multicast-router-termination');o.value('neighbour-advertisement');o.value('neighbour-solicitation');o.value('network-prohibited');o.value('network-redirect');o.value('network-unknown');o.value('network-unreachable');o.value('no-route');o.value('node-info-query');o.value('node-info-response');o.value('packet-too-big');o.value('parameter-problem');o.value('port-unreachable');o.value('precedence-cutoff');o.value('protocol-unreachable');o.value('redirect');o.value('required-option-missing');o.value('router-advertisement');o.value('router-renumbering');o.value('router-solicitation');o.value('rpl-control-message');o.value('source-quench');o.value('source-route-failed');o.value('time-exceeded');o.value('timestamp-reply');o.value('timestamp-request');o.value('TOS-host-redirect');o.value('TOS-host-unreachable');o.value('TOS-network-redirect');o.value('TOS-network-unreachable');o.value('ttl-zero-during-reassembly');o.value('ttl-zero-during-transit');o.value('v2-multicast-listener-report');o.value('unknown-header-type');o.value('unknown-option');o.depends({proto:'icmp','!contains':true});o.depends({proto:'icmpv6','!contains':true});o=s.taboption('general',widgets.ZoneSelect,'src',_('Source zone'));o.modalonly=true;o.nocreate=true;o.allowany=true;o.allowlocal='src';o=s.taboption('advanced',form.Value,'ipset',_('Use ipset'));uci.sections('firewall','ipset',function(s){if(typeof(s.name)=='string')
o.value(s.name,s.comment?'%s (%s)'.format(s.name,s.comment):s.name);});o.modalonly=true;o.rmempty=true;fwtool.addMACOption(s,'advanced','src_mac',_('Source MAC address'),null,hosts);fwtool.addIPOption(s,'general','src_ip',_('Source address'),null,'',hosts,true);o=s.taboption('general',form.Value,'src_port',_('Source port'));o.modalonly=true;o.datatype='list(neg(portrange))';o.placeholder=_('any');o.depends({proto:'tcp','!contains':true});o.depends({proto:'udp','!contains':true});o=s.taboption('general',widgets.ZoneSelect,'dest',_('Destination zone'));o.modalonly=true;o.nocreate=true;o.allowany=true;o.allowlocal=true;fwtool.addIPOption(s,'general','dest_ip',_('Destination address'),null,'',hosts,true);o=s.taboption('general',form.Value,'dest_port',_('Destination port'));o.modalonly=true;o.datatype='list(neg(portrange))';o.placeholder=_('any');o.depends({proto:'tcp','!contains':true});o.depends({proto:'udp','!contains':true});o=s.taboption('general',form.ListValue,'target',_('Action'));o.modalonly=true;o.default='ACCEPT';o.value('DROP',_('drop'));o.value('ACCEPT',_('accept'));o.value('REJECT',_('reject'));o.value('NOTRACK',_("don't track"));o.value('HELPER',_('assign conntrack helper'));o.value('MARK_SET',_('apply firewall mark'));o.value('MARK_XOR',_('XOR firewall mark'));o.value('DSCP',_('DSCP classification'));o.cfgvalue=function(section_id){const t=uci.get('firewall',section_id,'target');const m=uci.get('firewall',section_id,'set_mark');if(t=='MARK')
return m?'MARK_SET':'MARK_XOR';return t;};o.write=function(section_id,value){return this.super('write',[section_id,(value=='MARK_SET'||value=='MARK_XOR')?'MARK':value]);};fwtool.addMarkOption(s,1);fwtool.addMarkOption(s,2);fwtool.addDSCPOption(s,true);o=s.taboption('general',form.ListValue,'set_helper',_('Tracking helper'),_('Assign the specified connection tracking helper to matched traffic.'));o.modalonly=true;o.placeholder=_('any');o.depends('target','HELPER');for(let cth of ctHelpers)
o.value(cth.name,'%s (%s)'.format(cth.description,cth.name.toUpperCase()));o=s.taboption('advanced',form.Value,'helper',_('Match helper'),_('Match traffic using the specified connection tracking helper.'));o.modalonly=true;o.placeholder=_('any');for(let cth of ctHelpers)
o.value(cth.name,'%s (%s)'.format(cth.description,cth.name.toUpperCase()));o.validate=function(section_id,value){if(value==''||value==null)
return true;value=value.replace(/^!\s*/,'');for(let cth of ctHelpers)
if(value==cth.name)
return true;return _('Unknown or not installed conntrack helper "%s"').format(value);};fwtool.addMarkOption(s,false);fwtool.addDSCPOption(s,false);fwtool.addLimitOption(s);fwtool.addLimitBurstOption(s);o=s.taboption('advanced',form.Flag,'log',_('Enable logging'),_('Log matched packets to syslog.'));o.modalonly=true;o=s.taboption('advanced',form.Value,'log_limit',_('Limit log messages'));o.depends('log','1');o.placeholder='10/minute';o.modalonly=true;if(!L.hasSystemFeature('firewall4')){o=s.taboption('advanced',form.Value,'extra',_('Extra arguments'),_('Passes additional arguments to iptables. Use with care!'));o.modalonly=true;}
o=s.taboption('timed',form.MultiValue,'weekdays',_('Week Days'));o.modalonly=true;o.multiple=true;o.display=5;o.placeholder=_('Any day');o.value('Sun',_('Sunday'));o.value('Mon',_('Monday'));o.value('Tue',_('Tuesday'));o.value('Wed',_('Wednesday'));o.value('Thu',_('Thursday'));o.value('Fri',_('Friday'));o.value('Sat',_('Saturday'));o.write=function(section_id,value){return this.super('write',[section_id,L.toArray(value).join(' ')]);};o=s.taboption('timed',form.MultiValue,'monthdays',_('Month Days'));o.modalonly=true;o.multiple=true;o.display_size=15;o.placeholder=_('Any day');o.write=function(section_id,value){return this.super('write',[section_id,L.toArray(value).join(' ')]);};for(let i=1;i<=31;i++)
o.value(i);o=s.taboption('timed',form.Value,'start_time',_('Start Time (hh:mm:ss)'));o.modalonly=true;o.datatype='timehhmmss';o=s.taboption('timed',form.Value,'stop_time',_('Stop Time (hh:mm:ss)'));o.modalonly=true;o.datatype='timehhmmss';o=s.taboption('timed',form.Value,'start_date',_('Start Date (yyyy-mm-dd)'));o.modalonly=true;o.datatype='dateyyyymmdd';o=s.taboption('timed',form.Value,'stop_date',_('Stop Date (yyyy-mm-dd)'));o.modalonly=true;o.datatype='dateyyyymmdd';o=s.taboption('timed',form.Flag,'utc_time',_('Time in UTC'));o.modalonly=true;o.default=o.disabled;return m.render();}});