Skip to content

Commit

Permalink
Add support of nftables firewall
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Aven authored and Max Lv committed Jul 1, 2021
1 parent d1171f4 commit 4bbbe21
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 2 deletions.
36 changes: 36 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 1 addition & 1 deletion src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
3 changes: 2 additions & 1 deletion src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 230 additions & 0 deletions src/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
{
Expand All @@ -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
Expand Down Expand Up @@ -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 }
};
Expand Down Expand Up @@ -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++]);
Expand Down
6 changes: 6 additions & 0 deletions src/utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down

0 comments on commit 4bbbe21

Please sign in to comment.