Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9661b03
Refactored ipset to support per-rule configuration
EngineersBox Oct 22, 2024
eb349ad
Refactored ipset to support per-rule configuration
EngineersBox Oct 22, 2024
45f9bbe
Merged in upstream changes
EngineersBox Oct 22, 2024
5008820
Merged in upstream changes
EngineersBox Oct 22, 2024
ba17288
Added checkconf warning for ipset with TTL when compiled with BSD pf
EngineersBox Oct 22, 2024
c12ee39
Supported backwards compat with global ipsets
Oct 25, 2024
93ff2a5
Supported backwards compat with global ipsets
Oct 25, 2024
a1e3eb7
Supported backwards compat with global ipsets
Oct 25, 2024
6209e2d
Supported backwards compat with global ipsets
Oct 25, 2024
fd6740b
Removed unused lexer token return
Oct 25, 2024
124c612
Removed TODO comment
Oct 25, 2024
70c252e
Fixed memory leak and missing lexer tokens
EngineersBox Oct 25, 2024
d35a4d7
Fixed memory leak and missing lexer tokens
EngineersBox Oct 25, 2024
c1879ae
Fixed BSD warning flag not updated to true
EngineersBox Oct 25, 2024
89dedee
Added tests
EngineersBox Oct 28, 2024
11c7cab
Added tests
EngineersBox Oct 28, 2024
3e037da
Updated docs
EngineersBox Oct 28, 2024
d415337
Updated docs
EngineersBox Oct 28, 2024
1b39c41
Fixed timeouts
Nov 4, 2024
9b209ae
Merge branch 'master' of https://github.com/EngineersBox/unbound
Nov 4, 2024
839b7ab
Ensured creation with no TTL and no replacement enacted
Nov 4, 2024
1a94857
Ensured creation with no TTL and no replacement enacted
Nov 4, 2024
1c6c67c
Ensured creation with no TTL and no replacement enacted
Nov 4, 2024
deb6ec8
Added conversion from host byte order to net byte order
Nov 4, 2024
4c27eea
Merged in remote master
May 28, 2025
fc5074d
Fixed documentation
May 29, 2025
a5376f3
Fixed documentation
May 29, 2025
8b69e0f
Fixed documentation
May 29, 2025
0285652
Merge branch 'NLnetLabs:master' into master
EngineersBox Sep 13, 2025
611e822
Fixed daemon/remote retrieval of local_zones_ipsets using strlist ins…
EngineersBox Sep 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions daemon/remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -5153,7 +5153,7 @@ getmem_config_view(struct config_view* p)
+ getmem_config_strlist(s->local_data)
+ getmem_config_strlist(s->local_zones_nodefault)
#ifdef USE_IPSET
+ getmem_config_strlist(s->local_zones_ipset)
+ getmem_config_str4list(s->local_zones_ipset)
#endif
+ getmem_config_str2list(s->respip_actions)
+ getmem_config_str2list(s->respip_data);
Expand Down Expand Up @@ -5217,7 +5217,7 @@ config_file_getmem(struct config_file* cfg)
m += getmem_config_str2list(cfg->local_zones);
m += getmem_config_strlist(cfg->local_zones_nodefault);
#ifdef USE_IPSET
m += getmem_config_strlist(cfg->local_zones_ipset);
m += getmem_config_str4list(cfg->local_zones_ipset);
#endif
m += getmem_config_strlist(cfg->local_data);
m += getmem_config_str3list(cfg->local_zone_overrides);
Expand Down
49 changes: 49 additions & 0 deletions doc/unbound.conf.5.in
Original file line number Diff line number Diff line change
Expand Up @@ -2324,6 +2324,11 @@ queries.
Most modules that need to be listed here have to be listed at the beginning
of the line.
.sp
When the server is built with ipset support and has is run with the \fICAP_NET_ADMIN\fR
capability, the \fBipset\fP module can be utilised. Example configuration for this is
"\fIipset iterator\fR". It can easily be combined with any other module without
any issues.
.sp
The \fBsubnetcache\fP module has to be listed just before the iterator.
.sp
The \fBpython\fP module can be listed in different places, it then processes
Expand Down Expand Up @@ -2998,6 +3003,50 @@ answered from global local zone contents.
.UNINDENT
.INDENT 7.0
.TP
.B ipset
Used to specify an ipset to insert resolved addresses into. If the deprecated
global \fIipset\fR block is used, then it can be referenced using the form:
.INDENT 7.0
.INDENT 3.5
.sp
.nf
.ft C
server:
lcoal\-zone: "example.com." ipset
ipset:
name\-v4: <ipset name>
.ft P
.fi
.UNINDENT
.UNINDENT
.sp
However, per-rule declarations are also supported where delineation of
addresses is required. This is done via the form:
.INDENT 7.0
.INDENT 3.5
.sp
.nf
.ft C
local\-zone: "example.net." ipset <protocol> <ipset name> <tll | no\-ttl>
.ft P
.fi
.UNINDENT
.UNINDENT
.sp
Here you can specify the \fIprotocol\fR as \fIipv4\fR or \fIipv6\fR, the
name of the ipset and finally whether to use the DNS record TTL as an
auto-expiry on the inserted ipset entry. When declaring a local-zone with
TTL support, the associated ipset must be created with \fIIPSET_EXT_TIMEOUT\fR,
or with the \fIipset\fR CLI, using the \fItimeout <seconds>\fR option.
Note that on a BSD distribution, where the server is compiled with the packet
filter framework, there is no support for TTLs to be set on individual table
entries. The only support that pf provides is invoking manual expiry of table
entries past a delta of \fIn\fR seconds via the \fIpfctl -t <table> -T expire <seconds>\fR
flag (See \fIpftcl\fR(8)). Thus no support is added there for TTLs and a suitable
warning is raised from the parser when the config is checked.
.UNINDENT
.INDENT 7.0
.TP
.B nodefault
Used to turn off default contents for AS112 zones.
The other types also turn off default contents for the zone.
Expand Down
168 changes: 140 additions & 28 deletions ipset/ipset.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
#include "sldns/sbuffer.h"
#include "sldns/wire2str.h"
#include "sldns/parseutil.h"
#include <string.h>
#include <errno.h>
#include <time.h>

#ifdef HAVE_NET_PFVAR_H
#include <fcntl.h>
Expand Down Expand Up @@ -83,7 +86,8 @@ static void * open_filter() {
#endif

#ifdef HAVE_NET_PFVAR_H
static int add_to_ipset(filter_dev dev, const char *setname, const void *ipaddr, int af) {
static int add_to_ipset(filter_dev dev, const char *setname, const void *ipaddr,
int af, time_t _ttl) {
struct pfioc_table io;
struct pfr_addr addr;
const char *p;
Expand Down Expand Up @@ -139,10 +143,15 @@ static int add_to_ipset(filter_dev dev, const char *setname, const void *ipaddr,
return 0;
}
#else
static int add_to_ipset(filter_dev dev, const char *setname, const void *ipaddr, int af) {
static int add_to_ipset(filter_dev dev, const char *setname, const void *ipaddr,
int af, time_t ttl) {
int result;
int seq;
unsigned int port_id;
struct nlmsghdr *nlh;
struct nfgenmsg *nfg;
struct nlattr *nested[2];
char* recv_buffer;
static char buffer[BUFF_LEN];

if (strlen(setname) >= IPSET_MAXNAMELEN) {
Expand All @@ -154,9 +163,18 @@ static int add_to_ipset(filter_dev dev, const char *setname, const void *ipaddr,
return -1;
}

const bool set_ttl = ttl >= 0;
nlh = mnl_nlmsg_put_header(buffer);
nlh->nlmsg_type = IPSET_CMD_ADD | (NFNL_SUBSYS_IPSET << 8);
nlh->nlmsg_flags = NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
if (set_ttl) {
// Replace if we a TTL to extend the entry time
nlh->nlmsg_flags |= NLM_F_REPLACE | NLM_F_CREATE;
} else {
// Don't replace if we have no TTL since entry doesn't expire
nlh->nlmsg_flags |= NLM_F_EXCL;
}
nlh->nlmsg_seq = seq = time(NULL);

nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg));
nfg->nfgen_family = af;
Expand All @@ -170,42 +188,104 @@ static int add_to_ipset(filter_dev dev, const char *setname, const void *ipaddr,
mnl_attr_put(nlh, (af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6)
| NLA_F_NET_BYTEORDER, (af == AF_INET ? sizeof(struct in_addr) : sizeof(struct in6_addr)), ipaddr);
mnl_attr_nest_end(nlh, nested[1]);
if (set_ttl) {
// Netlink packets are packed based on a pointer and data size
// to memcpy into an appropriately sized buffer within the packet
// data section. Thus we need to ensure that the TTL is in a u32
// sized variable, otherwise we would end up copying the upper
// 32 bits of a 64 bit integer.
const uint32_t entry_ttl = (uint32_t) ttl > UINT32_MAX ? UINT32_MAX : ttl;
mnl_attr_put_u32(
nlh,
IPSET_ATTR_TIMEOUT | NLA_F_NET_BYTEORDER,
// Expecting net byte order, we should convert from host order
// into net byte order
htonl(entry_ttl)
);
}
mnl_attr_nest_end(nlh, nested[0]);

if (mnl_socket_sendto(dev, nlh, nlh->nlmsg_len) < 0) {
if ((result = mnl_socket_sendto(dev, nlh, nlh->nlmsg_len)) < 0) {
log_err("ipset: failed to send netlink packet: %s", strerror(errno));
return -1;
}
port_id = mnl_socket_get_portid(dev);
recv_buffer = (char*) calloc(MNL_SOCKET_BUFFER_SIZE, sizeof(char));
if (!recv_buffer) {
log_err("ipset: failed to allocate receive buffer");
return -1;
}
do {
result = mnl_socket_recvfrom(dev, recv_buffer, MNL_SOCKET_BUFFER_SIZE);
if (result < 0) {
log_err("ipset: failed to ACK netlink request: %s", strerror(errno));
free(recv_buffer);
return -1;
}
result = mnl_cb_run(recv_buffer, result, seq, port_id, NULL, NULL);
if (!set_ttl && errno == IPSET_ERR_EXIST) {
// If we have no TTL, then we don't replace entries.
// This error indicates we already have an entry, so we
// can ignore it and move on.
break;
}
if (result < 0) {
log_err("ipset: netlink response had error: %s", strerror(errno));
free(recv_buffer);
return -1;
} else if (result == 0) {
break;
}
} while (result > 0);
free(recv_buffer);
return 0;
}
#endif

static void
ipset_add_rrset_data(struct ipset_env *ie,
struct packed_rrset_data *d, const char* setname, int af,
const char* dname)
const char* dname, bool set_ttl)
{
int ret;
size_t j, rr_len, rd_len;
time_t rr_ttl;
uint8_t *rr_data;

/* to d->count, not d->rrsig_count, because we do not want to add the RRSIGs, only the addresses */
for (j = 0; j < d->count; j++) {
rr_len = d->rr_len[j];
rr_data = d->rr_data[j];
rr_ttl = d->rr_ttl[j];

rd_len = sldns_read_uint16(rr_data);
if(af == AF_INET && rd_len != INET_SIZE)
continue;
if(af == AF_INET6 && rd_len != INET6_SIZE)
continue;
if (!set_ttl) {
rr_ttl = -1;
}
if (rr_len - 2 >= rd_len) {
if(verbosity >= VERB_QUERY) {
char ip[128];
if(inet_ntop(af, rr_data+2, ip, (socklen_t)sizeof(ip)) == 0)
snprintf(ip, sizeof(ip), "(inet_ntop_error)");
verbose(VERB_QUERY, "ipset: add %s to %s for %s", ip, setname, dname);
if (set_ttl) {
verbose(
VERB_QUERY,
"ipset: add %s to %s for %s with ttl %lds",
ip, setname, dname, rr_ttl
);
} else {
verbose(
VERB_QUERY,
"ipset: add %s to %s for %s",
ip, setname, dname
);
}
}
ret = add_to_ipset((filter_dev)ie->dev, setname, rr_data + 2, af);
ret = add_to_ipset((filter_dev)ie->dev, setname, rr_data + 2, af, rr_ttl);
if (ret < 0) {
log_err("ipset: could not add %s into %s", dname, setname);

Expand All @@ -224,13 +304,13 @@ ipset_add_rrset_data(struct ipset_env *ie,
static int
ipset_check_zones_for_rrset(struct module_env *env, struct ipset_env *ie,
struct ub_packed_rrset_key *rrset, const char *qname, int qlen,
const char *setname, int af)
int af)
{
static char dname[BUFF_LEN];
const char *ds, *qs;
int dlen, plen;

struct config_strlist *p;
struct config_str4list *p;
struct packed_rrset_data *d;

dlen = sldns_wire2str_dname_buf(rrset->rk.dname, rrset->rk.dname_len, dname, BUFF_LEN);
Expand All @@ -252,20 +332,29 @@ ipset_check_zones_for_rrset(struct module_env *env, struct ipset_env *ie,
if (p->str[plen - 1] == '.') {
plen--;
}

int set_af;
if (strncasecmp(p->str2, "ipv4", 4) == 0) {
set_af = AF_INET;
} else if (strncasecmp(p->str2, "ipv6", 4) == 0) {
set_af = AF_INET6;
} else {
continue;
}
if (dlen == plen || (dlen > plen && dname[dlen - plen - 1] == '.' )) {
ds = dname + (dlen - plen);
}
if (qlen == plen || (qlen > plen && qname[qlen - plen - 1] == '.' )) {
qs = qname + (qlen - plen);
}
if ((ds && strncasecmp(p->str, ds, plen) == 0)
|| (qs && strncasecmp(p->str, qs, plen) == 0)) {
if (((ds && strncasecmp(p->str, ds, plen) == 0)
|| (qs && strncasecmp(p->str, qs, plen) == 0))
&& set_af == af) {
d = (struct packed_rrset_data*)rrset->entry.data;
ipset_add_rrset_data(ie, d, setname, af, dname);
bool set_ttl = strncasecmp(p->str4, "ttl", 3) == 0;
ipset_add_rrset_data(ie, d, p->str3, af, dname, set_ttl);
break;
}
}
}
return 0;
}

Expand Down Expand Up @@ -299,23 +388,16 @@ static int ipset_update(struct module_env *env, struct dns_msg *return_msg,
}

for(i = 0; i < return_msg->rep->rrset_count; i++) {
setname = NULL;
rrset = return_msg->rep->rrsets[i];
if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_A &&
ie->v4_enabled == 1) {
if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_A) {
af = AF_INET;
setname = ie->name_v4;
} else if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_AAAA &&
ie->v6_enabled == 1) {
} else if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_AAAA) {
af = AF_INET6;
setname = ie->name_v6;
}

if (setname) {
if(ipset_check_zones_for_rrset(env, ie, rrset, qname,
qlen, setname, af) == -1)
return -1;
}
if(ipset_check_zones_for_rrset(env, ie, rrset, qname,
qlen, af) == -1)
return -1;
}

return 0;
Expand Down Expand Up @@ -367,6 +449,35 @@ void ipset_destartup(struct module_env* env, int id) {
env->modinfo[id] = NULL;
}

int convert_global_ipset(struct module_env* env, struct ipset_env* ipset_env) {
struct config_str4list *p;
for (p = env->cfg->local_zones_ipset; p; p = p->next) {
if (strncmp(p->str3, "@global@", 8) != 0) {
continue;
}
if (ipset_env->v4_enabled) {
p->str2 = strdup("ipv4");
p->str3 = strdup(ipset_env->name_v4);
} else if (ipset_env->v6_enabled) {
p->str2 = strdup("ipv6");
p->str3 = strdup(ipset_env->name_v6);
continue;
}
if (ipset_env->v4_enabled && ipset_env->v6_enabled) {
if (!cfg_str4list_insert(
&env->cfg->local_zones_ipset,
strdup(p->str),
strdup("ipv6"),
strdup(ipset_env->name_v6),
strdup("no-ttl")
)) {
log_err("ipset: out of memory adding rule mapping for global declaration");
return 0;
}
}
}
}

int ipset_init(struct module_env* env, int id) {
struct ipset_env *ipset_env = env->modinfo[id];

Expand All @@ -377,9 +488,10 @@ int ipset_init(struct module_env* env, int id) {
ipset_env->v6_enabled = !ipset_env->name_v6 || (strlen(ipset_env->name_v6) == 0) ? 0 : 1;

if ((ipset_env->v4_enabled < 1) && (ipset_env->v6_enabled < 1)) {
log_err("ipset: set name no configuration?");
return 0;
return 1;
}

convert_global_ipset(env, ipset_env);

return 1;
}
Expand Down
Loading