libnfnetlink new API #2
From: | Pablo Neira Ayuso <pablo@netfilter.org> | |
To: | Netfilter Development Mailinglist <netfilter-devel@lists.netfilter.org> | |
Subject: | [RFC][PATCH] libnfnetlink new API #2 | |
Date: | Tue, 25 Jul 2006 17:39:43 +0200 | |
Cc: | Harald Welte <laforge@netfilter.org>, Patrick McHardy <kaber@trash.net> |
Hi, Since I'll be leaving for two weeks, I'd like to put a patch for libnfnetlink on the table that I'm currently distributing with conntrackd for further discussion. I'd like to see this patch or something similar in mainline someday. This patch: - Fixes error handling that is currently broken, errors are now reported via errno so everyone could use perror(...) to get a more detailed description to know what is going wrong. Basically the new functions return -1 and set errno appropiately. - Adds Documentation that comes handy for developers. - Introduces replacement for nfnl_listen (nfnl_receive_process) and for nfnl_talk (nfnl_send_received_process), both are integrated with the nfnl_subsys_handle logic introduced by Harald, that IMHO must be the right direction, and set errno appropiately in case of error. These new functions obsolete nfnl_listen and nfnl_talk, we can add a clause __deprecated to warn programmers without removing them. - Iterator API: to loop over a multipart netlink message and process it. This gives more control in the message processing. It is similar to Harald's nfnl_get_first_msg, nfnl_get_msg_next and nfnl_handle_packet set of functions but sets errno and move iterator private information out of nfnl_handle. I must confess that in this case I don't like too much the idea of providing too many function to do the same but my API looks friendlier I think, programmers are familiar with the concept of iterators. - Introduce assertions to check input data: This can catch up wrong use of the API and errors and the application can break "nicer" (if breakages would ever be nice...) that segfaulting. I have seen these in others libraries. In short: I think that we can deprecate old functions (just adding a warning in compilation time) and remove them in version 2, I have seen this in other libraries: we maintaining an old version 1 for those that don't want to move forward some time but provide a clean version 2 and drop early design errors. BTW, probably the name of some functions are ugly, I accept suggestions ;) @Patrick: I think that Harald has more in-deep knowledge about the libraries but, since he's really busy these days, your impressions on this issue can be also worth as well. -- The dawn of the fourth age of Linux firewalling is coming; a time of great struggle and heroic deeds -- J.Kadlecsik got inspired by J.Morris Index: include/libnfnetlink/libnfnetlink.h =================================================================== --- include/libnfnetlink/libnfnetlink.h (revisión: 6631) +++ include/libnfnetlink/libnfnetlink.h (copia de trabajo) @@ -98,6 +98,36 @@ const unsigned char *buf, size_t len); +/* join a certain netlink multicast group */ +extern int nfnl_join(const struct nfnl_handle *nfnlh, unsigned int group); + +/* process a netlink message */ +extern int nfnl_process(struct nfnl_handle *h, + const unsigned char *buf, + size_t len); + +/* iterator API */ + +extern struct nfnl_iterator * +nfnl_iterator_create(const struct nfnl_handle *h, + const char *buf, + size_t len); + +extern void nfnl_iterator_destroy(struct nfnl_iterator *it); + +extern int nfnl_iterator_process(struct nfnl_handle *h, + struct nfnl_iterator *it); + +extern int nfnl_iterator_next(const struct nfnl_handle *h, + struct nfnl_iterator *it); + +/* replacement for nfnl_listen */ +extern int nfnl_receive_process(struct nfnl_handle *h); + +/* replacement for nfnl_talk */ +extern int nfnl_send_receive_process(struct nfnl_handle *h, + struct nlmsghdr *nlh); + #define nfnl_attr_present(tb, attr) \ (tb[attr-1]) Index: src/libnfnetlink.c =================================================================== --- src/libnfnetlink.c (revisión: 6631) +++ src/libnfnetlink.c (copia de trabajo) @@ -1,6 +1,7 @@ /* libnfnetlink.c: generic library for communication with netfilter * * (C) 2002-2006 by Harald Welte <laforge@gnumonks.org> + * (C) 2006 by Pablo Neira Ayuso <pablo@netfilter.org> * * Based on some original ideas from Jay Schulist <jschlst@samba.org> * @@ -24,6 +25,13 @@ * 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 */ #include <stdlib.h> @@ -33,7 +41,7 @@ #include <string.h> #include <time.h> #include <netinet/in.h> - +#include <assert.h> #include <sys/types.h> #include <sys/socket.h> @@ -49,7 +57,7 @@ #define SOL_NETLINK 270 #endif - +/* FIXME: this should vanish, but it is used by listen() and talk() */ #define nfnl_error(format, args...) \ fprintf(stderr, "%s: " format "\n", __FUNCTION__, ## args) @@ -103,8 +111,16 @@ } } +/** + * 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; } @@ -116,14 +132,11 @@ 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 < 0) { - nfnl_error("bind(netlink): %s", strerror(errno)); - return err; - } + if (err == -1) + return -1; nfnlh->subscriptions = new_subscriptions; @@ -131,10 +144,13 @@ } /** - * nfnl_open - open a netlink socket + * nfnl_open - open a nfnetlink handler * - * nfnlh: libnfnetlink handle to be allocated by user + * 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) { @@ -148,10 +164,8 @@ memset(nfnlh, 0, sizeof(*nfnlh)); nfnlh->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER); - if (nfnlh->fd < 0) { - nfnl_error("socket(netlink): %s", strerror(errno)); + if (nfnlh->fd == -1) goto err_free; - } nfnlh->local.nl_family = AF_NETLINK; nfnlh->peer.nl_family = AF_NETLINK; @@ -160,12 +174,11 @@ err = getsockname(nfnlh->fd, (struct sockaddr *)&nfnlh->local, &addr_len); if (addr_len != sizeof(nfnlh->local)) { - nfnl_error("Bad address length (%u != %zd)", addr_len, - sizeof(nfnlh->local)); + errno = EINVAL; goto err_close; } if (nfnlh->local.nl_family != AF_NETLINK) { - nfnl_error("Bad address family %d", nfnlh->local.nl_family); + errno = EINVAL; goto err_close; } nfnlh->seq = time(NULL); @@ -182,8 +195,7 @@ err = getsockname(nfnlh->fd, (struct sockaddr *)&nfnlh->local, &addr_len); if (addr_len != sizeof(nfnlh->local)) { - nfnl_error("Bad address length (%u != %zd)", addr_len, - sizeof(nfnlh->local)); + errno = EINVAL; goto err_close; } @@ -198,11 +210,18 @@ /** * 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 * - * 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, @@ -210,30 +229,30 @@ { struct nfnl_subsys_handle *ssh; + 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 = malloc(sizeof(*(ssh->cb)) * cb_count); - if (!ssh->cb) { - + if (!ssh->cb) return NULL; - } ssh->nfnlh = nfnlh; ssh->cb_count = cb_count; ssh->subscriptions = subscriptions; ssh->subsys_id = subsys_id; - /* FIXME: reimplement this based on - * setsockopt(nfnlh->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,,) */ + /* although now we have nfnl_join to subscribe to certain + * groups, just keep this to ensure compatibility */ if (recalc_rebind_subscriptions(nfnlh) < 0) { free(ssh->cb); ssh->cb = NULL; @@ -243,8 +262,16 @@ 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) { @@ -254,15 +281,18 @@ } /** - * nfnl_close - close netlink socket + * nfnl_close - close a nfnetlink handler + * @nfnlh: nfnetlink handler * - * nfnlh: libnfnetlink handle - * + * 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 i, ret; + assert(nfnlh); + for (i = 0; i < NFNL_MAX_SUBSYS; i++) nfnl_subsys_close(&nfnlh->subsys[i]); @@ -276,13 +306,37 @@ } /** + * 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 * - * nfnlh: libnfnetlink handle - * n: netlink message + * On success, 0 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, @@ -292,6 +346,9 @@ 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); } @@ -300,6 +357,8 @@ { struct msghdr msg; + assert(nfnlh); + msg.msg_name = (struct sockaddr *) &nfnlh->peer; msg.msg_namelen = sizeof(nfnlh->peer); msg.msg_iov = (struct iovec *) iov; @@ -313,18 +372,17 @@ /** * 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 * - * nfnlh: libnfnetlink handle - * nlh: netlink header 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 - * - * NOTE: the nlmsghdr must point to a memory region of at least - * the size of struct nlmsghdr + struct nfgenmsg - * + * 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, @@ -333,6 +391,9 @@ u_int16_t msg_type, u_int16_t msg_flags) { + assert(ssh); + assert(nlh); + struct nfgenmsg *nfg = (struct nfgenmsg *) ((void *)nlh + sizeof(*nlh)); @@ -367,6 +428,22 @@ 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) { @@ -374,6 +451,10 @@ int status; struct nlmsghdr *nlh; struct sockaddr_nl peer; + + assert(h); + assert(buf); + assert(len > 0); if (len < sizeof(struct nlmsgerr) || len < sizeof(struct nlmsghdr)) @@ -399,9 +480,8 @@ } /** * nfnl_listen: listen for one or more netlink messages - * - * nfnhl: libnfnetlink handle - * handler: callback function to be called for every netlink message + * @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 @@ -412,8 +492,14 @@ * 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 + * @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, @@ -510,6 +596,20 @@ 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_send_received_process() 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 *), @@ -628,13 +728,11 @@ /** * 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 - * alen: attribute length - * + * @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, void *data, int alen) @@ -642,9 +740,12 @@ int len = NFA_LENGTH(alen); struct nfattr *nfa; + assert(n); + assert(maxlen > 0); + assert(type >= 0); + if ((NLMSG_ALIGN(n->nlmsg_len) + len) > maxlen) { - nfnl_error("%d greater than maxlen (%d)\n", - NLMSG_ALIGN(n->nlmsg_len) + len, maxlen); + errno = ENOSPC; return -1; } @@ -672,8 +773,14 @@ struct nfattr *subnfa; int len = NFA_LENGTH(alen); - if ((NFA_OK(nfa, nfa->nfa_len) + len) > maxlen) + assert(nfa); + assert(maxlen > 0); + assert(type >= 0); + + if ((NFA_OK(nfa, nfa->nfa_len) + len) > maxlen) { + errno = ENOSPC; return -1; + } subnfa = (struct nfattr *)(((char *)nfa) + NFA_OK(nfa, nfa->nfa_len)); subnfa->nfa_type = type; @@ -696,6 +803,9 @@ 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)); } @@ -712,6 +822,10 @@ 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)); } @@ -727,6 +841,9 @@ 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)); } @@ -743,6 +860,10 @@ 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)); } @@ -757,6 +878,10 @@ */ 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)) { @@ -764,8 +889,7 @@ tb[NFA_TYPE(nfa)-1] = nfa; nfa = NFA_NEXT(nfa,len); } - if (len) - nfnl_error("deficit (%d) len (%d).\n", len, nfa->nfa_len); + assert(len == 0); return 0; } @@ -783,6 +907,9 @@ 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; @@ -797,12 +924,25 @@ #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(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 */ @@ -817,13 +957,28 @@ 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)) @@ -840,6 +995,10 @@ 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 || @@ -871,9 +1030,21 @@ 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) return -EINVAL; @@ -882,8 +1053,18 @@ 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) return -EINVAL; @@ -896,6 +1077,10 @@ 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); @@ -996,3 +1181,341 @@ } 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 -1 and errno must + * be explicitely set. On success, 0 is returned + * and we're done, otherwise >0 is returned that + * 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, 0 is returned if the data processing has finished. If a + * value > 0 is returned, then there is more data to process. On error, + * -1 is returned and errno is set to the appropiate value. + * + * Note that the callback must return -1 and set errno in case of error. + * If your callback decides not to process data anymore for any reason, + * then it must return 0. Otherwise, if the callback continues the + * processing 1 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); + + while (len >= NLMSG_SPACE(0) && NLMSG_OK(nlh, len)) { + + ret = nfnl_step(h, nlh); + if (ret <= 0) + 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); + + 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 1 if there is still more messages to be processed, otherwise + * 0 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_receive_process - process responses from the nfnetlink system + * @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 your does not + * want to listen to events anymore, then it must return a value equal + * to -1 and set errno to 0 (success). + * + * Note that ENOBUFS is returned in case that nfnetlink is exhausted. In + * that case is possible that the information requested is incomplete. + */ +int nfnl_receive_process(struct nfnl_handle *h) +{ + int ret; + unsigned int size = NFNL_BUFFSIZE; + + assert(h); + + /* + * Since nfqueue can send big packets, we don't know how big + * must be the buffer that have to store the received data. + */ + { + unsigned char buf[size]; + struct sockaddr_nl peer; + struct iovec iov = { + .iov_len = size, + }; + struct msghdr msg = { + .msg_name = (void *) &peer, + .msg_namelen = sizeof(peer), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = 0 + }; + + memset(&peer, 0, sizeof(peer)); + peer.nl_family = AF_NETLINK; + iov.iov_base = buf; + iov.iov_len = size; + +retry: ret = recvmsg(h->fd, &msg, MSG_PEEK); + if (ret == -1) { + /* interrupted syscall must retry */ + if (errno == EINTR) + goto retry; + /* otherwise give up */ + return -1; + } + + if (msg.msg_flags & MSG_TRUNC) + /* maximum size of data received from netlink */ + size = 65535; + } + + /* now, receive data from netlink */ + while (1) { + unsigned char buf[size]; + + 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 <= 0) + break; + } + + return ret; +} + +/** + * nfnl_send_receive_process - request/response challenge + * @h: nfnetlink handler + * @nlh: nfnetlink message to be sent + * + * This function is sends a nfnetlink message to a certain subsystem + * and receives the response that is processed by 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 it must return a value + * lesser or equal to 0. + * + * Note that ENOBUFS is returned in case that nfnetlink is exhausted. In + * that case is possible that the information requested is incomplete. + */ +int nfnl_send_receive_process(struct nfnl_handle *h, struct nlmsghdr *nlh) +{ + assert(h); + assert(nlh); + + if (nfnl_send(h, nlh) == -1) + return -1; + + return nfnl_receive_process(h); +}