DragonOS/user/apps/c_unitest/test_netlink.c

246 lines
7.6 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>
// 定义一个足够大的缓冲区来接收Netlink消息
#define NL_BUFSIZE 8192
// 结构体,用于将请求消息封装起来
struct nl_req_t {
struct nlmsghdr nlh;
struct ifaddrmsg ifa;
};
void parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len) {
memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
while (RTA_OK(rta, len)) {
if (rta->rta_type <= max) {
tb[rta->rta_type] = rta;
}
rta = RTA_NEXT(rta, len);
}
}
int run_netlink_test() {
int sock_fd;
struct sockaddr_nl sa_nl;
struct nl_req_t req;
// struct iovec iov;
// struct msghdr msg;
// struct sockaddr_nl src_addr; // 用于接收发送方的地址
char buf[NL_BUFSIZE];
ssize_t len;
struct nlmsghdr *nlh;
// 创建Netlink套接字
sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sock_fd < 0) {
perror("socket creation failed");
return EXIT_FAILURE;
}
// 设置Netlink地址
memset(&sa_nl, 0, sizeof(sa_nl));
sa_nl.nl_family = AF_NETLINK;
sa_nl.nl_pid = getpid(); // 使用进程ID作为地址
// 绑定Netlink套接字
if (bind(sock_fd, (struct sockaddr *)&sa_nl, sizeof(sa_nl)) < 0) {
perror("socket bind failed");
close(sock_fd);
return EXIT_FAILURE;
}
// 构建RTM_GETADDR请求消息
memset(&req, 0, sizeof(req));
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
// 这是关键设置DUMP标志以获取所有地址
req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.nlh.nlmsg_type = RTM_GETADDR;
req.nlh.nlmsg_seq = 1; // 序列号,用于匹配请求和响应
req.nlh.nlmsg_pid = getpid();
req.ifa.ifa_family =
AF_INET; // 只请求 IPv4 地址 (也可以用 AF_UNSPEC 获取所有)
// 内核的目标地址
struct sockaddr_nl dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; // 0 for the kernel
dest_addr.nl_groups = 0; // Unicast
// 发送请求到内核
if (sendto(sock_fd,
&req,
req.nlh.nlmsg_len,
0,
(struct sockaddr *)&dest_addr,
sizeof(dest_addr)) < 0) {
perror("send failed");
close(sock_fd);
return EXIT_FAILURE;
}
printf("Sent RTM_GETADDR request with DUMP flag.\n\n");
// // 准备 recvmsg 所需的结构体
// // iovec 指向我们的主数据缓冲区
// iov.iov_base = buf;
// iov.iov_len = sizeof(buf);
// // msghdr 将所有部分组合在一起
// msg.msg_name = &src_addr; // 填充发送方地址
// msg.msg_namelen = sizeof(src_addr);
// msg.msg_iov = &iov; // 指向数据缓冲区
// msg.msg_iovlen = 1;
// msg.msg_control = NULL; // 我们暂时不处理控制消息
// msg.msg_controllen = 0;
int received_messages = 0;
// 循环接收响应
while ((len = recv(sock_fd, buf, sizeof(buf), 0)) > 0) {
// ssize_t len = recvmsg(sock_fd, &msg, 0);
// if (len < 0) {
// perror("recvmsg failed");
// break;
// }
// if (len == 0) {
// printf("EOF on netlink socket\n");
// break;
// }
// if (msg.msg_flags & MSG_TRUNC) {
// fprintf(
// stderr,
// "Warning: Message was truncated. Buffer may be too small.\n");
// }
// 使用 NLMSG_OK 遍历缓冲区中可能存在的多条消息
for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, len);
nlh = NLMSG_NEXT(nlh, len)) {
// 如果是 DUMP 结束的标志,则退出循环
if (nlh->nlmsg_type == NLMSG_DONE) {
printf("--- End of DUMP ---\n");
if (!received_messages) {
printf("(Received an empty list as expected)\n");
}
close(sock_fd);
return EXIT_SUCCESS;
}
// 如果是错误消息
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(nlh);
fprintf(stderr,
"Netlink error received: %s\n",
strerror(-err->error));
close(sock_fd);
return EXIT_FAILURE;
}
// 只处理我们期望的 RTM_NEWADDR 消息
if (nlh->nlmsg_type != RTM_NEWADDR) {
printf("Received unexpected message type: %d\n",
nlh->nlmsg_type);
continue;
}
// printf("Received message from PID: %u\n", src_addr.nl_pid);
// 表明我们至少接收到了一条信息
received_messages = 1;
struct ifaddrmsg *ifa = (struct ifaddrmsg *)NLMSG_DATA(nlh);
struct rtattr *rta_tb[IFA_MAX + 1];
// 解析消息中的路由属性
int rta_len = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa));
parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa), rta_len);
printf("Interface Index: %d, PrefixLen: %d, Scope: %d\n",
ifa->ifa_index,
ifa->ifa_prefixlen,
ifa->ifa_scope);
char ip_addr_str[INET6_ADDRSTRLEN];
// 打印 IFA_LABEL (对应你的 AddrAttr::Label)
if (rta_tb[IFA_LABEL]) {
printf("\tLabel: %s\n", (char *)RTA_DATA(rta_tb[IFA_LABEL]));
}
// 打印 IFA_ADDRESS (对应你的 AddrAttr::Address)
if (rta_tb[IFA_ADDRESS]) {
inet_ntop(ifa->ifa_family,
RTA_DATA(rta_tb[IFA_ADDRESS]),
ip_addr_str,
sizeof(ip_addr_str));
printf("\tAddress: %s\n", ip_addr_str);
}
// 打印 IFA_LOCAL (对应你的 AddrAttr::Local)
if (rta_tb[IFA_LOCAL]) {
inet_ntop(ifa->ifa_family,
RTA_DATA(rta_tb[IFA_LOCAL]),
ip_addr_str,
sizeof(ip_addr_str));
printf("\tLocal: %s\n", ip_addr_str);
}
printf("----------------------------------------\n");
}
}
if (len < 0) {
perror("recv failed");
}
close(sock_fd);
return EXIT_SUCCESS;
}
int main(int argc, char *argv[]) {
printf("=========== STAGE 1: Testing in Default Network Namespace "
"===========\n");
if (run_netlink_test() != 0) {
fprintf(stderr, "Test failed in the default namespace.\n");
return EXIT_FAILURE;
}
printf("\n\n=========== STAGE 2: Creating and Testing in a New Network "
"Namespace ===========\n");
// ** 关键步骤:创建新的网络命名空间 **
// 这个调用会将当前进程移入一个新的、隔离的网络栈中
if (unshare(CLONE_NEWNET) == -1) {
perror("unshare(CLONE_NEWNET) failed");
fprintf(stderr,
"This test requires root privileges (e.g., 'sudo "
"./your_program').\n");
return EXIT_FAILURE;
}
printf("Successfully created and entered a new network namespace.\n");
// 在新的命名空间中再次运行同样的测试
if (run_netlink_test() != 0) {
fprintf(stderr, "Test failed in the new namespace.\n");
return EXIT_FAILURE;
}
printf("\nAll tests completed successfully.\n");
return EXIT_SUCCESS;
}