summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/unbound/doc/README.ipset.md65
-rw-r--r--usr.sbin/unbound/ipset/ipset.c353
-rw-r--r--usr.sbin/unbound/ipset/ipset.h79
-rw-r--r--usr.sbin/unbound/testcode/delayer.c2
-rw-r--r--usr.sbin/unbound/testcode/fake_event.c2
-rw-r--r--usr.sbin/unbound/testcode/memstats.c11
-rw-r--r--usr.sbin/unbound/testcode/perf.c2
-rw-r--r--usr.sbin/unbound/testcode/unitmsgparse.c6
-rw-r--r--usr.sbin/unbound/util/data/msgencode.h4
9 files changed, 515 insertions, 9 deletions
diff --git a/usr.sbin/unbound/doc/README.ipset.md b/usr.sbin/unbound/doc/README.ipset.md
new file mode 100644
index 00000000000..4bd993e67ad
--- /dev/null
+++ b/usr.sbin/unbound/doc/README.ipset.md
@@ -0,0 +1,65 @@
+## Created a module to support the ipset that could add the domain's ip to a list easily.
+
+### Purposes:
+* In my case, I can't access the facebook, twitter, youtube and thousands web site for some reason. VPN is a solution. But the internet too slow whether all traffics pass through the vpn.
+So, I set up a transparent proxy to proxy the traffic which has been blocked only.
+At the final step, I need to install a dns service which would work with ipset well to launch the system.
+I did some research for this. Unfortunately, Unbound, My favorite dns service doesn't support ipset yet. So, I decided to implement it by my self and contribute the patch. It's good for me and the community.
+```
+# unbound.conf
+server:
+ ...
+ local-zone: "facebook.com" ipset
+ local-zone: "twitter.com" ipset
+ local-zone: "instagram.com" ipset
+ more social website
+
+ipset:
+ name-v4: "gfwlist"
+```
+```
+# iptables
+iptables -A PREROUTING -p tcp -m set --match-set gfwlist dst -j REDIRECT --to-ports 10800
+iptables -A OUTPUT -p tcp -m set --match-set gfwlist dst -j REDIRECT --to-ports 10800
+```
+
+* This patch could work with iptables rules to batch block the IPs.
+```
+# unbound.conf
+server:
+ ...
+ local-zone: "facebook.com" ipset
+ local-zone: "twitter.com" ipset
+ local-zone: "instagram.com" ipset
+ more social website
+
+ipset:
+ name-v4: "blacklist"
+ name-v6: "blacklist6"
+```
+```
+# iptables
+iptables -A INPUT -m set --set blacklist src -j DROP
+ip6tables -A INPUT -m set --set blacklist6 src -j DROP
+```
+
+### Notes:
+* To enable this module the root privileges is required.
+* Please create a set with ipset command first. eg. **ipset -N blacklist iphash**
+
+### How to use:
+```
+./configure --enable-ipset
+make && make install
+```
+
+### Configuration:
+```
+# unbound.conf
+server:
+ ...
+ local-zone: "example.com" ipset
+
+ipset:
+ name-v4: "blacklist"
+```
diff --git a/usr.sbin/unbound/ipset/ipset.c b/usr.sbin/unbound/ipset/ipset.c
new file mode 100644
index 00000000000..85b2edea9ed
--- /dev/null
+++ b/usr.sbin/unbound/ipset/ipset.c
@@ -0,0 +1,353 @@
+/**
+ * \file
+ * This file implements the ipset module. It can handle packets by putting
+ * the A and AAAA addresses that are configured in unbound.conf as type
+ * ipset (local-zone statements) into a firewall rule IPSet. For firewall
+ * blacklist and whitelist usage.
+ */
+#include "config.h"
+#include "ipset/ipset.h"
+#include "util/regional.h"
+#include "util/config_file.h"
+
+#include "services/cache/dns.h"
+
+#include "sldns/sbuffer.h"
+#include "sldns/wire2str.h"
+#include "sldns/parseutil.h"
+
+#include <libmnl/libmnl.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netfilter/ipset/ip_set.h>
+
+#define BUFF_LEN 256
+
+/**
+ * Return an error
+ * @param qstate: our query state
+ * @param id: module id
+ * @param rcode: error code (DNS errcode).
+ * @return: 0 for use by caller, to make notation easy, like:
+ * return error_response(..).
+ */
+static int error_response(struct module_qstate* qstate, int id, int rcode) {
+ verbose(VERB_QUERY, "return error response %s",
+ sldns_lookup_by_id(sldns_rcodes, rcode)?
+ sldns_lookup_by_id(sldns_rcodes, rcode)->name:"??");
+ qstate->return_rcode = rcode;
+ qstate->return_msg = NULL;
+ qstate->ext_state[id] = module_finished;
+ return 0;
+}
+
+static struct mnl_socket * open_mnl_socket() {
+ struct mnl_socket *mnl;
+
+ mnl = mnl_socket_open(NETLINK_NETFILTER);
+ if (!mnl) {
+ log_err("ipset: could not open netfilter.");
+ return NULL;
+ }
+
+ if (mnl_socket_bind(mnl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ mnl_socket_close(mnl);
+ log_err("ipset: could not bind netfilter.");
+ return NULL;
+ }
+ return mnl;
+}
+
+static int add_to_ipset(struct mnl_socket *mnl, const char *setname, const void *ipaddr, int af) {
+ struct nlmsghdr *nlh;
+ struct nfgenmsg *nfg;
+ struct nlattr *nested[2];
+ static char buffer[BUFF_LEN];
+
+ if (strlen(setname) >= IPSET_MAXNAMELEN) {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ if (af != AF_INET && af != AF_INET6) {
+ errno = EAFNOSUPPORT;
+ return -1;
+ }
+
+ 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;
+
+ nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg));
+ nfg->nfgen_family = af;
+ nfg->version = NFNETLINK_V0;
+ nfg->res_id = htons(0);
+
+ mnl_attr_put_u8(nlh, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL);
+ mnl_attr_put(nlh, IPSET_ATTR_SETNAME, strlen(setname) + 1, setname);
+ nested[0] = mnl_attr_nest_start(nlh, IPSET_ATTR_DATA);
+ nested[1] = mnl_attr_nest_start(nlh, IPSET_ATTR_IP);
+ 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]);
+ mnl_attr_nest_end(nlh, nested[0]);
+
+ if (mnl_socket_sendto(mnl, nlh, nlh->nlmsg_len) < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static int ipset_update(struct module_env *env, struct dns_msg *return_msg, struct ipset_env *ie) {
+ int ret;
+
+ struct mnl_socket *mnl;
+
+ size_t i, j;
+
+ const char *setname;
+
+ struct ub_packed_rrset_key *rrset;
+ struct packed_rrset_data *d;
+
+ int af;
+
+ static char dname[BUFF_LEN];
+ const char *s;
+ int dlen, plen;
+
+ struct config_strlist *p;
+
+ size_t rr_len, rd_len;
+
+ uint8_t *rr_data;
+
+ mnl = (struct mnl_socket *)ie->mnl;
+ if (!mnl) {
+ // retry to create mnl socket
+ mnl = open_mnl_socket();
+ if (!mnl) {
+ return -1;
+ }
+
+ ie->mnl = mnl;
+ }
+
+ for (i = 0; i < return_msg->rep->rrset_count; ++i) {
+ setname = NULL;
+
+ rrset = return_msg->rep->rrsets[i];
+
+ if (rrset->rk.type == htons(LDNS_RR_TYPE_A)) {
+ af = AF_INET;
+ if ((ie->v4_enabled == 1)) {
+ setname = ie->name_v4;
+ }
+ } else {
+ af = AF_INET6;
+ if ((ie->v6_enabled == 1)) {
+ setname = ie->name_v6;
+ }
+ }
+
+ if (setname) {
+ dlen = sldns_wire2str_dname_buf(rrset->rk.dname, rrset->rk.dname_len, dname, BUFF_LEN);
+ if (dlen == 0) {
+ log_err("bad domain name");
+ return -1;
+ }
+ if (dname[dlen - 1] == '.') {
+ dlen--;
+ }
+
+ for (p = env->cfg->local_zones_ipset; p; p = p->next) {
+ plen = strlen(p->str);
+
+ if (dlen >= plen) {
+ s = dname + (dlen - plen);
+
+ if (strncasecmp(p->str, s, plen) == 0) {
+ d = (struct packed_rrset_data*)rrset->entry.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];
+
+ rd_len = sldns_read_uint16(rr_data);
+ if (rr_len - 2 >= rd_len) {
+ ret = add_to_ipset(mnl, setname, rr_data + 2, af);
+ if (ret < 0) {
+ log_err("ipset: could not add %s into %s", dname, setname);
+
+ mnl_socket_close(mnl);
+ ie->mnl = NULL;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+int ipset_init(struct module_env* env, int id) {
+ struct ipset_env *ipset_env;
+
+ ipset_env = (struct ipset_env *)calloc(1, sizeof(struct ipset_env));
+ if (!ipset_env) {
+ log_err("malloc failure");
+ return 0;
+ }
+
+ env->modinfo[id] = (void *)ipset_env;
+
+ ipset_env->mnl = NULL;
+
+ ipset_env->name_v4 = env->cfg->ipset_name_v4;
+ ipset_env->name_v6 = env->cfg->ipset_name_v6;
+
+ ipset_env->v4_enabled = !ipset_env->name_v4 || (strlen(ipset_env->name_v4) == 0) ? 0 : 1;
+ 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;
+}
+
+void ipset_deinit(struct module_env *env, int id) {
+ struct mnl_socket *mnl;
+ struct ipset_env *ipset_env;
+
+ if (!env || !env->modinfo[id]) {
+ return;
+ }
+
+ ipset_env = (struct ipset_env *)env->modinfo[id];
+
+ mnl = (struct mnl_socket *)ipset_env->mnl;
+ if (mnl) {
+ mnl_socket_close(mnl);
+ ipset_env->mnl = NULL;
+ }
+
+ free(ipset_env);
+ env->modinfo[id] = NULL;
+}
+
+static int ipset_new(struct module_qstate* qstate, int id) {
+ struct ipset_qstate *iq = (struct ipset_qstate *)regional_alloc(
+ qstate->region, sizeof(struct ipset_qstate));
+ qstate->minfo[id] = iq;
+ if (!iq) {
+ return 0;
+ }
+
+ memset(iq, 0, sizeof(*iq));
+ /* initialise it */
+ /* TODO */
+
+ return 1;
+}
+
+void ipset_operate(struct module_qstate *qstate, enum module_ev event, int id,
+ struct outbound_entry *outbound) {
+ struct ipset_env *ie = (struct ipset_env *)qstate->env->modinfo[id];
+ struct ipset_qstate *iq = (struct ipset_qstate *)qstate->minfo[id];
+ verbose(VERB_QUERY, "ipset[module %d] operate: extstate:%s event:%s",
+ id, strextstate(qstate->ext_state[id]), strmodulevent(event));
+ if (iq) {
+ log_query_info(VERB_QUERY, "ipset operate: query", &qstate->qinfo);
+ }
+
+ /* perform ipset state machine */
+ if ((event == module_event_new || event == module_event_pass) && !iq) {
+ if (!ipset_new(qstate, id)) {
+ (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ return;
+ }
+ iq = (struct ipset_qstate*)qstate->minfo[id];
+ }
+
+ if (iq && (event == module_event_pass || event == module_event_new)) {
+ qstate->ext_state[id] = module_wait_module;
+ return;
+ }
+
+ if (iq && (event == module_event_moddone)) {
+ if (qstate->return_msg && qstate->return_msg->rep) {
+ ipset_update(qstate->env, qstate->return_msg, ie);
+ }
+ qstate->ext_state[id] = module_finished;
+ return;
+ }
+
+ if (iq && outbound) {
+ /* ipset does not need to process responses at this time
+ * ignore it.
+ ipset_process_response(qstate, iq, ie, id, outbound, event);
+ */
+ return;
+ }
+
+ if (event == module_event_error) {
+ verbose(VERB_ALGO, "got called with event error, giving up");
+ (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+ return;
+ }
+
+ if (!iq && (event == module_event_moddone)) {
+ /* during priming, module done but we never started */
+ qstate->ext_state[id] = module_finished;
+ return;
+ }
+
+ log_err("bad event for ipset");
+ (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL);
+}
+
+void ipset_inform_super(struct module_qstate *ATTR_UNUSED(qstate),
+ int ATTR_UNUSED(id), struct module_qstate *ATTR_UNUSED(super)) {
+ /* ipset does not use subordinate requests at this time */
+ verbose(VERB_ALGO, "ipset inform_super was called");
+}
+
+void ipset_clear(struct module_qstate *qstate, int id) {
+ struct cachedb_qstate *iq;
+ if (!qstate) {
+ return;
+ }
+ iq = (struct cachedb_qstate *)qstate->minfo[id];
+ if (iq) {
+ /* free contents of iq */
+ /* TODO */
+ }
+ qstate->minfo[id] = NULL;
+}
+
+size_t ipset_get_mem(struct module_env *env, int id) {
+ struct ipset_env *ie = (struct ipset_env *)env->modinfo[id];
+ if (!ie) {
+ return 0;
+ }
+ return sizeof(*ie);
+}
+
+/**
+ * The ipset function block
+ */
+static struct module_func_block ipset_block = {
+ "ipset",
+ &ipset_init, &ipset_deinit, &ipset_operate,
+ &ipset_inform_super, &ipset_clear, &ipset_get_mem
+};
+
+struct module_func_block * ipset_get_funcblock(void) {
+ return &ipset_block;
+}
+
diff --git a/usr.sbin/unbound/ipset/ipset.h b/usr.sbin/unbound/ipset/ipset.h
new file mode 100644
index 00000000000..f60a8be8c83
--- /dev/null
+++ b/usr.sbin/unbound/ipset/ipset.h
@@ -0,0 +1,79 @@
+/**
+ * ipset.h
+ *
+ * Author: Kevin Chou
+ * Email: k9982874@gmail.com
+ */
+#ifndef IPSET_H
+#define IPSET_H
+/** \file
+ *
+ * This file implements the ipset module. It can handle packets by putting
+ * the A and AAAA addresses that are configured in unbound.conf as type
+ * ipset (local-zone statements) into a firewall rule IPSet. For firewall
+ * blacklist and whitelist usage.
+ *
+ * To use the IPset module, install the libmnl-dev (or libmnl-devel) package
+ * and configure with --enable-ipset. And compile. Then enable the ipset
+ * module in unbound.conf with module-config: "ipset validator iterator"
+ * then create it with ipset -N blacklist iphash and then add
+ * local-zone: "example.com." ipset
+ * statements for the zones where you want the addresses of the names
+ * looked up added to the set.
+ *
+ * Set the name of the set with
+ * ipset:
+ * name-v4: "blacklist"
+ * name-v6: "blacklist6"
+ * in unbound.conf. The set can be used in this way:
+ * iptables -A INPUT -m set --set blacklist src -j DROP
+ * ip6tables -A INPUT -m set --set blacklist6 src -j DROP
+ */
+
+#include "util/module.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ipset_env {
+ void* mnl;
+
+ int v4_enabled;
+ int v6_enabled;
+
+ const char *name_v4;
+ const char *name_v6;
+};
+
+struct ipset_qstate {
+ int dummy;
+};
+
+/** Init the ipset module */
+int ipset_init(struct module_env* env, int id);
+/** Deinit the ipset module */
+void ipset_deinit(struct module_env* env, int id);
+/** Operate on an event on a query (in qstate). */
+void ipset_operate(struct module_qstate* qstate, enum module_ev event,
+ int id, struct outbound_entry* outbound);
+/** Subordinate query done, inform this super request of its conclusion */
+void ipset_inform_super(struct module_qstate* qstate, int id,
+ struct module_qstate* super);
+/** clear the ipset query-specific contents out of qstate */
+void ipset_clear(struct module_qstate* qstate, int id);
+/** return memory estimate for ipset module */
+size_t ipset_get_mem(struct module_env* env, int id);
+
+/**
+ * Get the function block with pointers to the ipset functions
+ * @return the function block for "ipset".
+ */
+struct module_func_block* ipset_get_funcblock(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* IPSET_H */
+
diff --git a/usr.sbin/unbound/testcode/delayer.c b/usr.sbin/unbound/testcode/delayer.c
index 4abcfc235dc..655e4a1e7f1 100644
--- a/usr.sbin/unbound/testcode/delayer.c
+++ b/usr.sbin/unbound/testcode/delayer.c
@@ -174,7 +174,7 @@ dl_tv_add(struct timeval* t1, const struct timeval* t2)
#ifndef S_SPLINT_S
t1->tv_sec += t2->tv_sec;
t1->tv_usec += t2->tv_usec;
- while(t1->tv_usec > 1000000) {
+ while(t1->tv_usec >= 1000000) {
t1->tv_usec -= 1000000;
t1->tv_sec++;
}
diff --git a/usr.sbin/unbound/testcode/fake_event.c b/usr.sbin/unbound/testcode/fake_event.c
index 713e247592b..d6e904a4d3c 100644
--- a/usr.sbin/unbound/testcode/fake_event.c
+++ b/usr.sbin/unbound/testcode/fake_event.c
@@ -100,7 +100,7 @@ timeval_add(struct timeval* d, const struct timeval* add)
#ifndef S_SPLINT_S
d->tv_sec += add->tv_sec;
d->tv_usec += add->tv_usec;
- if(d->tv_usec > 1000000) {
+ if(d->tv_usec >= 1000000) {
d->tv_usec -= 1000000;
d->tv_sec++;
}
diff --git a/usr.sbin/unbound/testcode/memstats.c b/usr.sbin/unbound/testcode/memstats.c
index dc29058ad77..a253b00ac50 100644
--- a/usr.sbin/unbound/testcode/memstats.c
+++ b/usr.sbin/unbound/testcode/memstats.c
@@ -106,9 +106,16 @@ get_codeline(rbtree_type* tree, char* key, char* func)
cl = calloc(1, sizeof(*cl));
if(!cl) return 0;
cl->codeline = strdup(key);
- if(!cl->codeline) return 0;
+ if(!cl->codeline) {
+ free(cl);
+ return 0;
+ }
cl->func = strdup(func);
- if(!cl->func) return 0;
+ if(!cl->func) {
+ free(cl->codeline);
+ free(cl);
+ return 0;
+ }
cl->alloc = 0;
cl->node.key = cl->codeline;
(void)rbtree_insert(tree, &cl->node);
diff --git a/usr.sbin/unbound/testcode/perf.c b/usr.sbin/unbound/testcode/perf.c
index d6d2b05298e..5b170ca5737 100644
--- a/usr.sbin/unbound/testcode/perf.c
+++ b/usr.sbin/unbound/testcode/perf.c
@@ -177,7 +177,7 @@ perf_tv_add(struct timeval* t1, struct timeval* t2)
#ifndef S_SPLINT_S
t1->tv_sec += t2->tv_sec;
t1->tv_usec += t2->tv_usec;
- while(t1->tv_usec > 1000000) {
+ while(t1->tv_usec >= 1000000) {
t1->tv_usec -= 1000000;
t1->tv_sec++;
}
diff --git a/usr.sbin/unbound/testcode/unitmsgparse.c b/usr.sbin/unbound/testcode/unitmsgparse.c
index c0b38bac76e..6f1edc6e9d6 100644
--- a/usr.sbin/unbound/testcode/unitmsgparse.c
+++ b/usr.sbin/unbound/testcode/unitmsgparse.c
@@ -179,7 +179,7 @@ perf_encode(struct query_info* qi, struct reply_info* rep, uint16_t id,
/* encode a couple times */
for(i=0; i<max; i++) {
ret = reply_info_encode(qi, rep, id, flags, out, timenow,
- r2, 65535, (int)(edns->bits & EDNS_DO) );
+ r2, 65535, (int)(edns->bits & EDNS_DO), 0);
unit_assert(ret != 0); /* udp packets should fit */
attach_edns_record(out, edns);
regional_free_all(r2);
@@ -342,7 +342,7 @@ testpkt(sldns_buffer* pkt, struct alloc_cache* alloc, sldns_buffer* out,
} else if(!check_formerr_gone) {
const size_t lim = 512;
ret = reply_info_encode(&qi, rep, id, flags, out, timenow,
- region, 65535, (int)(edns.bits & EDNS_DO) );
+ region, 65535, (int)(edns.bits & EDNS_DO), 0);
unit_assert(ret != 0); /* udp packets should fit */
attach_edns_record(out, &edns);
if(vbmp) printf("inlen %u outlen %u\n",
@@ -357,7 +357,7 @@ testpkt(sldns_buffer* pkt, struct alloc_cache* alloc, sldns_buffer* out,
ret = reply_info_encode(&qi, rep, id, flags, out,
timenow, region,
lim - calc_edns_field_size(&edns),
- (int)(edns.bits & EDNS_DO));
+ (int)(edns.bits & EDNS_DO), 0);
unit_assert(ret != 0); /* should fit, but with TC */
attach_edns_record(out, &edns);
if( LDNS_QDCOUNT(sldns_buffer_begin(out)) !=
diff --git a/usr.sbin/unbound/util/data/msgencode.h b/usr.sbin/unbound/util/data/msgencode.h
index eea129d98d5..30dc515cbe5 100644
--- a/usr.sbin/unbound/util/data/msgencode.h
+++ b/usr.sbin/unbound/util/data/msgencode.h
@@ -85,12 +85,14 @@ int reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep,
* @param region: to store temporary data in.
* @param udpsize: size of the answer, 512, from EDNS, or 64k for TCP.
* @param dnssec: if 0 DNSSEC records are omitted from the answer.
+ * @param minimise: if true, the answer is a minimal response, with
+ * authority and additional removed if possible.
* @return: nonzero is success, or
* 0 on error: malloc failure (no log_err has been done).
*/
int reply_info_encode(struct query_info* qinfo, struct reply_info* rep,
uint16_t id, uint16_t flags, struct sldns_buffer* buffer, time_t timenow,
- struct regional* region, uint16_t udpsize, int dnssec);
+ struct regional* region, uint16_t udpsize, int dnssec, int minimise);
/**
* Encode query packet. Assumes the buffer is large enough.