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

目录

    • 一、参考链接
    • 二、学习目标
    • 三、代码解析
      • 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;             // 套接字描述符初始化为-1int 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);  // 若设置失败,关闭套接字并返回-1return -1;}// 设置套接字接收缓冲区的大小ret = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));if (ret < 0) {close(sock);  // 若设置失败,关闭套接字并返回-1return -1;}// 启用对特定IPv6多播消息的监听val = 1;ret = setsockopt(sock, IPPROTO_IPV6, IPV6_MDMAC, &val, sizeof(val));if (ret < 0) {close(sock);  // 若设置失败,关闭套接字并返回-1return -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)  // 如果读取失败或无数据可读,则返回-1return -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_PACKETaddr.sll_protocol = htons(ETH_P_IPV6); // 设置协议类型为IPv6addr.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));// 检查下一个头部是否为ICMPv6if (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.hqwc.cn/news/288589.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

VCP-DCV VMware vSphere,12月23日即将开课~想了解点击查看

VCP-DCV VMware vSphere 本周开课~ 想报名的必须提前预约啦 &#x1f447;&#x1f447;&#x1f447; 课程介绍 本课程重点讲授如何安装、配置和管理VMware vSphere 8.0&#xff08;包括VMware ESXi™ 8.0和VMware vCenter Server™ 8.0&#xff09; 本课程将帮助您做好为任…

【项目管理】redmine

Redmine是用Ruby开发的基于web的项目管理软件&#xff0c;是用ROR框架开发的一套跨平台项目管理系统&#xff0c;据说是源于Basecamp的ror版而来&#xff0c;支持多种数据库&#xff0c;有不少自己独特的功能&#xff0c;例如提供wiki、新闻台等&#xff0c;还可以集成其他版本…

故障排查:shell脚本输出乱码

博客主页&#xff1a;https://tomcat.blog.csdn.net 博主昵称&#xff1a;农民工老王 主要领域&#xff1a;Java、Linux、K8S 期待大家的关注&#x1f496;点赞&#x1f44d;收藏⭐留言&#x1f4ac; 目录 故障详情故障原因解决方法iconv命令介绍 故障详情 最近的工作中遇到一…

【每日一题】【12.20】2828.判别首字母缩略词

&#x1f525;博客主页&#xff1a; A_SHOWY&#x1f3a5;系列专栏&#xff1a;力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 1.题目链接 2828. 判别首字母缩略词https://leetcode.cn/problems/check-if-a-string-is-an-acronym-of-words/ 2.题目描述 今天…

【动态规划】08路径问题_下降路径最小和_C++(medium)

题目链接&#xff1a;leetcode下降路径最小和 目录 题目解析&#xff1a; 算法原理 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值 编写代码 题目解析&#xff1a; 题目让我们求通过 matrix 的下降路径 的 最小和 由题可得&#xff1a; 在下一行选择的元…

Python 操作mysql实现事务处理

一、应用场景 Python项目对MySQL数据库进行增、删、改操作时&#xff0c;有时会出现执行sql异常的情况。在批量提交数据的时候&#xff0c;如果其中一个事务提交错误&#xff0c;往往导致预期的整个数据链不完整。 例如银行转账数据&#xff0c;用户A向用户B转账&#xff1a; …

Navicat16的下载与安装

Navicat16的下载与安装 1、官网下载地址&#xff1a;https://www.navicat.com.cn/download/navicat-premium 当然有的朋友在官网下载比较慢&#xff0c;我也为大家准备好了百度网盘链接 链接&#xff1a;https://pan.baidu.com/s/1dUcTSHr3761Oayh0-WfolA?pwdwfpl 提取码&am…

【Java】常用的时间类API

目录 Date(时间和日期) SimpleDateFormat&#xff08;解析字符串时间成为时间对象&#xff09; Calendar(系统此刻时间对应的日历) LocalDate&#xff08;年、月、日&#xff09; LocalTime&#xff08;时、分、秒&#xff09; LocalDateTime&#xff08;年、月、日、时、分、秒…

LeetCode 每日一题 Day 17 || 二分

1901. 寻找峰值 II 一个 2D 网格中的 峰值 是指那些 严格大于 其相邻格子(上、下、左、右)的元素。 给你一个 从 0 开始编号 的 m x n 矩阵 mat &#xff0c;其中任意两个相邻格子的值都 不相同 。找出 任意一个 峰值 mat[i][j] 并 返回其位置 [i,j] 。 你可以假设整个矩阵周…

vue 如何实现拖动:vue-draggable

vue-draggable 官方文档&#xff1a;传送门 特点&#xff1a; 支持触摸设备&#xff08;如vue项目的移动端开发Quasar&#xff09;支持拖拽和选择文本支持不同列表之间的拖拽视图模型的同步刷新与vue2的过渡动画&#xff08;transition-group&#xff09;兼容有很多监听函数…

Python MySQL数据库连接实现增删改查

一、应用场景 python项目连接MySQL数据库时&#xff0c;需要第三方库的支持。这篇文章使用的是PyMySQL库&#xff0c;适用于python3.x。 二、安装 pip install PyMySQL三、使用方法 1.导入模块 import pymysql2.连接数据库 db pymysql.connect(hostlocalhost,usercode_s…

甄选的董宇辉,颠覆新东方?

董宇辉又被推向浪尖。 一年前&#xff0c;新东方老师董宇辉出现在东方甄选主播间&#xff0c;用边带货边教英文的方式爆火出圈&#xff0c;成为了东方甄选的活招牌。一年后&#xff0c;一条常规宣发物料引发一场巨大的舆情风波&#xff0c;董宇辉“小作文”事件如闹剧般展开&a…