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:
committed by
Topi Miettinen
parent
e8f1b50f27
commit
ab51fd9dbd
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user