|
@@ -0,0 +1,378 @@
|
|
|
+/*************************************************************************
|
|
|
+ *
|
|
|
+ * Copyright (C) 2018-2022 Ruilin Peng (Nick) <[email protected]>.
|
|
|
+ *
|
|
|
+ * smartdns 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, either version 3 of the License, or
|
|
|
+ * (at your option) any later version.
|
|
|
+ *
|
|
|
+ * smartdns 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/>.
|
|
|
+ */
|
|
|
+
|
|
|
+#define _GNU_SOURCE /* See feature_test_macros(7) */
|
|
|
+#include "nftset.h"
|
|
|
+#include "../dns_conf.h"
|
|
|
+#include "../tlog.h"
|
|
|
+#include <errno.h>
|
|
|
+#include <linux/netfilter.h>
|
|
|
+#include <linux/netfilter/nf_tables.h>
|
|
|
+#include <linux/netfilter/nfnetlink.h>
|
|
|
+#include <linux/netlink.h>
|
|
|
+#include <linux/rtnetlink.h>
|
|
|
+#include <memory.h>
|
|
|
+#include <poll.h>
|
|
|
+#include <signal.h>
|
|
|
+#include <stdint.h>
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <sys/socket.h>
|
|
|
+#include <sys/types.h>
|
|
|
+#include <time.h>
|
|
|
+#include <unistd.h>
|
|
|
+
|
|
|
+struct nlmsgreq {
|
|
|
+ struct nlmsghdr h;
|
|
|
+ struct nfgenmsg m;
|
|
|
+};
|
|
|
+
|
|
|
+enum { PAYLOAD_MAX = 2048 };
|
|
|
+
|
|
|
+static int nftset_fd;
|
|
|
+
|
|
|
+static struct rtattr *_nftset_nlmsg_tail(struct nlmsghdr *n)
|
|
|
+{
|
|
|
+ return (struct rtattr *)((uint8_t *)n + NLMSG_ALIGN(n->nlmsg_len));
|
|
|
+}
|
|
|
+
|
|
|
+static int _nftset_addattr(struct nlmsghdr *n, int maxlen, __u16 type, const void *data, __u16 alen)
|
|
|
+{
|
|
|
+ const __u16 len = RTA_LENGTH(alen);
|
|
|
+ const ssize_t newlen = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
|
|
|
+
|
|
|
+ if (newlen > maxlen) {
|
|
|
+ errno = ENOSPC;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ struct rtattr *attr = _nftset_nlmsg_tail(n);
|
|
|
+ attr->rta_len = len;
|
|
|
+ attr->rta_type = type;
|
|
|
+
|
|
|
+ void *rta_data = RTA_DATA(attr);
|
|
|
+
|
|
|
+ memcpy(rta_data, data, alen);
|
|
|
+
|
|
|
+ n->nlmsg_len = newlen;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int _nftset_addattr_string(struct nlmsghdr *n, int maxlen, __u16 type, const char *s)
|
|
|
+{
|
|
|
+ return _nftset_addattr(n, maxlen, type, s, strlen(s) + 1);
|
|
|
+}
|
|
|
+
|
|
|
+static int __attribute__((unused)) _nftset_addattr_uint32(struct nlmsghdr *n, int maxlen, __u16 type, const uint32_t v)
|
|
|
+{
|
|
|
+ return _nftset_addattr(n, maxlen, type, &v, sizeof(uint32_t));
|
|
|
+}
|
|
|
+
|
|
|
+static int __attribute__((unused)) _nftset_addattr_uint16(struct nlmsghdr *n, int maxlen, __u16 type, const uint16_t v)
|
|
|
+{
|
|
|
+ return _nftset_addattr(n, maxlen, type, &v, sizeof(uint16_t));
|
|
|
+}
|
|
|
+
|
|
|
+static int __attribute__((unused)) _nftset_addattr_uint8(struct nlmsghdr *n, int maxlen, __u16 type, const uint8_t v)
|
|
|
+{
|
|
|
+ return _nftset_addattr(n, maxlen, type, &v, sizeof(uint8_t));
|
|
|
+}
|
|
|
+
|
|
|
+static struct rtattr *_nftset_addattr_nest(struct nlmsghdr *n, int maxlen, __u16 type)
|
|
|
+{
|
|
|
+ struct rtattr *attr = _nftset_nlmsg_tail(n);
|
|
|
+
|
|
|
+ if (-1 == _nftset_addattr(n, maxlen, type, NULL, 0)) {
|
|
|
+ return NULL;
|
|
|
+ }
|
|
|
+
|
|
|
+ return attr;
|
|
|
+}
|
|
|
+
|
|
|
+static void _nftset_addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest)
|
|
|
+{
|
|
|
+ const void *tail = _nftset_nlmsg_tail(n);
|
|
|
+ nest->rta_len = (uint8_t *)tail - (uint8_t *)nest;
|
|
|
+}
|
|
|
+
|
|
|
+static int _nftset_start_batch(void *buf, void **nextbuf)
|
|
|
+{
|
|
|
+ struct nlmsgreq *req = (struct nlmsgreq *)buf;
|
|
|
+ memset(buf, 0, sizeof(struct nlmsgreq));
|
|
|
+
|
|
|
+ req->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg));
|
|
|
+ req->h.nlmsg_flags = NLM_F_REQUEST;
|
|
|
+ req->h.nlmsg_type = NFNL_MSG_BATCH_BEGIN;
|
|
|
+ req->h.nlmsg_seq = time(NULL);
|
|
|
+
|
|
|
+ req->m.res_id = NFNL_SUBSYS_NFTABLES;
|
|
|
+
|
|
|
+ if (nextbuf) {
|
|
|
+ *nextbuf = (uint8_t *)buf + req->h.nlmsg_len;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int _nftset_end_batch(void *buf, void **nextbuf)
|
|
|
+{
|
|
|
+ struct nlmsgreq *req = (struct nlmsgreq *)buf;
|
|
|
+ memset(buf, 0, sizeof(struct nlmsgreq));
|
|
|
+
|
|
|
+ req->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg));
|
|
|
+ req->h.nlmsg_flags = NLM_F_REQUEST;
|
|
|
+ req->h.nlmsg_type = NFNL_MSG_BATCH_END;
|
|
|
+ req->h.nlmsg_seq = time(NULL);
|
|
|
+
|
|
|
+ req->m.res_id = NFNL_SUBSYS_NFTABLES;
|
|
|
+
|
|
|
+ if (nextbuf) {
|
|
|
+ *nextbuf = (uint8_t *)buf + req->h.nlmsg_len;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int _nftset_socket_init(void)
|
|
|
+{
|
|
|
+ struct sockaddr_nl addr = {0};
|
|
|
+ addr.nl_family = AF_NETLINK;
|
|
|
+ addr.nl_pid = 0;
|
|
|
+ int fd = 0;
|
|
|
+
|
|
|
+ if (nftset_fd > 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ fd = socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, NETLINK_NETFILTER);
|
|
|
+ if (fd < 0) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bind(fd, (struct sockaddr *)(&addr), sizeof(addr)) < 0) {
|
|
|
+ close(fd);
|
|
|
+ return -2;
|
|
|
+ }
|
|
|
+
|
|
|
+ nftset_fd = fd;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int _nftset_socket_send(void *msg, int msg_len)
|
|
|
+{
|
|
|
+ char recvbuff[1024];
|
|
|
+ int ret = -1;
|
|
|
+ struct pollfd pfds;
|
|
|
+ int do_recv = 0;
|
|
|
+ int last_errno = 0;
|
|
|
+
|
|
|
+ if (_nftset_socket_init() != 0) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (;;) {
|
|
|
+ int len = send(nftset_fd, msg, msg_len, 0);
|
|
|
+ if (len == msg_len) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
|
|
|
+ struct timespec waiter;
|
|
|
+ waiter.tv_sec = 0;
|
|
|
+ waiter.tv_nsec = 10000;
|
|
|
+ nanosleep(&waiter, NULL);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (dns_conf_nftset_debug_enable == 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ pfds.fd = nftset_fd;
|
|
|
+ pfds.events = POLLIN;
|
|
|
+ pfds.revents = 0;
|
|
|
+ ret = poll(&pfds, 1, 100);
|
|
|
+ if (ret <= 0) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((pfds.revents & POLLIN) == 0) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ memset(recvbuff, 0, sizeof(recvbuff));
|
|
|
+ for (;;) {
|
|
|
+ ret = recv(nftset_fd, recvbuff, 1024, 0);
|
|
|
+ if (ret < 0) {
|
|
|
+ if (errno == EAGAIN && do_recv == 1) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (errno == EAGAIN && last_errno != 0) {
|
|
|
+ errno = last_errno;
|
|
|
+ }
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ do_recv = 1;
|
|
|
+
|
|
|
+ struct nlmsghdr *nlh = (struct nlmsghdr *)recvbuff;
|
|
|
+ if (nlh->nlmsg_type == NLMSG_ERROR) {
|
|
|
+ struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);
|
|
|
+ if (err->error != 0) {
|
|
|
+ errno = -err->error;
|
|
|
+ last_errno = errno;
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int _nftset_del_element(const char *table_name, const char *setname, const void *data, int data_len, void *buf,
|
|
|
+ void **nextbuf)
|
|
|
+{
|
|
|
+ struct nlmsgreq *req = (struct nlmsgreq *)buf;
|
|
|
+ memset(buf, 0, sizeof(struct nlmsgreq));
|
|
|
+
|
|
|
+ req->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg));
|
|
|
+ req->h.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;
|
|
|
+ req->h.nlmsg_type = NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_DELSETELEM;
|
|
|
+ req->h.nlmsg_seq = time(NULL);
|
|
|
+
|
|
|
+ if (dns_conf_nftset_debug_enable) {
|
|
|
+ req->h.nlmsg_flags |= NLM_F_ACK;
|
|
|
+ }
|
|
|
+
|
|
|
+ req->m.nfgen_family = NFPROTO_INET;
|
|
|
+
|
|
|
+ struct nlmsghdr *n = &req->h;
|
|
|
+
|
|
|
+ _nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_TABLE, table_name);
|
|
|
+ _nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_SET, setname);
|
|
|
+ struct rtattr *nest_list = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_LIST_ELEMENTS);
|
|
|
+ struct rtattr *nest_elem = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED);
|
|
|
+
|
|
|
+ struct rtattr *nest_elem_key = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_KEY);
|
|
|
+ _nftset_addattr(n, PAYLOAD_MAX, NFTA_DATA_VALUE, data, data_len);
|
|
|
+ _nftset_addattr_nest_end(n, nest_elem_key);
|
|
|
+ _nftset_addattr_nest_end(n, nest_elem);
|
|
|
+ _nftset_addattr_nest_end(n, nest_list);
|
|
|
+
|
|
|
+ if (nextbuf) {
|
|
|
+ *nextbuf = (uint8_t *)buf + req->h.nlmsg_len;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int _nftset_add_element(const char *table_name, const char *setname, const void *data, int data_len,
|
|
|
+ unsigned long timeout, void *buf, void **nextbuf)
|
|
|
+{
|
|
|
+ struct nlmsgreq *req = (struct nlmsgreq *)buf;
|
|
|
+ memset(buf, 0, sizeof(struct nlmsgreq));
|
|
|
+
|
|
|
+ req->h.nlmsg_len = NLMSG_LENGTH(sizeof(struct nfgenmsg));
|
|
|
+ req->h.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;
|
|
|
+ req->h.nlmsg_type = NFNL_SUBSYS_NFTABLES << 8 | NFT_MSG_NEWSETELEM;
|
|
|
+ req->h.nlmsg_seq = time(NULL);
|
|
|
+
|
|
|
+ if (dns_conf_nftset_debug_enable) {
|
|
|
+ req->h.nlmsg_flags |= NLM_F_ACK;
|
|
|
+ }
|
|
|
+
|
|
|
+ req->m.nfgen_family = NFPROTO_INET;
|
|
|
+
|
|
|
+ struct nlmsghdr *n = &req->h;
|
|
|
+
|
|
|
+ _nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_TABLE, table_name);
|
|
|
+ _nftset_addattr_string(n, PAYLOAD_MAX, NFTA_SET_ELEM_LIST_SET, setname);
|
|
|
+ struct rtattr *nest_list = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_LIST_ELEMENTS);
|
|
|
+ struct rtattr *nest_elem = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED);
|
|
|
+
|
|
|
+ struct rtattr *nest_elem_key = _nftset_addattr_nest(n, PAYLOAD_MAX, NLA_F_NESTED | NFTA_SET_ELEM_KEY);
|
|
|
+ _nftset_addattr(n, PAYLOAD_MAX, NFTA_DATA_VALUE, data, data_len);
|
|
|
+ _nftset_addattr_nest_end(n, nest_elem_key);
|
|
|
+
|
|
|
+ if (timeout > 0) {
|
|
|
+ uint64_t timeout_value = htobe64(timeout * 1000);
|
|
|
+ _nftset_addattr(n, PAYLOAD_MAX, NFTA_SET_ELEM_TIMEOUT, &timeout_value, sizeof(timeout_value));
|
|
|
+ }
|
|
|
+
|
|
|
+ _nftset_addattr_nest_end(n, nest_elem);
|
|
|
+ _nftset_addattr_nest_end(n, nest_list);
|
|
|
+
|
|
|
+ if (nextbuf) {
|
|
|
+ *nextbuf = (uint8_t *)buf + req->h.nlmsg_len;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int nftset_del(const char *familyname, const char *tablename, const char *setname, const unsigned char addr[],
|
|
|
+ int addr_len)
|
|
|
+{
|
|
|
+ uint8_t buf[PAYLOAD_MAX];
|
|
|
+ void *next = buf;
|
|
|
+ int buffer_len = 0;
|
|
|
+
|
|
|
+ _nftset_start_batch(next, &next);
|
|
|
+ _nftset_del_element(tablename, setname, addr, addr_len, next, &next);
|
|
|
+ _nftset_end_batch(next, &next);
|
|
|
+ buffer_len = (uint8_t *)next - buf;
|
|
|
+
|
|
|
+ int ret = _nftset_socket_send(buf, buffer_len);
|
|
|
+ if (ret != 0 && errno != ENOENT) {
|
|
|
+ tlog(TLOG_ERROR, "nftset delete failed, family:%s, table:%s, set:%s, error:%s", familyname, tablename, setname,
|
|
|
+ strerror(errno));
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int nftset_add(const char *familyname, const char *tablename, const char *setname, const unsigned char addr[],
|
|
|
+ int addr_len, unsigned long timeout)
|
|
|
+{
|
|
|
+ uint8_t buf[PAYLOAD_MAX];
|
|
|
+ void *next = buf;
|
|
|
+ int buffer_len = 0;
|
|
|
+ int ret = -1;
|
|
|
+
|
|
|
+ if (dns_conf_nftset_timeout_enable == 0) {
|
|
|
+ timeout = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ nftset_del(familyname, tablename, setname, addr, addr_len);
|
|
|
+ _nftset_start_batch(next, &next);
|
|
|
+ _nftset_add_element(tablename, setname, addr, addr_len, timeout, next, &next);
|
|
|
+ _nftset_end_batch(next, &next);
|
|
|
+ buffer_len = (uint8_t *)next - buf;
|
|
|
+
|
|
|
+ ret = _nftset_socket_send(buf, buffer_len);
|
|
|
+ if (ret != 0) {
|
|
|
+ tlog(TLOG_ERROR, "nftset add failed, family:%s, table:%s, set:%s, error:%s", familyname, tablename, setname,
|
|
|
+ strerror(errno));
|
|
|
+ }
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|