十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SRS vnet 虚拟网络实战指南:为 pion 构建可编程的 NAT 与网络损伤测试环境

SRS vnet 虚拟网络实战指南:为 pion 构建可编程的 NAT 与网络损伤测试环境 SRS vnet 虚拟网络实战指南为 pion 构建可编程的 NAT 与网络损伤测试环境【免费下载链接】srsSRS is a simple, high-performance, AI-driven real-time media server supporting RTMP, WebRTC, HLS, HTTP-FLV, HTTP-TS, SRT, MPEG-DASH, and GB28181, with codec support for H.264, H.265, AV1, VP9, AAC, Opus, and G.711.项目地址: https://gitcode.com/GitHub_Trending/sr/srs导读vnetvirtual network是 pion/transport 提供的一个纯用户态虚拟网络层让开发者可以在单进程内搭建由路由器、NAT、网卡、DNS 解析器组成的可编程局域网/广域网专门用于 NAT 穿透如 ICE/STUN/TURN测试、应用层网络损伤模拟与任意接口的报文监控。本指南以该模块的官方 README 为核心结合 srs-bench 的真实集成源码讲透 vnet 的架构、核心 API、构建步骤与扩展机制读完你将能够亲手搭建一个带 NAT 和网络损伤的虚拟网络并理解 SRS 官方压测工具如何在完全不依赖真实网络环境的前提下完成 WebRTC 全链路回归。一、vnet 是什么设计目标与功能特性vnet 是一个「进程内的虚拟网络」与真实网卡、真实路由器完全无关所有报文都在内存中以Chunk对象的形式流转。官方 README 将其目标概括为三点让 NAT 穿透测试变得容易不必再在 Docker、虚拟机或物理机之间搭 NAT 环境直接在内存中实例化多台带 NAT 的路由器ICE 候选收集、STUN Binding 等流程即刻可测。在应用层模拟网络损伤用于测试通过丢包、延迟等过滤器把真实网络中的拥塞、抖动搬到测试代码里且结果可重复。在任意指定接口上监控报文由于每个接口都是可编程对象可以在转发路径上挂接自定义过滤器观察或改写经过的每个数据包。对应地其功能特性包括可配置的虚拟 LAN/WAN与虚拟托管的 ICE 服务器——STUN 服务器、TURN 服务器都可以作为普通 App 挂到虚拟网络上从而在纯软件环境内完成 WebRTC 信令与媒体链路的穿透验证。从源码结构看该模块由 net.go虚拟网络栈、router.go路由器、nat.goNAT 翻译器、chunk.go报文、resolver.goDNS、loss_filter.go / delay_filter.go / tbf.go损伤与限速过滤器以及 udpproxy.go虚实桥接构成下文将逐一展开。二、虚拟网络组件与整体架构2.1 顶层视图README 给出了 vnet 的整体拓扑图核心元素是App、Net、Router、NAT与 NIC------- ---- -------- | :App |-----------|:Net|--o-----|:Router | ------- ---- | | ----------- ---- | | |:STUNServer|-------|:Net|--o-----| | ----------- ---- | | ----------- ---- | | |:TURNServer|-------|:Net|--o-----| | ----------- ---- | | --------图中的约定如下o表示 NICNetwork Interface Controller即transport.Interface。[1]Net实现了 NIC 接口从而可以挂到路由器上接收报文。[2]根路由器没有 NAT视为 Internet/WAN所有子路由器始终带一个 NAT。[3]Router自己也实现了 NIC 接口供父路由器访问这样路由器可以级联成树。关键结论路由器以树状结构连接不允许成环README 的 Design Policy 明确写明 Routers are connected in a tree structure (no loop is allowed)这是为了简化路由逻辑、便于集中控制与监控统计。2.2 Net三面接口的虚拟网络栈Net是 vnet 的核心对象README 指出它对外暴露三类接口配置 API直接调用创建 Net、设置静态 IP 等网络 API等价于net.Xxx()ListenPacket、DialUDP、ResolveUDPAddr等用法与 Go 标准库net包一致通过 NIC 接口访问路由器Net实现 NIC 接口net.go 中onInboundChunk、getStaticIPs、setRouter等路由器据此把入站报文投递给对应的 Net。The instance ofNetwill be the one passed around the project. Net class has public methods for configuration and for application use.从源码实现看net.goNet内部持有interfaces网卡列表、staticIPs静态 IP、router所挂载的路由器与udpConnsUDP 连接表并通过编译期断言var _ transport.Net Net{}保证实现了transport.Net接口因此可以直接注入 pion 生态的各个模块。2.3 Router带转发队列与 NAT 的路由器Routerrouter.go是报文转发的引擎内部转发队列每个 Router 有一个chunkQueue默认QueueSize 0表示无限队列当队列满时新报文会被直接丢弃并记录 Warn 日志queue was full. dropped a chunk。逐跳转发processChunks()从队列弹出报文先依次经过所有ChunkFilter任一过滤器返回false即丢弃然后判断目的 IP在子网内则直接投递给目标 NIC在子网外则交给父路由器若是根路由器WAN且无路由则丢弃。自动分配 IPassignIPAddress()基于路由器 CIDR 的前三个字节从.1开始递增分配地址lastID达到0xfe即报地址空间耗尽。生命周期管理Start()启动内部 goroutine 处理报文并级联启动所有子路由器Stop()反向级联停止整棵树。因此 README 强调只需对根路由器调用Stop()无需逐个停止子路由器。2.4 NAT支持 RFC 4787 行为模型的地址翻译器每个非根路由器都带一个 NAT。源码 nat.go 提供了完整的 NAT 行为建模NATModeNATModeNormal标准 NAPTRFC 2663与NATModeNAT1To11:1 DNAT外部 IP 静态映射到内部 IP端口始终保留且忽略映射/过滤行为参数。EndpointDependencyTypeEndpointIndependent、EndpointAddrDependent、EndpointAddrPortDependent分别对应映射行为Mapping Behavior与过滤行为Filtering Behavior对远端 IP/端口的不同依赖程度模型参考 RFC 4787。默认 NATType子路由器未显式指定时映射行为EndpointIndependent、过滤行为EndpointAddrPortDependent、无 Hairpinning、无端口保留、映射生命周期 30 秒defaultNATMappingLifeTime见 nat.go。翻译过程出向报文translateOutbound按映射行为建立/复用映射映射端口从0xC000起递增分配按过滤行为维护允许访问的远端集合入向报文translateInbound则需命中已有映射且远端在过滤白名单内否则分别报errNoNATBindingFound/errHasNoPermission并丢弃。映射过期后会被清理MappingLifeTime到期并遵循 RFC 4847 关于入向刷新可能带来的安全风险说明见源码注释。这套模型让测试者能够构造出真实运营商级 NAT 环境如对称型、锥型 NAT这正是 WebRTC ICE 穿透测试最需要的场景。三、设计策略与内置简化约定README 的 Design Policy 是理解 vnet 行为边界的关键逐条解读如下每个 pion 包都应持有配置对象内含Net类型transport.Net属性——就像 pion 项目里到处分发LoggerFactory一样把网络栈实例作为依赖注入替换标准库net的使用。DNS 是一个简单的全局字典每个 Router 自带一个resolver默认内置localhost - 127.0.0.1见 resolver.go子路由器解析失败会向上级父路由器递归查询可通过Router.AddHost(hostName, ipAddr)手工添加域名映射。每个 Net 具备路由能力一个 goroutine报文在 goroutine 间以 Channel 传递。尽量使用net包提供的接口vnet 的类型设计transport.Interface、transport.UDPConn等刻意与标准库对齐迁移成本低。路由器以树连接、不允许成环简化路由并便于监控统计。根路由器无 NATWAN非根路由器恒有 NAT这是模拟公网/内网拓扑的基础。实例化 Net 时自动创建lo0与eth0两个接口lo0默认分配127.0.0.1MTU 16384eth0的 IP 在加入路由器时自动分配MTU 1500。详见 net.go。用户数据不分片但可选地丢弃超过 MTU 的大块数据。当前不支持 IPv6所有地址解析、分配与匹配均围绕 IPv4 展开。这些约定大幅降低了上手门槛你不必手动配置网卡和 IP只需决定「拓扑」与「静态 IP 映射」。四、搭建虚拟网络的四个基本步骤README 给出标准流程对应源码中的 router.go 的NewRouter/AddRouter/AddNet/Start/Stop创建根路由器WANvnet.NewRouter(RouterConfig{CIDR: 0.0.0.0/0, ...})CIDR 为0.0.0.0/0表示接受任意 IP。创建子路由器并挂到父路由器上构成树切勿成环通过parent.AddRouter(child)完成子路由器自动获得一个 NAT。把 Net 实例挂到各路由器上router.AddNet(nw)路由器会为 Net 的eth0自动分配 CIDR 内的 IP也可在NetConfig.StaticIP中指定静态 IP。启动根路由器测试结束调用Stop()Start()会级联启动整棵树Stop()会级联停止所有子路由器。4.1 完整示例WAN 单端点以下代码完整继承自 README 的示例可直接编译运行import ( net github.com/pion/transport github.com/pion/transport/vnet github.com/pion/logging ) // Create WAN (a root router). wan, err : vnet.NewRouter(RouterConfig{ CIDR: 0.0.0.0/0, LoggerFactory: logging.NewDefaultLoggerFactory(), }) // Create a network. // You can specify a static IP for the instance of Net to use. If not specified, // router will assign an IP address that is contained in the routers CIDR. nw : vnet.NewNet(vnet.NetConfig{ StaticIP: 27.1.2.3, }) // Add the network to the router. // The router will assign an IP address to nw. if err wan.AddNet(nw); err ! nil { // handle error } // Start router. // This will start internal goroutine to route packets. // If you set child routers (using AddRouter), the call on the root router // will start the rest of routers for you. if err wan.Start(); err ! nil { // handle error } // // Your application runs here using nw. // // Stop the router. // This will stop all internal Go routines in the router tree. // (No need to call Stop() on child routers) if err wan.Stop(); err ! nil { // handle error }要点说明NetConfig.StaticIP在源码中已被标记为deprecatednet.go推荐使用数组形式的NetConfig.StaticIPs。RouterConfig支持更多字段router.goName路由器名缺省自动生成router1、router2…、StaticIPs静态 IP根路由器忽略、QueueSize内部队列大小默认 0 无限、NATType仅对带父路由器的子路由器生效、MinDelay/MaxJitter延迟/抖动参数、LoggerFactory。4.2 如何在项目中传递 vnet.Net 实例vnet 的核心用法是把虚拟网络栈注入到应用的所有网络操作中。README 给出推荐模式用配置参数把transport.Net传给各个组件未显式提供时回退到真实网络或新建 vnettype AgentConfig struct { // ... Net: transport.Net, } type Agent struct { // ... net: transport.Net, } func NetAgent(config *AgentConfig) *Agent { if config.Net nil { config.Net vnet.NewNet() } return Agent{ // ... net: config.Net, } }之后应用代码中凡是使用net包的位置全部换成a.netvnet.Net 实例// a.net is the instance of vnet.Net class func (a *Agent) listenUDP(...) error { conn, err : a.net.ListenPacket(udpString, ...) if err ! nil { return nil, err } // ... }这正是 SRS 压测工具的做法在 srs/util.go 的testWebRTCAPI.Setup()中创建vnet.NewRouter与vnet.NewNet后通过v.settingEngine.SetNet(v.network)把虚拟网络注入 pion 的 WebRTC SettingEngine所有 ICE 候选收集与媒体收发便全部运行在虚拟网络上。五、与标准库 net 的兼容性对照README 提供了一张兼容性对照表a.net为 vnet.Net 实例类型定义在vnet包内net内置vnet备注net.Interfaces()a.net.Interfaces()net.InterfaceByName()a.net.InterfaceByName()net.ResolveUDPAddr()a.net.ResolveUDPAddr()net.ListenPacket()a.net.ListenPacket()net.ListenUDP()a.net.ListenUDP()推荐使用 ListenPacket()net.Listen()a.net.Listen()TODOnet.ListenTCP()不支持推荐用 Listen()net.Dial()a.net.Dial()net.DialUDP()a.net.DialUDP()net.DialTCP()不支持net.Interfacetransport.Interfacenet.PacketConn原样使用net.UDPConntransport.UDPConnnet.TCPConntransport.TCPConnTODO代码中使用 net.Connnet.Dialertransport.Dialer用 a.net.CreateDialer() 创建vnet.Dialer 目前是实验性的补充说明有源码依据TCP 尚不支持Net.DialTCP与Net.ListenTCP直接返回transport.ErrNotSupportednet.go因此 vnet 当前只覆盖 UDP 路径chunk.go中虽然定义了chunkTCP与 TCP 控制位SYN/FIN/RST/PSH/ACK但属于预留实现。大多数net包的其他接口类型可以直接原样使用。文档建议当需要新增其他类型/方法时通过 GitHub issue 提交给 vnet 项目。六、结合 SRS 源码vnet 的真实集成范式SRS 的官方压测工具 srs-bench 将 vnet 用于 WebRTC 压测与回归测试是理解 vnet 实战价值的最佳样本。其核心代码在 srs/util.go 的testWebRTCAPItype testWebRTCAPI struct { // ... router *vnet.Router network *vnet.Net proxy *vnet_proxy.UDPProxy }Setup()中的初始化流程srs/util.gosetupVnet : func(vnetClientIP string) (err error) { // 创建一个私有路由器CIDR 0.0.0.0/0 表示接受所有 IP不设子路由器 if v.router, err vnet.NewRouter(vnet.RouterConfig{ CIDR: 0.0.0.0/0, LoggerFactory: logging.NewDefaultLoggerFactory(), }); err ! nil { return errors.Wrapf(err, create router for api) } // 每个 api 绑定一个虚拟网络可静态指定客户端 IP v.network, err vnet.NewNet(vnet.NetConfig{ StaticIP: vnetClientIP, }) if err ! nil { return errors.Wrapf(err, create network for api) } if err v.router.AddNet(v.network); err ! nil { return errors.Wrapf(err, create network for api) } // 关键把虚拟网络注入 WebRTC SettingEngine v.settingEngine.SetNet(v.network) // 创建绑定到路由器的 UDP 代理桥接真实服务器 if v.proxy, err vnet_proxy.NewProxy(v.router); err ! nil { return errors.Wrapf(err, create proxy for router) } return v.router.Start() }这里有一个**「虚实桥接」**的关键设计客户端跑在虚拟网络里但 SRS 媒体服务器是真实进程。vnet 通过 UDPProxy 在虚拟 Net 与真实net.UDPConn之间双向拷贝报文使虚拟客户端能够与真实服务器通信。其工作原理见 udpproxy.go 源码为真实服务器地址在路由器上创建一个具有相同ip:port的虚拟vnet.UDPConn虚拟客户端发来的报文经虚拟网络到达该 socket 后UDPProxy按虚拟客户端地址维护端点映射endpoints sync.Map为每个虚拟客户端创建一条到真实服务器的 UDP 连接两条方向各由一个 goroutine 搬运数据vnetSocket.ReadFrom → realSocket.WriterealSocket.ReadFrom → vnetSocket.WriteTo空闲超时默认 2 分钟后自动回收端点Close()时取消所有 goroutine。命令行入口srs/util.go提供了-srs-vnet-client-ip参数默认192.168.168.168用于指定虚拟客户端 IP。这是 README「Configurable virtual LAN and WAN」与「Virtually hosted ICE servers」两大特性在真实压测项目中的直接落地。七、网络损伤与监控扩展过滤器机制README 的 TODO 中列出「增加网络损伤特性延迟/抖动、选择性丢包过滤器」而这些能力在当前版本中已经实现是 vnet 最有价值的扩展点7.1 ChunkFilter路由器级报文过滤Router.AddChunkFilter(filter ChunkFilter)router.go可挂载多个过滤器按添加顺序执行任一过滤器返回false即丢弃该报文后续过滤器不再收到它type ChunkFilter func(c Chunk) bool在processChunks()中报文在转发前会先过一遍所有过滤器router.go被过滤的报文直接丢弃不会命中目标 NIC。7.2 LossFilter按概率丢包loss_filter.go 提供NewLossFilter(nic NIC, chance int)以chance/100的概率丢弃每个入站报文func (f *LossFilter) onInboundChunk(c Chunk) { if rand.Intn(100) f.chance { //nolint:gosec return // 丢弃 } f.NIC.onInboundChunk(c) }LossFilter是一个包装 NIC 的装饰器实现了NIC接口因此可以插在路由器与 Net 之间模拟随机丢包率。7.3 DelayFilter固定延迟delay_filter.go 提供NewDelayFilter(nic NIC, delay time.Duration)把每个入站报文在内部队列中滞留delay时间再转发。注意其Run(ctx)必须在转发任何报文前调用否则报文不会前进func (f *DelayFilter) onInboundChunk(c Chunk) { f.queue.push(timedChunk{ Chunk: c, deadline: time.Now().Add(f.delay), }) f.push - struct{}{} }7.4 TokenBucketFilter令牌桶限速tbf.go 实现了标准的令牌桶限速算法支持以下配置项TBFRate(rate int)比特率默认1 * MBitTBFMaxBurst(size int)桶容量即桶满时能瞬间通过的最大字节数默认8 * KBitTBFQueueSizeInBytes(bytes int)等待队列最大字节数默认50000Set(opts ...TBFOption)运行时动态调整参数并返回旧值。令牌按minRefillDuration100ms粒度补充每个报文按UserData()长度扣减令牌不足则留在队列等待。7.5 RouterConfig 内置的延迟/抖动此外RouterConfig.MinDelay与MaxJitter直接在路由器层面引入传输时延与随机抖动processChunks()每轮处理前先time.Sleep(rand(0, maxJitter))模拟抖动再按minDelay计算报文是否到期router.go。这些过滤器结合UDPProxy的mockRealServerAddr字段可以构造出「虚拟客户端 → 有损虚拟网络 → 真实服务器」的完整测试链路无需任何外部网络工具。八、当前限制与后续路线README 明确列出 vnet 的 TODO / Next Step使用前需注意这些边界TCP 支持尚未完成TCPConn、Listen目前Net.DialTCP/Net.ListenTCP返回transport.ErrNotSupportedtransport.TCPConn在兼容表中标注为 TODO。IPv6 不支持所有地址均为 IPv4。更多虚拟网络构建示例待补充。路由器级网络损伤特性待增强引入更精细的延迟/抖动模型、可选择性丢弃报文的过滤处理器等当前版本已通过 ChunkFilter / LossFilter / DelayFilter / TokenBucketFilter 提供了基础能力。统计信息待实现包括各路由器转发报文总数、丢包总数、TCP 连接失败数等目前 README 中这些属于规划项尚不可用。九、参考资源本模块文档vnet README核心实现网络栈 net.go、路由器 router.go、NAT nat.go、报文 chunk.go、DNS resolver.go损伤过滤器丢包 loss_filter.go、延迟 delay_filter.go、限速 tbf.go虚实桥接udpproxy.go 与 srs-bench 的集成实现 srs/util.goNAT 行为模型参考RFC 4787NAT 行为分类、RFC 2663NAPT 定义、RFC 4847 第 4.3 节映射刷新【免费下载链接】srsSRS is a simple, high-performance, AI-driven real-time media server supporting RTMP, WebRTC, HLS, HTTP-FLV, HTTP-TS, SRT, MPEG-DASH, and GB28181, with codec support for H.264, H.265, AV1, VP9, AAC, Opus, and G.711.项目地址: https://gitcode.com/GitHub_Trending/sr/srs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表