From a0c885ae5a79457aa592cb70c27a7dee619762a4 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 16 Apr 2012 19:12:58 +0200 Subject: add new libnetfilter_queue API for libmnl Signed-off-by: Pablo Neira Ayuso --- Make_global.am | 2 +- Makefile.am | 4 +- configure.ac | 9 +- examples/Makefile.am | 7 + examples/nf-queue.c | 164 ++++++++++++++++++++++ include/Makefile.am | 4 +- include/libnetfilter_queue/Makefile.am | 5 +- include/libnetfilter_queue/libnetfilter_queue.h | 14 ++ include/linux/Makefile.am | 1 + include/linux/netfilter/Makefile.am | 1 + include/linux/netfilter/nfnetlink_queue.h | 98 +++++++++++++ m4/gcc4_visibility.m4 | 21 +++ src/Makefile.am | 7 +- src/internal.h | 12 ++ src/nlmsg.c | 174 ++++++++++++++++++++++++ 15 files changed, 510 insertions(+), 13 deletions(-) create mode 100644 examples/Makefile.am create mode 100644 examples/nf-queue.c create mode 100644 include/linux/Makefile.am create mode 100644 include/linux/netfilter/Makefile.am create mode 100644 include/linux/netfilter/nfnetlink_queue.h create mode 100644 m4/gcc4_visibility.m4 create mode 100644 src/internal.h create mode 100644 src/nlmsg.c diff --git a/Make_global.am b/Make_global.am index a4e9bd9..9bc8ea1 100644 --- a/Make_global.am +++ b/Make_global.am @@ -1,2 +1,2 @@ -AM_CPPFLAGS = -I${top_srcdir}/include ${LIBNFNETLINK_CFLAGS} +AM_CPPFLAGS = -I${top_srcdir}/include ${LIBNFNETLINK_CFLAGS} ${LIBMNL_CFLAGS} AM_CFLAGS = -Wall diff --git a/Makefile.am b/Makefile.am index bc2f61c..6b4ef77 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,8 +1,8 @@ ACLOCAL_AMFLAGS = -I m4 -EXTRA_DIST = $(man_MANS) +EXTRA_DIST = $(man_MANS) include/linux -SUBDIRS = include src utils +SUBDIRS = src utils include examples man_MANS = #nfnetlink_queue.3 nfnetlink_queue.7 diff --git a/configure.ac b/configure.ac index 4f17aee..6ebba4f 100644 --- a/configure.ac +++ b/configure.ac @@ -4,6 +4,7 @@ AC_INIT([libnetfilter_queue], [1.0.1]) AC_CONFIG_AUX_DIR([build-aux]) AC_CANONICAL_HOST AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE([-Wall foreign subdir-objects tar-pax no-dist-gzip dist-bzip2 1.6]) @@ -16,6 +17,7 @@ AM_PROG_CC_C_O AC_DISABLE_STATIC AM_PROG_LIBTOOL AC_PROG_INSTALL +CHECK_GCC_FVISIBILITY case "$host" in *-*-linux*) ;; @@ -24,8 +26,11 @@ esac dnl Dependencies PKG_CHECK_MODULES([LIBNFNETLINK], [libnfnetlink >= 0.0.41]) +PKG_CHECK_MODULES([LIBMNL], [libmnl >= 1.0.3]) dnl Output the makefiles -AC_CONFIG_FILES([Makefile include/Makefile include/libnetfilter_queue/Makefile - src/Makefile utils/Makefile libnetfilter_queue.pc doxygen.cfg]) +AC_CONFIG_FILES([Makefile src/Makefile utils/Makefile examples/Makefile + libnetfilter_queue.pc doxygen.cfg + include/Makefile include/libnetfilter_queue/Makefile + include/linux/Makefile include/linux/netfilter/Makefile]) AC_OUTPUT diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..1906697 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,7 @@ +include ${top_srcdir}/Make_global.am + +check_PROGRAMS = nf-queue + +nf_queue_SOURCES = nf-queue.c +nf_queue_LDADD = ../src/libnetfilter_queue.la +nf_queue_LDFLAGS = -dynamic -lmnl diff --git a/examples/nf-queue.c b/examples/nf-queue.c new file mode 100644 index 0000000..8b4b63d --- /dev/null +++ b/examples/nf-queue.c @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +static struct mnl_socket *nl; + +static struct nlmsghdr * +nfq_hdr_put(char *buf, int type, uint32_t queue_num) +{ + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = (NFNL_SUBSYS_QUEUE << 8) | type; + nlh->nlmsg_flags = NLM_F_REQUEST; + + struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); + nfg->nfgen_family = AF_UNSPEC; + nfg->version = NFNETLINK_V0; + nfg->res_id = htons(queue_num); + + return nlh; +} + +static int +nfq_send_verdict(int queue_num, uint32_t id) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + int ret; + + nlh = nfq_hdr_put(buf, NFQNL_MSG_VERDICT, queue_num); + nfq_nlmsg_verdict_put(nlh, id, NF_ACCEPT); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + perror("mnl_socket_send"); + exit(EXIT_FAILURE); + } + + return ret; +} + +static int queue_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nfqnl_msg_packet_hdr *ph = NULL; + struct nlattr *attr[NFQA_MAX+1]; + uint32_t id = 0; + struct nfgenmsg *nfg; + + if (nfq_nlmsg_parse(nlh, attr) < 0) { + perror("problems parsing"); + return MNL_CB_ERROR; + } + + nfg = mnl_nlmsg_get_payload(nlh); + + ph = (struct nfqnl_msg_packet_hdr *) + mnl_attr_get_payload(attr[NFQA_PACKET_HDR]); + if (ph == NULL) { + perror("problems retrieving metaheader"); + return MNL_CB_ERROR; + } + + id = ntohl(ph->packet_id); + + printf("packet received (id=%u hw=0x%04x hook=%u)\n", + id, ntohs(ph->hw_protocol), ph->hook); + + nfq_send_verdict(ntohs(nfg->res_id), id); + + return MNL_CB_OK; +} + +int main(int argc, char *argv[]) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + int ret; + unsigned int portid, queue_num; + + if (argc != 2) { + printf("Usage: %s [queue_num]\n", argv[0]); + exit(EXIT_FAILURE); + } + queue_num = atoi(argv[1]); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + perror("mnl_socket_open"); + exit(EXIT_FAILURE); + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + exit(EXIT_FAILURE); + } + portid = mnl_socket_get_portid(nl); + + nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, 0); + nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_PF_UNBIND); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + perror("mnl_socket_send"); + exit(EXIT_FAILURE); + } + + nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, 0); + nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_PF_BIND); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + perror("mnl_socket_send"); + exit(EXIT_FAILURE); + } + + nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, queue_num); + nfq_nlmsg_cfg_put_cmd(nlh, AF_INET, NFQNL_CFG_CMD_BIND); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + perror("mnl_socket_send"); + exit(EXIT_FAILURE); + } + + nlh = nfq_hdr_put(buf, NFQNL_MSG_CONFIG, queue_num); + nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + perror("mnl_socket_send"); + exit(EXIT_FAILURE); + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret == -1) { + perror("mnl_socket_recvfrom"); + exit(EXIT_FAILURE); + } + while (ret > 0) { + uint32_t id; + + ret = mnl_cb_run(buf, ret, 0, portid, queue_cb, NULL); + if (ret < 0){ + perror("mnl_cb_run"); + exit(EXIT_FAILURE); + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret == -1) { + perror("mnl_socket_recvfrom"); + exit(EXIT_FAILURE); + } + } + + mnl_socket_close(nl); + + return 0; +} diff --git a/include/Makefile.am b/include/Makefile.am index 42fd733..54ea0b4 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -1,3 +1 @@ - -SUBDIRS = libnetfilter_queue - +SUBDIRS= libnetfilter_queue linux diff --git a/include/libnetfilter_queue/Makefile.am b/include/libnetfilter_queue/Makefile.am index 188a927..1a92fc6 100644 --- a/include/libnetfilter_queue/Makefile.am +++ b/include/libnetfilter_queue/Makefile.am @@ -1,3 +1,2 @@ - -pkginclude_HEADERS = libnetfilter_queue.h linux_nfnetlink_queue.h - +pkginclude_HEADERS = libnetfilter_queue.h \ + linux_nfnetlink_queue.h diff --git a/include/libnetfilter_queue/libnetfilter_queue.h b/include/libnetfilter_queue/libnetfilter_queue.h index 6b8acd2..b9f16e2 100644 --- a/include/libnetfilter_queue/libnetfilter_queue.h +++ b/include/libnetfilter_queue/libnetfilter_queue.h @@ -130,6 +130,20 @@ enum { extern int nfq_snprintf_xml(char *buf, size_t len, struct nfq_data *tb, int flags); +/* + * New API based on libmnl + */ + +void nfq_nlmsg_cfg_put_cmd(struct nlmsghdr *nlh, uint16_t pf, uint8_t cmd); +void nfq_nlmsg_cfg_put_params(struct nlmsghdr *nlh, uint8_t mode, int range); +void nfq_nlmsg_cfg_put_qmaxlen(struct nlmsghdr *nlh, uint32_t qmaxlen); + +void nfq_nlmsg_verdict_put(struct nlmsghdr *nlh, int id, int verdict); +void nfq_nlmsg_verdict_put_mark(struct nlmsghdr *nlh, uint32_t mark); +void nfq_nlmsg_verdict_put_pkt(struct nlmsghdr *nlh, const void *pkt, uint32_t pktlen); + +int nfq_nlmsg_parse(const struct nlmsghdr *nlh, struct nlattr **pkt); + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/include/linux/Makefile.am b/include/linux/Makefile.am new file mode 100644 index 0000000..38eb109 --- /dev/null +++ b/include/linux/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = netfilter diff --git a/include/linux/netfilter/Makefile.am b/include/linux/netfilter/Makefile.am new file mode 100644 index 0000000..d0937cb --- /dev/null +++ b/include/linux/netfilter/Makefile.am @@ -0,0 +1 @@ +noinst_HEADERS = nfnetlink_queue.h diff --git a/include/linux/netfilter/nfnetlink_queue.h b/include/linux/netfilter/nfnetlink_queue.h new file mode 100644 index 0000000..da44b33 --- /dev/null +++ b/include/linux/netfilter/nfnetlink_queue.h @@ -0,0 +1,98 @@ +#ifndef _NFNETLINK_QUEUE_H +#define _NFNETLINK_QUEUE_H + +#include +#include + +enum nfqnl_msg_types { + NFQNL_MSG_PACKET, /* packet from kernel to userspace */ + NFQNL_MSG_VERDICT, /* verdict from userspace to kernel */ + NFQNL_MSG_CONFIG, /* connect to a particular queue */ + NFQNL_MSG_VERDICT_BATCH, /* batchv from userspace to kernel */ + + NFQNL_MSG_MAX +}; + +struct nfqnl_msg_packet_hdr { + __be32 packet_id; /* unique ID of packet in queue */ + __be16 hw_protocol; /* hw protocol (network order) */ + __u8 hook; /* netfilter hook */ +} __attribute__ ((packed)); + +struct nfqnl_msg_packet_hw { + __be16 hw_addrlen; + __u16 _pad; + __u8 hw_addr[8]; +}; + +struct nfqnl_msg_packet_timestamp { + __aligned_be64 sec; + __aligned_be64 usec; +}; + +enum nfqnl_attr_type { + NFQA_UNSPEC, + NFQA_PACKET_HDR, + NFQA_VERDICT_HDR, /* nfqnl_msg_verdict_hrd */ + NFQA_MARK, /* __u32 nfmark */ + NFQA_TIMESTAMP, /* nfqnl_msg_packet_timestamp */ + NFQA_IFINDEX_INDEV, /* __u32 ifindex */ + NFQA_IFINDEX_OUTDEV, /* __u32 ifindex */ + NFQA_IFINDEX_PHYSINDEV, /* __u32 ifindex */ + NFQA_IFINDEX_PHYSOUTDEV, /* __u32 ifindex */ + NFQA_HWADDR, /* nfqnl_msg_packet_hw */ + NFQA_PAYLOAD, /* opaque data payload */ + NFQA_CT, /* nf_conntrack_netlink.h */ + NFQA_CT_INFO, /* enum ip_conntrack_info */ + + __NFQA_MAX +}; +#define NFQA_MAX (__NFQA_MAX - 1) + +struct nfqnl_msg_verdict_hdr { + __be32 verdict; + __be32 id; +}; + + +enum nfqnl_msg_config_cmds { + NFQNL_CFG_CMD_NONE, + NFQNL_CFG_CMD_BIND, + NFQNL_CFG_CMD_UNBIND, + NFQNL_CFG_CMD_PF_BIND, + NFQNL_CFG_CMD_PF_UNBIND, +}; + +struct nfqnl_msg_config_cmd { + __u8 command; /* nfqnl_msg_config_cmds */ + __u8 _pad; + __be16 pf; /* AF_xxx for PF_[UN]BIND */ +}; + +enum nfqnl_config_mode { + NFQNL_COPY_NONE, + NFQNL_COPY_META, + NFQNL_COPY_PACKET, +}; + +struct nfqnl_msg_config_params { + __be32 copy_range; + __u8 copy_mode; /* enum nfqnl_config_mode */ +} __attribute__ ((packed)); + +enum nfqnl_flags { + NFQNL_F_NONE = 0, + NFQNL_F_CONNTRACK = (1 << 0), +}; + +enum nfqnl_attr_config { + NFQA_CFG_UNSPEC, + NFQA_CFG_CMD, /* nfqnl_msg_config_cmd */ + NFQA_CFG_PARAMS, /* nfqnl_msg_config_params */ + NFQA_CFG_QUEUE_MAXLEN, /* __u32 */ + NFQA_CFG_FLAGS, /* __u32 */ + __NFQA_CFG_MAX +}; +#define NFQA_CFG_MAX (__NFQA_CFG_MAX-1) + +#endif /* _NFNETLINK_QUEUE_H */ diff --git a/m4/gcc4_visibility.m4 b/m4/gcc4_visibility.m4 new file mode 100644 index 0000000..214d3f3 --- /dev/null +++ b/m4/gcc4_visibility.m4 @@ -0,0 +1,21 @@ + +# GCC 4.x -fvisibility=hidden + +AC_DEFUN([CHECK_GCC_FVISIBILITY], [ + AC_LANG_PUSH([C]) + saved_CFLAGS="$CFLAGS" + CFLAGS="$saved_CFLAGS -fvisibility=hidden" + AC_CACHE_CHECK([whether compiler accepts -fvisibility=hidden], + [ac_cv_fvisibility_hidden], AC_COMPILE_IFELSE( + [AC_LANG_SOURCE()], + [ac_cv_fvisibility_hidden=yes], + [ac_cv_fvisibility_hidden=no] + )) + if test "$ac_cv_fvisibility_hidden" = "yes"; then + AC_DEFINE([HAVE_VISIBILITY_HIDDEN], [1], + [True if compiler supports -fvisibility=hidden]) + AC_SUBST([GCC_FVISIBILITY_HIDDEN], [-fvisibility=hidden]) + fi + CFLAGS="$saved_CFLAGS" + AC_LANG_POP([C]) +]) diff --git a/src/Makefile.am b/src/Makefile.am index 2196aef..884311f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -24,7 +24,10 @@ include ${top_srcdir}/Make_global.am lib_LTLIBRARIES = libnetfilter_queue.la +include_HEADERS = internal.h + libnetfilter_queue_la_LDFLAGS = -Wc,-nostartfiles -lnfnetlink \ -version-info $(LIBVERSION) -libnetfilter_queue_la_SOURCES = libnetfilter_queue.c -libnetfilter_queue_la_LIBADD = ${LIBNFNETLINK_LIBS} +libnetfilter_queue_la_SOURCES = libnetfilter_queue.c \ + nlmsg.c +libnetfilter_queue_la_LIBADD = ${LIBNFNETLINK_LIBS} ${LIBMNL_LIBS} diff --git a/src/internal.h b/src/internal.h new file mode 100644 index 0000000..3a88d1a --- /dev/null +++ b/src/internal.h @@ -0,0 +1,12 @@ +#ifndef INTERNAL_H +#define INTERNAL_H 1 + +#include "config.h" +#ifdef HAVE_VISIBILITY_HIDDEN +# define __visible __attribute__((visibility("default"))) +# define EXPORT_SYMBOL(x) typeof(x) (x) __visible +#else +# define EXPORT_SYMBOL +#endif + +#endif diff --git a/src/nlmsg.c b/src/nlmsg.c new file mode 100644 index 0000000..4637fd5 --- /dev/null +++ b/src/nlmsg.c @@ -0,0 +1,174 @@ +/* + * (C) 2012 by Pablo Neira Ayuso + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ +#include +#include +#include +#include +#include + +#include + +#ifndef __aligned_be64 +#define __aligned_be64 __be64 __attribute__((aligned(8))) +#define __aligned_le64 __le64 __attribute__((aligned(8))) +#endif + +#include + +#include + +#include "internal.h" + +/** + * \defgroup nfq_verd Queue verdict object handling + * @{ + */ + +void nfq_nlmsg_verdict_put(struct nlmsghdr *nlh, int id, int verdict) +{ + struct nfqnl_msg_verdict_hdr vh = { + .verdict = htonl(verdict), + .id = htonl(id), + }; + mnl_attr_put(nlh, NFQA_VERDICT_HDR, sizeof(vh), &vh); +} +EXPORT_SYMBOL(nfq_nlmsg_verdict_put); + +void nfq_nlmsg_verdict_put_mark(struct nlmsghdr *nlh, uint32_t mark) +{ + mnl_attr_put_u32(nlh, NFQA_MARK, htonl(mark)); +} +EXPORT_SYMBOL(nfq_nlmsg_verdict_put_mark); + +void +nfq_nlmsg_verdict_put_pkt(struct nlmsghdr *nlh, const void *pkt, uint32_t plen) +{ + mnl_attr_put(nlh, NFQA_PAYLOAD, plen, pkt); +} +EXPORT_SYMBOL(nfq_nlmsg_verdict_put_pkt); + +/** + * @} + */ + +/** + * \defgroup nfq_cfg Queue config object handling + * @{ + */ + +/** + * nfq_nlmsg_cfg_build_request- build netlink config message + * \param buf Buffer where netlink message is going to be written. + * \param cfg Structure that contains the config parameters. + * \param command nfqueue nfnetlink command. + * + * This function returns a pointer to the netlink message. If something goes + * wrong it returns NULL. + * + * Possible commands are: + * + * - NFQNL_CFG_CMD_NONE: Do nothing. It can be useful to know if the queue + * subsystem is working. + * - NFQNL_CFG_CMD_BIND: Binds the program to a specific queue. + * - NFQNL_CFG_CMD_UNBIND: Unbinds the program to a specifiq queue. + * - NFQNL_CFG_CMD_PF_BIND: Binds to process packets belonging to the given + * protocol family (ie. PF_INET, PF_INET6, etc). + * - NFQNL_CFG_CMD_PF_UNBIND: Unbinds from processing packets belonging to the + * given protocol family. + */ +void nfq_nlmsg_cfg_put_cmd(struct nlmsghdr *nlh, uint16_t pf, uint8_t cmd) +{ + struct nfqnl_msg_config_cmd command = { + .command = cmd, + .pf = htons(pf), + }; + mnl_attr_put(nlh, NFQA_CFG_CMD, sizeof(command), &command); +} +EXPORT_SYMBOL(nfq_nlmsg_cfg_put_cmd); + +void nfq_nlmsg_cfg_put_params(struct nlmsghdr *nlh, uint8_t mode, int range) +{ + struct nfqnl_msg_config_params params = { + .copy_range = htonl(range), + .copy_mode = mode, + }; + mnl_attr_put(nlh, NFQA_CFG_PARAMS, sizeof(params), ¶ms); +} +EXPORT_SYMBOL(nfq_nlmsg_cfg_put_params); + +void nfq_nlmsg_cfg_put_qmaxlen(struct nlmsghdr *nlh, uint32_t queue_maxlen) +{ + mnl_attr_put_u32(nlh, NFQA_CFG_QUEUE_MAXLEN, htonl(queue_maxlen)); +} +EXPORT_SYMBOL(nfq_nlmsg_cfg_put_qmaxlen); + +/** + * @} + */ + +/** + * \defgroup nlmsg Netlink message helper functions + * @{ + */ + +static int nfq_pkt_parse_attr_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb = data; + int type = mnl_attr_get_type(attr); + + /* skip unsupported attribute in user-space */ + if (mnl_attr_type_valid(attr, NFQA_MAX) < 0) + return MNL_CB_OK; + + switch(type) { + case NFQA_MARK: + case NFQA_IFINDEX_INDEV: + case NFQA_IFINDEX_OUTDEV: + case NFQA_IFINDEX_PHYSINDEV: + case NFQA_IFINDEX_PHYSOUTDEV: + if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) + return MNL_CB_ERROR; + break; + case NFQA_TIMESTAMP: + if (mnl_attr_validate2(attr, MNL_TYPE_UNSPEC, + sizeof(struct nfqnl_msg_packet_timestamp)) < 0) { + return MNL_CB_ERROR; + } + break; + case NFQA_HWADDR: + if (mnl_attr_validate2(attr, MNL_TYPE_UNSPEC, + sizeof(struct nfqnl_msg_packet_hw)) < 0) { + return MNL_CB_ERROR; + } + break; + case NFQA_PAYLOAD: + break; + } + tb[type] = attr; + return MNL_CB_OK; +} + +/** + * nfq_pkt_parse - set packet attributes from netlink message + * \param nlh netlink message that you want to read. + * \param pkt pointer to the packet to set. + * + * This function returns MNL_CB_ERROR if any error occurs, or MNL_CB_OK on + * success. + */ +int nfq_nlmsg_parse(const struct nlmsghdr *nlh, struct nlattr **attr) +{ + return mnl_attr_parse(nlh, sizeof(struct nfgenmsg), + nfq_pkt_parse_attr_cb, attr); +} +EXPORT_SYMBOL(nfq_nlmsg_parse); + +/** + * @} + */ -- cgit v1.2.3