97.邻居发现协议(NDP) 大白话:IPv6 没有 ARP,改用 NDP。它一个协议干好几件事:找邻居 MAC、找路由器、自动配地址、检测地址冲突、检测邻居是否还活着。全靠5种 ICMPv6 报文实现。 🟡 模拟演示:<?php// NDP 的 5 种 ICMPv6 报文类型(这是真实的类型号)$ndpTypes=[133=>['RS 路由器请求','主机问:有路由器吗?'],134=>['RA 路由器通告','路由器答:我在,前缀是xx'],135=>['NS 邻居请求','问:这个IPv6的MAC是?(替代ARP)'],136=>['NA 邻居通告','答:我的MAC是xx'],137=>['Redirect 重定向','路由器说:有更优的下一跳'],];echo"NDP 基于 ICMPv6,五种报文各司其职:\n";foreach($ndpTypes as $type=>[$name,$desc]){printf(" 类型%d %-16s | %s\n",$type,$name,$desc);}echo"NDP = IPv4的(ARP + 路由器发现 + ICMP重定向) 三合一\n";---98.路由器请求(RS) 大白话:主机刚接入网络,等不及路由器定期广播,就主动发一个 RS(类型133)问:「网络里有路由器吗?谁来告诉我配置?」催路由器赶紧回个 RA。 🟡 模拟演示:<?php// 主机发送 RS,催路由器应答functionbuildRouterSolicitation(string $srcAddr,?string $srcMac):array{return['ICMPv6类型'=>'133 (Router Solicitation)','源地址'=>$srcAddr.' (未配置时用 :: 未指定地址)','目的地址'=>'ff02::2 (所有路由器的组播地址)','选项'=>$srcMac?"源链路层地址=$srcMac":'(无,因源地址未指定)','目的'=>'催促路由器立即发送RA,不必等周期性通告',];}echo"主机接入网络,发送 RS:\n";foreach(buildRouterSolicitation('fe80::21f:5bff:fe12:3456','00:1f:5b:12:34:56')as $k=>$v){printf(" %-11s: %s\n",$k,$v);}---99.路由器通告(RA) 大白话:路由器定期(或被 RS 触发)广播 RA(类型134),告诉全网段:「我是默认网关,网络前缀是2001:db8::/64,MTU 是多少,你们可以用 SLAAC 自己配地址」。这是 IPv6 自动配置的核心。 🟡 模拟演示:<?php// 路由器发送 RA,携带关键配置信息functionbuildRouterAdvertisement():array{return['ICMPv6类型'=>'134 (Router Advertisement)','目的地址'=>'ff02::1 (所有节点组播)','跳数限制'=>'64 (告诉主机默认TTL)','M标志'=>'0 (是否用DHCPv6取地址)','O标志'=>'0 (是否用DHCPv6取其他配置)','路由器生存期'=>'1800秒 (我作为默认网关的有效期)','前缀信息选项'=>'2001:db8::/64 (拿来做SLAAC)','MTU选项'=>'1500','源链路层地址'=>'00:gw:gw:gw:gw:01 (网关MAC)',];}echo"路由器周期性发送 RA:\n";foreach(buildRouterAdvertisement()as $k=>$v)printf(" %-13s: %s\n",$k,$v);---100.邻居请求(NS) 大白话:IPv6 版的「ARP 请求」。想知道某个 IPv6 地址的 MAC,就发 NS(类型135)。但它不广播,而是发给「被请求节点组播地址」,只惊扰相关主机,比 ARP 广播更省。 🟡 模拟演示+🟢 真实计算(被请求节点组播地址是真算法):<?php// NS:替代ARP。目的地址用"被请求节点组播地址"(精准,不广播)functionsolicitedNodeMulticast(string $targetIp):string{// 真实算法:ff02::1:ff + 目标地址的后24位$bin=inet_pton($targetIp);$last24=substr($bin,-3);// 后3字节$multicast=inet_pton('ff02::1:ff00:0');$multicast=substr($multicast,0,13).$last24;// 拼接returninet_ntop($multicast);}$target='2001:db8::21f:5bff:fe12:3456';echo"想解析 $target 的MAC,发送 NS:\n";echo" ICMPv6类型: 135 (Neighbor Solicitation)\n";echo" 目的地址: ".solicitedNodeMulticast($target)." (被请求节点组播,只扰相关主机)\n";echo" 对比ARP: ARP是广播全网段,NS只发给特定组播组,更高效\n";---101.邻居通告(NA) 大白话:IPv6 版的「ARP 应答」。收到 NS 后,目标主机回 NA(类型136),报上自己的 MAC。免费 NA(主动发)还能通知别人「我的 MAC 变了」。 🟡 模拟演示:<?php// NA:应答NS,报告自己的MACfunctionbuildNeighborAdvertisement(string $myIp,string $myMac,bool $isResponse):array{return['ICMPv6类型'=>'136 (Neighbor Advertisement)','R标志(路由器)'=>'0 (我是主机不是路由器)','S标志(被请求)'=>$isResponse?'1 (这是对NS的应答)':'0 (主动通告)','O标志(覆盖)'=>'1 (要求对方覆盖旧缓存)','目标地址'=>$myIp,'目标链路层地址'=>"$myMac (这就是答案)",];}echo"收到NS,回复NA:\n";foreach(buildNeighborAdvertisement('2001:db8::1','00:1f:5b:12:34:56',true)as $k=>$v){printf(" %-15s: %s\n",$k,$v);}---102.重定向消息 大白话:IPv6 版「ICMP 重定向」。路由器发现你发包绕远了,本网段有更近的下一跳,就发重定向(类型137)告诉你:「下次直接发给那个邻居,别经过我」。优化路径。 🟡 模拟演示:<?php// 路由器发现更优路径,发重定向引导主机class Ipv6Redirect{public functioncheck(string $dst,string $currentNextHop,string $betterNextHop):void{echo"主机把发往 $dst 的包,先发给了路由器 $currentNextHop\n";if($betterNextHop!==$currentNextHop){echo" 路由器发现:同网段有更优下一跳 $betterNextHop\n";echo" 发送 Redirect(类型137):\n";echo" 目标地址 = $dst\n";echo" 更优下一跳 = $betterNextHop\n";echo" 主机更新路由,以后直接发给 $betterNextHop\n";}}}(newIpv6Redirect())->check('2001:db8::100','fe80::gw','fe80::better');---103.邻居不可达检测(NUD) 大白话:缓存里的邻居可能突然下线/换了网卡。NUD 持续跟踪每个邻居的「可达状态」(5个状态机),发现没反应就发 NS 探测,确认死了就删,保证缓存可靠。 🟡 模拟演示:<?php// NUD 邻居状态机class NudStateMachine{// 5个状态(这是真实的NDP状态)constSTATES=['INCOMPLETE'=>'正在解析,已发NS等NA','REACHABLE'=>'近期确认可达(最健康)','STALE'=>'可达性过期,但还没用到','DELAY'=>'刚用到STALE条目,等一会再探测','PROBE'=>'正在主动发NS探测',];public functiontransition(string $event):void{$flow=['发送NS解析'=>'INCOMPLETE','收到NA确认'=>'REACHABLE','可达计时器到期'=>'STALE','有数据要发给它'=>'DELAY -> PROBE(发NS探测)','探测无应答'=>'删除条目(邻居不可达)',];echo"事件[$event] -> 状态: {$flow[$event]}\n";}}$nud=newNudStateMachine();foreach(['发送NS解析','收到NA确认','可达计时器到期','有数据要发给它','探测无应答']as $e){$nud->transition($e);}---104.重复地址检测(DAD) 大白话:主机配一个新 IPv6 地址前,先发 NS 问「这地址有人用了吗?」如果有人回 NA,说明撞了,这地址不能用。没人回才敢用。防止地址冲突。 🟡 模拟演示:<?php// DAD:启用新地址前先检测是否冲突class DuplicateAddressDetection{public function__construct(private array $addressesInUse){}public functioncheck(string $tentativeAddr):bool{echo"准备启用地址 $tentativeAddr,先做DAD检测\n";echo" 发送NS(源地址=::, 目标=$tentativeAddr)\n";if(in_array($tentativeAddr,$this->addressesInUse)){echo" ✗ 收到NA应答! 地址已被占用,DAD失败,不能使用\n";returnfalse;}echo" ✓ 无人应答,地址唯一,DAD通过,正式启用\n";returntrue;}}$dad=newDuplicateAddressDetection(['fe80::1','2001:db8::5']);$dad->check('2001:db8::5');// 冲突$dad->check('2001:db8::99');// 通过---105.无状态地址自动配置(SLAAC) 大白话:IPv6 不用 DHCP 也能自动配地址。流程:①从MAC 算出链路本地地址 ②做DAD 确认不冲突 ③发RS 收 RA 拿到网络前缀 ④前缀+接口标识符=完整全球地址。全程无需服务器。 🟢 真实可运行(EUI-64计算是真实算法)+🟡流程模拟:<?php// SLAAC 完整流程class Slaac{// 真实算法:MAC -> EUI-64 接口标识符private functionmacToEui64(string $mac):string{$bytes=array_map('hexdec',explode(':',$mac));$bytes[0]^=0x02;// 翻转U/L位$eui=array_merge(array_slice($bytes,0,3),[0xFF,0xFE],array_slice($bytes,3));$hex=implode('',array_map(fn($b)=>sprintf('%02x',$b),$eui));returnimplode(':',str_split($hex,4));}public functionautoConfig(string $mac,string $prefix):void{echo"=== SLAAC 自动配置 (MAC: $mac) ===\n";$iid=$this->macToEui64($mac);echo"1. 生成接口标识符(EUI-64): $iid\n";$linkLocal="fe80::$iid";echo"2. 拼成链路本地地址: $linkLocal\n";echo"3. 对其做DAD检测...通过\n";echo"4. 发RS -> 收RA,拿到前缀: $prefix\n";$global=str_replace('::',":$iid",rtrim($prefix,'/64'));echo"5. 前缀+接口ID = 全球地址: {$prefix}里的网络 + $iid\n";echo" 最终: ".substr($prefix,0,strpos($prefix,'/'))."$iid\n";echo"全程无需DHCP服务器!\n";}}(newSlaac())->autoConfig('00:1f:5b:12:34:56','2001:db8::/64');---106.前缀发现 大白话:主机怎么知道本网段用哪个 IPv6 前缀?靠 RA 里的「前缀信息选项」。路由器在 RA 中告知前缀(如2001:db8::/64)、有效期,主机据此判断哪些地址是「本地直达」,哪些要走网关。 🟡 模拟演示:<?php// 从RA的前缀信息选项中学习网络前缀class PrefixDiscovery{private array $prefixList=[];public functionlearnFromRA(array $prefixOption):void{echo"从RA收到前缀信息选项:\n";printf(" 前缀: %s/%d\n",$prefixOption['prefix'],$prefixOption['len']);printf(" L标志(链路上): %d (该前缀的地址本网段直达,不用网关)\n",$prefixOption['L']);printf(" A标志(自治): %d (可用此前缀做SLAAC)\n",$prefixOption['A']);printf(" 有效生存期: %d秒\n",$prefixOption['valid']);$this->prefixList[]=$prefixOption['prefix'].'/'.$prefixOption['len'];}public functionisOnLink(string $dst):void{foreach($this->prefixList as $p){[$net]=explode('/',$p);$netPrefix=substr($net,0,strrpos($net,':'));if(str_starts_with($dst,rtrim($netPrefix,':'))){echo" $dst 匹配前缀 $p -> 本网段直达\n";return;}}echo" $dst 不在已知前缀内 -> 走默认网关\n";}}$pd=newPrefixDiscovery();$pd->learnFromRA(['prefix'=>'2001:db8::','len'=>64,'L'=>1,'A'=>1,'valid'=>2592000]);$pd->isOnLink('2001:db8::100');// 直达$pd->isOnLink('2001:dead::1');// 走网关---🟢 附:真实查看本机 IPv6 邻居表<?php// 真实读取本机 IPv6 邻居缓存(相当于IPv6版的arp -a)functionreadIpv6Neighbors():void{// Linux: ip -6 neigh;Mac: ndp -a$output=shell_exec('ip -6 neigh show 2>/dev/null')?:shell_exec('ndp -a -n 2>/dev/null')?:'';if(trim($output)===''){echo"(无IPv6邻居或命令不可用)\n";return;}echo"本机真实 IPv6 邻居表:\n";foreach(explode("\n",trim($output))as $line){if(trim($line))echo" $line\n";}// 状态字段就是 NUD 的状态:REACHABLE/STALE/DELAY/PROBE...}readIpv6Neighbors();---本章核心串讲 ┌───────────┬─────────┬─────────────────────┬──────────────────┐ │ 报文/机制 │ 类型号 │ 干什么 │ 对应 IPv4 │ ├───────────┼─────────┼─────────────────────┼──────────────────┤ │ RS │133│ 主机催路由器 │ (无) │ ├───────────┼─────────┼─────────────────────┼──────────────────┤ │ RA │134│ 路由器告知前缀/网关 │ ICMP 路由器通告 │ ├───────────┼─────────┼─────────────────────┼──────────────────┤ │ NS │135│ 问邻居 MAC │ ARP 请求 │ ├───────────┼─────────┼─────────────────────┼──────────────────┤ │ NA │136│ 答 MAC │ ARP 应答 │ ├───────────┼─────────┼─────────────────────┼──────────────────┤ │ Redirect │137│ 告知更优下一跳 │ ICMP 重定向 │ ├───────────┼─────────┼─────────────────────┼──────────────────┤ │ NUD │ — │ 跟踪邻居死活 │ (ARP 无此机制) │ ├───────────┼─────────┼─────────────────────┼──────────────────┤ │ DAD │ 用 NS │ 防地址冲突 │ 免费 ARP │ ├───────────┼─────────┼─────────────────────┼──────────────────┤ │ SLAAC │ RS+RA │ 无服务器自动配地址 │ DHCP(但更轻) │ ├───────────┼─────────┼─────────────────────┼──────────────────┤ │ 前缀发现 │ RA 选项 │ 学习本网段前缀 │ 子网掩码 │ └───────────┴─────────┴─────────────────────┴──────────────────┘ 贯穿全章的主线:NDP 把 IPv4 里散落的 ARP、ICMP 重定向、路由器发现整合进 ICMPv6,并新增了 NUD(邻居健康检查)和 DAD(防冲突),让 IPv6 能不依赖任何服务器就自动组网——这就是SLAAC 的威力。