268 lines
7.8 KiB
Markdown
268 lines
7.8 KiB
Markdown
|
|
---
|
|||
|
|
tags: [计算机网络, SRv6, SDN, OpenFlow, P4, Go网络编程]
|
|||
|
|
create time: 2026-05-18 05:40
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# SDN、SRv6 与 Go 网络编程最佳实践
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
当 eBPF 让内核可编程之后,SDN 让整个网络架构变得可编程。SRv6 将路由策略编码进 IPv6 地址本身,而 Source Routing 让源端可以精确控制数据包经过的每一跳。本章从原理走向工程实践。
|
|||
|
|
|
|||
|
|
## SRv6(Segment Routing over IPv6)
|
|||
|
|
|
|||
|
|
### 核心理念
|
|||
|
|
|
|||
|
|
SRv6 是 MPLS 的 IPv6 等价物,但有一个关键区别:**路径信息直接编码在 IPv6 地址中**。
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
传统 MPLS: SRv6:
|
|||
|
|
标签栈 (stacked labels) IPv6 Ext Header: SRH (Segment Routing Header)
|
|||
|
|
每个中间节点查转发表 每个 Segment = 一个操作
|
|||
|
|
中间节点依赖 LSP 源端指定整条路径 (source routing)
|
|||
|
|
部署需要全局配置 LDP/RSVP-TE Controller 集中下发
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### SRv6 数据包结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
IPv6 Header:
|
|||
|
|
Src: controller-assigned address
|
|||
|
|
Dst: SRH (Segment Routing Header)
|
|||
|
|
Next Header: 59 (NoNextHeader, 表示后面没有额外 header)
|
|||
|
|
Hdr Ext Len: N-1
|
|||
|
|
Routing Type: 4
|
|||
|
|
Segments Left: N
|
|||
|
|
Last Entry: N-1
|
|||
|
|
Flags: 0
|
|||
|
|
Tag: 0
|
|||
|
|
Segments[N]: endpoint6::action1 ← 第 N 段
|
|||
|
|
Segments[N-1]: endpoint6::action2 ← 第 N-1 段
|
|||
|
|
...
|
|||
|
|
Segments[1]: next-hop-ip ← 最后一段就是实际目的 IP
|
|||
|
|
Payload: actual data (TCP/UDP/ICMP)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
SRv6 典型路径:
|
|||
|
|
──────────────────
|
|||
|
|
Client ──→ PE1 ──→ PE2 ──→ PE3 ──→ Provider Edge ──→ Destination
|
|||
|
|
↑ ↑
|
|||
|
|
SRH[2]=PE2 SRH[1]=PE3
|
|||
|
|
|
|||
|
|
数据包发出时 SRH = [PE2, PE3, Dest]
|
|||
|
|
经过 PE1 后: Segments Left=2, 目的地址变为 PE2::action
|
|||
|
|
经过 PE2 后: Segments Left=1, 目的地址变为 PE3::action
|
|||
|
|
到达 PE3: Segments Left=0, 交付给最终目的地
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
SRv6 的 End.SID 动作类型:
|
|||
|
|
├── End : 转发到目的地址 (最基本)
|
|||
|
|
├── End.X : 转发到指定邻接关系 (二层转发)
|
|||
|
|
├── End.DX : 解封装并三层转发 (VxLAN 出口)
|
|||
|
|
├── End.DT4 : 解封装并从 IPv4 VRF 转发
|
|||
|
|
├── End.DT6 : 解封装并从 IPv6 VRF 转发
|
|||
|
|
├── End.M : 添加到 MPLS 标签栈
|
|||
|
|
└── End.B6 : 绑定列表 + 执行 SRGB 操作
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## SDN(Software Defined Networking)
|
|||
|
|
|
|||
|
|
### SDN 三层架构
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
flowchart TB
|
|||
|
|
subgraph "Application Plane"
|
|||
|
|
App["Traffic Engineering<br/>Security Policies<br/>Multi-tenant Isolation<br/>Bandwidth On-Demand"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph "Control Plane"
|
|||
|
|
Controller["SDN Controller<br/>OpenDaylight / ONOS / Ryu / FRRouting"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph "Data Plane"
|
|||
|
|
SW1["Switch A<br/>OpenFlow Protocol"]
|
|||
|
|
SW2["Switch B<br/>OpenFlow Protocol"]
|
|||
|
|
SW3["Router C<br/>P4 可编程"]
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
App -->|"REST API"| Controller
|
|||
|
|
Controller -->|"OpenFlow<br/>NetConf/YANG"| SW1
|
|||
|
|
Controller -->|"OpenFlow"| SW2
|
|||
|
|
Controller -->|"P4Runtime"| SW3
|
|||
|
|
|
|||
|
|
style Controller fill:#DDA0DD,color:#000
|
|||
|
|
style App fill:#98FB98,color:#000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### OpenFlow 工作原理
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
传统交换机: OpenFlow 交换机:
|
|||
|
|
数据面 + 控制面耦合 数据面与控制面分离
|
|||
|
|
每台设备自己算路由 控制器决定一切转发逻辑
|
|||
|
|
|
|||
|
|
主机收到包 → 本地查表 → 转发 主机收到包 → 查 Flow Table
|
|||
|
|
├─ 命中 → 按 Action 转发
|
|||
|
|
└─ 未命中 → Packet-In 问 Controller
|
|||
|
|
Controller → Flow-Mod 加规则
|
|||
|
|
↓
|
|||
|
|
下次命中 → 直接转发 (高速)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```python
|
|||
|
|
# 用 ryu 框架写一个简单的 SDN Controller (Python)
|
|||
|
|
from ryu.base import app_manager
|
|||
|
|
from ryu.controller import ofp_event
|
|||
|
|
from ryu.controller.handler import MAIN_DISPATCHER
|
|||
|
|
from ryu.controller.handler import set_ev_cls
|
|||
|
|
from ryun.ofproto import ofproto_v1_3
|
|||
|
|
|
|||
|
|
class SimpleSDN(app_manager.RyuApp):
|
|||
|
|
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
|
|||
|
|
|
|||
|
|
def __init__(self, *args, **kwargs):
|
|||
|
|
super().__init__(*args, **kwargs)
|
|||
|
|
|
|||
|
|
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
|
|||
|
|
def packet_in_handler(self, ev):
|
|||
|
|
msg = ev.msg
|
|||
|
|
dp = msg.datapath
|
|||
|
|
ofp = dp.ofproto
|
|||
|
|
ofp_parser = dp.ofproto_parser
|
|||
|
|
|
|||
|
|
# 添加 FLOW_MOD: match 全部流量 → 泛洪到所有端口
|
|||
|
|
actions = [ofp_parser.OFPActionOutput(ofp.OFPP_FLOOD)]
|
|||
|
|
|
|||
|
|
out = ofp_parser.OFPPacketOut(
|
|||
|
|
datapath=dp, buffer_id=msg.buffer_id,
|
|||
|
|
in_port=msg.match['in_port'], actions=actions
|
|||
|
|
)
|
|||
|
|
dp.send_msg(out)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### P4 —— 可编程数据平面
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
P4 (Programming Protocol-independent Packet Processors):
|
|||
|
|
允许你定义数据包的解析器和处理流水线,然后编译成
|
|||
|
|
特定 ASIC/FPGA/NIC 上的硬件代码。
|
|||
|
|
|
|||
|
|
核心概念:
|
|||
|
|
parser → 如何解析报文头
|
|||
|
|
deparser → 如何组装报文
|
|||
|
|
pipeline → 匹配- action 表的流处理逻辑
|
|||
|
|
control → 整个管道的编排
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```p4
|
|||
|
|
// P4 示例: 简单的负载均衡器
|
|||
|
|
header ethernet_t ethernet;
|
|||
|
|
header ipv4_t ipv4;
|
|||
|
|
|
|||
|
|
struct headers {
|
|||
|
|
ethernet_t ethernet;
|
|||
|
|
ipv4_t ipv4;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
parser parse_headers(packet_in& p, out headers_t hdr) {
|
|||
|
|
p.extract(hdr.ethernet);
|
|||
|
|
p.extract(hdr.ipv4);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
control ingress(packet_in& p, inout headers_t hdr) {
|
|||
|
|
// 基于目标 IP 选择后端
|
|||
|
|
action forward_to_server1() {
|
|||
|
|
modify_field(hdr.ipv4.dstAddr, SERVER1_IP);
|
|||
|
|
}
|
|||
|
|
action forward_to_server2() {
|
|||
|
|
modify_field(hdr.ipv4.dstAddr, SERVER2_IP);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
table lb_table {
|
|||
|
|
key = {
|
|||
|
|
hdr.ipv4.dstAddr: exact;
|
|||
|
|
}
|
|||
|
|
actions = { forward_to_server1; forward_to_server2; }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Go 中的网络编程最佳实践汇总
|
|||
|
|
|
|||
|
|
### 高性能 HTTP Server 模板
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
package main
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"crypto/tls"
|
|||
|
|
"net"
|
|||
|
|
"net/http"
|
|||
|
|
"time"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func NewServer(addr string, handler http.Handler) *http.Server {
|
|||
|
|
return &http.Server{
|
|||
|
|
Addr: addr,
|
|||
|
|
Handler: handler,
|
|||
|
|
|
|||
|
|
// 超时设置 (防 Slowloris)
|
|||
|
|
ReadTimeout: 10 * time.Second,
|
|||
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|||
|
|
WriteTimeout: 30 * time.Second,
|
|||
|
|
IdleTimeout: 120 * time.Second,
|
|||
|
|
MaxHeaderBytes: 1 << 20, // 1MB
|
|||
|
|
|
|||
|
|
TLSConfig: &tls.Config{
|
|||
|
|
MinVersion: tls.VersionTLS13,
|
|||
|
|
NextProtos: []string{"h2", "http/1.1"},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// StartListener 用自定义 listener 启动
|
|||
|
|
func StartListener(srv *http.Server, network, addr string) error {
|
|||
|
|
ln, err := net.Listen(network, addr)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
// 底层使用 SO_REUSEPORT 实现多 worker 共享端口
|
|||
|
|
return srv.Serve(ln)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 出站请求的高性能 Transport
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
import "golang.org/x/net/http2"
|
|||
|
|
|
|||
|
|
func HighPerfTransport() *http.Transport {
|
|||
|
|
transport := &http.Transport{
|
|||
|
|
MaxIdleConns: 200,
|
|||
|
|
MaxIdleConnsPerHost: 50, // ← Go 默认只有 2, 务必显式设置!
|
|||
|
|
IdleConnTimeout: 90 * time.Second,
|
|||
|
|
|
|||
|
|
TLSHandshakeTimeout: 10 * time.Second,
|
|||
|
|
DialContext: (&net.Dialer{
|
|||
|
|
Timeout: 5 * time.Second,
|
|||
|
|
KeepAlive: 30 * time.Second,
|
|||
|
|
DualStack: true, // RFC 6724 happy eyeballs
|
|||
|
|
}).DialContext,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 强制 HTTP/2 (Go 1.6+ 自动协商, 显式配置确保)
|
|||
|
|
http2.ConfigureTransport(transport)
|
|||
|
|
|
|||
|
|
return transport
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 关联笔记
|
|||
|
|
|
|||
|
|
- [[hhs/NETWORK/WireGuardVPN原理与实践]] — WireGuard 也可以作为 SDN 的数据平面组件
|
|||
|
|
- [[hhs/NETWORK/eBPF与ServiceMesh]] — eBPF 可以与 SDN Controller 协同工作
|
|||
|
|
- [[hhs/NETWORK/03-零拷贝与GoNetpoller]] — Go 网络编程的基础技术
|