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.

1656 lines
41 KiB

/* libnfnetlink.c: generic library for communication with netfilter
*
* (C) 2002-2006 by Harald Welte <laforge@gnumonks.org>
* (C) 2006-2011 by Pablo Neira Ayuso <pablo@netfilter.org>
*
* Based on some original ideas from Jay Schulist <jschlst@samba.org>
*
* Development of this code funded by Astaro AG (http://www.astaro.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* 2005-09-14 Pablo Neira Ayuso <pablo@netfilter.org>:
* Define structure nfnlhdr
* Added __be64_to_cpu function
* Use NFA_TYPE macro to get the attribute type
*
* 2006-01-14 Harald Welte <laforge@netfilter.org>:
* introduce nfnl_subsys_handle
*
* 2006-01-15 Pablo Neira Ayuso <pablo@netfilter.org>:
* set missing subsys_id in nfnl_subsys_open
* set missing nfnlh->local.nl_pid in nfnl_open
*
* 2006-01-26 Harald Welte <laforge@netfilter.org>:
* remove bogus nfnlh->local.nl_pid from nfnl_open ;)
* add 16bit attribute functions
*
* 2006-07-03 Pablo Neira Ayuso <pablo@netfilter.org>:
* add iterator API
* add replacements for nfnl_listen and nfnl_talk
* fix error handling
* add assertions
* add documentation
* minor cleanups
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <netinet/in.h>
#include <assert.h>
#include <linux/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <linux/netlink.h>
#include <libnfnetlink/libnfnetlink.h>
#ifndef NETLINK_ADD_MEMBERSHIP
#define NETLINK_ADD_MEMBERSHIP 1
#endif
#ifndef SOL_NETLINK
#define SOL_NETLINK 270
#endif
#define nfnl_error(format, args...) \
fprintf(stderr, "%s: " format "\n", __FUNCTION__, ## args)
#ifdef _NFNL_DEBUG
#define nfnl_debug_dump_packet nfnl_dump_packet
#else
#define nfnl_debug_dump_packet(a, b, ...)
#endif
struct nfnl_subsys_handle {
struct nfnl_handle *nfnlh;
u_int32_t subscriptions;
u_int8_t subsys_id;
u_int8_t cb_count;
struct nfnl_callback *cb; /* array of callbacks */
};
#define NFNL_MAX_SUBSYS 16 /* enough for now */
#define NFNL_F_SEQTRACK_ENABLED (1 << 0)
struct nfnl_handle {
int fd;
struct sockaddr_nl local;
struct sockaddr_nl peer;
u_int32_t subscriptions;
u_int32_t seq;
u_int32_t dump;
u_int32_t rcv_buffer_size; /* for nfnl_catch */
u_int32_t flags;
struct nlmsghdr *last_nlhdr;
struct nfnl_subsys_handle subsys[NFNL_MAX_SUBSYS+1];
};
void nfnl_dump_packet(struct nlmsghdr *nlh, int received_len, char *desc)
{
void *nlmsg_data = NLMSG_DATA(nlh);
struct nfattr *nfa = NFM_NFA(NLMSG_DATA(nlh));
int len = NFM_PAYLOAD(nlh);
printf("%s called from %s\n", __FUNCTION__, desc);
printf(" nlmsghdr = %p, received_len = %u\n", nlh, received_len);
printf(" NLMSG_DATA(nlh) = %p (+%td bytes)\n", nlmsg_data,
(nlmsg_data - (void *)nlh));
printf(" NFM_NFA(NLMSG_DATA(nlh)) = %p (+%td bytes)\n",
nfa, ((void *)nfa - (void *)nlh));
printf(" NFM_PAYLOAD(nlh) = %u\n", len);
printf(" nlmsg_type = %u, nlmsg_len = %u, nlmsg_seq = %u "
"nlmsg_flags = 0x%x\n", nlh->nlmsg_type, nlh->nlmsg_len,
nlh->nlmsg_seq, nlh->nlmsg_flags);
while (NFA_OK(nfa, len)) {
printf(" nfa@%p: nfa_type=%u, nfa_len=%u\n",
nfa, NFA_TYPE(nfa), nfa->nfa_len);
nfa = NFA_NEXT(nfa,len);
}
}
/**
* nfnl_fd - returns the descriptor that identifies the socket
* @nfnlh: nfnetlink handler
*
* Use this function if you need to interact with the socket. Common
* scenarios are the use of poll()/select() to achieve multiplexation.
*/
int nfnl_fd(struct nfnl_handle *h)
{
assert(h);
return h->fd;
}
/**
* nfnl_portid - returns the Netlink port ID of this socket
* @h: nfnetlink handler
*/
unsigned int nfnl_portid(const struct nfnl_handle *h)
{
assert(h);
return h->local.nl_pid;
}
static int recalc_rebind_subscriptions(struct nfnl_handle *nfnlh)
{
int i, err;
u_int32_t new_subscriptions = nfnlh->subscriptions;
for (i = 0; i < NFNL_MAX_SUBSYS; i++)
new_subscriptions |= nfnlh->subsys[i].subscriptions;
nfnlh->local.nl_groups = new_subscriptions;
err = bind(nfnlh->fd, (struct sockaddr *)&nfnlh->local,
sizeof(nfnlh->local));
if (err == -1)
return -1;
nfnlh->subscriptions = new_subscriptions;
return 0;
}
static void recalc_subscriptions(struct nfnl_handle *nfnlh)
{
int i;
u_int32_t new_subscriptions = nfnlh->subscriptions;
for (i = 0; i < NFNL_MAX_SUBSYS; i++)
new_subscriptions |= nfnlh->subsys[i].subscriptions;
nfnlh->local.nl_groups = new_subscriptions;
nfnlh->subscriptions = new_subscriptions;
}
/**
* nfnl_open - open a nfnetlink handler
*
* This function creates a nfnetlink handler, this is required to establish
* a communication between the userspace and the nfnetlink system.
*
* On success, a valid address that points to a nfnl_handle structure
* is returned. On error, NULL is returned and errno is set approapiately.
*/
struct nfnl_handle *nfnl_open(void)
{
struct nfnl_handle *nfnlh;
int fd;
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER);
if (fd == -1)
return NULL;
nfnlh = nfnl_open2(fd, true);
if (nfnlh == NULL)
close(fd);
return nfnlh;
}
/**
* nfnl_open2 - open a nfnetlink handler
* @fd: passing file descriptor
* @bind: indicate the passing fd needs to be binded or not
*
* This function creates a nfnetlink handler, this is required to establish
* a communication between the userspace and the nfnetlink system.
*
* On success, a valid address that points to a nfnl_handle structure
* is returned. On error, NULL is returned and errno is set approapiately.
*/
struct nfnl_handle *nfnl_open2(int fd, bool bind)
{
struct nfnl_handle *nfnlh;
unsigned int addr_len;
if (fd < 0)
goto err;
nfnlh = malloc(sizeof(*nfnlh));
if (!nfnlh)
return NULL;
memset(nfnlh, 0, sizeof(*nfnlh));
nfnlh->fd = fd;
nfnlh->local.nl_family = AF_NETLINK;
nfnlh->peer.nl_family = AF_NETLINK;
addr_len = sizeof(nfnlh->local);
getsockname(nfnlh->fd, (struct sockaddr *)&nfnlh->local, &addr_len);
if (addr_len != sizeof(nfnlh->local)) {
errno = EINVAL;
goto err_free;
}
if (nfnlh->local.nl_family != AF_NETLINK) {
errno = EINVAL;
goto err_free;
}
nfnlh->seq = time(NULL);
nfnlh->rcv_buffer_size = NFNL_BUFFSIZE;
/* don't set pid here, only first socket of process has real pid !!!
* binding to pid '0' will default */
/* let us do the initial bind */
if (bind) {
if (recalc_rebind_subscriptions(nfnlh) < 0)
goto err_free;
} else {
recalc_subscriptions(nfnlh);
}
/* use getsockname to get the netlink pid that the kernel assigned us */
addr_len = sizeof(nfnlh->local);
getsockname(nfnlh->fd, (struct sockaddr *)&nfnlh->local, &addr_len);
if (addr_len != sizeof(nfnlh->local)) {
errno = EINVAL;
goto err_free;
}
/* sequence tracking enabled by default */
nfnlh->flags |= NFNL_F_SEQTRACK_ENABLED;
return nfnlh;
err_free:
free(nfnlh);
err:
return NULL;
}
/**
* nfnl_set_sequence_tracking - set netlink sequence tracking
* @h: nfnetlink handler
*/
void nfnl_set_sequence_tracking(struct nfnl_handle *h)
{
h->flags |= NFNL_F_SEQTRACK_ENABLED;
}
/**
* nfnl_unset_sequence_tracking - set netlink sequence tracking
* @h: nfnetlink handler
*/
void nfnl_unset_sequence_tracking(struct nfnl_handle *h)
{
h->flags &= ~NFNL_F_SEQTRACK_ENABLED;
}
/**
* nfnl_set_rcv_buffer_size - set the size of the receive buffer
* @h: libnfnetlink handler
* @size: buffer size
*
* This function sets the size of the receive buffer size, i.e. the size
* of the buffer used by nfnl_recv. Default value is 4096 bytes.
*/
void nfnl_set_rcv_buffer_size(struct nfnl_handle *h, unsigned int size)
{
h->rcv_buffer_size = size;
}
/**
* nfnl_subsys_open - open a netlink subsystem
* @nfnlh: libnfnetlink handle
* @subsys_id: which nfnetlink subsystem we are interested in
* @cb_count: number of callbacks that are used maximum.
* @subscriptions: netlink groups we want to be subscribed to
*
* This function creates a subsystem handler that contains the set of
* callbacks that handle certain types of messages coming from a netfilter
* subsystem. Initially the callback set is empty, you can register callbacks
* via nfnl_callback_register().
*
* On error, NULL is returned and errno is set appropiately. On success,
* a valid address that points to a nfnl_subsys_handle structure is returned.
*/
struct nfnl_subsys_handle *
nfnl_subsys_open(struct nfnl_handle *nfnlh, u_int8_t subsys_id,
u_int8_t cb_count, u_int32_t subscriptions)
{
return nfnl_subsys_open2 (nfnlh, subsys_id, cb_count, subscriptions, true);
}
/**
* nfnl_subsys_open2 - open a netlink subsystem
* @nfnlh: libnfnetlink handle
* @subsys_id: which nfnetlink subsystem we are interested in
* @cb_count: number of callbacks that are used maximum.
* @subscriptions: netlink groups we want to be subscribed to
* @bind: indicate the passing fd needs to be binded or not
*
* This function creates a subsystem handler that contains the set of
* callbacks that handle certain types of messages coming from a netfilter
* subsystem. Initially the callback set is empty, you can register callbacks
* via nfnl_callback_register().
*
* On error, NULL is returned and errno is set appropiately. On success,
* a valid address that points to a nfnl_subsys_handle structure is returned.
*/
struct nfnl_subsys_handle *
nfnl_subsys_open2(struct nfnl_handle *nfnlh, u_int8_t subsys_id,
u_int8_t cb_count, u_int32_t subscriptions, bool bind)
{
struct nfnl_subsys_handle *ssh;
int err = 0;
assert(nfnlh);
if (subsys_id > NFNL_MAX_SUBSYS) {
errno = ENOENT;
return NULL;
}
ssh = &nfnlh->subsys[subsys_id];
if (ssh->cb) {
errno = EBUSY;
return NULL;
}
ssh->cb = calloc(cb_count, sizeof(*(ssh->cb)));
if (!ssh->cb)
return NULL;
ssh->nfnlh = nfnlh;
ssh->cb_count = cb_count;
ssh->subscriptions = subscriptions;
ssh->subsys_id = subsys_id;
/* although now we have nfnl_join to subscribe to certain
* groups, just keep this to ensure compatibility */
if (bind)
err = recalc_rebind_subscriptions(nfnlh);
else
recalc_subscriptions(nfnlh);
if (err < 0) {
free(ssh->cb);
ssh->cb = NULL;
return NULL;
}
return ssh;
}
/**
* nfnl_subsys_close - close a nfnetlink subsys handler
* @ssh: nfnetlink subsystem handler
*
* Release all the callbacks registered in a subsystem handler.
*/
void nfnl_subsys_close(struct nfnl_subsys_handle *ssh)
{
assert(ssh);
ssh->subscriptions = 0;
ssh->cb_count = 0;
if (ssh->cb) {
free(ssh->cb);
ssh->cb = NULL;
}
}
/**
* nfnl_close - close a nfnetlink handler
* @nfnlh: nfnetlink handler
*
* This function closes the nfnetlink handler. On success, 0 is returned.
* On error, -1 is returned and errno is set appropiately.
*/
int nfnl_close(struct nfnl_handle *nfnlh)
{
int ret;
assert(nfnlh);
ret = close(nfnlh->fd);
if (ret < 0)
return ret;
return nfnl_close2(nfnlh);
}
/**
* nfnl_close2 - close a nfnetlink handler but keep fd
* @nfnlh: nfnetlink handler
*
* This function closes the nfnetlink handler. On success, 0 is returned.
* On error, -1 is returned and errno is set appropiately.
*/
int nfnl_close2(struct nfnl_handle *nfnlh)
{
int i;
assert(nfnlh);
for (i = 0; i < NFNL_MAX_SUBSYS; i++)
nfnl_subsys_close(&nfnlh->subsys[i]);
free(nfnlh);
return 0;
}
/**
* nfnl_join - join a nfnetlink multicast group
* @nfnlh: nfnetlink handler
* @group: group we want to join
*
* This function is used to join a certain multicast group. It must be
* called once the nfnetlink handler has been created. If any doubt,
* just use it if you have to listen to nfnetlink events.
*
* On success, 0 is returned. On error, -1 is returned and errno is set
* approapiately.
*/
int nfnl_join(const struct nfnl_handle *nfnlh, unsigned int group)
{
assert(nfnlh);
return setsockopt(nfnlh->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
&group, sizeof(group));
}
/**
* nfnl_send - send a nfnetlink message through netlink socket
* @nfnlh: nfnetlink handler
* @n: netlink message
*
* On success, the number of bytes is returned. On error, -1 is returned
* and errno is set appropiately.
*/
int nfnl_send(struct nfnl_handle *nfnlh, struct nlmsghdr *n)
{
assert(nfnlh);
assert(n);
nfnl_debug_dump_packet(n, n->nlmsg_len+sizeof(*n), "nfnl_send");
return sendto(nfnlh->fd, n, n->nlmsg_len, 0,
(struct sockaddr *)&nfnlh->peer, sizeof(nfnlh->peer));
}
int nfnl_sendmsg(const struct nfnl_handle *nfnlh, const struct msghdr *msg,
unsigned int flags)
{
assert(nfnlh);
assert(msg);
return sendmsg(nfnlh->fd, msg, flags);
}
int nfnl_sendiov(const struct nfnl_handle *nfnlh, const struct iovec *iov,
unsigned int num, unsigned int flags)
{
struct msghdr msg;
assert(nfnlh);
msg.msg_name = (struct sockaddr *) &nfnlh->peer;
msg.msg_namelen = sizeof(nfnlh->peer);
msg.msg_iov = (struct iovec *) iov;
msg.msg_iovlen = num;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = 0;
return nfnl_sendmsg(nfnlh, &msg, flags);
}
/**
* nfnl_fill_hdr - fill in netlink and nfnetlink header
* @nfnlh: nfnetlink handle
* @nlh: netlink message to be filled in
* @len: length of _payload_ bytes (not including nfgenmsg)
* @family: AF_INET / ...
* @res_id: resource id
* @msg_type: nfnetlink message type (without subsystem)
* @msg_flags: netlink message flags
*
* This function sets up appropiately the nfnetlink header. See that the
* pointer to the netlink message passed must point to a memory region of
* at least the size of struct nlmsghdr + struct nfgenmsg.
*/
void nfnl_fill_hdr(struct nfnl_subsys_handle *ssh,
struct nlmsghdr *nlh, unsigned int len,
u_int8_t family,
u_int16_t res_id,
u_int16_t msg_type,
u_int16_t msg_flags)
{
assert(ssh);
assert(nlh);
struct nfgenmsg *nfg = (void *)nlh + sizeof(*nlh);
nlh->nlmsg_len = NLMSG_LENGTH(len+sizeof(*nfg));
nlh->nlmsg_type = (ssh->subsys_id<<8)|msg_type;
nlh->nlmsg_flags = msg_flags;
nlh->nlmsg_pid = 0;
if (ssh->nfnlh->flags & NFNL_F_SEQTRACK_ENABLED) {
nlh->nlmsg_seq = ++ssh->nfnlh->seq;
/* kernel uses sequence number zero for events */
if (!ssh->nfnlh->seq)
nlh->nlmsg_seq = ssh->nfnlh->seq = time(NULL);
} else {
/* unset sequence number, ignore it */
nlh->nlmsg_seq = 0;
}
nfg->nfgen_family = family;
nfg->version = NFNETLINK_V0;
nfg->res_id = htons(res_id);
}
struct nfattr *
nfnl_parse_hdr(const struct nfnl_handle *nfnlh,
const struct nlmsghdr *nlh,
struct nfgenmsg **genmsg)
{
if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(struct nfgenmsg)))
return NULL;
if (nlh->nlmsg_len == NLMSG_LENGTH(sizeof(struct nfgenmsg))) {
if (genmsg)
*genmsg = (void *)nlh + sizeof(*nlh);
return NULL;
}
if (genmsg)
*genmsg = (void *)nlh + sizeof(*nlh);
return (void *)nlh + NLMSG_LENGTH(sizeof(struct nfgenmsg));
}
/**
* nfnl_recv - receive data from a nfnetlink subsystem
* @h: nfnetlink handler
* @buf: buffer where the data will be stored
* @len: size of the buffer
*
* This function doesn't perform any sanity checking. So do no expect
* that the data is well-formed. Such checkings are done by the parsing
* functions.
*
* On success, 0 is returned. On error, -1 is returned and errno is set
* appropiately.
*
* Note that ENOBUFS is returned in case that nfnetlink is exhausted. In
* that case is possible that the information requested is incomplete.
*/
ssize_t
nfnl_recv(const struct nfnl_handle *h, unsigned char *buf, size_t len)
{
socklen_t addrlen;
int status;
struct sockaddr_nl peer;
assert(h);
assert(buf);
assert(len > 0);
if (len < sizeof(struct nlmsgerr)
|| len < sizeof(struct nlmsghdr)) {
errno = EBADMSG;
return -1;
}
addrlen = sizeof(h->peer);
status = recvfrom(h->fd, buf, len, 0, (struct sockaddr *)&peer,
&addrlen);
if (status <= 0)
return status;
if (addrlen != sizeof(peer)) {
errno = EINVAL;
return -1;
}
if (peer.nl_pid != 0) {
errno = ENOMSG;
return -1;
}
return status;
}
/**
* nfnl_listen: listen for one or more netlink messages
* @nfnhl: libnfnetlink handle
* @handler: callback function to be called for every netlink message
* - the callback handler should normally return 0
* - but may return a negative error code which will cause
* nfnl_listen to return immediately with the same error code
* - or return a postivie error code which will cause
* nfnl_listen to return after it has finished processing all
* the netlink messages in the current packet
* Thus a positive error code will terminate nfnl_listen "soon"
* without any loss of data, a negative error code will terminate
* nfnl_listen "very soon" and throw away data already read from
* the netlink socket.
* @jarg: opaque argument passed on to callback
*
* This function is used to receive and process messages coming from an open
* nfnetlink handler like events or information request via nfnl_send().
*
* On error, -1 is returned, unfortunately errno is not always set
* appropiately. For that reason, the use of this function is DEPRECATED.
* Please, use nfnl_receive_process() instead.
*/
int nfnl_listen(struct nfnl_handle *nfnlh,
int (*handler)(struct sockaddr_nl *, struct nlmsghdr *n,
void *), void *jarg)
{
struct sockaddr_nl nladdr;
char buf[NFNL_BUFFSIZE] __attribute__ ((aligned));
struct iovec iov;
int remain;
struct nlmsghdr *h;
struct nlmsgerr *msgerr;
int quit=0;
struct msghdr msg = {
.msg_name = &nladdr,
.msg_namelen = sizeof(nladdr),
.msg_iov = &iov,
.msg_iovlen = 1,
};
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
while (! quit) {
remain = recvmsg(nfnlh->fd, &msg, 0);
if (remain < 0) {
if (errno == EINTR)
continue;
/* Bad file descriptor */
else if (errno == EBADF)
break;
else if (errno == EAGAIN)
break;
nfnl_error("recvmsg overrun: %s", strerror(errno));
continue;
}
if (remain == 0) {
nfnl_error("EOF on netlink");
return -1;
}
if (msg.msg_namelen != sizeof(nladdr)) {
nfnl_error("Bad sender address len (%d)",
msg.msg_namelen);
return -1;
}
for (h = (struct nlmsghdr *)buf; remain >= sizeof(*h);) {
int err;
int len = h->nlmsg_len;
int l = len - sizeof(*h);
if (l < 0 || len > remain) {
if (msg.msg_flags & MSG_TRUNC) {
nfnl_error("MSG_TRUNC");
return -1;
}
nfnl_error("Malformed msg (len=%d)", len);
return -1;
}
/* end of messages reached, let's return */
if (h->nlmsg_type == NLMSG_DONE)
return 0;
/* Break the loop if success is explicitely
* reported via NLM_F_ACK flag set */
if (h->nlmsg_type == NLMSG_ERROR) {
msgerr = NLMSG_DATA(h);
return msgerr->error;
}
err = handler(&nladdr, h, jarg);
if (err < 0)
return err;
quit |= err;
/* FIXME: why not _NEXT macros, etc.? */
//h = NLMSG_NEXT(h, remain);
remain -= NLMSG_ALIGN(len);
h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
}
if (msg.msg_flags & MSG_TRUNC) {
nfnl_error("MSG_TRUNC");
continue;
}
if (remain) {
nfnl_error("remnant size %d", remain);
return -1;
}
}
return quit;
}
/**
* nfnl_talk - send a request and then receive and process messages returned
* @nfnlh: nfnetelink handler
* @n: netlink message that contains the request
* @peer: peer PID
* @groups: netlink groups
* @junk: callback called if out-of-sequence messages were received
* @jarg: data for the junk callback
*
* This function is used to request an action that does not returns any
* information. On error, a negative value is returned, errno could be
* set appropiately. For that reason, the use of this function is DEPRECATED.
* Please, use nfnl_query() instead.
*/
int nfnl_talk(struct nfnl_handle *nfnlh, struct nlmsghdr *n, pid_t peer,
unsigned groups, struct nlmsghdr *answer,
int (*junk)(struct sockaddr_nl *, struct nlmsghdr *n, void *),
void *jarg)
{
char buf[NFNL_BUFFSIZE] __attribute__ ((aligned));
struct sockaddr_nl nladdr;
struct nlmsghdr *h;
unsigned int seq;
int status;
struct iovec iov = {
n, n->nlmsg_len
};
struct msghdr msg = {
.msg_name = &nladdr,
.msg_namelen = sizeof(nladdr),
.msg_iov = &iov,
.msg_iovlen = 1,
};
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
nladdr.nl_pid = peer;
nladdr.nl_groups = groups;
n->nlmsg_seq = seq = ++nfnlh->seq;
/* FIXME: why ? */
if (!answer)
n->nlmsg_flags |= NLM_F_ACK;
status = sendmsg(nfnlh->fd, &msg, 0);
if (status < 0) {
nfnl_error("sendmsg(netlink) %s", strerror(errno));
return -1;
}
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
while (1) {
status = recvmsg(nfnlh->fd, &msg, 0);
if (status < 0) {
if (errno == EINTR)
continue;
nfnl_error("recvmsg over-run");
continue;
}
if (status == 0) {
nfnl_error("EOF on netlink");
return -1;
}
if (msg.msg_namelen != sizeof(nladdr)) {
nfnl_error("Bad sender address len %d",
msg.msg_namelen);
return -1;
}
for (h = (struct nlmsghdr *)buf; status >= sizeof(*h); ) {
int len = h->nlmsg_len;
int l = len - sizeof(*h);
int err;
if (l < 0 || len > status) {
if (msg.msg_flags & MSG_TRUNC) {
nfnl_error("Truncated message\n");
return -1;
}
nfnl_error("Malformed message: len=%d\n", len);
return -1; /* FIXME: libnetlink exits here */
}
if (h->nlmsg_pid != nfnlh->local.nl_pid ||
h->nlmsg_seq != seq) {
if (junk) {
err = junk(&nladdr, h, jarg);
if (err < 0)
return err;
}
goto cont;
}
if (h->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = NLMSG_DATA(h);
if (l < sizeof(struct nlmsgerr))
nfnl_error("ERROR truncated\n");
else {
errno = -err->error;
if (errno == 0) {
if (answer)
memcpy(answer, h, h->nlmsg_len);
return 0;
}
perror("NFNETLINK answers");
}
return err->error;
}
if (answer) {
memcpy(answer, h, h->nlmsg_len);
return 0;
}
nfnl_error("Unexpected reply!\n");
cont:
status -= NLMSG_ALIGN(len);
h = (struct nlmsghdr *)((char *)h + NLMSG_ALIGN(len));
}
if (msg.msg_flags & MSG_TRUNC) {
nfnl_error("Messages truncated\n");
continue;
}
if (status)
nfnl_error("Remnant of size %d\n", status);
}
}
/**
* nfnl_addattr_l - Add variable length attribute to nlmsghdr
* @n: netlink message header to which attribute is to be added
* @maxlen: maximum length of netlink message header
* @type: type of new attribute
* @data: content of new attribute
* @len: attribute length
*/
int nfnl_addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data,
int alen)
{
int len = NFA_LENGTH(alen);
struct nfattr *nfa;
assert(n);
assert(maxlen > 0);
assert(type >= 0);
if ((NLMSG_ALIGN(n->nlmsg_len) + len) > maxlen) {
errno = ENOSPC;
return -1;
}
nfa = NLMSG_TAIL(n);
nfa->nfa_type = type;
nfa->nfa_len = len;
memcpy(NFA_DATA(nfa), data, alen);
n->nlmsg_len = (NLMSG_ALIGN(n->nlmsg_len) + NFA_ALIGN(len));
return 0;
}
/**
* nfnl_nfa_addattr_l - Add variable length attribute to struct nfattr
*
* @nfa: struct nfattr
* @maxlen: maximal length of nfattr buffer
* @type: type for new attribute
* @data: content of new attribute
* @alen: length of new attribute
*
*/
int nfnl_nfa_addattr_l(struct nfattr *nfa, int maxlen, int type,
const void *data, int alen)
{
struct nfattr *subnfa;
int len = NFA_LENGTH(alen);
assert(nfa);
assert(maxlen > 0);
assert(type >= 0);
if (NFA_ALIGN(nfa->nfa_len) + len > maxlen) {
errno = ENOSPC;
return -1;
}
subnfa = (struct nfattr *)(((char *)nfa) + NFA_ALIGN(nfa->nfa_len));
subnfa->nfa_type = type;
subnfa->nfa_len = len;
memcpy(NFA_DATA(subnfa), data, alen);
nfa->nfa_len = NFA_ALIGN(nfa->nfa_len) + len;
return 0;
}
/**
* nfnl_addattr8 - Add u_int8_t attribute to nlmsghdr
*
* @n: netlink message header to which attribute is to be added
* @maxlen: maximum length of netlink message header
* @type: type of new attribute
* @data: content of new attribute
*/
int nfnl_addattr8(struct nlmsghdr *n, int maxlen, int type, u_int8_t data)
{
assert(n);
assert(maxlen > 0);
assert(type >= 0);
return nfnl_addattr_l(n, maxlen, type, &data, sizeof(data));
}
/**
* nfnl_nfa_addattr16 - Add u_int16_t attribute to struct nfattr
*
* @nfa: struct nfattr
* @maxlen: maximal length of nfattr buffer
* @type: type for new attribute
* @data: content of new attribute
*
*/
int nfnl_nfa_addattr16(struct nfattr *nfa, int maxlen, int type,
u_int16_t data)
{
assert(nfa);
assert(maxlen > 0);
assert(type >= 0);
return nfnl_nfa_addattr_l(nfa, maxlen, type, &data, sizeof(data));
}
/**
* nfnl_addattr16 - Add u_int16_t attribute to nlmsghdr
*
* @n: netlink message header to which attribute is to be added
* @maxlen: maximum length of netlink message header
* @type: type of new attribute
* @data: content of new attribute
*
*/
int nfnl_addattr16(struct nlmsghdr *n, int maxlen, int type,
u_int16_t data)
{
assert(n);
assert(maxlen > 0);
assert(type >= 0);
return nfnl_addattr_l(n, maxlen, type, &data, sizeof(data));
}
/**
* nfnl_nfa_addattr32 - Add u_int32_t attribute to struct nfattr
*
* @nfa: struct nfattr
* @maxlen: maximal length of nfattr buffer
* @type: type for new attribute
* @data: content of new attribute
*
*/
int nfnl_nfa_addattr32(struct nfattr *nfa, int maxlen, int type,
u_int32_t data)
{
assert(nfa);
assert(maxlen > 0);
assert(type >= 0);
return nfnl_nfa_addattr_l(nfa, maxlen, type, &data, sizeof(data));
}
/**
* nfnl_addattr32 - Add u_int32_t attribute to nlmsghdr
*
* @n: netlink message header to which attribute is to be added
* @maxlen: maximum length of netlink message header
* @type: type of new attribute
* @data: content of new attribute
*
*/
int nfnl_addattr32(struct nlmsghdr *n, int maxlen, int type,
u_int32_t data)
{
assert(n);
assert(maxlen > 0);
assert(type >= 0);
return nfnl_addattr_l(n, maxlen, type, &data, sizeof(data));
}
/**
* nfnl_parse_attr - Parse a list of nfattrs into a pointer array
*
* @tb: pointer array, will be filled in (output)
* @max: size of pointer array
* @nfa: pointer to list of nfattrs
* @len: length of 'nfa'
*
* The returned value is equal to the number of remaining bytes of the netlink
* message that cannot be parsed.
*/
int nfnl_parse_attr(struct nfattr *tb[], int max, struct nfattr *nfa, int len)
{
assert(tb);
assert(max > 0);
assert(nfa);
memset(tb, 0, sizeof(struct nfattr *) * max);
while (NFA_OK(nfa, len)) {
if (NFA_TYPE(nfa) <= max)
tb[NFA_TYPE(nfa)-1] = nfa;
nfa = NFA_NEXT(nfa,len);
}
return len;
}
/**
* nfnl_build_nfa_iovec - Build two iovec's from tag, length and value
*
* @iov: pointer to array of two 'struct iovec' (caller-allocated)
* @nfa: pointer to 'struct nfattr' (caller-allocated)
* @type: type (tag) of attribute
* @len: length of value
* @val: pointer to buffer containing 'value'
*
*/
void nfnl_build_nfa_iovec(struct iovec *iov, struct nfattr *nfa,
u_int16_t type, u_int32_t len, unsigned char *val)
{
assert(iov);
assert(nfa);
/* Set the attribut values */
nfa->nfa_len = sizeof(struct nfattr) + len;
nfa->nfa_type = type;
iov[0].iov_base = nfa;
iov[0].iov_len = sizeof(*nfa);
iov[1].iov_base = val;
iov[1].iov_len = NFA_ALIGN(len);
}
#ifndef SO_RCVBUFFORCE
#define SO_RCVBUFFORCE (33)
#endif
/**
* nfnl_rcvbufsiz - set the socket buffer size
* @h: nfnetlink handler
* @size: size of the buffer we want to set
*
* This function sets the new size of the socket buffer. Use this setting
* to increase the socket buffer size if your system is reporting ENOBUFS
* errors.
*
* This function returns the new size of the socket buffer.
*/
unsigned int nfnl_rcvbufsiz(const struct nfnl_handle *h, unsigned int size)
{
int status;
socklen_t socklen = sizeof(size);
unsigned int read_size = 0;
assert(h);
/* first we try the FORCE option, which is introduced in kernel
* 2.6.14 to give "root" the ability to override the system wide
* maximum */
status = setsockopt(h->fd, SOL_SOCKET, SO_RCVBUFFORCE, &size, socklen);
if (status < 0) {
/* if this didn't work, we try at least to get the system
* wide maximum (or whatever the user requested) */
setsockopt(h->fd, SOL_SOCKET, SO_RCVBUF, &size, socklen);
}
getsockopt(h->fd, SOL_SOCKET, SO_RCVBUF, &read_size, &socklen);
return read_size;
}
/**
* nfnl_get_msg_first - get the first message of a multipart netlink message
* @h: nfnetlink handle
* @buf: data received that we want to process
* @len: size of the data received
*
* This function returns a pointer to the first netlink message contained
* in the chunk of data received from certain nfnetlink subsystem.
*
* On success, a valid address that points to the netlink message is returned.
* On error, NULL is returned.
*/
struct nlmsghdr *nfnl_get_msg_first(struct nfnl_handle *h,
const unsigned char *buf,
size_t len)
{
struct nlmsghdr *nlh;
assert(h);
assert(buf);
assert(len > 0);
/* first message in buffer */
nlh = (struct nlmsghdr *)buf;
if (!NLMSG_OK(nlh, len))
return NULL;
h->last_nlhdr = nlh;
return nlh;
}
struct nlmsghdr *nfnl_get_msg_next(struct nfnl_handle *h,
const unsigned char *buf,
size_t len)
{
struct nlmsghdr *nlh;
size_t remain_len;
assert(h);
assert(buf);
assert(len > 0);
/* if last header in handle not inside this buffer,
* drop reference to last header */
if (!h->last_nlhdr ||
(unsigned char *)h->last_nlhdr >= (buf + len) ||
(unsigned char *)h->last_nlhdr < buf) {
h->last_nlhdr = NULL;
return NULL;
}
/* n-th part of multipart message */
if (h->last_nlhdr->nlmsg_type == NLMSG_DONE ||
h->last_nlhdr->nlmsg_flags & NLM_F_MULTI) {
/* if last part in multipart message or no
* multipart message at all, return */
h->last_nlhdr = NULL;
return NULL;
}
remain_len = (len - ((unsigned char *)h->last_nlhdr - buf));
nlh = NLMSG_NEXT(h->last_nlhdr, remain_len);
if (!NLMSG_OK(nlh, remain_len)) {
h->last_nlhdr = NULL;
return NULL;
}
h->last_nlhdr = nlh;
return nlh;
}
/**
* nfnl_callback_register - register a callback for a certain message type
* @ssh: nfnetlink subsys handler
* @type: subsys call
* @cb: nfnetlink callback to be registered
*
* On success, 0 is returned. On error, -1 is returned and errno is set
* appropiately.
*/
int nfnl_callback_register(struct nfnl_subsys_handle *ssh,
u_int8_t type, struct nfnl_callback *cb)
{
assert(ssh);
assert(cb);
if (type >= ssh->cb_count) {
errno = EINVAL;
return -1;
}
memcpy(&ssh->cb[type], cb, sizeof(*cb));
return 0;
}
/**
* nfnl_callback_unregister - unregister a certain callback
* @ssh: nfnetlink subsys handler
* @type: subsys call
*
* On sucess, 0 is returned. On error, -1 is returned and errno is
* set appropiately.
*/
int nfnl_callback_unregister(struct nfnl_subsys_handle *ssh, u_int8_t type)
{
assert(ssh);
if (type >= ssh->cb_count) {
errno = EINVAL;
return -1;
}
ssh->cb[type].call = NULL;
return 0;
}
int nfnl_check_attributes(const struct nfnl_handle *h,
const struct nlmsghdr *nlh,
struct nfattr *nfa[])
{
assert(h);
assert(nlh);
assert(nfa);
int min_len;
u_int8_t type = NFNL_MSG_TYPE(nlh->nlmsg_type);
u_int8_t subsys_id = NFNL_SUBSYS_ID(nlh->nlmsg_type);
const struct nfnl_subsys_handle *ssh;
struct nfnl_callback *cb;
if (subsys_id > NFNL_MAX_SUBSYS)
return -EINVAL;
ssh = &h->subsys[subsys_id];
cb = &ssh->cb[type];
#if 1
/* checks need to be enabled as soon as this is called from
* somebody else than __nfnl_handle_msg */
if (type >= ssh->cb_count)
return -EINVAL;
min_len = NLMSG_SPACE(sizeof(struct nfgenmsg));
if (nlh->nlmsg_len < min_len)
return -EINVAL;
#endif
memset(nfa, 0, sizeof(struct nfattr *) * cb->attr_count);
if (nlh->nlmsg_len > min_len) {
struct nfattr *attr = NFM_NFA(NLMSG_DATA(nlh));
int attrlen = nlh->nlmsg_len - NLMSG_ALIGN(min_len);
while (NFA_OK(attr, attrlen)) {
unsigned int flavor = NFA_TYPE(attr);
if (flavor) {
if (flavor > cb->attr_count) {
/* we have received an attribute from
* the kernel which we don't understand
* yet. We have to silently ignore this
* for the sake of future compatibility */
attr = NFA_NEXT(attr, attrlen);
continue;
}
nfa[flavor - 1] = attr;
}
attr = NFA_NEXT(attr, attrlen);
}
}
return 0;
}
static int __nfnl_handle_msg(struct nfnl_handle *h, struct nlmsghdr *nlh,
int len)
{
struct nfnl_subsys_handle *ssh;
u_int8_t type = NFNL_MSG_TYPE(nlh->nlmsg_type);
u_int8_t subsys_id = NFNL_SUBSYS_ID(nlh->nlmsg_type);
int err = 0;
if (subsys_id > NFNL_MAX_SUBSYS)
return -1;
ssh = &h->subsys[subsys_id];
if (nlh->nlmsg_len < NLMSG_LENGTH(NLMSG_ALIGN(sizeof(struct nfgenmsg))))
return -1;
if (type >= ssh->cb_count)
return -1;
if (ssh->cb[type].attr_count) {
struct nfattr *nfa[ssh->cb[type].attr_count];
err = nfnl_check_attributes(h, nlh, nfa);
if (err < 0)
return err;
if (ssh->cb[type].call)
return ssh->cb[type].call(nlh, nfa, ssh->cb[type].data);
}
return 0;
}
int nfnl_handle_packet(struct nfnl_handle *h, char *buf, int len)
{
while (len >= NLMSG_SPACE(0)) {
u_int32_t rlen;
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
if (nlh->nlmsg_len < sizeof(struct nlmsghdr)
|| len < nlh->nlmsg_len)
return -1;
rlen = NLMSG_ALIGN(nlh->nlmsg_len);
if (rlen > len)
rlen = len;
if (__nfnl_handle_msg(h, nlh, rlen) < 0)
return -1;
len -= rlen;
buf += rlen;
}
return 0;
}
static int nfnl_is_error(struct nfnl_handle *h, struct nlmsghdr *nlh)
{
/* This message is an ACK or a DONE */
if (nlh->nlmsg_type == NLMSG_ERROR ||
(nlh->nlmsg_type == NLMSG_DONE &&
nlh->nlmsg_flags & NLM_F_MULTI)) {
if (nlh->nlmsg_len < NLMSG_ALIGN(sizeof(struct nlmsgerr))) {
errno = EBADMSG;
return 1;
}
errno = -(*((int *)NLMSG_DATA(nlh)));
return 1;
}
return 0;
}
/* On error, -1 is returned and errno is set appropiately. On success,
* 0 is returned if there is no more data to process, >0 if there is
* more data to process */
static int nfnl_step(struct nfnl_handle *h, struct nlmsghdr *nlh)
{
struct nfnl_subsys_handle *ssh;
u_int8_t type = NFNL_MSG_TYPE(nlh->nlmsg_type);
u_int8_t subsys_id = NFNL_SUBSYS_ID(nlh->nlmsg_type);
/* Is this an error message? */
if (nfnl_is_error(h, nlh)) {
/* This is an ACK */
if (errno == 0)
return 0;
/* This an error message */
return -1;
}
/* nfnetlink sanity checks: check for nfgenmsg size */
if (nlh->nlmsg_len < NLMSG_SPACE(sizeof(struct nfgenmsg))) {
errno = ENOSPC;
return -1;
}
if (subsys_id > NFNL_MAX_SUBSYS) {
errno = ENOENT;
return -1;
}
ssh = &h->subsys[subsys_id];
if (!ssh) {
errno = ENOENT;
return -1;
}
if (type >= ssh->cb_count) {
errno = ENOENT;
return -1;
}
if (ssh->cb[type].attr_count) {
int err;
struct nfattr *tb[ssh->cb[type].attr_count];
struct nfattr *attr = NFM_NFA(NLMSG_DATA(nlh));
int min_len = NLMSG_SPACE(sizeof(struct nfgenmsg));
int len = nlh->nlmsg_len - NLMSG_ALIGN(min_len);
err = nfnl_parse_attr(tb, ssh->cb[type].attr_count, attr, len);
if (err == -1)
return -1;
if (ssh->cb[type].call) {
/*
* On error, the callback returns NFNL_CB_FAILURE and
* errno must be explicitely set. On success,
* NFNL_CB_STOP is returned and we're done, otherwise
* NFNL_CB_CONTINUE means that we want to continue
* data processing.
*/
return ssh->cb[type].call(nlh,
tb,
ssh->cb[type].data);
}
}
/* no callback set, continue data processing */
return 1;
}
/**
* nfnl_process - process data coming from a nfnetlink system
* @h: nfnetlink handler
* @buf: buffer that contains the netlink message
* @len: size of the data contained in the buffer (not the buffer size)
*
* This function processes all the nfnetlink messages contained inside a
* buffer. It performs the appropiate sanity checks and passes the message
* to a certain handler that is registered via register_callback().
*
* On success, NFNL_CB_STOP is returned if the data processing has finished.
* If a value NFNL_CB_CONTINUE is returned, then there is more data to
* process. On error, NFNL_CB_CONTINUE is returned and errno is set to the
* appropiate value.
*
* In case that the callback returns NFNL_CB_FAILURE, errno may be set by
* the library client. If your callback decides not to process data anymore
* for any reason, then it must return NFNL_CB_STOP. Otherwise, if the
* callback continues the processing NFNL_CB_CONTINUE is returned.
*/
int nfnl_process(struct nfnl_handle *h, const unsigned char *buf, size_t len)
{
int ret = 0;
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
assert(h);
assert(buf);
assert(len > 0);
/* check for out of sequence message */
if (nlh->nlmsg_seq && nlh->nlmsg_seq != h->seq) {
errno = EILSEQ;
return -1;
}
while (len >= NLMSG_SPACE(0) && NLMSG_OK(nlh, len)) {
ret = nfnl_step(h, nlh);
if (ret <= NFNL_CB_STOP)
break;
nlh = NLMSG_NEXT(nlh, len);
}
return ret;
}
/*
* New parsing functions based on iterators
*/
struct nfnl_iterator {
struct nlmsghdr *nlh;
unsigned int len;
};
/**
* nfnl_iterator_create: create an nfnetlink iterator
* @h: nfnetlink handler
* @buf: buffer that contains data received from a nfnetlink system
* @len: size of the data contained in the buffer (not the buffer size)
*
* This function creates an iterator that can be used to parse nfnetlink
* message one by one. The iterator gives more control to the programmer
* in the messages processing.
*
* On success, a valid address is returned. On error, NULL is returned
* and errno is set to the appropiate value.
*/
struct nfnl_iterator *
nfnl_iterator_create(const struct nfnl_handle *h,
const char *buf,
size_t len)
{
struct nlmsghdr *nlh;
struct nfnl_iterator *it;
assert(h);
assert(buf);
assert(len > 0);
it = malloc(sizeof(struct nfnl_iterator));
if (!it) {
errno = ENOMEM;
return NULL;
}
/* first message in buffer */
nlh = (struct nlmsghdr *)buf;
if (len < NLMSG_SPACE(0) || !NLMSG_OK(nlh, len)) {
free(it);
errno = EBADMSG;
return NULL;
}
it->nlh = nlh;
it->len = len;
return it;
}
/**
* nfnl_iterator_destroy - destroy a nfnetlink iterator
* @it: nfnetlink iterator
*
* This function destroys a certain iterator. Nothing is returned.
*/
void nfnl_iterator_destroy(struct nfnl_iterator *it)
{
assert(it);
free(it);
}
/**
* nfnl_iterator_process - process a nfnetlink message
* @h: nfnetlink handler
* @it: nfnetlink iterator that contains the current message to be proccesed
*
* This function process just the current message selected by the iterator.
* On success, a value greater or equal to zero is returned. On error,
* -1 is returned and errno is appropiately set.
*/
int nfnl_iterator_process(struct nfnl_handle *h, struct nfnl_iterator *it)
{
assert(h);
assert(it->nlh);
/* check for out of sequence message */
if (it->nlh->nlmsg_seq && it->nlh->nlmsg_seq != h->seq) {
errno = EILSEQ;
return -1;
}
if (it->len < NLMSG_SPACE(0) || !NLMSG_OK(it->nlh, it->len)) {
errno = EBADMSG;
return -1;
}
return nfnl_step(h, it->nlh);
}
/**
* nfnl_iterator_next - get the next message hold by the iterator
* @h: nfnetlink handler
* @it: nfnetlink iterator that contains the current message processed
*
* This function update the current message to be processed pointer.
* It returns NFNL_CB_CONTINUE if there is still more messages to be
* processed, otherwise NFNL_CB_STOP is returned.
*/
int nfnl_iterator_next(const struct nfnl_handle *h, struct nfnl_iterator *it)
{
assert(h);
assert(it);
it->nlh = NLMSG_NEXT(it->nlh, it->len);
if (!it->nlh)
return 0;
return 1;
}
/**
* nfnl_catch - get responses from the nfnetlink system and process them
* @h: nfnetlink handler
*
* This function handles the data received from the nfnetlink system.
* For example, events generated by one of the subsystems. The message
* is passed to the callback registered via callback_register(). Note that
* this a replacement of nfnl_listen and its use is recommended.
*
* On success, 0 is returned. On error, a -1 is returned. If you do not
* want to listen to events anymore, then your callback must return
* NFNL_CB_STOP.
*
* Note that ENOBUFS is returned in case that nfnetlink is exhausted. In
* that case is possible that the information requested is incomplete.
*/
int nfnl_catch(struct nfnl_handle *h)
{
int ret;
assert(h);
while (1) {
unsigned char buf[h->rcv_buffer_size]
__attribute__ ((aligned));
ret = nfnl_recv(h, buf, sizeof(buf));
if (ret == -1) {
/* interrupted syscall must retry */
if (errno == EINTR)
continue;
break;
}
ret = nfnl_process(h, buf, ret);
if (ret <= NFNL_CB_STOP)
break;
}
return ret;
}
/**
* nfnl_query - request/response communication challenge
* @h: nfnetlink handler
* @nlh: nfnetlink message to be sent
*
* This function sends a nfnetlink message to a certain subsystem and
* receives the response messages associated, such messages are passed to
* the callback registered via register_callback(). Note that this function
* is a replacement for nfnl_talk, its use is recommended.
*
* On success, 0 is returned. On error, a negative is returned. If your
* does not want to listen to events anymore, then your callback must
* return NFNL_CB_STOP.
*
* Note that ENOBUFS is returned in case that nfnetlink is exhausted. In
* that case is possible that the information requested is incomplete.
*/
int nfnl_query(struct nfnl_handle *h, struct nlmsghdr *nlh)
{
assert(h);
assert(nlh);
if (nfnl_send(h, nlh) == -1)
return -1;
return nfnl_catch(h);
}