network: firewall integration with NFT sets

New directives `NFTSet=`, `IPv4NFTSet=` and `IPv6NFTSet=` provide a method for
integrating configuration of dynamic networks into firewall rules with NFT
sets.

/etc/systemd/network/eth.network
```
[DHCPv4]
...
NFTSet=netdev:filter:eth_ipv4_address
```

```
table netdev filter {
        set eth_ipv4_address {
                type ipv4_addr
                flags interval
        }
        chain eth_ingress {
                type filter hook ingress device "eth0" priority filter; policy drop;
                ip saddr != @eth_ipv4_address drop
                accept
        }
}
```
```
sudo nft list set netdev filter eth_ipv4_address
table netdev filter {
        set eth_ipv4_address {
                type ipv4_addr
                flags interval
                elements = { 10.0.0.0/24 }
        }
}
```
This commit is contained in:
Topi Miettinen
2022-06-08 16:12:25 +00:00
committed by Topi Miettinen
parent e8f1b50f27
commit ab51fd9dbd
13 changed files with 792 additions and 7 deletions
+290 -7
View File
@@ -14,11 +14,13 @@
#include "sd-netlink.h"
#include "alloc-util.h"
#include "extract-word.h"
#include "firewall-util.h"
#include "firewall-util-private.h"
#include "in-addr-util.h"
#include "macro.h"
#include "socket-util.h"
#include "string-table.h"
#include "time-util.h"
#define NFT_SYSTEMD_DNAT_MAP_NAME "map_port_ipport"
@@ -848,9 +850,12 @@ static int nft_message_add_setelem_ip6range(
#define NFT_MASQ_MSGS 3
static int fw_nftables_add_masquerade_internal(
FirewallContext *ctx,
static int nft_set_element_op_in_addr(
sd_netlink *nfnl,
const char *table,
const char *set,
bool add,
int nfproto,
int af,
const union in_addr_union *source,
unsigned int source_prefixlen) {
@@ -865,14 +870,14 @@ static int fw_nftables_add_masquerade_internal(
if (af == AF_INET6 && source_prefixlen < 8)
return -EINVAL;
r = sd_nfnl_message_batch_begin(ctx->nfnl, &transaction[0]);
r = sd_nfnl_message_batch_begin(nfnl, &transaction[0]);
if (r < 0)
return r;
tsize = 1;
if (add)
r = sd_nfnl_nft_message_new_setelems_begin(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME);
r = sd_nfnl_nft_message_new_setelems_begin(nfnl, &transaction[tsize], nfproto, table, set);
else
r = sd_nfnl_nft_message_del_setelems_begin(ctx->nfnl, &transaction[tsize], af, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME);
r = sd_nfnl_nft_message_del_setelems_begin(nfnl, &transaction[tsize], nfproto, table, set);
if (r < 0)
goto out_unref;
@@ -885,12 +890,12 @@ static int fw_nftables_add_masquerade_internal(
++tsize;
assert(tsize < NFT_MASQ_MSGS);
r = sd_nfnl_message_batch_end(ctx->nfnl, &transaction[tsize]);
r = sd_nfnl_message_batch_end(nfnl, &transaction[tsize]);
if (r < 0)
return r;
++tsize;
r = nfnl_netlink_sendv(ctx->nfnl, transaction, tsize);
r = nfnl_netlink_sendv(nfnl, transaction, tsize);
out_unref:
while (tsize > 0)
@@ -898,6 +903,65 @@ out_unref:
return r < 0 ? r : 0;
}
static int nft_set_element_op_in_addr_open(
bool add,
const NFTSetContext *nft_set_context,
int af,
const union in_addr_union *address,
unsigned int prefixlen) {
_cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL;
_cleanup_free_ char *addr_str = NULL;
int r, nfproto;
const char *table, *set;
assert(nft_set_context);
nfproto = nft_set_context->nfproto;
table = nft_set_context->table;
assert(table);
set = nft_set_context->set;
assert(set);
r = sd_nfnl_socket_open(&nfnl);
if (r < 0)
return r;
r = nft_set_element_op_in_addr(nfnl, table, set,
add, nfproto, af, address, prefixlen);
(void) in_addr_prefix_to_string(af, address, prefixlen, &addr_str);
log_debug("%s NFT family %s table %s set %s IP addresss %s", add? "Added" : "Deleted",
nfproto_to_string(nfproto), table, set, strna(addr_str));
return r;
}
int nft_set_element_add_in_addr(
const NFTSetContext *nft_set_context,
int af,
const union in_addr_union *address,
unsigned int prefixlen) {
return nft_set_element_op_in_addr_open(true, nft_set_context, af, address, prefixlen);
}
int nft_set_element_del_in_addr(
const NFTSetContext *nft_set_context,
int af,
const union in_addr_union *address,
unsigned int prefixlen) {
return nft_set_element_op_in_addr_open(false, nft_set_context, af, address, prefixlen);
}
static int fw_nftables_add_masquerade_internal(
FirewallContext *ctx,
bool add,
int af,
const union in_addr_union *source,
unsigned int source_prefixlen) {
return nft_set_element_op_in_addr(ctx->nfnl, NFT_SYSTEMD_TABLE_NAME, NFT_SYSTEMD_MASQ_SET_NAME,
add, af, af, source, source_prefixlen);
}
int fw_nftables_add_masquerade(
FirewallContext *ctx,
bool add,
@@ -1071,3 +1135,222 @@ int fw_nftables_add_local_dnat(
/* table created anew; previous address already gone */
return fw_nftables_add_local_dnat_internal(ctx, add, af, protocol, local_port, remote, remote_port, NULL);
}
static const char *const nfproto_table[] = {
[NFPROTO_ARP] = "arp",
[NFPROTO_BRIDGE] = "bridge",
[NFPROTO_INET] = "inet",
[NFPROTO_IPV4] = "ip",
[NFPROTO_IPV6] = "ip6",
[NFPROTO_NETDEV] = "netdev",
};
DEFINE_STRING_TABLE_LOOKUP(nfproto, int);
#define NFT_SET_MSGS 3
static int nft_set_element_op(bool add, const NFTSetContext *nft_set_context, void *element, size_t element_size) {
_cleanup_(sd_netlink_unrefp) sd_netlink *nfnl = NULL;
sd_netlink_message *transaction[NFT_SET_MSGS] = {};
_cleanup_free_ uint32_t *serial = NULL;
size_t tsize;
int r, nfproto;
const char *table, *set;
assert(nft_set_context);
nfproto = nft_set_context->nfproto;
table = nft_set_context->table;
assert(table);
set = nft_set_context->set;
assert(set);
assert(element);
r = sd_nfnl_socket_open(&nfnl);
if (r < 0)
return r;
r = sd_nfnl_message_batch_begin(nfnl, &transaction[0]);
if (r < 0)
return r;
tsize = 1;
if (add)
r = sd_nfnl_nft_message_new_setelems_begin(nfnl, &transaction[tsize], nfproto, table, set);
else
r = sd_nfnl_nft_message_del_setelems_begin(nfnl, &transaction[tsize], nfproto, table, set);
if (r < 0)
goto out_unref;
r = sd_nfnl_nft_message_add_setelem(transaction[tsize], 0, element, element_size, NULL, 0);
if (r < 0)
return r;
r = sd_nfnl_nft_message_add_setelem_end(transaction[tsize]);
if (r < 0)
return r;
++tsize;
assert(tsize < ELEMENTSOF(transaction));
r = sd_nfnl_message_batch_end(nfnl, &transaction[tsize]);
if (r < 0)
return r;
++tsize;
r = sd_netlink_sendv(nfnl, transaction, tsize, &serial);
out_unref:
while (tsize > 0)
sd_netlink_message_unref(transaction[--tsize]);
return r < 0 ? r : 0;
}
int nft_set_element_add_uint32(const NFTSetContext *nft_set_context, uint32_t element) {
int r;
assert(nft_set_context);
r = nft_set_element_op(true, nft_set_context, &element, sizeof(element));
if (r == 0)
log_debug("Added NFT family %s table %s set %s element %d",
nfproto_to_string(nft_set_context->nfproto), nft_set_context->table, nft_set_context->set, element);
return r;
}
int nft_set_element_del_uint32(const NFTSetContext *nft_set_context, uint32_t element) {
int r;
assert(nft_set_context);
r = nft_set_element_op(false, nft_set_context, &element, sizeof(element));
if (r == 0)
log_debug("Deleted NFT family %s table %s set %s element %d",
nfproto_to_string(nft_set_context->nfproto), nft_set_context->table, nft_set_context->set, element);
return r;
}
int nft_set_element_add_uint64(const NFTSetContext *nft_set_context, uint64_t element) {
int r;
assert(nft_set_context);
r = nft_set_element_op(true, nft_set_context, &element, sizeof(element));
if (r == 0)
log_debug("Added NFT family %s table %s set %s element %"PRIu64,
nfproto_to_string(nft_set_context->nfproto), nft_set_context->table, nft_set_context->set, element);
return r;
}
int nft_set_element_del_uint64(const NFTSetContext *nft_set_context, uint64_t element) {
int r;
assert(nft_set_context);
r = nft_set_element_op(false, nft_set_context, &element, sizeof(element));
if (r == 0)
log_debug("Deleted NFT family %s table %s set %s element %"PRIu64,
nfproto_to_string(nft_set_context->nfproto), nft_set_context->table, nft_set_context->set, element);
return r;
}
NFTSetContext* nft_set_context_free_many(NFTSetContext *s, size_t *n) {
assert(n);
assert(s || *n == 0);
for (size_t i = 0; i < *n; i++) {
free(s[i].table);
free(s[i].set);
}
free(s);
*n = 0;
return NULL;
}
int nft_set_context_add(NFTSetContext **s, size_t *n, int nfproto, const char *table, const char *set) {
_cleanup_free_ char *table_dup = NULL, *set_dup = NULL;
assert(s);
assert(n);
table_dup = strdup(table);
if (!table_dup)
return -ENOMEM;
set_dup = strdup(set);
if (!set_dup)
return -ENOMEM;
NFTSetContext *c;
c = reallocarray(*s, *n + 1, sizeof(NFTSetContext));
if (!c)
return -ENOMEM;
*s = c;
c[(*n) ++] = (NFTSetContext) {
.nfproto = nfproto,
.table = TAKE_PTR(table_dup),
.set = TAKE_PTR(set_dup),
};
return 0;
}
int config_parse_nft_set_context(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
NFTSetContext **nft_set_context,
size_t *n) {
_cleanup_free_ char *family_str = NULL, *table = NULL, *set = NULL;
int nfproto, r;
assert(filename);
assert(lvalue);
assert(rvalue);
assert(nft_set_context);
if (isempty(rvalue)) {
nft_set_context_free_many(*nft_set_context, n);
return 0;
}
for (const char *p = rvalue;;) {
r = extract_many_words(&p, ":" WHITESPACE, EXTRACT_CUNESCAPE, &family_str, &table, &set, NULL);
if (r == -ENOMEM)
return log_oom();
if (r == 0)
return 0;
if (r != 3) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse IPvxNFT set, ignoring: %s", rvalue);
return 0;
}
nfproto = nfproto_from_string(family_str);
if (nfproto < 0) {
log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown NFT protocol family, ignoring: %s", family_str);
return 0;
}
if (nft_identifier_bad(table))
return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid table name %s, ignoring", table);
if (nft_identifier_bad(set))
return log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid set name %s, ignoring", set);
NFTSetContext *c;
c = reallocarray(*nft_set_context, *n + 1, sizeof(NFTSetContext));
if (!c)
return -ENOMEM;
*nft_set_context = c;
c[(*n) ++] = (NFTSetContext) {
.nfproto = nfproto,
.table = TAKE_PTR(table),
.set = TAKE_PTR(set),
};
}
return 0;
}
+40
View File
@@ -29,3 +29,43 @@ int fw_add_local_dnat(
const union in_addr_union *remote,
uint16_t remote_port,
const union in_addr_union *previous_remote);
struct NFTSetContext {
int nfproto;
char *table;
char *set;
};
typedef struct NFTSetContext NFTSetContext;
int nft_set_context_add(NFTSetContext **s, size_t *n, int nfproto, const char *table, const char *set);
NFTSetContext* nft_set_context_free_many(NFTSetContext *s, size_t *n);
int config_parse_nft_set_context(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
NFTSetContext **nft_set_context,
size_t *n);
const char *nfproto_to_string(int i) _const_;
int nfproto_from_string(const char *s) _pure_;
int nft_set_element_add_in_addr(
const NFTSetContext *nft_set_context,
int af,
const union in_addr_union *address,
unsigned int prefixlen);
int nft_set_element_del_in_addr(
const NFTSetContext *nft_set_context,
int af,
const union in_addr_union *address,
unsigned int prefixlen);
int nft_set_element_add_uint32(const NFTSetContext *nft_set_context, uint32_t element);
int nft_set_element_del_uint32(const NFTSetContext *nft_set_context, uint32_t element);
int nft_set_element_add_uint64(const NFTSetContext *nft_set_context, uint64_t element);
int nft_set_element_del_uint64(const NFTSetContext *nft_set_context, uint64_t element);