From 0ed79a2f0fc9b20284c6d13f0c3f5d18cec9d832 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Fri, 28 Nov 2025 09:14:08 -0400 Subject: [PATCH] MT#55283 add hand-rolled netlink layer Change-Id: I3af7dcf0ed109db984c7eafca260eff89ec2b852 --- lib/netfilter_api.c | 436 ++++++++++++++++++++++++++++++++++++++++++++ lib/netfilter_api.h | 55 ++++++ 2 files changed, 491 insertions(+) create mode 100644 lib/netfilter_api.c create mode 100644 lib/netfilter_api.h diff --git a/lib/netfilter_api.c b/lib/netfilter_api.c new file mode 100644 index 000000000..38059bb53 --- /dev/null +++ b/lib/netfilter_api.c @@ -0,0 +1,436 @@ +#include "netfilter_api.h" + +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct nfapi_socket { + int fd; + struct sockaddr_nl addr; // local +}; + +struct nfapi_buf { + GString *s; // buffer + ssize_t last_hdr; + GQueue nested; +}; + + +static const struct sockaddr_nl zero_nl_sockaddr = { .nl_family = AF_NETLINK }; + + +nfapi_socket *nfapi_socket_open(void) { + int fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER); + if (fd == -1) + return NULL; + + int ret = bind(fd, (struct sockaddr *) &zero_nl_sockaddr, sizeof(zero_nl_sockaddr)); + if (ret != 0) + return NULL; + + struct sockaddr_nl saddr; + socklen_t slen = sizeof(saddr); + ret = getsockname(fd, (struct sockaddr *) &saddr, &slen); + if (slen < sizeof(saddr)) + return NULL; + + nfapi_socket *s = g_new0(__typeof(*s), 1); + s->fd = fd; + s->addr = saddr; + + return s; +} + +void nfapi_socket_close(nfapi_socket *s) { + if (s->fd != -1) + close(s->fd); + g_free(s); +} + + +nfapi_buf *nfapi_buf_new(void) { + nfapi_buf *b = g_new0(__typeof(*b), 1); + b->s = g_string_new(""); + b->last_hdr = -1; + return b; +} + +void nfapi_buf_free(nfapi_buf *b) { + g_string_free(b->s, TRUE); + g_free(b); +} + + +static void *buf_add_store(GString *b, size_t s, ssize_t *store) { + size_t cur = b->len; + g_string_set_size(b, cur + s); + if (store) + *store = cur; + return b->str + cur; +} + +static struct nlmsghdr *hdr_add(nfapi_buf *b, size_t s) { + return buf_add_store(b->s, s, &b->last_hdr); +} + +static struct nlmsghdr *hdr_get_last(nfapi_buf *b) { + assert(b->last_hdr != -1); + return (struct nlmsghdr *) (b->s->str + b->last_hdr); +} + +static void *item_add(nfapi_buf *b, size_t s) { + void *ret = buf_add_store(b->s, s, NULL); + __auto_type hdr = hdr_get_last(b); + hdr->nlmsg_len += s; + for (__auto_type l = b->nested.head; l; l = l->next) { + size_t off = (size_t) l->data; + struct nlattr *attr = (struct nlattr *) (b->s->str + off); + attr->nla_len += s; + } + return ret; +} + +static void add_msg(nfapi_buf *b, uint16_t type, uint16_t family, uint16_t flags, uint16_t seq, uint16_t res_id) { + struct nlmsghdr *hdr = hdr_add(b, sizeof(*hdr)); + *hdr = (__typeof(*hdr)) { + .nlmsg_type = type, + .nlmsg_flags = flags, + .nlmsg_seq = seq, + .nlmsg_pid = 0, + .nlmsg_len = sizeof(*hdr), + }; + struct nfgenmsg *fam = item_add(b, sizeof(*fam)); + *fam = (__typeof(*fam)) { + .nfgen_family = family, + .version = NFNETLINK_V0, + .res_id = htons(res_id), + }; +} + +void nfapi_add_msg(nfapi_buf *b, uint16_t type, uint16_t family, uint16_t flags) { + return add_msg(b, (NFNL_SUBSYS_NFTABLES << 8) | type, family, flags, 0, 0); +} + +void nfapi_batch_begin(nfapi_buf *b) { + add_msg(b, NFNL_MSG_BATCH_BEGIN, AF_UNSPEC, NLM_F_REQUEST, 0, NFNL_SUBSYS_NFTABLES); +} +void nfapi_batch_end(nfapi_buf *b) { + add_msg(b, NFNL_MSG_BATCH_END, AF_UNSPEC, NLM_F_REQUEST, 0, NFNL_SUBSYS_NFTABLES); +} + + + +void nfapi_add_attr(nfapi_buf *b, uint16_t type, const void *data, size_t len) { + struct nlattr *attr = item_add(b, sizeof(*attr)); + *attr = (__typeof(*attr)) { + .nla_type = type, + .nla_len = sizeof(*attr) + len, + }; + void *d = item_add(b, NFA_ALIGN(len)); + memset(d, 0, NFA_ALIGN(len)); + memcpy(d, data, len); +} + +void nfapi_add_str_attr(nfapi_buf *b, uint16_t type, const char *s) { + nfapi_add_attr(b, type, s, strlen(s) + 1); +} +void nfapi_add_u32_attr(nfapi_buf *b, uint16_t type, uint32_t u) { + nfapi_add_attr(b, type, &u, sizeof(u)); +} +void nfapi_add_u64_attr(nfapi_buf *b, uint16_t type, uint64_t u) { + nfapi_add_attr(b, type, &u, sizeof(u)); +} + +void nfapi_nested_begin(nfapi_buf *b, uint16_t type) { + g_queue_push_tail(&b->nested, (void *) b->s->len); + nfapi_add_attr(b, type | NLA_F_NESTED, NULL, 0); +} + +void nfapi_nested_end(nfapi_buf *b) { + assert(b->nested.length != 0); + g_queue_pop_tail(&b->nested); +} + + + +bool nfapi_send_buf(nfapi_socket *s, nfapi_buf *b) { + ssize_t ret = sendto(s->fd, b->s->str, b->s->len, 0, (struct sockaddr *) &zero_nl_sockaddr, + sizeof(zero_nl_sockaddr)); + if (ret != b->s->len) + return false; + return true; +} + +const char *nfapi_recv_iter(nfapi_socket *s, const nfapi_callbacks *c, void *userdata) { + while (true) { + int8_t buf[8192]; + + union { + struct sockaddr_storage sst; + struct sockaddr_nl ssn; + } ss; + socklen_t ssl = sizeof(ss); + errno = 0; + ssize_t r = recvfrom(s->fd, buf, sizeof(buf), 0, (struct sockaddr *) &ss.sst, &ssl); + + if (r < 0 || r > sizeof(buf) + || ssl < sizeof(ss.ssn) + || ss.ssn.nl_family != AF_NETLINK + || ss.ssn.nl_pid != 0) + return "error while receiving from netlink socket"; + + if (r == 0) + return NULL; + + size_t off = 0; + while (off < r) { + const struct nlmsghdr *hdr; + if (off + sizeof(hdr) > r) + return "message too short for header"; + + hdr = (struct nlmsghdr *) (buf + off); + uint16_t subsys = NFNL_SUBSYS_ID(hdr->nlmsg_type); + uint16_t type = NFNL_MSG_TYPE(hdr->nlmsg_type); + + if (hdr->nlmsg_len == 0) + return "zero length message"; + + size_t next = off + sizeof(*hdr); + + off += hdr->nlmsg_len; + + assert(hdr->nlmsg_pid == s->addr.nl_pid); + + if (subsys == NFNL_SUBSYS_NFTABLES) { + struct nfgenmsg *fam; + if (next + sizeof(*fam) > r) + return "message too short for genmsg"; + + fam = (struct nfgenmsg *) (buf + next); + next += sizeof(*fam); + + if (next > off) + return "message too short after genmsg"; + + if (fam->version != NFNETLINK_V0) + return "netlink version not v0"; + + switch (type) { + case NFT_MSG_NEWRULE: + if (c && c->rule) + c->rule(buf + next, off - next, userdata); + break; + + case NFT_MSG_NEWCHAIN: + if (c && c->chain) + c->chain(buf + next, off - next, userdata); + break; + + default: + abort(); + }; + } + else { + if (type == NLMSG_DONE) + return NULL; + + if (type == NLMSG_ERROR) { + struct nlmsgerr *err; + errno = ERANGE; + if (next + sizeof(*err) > r) + return "error but also message too short"; + + err = (struct nlmsgerr *) (buf + next); + + if (err->error == 0) + return NULL; + + errno = -err->error; + return "error returned from netlink, see errno"; + } + else + abort(); + } + } + } +} + +#define foreach_nlattr(l, attr, type, data, data_len, fail_ret) \ + size_t __off = 0; \ + \ + while (__off < l) { \ + const struct nlattr *attr; \ + errno = EMSGSIZE; \ + if (__off + sizeof(*attr) > l) \ + return fail_ret; \ + \ + attr = (struct nlattr *) (buf + __off); \ + errno = ERANGE; \ + if (attr->nla_len == 0 || __off + attr->nla_len > l) \ + return fail_ret; \ + \ + uint16_t type = attr->nla_type & NLA_TYPE_MASK; \ + \ + const int8_t *data = buf + __off + sizeof(*attr); \ + size_t data_len __attribute__((unused)) = attr->nla_len - sizeof(*attr); \ + \ + __off += NFA_ALIGN(attr->nla_len); \ + + +static bool nested_expr_iter(const int8_t *buf, size_t l, + const char **name, const int8_t **expr_data, size_t *expr_len) +{ + foreach_nlattr(l, attr, type, data, data_len, false) + switch (type) { + case NFTA_EXPR_NAME: + *name = (char *) data; + break; + case NFTA_EXPR_DATA: + *expr_data = data; + *expr_len = data_len; + break; + } + } + + return true; +} + +static bool nested_verdict_iter(const int8_t *buf, size_t l, int32_t *code, const char **chain) { + foreach_nlattr(l, attr, type, data, data_len, false) + switch (type) { + case NFTA_VERDICT_CODE: + if (data_len != sizeof(int32_t)) + return false; + *code = ntohl(*(int32_t *) data); + break; + + case NFTA_VERDICT_CHAIN: + *chain = (char *) data; + break; + } + } + + return true; +} + +static bool nested_immediate_iter(const int8_t *buf, size_t l, int32_t *code, const char **chain) { + foreach_nlattr(l, attr, type, data, data_len, false) + if (type == NFTA_DATA_VERDICT) { + if (!nested_verdict_iter(data, data_len, code, chain)) + return false; + } + } + + return true; +} + +static const char *expr_iter(const int8_t *buf, size_t l, const nfapi_callbacks *c, void *userdata) { + foreach_nlattr(l, attr, type, data, data_len, "error in expression message format") + if (type != NFTA_LIST_ELEM) + return "not a list element"; + + const char *name = NULL; + const int8_t *expr_data = NULL; + size_t expr_len = 0; + + if (!nested_expr_iter(data, data_len, &name, &expr_data, &expr_len)) + return "error in expression items"; + + if (!name) + return "expression has no name"; + + if (c && c->expression) { + const char *err = c->expression(name, expr_data, expr_len, userdata); + if (err) + return err; + } + } + + return NULL; +} + +const char *nfapi_rule_iter(const int8_t *buf, size_t l, const nfapi_callbacks *c, void *userdata) { + //const char *table = NULL; + //const char *chain = NULL; + int64_t handle = -1; + + foreach_nlattr(l, attr, type, data, data_len, "error in rule message format") + switch (type) { + case NFTA_RULE_TABLE: + //table = data; + //printf("table %s\n", data); + break; + case NFTA_RULE_CHAIN: + //chain = data; + //printf("chain %s\n", data); + break; + case NFTA_RULE_HANDLE: + if (data_len != sizeof(handle)) + return "handle size incorrect"; + handle = *(int64_t *) data; + if (c && c->handle) + c->handle(handle, userdata); + break; + case NFTA_RULE_EXPRESSIONS:; + const char *err = expr_iter(data, data_len, c, userdata); + if (err) + return err; + break; + } + } + + return NULL; +} + +const char *nfapi_get_immediate_chain(const int8_t *buf, size_t l) { + const char *chain = NULL; + int32_t verdict_code = 0; + + foreach_nlattr(l, attr, type, data, data_len, NULL) + if (type == NFTA_IMMEDIATE_DATA && data_len >= sizeof(struct nlattr)) { + if (!nested_immediate_iter(data, data_len, &verdict_code, &chain)) + return NULL; + } + } + + if ((verdict_code == NFT_JUMP || verdict_code == NFT_GOTO) && chain) + return chain; + + return NULL; +} + +const char *nfapi_get_target(const int8_t *buf, size_t l, void *info, size_t *info_len) { + const char *tg = NULL; + + size_t buf_len = 0; + if (info_len && info) { + buf_len = *info_len; + *info_len = 0; + } + + foreach_nlattr(l, attr, type, data, data_len, NULL) + switch (type) { + case NFTA_TARGET_NAME: + tg = (char *) data; + break; + + case NFTA_TARGET_INFO: + if (!buf_len) + break; + buf_len = MIN(buf_len, data_len); + memcpy(info, data, buf_len); + break; + } + } + + return tg; +} diff --git a/lib/netfilter_api.h b/lib/netfilter_api.h new file mode 100644 index 000000000..22fefe23b --- /dev/null +++ b/lib/netfilter_api.h @@ -0,0 +1,55 @@ +#ifndef _NETFILTER_API_H +#define _NETFILTER_API_H + +#include +#include +#include + + +typedef struct nfapi_socket nfapi_socket; +typedef struct nfapi_buf nfapi_buf; + +typedef struct { + const char *(*rule)(const int8_t *, size_t, void *userdata); + const char *(*chain)(const int8_t *, size_t, void *userdata); + const char *(*expression)(const char *, const int8_t *, size_t, void *userdata); + void (*handle)(int64_t, void *userdata); +} nfapi_callbacks; + + +nfapi_socket *nfapi_socket_open(void); +void nfapi_socket_close(nfapi_socket *); + + +nfapi_buf *nfapi_buf_new(void); +void nfapi_buf_free(nfapi_buf *); + +void nfapi_add_msg(nfapi_buf *, uint16_t type, uint16_t family, uint16_t flags); + +void nfapi_add_attr(nfapi_buf *b, uint16_t type, const void *data, size_t len); +void nfapi_add_str_attr(nfapi_buf *b, uint16_t type, const char *s); +void nfapi_add_u32_attr(nfapi_buf *b, uint16_t type, uint32_t u); +void nfapi_add_u64_attr(nfapi_buf *b, uint16_t type, uint64_t u); + +void nfapi_nested_begin(nfapi_buf *, uint16_t type); +void nfapi_nested_end(nfapi_buf *); + +void nfapi_batch_begin(nfapi_buf *); +void nfapi_batch_end(nfapi_buf *); + + +bool nfapi_send_buf(nfapi_socket *, nfapi_buf *); + +const char *nfapi_recv_iter(nfapi_socket *, const nfapi_callbacks *, void *userdata); + +const char *nfapi_rule_iter(const int8_t *, size_t, const nfapi_callbacks *, void *userdata); + +const char *nfapi_get_immediate_chain(const int8_t *, size_t); +const char *nfapi_get_target(const int8_t *, size_t, void *info, size_t *info_len); + + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(nfapi_socket, nfapi_socket_close); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(nfapi_buf, nfapi_buf_free); + + +#endif