0%

ipv6邻居发现协议NDP

ICMPv6邻居通告NA(Neighbor Advertisement)消息是IPv6节点对ICMPv6邻居请求NS(Neighbor Solicitation)消息的响应,同时IPv6节点在链路层变化时也可以主动发送NA消息。

开启IPv6功能

开启IPv6功能,网卡会自动获得一个本地的链路的地址fe80开头的,如果没有这个locallink地址,说明没有使能,如果没有使能,在NDP(邻居发现协议)中没法进行 链路数据交换中会存在问题。

image-20250212205720449

报文格式

1
2
3
4
5
6
7
8
9
+0-------7-------15---------------31
| Type | Code | Checksum |
+----------------------------------
|R|S|O| Reserved |
+----------------------------------
| Target Address |
+----------------------------------
| Options... |
+---------------------------------+
字段 长度 含义
Type 1 Byte 消息类型,此处值为136。
Code 1 Byte 该ICMPv6差错报文的始发者必须将该字段置为0,且接收端忽略该字段。
Checksum 2 Byte 用来在ICMPv6报文中检验数据和部分IPv6首部的完整性。
R 1 bit 路由器标记。当置1时,R位指出发送者是路由器。R位由Neighbor Unreachability Detection使用,用于检测改变为主机的路由器。
S 1 bit 请求标记。当置1时,S位指出通告被发送以响应来自目的地地址的Neighbor Solicitation。S位用作Neighbor Unreachability Detection的可达性确认。在多播通告和非请求单播通告中置0。
O 1 bit 替代标记。替代标志,1表示通告中的信息替代缓存,如更新链路层地址时,对于任播的回应则不应置位。在针对任播地址的请求通告中,以及在请求的前缀通告中它不能被置1。在其他请求通告中和在非请求通告中它应当被置1。
Reserved 29 bit 29位未使用字段。它必须由发送者初始化为0,接收者必须忽略它。
Target Address 16 Byte 对于请求的通告,是在Neighbor Solicitation消息(该消息催促这个通告)中的Target Address字段。对于非请求通告,是其链路层地址已经改变的地址。Target Address必须不是多播地址。
Options 可变 选项:Target link-layer address: 目标的链路层地址,即,通告发送者。当响应多播请求时,在有地址的链路层上必须包括此选项。当响应单播Neighbor Solicitation时应当包括此选项。当对端节点由于没有缓存条目从而不能返回一个Neighbor Advertisements消息时,为了避免无休止的Neighbor Solicitation“递归”,对于多播请求必须包括 此选项。当响应单播请求时,可忽略此选项,因为请求的发送者有正确的链路层地址;其他情况,此选项不能在第一位置发送单播请求。然而,在此情况,包括链路层地址仅增加了少许开销,却消除了潜在的竞争条件,那里在收到对先前的请求的响应之前,发送者删除缓存的链路层地址。为TLV格式:Type: =2,字段长度为1字节。Length: 1字节,表示选项的长度(包括类型字段和长度字段),以8字节为单位计算。例如,IEEE802 地址的长度是1。Link-Layer Address: 可变长度的链路层地址。此字段的内容和形式(包括字节和比特顺序)一般由描述IPv6在不同链路层上如何运行的特定文件中规定。
  • 邻居通告报文通常在本地链路范围内使用,因此跳数限制通常设置为255,以防止被路由器转发。根据RFC 4861,NDP报文应该具有255的跳数限制,作为有效性检查的一部分,以防止来自其他网络的欺骗。因此,如果用户将 hop_limit 设置为非255的值,可能会导致接收方忽略该报文,因为不符合协议要求。

配置项

  • /proc/sys/net/ipv6/conf目录下,有一些可选的配置项目,可以在sysctl.conf中配置相关的项。

image-20250212205111567

举例:

net.ipv6.conf.default.drop_unsolicited_na = 1 丢弃掉未经NS请求的NA报文,增强安全性。

NA报文

(1)当收到NS请求的时候,可以响应NS请求发送NA报文。

(2)当节点链路发生故障的时候,也可以主动发送NA通告。

image-20250212201732289

  • ICMPv6

image-20250212202029649

主动构造NA通告报文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/icmp6.h>
#include <netinet/ip6.h>
#include <netinet/ether.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>

#define IFNAME "eth0" // 修改为你的网络接口
#define HOP_LIMIT 255

// 计算ICMPv6校验和
unsigned short in6_checksum(const struct in6_addr *src,
const struct in6_addr *dst,
unsigned short len,
unsigned char *data) {
uint32_t sum = 0;
uint16_t *w;

// 伪头部
for (w = (uint16_t *)src; w < (uint16_t *)(src + 1); w++)
sum += *w;
for (w = (uint16_t *)dst; w < (uint16_t *)(dst + 1); w++)
sum += *w;
sum += htons(len);
sum += htons(IPPROTO_ICMPV6);

// ICMP数据
for (w = (uint16_t *)data; len > 1; len -= 2, w++)
sum += *w;
if (len == 1)
sum += *(uint8_t *)w;

// 折叠进位
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
return ~sum;
}

int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in6 dst_addr;
unsigned char packet[1024] = {0};
struct nd_neighbor_advert *na;
struct nd_opt_hdr *opt;
int hop_limit = HOP_LIMIT;

// 获取接口MAC地址
struct ifreq ifr;
int fd = socket(AF_INET, SOCK_DGRAM, 0);
strncpy(ifr.ifr_name, IFNAME, IFNAMSIZ);
ioctl(fd, SIOCGIFHWADDR, &ifr);
close(fd);
unsigned char *mac = (unsigned char *)ifr.ifr_hwaddr.sa_data;

// 创建原始套接字
if ((sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6)) < 0)
{
perror("socket");
exit(1);
}

// 设置跳数限制,根据协议规范 RFC 4861 NDP需要设置成255
if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
&(int)hop_limit, sizeof(int)) < 0) {
perror("setsockopt");
close(sock);
exit(1);
}

// 构造目标地址(链路本地所有节点)
memset(&dst_addr, 0, sizeof(dst_addr));
dst_addr.sin6_family = AF_INET6;
inet_pton(AF_INET6, "ff02::1", &dst_addr.sin6_addr); // 多播地址,发送给本地所有的节点
dst_addr.sin6_scope_id = if_nametoindex(IFNAME);

// 构造NA报文
na = (struct nd_neighbor_advert *)packet;
na->nd_na_type = ND_NEIGHBOR_ADVERT;
na->nd_na_code = 0;
na->nd_na_flags_reserved = ND_NA_FLAG_OVERRIDE; // 设置Override标志
na->nd_na_target = in6addr_any; // 需要替换为实际地址

// 添加目标链路层地址选项
opt = (struct nd_opt_hdr *)(packet + sizeof(struct nd_neighbor_advert));
opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
opt->nd_opt_len = 1; // 单位是8字节,1表示8字节长度
memcpy((unsigned char *)(opt + 1), mac, 6);

// 计算校验和
struct in6_addr src_addr;
// 此处应替换为接口的链路本地地址
inet_pton(AF_INET6, "fe80::1234:5678:9abc", &src_addr);
na->nd_na_cksum = in6_checksum(&src_addr, &dst_addr.sin6_addr,
sizeof(*na) + 8, // ICMP头 + 选项长度
(unsigned char *)na);

// 发送报文
if (sendto(sock, packet, sizeof(*na) + 8, 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr)) < 0)
{
perror("sendto");
close(sock);
exit(1);
}

close(sock);
return 0;
}

本地链路地址

  • IPV6地址冲突会DAD检测,出现tentative则说明地址冲突。

image-20250213101227983

  • 本地链路地址刚激活的时候,发现有链路地址,过一段时间后地址消失,执行ifconfig 或者 ip a 都是up状态,执行nmcli dev 命令看网卡不是处于连接的状态,原来是NetworkManager在搞鬼。

(1)开启dhcp,不成功,本地链路地址会掉,查看NetworkManager.service服务日志,报错如下。

image-20250213104053886

(2)关闭网卡配置dhcp功能后,BOOTPROTO=none,重启NetworkManager.service 服务,此问题解决。

image-20250213104149893

(3)如果还不能解决,将网卡的从NetworkManager的管理中移除。修改对应的网卡配置文件ifc-ethxx,在文件末尾添加。

1
2
3
4
NM_CONTROLLED=no

# 重启NM服务,使配置生效。
systemctl restart NetworkManager.service

reference

[1] https://datatracker.ietf.org/doc/html/rfc4291

[2] https://info.support.huawei.com/hedex/api/pages/EDOC1100362944/AZN04029/05/resources/software/nev8r10_vrpv8r16/user/vrp/feature_0003992361.html

[3] https://blog.csdn.net/qq_36428903/article/details/134563965

小主,路过打个赏再走呗~