From bbee6887627ac32f38cbba5bd8aae2bf18629c1a Mon Sep 17 00:00:00 2001 From: Carsten Beckmann Date: Tue, 23 Jan 2024 11:58:48 +0100 Subject: [PATCH 1/2] Add support for negated addresses Static addresses can now be negated by prefixing them with the '!' operator. Negated addresses will be loaded into the configured pf tables immediately when pfresolved starts and will prevent the pf tables from matching these addresses. If a host resolves to such an address it will just reference the negated address internally, preventing the resolved address from being added to the pf table in the non-negated form. --- Changes | 1 + parse.y | 36 +++++++++++++++++++++++++++++++----- pfresolved.conf.5 | 6 +++++- pfresolved.h | 1 + pftable.c | 1 + 5 files changed, 39 insertions(+), 6 deletions(-) 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++; } From c60c212d5f669925ce65529d7bdea087a4d93a32 Mon Sep 17 00:00:00 2001 From: Carsten Beckmann Date: Tue, 23 Jan 2024 13:40:20 +0100 Subject: [PATCH 2/2] Add tests for negated addresses Test that negated addresses are added into pf tables and that hosts resolving to these addresses won't have their addresses added to the table. Test that networks cannot be negated. Test that the same address cannot be specified in normal and negated form. --- regress/Proc.pm | 4 +- regress/args-negated-address.pl | 44 ++++++++++++++++++++++ regress/args-negated-network.pl | 22 +++++++++++ regress/args-normal-and-negated-address.pl | 23 +++++++++++ 4 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 regress/args-negated-address.pl create mode 100644 regress/args-negated-network.pl create mode 100644 regress/args-normal-and-negated-address.pl 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;