📄 netlink.htm
字号:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>netlink 编程介绍</title>
</head>
<body>
<div style="width:800px;">
<h1 align="center">netlink 编程介绍(V0.2)</h1>
<h4 align="center">作者: Hoyt Luo <hoytluo@21cn.com></hoytluo@21cn.com></h4>
<p style="text-indent:30px;line-height:25px;">Linux从2.2开始支持PF_NETLINK域的通讯方式,这个方式主要的用途是在Linux的内核空间和用户空间进行通讯。目前在网络上面关于netlink编程的中文资料很少,为了促进对netlink编程的理解我编写了这篇文章,由于我对netlink的了解不是很透彻,特别是对于内核部分不是很熟悉,所以文章中肯定有很多错误的地方还请大家指正。文章分下面几个部分进行讲述</p>
<ol style="text-indent:30px;line-height:25px;" type="A">
<li><a href="#netlink基础知识">netlink 基础知识</a></li>
<li><a href="#nlmsghdr结构介绍">nlmsghdr 结构介绍</a></li>
<li><a href="#解析nlmsghdr数据">解析nlmsghdr数据</a></li>
<li><a href="#sockaddr_nl结构介绍">sockaddr_nl 结构介绍</a></li>
<li><a href="#NETLINK_ROUTE协议介绍">NETLINK_ROUTE 协议介绍</a></li>
<li><a href="#NETLINK_SKIP协议介绍">NETLINK_SKIP 协议介绍</a></li>
<li><a href="#NETLINK_USERSOCK协议介绍">NETLINK_USERSOCK协议介绍</a></li>
<li><a href="#NETLINK_FIREWALL协议介绍">NETLINK_FIREWALL 协议介绍</a></li>
<li><a href="#NETLINK_TCPDIAG协议介绍">NETLINK_TCPDIAG 协议介绍</a></li>
<li><a href="#NETLINK_NFLOG协议介绍">NETLINK_NFLOG 协议介绍</a></li>
<li><a href="#NETLINK_ARPD协议介绍">NETLINK_ARPD 协议介绍</a></li>
<li><a href="#NETLINK_ROUTE6协议介绍">NETLINK_ROUTE6 协议介绍</a></li>
<li><a href="#NETLINK_IP6_FW协议介绍">NETLINK_IP6_FW 协议介绍</a></li>
<li><a href="#NETLINK_DNRTMSG协议介绍">NETLINK_DNRTMSG 协议介绍</a></li>
<li><a href="#NETLINK_TAPBASE协议介绍">NETLINK_TAPBASE 协议介绍</a></li>
<li><a href="#参考资料">参考资料</a></li>
<li><a href="#版权说明">版权说明</a></li>
<li><a href="#修改记录">修改记录</a></li>
</ol>
<ol type="A">
<h1><li><a id="netlink基础知识">netlink基础知识</a></li></h1>
<p style="text-indent:30px;line-height:25px;">我们在使用socket(2)的man手册时候可以找到man手册中有下面一行说明
<pre>PF_NETLINK Kernel user interface device netlink(7)</pre>
<p style="text-indent:30px;line-height:25px;">在我们通过PF_NETLINK创建一个SOCKET以后表示我们期望同内核进行消息通讯。使用netlink(7)的手册可以看到关于PF_NETLINK的详细说明。
<pre>
#include <asm/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
netlink_socket = socket(PF_NETLINK, socket_type, netlink_family);
</pre>
<p style="text-indent:30px;line-height:25px;">按照netlink的手册,socket_type可以取SOCK_RAW和SOCK_DGRAM,不过内核不区分这两个字段。netlink_family字段指定了我们期望的通讯协议,主要有:
<ul>
<li>NETLINK_ROUTE</li> 用来获取,创建和修改设备的各种信息,详细参见 rtnetlink(7)
<li>NETLINK_SKIP</li> Enskip 的保留选项
<li>NETLINK_USERSOCK</li> 为今后用户程序空间协议用保留选项
<li>NETLINK_FIREWALL</li> 接收 IPv4 防火墙编码发送的数据包
<li>NETLINK_TCPDIAG</li> TCP套接字监控
<li>NETLINK_NFLOG</li> netfilter的用户空间日志
<li>NETLINK_ARPD</li> 用以维护用户地址空间里的 arp 表
<li>NETLINK_ROUTE6</li> 接收和发送 IPv6 路由表更新消息
<li>NETLINK_IP6_FW</li> 接收未通过 IPv6 防火墙检查的数据包(尚未实现)
<li>NETLINK_TAPBASE</li> 是 ethertap 设备实例
</ul>
后面我们会对每一个协议进行解释和说明.
<h1><li><a id="nlmsghdr结构介绍">nlmsghdr结构介绍</a></li></h1>
<p style="text-indent:30px;line-height:25px;">每一个发送给内核或者从内核介绍的报文都有一个相同的报文头,这个报文头的结构如下定义:
<pre>
struct nlmsghdr
{
__u32 nlmsg_len; /* 包括报头在内的消息长度*/
__u16 nlmsg_type; /* 消息正文 */
__u16 nlmsg_flags; /* 附加标志*/
__u32 nlmsg_seq; /* 序列号*/
__u32 nlmsg_pid; /* 发送进程号 PID */
};
</pre>
<p style="text-indent:30px;line-height:25px;">所有发送给内核或者内核的报文的第一部分都必须使用这个机构,后面跟随相应的内容。nlmsg_type为后面消息的内容个数,对于前面我们提到的不同通讯协议有着不同的消息类型。下面是三个通用的消息类型
<ul>
<li>NLMSG_NOOP</li> 这个消息类型表示消息内容为空,应用可以忽略该报文
<li>NLMSG_ERROR</li> 这个消息类型表示后面的消息是一个错误信息,错误信息的机构为nlmsgerr
<pre>
struct nlmsgerr
{
int error; /* 负数表示的出错号 errno 或为 0 要求确认 acks*/
struct nlmsghdr msg; /* 造成出错的消息报头*/
};
</pre>
<li>NLMSG_DONE</li> 在我们接收或者发送消息给内核的时候,我们有可能一次发送多个报文,这个消息类型表示是报文的最后一个,类似于在链表中我们将最后一个成员的next指针设置为NULL。
</ul>
附加的标志用于控制或者表示消息的其它信息,一些比较通用的标志是
<ul>
<li>NLM_F_REQUEST</li> 表示这个消息是一个请求消息,这个消息可以同以下一个标志组合
<ul>
<li>NLM_F_ROOT</li> 返回树的根
<li>NLM_F_MATCH</li> 返回所有匹配的
<li>NLM_F_ATOMIC</li> 返回对象表的单一快照
<li>NLM_F_DUMP</li> 被定义为NLM_F_ROOT|NLM_F_MATCH
<li>NLM_F_REPLACE</li> 表示替换现有的规则
<li>NLM_F_EXCL</li> 如果现有规则存在则不修改
<li>NLM_F_CREAT</li> 创建一个规则
<li>NLM_F_APPEND</li> 追加一个规则
</ul>
<li>NLM_F_MULTI</li> 表示这个消息是多个报文中的一个,报文的结尾通过NLMSG_DONE来表示
<li>NLM_F_ACK</li> 表示这个消息是一个应答消息
<li>NLM_F_ECHO</li> 表示这个消息是一个要求返回请求信息的消息
</ul>
<h1><li><a id="解析nlmsghdr数据">解析nlmsghdr数据</a></li></h1>
<p style="text-indent:30px;line-height:25px;">为了获取netlink报文中数据的方便,netlink提供了下面几个宏进行数据的获取和解包操作
<pre>
#include <asm/types.h>
#include <linux/netlink.h>
int NLMSG_ALIGN(size_t len);
int NLMSG_LENGTH(size_t len);
int NLMSG_SPACE(size_t len);
void *NLMSG_DATA(struct nlmsghdr *nlh);
struct nlmsghdr *NLMSG_NEXT(struct nlmsghdr *nlh, int len);
int NLMSG_OK(struct nlmsghdr *nlh, int len);
int NLMSG_PAYLOAD(struct nlmsghdr *nlh, int len);
</pre>
NLMSG_ALIGN:进行数据长度的对齐操作<br>
NLMSG_DATA:获取通讯报文中的数据<br>
NLMSG_NEXT:获取下一个报文<br>
NLMSG_OK:判断是否数据可以继续获取<br>
NLMSG_PAYLOAD:获取数据的长度<br>
在我们后面的实例中会介绍如何使用这几个宏。
<h1><li><a id="sockaddr_nl结构介绍">sockaddr_nl结构介绍</a></li></h1>
<p style="text-indent:30px;line-height:25px;">在socket程序中,如果我们要求接收报文则要求调用bind,表示我们期望接收什么样的报文。对于netlink也一样,我们要求指定我们期望接收的地址信息,不过同传统的sockaddr不同,这个地方是一个sockaddr_nl的结构:
<pre>
struct sockaddr_nl
{
sa_family_t nl_family; /* AF_NETLINK */
unsigned short nl_pad; /* 用来填充的字段,赋值为0 */
pid_t nl_pid; /* 进程标识号pid */
__u32 nl_groups; /* 多址广播组掩码*/
};
</pre>
<p style="text-indent:30px;line-height:25px;">每一个 netlink 数据类都有一个32位广播分组,当对套接字调用 bind(2) 时, sockaddr_nl 中的 nl_groups 字段设置成所要侦听的广播组的位掩码。其默认值为 0,表示不接收任何广播,我们会在后面中看到如何使用这个广播组的例子。
<h1><li><a id="NETLINK_ROUTE协议介绍">NETLINK_ROUTE协议介绍</a></li></h1>
<p style="text-indent:30px;line-height:25px;">netlink目前使用最广泛的是通过这个选项来获取网络设备或者网址的一些信息。在使用这个协议时候支持的类型有:
<ul>
<li>RTM_NEWLINK, RTM_DELLINK, RTM_GETLINK</li> 创建,删除或者获取网络设备的信息
<li>RTM_NEWADDR, RTM_DELADDR, RTM_GETADDR</li> 创建,删除或者获取网络设备的IP信息
<li>RTM_NEWROUTE, RTM_DELROUTE, RTM_GETROUTE</li> 创建,删除或者获取网络设备的路由信息
<li>RTM_NEWNEIGH, RTM_DELNEIGH, RTM_GETNEIGH</li> 创建,删除或者获取网络设备的相邻信息
<li>RTM_NEWRULE, RTM_DELRULE, RTM_GETRULE</li> 创建,删除或者获取路由规则信息
<li>RTM_NEWQDISC, RTM_DELQDISC, RTM_GETQDISC</li> 创建,删除或者获取队列的原则
<li>RTM_NEWTCLASS, RTM_DELTCLASS, RTM_GETTCLASS</li> 创建,删除或者获取流量的类别
<li>RTM_NEWTFILTER, RTM_DELTFILTER, RTM_GETTFILTER</li> 创建,删除或者获取流量的过虑
</ul>
由于NETLINK_ROUTE支持的类型实在太多了,我们这个地方只重点介绍一下RTM_GETLINK这个类型,然后通过一个例子介绍如何同内核进行通讯。关于其它类型的用法大家可以参考rtnetlink(7)的man手册。
<p style="text-indent:30px;line-height:25px;">按照rtnetlink的说法,在获取设备信息的时候我们要求首先发送一个报文给内核表示我们的请求,这个报文的格式是1个nlmsghdr头部机构+1个ifinfomsg接口结构+多个rtattr属性机构。其中后面两个结构的定义是:
<pre>
struct ifinfomsg
{
unsigned char ifi_family; /* AF_UNSPEC */
unsigned short ifi_type; /* Device type */
int ifi_index; /* Interface index */
unsigned int ifi_flags; /* Device flags */
unsigned int ifi_change; /* change mask */
};
struct rtattr
{
unsigned short rta_len; /* Length of option */
unsigned short rta_type; /* Type of option */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -