TCP/ip详解=ARP:地址解析协议

TCP/ip详解=ARP:地址解析协议 85. 地址解析协议ARP 大白话你知道对方的 IP但局域网里真正发数据要用 MAC 地址。ARP 就是「拿 IP 换 MAC」的翻译官——在本网段喊一嗓子「谁是这个IP」对方把 MAC 报上来。 模拟演示?php // ARP 核心职责:IP -MAC 的解析 class Arp{// 模拟本网段的IP-MAC真实对应(现实中靠广播问出来)private array$network[192.168.1.100:11:22:33:44:01, // 网关192.168.1.1000:11:22:33:44:0a,];publicfunctionresolve(string$ip): ?string{echo我要给$ip发数据,但只有IP没有MAC\n;$mac$this-network[$ip]?? null;echo$mac? ARP解析到 MAC:$mac\n: 无人应答,该IP不在本网段\n;return$mac;}}(new Arp())-resolve(192.168.1.1);---86. ARP 高速缓存 大白话每次都广播问太慢所以解析过的 IP→MAC会存进缓存表下次直接查。每台电脑都有这张表arp-a能看到。 模拟演示?php // 带缓存的 ARP:先查缓存,没有才广播 class ArpCache{private array$cache[];//ip[mac..,time..]publicfunctionlookup(string$ip): ?string{if(isset($this-cache[$ip])){echo缓存命中$ip- {$this-cache[$ip][mac]} (不用广播)\n;return$this-cache[$ip][mac];}echo缓存未命中$ip- 需要发ARP广播\n;returnnull;}publicfunctionstore(string$ip, string$mac, int$now): void{$this-cache[$ip][mac$mac,time$now];echo写入缓存:$ip-$mac\n;}}$cnew ArpCache();$c-lookup(192.168.1.1);// 未命中$c-store(192.168.1.1,00:11:22:33:44:01,1000);$c-lookup(192.168.1.1);// 命中 ---87. ARP 请求 大白话缓存没有时主机用广播目的 MAC 全 ff问全网段「IP 是192.168.1.1 的请告诉我你的 MAC」所有设备都收到但只有那个 IP 的主人会回。 模拟演示?php // 构造 ARP 请求(广播发出)functionbuildArpRequest(string$senderIp, string$senderMac, string$targetIp): array{return[以太网目的MACff:ff:ff:ff:ff:ff (广播,全网段都收),以太网源MAC$senderMac,操作码 op1 (request 请求),发送方IP$senderIp,发送方MAC$senderMac,目标IP$targetIp,目标MAC00:00:00:00:00:00 (未知,正是要问的),];}echoARP请求(广播): 谁是 192.168.1.1?\n;foreach(buildArpRequest(192.168.1.5,aa:bb:cc:00:00:05,192.168.1.1)as$k$v){printf( %-14s: %s\n,$k,$v);}---88. ARP 应答 大白话被问到的那台主机用单播直接回给提问者说「我就是192.168.1.1我的 MAC 是 xx」。提问方收到后存进缓存。 模拟演示?php // 收到请求 -若问的是我,就单播应答functionbuildArpReply(string$myIp, string$myMac, string$askerIp, string$askerMac): array{return[以太网目的MAC$askerMac(单播,只回给提问者),以太网源MAC$myMac,操作码 op2 (reply 应答),发送方IP$myIp,发送方MAC$myMac, // 这就是答案目标IP$askerIp,目标MAC$askerMac,];}echoARP应答(单播): 我是192.168.1.1, MAC是 00:11:22:33:44:01\n;foreach(buildArpReply(192.168.1.1,00:11:22:33:44:01,192.168.1.5,aa:bb:cc:00:00:05)as$k$v){printf( %-14s: %s\n,$k,$v);}---89. ARP 分组格式 大白话ARP 包字段固定28字节硬件类型、协议类型、地址长度、操作码、双方的 MAC 和 IP。理解了字段就能解析真实抓到的 ARP 包。 真实可运行用 unpack 解析真实 ARP 包二进制?php // 解析一个真实的 ARP 请求字节流(28字节)// 这是真实抓包能得到的十六进制 ——用 unpack 是真解析$arpHex0001.// 硬件类型:1以太网0800.// 协议类型:0x0800IPv406.// 硬件地址长度:6(MAC)04.// 协议地址长度:4(IPv4)0001.// 操作码:1请求aabbcc000005.// 发送方MACc0a80105.// 发送方IP:192.168.1.5000000000000.// 目标MAC(未知)c0a80101;// 目标IP:192.168.1.1$binhex2bin($arpHex);$arpunpack(nhtype/nptype/Chlen/Cplen/nop/H12smac/Nsip/H12tmac/Ntip,$bin);printf(硬件类型: %d (%s)\n,$arp[htype],$arp[htype]1?以太网:其他);printf(协议类型: 0x%04X (%s)\n,$arp[ptype],$arp[ptype]0x0800 ?IPv4:其他);printf(操作码: %d (%s)\n,$arp[op],$arp[op]1?请求:应答);printf(发送方: %s / %s\n, chunk_split($arp[smac],2,:), long2ip($arp[sip]));printf(目标: %s / %s\n, chunk_split($arp[tmac],2,:), long2ip($arp[tip]));---90. ARP 代理 大白话路由器「替别人答应」。当你问的 IP 不在本网段但路由器知道怎么到它路由器就用自己的 MAC 回复你假装「我就是它」让你把包发给它再转发。 模拟演示?php // 代理ARP:路由器替不在本网段的主机应答 class ProxyArpRouter{publicfunction__construct(public string$routerMac, private array$reachable){}publicfunctionhandleRequest(string$targetIp, string$localSubnet): void{if(str_starts_with($targetIp,$localSubnet)){echo$targetIp在本网段 - 路由器不插手,由主机自己应答\n;}elseif(in_array($targetIp,$this-reachable)){// 目标在别的网段,但我能到达 -我替它答echo$targetIp在其他网段但我可达 - 代理ARP\n;echo 路由器用自己的MAC {$this-routerMac} 应答,数据先发给我再转发\n;}}}$rnew ProxyArpRouter(00:gw:gw:gw:gw:01,[10.0.0.5]);$r-handleRequest(192.168.1.20,192.168.1);// 本网段$r-handleRequest(10.0.0.5,192.168.1);// 跨网段,代理 ---91. 免费 ARPGratuitous ARP 大白话主机主动广播一条「我是 X.X.X.XMAC 是 yy」没人问也发。用途①检测IP 冲突有人回应说明撞了②通知全网更新缓存比如主备切换换了网卡。 模拟演示?php // 免费ARP:目标IP填自己,主动广播functiongratuitousArp(string$myIp, string$myMac): array{return[操作码1或2均可,发送方IP$myIp,发送方MAC$myMac,目标IP$myIp(填自己! 这是关键特征),目标MACff:ff:ff:ff:ff:ff (广播),用途1IP冲突检测:若有主机回应 - IP已被占用,用途2主备切换:广播让全网刷新 IP-新MAC 的缓存,];}echo发送免费ARP宣告: 我是192.168.1.100\n;foreach(gratuitousArp(192.168.1.100,00:aa:bb:cc:dd:ee)as$k$v){printf( %-9s: %s\n,$k,$v);}---92. arp 命令 大白话操作系统自带的查 / 改 ARP 表的命令。arp-a看缓存arp-d删条目arp-s加静态。 真实可运行PHP 调系统 arp 命令读真实缓存表?php // 真实读取本机 ARP 缓存表并解析functionreadArpTable(): array{// Linux/Mac 用 arp -a;Windows 也是 arp-a$outputshell_exec(arp -a 2/dev/null)??;$entries[];foreach(explode(\n, trim($output))as$line){// 匹配形如(192.168.1.1)at 00:11:22:33:44:01 的行if(preg_match(/\(?([\d.])\)?\s.*?([0-9a-f:]{17})/i,$line,$m)){$entries[][ip$m[1],mac$m[2]];}}return$entries;}echo本机真实 ARP 缓存表:\n;foreach(readArpTable()as$e){printf( %-15s - %s\n,$e[ip],$e[mac]);}echo\n常用命令: arp -a(查) arp -d ip(删) arp -s ip mac(加静态)\n;---93. ARP 超时 大白话ARP 缓存的条目不会永久有效每条有个生存时间一般几分钟。到期前若一直在用会续命没用就等着被删保证 MAC 变了能及时更新。 模拟演示?php // 带超时的 ARP 缓存条目 class ArpEntry{const TTL1200;// 默认1200秒(20分钟,各系统不同)publicfunction__construct(public string$mac, public int$createdAt){}publicfunctionisExpired(int$now): bool{return($now-$this-createdAt)self::TTL;}publicfunctionremainingTtl(int$now): int{returnmax(0, self::TTL -($now-$this-createdAt));}}$entrynew ArpEntry(00:11:22:33:44:01,1000);echo创建于 t1000, TTL.ArpEntry::TTL.秒\n;echot1500 剩余: .$entry-remainingTtl(1500).秒, 过期? .($entry-isExpired(1500)?是:否).\n;echot2500 剩余: .$entry-remainingTtl(2500).秒, 过期? .($entry-isExpired(2500)?是:否).\n;---94. ARP 缓存老化 大白话系统定期扫描缓存把过期的条目清掉老化。有的实现还分「完整态/不完整态」长时间不用先标记再删。目的是让缓 存始终新鲜。 模拟演示?php // 缓存老化:周期性清理过期条目 class AgingArpCache{private array$cache[];//ipArpEntry const TTL1200;publicfunctionadd(string$ip, string$mac, int$now): void{$this-cache[$ip][mac$mac,time$now];}publicfunctionage(int$now): void{// 老化扫描echo[t$now] 执行老化扫描:\n;foreach($this-cache as$ip$e){$age$now-$e[time];if($ageself::TTL){unset($this-cache[$ip]);echo 删除$ip(已存活 {$age}s, 超过TTL)\n;}else{echo 保留$ip(存活 {$age}s)\n;}}}}$cnew AgingArpCache();$c-add(192.168.1.1,00:11:..,0);$c-add(192.168.1.2,00:22:..,1000);$c-age(1300);//1.1过期(1300s),1.2保留(300s)---95. RARP 逆地址解析协议 大白话ARP 的反向——已知自己的MAC想问「我的 IP 是多少」。早期无盘工作站开机时用它从服务器要 IP。现在基本被 DHCP/BOOTP 取代。 模拟演示?php // RARP:用MAC问IP(无盘机开机自举用,现已被DHCP取代)class RarpServer{private array$macToIp[00:11:22:33:44:0a192.168.1.10,00:11:22:33:44:0b192.168.1.11,];publicfunctionhandle(string$myMac): void{echo无盘工作站广播 RARP请求: 我的MAC是$myMac,我的IP是多少?\n;$ip$this-macToIp[$myMac]?? null;echo$ip? RARP服务器应答: 你的IP是$ip\n: 无配置,无法分配\n;}}(new RarpServer())-handle(00:11:22:33:44:0a);echo对比: ARP是 IP-MAC, RARP是 MAC-IP\n;echo现状: RARP功能弱,已被 BOOTP/DHCP 全面取代\n;---96. ARP 攻击与欺骗 大白话ARP 没有任何认证谁都能伪造应答。攻击者狂发假 ARP「网关 IP 的 MAC 是我」骗局域网把流量发给他实现中间人监听/断网。防御靠静态绑定、检测异常。 真实可运行检测逻辑抓本机 ARP 表找一个 MAC 对应多个 IP 的异常?php // ARP欺骗原理(模拟) 真实检测逻辑echo 欺骗原理(模拟) \n;class ArpSpoofDemo{private array$victimCache[];publicfunctionpoison(string$ip, string$fakeMac, string$attacker): void{$this-victimCache[$ip]$fakeMac;// 受害者被写入假MACecho攻击者($attacker)伪造:$ip-$fakeMac\n;echo 受害者把发往$ip的流量,实际发给了攻击者(中间人)\n;}}(new ArpSpoofDemo())-poison(192.168.1.1,de:ad:be:ef:00:01,黑客);echo\n 真实检测:一个MAC绑了多个IP 可疑 \n;functiondetectArpSpoof(): void{$outputshell_exec(arp -a 2/dev/null)??;$macToIps[];foreach(explode(\n,$output)as$line){if(preg_match(/\(?([\d.])\)?\s.*?([0-9a-f:]{17})/i,$line,$m)){$macToIps[$m[2]][]$m[1];}}foreach($macToIpsas$mac$ips){if(count(array_unique($ips))1){echo⚠可疑! MAC$mac对应多个IP: .implode(, ,$ips). (可能ARP欺骗)\n;}}echo检测完成。防御: 静态ARP绑定 / DAI / arpwatch\n;}detectArpSpoof();--- 本章核心串讲 ┌───────────┬────────────────────────────┬─────────────────────────┐ │ 概念 │ 一句话 │ 代码类型 │ ├───────────┼────────────────────────────┼─────────────────────────┤ │ ARP 本质 │ 局域网内 IP→MAC翻译 │ 解析器(85)│ ├───────────┼────────────────────────────┼─────────────────────────┤ │ 缓存机制 │ 存起来不重复问会超时老化 │ 缓存类(86、93、94)│ ├───────────┼────────────────────────────┼─────────────────────────┤ │ 请求/应答 │ 广播问、单播答 │ 帧构造(87、88)│ ├───────────┼────────────────────────────┼─────────────────────────┤ │ 包格式 │28字节固定结构 │ 真实 unpack(89)│ ├───────────┼────────────────────────────┼─────────────────────────┤ │ 特殊用法 │ 代理 ARP、免费 ARP │ (90、91)│ ├───────────┼────────────────────────────┼─────────────────────────┤ │ 运维 │ arp 命令查改表 │ 真实 shell_exec(92)│ ├───────────┼────────────────────────────┼─────────────────────────┤ │ 反向/安全 │ RARP、ARP 欺骗与检测 │ 真实检测(95、96)│ └───────────┴────────────────────────────┴─────────────────────────┘ 贯穿全章的关键ARP 是「广播问、单播答、缓存存」三步走且完全无认证——这既让它简单高效也埋下了ARP 欺骗这个安全大坑。