Add --ipset option.
diff --git a/src/config.h b/src/config.h
index cff278d..9c8e785 100644
--- a/src/config.h
+++ b/src/config.h
@@ -97,6 +97,10 @@
a build-dependency on libnetfilter_conntrack, but the resulting binary will
still run happily on a kernel without conntrack support.
+HAVE_IPSET
+ define this to include the ability to selectively add resolved ip addresses
+ to given ipsets.
+
HAVE_AUTH
define this to include the facility to act as an authoritative DNS
server for one or more zones.
@@ -136,7 +140,7 @@
/* #define HAVE_DBUS */
/* #define HAVE_IDN */
/* #define HAVE_CONNTRACK */
-
+/* #define HAVE_IPSET */
/* Default locations for important system files. */
@@ -323,6 +327,10 @@
#undef HAVE_AUTH
#endif
+#ifndef HAVE_LINUX_NETWORK
+#undef HAVE_IPSET
+#endif
+
/* Define a string indicating which options are in use.
DNSMASQP_COMPILE_OPTS is only defined in dnsmasq.c */
@@ -381,6 +389,10 @@
"no-"
#endif
"conntrack "
+#ifndef HAVE_IPSET
+"no-"
+#endif
+"ipset "
#ifndef HAVE_AUTH
"no-"
#endif
diff --git a/src/dnsmasq.c b/src/dnsmasq.c
index e36835d..43b8cb1 100644
--- a/src/dnsmasq.c
+++ b/src/dnsmasq.c
@@ -213,6 +213,11 @@
#endif
+#ifdef HAVE_IPSET
+ if (daemon->ipsets)
+ ipset_init();
+#endif
+
#ifdef HAVE_LINUX_NETWORK
netlink_init();
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 21a309c..a24cf41 100644
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -430,6 +430,12 @@
struct server *next;
};
+struct ipsets {
+ char **sets;
+ char *domain;
+ struct ipsets *next;
+};
+
struct irec {
union mysockaddr addr;
struct in_addr netmask; /* only valid for IPv4 */
@@ -779,6 +785,7 @@
struct iname *if_names, *if_addrs, *if_except, *dhcp_except, *auth_peers;
struct bogus_addr *bogus_addr;
struct server *servers;
+ struct ipsets *ipsets;
int log_fac; /* log facility */
char *log_file; /* optional log file */
int max_logs; /* queue limit */
@@ -903,7 +910,8 @@
struct all_addr *addrp, unsigned int flags,
unsigned long local_ttl);
int extract_addresses(struct dns_header *header, size_t qlen, char *namebuff,
- time_t now, int is_sign, int checkrebind, int checking_disabled);
+ time_t now, char **ipsets, int is_sign, int checkrebind,
+ int checking_disabled);
size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
struct in_addr local_addr, struct in_addr local_netmask, time_t now);
int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
@@ -1117,6 +1125,12 @@
# endif
#endif
+/* ipset.c */
+#ifdef HAVE_IPSET
+void ipset_init(void);
+int add_to_ipset(const char *setname, const struct all_addr *ipaddr, int flags, int remove);
+#endif
+
/* helper.c */
#if defined(HAVE_SCRIPT)
int create_helper(int event_fd, int err_fd, uid_t uid, gid_t gid, long max_fd);
diff --git a/src/forward.c b/src/forward.c
index fb0b4c4..9a1e15a 100644
--- a/src/forward.c
+++ b/src/forward.c
@@ -439,9 +439,28 @@
struct server *server, size_t n, int check_rebind, int checking_disabled)
{
unsigned char *pheader, *sizep;
+ char **sets = 0;
int munged = 0, is_sign;
size_t plen;
+#ifdef HAVE_IPSET
+ /* Similar algorithm to search_servers. */
+ struct ipsets *ipset_pos;
+ unsigned int namelen = strlen(daemon->namebuff);
+ unsigned int matchlen = 0;
+ for (ipset_pos = daemon->ipsets; ipset_pos; ipset_pos = ipset_pos->next)
+ {
+ unsigned int domainlen = strlen(ipset_pos->domain);
+ char *matchstart = daemon->namebuff + namelen - domainlen;
+ if (namelen >= domainlen && hostname_isequal(matchstart, ipset_pos->domain) &&
+ (domainlen == 0 || namelen == domainlen || *(matchstart - 1) == '.' ) &&
+ domainlen >= matchlen) {
+ matchlen = domainlen;
+ sets = ipset_pos->sets;
+ }
+ }
+#endif
+
/* If upstream is advertising a larger UDP packet size
than we allow, trim it so that we don't get overlarge
requests for the client. We can't do this for signed packets. */
@@ -494,7 +513,7 @@
SET_RCODE(header, NOERROR);
}
- if (extract_addresses(header, n, daemon->namebuff, now, is_sign, check_rebind, checking_disabled))
+ if (extract_addresses(header, n, daemon->namebuff, now, sets, is_sign, check_rebind, checking_disabled))
{
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
munged = 1;
diff --git a/src/ipset.c b/src/ipset.c
new file mode 100644
index 0000000..c16cc85
--- /dev/null
+++ b/src/ipset.c
@@ -0,0 +1,205 @@
+/* ipset.c is Copyright (c) 2013 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; version 2 dated June, 1991, or
+ (at your option) version 3 dated 29 June, 2007.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "dnsmasq.h"
+
+#ifdef HAVE_IPSET
+
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <arpa/inet.h>
+#include <linux/version.h>
+#include <linux/netlink.h>
+#include <linux/netfilter/nfnetlink.h>
+#ifndef NFNL_SUBSYS_IPSET
+#define NFNL_SUBSYS_IPSET 6
+#define IPSET_ATTR_DATA 7
+#define IPSET_ATTR_IP 1
+#define IPSET_ATTR_IPADDR_IPV4 1
+#define IPSET_ATTR_IPADDR_IPV6 2
+#define IPSET_ATTR_PROTOCOL 1
+#define IPSET_ATTR_SETNAME 2
+#define IPSET_CMD_ADD 9
+#define IPSET_CMD_DEL 10
+#define IPSET_MAXNAMELEN 32
+#define IPSET_PROTOCOL 6
+#else
+#include <linux/netfilter/ipset/ip_set.h>
+#endif
+
+/* data structure size in here is fixed */
+#define BUFF_SZ 256
+
+#define NL_ALIGN(len) (((len)+3) & ~(3))
+static const struct sockaddr_nl snl = { .nl_family = AF_NETLINK };
+static int ipset_sock, old_kernel;
+static char *buffer;
+
+static inline void add_attr(struct nlmsghdr *nlh, uint16_t type, size_t len, const void *data)
+{
+ struct nlattr *attr = (void *)nlh + NL_ALIGN(nlh->nlmsg_len);
+ uint16_t payload_len = NL_ALIGN(sizeof(struct nlattr)) + len;
+ attr->nla_type = type;
+ attr->nla_len = payload_len;
+ memcpy((void *)attr + NL_ALIGN(sizeof(struct nlattr)), data, len);
+ nlh->nlmsg_len += NL_ALIGN(payload_len);
+}
+
+void ipset_init(void)
+{
+ struct utsname utsname;
+ int version;
+ char *split;
+
+ if (uname(&utsname) < 0)
+ die(_("failed to find kernel version: %s"), NULL, EC_MISC);
+
+ split = strtok(utsname.release, ".");
+ version = (split ? atoi(split) : 0);
+ split = strtok(NULL, ".");
+ version = version * 256 + (split ? atoi(split) : 0);
+ split = strtok(NULL, ".");
+ version = version * 256 + (split ? atoi(split) : 0);
+ old_kernel = (version < KERNEL_VERSION(2,6,32));
+
+ if (old_kernel && (ipset_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) != -1)
+ return;
+
+ if (!old_kernel &&
+ (buffer = safe_malloc(BUFF_SZ)) &&
+ (ipset_sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER)) != -1 &&
+ (bind(ipset_sock, (struct sockaddr *)&snl, sizeof(snl)) != -1))
+ return;
+
+ die (_("failed to create IPset control socket: %s"), NULL, EC_MISC);
+}
+
+static int new_add_to_ipset(const char *setname, const struct all_addr *ipaddr, int af, int remove)
+{
+ struct nlmsghdr *nlh;
+ struct nfgenmsg *nfg;
+ struct nlattr *nested[2];
+ uint8_t proto;
+ int addrsz = INADDRSZ;
+
+#ifdef HAVE_IPV6
+ if (af == AF_INET6)
+ addrsz = IN6ADDRSZ;
+#endif
+
+ if (strlen(setname) >= IPSET_MAXNAMELEN)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ memset(buffer, 0, sizeof(buffer));
+
+ nlh = (struct nlmsghdr *)buffer;
+ nlh->nlmsg_len = NL_ALIGN(sizeof(struct nlmsghdr));
+ nlh->nlmsg_type = (remove ? IPSET_CMD_DEL : IPSET_CMD_ADD) | (NFNL_SUBSYS_IPSET << 8);
+ nlh->nlmsg_flags = NLM_F_REQUEST;
+
+ nfg = (struct nfgenmsg *)(buffer + nlh->nlmsg_len);
+ nlh->nlmsg_len += NL_ALIGN(sizeof(struct nfgenmsg));
+ nfg->nfgen_family = af;
+ nfg->version = NFNETLINK_V0;
+ nfg->res_id = htons(0);
+
+ proto = IPSET_PROTOCOL;
+ add_attr(nlh, IPSET_ATTR_PROTOCOL, sizeof(proto), &proto);
+ add_attr(nlh, IPSET_ATTR_SETNAME, strlen(setname) + 1, setname);
+ nested[0] = (struct nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len));
+ nlh->nlmsg_len += NL_ALIGN(sizeof(struct nlattr));
+ nested[0]->nla_type = NLA_F_NESTED | IPSET_ATTR_DATA;
+ nested[1] = (struct nlattr *)(buffer + NL_ALIGN(nlh->nlmsg_len));
+ nlh->nlmsg_len += NL_ALIGN(sizeof(struct nlattr));
+ nested[1]->nla_type = NLA_F_NESTED | IPSET_ATTR_IP;
+ add_attr(nlh,
+ (af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6) | NLA_F_NET_BYTEORDER,
+ addrsz, &ipaddr->addr);
+ nested[1]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[1];
+ nested[0]->nla_len = (void *)buffer + NL_ALIGN(nlh->nlmsg_len) - (void *)nested[0];
+
+ if (sendto(ipset_sock, buffer, nlh->nlmsg_len, 0, (struct sockaddr *)&snl, sizeof(snl)) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+static int old_add_to_ipset(const char *setname, const struct all_addr *ipaddr, int remove)
+{
+ socklen_t size;
+ struct ip_set_req_adt_get {
+ unsigned op;
+ unsigned version;
+ union {
+ char name[IPSET_MAXNAMELEN];
+ uint16_t index;
+ } set;
+ char typename[IPSET_MAXNAMELEN];
+ } req_adt_get;
+ struct ip_set_req_adt {
+ unsigned op;
+ uint16_t index;
+ uint32_t ip;
+ } req_adt;
+
+ if (strlen(setname) >= sizeof(req_adt_get.set.name))
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ req_adt_get.op = 0x10;
+ req_adt_get.version = 3;
+ strcpy(req_adt_get.set.name, setname);
+ size = sizeof(req_adt_get);
+ if (getsockopt(ipset_sock, SOL_IP, 83, &req_adt_get, &size) < 0)
+ return -1;
+ req_adt.op = remove ? 0x102 : 0x101;
+ req_adt.index = req_adt_get.set.index;
+ req_adt.ip = ntohl(ipaddr->addr.addr4.s_addr);
+ if (setsockopt(ipset_sock, SOL_IP, 83, &req_adt, sizeof(req_adt)) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+
+int add_to_ipset(const char *setname, const struct all_addr *ipaddr, int flags, int remove)
+{
+ int af = AF_INET;
+
+#ifdef HAVE_IPV6
+ if (flags & F_IPV6)
+ {
+ af = AF_INET6;
+ /* old method only supports IPv4 */
+ if (old_kernel)
+ return -1;
+ }
+#endif
+
+ return old_kernel ? old_add_to_ipset(setname, ipaddr, remove) : new_add_to_ipset(setname, ipaddr, af, remove);
+}
+
+#endif
diff --git a/src/option.c b/src/option.c
index 3fc3e03..9315694 100644
--- a/src/option.c
+++ b/src/option.c
@@ -127,6 +127,7 @@
#define LOPT_AUTHSOA 316
#define LOPT_AUTHSFS 317
#define LOPT_AUTHPEER 318
+#define LOPT_IPSET 319
#ifdef HAVE_GETOPT_LONG
static const struct option opts[] =
@@ -259,6 +260,7 @@
{ "auth-soa", 1, 0, LOPT_AUTHSOA },
{ "auth-sec-servers", 1, 0, LOPT_AUTHSFS },
{ "auth-peer", 1, 0, LOPT_AUTHPEER },
+ { "ipset", 1, 0, LOPT_IPSET },
{ NULL, 0, 0, 0 }
};
@@ -397,6 +399,7 @@
{ LOPT_AUTHSOA, ARG_ONE, "<serial>[,...]", gettext_noop("Set authoritive zone information"), NULL },
{ LOPT_AUTHSFS, ARG_DUP, "<NS>[,<NS>...]", gettext_noop("Secondary authoritative nameservers for forward domains"), NULL },
{ LOPT_AUTHPEER, ARG_DUP, "<ipaddr>[,<ipaddr>...]", gettext_noop("Peers which are allowed to do zone transfer"), NULL },
+ { LOPT_IPSET, ARG_DUP, "/<domain>/<ipset>[,<ipset>...]", gettext_noop("Specify ipsets to which matching domains should be added"), NULL },
{ 0, 0, NULL, NULL, NULL }
};
@@ -2021,6 +2024,74 @@
daemon->servers = newlist;
break;
}
+
+ case LOPT_IPSET: /* --ipset */
+#ifndef HAVE_IPSET
+ ret_err(_("recompile with HAVE_IPSET defined to enable ipset directives"));
+ break;
+#else
+ {
+ struct ipsets ipsets_head;
+ struct ipsets *ipsets = &ipsets_head;
+ int size;
+ char *end;
+ char **sets, **sets_pos;
+ memset(ipsets, 0, sizeof(struct ipsets));
+ unhide_metas(arg);
+ if (arg && *arg == '/')
+ {
+ arg++;
+ while ((end = split_chr(arg, '/')))
+ {
+ char *domain = NULL;
+ /* elide leading dots - they are implied in the search algorithm */
+ while (*arg == '.')
+ arg++;
+ /* # matches everything and becomes a zero length domain string */
+ if (strcmp(arg, "#") == 0 || !*arg)
+ domain = "";
+ else if (strlen(arg) != 0 && !(domain = canonicalise_opt(arg)))
+ option = '?';
+ ipsets->next = opt_malloc(sizeof(struct ipsets));
+ ipsets = ipsets->next;
+ memset(ipsets, 0, sizeof(struct ipsets));
+ ipsets->domain = domain;
+ arg = end;
+ }
+ }
+ else
+ {
+ ipsets->next = opt_malloc(sizeof(struct ipsets));
+ ipsets = ipsets->next;
+ memset(ipsets, 0, sizeof(struct ipsets));
+ ipsets->domain = "";
+ }
+ if (!arg || !*arg)
+ {
+ option = '?';
+ break;
+ }
+ size = 2;
+ for (end = arg; *end; ++end)
+ if (*end == ',')
+ ++size;
+
+ sets = sets_pos = opt_malloc(sizeof(char *) * size);
+
+ do {
+ end = split(arg);
+ *sets_pos++ = opt_string_alloc(arg);
+ arg = end;
+ } while (end);
+ *sets_pos = 0;
+ for (ipsets = &ipsets_head; ipsets->next; ipsets = ipsets->next)
+ ipsets->next->sets = sets;
+ ipsets->next = daemon->ipsets;
+ daemon->ipsets = ipsets_head.next;
+
+ break;
+ }
+#endif
case 'c': /* --cache-size */
{
diff --git a/src/rfc1035.c b/src/rfc1035.c
index 721cd61..8d55ffd 100644
--- a/src/rfc1035.c
+++ b/src/rfc1035.c
@@ -777,13 +777,18 @@
expired and cleaned out that way.
Return 1 if we reject an address because it look like part of dns-rebinding attack. */
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
- int is_sign, int check_rebind, int checking_disabled)
+ char **ipsets, int is_sign, int check_rebind, int checking_disabled)
{
unsigned char *p, *p1, *endrr, *namep;
int i, j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0;
unsigned long ttl = 0;
struct all_addr addr;
-
+#ifdef HAVE_IPSET
+ char **ipsets_cur;
+#else
+ (void)ipsets; /* unused */
+#endif
+
cache_start_insert();
/* find_soa is needed for dns_doctor and logging side-effects, so don't call it lazily if there are any. */
@@ -966,6 +971,15 @@
(flags & F_IPV4) &&
private_net(addr.addr.addr4, !option_bool(OPT_LOCAL_REBIND)))
return 1;
+
+#ifdef HAVE_IPSET
+ if (ipsets && (flags & (F_IPV4 | F_IPV6)))
+ {
+ ipsets_cur = ipsets;
+ while (*ipsets_cur)
+ add_to_ipset(*ipsets_cur++, &addr, flags, 0);
+ }
+#endif
newc = cache_insert(name, &addr, now, attl, flags | F_FORWARD);
if (newc && cpp)