From 4bbbe212eb65e356be97a11bb98dbebb2e7923fc Mon Sep 17 00:00:00 2001 From: Aven Date: Tue, 29 Jun 2021 21:09:12 +0800 Subject: [PATCH] Add support of nftables firewall The change is to support the 'fail2ban' feature. Instead of blocking IPs by server itself, server just add malicious IPs to specified sets of nftables. So admin can configure rules to deal with those IPs. Notes: cap_net_admin capability is required. Example configuration: # nft add table ip fail2ban # nft add chain ip fail2ban input { type filter hook input priority 0 \; } # nft add set ip fail2ban badips { type ipv4_addr \; flags dynamic, timeout \; timeout 1h \; } # nft add rule ip fail2ban input ip saddr @badips drop # ss-server -c config.json --nftables-sets badips --- configure.ac | 36 ++++++++ src/Makefile.am | 2 +- src/common.h | 3 +- src/server.c | 230 ++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.c | 6 ++ 5 files changed, 275 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 207c0414..84d6e828 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 b81ced7a..c255b608 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 6b8aeba3..66e985cd 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 585ae807..6447e846 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 16defb28..4abeb59d 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(