浅析iptables NFQUEUE机制——使用篇

1,617 阅读1分钟

iptables的NFQUEUE的功能主要用于将包传递给用户空间,通过nfnetlink_queue的方式将数据包传给自定义的用户态程序来做决策或者作出修改。

libnetfilter_queue将内核数据包的操作进行了封装成API,以供我们更容易的进行操作,而它的源码中也提供了一个例子nf_queue以供参考,我就以nf_queue为例子进行简要梳理使用的关键点。主要解析nf_queue如何承接数据包。

建立netlink连接

前面说了,数据包是通过nfnetlink_queue的方式将包传输,那nf_queue这边就必须要建立netlink套接字来与内核进行数据交互。

// 传递NETLINK_NETFILTER类型,创建netlink套接字
nl = mnl_socket_open(NETLINK_NETFILTER);
// 绑定进程pid,此处传递的是MNL_SOCKET_AUTOPID
if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) {
	perror("mnl_socket_bind");
	exit(EXIT_FAILURE);
}
// 通过给定的netlink套接字获取netlink端口id
portid = mnl_socket_get_portid(nl);

建立nfnetlink连接

nfnetlink连接在netlink之上,本质上还是netlink,借助libnetfilter_queue提供的API将消息封装成nfnetlink所需的格式

//构造nfnetlink数据包,传递queue_num
nlh = nfq_nlmsg_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_nlmsg_put(buf, NFQNL_MSG_CONFIG, queue_num);
nfq_nlmsg_cfg_put_params(nlh, NFQNL_COPY_PACKET, 0xffff);

mnl_attr_put_u32(nlh, NFQA_CFG_FLAGS, htonl(NFQA_CFG_F_GSO));
mnl_attr_put_u32(nlh, NFQA_CFG_MASK, htonl(NFQA_CFG_F_GSO));

if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
        perror("mnl_socket_send");
        exit(EXIT_FAILURE);
}

接收nfnetlink数据

接收nfnetlink数据,并传给回调函数处理

ret = mnl_socket_recvfrom(nl, buf, sizeof_buf);
ret = mnl_cb_run(buf, ret, 0, portid, queue_cb, NULL);

回调函数处理

分析回调函数获取的函数

static int queue_cb(const struct nlmsghdr *nlh, void *data);

回调函数使用时传参情况,见libmnl源码

git.netfilter.org/libmnl/tree…

if (cb_data){
        ret = cb_data(nlh, data);
        if (ret <= MNL_CB_STOP)
                goto out;
}

data为null,主要看nlh这个参数,是一个netlink 消息头

struct nlmsghdr {
	__u32		nlmsg_len;	/* Length of message including header */
	__u16		nlmsg_type;	/* Message content */
	__u16		nlmsg_flags;	/* Additional flags */
	__u32		nlmsg_seq;	/* Sequence number */
	__u32		nlmsg_pid;	/* Sending process port ID */
};

解析netlink message

Netlink message格式

* Netlink message:
 * \verbatim
	|<----------------- 4 bytes ------------------->|
	|<----- 2 bytes ------>|<------- 2 bytes ------>|
	|-----------------------------------------------|
	|      Message length (including header)        |
	|-----------------------------------------------|
	|     Message type     |     Message flags      |
	|-----------------------------------------------|
	|           Message sequence number             |
	|-----------------------------------------------|
	|                 Netlink PortID                |
	|-----------------------------------------------|
	|                                               |
	.                   Payload                     .
	|_______________________________________________|
\endverbatim

<------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
+---------------------+- - -+- - - - - - - - - -+- - -+
|        Header       | Pad |     Payload       | Pad |
|   (struct nlattr)   | ing |                   | ing |
+---------------------+- - -+- - - - - - - - - -+- - -+
<-------------- nlattr->nla_len -------------->
struct nlattr {
	__u16           nla_len;
	__u16           nla_type;
};
//将netlink消息设置数据包属性
if (nfq_nlmsg_parse(nlh, attr) < 0) {
        perror("problems parsing");
        return MNL_CB_ERROR;
}
//获取消息负载
nfg = mnl_nlmsg_get_payload(nlh);

/* General form of address family dependent message.
 */
 //消息负载结构体
struct nfgenmsg {
	__u8  nfgen_family;		/* AF_xxx */
	__u8  version;		/* nfnetlink version */
	__be16    res_id;		/* resource id */
};
//获取指向属性有效负载的指针
ph = mnl_attr_get_payload(attr[NFQA_PACKET_HDR]);
//获取属性有效负载值长度
plen = mnl_attr_get_payload_len(attr[NFQA_PAYLOAD]);

通过 mnl_attr_get_payload从负载中获取想要的信息,但这个例子并没有有效利用libnetfilter提供的一些封装函数。 负载属性列表

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,			/* nfnetlink_conntrack.h */
	NFQA_CT_INFO,			/* enum ip_conntrack_info */
	NFQA_CAP_LEN,			/* __u32 length of captured packet */
	NFQA_SKB_INFO,			/* __u32 skb meta information */
	NFQA_EXP,			/* nfnetlink_conntrack.h */
	NFQA_UID,			/* __u32 sk uid */
	NFQA_GID,			/* __u32 sk gid */
	NFQA_SECCTX,			/* security context string */
	NFQA_VLAN,			/* nested attribute: packet vlan info */
	NFQA_L2HDR,			/* full L2 header */
	NFQA_PRIORITY,			/* skb->priority */

	__NFQA_MAX
};

对数据包作出决策

在例子中将队列中数据包的唯一id,NF_ACCEPT,放入决策包中发送出去。

nfq_send_verdict(ntohs(nfg->res_id), id);

static void
nfq_send_verdict(int queue_num, uint32_t id)
{
	char buf[MNL_SOCKET_BUFFER_SIZE];
	struct nlmsghdr *nlh;
	struct nlattr *nest;

	nlh = nfq_nlmsg_put(buf, NFQNL_MSG_VERDICT, queue_num);
	nfq_nlmsg_verdict_put(nlh, id, NF_ACCEPT);

	/* example to set the connmark. First, start NFQA_CT section: */
	nest = mnl_attr_nest_start(nlh, NFQA_CT);

	/* then, add the connmark attribute: */
	mnl_attr_put_u32(nlh, CTA_MARK, htonl(42));
	/* more conntrack attributes, e.g. CTA_LABELS could be set here */

	/* end conntrack section */
	mnl_attr_nest_end(nlh, nest);

	if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) {
		perror("mnl_socket_send");
		exit(EXIT_FAILURE);
	}
}

至此,这个简单的案例就已经简要分析完毕了。