一、Netlink概述
二、用户态如何使用netlink
三、内核态如何使用netlink
四、内核态和用户态之间使用netlink实例(NETD)
五、内核态和用户态之间使用netlink实例(SYN FIREWALL)
(本段摘抄自网络)
Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink。
Netlink 相对于系统调用,ioctl 以及 /proc 文件系统而言具有以下优点:
1,为了使用 netlink,用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义即可, 如 #define NETLINK_MYTEST 17 然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换。但系统调用需要增加新的系统调用,ioctl 则需要增加设备或文件, 那需要不少代码,proc 文件系统则需要在 /proc 下添加新的文件或目录,那将使本来就混乱的 /proc 更加混乱。
2. netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接 收队列,而不需要等待接收者收到消息,但系统调用与 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。
3.使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,它无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。
4.netlink 支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件。
5.内核可以使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。
6.netlink 使用标准的 socket API,因此很容易使用,但系统调用和 ioctl则需要专门的培训才能使用。
使用netlink进行应用与内核通信的应用很多:
#define NETLINK_ROUTE 0 /* Routing/device hook */
#define NETLINK_UNUSED 1 /* Unused number */
#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */
#define NETLINK_FIREWALL 3 /* Firewalling hook */
#define NETLINK_SOCK_DIAG 4 /* socket monitoring */
#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */
#define NETLINK_XFRM 6 /* ipsec */
#define NETLINK_SELINUX 7 /* SELinux event notifications */
#define NETLINK_ISCSI 8 /* Open-iSCSI */
#define NETLINK_AUDIT 9 /* auditing */
#define NETLINK_FIB_LOOKUP 10
#define NETLINK_CONNECTOR 11
#define NETLINK_NETFILTER 12 /* netfilter subsystem */
#define NETLINK_IP6_FW 13
#define NETLINK_DNRTMSG 14 /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
#define NETLINK_GENERIC 16
/* leave room for NETLINK_DM (DM Events) */
#define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */
#define NETLINK_ECRYPTFS 19
#define NETLINK_RDMA 20
#define NETLINK_CRYPTO 21 /* Crypto layer */
其中,比较重点的是NETLINK_KOBJECT_UEVENT,即kernel uevent事件,内核设备驱动在某个时刻触发的某个事件(比如up/down)会通过netlink通知给用户空间。
用户态应用使用标准的socket APIs, socket(), bind(), sendmsg(), recvmsg()和close()就能很容易地使用netlink socket。
使用netlink步骤如下:
1.包含头文件linux/netlink.h以及sys/socket.h
2.创建一个netlink socket
socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
第一个参数必须是AF_NETLINK或PF_NETLINK,表示要使用netlink。
第二个参数必须是SOCK_RAW或SOCK_DGRAM,
第三个参数指定netlink协议类型,即第一节讲的义协议类型,如果是要监控内核设备驱动,直接用现成的NETLINK_KOBJECT_UEVENT即可。NETLINK_GENERIC是一个通用的协议类型,它是专门为用户使用的,因此,用户可以直接使用它,而不必再添加新的协议类型。
3.设置socket的选项(可选)
setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));
setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on));
4.使用bind() 把创建的netlink socket与netlink socket地址绑定在一起
bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr));
其中,nladdr是sockaddr_nl结构体:
struct sockaddr_nl
{
sa_family_t nl_family;
unsigned short nl_pad;
__u32 nl_pid;
__u32 nl_groups;
};
字段nl_family必须设置为AF_NETLINK或着PF_NETLINK,字段nl_pad当前没有使用,因此要总是设置为0,字段nl_pid为接收或发送消息的进程的ID,如果希望内核处理消息或多播消息,就把该字段设置为0,否则设置为处理消息的进程ID。字段nl_groups用于指定多播组,bind函数用于把调用进程加入到该字段指定的多播组,如果设置为0,表示调用者不加入任何多播组。
struct sockaddr_nl nladdr;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
nladdr.nl_pid = getpid();
nladdr.nl_groups = 0xffffffff;
5.创建一个listener监听该socket,重写listener的onEvent函数,来完成对监听到的数据进行筛选/分类/处理。
mHandler = new NetlinkHandler(mSock);
内核模块使用netlink,不同于用户态应用对netlink的使用,需要专门的netlink API。
使用netlink步骤如下:
1.包含头文件linux/netlink.h
2.创建一个netlink socket。建立OK后,建立了同样协议类型netlink的用户态通过sendmsg()即可向内核态发送消息指令了。其中第四个参数为接收用户态消息的函数指针,为null说明为单向的,即内核不处理用户态发来的消息,只是向用户态通知内核事件。
ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT,
1, NULL, NULL, THIS_MODULE);
PS:由于NETLINK_KOBJECT_UEVENT是表示的内核设备驱动事件,所以新增的设备驱动无需建立netlink socket,建立了NETLINK_KOBJECT_UEVENT类型netlink用户态都可以收到新增设备驱动的消息事件。只有新增的内核模块,才需要建立netlink socket。
3.向用户态上报消息
int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);
void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation);
其中,参数sk为函数netlink_kernel_create()返回的socket,参数skb存放消息,参数pid为接收消息进程的pid,参数group为接收消息的多播组。
1.内核态部分
由于netd监听的是已有设备驱动的消息事件,所以内核态无需修改。
2.用户态部分
2.1在netd中新增NetlinkManager类,用以建立netlink socket
int NetlinkManager::start() {
if ((mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT,
0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII)) == NULL) {
return -1;
}
...
}
NetlinkHandler *NetlinkManager::setupSocket(int *sock, int netlinkFamily,
int groups, int format) {
struct sockaddr_nl nladdr;
int sz = 64 * 1024;
int on = 1;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
nladdr.nl_pid = getpid();
nladdr.nl_groups = groups;
if ((*sock = socket(PF_NETLINK, SOCK_DGRAM, netlinkFamily)) < 0) {
...}
if (setsockopt(*sock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {
...}
if (setsockopt(*sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
...}
if (bind(*sock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {
...}
NetlinkHandler *handler = new NetlinkHandler(this, *sock, format);
if (handler->start()) {
...}
return handler;
}
int NetlinkManager::stop() {
...
if (mUeventHandler->stop()) {
...}
delete mUeventHandler;
mUeventHandler = NULL;
close(mUeventSock);
mUeventSock = -1;
...
}
2.2在netd中新增NetlinkHandler类,用以筛选/分类/处理netlink消息
int NetlinkHandler::start() {
return this->startListener();
}
int NetlinkHandler::stop() {
return this->stopListener();
}
void NetlinkHandler::onEvent(NetlinkEvent *evt) {
const char *subsys = evt->getSubsystem();
...
if (!strcmp(subsys, "net")) {
int action = evt->getAction();
const char *iface = evt->findParam("INTERFACE");
if (action == evt->NlActionAdd) {
notifyInterfaceAdded(iface);
} else if (action == evt->NlActionRemove) {
notifyInterfaceRemoved(iface);
} else if (action == evt->NlActionChange) {
evt->dump();
notifyInterfaceChanged("nana", true);
} else if (action == evt->NlActionLinkUp) {
notifyInterfaceLinkChanged(iface, true);
} else if (action == evt->NlActionLinkDown) {
notifyInterfaceLinkChanged(iface, false);
} else if (action == evt->NlActionAddressUpdated ||
action == evt->NlActionAddressRemoved) {
const char *address = evt->findParam("ADDRESS");
const char *flags = evt->findParam("FLAGS");
const char *scope = evt->findParam("SCOPE");
if (iface && flags && scope) {
notifyAddressChanged(action, address, iface, flags, scope);
...}
...}
...}
}
2.3在netd.main()中start NetlinkManager,即完成对netlink的使用,监听到NETLINK_KOBJECT_UEVENT,即kernel uevent事件,内核设备驱动(比如rmnet0)在某个时刻触发的某个事件(比如up/down)会通过netlink通知给netd。
int main() {
NetlinkManager *nm;
if (!(nm = NetlinkManager::Instance())) {
...};
cl = new CommandListener(rangeMap);
nm->setBroadcaster((SocketListener *) cl);
if (nm->start()) {
...}
...}
1.内核态
1.1 建立netlink socket
新增的设备驱动无需建立netlink socket,建立了NETLINK_KOBJECT_UEVENT类型netlink用户态都可以收到新增设备驱动的消息事件。只有新增的内核模块,才需要建立netlink socket。
此处只是新增了一个switch device,而非新增module,故无需新建netlink socket。
ret = switch_dev_register(&ipv4_sdev);
1.2 向用户态上报state change事件
switch_set_state(&ipv4_sdev, 0x10000|256*port1+port2);
调用关系如下:
switch_set_state() --> kobject_uevent_env() --> netlink_broadcast_filtered()
2.用户态
框架同netd部分,不再赘述。