diff --git a/configure.ac b/configure.ac index 207c04146..84d6e8283 100755 --- a/configure.ac +++ b/configure.ac @@ -272,4 +272,40 @@ if test x"$enable_connmarktos" = "xyes" ; then fi fi +AC_ARG_ENABLE(nftables, +[AS_HELP_STRING(--enable-nftables, report malicious IP to nftables)], +[ enable_nftables="$enableval" ]) + +if test x"$enable_nftables" = "xyes" ; then + AC_MSG_NOTICE([Linux nftables support requested by --enable-nftables: ${enable_nftables}]) + if test "x$enable_nftables" != "xno"; then + PKG_CHECK_MODULES([NFTABLES], [libmnl libnftnl],, + [AC_SEARCH_LIBS([mnl_socket_open], [mnl],,[ + if test x"$enable_nftables" = "xyes"; then + AC_MSG_ERROR([--enable-nftables specified but libmnl library not found]) + fi + with_nftables=no], [-lmnl]) + AC_CHECK_HEADERS([libmnl/libmnl.h],,[ + if test x"$enable_nftables" = "xyes"; then + AC_MSG_ERROR([--enable-nftables specified but libmnl headers not found]) + fi + with_nftables=no]) + AC_SEARCH_LIBS([nftnl_nlmsg_build_hdr], [nftnl],,[ + if test x"$enable_nftables" = "xyes"; then + AC_MSG_ERROR([--enable-nftables specified but libnftnl library not found]) + fi + with_nftables=no], [-lnftnl]) + AC_CHECK_HEADERS([libnftnl/common.h],,[ + if test x"$enable_nftables" = "xyes"; then + AC_MSG_ERROR([--enable-nftables specified but libnftnl headers not found]) + fi + with_nftables=no])]) + # If nothing is broken; enable the libraries usage. + if test "x$with_nftables" != "xno"; then + with_nftables=yes + AC_DEFINE(USE_NFTABLES, 1, [Enable support for nftables firewall]) + fi + fi +fi + AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index b81ced7aa..c255b608d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -9,7 +9,7 @@ AM_CFLAGS += -I$(top_srcdir)/libcork/include endif AM_CFLAGS += $(LIBPCRE_CFLAGS) -SS_COMMON_LIBS = $(INET_NTOP_LIB) $(LIBPCRE_LIBS) $(NETFILTER_CONNTRACK_LIBS) +SS_COMMON_LIBS = $(INET_NTOP_LIB) $(LIBPCRE_LIBS) $(NETFILTER_CONNTRACK_LIBS) $(NFTABLES_LIBS) if !USE_SYSTEM_SHARED_LIB SS_COMMON_LIBS += $(top_builddir)/libbloom/libbloom.la \ $(top_builddir)/libipset/libipset.la \ diff --git a/src/common.h b/src/common.h index 6b8aeba31..66e985cd4 100644 --- a/src/common.h +++ b/src/common.h @@ -74,7 +74,8 @@ enum { GETOPT_VAL_TCP_INCOMING_SNDBUF, GETOPT_VAL_TCP_INCOMING_RCVBUF, GETOPT_VAL_TCP_OUTGOING_SNDBUF, - GETOPT_VAL_TCP_OUTGOING_RCVBUF + GETOPT_VAL_TCP_OUTGOING_RCVBUF, + GETOPT_VAL_NFTABLES_SETS }; #endif // _COMMON_H diff --git a/src/server.c b/src/server.c index 585ae8079..6447e8465 100644 --- a/src/server.c +++ b/src/server.c @@ -51,6 +51,20 @@ #define SET_INTERFACE #endif +#ifdef USE_NFTABLES +#include +#include +#include +#include +#include +/* the datatypes enum is picked from libnftables/datatype.h + to avoid to depend libnftables */ +enum datatypes { + TYPE_IPADDR = 7, + TYPE_IP6ADDR +}; +#endif + #include "netutils.h" #include "utils.h" #include "acl.h" @@ -278,6 +292,207 @@ stop_server(EV_P_ server_t *server) server->stage = STAGE_STOP; } +#ifdef USE_NFTABLES +struct nftbl_set_info { + uint32_t family; + char *table; + char *name; + uint32_t type; +}* nftbl_badip_sets[16]; + +static struct nftnl_set * +nftbl_build_set(const char* table, const char* name, void* addr, size_t len) +{ + struct nftnl_set *set = nftnl_set_alloc(); + if (set == NULL) return NULL; + nftnl_set_set_str(set, NFTNL_SET_TABLE, table); + nftnl_set_set_str(set, NFTNL_SET_NAME, name); + + struct nftnl_set_elem *elem = nftnl_set_elem_alloc(); + if (elem == NULL) { + nftnl_set_free(set); + return NULL; + } + nftnl_set_elem_set(elem, NFTNL_SET_ELEM_KEY, addr, len); + nftnl_set_elem_add(set, elem); + return set; +} + +static uint32_t +nftbl_build_nlmsg(void* buf, size_t *len, uint32_t family, + struct nftnl_set *set) +{ + uint32_t seq = time(NULL); + struct nlmsghdr *nlh; + struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(buf, *len); + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq); + mnl_nlmsg_batch_next(batch); + nlh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), + NFT_MSG_NEWSETELEM, family, + NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK, + ++seq); + nftnl_set_elems_nlmsg_build_payload(nlh, set); + mnl_nlmsg_batch_next(batch); + nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq + 1); + mnl_nlmsg_batch_next(batch); + *len = mnl_nlmsg_batch_size(batch); + mnl_nlmsg_batch_stop(batch); + return seq; +} + +static int +nftbl_send_request(void *request, size_t len, uint32_t seq, + mnl_cb_t cb, void *data) +{ + struct mnl_socket *nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) return -1; + + int ret = -1; + uint8_t buf[MNL_SOCKET_BUFFER_SIZE]; + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) == 0 && + mnl_socket_sendto(nl, request, len) >= 0) { + uint32_t portid = mnl_socket_get_portid(nl); + while ((ret = mnl_socket_recvfrom(nl, buf, sizeof(buf))) > 0) { + ret = mnl_cb_run(buf, ret, seq, portid, cb, data); + if (ret != MNL_CB_OK) + break; + } + mnl_socket_close(nl); + } + return ret; +} + +static void +nftbl_report_addr(const struct sockaddr* addr) +{ + uint32_t type; + void* data; + size_t size; + if (addr->sa_family == AF_INET) { + type = TYPE_IPADDR; + data = &((struct sockaddr_in*)addr)->sin_addr; + size = sizeof(struct in_addr); + } else if (addr->sa_family == AF_INET6) { + type = TYPE_IP6ADDR; + data = &((struct sockaddr_in6*)addr)->sin6_addr; + size = sizeof(struct in6_addr); + } else { + return; + } + + char buf[MNL_SOCKET_BUFFER_SIZE]; + for (int i = 0; nftbl_badip_sets[i]; ++i) { + struct nftbl_set_info* si = nftbl_badip_sets[i]; + struct nftnl_set *set; + if (si->type == type && + (set = nftbl_build_set(si->table, si->name, data, size))) { + size_t len = sizeof(buf); + uint32_t seq = nftbl_build_nlmsg(buf, &len, si->family, set); + nftnl_set_free(set); + if (nftbl_send_request(buf, len, seq, NULL, NULL) < 0 && + errno != EEXIST) + ERROR("nftbl_report_addr"); + } + } +} + +static int +nftbl_check_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nftnl_set *set = (struct nftnl_set*)data; + if (nftnl_set_nlmsg_parse(nlh, set) < 0) + return MNL_CB_ERROR; + + uint32_t type = nftnl_set_get_u32(set, NFTNL_SET_KEY_TYPE); + if (type != TYPE_IPADDR && type != TYPE_IP6ADDR) + return MNL_CB_OK; + + uint32_t len; + const char *name = nftnl_set_get_data(set, NFTNL_SET_NAME, &len); + for (int i = 0; nftbl_badip_sets[i]; ++i) { + struct nftbl_set_info* si = nftbl_badip_sets[i]; + if (!memcmp(name, si->name, len)) { + name = nftnl_set_get_data(set, NFTNL_SET_TABLE, &len); + if (!si->table) { + size_t l = strlen(si->name) + 1; + si = realloc(si, sizeof(*si) + l + len); + si->name = (char*)(si + 1); + si->table = memcpy(si->name + l, name, len); + nftbl_badip_sets[i] = si; + } else if (memcmp(name, si->table, len)) { + continue; /* table name not match */ + } + si->family = nftnl_set_get_u32(set, NFTNL_SET_FAMILY); + si->type = type; + } + } + return MNL_CB_OK; +} + +static int +nftbl_check(void) +{ + struct nftnl_set *set = nftnl_set_alloc(); + if (!set) return -1; + + int ret; + char buf[MNL_SOCKET_BUFFER_SIZE]; + uint32_t seq = time(NULL); + struct nlmsghdr *nlh; + nlh = nftnl_set_nlmsg_build_hdr(buf, NFT_MSG_GETSET, NFPROTO_UNSPEC, + NLM_F_DUMP|NLM_F_ACK, seq); + nftnl_set_nlmsg_build_payload(nlh, set); + ret = nftbl_send_request(nlh, nlh->nlmsg_len, seq, nftbl_check_cb, set); + nftnl_set_free(set); + if (ret < 0) return ret; + + for (int i = 0; nftbl_badip_sets[i]; ++i) { + struct nftbl_set_info* si = nftbl_badip_sets[i]; + if (si->family == NFPROTO_UNSPEC) { + if (si->table) + LOGE("set '%s' not found in table '%s'", si->name, si->table); + else + LOGE("set '%s' not found", si->name); + ret = -1; + } + } + if (ret < 0) + FATAL("Check nftables configuration."); + return ret; +} + +static int +nftbl_init(const char* set_str) +{ + struct nftbl_set_info* si; + const char *p0 = set_str, *p = p0, *d = NULL; + int i = 0; + do { + if (*p == ':') { + d = p; + } else if (*p == ',' || *p == '\0') { + size_t l = p - p0 + 1; + si = malloc(sizeof(*si) + l); + memset(si, 0, sizeof(*si)); + si->name = memcpy(si + 1, p0, l); + si->name[l - 1] = '\0'; + if (d) { + si->table = si->name; + si->name = si->table + (d - p0); + *(si->name++) = '\0'; + d = NULL; + } + nftbl_badip_sets[i++] = si; + if (i == sizeof(nftbl_badip_sets) / sizeof(*si) - 1) + break; + while (*p && isspace(*(++p))); + p0 = p; + } + } while (*(p++)); + return nftbl_check(); +} +#endif + static void report_addr(int fd, const char *info) { @@ -286,6 +501,13 @@ report_addr(int fd, const char *info) if (peer_name != NULL) { LOGE("failed to handshake with %s: %s", peer_name, info); } + +#ifdef USE_NFTABLES + struct sockaddr_in6 addr; + socklen_t len = sizeof(struct sockaddr_in6); + if (!getpeername(fd, (struct sockaddr *)&addr, &len)) + nftbl_report_addr((struct sockaddr *)&addr); +#endif } int @@ -1614,6 +1836,9 @@ main(int argc, char **argv) { "key", required_argument, NULL, GETOPT_VAL_KEY }, #ifdef __linux__ { "mptcp", no_argument, NULL, GETOPT_VAL_MPTCP }, +#ifdef USE_NFTABLES + { "nftables-sets", required_argument, NULL, GETOPT_VAL_NFTABLES_SETS }, +#endif #endif { NULL, 0, NULL, 0 } }; @@ -1671,6 +1896,11 @@ main(int argc, char **argv) case GETOPT_VAL_TCP_OUTGOING_RCVBUF: tcp_outgoing_rcvbuf = atoi(optarg); break; +#ifdef USE_NFTABLES + case GETOPT_VAL_NFTABLES_SETS: + nftbl_init(optarg); + break; +#endif case 's': if (server_num < MAX_REMOTE_NUM) { parse_addr(optarg, &server_addr[server_num++]); diff --git a/src/utils.c b/src/utils.c index 16defb28a..4abeb59d9 100644 --- a/src/utils.c +++ b/src/utils.c @@ -414,6 +414,12 @@ usage() #ifdef __linux__ printf( " [--mptcp] Enable Multipath TCP on MPTCP Kernel.\n"); +#ifdef USE_NFTABLES + printf( + " [--nftables-sets ] Add malicious IP into nftables sets.\n"); + printf( + " sets spec: [:][,[:]...]\n"); +#endif #endif #ifndef MODULE_MANAGER printf(