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);
}
}
至此,这个简单的案例就已经简要分析完毕了。