2022-01-21 19:22:22 +00:00
'use strict' ;
'require view' ;
'require poll' ;
'require fs' ;
'require ui' ;
'require dom' ;
'require tools.firewall as fwtool' ;
var expr _translations = {
'meta.iifname' : _ ( 'Ingress device name' , 'nft meta iifname' ) ,
'meta.oifname' : _ ( 'Egress device name' , 'nft meta oifname' ) ,
'meta.iif' : _ ( 'Ingress device id' , 'nft meta iif' ) ,
2022-04-26 13:04:58 +00:00
'meta.iif' : _ ( 'Egress device id' , 'nft meta oif' ) ,
2022-01-21 19:22:22 +00:00
'meta.l4proto' : _ ( 'IP protocol' , 'nft meta l4proto' ) ,
'meta.l4proto.tcp' : 'TCP' ,
'meta.l4proto.udp' : 'UDP' ,
'meta.l4proto.icmp' : 'ICMP' ,
'meta.l4proto.icmpv6' : 'ICMPv6' ,
'meta.l4proto.ipv6-icmp' : 'ICMPv6' ,
'meta.nfproto' : _ ( 'Address family' , 'nft meta nfproto' ) ,
'meta.nfproto.ipv4' : 'IPv4' ,
'meta.nfproto.ipv6' : 'IPv6' ,
2022-02-10 19:44:54 +00:00
'meta.mark' : _ ( 'Packet mark' , 'nft meta mark' ) ,
2022-01-21 19:22:22 +00:00
2022-12-13 22:49:11 +00:00
'meta.time' : _ ( 'Packet receive time' , 'nft meta time' ) ,
2022-04-26 13:04:58 +00:00
'meta.hour' : _ ( 'Current time' , 'nft meta hour' ) ,
'meta.day' : _ ( 'Current weekday' , 'nft meta day' ) ,
2022-01-21 19:22:22 +00:00
'ct.state' : _ ( 'Conntrack state' , 'nft ct state' ) ,
'ct.status' : _ ( 'Conntrack status' , 'nft ct status' ) ,
'ct.status.dnat' : 'DNAT' ,
'ip.protocol' : _ ( 'IP protocol' , 'nft ip protocol' ) ,
'ip.protocol.tcp' : 'TCP' ,
'ip.protocol.udp' : 'UDP' ,
'ip.protocol.icmp' : 'ICMP' ,
'ip.protocol.icmpv6' : 'ICMPv6' ,
'ip.protocol.ipv6-icmp' : 'ICMPv6' ,
'ip.saddr' : _ ( 'Source IP' , 'nft ip saddr' ) ,
'ip.daddr' : _ ( 'Destination IP' , 'nft ip daddr' ) ,
'ip.sport' : _ ( 'Source port' , 'nft ip sport' ) ,
'ip.dport' : _ ( 'Destination port' , 'nft ip dport' ) ,
'ip6.saddr' : _ ( 'Source IPv6' , 'nft ip6 saddr' ) ,
'ip6.daddr' : _ ( 'Destination IPv6' , 'nft ip6 daddr' ) ,
2022-09-18 08:09:42 +00:00
'icmp.code' : _ ( 'ICMP code' , 'nft icmp code' ) ,
'icmp.type' : _ ( 'ICMP type' , 'nft icmp type' ) ,
2022-01-21 19:22:22 +00:00
'icmpv6.code' : _ ( 'ICMPv6 code' , 'nft icmpv6 code' ) ,
'icmpv6.type' : _ ( 'ICMPv6 type' , 'nft icmpv6 type' ) ,
'tcp.sport' : _ ( 'TCP source port' , 'nft tcp sport' ) ,
'tcp.dport' : _ ( 'TCP destination port' , 'nft tcp dport' ) ,
'udp.sport' : _ ( 'UDP source port' , 'nft udp sport' ) ,
'udp.dport' : _ ( 'UDP destination port' , 'nft udp dport' ) ,
'tcp.flags' : _ ( 'TCP flags' , 'nft tcp flags' ) ,
2022-04-26 13:04:58 +00:00
'th.sport' : _ ( 'Transport header source port' , 'nft th sport' ) ,
'th.dport' : _ ( 'Transport header destination port' , 'nft th dport' ) ,
2022-01-21 19:22:22 +00:00
'natflag.random' : _ ( 'Randomize source port mapping' , 'nft nat flag random' ) ,
'natflag.fully-random' : _ ( 'Full port randomization' , 'nft nat flag fully-random' ) ,
'natflag.persistent' : _ ( 'Use same source and destination for each connection' , 'nft nat flag persistent' ) ,
'rt.mtu' : _ ( 'Effective route MTU' , 'nft rt mtu' ) ,
'tcpoption.maxseg.size' : _ ( 'TCP MSS' , 'nft tcp option maxseg size' ) ,
'unit.packets' : _ ( 'packets' , 'nft unit' ) ,
'unit.mbytes' : _ ( 'MiB' , 'nft unit' ) ,
'unit.kbytes' : _ ( 'KiB' , 'nft unit' ) ,
'unit.week' : _ ( 'week' , 'nft unit' ) ,
'unit.day' : _ ( 'day' , 'nft unit' ) ,
'unit.hour' : _ ( 'hour' , 'nft unit' ) ,
'unit.minute' : _ ( 'minute' , 'nft unit' ) ,
2022-04-26 13:04:58 +00:00
'payload.ll' : _ ( 'Link layer header bits %d-%d' , 'nft @ll,off,len' ) ,
'payload.nh' : _ ( 'Network header bits %d-%d' , 'nft @nh,off,len' ) ,
'payload.th' : _ ( 'Transport header bits %d-%d' , 'nft @th,off,len' )
2022-01-21 19:22:22 +00:00
} ;
var op _translations = {
'==' : _ ( '<var>%s</var> is <strong>%s</strong>' , 'nft relational "==" operator expression' ) ,
'!=' : _ ( '<var>%s</var> not <strong>%s</strong>' , 'nft relational "!=" operator expression' ) ,
'>=' : _ ( '<var>%s</var> greater than or equal to <strong>%s</strong>' , 'nft relational ">=" operator expression' ) ,
'<=' : _ ( '<var>%s</var> lower than or equal to <strong>%s</strong>' , 'nft relational "<=" operator expression' ) ,
'>' : _ ( '<var>%s</var> greater than <strong>%s</strong>' , 'nft relational ">" operator expression' ) ,
'<' : _ ( '<var>%s</var> lower than <strong>%s</strong>' , 'nft relational "<" operator expression' ) ,
'in' : _ ( '<var>%s</var> is one of <strong>%s</strong>' , 'nft relational "in" operator expression' ) ,
'in_set' : _ ( '<var>%s</var> in set <strong>%s</strong>' , 'nft set match expression' ) ,
'not_in_set' : _ ( '<var>%s</var> not in set <strong>%s</strong>' , 'nft not in set match expression' ) ,
} ;
var action _translations = {
'accept' : _ ( 'Accept packet' , 'nft accept action' ) ,
'drop' : _ ( 'Drop packet' , 'nft drop action' ) ,
'jump' : _ ( 'Continue in <strong><a href="#%q.%q">%h</a></strong>' , 'nft jump action' ) ,
2022-12-13 22:49:11 +00:00
'log' : _ ( 'Log event "<strong>%h</strong>…"' , 'nft log action' ) ,
2022-01-21 19:22:22 +00:00
'reject.tcp reset' : _ ( 'Reject packet with <strong>TCP reset</strong>' , 'nft reject with tcp reset' ) ,
'reject.icmp' : _ ( 'Reject IPv4 packet with <strong>ICMP type %h</strong>' , 'nft reject with icmp type' ) ,
'reject.icmpv6' : _ ( 'Reject packet with <strong>ICMPv6 type %h</strong>' , 'nft reject with icmpv6 type' ) ,
'reject.icmpx' : _ ( 'Reject packet with <strong>ICMP type %h</strong>' , 'nft reject with icmpx type' ) ,
'snat.ip.addr' : _ ( 'Rewrite source to <strong>%h</strong>' , 'nft snat ip to addr' ) ,
'snat.ip.addr.port' : _ ( 'Rewrite source to <strong>%h</strong>, port <strong>%h</strong>' , 'nft snat ip to addr:port' ) ,
'snat.ip6.addr' : _ ( 'Rewrite source to <strong>%h</strong>' , 'nft snat ip6 to addr' ) ,
'snat.ip6.addr.port' : _ ( 'Rewrite source to <strong>%h</strong>, port <strong>%h</strong>' , 'nft snat ip6 to addr:port' ) ,
'dnat.ip.addr' : _ ( 'Rewrite destination to <strong>%h</strong>' , 'nft dnat ip to addr' ) ,
'dnat.ip.addr.port' : _ ( 'Rewrite destination to <strong>%h</strong>, port <strong>%h</strong>' , 'nft dnat ip to addr:port' ) ,
'dnat.ip6.addr' : _ ( 'Rewrite destination to <strong>%h</strong>' , 'nft dnat ip6 to addr' ) ,
'dnat.ip6.addr.port' : _ ( 'Rewrite destination to <strong>%h</strong>, port <strong>%h</strong>' , 'nft dnat ip6 to addr:port' ) ,
'redirect' : _ ( 'Redirect to local system' , 'nft redirect' ) ,
'redirect.port' : _ ( 'Redirect to local port <strong>%h</strong>' , 'nft redirect to port' ) ,
'masquerade' : _ ( 'Rewrite to egress device address' ) ,
'mangle' : _ ( 'Set header field <var>%s</var> to <strong>%s</strong>' , 'nft mangle' ) ,
'limit' : _ ( 'At most <strong>%h</strong> per <strong>%h</strong>, burst of <strong>%h</strong>' ) ,
'limit.burst' : _ ( 'At most <strong>%h</strong> per <strong>%h</strong>, burst of <strong>%h</strong>' ) ,
'limit.inv' : _ ( 'At least <strong>%h</strong> per <strong>%h</strong>, burst of <strong>%h</strong>' ) ,
'limit.inv.burst' : _ ( 'At least <strong>%h</strong> per <strong>%h</strong>, burst of <strong>%h</strong>' ) ,
'return' : _ ( 'Continue in calling chain' ) ,
'flow' : _ ( 'Utilize flow table <strong>%h</strong>' )
} ;
return view . extend ( {
load : function ( ) {
2022-04-27 11:17:03 +00:00
return Promise . all ( [
2022-07-05 11:13:50 +00:00
L . resolveDefault ( fs . exec _direct ( '/usr/sbin/nft' , [ '--terse' , '--json' , 'list' , 'ruleset' ] , 'json' ) , { } ) ,
2022-04-27 11:17:03 +00:00
L . resolveDefault ( fs . exec _direct ( '/usr/sbin/iptables-save' ) , '' ) ,
L . resolveDefault ( fs . exec _direct ( '/usr/sbin/ip6tables-save' ) , '' )
] ) ;
2022-01-21 19:22:22 +00:00
} ,
isActionExpression : function ( expr ) {
for ( var k in expr ) {
if ( expr . hasOwnProperty ( k ) ) {
switch ( k ) {
case 'accept' :
case 'reject' :
case 'drop' :
case 'jump' :
case 'snat' :
case 'dnat' :
case 'redirect' :
case 'mangle' :
case 'masquerade' :
case 'return' :
case 'flow' :
2022-12-13 22:49:11 +00:00
case 'log' :
2022-01-21 19:22:22 +00:00
return true ;
}
}
}
return false ;
} ,
exprToKey : function ( expr ) {
var kind , spec ;
if ( ! Array . isArray ( expr ) && typeof ( expr ) == 'object' ) {
for ( var k in expr ) {
if ( expr . hasOwnProperty ( k ) ) {
kind = k ;
spec = expr [ k ] ;
break ;
}
}
}
switch ( kind || '-' ) {
case 'meta' :
case 'ct' :
case 'rt' :
return '%h.%h' . format ( kind , spec . key ) ;
case 'tcp option' :
return 'tcpoption.%h.%h' . format ( spec . name , spec . field ) ;
case 'reject' :
return 'reject.%h' . format ( spec . type ) ;
}
return null ;
} ,
exprToString : function ( expr , hint ) {
var kind , spec ;
if ( typeof ( expr ) != 'object' ) {
var s ;
if ( hint )
s = expr _translations [ '%s.%h' . format ( hint , expr ) ] ;
return s || '%h' . format ( expr ) ;
}
if ( Array . isArray ( expr ) ) {
kind = 'list' ;
spec = expr ;
}
else {
for ( var k in expr ) {
if ( expr . hasOwnProperty ( k ) ) {
kind = k ;
spec = expr [ k ] ;
}
}
}
if ( ! kind )
return '' ;
switch ( kind ) {
case 'prefix' :
return '%h/%d' . format ( spec . addr , spec . len ) ;
case 'set' :
case 'list' :
var items = [ ] ,
lis = [ ] ;
for ( var i = 0 ; i < spec . length ; i ++ ) {
items . push ( '<span class="nft-set-item">%s</span>' . format ( this . exprToString ( spec [ i ] ) ) ) ;
lis . push ( '<span class="ifacebadge">%s</span>' . format ( this . exprToString ( spec [ i ] ) ) ) ;
}
var tpl ;
if ( kind == 'set' )
tpl = '<div class="nft-set cbi-tooltip-container">{ <span class="nft-set-items">%s</span> }<div class="cbi-tooltip">%s</div></div>' ;
else
tpl = '<div class="nft-list cbi-tooltip-container"><span class="nft-list-items">%s</span><div class="cbi-tooltip">%s</div></div>' ;
return tpl . format ( items . join ( ', ' ) , lis . join ( '<br />' ) ) ;
case 'concat' :
var items = [ ] ;
for ( var i = 0 ; i < spec . length ; i ++ )
items . push ( this . exprToString ( spec [ i ] ) ) ;
return items . join ( '+' ) ;
case 'range' :
return '%s-%s' . format ( this . exprToString ( spec [ 0 ] , hint ) , this . exprToString ( spec [ 1 ] , hint ) ) ;
2022-04-26 13:04:58 +00:00
case 'payload' :
if ( spec . protocol && spec . field ) {
var k = '%h.%h' . format ( spec . protocol , spec . field ) ;
return expr _translations [ k ] || '<em>%s</em>' . format ( k ) ;
}
else if ( spec . base && spec . offset != null && spec . len != null ) {
var k = 'payload.%h' . format ( spec . base ) ;
return ( expr _translations [ k ] || '<em>@%s,%%d,%%d</em>' . format ( spec . base ) ) . format ( spec . offset + 1 , spec . offset + spec . len + 1 ) ;
}
return 'payload: %s' . format ( kind , JSON . stringify ( spec ) ) ;
2022-01-21 19:22:22 +00:00
case '&' :
case '|' :
case '^' :
return '%s %h %s' . format (
this . exprToString ( spec [ 0 ] , hint ) ,
kind ,
Array . isArray ( spec [ 1 ] ) ? '(%h)' . format ( spec [ 1 ] . join ( '|' ) ) : this . exprToString ( spec [ 1 ] , hint ) ) ;
default :
var k = this . exprToKey ( expr ) ;
if ( k )
return expr _translations [ k ] || '<em>%s</em>' . format ( k ) ;
return '%s: %s' . format ( kind , JSON . stringify ( spec ) ) ;
}
} ,
renderMatchExpr : function ( spec ) {
switch ( spec . op ) {
case '==' :
case '!=' :
if ( ( typeof ( spec . right ) == 'object' && spec . right . set ) ||
( typeof ( spec . right ) == 'string' && spec . right . charAt ( 0 ) == '@' ) )
spec . op = ( spec . op == '==' ) ? 'in_set' : 'not_in_set' ;
break ;
case 'in' :
if ( typeof ( spec . right ) != 'object' )
spec . op = '==' ;
break ;
}
return E ( 'span' , { 'class' : 'ifacebadge' } ,
( op _translations [ spec . op ] || '<var>%%s</var> %h <strong>%%s</strong>' . format ( spec . op ) ) . format (
this . exprToString ( spec . left ) ,
this . exprToString ( spec . right , this . exprToKey ( spec . left ) )
)
) ;
} ,
renderNatFlags : function ( spec ) {
var f = [ ] ;
if ( spec && Array . isArray ( spec . flags ) ) {
for ( var i = 0 ; i < spec . flags . length ; i ++ )
f . push ( expr _translations [ 'natflag.%h' . format ( spec . flags [ i ] ) ] || spec . flags [ i ] ) ;
}
return f . length ? E ( 'small' , { 'class' : 'cbi-tooltip-container' } , [
' (' ,
N _ ( f . length , '1 flag' , '%d flags' , 'nft amount of flags' ) . format ( f . length ) ,
')' ,
E ( 'span' , { 'class' : 'cbi-tooltip' } , f . join ( '<br />' ) )
] ) : E ( [ ] ) ;
} ,
renderRateUnit : function ( value , unit ) {
if ( ! unit )
unit = 'packets' ;
return '%d\xa0%s' . format (
value ,
expr _translations [ 'unit.%h' . format ( unit ) ] || unit
) ;
} ,
renderExpr : function ( expr , table ) {
var kind , spec ;
for ( var k in expr ) {
if ( expr . hasOwnProperty ( k ) ) {
kind = k ;
spec = expr [ k ] ;
}
}
if ( ! kind )
return E ( [ ] ) ;
switch ( kind ) {
case 'match' :
return this . renderMatchExpr ( spec ) ;
case 'reject' :
var k = 'reject.%s' . format ( spec . type ) ;
return E ( 'span' , {
2022-12-13 22:49:11 +00:00
'class' : 'ifacebadge'
2022-01-21 19:22:22 +00:00
} , ( action _translations [ k ] || k ) . format ( this . exprToString ( spec . expr ) ) ) ;
case 'accept' :
case 'drop' :
return E ( 'span' , {
'class' : 'ifacebadge'
} , action _translations [ kind ] || '<em>%h</em>' . format ( kind ) ) ;
case 'jump' :
return E ( 'span' , {
'class' : 'ifacebadge'
} , action _translations . jump . format ( table , spec . target , spec . target ) ) ;
case 'return' :
return E ( 'span' , {
'class' : 'ifacebadge'
} , action _translations . return ) ;
case 'snat' :
case 'dnat' :
var k = '%h.%h' . format ( kind , spec . family ) ,
a = [ ] ;
if ( spec . addr ) {
k += '.addr' ;
a . push ( this . exprToString ( spec . addr ) ) ;
}
if ( spec . port ) {
k += '.port' ;
a . push ( this . exprToString ( spec . port ) ) ;
}
return E ( 'span' , { 'class' : 'ifacebadge' } , [
E ( 'span' , '' . format . apply ( action _translations [ k ] || k , a ) ) ,
this . renderNatFlags ( spec )
] ) ;
case 'redirect' :
var k = 'redirect' ,
a = [ ] ;
if ( spec && spec . port ) {
k += '.port' ;
a . push ( this . exprToString ( spec . port ) ) ;
}
return E ( 'span' , { 'class' : 'ifacebadge' } , [
E ( 'span' , '' . format . apply ( action _translations [ k ] || k , a ) ) ,
this . renderNatFlags ( spec )
] ) ;
case 'masquerade' :
return E ( 'span' , { 'class' : 'ifacebadge' } , [
E ( 'span' , action _translations . masquerade ) ,
this . renderNatFlags ( spec )
] ) ;
case 'mangle' :
return E ( 'span' , { 'class' : 'ifacebadge' } ,
action _translations . mangle . format (
this . exprToString ( spec . key ) ,
this . exprToString ( spec . value )
) ) ;
case 'limit' :
var k = 'limit' ;
var a = [
this . renderRateUnit ( spec . rate , spec . rate _unit ) ,
expr _translations [ 'unit.%h' . format ( spec . per ) ] || spec . per
] ;
if ( spec . inv )
k += '.inv' ;
if ( spec . burst ) {
k += '.burst' ;
a . push ( this . renderRateUnit ( spec . burst , spec . burst _unit ) ) ;
}
return E ( 'span' , { 'class' : 'ifacebadge' , 'cbi-tooltip' : JSON . stringify ( spec ) } ,
'' . format . apply ( action _translations [ k ] || k , a ) ) ;
case 'flow' :
return E ( 'span' , {
'class' : 'ifacebadge'
2022-02-10 19:52:34 +00:00
} , action _translations . flow . format ( spec . flowtable . replace ( /^@/ , '' ) ) ) ;
2022-01-21 19:22:22 +00:00
2022-12-13 22:49:11 +00:00
case 'log' :
return E ( 'span' , {
'class' : 'ifacebadge'
} , action _translations . log . format ( spec . prefix ) ) ;
2022-01-21 19:22:22 +00:00
default :
return E ( 'span' , {
'class' : 'ifacebadge' ,
'data-tooltip' : JSON . stringify ( spec )
} , [ '{ ' , E ( 'strong' , { } , [ kind ] ) , ' }' ] ) ;
}
} ,
renderCounter : function ( data ) {
return E ( 'span' , { 'class' : 'ifacebadge cbi-tooltip-container nft-counter' } , [
E ( 'var' , [ '%.1024mB' . format ( data . bytes ) ] ) ,
E ( 'div' , { 'class' : 'cbi-tooltip' } , [
_ ( 'Traffic matched by rule: %.1000mPackets, %.1024mBytes' , 'nft counter' ) . format ( data . packets , data . bytes )
] )
] ) ;
} ,
renderComment : function ( comment ) {
return E ( 'span' , { 'class' : 'ifacebadge cbi-tooltip-container nft-comment' } , [
E ( 'var' , [ '#' ] ) ,
E ( 'div' , { 'class' : 'cbi-tooltip' } , [
_ ( 'Rule comment: %s' , 'nft comment' ) . format ( comment . replace ( /^!fw4: / , '' ) )
] )
] ) ;
} ,
renderRule : function ( data , spec ) {
var empty = true ;
var row = E ( 'tr' , { 'class' : 'tr' } , [
E ( 'td' , { 'class' : 'td' , 'style' : 'width:60%' } ) ,
E ( 'td' , { 'class' : 'td' , 'style' : 'width:40%' } )
] ) ;
if ( Array . isArray ( spec . expr ) ) {
for ( var i = 0 ; i < spec . expr . length ; i ++ ) {
// nftables JSON format bug, `flow` targets are currently not properly serialized
2022-02-10 19:52:34 +00:00
if ( typeof ( spec . expr [ i ] ) == 'string' && spec . expr [ i ] . match ( /^flow add (@\S+)$/ ) )
spec . expr [ i ] = { flow : { op : "add" , flowtable : RegExp . $1 } } ;
2022-01-21 19:22:22 +00:00
var res = this . renderExpr ( spec . expr [ i ] , spec . table ) ;
if ( typeof ( spec . expr [ i ] ) == 'object' && spec . expr [ i ] . counter ) {
row . childNodes [ 0 ] . insertBefore (
this . renderCounter ( spec . expr [ i ] . counter ) ,
row . childNodes [ 0 ] . firstChild ) ;
}
else if ( this . isActionExpression ( spec . expr [ i ] ) ) {
dom . append ( row . childNodes [ 1 ] , [ res ] ) ;
}
else {
dom . append ( row . childNodes [ 0 ] , [ res ] ) ;
empty = false ;
}
}
}
if ( spec . comment ) {
row . childNodes [ 0 ] . insertBefore (
this . renderComment ( spec . comment ) ,
row . childNodes [ 0 ] . firstChild ) ;
}
if ( empty )
dom . content ( row . childNodes [ 0 ] , E ( 'em' , [ _ ( 'Any packet' , 'nft match any traffic' ) ] ) ) ;
return row ;
} ,
renderChain : function ( data , spec ) {
var title , policy , hook ;
switch ( spec . type ) {
case 'filter' :
title = _ ( 'Traffic filter chain "%h"' ) . format ( spec . name ) ;
break ;
case 'route' :
title = _ ( 'Route action chain "%h"' ) . format ( spec . name ) ;
break ;
case 'nat' :
title = _ ( 'NAT action chain "%h"' ) . format ( spec . name ) ;
break ;
default :
title = _ ( 'Rule container chain "%h"' ) . format ( spec . name ) ;
break ;
}
switch ( spec . policy ) {
case 'drop' :
policy = _ ( 'Drop unmatched packets' , 'Chain policy: drop' ) ;
break ;
default :
policy = _ ( 'Continue processing unmatched packets' , 'Chain policy: accept' ) ;
break ;
}
switch ( spec . hook ) {
case 'ingress' :
hook = _ ( 'Capture packets directly after the NIC received them' , 'Chain hook: ingress' ) ;
break ;
case 'prerouting' :
hook = _ ( 'Capture incoming packets before any routing decision' , 'Chain hook: prerouting' ) ;
break ;
case 'input' :
hook = _ ( 'Capture incoming packets routed to the local system' , 'Chain hook: input' ) ;
break ;
case 'forward' :
hook = _ ( 'Capture incoming packets addressed to other hosts' , 'Chain hook: forward' ) ;
break ;
case 'output' :
hook = _ ( 'Capture outgoing packets originating from the local system' , 'Chain hook: output' ) ;
break ;
case 'postrouting' :
hook = _ ( 'Capture outgoing packets after any routing decision' , 'Chain hook: postrouting' ) ;
break ;
default :
hook = _ ( 'Chain hook "%h"' , 'Yet unknown nftables chain hook' ) . format ( spec . hook ) ;
break ;
}
var node = E ( 'div' , { 'class' : 'nft-chain' } , [
E ( 'h4' , {
'id' : '%h.%h' . format ( spec . table , spec . name )
} , [ title ] )
] ) ;
if ( spec . hook ) {
node . appendChild ( E ( 'div' , { 'class' : 'nft-chain-hook' } , [
E ( 'ul' , { } , [
E ( 'li' , { } , _ ( 'Hook: <strong>%h</strong> (%h), Priority: <strong>%d</strong>' , 'Chain hook description' ) . format ( spec . hook , hook , spec . prio ) ) ,
E ( 'li' , { } , _ ( 'Policy: <strong>%h</strong> (%h)' , 'Chain hook policy' ) . format ( spec . policy , policy ) )
] )
] ) ) ;
}
node . appendChild ( E ( 'table' , { 'class' : 'nft-rules table cbi-section-table' } , [
E ( 'tr' , { 'class' : 'tr table-titles' } , [
E ( 'th' , { 'class' : 'th' , 'style' : 'width:60%' } , [ _ ( 'Rule matches' ) ] ) ,
E ( 'th' , { 'class' : 'th' , 'style' : 'width:40%' } , [ _ ( 'Rule actions' ) ] )
] )
] ) ) ;
for ( var i = 0 ; i < data . length ; i ++ )
if ( typeof ( data [ i ] . rule ) == 'object' && data [ i ] . rule . table == spec . table && data [ i ] . rule . chain == spec . name )
node . lastElementChild . appendChild ( this . renderRule ( data , data [ i ] . rule ) ) ;
if ( node . lastElementChild . childNodes . length == 1 )
node . lastElementChild . appendChild ( E ( 'tr' , { 'class' : 'tr' } , [
E ( 'td' , { 'class' : 'td center' , 'colspan' : 3 } , [
E ( 'em' , [ _ ( 'No rules in this chain' , 'nft chain is empty' ) ] )
] )
] ) ) ;
return node ;
} ,
renderTable : function ( data , spec ) {
var title ;
switch ( spec . family ) {
case 'ip' :
title = _ ( 'IPv4 traffic table "%h"' ) . format ( spec . name ) ;
break ;
case 'ip6' :
title = _ ( 'IPv6 traffic table "%h"' ) . format ( spec . name ) ;
break ;
case 'inet' :
title = _ ( 'IPv4/IPv6 traffic table "%h"' ) . format ( spec . name ) ;
break ;
case 'arp' :
title = _ ( 'ARP traffic table "%h"' ) . format ( spec . name ) ;
break ;
case 'bridge' :
title = _ ( 'Bridge traffic table "%h"' ) . format ( spec . name ) ;
break ;
case 'netdev' :
title = _ ( 'Network device table "%h"' ) . format ( spec . name ) ;
break ;
default :
title = _ ( '"%h" table "%h"' , 'Yet unknown nftables table family ("family" table "name")' ) . format ( spec . family , spec . name ) ;
break ;
}
var node = E ( [ ] , [
E ( 'style' , { 'type' : 'text/css' } , [
'.nft-rules .ifacebadge { margin: .125em }' ,
'.nft-rules tr > td { padding: .25em !important }' ,
'.nft-set, .nft-list { display: inline-block; vertical-align: middle }' ,
'.nft-set-items, .nft-list-items { display: inline-block; vertical-align: middle; max-width: 200px; overflow: hidden; text-overflow: ellipsis }' ,
'.ifacebadge.cbi-tooltip-container { cursor: help }' ,
'.ifacebadge.cbi-tooltip-container .cbi-tooltip { padding: .5em }'
] ) ,
E ( 'div' , { 'class' : 'nft-table' } , [
E ( 'h3' , [ title ] ) ,
E ( 'div' , { 'class' : 'nft-chains' } )
] )
] ) ;
for ( var i = 0 ; i < data . length ; i ++ )
if ( typeof ( data [ i ] . chain ) == 'object' && data [ i ] . chain . table == spec . name )
node . lastElementChild . lastElementChild . appendChild ( this . renderChain ( data , data [ i ] . chain ) ) ;
return node ;
} ,
2022-04-27 11:17:03 +00:00
checkLegacyRules : function ( ipt4save , ipt6save ) {
if ( ipt4save . match ( /\n-A / ) || ipt6save . match ( /\n-A / ) ) {
ui . addNotification ( _ ( 'Legacy rules detected' ) , [
E ( 'p' , _ ( 'There are legacy iptables rules present on the system. Mixing iptables and nftables rules is discouraged and may lead to incomplete traffic filtering.' ) ) ,
E ( 'button' , {
'class' : 'btn cbi-button' ,
'click' : function ( ) { location . href = 'nftables/iptables' }
} , _ ( 'Open iptables rules overview…' ) )
] , 'warning' ) ;
}
} ,
2022-01-21 19:22:22 +00:00
render : function ( data ) {
2022-04-27 11:17:03 +00:00
var view = E ( 'div' ) ,
nft = data [ 0 ] ,
ipt = data [ 1 ] ,
ipt6 = data [ 2 ] ;
this . checkLegacyRules ( ipt , ipt6 ) ;
2022-01-21 19:22:22 +00:00
2022-04-27 11:17:03 +00:00
if ( ! Array . isArray ( nft . nftables ) )
return E ( 'em' , _ ( 'No nftables ruleset loaded.' ) ) ;
2022-01-21 19:22:22 +00:00
2022-04-27 11:17:03 +00:00
for ( var i = 0 ; i < nft . nftables . length ; i ++ )
if ( nft . nftables [ i ] . hasOwnProperty ( 'table' ) )
view . appendChild ( this . renderTable ( nft . nftables , nft . nftables [ i ] . table ) ) ;
2022-01-21 19:22:22 +00:00
return view ;
} ,
handleSaveApply : null ,
handleSave : null ,
handleReset : null
} ) ;