diff options
-rw-r--r-- | usr.sbin/unbound/doc/README.ipset.md | 65 | ||||
-rw-r--r-- | usr.sbin/unbound/ipset/ipset.c | 353 | ||||
-rw-r--r-- | usr.sbin/unbound/ipset/ipset.h | 79 | ||||
-rw-r--r-- | usr.sbin/unbound/testcode/delayer.c | 2 | ||||
-rw-r--r-- | usr.sbin/unbound/testcode/fake_event.c | 2 | ||||
-rw-r--r-- | usr.sbin/unbound/testcode/memstats.c | 11 | ||||
-rw-r--r-- | usr.sbin/unbound/testcode/perf.c | 2 | ||||
-rw-r--r-- | usr.sbin/unbound/testcode/unitmsgparse.c | 6 | ||||
-rw-r--r-- | usr.sbin/unbound/util/data/msgencode.h | 4 |
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. |