Linux netlink socket使用总结

本文深入解析Linux中的Netlink机制,介绍其引入背景、优势及如何在用户态和内核态中使用,包括创建、绑定、发送接收消息及关闭Netlink socket的过程。同时,通过实例演示了Netlink机制的具体应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、netlink机制的引入

Linux提供了多种机制来完成内核空间与用户空间之间的数据交换,分别有内核启动参数、模块参数、sysfs、sysctl、系统调用、procfs、seq_file、debugfs、relayfs。其中,模块参数、sysfs、sysctl、procfs、seq_file、debugfs、relayfs是基于文件系统的通信机制,用于内核空间向用户空间输出信息;sysctl、系统调用是由用户空间发起的通信机制。以上均为单工通信机制,在内核空间与用户空间的双向数据交换上略显不足。 
netlink是基于socket的通信机制,由于socket本身的双工性、突发性、不阻塞性等特点,能够很好地满足内核空间与用户空间小量数据的及时交互,因此在Linux 2.6内核开始被广泛使用,例如内核态的netfilter与用户态的iptables的数据交换就是通过netlink机制完成的。

二、netlink机制的优点

netlink相对于其他的通信机制具有以下优点: 
1. 使用netlink通过自定义一种新的协议并加入协议族即可通过socket API使用netlink协议完成数据交换,而ioctl和proc文件系统均需要通过程序加入相应的设备或文件。 
2. netlink使用socket缓冲队列,是一种异步通信机制,而ioctl是同步通信,如果传输数据量较大会影响系统性能。 
3. netlink支持多播,属于一个netlink组的模块和进程都能获得该多播消息。 
4. netlink允许内核发起会话,而ioctl和系统调用只能由用户空间发起。

三、netlink机制的使用

用户态使用标准的socket API如socket,bind,sendmsg,recvmsg和close等接口就能很容易地使用netlink socket。

3.1.1 创建netlink socket

使用如下参数创建一个netlink socket:

socket(AF_NETLINK/PF_NETLINK, int type, int protocol);

其中type可以是SOCK_RAW或SOCK_DGRAM,protocol指定netlink协议类型,目前已经支持的协议类型在linux/netlink.h中定义:

#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_INET_DIAG    4    /* INET 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

3.1.2 将netlink socket与进程进行绑定

函数bind用于把一个打开的netlink socket与进程进行绑定,需要进行绑定的netlink socket地址结构如下:

struct sockaddr_nl
{
    sa_family_t        nl_family;    /* AF_NETLINK    */
    unsigned short    nl_pad;        /* zero        */
    __u32            nl_pid;        /* port ID    */
   __u32            nl_groups;    /* multicast groups mask */
};

其中nl_family固定为AF_NETLINK或PF_NETLINK,nl_pad为填充字段,设置为0,nl_pid设置为当前进程的PID,nl_groups用于指定多播组,每一个bit对应一个多播组,如果设置为0,表示不加入任何多播组。 
填充完netlink socket地址后,就可以调用bind接口进行绑定了,调用方法如下:

bind(sock_fd, (struct sockaddr*)&nl_addr, sizeof(&nl_addr));

3.1.3 发送netlink消息

为了能够把netlink消息发送给内核或者别的用户进程,需要使用另外一个结构体struct sockaddr_nl作为目的地址。如果消息是发往内核的话,nl_pid和nl_groups都应该设置为0,;如果消息是发往另一个进程,nl_pid应该设置为接受者进程的PID,nl_groups用于设置需要发往的多播组。 
填充好了目的地址后,就可以将netlink地址应用到结构体struct msghdr中,供函数sendmsg来调用:

struct msghdr msg;
msg.msg_name = (void *)&nladdr;
msg.msg_namelen = sizeof(nladdr);

由于linux内核的netlink部分总是认为每个netlink消息体中已经包含了下面的消息头,所以每个应用程序在发送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 */
};

nlmsg_len需要填充为包含netlink消息头的整个netlink消息的长度,nlmsg_type和nlmsg_flags根据不同应用的需要进行填充,nlmsg_seq是用于跟踪消息的序号,nlmsg_pid设置为当前进程的PID。 
填充完消息头后,在消息头后面就可以填充消息体的内容了,填充完消息体,使用struct iovec结构体,使iov_base指向包含netlink消息的缓冲区,即可调用sendmsg接口发送netlink消息。struct iovec结构定义如下:

struct iovec
{
    void __user *iov_base;    /* BSD uses caddr_t (1003.1g requires void *) */
    __kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};

发送netlink消息的代码如下:

struct iovec iov;
iov.iov_base = (void *)msg_buffer;
iov.iov_len = nlh->nlmsg_len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
sendmsg(sock_fd, &msg, 0);

接收netlink消息

接收程序需要申请足够大的空间来存储netlink消息头和消息的payload部分。用如下的方式填充结构体struct msghdr,然后调用recvmsg接口来接收netlink消息:

char msg_buffer[MSX_NL_MSG_LEN];
struct sockaddr_nl nladdr;
struct msghdr msg;
struct iovec iov;

iov.iov_base = (void *)msg_buffer;
iov.iov_len = MAX_NL_MSG_LEN;

msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

recvmsg(sock_fd, &msg, 0);

当消息正确接收后,msg_buffer里保存包含netlink消息头的整个netlink消息,nladdr包含接收到的消息体的目的地址信息。

3.1.5 关闭netlink socket

使用完前面创建的netlink socket后,就可以使用close接口关闭netlink socket,释放socket资源。关闭netlink socket的代码与关闭其他socket一致,代码如下:

close(sock_fd);

内核空间的netlink API接口是由内核中的netlink核心代码支持的,在net/core/af_netlink.c中实现。内核模块通过这些API访问netlink socket并与用户空间的程序进行通信。

3.2.1 添加自定义协议类型

内核已经支持的协议类型在linux/netlink.h中定义,如果要使用自定义的协议类型,需要在此文件中新增一个协议类型,然后就可以在linux内核模块中使用这个协议类型了。

3.2.2 在内核创建netlink socket

在内核空间,通过如下接口创建netlink socket,新的netlink_kernel_create()函数只有3个参数了。

/******************************
net:    linux网络命名空间结构体指针
uint:   netlink协议类型
cfg:    netlink内核配置参数结构体指针

返回: sock结构指针
******************************/

static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)

第一个参数:一般传入&init_net即可,它是在net_namespace.c文件中定义的一个结构体。

第二个参数:是代表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   /* Unused number, formerly ip_queue     */
#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 */

#define NETLINK_INET_DIAG   NETLINK_SOCK_DIAG

#define MAX_LINKS 32

最多可以定义32中类型,若 uint > MAX_LINKS 则该函数返回NULL,源代码片段如下

__netlink_kernel_create(struct net *net, int unit, struct module *module,
            struct netlink_kernel_cfg *cfg)
{
    struct socket *sock;
    struct sock *sk;
    struct netlink_sock *nlk;
    struct listeners *listeners = NULL;
    struct mutex *cb_mutex = cfg ? cfg->cb_mutex : NULL;
    unsigned int groups;

    BUG_ON(!nl_table);

    if (unit < 0 || unit >= MAX_LINKS)
        return NULL;
    ...
}

第三个参数:cfg存放的是netlink内核配置参数,配置参数中的input成员用于处理接收到的消息。该函数的参数变短了,实际是因为放到该结构体中来了

struct netlink_kernel_cfg {
    unsigned int    groups;// netlink组,应该是多播时使用,单播的情况就是0了
    unsigned int    flags;
    void        (*input)(struct sk_buff *skb); //回调函数,当收到消息时,内核会调用该函数指针指向的函数进行处理
    struct mutex    *cb_mutex;
    void        (*bind)(int group);
};

 

在内核创建了netlink socket后,当用户程序发送一个netlink消息到内核时,函数input都会被调用。下面是一个实现了消息处理函数input的例子:

void input (struct sock *sk, int len)
{
   struct sk_buff *skb;
   struct nlmsghdr *nlh = NULL;
  u8 *payload = NULL;

while ((skb = skb_dequeue(&sk->receive_queue))!= NULL)
  {
     /* process netlink message pointed by skb->data */
     nlh = (struct nlmsghdr *)skb->data;
     payload = NLMSG_DATA(nlh);
     /* process netlink message with header pointed by
     * nlh        and payload pointed by payload
     */
   }
}

回调函数input是在用户进程调用sendmsg系统调用时被调用的。因此该函数中的处理时间不能太长,否则会导致其他系统调用被阻塞。比较好的做法是在该函数中创建一个新的内核线程来处理netlink socket消息。

3.2.3 在内核中发送netlink消息

在内核空间发送netlink消息时有两个接口可以使用,netlink_unicast接口用来发送一个单播消息,其定义如下:

int netlink_unicast(struct sock *ssk, struct sk_buff *skb,
            u32 pid, int nonblock)

ssk是调用netlink_kernel_create接口所创建的socket控制块,skb中的data指向需要发送的netlink消息体,pid为要发往的用户进程的PID,nonblock指示当发送缓冲区不可用时尝试发送的超时时间。 
netlink消息的源地址和目的地址可以通过如下代码进行设置:

NETLINK_CB(skb).groups = local_groups;
NETLINK_CB(skb).pid = 0;   /* from kernel */
NETLINK_CB(skb).dst_groups = dst_groups;
NETLINK_CB(skb).dst_pid = dst_pid;

使用netlink_broadcast接口可以发送一个多播消息,其定义如下:

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid,
              u32 group, gfp_t allocation)

其中group是接收消息的各个组的比特位进行与运算的结果。allocation是内核申请内存的类型,通常情况下在中断上下文中使用GFP_ATOMIC,否则使用GFP_KERNEL。

在内核中关闭netlink socket

假设netlink_kernel_create函数返回的netlink socket为struct sock *nl_sk,我们可以通过如下API来关闭这个netlink socket:

sock_release(nl_sk->socket);
sock_release(nl_sk->socket);

如下代码是一个使用netlink机制的简单实例: 
netlink_test_user.c:

#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/socket.h>

#define NETLINK_TEST 21

#define MAX_PAYLOAD 1024 /* maximum payload size*/
struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
struct iovec iov;
int sock_fd;
struct msghdr msg;

int main(int argc, char* argv[])
{
        sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
        memset(&msg, 0, sizeof(msg));
        memset(&src_addr, 0, sizeof(src_addr));
        src_addr.nl_family = AF_NETLINK;
        src_addr.nl_pid = getpid(); /* self pid */
        src_addr.nl_groups = 0; /* not in mcast groups */
        bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr));
        memset(&dest_addr, 0, sizeof(dest_addr));
        dest_addr.nl_family = AF_NETLINK;
        dest_addr.nl_pid = 0; /* For Linux Kernel */
        dest_addr.nl_groups = 0; /* unicast */
                nlh=(struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
        /* Fill the netlink message header */
        nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
        nlh->nlmsg_pid = getpid(); /* self pid */
        nlh->nlmsg_flags = 0;
        /* Fill in the netlink message payload */
        strcpy(NLMSG_DATA(nlh), "This is a netlink test !");

        iov.iov_base = (void *)nlh;
        iov.iov_len = nlh->nlmsg_len;
        msg.msg_name = (void *)&dest_addr;
        msg.msg_namelen = sizeof(dest_addr);
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;

        printf(" Sending message. ...\n");
        sendmsg(sock_fd, &msg, 0);

        /* Read message from kernel */
        memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
        printf(" Waiting message. ...\n");
        recvmsg(sock_fd, &msg, 0);
        printf(" Received message payload: %s\n",NLMSG_DATA(nlh));

         /* Close Netlink Socket */
        close(sock_fd);

netlink_test_kernel.c:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <net/sock.h>
#include <net/netlink.h>
#define NETLINK_TEST 21

struct sock *nl_sk = NULL;
EXPORT_SYMBOL_GPL(nl_sk);

void nl_data_ready (struct sk_buff *__skb)
{
  struct sk_buff *skb;
  struct nlmsghdr *nlh;
  u32 pid;
  int rc;
  int len = NLMSG_SPACE(1200);
  char str[100];

  printk("net_link: data is ready to read.\n");
  skb = skb_get(__skb);

  if (skb->len >= NLMSG_SPACE(0)) {
    nlh = nlmsg_hdr(skb);
    printk("net_link: recv %s.\n", (char *)NLMSG_DATA(nlh));
    memcpy(str,NLMSG_DATA(nlh), sizeof(str));
    pid = nlh->nlmsg_pid; /*pid of sending process */
    printk("net_link: pid is %d\n", pid);
    kfree_skb(skb);

    skb = alloc_skb(len, GFP_ATOMIC);
    if (!skb){
      printk(KERN_ERR "net_link: allocate failed.\n");
      return;
    }
    nlh = nlmsg_put(skb,0,0,0,1200,0);
    NETLINK_CB(skb).pid = 0; /* from kernel */

    memcpy(NLMSG_DATA(nlh), str, sizeof(str));
    printk("net_link: going to send.\n");
    rc = netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
    if (rc < 0) {
      printk(KERN_ERR "net_link: can not unicast skb (%d)\n", rc);
    }
    printk("net_link: send is ok.\n");
  }
  return;
}

static int test_netlink(void) 
{
  nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, 0, nl_data_ready, NULL, THIS_MODULE);

  if (!nl_sk) {
    printk(KERN_ERR "net_link: Cannot create netlink socket.\n");
    return -EIO;
  }
  printk("net_link: create socket ok.\n");
  return 0;
}

int init_module()
{
  test_netlink();
  return 0;
}
void cleanup_module( )
{
  if (nl_sk != NULL){
    sock_release(nl_sk->sk_socket);
  }
  printk("net_link: remove ok.\n");
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("sprintwind");

Makefile:

MODULE_NAME := netlink_test_kernel
obj-m :=$(MODULE_NAME).o

KERNEL_VER := $(shell uname -r)
KERNEL_DIR := /lib/modules/$(KERNEL_VER)/build
PWD := $(shell pwd)

all:
    $(MAKE) -C $(KERNEL_DIR) M=$(PWD)
    gcc -o netlink_test_user netlink_test_user.c
clean:
    rm -fr *.ko *.o *.cmd netlink_test_user $(MODULE_NAME).mod.c

使用方法:将这3个文件放在同一个目录,修改Makefile的属性为可执行,执行make命令进行编译,将编译出来的内核模块使用insmod命令插入内核,然后执行./netlink_test_user查看运行结果。内核模块的打印可以通过dmesg命令查看。

<think>好的,用户现在的问题是“linuxC netlink socket如何监听WIFI设备连接情况”。我需要仔细分析这个问题,并给出详细的解决方案。首先,我需要确定用户的需求是什么。用户可能想要监控WiFi设备的连接状态变化,比如设备连接或断开,或者连接到不同的网络。首先,回顾之前的历史对话,用户之前询问过如何监听WiFi状态变化,我给出的答案是通过NETLINK_ROUTE协议,监听RTMGRP_LINK组播组来获取接口状态变化。但这次的问题更具体,是关于“设备连接情况”,这可能涉及更详细的连接信息,比如SSID变更、认证状态等,而不仅仅是接口的UP/DOWN状态。接下来,我需要确定使用哪种netlink协议组播组。之前的回答用的是NETLINK_ROUTE,但可能对于更详细的连接信息,可能需要使用其他协议,比如NL80211(属于NETLINK_GENERIC家族)。NL80211是专门用于无线设备的netlink接口,可以获取更多无线相关的信息,比如扫描结果、连接状态、信号强度等。 因此,解决方案可能需要使用NETLINK_GENERIC,并找到对应的nl80211家族标识。这需要包含linux/nl80211.h头文件,并且可能需要查找家族ID,或者使用libnl库来简化操作。不过用户要求使用纯C代码,可能需要手动处理这些步骤。然后,考虑如何创建socket,绑定到正确的组播组。NETLINK_GENERIC需要指定正确的协议类型,并且组播组可能需要订阅特定的事件,比如NL80211_MULTICAST_GROUP_MLME,这个组播组用于管理事件,比如连接断开。接下来,解析接收到的消息。需要处理NL80211_CMD_CONNECTNL80211_CMD_DISCONNECT事件,从中提取SSID等信息。这需要解析netlink消息的属性,比如NL80211_ATTR_MAC(AP的MAC地址)、NL80211_ATTR_SSID等。 需要注意的是,用户可能需要root权限来执行这些操作,因为访问无线设备的信息通常需要特权。此外,代码可能需要处理多个属性,并且正确解析嵌套的属性结构。另外,可能还需要考虑如何区分不同的无线接口,比如wlan0、wlp3s0等,这可以通过检查接口名称或索引来实现。在之前的回答中,用户提到过滤接口名称以"wlan"开头,但可能需要更灵活的方法,比如动态获取所有无线接口。 然后,编写示例代码。代码需要创建netlink socket,绑定到NL80211家族,订阅相应的组播组,然后循环接收消息,解析事件类型属性。需要处理nlmsg结构,使用nlmsg_parse等函数来提取属性。可能遇到的问题包括如何获取NL80211家族ID,这可能需要通过genl_ctrl消息来查询。或者,如果系统不支持nl80211,可能需要使用其他方法。不过大多数现代Linux系统使用nl80211作为无线设备的配置接口。另外,编译代码时需要链接libnl库,比如使用pkg-config来获取正确的编译选项。但在用户的问题中,可能需要给出纯C的实现,不依赖外部库,因此可能需要手动处理genl消息,这会比较复杂。综合考虑,给出一个使用libnl3库的例子可能更简洁,但用户可能希望避免依赖第三方库。因此,可能需要使用原始的socket操作,手动构造解析消息。不过这会增加代码的复杂性,尤其是处理属性嵌套结构。因此,权衡之后,决定使用libnl3库来简化代码,因为手动处理genl消息非常繁琐且容易出错。同时,说明编译时需要安装libnl3库,并链接相应的库文件。最后,确保代码示例能够正确编译运行,并解释关键部分,比如socket创建、消息订阅、事件解析等。同时提醒用户需要root权限,以及可能的依赖安装步骤。</think>以下是使用Linux netlink socket监听WiFi连接状态的实现方法,基于`NL80211`协议实现更精细化的无线事件监控: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <linux/netlink.h> #include <linux/genetlink.h> #include <linux/nl80211.h> #define BUFFER_SIZE 8192 // 获取NL80211家族ID int get_nl80211_family_id(int sock) { struct { struct nlmsghdr hdr; struct genlmsghdr gnlhdr; char buf[256]; } req; memset(&req, 0, sizeof(req)); req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct genlmsghdr)); req.hdr.nlmsg_type = GENL_ID_CTRL; req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; req.gnlhdr.cmd = CTRL_CMD_GETFAMILY; req.gnlhdr.version = 1; struct nlattr *attr = (struct nlattr*)((char*)&req + NLMSG_ALIGN(req.hdr.nlmsg_len)); attr->nla_type = CTRL_ATTR_FAMILY_NAME; attr->nla_len = strlen("nl80211") + 1 + NLA_HDRLEN; strcpy((char*)attr + NLA_HDRLEN, "nl80211"); req.hdr.nlmsg_len += NLMSG_ALIGN(attr->nla_len); if (send(sock, &req, req.hdr.nlmsg_len, 0) < 0) { perror("send"); return -1; } char resp[BUFFER_SIZE]; ssize_t len = recv(sock, resp, sizeof(resp), 0); if (len < 0) { perror("recv"); return -1; } struct nlmsghdr *nlh = (struct nlmsghdr *)resp; struct nlattr *tb[CTRL_ATTR_MAX + 1] = {}; struct genlmsghdr *gnlh = NLMSG_DATA(nlh); struct nlattr *nla = (struct nlattr*)((char*)gnlh + GENL_HDRLEN); int remaining = len - NLMSG_HDRLEN - GENL_HDRLEN; nla_parse(tb, CTRL_ATTR_MAX, nla, remaining, NULL); if (tb[CTRL_ATTR_FAMILY_ID]) { return *(__u16 *)nla_data(tb[CTRL_ATTR_FAMILY_ID]); } return -1; } int main() { struct sockaddr_nl src_addr; int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC); if (sock < 0) { perror("socket"); return EXIT_FAILURE; } memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); if (bind(sock, (struct sockaddr*)&src_addr, sizeof(src_addr)) < 0) { perror("bind"); close(sock); return EXIT_FAILURE; } int nl80211_id = get_nl80211_family_id(sock); if (nl80211_id < 0) { fprintf(stderr, "nl80211 not found\n"); close(sock); return EXIT_FAILURE; } // 订阅MLME事件(管理帧事件) struct nl_msg *msg = nlmsg_alloc(); genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, nl80211_id, 0, NLM_F_REQUEST, NL80211_CMD_SUBSCRIBE_MLME, 0); nla_put_u32(msg, NL80211_ATTR_IFINDEX, 1); // 监控所有接口 if (send(sock, nlmsg_hdr(msg), nlmsg_len(msg), 0) < 0) { perror("subscribe failed"); nlmsg_free(msg); close(sock); return EXIT_FAILURE; } nlmsg_free(msg); printf("Listening for WiFi connection events...\n"); char buffer[BUFFER_SIZE]; while (1) { ssize_t len = recv(sock, buffer, sizeof(buffer), 0); if (len < 0) { perror("recv"); continue; } struct nlmsghdr *nlh = (struct nlmsghdr *)buffer; for (; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) { struct genlmsghdr *gnlh = NLMSG_DATA(nlh); if (gnlh->cmd == NL80211_CMD_CONNECT) { printf("Device connected to AP\n"); // 解析连接详细信息 struct nlattr *tb[NL80211_ATTR_MAX + 1]; nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL); if (tb[NL80211_ATTR_MAC]) { unsigned char *mac = nla_data(tb[NL80211_ATTR_MAC]); printf("AP MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } if (tb[NL80211_ATTR_SSID]) { char *ssid = nla_data(tb[NL80211_ATTR_SSID]); int len = nla_len(tb[NL80211_ATTR_SSID]); printf("SSID: %.*s\n", len, ssid); } } else if (gnlh->cmd == NL80211_CMD_DISCONNECT) { printf("Device disconnected\n"); } } } close(sock); return EXIT_SUCCESS; } ``` 代码解析: 1. `get_nl80211_family_id` 通过GENL控制接口查询nl80211的家族ID 2. 主程序创建NETLINK_GENERIC类型的socket并绑定 3. 订阅NL80211_CMD_SUBSCRIBE_MLME事件来接收连接状态变更 4. 消息解析: - 识别NL80211_CMD_CONNECT/NL80211_CMD_DISCONNECT事件 - 解析NL80211_ATTR_MAC获取AP的MAC地址 - 解析NL80211_ATTR_SSID获取连接的网络名称 编译依赖: ```bash # 需要安装libnl-3-dev gcc -o wifi_conn_monitor wifi_conn_monitor.c -lnl-3 -lnl-genl-3 ``` 注意事项: 1. 需要root权限运行 2. 可以捕获真实连接事件(包括漫游切换) 3. 需要根据实际接口调整NL80211_ATTR_IFINDEX值 4. 完整实现需要处理更多属性(如认证状态、信号强度等)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值