Netlink实现异步检测网线插拔并分配IP
今天遇到开发板无法在插拔时通过dhcp来获取ip的问题,同时用户有实时性的要求因此也不能一直轮询检测插拔状态。因此就会打算用C语言和脚本来实现异步检测功能。
C代码
C代码后台运行负责检测插拔并执行脚本
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <unistd.h>
#define IFF_LOWER_UP 1<<16/* volatile */
#define MAX_BUFFER_SIZE 4096
int conn_flag0 = 0;
int conn_flag1 = 0;
int main() {
int nlSock;
struct sockaddr_nl srcAddr;
struct nlmsghdr *nlMsg;
char buffer[MAX_BUFFER_SIZE];
// 创建 Netlink Socket
nlSock = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (nlSock < 0) {
perror("Failed to create Netlink socket");
return -1;
}
memset(&srcAddr, 0, sizeof(srcAddr));
srcAddr.nl_family = AF_NETLINK;
srcAddr.nl_groups = RTMGRP_LINK | RTMGRP_NOTIFY;
// 绑定 Socket
if (bind(nlSock, (struct sockaddr *)&srcAddr, sizeof(srcAddr)) < 0) {
perror("Failed to bind Netlink socket");
close(nlSock);
return -1;
}
// 设置非阻塞模式
int flags = fcntl(nlSock, F_GETFL, 0);
fcntl(nlSock, F_SETFL, flags | O_NONBLOCK);
while (1) {
fd_set readSet;
FD_ZERO(&readSet);
FD_SET(nlSock, &readSet);
// 设置超时时间为 1 秒
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
// 监视 Netlink Socket 的可读状态
int result = select(nlSock + 1, &readSet, NULL, NULL, &timeout);
if (result == -1) {
perror("Failed to select");
close(nlSock);
return -1;
} else if (result == 0) {
// 超时,没有新数据到达
// printf("Timeout\n");
continue;
}
if (FD_ISSET(nlSock, &readSet)) {
// 接收消息
int bytesRead = recv(nlSock, buffer, MAX_BUFFER_SIZE, 0);
if (bytesRead < 0) {
perror("Failed to receive message from Netlink socket");
close(nlSock);
return -1;
}
// 遍历接收到的消息,获取到Netlink的消息头。
nlMsg = (struct nlmsghdr *)buffer;
while (NLMSG_OK(nlMsg, bytesRead)) {
if (nlMsg->nlmsg_type == NLMSG_DONE) {
break;
}
if (nlMsg->nlmsg_type == RTM_NEWLINK || nlMsg->nlmsg_type == RTM_DELLINK) {
//NLMSG_DATA()获取完整的ifinfomsg结构体的指针
//根据消息头nlMsg获取到其后面的数据部分ifinfomsg结构体的指针
struct ifinfomsg *ifInfo = (struct ifinfomsg *)NLMSG_DATA(nlMsg);
if (ifInfo->ifi_family == AF_UNSPEC) {
//获取路由信息的属性,以及消息负载的长度,通过这两个参数来判断本次的数据是否有效
struct rtattr *attribute = IFLA_RTA(ifInfo);
int attributeLen = IFLA_PAYLOAD(nlMsg);
while (RTA_OK(attribute, attributeLen)) {
//如果这个本次数据的属性是接口属性(IFLA_IFNAME)则开始接收
if (attribute->rta_type == IFLA_IFNAME) {
//RTA_DATA()获取数据内容的指针
char *ifaceName = (char *)RTA_DATA(attribute);
printf("Interface: %s \n", ifaceName);
/*如果接口处于新链接(RTM_NEWLINK)且是启动状态(IFF_LOWER_UP)则是插上动作
由此可以看出插拔有网络链接和接口启动两个动作来实现
*/
if (nlMsg->nlmsg_type == RTM_NEWLINK && (ifInfo->ifi_flags & IFF_LOWER_UP)) {
// printf("Interface plugged-in\n");
if (strcmp(ifaceName, "eth0") == 0) {1
if(!conn_flag0){
conn_flag0 = 1;
system("/bin/sh ./autoAssignIp.sh 0");
printf("eth0 Interface plugged-in\n");
}
}else if(strcmp(ifaceName, "eth1") == 0){
if(!conn_flag1){
conn_flag1 = 1;
system("/bin/sh ./autoAssignIp.sh 1");
printf("eth1 Interface plugged-in\n");
}
}else{
printf("other netplan Interface plugged-in\n");
}
} else if (nlMsg->nlmsg_type == RTM_DELLINK) {
if (strcmp(ifaceName, "eth0") == 0) {
if(conn_flag0){
conn_flag0 = 0;
system("/bin/sh ./autoAssignIp.sh 0");
}
}else if(strcmp(ifaceName, "eth1") == 0){
if(conn_flag1){
conn_flag1 = 0;
system("/bin/sh ./autoAssignIp.sh 1");
}
}else{
printf("Not plugged with Eth\n");
}
// printf("Interface unplugged\n");
}
}
attribute = RTA_NEXT(attribute, attributeLen);
}
}
}
nlMsg = NLMSG_NEXT(nlMsg, bytesRead);
}
}
}
close(nlSock);
return 0;
}
脚本代码
脚本代码负责发送dhcp获得ip
#!/bin/bash
check_ip() {
local interface="eth$1"
if ! ip link show "$interface" >/dev/null 2>&1; then
echo "Interface $interface does not exist!"
exit 1
fi
if ip link show "$interface" | grep -q "state UP"; then
echo "Interface $interface is connected"
ip link set dev "$interface" down
ip link set dev "$interface" up
udhcpc -i "$interface"
ifconfig "$interface" down
ifconfig "$interface" up
echo "IP has been assigned"
else
echo "Interface $interface does not exist"
fi
}
check_ip $1
总结:
通过解决本题,我了解了nlmsghdr
、ifinfomsg
和 rtattr
是 Linux 内核中用于处理 Netlink 消息的三个相关结构体,它们之间也存在关联关系。
nlmsghdr
(Netlink Message Header)是 Netlink 消息的消息头结构体,定义在<linux/netlink.h>
头文件中。它包含了消息的通用信息,比如消息长度、类型等。在struct nlmsghdr
中有一个成员变量nlmsg_type
,用于标识消息的类型,可以表示不同的消息类型,如路由消息、接口消息等。ifinfomsg
(Interface Information Message)是用于表示网络接口信息的结构体,定义在<linux/if.h>
头文件中。它是 Netlink 消息中一种特定类型的有效负载(payload)。ifinfomsg
结构体包含了关于网络接口的各种信息,例如接口索引、接口状态等。rtattr
(Route Attribute)是用于封装可选路由信息的结构体,定义在<linux/rtnetlink.h>
头文件中。它也是 Netlink 消息中的一种特定类型的有效负载(payload)。rtattr
结构体用于封装和传递不同类型的路由信息,以满足网络协议栈的需求。它有rta_type
和rta_len
两个字段,用于表示属性的类型和长度,后面紧跟着属性的值。- 在 Netlink 消息中,
nlmsghdr
结构体用于表示消息头,它包含所有消息的通用信息。消息的具体有效负载(payload)部分可以是不同类型的结构体,如ifinfomsg
或rtattr
。这些结构体可以根据消息的类型进行解析和处理,从而获取特定类型的信息。
综上所述,nlmsghdr
是 Netlink 消息的消息头,ifinfomsg
和 rtattr
是不同类型消息的有效负载(payload),它们之间存在关联关系。在处理 Netlink 消息时,可以使用 nlmsghdr
的字段来确定消息的类型,再根据类型选择相应的结构体类型(如 ifinfomsg
、rtattr
),以便解析和处理消息中的具体信息。