【openwrt学习笔记】IPV6 ND协议学习和socket编程

news2024/9/27 9:30:12

目录

    • 一、参考链接
    • 二、学习目标
    • 三、代码解析
      • 3.1 仅解析NA报文保存设备mac和ipv6地址信息
        • 3.1.1 open_ns_socket
        • 3.1.2 recv_ns_pack
      • 3.2 解析NA和NS报文中DAD报文保存设备mac和ipv6地址信息
        • 3.2.1 open_ns_na_socket
        • 3.2.2 recv_ns_na_pack
    • 四、代码优化
      • 4.1 BPF参考学习资料
      • 4.2 代码实现
        • 4.2.1 方式一:使用指令直接编写BPF程序
        • 4.2.1 方式二:使用 tcpdump -dd 命令生成BPF字节码
      • 4.3 二者优缺点

一、参考链接

IPv6知识 - ND协议【一文通透】
IPV6 ND协议–源码解析【根源分析】
Raw Socket 接收和发送数据包

二、学习目标

(1)使用socket进行网络编程,创建并接受icmpv6中的NS和NA报文;
(2)要解析出NS中的DAD报文和NA报文,需要保存其源mac地址和和请求ipv6地址,在路由器中可用于存储设备的mac地址
(说明:本笔记主要是实现从DAD报文中解析出源mac地址和和请求ipv6地址,原来的程序实现只是过滤NA报文,然后解析数据,但是经常会出现无法及时解析出设备ipv6地址,甚至长时间获取不到的情况,这里增加DAD检测,一开始就保存设备的所有ipv6地址,后续如果更新在将不使用的ipv6地址老化掉。)
(3)socket网络编程实战,之前没怎么实操过,这一次正好复习巩固。

三、代码解析

3.1 仅解析NA报文保存设备mac和ipv6地址信息

3.1.1 open_ns_socket
int open_ns_socket(int idx) {
    struct icmp6_filter filt;  // ICMPv6消息过滤器
    int val = 0;               // 用于设置套接字选项的临时变量
    int sock = -1;             // 套接字描述符初始化为-1
    int ret = -1;              // 存储返回值的变量
    int buffersize = 100 * 1024;  // 接收缓冲区大小
    char *ifname = brname[idx];  // 网络接口名称

    // 创建用于ICMPv6通信的原始套接字
    sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
    if (sock < 0) {
        return -1;  // 若套接字创建失败,直接返回-1
    }

    // 设置ICMPv6过滤器,阻止所有ICMPv6消息并仅允许邻居通告消息通过
    ICMP6_FILTER_SETBLOCKALL(&filt);
    ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filt);
    ret = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filt, sizeof(filt));
    if (ret < 0) {
        close(sock);  // 若设置失败,关闭套接字并返回-1
        return -1;
    }

    // 设置套接字接收缓冲区的大小
    ret = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));
    if (ret < 0) {
        close(sock);  // 若设置失败,关闭套接字并返回-1
        return -1;
    }

    // 启用对特定IPv6多播消息的监听
    val = 1;
    ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MDMAC, &val, sizeof(val));
    if (ret < 0) {
        close(sock);  // 若设置失败,关闭套接字并返回-1
        return -1;
    }

    // 绑定套接字到具体网络接口
    ret=setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname));
	if (ret < 0) //Dana
	{
		close(sock);
		return -1;
	}
	
	return sock;
}

3.1.2 recv_ns_pack
int recv_ns_pack(int sock) {
    uint8_t buf[1024], cmsg_buf[64];  // 分别用于存储接收数据和控制消息的缓冲区
    struct cmsghdr *ch = NULL;  // 指向cmsghdr结构的指针
    ssize_t len = -1;  // 接收到的数据长度
    uint8 mac[ETH_ALEN];  // 用于存储MAC地址的数组
    struct sockaddr_in6 from;  // 存储源IPv6地址的结构
    struct iovec iov = {buf, sizeof(buf)};  // iov结构,指向数据缓冲区
    struct msghdr msg = {
        .msg_name = (void *) &from,  // 指向存放源地址的结构体
        .msg_namelen = sizeof(from),  // 地址结构体的长度
        .msg_iov = &iov,  // 指向iovec结构数组的指针
        .msg_iovlen = 1,  // iovec结构数组的长度
        .msg_control = cmsg_buf,  // 指向辅助数据的缓冲区
        .msg_controllen = sizeof(cmsg_buf),  // 辅助数据缓冲区的长度
        .msg_flags = 0  // 接收消息的标志位(未设置)
    };

    // 使用recvmsg非阻塞地接收数据
    len = recvmsg(sock, &msg, MSG_DONTWAIT);
    if (len <= 0)  // 如果读取失败或无数据可读,则返回-1
        return -1;

    // 遍历所有控制消息
    for (ch = CMSG_FIRSTHDR(&msg); ch != NULL; ch = CMSG_NXTHDR(&msg, ch)) {
        // 查找IPV6层级的控制消息,类型为MAC地址
        if (ch->cmsg_level == IPPROTO_IPV6 && ch->cmsg_type == IPV6_MDMAC) {
            // 将MAC地址复制到mac数组
            memcpy(mac, CMSG_DATA(ch), ETH_ALEN);
            break;
        }
    }

    // 调用函数使用接收到的数据重建ARP表
    rebuild_arp_table(mac, from.sin6_addr);

    // 返回接收到的数据长度
    return len;
}

3.2 解析NA和NS报文中DAD报文保存设备mac和ipv6地址信息

3.2.1 open_ns_na_socket
int open_ns_na_socket(int idx) {
    int sock; // 套接字描述符
    int buffersize = 100 * 1024; // 接收缓冲区大小
    int ret = -1; // 用于存储返回值
    struct sockaddr_ll addr; // 低级别的地址定义
    struct ifreq ifr; // 接口请求结构体
    char *ifname = netscan.brname[idx]; // 网络接口名称

    // 创建一个原始套接字用于接收IPv6数据包
    if ((sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IPV6))) < 0) {
        return -1; // 如果创建失败,返回-1
    }

    // 设置套接字选项,增大接收缓冲区以避免数据包丢失
    ret = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));
    if (ret < 0) // 如果设置失败
    {
        close(sock); // 关闭套接字
        return -1; // 返回-1
    }

    // 将网络接口名称复制到ifr结构体中,以便获取接口索引
    strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
    if (ioctl(sock, SIOCGIFINDEX, &ifr) < 0) { // 使用ioctl获取接口索引
        close(sock); // 如果失败,则关闭套接字
        return -1; // 返回-1
    }

    // 设置地址结构体
    memset(&addr, 0, sizeof(addr)); // 地址结构体清零
    addr.sll_family = AF_PACKET; // 协议族为AF_PACKET
    addr.sll_protocol = htons(ETH_P_IPV6); // 设置协议类型为IPv6
    addr.sll_ifindex = ifr.ifr_ifindex; // 设置网络接口索引
    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { // 绑定套接字到指定的网络接口
        close(sock); // 如果绑定失败,则关闭套接字
        return -1; // 返回-1
    }

    return sock; // 绑定成功,返回套接字描述符
}

3.2.2 recv_ns_na_pack
int recv_ns_na_pack(int sock) {
    char buf[2048];  // 缓冲区,用于存放接收的数据包
    struct ip6_hdr *ipv6_hdr;  // 指向IPv6头部的指针
    struct icmp6_hdr *icmp6_hdr;  // 指向ICMPv6头部的指针
    struct sockaddr_ll addr;  // 用于存储发送方地址信息的结构体
    socklen_t addr_len = sizeof(addr);  // 发送方地址信息结构体的大小
    ssize_t numbytes;  // 接收到的字节数
    uint8_t src_mac[ETH_ALEN];  // 用于存储源MAC地址的数组

    // 从套接字接收数据,并填充发送方地址信息
    numbytes = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &addr_len);
    if (numbytes > 0) {
        // 获取IPv6头部,并跳过以太网头
        ipv6_hdr = (struct ip6_hdr *)(buf + sizeof(struct ethhdr));
        // 检查下一个头部是否为ICMPv6
        if (ipv6_hdr->ip6_nxt == IPPROTO_ICMPV6) {
            // 从以太网帧中提取源MAC地址
            memcpy(src_mac, buf + 6, ETH_ALEN);
            // 获取ICMPv6头部
            icmp6_hdr = (struct icmp6_hdr *)(buf + sizeof(struct ethhdr) + sizeof(struct ip6_hdr));
            // 如果是邻居请求
            if (icmp6_hdr->icmp6_type == ND_NEIGHBOR_SOLICIT) {
                // 如果源IPv6地址是未指定地址
                if (IN6_IS_ADDR_UNSPECIFIED(&ipv6_hdr->ip6_src)) {
                    // 转换为邻居请求结构体
                    struct nd_neighbor_solicit *ns = (struct nd_neighbor_solicit *)icmp6_hdr;
                    // 使用目标地址和源MAC地址更新ARP表
                    rebuild_arp_table(src_mac, ns->nd_ns_target);
                }
            }
            // 如果是邻居广告
            else if (icmp6_hdr->icmp6_type == ND_NEIGHBOR_ADVERT) {
                // 转换为邻居广告结构体
                struct nd_neighbor_advert *na = (struct nd_neighbor_advert *)icmp6_hdr;
                // 使用目标地址和源MAC地址更新ARP表
                rebuild_arp_table(src_mac, na->nd_na_target);
            }
        }
    }
    // 返回接收到的字节数
    return numbytes;
}

说明:

  • 使用AF_PACKET和SOCK_RAW创建的套接字允许你在更低的层级上操作,直接处理硬件发送和接收的以太网帧,这通常用于实现底层网络协议或进行网络数据包的捕获。
  • 使用AF_INET6和SOCK_RAW创建的套接字让你可以处理ICMPv6数据包,同时自动处理IPv6的数据链路层细节。你将接收到的是从IPv6头部开始的数据包,无需自己解析以太网头部。

由于需要获取DAD报文的mac,只能从eth层获取,所以这里才AF_PACKET创建套接字。

四、代码优化

上述修改后的socket使用AF_PACKET和SOCK_RAW创建的套接字,将接收所有的ipv6报文,并未进行过滤,如果在跑流或者组播测试时,一旦有大量的ipv6报文,会很大的占用资源,造成浪费和严重后果。
所以这里可以使用BPF(Berkeley Packet Filter)伯克利包过滤器进行过滤。

4.1 BPF参考学习资料

  1. Linux网络编程:原始套接字–包过滤器BPF
  2. linux网络和BPF
  3. Linux bpf 3.1、Berkeley Packet Filter (BPF) (Kernel Document)

4.2 代码实现

4.2.1 方式一:使用指令直接编写BPF程序
struct sock_filter bpf_code[] = {
    // Load Ethernet Protocol Type into the BPF accumulator from the Ethernet header
    {BPF_LD + BPF_H + BPF_ABS, 0, 0, offsetof(struct ethhdr, h_proto)},
    // Jump to next instruction if Protocol Type is IPv6
    {BPF_JMP + BPF_JEQ + BPF_K, 0, 1, htons(ETH_P_IPV6)},
    // Go to reject packet
    {BPF_JMP + BPF_JA, 0, 0, 6},
    // Load the Next Header field from the IPv6 header
    {BPF_LD + BPF_B + BPF_ABS, 0, 0, ETH_HLEN + 6},
    // Jump to next instruction if Next Header is ICMPv6
    {BPF_JMP + BPF_JEQ + BPF_K, 0, 1, IPPROTO_ICMPV6},
    // Go to reject packet
    {BPF_JMP + BPF_JA, 0, 0, 4},
    // Load the ICMPv6 message type
    {BPF_LD + BPF_B + BPF_ABS, 0, 0, ETH_HLEN + 40},
    // Check if it's a Neighbor Solicitation message
    {BPF_JMP + BPF_JEQ + BPF_K, 0, 1, ND_NEIGHBOR_SOLICIT},
    // Check if it's a Neighbor Advertisement message
    {BPF_JMP + BPF_JEQ + BPF_K, 0, 0, ND_NEIGHBOR_ADVERT},
    // Reject packet
    {BPF_RET + BPF_K, 0, 0, 0},
    // Accept packet
    {BPF_RET + BPF_K, 0, 0, 0xffffffff},
};


struct sock_fprog bpf = {
    .len = ARRAY_SIZE(bpf_code),
    .filter = bpf_code
};

// 将BPF过滤器附加到套接字
ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
if (ret < 0) {
    close(sock);
    return -1;
}
4.2.1 方式二:使用 tcpdump -dd 命令生成BPF字节码

使用下面这种方式可以进行更好的扩展并指定接口,然后生成BPF字节码进行使用

#define MAX_FILTERS 256 // 假设一个最大的过滤器数量
#define COMMAND_SIZE 256 // 命令字符串的最大长度

int get_BPF_bytecode(char *ifname) {
    FILE *fp;
    char path[1035];
    struct sock_filter bpf_code[MAX_FILTERS];
    int bpf_code_size = 0;

    // 使用snprintf构建tcpdump命令
    snprintf(command, COMMAND_SIZE, "tcpdump -dd -i %s 'icmp6 && ip6[40] == 135 || ip6[40] == 136'", ifname);

    // 执行tcpdump命令
    fp = popen(command, "r");
    if (fp == NULL) {
        printf("Failed to run command\n" );
        exit(1);
    }

    // 读取输出并解析
    while (fgets(path, sizeof(path), fp) != NULL) {
        struct sock_filter filter;
        if (sscanf(path, "{ 0x%x, %d, %d, 0x%x },", &filter.code, &filter.jt, &filter.jf, &filter.k) == 4) {
            bpf_code[bpf_code_size++] = filter;
            if(bpf_code_size >= MAX_FILTERS) {
                fprintf(stderr, "Too many filters, max is %d\n", MAX_FILTERS);
                break; // 防止数组溢出
            }
        }
    }
    return 0;
}

当然也可以直接使用命令进行生成,然后复制过去使用,这种方式就比较局限无法扩展,并且不易于维护。
在这里插入图片描述

4.3 二者优缺点

使用 tcpdump -dd 命令生成BPF字节码和直接编写一个BPF程序本质上是两个不同的操作层级,它们各自有优势和劣势:

一、使用 tcpdump -dd 生成BPF字节码:

优点:

  • 简单易用:对非专家用户而言,使用 tcpdump -dd 可以非常简单快速地生成复杂过滤逻辑的字节码,无需深入了解BPF的内部语言和结构。
  • 快速迭代:可以通过修改 tcpdump 的表达式快速更改过滤器的逻辑,并重新生成字节码。
  • 广泛支持:tcpdump 表达式被广泛使用和支持,有许多文档和社区可以提供帮助。

缺点:

  • 灵活性有限:受限于 tcpdump 表达式的能力,可能无法实现一些更复杂或特定需求的BPF程序逻辑。
  • 外部依赖:需要在系统上安装 tcpdump 工具,对于嵌入式系统或严格的生产环境可能不是最优选择。

二、 直接编写BPF程序:
优点:

  • 更灵活:可以编写任何复杂度的BPF程序,不受 tcpdump 表达式语法的限制。
  • 性能优化:专业的BPF开发者可以精细调整每条指令,优化性能和资源使用。
  • 深度集成:对于需要在运行时动态生成或修改BPF程序的应用,直接编程提供了更高的控制精度。

缺点:

  • 复杂性高:编写原始BPF程序需要对BPF虚拟机的工作方式有深入理解,对于初学者来说门槛较高。
  • 调试困难:BPF程序的调试通常比较困难,尤其是在高级的优化和调整阶段。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1323846.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DSSAT作物模型建模方法与进阶基于Python语言快速批量运行DSSAT模型及交叉融合、扩展应用技术应用

随着数字农业和智慧农业的发展&#xff0c;基于过程的作物生长模型&#xff08;Process-based Crop Growth Simulation Model&#xff09;在模拟作物对气候变化的响应与适应、农田管理优化、作物品种和株型筛选、农业碳中和、农田固碳减排等领域扮演着越来越重要的作用。Decisi…

跨境卖家必看!TikTok带货经验分享,TikTok直播带货怎么做?

如今直播带货正发展得如火如荼&#xff0c;不少跨境人也纷纷做起了带货&#xff0c;其中TikTok带货的力量不容小觑&#xff0c;也已经成为了跨境电商运营非常火爆的营销方式&#xff0c;有很多朋友问龙哥TikTok带货怎么做&#xff0c;其实以龙哥这么多年的经验来看&#xff0c;…

智能物联网汽车3d虚拟漫游展示增强消费者对品牌的认同感和归属感

汽车3D虚拟展示系统是一种基于web3D开发建模和VR虚拟现实技术制作的360度立体化三维汽车全景展示。它通过计算机1:1模拟真实的汽车外观、内饰和驾驶体验&#xff0c;让消费者在购车前就能够更加深入地了解车辆的性能、特点和设计风格。 华锐视点云展平台是一个专业的三维虚拟展…

JRT打印元素绘制协议整合PDF

打印不光要能打印内部的单据&#xff0c;对于检验的打印还有外送回传的PDF报告也需要能够打印&#xff0c;所以需要把打印PDF文件整合进来&#xff0c;为此给打印元素绘制协议增加PDF类型的元素。 定义如下&#xff0c;由绘制协议按地址下载文件后和其他打印元素整合&#xff…

【BEV感知】BEVFormer 融合多视角图形的空间特征和时序特征 ECCV 2022

前言 本文分享BEV感知方案中&#xff0c;具有代表性的方法&#xff1a;BEVFormer。 它基于Deformable Attention&#xff0c;实现了一种融合多视角相机空间特征和时序特征的端到端框架&#xff0c;适用于多种自动驾驶感知任务。 主要由3个关键模块组成&#xff1a; BEV Que…

14 v-model绑定输入框

概述 v-model用于实现双向数据绑定&#xff0c;使用v-model绑定输入框是Vue3中最常见的用法之一。 比如&#xff0c;在制作登录界面的时候&#xff0c;我们会使用v-model绑定用户名和密码&#xff0c;这里的用户名和密码都是输入框。 基本用法 我们创建src/components/Demo…

基于Java+SpringBoot+Mybaties-plus+Vue+ElementUI+Vant 电影院订票管理系统 的设计与实现

一.项目介绍 基于SpringBootVue 电影院订票管理系统 分为前端和后端。 前端&#xff08;用户&#xff09;&#xff1a; 登录后支持查看首页、电影、影院和我的信息 支持查看正在热映和即将上映的电影信息 支持购票&#xff08;需选择影院座位&#xff09;、看过&#xff08;评论…

接口测试和测试用例分析

只要有软件产品的公司百分之九十以上都会做接口测试&#xff0c;要做接口测试的公司那是少不了接口测试工程师的&#xff0c;接口测试工程师相对于其他的职位又比较轻松并且容易胜任。如果你想从事接口测试的工作那就少不了对接口进行分析&#xff0c;同时也会对测试用例进行研…

error while writing to output stream

unable to start device cairo_pdf 报错一般2种原因&#xff1a; 1.文件路径错误 2. 有其他软件打开文件导致不能写入

EMD、EEMD、FEEMD、CEEMD、CEEMDAN的区别、原理和Python实现(三)FEEMD

往期精彩内容&#xff1a; 风速预测&#xff08;一&#xff09;数据集介绍和预处理-CSDN博客 风速预测&#xff08;二&#xff09;基于Pytorch的EMD-LSTM模型-CSDN博客 风速预测&#xff08;三&#xff09;EMD-LSTM-Attention模型-CSDN博客 风速预测&#xff08;四&#xf…

STM32/STM8资源节约主义编程方式

STM32/STM8资源节约主义编程方式 在小资源芯片进行代码设计时&#xff0c;如STM32C0系列&#xff0c;STM8系列&#xff0c;因为官方库本身要包含各种场景应用特征的支持&#xff0c;所以会有一些冗余的代码占用更多FLASH空间。当需要实现资源占用最简化设计方式时&#xff0c;…

Android年份选择器(超简单-可直接复制使用)

效果图 思路 1、流程&#xff1a; 通过点击textview触发年份选择器dialog显示&#xff0c;选中年份后&#xff0c;更新到textview。 2、如何只显示年份&#xff1f; 隐藏月份和天数即可&#xff08;但仍需给一个初始化数据&#xff09;。 实现 1、直接新建一个工具类OnPickY…

实时天气预警信息API:全面提供各种天气灾害预警

前言 随着气候变化的不断加剧&#xff0c;天气灾害成为大家关注的焦点。人们对于天气信息的获取需求越来越大&#xff0c;特别是在天气灾害发生时&#xff0c;及时、准确的天气预警信息能够极大地帮助人们做好防范准备&#xff0c;减少灾害带来的损失。为了满足这一需求&#…

去掉乘法运算的加法移位神经网络架构

[CVPR 2020] AdderNet: Do We Really Need Multiplications in Deep Learning? 代码&#xff1a;https://github.com/huawei-noah/AdderNet/tree/master 核心贡献 用filter与input feature之间的L1-范数距离作为“卷积层”的输出为了提升模型性能&#xff0c;提出全精度梯度…

鸿蒙 - arkTs:快速开始

index.ets文件理解&#xff1a; 新建模拟器&#xff1a; 1. 找到并打开设备管理器 2. 点击新建模拟器 3. 选择硬件之后下一步 4. 选择系统镜像&#xff0c;没有安装的话需要先安装 5. 设置模拟设备名称并点击完成 6. 提示创建成功代表刚才创建的模拟设备可以进行使用了…

【实战】如何在Docker Image中轻松运行MySQL

定义 使用Docker运行MySQL有许多优势。它允许数据库程序和数据分离&#xff0c;增强了数据的安全性和可靠性。Docker Image的轻便性简化了MySQL的部署和迁移&#xff0c;而Docker的资源隔离功能确保了应用程序之间无冲突。结合中间件和容器化系统&#xff0c;Docker为MySQL提供…

CentOs7.x安装部署SeaTunnelWeb遇到的坑

CentOs7.x安装部署SeaTunnelWeb遇到的坑 文章目录 1. 环境2. SeaTunnel安装部署2.1下载安装包2.2 设置环境变量2.3 安装连接器插件2.4 拷贝jar包到lib下2.5 启动命令2.6 执行官方client提交任务demo 3. SeaTunnel-Web安装部署3.1 下载安装包3.2 初始化数据库脚本或修改配置appl…

Springboot数据校验与异常篇

一、异常处理 1.1Http状态码 HTTP状态码是指在HTTP通信过程中&#xff0c;服务器向客户端返回的响应状态。它通过3位数字构成&#xff0c;第一个数字定义了响应的类别&#xff0c;后两位数字没有具体分类作用。以下是常见的HTTP状态码及其含义&#xff1a; - 1xx&#xff08;信…

基于ssm同学录网站论文

同学录网站 摘要 本文介绍了同学录网站的开发全过程。通过分析企业对于同学录网站的需求&#xff0c;创建了一个计算机管理同学录网站的方案。文章介绍了同学录网站的系统分析部分&#xff0c;包括可行性分析等&#xff0c;系统设计部分主要介绍了系统功能设计和数据库设计。…

【学习笔记】部署yolov8到安卓手机

一、环境配置和源码安装 首先你需要配置好pytorch环境&#xff0c;本文不再详细阐述&#xff0c;若未配置好环境&#xff0c;可以参考我的另一篇博客&#xff1a;https://blog.csdn.net/liujiahao123987/article/details/128743017 yolov8的安装可参考&#xff1a;https://blog…