From 3d8155e67bcb94004bec28e97469d1959ae6e246 Mon Sep 17 00:00:00 2001 From: Nick Peng Date: Sat, 15 Dec 2018 22:02:43 +0800 Subject: [PATCH] Support ipset feature --- ReadMe.md | 1 + ReadMe_zh-CN.md | 1 + etc/smartdns/smartdns.conf | 4 + src/dns_conf.c | 132 +++++++++++++++++++++++++++++- src/dns_conf.h | 15 +++- src/dns_server.c | 31 +++++++ src/util.c | 163 ++++++++++++++++++++++++++++++++++++- src/util.h | 4 + 8 files changed, 342 insertions(+), 9 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 0e0037b..930933e 100755 --- a/ReadMe.md +++ b/ReadMe.md @@ -392,6 +392,7 @@ Note: Merlin firmware is derived from ASUS firmware and can theoretically be use |server-tcp|Upstream TCP DNS server|None|[IP][:port] [-blacklist-ip], Repeatable, blacklist-ip parameter represents filtering the result of IPs with blacklist-ip configuration.| server-tcp 8.8.8.8:53 |server-tls|Upstream TLS DNS server|None|[IP][:port] [-blacklist-ip], Repeatable, blacklist-ip parameter represents filtering the result of IPs with blacklist-ip configuration.| server-tls 8.8.8.8:853 |address|Domain IP address|None|address /domain/ip| address /www.example.com/1.2.3.4 +|ipset|Domain IPSet|None|ipset /domain/ipset|ipset /www.example.com/pass |bogus-nxdomain|bogus IP address|None|[IP/subnet], Repeatable| bogus-nxdomain 1.2.3.4/16 |blacklist-ip|ip blacklist|None|[ip/subnet], Repeatable,When the filtering server responds IPs in the IP blacklist, The result will be discarded directly| blacklist-ip 1.2.3.4/16 |force-AAAA-SOA|force AAAA query return SOA|no|[yes\|no]|force-AAAA-SOA yes diff --git a/ReadMe_zh-CN.md b/ReadMe_zh-CN.md index 11f60f2..a54febb 100644 --- a/ReadMe_zh-CN.md +++ b/ReadMe_zh-CN.md @@ -392,6 +392,7 @@ rtt min/avg/max/mdev = 5.954/6.133/6.313/0.195 ms |server-tcp|上游TCP DNS|无|[IP][:port] [-blacklist-ip],可重复,blacklist-ip参数指定使用blacklist-ip配置IP过滤结果| server-tcp 8.8.8.8:53 |server-tls|上游TLS DNS|无|[IP][:port] [-blacklist-ip],可重复,blacklist-ip参数指定使用blacklist-ip配置IP过滤结果| server-tls 8.8.8.8:853 |address|指定域名IP地址|无|address /domain/ip| address /www.example.com/1.2.3.4 +|ipset|域名IPSET|None|ipset /domain/ipset|ipset /www.example.com/pass |bogus-nxdomain|假冒IP地址过滤|无|[ip/subnet],可重复| bogus-nxdomain 1.2.3.4/16 |blacklist-ip|黑名单IP地址|无|[ip/subnet],可重复| blacklist-ip 1.2.3.4/16 |force-AAAA-SOA|强制AAAA地址返回SOA|no|[yes\|no]|force-AAAA-SOA yes diff --git a/etc/smartdns/smartdns.conf b/etc/smartdns/smartdns.conf index 3e8bb6d..d0f85c0 100644 --- a/etc/smartdns/smartdns.conf +++ b/etc/smartdns/smartdns.conf @@ -84,3 +84,7 @@ log-level info # specific address to domain # address /domain/ip # address /www.example.com/1.2.3.4 + +# specific ipset to domain +# ipset /domain/ipset +# ipset /www.example.com/block \ No newline at end of file diff --git a/src/dns_conf.c b/src/dns_conf.c index 528b6f0..7d29f6b 100644 --- a/src/dns_conf.c +++ b/src/dns_conf.c @@ -11,6 +11,11 @@ #define DEFAULT_DNS_CACHE_SIZE 512 +struct dns_ipset_table { + DECLARE_HASHTABLE(ipset, 8); +}; +struct dns_ipset_table dns_ipset_table; + char dns_conf_server_ip[DNS_MAX_IPLEN]; char dns_conf_server_tcp_ip[DNS_MAX_IPLEN]; int dns_conf_tcp_idle_time = 120; @@ -64,10 +69,10 @@ int config_server(int argc, char *argv[], dns_server_type_t type, int default_po } switch (opt) { - case 'b': { - result_flag |= DNSSERVER_FLAG_BLACKLIST_IP; - break; - } + case 'b': { + result_flag |= DNSSERVER_FLAG_BLACKLIST_IP; + break; + } } } @@ -186,6 +191,117 @@ errout: return 0; } +void config_ipset_table_destroy(void) +{ + struct dns_ipset_name *ipset_name = NULL; + struct hlist_node *tmp = NULL; + int i; + + hash_for_each_safe(dns_ipset_table.ipset, i, tmp, ipset_name, node) + { + hlist_del_init(&ipset_name->node); + free(ipset_name); + } +} + +const char *dns_conf_get_ipset(const char *ipsetname) +{ + uint32_t key = 0; + struct dns_ipset_name *ipset_name = NULL; + + key = hash_string(ipsetname); + hash_for_each_possible(dns_ipset_table.ipset, ipset_name, node, key) + { + if (strncmp(ipset_name->ipsetname, ipsetname, DNS_MAX_IPSET_NAMELEN) == 0) { + return ipset_name->ipsetname; + } + } + + ipset_name = malloc(sizeof(*ipset_name)); + if (ipset_name == NULL) { + goto errout; + } + + key = hash_string(ipsetname); + strncpy(ipset_name->ipsetname, ipsetname, DNS_MAX_IPSET_NAMELEN); + hash_add(dns_ipset_table.ipset, &ipset_name->node, key); + + return ipset_name->ipsetname; +errout: + if (ipset_name) { + free(ipset_name); + } + + return NULL; +} + +int config_ipset(void *data, int argc, char *argv[]) +{ + struct dns_ipset_rule *ipset_rule = NULL; + char domain[DNS_MAX_CONF_CNAME_LEN]; + char ipsetname[DNS_MAX_CONF_CNAME_LEN]; + const char *ipset = NULL; + char *begin = NULL; + char *end = NULL; + int len = 0; + char *value = argv[1]; + + if (argc <= 1) { + goto errout; + } + + begin = strstr(value, "/"); + if (begin == NULL) { + goto errout; + } + + begin++; + end = strstr(begin, "/"); + if (end == NULL) { + goto errout; + } + + /* remove prefix . */ + while (*begin == '.') { + begin++; + } + + len = end - begin; + memcpy(domain, begin, len); + domain[len] = '\0'; + + len = strlen(end + 1); + if (len <= 0) { + goto errout; + } + + strncpy(ipsetname, end + 1, DNS_MAX_IPSET_NAMELEN); + ipset = dns_conf_get_ipset(ipsetname); + if (ipset == NULL) { + goto errout; + } + + ipset_rule = malloc(sizeof(*ipset_rule)); + if (ipset_rule == NULL) { + goto errout; + } + + ipset_rule->ipsetname = ipset; + + if (config_domain_rule_add(domain, DOMAIN_RULE_IPSET, ipset_rule) != 0) { + goto errout; + } + + return 0; +errout: + if (ipset_rule) { + free(ipset_rule); + } + + tlog(TLOG_ERROR, "add ipset %s failed", value); + return 0; +} + int config_address(void *data, int argc, char *argv[]) { struct dns_address_IPV4 *address_ipv4 = NULL; @@ -202,6 +318,10 @@ int config_address(void *data, int argc, char *argv[]) socklen_t addr_len = sizeof(addr); enum domain_rule type = 0; + if (argc <= 1) { + goto errout; + } + begin = strstr(value, "/"); if (begin == NULL) { goto errout; @@ -385,6 +505,7 @@ struct config_item config_item[] = { CONF_CUSTOM("server-tcp", config_server_tcp, NULL), CONF_CUSTOM("server-tls", config_server_tls, NULL), CONF_CUSTOM("address", config_address, NULL), + CONF_CUSTOM("ipset", config_ipset, NULL), CONF_INT("tcp-idle-time", &dns_conf_tcp_idle_time, 0, 3600), CONF_INT("cache-size", &dns_conf_cachesize, 0, CONF_INT_MAX), CONF_YESNO("prefetch-domain", &dns_conf_prefetch), @@ -426,6 +547,8 @@ int _dns_server_load_conf_init(void) return -1; } + hash_init(dns_ipset_table.ipset); + return 0; } @@ -433,6 +556,7 @@ void dns_server_load_exit(void) { config_domain_destroy(); Destroy_Radix(dns_conf_address_rule, config_address_destroy, NULL); + config_ipset_table_destroy(); } int dns_server_load_conf(const char *file) diff --git a/src/dns_conf.h b/src/dns_conf.h index 650089b..f949e06 100644 --- a/src/dns_conf.h +++ b/src/dns_conf.h @@ -11,6 +11,7 @@ #include "radix.h" #define DNS_MAX_SERVERS 32 +#define DNS_MAX_IPSET_NAMELEN 32 #define DNS_MAX_IPLEN 64 #define DNS_MAX_PATH 1024 #define DEFAULT_DNS_PORT 53 @@ -23,6 +24,7 @@ enum domain_rule { DOMAIN_RULE_ADDRESS_IPV4 = 1, DOMAIN_RULE_ADDRESS_IPV6 = 2, + DOMAIN_RULE_IPSET = 3, DOMAIN_RULE_MAX, }; @@ -34,6 +36,15 @@ struct dns_address_IPV6 { unsigned char ipv6_addr[DNS_RR_AAAA_LEN]; }; +struct dns_ipset_name { + struct hlist_node node; + char ipsetname[DNS_MAX_IPSET_NAMELEN]; +}; + +struct dns_ipset_rule { + const char *ipsetname; +}; + struct dns_domain_rule { void *rules[DOMAIN_RULE_MAX]; }; @@ -66,10 +77,6 @@ struct dns_ip_address_rule { unsigned int bogus : 1; }; -struct dns_bogus_nxdomain { - DECLARE_HASHTABLE(ip_hash, 12); -}; - extern char dns_conf_server_ip[DNS_MAX_IPLEN]; extern char dns_conf_server_tcp_ip[DNS_MAX_IPLEN]; extern int dns_conf_tcp_idle_time; diff --git a/src/dns_server.c b/src/dns_server.c index f16aeb0..7105a7f 100644 --- a/src/dns_server.c +++ b/src/dns_server.c @@ -419,6 +419,36 @@ static int _dns_reply(struct dns_request *request) return _dns_reply_inpacket(request, inpacket, encode_len); } +static int _dns_setup_ipset(struct dns_request *request) +{ + struct dns_ipset_rule *ipset_rule = NULL; + int ret = 0; + + if (request->domain_rule == NULL) { + return 0; + } + + ipset_rule = request->domain_rule->rules[DOMAIN_RULE_IPSET]; + if (ipset_rule == NULL) { + return 0; + } + + if (request->has_ipv4 && request->qtype == DNS_T_A) { + ret |= ipset_add(ipset_rule->ipsetname, request->ipv4_addr, DNS_RR_A_LEN); + } + + if (request->has_ipv6 && request->qtype == DNS_T_AAAA) { + if (request->has_ipv4) { + ret |= ipset_add(ipset_rule->ipsetname, request->ipv4_addr, DNS_RR_A_LEN); + } + ret |= ipset_add(ipset_rule->ipsetname, request->ipv6_addr, DNS_RR_AAAA_LEN); + } + + tlog(TLOG_DEBUG, "IPSET-MATCH: domain:%s, ipset:%s, result: %d", request->domain, ipset_rule->ipsetname, ret); + + return ret; +} + int _dns_server_request_complete(struct dns_request *request) { char *cname = NULL; @@ -476,6 +506,7 @@ int _dns_server_request_complete(struct dns_request *request) return 0; } + _dns_setup_ipset(request); _dns_reply(request); return 0; diff --git a/src/util.c b/src/util.c index d2593bd..9bd21d4 100644 --- a/src/util.c +++ b/src/util.c @@ -2,11 +2,57 @@ #include #include #include +#include #include #include #include #include +#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_ADD 9 +#define IPSET_DEL 10 +#define IPSET_MAXNAMELEN 32 +#define IPSET_PROTOCOL 6 + +#define IPV6_ADDR_LEN 16 +#define IPV4_ADDR_LEN 4 + +#ifndef NFNETLINK_V0 +#define NFNETLINK_V0 0 +#endif + +#ifndef NLA_F_NESTED +#define NLA_F_NESTED (1 << 15) +#endif + +#ifndef NLA_F_NET_BYTEORDER +#define NLA_F_NET_BYTEORDER (1 << 14) +#endif + +#define NETLINK_ALIGN(len) (((len) + 3) & ~(3)) + +#define BUFF_SZ 256 + +struct ipset_netlink_attr { + unsigned short len; + unsigned short type; +}; + +struct ipset_netlink_msg { + unsigned char family; + unsigned char version; + __be16 res_id; +}; + +static int ipset_fd; + unsigned long get_tick_count(void) { struct timespec ts; @@ -159,7 +205,7 @@ char *reverse_string(char *output, char *input, int len) *output = 0; return output; } - + len--; while (len >= 0) { *output = *(input + len); @@ -171,3 +217,118 @@ char *reverse_string(char *output, char *input, int len) return begin; } + +static inline void _ipset_add_attr(struct nlmsghdr *netlink_head, uint16_t type, size_t len, const void *data) +{ + struct ipset_netlink_attr *attr = (void *)netlink_head + NETLINK_ALIGN(netlink_head->nlmsg_len); + uint16_t payload_len = NETLINK_ALIGN(sizeof(struct ipset_netlink_attr)) + len; + attr->type = type; + attr->len = payload_len; + memcpy((void *)attr + NETLINK_ALIGN(sizeof(struct ipset_netlink_attr)), data, len); + netlink_head->nlmsg_len += NETLINK_ALIGN(payload_len); +} + +static int _ipset_socket_init(void) +{ + if (ipset_fd > 0) { + return 0; + } + + ipset_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER); + + if (ipset_fd < 0) { + return -1; + } + + return 0; +} + +static int _ipset_operate(const char *ipsetname, const unsigned char addr[], int addr_len, int operate) +{ + struct nlmsghdr *netlink_head; + struct ipset_netlink_msg *netlink_msg; + struct ipset_netlink_attr *nested[2]; + char buffer[BUFF_SZ]; + uint8_t proto; + ssize_t rc; + int af = 0; + static const struct sockaddr_nl snl = {.nl_family = AF_NETLINK}; + + if (addr_len != IPV4_ADDR_LEN && addr_len != IPV6_ADDR_LEN) { + errno = EINVAL; + return -1; + } + + if (addr_len == IPV4_ADDR_LEN) { + af = AF_INET; + } else if (addr_len == IPV6_ADDR_LEN) { + af = AF_INET6; + } else { + errno = EINVAL; + return -1; + } + + if (_ipset_socket_init() != 0) { + return -1; + } + + if (strlen(ipsetname) >= IPSET_MAXNAMELEN) { + errno = ENAMETOOLONG; + return -1; + } + + memset(buffer, 0, BUFF_SZ); + + netlink_head = (struct nlmsghdr *)buffer; + netlink_head->nlmsg_len = NETLINK_ALIGN(sizeof(struct nlmsghdr)); + netlink_head->nlmsg_type = operate | (NFNL_SUBSYS_IPSET << 8); + netlink_head->nlmsg_flags = NLM_F_REQUEST; + + netlink_msg = (struct ipset_netlink_msg *)(buffer + netlink_head->nlmsg_len); + netlink_head->nlmsg_len += NETLINK_ALIGN(sizeof(struct ipset_netlink_msg)); + netlink_msg->family = af; + netlink_msg->version = NFNETLINK_V0; + netlink_msg->res_id = htons(0); + + proto = IPSET_PROTOCOL; + _ipset_add_attr(netlink_head, IPSET_ATTR_PROTOCOL, sizeof(proto), &proto); + _ipset_add_attr(netlink_head, IPSET_ATTR_SETNAME, strlen(ipsetname) + 1, ipsetname); + + nested[0] = (struct ipset_netlink_attr *)(buffer + NETLINK_ALIGN(netlink_head->nlmsg_len)); + netlink_head->nlmsg_len += NETLINK_ALIGN(sizeof(struct ipset_netlink_attr)); + nested[0]->type = NLA_F_NESTED | IPSET_ATTR_DATA; + nested[1] = (struct ipset_netlink_attr *)(buffer + NETLINK_ALIGN(netlink_head->nlmsg_len)); + netlink_head->nlmsg_len += NETLINK_ALIGN(sizeof(struct ipset_netlink_attr)); + nested[1]->type = NLA_F_NESTED | IPSET_ATTR_IP; + _ipset_add_attr(netlink_head, (af == AF_INET ? IPSET_ATTR_IPADDR_IPV4 : IPSET_ATTR_IPADDR_IPV6) | NLA_F_NET_BYTEORDER, addr_len, addr); + + nested[1]->len = (void *)buffer + NETLINK_ALIGN(netlink_head->nlmsg_len) - (void *)nested[1]; + nested[0]->len = (void *)buffer + NETLINK_ALIGN(netlink_head->nlmsg_len) - (void *)nested[0]; + + for (;;) { + rc = sendto(ipset_fd, buffer, netlink_head->nlmsg_len, 0, (struct sockaddr *)&snl, sizeof(snl)); + if (rc >= 0) { + break; + } + + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + struct timespec waiter; + waiter.tv_sec = 0; + waiter.tv_nsec = 10000; + nanosleep(&waiter, NULL); + continue; + } + } + + return rc; +} + +int ipset_add(const char *ipsetname, const unsigned char addr[], int addr_len) +{ + return _ipset_operate(ipsetname, addr, addr_len, IPSET_ADD); +} + +int ipset_del(const char *ipsetname, const unsigned char addr[], int addr_len) +{ + return _ipset_operate(ipsetname, addr, addr_len, IPSET_DEL); +} \ No newline at end of file diff --git a/src/util.h b/src/util.h index d529e58..1621905 100644 --- a/src/util.h +++ b/src/util.h @@ -22,4 +22,8 @@ char *reverse_string(char *output, char *input, int len); void print_stack(void); +int ipset_add(const char *ipsetname, const unsigned char addr[], int addr_len); + +int ipset_del(const char *ipsetname, const unsigned char addr[], int addr_len); + #endif \ No newline at end of file