mirror of
https://github.com/webmin/webmin.git
synced 2026-05-06 23:30:29 +01:00
Expand parser, cover more kinds of rules
This commit is contained in:
105
edit_rule.cgi
105
edit_rule.cgi
@@ -12,6 +12,10 @@ my $table = $tables[$in{'table'}];
|
||||
my $rule;
|
||||
my $chain_def;
|
||||
my $chain_hook;
|
||||
my $action_sel;
|
||||
my $proto_sel;
|
||||
my $icmp_type;
|
||||
my $log_enabled;
|
||||
|
||||
if ($in{'new'}) {
|
||||
&ui_print_header(undef, $text{'edit_title_new'}, "", "intro", 1, 1);
|
||||
@@ -24,6 +28,26 @@ if ($table && $rule->{'chain'}) {
|
||||
$chain_def = $table->{'chains'}->{$rule->{'chain'}};
|
||||
$chain_hook = $chain_def ? $chain_def->{'hook'} : undef;
|
||||
}
|
||||
if ($rule) {
|
||||
if ($rule->{'jump'}) {
|
||||
$action_sel = 'jump';
|
||||
}
|
||||
elsif ($rule->{'goto'}) {
|
||||
$action_sel = 'goto';
|
||||
}
|
||||
else {
|
||||
$action_sel = $rule->{'action'};
|
||||
}
|
||||
$action_sel ||= 'accept';
|
||||
$proto_sel = $rule->{'proto'} || $rule->{'l4proto'};
|
||||
if (!$proto_sel) {
|
||||
$proto_sel = 'icmp' if ($rule->{'icmp_type'});
|
||||
$proto_sel = 'icmpv6' if ($rule->{'icmpv6_type'});
|
||||
}
|
||||
$proto_sel ||= 'tcp' if ($in{'new'});
|
||||
$icmp_type = $rule->{'icmp_type'} || $rule->{'icmpv6_type'};
|
||||
$log_enabled = $rule->{'log'} || $rule->{'log_prefix'} || $rule->{'log_level'};
|
||||
}
|
||||
|
||||
print &ui_form_start("save_rule.cgi");
|
||||
print &ui_hidden("table", $in{'table'});
|
||||
@@ -39,26 +63,23 @@ print &ui_table_row($text{'edit_comment'},
|
||||
|
||||
# Action
|
||||
print &ui_table_row($text{'edit_action'},
|
||||
&ui_select("action", $rule->{'action'},
|
||||
&ui_select("action", $action_sel,
|
||||
[
|
||||
[ "accept", $text{'index_accept'} ],
|
||||
[ "drop", $text{'index_drop'} ],
|
||||
[ "reject", $text{'index_reject'} ],
|
||||
[ "return", $text{'edit_return'} ],
|
||||
[ "jump", $text{'edit_jump_action'} ],
|
||||
[ "goto", $text{'edit_goto_action'} ],
|
||||
]));
|
||||
|
||||
# Protocol
|
||||
print &ui_table_row($text{'edit_proto'},
|
||||
&ui_select("proto", $rule->{'proto'},
|
||||
[
|
||||
[ "tcp", "TCP" ],
|
||||
[ "udp", "UDP" ],
|
||||
[ "icmp", "ICMP" ],
|
||||
]));
|
||||
|
||||
# Destination port
|
||||
print &ui_table_row($text{'edit_dport'},
|
||||
&ui_textbox("dport", $rule->{'dport'}, 10));
|
||||
# Jump/Goto target chain
|
||||
print &ui_table_row($text{'edit_jump'},
|
||||
&ui_textbox("jump", $rule->{'jump'}, 20));
|
||||
print &ui_table_row($text{'edit_goto'},
|
||||
&ui_textbox("goto", $rule->{'goto'}, 20));
|
||||
|
||||
# Interfaces
|
||||
if ($chain_hook && $chain_hook eq 'input') {
|
||||
# Incoming interface
|
||||
print &ui_table_row($text{'edit_iif'},
|
||||
@@ -77,6 +98,64 @@ else {
|
||||
&interface_choice("oif", $rule->{'oif'}, $text{'edit_if_any'}));
|
||||
}
|
||||
|
||||
# Addresses
|
||||
print &ui_table_row($text{'edit_saddr'},
|
||||
&ui_textbox("saddr", $rule->{'saddr'}, 30));
|
||||
print &ui_table_row($text{'edit_daddr'},
|
||||
&ui_textbox("daddr", $rule->{'daddr'}, 30));
|
||||
|
||||
# Protocol
|
||||
print &ui_table_row($text{'edit_proto'},
|
||||
&ui_select("proto", $proto_sel,
|
||||
[
|
||||
[ "", $text{'edit_proto_any'} ],
|
||||
[ "tcp", "TCP" ],
|
||||
[ "udp", "UDP" ],
|
||||
[ "icmp", "ICMP" ],
|
||||
[ "icmpv6", "ICMPv6" ],
|
||||
]));
|
||||
|
||||
# Ports
|
||||
print &ui_table_row($text{'edit_sport'},
|
||||
&ui_textbox("sport", $rule->{'sport'}, 10));
|
||||
print &ui_table_row($text{'edit_dport'},
|
||||
&ui_textbox("dport", $rule->{'dport'}, 10));
|
||||
|
||||
# ICMP type
|
||||
print &ui_table_row($text{'edit_icmp_type'},
|
||||
&ui_textbox("icmp_type", $icmp_type, 20));
|
||||
|
||||
# Conntrack state
|
||||
print &ui_table_row($text{'edit_ct_state'},
|
||||
&ui_textbox("ct_state", $rule->{'ct_state'}, 30));
|
||||
|
||||
# TCP flags
|
||||
print &ui_table_row($text{'edit_tcp_flags'},
|
||||
&ui_textbox("tcp_flags", $rule->{'tcp_flags'}, 20));
|
||||
print &ui_table_row($text{'edit_tcp_flags_mask'},
|
||||
&ui_textbox("tcp_flags_mask", $rule->{'tcp_flags_mask'}, 20));
|
||||
|
||||
# Limit
|
||||
print &ui_table_row($text{'edit_limit_rate'},
|
||||
&ui_textbox("limit_rate", $rule->{'limit_rate'}, 20));
|
||||
print &ui_table_row($text{'edit_limit_burst'},
|
||||
&ui_textbox("limit_burst", $rule->{'limit_burst'}, 10));
|
||||
|
||||
# Log
|
||||
my $log_row = &ui_checkbox("log", 1, $text{'edit_log_enable'}, $log_enabled);
|
||||
$log_row .= "<br>".&text('edit_log_prefix', &ui_textbox("log_prefix", $rule->{'log_prefix'}, 20));
|
||||
$log_row .= " ".&text('edit_log_level', &ui_textbox("log_level", $rule->{'log_level'}, 10));
|
||||
print &ui_table_row($text{'edit_log'}, $log_row);
|
||||
|
||||
# Counter
|
||||
print &ui_table_row($text{'edit_counter'},
|
||||
&ui_checkbox("counter", 1, $text{'edit_counter_enable'}, $rule->{'counter'}));
|
||||
|
||||
# Raw rule (read-only)
|
||||
print &ui_table_row($text{'edit_raw_rule'},
|
||||
&ui_textarea("raw_rule", $rule->{'text'}, 4, 60, undef, undef,
|
||||
"readonly='true'"));
|
||||
|
||||
print &ui_table_end();
|
||||
my @buttons;
|
||||
if ($in{'new'}) {
|
||||
|
||||
44
lang/en
44
lang/en
@@ -7,6 +7,7 @@ index_change=Show table:
|
||||
index_table_filter=Packet filtering
|
||||
index_table_nat=Network address translation
|
||||
index_table_mangle=Packet alteration
|
||||
index_table_ok=Display table
|
||||
index_chain=Chain $1
|
||||
index_action=Action
|
||||
index_desc=Condition
|
||||
@@ -67,15 +68,58 @@ index_rule_desc=Action $1 for protocol $2 and destination port $3
|
||||
index_rule_desc2=Action $1 for outgoing interface $2
|
||||
index_rule_desc3=Action $1 for incoming interface $2
|
||||
index_rule_desc4=Action $1 for incoming interface $2 and outgoing interface $3
|
||||
index_rule_desc_generic=Action $1 for $2
|
||||
index_rule_desc_action=Action $1
|
||||
index_rule_iif=Incoming interface $1
|
||||
index_rule_oif=Outgoing interface $1
|
||||
index_rule_saddr=Source $1
|
||||
index_rule_daddr=Destination $1
|
||||
index_rule_proto=Protocol $1
|
||||
index_rule_sport=Source port $1
|
||||
index_rule_dport=Destination port $1
|
||||
index_rule_icmp=ICMP type $1
|
||||
index_rule_icmpv6=ICMPv6 type $1
|
||||
index_rule_ct=Conntrack state $1
|
||||
index_rule_tcpflags=TCP flags $1
|
||||
index_rule_limit=Limit $1
|
||||
index_rule_log=Log
|
||||
index_rule_log_prefix=Log prefix $1
|
||||
index_rule_log_level=Log level $1
|
||||
index_rule_counter=Counter
|
||||
index_rule_jump=Jump $1
|
||||
index_rule_goto=Goto $1
|
||||
index_accept=Accept
|
||||
index_drop=Drop
|
||||
index_reject=Reject
|
||||
index_return_action=Return
|
||||
edit_title_new=Create Rule
|
||||
edit_title_edit=Edit Rule
|
||||
edit_comment=Comment
|
||||
edit_action=Action
|
||||
edit_return=Return
|
||||
edit_jump_action=Jump
|
||||
edit_goto_action=Goto
|
||||
edit_jump=Jump target chain
|
||||
edit_goto=Goto target chain
|
||||
edit_proto=Protocol
|
||||
edit_proto_any=Any
|
||||
edit_saddr=Source address
|
||||
edit_daddr=Destination address
|
||||
edit_sport=Source Port
|
||||
edit_dport=Destination Port
|
||||
edit_icmp_type=ICMP/ICMPv6 type
|
||||
edit_ct_state=Conntrack state
|
||||
edit_tcp_flags=TCP flags
|
||||
edit_tcp_flags_mask=TCP flags mask
|
||||
edit_limit_rate=Limit rate
|
||||
edit_limit_burst=Limit burst
|
||||
edit_log=Logging
|
||||
edit_log_enable=Enable logging
|
||||
edit_log_prefix=Prefix $1
|
||||
edit_log_level=Level $1
|
||||
edit_counter=Counter
|
||||
edit_counter_enable=Enable counter
|
||||
edit_raw_rule=Raw rule
|
||||
edit_oif=Outgoing Interface
|
||||
edit_iif=Incoming Interface
|
||||
edit_if_any=Any
|
||||
|
||||
716
nftables-lib.pl
716
nftables-lib.pl
@@ -74,31 +74,11 @@ for(my $i=0; $i<@lines; $i++) {
|
||||
'index' => scalar(@{$table->{'rules'}}),
|
||||
'line' => $lnum
|
||||
};
|
||||
if ($rule_str =~ /\bcomment\s+"((?:\\.|[^"\\])*)"/) {
|
||||
my $c = $1;
|
||||
$c =~ s/\\"/"/g;
|
||||
$c =~ s/\\\\/\\/g;
|
||||
$rule->{'comment'} = $c;
|
||||
}
|
||||
if ($rule_str =~ /(\S+)\s+dport\s+(\d+)/) {
|
||||
$rule->{'proto'} = $1;
|
||||
$rule->{'dport'} = $2;
|
||||
}
|
||||
if ($rule_str =~ /\biif\s+"([^"]+)"/) {
|
||||
$rule->{'iif'} = $1;
|
||||
}
|
||||
elsif ($rule_str =~ /\biif\s+(\S+)/) {
|
||||
$rule->{'iif'} = $1;
|
||||
}
|
||||
if ($rule_str =~ /\boif\s+"([^"]+)"/) {
|
||||
$rule->{'oif'} = $1;
|
||||
}
|
||||
elsif ($rule_str =~ /\boif\s+(\S+)/) {
|
||||
$rule->{'oif'} = $1;
|
||||
}
|
||||
my @actions = ($rule_str =~ /\b(accept|drop|reject)\b/g);
|
||||
if (@actions) {
|
||||
$rule->{'action'} = $actions[-1];
|
||||
my $parsed = &parse_rule_text($rule_str);
|
||||
if ($parsed) {
|
||||
foreach my $k (keys %$parsed) {
|
||||
$rule->{$k} = $parsed->{$k};
|
||||
}
|
||||
}
|
||||
push(@{$table->{'rules'}}, $rule);
|
||||
}
|
||||
@@ -108,6 +88,604 @@ for(my $i=0; $i<@lines; $i++) {
|
||||
return @rv;
|
||||
}
|
||||
|
||||
sub tokenize_nft_rule
|
||||
{
|
||||
my ($line) = @_;
|
||||
my @tokens;
|
||||
my $i = 0;
|
||||
my $len = length($line);
|
||||
while ($i < $len) {
|
||||
my $ch = substr($line, $i, 1);
|
||||
if ($ch =~ /\s/) {
|
||||
$i++;
|
||||
next;
|
||||
}
|
||||
if ($ch eq '"' || $ch eq "'") {
|
||||
my $q = $ch;
|
||||
my $j = $i + 1;
|
||||
my $esc = 0;
|
||||
while ($j < $len) {
|
||||
my $c = substr($line, $j, 1);
|
||||
if ($esc) {
|
||||
$esc = 0;
|
||||
}
|
||||
elsif ($c eq "\\") {
|
||||
$esc = 1;
|
||||
}
|
||||
elsif ($c eq $q) {
|
||||
$j++;
|
||||
last;
|
||||
}
|
||||
$j++;
|
||||
}
|
||||
push(@tokens, substr($line, $i, $j-$i));
|
||||
$i = $j;
|
||||
next;
|
||||
}
|
||||
if ($ch eq '{') {
|
||||
my $j = $i + 1;
|
||||
my $depth = 1;
|
||||
while ($j < $len && $depth > 0) {
|
||||
my $c = substr($line, $j, 1);
|
||||
if ($c eq '{') {
|
||||
$depth++;
|
||||
}
|
||||
elsif ($c eq '}') {
|
||||
$depth--;
|
||||
}
|
||||
$j++;
|
||||
}
|
||||
push(@tokens, substr($line, $i, $j-$i));
|
||||
$i = $j;
|
||||
next;
|
||||
}
|
||||
my $j = $i;
|
||||
while ($j < $len && substr($line, $j, 1) !~ /\s/) {
|
||||
$j++;
|
||||
}
|
||||
push(@tokens, substr($line, $i, $j-$i));
|
||||
$i = $j;
|
||||
}
|
||||
return @tokens;
|
||||
}
|
||||
|
||||
sub unquote_nft_string
|
||||
{
|
||||
my ($s) = @_;
|
||||
return $s if (!defined($s));
|
||||
if ($s =~ /^"(.*)"$/s) {
|
||||
$s = $1;
|
||||
$s =~ s/\\(["\\])/$1/g;
|
||||
}
|
||||
elsif ($s =~ /^'(.*)'$/s) {
|
||||
$s = $1;
|
||||
$s =~ s/\\(['\\])/$1/g;
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
|
||||
sub escape_nft_string
|
||||
{
|
||||
my ($s) = @_;
|
||||
return "" if (!defined($s));
|
||||
$s =~ s/\\/\\\\/g;
|
||||
$s =~ s/"/\\"/g;
|
||||
return $s;
|
||||
}
|
||||
|
||||
sub guess_addr_family
|
||||
{
|
||||
my ($addr, $fallback) = @_;
|
||||
return $fallback if ($fallback);
|
||||
return "ip6" if (defined($addr) && $addr =~ /:/);
|
||||
return "ip";
|
||||
}
|
||||
|
||||
sub format_addr_expr
|
||||
{
|
||||
my ($dir, $rule) = @_;
|
||||
my $val = $rule->{$dir};
|
||||
return undef if (!defined($val) || $val eq '');
|
||||
my $fam = &guess_addr_family($val, $rule->{$dir."_family"});
|
||||
return $fam." ".$dir." ".$val;
|
||||
}
|
||||
|
||||
sub format_l4proto_expr
|
||||
{
|
||||
my ($rule) = @_;
|
||||
my $proto = $rule->{'l4proto'};
|
||||
return undef if (!defined($proto) || $proto eq '');
|
||||
my $fam = $rule->{'l4proto_family'} || 'meta';
|
||||
if ($fam eq 'ip' || $fam eq 'ip6') {
|
||||
return $fam." protocol ".$proto;
|
||||
}
|
||||
return "meta l4proto ".$proto;
|
||||
}
|
||||
|
||||
sub format_port_expr
|
||||
{
|
||||
my ($dir, $rule) = @_;
|
||||
my $val = $rule->{$dir};
|
||||
return undef if (!defined($val) || $val eq '');
|
||||
my $proto;
|
||||
if ($dir eq 'sport') {
|
||||
$proto = $rule->{'sport_proto'} || $rule->{'proto'} || $rule->{'l4proto'};
|
||||
}
|
||||
else {
|
||||
$proto = $rule->{'proto'} || $rule->{'l4proto'};
|
||||
}
|
||||
return undef if (!defined($proto) || $proto eq '');
|
||||
return $proto." ".$dir." ".$val;
|
||||
}
|
||||
|
||||
sub format_tcp_flags_expr
|
||||
{
|
||||
my ($rule) = @_;
|
||||
return undef if (!defined($rule->{'tcp_flags'}) || $rule->{'tcp_flags'} eq '');
|
||||
my $val = $rule->{'tcp_flags'};
|
||||
if (defined($rule->{'tcp_flags_mask'}) && $rule->{'tcp_flags_mask'} ne '') {
|
||||
return "tcp flags & ".$rule->{'tcp_flags_mask'}." == ".$val;
|
||||
}
|
||||
return "tcp flags ".$val;
|
||||
}
|
||||
|
||||
sub format_limit_expr
|
||||
{
|
||||
my ($rule) = @_;
|
||||
return undef if (!defined($rule->{'limit_rate'}) || $rule->{'limit_rate'} eq '');
|
||||
my $out = "limit rate ".$rule->{'limit_rate'};
|
||||
if (defined($rule->{'limit_burst'}) && $rule->{'limit_burst'} ne '') {
|
||||
my $burst = $rule->{'limit_burst'};
|
||||
$out .= " burst ".$burst;
|
||||
$out .= " packets" if ($burst =~ /^\d+$/);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
sub format_log_expr
|
||||
{
|
||||
my ($rule) = @_;
|
||||
return undef if (!$rule->{'log'} && !$rule->{'log_prefix'} && !$rule->{'log_level'});
|
||||
my @p = ("log");
|
||||
if (defined($rule->{'log_prefix'}) && $rule->{'log_prefix'} ne '') {
|
||||
my $pfx = &escape_nft_string($rule->{'log_prefix'});
|
||||
push(@p, "prefix", "\"".$pfx."\"");
|
||||
}
|
||||
if (defined($rule->{'log_level'}) && $rule->{'log_level'} ne '') {
|
||||
push(@p, "level", $rule->{'log_level'});
|
||||
}
|
||||
return join(" ", @p);
|
||||
}
|
||||
|
||||
sub parse_rule_text
|
||||
{
|
||||
my ($line) = @_;
|
||||
return { } if (!defined($line));
|
||||
my %rule;
|
||||
my @tokens = &tokenize_nft_rule($line);
|
||||
my @exprs;
|
||||
my $i = 0;
|
||||
while ($i < @tokens) {
|
||||
my $tok = $tokens[$i];
|
||||
|
||||
if ($tok eq 'comment' && $i+1 < @tokens) {
|
||||
my $raw = $tokens[$i]." ".$tokens[$i+1];
|
||||
$rule{'comment'} = &unquote_nft_string($tokens[$i+1]);
|
||||
push(@exprs, { 'type' => 'comment', 'text' => $raw });
|
||||
$i += 2;
|
||||
next;
|
||||
}
|
||||
if (($tok eq 'iif' || $tok eq 'iifname') && $i+1 < @tokens) {
|
||||
my $raw = $tok." ".$tokens[$i+1];
|
||||
$rule{'iif'} = &unquote_nft_string($tokens[$i+1]);
|
||||
$rule{'iif_type'} = $tok;
|
||||
push(@exprs, { 'type' => 'iif', 'text' => $raw });
|
||||
$i += 2;
|
||||
next;
|
||||
}
|
||||
if (($tok eq 'oif' || $tok eq 'oifname') && $i+1 < @tokens) {
|
||||
my $raw = $tok." ".$tokens[$i+1];
|
||||
$rule{'oif'} = &unquote_nft_string($tokens[$i+1]);
|
||||
$rule{'oif_type'} = $tok;
|
||||
push(@exprs, { 'type' => 'oif', 'text' => $raw });
|
||||
$i += 2;
|
||||
next;
|
||||
}
|
||||
if (($tok eq 'ip' || $tok eq 'ip6') && $i+2 < @tokens &&
|
||||
($tokens[$i+1] eq 'saddr' || $tokens[$i+1] eq 'daddr')) {
|
||||
my $which = $tokens[$i+1];
|
||||
my $val = $tokens[$i+2];
|
||||
my $raw = $tok." ".$which." ".$val;
|
||||
$rule{$which} = $val;
|
||||
$rule{$which."_family"} = $tok;
|
||||
push(@exprs, { 'type' => $which, 'text' => $raw });
|
||||
$i += 3;
|
||||
next;
|
||||
}
|
||||
if (($tok eq 'ip' || $tok eq 'ip6') && $i+2 < @tokens &&
|
||||
$tokens[$i+1] eq 'protocol') {
|
||||
my $val = $tokens[$i+2];
|
||||
my $raw = $tok." protocol ".$val;
|
||||
$rule{'l4proto'} = $val;
|
||||
$rule{'l4proto_family'} = $tok;
|
||||
push(@exprs, { 'type' => 'l4proto', 'text' => $raw });
|
||||
$i += 3;
|
||||
next;
|
||||
}
|
||||
if ($tok eq 'meta' && $i+2 < @tokens &&
|
||||
$tokens[$i+1] eq 'l4proto') {
|
||||
my $val = $tokens[$i+2];
|
||||
my $raw = "meta l4proto ".$val;
|
||||
$rule{'l4proto'} = $val;
|
||||
$rule{'l4proto_family'} = 'meta';
|
||||
push(@exprs, { 'type' => 'l4proto', 'text' => $raw });
|
||||
$i += 3;
|
||||
next;
|
||||
}
|
||||
if ($tok eq 'tcp' && $i+1 < @tokens && $tokens[$i+1] eq 'flags') {
|
||||
my $j = $i + 2;
|
||||
my $mask;
|
||||
my $val;
|
||||
if ($j < @tokens && $tokens[$j] eq '&' && $j+1 < @tokens) {
|
||||
$mask = $tokens[$j+1];
|
||||
$j += 2;
|
||||
}
|
||||
if ($j < @tokens && $tokens[$j] eq '==' && $j+1 < @tokens) {
|
||||
$val = $tokens[$j+1];
|
||||
$j += 2;
|
||||
}
|
||||
elsif ($j < @tokens) {
|
||||
$val = $tokens[$j];
|
||||
$j++;
|
||||
}
|
||||
my $raw = join(" ", @tokens[$i..($j-1)]);
|
||||
$rule{'tcp_flags'} = $val if (defined($val));
|
||||
$rule{'tcp_flags_mask'} = $mask if (defined($mask));
|
||||
push(@exprs, { 'type' => 'tcp_flags', 'text' => $raw });
|
||||
$i = $j;
|
||||
next;
|
||||
}
|
||||
if (($tok eq 'tcp' || $tok eq 'udp') && $i+2 < @tokens &&
|
||||
($tokens[$i+1] eq 'dport' || $tokens[$i+1] eq 'sport')) {
|
||||
my $dir = $tokens[$i+1];
|
||||
my $val = $tokens[$i+2];
|
||||
my $raw = $tok." ".$dir." ".$val;
|
||||
if ($dir eq 'dport') {
|
||||
$rule{'proto'} = $tok;
|
||||
$rule{'dport'} = $val;
|
||||
}
|
||||
else {
|
||||
$rule{'sport'} = $val;
|
||||
$rule{'sport_proto'} = $tok;
|
||||
}
|
||||
push(@exprs, { 'type' => $dir, 'text' => $raw, 'proto' => $tok });
|
||||
$i += 3;
|
||||
next;
|
||||
}
|
||||
if (($tok eq 'icmp' || $tok eq 'icmpv6') && $i+2 < @tokens &&
|
||||
$tokens[$i+1] eq 'type') {
|
||||
my $val = $tokens[$i+2];
|
||||
my $raw = $tok." type ".$val;
|
||||
if ($tok eq 'icmp') {
|
||||
$rule{'icmp_type'} = $val;
|
||||
}
|
||||
else {
|
||||
$rule{'icmpv6_type'} = $val;
|
||||
}
|
||||
push(@exprs, { 'type' => $tok, 'text' => $raw });
|
||||
$i += 3;
|
||||
next;
|
||||
}
|
||||
if ($tok eq 'ct' && $i+2 < @tokens && $tokens[$i+1] eq 'state') {
|
||||
my $val = $tokens[$i+2];
|
||||
my $raw = "ct state ".$val;
|
||||
$rule{'ct_state'} = $val;
|
||||
push(@exprs, { 'type' => 'ct_state', 'text' => $raw });
|
||||
$i += 3;
|
||||
next;
|
||||
}
|
||||
if ($tok eq 'limit') {
|
||||
my $j = $i + 1;
|
||||
my @lt = ($tok);
|
||||
if ($j < @tokens && $tokens[$j] eq 'rate' && $j+1 < @tokens) {
|
||||
push(@lt, $tokens[$j], $tokens[$j+1]);
|
||||
$rule{'limit_rate'} = $tokens[$j+1];
|
||||
$j += 2;
|
||||
if ($j < @tokens && $tokens[$j] eq 'burst' && $j+1 < @tokens) {
|
||||
push(@lt, $tokens[$j], $tokens[$j+1]);
|
||||
$rule{'limit_burst'} = $tokens[$j+1];
|
||||
$j += 2;
|
||||
if ($j < @tokens && $tokens[$j] eq 'packets') {
|
||||
push(@lt, $tokens[$j]);
|
||||
$j++;
|
||||
}
|
||||
}
|
||||
}
|
||||
my $raw = join(" ", @lt);
|
||||
push(@exprs, { 'type' => 'limit', 'text' => $raw });
|
||||
$i = $j;
|
||||
next;
|
||||
}
|
||||
if ($tok eq 'log') {
|
||||
my $j = $i + 1;
|
||||
my @lt = ($tok);
|
||||
while ($j < @tokens) {
|
||||
if ($tokens[$j] eq 'prefix' && $j+1 < @tokens) {
|
||||
$rule{'log_prefix'} = &unquote_nft_string($tokens[$j+1]);
|
||||
push(@lt, $tokens[$j], $tokens[$j+1]);
|
||||
$j += 2;
|
||||
next;
|
||||
}
|
||||
if ($tokens[$j] eq 'level' && $j+1 < @tokens) {
|
||||
$rule{'log_level'} = $tokens[$j+1];
|
||||
push(@lt, $tokens[$j], $tokens[$j+1]);
|
||||
$j += 2;
|
||||
next;
|
||||
}
|
||||
last;
|
||||
}
|
||||
$rule{'log'} = 1;
|
||||
my $raw = join(" ", @lt);
|
||||
push(@exprs, { 'type' => 'log', 'text' => $raw });
|
||||
$i = $j;
|
||||
next;
|
||||
}
|
||||
if ($tok eq 'counter') {
|
||||
$rule{'counter'} = 1;
|
||||
push(@exprs, { 'type' => 'counter', 'text' => $tok });
|
||||
$i++;
|
||||
next;
|
||||
}
|
||||
if ($tok =~ /^(accept|drop|reject|return)$/) {
|
||||
$rule{'action'} = $tok;
|
||||
push(@exprs, { 'type' => 'action', 'text' => $tok });
|
||||
$i++;
|
||||
next;
|
||||
}
|
||||
if (($tok eq 'jump' || $tok eq 'goto') && $i+1 < @tokens) {
|
||||
my $raw = $tok." ".$tokens[$i+1];
|
||||
$rule{$tok} = $tokens[$i+1];
|
||||
push(@exprs, { 'type' => $tok, 'text' => $raw });
|
||||
$i += 2;
|
||||
next;
|
||||
}
|
||||
|
||||
push(@exprs, { 'type' => 'raw', 'text' => $tok });
|
||||
$i++;
|
||||
}
|
||||
$rule{'exprs'} = \@exprs;
|
||||
return \%rule;
|
||||
}
|
||||
|
||||
sub format_rule_text
|
||||
{
|
||||
my ($rule) = @_;
|
||||
return "" if (!$rule || ref($rule) ne 'HASH');
|
||||
my @parts;
|
||||
my %used;
|
||||
my $exprs = $rule->{'exprs'};
|
||||
if ($exprs && ref($exprs) eq 'ARRAY' && @$exprs) {
|
||||
foreach my $e (@$exprs) {
|
||||
my $type = $e->{'type'} || 'raw';
|
||||
if ($type eq 'action' || $type eq 'comment') {
|
||||
next;
|
||||
}
|
||||
if ($type eq 'iif') {
|
||||
if (!$used{'iif'} && defined($rule->{'iif'}) && $rule->{'iif'} ne '') {
|
||||
my $iftype = $rule->{'iif_type'} || 'iif';
|
||||
my $ival = &escape_nft_string($rule->{'iif'});
|
||||
push(@parts, $iftype." \"".$ival."\"");
|
||||
$used{'iif'} = 1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'oif') {
|
||||
if (!$used{'oif'} && defined($rule->{'oif'}) && $rule->{'oif'} ne '') {
|
||||
my $oftype = $rule->{'oif_type'} || 'oif';
|
||||
my $oval = &escape_nft_string($rule->{'oif'});
|
||||
push(@parts, $oftype." \"".$oval."\"");
|
||||
$used{'oif'} = 1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'saddr') {
|
||||
if (!$used{'saddr'}) {
|
||||
my $addr = &format_addr_expr('saddr', $rule);
|
||||
if ($addr) {
|
||||
push(@parts, $addr);
|
||||
$used{'saddr'} = 1;
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'daddr') {
|
||||
if (!$used{'daddr'}) {
|
||||
my $addr = &format_addr_expr('daddr', $rule);
|
||||
if ($addr) {
|
||||
push(@parts, $addr);
|
||||
$used{'daddr'} = 1;
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'l4proto') {
|
||||
if (!$used{'l4proto'}) {
|
||||
my $lp = &format_l4proto_expr($rule);
|
||||
if ($lp) {
|
||||
push(@parts, $lp);
|
||||
$used{'l4proto'} = 1;
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'sport') {
|
||||
if (!$used{'sport'}) {
|
||||
my $sp = &format_port_expr('sport', $rule);
|
||||
if ($sp) {
|
||||
push(@parts, $sp);
|
||||
$used{'sport'} = 1;
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'dport') {
|
||||
if (!$used{'dport'} && $rule->{'proto'} && $rule->{'dport'}) {
|
||||
my $dp = &format_port_expr('dport', $rule);
|
||||
if ($dp) {
|
||||
push(@parts, $dp);
|
||||
$used{'dport'} = 1;
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'icmp') {
|
||||
if (!$used{'icmp'} && $rule->{'icmp_type'}) {
|
||||
push(@parts, "icmp type ".$rule->{'icmp_type'});
|
||||
$used{'icmp'} = 1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'icmpv6') {
|
||||
if (!$used{'icmpv6'} && $rule->{'icmpv6_type'}) {
|
||||
push(@parts, "icmpv6 type ".$rule->{'icmpv6_type'});
|
||||
$used{'icmpv6'} = 1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'ct_state') {
|
||||
if (!$used{'ct_state'} && $rule->{'ct_state'}) {
|
||||
push(@parts, "ct state ".$rule->{'ct_state'});
|
||||
$used{'ct_state'} = 1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'tcp_flags') {
|
||||
if (!$used{'tcp_flags'}) {
|
||||
my $tf = &format_tcp_flags_expr($rule);
|
||||
if ($tf) {
|
||||
push(@parts, $tf);
|
||||
$used{'tcp_flags'} = 1;
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'limit') {
|
||||
if (!$used{'limit'}) {
|
||||
my $lim = &format_limit_expr($rule);
|
||||
if ($lim) {
|
||||
push(@parts, $lim);
|
||||
$used{'limit'} = 1;
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'log') {
|
||||
if (!$used{'log'}) {
|
||||
my $lg = &format_log_expr($rule);
|
||||
if ($lg) {
|
||||
push(@parts, $lg);
|
||||
$used{'log'} = 1;
|
||||
}
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'counter') {
|
||||
if (!$used{'counter'} && $rule->{'counter'}) {
|
||||
push(@parts, "counter");
|
||||
$used{'counter'} = 1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'jump') {
|
||||
if (!$used{'jump'} && $rule->{'jump'}) {
|
||||
push(@parts, "jump ".$rule->{'jump'});
|
||||
$used{'jump'} = 1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
if ($type eq 'goto') {
|
||||
if (!$used{'goto'} && $rule->{'goto'}) {
|
||||
push(@parts, "goto ".$rule->{'goto'});
|
||||
$used{'goto'} = 1;
|
||||
}
|
||||
next;
|
||||
}
|
||||
push(@parts, $e->{'text'}) if ($e->{'text'});
|
||||
}
|
||||
}
|
||||
if (!$used{'iif'} && defined($rule->{'iif'}) && $rule->{'iif'} ne '') {
|
||||
my $iftype = $rule->{'iif_type'} || 'iif';
|
||||
my $ival = &escape_nft_string($rule->{'iif'});
|
||||
push(@parts, $iftype." \"".$ival."\"");
|
||||
}
|
||||
if (!$used{'oif'} && defined($rule->{'oif'}) && $rule->{'oif'} ne '') {
|
||||
my $oftype = $rule->{'oif_type'} || 'oif';
|
||||
my $oval = &escape_nft_string($rule->{'oif'});
|
||||
push(@parts, $oftype." \"".$oval."\"");
|
||||
}
|
||||
if (!$used{'saddr'}) {
|
||||
my $addr = &format_addr_expr('saddr', $rule);
|
||||
push(@parts, $addr) if ($addr);
|
||||
}
|
||||
if (!$used{'daddr'}) {
|
||||
my $addr = &format_addr_expr('daddr', $rule);
|
||||
push(@parts, $addr) if ($addr);
|
||||
}
|
||||
if (!$used{'l4proto'}) {
|
||||
my $lp = &format_l4proto_expr($rule);
|
||||
push(@parts, $lp) if ($lp);
|
||||
}
|
||||
if (!$used{'sport'}) {
|
||||
my $sp = &format_port_expr('sport', $rule);
|
||||
push(@parts, $sp) if ($sp);
|
||||
}
|
||||
if (!$used{'dport'}) {
|
||||
my $dp = &format_port_expr('dport', $rule);
|
||||
push(@parts, $dp) if ($dp);
|
||||
}
|
||||
if (!$used{'icmp'} && $rule->{'icmp_type'}) {
|
||||
push(@parts, "icmp type ".$rule->{'icmp_type'});
|
||||
}
|
||||
if (!$used{'icmpv6'} && $rule->{'icmpv6_type'}) {
|
||||
push(@parts, "icmpv6 type ".$rule->{'icmpv6_type'});
|
||||
}
|
||||
if (!$used{'tcp_flags'}) {
|
||||
my $tf = &format_tcp_flags_expr($rule);
|
||||
push(@parts, $tf) if ($tf);
|
||||
}
|
||||
if (!$used{'ct_state'} && $rule->{'ct_state'}) {
|
||||
push(@parts, "ct state ".$rule->{'ct_state'});
|
||||
}
|
||||
if (!$used{'limit'}) {
|
||||
my $lim = &format_limit_expr($rule);
|
||||
push(@parts, $lim) if ($lim);
|
||||
}
|
||||
if (!$used{'log'}) {
|
||||
my $lg = &format_log_expr($rule);
|
||||
push(@parts, $lg) if ($lg);
|
||||
}
|
||||
if (!$used{'counter'} && $rule->{'counter'}) {
|
||||
push(@parts, "counter");
|
||||
}
|
||||
if (!$used{'jump'} && $rule->{'jump'}) {
|
||||
push(@parts, "jump ".$rule->{'jump'});
|
||||
}
|
||||
if (!$used{'goto'} && $rule->{'goto'}) {
|
||||
push(@parts, "goto ".$rule->{'goto'});
|
||||
}
|
||||
if ($rule->{'action'} && !$rule->{'jump'} && !$rule->{'goto'}) {
|
||||
push(@parts, $rule->{'action'});
|
||||
}
|
||||
if (defined($rule->{'comment'}) && $rule->{'comment'} ne '') {
|
||||
my $c = &escape_nft_string($rule->{'comment'});
|
||||
push(@parts, "comment \"".$c."\"");
|
||||
}
|
||||
my $text = join(" ", grep { defined($_) && $_ ne '' } @parts);
|
||||
$text =~ s/^\s+//;
|
||||
$text =~ s/\s+$//;
|
||||
return $text;
|
||||
}
|
||||
|
||||
|
||||
# dump_nftables_save(@tables)
|
||||
# Returns a string representation of the firewall rules
|
||||
@@ -191,23 +769,87 @@ return undef;
|
||||
sub describe_rule
|
||||
{
|
||||
my ($r) = @_;
|
||||
my $desc;
|
||||
if ($r->{'proto'} && $r->{'dport'} && $r->{'action'}) {
|
||||
$desc = &text('index_rule_desc', $r->{'action'}, $r->{'proto'}, $r->{'dport'});
|
||||
my @conds;
|
||||
if ($r->{'iif'}) {
|
||||
push(@conds, &text('index_rule_iif', &html_escape($r->{'iif'})));
|
||||
}
|
||||
elsif ($r->{'iif'} && $r->{'oif'} && $r->{'action'}) {
|
||||
$desc = &text('index_rule_desc4', $r->{'action'}, $r->{'iif'}, $r->{'oif'});
|
||||
if ($r->{'oif'}) {
|
||||
push(@conds, &text('index_rule_oif', &html_escape($r->{'oif'})));
|
||||
}
|
||||
elsif ($r->{'iif'} && $r->{'action'}) {
|
||||
$desc = &text('index_rule_desc3', $r->{'action'}, $r->{'iif'});
|
||||
if ($r->{'saddr'}) {
|
||||
push(@conds, &text('index_rule_saddr', &html_escape($r->{'saddr'})));
|
||||
}
|
||||
elsif ($r->{'oif'} && $r->{'action'}) {
|
||||
$desc = &text('index_rule_desc2', $r->{'action'}, $r->{'oif'});
|
||||
if ($r->{'daddr'}) {
|
||||
push(@conds, &text('index_rule_daddr', &html_escape($r->{'daddr'})));
|
||||
}
|
||||
else {
|
||||
$desc = &html_escape($r->{'text'});
|
||||
if ($r->{'l4proto'} || ($r->{'proto'} && !$r->{'dport'} && !$r->{'sport'})) {
|
||||
my $p = $r->{'l4proto'} || $r->{'proto'};
|
||||
push(@conds, &text('index_rule_proto', &html_escape($p)));
|
||||
}
|
||||
return $desc;
|
||||
if ($r->{'sport'}) {
|
||||
push(@conds, &text('index_rule_sport', &html_escape($r->{'sport'})));
|
||||
}
|
||||
if ($r->{'dport'}) {
|
||||
push(@conds, &text('index_rule_dport', &html_escape($r->{'dport'})));
|
||||
}
|
||||
if ($r->{'icmp_type'}) {
|
||||
push(@conds, &text('index_rule_icmp', &html_escape($r->{'icmp_type'})));
|
||||
}
|
||||
if ($r->{'icmpv6_type'}) {
|
||||
push(@conds, &text('index_rule_icmpv6', &html_escape($r->{'icmpv6_type'})));
|
||||
}
|
||||
if ($r->{'ct_state'}) {
|
||||
push(@conds, &text('index_rule_ct', &html_escape($r->{'ct_state'})));
|
||||
}
|
||||
if ($r->{'tcp_flags'}) {
|
||||
my $tf = $r->{'tcp_flags'};
|
||||
if ($r->{'tcp_flags_mask'}) {
|
||||
$tf = $r->{'tcp_flags_mask'}."==".$r->{'tcp_flags'};
|
||||
}
|
||||
push(@conds, &text('index_rule_tcpflags', &html_escape($tf)));
|
||||
}
|
||||
if ($r->{'limit_rate'}) {
|
||||
my $lim = $r->{'limit_rate'};
|
||||
if ($r->{'limit_burst'}) {
|
||||
$lim .= " burst ".$r->{'limit_burst'};
|
||||
}
|
||||
push(@conds, &text('index_rule_limit', &html_escape($lim)));
|
||||
}
|
||||
if ($r->{'log_prefix'}) {
|
||||
push(@conds, &text('index_rule_log_prefix', &html_escape($r->{'log_prefix'})));
|
||||
}
|
||||
if ($r->{'log_level'}) {
|
||||
push(@conds, &text('index_rule_log_level', &html_escape($r->{'log_level'})));
|
||||
}
|
||||
if ($r->{'log'} && !$r->{'log_prefix'} && !$r->{'log_level'}) {
|
||||
push(@conds, &text('index_rule_log'));
|
||||
}
|
||||
if ($r->{'counter'}) {
|
||||
push(@conds, &text('index_rule_counter'));
|
||||
}
|
||||
|
||||
my $action_label;
|
||||
if ($r->{'jump'}) {
|
||||
$action_label = &text('index_rule_jump', &html_escape($r->{'jump'}));
|
||||
}
|
||||
elsif ($r->{'goto'}) {
|
||||
$action_label = &text('index_rule_goto', &html_escape($r->{'goto'}));
|
||||
}
|
||||
elsif ($r->{'action'}) {
|
||||
if ($r->{'action'} eq 'return') {
|
||||
$action_label = &text('index_return_action');
|
||||
}
|
||||
else {
|
||||
$action_label = &text('index_'.lc($r->{'action'}));
|
||||
}
|
||||
}
|
||||
if ($action_label) {
|
||||
if (@conds) {
|
||||
return &text('index_rule_desc_generic', $action_label, join(", ", @conds));
|
||||
}
|
||||
return &text('index_rule_desc_action', $action_label);
|
||||
}
|
||||
return &html_escape($r->{'text'});
|
||||
}
|
||||
|
||||
# interface_choice(name, value, blanktext)
|
||||
|
||||
@@ -26,9 +26,81 @@ if ($in{'delete'}) {
|
||||
}
|
||||
|
||||
$rule->{'comment'} = $in{'comment'};
|
||||
$rule->{'action'} = $in{'action'};
|
||||
$rule->{'proto'} = $in{'proto'};
|
||||
$rule->{'dport'} = $in{'dport'};
|
||||
my $action = $in{'action'} || 'accept';
|
||||
$rule->{'action'} = undef;
|
||||
$rule->{'jump'} = undef;
|
||||
$rule->{'goto'} = undef;
|
||||
if ($action eq 'jump') {
|
||||
$rule->{'jump'} = $in{'jump'};
|
||||
}
|
||||
elsif ($action eq 'goto') {
|
||||
$rule->{'goto'} = $in{'goto'};
|
||||
}
|
||||
else {
|
||||
$rule->{'action'} = $action;
|
||||
}
|
||||
|
||||
$rule->{'saddr'} = (defined($in{'saddr'}) && $in{'saddr'} ne '') ? $in{'saddr'} : undef;
|
||||
$rule->{'daddr'} = (defined($in{'daddr'}) && $in{'daddr'} ne '') ? $in{'daddr'} : undef;
|
||||
$rule->{'saddr_family'} = $rule->{'saddr'} ? &guess_addr_family($rule->{'saddr'}) : undef;
|
||||
$rule->{'daddr_family'} = $rule->{'daddr'} ? &guess_addr_family($rule->{'daddr'}) : undef;
|
||||
|
||||
my $proto = $in{'proto'};
|
||||
$proto = undef if (defined($proto) && $proto eq '');
|
||||
$rule->{'sport'} = (defined($in{'sport'}) && $in{'sport'} ne '') ? $in{'sport'} : undef;
|
||||
$rule->{'dport'} = (defined($in{'dport'}) && $in{'dport'} ne '') ? $in{'dport'} : undef;
|
||||
if (!$proto && ($rule->{'sport'} || $rule->{'dport'})) {
|
||||
$proto = 'tcp';
|
||||
}
|
||||
$rule->{'l4proto'} = undef;
|
||||
$rule->{'l4proto_family'} = undef;
|
||||
$rule->{'proto'} = undef;
|
||||
$rule->{'sport_proto'} = undef;
|
||||
if ($proto && ($proto eq 'tcp' || $proto eq 'udp')) {
|
||||
$rule->{'proto'} = $proto if ($rule->{'sport'} || $rule->{'dport'});
|
||||
$rule->{'sport_proto'} = $proto if ($rule->{'sport'});
|
||||
}
|
||||
elsif ($proto && $proto !~ /^(tcp|udp)$/) {
|
||||
$rule->{'sport'} = undef;
|
||||
$rule->{'dport'} = undef;
|
||||
}
|
||||
if ($proto) {
|
||||
if (($proto eq 'tcp' || $proto eq 'udp') && ($rule->{'sport'} || $rule->{'dport'})) {
|
||||
# L4 proto implied by port match
|
||||
}
|
||||
else {
|
||||
$rule->{'l4proto'} = $proto;
|
||||
$rule->{'l4proto_family'} = 'meta';
|
||||
}
|
||||
}
|
||||
|
||||
my $icmp_type = $in{'icmp_type'};
|
||||
$rule->{'icmp_type'} = undef;
|
||||
$rule->{'icmpv6_type'} = undef;
|
||||
if ($proto && $proto eq 'icmp') {
|
||||
$rule->{'icmp_type'} = $icmp_type if (defined($icmp_type) && $icmp_type ne '');
|
||||
}
|
||||
elsif ($proto && $proto eq 'icmpv6') {
|
||||
$rule->{'icmpv6_type'} = $icmp_type if (defined($icmp_type) && $icmp_type ne '');
|
||||
}
|
||||
elsif (!$proto && defined($icmp_type) && $icmp_type ne '') {
|
||||
$rule->{'icmp_type'} = $icmp_type;
|
||||
$rule->{'l4proto'} = 'icmp';
|
||||
$rule->{'l4proto_family'} = 'meta';
|
||||
}
|
||||
|
||||
$rule->{'ct_state'} = (defined($in{'ct_state'}) && $in{'ct_state'} ne '') ? $in{'ct_state'} : undef;
|
||||
$rule->{'tcp_flags'} = (defined($in{'tcp_flags'}) && $in{'tcp_flags'} ne '') ? $in{'tcp_flags'} : undef;
|
||||
$rule->{'tcp_flags_mask'} = (defined($in{'tcp_flags_mask'}) && $in{'tcp_flags_mask'} ne '') ? $in{'tcp_flags_mask'} : undef;
|
||||
$rule->{'limit_rate'} = (defined($in{'limit_rate'}) && $in{'limit_rate'} ne '') ? $in{'limit_rate'} : undef;
|
||||
$rule->{'limit_burst'} = (defined($in{'limit_burst'}) && $in{'limit_burst'} ne '') ? $in{'limit_burst'} : undef;
|
||||
|
||||
my $log_enabled = $in{'log'} || $in{'log_prefix'} || $in{'log_level'};
|
||||
$rule->{'log'} = $log_enabled ? 1 : undef;
|
||||
$rule->{'log_prefix'} = $log_enabled && defined($in{'log_prefix'}) && $in{'log_prefix'} ne '' ? $in{'log_prefix'} : undef;
|
||||
$rule->{'log_level'} = $log_enabled && defined($in{'log_level'}) && $in{'log_level'} ne '' ? $in{'log_level'} : undef;
|
||||
$rule->{'counter'} = $in{'counter'} ? 1 : undef;
|
||||
|
||||
my $iif = $in{'iif'};
|
||||
my $oif = $in{'oif'};
|
||||
$iif = $in{'iif_other'} if (defined($iif) && $iif eq 'other');
|
||||
@@ -36,24 +108,7 @@ if ($in{'delete'}) {
|
||||
$rule->{'iif'} = (defined($iif) && $iif ne '') ? $iif : undef;
|
||||
$rule->{'oif'} = (defined($oif) && $oif ne '') ? $oif : undef;
|
||||
|
||||
my $rule_text = "";
|
||||
if ($rule->{'proto'} && $rule->{'dport'}) {
|
||||
$rule_text .= "$rule->{'proto'} dport $rule->{'dport'} ";
|
||||
}
|
||||
if ($rule->{'iif'}) {
|
||||
$rule_text .= "iif \"$rule->{'iif'}\" ";
|
||||
}
|
||||
if ($rule->{'oif'}) {
|
||||
$rule_text .= "oif \"$rule->{'oif'}\" ";
|
||||
}
|
||||
$rule_text .= $rule->{'action'};
|
||||
if ($rule->{'comment'}) {
|
||||
my $comment = $rule->{'comment'};
|
||||
$comment =~ s/\\/\\\\/g;
|
||||
$comment =~ s/"/\\"/g;
|
||||
$rule_text .= " comment \"$comment\"";
|
||||
}
|
||||
$rule->{'text'} = $rule_text;
|
||||
$rule->{'text'} = &format_rule_text($rule);
|
||||
|
||||
if ($in{'new'}) {
|
||||
push(@{$table->{'rules'}}, $rule);
|
||||
|
||||
8
t/rulesets/basic.nft
Normal file
8
t/rulesets/basic.nft
Normal file
@@ -0,0 +1,8 @@
|
||||
table inet filter {
|
||||
chain input {
|
||||
type filter hook input priority 0; policy drop;
|
||||
iif "lo" accept
|
||||
ip saddr 192.168.1.0/24 tcp dport 22 accept comment "ssh"
|
||||
ct state established,related accept
|
||||
}
|
||||
}
|
||||
131
t/run-tests.t
Executable file
131
t/run-tests.t
Executable file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
use File::Temp qw(tempdir);
|
||||
|
||||
sub script_dir
|
||||
{
|
||||
my $path = $0;
|
||||
if ($path =~ m{^/}) {
|
||||
$path =~ s{/[^/]+$}{};
|
||||
return $path;
|
||||
}
|
||||
my $cwd = `pwd`;
|
||||
chomp($cwd);
|
||||
if ($path =~ m{/}) {
|
||||
$path =~ s{/[^/]+$}{};
|
||||
return $cwd.'/'.$path;
|
||||
}
|
||||
return $cwd;
|
||||
}
|
||||
|
||||
my $bindir = &script_dir();
|
||||
|
||||
my $confdir = tempdir(CLEANUP => 1);
|
||||
my $vardir = tempdir(CLEANUP => 1);
|
||||
open(my $cfh, ">", "$confdir/config") or die "config: $!";
|
||||
print $cfh "os_type=linux\nos_version=0\n";
|
||||
close($cfh);
|
||||
open(my $vfh, ">", "$confdir/var-path") or die "var-path: $!";
|
||||
print $vfh "$vardir\n";
|
||||
close($vfh);
|
||||
$ENV{'WEBMIN_CONFIG'} = $confdir;
|
||||
$ENV{'WEBMIN_VAR'} = $vardir;
|
||||
$ENV{'FOREIGN_MODULE_NAME'} = 'nftables';
|
||||
$ENV{'FOREIGN_ROOT_DIRECTORY'} = '/usr/libexec/webmin';
|
||||
|
||||
chdir("$bindir/..") or die "chdir: $!";
|
||||
|
||||
require "$bindir/../nftables-lib.pl";
|
||||
|
||||
sub check_fields
|
||||
{
|
||||
my ($name, $got, $expect) = @_;
|
||||
foreach my $k (sort keys %$expect) {
|
||||
is($got->{$k}, $expect->{$k}, "$name $k");
|
||||
}
|
||||
}
|
||||
|
||||
my @cases = (
|
||||
{
|
||||
name => 'tcp dport accept',
|
||||
line => 'tcp dport 22 accept',
|
||||
expect => { proto => 'tcp', dport => '22', action => 'accept' },
|
||||
},
|
||||
{
|
||||
name => 'iif oif drop',
|
||||
line => 'iif "eth0" oif "eth1" drop',
|
||||
expect => { iif => 'eth0', oif => 'eth1', action => 'drop' },
|
||||
},
|
||||
{
|
||||
name => 'comment with quotes',
|
||||
line => 'tcp dport 80 accept comment "a \\"quote\\""',
|
||||
expect => { proto => 'tcp', dport => '80', action => 'accept', comment => 'a "quote"' },
|
||||
},
|
||||
{
|
||||
name => 'ct state',
|
||||
line => 'ct state established,related accept',
|
||||
expect => { ct_state => 'established,related', action => 'accept' },
|
||||
},
|
||||
{
|
||||
name => 'icmp type',
|
||||
line => 'icmp type echo-request accept',
|
||||
expect => { icmp_type => 'echo-request', action => 'accept' },
|
||||
},
|
||||
{
|
||||
name => 'limit log counter',
|
||||
line => 'tcp dport 22 limit rate 10/second burst 20 packets log prefix "ssh" level info counter accept',
|
||||
expect => {
|
||||
proto => 'tcp',
|
||||
dport => '22',
|
||||
limit_rate => '10/second',
|
||||
limit_burst => '20',
|
||||
log_prefix => 'ssh',
|
||||
log_level => 'info',
|
||||
counter => 1,
|
||||
action => 'accept',
|
||||
},
|
||||
},
|
||||
{
|
||||
name => 'unknown tokens preserved',
|
||||
line => 'tcp dport 22 meta skgid 1000 accept',
|
||||
expect => { proto => 'tcp', dport => '22', action => 'accept' },
|
||||
preserve => 'meta skgid 1000',
|
||||
},
|
||||
);
|
||||
|
||||
foreach my $c (@cases) {
|
||||
my $r = &parse_rule_text($c->{line});
|
||||
ok($r && ref($r) eq 'HASH', "$c->{name} parse hash");
|
||||
check_fields($c->{name}, $r, $c->{expect});
|
||||
|
||||
my $out = &format_rule_text($r);
|
||||
ok($out =~ /\S/, "$c->{name} formatted non-empty");
|
||||
if ($c->{preserve}) {
|
||||
like($out, qr/\Q$c->{preserve}\E/, "$c->{name} preserves unknowns");
|
||||
}
|
||||
|
||||
my $r2 = &parse_rule_text($out);
|
||||
check_fields($c->{name}.' roundtrip', $r2, $c->{expect});
|
||||
}
|
||||
|
||||
my $ruleset = "$bindir/rulesets/basic.nft";
|
||||
my @tables = &get_nftables_save($ruleset);
|
||||
ok(@tables == 1, 'ruleset table count');
|
||||
my $t = $tables[0];
|
||||
is($t->{family}, 'inet', 'ruleset family');
|
||||
is($t->{name}, 'filter', 'ruleset name');
|
||||
my $chain = $t->{chains}->{input};
|
||||
ok($chain, 'input chain present');
|
||||
is($chain->{type}, 'filter', 'chain type');
|
||||
is($chain->{hook}, 'input', 'chain hook');
|
||||
is($chain->{priority}, '0', 'chain priority');
|
||||
is($chain->{policy}, 'drop', 'chain policy');
|
||||
|
||||
my @rules = @{$t->{rules}};
|
||||
check_fields('ruleset r1', $rules[0], { iif => 'lo', action => 'accept' });
|
||||
check_fields('ruleset r2', $rules[1], { saddr => '192.168.1.0/24', proto => 'tcp', dport => '22', action => 'accept', comment => 'ssh' });
|
||||
check_fields('ruleset r3', $rules[2], { ct_state => 'established,related', action => 'accept' });
|
||||
|
||||
done_testing();
|
||||
Reference in New Issue
Block a user