一台设备已经有 fe80:: 地址,却无法访问互联网;另一台已经有全局地址,却解析不了域名。两者并不矛盾:地址、下一跳、DNS 与路径包大小来自不同机制。理解 IPv6,需要把这些状态逐项连接起来,而不能把“自动配置成功”理解成所有网络能力同时就绪。
| 查阅问题 | 核心判据 |
| 一个包到底多长 | 40 B 基本头加 Payload Length,扩展头也占载荷长度 |
| TCP/UDP 头在哪里 | 沿 Next Header 链解析,不能总取偏移 40 |
| 有地址为何仍不能上网 | 分别核对地址状态、路由、邻居、DNS 与 PMTU |
| 大包由谁拆分 | 源端;中间路由器反馈 Packet Too Big |
| SLAAC 是否必须用 MAC | 不必,稳定不透明 IID 与临时地址均有用途 |
| IPv6 是否自动安全 | 地址空间、IPsec 能力与实际访问控制分别判断 |
地址:先分清表示、范围与接口
IPv6 地址为 128 bit,通常写成八组十六进制。每组前导零可省略,连续全零组可以用一次 :: 压缩;只能出现一次,否则无法确定各处省去了多少组。例如 2001:0db8:0001:0000:0000:0000:0000:000a 与 2001:db8:1::a 表示同一地址。
| 地址/前缀 | 含义 | 不能据此推断的事 |
:: | 未指定地址,可用于特定初始化报文的源地址 | 不能当普通可达主机地址 |
::1 | 本机回环 | 不能转发到另一台设备 |
fe80::/10 | 链路本地单播范围 | 无法跨路由器访问;常见地址构造使用 64 bit IID |
ff00::/8 | 多播,包含作用域信息 | 不是所有多播都只限当前链路 |
| 全局单播地址 | 为跨链路单播通信提供地址 | 有地址不保证路由发布、防火墙放行或服务监听 |
IPv6 没有广播地址,邻居发现等使用相应多播组。任播则从单播地址空间分配,同一地址可在多处提供服务,由路由选择其中一处;“最近”是路由意义,不保证地理最近。RFC 4291:地址架构
fe80::1%eth0 中 %eth0 是本机用来指定作用域接口的标识,不是线上 128 bit 地址的一部分。两张网卡所在链路都可能存在 fe80::1,因此只给地址可能不够。对于普通以太网 SLAAC,/64 前缀与 64 bit IID 配合;这不表示 IPv6 路由表只允许 /64,聚合路由与主机路由仍有各自长度。
基本头:固定的是入口,不是所有头部总长度
| 字段 | 位数 | 读取方式 |
| Version | 4 | 值为 6 |
| Traffic Class | 8 | DSCP 与 ECN 的分类、拥塞语义 |
| Flow Label | 20 | 与源/目的等一起辅助识别流 |
| Payload Length | 16 | 基本头之后的字节数 |
| Next Header | 8 | 下一个扩展头或上层协议编号 |
| Hop Limit | 8 | 每次路由转发递减,耗尽则丢弃 |
| Source / Destination | 各 128 | 源地址与目的地址 |
普通非 jumbogram 包的 Payload Length 最大为 65535,整个包还要加 40 B 基本头。不能照搬 IPv4 Total Length 的含义。Jumbogram 有额外规则,不应把长度字段为零一概当作“任意大包”或解析器免检开关。
IPv6 没有基本头校验和,也没有 IHL;选项通过扩展头表达。TCP/UDP 等仍有自己的校验规则,其中伪首部纳入 IPv6 地址等信息,但校验不提供密码学认证。支持 IPsec 也不意味着每个 IPv6 包已加密或认证。RFC 8200:基本头及上层接口
Flow Label 能做什么
源端为同一流使用一致的非零标签,有助于网络在不深入读取传输端口时做流分类或负载分担;零标签仍有定义。20 bit 空间可能碰撞,设备通常结合源/目的等字段使用,不能拿标签当全网唯一连接 ID。
标签也不自动预留带宽或提高优先级;资源调度需要网络策略。应用、内核版本和配置都会影响标签生成,不能把某个 Linux 默认行为推广成协议要求。RFC 6437:Flow Label
扩展头:必须按类型解码
基本头 Next Header=6 可以直接跟 TCP;若为 0,则先进入 Hop-by-Hop Options,再根据该头的 Next Header 继续。上图的 UDP 从第 48 字节开始,若解析器固定从第 40 字节读端口,就会把扩展头字段当作端口。
| 头类型 | 编号 | 长度解析要点 |
| Hop-by-Hop / Destination Options | 0 / 60 | 总长为 (Hdr Ext Len+1)×8 |
| Routing | 43 | 通用头含长度和路由类型,具体内容按类型解释 |
| Fragment | 44 | 固定 8 B,无通用的 Hdr Ext Len 字段 |
| AH | 51 | 有自己的 Payload Len 规则,单位为 4 B |
| ESP | 50 | 有独立封装格式,Next Header 位于其尾部结构 |
| No Next Header | 59 | 不再跟另一个协议头 |
因此不能对所有扩展头执行同一个“读第二字节、乘八、跳过去”的循环。安全解析还需要检查实际剩余长度、允许的头链和资源上限,遇到无法解释的加密载荷也不能继续猜端口。
RFC 8200 给出推荐排列次序;Hop-by-Hop 若存在,必须紧跟基本头。不同头的处理节点不同,普通转发节点并不需要解释每一种终点选项。扩展头支持情况会影响真实路径可达性,应分别验证端点和中间设备,而非只证明报文字节符合格式。RFC 8200:扩展头
UDP 为什么不总在第40字节?
把一条链看成按类型驱动的游标移动。Options 的第二字节是长度编码,Fragment 同一位置却不能套用这个公式;选择完整首片示例后,仍要扣出 Fragment Header 才能读 UDP。截断在边界附近时,“还没拿到字节”应与“已经读到下一个协议”区分。
正在呈现知识画面
UDP 为什么不总在第40字节?Next Header 决定怎样读长度。Options 与 Fragment 不能共用一种长度公式;完整链之前,固定偏移猜端口会读错对象。
{"id":"ipv6-header-walk","title":"UDP 为什么不总在第40字节?","summary":"Next Header 决定怎样读长度。Options 与 Fragment 不能共用一种长度公式;完整链之前,固定偏移猜端口会读错对象。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"chain\"><button data-mode=\"direct\" data-i18n=\"direct\"></button><button data-mode=\"hop\" data-i18n=\"hop\"></button><button data-mode=\"dest\" data-i18n=\"dest\"></button><button data-mode=\"frag\" data-i18n=\"frag\"></button></div><div class=\"control\"><label for=\"available\" data-i18n=\"available\"></label><output for=\"available\" id=\"available-value\"></output><input id=\"available\" type=\"range\" min=\"0\" max=\"64\" value=\"56\" step=\"1\"></div><div id=\"bytes\" class=\"rangegrid\"></div><section class=\"panel\"><h3 data-i18n=\"trace\"></h3><div id=\"trace\"></div></section><div class=\"metrics\"><div><span data-i18n=\"offset\"></span><strong id=\"offset\"></strong></div><div><span data-i18n=\"status\"></span><strong id=\"status\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#trace{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui)}.metrics strong{font-size:17px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let mode='hop';function packet(){const ext=mode==='direct'?0:mode==='dest'?16:8,bytes=new Uint8Array(48+ext);bytes[0]=0x60;bytes[5]=8+ext;bytes[6]=({direct:17,hop:0,dest:60,frag:44})[mode];if(ext){bytes[40]=17;bytes[41]=mode==='dest'?1:0}bytes[40+ext]=0x04;bytes[41+ext]=0xd2;bytes[42+ext]=0;bytes[43+ext]=53;bytes[45+ext]=8;return bytes}function draw(){const data=packet(),n=Math.min(+$('input').value,data.length);$('#available').max=data.length;set('available-value',n);let pos=0,next=null,ok=false,log='';if(n<40)log='need IPv6[0,40)';else{pos=40;next=data[6];log='IPv6: next='+next;while(next!==17){if(next!==44&&n<pos+2){log+='\\nneed length byte at '+(pos+1);break}const len=next===44?8:(data[pos+1]+1)*8;log+='\\n'+(next===44?'Fragment: 8':'Options: ('+data[pos+1]+'+1)×8')+' = '+len;if(n<pos+len){log+='\\nneed ['+pos+','+(pos+len)+')';break}next=data[pos];pos+=len;}if(next===17){ok=n>=pos+8;log+='\\nUDP ['+pos+','+(pos+8)+')'}}cells('bytes',Array.from({length:16},(_,i)=>({text:(i*4)+'…',cls:i*4>=n?'empty':i*4<40?'':i*4<pos?'gold':'on'})));set('trace',log);set('offset',next===17?pos:'—');set('status',t(ok?'ok':'wait'));$('#status').dataset.ready=String(ok);$('#offset').dataset.offset=next===17?pos:'';pressed(mode);}$('#available').oninput=draw;document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{mode=b.dataset.mode;$('#available').max=packet().length;$('#available').value=packet().length;draw()});draw();","audio":false,"strings":{"available":"当前可用字节","chain":"头链","dest":"Destination → UDP","direct":"直接 UDP","frag":"Fragment → UDP","heading":"UDP 为什么不总在第40字节?","hop":"Hop-by-Hop → UDP","intro":"切换合法头链并截断可用字节,观察解析器按类型读取长度与下一头。","offset":"UDP 起始偏移","ok":"完整 UDP 头","reset":"重置","scope":"只构造无扩展、8B Hop-by-Hop、16B Destination Options、首片Fragment四种链;不处理ESP、AH、未知头或非首片。截断指可用缓冲区不足。","scopeLabel":"模型范围与约定","status":"解析结果","trace":"按真实规则前进","wait":"等待更多字节"}}
分片算例:没有 DF 位,只有源端分片
当转发出口无法容纳当前 IPv6 包时,路由器不做 IPv6 分片,而是丢弃并生成相应 ICMPv6 Packet Too Big(Type 2、Code 0)。把这简称为“DF 永远是 1”会误导字段解析:IPv6 根本没有 DF 位。源端通过 PMTUD 调整发送大小,也可在需要时生成 Fragment Header。RFC 8201:IPv6 PMTUD
下面是独立教学算例:原包 3000 B,含基本头 40 B、可分片部分 2960 B,无其他扩展头;源端已知 PMTU=1280。每片额外需要 8 B 分片头,非末片数据容量为 floor((1280−40−8)/8)×8=1232 B。
| 片 | 数据区间 | Offset | M | Payload Length | 整包长 |
| 1 | 0~1231 | 0 | 1 | 1240 | 1280 |
| 2 | 1232~2463 | 154 | 1 | 1240 | 1280 |
| 3 | 2464~2959 | 308 | 0 | 504 | 544 |
末片的 308×8+496=2960 给出原可分片部分长度。重组还要等前面区间到齐,不能只看 M=0;重叠分片按 IPv6 规则导致整份重组丢弃。若有必须随每片携带的其他头,容量需要重新扣除,不能永久套用 1232。首片还必须包含完整的 IPv6 头链及上层头,不能随意切开到后片。RFC 8200:分片与重组
1280 B 是 IPv6 的最小链路 MTU 要求,较小物理传输单元需要链路下层适配;它不是所有路径的实际最大值。过滤必要 ICMPv6 会破坏大小反馈;TCP MSS 也不能替 UDP 应用解决全部分包问题。
自动配置:四种信息分别建立
先形成可用的链路本地地址,主机可以通过 RS 请求 RA,也可以收到路由器主动多播的 RA。RA 的目的并不总是单播。下面区分最容易混淆的信息:RFC 4861:路由器发现
| 信息 | 主要来源 | 生命周期或条件 |
| 默认路由器 | RA Router Lifetime 等 | 寿命为零不表示 RA 内所有前缀都无效 |
| 前缀在链路上 | PIO 的 L 标志 | 与是否生成地址分开判断 |
| SLAAC 地址 | PIO 的 A 标志、合适前缀及本地 IID | 受 preferred/valid lifetime 约束 |
| DNS 服务器 | RA RDNSS、DHCPv6 或静态配置 | 不由“已有地址”自动推得 |
例如收到 A=1、L=0 的合适 PIO,主机可以形成地址,但不能仅凭这条 PIO 认定整个前缀上的所有目的都可直接做邻居解析。M 标志指示有状态地址配置,O 指示其他配置;O 并非专指 DNS,M 也不是“额外获取 DNS”。是否启用对应客户端还取决于主机实现和管理配置。
地址通常先处于 tentative,通过 DAD 后才能按相应规则使用。Preferred Lifetime 到期后地址 deprecated,通常避免为新通信选它,但在 Valid Lifetime 到期前仍有使用意义;有效期到期后不再作为有效地址。这样,更新前缀可以逐步迁移连接,而非立即切断全部旧地址。RFC 4862:SLAAC
RA 的 RDNSS 选项可直接提供递归 DNS 服务器,因此“SLAAC 必须再跑 DHCPv6 才有 DNS”不成立。反过来,只收到一个地址配置结果也不证明默认路由与 DNS 均已存在。RFC 8106:RA DNS 选项
稳定地址与临时地址各解决什么
IID 不必嵌入 MAC。稳定地址有利于地址持续性;临时地址通过轮换降低跨时间关联。临时地址有 preferred/valid lifetime、提前生成替代地址等约束,具体寿命不能统一写死为 24 小时,更不能认为轮换后所有已建连接立即换源地址。
实际源地址选择还受目的作用域、地址状态、策略和应用绑定影响。临时地址不隐藏应用账号、DNS 查询或其他跟踪信号,也不让入站防火墙失去意义。RFC 8981:临时地址
IPv4 共存:封装和翻译改变的东西不同
| 机制 | 数据路径 | 适合解释的问题 |
| 双栈 | 主机与网络提供两种协议 | 某一地址族故障为何被另一族掩盖 |
| DS-Lite | B4 将 IPv4 封装进 IPv6,AFTR 解封装并承担 NAT44 | 接入网怎样用 IPv6 承载共享 IPv4 出口 |
| NAT64 + DNS64 | 合成相应 AAAA,IPv6 流量在翻译器处转为 IPv4 | IPv6 客户端怎样访问只有 IPv4 的服务 |
| 464XLAT | CLAT 做无状态翻译,PLAT 做有状态 NAT64 | IPv4 应用怎样穿过 IPv6 接入网 |
DS-Lite 保留隧道内 IPv4 语义;NAT64 改变网络层地址族,两者并非同一种“套个 IPv6 头”。对于 NAT64,DNS64 帮助按名称访问,但应用硬编码 IPv4 字面量时不会经过这种 AAAA 合成;464XLAT 可为相应 IPv4 应用提供衔接。DS-Lite RFC 6333、NAT64 RFC 6146、464XLAT RFC 6877
双栈应用可用 Happy Eyeballs 安排候选连接,降低某个地址族失效带来的等待;它涉及解析、排序与延迟启动竞争,不是所有请求都同时完整发送两遍。成功回退可能掩盖 IPv6 故障,诊断时仍需分别看两族的路径和连接结果。RFC 8305
本地复算:长度、字段和数据区间
import struct
from ipaddress import IPv6Address
src = IPv6Address('2001:db8:1::a')
dst = IPv6Address('2001:db8:2::b')
assert src.exploded == '2001:0db8:0001:0000:0000:0000:0000:000a'
traffic_class, flow_label = 0xBA, 0x12345
word = (6<<28) | (traffic_class<<20) | flow_label
header = struct.pack('!IHBB16s16s', word, 116, 0, 64, src.packed, dst.packed)
assert len(header) == 40
assert word>>28 == 6 and (word>>20)&255 == 0xBA
assert word&0xfffff == flow_label
assert 40 + struct.unpack('!H', header[4:6])[0] == 156
payload = bytes(i%251 for i in range(2960))
capacity = ((1280-40-8)//8)*8
pieces = []
for start in range(0,len(payload),capacity):
data = payload[start:start+capacity]
more = int(start+len(data)<len(payload))
offset = start//8
frag_header = struct.pack('!BBHI', 17, 0, (offset<<3)|more, 123456)
assert len(frag_header) == 8
pieces.append((offset,more,8+len(data),48+len(data),data))
assert [x[:4] for x in pieces] == [(0,1,1240,1280),(154,1,1240,1280),(308,0,504,544)]
assert b''.join(x[4] for x in pieces) == payload
assert pieces[-1][0]*8 + len(pieces[-1][4]) == 2960
print('IPv6 字段、长度、分片偏移与区间检查通过')
示例验证局部编码和计算关系,随机载荷并非完整 UDP 报文;未向网络发送数据或修改接口配置。
继续查阅