You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

446 lines
10 KiB

#include "netfilter_api.h"
//#include <asm/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/netfilter.h>
#include <linux/netlink.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter/nf_tables_compat.h>
#include <glib.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
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) {
close(fd);
return NULL;
}
struct sockaddr_nl saddr;
socklen_t slen = sizeof(saddr);
ret = getsockname(fd, (struct sockaddr *) &saddr, &slen);
if (slen < sizeof(saddr)) {
close(fd);
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_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_USERDATA:;
const struct {
uint16_t len;
char comment[];
} *comment = (void *) data;
if (c && c->comment && data_len <= ntohs(comment->len) + 2)
c->comment(comment->comment, 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);
*info_len = buf_len;
break;
}
}
return tg;
}