diff --git a/nftables/edit_rule.cgi b/nftables/edit_rule.cgi
index 5fb0688a7..f531a2964 100755
--- a/nftables/edit_rule.cgi
+++ b/nftables/edit_rule.cgi
@@ -104,11 +104,11 @@ if ($table && $table->{'sets'} && ref($table->{'sets'}) eq 'HASH') {
my $label = $s;
$label .= " ($set->{'type'})" if ($set->{'type'});
my $kind = set_type_kind($set->{'type'});
- if (!$kind || $kind eq 'addr') {
+ if ($kind && $kind eq 'addr') {
push(@addr_set_opts, [ $s, $label ]);
$addr_set_seen{$s} = 1;
}
- if (!$kind || $kind eq 'port') {
+ if ($kind && $kind eq 'port') {
push(@port_set_opts, [ $s, $label ]);
$port_set_seen{$s} = 1;
}
@@ -234,6 +234,7 @@ if (@port_set_opts > 1) {
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_row(undef, ui_note($text{'edit_ports_note'}, 0), 2);
print ui_table_end();
diff --git a/nftables/help/dport.html b/nftables/help/dport.html
index 45089e007..7bbbd3a3e 100644
--- a/nftables/help/dport.html
+++ b/nftables/help/dport.html
@@ -1,3 +1,3 @@
TCP/UDP destination port number or range (e.g., 80 or 1000-2000).
+TCP/UDP destination port number or range on the local service being reached (e.g., 22, 80 or 1000-2000).
diff --git a/nftables/help/sport.html b/nftables/help/sport.html index ee9c8730e..01d842532 100644 --- a/nftables/help/sport.html +++ b/nftables/help/sport.html @@ -1,3 +1,3 @@TCP/UDP source port number or range (e.g., 22 or 1000-2000).
+TCP/UDP source port number or range on the remote side of the connection (e.g., 22 or 1000-2000).
diff --git a/nftables/lang/en b/nftables/lang/en index ec8058de1..4017f373a 100644 --- a/nftables/lang/en +++ b/nftables/lang/en @@ -69,6 +69,7 @@ save_err=Failed to save rule apply_err=Failed to apply configuration apply_enone=No saved nftables tables were found to apply. apply_eexternal=Cannot apply configuration because table $1 is currently marked as externally managed. +apply_esettype=Set $1 in table $2 has type $3, but chain $4 uses it for $5. Use ipv4_addr or ipv6_addr sets for address fields, and inet_service sets for port fields. setup_title=Create Ruleset Profile setup_header=Ruleset profile setup_err=Failed to create ruleset profile @@ -170,13 +171,14 @@ edit_proto=Protocol edit_proto_any=Any edit_saddr=Source address edit_daddr=Destination address -edit_sport=Source Port -edit_dport=Destination Port +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_ports_note=For inbound services such as SSH, match the destination port. Rules are evaluated from top to bottom, so a later drop will not override an earlier accept. edit_icmp_type=ICMP/ICMPv6 type edit_ct_state=Conntrack state edit_tcp_flags=TCP flags @@ -248,6 +250,7 @@ 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. +save_set_type=Selected set $1 has type $2, which cannot be used for $3. move_err=Failed to move rule move_failed=Failed to move rule:$1move_notable=No such table selected diff --git a/nftables/nftables-lib.pl b/nftables/nftables-lib.pl index cc4d145ef..ec2b6fa89 100644 --- a/nftables/nftables-lib.pl +++ b/nftables/nftables-lib.pl @@ -958,6 +958,39 @@ foreach my $r (@{$table->{'rules'}}) { return $count; } +# validate_set_references(&table) +# Returns an error if any structured rule uses a set in an incompatible field +sub validate_set_references +{ +my ($table) = @_; +return if (!$table || ref($table) ne 'HASH'); +return if (!$table->{'sets'} || ref($table->{'sets'}) ne 'HASH'); +return if (!$table->{'rules'} || ref($table->{'rules'}) ne 'ARRAY'); +foreach my $r (@{$table->{'rules'}}) { + next if (!$r || ref($r) ne 'HASH'); + foreach my $check ( + [ 'saddr', 'addr', text('edit_saddr') ], + [ 'daddr', 'addr', text('edit_daddr') ], + [ 'sport', 'port', text('edit_sport') ], + [ 'dport', 'port', text('edit_dport') ], + ) { + my ($field, $want, $label) = @$check; + my $setname = set_name_from_value($r->{$field}); + next if (!$setname); + my $set = $table->{'sets'}->{$setname}; + next if (!$set); + my $kind = set_type_kind($set->{'type'}); + if (!$kind || $kind ne $want) { + my $type = $set->{'type'} || text('set_type_select'); + return text('apply_esettype', $setname, + nft_table_spec($table), $type, + $r->{'chain'} || "-", $label); + } + } + } +return; +} + # dump_nftables_save(@tables) # Returns a string representation of the firewall rules @@ -1097,6 +1130,8 @@ foreach my $t (@$active) { $active{table_key($t)} = $t; } foreach my $t (@tables) { + my $set_err = validate_set_references($t); + return $set_err if ($set_err); my $active_table = $active{table_key($t)}; if ($active_table && table_is_externally_managed($active_table)) { return text('apply_eexternal', nft_table_spec($t)); diff --git a/nftables/save_rule.cgi b/nftables/save_rule.cgi index 7efc803e2..cb2e0461e 100755 --- a/nftables/save_rule.cgi +++ b/nftables/save_rule.cgi @@ -17,6 +17,21 @@ foreach my $sfield (qw(saddr_set daddr_set sport_set dport_set)) { error(text('save_set_missing', $in{$sfield})); } } +foreach my $check ( + [ 'saddr_set', 'addr', $text{'edit_saddr'} ], + [ 'daddr_set', 'addr', $text{'edit_daddr'} ], + [ 'sport_set', 'port', $text{'edit_sport'} ], + [ 'dport_set', 'port', $text{'edit_dport'} ], + ) { + my ($sfield, $want, $label) = @$check; + next if (!$in{$sfield}); + my $set = $table->{'sets'}->{$in{$sfield}}; + my $kind = set_type_kind($set->{'type'}); + if (!$kind || $kind ne $want) { + my $type = $set->{'type'} || $text{'set_type_select'}; + error(text('save_set_type', $in{$sfield}, $type, $label)); + } +} sub join_multi_value { diff --git a/nftables/save_set.cgi b/nftables/save_set.cgi index fe3649988..ff5f9267e 100755 --- a/nftables/save_set.cgi +++ b/nftables/save_set.cgi @@ -54,6 +54,9 @@ $set->{'raw_lines'} ||= [ ]; $table->{'sets'}->{$name} = $set; +my $set_err = validate_set_references($table); +error($set_err) if ($set_err); + my $err = save_table_configuration($table, @tables); error(text('set_failed', $err)) if ($err);