Browse Source

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
pull/2827/head
Aven 3 years ago
committed by Max Lv
parent
commit
4bbbe212eb
5 changed files with 275 additions and 2 deletions
  1. 36
      configure.ac
  2. 2
      src/Makefile.am
  3. 3
      src/common.h
  4. 230
      src/server.c
  5. 6
      src/utils.c

36
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

2
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 \

3
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

230
src/server.c

@ -51,6 +51,20 @@
#define SET_INTERFACE
#endif
#ifdef USE_NFTABLES
#include <ctype.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <libmnl/libmnl.h>
#include <libnftnl/set.h>
/* 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++]);

6
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 <sets>] Add malicious IP into nftables sets.\n");
printf(
" sets spec: [<table1>:]<set1>[,[<table2>:]<set2>...]\n");
#endif
#endif
#ifndef MODULE_MANAGER
printf(

Loading…
Cancel
Save