/* libnfnetlink.c: generic library for communication with netfilter * * (C) 2002-2006 by Harald Welte * (C) 2006 by Pablo Neira Ayuso * * Based on some original ideas from Jay Schulist * * Development of this code funded by Astaro AG (http://www.astaro.com) * * this software may be used and distributed according to the terms * of the gnu general public license, incorporated herein by reference. * * 2005-09-14 Pablo Neira Ayuso : * Define structure nfnlhdr * Added __be64_to_cpu function * Use NFA_TYPE macro to get the attribute type * * 2006-01-14 Harald Welte : * introduce nfnl_subsys_handle * * 2006-01-15 Pablo Neira Ayuso : * set missing subsys_id in nfnl_subsys_open * set missing nfnlh->local.nl_pid in nfnl_open * * 2006-01-26 Harald Welte : * remove bogus nfnlh->local.nl_pid from nfnl_open ;) * add 16bit attribute functions * * 2006-07-03 Pablo Neira Ayuso : * add iterator API * add replacements for nfnl_listen and nfnl_talk * fix error handling * add assertions * add documentation * minor cleanups */ #include #include #include #include #include #include #include #include #include #include #include #include #include #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 */ 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 */ 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; } 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; } /** * 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; unsigned int addr_len; int err; nfnlh = malloc(sizeof(*nfnlh)); if (!nfnlh) return NULL; memset(nfnlh, 0, sizeof(*nfnlh)); nfnlh->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER); if (nfnlh->fd == -1) goto err_free; nfnlh->local.nl_family = AF_NETLINK; nfnlh->peer.nl_family = AF_NETLINK; addr_len = sizeof(nfnlh->local); err = getsockname(nfnlh->fd, (struct sockaddr *)&nfnlh->local, &addr_len); if (addr_len != sizeof(nfnlh->local)) { errno = EINVAL; goto err_close; } if (nfnlh->local.nl_family != AF_NETLINK) { errno = EINVAL; goto err_close; } 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 (recalc_rebind_subscriptions(nfnlh) < 0) goto err_close; /* use getsockname to get the netlink pid that the kernel assigned us */ addr_len = sizeof(nfnlh->local); err = getsockname(nfnlh->fd, (struct sockaddr *)&nfnlh->local, &addr_len); if (addr_len != sizeof(nfnlh->local)) { errno = EINVAL; goto err_close; } return nfnlh; err_close: close(nfnlh->fd); err_free: free(nfnlh); return NULL; } /** * 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) { 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 = 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 (recalc_rebind_subscriptions(nfnlh) < 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 i, ret; assert(nfnlh); for (i = 0; i < NFNL_MAX_SUBSYS; i++) nfnl_subsys_close(&nfnlh->subsys[i]); ret = close(nfnlh->fd); if (ret < 0) return ret; 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 = (struct nfgenmsg *) ((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; nlh->nlmsg_seq = ++ssh->nfnlh->seq; /* check for wraparounds: assume that seqnum 0 is only used by events */ if (!ssh->nfnlh->seq) nlh->nlmsg_seq = ssh->nfnlh->seq = time(NULL); 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 = (struct nfgenmsg *)((void *)nlh+sizeof(nlh)); return NULL; } if (genmsg) *genmsg = (struct nfgenmsg *)((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]; struct iovec iov; int remain; struct nlmsghdr *h; struct nlmsgerr *msgerr; int quit=0; struct msghdr msg = { (void *)&nladdr, sizeof(nladdr), &iov, 1, NULL, 0, 0 }; 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]; struct sockaddr_nl nladdr; struct nlmsghdr *h; unsigned int seq; int status; struct iovec iov = { (void *)n, n->nlmsg_len }; struct msghdr msg = { (void *)&nladdr, sizeof(nladdr), &iov, 1, NULL, 0, 0 }; 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); exit(1); } } } /** * 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_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; subnfa->nfa_len = len; memcpy(NFA_DATA(subnfa), data, alen); nfa->nfa_len = (NLMSG_ALIGN(nfa->nfa_len) + len); return 0; } /** * 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]; 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); }