diff --git a/Changes b/Changes index 2a449b7..5c50ad0 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,7 @@ Revision history for pfresolved pf table DNS update daemon 1.01 + * Add support for negated addresses. 1.00 2023-11-24 * Initial public release. diff --git a/parse.y b/parse.y index 5f7df85..bb2f082 100644 --- a/parse.y +++ b/parse.y @@ -85,7 +85,7 @@ static struct pfresolved_table *cur_table = NULL; int add_table_value(struct pfresolved_table *, const char *); int add_static_address(struct pfresolved_table *, - const char *); + const char *, int); int add_host(struct pfresolved_table *, const char *); struct pfresolved_table *table_lookup_or_create(const char *); @@ -166,6 +166,15 @@ table_values : /* empty */ } free($2); } + | table_values '!' table_value optcomma optnl + { + if (add_static_address(cur_table, $3, 1) == -1) { + yyerror("add_static_address failed"); + free($3); + YYERROR; + } + free($3); + } ; table_name : STRING @@ -728,7 +737,7 @@ add_table_value(struct pfresolved_table *table, const char *value) if (!table) return (-1); - if (add_static_address(table, value) == -1 && + if (add_static_address(table, value, 0) == -1 && add_host(table, value) == -1) return (-1); @@ -736,7 +745,7 @@ add_table_value(struct pfresolved_table *table, const char *value) } int -add_static_address(struct pfresolved_table *table, const char *value) +add_static_address(struct pfresolved_table *table, const char *value, int negate) { struct pfresolved_table_entry *entry, *old; struct in_addr in4; @@ -750,12 +759,22 @@ add_static_address(struct pfresolved_table *table, const char *value) fatal("%s: calloc", __func__); if ((bits = inet_net_pton(AF_INET, value, &in4, sizeof(in4))) != -1) { + if (negate && bits != 32) { + yyerror("negation is not allowed for networks"); + free(entry); + return (-1); + } applymask4(&in4, bits); entry->pfte_addr.pfa_af = AF_INET; entry->pfte_addr.pfa_addr.in4 = in4; entry->pfte_addr.pfa_prefixlen = bits; } else if ((bits = inet_net_pton(AF_INET6, value, &in6, sizeof(in6))) != -1) { + if (negate && bits != 128) { + yyerror("negation is not allowed for networks"); + free(entry); + return (-1); + } applymask6(&in6, bits); entry->pfte_addr.pfa_af = AF_INET6; entry->pfte_addr.pfa_addr.in6 = in6; @@ -766,12 +785,19 @@ add_static_address(struct pfresolved_table *table, const char *value) } entry->pfte_static = 1; + entry->pfte_negate = negate; old = RB_INSERT(pfresolved_table_entries, &table->pft_entries, entry); if (old) { free(entry); - log_warn("duplicate entry in config: %s %s", table->pft_name, - value); + if (old->pfte_negate != negate) { + yyerror("the same address cannot be specified in normal" + " and negated form"); + return (-1); + } else { + log_warn("duplicate entry in config: %s %s", + table->pft_name, value); + } } return (0); } diff --git a/pfresolved.conf.5 b/pfresolved.conf.5 index bcee60a..b57835a 100644 --- a/pfresolved.conf.5 +++ b/pfresolved.conf.5 @@ -86,9 +86,12 @@ it will be created by A list of hostnames that should be resolved by .Xr pfresolved 8 for the specified table. -The list can also contain IP addresses. +The list can also contain IP addresses and networks. These will be directly added to the table when the configuration file is loaded. +IP addresses can also be negated by prefixing them with the +.Cm !\& +operator. Entries in the list may be separated by comma or newline. .El .Sh EXAMPLES @@ -99,6 +102,7 @@ myTable2 { example.net example.org 198.51.100.0 + ! 198.51.100.1 include "/list/with/hosts" } .Ed diff --git a/pfresolved.h b/pfresolved.h index e755a24..d8ffccd 100644 --- a/pfresolved.h +++ b/pfresolved.h @@ -181,6 +181,7 @@ struct pfresolved_address { struct pfresolved_table_entry { struct pfresolved_address pfte_addr; int pfte_static; + int pfte_negate; int pfte_refcount; RB_ENTRY(pfresolved_table_entry) pfte_node; }; diff --git a/pftable.c b/pftable.c index 384df57..bab2c24 100644 --- a/pftable.c +++ b/pftable.c @@ -51,6 +51,7 @@ pftable_set_addresses(struct pfresolved *env, struct pfresolved_table *table) buffer[count].pfra_ip6addr = entry->pfte_addr.pfa_addr.in6; } buffer[count].pfra_net = entry->pfte_addr.pfa_prefixlen; + buffer[count].pfra_not = entry->pfte_negate; count++; } diff --git a/regress/Proc.pm b/regress/Proc.pm index eb936cd..34a9f28 100644 --- a/regress/Proc.pm +++ b/regress/Proc.pm @@ -146,9 +146,11 @@ sub loggrep { my $end; $end = time() + $timeout if $timeout; + my $expected_status = $self->{expected_status} || 0; + do { my($kid, $status, $code) = $self->wait(WNOHANG); - if ($kid > 0 && $status != 0) { + if ($kid > 0 && $status != $expected_status << 8) { # child terminated with failure die ref($self), " child status: $status $code"; } diff --git a/regress/args-negated-address.pl b/regress/args-negated-address.pl new file mode 100644 index 0000000..d8e0e1a --- /dev/null +++ b/regress/args-negated-address.pl @@ -0,0 +1,44 @@ +# Create zone file with A and AAAA records in zone regress. +# Start nsd with zone file listening on 127.0.0.1. +# Write hosts of regress zone into pfresolved config. +# Write negated addresses for hosts in regress zone into pfresolved config. +# Start pfresolved with nsd as resolver. +# Wait until pfresolved creates table regress-pfresolved. +# Read IP addresses from pf table with pfctl. +# Check that pfresolved resolved IPv4 and IPv6 addresses. +# Check that pf table only contains the negated IPv4 and IPv6 addresses. + +use strict; +use warnings; +use Socket; + +our %args = ( + nsd => { + record_list => [ + "foo IN A 192.0.2.1", + "foo IN AAAA 2001:DB8::1", + ], + }, + pfresolved => { + address_list => [ + "foo.regress.", + "! 192.0.2.1", + "! 2001:DB8::1", + ], + loggrep => { + qr{added: 192.0.2.1/32,} => 1, + qr{added: 2001:db8::1/128,} => 1, + }, + }, + pfctl => { + updated => [2, 0], + loggrep => { + qr/^ !192.0.2.1$/ => 1, + qr/^ 192.0.2.1$/ => 0, + qr/^ !2001:db8::1$/ => 1, + qr/^ 2001:db8::1$/ => 0, + }, + }, +); + +1; diff --git a/regress/args-negated-network.pl b/regress/args-negated-network.pl new file mode 100644 index 0000000..89cd0d7 --- /dev/null +++ b/regress/args-negated-network.pl @@ -0,0 +1,22 @@ +# Write negated network into pfresolved config. +# Start pfresolved. +# Check that configuration parsing fails. + +use strict; +use warnings; +use Socket; + +our %args = ( + pfresolved => { + address_list => [ + "! 192.0.2.1/24", + ], + loggrep => { + qr{negation is not allowed for networks} => 1, + }, + expected_status => 1, + down => "parent: parsing configuration failed", + }, +); + +1; diff --git a/regress/args-normal-and-negated-address.pl b/regress/args-normal-and-negated-address.pl new file mode 100644 index 0000000..ffbcb99 --- /dev/null +++ b/regress/args-normal-and-negated-address.pl @@ -0,0 +1,23 @@ +# Write the same address in normal and negated form into pfresolved config. +# Start pfresolved. +# Check that configuration parsing fails. + +use strict; +use warnings; +use Socket; + +our %args = ( + pfresolved => { + address_list => [ + "192.0.2.1", + "! 192.0.2.1", + ], + loggrep => { + qr{the same address cannot be specified in normal and negated form} => 1, + }, + expected_status => 1, + down => "parent: parsing configuration failed", + }, +); + +1;