From 9fafb58a07226b2e8762f4b519c48aaa3123e7e2 Mon Sep 17 00:00:00 2001 From: "Alexander V. Buev" Date: Mon, 2 Dec 2024 13:02:16 +0300 Subject: [PATCH] added nftables support for ipset nftables support implemented for ipset Added following parameters for ipset module: engine - can be 'iptables' or 'nftables' for linux can only be 'pf' for bsd 'iptables' or 'pf' used as default table_v4, table_v6 - specify table name for nftables proto_v4, proto_v6 - specify protocol for nftables 'ipv6' 'ipv4' or 'inet' can be used --- ipset/ipset.c | 195 ++++++++++++++++++++++++++++++++++++++++++- ipset/ipset.h | 5 ++ util/config_file.c | 15 ++++ util/config_file.h | 5 ++ util/configlexer.lex | 5 ++ util/configparser.y | 79 +++++++++++++++++- 6 files changed, 300 insertions(+), 4 deletions(-) diff --git a/ipset/ipset.c b/ipset/ipset.c index 1ad2c09f4..94dc7c3ce 100644 --- a/ipset/ipset.c +++ b/ipset/ipset.c @@ -28,6 +28,8 @@ typedef intptr_t filter_dev; #include #include #include +#include +#include typedef struct mnl_socket * filter_dev; #endif @@ -177,8 +179,166 @@ static int add_to_ipset(filter_dev dev, const char *setname, const void *ipaddr, } return 0; } + +static struct nlmsghdr * +build_nft_hdr(char *buf, uint16_t type, uint16_t family, + uint16_t flags, uint32_t seq, uint16_t res_id) +{ + struct nlmsghdr *nlh; + struct nfgenmsg *nfh; + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = type; + nlh->nlmsg_flags = NLM_F_REQUEST | flags; + nlh->nlmsg_seq = seq; + + nfh = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); + nfh->nfgen_family = family; + nfh->version = NFNETLINK_V0; + nfh->res_id = htons(res_id); + + return nlh; +} + +static uint16_t parse_nft_proto(const char *protoname) +{ + uint16_t proto; + + if (!(protoname && protoname[0])) + proto = NFPROTO_INET; + else if (!strcmp(protoname, "inet")) + proto = NFPROTO_INET; + else if (!strcmp(protoname, "ipv4")) + proto = NFPROTO_IPV4; + else if (!strcmp(protoname, "ipv6")) + proto = NFPROTO_IPV6; + else + proto = NFPROTO_UNSPEC; + + return proto; +} + +static int add_to_nftset(filter_dev dev, struct ipset_env *ie, const void *ipaddr, int af) +{ + struct nlmsghdr *nlh; + struct nlattr *nested[3]; + static char b[BUFF_LEN]; + const char *tablename, *setname; + size_t l = 0, addr_size; + uint16_t nfproto = NFPROTO_UNSPEC; + int err; + + if (af == AF_INET) { + nfproto = ie->v4_proto; + tablename = ie->table_v4; + setname = ie->name_v4; + addr_size = sizeof(struct in_addr); + } else if (af == AF_INET6) { + nfproto = ie->v6_proto; + tablename = ie->table_v6; + setname = ie->name_v6; + addr_size = sizeof(struct in6_addr); + } else { + errno = EAFNOSUPPORT; + return -1; + } + + if (!(tablename && setname)) { + errno = EINVAL; + return -1; + } + + if ((strlen(setname) >= NFT_SET_MAXNAMELEN) || + (strlen(tablename) >= NFT_TABLE_MAXNAMELEN)) { + errno = ENAMETOOLONG; + return -1; + } + + nlh = build_nft_hdr(b, NFNL_MSG_BATCH_BEGIN, + NFPROTO_UNSPEC, 0, 1, NFNL_SUBSYS_NFTABLES); + if (!nlh) { + errno = ENOMEM; + return -1; + }; + + l += nlh->nlmsg_len; + + nlh = build_nft_hdr(b + l, (NFNL_SUBSYS_NFTABLES << 8) | NFT_MSG_NEWSETELEM, + nfproto, NLM_F_CREATE|NLM_F_EXCL, 2, 0); + if (!nlh) { + errno = ENOMEM; + return -1; + }; + + mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_TABLE, tablename); + mnl_attr_put_strz(nlh, NFTA_SET_ELEM_LIST_SET, setname); + mnl_attr_put_u32(nlh, NFTA_SET_ELEM_LIST_SET_ID, htonl(1)); + + nested[0] = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_LIST_ELEMENTS); + nested[1] = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_KEY); + nested[2] = mnl_attr_nest_start(nlh, NFTA_SET_ELEM_KEY); + + mnl_attr_put(nlh, NFTA_DATA_VALUE | NLA_F_NET_BYTEORDER, addr_size, ipaddr); + + mnl_attr_nest_end(nlh, nested[2]); + mnl_attr_nest_end(nlh, nested[1]); + mnl_attr_nest_end(nlh, nested[0]); + + l += nlh->nlmsg_len; + + nlh = build_nft_hdr(b + l, NFNL_MSG_BATCH_END, + AF_UNSPEC, 0, 3, NFNL_SUBSYS_NFTABLES); + if (!nlh) { + errno = ENOMEM; + return -1; + }; + + l += nlh->nlmsg_len; + + err = mnl_socket_sendto(dev, b, l); + if (err < 0) { + log_err("ipset: can't add address into %u %s->%s (%i)", + nfproto, tablename, setname, err); + errno = -err; + return -1; + } + + return 0; + +} #endif +#ifdef HAVE_NET_PFVAR_H + #define IPSET_ENGINE_PF (0) + #define IPSET_ENGINE_DEFAULT IPSET_ENGINE_PF +#else + #define IPSET_ENGINE_IPTABLES (0) + #define IPSET_ENGINE_NFTABLES (1) + #define IPSET_ENGINE_DEFAULT IPSET_ENGINE_IPTABLES +#endif + +static int parse_ipset_engine(const char *enginename) +{ + int engine; + + if (!(enginename && enginename[0])) + engine = IPSET_ENGINE_DEFAULT; + else if (!strcmp(enginename, "default")) + engine = IPSET_ENGINE_DEFAULT; +#ifdef HAVE_NET_PFVAR_H + else if (!strcmp(protoname, "pf")) + engine = IPSET_ENGINE_PF; +#else + else if (!strcmp(enginename, "iptables")) + engine = IPSET_ENGINE_IPTABLES; + else if (!strcmp(enginename, "nftables")) + engine = IPSET_ENGINE_NFTABLES; +#endif + else + engine = -1; + return engine; +} + static void ipset_add_rrset_data(struct ipset_env *ie, struct packed_rrset_data *d, const char* setname, int af, @@ -205,7 +365,15 @@ ipset_add_rrset_data(struct ipset_env *ie, snprintf(ip, sizeof(ip), "(inet_ntop_error)"); verbose(VERB_QUERY, "ipset: add %s to %s for %s", ip, setname, dname); } - ret = add_to_ipset((filter_dev)ie->dev, setname, rr_data + 2, af); + switch (ie->engine) { + default: + case IPSET_ENGINE_DEFAULT: + ret = add_to_ipset((filter_dev)ie->dev, setname, rr_data + 2, af); + break; + case IPSET_ENGINE_NFTABLES: + ret = add_to_nftset((filter_dev)ie->dev, ie, rr_data + 2, af); + break; + } if (ret < 0) { log_err("ipset: could not add %s into %s", dname, setname); @@ -376,6 +544,29 @@ int ipset_init(struct module_env* env, int id) { ipset_env->v4_enabled = !ipset_env->name_v4 || (strlen(ipset_env->name_v4) == 0) ? 0 : 1; ipset_env->v6_enabled = !ipset_env->name_v6 || (strlen(ipset_env->name_v6) == 0) ? 0 : 1; + ipset_env->table_v4 = env->cfg->ipset_table_v4; + ipset_env->table_v6 = env->cfg->ipset_table_v6; + ipset_env->engine = parse_ipset_engine(env->cfg->ipset_engine); + if (ipset_env->engine < 0) { + log_err("ipset: wrong engine %s specified", env->cfg->ipset_engine); + return 0; + } + + if (ipset_env->engine == IPSET_ENGINE_NFTABLES) { + ipset_env->v4_proto = parse_nft_proto(env->cfg->ipset_family_v4); + ipset_env->v6_proto = parse_nft_proto(env->cfg->ipset_family_v6); + + if (ipset_env->v4_proto == NFPROTO_UNSPEC || ipset_env->v6_proto == NFPROTO_UNSPEC) { + log_err("ipset: wrong proto specified"); + return 0; + } + + if (ipset_env->v4_enabled) + ipset_env->v4_enabled = (ipset_env->table_v4 && ipset_env->table_v4[0]) ? 1 : 0; + if (ipset_env->v6_enabled) + ipset_env->v6_enabled = (ipset_env->table_v6 && ipset_env->table_v6[0]) ? 1 : 0; + } + if ((ipset_env->v4_enabled < 1) && (ipset_env->v6_enabled < 1)) { log_err("ipset: set name no configuration?"); return 0; @@ -487,7 +678,7 @@ size_t ipset_get_mem(struct module_env *env, int id) { } /** - * The ipset function block + * The ipset function block */ static struct module_func_block ipset_block = { "ipset", diff --git a/ipset/ipset.h b/ipset/ipset.h index 195c7db93..e2bfd4c08 100644 --- a/ipset/ipset.h +++ b/ipset/ipset.h @@ -41,9 +41,14 @@ struct ipset_env { int v4_enabled; int v6_enabled; + int v4_proto; + int v6_proto; + int engine; const char *name_v4; const char *name_v6; + const char *table_v4; + const char *table_v6; }; struct ipset_qstate { diff --git a/util/config_file.c b/util/config_file.c index c1c55c529..26b7c227d 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -412,6 +412,11 @@ config_create(void) #ifdef USE_IPSET cfg->ipset_name_v4 = NULL; cfg->ipset_name_v6 = NULL; + cfg->ipset_engine = NULL; + cfg->ipset_table_v4 = NULL; + cfg->ipset_table_v6 = NULL; + cfg->ipset_family_v4 = NULL; + cfg->ipset_family_v6 = NULL; #endif cfg->ede = 0; cfg->iter_scrub_ns = 20; @@ -1388,6 +1393,11 @@ config_get_option(struct config_file* cfg, const char* opt, #ifdef USE_IPSET else O_STR(opt, "name-v4", ipset_name_v4) else O_STR(opt, "name-v6", ipset_name_v6) + else O_STR(opt, "engine", ipset_engine) + else O_STR(opt, "table-v4", ipset_table_v4) + else O_STR(opt, "table-v6", ipset_table_v6) + else O_STR(opt, "family-v4", ipset_family_v4) + else O_STR(opt, "family-v6", ipset_family_v6) #endif /* not here: * outgoing-permit, outgoing-avoid - have list of ports @@ -1768,6 +1778,11 @@ config_delete(struct config_file* cfg) #ifdef USE_IPSET free(cfg->ipset_name_v4); free(cfg->ipset_name_v6); + free(cfg->ipset_engine); + free(cfg->ipset_table_v4); + free(cfg->ipset_table_v6); + free(cfg->ipset_family_v4); + free(cfg->ipset_family_v6); #endif free(cfg); } diff --git a/util/config_file.h b/util/config_file.h index 2969f8433..b9f900684 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -770,6 +770,11 @@ struct config_file { #ifdef USE_IPSET char* ipset_name_v4; char* ipset_name_v6; + char* ipset_engine; + char* ipset_table_v4; + char* ipset_table_v6; + char* ipset_family_v4; + char* ipset_family_v6; #endif /** respond with Extended DNS Errors (RFC8914) */ int ede; diff --git a/util/configlexer.lex b/util/configlexer.lex index 4c0416f73..42a84979b 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -584,6 +584,11 @@ redis-logical-db{COLON} { YDVAR(1, VAR_CACHEDB_REDISLOGICALDB) } ipset{COLON} { YDVAR(0, VAR_IPSET) } name-v4{COLON} { YDVAR(1, VAR_IPSET_NAME_V4) } name-v6{COLON} { YDVAR(1, VAR_IPSET_NAME_V6) } +family-v4{COLON} { YDVAR(1, VAR_IPSET_FAMILY_V4) } +family-v6{COLON} { YDVAR(1, VAR_IPSET_FAMILY_V6) } +table-v4{COLON} { YDVAR(1, VAR_IPSET_TABLE_V4) } +table-v6{COLON} { YDVAR(1, VAR_IPSET_TABLE_V6) } +engine{COLON} { YDVAR(1, VAR_IPSET_ENGINE) } udp-upstream-without-downstream{COLON} { YDVAR(1, VAR_UDP_UPSTREAM_WITHOUT_DOWNSTREAM) } tcp-connection-limit{COLON} { YDVAR(2, VAR_TCP_CONNECTION_LIMIT) } answer-cookie{COLON} { YDVAR(1, VAR_ANSWER_COOKIE ) } diff --git a/util/configparser.y b/util/configparser.y index c10a5f475..0afd5b9c1 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -194,7 +194,7 @@ extern struct config_parser_state* cfg_parser; %token VAR_DISCARD_TIMEOUT VAR_WAIT_LIMIT VAR_WAIT_LIMIT_COOKIE %token VAR_WAIT_LIMIT_NETBLOCK VAR_WAIT_LIMIT_COOKIE_NETBLOCK %token VAR_STREAM_WAIT_SIZE VAR_TLS_CIPHERS VAR_TLS_CIPHERSUITES VAR_TLS_USE_SNI -%token VAR_IPSET VAR_IPSET_NAME_V4 VAR_IPSET_NAME_V6 +%token VAR_IPSET VAR_IPSET_NAME_V4 VAR_IPSET_NAME_V6 VAR_IPSET_ENGINE VAR_IPSET_FAMILY_V4 VAR_IPSET_FAMILY_V6 VAR_IPSET_TABLE_V4 VAR_IPSET_TABLE_V6 %token VAR_TLS_SESSION_TICKET_KEYS VAR_RPZ VAR_TAGS VAR_RPZ_ACTION_OVERRIDE %token VAR_RPZ_CNAME_OVERRIDE VAR_RPZ_LOG VAR_RPZ_LOG_NAME %token VAR_DYNLIB VAR_DYNLIB_FILE VAR_EDNS_CLIENT_STRING @@ -4119,7 +4119,7 @@ ipsetstart: VAR_IPSET ; contents_ipset: contents_ipset content_ipset | ; -content_ipset: ipset_name_v4 | ipset_name_v6 +content_ipset: ipset_name_v4 | ipset_name_v6 | ipset_engine | ipset_family_v4 | ipset_family_v6 | ipset_table_v4 | ipset_table_v6 ; ipset_name_v4: VAR_IPSET_NAME_V4 STRING_ARG { @@ -4151,6 +4151,81 @@ ipset_name_v6: VAR_IPSET_NAME_V6 STRING_ARG #endif } ; + ipset_family_v4: VAR_IPSET_FAMILY_V4 STRING_ARG + { + #ifdef USE_IPSET + OUTYY(("P(family-v4:%s)\n", $2)); + if(cfg_parser->cfg->ipset_family_v4) + yyerror("ipset family v4 override, there must be one " + "family name for ip v4"); + free(cfg_parser->cfg->ipset_family_v4); + cfg_parser->cfg->ipset_family_v4 = $2; + #else + OUTYY(("P(Compiled without ipset, ignoring)\n")); + free($2); + #endif + } + ; + ipset_family_v6: VAR_IPSET_FAMILY_V6 STRING_ARG + { + #ifdef USE_IPSET + OUTYY(("P(family-v6:%s)\n", $2)); + if(cfg_parser->cfg->ipset_family_v6) + yyerror("ipset family v6 override, there must be one " + "family name for ip v6"); + free(cfg_parser->cfg->ipset_family_v6); + cfg_parser->cfg->ipset_family_v6 = $2; + #else + OUTYY(("P(Compiled without ipset, ignoring)\n")); + free($2); + #endif + } + ; + ipset_table_v4: VAR_IPSET_TABLE_V4 STRING_ARG + { + #ifdef USE_IPSET + OUTYY(("P(table-v4:%s)\n", $2)); + if(cfg_parser->cfg->ipset_table_v4) + yyerror("ipset table v4 override, there must be one " + "table name for ip v4"); + free(cfg_parser->cfg->ipset_table_v4); + cfg_parser->cfg->ipset_table_v4 = $2; + #else + OUTYY(("P(Compiled without ipset, ignoring)\n")); + free($2); + #endif + } + ; + ipset_table_v6: VAR_IPSET_TABLE_V6 STRING_ARG + { + #ifdef USE_IPSET + OUTYY(("P(table-v6:%s)\n", $2)); + if(cfg_parser->cfg->ipset_table_v6) + yyerror("ipset table v6 override, there must be one " + "table name for ip v6"); + free(cfg_parser->cfg->ipset_table_v6); + cfg_parser->cfg->ipset_table_v6 = $2; + #else + OUTYY(("P(Compiled without ipset, ignoring)\n")); + free($2); + #endif + } + ; + ipset_engine: VAR_IPSET_ENGINE STRING_ARG + { + #ifdef USE_IPSET + OUTYY(("P(engine:%s)\n", $2)); + if(cfg_parser->cfg->ipset_engine) + yyerror("ipset engine override, there must be one " + "engine name"); + free(cfg_parser->cfg->ipset_engine); + cfg_parser->cfg->ipset_engine = $2; + #else + OUTYY(("P(Compiled without ipset, ignoring)\n")); + free($2); + #endif + } + ; %% /* parse helper routines could be here */