mirror of
https://github.com/webmin/webmin.git
synced 2026-05-06 15:20:29 +01:00
Merge swelljoe/nftables as new nftables module
Import https://github.com/swelljoe/nftables into the Webmin tree under nftables/ while preserving upstream history.
This commit is contained in:
16
nftables/apply.cgi
Executable file
16
nftables/apply.cgi
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/perl
|
||||
# apply.cgi
|
||||
# Apply the current configuration
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
error_setup($text{'apply_err'});
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
my $err = apply_restore();
|
||||
error($err) if ($err);
|
||||
|
||||
redirect("index.cgi");
|
||||
12
nftables/config.info
Normal file
12
nftables/config.info
Normal file
@@ -0,0 +1,12 @@
|
||||
line0=Configurable global options,11
|
||||
perpage=Number of rules to display per page,3,Default (50)
|
||||
view_condition=Display condition in rules list?,1,1-Yes,0-No
|
||||
view_comment=Display comment in rules list?,1,1-Yes,0-No
|
||||
before_cmd=Command to run before changing rules,3,None
|
||||
after_cmd=Command to run after changing rules,3,None
|
||||
before_apply_cmd=Command to run before applying configuration,3,None
|
||||
after_apply_cmd=Command to run after applying configuration,3,None
|
||||
line2=NFTables configuration,11
|
||||
nft_cmd=Full path to nft command,0
|
||||
save_file=File to save/edit NFTables rules,3,Use operating system or Webmin default
|
||||
direct=Directly edit firewall rules instead of save file?,1,1-Yes,0-No
|
||||
55
nftables/create_table.cgi
Executable file
55
nftables/create_table.cgi
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/perl
|
||||
# create_table.cgi
|
||||
# Create a new nftables table
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
error_setup($text{'create_err'});
|
||||
|
||||
my @families = qw(ip ip6 inet arp bridge netdev);
|
||||
my %family_ok = map { $_ => 1 } @families;
|
||||
|
||||
if ($in{'create'}) {
|
||||
my $name = $in{'name'};
|
||||
my $family = $in{'family'};
|
||||
|
||||
$name =~ /^\w[\w-]*$/ || error($text{'create_ename'});
|
||||
$family_ok{$family} || error($text{'create_efamily'});
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
foreach my $t (@tables) {
|
||||
if ($t->{'name'} eq $name && $t->{'family'} eq $family) {
|
||||
error($text{'create_edup'});
|
||||
}
|
||||
}
|
||||
|
||||
push(@tables, { 'name' => $name,
|
||||
'family' => $family,
|
||||
'rules' => [],
|
||||
'chains' => {},
|
||||
'sets' => {} });
|
||||
my $err = save_configuration(@tables);
|
||||
error(text('create_failed', $err)) if ($err);
|
||||
webmin_log("create", "table", $name, { 'family' => $family });
|
||||
|
||||
my $idx = $#tables;
|
||||
redirect("index.cgi?table=$idx");
|
||||
}
|
||||
|
||||
ui_print_header(undef, $text{'create_title'}, "", "intro", 1, 1);
|
||||
print ui_form_start("create_table.cgi");
|
||||
print ui_hidden("create", 1);
|
||||
|
||||
print ui_table_start($text{'create_header'}, "width=100%", 2);
|
||||
print ui_table_row($text{'create_family'},
|
||||
ui_select("family", $in{'family'} || "inet",
|
||||
[ map { [ $_, $_ ] } @families ]));
|
||||
print ui_table_row($text{'create_name'},
|
||||
ui_textbox("name", $in{'name'}, 20));
|
||||
print ui_table_end();
|
||||
|
||||
print ui_form_end([ [ undef, $text{'create_ok'} ] ]);
|
||||
ui_print_footer("index.cgi", $text{'index_return'});
|
||||
54
nftables/delete_chain.cgi
Normal file
54
nftables/delete_chain.cgi
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/perl
|
||||
# delete_chain.cgi
|
||||
# Delete an existing nftables chain
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
error_setup($text{'delete_chain_err'});
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
my $table = $tables[$in{'table'}];
|
||||
$table || error($text{'chain_notable'});
|
||||
|
||||
my $chain = $table->{'chains'}->{$in{'chain'}};
|
||||
$chain || error($text{'chain_nochain'});
|
||||
|
||||
my @refs = grep {
|
||||
($_->{'jump'} && $_->{'jump'} eq $in{'chain'}) ||
|
||||
($_->{'goto'} && $_->{'goto'} eq $in{'chain'})
|
||||
} @{$table->{'rules'}};
|
||||
|
||||
if ($in{'confirm'}) {
|
||||
@refs && error(text('delete_chain_inuse', $in{'chain'}, scalar(@refs)));
|
||||
|
||||
@{$table->{'rules'}} = grep { $_->{'chain'} ne $in{'chain'} } @{$table->{'rules'}};
|
||||
delete($table->{'chains'}->{$in{'chain'}});
|
||||
|
||||
my $err = save_configuration(@tables);
|
||||
error(text('delete_chain_failed', $err)) if ($err);
|
||||
webmin_log("delete", "chain", $in{'chain'},
|
||||
{ 'table' => $table->{'name'}, 'family' => $table->{'family'} });
|
||||
redirect("index.cgi?table=$in{'table'}");
|
||||
}
|
||||
|
||||
ui_print_header(undef, $text{'delete_chain_title'}, "", "intro", 1, 1);
|
||||
print ui_form_start("delete_chain.cgi");
|
||||
print ui_hidden("table", $in{'table'});
|
||||
print ui_hidden("chain", $in{'chain'});
|
||||
print "<center><b>",
|
||||
text('delete_chain_confirm',
|
||||
"<tt>$in{'chain'}</tt>",
|
||||
"<tt>$table->{'family'} $table->{'name'}</tt>"),
|
||||
"</b>";
|
||||
if (@refs) {
|
||||
print "<br><br>", text('delete_chain_inuse', $in{'chain'}, scalar(@refs));
|
||||
}
|
||||
print "<p>\n";
|
||||
print ui_submit($text{'delete'}, "confirm");
|
||||
print "</center>\n";
|
||||
print ui_form_end();
|
||||
ui_print_footer("index.cgi?table=$in{'table'}", $text{'index_return'});
|
||||
|
||||
48
nftables/delete_set.cgi
Executable file
48
nftables/delete_set.cgi
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/perl
|
||||
# delete_set.cgi
|
||||
# Delete an existing nftables set
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
error_setup($text{'delete_set_err'});
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
my $table = $tables[$in{'table'}];
|
||||
$table || error($text{'set_notable'});
|
||||
|
||||
my $set = $table->{'sets'}->{$in{'set'}};
|
||||
$set || error($text{'set_noset'});
|
||||
|
||||
my $refs = count_set_references($table, $in{'set'});
|
||||
|
||||
if ($in{'confirm'}) {
|
||||
$refs && error(text('delete_set_inuse', $in{'set'}, $refs));
|
||||
|
||||
delete($table->{'sets'}->{$in{'set'}});
|
||||
my $err = save_configuration(@tables);
|
||||
error(text('delete_set_failed', $err)) if ($err);
|
||||
webmin_log("delete", "set", $in{'set'},
|
||||
{ 'table' => $table->{'name'}, 'family' => $table->{'family'} });
|
||||
redirect("index.cgi?table=$in{'table'}");
|
||||
}
|
||||
|
||||
ui_print_header(undef, $text{'delete_set_title'}, "", "intro", 1, 1);
|
||||
print ui_form_start("delete_set.cgi");
|
||||
print ui_hidden("table", $in{'table'});
|
||||
print ui_hidden("set", $in{'set'});
|
||||
print "<center><b>",
|
||||
text('delete_set_confirm',
|
||||
"<tt>$in{'set'}</tt>",
|
||||
"<tt>$table->{'family'} $table->{'name'}</tt>"),
|
||||
"</b>";
|
||||
if ($refs) {
|
||||
print "<br><br>", text('delete_set_inuse', $in{'set'}, $refs);
|
||||
}
|
||||
print "<p>\n";
|
||||
print ui_submit($text{'delete'}, "confirm");
|
||||
print "</center>\n";
|
||||
print ui_form_end();
|
||||
ui_print_footer("index.cgi?table=$in{'table'}", $text{'index_return'});
|
||||
36
nftables/delete_table.cgi
Executable file
36
nftables/delete_table.cgi
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/perl
|
||||
# delete_table.cgi
|
||||
# Delete an existing nftables table
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
error_setup($text{'delete_err'});
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
my $table = $tables[$in{'table'}];
|
||||
$table || error($text{'delete_notable'});
|
||||
|
||||
if ($in{'confirm'}) {
|
||||
splice(@tables, $in{'table'}, 1);
|
||||
my $err = save_configuration(@tables);
|
||||
error(text('delete_failed', $err)) if ($err);
|
||||
webmin_log("delete", "table", $table->{'name'},
|
||||
{ 'family' => $table->{'family'} });
|
||||
redirect("index.cgi");
|
||||
}
|
||||
|
||||
ui_print_header(undef, $text{'delete_title'}, "", "intro", 1, 1);
|
||||
print ui_form_start("delete_table.cgi");
|
||||
print ui_hidden("table", $in{'table'});
|
||||
print "<center><b>",
|
||||
text('delete_confirm',
|
||||
"<tt>$table->{'family'} $table->{'name'}</tt>"),
|
||||
"</b><p>\n";
|
||||
print ui_submit($text{'delete'}, "confirm");
|
||||
print "</center>\n";
|
||||
print ui_form_end();
|
||||
ui_print_footer("index.cgi?table=$in{'table'}", $text{'index_return'});
|
||||
|
||||
87
nftables/edit_chain.cgi
Normal file
87
nftables/edit_chain.cgi
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/perl
|
||||
# edit_chain.cgi
|
||||
# Display a form for creating or editing a chain
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
my $table = $tables[$in{'table'}];
|
||||
$table || error($text{'chain_notable'});
|
||||
|
||||
my $chain = { };
|
||||
my $chain_name = "";
|
||||
my $is_new = $in{'new'} ? 1 : 0;
|
||||
|
||||
if ($is_new) {
|
||||
ui_print_header(undef, $text{'chain_title_new'}, "", "intro", 1, 1);
|
||||
} else {
|
||||
$chain_name = $in{'chain'};
|
||||
$chain = $table->{'chains'}->{$chain_name};
|
||||
$chain || error($text{'chain_nochain'});
|
||||
ui_print_header(undef, $text{'chain_title_edit'}, "", "intro", 1, 1);
|
||||
}
|
||||
|
||||
my @type_opts = (
|
||||
[ "", $text{'chain_type_none'} ],
|
||||
map { [ $_, $_ ] } qw(filter nat route)
|
||||
);
|
||||
my @hook_opts = (
|
||||
[ "", $text{'chain_hook_none'} ],
|
||||
map { [ $_, $_ ] } qw(prerouting input forward output postrouting ingress)
|
||||
);
|
||||
my @policy_opts = (
|
||||
[ "", $text{'chain_policy_none'} ],
|
||||
map { [ $_, $_ ] } qw(accept drop reject return queue continue)
|
||||
);
|
||||
|
||||
print ui_form_start("save_chain.cgi");
|
||||
print ui_hidden("table", $in{'table'});
|
||||
print ui_hidden("new", $is_new);
|
||||
|
||||
print ui_table_start($text{'chain_header'}, "width=100%", 2);
|
||||
|
||||
my $name_tags = $is_new ? undef : "readonly";
|
||||
print ui_table_row(hlink($text{'chain_name'}, "chain_name"),
|
||||
ui_textbox("chain_name", $chain_name, 20, 0, undef, $name_tags));
|
||||
|
||||
print ui_table_row(hlink($text{'chain_type'}, "chain_type"),
|
||||
ui_select("chain_type", $chain->{'type'}, \@type_opts, 1, 0, 1, 0,
|
||||
"onchange='toggle_chain_base()'"));
|
||||
print ui_table_row(hlink($text{'chain_hook'}, "chain_hook"),
|
||||
ui_select("chain_hook", $chain->{'hook'}, \@hook_opts, 1, 0, 1));
|
||||
print ui_table_row(hlink($text{'chain_priority'}, "chain_priority"),
|
||||
ui_textbox("chain_priority", $chain->{'priority'}, 10));
|
||||
print ui_table_row(hlink($text{'chain_policy'}, "chain_policy"),
|
||||
ui_select("chain_policy", $chain->{'policy'}, \@policy_opts, 1, 0, 1));
|
||||
|
||||
print ui_table_end();
|
||||
|
||||
print ui_form_end([ [ undef, $text{$is_new ? 'create' : 'save'} ] ]);
|
||||
|
||||
print <<'EOF';
|
||||
<script>
|
||||
function toggle_chain_base() {
|
||||
var type = document.getElementById('chain_type');
|
||||
var disabled = !type || !type.value;
|
||||
var ids = ['chain_hook', 'chain_priority', 'chain_policy'];
|
||||
for (var i = 0; i < ids.length; i++) {
|
||||
var el = document.getElementById(ids[i]);
|
||||
if (el) {
|
||||
el.disabled = disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (window.addEventListener) {
|
||||
window.addEventListener('load', toggle_chain_base);
|
||||
} else if (window.attachEvent) {
|
||||
window.attachEvent('onload', toggle_chain_base);
|
||||
}
|
||||
</script>
|
||||
EOF
|
||||
|
||||
ui_print_footer("index.cgi?table=$in{'table'}", $text{'index_return'});
|
||||
|
||||
646
nftables/edit_rule.cgi
Executable file
646
nftables/edit_rule.cgi
Executable file
@@ -0,0 +1,646 @@
|
||||
#!/usr/bin/perl
|
||||
# edit_rule.cgi
|
||||
# Display a form for creating or editing a rule
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text, %config);
|
||||
ReadParse();
|
||||
my @tables = get_nftables_save();
|
||||
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;
|
||||
my $raw_extra = "";
|
||||
my $ct_state_sel;
|
||||
my $tcp_flags_sel;
|
||||
my $advanced_open;
|
||||
my $saddr_set;
|
||||
my $daddr_set;
|
||||
my $sport_set;
|
||||
my $dport_set;
|
||||
my $saddr_val;
|
||||
my $daddr_val;
|
||||
my $sport_val;
|
||||
my $dport_val;
|
||||
my @addr_set_opts;
|
||||
my @port_set_opts;
|
||||
my %set_families;
|
||||
|
||||
sub split_multi_value
|
||||
{
|
||||
my ($v) = @_;
|
||||
return if (!defined($v) || $v eq '');
|
||||
$v =~ s/^\s*\{//;
|
||||
$v =~ s/\}\s*$//;
|
||||
$v =~ s/^\s+//;
|
||||
$v =~ s/\s+$//;
|
||||
return if ($v eq '');
|
||||
my @vals = split(/\s*,\s*/, $v);
|
||||
@vals = grep { $_ ne '' } @vals;
|
||||
return \@vals;
|
||||
}
|
||||
|
||||
if ($in{'new'}) {
|
||||
ui_print_header(undef, $text{'edit_title_new'}, "", "intro", 1, 1);
|
||||
$rule = { 'chain' => $in{'chain'} };
|
||||
} else {
|
||||
ui_print_header(undef, $text{'edit_title_edit'}, "", "intro", 1, 1);
|
||||
$rule = $table->{'rules'}->[$in{'idx'}];
|
||||
}
|
||||
if ($table && $rule->{'chain'}) {
|
||||
$chain_def = $table->{'chains'}->{$rule->{'chain'}};
|
||||
$chain_hook = $chain_def ? $chain_def->{'hook'} : undef;
|
||||
}
|
||||
if ($rule) {
|
||||
if ($rule->{'exprs'} && ref($rule->{'exprs'}) eq 'ARRAY') {
|
||||
my @raw = map { $_->{'text'} }
|
||||
grep { $_->{'type'} && $_->{'type'} eq 'raw' }
|
||||
@{$rule->{'exprs'}};
|
||||
$raw_extra = join(" ", @raw);
|
||||
}
|
||||
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'};
|
||||
$ct_state_sel = split_multi_value($rule->{'ct_state'});
|
||||
$tcp_flags_sel = split_multi_value($rule->{'tcp_flags'});
|
||||
$log_enabled = $rule->{'log'} || $rule->{'log_prefix'} || $rule->{'log_level'};
|
||||
}
|
||||
$saddr_set = set_name_from_value($rule->{'saddr'});
|
||||
$daddr_set = set_name_from_value($rule->{'daddr'});
|
||||
$sport_set = set_name_from_value($rule->{'sport'});
|
||||
$dport_set = set_name_from_value($rule->{'dport'});
|
||||
$saddr_val = $saddr_set ? "" : $rule->{'saddr'};
|
||||
$daddr_val = $daddr_set ? "" : $rule->{'daddr'};
|
||||
$sport_val = $sport_set ? "" : $rule->{'sport'};
|
||||
$dport_val = $dport_set ? "" : $rule->{'dport'};
|
||||
|
||||
@addr_set_opts = ( [ "", $text{'edit_set_none'} ] );
|
||||
@port_set_opts = ( [ "", $text{'edit_set_none'} ] );
|
||||
my %addr_set_seen;
|
||||
my %port_set_seen;
|
||||
if ($table && $table->{'sets'} && ref($table->{'sets'}) eq 'HASH') {
|
||||
foreach my $s (sort keys %{$table->{'sets'}}) {
|
||||
my $set = $table->{'sets'}->{$s} || { };
|
||||
my $label = $s;
|
||||
$label .= " ($set->{'type'})" if ($set->{'type'});
|
||||
my $kind = set_type_kind($set->{'type'});
|
||||
if (!$kind || $kind eq 'addr') {
|
||||
push(@addr_set_opts, [ $s, $label ]);
|
||||
$addr_set_seen{$s} = 1;
|
||||
}
|
||||
if (!$kind || $kind eq 'port') {
|
||||
push(@port_set_opts, [ $s, $label ]);
|
||||
$port_set_seen{$s} = 1;
|
||||
}
|
||||
my $fam = set_type_family($set->{'type'});
|
||||
$set_families{$s} = $fam if ($fam);
|
||||
}
|
||||
}
|
||||
if ($saddr_set && !$addr_set_seen{$saddr_set}) {
|
||||
push(@addr_set_opts, [ $saddr_set, $saddr_set ]);
|
||||
}
|
||||
if ($daddr_set && !$addr_set_seen{$daddr_set}) {
|
||||
push(@addr_set_opts, [ $daddr_set, $daddr_set ]);
|
||||
}
|
||||
if ($sport_set && !$port_set_seen{$sport_set}) {
|
||||
push(@port_set_opts, [ $sport_set, $sport_set ]);
|
||||
}
|
||||
if ($dport_set && !$port_set_seen{$dport_set}) {
|
||||
push(@port_set_opts, [ $dport_set, $dport_set ]);
|
||||
}
|
||||
$advanced_open = 1 if ($action_sel && ($action_sel eq 'jump' || $action_sel eq 'goto'));
|
||||
$advanced_open = 1 if ($rule && (
|
||||
$rule->{'jump'} || $rule->{'goto'} ||
|
||||
$rule->{'iif'} || $rule->{'oif'} ||
|
||||
$icmp_type ||
|
||||
$rule->{'ct_state'} ||
|
||||
$rule->{'tcp_flags'} || $rule->{'tcp_flags_mask'} ||
|
||||
$rule->{'limit_rate'} || $rule->{'limit_burst'} ||
|
||||
$log_enabled ||
|
||||
$rule->{'counter'}
|
||||
));
|
||||
|
||||
my @icmp_types = qw(
|
||||
echo-reply destination-unreachable source-quench redirect echo-request
|
||||
router-advertisement router-solicitation time-exceeded parameter-problem
|
||||
timestamp-request timestamp-reply info-request info-reply
|
||||
address-mask-request address-mask-reply
|
||||
);
|
||||
my @icmpv6_types = qw(
|
||||
destination-unreachable packet-too-big time-exceeded parameter-problem
|
||||
echo-request echo-reply mld-listener-query mld-listener-report
|
||||
mld-listener-done mld-listener-reduction nd-router-solicit
|
||||
nd-router-advert nd-neighbor-solicit nd-neighbor-advert nd-redirect
|
||||
router-renumbering ind-neighbor-solicit ind-neighbor-advert
|
||||
mld2-listener-report
|
||||
);
|
||||
my %icmp_seen;
|
||||
my @icmp_type_opts = ( [ "", $text{'edit_proto_any'} ] );
|
||||
foreach my $t (@icmp_types, @icmpv6_types) {
|
||||
next if ($icmp_seen{$t}++);
|
||||
push(@icmp_type_opts, [ $t, $t ]);
|
||||
}
|
||||
my @ct_state_opts = (
|
||||
[ "", $text{'edit_proto_any'} ],
|
||||
map { [ $_, $_ ] } qw(invalid new established related untracked),
|
||||
);
|
||||
my @tcp_flags_opts = (
|
||||
[ "", $text{'edit_proto_any'} ],
|
||||
map { [ $_, $_ ] } qw(fin syn rst psh ack urg ecn cwr),
|
||||
);
|
||||
|
||||
print ui_form_start("save_rule.cgi");
|
||||
print ui_hidden("table", $in{'table'});
|
||||
print ui_hidden("idx", $in{'idx'});
|
||||
print ui_hidden("chain", $rule->{'chain'});
|
||||
print ui_hidden("new", $in{'new'});
|
||||
print ui_hidden("raw_extra", $raw_extra);
|
||||
|
||||
print ui_table_start($text{'edit_header'}, "width=100%", 2);
|
||||
|
||||
# Rule comment
|
||||
print ui_table_row(hlink($text{'edit_comment'}, "comment"),
|
||||
ui_textbox("comment", $rule->{'comment'}, 50));
|
||||
|
||||
# Action
|
||||
print ui_table_row(hlink($text{'edit_action'}, "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'} ],
|
||||
]));
|
||||
|
||||
# Addresses
|
||||
my $saddr_row = ui_textbox("saddr", $saddr_val, 30);
|
||||
if (@addr_set_opts > 1) {
|
||||
$saddr_row .= "<br>".text('edit_saddr_set',
|
||||
ui_select("saddr_set", $saddr_set, \@addr_set_opts, 1, 0, 1));
|
||||
}
|
||||
print ui_table_row(hlink($text{'edit_saddr'}, "saddr"), $saddr_row);
|
||||
|
||||
my $daddr_row = ui_textbox("daddr", $daddr_val, 30);
|
||||
if (@addr_set_opts > 1) {
|
||||
$daddr_row .= "<br>".text('edit_daddr_set',
|
||||
ui_select("daddr_set", $daddr_set, \@addr_set_opts, 1, 0, 1));
|
||||
}
|
||||
print ui_table_row(hlink($text{'edit_daddr'}, "daddr"), $daddr_row);
|
||||
|
||||
# Protocol
|
||||
print ui_table_row(hlink($text{'edit_proto'}, "proto"),
|
||||
ui_select("proto", $proto_sel,
|
||||
[
|
||||
[ "", $text{'edit_proto_any'} ],
|
||||
[ "tcp", "TCP" ],
|
||||
[ "udp", "UDP" ],
|
||||
[ "icmp", "ICMP" ],
|
||||
[ "icmpv6", "ICMPv6" ],
|
||||
]));
|
||||
|
||||
# Ports
|
||||
my $sport_row = ui_textbox("sport", $sport_val, 10);
|
||||
if (@port_set_opts > 1) {
|
||||
$sport_row .= "<br>".text('edit_sport_set',
|
||||
ui_select("sport_set", $sport_set, \@port_set_opts, 1, 0, 1));
|
||||
}
|
||||
print ui_table_row(hlink($text{'edit_sport'}, "sport"), $sport_row);
|
||||
|
||||
my $dport_row = ui_textbox("dport", $dport_val, 10);
|
||||
if (@port_set_opts > 1) {
|
||||
$dport_row .= "<br>".text('edit_dport_set',
|
||||
ui_select("dport_set", $dport_set, \@port_set_opts, 1, 0, 1));
|
||||
}
|
||||
print ui_table_row(hlink($text{'edit_dport'}, "dport"), $dport_row);
|
||||
|
||||
print ui_table_end();
|
||||
|
||||
print ui_hidden_table_start($text{'edit_advanced'}, "width=100%", 2,
|
||||
"advanced", $advanced_open ? 1 : 0);
|
||||
|
||||
# Jump/Goto target chain
|
||||
print ui_table_row(hlink($text{'edit_jump'}, "jump"),
|
||||
ui_textbox("jump", $rule->{'jump'}, 20));
|
||||
print ui_table_row(hlink($text{'edit_goto'}, "goto"),
|
||||
ui_textbox("goto", $rule->{'goto'}, 20));
|
||||
|
||||
# Interfaces
|
||||
if ($chain_hook && $chain_hook eq 'input') {
|
||||
# Incoming interface
|
||||
print ui_table_row(hlink($text{'edit_iif'}, "iif"),
|
||||
interface_choice("iif", $rule->{'iif'}, $text{'edit_if_any'}));
|
||||
}
|
||||
elsif ($chain_hook && $chain_hook eq 'output') {
|
||||
# Outgoing interface
|
||||
print ui_table_row(hlink($text{'edit_oif'}, "oif"),
|
||||
interface_choice("oif", $rule->{'oif'}, $text{'edit_if_any'}));
|
||||
}
|
||||
else {
|
||||
# Forward or unknown chain - allow both
|
||||
print ui_table_row(hlink($text{'edit_iif'}, "iif"),
|
||||
interface_choice("iif", $rule->{'iif'}, $text{'edit_if_any'}));
|
||||
print ui_table_row(hlink($text{'edit_oif'}, "oif"),
|
||||
interface_choice("oif", $rule->{'oif'}, $text{'edit_if_any'}));
|
||||
}
|
||||
|
||||
# ICMP type
|
||||
print ui_table_row(hlink($text{'edit_icmp_type'}, "icmp_type"),
|
||||
ui_select("icmp_type", $icmp_type, \@icmp_type_opts, 1, 0, 1));
|
||||
|
||||
# Conntrack state
|
||||
print ui_table_row(hlink($text{'edit_ct_state'}, "ct_state"),
|
||||
ui_select("ct_state", $ct_state_sel, \@ct_state_opts, 5, 1, 1));
|
||||
|
||||
# TCP flags
|
||||
print ui_table_row(hlink($text{'edit_tcp_flags'}, "tcp_flags"),
|
||||
ui_select("tcp_flags", $tcp_flags_sel, \@tcp_flags_opts, 8, 1, 1));
|
||||
print ui_table_row(hlink($text{'edit_tcp_flags_mask'}, "tcp_flags_mask"),
|
||||
ui_textbox("tcp_flags_mask", $rule->{'tcp_flags_mask'}, 20));
|
||||
|
||||
# Limit
|
||||
print ui_table_row(hlink($text{'edit_limit_rate'}, "limit_rate"),
|
||||
ui_textbox("limit_rate", $rule->{'limit_rate'}, 20));
|
||||
print ui_table_row(hlink($text{'edit_limit_burst'}, "limit_burst"),
|
||||
ui_textbox("limit_burst", $rule->{'limit_burst'}, 10));
|
||||
|
||||
# Log
|
||||
my $log_row = ui_checkbox("log", 1, hlink($text{'edit_log_enable'}, "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(hlink($text{'edit_counter'}, "counter"),
|
||||
ui_checkbox("counter", 1, $text{'edit_counter_enable'}, $rule->{'counter'}));
|
||||
|
||||
print ui_hidden_table_end("advanced");
|
||||
|
||||
print ui_table_start($text{'edit_rule'}, "width=100%", 2);
|
||||
|
||||
# Raw rule (read-only unless edit direct is checked)
|
||||
my $raw_controls = ui_checkbox("edit_direct", 1, $text{'edit_raw_rule_direct'}, 0);
|
||||
my $raw_area = ui_textarea("raw_rule", $rule->{'text'}, 4, 60, undef, undef,
|
||||
"readonly='true'");
|
||||
print ui_table_row(hlink($text{'edit_raw_rule'}, "raw_rule"), $raw_controls."<br>".$raw_area,
|
||||
undef, undef, ["data-column-span='all' data-column-locked='1'"]);
|
||||
|
||||
print ui_table_end();
|
||||
my @buttons;
|
||||
if ($in{'new'}) {
|
||||
push(@buttons, [ undef, $text{'create'} ]);
|
||||
} else {
|
||||
push(@buttons, [ undef, $text{'save'} ]);
|
||||
push(@buttons, [ 'delete', $text{'delete'} ]);
|
||||
}
|
||||
print ui_form_end(\@buttons);
|
||||
|
||||
sub js_array
|
||||
{
|
||||
my (@vals) = @_;
|
||||
return "[".join(",", map {
|
||||
my $v = $_;
|
||||
$v =~ s/\\/\\\\/g;
|
||||
$v =~ s/"/\\"/g;
|
||||
"\"$v\"";
|
||||
} @vals)."]";
|
||||
}
|
||||
|
||||
sub js_object
|
||||
{
|
||||
my (%vals) = @_;
|
||||
return "{".join(",", map {
|
||||
my $k = $_;
|
||||
my $v = $vals{$k};
|
||||
$k =~ s/\\/\\\\/g;
|
||||
$k =~ s/"/\\"/g;
|
||||
$v =~ s/\\/\\\\/g if (defined($v));
|
||||
$v =~ s/"/\\"/g if (defined($v));
|
||||
"\"$k\":\"$v\"";
|
||||
} sort keys %vals)."}";
|
||||
}
|
||||
|
||||
my $icmp_js = js_array(@icmp_types);
|
||||
my $icmpv6_js = js_array(@icmpv6_types);
|
||||
my $icmp_any = $text{'edit_proto_any'};
|
||||
$icmp_any =~ s/\\/\\\\/g;
|
||||
$icmp_any =~ s/"/\\"/g;
|
||||
my $set_fam_js = js_object(%set_families);
|
||||
|
||||
print "<script>\n";
|
||||
print "(function() {\n";
|
||||
print " var icmpTypes = $icmp_js;\n";
|
||||
print " var icmpv6Types = $icmpv6_js;\n";
|
||||
print " var icmpAnyLabel = \"$icmp_any\";\n";
|
||||
print " var setFamilies = $set_fam_js;\n";
|
||||
print <<'EOF';
|
||||
function byName(name) {
|
||||
var els = document.getElementsByName(name);
|
||||
return els && els.length ? els[0] : null;
|
||||
}
|
||||
function val(name) {
|
||||
var el = byName(name);
|
||||
if (!el) return "";
|
||||
if (el.tagName === "SELECT" && el.multiple) {
|
||||
var vals = [];
|
||||
var sawEmpty = false;
|
||||
for (var i = 0; i < el.options.length; i++) {
|
||||
var opt = el.options[i];
|
||||
if (opt.selected) {
|
||||
if (opt.value === "") sawEmpty = true;
|
||||
else vals.push(opt.value);
|
||||
}
|
||||
}
|
||||
if (vals.length && sawEmpty) {
|
||||
for (var j = 0; j < el.options.length; j++) {
|
||||
if (el.options[j].value === "") el.options[j].selected = false;
|
||||
}
|
||||
}
|
||||
return vals.join(",");
|
||||
}
|
||||
if (el.type === "checkbox") {
|
||||
return el.checked ? (el.value || "1") : "";
|
||||
}
|
||||
return el.value || "";
|
||||
}
|
||||
function ifaceVal(name) {
|
||||
var v = val(name);
|
||||
if (v === "other") {
|
||||
return val(name + "_other");
|
||||
}
|
||||
return v;
|
||||
}
|
||||
function escapeNft(s) {
|
||||
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
||||
}
|
||||
function isNumeric(s) {
|
||||
return /^[0-9]+$/.test(s);
|
||||
}
|
||||
function guessFamily(addr) {
|
||||
return addr.indexOf(":") >= 0 ? "ip6" : "ip";
|
||||
}
|
||||
function familyForSet(name) {
|
||||
return setFamilies && setFamilies[name] ? setFamilies[name] : "";
|
||||
}
|
||||
function familyForValue(val) {
|
||||
if (!val) return "";
|
||||
if (val.charAt(0) === "@") {
|
||||
var fam = familyForSet(val.substr(1));
|
||||
if (fam) return fam;
|
||||
}
|
||||
return guessFamily(val);
|
||||
}
|
||||
function buildRule() {
|
||||
var direct = byName("edit_direct");
|
||||
if (direct && direct.checked) return;
|
||||
var parts = [];
|
||||
|
||||
var iif = ifaceVal("iif");
|
||||
if (iif) parts.push("iif \"" + escapeNft(iif) + "\"");
|
||||
var oif = ifaceVal("oif");
|
||||
if (oif) parts.push("oif \"" + escapeNft(oif) + "\"");
|
||||
|
||||
var saddrSet = val("saddr_set");
|
||||
var saddr = val("saddr");
|
||||
if (saddrSet) {
|
||||
var sf = familyForSet(saddrSet) || guessFamily("@" + saddrSet);
|
||||
parts.push(sf + " saddr @" + saddrSet);
|
||||
} else if (saddr) {
|
||||
parts.push(familyForValue(saddr) + " saddr " + saddr);
|
||||
}
|
||||
var daddrSet = val("daddr_set");
|
||||
var daddr = val("daddr");
|
||||
if (daddrSet) {
|
||||
var df = familyForSet(daddrSet) || guessFamily("@" + daddrSet);
|
||||
parts.push(df + " daddr @" + daddrSet);
|
||||
} else if (daddr) {
|
||||
parts.push(familyForValue(daddr) + " daddr " + daddr);
|
||||
}
|
||||
|
||||
var proto = val("proto");
|
||||
var sportSet = val("sport_set");
|
||||
var dportSet = val("dport_set");
|
||||
var sport = sportSet ? ("@" + sportSet) : val("sport");
|
||||
var dport = dportSet ? ("@" + dportSet) : val("dport");
|
||||
var icmpType = val("icmp_type");
|
||||
if (!proto && (sport || dport)) {
|
||||
proto = "tcp";
|
||||
}
|
||||
|
||||
var l4proto = "";
|
||||
var portProto = "";
|
||||
if (proto && (proto === "tcp" || proto === "udp")) {
|
||||
portProto = proto;
|
||||
if (!sport && !dport) {
|
||||
l4proto = proto;
|
||||
}
|
||||
}
|
||||
else if (proto) {
|
||||
l4proto = proto;
|
||||
}
|
||||
if (l4proto) {
|
||||
parts.push("meta l4proto " + l4proto);
|
||||
}
|
||||
if (sport && portProto) parts.push(portProto + " sport " + sport);
|
||||
if (dport && portProto) parts.push(portProto + " dport " + dport);
|
||||
|
||||
if (proto === "icmp" && icmpType) parts.push("icmp type " + icmpType);
|
||||
if (proto === "icmpv6" && icmpType) parts.push("icmpv6 type " + icmpType);
|
||||
if (!proto && icmpType) {
|
||||
var inIcmp = icmpTypes.indexOf(icmpType) >= 0;
|
||||
var inIcmpv6 = icmpv6Types.indexOf(icmpType) >= 0;
|
||||
if (inIcmpv6 && !inIcmp) {
|
||||
parts.push("meta l4proto icmpv6");
|
||||
parts.push("icmpv6 type " + icmpType);
|
||||
} else {
|
||||
parts.push("meta l4proto icmp");
|
||||
parts.push("icmp type " + icmpType);
|
||||
}
|
||||
}
|
||||
|
||||
var tcpFlags = val("tcp_flags");
|
||||
var tcpMask = val("tcp_flags_mask");
|
||||
if (tcpFlags) {
|
||||
if (tcpMask) parts.push("tcp flags & " + tcpMask + " == " + tcpFlags);
|
||||
else parts.push("tcp flags " + tcpFlags);
|
||||
}
|
||||
|
||||
var ctState = val("ct_state");
|
||||
if (ctState) parts.push("ct state " + ctState);
|
||||
|
||||
var limitRate = val("limit_rate");
|
||||
var limitBurst = val("limit_burst");
|
||||
if (limitRate) {
|
||||
var lim = "limit rate " + limitRate;
|
||||
if (limitBurst) {
|
||||
lim += " burst " + limitBurst;
|
||||
if (isNumeric(limitBurst)) lim += " packets";
|
||||
}
|
||||
parts.push(lim);
|
||||
}
|
||||
|
||||
var logBox = byName("log");
|
||||
var logEnabled = logBox && logBox.checked;
|
||||
var logPrefix = val("log_prefix");
|
||||
var logLevel = val("log_level");
|
||||
if (logEnabled || logPrefix || logLevel) {
|
||||
var lp = ["log"];
|
||||
if (logPrefix) lp.push("prefix \"" + escapeNft(logPrefix) + "\"");
|
||||
if (logLevel) lp.push("level " + logLevel);
|
||||
parts.push(lp.join(" "));
|
||||
}
|
||||
|
||||
var counter = byName("counter");
|
||||
if (counter && counter.checked) parts.push("counter");
|
||||
|
||||
var action = val("action");
|
||||
var jump = val("jump");
|
||||
var go = val("goto");
|
||||
if (action === "jump" && jump) parts.push("jump " + jump);
|
||||
else if (action === "goto" && go) parts.push("goto " + go);
|
||||
else if (action && action !== "jump" && action !== "goto") parts.push(action);
|
||||
|
||||
var comment = val("comment");
|
||||
if (comment) parts.push("comment \"" + escapeNft(comment) + "\"");
|
||||
|
||||
var extra = val("raw_extra");
|
||||
if (extra) parts.push(extra);
|
||||
|
||||
var raw = parts.join(" ").replace(/^\s+|\s+$/g, "");
|
||||
var rawEl = byName("raw_rule");
|
||||
if (rawEl) rawEl.value = raw;
|
||||
}
|
||||
|
||||
function toggleDirect() {
|
||||
var direct = byName("edit_direct");
|
||||
var on = direct && direct.checked;
|
||||
var form = direct ? direct.form : document.forms[0];
|
||||
if (!form) return;
|
||||
var els = form.querySelectorAll("input, select, textarea");
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
var el = els[i];
|
||||
if (el.name === "edit_direct" || el.name === "raw_rule") continue;
|
||||
if (el.type === "hidden" || el.type === "submit" || el.type === "button") continue;
|
||||
el.disabled = on;
|
||||
}
|
||||
var rawEl = byName("raw_rule");
|
||||
if (rawEl) rawEl.readOnly = !on;
|
||||
if (!on) buildRule();
|
||||
}
|
||||
|
||||
function uniqList(list) {
|
||||
var seen = {};
|
||||
var out = [];
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var v = list[i];
|
||||
if (seen[v]) continue;
|
||||
seen[v] = true;
|
||||
out.push(v);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function updateIcmpTypes() {
|
||||
var el = byName("icmp_type");
|
||||
if (!el) return;
|
||||
var proto = val("proto");
|
||||
var current = el.value || "";
|
||||
var list;
|
||||
if (proto === "icmp") list = icmpTypes;
|
||||
else if (proto === "icmpv6") list = icmpv6Types;
|
||||
else list = uniqList(icmpTypes.concat(icmpv6Types));
|
||||
|
||||
while (el.options.length) {
|
||||
el.remove(0);
|
||||
}
|
||||
var optAny = document.createElement("option");
|
||||
optAny.value = "";
|
||||
optAny.text = icmpAnyLabel;
|
||||
el.add(optAny);
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var opt = document.createElement("option");
|
||||
opt.value = list[i];
|
||||
opt.text = list[i];
|
||||
el.add(opt);
|
||||
}
|
||||
if (current && list.indexOf(current) >= 0) el.value = current;
|
||||
else el.value = "";
|
||||
}
|
||||
|
||||
function maybeSetProtoFromIcmp() {
|
||||
var protoEl = byName("proto");
|
||||
if (!protoEl || protoEl.value) return;
|
||||
var t = val("icmp_type");
|
||||
if (!t) return;
|
||||
var inIcmp = icmpTypes.indexOf(t) >= 0;
|
||||
var inIcmpv6 = icmpv6Types.indexOf(t) >= 0;
|
||||
if (inIcmp && !inIcmpv6) protoEl.value = "icmp";
|
||||
else if (inIcmpv6 && !inIcmp) protoEl.value = "icmpv6";
|
||||
if (protoEl.value) updateIcmpTypes();
|
||||
}
|
||||
|
||||
function bind() {
|
||||
var direct = byName("edit_direct");
|
||||
var form = direct ? direct.form : document.forms[0];
|
||||
if (!form) return;
|
||||
var els = form.querySelectorAll("input, select, textarea");
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
var el = els[i];
|
||||
if (el.name === "raw_rule") continue;
|
||||
if (el.name === "edit_direct") {
|
||||
el.addEventListener("change", toggleDirect);
|
||||
continue;
|
||||
}
|
||||
if (el.name === "proto") {
|
||||
el.addEventListener("change", function() {
|
||||
updateIcmpTypes();
|
||||
buildRule();
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (el.name === "icmp_type") {
|
||||
el.addEventListener("change", function() {
|
||||
maybeSetProtoFromIcmp();
|
||||
buildRule();
|
||||
});
|
||||
continue;
|
||||
}
|
||||
el.addEventListener("input", buildRule);
|
||||
el.addEventListener("change", buildRule);
|
||||
}
|
||||
updateIcmpTypes();
|
||||
toggleDirect();
|
||||
buildRule();
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", bind);
|
||||
} else {
|
||||
bind();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
EOF
|
||||
|
||||
ui_print_footer("index.cgi?table=$in{'table'}", $text{'index_return'});
|
||||
86
nftables/edit_set.cgi
Executable file
86
nftables/edit_set.cgi
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/perl
|
||||
# edit_set.cgi
|
||||
# Display a form for creating or editing a set
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
my $table = $tables[$in{'table'}];
|
||||
$table || error($text{'set_notable'});
|
||||
|
||||
my $set = { };
|
||||
my $set_name = "";
|
||||
my $is_new = $in{'new'} ? 1 : 0;
|
||||
|
||||
if ($is_new) {
|
||||
ui_print_header(undef, $text{'set_title_new'}, "", "intro", 1, 1);
|
||||
}
|
||||
else {
|
||||
$set_name = $in{'set'};
|
||||
$set = $table->{'sets'}->{$set_name};
|
||||
$set || error($text{'set_noset'});
|
||||
ui_print_header(undef, $text{'set_title_edit'}, "", "intro", 1, 1);
|
||||
}
|
||||
|
||||
my $elements_text = set_elements_text($set);
|
||||
my @type_opts = (
|
||||
[ "", $text{'set_type_select'} ],
|
||||
[ "ipv4_addr", "ipv4_addr" ],
|
||||
[ "ipv6_addr", "ipv6_addr" ],
|
||||
[ "ether_addr", "ether_addr" ],
|
||||
[ "inet_proto", "inet_proto" ],
|
||||
[ "inet_service", "inet_service" ],
|
||||
[ "mark", "mark" ],
|
||||
);
|
||||
my %type_seen = map { $_->[0] => 1 } @type_opts;
|
||||
if ($set->{'type'} && !$type_seen{$set->{'type'}}) {
|
||||
push(@type_opts, [ $set->{'type'}, $set->{'type'} ]);
|
||||
}
|
||||
my @flag_opts = (
|
||||
[ "constant", "constant" ],
|
||||
[ "dynamic", "dynamic" ],
|
||||
[ "interval", "interval" ],
|
||||
[ "timeout", "timeout" ],
|
||||
);
|
||||
my @flags_sel;
|
||||
my $flags_sel;
|
||||
if ($set->{'flags'}) {
|
||||
@flags_sel = split(/\s+|,\s*/, $set->{'flags'});
|
||||
@flags_sel = grep { $_ ne '' } @flags_sel;
|
||||
my %flag_seen = map { $_->[0] => 1 } @flag_opts;
|
||||
foreach my $f (@flags_sel) {
|
||||
push(@flag_opts, [ $f, $f ]) if (!$flag_seen{$f}++);
|
||||
}
|
||||
}
|
||||
$flags_sel = @flags_sel ? \@flags_sel : undef;
|
||||
|
||||
print ui_form_start("save_set.cgi");
|
||||
print ui_hidden("table", $in{'table'});
|
||||
print ui_hidden("new", $is_new);
|
||||
print ui_hidden("set", $set_name) if (!$is_new);
|
||||
|
||||
print ui_table_start($text{'set_header'}, "width=100%", 2);
|
||||
|
||||
my $name_tags = $is_new ? undef : "readonly";
|
||||
print ui_table_row(hlink($text{'set_name'}, "set_name"),
|
||||
ui_textbox("set_name", $set_name, 20, 0, undef, $name_tags));
|
||||
|
||||
print ui_table_row(hlink($text{'set_type'}, "set_type"),
|
||||
ui_select("set_type", $set->{'type'}, \@type_opts, 1, 0, 1));
|
||||
|
||||
print ui_table_row(hlink($text{'set_flags'}, "set_flags"),
|
||||
ui_select("set_flags", $flags_sel, \@flag_opts, 5, 1, 1));
|
||||
|
||||
my $elem_field = ui_textarea("set_elements", $elements_text, 6, 60);
|
||||
$elem_field .= "<br><small>$text{'set_elements_desc'}</small>";
|
||||
print ui_table_row(hlink($text{'set_elements'}, "set_elements"),
|
||||
$elem_field);
|
||||
|
||||
print ui_table_end();
|
||||
|
||||
print ui_form_end([ [ undef, $text{$is_new ? 'create' : 'save'} ] ]);
|
||||
ui_print_footer("index.cgi?table=$in{'table'}", $text{'index_return'});
|
||||
7
nftables/help/action.html
Normal file
7
nftables/help/action.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<header>Action</header>
|
||||
<p>Verdict or control action for matching packets.</p>
|
||||
<ul>
|
||||
<li>Accept, Drop, Reject, Return</li>
|
||||
<li>Jump or Goto to another chain</li>
|
||||
</ul>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/chain_hook.html
Normal file
3
nftables/help/chain_hook.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Hook</header>
|
||||
<p>Base chain hook point, such as <tt>prerouting</tt>, <tt>input</tt>, <tt>forward</tt>, <tt>output</tt>, <tt>postrouting</tt>, or <tt>ingress</tt>.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/chain_name.html
Normal file
3
nftables/help/chain_name.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Chain name</header>
|
||||
<p>Unique name for the chain within this table. Use letters, numbers, underscores, and dashes.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/chain_policy.html
Normal file
3
nftables/help/chain_policy.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Policy</header>
|
||||
<p>Default action for this base chain, such as <tt>accept</tt>, <tt>drop</tt>, <tt>reject</tt>, <tt>queue</tt>, or <tt>continue</tt>.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/chain_priority.html
Normal file
3
nftables/help/chain_priority.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Priority</header>
|
||||
<p>Priority for this base chain. Lower values run earlier. Common values include <tt>-300</tt>, <tt>-150</tt>, <tt>0</tt>, or <tt>100</tt>.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/chain_type.html
Normal file
3
nftables/help/chain_type.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Chain type</header>
|
||||
<p>Base chain type, such as <tt>filter</tt>, <tt>nat</tt>, or <tt>route</tt>. Leave blank to create a regular chain with no hook.</p>
|
||||
<footer>nft(8)</footer>
|
||||
6
nftables/help/comment.html
Normal file
6
nftables/help/comment.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<header>Comment</header>
|
||||
|
||||
Optional note stored with the rule.<p>
|
||||
Saved as comment "text"; quotes and backslashes are escaped.<p>
|
||||
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/counter.html
Normal file
3
nftables/help/counter.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Counter</header>
|
||||
<p>Add a counter to track packets and bytes.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/counter_1.html
Normal file
3
nftables/help/counter_1.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Counter</header>
|
||||
<p>Add a counter to track packets and bytes.</p>
|
||||
<footer>nft(8)</footer>
|
||||
10
nftables/help/ct_state.html
Normal file
10
nftables/help/ct_state.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<header>Conntrack state</header>
|
||||
<p>Select one or more states to match.</p>
|
||||
<ul>
|
||||
<li>invalid</li>
|
||||
<li>new</li>
|
||||
<li>established</li>
|
||||
<li>related</li>
|
||||
<li>untracked</li>
|
||||
</ul>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/daddr.html
Normal file
3
nftables/help/daddr.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Destination address</header>
|
||||
<p>IPv4/IPv6 address or CIDR (e.g., 2001:db8::/32).</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/dport.html
Normal file
3
nftables/help/dport.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Destination port</header>
|
||||
<p>TCP/UDP destination port number or range (e.g., 80 or 1000-2000).</p>
|
||||
<footer>nft(8)</footer>
|
||||
4
nftables/help/edit_direct.html
Normal file
4
nftables/help/edit_direct.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<header>Edit directly</header>
|
||||
<p>Disable structured fields and edit the raw rule line.</p>
|
||||
<p>Validation occurs on save.</p>
|
||||
<footer>nft(8)</footer>
|
||||
4
nftables/help/edit_direct_1.html
Normal file
4
nftables/help/edit_direct_1.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<header>Edit directly</header>
|
||||
<p>Disable structured fields and edit the raw rule line.</p>
|
||||
<p>Validation occurs on save.</p>
|
||||
<footer>nft(8)</footer>
|
||||
4
nftables/help/goto.html
Normal file
4
nftables/help/goto.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<header>Goto target chain</header>
|
||||
<p>Name of the chain to transfer control to.</p>
|
||||
<p>Does not return to the calling chain.</p>
|
||||
<footer>nft(8)</footer>
|
||||
5
nftables/help/icmp_type.html
Normal file
5
nftables/help/icmp_type.html
Normal file
@@ -0,0 +1,5 @@
|
||||
<header>ICMP/ICMPv6 type</header>
|
||||
<p>Select a type name (or leave blank for any). The valid names depend on the protocol.</p>
|
||||
<p>ICMP types: echo-reply, destination-unreachable, source-quench, redirect, echo-request, router-advertisement, router-solicitation, time-exceeded, parameter-problem, timestamp-request, timestamp-reply, info-request, info-reply, address-mask-request, address-mask-reply.</p>
|
||||
<p>ICMPv6 types: destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-done, mld-listener-reduction, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, router-renumbering, ind-neighbor-solicit, ind-neighbor-advert, mld2-listener-report.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/iif.html
Normal file
3
nftables/help/iif.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Incoming interface</header>
|
||||
<p>Match the incoming interface name (e.g., eth0).</p>
|
||||
<footer>nft(8)</footer>
|
||||
4
nftables/help/intro.html
Normal file
4
nftables/help/intro.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<header>Introduction</header>
|
||||
<p>nftables stores firewall rules in tables. Each table belongs to a family (such as <tt>inet</tt>, <tt>ip</tt>, or <tt>ip6</tt>) and contains one or more chains. Chains contain rules, and each rule is a sequence of tests (matches) followed by an action like <tt>accept</tt>, <tt>drop</tt>, <tt>jump</tt>, or <tt>log</tt>. Named sets can group addresses or services for reuse in multiple rules.</p>
|
||||
<p>To get started, use the Setup page to create a default ruleset, or create a table and chain manually. Then add rules (and sets) from the table view. When you are ready to activate your changes, click Apply Configuration to load the ruleset into the kernel.</p>
|
||||
<footer>nft(8)</footer>
|
||||
4
nftables/help/jump.html
Normal file
4
nftables/help/jump.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<header>Jump target chain</header>
|
||||
<p>Name of the chain to call.</p>
|
||||
<p>Control returns to the next rule after the jump.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/limit_burst.html
Normal file
3
nftables/help/limit_burst.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Limit burst</header>
|
||||
<p>Optional burst size in packets.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/limit_rate.html
Normal file
3
nftables/help/limit_rate.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Limit rate</header>
|
||||
<p>Rate limit (e.g., 10/second, 5/minute, 100/hour).</p>
|
||||
<footer>nft(8)</footer>
|
||||
4
nftables/help/log.html
Normal file
4
nftables/help/log.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<header>Logging</header>
|
||||
<p>Add a log statement before the verdict.</p>
|
||||
<p>Use prefix and level to customize.</p>
|
||||
<footer>nft(8)</footer>
|
||||
4
nftables/help/log_1.html
Normal file
4
nftables/help/log_1.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<header>Logging</header>
|
||||
<p>Add a log statement before the verdict.</p>
|
||||
<p>Use prefix and level to customize.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/log_level.html
Normal file
3
nftables/help/log_level.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Log level</header>
|
||||
<p>Common values: emerg, alert, crit, err, warning, notice, info, debug.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/log_prefix.html
Normal file
3
nftables/help/log_prefix.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Log prefix</header>
|
||||
<p>String prepended to the log message.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/oif.html
Normal file
3
nftables/help/oif.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Outgoing interface</header>
|
||||
<p>Match the outgoing interface name (e.g., eth1).</p>
|
||||
<footer>nft(8)</footer>
|
||||
7
nftables/help/proto.html
Normal file
7
nftables/help/proto.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<header>Protocol</header>
|
||||
<p>Select a protocol to match.</p>
|
||||
<ul>
|
||||
<li>Any, TCP, UDP, ICMP, ICMPv6</li>
|
||||
</ul>
|
||||
<p>Ports apply to TCP/UDP; ICMP uses the type field.</p>
|
||||
<footer>nft(8)</footer>
|
||||
4
nftables/help/raw_rule.html
Normal file
4
nftables/help/raw_rule.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<header>Raw rule</header>
|
||||
<p>Generated rule line.</p>
|
||||
<p>In Edit directly mode, this is the rule that will be saved.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/saddr.html
Normal file
3
nftables/help/saddr.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Source address</header>
|
||||
<p>IPv4/IPv6 address or CIDR (e.g., 192.168.1.0/24).</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/set_elements.html
Normal file
3
nftables/help/set_elements.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Set elements</header>
|
||||
<p>Elements to store in the set. Separate entries with commas or newlines. Use nftables syntax, such as IPs/CIDR for address sets or ports for service sets.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/set_flags.html
Normal file
3
nftables/help/set_flags.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Set flags</header>
|
||||
<p>Optional flags for the set, like <tt>interval</tt> or <tt>timeout</tt>. Use the same syntax as nftables, separated by spaces if multiple.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/set_name.html
Normal file
3
nftables/help/set_name.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Set name</header>
|
||||
<p>Unique name for the set within the table. Use letters, numbers, underscores, or dashes. Rules reference a set as <tt>@name</tt>.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/set_type.html
Normal file
3
nftables/help/set_type.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Set type</header>
|
||||
<p>Type of elements stored in the set, such as <tt>ipv4_addr</tt>, <tt>ipv6_addr</tt>, or <tt>inet_service</tt>. This must match the elements you enter.</p>
|
||||
<footer>nft(8)</footer>
|
||||
3
nftables/help/sport.html
Normal file
3
nftables/help/sport.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<header>Source port</header>
|
||||
<p>TCP/UDP source port number or range (e.g., 22 or 1000-2000).</p>
|
||||
<footer>nft(8)</footer>
|
||||
14
nftables/help/tcp_flags.html
Normal file
14
nftables/help/tcp_flags.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<header>TCP flags</header>
|
||||
<p>Select one or more TCP flags to match.</p>
|
||||
<ul>
|
||||
<li>fin</li>
|
||||
<li>syn</li>
|
||||
<li>rst</li>
|
||||
<li>psh</li>
|
||||
<li>ack</li>
|
||||
<li>urg</li>
|
||||
<li>ecn</li>
|
||||
<li>cwr</li>
|
||||
</ul>
|
||||
<p>Use the mask field for a bitmask match.</p>
|
||||
<footer>nft(8)</footer>
|
||||
4
nftables/help/tcp_flags_mask.html
Normal file
4
nftables/help/tcp_flags_mask.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<header>TCP flags mask</header>
|
||||
<p>Optional mask used with tcp flags & mask == value.</p>
|
||||
<p>Example mask: syn|rst.</p>
|
||||
<footer>nft(8)</footer>
|
||||
BIN
nftables/images/icon.gif
Normal file
BIN
nftables/images/icon.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
206
nftables/index.cgi
Executable file
206
nftables/index.cgi
Executable file
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/perl
|
||||
# index.cgi
|
||||
# Display current nftables configuration
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text, %config);
|
||||
ReadParse();
|
||||
my $partial = $in{'partial'};
|
||||
if (!$partial) {
|
||||
ui_print_header(undef, $text{'index_title'}, "", "intro", 1, 1);
|
||||
}
|
||||
|
||||
# Check for nft command
|
||||
my $cmd = $config{'nft_cmd'} || has_command("nft");
|
||||
if (!$cmd) {
|
||||
print text('index_ecommand', "<tt>nft</tt>");
|
||||
if (!$partial) {
|
||||
ui_print_footer("/", $text{'index'});
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
# Check if kernel supports it (basic check)
|
||||
my $out = backquote_command("$cmd list ruleset 2>&1");
|
||||
if ($? && $out !~ /no ruleset/i) {
|
||||
# If it fails and not just empty
|
||||
print text('index_ekernel', "<pre>$out</pre>");
|
||||
if (!$partial) {
|
||||
ui_print_footer("/", $text{'index'});
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
# Load tables
|
||||
my @tables = get_nftables_save();
|
||||
my $rules_html = "";
|
||||
|
||||
if (!@tables) {
|
||||
$rules_html .= "<b>$text{'index_none'}</b><p>\n";
|
||||
$rules_html .= ui_buttons_start();
|
||||
$rules_html .= ui_buttons_row("setup.cgi", $text{'index_setup'}, $text{'index_setupdesc'});
|
||||
$rules_html .= ui_buttons_row("create_table.cgi", $text{'index_table_create'},
|
||||
$text{'index_table_createdesc'});
|
||||
$rules_html .= ui_buttons_end();
|
||||
} else {
|
||||
# Select table
|
||||
if (!defined($in{'table'}) || $in{'table'} !~ /^\d+$/ ||
|
||||
$in{'table'} > $#tables) {
|
||||
$in{'table'} = 0;
|
||||
}
|
||||
my @table_opts;
|
||||
for (my $i = 0; $i <= $#tables; $i++) {
|
||||
my $t = $tables[$i];
|
||||
push(@table_opts, [ $i, $t->{'family'}." ".$t->{'name'} ]);
|
||||
}
|
||||
|
||||
if (!$partial) {
|
||||
print ui_form_start("index.cgi");
|
||||
print "<div class='nftables_table_select'>\n";
|
||||
print text('index_change')," ";
|
||||
print ui_select("table", $in{'table'}, \@table_opts, 1, 0, 1, 0,
|
||||
"onchange='this.form.querySelector(\"[name=nft_submit]\").click()'");
|
||||
print ui_submit("", "nft_submit", 0, "style='display:none'");
|
||||
print "</div>\n";
|
||||
print ui_form_end();
|
||||
}
|
||||
|
||||
# Identify current table
|
||||
my $curr = $tables[$in{'table'}];
|
||||
|
||||
if ($curr) {
|
||||
# Show sets
|
||||
$rules_html .= ui_hr();
|
||||
$rules_html .= "<b>$text{'index_sets'}</b><br>\n";
|
||||
if ($curr->{'sets'} && ref($curr->{'sets'}) eq 'HASH' &&
|
||||
keys %{$curr->{'sets'}}) {
|
||||
$rules_html .= ui_columns_start(
|
||||
[ $text{'index_set_name'}, $text{'index_set_type'},
|
||||
$text{'index_set_flags'}, $text{'index_set_elements'},
|
||||
$text{'index_set_actions'} ], 100);
|
||||
foreach my $s (sort keys %{$curr->{'sets'}}) {
|
||||
my $set = $curr->{'sets'}->{$s} || { };
|
||||
my $actions_html =
|
||||
ui_link("edit_set.cgi?table=$in{'table'}&set=".
|
||||
urlize($s), $text{'index_set_edit'})."<br>".
|
||||
ui_link("delete_set.cgi?table=$in{'table'}&set=".
|
||||
urlize($s), $text{'index_set_delete'});
|
||||
$rules_html .= ui_columns_row([
|
||||
$s,
|
||||
$set->{'type'} || "-",
|
||||
$set->{'flags'} || "-",
|
||||
set_elements_summary($set),
|
||||
$actions_html
|
||||
]);
|
||||
}
|
||||
$rules_html .= ui_columns_end();
|
||||
}
|
||||
else {
|
||||
$rules_html .= "<i>$text{'index_sets_none'}</i><br>\n";
|
||||
}
|
||||
$rules_html .= ui_buttons_start();
|
||||
$rules_html .= ui_buttons_row(
|
||||
"edit_set.cgi?table=$in{'table'}&new=1",
|
||||
$text{'index_set_create'},
|
||||
$text{'index_set_createdesc'});
|
||||
$rules_html .= ui_buttons_end();
|
||||
|
||||
# Show chains and rules
|
||||
$rules_html .= ui_hr();
|
||||
$rules_html .= ui_columns_start(
|
||||
[ $text{'index_chain_col'}, $text{'index_type'},
|
||||
$text{'index_hook'}, $text{'index_priority'},
|
||||
$text{'index_policy_col'}, $text{'index_rules'},
|
||||
$text{'index_actions'} ], 100);
|
||||
|
||||
foreach my $c (sort keys %{$curr->{'chains'}}) {
|
||||
my $chain_def = $curr->{'chains'}->{$c} || { };
|
||||
my $policy = $chain_def->{'policy'};
|
||||
my $policy_label = $policy ?
|
||||
($text{'index_policy_'.lc($policy)} || uc($policy)) : "-";
|
||||
my @rules = grep { $_->{'chain'} eq $c } @{$curr->{'rules'}};
|
||||
my $rules_html_row;
|
||||
if (@rules) {
|
||||
my $ri = 0;
|
||||
$rules_html_row = "<table class='nftables_rules_table' width='100%'>\n";
|
||||
foreach my $r (@rules) {
|
||||
my $desc = describe_rule($r);
|
||||
my $rule_link = ui_link(
|
||||
"edit_rule.cgi?table=$in{'table'}&chain=".
|
||||
urlize($c)."&idx=$r->{'index'}",
|
||||
$desc);
|
||||
my $move = ui_up_down_arrows(
|
||||
"move_rule.cgi?table=$in{'table'}&chain=".
|
||||
urlize($c)."&idx=$r->{'index'}&dir=up",
|
||||
"move_rule.cgi?table=$in{'table'}&chain=".
|
||||
urlize($c)."&idx=$r->{'index'}&dir=down",
|
||||
$ri > 0,
|
||||
$ri < $#rules);
|
||||
$rules_html_row .= "<tr><td>$rule_link</td>".
|
||||
"<td align='right' style='white-space:nowrap'>$move</td></tr>\n";
|
||||
$ri++;
|
||||
}
|
||||
$rules_html_row .= "<tr><td colspan='2'>".
|
||||
ui_link("edit_rule.cgi?table=$in{'table'}&chain=".
|
||||
urlize($c)."&new=1", $text{'index_radd'}).
|
||||
"</td></tr>\n";
|
||||
$rules_html_row .= "</table>";
|
||||
} else {
|
||||
$rules_html_row = "<i>$text{'index_rules_none'}</i>";
|
||||
$rules_html_row .= "<br>".
|
||||
ui_link("edit_rule.cgi?table=$in{'table'}&chain=".
|
||||
urlize($c)."&new=1", $text{'index_radd'});
|
||||
}
|
||||
|
||||
my $actions_html =
|
||||
ui_link("edit_chain.cgi?table=$in{'table'}&chain=".
|
||||
urlize($c), $text{'index_cedit'})."<br>".
|
||||
ui_link("rename_chain.cgi?table=$in{'table'}&chain=".
|
||||
urlize($c), $text{'index_crename'})."<br>".
|
||||
ui_link("delete_chain.cgi?table=$in{'table'}&chain=".
|
||||
urlize($c), $text{'index_cdelete'});
|
||||
$rules_html .= ui_columns_row([
|
||||
$c,
|
||||
$chain_def->{'type'} || "-",
|
||||
$chain_def->{'hook'} || "-",
|
||||
defined($chain_def->{'priority'}) ? $chain_def->{'priority'} : "-",
|
||||
$policy_label,
|
||||
$rules_html_row,
|
||||
$actions_html
|
||||
]);
|
||||
}
|
||||
$rules_html .= ui_columns_end();
|
||||
$rules_html .= ui_hr();
|
||||
$rules_html .= ui_buttons_start();
|
||||
$rules_html .= ui_buttons_row(
|
||||
"edit_chain.cgi?table=$in{'table'}&new=1",
|
||||
$text{'index_chain_create'},
|
||||
$text{'index_chain_createdesc'});
|
||||
$rules_html .= ui_buttons_row("delete_table.cgi?table=$in{'table'}",
|
||||
$text{'index_table_delete'},
|
||||
$text{'index_table_deletedesc'});
|
||||
$rules_html .= ui_buttons_end();
|
||||
}
|
||||
}
|
||||
|
||||
if ($partial) {
|
||||
print $rules_html;
|
||||
exit;
|
||||
}
|
||||
|
||||
print "<div id='nftables_ruleset'>\n";
|
||||
print $rules_html;
|
||||
print "</div>\n";
|
||||
|
||||
if (@tables) {
|
||||
print ui_hr();
|
||||
print ui_buttons_start();
|
||||
print ui_buttons_row("create_table.cgi", $text{'index_table_create'},
|
||||
$text{'index_table_createdesc'});
|
||||
print ui_buttons_row("apply.cgi", $text{'index_apply'}, $text{'index_applydesc'});
|
||||
print ui_buttons_end();
|
||||
}
|
||||
|
||||
ui_print_footer("/", $text{'index'});
|
||||
18
nftables/install_check.pl
Normal file
18
nftables/install_check.pl
Normal file
@@ -0,0 +1,18 @@
|
||||
# install_check.pl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our %config;
|
||||
do './nftables-lib.pl';
|
||||
|
||||
# is_installed(mode)
|
||||
# For mode 1, returns 2 if the server is installed and configured for use by
|
||||
# Webmin, 1 if installed but not configured, or 0 otherwise.
|
||||
# For mode 0, returns 1 if installed, 0 if not
|
||||
sub is_installed {
|
||||
my ($mode) = @_;
|
||||
|
||||
# Available config file in the default location?
|
||||
return 0 if (!-x $config{'nft_cmd'});
|
||||
return $mode ? 2 : 0;
|
||||
}
|
||||
222
nftables/lang/en
Normal file
222
nftables/lang/en
Normal file
@@ -0,0 +1,222 @@
|
||||
index_title=NFTables Firewall
|
||||
index_editing=Rules file $1
|
||||
index_ecommand=The NFTables command $1 was not found on your system.
|
||||
index_ekernel=An error occurred checking your current NFTables configuration: $1
|
||||
index_header=Existing NFTables Rules
|
||||
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
|
||||
index_comm=Comment
|
||||
index_move=Move
|
||||
index_add=Add
|
||||
index_none=No rules defined for this chain.
|
||||
index_policy=Set default action:
|
||||
index_policy_accept=Accept
|
||||
index_policy_drop=Drop
|
||||
index_policy_queue=Userspace
|
||||
index_policy_return=Return
|
||||
index_chain_col=Chain
|
||||
index_type=Type
|
||||
index_hook=Hook
|
||||
index_priority=Priority
|
||||
index_policy_col=Policy
|
||||
index_rules=Rules
|
||||
index_rules_none=No rules
|
||||
index_actions=Actions
|
||||
index_sets=Sets
|
||||
index_sets_none=No sets defined.
|
||||
index_set_name=Name
|
||||
index_set_type=Type
|
||||
index_set_flags=Flags
|
||||
index_set_elements=Elements
|
||||
index_set_actions=Actions
|
||||
index_set_create=Create set
|
||||
index_set_createdesc=Add a new set to this table.
|
||||
index_set_edit=Edit set
|
||||
index_set_delete=Delete set
|
||||
index_chain_create=Create chain
|
||||
index_chain_createdesc=Add a new chain to this table.
|
||||
index_cedit=Edit Chain
|
||||
index_cdelete=Delete Chain
|
||||
index_crename=Rename Chain
|
||||
index_cclear=Clear All Rules
|
||||
index_cdeletesel=Delete Selected
|
||||
index_cmovesel=Move Selected
|
||||
index_radd=Add Rule
|
||||
index_apply=Apply Configuration
|
||||
index_applydesc=Click this button to make the firewall configuration listed above active. Any current firewall rules will be flushed and replaced
|
||||
index_unapply=Revert Configuration
|
||||
index_unapplydesc=Click this button to reset the configuration listed above to the one that is currently active.
|
||||
index_bootup=Activate at Boot
|
||||
index_bootupdesc=Change whether this firewall is activated at boot time or not.
|
||||
index_return=rules list
|
||||
edit_title=Edit Rule
|
||||
edit_header=Rule Details
|
||||
edit_rule=Rule definition
|
||||
create=Create
|
||||
save=Save
|
||||
delete=Delete
|
||||
save_err=Failed to save rule
|
||||
apply_err=Failed to apply configuration
|
||||
setup_title=Setup Default Ruleset
|
||||
setup_header=Create Default Ruleset
|
||||
setup_desc=This page allows you to create a default nftables ruleset. Select one of the options below and click 'Create'.
|
||||
setup_deny_note=Deny options will still allow SSH (port 22), Webmin (port $1), and localhost (loopback).
|
||||
setup_allow_all=Allow all traffic
|
||||
setup_deny_incoming=Deny all incoming traffic (except SSH and Webmin), allow all outgoing
|
||||
setup_deny_all=Deny all traffic (except SSH and Webmin)
|
||||
setup_create=Create
|
||||
setup_invalid_type=Invalid ruleset type selected.
|
||||
setup_failed=Failed to create default ruleset: <pre>$1</pre>
|
||||
index_setup=Create Default Ruleset
|
||||
index_setupdesc=Create a default set of rules, for example to allow all traffic.
|
||||
index_table_create=Create table
|
||||
index_table_createdesc=Add a new nftables table.
|
||||
index_table_delete=Delete table
|
||||
index_table_deletedesc=Remove the currently selected table.
|
||||
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_set_none=None
|
||||
edit_saddr_set=Use set $1
|
||||
edit_daddr_set=Use set $1
|
||||
edit_sport_set=Use set $1
|
||||
edit_dport_set=Use set $1
|
||||
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_raw_rule_direct=Edit directly
|
||||
edit_advanced=Advanced options
|
||||
edit_oif=Outgoing Interface
|
||||
edit_iif=Incoming Interface
|
||||
edit_if_any=Any
|
||||
chain_title_new=Create chain
|
||||
chain_title_edit=Edit chain
|
||||
chain_header=Chain Details
|
||||
chain_name=Chain name
|
||||
chain_type=Type
|
||||
chain_hook=Hook
|
||||
chain_priority=Priority
|
||||
chain_policy=Policy
|
||||
chain_type_none=Regular (no hook)
|
||||
chain_hook_none=None
|
||||
chain_policy_none=None
|
||||
chain_err=Failed to save chain
|
||||
chain_failed=Failed to save chain: <pre>$1</pre>
|
||||
chain_ename=Chain name is invalid
|
||||
chain_edup=A chain with that name already exists
|
||||
chain_notable=No such table selected
|
||||
chain_nochain=No such chain selected
|
||||
chain_ebase=Base chains require type, hook, priority, and policy.
|
||||
delete_chain_title=Delete chain
|
||||
delete_chain_err=Failed to delete chain
|
||||
delete_chain_failed=Failed to delete chain: <pre>$1</pre>
|
||||
delete_chain_confirm=Are you sure you want to delete chain $1 from table $2?
|
||||
delete_chain_inuse=Chain $1 is referenced by $2 rule(s) via jump/goto. Remove those rules first.
|
||||
rename_chain_title=Rename chain
|
||||
rename_chain_header=Rename chain
|
||||
rename_chain_old=Current name
|
||||
rename_chain_new=New name
|
||||
rename_chain_ok=Rename
|
||||
rename_chain_failed=Failed to rename chain: <pre>$1</pre>
|
||||
create_title=Create table
|
||||
create_header=Create a new table
|
||||
create_family=Table family
|
||||
create_name=Table name
|
||||
create_ok=Create
|
||||
create_err=Failed to create table
|
||||
create_failed=Failed to create table: <pre>$1</pre>
|
||||
create_ename=Table name is invalid
|
||||
create_edup=A table with that name and family already exists
|
||||
create_efamily=Invalid table family selected
|
||||
delete_title=Delete table
|
||||
delete_err=Failed to delete table
|
||||
delete_failed=Failed to delete table: <pre>$1</pre>
|
||||
delete_confirm=Are you sure you want to delete table $1?
|
||||
delete_notable=No such table selected
|
||||
save_failed=Failed to save rule: <pre>$1</pre>
|
||||
save_raw_empty=Raw rule cannot be empty.
|
||||
save_raw_multiline=Raw rule must be a single line.
|
||||
save_invalid_rule=Raw rule is invalid: $1
|
||||
save_set_missing=Selected set $1 does not exist.
|
||||
move_err=Failed to move rule
|
||||
move_failed=Failed to move rule: <pre>$1</pre>
|
||||
move_notable=No such table selected
|
||||
move_nochain=No such chain selected
|
||||
move_norule=No such rule selected
|
||||
set_title_new=Create set
|
||||
set_title_edit=Edit set
|
||||
set_header=Set Details
|
||||
set_name=Set name
|
||||
set_type=Type
|
||||
set_type_select=Select type
|
||||
set_flags=Flags
|
||||
set_elements=Elements
|
||||
set_elements_desc=Separate elements with commas or newlines.
|
||||
set_err=Failed to save set
|
||||
set_failed=Failed to save set: <pre>$1</pre>
|
||||
set_ename=Set name is invalid
|
||||
set_edup=A set with that name already exists
|
||||
set_etype=Set type is required
|
||||
set_notable=No such table selected
|
||||
set_noset=No such set selected
|
||||
delete_set_title=Delete set
|
||||
delete_set_err=Failed to delete set
|
||||
delete_set_failed=Failed to delete set: <pre>$1</pre>
|
||||
delete_set_confirm=Are you sure you want to delete set $1 from table $2?
|
||||
delete_set_inuse=Set $1 is referenced by $2 rule(s). Remove those rules first.
|
||||
5
nftables/module.info
Normal file
5
nftables/module.info
Normal file
@@ -0,0 +1,5 @@
|
||||
name=Nftables
|
||||
desc=NFTables Firewall
|
||||
os_support=*-linux
|
||||
category=net
|
||||
longdesc=Configure a Linux firewall using NFTables. Allows the editing of all tables, chains, and rules.
|
||||
40
nftables/move_rule.cgi
Executable file
40
nftables/move_rule.cgi
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/perl
|
||||
# move_rule.cgi
|
||||
# Move a rule up or down within a chain
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
error_setup($text{'move_err'});
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
my $table = $tables[$in{'table'}];
|
||||
$table || error($text{'move_notable'});
|
||||
|
||||
my $chain = $in{'chain'};
|
||||
$chain || error($text{'move_nochain'});
|
||||
|
||||
my $dir = $in{'dir'};
|
||||
$dir = '' if (!defined($dir));
|
||||
|
||||
my $idx = $in{'idx'};
|
||||
$idx =~ /^\d+$/ || error($text{'move_norule'});
|
||||
|
||||
my $rv = move_rule_in_chain($table, $chain, $idx, $dir);
|
||||
if (!defined($rv)) {
|
||||
error($text{'move_norule'});
|
||||
}
|
||||
|
||||
if ($rv) {
|
||||
my $err = save_configuration(@tables);
|
||||
error(text('move_failed', $err)) if ($err);
|
||||
webmin_log("move", "rule", undef,
|
||||
{ 'table' => $table->{'name'},
|
||||
'family' => $table->{'family'},
|
||||
'chain' => $chain,
|
||||
'dir' => $dir });
|
||||
}
|
||||
|
||||
redirect("index.cgi?table=$in{'table'}");
|
||||
1140
nftables/nftables-lib.pl
Normal file
1140
nftables/nftables-lib.pl
Normal file
File diff suppressed because it is too large
Load Diff
33
nftables/rename_chain.cgi
Normal file
33
nftables/rename_chain.cgi
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/perl
|
||||
# rename_chain.cgi
|
||||
# Rename an existing chain
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
my $table = $tables[$in{'table'}];
|
||||
$table || error($text{'chain_notable'});
|
||||
|
||||
my $chain = $table->{'chains'}->{$in{'chain'}};
|
||||
$chain || error($text{'chain_nochain'});
|
||||
|
||||
ui_print_header(undef, $text{'rename_chain_title'}, "", "intro", 1, 1);
|
||||
print ui_form_start("save_chain.cgi");
|
||||
print ui_hidden("table", $in{'table'});
|
||||
print ui_hidden("rename", 1);
|
||||
print ui_hidden("chain_old", $in{'chain'});
|
||||
|
||||
print ui_table_start($text{'rename_chain_header'}, "width=100%", 2);
|
||||
print ui_table_row($text{'rename_chain_old'},
|
||||
"<tt>".html_escape($in{'chain'})."</tt>");
|
||||
print ui_table_row(hlink($text{'rename_chain_new'}, "chain_name"),
|
||||
ui_textbox("chain_name", $in{'chain'}, 20));
|
||||
print ui_table_end();
|
||||
|
||||
print ui_form_end([ [ undef, $text{'rename_chain_ok'} ] ]);
|
||||
ui_print_footer("index.cgi?table=$in{'table'}", $text{'index_return'});
|
||||
|
||||
99
nftables/save_chain.cgi
Normal file
99
nftables/save_chain.cgi
Normal file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/perl
|
||||
# save_chain.cgi
|
||||
# Save a new or existing chain
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
error_setup($text{'chain_err'});
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
my $table = $tables[$in{'table'}];
|
||||
$table || error($text{'chain_notable'});
|
||||
|
||||
my $is_new = $in{'new'} ? 1 : 0;
|
||||
my $is_rename = $in{'rename'} ? 1 : 0;
|
||||
my $name = $in{'chain_name'};
|
||||
$name =~ s/^\s+// if (defined($name));
|
||||
$name =~ s/\s+$// if (defined($name));
|
||||
$name =~ /^\w[\w-]*$/ || error($text{'chain_ename'});
|
||||
|
||||
my $old = $is_rename ? $in{'chain_old'} : $name;
|
||||
$old =~ s/^\s+// if (defined($old));
|
||||
$old =~ s/\s+$// if (defined($old));
|
||||
|
||||
if ($is_new) {
|
||||
$table->{'chains'}->{$name} && error($text{'chain_edup'});
|
||||
} elsif ($is_rename) {
|
||||
$table->{'chains'}->{$old} || error($text{'chain_nochain'});
|
||||
if ($name ne $old && $table->{'chains'}->{$name}) {
|
||||
error($text{'chain_edup'});
|
||||
}
|
||||
} else {
|
||||
$table->{'chains'}->{$name} || error($text{'chain_nochain'});
|
||||
}
|
||||
|
||||
if ($is_rename) {
|
||||
if ($name eq $old) {
|
||||
redirect("index.cgi?table=$in{'table'}");
|
||||
}
|
||||
if ($name ne $old) {
|
||||
$table->{'chains'}->{$name} = $table->{'chains'}->{$old};
|
||||
delete($table->{'chains'}->{$old});
|
||||
|
||||
foreach my $r (@{$table->{'rules'}}) {
|
||||
$r->{'chain'} = $name if ($r->{'chain'} && $r->{'chain'} eq $old);
|
||||
my $changed = 0;
|
||||
if ($r->{'jump'} && $r->{'jump'} eq $old) {
|
||||
$r->{'jump'} = $name;
|
||||
$changed = 1;
|
||||
}
|
||||
if ($r->{'goto'} && $r->{'goto'} eq $old) {
|
||||
$r->{'goto'} = $name;
|
||||
$changed = 1;
|
||||
}
|
||||
$r->{'text'} = format_rule_text($r) if ($changed);
|
||||
}
|
||||
}
|
||||
|
||||
my $err = save_configuration(@tables);
|
||||
error(text('rename_chain_failed', $err)) if ($err);
|
||||
webmin_log("rename", "chain", $old,
|
||||
{ 'new' => $name,
|
||||
'table' => $table->{'name'},
|
||||
'family' => $table->{'family'} });
|
||||
redirect("index.cgi?table=$in{'table'}");
|
||||
}
|
||||
|
||||
my $type = $in{'chain_type'};
|
||||
my $hook = $in{'chain_hook'};
|
||||
my $priority = $in{'chain_priority'};
|
||||
my $policy = $in{'chain_policy'};
|
||||
|
||||
for my $v (\$type, \$hook, \$priority, \$policy) {
|
||||
$$v =~ s/^\s+// if (defined($$v));
|
||||
$$v =~ s/\s+$// if (defined($$v));
|
||||
}
|
||||
$type = undef if (!defined($type) || $type eq '');
|
||||
$hook = undef if (!defined($hook) || $hook eq '');
|
||||
$priority = undef if (!defined($priority) || $priority eq '');
|
||||
$policy = undef if (!defined($policy) || $policy eq '');
|
||||
|
||||
validate_chain_base($type, $hook, $priority, $policy) ||
|
||||
error($text{'chain_ebase'});
|
||||
|
||||
my $chain = $table->{'chains'}->{$name} || { };
|
||||
$chain->{'type'} = $type;
|
||||
$chain->{'hook'} = $hook;
|
||||
$chain->{'priority'} = $priority;
|
||||
$chain->{'policy'} = $policy;
|
||||
$table->{'chains'}->{$name} = $chain;
|
||||
|
||||
my $err = save_configuration(@tables);
|
||||
error(text('chain_failed', $err)) if ($err);
|
||||
|
||||
webmin_log($is_new ? "create" : "modify", "chain", $name,
|
||||
{ 'table' => $table->{'name'}, 'family' => $table->{'family'} });
|
||||
redirect("index.cgi?table=$in{'table'}");
|
||||
180
nftables/save_rule.cgi
Executable file
180
nftables/save_rule.cgi
Executable file
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/perl
|
||||
# save_rule.cgi
|
||||
# Save a new or existing rule
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text, %config);
|
||||
ReadParse();
|
||||
error_setup($text{'save_err'});
|
||||
my @tables = get_nftables_save();
|
||||
my $table = $tables[$in{'table'}];
|
||||
|
||||
foreach my $sfield (qw(saddr_set daddr_set sport_set dport_set)) {
|
||||
if ($in{$sfield}) {
|
||||
$table->{'sets'}->{$in{$sfield}} ||
|
||||
error(text('save_set_missing', $in{$sfield}));
|
||||
}
|
||||
}
|
||||
|
||||
sub join_multi_value
|
||||
{
|
||||
my ($v) = @_;
|
||||
return if (!defined($v) || $v eq '');
|
||||
my @vals = split(/\0/, $v);
|
||||
@vals = grep { defined($_) && $_ ne '' } @vals;
|
||||
return if (!@vals);
|
||||
return join(",", @vals);
|
||||
}
|
||||
|
||||
if ($in{'delete'}) {
|
||||
# Delete the rule
|
||||
my $rule = $table->{'rules'}->[$in{'idx'}];
|
||||
splice(@{$table->{'rules'}}, $in{'idx'}, 1);
|
||||
webmin_log("delete", "rule", $rule ? $rule->{'text'} : undef);
|
||||
} else {
|
||||
my $rule = {};
|
||||
if ($in{'new'}) {
|
||||
$rule->{'chain'} = $in{'chain'};
|
||||
$rule->{'index'} = scalar(@{$table->{'rules'}});
|
||||
} else {
|
||||
$rule = $table->{'rules'}->[$in{'idx'}];
|
||||
}
|
||||
|
||||
if ($in{'edit_direct'}) {
|
||||
my $raw = $in{'raw_rule'};
|
||||
$raw =~ s/\r//g if (defined($raw));
|
||||
$raw =~ s/^\s+// if (defined($raw));
|
||||
$raw =~ s/\s+$// if (defined($raw));
|
||||
error($text{'save_raw_empty'}) if (!defined($raw) || $raw eq '');
|
||||
error($text{'save_raw_multiline'}) if ($raw =~ /[\r\n]/);
|
||||
$rule->{'text'} = $raw;
|
||||
}
|
||||
else {
|
||||
$rule->{'comment'} = $in{'comment'};
|
||||
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;
|
||||
}
|
||||
|
||||
my $saddr = $in{'saddr'};
|
||||
my $daddr = $in{'daddr'};
|
||||
$saddr = '@'.$in{'saddr_set'} if ($in{'saddr_set'});
|
||||
$daddr = '@'.$in{'daddr_set'} if ($in{'daddr_set'});
|
||||
$rule->{'saddr'} = (defined($saddr) && $saddr ne '') ? $saddr : undef;
|
||||
$rule->{'daddr'} = (defined($daddr) && $daddr ne '') ? $daddr : undef;
|
||||
$rule->{'saddr_family'} = $rule->{'saddr'} ? guess_addr_family($rule->{'saddr'}) : undef;
|
||||
$rule->{'daddr_family'} = $rule->{'daddr'} ? guess_addr_family($rule->{'daddr'}) : undef;
|
||||
if ($rule->{'saddr'} && $rule->{'saddr'} =~ /^\@(\S+)/) {
|
||||
my $fam = set_type_family($table->{'sets'}->{$1}->{'type'});
|
||||
$rule->{'saddr_family'} = $fam if ($fam);
|
||||
}
|
||||
if ($rule->{'daddr'} && $rule->{'daddr'} =~ /^\@(\S+)/) {
|
||||
my $fam = set_type_family($table->{'sets'}->{$1}->{'type'});
|
||||
$rule->{'daddr_family'} = $fam if ($fam);
|
||||
}
|
||||
|
||||
my $proto = $in{'proto'};
|
||||
$proto = undef if (defined($proto) && $proto eq '');
|
||||
my $sport = $in{'sport'};
|
||||
my $dport = $in{'dport'};
|
||||
$sport = '@'.$in{'sport_set'} if ($in{'sport_set'});
|
||||
$dport = '@'.$in{'dport_set'} if ($in{'dport_set'});
|
||||
$rule->{'sport'} = (defined($sport) && $sport ne '') ? $sport : undef;
|
||||
$rule->{'dport'} = (defined($dport) && $dport ne '') ? $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';
|
||||
}
|
||||
|
||||
my $ct_state = join_multi_value($in{'ct_state'});
|
||||
my $tcp_flags = join_multi_value($in{'tcp_flags'});
|
||||
$rule->{'ct_state'} = defined($ct_state) ? $ct_state : undef;
|
||||
$rule->{'tcp_flags'} = defined($tcp_flags) ? $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');
|
||||
$oif = $in{'oif_other'} if (defined($oif) && $oif eq 'other');
|
||||
$rule->{'iif'} = (defined($iif) && $iif ne '') ? $iif : undef;
|
||||
$rule->{'oif'} = (defined($oif) && $oif ne '') ? $oif : undef;
|
||||
|
||||
$rule->{'text'} = format_rule_text($rule);
|
||||
}
|
||||
|
||||
if ($in{'new'}) {
|
||||
push(@{$table->{'rules'}}, $rule);
|
||||
}
|
||||
|
||||
if ($in{'edit_direct'}) {
|
||||
my $cmd = $config{'nft_cmd'} || has_command("nft");
|
||||
if ($cmd) {
|
||||
my $tmp = tempname();
|
||||
open_tempfile(my $fh, ">$tmp");
|
||||
print_tempfile($fh, dump_nftables_save(@tables));
|
||||
close_tempfile($fh);
|
||||
my $out = backquote_logged("$cmd -c -f $tmp 2>&1");
|
||||
unlink_file($tmp);
|
||||
error(text('save_invalid_rule', "<pre>$out</pre>")) if ($?);
|
||||
}
|
||||
}
|
||||
|
||||
webmin_log("save", $in{'new'} ? "create" : "modify", $rule->{'text'});
|
||||
}
|
||||
my $err = save_configuration(@tables);
|
||||
error(text('save_failed', $err)) if ($err);
|
||||
redirect("index.cgi?table=$in{'table'}");
|
||||
62
nftables/save_set.cgi
Executable file
62
nftables/save_set.cgi
Executable file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/perl
|
||||
# save_set.cgi
|
||||
# Save a new or existing set
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text);
|
||||
ReadParse();
|
||||
error_setup($text{'set_err'});
|
||||
|
||||
my @tables = get_nftables_save();
|
||||
my $table = $tables[$in{'table'}];
|
||||
$table || error($text{'set_notable'});
|
||||
|
||||
my $is_new = $in{'new'} ? 1 : 0;
|
||||
my $name = $in{'set_name'};
|
||||
$name =~ /^\w[\w-]*$/ || error($text{'set_ename'});
|
||||
|
||||
if ($is_new && $table->{'sets'}->{$name}) {
|
||||
error($text{'set_edup'});
|
||||
}
|
||||
|
||||
my $type = $in{'set_type'};
|
||||
$type = undef if (defined($type) && $type =~ /^\s*$/);
|
||||
error($text{'set_etype'}) if (!$type);
|
||||
|
||||
my $flags = $in{'set_flags'};
|
||||
if (defined($flags) && $flags ne '') {
|
||||
my @vals = split(/\0/, $flags);
|
||||
@vals = grep { defined($_) && $_ ne '' } @vals;
|
||||
$flags = @vals ? join(" ", @vals) : undef;
|
||||
}
|
||||
$flags = undef if (defined($flags) && $flags =~ /^\s*$/);
|
||||
|
||||
my $elements = parse_set_elements_input($in{'set_elements'});
|
||||
|
||||
my $set;
|
||||
if ($is_new) {
|
||||
$set = { 'name' => $name, 'raw_lines' => [ ] };
|
||||
}
|
||||
else {
|
||||
my $orig = $in{'set'};
|
||||
$set = $table->{'sets'}->{$orig};
|
||||
$set || error($text{'set_noset'});
|
||||
$name = $orig;
|
||||
}
|
||||
|
||||
$set->{'name'} = $name;
|
||||
$set->{'type'} = $type;
|
||||
$set->{'flags'} = $flags;
|
||||
$set->{'elements'} = $elements;
|
||||
$set->{'raw_lines'} ||= [ ];
|
||||
|
||||
$table->{'sets'}->{$name} = $set;
|
||||
|
||||
my $err = save_configuration(@tables);
|
||||
error(text('set_failed', $err)) if ($err);
|
||||
|
||||
webmin_log($is_new ? "create" : "save", "set", $name,
|
||||
{ 'table' => $table->{'name'}, 'family' => $table->{'family'} });
|
||||
redirect("index.cgi?table=$in{'table'}");
|
||||
197
nftables/setup.cgi
Normal file
197
nftables/setup.cgi
Normal file
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/perl
|
||||
# setup.cgi
|
||||
# Create a default nftables ruleset
|
||||
|
||||
require './nftables-lib.pl'; ## no critic
|
||||
use strict;
|
||||
use warnings;
|
||||
our (%in, %text, %config);
|
||||
ReadParse();
|
||||
if ($in{'action'} eq 'create') {
|
||||
my $type = $in{'type'};
|
||||
my @tables;
|
||||
if ($type eq 'allow_all') {
|
||||
@tables = create_allow_all_ruleset();
|
||||
}
|
||||
elsif ($type eq 'deny_incoming') {
|
||||
@tables = create_deny_incoming_ruleset();
|
||||
}
|
||||
elsif ($type eq 'deny_all') {
|
||||
@tables = create_deny_all_ruleset();
|
||||
}
|
||||
else {
|
||||
error($text{'setup_invalid_type'});
|
||||
}
|
||||
|
||||
my $error = save_configuration(@tables);
|
||||
if ($error) {
|
||||
error(text('setup_failed', $error));
|
||||
}
|
||||
$error = apply_restore();
|
||||
if ($error) {
|
||||
error(text('setup_failed', $error));
|
||||
}
|
||||
webmin_log("setup", "create", $type);
|
||||
redirect("index.cgi");
|
||||
}
|
||||
|
||||
ui_print_header(undef, $text{'setup_title'}, "", "intro", 1, 1);
|
||||
|
||||
print "<h3>$text{'setup_header'}</h3>";
|
||||
my $webmin_port = get_webmin_port();
|
||||
print "<p>$text{'setup_desc'}</p>";
|
||||
print "<p>",text('setup_deny_note', $webmin_port),"</p>";
|
||||
|
||||
print ui_form_start("setup.cgi");
|
||||
print ui_hidden("action", "create");
|
||||
|
||||
my @type_opts = (
|
||||
[ 'allow_all', $text{'setup_allow_all'} . "<br>" ],
|
||||
[ 'deny_incoming', $text{'setup_deny_incoming'} . "<br>" ],
|
||||
[ 'deny_all', $text{'setup_deny_all'} ],
|
||||
);
|
||||
print ui_radio("type", "allow_all", \@type_opts);
|
||||
|
||||
print ui_form_end([ [ undef, $text{'setup_create'} ] ]);
|
||||
|
||||
sub create_allow_all_ruleset
|
||||
{
|
||||
my @tables;
|
||||
my $table = {
|
||||
'name' => 'inet_filter',
|
||||
'family' => 'inet',
|
||||
'rules' => [],
|
||||
'sets' => {},
|
||||
'chains' => {
|
||||
'input' => {
|
||||
'type' => 'filter',
|
||||
'hook' => 'input',
|
||||
'priority' => 0,
|
||||
'policy' => 'accept'
|
||||
},
|
||||
'forward' => {
|
||||
'type' => 'filter',
|
||||
'hook' => 'forward',
|
||||
'priority' => 0,
|
||||
'policy' => 'accept'
|
||||
},
|
||||
'output' => {
|
||||
'type' => 'filter',
|
||||
'hook' => 'output',
|
||||
'priority' => 0,
|
||||
'policy' => 'accept'
|
||||
}
|
||||
}
|
||||
};
|
||||
push(@tables, $table);
|
||||
return @tables;
|
||||
}
|
||||
|
||||
sub create_deny_incoming_ruleset
|
||||
{
|
||||
my @tables;
|
||||
my $webmin_port = get_webmin_port();
|
||||
my $table = {
|
||||
'name' => 'inet_filter',
|
||||
'family' => 'inet',
|
||||
'rules' => [
|
||||
{
|
||||
'text' => 'ct state established,related accept',
|
||||
'chain' => 'input'
|
||||
},
|
||||
{
|
||||
'text' => 'iif "lo" accept',
|
||||
'chain' => 'input'
|
||||
},
|
||||
{
|
||||
'text' => 'tcp dport 22 accept',
|
||||
'chain' => 'input'
|
||||
},
|
||||
{
|
||||
'text' => "tcp dport $webmin_port accept",
|
||||
'chain' => 'input'
|
||||
}
|
||||
],
|
||||
'sets' => {},
|
||||
'chains' => {
|
||||
'input' => {
|
||||
'type' => 'filter',
|
||||
'hook' => 'input',
|
||||
'priority' => 0,
|
||||
'policy' => 'drop'
|
||||
},
|
||||
'forward' => {
|
||||
'type' => 'filter',
|
||||
'hook' => 'forward',
|
||||
'priority' => 0,
|
||||
'policy' => 'accept'
|
||||
},
|
||||
'output' => {
|
||||
'type' => 'filter',
|
||||
'hook' => 'output',
|
||||
'priority' => 0,
|
||||
'policy' => 'accept'
|
||||
}
|
||||
}
|
||||
};
|
||||
push(@tables, $table);
|
||||
return @tables;
|
||||
}
|
||||
|
||||
sub create_deny_all_ruleset
|
||||
{
|
||||
my @tables;
|
||||
my $webmin_port = get_webmin_port();
|
||||
my $table = {
|
||||
'name' => 'inet_filter',
|
||||
'family' => 'inet',
|
||||
'rules' => [],
|
||||
'sets' => {},
|
||||
'chains' => {
|
||||
'input' => {
|
||||
'type' => 'filter',
|
||||
'hook' => 'input',
|
||||
'priority' => 0,
|
||||
'policy' => 'drop'
|
||||
},
|
||||
'forward' => {
|
||||
'type' => 'filter',
|
||||
'hook' => 'forward',
|
||||
'priority' => 0,
|
||||
'policy' => 'drop'
|
||||
},
|
||||
'output' => {
|
||||
'type' => 'filter',
|
||||
'hook' => 'output',
|
||||
'priority' => 0,
|
||||
'policy' => 'drop'
|
||||
}
|
||||
}
|
||||
};
|
||||
$table->{'rules'} = [
|
||||
{
|
||||
'text' => 'ct state established,related accept',
|
||||
'chain' => 'output'
|
||||
},
|
||||
{
|
||||
'text' => 'iif "lo" accept',
|
||||
'chain' => 'input'
|
||||
},
|
||||
{
|
||||
'text' => 'oif "lo" accept',
|
||||
'chain' => 'output'
|
||||
},
|
||||
{
|
||||
'text' => 'tcp dport 22 accept',
|
||||
'chain' => 'input'
|
||||
},
|
||||
{
|
||||
'text' => "tcp dport $webmin_port accept",
|
||||
'chain' => 'input'
|
||||
}
|
||||
];
|
||||
push(@tables, $table);
|
||||
return @tables;
|
||||
}
|
||||
|
||||
ui_print_footer("/", $text{'index'});
|
||||
61
nftables/t/perlcritic.t
Normal file
61
nftables/t/perlcritic.t
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use Test::More;
|
||||
|
||||
BEGIN {
|
||||
eval { require Perl::Critic; 1 }
|
||||
or plan skip_all => 'Perl::Critic not installed';
|
||||
}
|
||||
|
||||
use File::Find;
|
||||
|
||||
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 $module_dir = "$bindir/..";
|
||||
chdir($module_dir) or die "chdir: $!";
|
||||
|
||||
my @files;
|
||||
find(
|
||||
sub {
|
||||
return if -d;
|
||||
return unless /\.(pl|cgi)\z/;
|
||||
push(@files, $File::Find::name);
|
||||
},
|
||||
'.'
|
||||
);
|
||||
|
||||
@files = sort @files;
|
||||
if (!@files) {
|
||||
plan skip_all => 'no perl files to check';
|
||||
}
|
||||
|
||||
my $critic = Perl::Critic->new(
|
||||
-severity => 5,
|
||||
-profile => '',
|
||||
);
|
||||
|
||||
foreach my $file (@files) {
|
||||
my @violations = $critic->critique($file);
|
||||
is(scalar @violations, 0, "$file perlcritic");
|
||||
if (@violations) {
|
||||
diag join("", @violations);
|
||||
}
|
||||
}
|
||||
|
||||
done_testing();
|
||||
8
nftables/t/rulesets/basic.nft
Normal file
8
nftables/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
|
||||
}
|
||||
}
|
||||
18
nftables/t/rulesets/sets.nft
Normal file
18
nftables/t/rulesets/sets.nft
Normal file
@@ -0,0 +1,18 @@
|
||||
table inet filter {
|
||||
set trusted_v4 {
|
||||
type ipv4_addr;
|
||||
flags interval;
|
||||
elements = { 192.168.1.0/24, 10.0.0.1 }
|
||||
}
|
||||
set web_ports {
|
||||
type inet_service;
|
||||
elements = {
|
||||
80,
|
||||
443
|
||||
}
|
||||
}
|
||||
chain input {
|
||||
type filter hook input priority 0; policy drop;
|
||||
ip saddr @trusted_v4 tcp dport @web_ports accept
|
||||
}
|
||||
}
|
||||
185
nftables/t/run-tests.t
Executable file
185
nftables/t/run-tests.t
Executable file
@@ -0,0 +1,185 @@
|
||||
#!/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' });
|
||||
|
||||
my $ruleset_sets = "$bindir/rulesets/sets.nft";
|
||||
my @tables_sets = get_nftables_save($ruleset_sets);
|
||||
ok(@tables_sets == 1, 'sets ruleset table count');
|
||||
my $ts = $tables_sets[0];
|
||||
ok($ts->{sets} && $ts->{sets}->{trusted_v4}, 'trusted_v4 set present');
|
||||
is($ts->{sets}->{trusted_v4}->{type}, 'ipv4_addr', 'trusted_v4 type');
|
||||
is($ts->{sets}->{trusted_v4}->{flags}, 'interval', 'trusted_v4 flags');
|
||||
is_deeply($ts->{sets}->{trusted_v4}->{elements},
|
||||
[ '192.168.1.0/24', '10.0.0.1' ],
|
||||
'trusted_v4 elements');
|
||||
ok($ts->{sets}->{web_ports}, 'web_ports set present');
|
||||
is($ts->{sets}->{web_ports}->{type}, 'inet_service', 'web_ports type');
|
||||
is_deeply($ts->{sets}->{web_ports}->{elements},
|
||||
[ '80', '443' ],
|
||||
'web_ports elements');
|
||||
|
||||
my $rset = $ts->{rules}->[0];
|
||||
check_fields('set rule', $rset,
|
||||
{ saddr => '@trusted_v4', proto => 'tcp', dport => '@web_ports', action => 'accept' });
|
||||
my $rset_out = format_rule_text($rset);
|
||||
like($rset_out, qr/\@trusted_v4/, 'set rule format preserves address set');
|
||||
like($rset_out, qr/\@web_ports/, 'set rule format preserves port set');
|
||||
|
||||
ok(validate_chain_base('filter', 'input', '0', 'accept'),
|
||||
'chain base allows zero priority');
|
||||
ok(!validate_chain_base('filter', 'input', undef, 'accept'),
|
||||
'chain base missing priority invalid');
|
||||
ok(validate_chain_base(undef, undef, undef, undef),
|
||||
'chain base none set valid');
|
||||
|
||||
my $table_move = {
|
||||
rules => [
|
||||
{ chain => 'input', index => 0, text => 'r0' },
|
||||
{ chain => 'input', index => 1, text => 'r1' },
|
||||
{ chain => 'forward', index => 2, text => 'r2' },
|
||||
{ chain => 'input', index => 3, text => 'r3' },
|
||||
],
|
||||
};
|
||||
ok(move_rule_in_chain($table_move, 'input', 1, 'down'),
|
||||
'move rule down returns true');
|
||||
is($table_move->{rules}->[1]->{text}, 'r3', 'rule moved down in array');
|
||||
is($table_move->{rules}->[3]->{text}, 'r1', 'rule swapped down in array');
|
||||
is($table_move->{rules}->[1]->{index}, 1, 'moved rule index updated');
|
||||
is($table_move->{rules}->[3]->{index}, 3, 'swapped rule index updated');
|
||||
|
||||
my $table_move2 = {
|
||||
rules => [
|
||||
{ chain => 'input', index => 0, text => 'r0' },
|
||||
{ chain => 'input', index => 1, text => 'r1' },
|
||||
],
|
||||
};
|
||||
is(move_rule_in_chain($table_move2, 'input', 0, 'up'), 0,
|
||||
'top rule cannot move up');
|
||||
|
||||
done_testing();
|
||||
Reference in New Issue
Block a user