云计算与虚拟化技术丛书

云数据中心网络与SDN：技术架构与实现

张晨　编著

ISBN：978-7-111-59121-4

本书纸版由机械工业出版社于2018年出版，电子版由华章分社（北京华章图文信息有限公司，北京奥维博世图书发行有限公司）全球范围内制作与发行。

版权所有，侵权必究

客服热线：+ 86-10-68995265

客服信箱：service@bbbvip.com

官方网址：www.hzmedia.com.cn

新浪微博 @华章数媒

微信公众号 华章电子书（微信号：hzebook）

目录

技术审校

本书赞誉

前言

第1章　云数据中心网络演进

1.1　传统的3-Tier架构

1.2　设备“多虚一”——虚拟机框

1.2.1　Cisco　VSS

1.2.2　Juniper　VC与H3C　IRF

1.3　高级STP欺骗——跨设备链路聚合

1.3.1　Cisco　vPC

1.3.2　Juniper　MC-LAG和Arista　M-LAG

1.4　变革3-Tier——向Leaf-Spine演进

1.5　初识大二层

1.6　插叙——虚拟机的接入

1.6.1　VEB

1.6.2　Cisco　VN-TAG

1.6.3　VEPA

1.6.4　VEB性能优化

1.7　消除STP——Underlay　L2MP

1.7.1　TRILL

1.7.2　SPB

1.8　Cisco私有的大二层——FabricPath

1.8.1　整体设计

1.8.2　控制与转发过程分析

1.8.3　其他技术细节

1.9　Juniper私有的大二层——QFabric

1.9.1　整体设计

1.9.2　集中式的控制机制

1.9.3　控制与转发过程分析

1.10　Brocade私有的大二层——VCS

1.10.1　整体设计

1.10.2　控制与转发过程分析

1.10.3　其他技术细节

1.11　跨越数据中心的二层——DCI优化

1.11.1　Cisco　OTV

1.11.2　HUAWEI　EVN与H3C　EVI

1.12　端到端的二层——NVo3的崛起

1.12.1　VxLAN

1.12.2　NvGRE

1.12.3　STT

1.12.4　Geneve

1.13　新时代的开启——SDN入场

1.14　Overlay最新技术——EVPN

1.14.1　传统网络对SDN的反击

1.14.2　组网与数据模型

1.14.3　控制信令的设计

1.15　Underlay最新技术——Segment　Routing

1.15.1　SID与Label

1.15.2　控制与转发机制

1.15.3　SDN　2.0？

1.16　本章小结

第2章　杂谈SDN

2.1　SDN与传统网络——新概念下的老问题

2.2　转控分离——白盒的曙光

2.2.1　芯片级开放

2.2.2　操作系统级开放

2.2.3　应用级开放

2.2.4　机箱级开放

2.2.5　白盒的“通”与“痛”

2.3　网络可编程——百家争鸣

2.3.1　芯片可编程

2.3.2　FIB可编程

2.3.3　RIB可编程

2.3.4　设备配置可编程

2.3.5　设备OS和控制器可编程

2.3.6　业务可编程

2.4　集中式控制——与分布式的哲学之争

2.4.1　在功能上找到平衡点

2.4.2　在扩展性和可用性上找到平衡点

2.5　回归软件本源——从N到D再到S

2.5.1　模块管理

2.5.2　模块间通信

2.5.3　接口协议适配

2.5.4　数据库

2.5.5　集群与分布式

2.5.6　容器与微服务

2.6　本章小结

第3章　SDDCN概述

3.1　需求

3.1.1　自动化与集中式控制

3.1.2　应用感知

3.2　整体架构

3.2.1　实现形态

3.2.2　功能设计

3.3　关键技术

3.3.1　网络边缘

3.3.2　网络传输

3.3.3　服务链

3.3.4　可视化

3.3.5　安全

3.3.6　高可用

3.4　本章小结

第4章　商用SDDCN解决方案

4.1　VMware　NSX

4.1.1　从NVP到NSX

4.1.2　NVP控制平面设计

4.1.3　NVP数据平面设计

4.1.4　NVP转发过程分析

4.1.5　NSX-V整体架构

4.1.6　NSX-V管理平面设计

4.1.7　NSX-V控制平面设计

4.1.8　NSX-V数据平面设计

4.1.9　NSX-V转发过程分析

4.1.10　NSX-MH与NSX-T

4.2　Cisco　ACI

4.2.1　整体架构

4.2.2　管理与控制平面设计

4.2.3　数据平面设计

4.2.4　转发过程分析

4.2.5　议ACI与SDN

4.3　Cisco　VTS

4.3.1　整体架构

4.3.2　管理与控制平面设计

4.3.3　数据平面设计

4.4　Juniper　Contrail

4.4.1　整体架构

4.4.2　管理与控制平面设计

4.4.3　数据平面设计

4.4.4　转发过程分析

4.5　Nuage　VCS

4.5.1　整体架构

4.5.2　管理平面设计

4.5.3　控制平面设计

4.5.4　数据平面设计

4.6　Arista　EOS与CloudVison

4.6.1　整体架构

4.6.2　管理与控制平面设计

4.6.3　数据平面设计

4.7　HUAWEI　AC-DCN

4.7.1　整体架构

4.7.2　管理平面设计

4.7.3　控制平面设计

4.7.4　数据平面设计

4.8　Bigswitch　BCF与BMF

4.8.1　整体架构

4.8.2　BCF控制平面设计

4.8.3　BMF控制平面设计

4.8.4　数据平面设计

4.9　Midokura　Midonet

4.9.1　整体架构

4.9.2　控制平面设计

4.9.3　数据平面设计

4.10　PLUMgrid　ONS

4.10.1　整体架构

4.10.2　数据平面设计

4.10.3　控制平面设计

4.10.4　转发过程分析

4.11　Plexxi　Switch与Control

4.11.1　整体架构

4.11.2　数据平面设计

4.11.3　控制平面设计

4.12　Pluribus

4.12.1　Server　Switch设计

4.12.2　Netvisor设计

4.12.3　再议数据中心SDN

4.13　本章小结

第5章　开源SDDCN：OpenStack　Neutron的设计与实现

5.1　网络基础

5.1.1　网络结构与网络类型

5.1.2　VLAN网络类型中流量的处理

5.2　软件架构

5.2.1　分布式组件

5.2.2　Core　Plugin与Service　Plugin

5.3　WSGI与RPC的实现

5.3.1　Neutron　Server的WSGI

5.3.2　Neutron　Plugin与Neutron　Agent间的RPC

5.4　虚拟机启动过程中网络的相关实现

5.4.1　虚拟机的启动流程

5.4.2　Nova请求Port资源

5.4.3　Neutron生成Port资源

5.4.4　Neutron将Port相关信息通知给DHCP　Agent

5.4.5　DHCP　Agent将Port相关信息通知给DHCP　Server

5.4.6　Nova拉起虚拟机并通过相应的Port接入网络

5.5　OVS　Agent的实现

5.5.1　网桥的初始化

5.5.2　使能RPC

5.6　OVS　Agent对Overlay　L2的处理

5.6.1　标准转发机制

5.6.2　arp_responder

5.6.3　l2_population

5.7　OVS　Agent对Overlay　L3的处理

5.7.1　标准转发机制

5.7.2　DVR对东西向流量的处理

5.7.3　DVR对南北向流量的处理

5.8　Security-Group与FWaaS

5.8.1　Neutron-Security-Group

5.8.2　FWaaS　v1

5.8.3　FWaaS　v2

5.9　LBaaS

5.9.1　LBaaS　v1

5.9.2　LBaaS　v2

5.9.3　Octavia

5.10　TaaS

5.11　SFC

5.12　L2-Gateway

5.13　Dynamic　Routing

5.14　VPNaaS

5.15　Networking-BGPVPN与BagPipe

5.15.1　Networking-BGPVPN

5.15.2　BagPipe

5.16　DragonFlow

5.17　OVN

5.18　本章小结

第6章　开源SDDCN：OpenDaylight相关项目的设计与实现

6.1　架构分析

6.1.1　AD-SAL架构

6.1.2　MD-SAL架构

6.1.3　YANG和YANG-Tools

6.1.4　MD-SAL的内部设计

6.1.5　MD-SAL的集群机制

6.1.6　其他

6.2　OpenFlow的示例实现

6.2.1　OF交换机的上线

6.2.2　l2switch获得PacketIn

6.2.3　l2switch下发PacketOut和FlowMod

6.3　OpenStack　Networking-ODL

6.3.1　v1

6.3.2　v2

6.4　Neutron-Northbound的实现

6.4.1　对接Networking-ODL

6.4.2　RESTful请求的处理示例

6.5　Netvirt简介

6.5.1　OVSDB-Netvirt和VPNService的合并

6.5.2　Genius

6.6　Netvirt-OVSDB-Neutron的实现

6.6.1　net-virt分支

6.6.2　net-virt-providers分支

6.7　Netvirt-VPNService的实现

6.7.1　elanmanager

6.7.2　vpnmanager

6.8　SFC的实现

6.8.1　sfc-openflow-renderer分支

6.8.2　sfc-scf-openflow分支

6.9　VTN　Manager的实现

6.9.1　neutron分支

6.9.2　implementation分支

6.10　本章小结

第7章　开源SDDCN：ONOS相关项目的设计与实现

7.1　架构分析

7.1.1　分层架构

7.1.2　分层架构的实现

7.1.3　模块的开发

7.1.4　分层架构存在的问题

7.1.5　数据存储与集群

7.1.6　其他

7.2　OpenFlow的示例实现

7.2.1　OF交换机的上线

7.2.2　fwd获得PacketIn

7.2.3　fwd下发PacketOut和FlowMod

7.3　ONOSFW的实现

7.3.1　vtnmgr分支

7.3.2　sfcmgr分支

7.4　SONA的实现

7.4.1　openstacknode分支

7.4.2　openstacknetworking分支

7.5　CORD简介

7.5.1　R-CORD的架构

7.5.2　R-CORD的控制与转发机制

7.6　本章小结

第8章　学术界相关研究

8.1　拓扑

8.1.1　FatTree

8.1.2　VL2

8.1.3　DCell

8.1.4　FiConn

8.1.5　BCube

8.1.6　MDCube

8.1.7　CamCube

8.2　路由

8.2.1　Seattle

8.2.2　FatTree

8.2.3　VL2

8.2.4　PortLand

8.2.5　SecondNet

8.2.6　SiBF

8.2.7　SPAIN

8.2.8　WCMP

8.2.9　OF-based　DLB

8.2.10　Flowlet与CONGA

8.2.11　Hedera

8.2.12　DevoFlow

8.2.13　MicroTE

8.2.14　Mahout

8.2.15　F10

8.2.16　DDC

8.2.17　SlickFlow

8.2.18　COXCast

8.2.19　Avalanche

8.3　虚拟化

8.3.1　NetLord

8.3.2　FlowN

8.3.3　FlowVisor

8.3.4　ADVisor

8.3.5　VeRTIGO

8.3.6　OpenVirteX

8.3.7　CoVisor

8.4　服务链

8.4.1　pSwitch

8.4.2　FlowTags

8.4.3　Simple

8.4.4　StEERING

8.4.5　OpenSCaaS

8.4.6　SPFRI

8.5　服务质量

8.5.1　NetShare

8.5.2　Seawall

8.5.3　GateKeeper

8.5.4　ElasticSwitch

8.5.5　SecondNet

8.5.6　Oktopus

8.6　传输层优化

8.6.1　MPTCP

8.6.2　DCTCP

8.6.3　D3

8.6.4　pFabric

8.6.5　Fastpass

8.6.6　OpenTCP

8.6.7　vCC

8.7　测量与分析

8.7.1　Pingmesh

8.7.2　OpenNetMon

8.7.3　FlowSense

8.7.4　Dream

8.7.5　OpenSample

8.7.6　Planck

8.7.7　OpenSketch

8.8　安全

8.8.1　SOM

8.8.2　FloodGuard

8.8.3　TopoGuard

8.8.4　FortNox

8.8.5　AVANT　GUARD

8.8.6　OF-RHM

8.8.7　Fresco

8.9　高可用

8.9.1　ElastiCon

8.9.2　Ravana

8.9.3　BFD　for　OpenFlow

8.9.4　In-Band　Control　Recovery

8.9.5　OF-based　SLB

8.9.6　Anata

8.9.7　Duet

8.10　大数据优化

8.10.1　BASS

8.10.2　OFScheduler

8.10.3　Phurti

8.10.4　Application-Aware　Networking

8.10.5　CoFlow

8.11　本章小结

第9章　番外——容器网络

9.1　容器网络概述

9.2　容器网络模型

9.2.1　接入方式

9.2.2　跨主机通信

9.2.3　通用数据模型

9.3　Docker网络

9.3.1　docker0

9.3.2　pipework

9.3.3　libnetwork

9.4　Kubernetes网络

9.4.1　基于POD的组网模型

9.4.2　Service　VIP机制

9.5　第三方组网方案

9.5.1　Flannel

9.5.2　Weave

9.5.3　Calico

9.5.4　Romana

9.5.5　Contiv

9.6　Neutron网络与容器的对接

9.7　本章小结

第10章　番外——异构网络与融合

10.1　融合以太网基础

10.1.1　PFC

10.1.2　ETS

10.1.3　QCN

10.1.4　DCBX

10.2　存储网络及其融合

10.2.1　FC的协议栈

10.2.2　FC的控制与转发机制

10.2.3　FCoE的控制与转发机制

10.2.4　昙花一现的SDSAN

10.3　高性能计算网络及其融合

10.3.1　InfiniBand的协议栈

10.3.2　InfiniBand的控制与转发机制

10.3.3　RoCE与RoCEv2

10.4　本章小结

技术审校

龚永生　社区绰号“大师兄”，浙江九州云CTO，OpenStack Neutron发起人之一，OpenStack NFV编排器项目Tacker PTL及ONOS、OpenDaylight、CORD项目参与者和培训讲师。

张卫峰　盛科网络SDN白牌交换机CTO，在数通领域从业16年，熟悉交换芯片到路由交换协议，参与了盛科网络每个SDN商业案例的落地，也是《深度解析SDN》一书作者。

杨文嘉　美国Arista网络公司高级服务工程师，擅长大规模IP/MPLS电信运营商级别网络的规划设计、实施与排错，在为AT&T、中国电信、BMW公司、花旗集团、Freddie Mac等公司设计和实施运营商网络，数据中心网络和企业网络方面积累了超过17年的丰富经验。

本书赞誉

随着“互联网+”的蓬勃发展以及云计算、大数据、人工智能等新兴技术的深入应用，云数据中心的价值日益突显，已成为整个社会的重要基础设施之一。本书针对云数据中心的场景，深入回顾了数据中心网络技术和软件定义网络技术的前世今生，在介绍和剖析关键技术的同时，重点分享了VMware、Cisco等公司的很多案例以及技术创新背后的故事。我相信，本书将会帮助读者更加深入地理解数据中心网络和SDN技术的本质，也将对未来数据中心网络的发展思考有所启发。

——黄韬，北京邮电大学教授，江苏省未来网络创新研究院副院长

当前，SDN技术发展得如火如荼，云数据中心正是SDN的主战场之一。作者以其丰富的行业经验和扎实的工作基础，向读者系统地展示了SDN及其附属技术在云数据中心的应用情况。作者还以其独特的视角进一步对众多技术的产生和演进历程做了分析和介绍。相信作者多年积累的深厚网络功底以及他对SDN和云数据中心方向的跟进与研究一定能给读者带来全新的视野。

——毕军，清华大学网络研究院网络体系结构研究室主任，长江学者特聘教授

SDN自诞生之日就饱受争议，从其架构的集中式、分布式之争，到何为SDN、广义狭义之争，SDN似乎一直在人们多样的眼光中生存和发展。有幸能够读到本书，从本书作者对SDN及云数据中心网络技术全面深刻的理解、对产业发展的独到见解，乃至对网络架构与产业的前景展望，都足以看出作者深厚的技术功底。未来网络的发展是由IT和CT深度融合所推动的，作者长期以来在运营商、设备商以及开源领域的积累，也使得本书在每一个知识点上都能够恰到好处地切中要害。看得出来，本书是作者倾其全力、认真雕琢的好书。全书读下来，酣畅淋漓，受益匪浅，又似乎有着说不完的共同语言，每每看到思维火花的碰撞，总希望能与作者当面切磋。希望本书能成为SDN领域的经典之作，带给业界同仁更多的思考与感悟。

——孙琼，中国电信北京研究院SDN技术研发中心主任

数据中心是SDN最典型的应用场景，正是在数据中心的成功落地将SDN推向了更广阔的应用领域。本书介绍了数据中心的网络技术发展历程，并重点分析了SDDCN相关的技术、产品、解决方案与开源项目。全书内容系统、丰富，从过去到现在，从组网设计到技术细节，从研究到开发，具有重要的学习与参考价值。能够如此有点有面地展现云数据中心网络这一特定场景的全貌，对于SDN这样一个新的技术来讲，是相当不容易的。另外，本书的内容来源于作者的亲身点滴积累，加之文笔流畅，让读者能够自然、清晰地了解技术的来龙去脉和实质内容，而且在行文的过程中还穿插了大量的观点，新颖并且很有启发。从方方面面来看，这都是一本诚意之作，绝对不容错过。

——王璐，中国移动研究院网络所承载网技术经理

伴随着近年来互联网的高速发展，云数据中心也进入发展高峰，SDN技术恰恰迎合了云数据中心的发展需求。本书从多个角度对云数据中心网络、SDN、SDDCN的技术原理与解决方案进行深入的介绍和分析，内容全面，无论是刚接触数据中心网络的技术爱好者，还是数据中心研发人员、网络架构师、系统运维人员以及产品经理，都能在阅读的过程中获益。

——徐雷，中国联通研究院云计算研究中心主任

云数据中心需要与之相适应的“云计算网络”，网络虚拟化正成为变革传统网络的高地，网络的价值正不可阻挡地向软件转移。本书全面回顾了数据中心网络技术的演进，以通俗的语言讲清楚了“SDN是什么、不是什么”以及“SDN能做什么”，客观地介绍了主流商用SDN方案和多个开源平台，对于SDN初学者和专业人士来说都是一本佳作。

——叶逾健，VMware大中华区网络和安全产品销售总监

与张晨相识在SDN用户组，并参加了他的《SDN控制器架构分析》在线分享。为这个年轻作者具备的技术洞察力赞叹不已。随着SDN的引入，给传统数据中心网络Underlay/Overlay体系架构带来了深刻的影响。网络行业螺旋上升式发展，层出不穷的各种SDN控制器方案所解决的问题无外乎如何用软件来定义系统的管理/控制/转发平面逻辑。戏法人人会变，各有巧妙不同。对于广大读者来讲，如何在纷纭杂沓的各种技术中快速去芜存菁，找到适合的SDN技术来解决现实的网络问题呢？本书从数据中心CLOS架构开始讲起，逐步深入浅出、抽丝剥茧地介绍各种最新前沿技术，涵盖了Underlay的各种技术，也谈到Overlay的各种开源项目ODL/ONOS和商用SDN控制器，比如VMware NSX、Cisco APIC、Juniper Contrail等。难能可贵的是，本书对各种容器、超融合新技术也进行了详细的介绍。本书对网络工作者深入理解数据中心最新网络架构和SDN的种种解决方案大有裨益。作为网络行业18年从业者，强烈推荐本书。

——马绍文，Juniper亚太区产品总监

最近有幸拜读张晨的这本书。文笔流畅，覆盖面广泛，从2003年的ForCES讲到近期的OpenFlow、ODL、ONOS、Neutron等。看了前面两章后就一口气读了下来。作者对SDN的来龙去脉做了很全面的阐释，同时覆盖了各大厂商和开源组织的解决方案，确实是一本深入了解SDN的必读之作。很难得的是作者不光只讲网络，而是从相关业务和应用方面等切入，例如虚拟化、容器、RoCE等，把SDN的发展趋势讲得很通俗易懂。除了广度以外，作者对技术的深度也很有要求，例如白盒方案里面涉及芯片的SDK和API开发，SONiC基于SAI的设计实现等。总而言之，我觉得无论是传统网工，还是SDN程序员，或者是管理层，都可从此书中获得启发和灵感。

——池惠澄，Arista Networks大中华区工程师经理

本书作者凭借阅读各个厂家的技术白皮书，钻研开源代码和其他官方介绍总结出各类技术和产品的精髓。书中所涉及的数据中心网络相关技术不光在广度上涵盖了行业中几乎所有厂商，更在技术深度上进行了充分挖掘整理。本书最难能可贵的地方在于，作者跳出了技术本身，站在一个更高的高度上对整个行业的来龙去脉进行了准确且深入的梳理，本书无疑会成为数据中心从业人员必读的佳作。

——蒋刊哲，BigSwitch大中华区总经理

市面上SDN之类书籍已经不少，但专注于数据中心场景的这还是第一本。本书兼顾广度和深度，并对大量相关商用开源实战性技术体系做了系统的归纳、分析与点评，是SDN数据中心用例的精品指南，也是相关从业者不可或缺的案头常备参考书。

——张宇峰，互联港湾CTO

本书介绍了数据中心网络技术的演进，对SDN在云数据中心网络中如何应用进行了完整、详细的阐述。内容上不仅包括各类网络技术分析，还具有很强的实践性，对数据中心网络基础架构目前所采用的主流产品、技术方向进行了深入研究总结。本书所涉及的网络、SDN技术涵盖了在实践中广泛应用的商用产品、开源项目，对于从事数据中心网络相关开发、运维、架构师或相关工作人员都是有益的参考资料。

——张天鹏，云杉网络CTO

本书可谓业界目前最系统、全面的阐述云数据中心网络的中文书籍，从工业界到学术界，从开源软件到厂商方案都有涉及。前面从网络的演进展示了业界对云数据中心网络设计从模糊到成熟的过程，后面对厂商当前的主推方案与流行的开源控制器、IaaS、容器等进行深入解读，一次性把所有“云数据中心”的话题整合到了一本书里，可供初学者按图索骥，可供产品经理“知己知彼”，可供架构师“兼权尚计”，是一本不可多得的“云网综述”。

——王为，ZStack网络研发总监

SDN作为当今IT领域炙手可热的行业趋势之一，有很多种不同的解读，名字里虽然没有“云”，却比云计算更加“云里雾里”。我非常高兴，因为我终于看到了本书即将面世。本书作者是SDN圈子里资深的网络专家，多年笔耕不辍，分享了大量的技术文章。这次他将自己学习与工作中的积淀，切入SDN这个具体的行业应用场景，并系统化地总结成书，我相信本书定能帮助相关从业者提纲挈领般理解SDN，并能以不变应万变对浩瀚SDN知识海洋中的每一个碎片化知识清晰地定位。

——张华，Canonical全栈软件工程师，OpenStack早期代码贡献者

前言

为什么要写这本书

这本书的写作前前后后花了两年多的时间，我曾无数次地想过会有那么一个周末的下午，洋洋洒洒地写着前言，每念及于此，总有一种激扬文字、挥斥方遒的感觉。到了这一天，酝酿多时后坐在桌前，先是写了一些行业点评，虽然气势磅礴但不够接地气，反反复复地修改却始终没能令自己满意。最后想着，反正写前言就是在和读者聊天，也没有必要给书里的内容扣个大大的帽子，倒不如来说说情怀、谈谈理想，就当是给自己这几年的青春做一个注脚吧。

最开始其实并没有写书的打算，做研究的动力一方面是来源于对网络技术的兴趣与热爱，另一方面，坦白讲，那个时候在数据中心网络这块想到了一些新的点子，希望能够借着SDN的东风把想法做成实打实的产品。于是，最初是带着做调研的想法，开始对云数据中心SDN的技术与产品进行梳理。没有想到的是，这调研一做竟是两年多的时间，这两年里既看到了很多原来自己并不了解的技术，数次感叹于知识海洋的广袤无垠，也目睹了自己的一些想法被别人进行了变现，无奈地明白做产品需要分秒必争。

确立写书的想法是在2016年，实际上当时SDN圈子的风潮已经开始从云数据中心转向了企业级广域网，各路厂商、创业公司和媒体都已经开始围着SDWAN的概念在转了。和业界的一些同行聊过之后，其中有一部分人认为云数据中心的网络已经没什么值得研究的了。听多了类似的说法，给我个人的感觉是：SDN的出现既让网络这个圈子变得时髦，又让圈子里的人变得浮躁。于是，我决定把调研的内容写成一本书，希望能够使读者了解到，数据中心SDN在技术上还远远没有达到标准化，在产品上还有较大的提升空间，很多同行仍然在全力地投身于相关技术的演进和产品的迭代中。跟随先进的理念固然重要，但是踏踏实实地做事才是行业能够得以长久发展的关键。

本书还有另外一个出发点，是由于传统网络与SDN的纠葛长久以来都没能真正化解开来，很多人还是抱着非此即彼的错误观点。因此本书在介绍各个SDN技术和产品的时候，都会力争把控制和转发的逻辑说清楚，能分析到数据包处理流程这个层面的，都会加以详细分析。这么做的目的，是希望读者能够明白SDN虽然改变了网络的交付模式，但是API远远不是SDN的全部，网络架构本身的设计仍然是不可动摇的技术基础。另外，在一些情况下，SDN并不是解决问题的最好方式，因此本书还介绍了一些非SDN的数据中心网络技术，希望能够帮助读者拓宽技术视野，打开新的研究思路。

成书的过程中，最大的感悟是新技术发展得太快，很多章节的内容都不得不多次进行更新与修订。ICT的融合为传统的通信行业带来了前所未有的机遇，同时也要求我们能够调整好学习的心态。回顾这两年多的时间，几乎每日每夜都把自己泡在了各种各样的资料中，读了厂商不计其数的产品手册和技术文档，一行一行地去啃开源平台的代码，其中不乏很多有价值的内容，但由于成书时间与篇幅上的限制未能在书中进行介绍，多少还是有些遗憾吧。

同时，伴随着知识的逐步丰富，我也更加真切地体会到了个人的渺小。学习永无止境，不忘初心，方得始终，在探索技术的路上愿与诸君共勉。

本书特色

本书涉及云数据中心网络很多块的内容，主要包括传统技术、商用SDN方案、开源SDN方案以及相关的学术论文，覆盖了目前绝大部分的主流技术以及一些其他较为新颖的技术，是目前市面上少数能够切入SDN某一个具体行业应用场景，并对相关技术和产品进行系统性介绍的书籍之一。

实际上，把本书中每一块的内容拿出来都能单独写一本书。不过，为了让读者能够通过一本书，迅速并全面地掌握这些块内容，因此在写作的过程中就不得不去抠每一句话，甚至每一个字和词，力争简短、清晰、准确地呈现出核心的技术内容。如果相关行业的技术宽度为100，每种技术的深度为100，本书希望能够同时在宽度和深度上达到80+的水平，做到“样样通”而“样样不松”。

同时，为了能够捋顺这些五花八门的技术间的关系，行文过程中还对部分技术的产生背景与演进历程进行了介绍，希望读者在读过本书后，做到既能“知其然”，又能“知其所以然”。

读者对象

·CTO、CIO

·数据中心架构师

·SDN架构师

·SDN产品经理

·SDN研发人员

·高校与科研院所网络研究人员

·其他对网络技术感兴趣的人员

如何阅读本书

阅读本书前，需要读者对传统网络技术和SDN基础知识有一定的了解。

第1章会对云数据中心网络演进的技术主线进行介绍，主要包括从3-Tier架构向Leaf-Spine架构的演进、从xSTP向大二层的演进、从传统网络向SDN的演进，以及传统网络的最新进展等内容。第1章可以看作本书的背景知识，用于引出后面章节对于数据中心SDN的介绍，讲述的过程中会以技术特征为分类依据，介绍厂商相关的私有技术与产品，以及IEEE、IETF相关的技术标准。第1章中所介绍的一些技术目前已经退出了历史舞台，为了保证讲述的完整性仍然保留了相关的介绍，读者可以有选择地进行阅读。

第2章将以杂谈的形式对SDN的本质进行论述，包括转发与控制分离、网络可编程以及集中式控制，并对SDN在IT层面的设计思路进行了总结。通过第2章的内容，希望可以帮助读者重新认识SDN，并捋顺SDN与传统网络间的关系。第2章的内容相比于其他章节的内容较为独立，读者可以灵活地调整本章的阅读顺序。

从第3章开始，正式开始介绍本书的核心内容——云数据中心SDN（Software Defined Data Center Networking，以下简称SDDCN）。第3章首先对SDDCN落地需求、实现架构以及关键技术进行介绍，希望能够为后续章节的内容起到提纲挈领的作用。第3章的表述相对抽象，有网络架构设计经验或者实际工程经验的读者可以仔细阅读与体会，如果读者之前并没有接触过相关工作，阅读第3章时可以先简单地看一遍，完成后续章节内容的阅读后，回过头来再去体会第3章中的内容，会获得更好的效果。

第4章将对十余个商用SDDCN解决方案进行深入的介绍，收录了主流厂商和一些具有技术代表性的创业公司的SDDCN产品，希望通过本章的内容，帮助读者了解这些方案内部的设计机制与技术细节，而不是仅仅看到一个“网络黑箱”，否则SDN不过是另外一个黑箱。

第5章对OpenStack Neutron进行了详细的介绍，主要内容包括Neutron的组网原理、软件设计架构、虚拟机的接入、OVS Plugin/Agent的设计与实现，以及其他主要的扩展Plugin/Agent。第6章对OpenDaylight的架构设计、OpenFlow的典型实现，以及OpenDaylight中的SDDCN的相关实现进行了详细的介绍。第7章对ONOS的架构设计、OpenFlow的典型实现，以及ONOS中的SDDCN的相关实现进行了详细的介绍。

第5、6、7章的内容将涉及各个开源平台的核心代码，力争同时呈现出代码中上层的业务逻辑以及底层的控制与转发逻辑，希望帮助读者在使用这些开源方案时，能够具备二次开发的能力。需要说明的是，代码所用版本以章前引言中所提及的版本为准，代码的呈现方式将以“===”形式的分割线注明代码所在文件，通过“//”形式的注释符对关键代码的逻辑进行解析。受限于篇幅，这三章跳过了非核心代码，同时省略了非关键参数与Java中的花括号。

第8章对学术界在云数据中心网络的典型研究工作进行介绍，包括拓扑、路由、虚拟化、服务链、服务质量、传输层优化、测量与分析、安全、高可用、大数据优化这十个方向。这一章在内容上会力争把每个研究工作的技术要点都讲清楚，而不是仅仅用一两句话来概括思路。这些研究工作既包括SDN的设计，又包括很多非SDN的设计，希望能够帮助读者拓展技术视野，启发新的设计灵感。

第9章和第10章是本书的番外部分。第9章对容器网络进行了概要性的介绍，容器是与虚拟机相对应的负载形态，因此独立成章，其中涉及的网络技术都在其他章节中介绍过，用在容器网络里只是接入了不同形态的负载，而技术的本质并没有发生变化，因此第9章中不会再去详细讨论这些已经介绍过的技术。第10章以Fiber Channel和InfiniBand为代表，对数据中心内部的存储网络和高性能计算网络的技术体系进行了概要性的介绍，这两者是与以太网和TCP/IP相对应的异构网络技术，因此独立成章，另外第10章中还简单地介绍了将以太网与Fiber Channel和InfiniBand进行融合的思路。

最后要说明的是，本书所介绍的内容均为云数据中心内部的网络技术，限于成书时间和篇幅上的限制，对于云数据中心外部的网络技术，如企业网络入云、Internet流量入云、云数据中心ISP上行出口优化、云数据中心间流量优化，均未能进行介绍。另外NFV相关的内容，包括vSwitch、vRouter、BPF、VPP等datapath技术，以及DPDK、SR-IOV等IO加速技术，本书也未做专题性介绍。

勘误和支持

由于笔者的水平有限，书中难免会出现一些错误或者不准确的地方，恳请读者批评指正。读者可通过微信号gokd35直接联系笔者，或者通过邮箱sddcn_qa@126.com提出问题或者建议。愿我们在网络技术的进修之路上互勉共进。

致谢

感谢九州云的龚永生先生，盛科网络的张卫峰先生，Arista的杨文嘉先生，为本书进行了专业的技术审校。

感谢北京邮电大学的黄韬教授、清华大学的毕军教授、电信北京研究院的孙琼女士，移动研究院的王璐女士、联通研究院的徐雷先生、VMware的叶逾健先生、Juniper的马绍文先生、Arista的池惠澄先生、BigSwitch的蒋刊哲先生、互联港湾的张宇峰先生、云杉网络的张天鹏先生、ZStack的王为先生、Canonical的张华先生，为本书进行了精彩的推荐。

感谢北京邮电大学的李昕教授、F5的范恂毅先生、VMware的吉白先生、BigSwitch的吴鑫先生，在本书写作过程中提供的帮助。

再一次感谢上述各位业界前辈，在繁忙的工作后利用宝贵的休息时间来审阅此书，是他们的无私支持给了我修缮此书的动力。同时，他们为业界发展所做出的贡献令人尊重和钦佩，将不断激励着我们后辈学习与前进！

感谢机械工业出版社的编辑高婧雅女士，对本书写作的悉心指导，以及对我本人的包容和理解。感谢SDNLab的方辉先生，在我形成写书想法时的鼓励与支持。没有他们，就不会有这本书的出版。

最后感谢我的家人和朋友，他们在生活中的关怀和鼓励使我能够在这两年中踏踏实实地完成本书内容的编写。

声明

本书中所述一切观点仅代表作者本人，与作者所在单位以及其他机构无关。

第1章　云数据中心网络演进

过去的十年间，IT技术的发展风起云涌，云计算站到了浪潮之巅。云计算深刻地改变了人们对于IT商业模式的认识，随之而来的是对IT基础架构所提出的全新挑战，数据中心作为实现云计算的载体，正在经历着云化所带来的轰轰烈烈的变革。网络作为数据中心的三大基础资源之一，也在这场革命中不断地进行着演进。在本书的第1章，我们先来仔细地回顾一下这段演进的主线历程。

1.1　传统的3-Tier架构

3-Tier的网络架构起源于园区网，传统数据中心，包括企业数据中心EDC和互联网数据中心IDC的网络将其沿用了下来。如图1-1所示，3-Tier架构将网络分为接入（access）、汇聚（aggregation）和核心（core）三层。接入层通常使用二层交换机，主要负责接入服务器、标记VLAN以及转发二层的流量。汇聚层通常使用三层交换机，主要负责同一POD内的路由，并实现ACL等安全策略。核心层通常使用业务路由器，主要负责实现跨POD的路由，并提供Internet接入以及VPN等服务。在3-Tier的网络规划中，接入层和汇聚层间通常是二层网络，接入交换机双上联到汇聚交换机，并运行STP来消除环路，汇聚交换机作为网关终结掉二层，和核心路由器起IGP来学习路由，少数情况下会选择BGP。

图1-1　3-Tier的网络架构

沿用了架构，也自然就继承了它的问题，其中当属STP最让人头疼。了解网络的人再熟悉不过了，MAC自学习是以太网的根基，它以泛洪帧为探针同步转发表，其实现足够简单，不需要任何复杂的控制协议。然而事情的另一面是，至简的设计却使得MAC自学习并不够健壮，如果网络中存在环路，泛洪会在极短的时间内使网络瘫痪。为了弥补MAC自学习的这一缺陷，STP被设计出来以破除转发环路，不过STP的引入却带来了更多问题，如收敛慢、链路利用率低、规模受限、难以定位故障等。

虽然业界为STP的优化费劲了心思，但打补丁的方式只能治标而不能治本。在园区网中STP还勉强可用，但数据中心网络是一个IO更为密集的环境，而且更追求自动化和扩展性，这些都是STP所难以实现的。随着数据中心业务的不断发展，STP成了网络最为明显的一块短板，处理掉STP自然也就成了数据中心网络架构演进打响的第一枪。

1.2　设备“多虚一”——虚拟机框

首先要介绍的是虚拟机框技术，这种技术能够将多台设备中的控制平面进行整合，形成一台统一的逻辑设备，这台逻辑设备不但具有统一的管理IP，而且在各种L2和L3协议中也将表现为一个整体，如图1-2所示。因此在完成整合后，STP所看到的拓扑自然就是无环的了，这就间接地规避了STP的种种问题。

虚拟机框和传统的堆叠技术一样，二者都实现了设备的“多虚一”，不同的物理设备分享同一个控制平面，实际上就相当于为物理网络设备做了个集群，也有选主和倒换的过程。相比之下，虚拟机框在组网上的限制较少，而且在可用性方面的设计普遍要好于堆叠，因此可以看作对于堆叠技术的升级。虚拟机框技术以Cisco的VSS、Juniper的Virtual Chassis以及H3C的IRF 2为代表。

图1-2　虚拟机框技术逻辑示意

1.2.1　Cisco VSS

VSS是Cisco Catalyst 6500系列交换机推出的虚拟机框技术，支持对两台6500设备以主备模式进行整合。其中主用交换机中的引擎负责逻辑设备的转发决策，形成转发表；备用交换机只会不断地从主用交换机同步相关的状态，而不会进行协议的交互和计算。主备用设备的线卡都是分布式的，能够同时进行数据的转发。VSS主要有以下技术要点：

1）VSL（Virtual Switch Link）互联。VSS技术通过VSL将两个机架绑定成一个虚拟的交换系统，它依赖于控制信令VSLP的交互，以完成主备的协商以及状态的同步。同时，VSL也能在主备交换机间传输业务流量，这时它就相当于逻辑设备中的背板走线。为了实现逻辑设备的线速转发，VSL链路应该具备足够的带宽。

2）高可用NSF/SSO。SSO（Stateful SwitchOver）保证了主备引擎、线卡能够以最短的时间进行故障切换。不过即使中断时间再短，数据转发也可能出现中断，路由可能需要重新收敛。NSF（NonStop Forwarding）实现了不中断转发，解决了上述问题。

3）跨设备链路聚合MEC（Multichassis EtherChannel）。MEC能够将位于两台不同交换机中的端口聚合为Port Group，统一为一个逻辑的interface，从而绕过了STP的问题。在VSS拓扑结构中使用MEC，所有链路都是激活的，在提供拓扑结构高可用的同时也提高了链路的利用率。

4）双主监测机制。当VSL故障后将出现“Split Brain”的情况，两台物理设备都将成为主用交换机，为防止转发上的混乱，VSS采用以下机制进行监控：如果有跨设备链路聚合时，使用PAgP来互相检测通知，如果有多余的接口，则可以单独拉根直连线路，通过VSLP Fast Hello专门用作监控。另外，VSS使用IP BFD通过互联的三层链路进行监控。监测到双主出现后，立刻禁用原来备用交换机的端口，直到VSL链路恢复。

虽然VSS只允许对两台设备做虚拟化，但是通过逻辑设备间的互联仍可进一步简化网络拓扑。在图1-3中，汇聚层和核心层均使用了VSS技术，如虚线框里的整合后的逻辑设备，使整个网络形成一个天然无环的拓扑结构。

图1-3　使用VSS整合3-Tier网络拓扑

1.2.2　Juniper VC与H3C IRF

VC（Virtual Chassis）是Juniper的虚拟机框技术。VC能够支持10台设备的虚拟化，可通过专用的VCP端口或者以太网口进行互联，VC设备间运行VCCP作为控制信令，VCCP会发现VC设备间的组网拓扑，然后进行选主，再通过SPF算法计算数据流量在VC设备间的传输路径，当VC中某台设备Down掉后能够进行快速重路由。主控的平滑切换和不中断转发通过GRES/NSR来实现。

IRF 2是H3C的虚拟机框技术，技术特点上和VC基本类似。IRF 2的前身是IRF 1，IRF 1只能支持盒式设备的多虚一，而IRF 2同时支持盒式和框式设备的多虚一。IRF 2能支持4台框式交换机或者9台盒式交换机的虚拟化，通过专用的IRF-Port或者以太网口进行互联，IRF设备间运行IRF Hello来发现拓扑，然后进行选主，再通过SPF算法计算数据流量在IRF设备间的传输路径，IRF某台设备Down掉后能够进行快速重路由，通过LACP/BFD来监测双主/多主。主控的平滑切换和不中断转发使用GR/NSF技术实现。

1.3　高级STP欺骗——跨设备链路聚合

STP会严重浪费链路资源，根源在于它会禁止冗余链路上的转发。如果通过一种办法来“欺骗”STP，让它认为物理拓扑中没有冗余链路，那么就可以解决上述问题。实际上，Port Channel技术就是这么做的，一个Port Channel对STP只表现为一个逻辑上的Port Channel Interface，Port Channel中的物理端口并不直接表现在STP中。只要STP允许了Port Channel Interface的转发，那么Port Channel中的物理端口就都可以用于转发流量，具体选择哪个物理端口由该Port Channel的负载均衡机制来决定。不过在传统的Port Channel中，物理端口都必须在同一台物理设备上，甚至是同一块线卡上，无法实现跨设备的链路聚合，那么一旦设备挂了就没有办法了。为了解决这个问题，就需要更加高级的STP“欺骗”机制。

虚拟机框技术中实际上就用到了跨设备链路聚合，不过在实现上过于复杂，因此在虚拟机框后又出现了一类技术，它们不再为控制平面做集群，只保留了跨设备链路聚合的能力。这类技术以Cisco的vPC、Juniper的MC-LAG和Arista的M-LAG为代表。

1.3.1　Cisco vPC

Cisco在Nexus系列交换机中推出了vPC（virtual Port-Channel）特性。VSS是整机级别的虚拟化，vPC则是接口级的虚拟化，其实就是把VSS中的跨设备链路聚合MEC加强实现了。因此，在vPC中只需要同步链路聚合的相关信息即可，不需要对整机进行状态同步。vPC中的一些关键概念如图1-4所示，vPC Domain中由两台设备形成vPC Peer，彼此之间通过Peer-Link互联，通过CFS消息来同步控制信息，Peer-Keepalive Link可跨越三层进行状态监测，以防Peer Link失效后的“Split Brain”问题。交换机或主机双上联到vPC Peer，直接运行LACP即可形成vPC，单上联的交换机或主机称为Orphan Device，用于接入Orphan Device的端口称为Orphan Port。

图1-4　vPC中的一些关键概念

下面通过一个二层转发的实例来介绍vPC中的转发流程，图1-5a为ARP Request的处理，图1-5b为ARP Reply的处理。假设配置vPC后，SW1 port 11和SW2 port 21属于vPC 1，SW1 port12和SW2 port 22属于vPC 1。SW1通过port 11收到ARP Request，之后在Peer-Link和port 12上进行泛洪。SW2收到该Request后，判定port 21、port 22分别与SW1中的端口进行了vPC绑定，因此不会从这两个端口进行转发。之所以要通过Peer Link泛洪给SW2，是考虑到目的主机有可能是一台只连接到SW2，而未连接SW1的Orphan Device。这时必须先泛洪给SW2，再由SW2通过相关的Orphan Port送到目的主机上。同时SW1会通过CFS消息告诉SW2，ARP Request的源地址MAC_A是通过vPC 1接入的。当SW2通过port 22收到ARP Reply后，查找MAC地址表，得知目的地址为MAC_A的帧应该向vPC 1的成员端口转发，于是通过port 21转发给SW3，同时通过CFS告知SW1，ARP Reply的源地址MAC_B接在vPC 2上。之后的通信得以按照学习到的信息进行转发。

图1-5　vPC对二层流量的处理

图1-5　（续）

对外来看，实现vPC的设备还是两台设备，在管理上还是要分开配置，两台设备的转发实例、转发表等都是独立的。另外，vPC是一种2层的技术，在对接3层的时候可能会需要一些额外的机制，如在Cisco FabricPath中使用了vPC+去对接HSRP。

1.3.2　Juniper MC-LAG和Arista M-LAG

Juniper的跨设备链路技术叫作MC-LAG，两个设备间通过ICCP传输控制信令，实现MC-LAG端口信息以及MAC地址表的同步，两个设备间通过ICP-PL来转发流量。MC-LAG上单播的转发流程可参考vPC中的介绍，主要就是通过ICP-PL泛洪ARP Request，通过ICCP同步源MAC地址。MC-LAG可支持主备和双活两种转发模式。Juniper的Fushion Data Center即采用了MC-LAG，可以支持两台Aggregation（汇聚层设备）以及多台Satellite（接入层设备）间的多路径。

Arista也有个类似的技术叫作M-LAG，是Arista实现L2的支撑性技术。M-LAG的工作原理和vPC、MC-LAG基本一样，这里不再赘述。M-LAG只能支持双活的转发模式。

1.4　变革3-Tier——向Leaf-Spine演进

传统数据中心是Web服务器的聚集地，流量多从Internet而来访问Web服务，这些流量通常被称为数据中心的“南北向流量”。与“南北向流量”对应的是发生在数据中心内部服务器间的“东西向流量”，不过这部分流量是很少的，对于传统的数据中心来说，南北向流量可以占据到80%。

2005年前后，亚马逊开始推广AWS，从此IT基础架构进入云计算时代，传统数据中心开始向云数据中心转型，虚拟化技术得到了高速的发展。IT应用架构方面，企业应用逐步从单体架构完成了向“Web-App-DB”架构的转型，分布式技术在谷歌公开“三驾马车”后开始盛行。这些技术趋势给数据中心带来的直观变化，是虚拟机数量几何级的增长，而从深一些层次上来看，则是虚拟机间的分工则越来越精细。从网络的视角来看，在分工变得精细后，原来很多在服务器中内部消化的流量都跑到了网络上，再加上虚拟机迁移、业务备份等流量的增长，东西向流量开始取代南北向流量，在数据中心占据主导地位，比例可达70%以上。

流量模型的转变对于数据中心的网络架构提出了全新的要求。

首先，东西向流量的爆发意味着数据中心内大部分的流量都将发生在服务器之间，在3-Tier网络架构中，这类流量的处理需要经过层层的设备，导致流量的通信时延较长，而不同服务器间的通信路径很有可能是不同的，从而又导致了时延的不可预测性，这两个问题对于数据中心的一些关键应用（如大数据）是不可接受的。

其次，随着虚拟化技术得到普遍的应用，物理的位置变得无关紧要，跨机架的流量在统计上的分布趋于更加均匀，3-Tier网络中通常所采用的10∶1、6∶1这种大收敛比将使得跨机架流量在上联口被大量地阻塞，降低网络的通信效率。因此，传统的3-Tier网络架构并不适合用于东西向流量的传输。

虚拟机框和跨设备链路聚合技术虽然解决了STP的问题，但是它们并没有改变3-Tier的架构，而且在扩展性上也非常受限。解决东西向流量的问题，必须从3-Tier架构本身入手进行变革，数据中心网络开始向扁平化的、无阻塞的网络架构进行演进。

针对于此，一些大型OTT开始在其数据中心中构建一种名为Leaf-Spine的网络架构，如图1-6所示。相比于3-Tier，Leaf-Spine拿掉了核心层，实现了层次的扁平化，Leaf负责所有的接入，Spine只负责在Leaf间进行高速传输，网络中任意两个服务器都是Leaf-Spine-Leaf三跳可达的。Leaf和Spine间是Full-Mesh的，即两个Leaf间可以通过任意一个Spine进行中继，Leaf通过ECMP可以将不同的流量分散到不同的Spine上进行负载均衡，如果一个Spine挂掉了，那么原来经过这个Spine转发的流量可以迅速地切换到其他Spine上。Leaf和Spine均可以使用COTS交换机，如果接入端口不够用了就直接在Leaf层新接交换机，如果Leaf间的带宽不够用了就直接在Spine层新接交换机。这不仅降低了系统的构建成本，还极大地提高了网络的可扩展性。

做一个简单的图形变换就可以发现，Leaf-Spine就是对CLOS进行了折叠（图1-6即相当于3-Stage CLOS），因此Leaf-Spine在结构上是无阻塞的。在所有端口速率一致的情况下，如果能够使用Leaf中一半的端口来上联Spine，那么理论上就可以得到1∶1的收敛比。不过考虑到成本问题，实际情况中能做到3∶1到2∶1之间，就可以近似地认为能够支撑无阻塞转发了。

图1-6　Leaf-Spine组网（3-Stage CLOS）

如果3-Stage的Leaf-Spine仍然无法满足其网络在扩展性方面的需求，此时可采用5-Stage的Leaf-Spine结构，如图1-7所示，每个POD都是一个3-Stage的Leaf-Spine，不同的POD通过Core交换机进行互联，跨POD的流量都是Leaf-Spine-Core-Spine-Leaf五跳可达，因此Core也可以看作Super Spine。Spine与Core间做Full-Mesh的连接，如果POD不够用了就加POD，如果POD间带宽不够用了就加Core。目前，5-Stage的Leaf-Spine所能够支持的端口数，足以支持单数据中心对组网容量的需求，一般情况下3-Stage Leaf-Spine就足够用了，只有一些超大规模的数据中心才会使用到5-Stage Leaf-Spine。

图1-7　Leaf-Spine组网（5-Stage CLOS）

图1-8是Facebook数据中心网络的立体结构，每个POD中48台Leaf（图中的Rack Switch），4台Spine（图中的Fabric Switch），POD间通过Core（图中的Spine Switch）互联。图1-9是与图1-8等效的平面结构，可以清楚地看到，这是一种典型的5-Stage Leaf-Spine结构。Facebook在Core层中使用了Fat-Tree的设计，将5-Stage Leaf-Spine中的每个Core层的交换机都替换为由48个Spine Switch组成的Spine Plane，以尽可能地提高Spine-Core间的收敛比。

图1-8　Facebook 5-Stage Leaf-Spine（立体结构）

图1-9　Facebook 5-Stage Leaf-Spine（平面结构）

1.5　初识大二层

虽然Leaf-Spine为无阻塞传输提供了拓扑的基础，但是还需有配套合适的转发协议才能完全发挥出拓扑的能力。STP的设计哲学与Leaf-Spine完全就是不相容的，冗余链路得不到利用，灵活性和扩展性极差，废除STP而转向“大二层”成了业界的基本共识。

说到大二层的“大”，首先体现在物理范围上。虚拟机迁移是云数据中心的刚性需求，由于一些License与MAC地址是绑定的，迁移前后虚拟机的MAC地址最好不变，同时为了保证业务连续，迁移前后虚拟机的IP地址也不可以变化。迁移的位置是由众多资源因素综合决定的，网络必须支持虚拟机迁移到任何位置，并能够保持位于同一个二层网络中。所以大二层要大到横贯整个数据中心网络，甚至是在多个数据中心之间。

大二层的“大”，还意味着业务支撑能力的提升。随着公有云的普及，“多租户”成了云数据中心网络的基础能力。而传统二层网络中，VLAN最多支持的租户数量为4096，当租户间IP地址重叠的时候，规划和配置起来也比较麻烦，因此VLAN不能很好地支撑公有云业务的飞速发展。同理，对于大规模私有云而言，VLAN也难以胜任其对于网络虚拟化提出的要求。

“大二层”的提出有些年头了，至今仍然是网络圈子内的热词。为了实现“大二层”，数据中心的网络技术在近十年间经历了不断的迭代与优化，主要包括二层多路径、数据中心二层互联、端到端Overlay隧道等几大类技术，一些产商还推出了私有的大二层解决方案，本章后面会花上相当多的篇幅对它们进行介绍。

1.6　插叙——虚拟机的接入

云数据中心最基本的诉求就是为用户提供虚拟机。为了尽可能地提高CPU、内存的利用率，一台物理服务器中往往运行着数十台虚拟机。接入是虚拟机联网的第一跳，做不好接入，网络架构的任何优势都将无从谈起。因此在开始介绍大二层技术之前，本节先来介绍虚拟机的接入。

1.6.1　VEB

虚拟机工作在服务器内部，那么自然而然的想法就是虚拟机间的通信也在服务器内部进行处理，做法就是在HyperVisor中加一个虚拟交换机VEB（Virtual Ethernet Bridge），通过虚拟端口关联虚拟机，用于虚拟机的接入和本地交换。VEB会通过一块服务器物理网卡上联硬件交换机，如果通信目标不在本地，就交给上游的硬件交换机处理。上游的交换机也感知不到它是个软件交换机，因此在处理上与传统的以太网没有任何区别。

商用的VEB产品，如VMware的VDS、Cisco的Nexus 1000V，已经较为成熟。不过，由于VEB是基于通用x86工作的，因此对于一些复杂网络功能的处理，VEB在性能上与基于ASIC的硬件交换机有着明显的差距。通过多占用一些CPU可以或多或少地弥补这个差距，但是这意味着服务器中能够给虚拟机分配的CPU资源就变少了，会有些得不偿失。

解决问题的一种思路是，VEB只保留接入端口和一些简化的转发功能，复杂的接入策略则转移给上游物理交换机。除了能够提升性能，这种思路在当时还有另外一个非技术方面的驱动因素——网络复杂的功能分离到了服务器外面，使得服务器团队和网络团队的管理边界变得十分明确，避免了运维中很多不必要的麻烦。其代表技术为Cisco的VN-TAG和HP提出的VEPA。

1.6.2　Cisco VN-TAG

VN-TAG是Cisco的私有标准，利用一个全新的标签实现了完整的数据中心接入方案，主要包括VN-Link技术和FEX技术。VN-Link部署在服务器的交换组件上，负责虚拟机的接入；而FEX部署在接入层的物理交换机上，负责虚拟机间的互通。图1-10是VN-TAG的封装格式，6个字节的新字段被插入到VLAN的前面，其中DVIF_ID/SVIF_ID是目的/源虚拟机被分配的唯一标识，各有12位，在Port Profile中会将VIF_ID与虚拟机接入端口进行一对一的通道绑定，VN-TAG物理交换机将根据VIF_ID来识别虚拟机。其他的标志位用于FEX系统中，D位标识报文的走向，P位标识报文是否需要复制，L位标识源主机和目的主机是否连接在同一台物理交换机上。

图1-10　VN-TAG的封装格式

VN-TAG的通信流程概括如下：服务器中的交换组件不进行MAC寻址，它接收源虚拟机的流量，封装好VN-TAG（标记SVI_ID），然后直接交给上游的物理交换机，上游的交换机完成SVI_ID对源MAC地址、VLAN和入端口的学习，根据目的MAC地址标记DVI_ID，然后转发给目标服务器，由目标服务器根据DVI_ID进行通道转发，剥掉VN-TAG后转发给目标虚拟机。VN-TAG的通信示例如图1-11所示。

在VN-TAG的架构中，VN-Link技术负责在服务器内部接入虚拟机，FEX技术负责从服务器上联接入交换机。VN-Link由支持VN-TAG的网卡实现，该网卡只负责VN-TAG的封装/解封装，不做任何寻址或策略相关的工作。FEX由Cisco N2K+N5K的组合实现，其中N5K负责寻址转发和策略的制定，N2K则作为N5K的远端板卡部署在ToR实现分布式接入。N2K和N5K间通过SDP（Satellite Discovery Protocol）协议完成发现，N2K通过SRP（Satellite Registration Protocol）协议向N5K进行注册，N5K通过VIC（Virtual Interface Cornfiguration）协议控制N2K上的转发。

VN-TAG的目标是将数据中心的接入网络虚拟成一个大的接入交换机，由VN-Link充当网线，由N2K充当分布式线卡，N5K充当主控板，处于任何物理位置的虚拟机都好像连接在这个大的接入交换机上。N2K与N5K间的网络连接具有高带宽、无阻塞和低时延等特性，保证了分布式接入的性能。在N5K上可以基于虚拟机对应的SVI_ID/DVI_ID制定ACL、QoS和流控等高级接入策略，网络策略能够随虚拟机进行任意的迁移。

图1-11　VN-TAG的通信示例

Juniper Fushion在转发机制的设计上和VN-TAG有些类似，Satellite交换机收到入向流量后并不进行转发，而是插入E-TAG Header标记入端口的ECID，然后交给Aggregation交换机。Aggregation交换机对流量源的<MAC，ECID>进行学习，并重新封装E-TAG Header，根据目的MAC地址标记出端口ECID，然后送到目的所在的Satellite交换机上，Satellite交换机将根据ECID映射出端口，并直接进行转发。另外，华为有一个类似于FEX的技术叫作SVF，CE6800相当于N5K做接入、转发决策，CE5800相当于N2K作为远端板卡进行分布式接入、转发，SVF整合CE5800/6800形成一台大的虚拟接入交换机。

作为网络厂商，Cisco的想法自然是把网络延伸到服务器当中去，于是研发了新的芯片支持VN-TAG，“顺便”集成到了UCS刀片服务器中。如果用户想用VN-TAG，就需要买Cisco的刀片。作为服务器的大玩家，HP的想法自然和Cisco是不一样的，于是就拉起了VEPA的阵营作为对VN-TAG的回应。

1.6.3　VEPA

VEPA（Virtual Ethernet Port Aggregator，802.1Qbg）是HP提出的虚拟机接入方案，其设计目标就是尽量避免服务器上的大幅度修改。前面提到，无论是VN-TAG还是VEPA，都是为了解决VEB消耗大量服务器资源的问题。HP没办法搞定芯片来支持VN-TAG这样的新字段，做不了加法就只好在原有的基础上动脑筋。VEPA对以太网帧格式没有任何的修改，而是对以太网转发规则进行巧妙的修改，同样卸载了服务器的负担。

802.1Qbg中，服务器中的VEPA组件间与虚拟机间运行VDP（Virtual Station Interface Discovery Protocol）协议，以识别虚拟机接入位置，同时支持对虚拟机迁移的感知。标准VEPA中，服务器的VEPA组件对于在虚拟机端口接收到的流量不做任何处理，一律传给上游的VEPA物理交换机。上游交换机正常做自学习，其改动之处在于VEPA修改了STP以允许入端口泛洪/转发，如果发现目的MAC地址在同一台服务器中，就再交给该服务器的VEPA组件。VEPA组件对从上游交换机收到的流量进行MAC寻址转发，完成虚拟机间的通信。这个过程如图1-12所示，本地流量被强行绕弯通过上游交换机，俗称“发卡弯”（harpin），简单却十分有效。

图1-12　VEPA的hairpin

VEPA的优势在于不需要对芯片做改动，交换机和服务器网卡的软件做升级后就能支持VEPA了。但是，简单也往往意味着功能较弱。由于不带任何新的标签，所有流量都混在一起，上游VEPA交换机上的控制策略就很难做了。增强型VEPA（802.1Qbg Multi-Channel）在标准VEPA的基础上使用了QinQ的S-TAG来标识虚拟机，在Multi-Channel中，部分S-TAG的流量可以在本地直接进行转发，而不用hairpin。另外，对于广播/组播流量的处理，VEPA也做了一定的完善，允许在合理的位置进行复制，而VN-TAG只允许在N2K上进行复制。

抛开一些细节不谈，Multi-Channel与VN-TAG并没有本质上的区别，服务器为上行流量打标记，上游交换机做寻址、定策略，下行流量服务器上做通道转发。相比于VN-TAG使用FEX作为分布式接入技术，Multi-Channel使用的是QinQ，不过S-TAG无法在传统的以太网设备上透传，只能在第一跳VEPA交换机终结，因此在接入规模方面会受到一定的限制。

1.6.4　VEB性能优化

前面提到过，VN-TAG和VEPA的设计在很大程度上是为了解决服务器团队和网络团队的管理定界问题。不过后来随着以OpenvSwitch为代表的开源VEB在OpenStack大潮中迅速成熟，业界看到了VEB在自动化与灵活性上无与伦比的优势，因此VEB在虚拟机接入这一块又形成了绝对的优势，VN-TAG和VEPA的技术标准之争还没打响也就匆匆结束了，而VEB性能方面的问题则再次被搬上了台面。

VEB的性能优化是个很大的话题，大体而言可以分为两个方向：一是IO，二是转发通道。其中，涉及大量x86体系架构和内核领域的知识，超越了本书的覆盖范围，这里只能粗略地对这两个方向进行介绍。

IO的优化，又大致可以分为“虚拟机IO优化”以及“物理网卡IO优化”两个方面。传统的VEB通常工作在内核态，而虚拟机则运行在用户态，这意味着在“虚拟机–虚拟机”以及“虚拟机–物理网卡”间进行IO时，每个包都需要进行多次的空间切换与数据拷贝，构成了VEB IO瓶颈的主要来源，因此“虚拟机IO优化”以及“物理网卡IO优化”都是围绕着这一问题所展开的。“虚拟机IO优化”主要涉及虚拟化的技术，其演化主线为“全虚拟化—virtio—vhost-net—vhost-user”，目前来看最理想的方式是将VEB从内核态完全转移到用户态，从而实现虚拟机与VEB间IO的零切换和零拷贝。“物理网卡IO优化”主要涉及内核旁路的技术，包括PF_RING、Netmap、DPDK等，允许用户态VEB绕过低效的内核协议栈直接访问网卡的缓冲区，实现物理网卡与用户态VEB间的零拷贝。另外，Intel的VT-d+SR-IOV允许在虚拟机与物理网卡间实现IO直通，这种方式性能最好，但由于绕过了VEB，因此无法提供复杂的接入功能。不过用户态VEB也可以通过SR-IOV与物理网卡实现IO直通，再加上通过vhost-user与虚拟机间实现的零切换、零拷贝IO，即可获得相当可观的IO性能。

转发通道的优化，目标是提高L2/L3转发的效率。数据包通过IO输入VEB后，转发通道上主要会经历包头解析、表项查找、动作执行三个阶段，其中以表项查找的开销最大，通常的做法是优化表项的数据结构以及相应的查找算法，并合理地构建Cache。不过，无论是内核态VEB还是用户态VEB，即使转发通道上的结构和算法设计得再为精巧，转发的过程也不可避免地会消耗掉大量的CPU，减少了虚拟机可用的CPU资源。前面提到过，VN-TAG和VEPA的思路是复杂的转发功能交给物理网络去实现，但是损失掉了VEB的自动化和灵活性，业界最近流行起来的思路是保留VEB的全部能力，在此基础上通过服务器网卡对VEB进行硬件加速。这种网卡被称为SmartNIC，它需要具备非常灵活的能力，因此通常会使用更多的DRAM，一些实现中还要借助于FGPA。SmartNIC的加速机制，是通过配套的软件去同步VEB中的转发规则与状态，然后更新到网卡的硬件中，于是SmartNIC即可代替VEB行使其转发功能，遇到无法处理的数据包再交给VEB进行分析，因此实际上可以把SmartNIC看作VEB的硬件Cache。SmartNIC目前虽然还没有大规模的使用，不过在一些大型OTT数据中心内部已经有了成功落地的案例，SmartNIC未来的发展值得期待。

1.7　消除STP——Underlay L2MP

前一节插叙了虚拟机的接入技术，本节将回到之前的话题上，开始对大二层技术展开介绍。跨设备链路聚合技术中，能够聚合的设备数量较为有限，限制了网络的规模。而且，跨设备链路聚合在本质上只是欺骗了STP，并没有在根本上消除STP所带来的问题，因此需要按照一定的结构进行连接，在组网的灵活性上也比较受限。实现大二层的思路，自然就是直接摆脱掉STP，根除它所带来的链路利用率低下的痼疾，实现二层的多路径传输。

二层的多路径传输，说学术界叫做L2MP，业界更多地称其为L2 Fabric技术。标准的L2 Fabric技术主要以TRILL和SPB为代表，厂商私有的Fabric技术主要有Cisco的FabricPath、Juniper的QFabric以及Brocade的VCS。

1.7.1　TRILL

TRILL（Transparent Interconnection of Lots of Links，RFC 6325）是一种把三层的链路状态路由技术应用于二层流量传输的协议。TRILL为二层网络添加了基于IS-IS路由协议的控制平面，这种基于路由的寻址方式使得二层网络的转发变得更加智能，同时也使得二层网络摆脱了STP的束缚，获得了高效性和可扩展性。

图1-13是TRILL的标准封装格式。TRILL的封装在本质上是一种路由封装，它的寻址发生在网络层，因此不妨将TRILL比对着IP路由来看。TRILL设备被称作RB（Routing Bridge），可类比IP路由器。外层以太网的DA/SA为Egress/Ingress RB的MAC地址，在转发过程中逐跳重写。TRILL Header可类比为IP Header，其中Ingress/Egress Nickname唯一标识了Egress/Ingress RB的网络层地址，类比于源/目的IP地址，是TRILL进行寻址的依据，M位用于BUM流量的转发，Hop Count类似于TTL，用于环路避免。

TRILL控制平面的工作可概括如下：首先在RB间建立邻居，生成RB拓扑，然后生成RB Nickname路由表，这些都是通过对IS-IS进行扩展来完成的。可通过ESADI协议维护虚拟机MAC地址和其所在RB的Nickname间的映射关系。

图1-13　TRILL的标准封装格式

数据平面转发流程可概括如下：收到虚拟机的原始帧后，Ingress RB为Original Frame封装TRILL header，根据原始帧的目的MAC地址标记Egress Nickname，并根据Egress Nickname路由给下一跳的Transit RB，Transit RB继续逐跳路由到Egress RB，最后Egress RB剥掉外层以太网和TRILL Header，根据原始帧的目的MAC地址进行转发。TRILL域中，单播流量匹配的是RB Nickname路由表，组播流量匹配的是组播分发树MDT。图1-14给出了TRILL域中，终端A向终端C发送单播的完整通信流程，和IP的路由过程基本上是一致的，只不过网络层用的是RB Nickname。

TRILL是独立于IP的网络层协议，是一套庞大的协议集。除了上述的转发过程外，还规范了RB间点对点的PPP封装、Nickname的动态配置协议DNCP（类似于DHCP）、Nickname的解析机制（类似于ARP）、RB发现机制TRILL Hello、Nickname的单播路由协议TRILL IS-IS、多路径负载均衡机制、MAC地址分发协议ESADI、组播分发树计算、路径MTU探测机制等。TRILL还考虑了RB间通过以太网交换机互联可能产生的种种问题，支持TRILL RB与以太网交换机的混合组网。

TRILL的技术细节是没有办法通过一节内容都说清楚的，下面列出RFC 6325中的一些技术点，供读者参考。

·RB的MAC地址学习分为本地VM学习和远端VM学习。本地学习机制同传统二层自学习，维护（VLAN，MAC，Local Port）的转发表。远端VM学习维护（VLAN，MAC，Remote RB Nickname）的转发表，可以通过很多方式进行学习，必选的方式包括两种：①同传统二层自学习，解封装后学习内层源MAC地址和外层源Nickname的映射关系；②通过ESADI（End-Station Address Distribution Information）协议进行学习。

图1-14　TRILL对单播的转发

·ESADI协议封装在TRILL的标准数据帧（TRILL Data）中，包含了RB Nickname和该RB本地的MAC地址信息。它使用All-ESADI-RBridges Multicast Address作为内层的MAC地址，在所有参与ESADI学习的RB中进行组播，能够透传以太网设备。ESADI支持对VM迁移的感知。

·RB的Nickname与MAC地址的解析通过TRILL Hello实现。

·RB之间可以通过点对点进行互联，也可以通过以太网进行点对多点的互联。通过以太网互联的RB间需要选举出一个DRB，负责确定以太网中TRILL Hello、TRILL IS-IS、TRILL Data和普通以太网帧所使用的VLAN，并确定一个Appointed VLAN-X Forwarder。Appointed VLAN-X Forwarder负责避免以太网上的环路，监听STP BPDU、IGMP等以太网信令，生成、转发ESADI消息等功能。

·TRILL Data帧在以太网上传输时，外层帧中的VLAN ID应该与内层原始帧的VLAN ID保持一致。TRILL的控制信令在以太网上分配的VLAN应该使用较高的优先级。

·基于哈希，支持16条路径的ECMP。

·整个TRILL域只计算一棵以某个RB为根的分发树，其余RB间的通信都依赖于这棵树。可以通过IGMP、MLD或者MRD进行per VLAN MDT Optimization。

·不支持头端复制的伪广播。

·有两种防环机制：一种是Hop Decrement；另一种是RPF，用于避免分发树中的环路。

·规定了两种新的IS-IS消息用于路径MTU发现——MTU-probe和MTU-ack。

标准的TRILL使用VLAN作为租户的标签，在租户数量上受到了很大的限制。因此，TRILL工作组在RFC 7172中提出了对TRILL中VLAN标签的扩展技术FGL（Fine-Grained Labelling），其封装格式如图1-15所示，Inner Label High Part和Inner Label Low Part中的后12位，合起来提供了1600万的租户数量。

图1-15　TRILL FGL对VLAN的扩展

由于TRILL在封装中使用了新字段，就避免不了要设计新的芯片来提供支持，而且TRILL增加了路由的报头，因此路径上每一跳RB都需要使用TRILL专用的设备，而且TRILL的OAM机制不是很成熟，这也是TRILL最大的问题。TRILL在2010～2014年间被讨论得比较多，一些大型客户（比如石油和银行）在私有的数据中心做了落地。不过TRILL的市场窗口比较短，VxLAN发展起来之后（见1.12节），TRILL的热度就迅速降下来了，目前厂商方面基本上就只剩下华为还在支撑TRILL的演进。

1.7.2　SPB

SPB（Shortest Path Bridging，802.1aq）常指SPBM，由PBB（Provider Backbone Bridging 802.1ah）进化而来。SPB和PBB的封装完全相同，都属于MACinMAC的隧道技术，两者主要区别在于PBB的转发是通过STP和自学习完成的，而SPB为以太网引入了控制平面，通过IS-IS学习转发信息。PBB多用于运营商城域网，SPB设计之初是为了解决PBB中STP收敛太慢、链路利用率低下的问题。数据中心内部的大二层网络要解决的是相同的问题，看准了TRILL逐跳都需要使用专用设备的问题，而SPB使用的MACinMAC只需要对边缘交换机做改动，因此一些厂商（当时主要是Avaya）便将SPB推向了数据中心。

图1-16给出了SPBM MACinMAC的帧格式。B-SA和B-DA为外层的B-MAC地址，B-TAG是底层承载以太网中的传输标识，包括传输的VLAN和优先级，底层网络即通过B-DA和B-TAG来转发流量。I-Tag是服务实例标签，PCP和DEI代表传输和丢弃的优先级，I-SID是24位的业务标识，能够提供1600万的租户数量，C-DA和C-SA是原始帧的MAC地址。除了SPBM以外，SPB还有另外一种模式SPBV。这种模式与802.1ad类似，是一种QinQ的VLAN标签栈技术，很少用于数据中心内部，下面主要对SPBM模式进行介绍。

图1-16　SPBM采用了IEEE802.1ah的帧格式

SPB控制平面上ISIS的工作可概括为：建立邻居，生成拓扑，然后根据最短路径算法生成B-MAC转发表。数据平面上，设备可分为BEB（Backbone Edge Bridge）和BCB（Backbone Core Bridge）两种角色，通过B-MAC地址进行唯一的标识，BEB负责业务接入，而BCB负责BEB间的传输。入口的BEB进行MAC自学习，并根据C-DA来封装外层报头。如果为BUM流量，则进行泛洪；如果为已知单播流量，则将B-DA标记为C-DA所在BEB的B-MAC，并查找B-MAC转发表传给下一跳的BCB。BCB根据B-DA逐跳转发到出口的BEB，出口的BEB进行解封装，完成MAC地址学习，再根据C-DA进行转发。

SPB有一套十分复杂的控制机制，不过相比于TRILL，SPB由于使用MAC进行寻址，不用设计新的网络层协议，因此相对来说要简单一些。下面列出RFC 6329中提到的一些主要的技术点，供读者参考。

·SPB的地址学习机制如下：BEB上的本地C-MAC转发表（C-VLAN，C-MAC，Port），远端C-MAC转发表（I-SID，C-MAC，B-MAC）都是通过自学习获得的，而BCB上的B-MAC转发表（B-VLAN，B-MAC，Port）通过IS-IS学习。

·B-VLAN与I-SID不是一一映射的，多个租户实例可以映射到同一个B-VLAN中。

·基于ECT（Equal Cost Tree），支持16条路径的ECMP。

·SPB的组播通过SPT实现，每个BEB都会以自己为根计算一棵最短路径树SPT，而不是像TRILL一样全网一棵分发树MDT。

·SPB中组播流量的B-DA格式如图1-17所示，其中SPSrc标识了SPT的根即入口BEB，而I-SID的低24位标识了租户特定的组播组。

·支持头端复制的伪广播。

·支持IS-IS多拓扑，允许针对不同的业务为链路分配不同的metric。

·兼容现有以太网OAM机制，如802.1ag和Y.1731。

图1-17　SPB中组播流量的B-DA格式

SPB经常被作为L2 Fabric技术拿来和TRILL作比较，不过SPB实际上也体现了很多Overlay的技术基因。相比于TRILL需要更换设备SPB只需对设备软件进行升级，利旧性很好；相比VxLAN的MACinUDP，SPB的MACinMAC也并没有本质的区别，不过SPB在数据中心却并没有能够获得VxLAN的地位。从技术角度来说，SPB的Overlay基因并不纯正，仍然需要对硬件交换机的软件做升级，而且Underlay上的运维也要求熟悉SPB的工作机制，因此SPB的部署门槛要比VxLAN高很多。从市场角度来说，SPB对vSwitch在数据中心的地位并未给予足够的重视，而且运营商的技术堆栈在数据中心接受起来也较为困难。随着Ayava的收购和破产风波，SPB基本上也已经退出了数据中心网络的舞台。

1.8　Cisco私有的大二层——FabricPath

Cisco于2010年推出了FabricPath，在业界普遍的说法中，FabricPath是对TRILL的升级，但实际上除了控制平面都是用的ISIS协议外，FabricPath的实现和TRILL区别很大，起码从封装上来看两者就是完全不一样的，本节就来介绍FabricPath的实现。

1.8.1　整体设计

如图1-18所示，FabricPath采用的是MACinMAC的封装，对外层的MAC地址有明确的编址规则，外层的MAC地址主要包含三个信息——SID、SSID和LID。其中：12bit的SID是一个FabricPath域内交换机的唯一标识，8bit的SSID是vPC+Port Channel在vPC+Domain中的编号（不使用vPC+时SSID默认为0），16bit的LID是MAC地址接入的物理端口号，因此在FabricPath中即可将目的地所在的位置表示为SID.SSID.LID。U/L bit表示MAC地址是否为IEEE定义的标准MAC地址，由于FabricPath自定义了外层的MAC地址，因此大部分情况下（已知单播）该bit都会被置位。I/G bit标识数据帧是否为广播帧，如果是广播帧则置位。OOO（Out Of Order）bit可以用于数据包乱序后的重排。FabricPath的以太网类型是0x8903，通过10bit的Ftag来标识多拓扑，以便为不同的VLAN构造不同的BUM转发树，TTL默认初始值为32并逐跳递减用于防环。内层MAC头中，802.1Q字段会被保留，用于VLAN隔离和基于COS的QoS机制。FabricPath也支持TRILL的FGL扩展，可以通过两个VLAN ID来形成24位的w/Segment-ID，以支持更多的租户数量。

图1-18　FabricPath封装格式

对比TRILL可以发现，FabricPath使用的是完全不同的封装，因此在实现的各个方面都和TRILL有着较为明显的差别（见图1-19）。TRILL中所有RBridge的角色都是一样的，endpoint的MAC地址会分布在所有的RBridge上，虽然TRILL Header是End-to-End的，但是外层的MAC头是Hop-by-Hop的。而FabricPath中交换机分为Leaf和Spine两种角色，endpoint的MAC地址只存在于Leaf上而不会存在于Spine上，外层的MAC头即为FabricPath Header，是End-to-End的。因此，单从封装上来看，FabricPath倒是和SPB更类似一些。

图1-19　FabricPath与TRILL的对比

FabricPath将交换机分为两类：一类是Ingress/Egress交换机（Leaf）；另一类是Core交换机（Spine）。Spine上所有的端口，以及Leaf上与Spine相连的端口都需要工作在fabricpath mode下，如果两个Leaf存在vPC Peer的关系，那么Leaf间用作Peer-Link的端口也需要工作在fabricpath mode下。Leaf上用于接入的端口工作在CE（Classical Ethernet）模式下。

控制平面上，FabricPath支持使用DRAP（Dynamic Resource Allocation Protocol）协议来自动分配SID和Ftag。SID和Ftag是FabricPath中最为关键的两个参数，分别用于已知单播和BUM的转发。一个Ftag对应物理网络的一个子拓扑，一个Ftag子拓扑可以对应多个VLAN，这些VLAN的泛洪和广播都将基于这个Ftag子拓扑来完成，这和MSTP实现的功能是类似的。SID标识着一个FabricPath交换机，一个交换机的SID在任何的Ftag子拓扑中都是不变的。DRAP能够自动地分配SID和Ftag，并且保证在一个FabricPath域内SID和Ftag的唯一性，SID是随机生成的，而Ftag是从1开始递增的。

除了DRAP以外，FabricPath在Underlay使用了ISIS，主要用于物理拓扑发现，形成SID转发表以及BUM转发表。Overlay中的MAC地址学习，FabricPath采用的是一种“基于会话”的自学习机制，能够有效地减少交换机上的MAC表项。下面来看数据包在FabricPath中具体的转发流程，来对上面提到的机制进行具体的理解。

1.8.2　控制与转发过程分析

现考虑以下拓扑：3台Leaf的SID分别为100/200/300，4台Spine的SID分别为10/20/30/40，现有两台主机MAC A和MAC B分别接在S 100和S 300下。建立好拓扑之后，ISIS发现拓扑，DRAP分配好SID和Ftag，ISIS通过SPF算法计算出SID转发表（Fabric-Path Routing Table），通过Root选举形成BUM转发表（Multidestination Tree），此时系统开始进入转发状态。

（1）对ARP Request的处理流程

图1-20给出了FabricPath对ARP Request的处理流程。

图1-20　FabricPath对ARP Request的处理流程

1）VLAN 10（图中未标出）中的A广播ARP Request，请求B的MAC地址。

2）S 100学习<VLAN 10，MAC A>与e1/13的对应关系，记录在MAC Table中。

3）S 100发现这是一个广播包，通过哈希选择了Ftag=1的子拓扑，并在该子拓扑形成的BUM转发树上进行转发。查Ftag转发表可知S 100上对应于该树的端口只有PO10，于是S 100封装外层包头，目的MAC为全F，使用SID.SSID.LID=100.0.12来生成源MAC地址（e1/13在S 100上的LID为12），标记Ftag，并将数据包从PO10发给S 10。

4）S 10从PO 100收到该数据包，发现它是一个广播包，其Ftag=1，查对应的BUM转发表可知本地对应于该树的端口有PO100、PO200和PO300，S 10将数据包的TTL减1，然后从PO 200和PO 300转发数据包。

5）S 300从PO 10收到数据包，发现它是一个广播包，其Ftag=1，查对应的BUM转发表可知本地对应于该树的端口有PO 10、PO 20、PO 30和PO 40，S 300将数据包的TTL减1，然后从PO 20、PO 30和PO 40转发数据包。

6）S 300发现本地有位于VLAN 10的CE端口，去掉外层包头后在这些CE端口上进行泛洪，于是B就收到了A的ARP请求。

这里需要注意的一点是，该ARP广播包并不会触发S 300上的MAC地址学习，因为FabricPath采用的是“基于会话”的MAC地址学习机制，只有内层目的MAC是已知的单播地址，才会对源MAC地址进行学习，保证学习到的MAC地址在接下来的一段时间内都会是活跃的。而在传统的MAC自学习机制中，凡是没有看见过的源MAC地址交换机都会进行学习，这意味着所有非静默主机的MAC地址会在所有的交换机上存在（假设均未老化），无论交换机是否存在于真实的MAC转发路径上。因此，FabricPath可以有效地减少Leaf交换机上无用的MAC地址表项。而对于Spine（如S 10）来说，它们永远看不到内层的MAC地址，其转发逻辑就是根据外层包头解析出SID/Ftag，然后查找ISIS已经计算好的SID转发表/BUM转发表进行转发，也就是说，Spine是不需要对任何endpoint的MAC地址进行学习的。

（2）对ARP Reply的处理流程

图1-21给出了Fabric Path对ARP Reply的处理流程。

1）B向A单播ARP Reply。

2）S 300学习<VLAN 10，MAC B>与e2/29的对应关系，记录在MAC Table中，S 300还发现这是个目的未知的单播包（由于之前S 300并没有学习A的MAC地址）。

3）S 200通过哈希选择了Ftag=1的子拓扑，并在该子拓扑形成的BUM转发树上进行转发。查Ftag转发表可知S 100上对应于该树的端口只有PO10、PO20、PO30和PO40，于是S 100封装外层包头，目的MAC置为周知的FabricPath Unknown Unicast地址01：0F：FF：C1：01：C0，使用SID.SSID.LID=300.0.64来生成源MAC地址（e2/29在S 200上的LID为64），标记Ftag，并将数据包从PO 10、PO 20、PO 30和PO 40发出。

4）S 10从PO 300收到数据包，发现它是目的未知的单播包，其Ftag=1，查对应的BUM转发表可知本地对应于该树的端口有PO 100、PO 200和PO 300，S 10将数据包的TTL减1，然后从PO 100和PO 200转发数据包。

5）S 100从PO 10收到数据包，发现它是未知的单播包，其Ftag=1，查对应的BUM转发表可知本地对应于该树的端口只有PO 10，说明S 100已经是Ftag=1的子拓扑的Leaf节点了。

6）由于本地有位于VLAN 10的CE端口，因此S 100去掉外层包头，查找MAC Table发现目的MAC地址是已知的单播地址MAC A，于是对源MAC地址进行学习，并通过e1/13进行转发。

图1-21　FabricPath对ARP Reply的处理流程

这里需要注意的是，S 100在学习远端的MAC B时，使用的是<VLAN，MAC>：SID.SSID.LID这种数据结构，即<VLAN 10，MAC B>：300.0.64，后续A向B发出的单播包外层的目的MAC地址就会通过300.0.64来生成。

（3）已知会话的单播处理流程

接下来，A开始向B发送单播的数据包，如图1-22所示。

1）A开始向B发送单播的数据包。

2）S 100查找MAC Table，解析出MAC B对应的远端地址300.0.64。

3）S 100查找SID转发表，得知SID=300的交换机可以通过PO 10、PO 20、PO 30和PO 40进行转发，通过哈希后选择了PO 30转发该数据包，通过300.0.64生成外层目的MAC，通过100.0.12生成外层源MAC，标记Ftag=1。

4）S 30从PO 100收到数据包，发现这是个目的地已知的单播包，通过外层目的MAC解析出SID，查找SID转发表并从PO 300转发数据包。

5）S 300通过PO 30收到数据包，发现这是个目的地已知的单播包，通过外层目的MAC解析出SID，发现目的地即在本地，再通过外层目的MAC解析出LID=64，然后去掉外层包头转发给B，同时对源MAC地址进行学习。

图1-22　FabricPath对已建立会话的流量的处理流程

这里要注意的是，在S 300上既可以通过LID关联到e2/29进行转发，也可以通过查找MAC Table直接获得B所在的端口e2/29进行转发。另外有些资料中也提到，FabricPath也在考虑通过集成MP-BGP来增强Overlay的控制平面，这里就不进行详细的介绍了。

上述就是FabricPath典型的转发流程。关于“基于会话”的MAC地址学习，图1-23更为清晰地给出了FabricPath“基于会话”的MAC地址学习的工作机制。

图1-23　FabricPath“基于会话”的MAC地址学习的工作机制

1.8.3　其他技术细节

另外，FabricPath还有一些其他的技术点，下面总结出几点供读者参考。

·FabricPath不支持点对多点的连接，不可以用普通的以太网交换机连接多个处于fabricpath mode下的端口。

·可以支持到16路的ECMP，可以通过L2～L4层字段的组合进行哈希。

·VLAN子树的修剪可以通过广播GM-LSP（Group Membership LSP）来完成。

·通过监听IGMP来支持IP Multicast，提供RPF机制防止生成环路。

·主机或者普通以太网交换机可以通过vPC+以多活的方式接入FabricPath，设备只需要支持标准的Port-Channel技术即可实现vPC+。vPC和vPC的主要区别是Peer-Link上的端口必须配置为fabricpath mode，并且还需要配置一个虚拟的SID，MAC地址学习时即学习该虚拟的SID，以便于实现负载均衡。

·Leaf是Pure L2，Routing需要在Spine上开启SVI来实现。开启SVI后，Spine需要学习各个VLAN中的MAC地址，对硬件资源要求很高。此时可以插入M1/M2 module，使用VDC虚出来一个新的控制平面，并通过Proxy L2 Learning将Routing的功能卸载到M1/M2 module上去。

·可以使用vPC+在FabricPath中实现双活的Gateway，也可以使用Anycast HSRP支持N路多活的Gateway。在这两种方式下，L3 GW的MAC地址均可以看作通过虚拟的SID接入到FabricPath中。

·FabricPath对于STP来说相当于一个Bridge，所有的CE端口上均以c84c.75fa.60xx（xx为FabricPath的Domain号）作为Bridge ID发送BDPU。除了TCN BDPU以外，CE端口收到其他BPDU时不会在FabricPath内进行扩散。在收到TCN后，则需要通过ISIS在FabricPath内进行扩散，以更新MAC的转发信息。如果一个FabricPath域通过多个CE端口与同一个STP Domain连接，FabricPath还需要将TCN消息扩散到该STP Domain其余的部分中去。

·支持ISIS LSP OverLoad，计算SPF时会绕过相应的交换机。一个应用场景是，在新交换机上线时，将该交换机发出的LSP报文置位overload bit，直到该交换机完成收敛后复位，防止在收敛过程中出现路由黑洞。

·支持通过BFD进行状态监测。

FabricPath主要实现在Cisco Nexus 5K和7K系列的交换机中，与面向SDN的Nexus 9K目前属于两条独立的产品线。如果想从FabricPath升级为ACI（见4.2节），就需要把5K和7K都换掉。由于FabricPath已经有了很多的部署，因此Cisco目前在数据中心还是两条腿走路，不过FabricPath已经不再是Cisco主推的解决方案了。

1.9　Juniper私有的大二层——QFabric

QFabric是Juniper于2011年推出的一款数据中心网络架构，号称使用了当时最为先进的技术理念，引入了集中式控制，并使用了EVPN的雏形协议，能够为超过6000个接入端口提供所谓“1-Tier”的互联。不过，当时有如此大规模数据中心的客户本身就比较稀少，屈指可数的几个目标客户当时都开始走上了数据中心网络的自研之路，而且基于Leaf-Spine的开放式CLOS架构开始大行其道，因此市场上的表现没有达到技术和产品的预期。之后，Juniper也发现了这个问题，推出了面向中型企业的精简版QFabric。不过，在SDN兴起之后，Juniper就把数据中心的产品中心转移到了Contrail上，QFabric还未兴起就已衰落，精致的技术又一次败给了残酷的市场。

或许是因为技术理念太过于先进，Juniper对QFabric的实现讳莫如深，可参考的公开资料也非常少，不过业界对QFabric技术实现的猜测却从未停止过。在这一节中，通过对有限的资料进行整理，将尝试把QFabric尽可能多的技术特征展现出来，和读者一起领略一下J家的“黑科技”。

1.9.1　整体设计

QFabric设计的出发点是，对传统Chassis交换机进行解构，实现一个物理上完全分布式的CLOS架构。Chassis交换机有三个核心的组件：I/O Modules负责接入设备，集成了丰富的L2/L3功能；Fabric负责I/O Modules间背板的高速互联，功能简单但是转发性能十分强大；Route Engine负责处理网络的控制信令。与之类似，QFabric也有三个组件：负责边缘接入的QFabric Node，相当于Chassis交换机的I/O Modules；负责核心互联的QFabric Interconnect，相当于Chassis交换机的Fabric；负责网络管理与控制的QFabric Director，相当于Chassis交换机的Route Engine。图1-24为QFabric与传统Chassis交换机的对比。

图1-24　QFabric与传统Chassis交换机的对比

在Chassis交换机中，I/O Modules间是通过Fabric的插槽和走线互联的，Route Engine也是通过Fabric（或者以太网）控制I/O Modules的。受限于功耗、散热、信号串扰等问题，Chassis交换机能够提供的容量是比较有限的，即使是通过虚拟机框等技术实现了Chassis的多虚一，系统的容量有所提升，但是其架构上的可扩展性仍然是受限的。QFabric通过将Node、Interconnect和Director间物理解耦，可以规避Chassis硬件实现上的限制，从而解决了Chassis难于扩展的问题。Node和Interconnect间通过40GE以太网互联，这张网（图1-25）用于传输实际的业务流量，在QFabric中被称为FTE（Fabric Transport Ethernet），Director通过一张独立的以太网管理、控制各个Node和Interconnect，这张网在QFabric中被称为CPE（Control Plane Ethernet）。实际上，以现在的技术视角来看QFabric，简单地说，就是Leaf-Spine的Underlay Fabric再加上Out-of-Band的集中式控制。以当时的视角来看，QFabric的技术可以说的确是非常超前了。

图1-25　QFabric的组网结构

QFabric一个比较亮眼的关键词就是“1-Tier”，字面上的意思是将网络从Core-Aggregation-Access三层扁平化成了一层，提供any-to-any的连接，于是就有了图1-26所示的架构。实际上这个词和这张图都十分具有迷惑性，感觉上像是Node之间做了全互联，但实际上如前面所说，QFabric也是Leaf-Spine架构，Node间还是要通过Interconnect做中继的。对于“1-Tier”这个词的理解应该是，Interconnect上不需要维护复杂的host路由，只做简单的tag-based的转发，也就没有了下一跳的概念，Node和Node间就像是直连一样，因此称为“1-Tier”。

QFabric中，Node被分为以下三种角色，如图1-27和图1-28所示。

·SNG（Server Node Group）指的是设备通过LAG接入同一台Node，这台Node即被称为SNG。

·RSNG（Redundant Server Node Group）指的是设备通过LAG接入到两台不同的Node上，这两台Node被称为一个RSNG，RSNG即是类似于vPC Domain的一个概念。

·NWNG（NetWork Node Group）指的是与外界路由器相连的一组Node，一个NNG中最多可以有8台Node。一个QFabric系统中SNG/RSNG可以有多个，而NWNG只能有1个。

图1-26　QFabric所提出的“1-Tier”架构

图1-27　QFabric中的SNG和RSNG

图1-28　QFabric中的NWNG

这里需要说明的是，Node中控制平面RE（Routing Engine）和转发平面PFE（Packet Forwarding Engine）是分离的，当然对于Interconnect来说也是一样的。SNG/RSNG中Node的RE上运行着与主机相关的协议栈，如ARP/MAC Learning/IGMP/LACP等，以及QFabric私有的基于IS-IS的拓扑发现和早期的EVPN雏形协议（下面会有详细介绍），而不运行路由控制协议。RSNG中两个Node的RE为一主一备。NWNG中Node的RE被禁用了，NWNG的控制逻辑运行在Director Group中的NWNG-VM上，NWNG中的Node看到控制信令后会通过前面提到的CFE上报给Director Group中的NWNG-VM，NWNG-VM上除了运行着和SNG/RSNG RE相同的协议之外，还运行着OSPF、BGP等路由控制协议，以便和QFabric外界的路由器交换路由信息。Director Group中有一主一备两台NWNG-VM。

1.9.2　集中式的控制机制

Director是QFabric的中央管理/控制平面，在生产环境中需要多个Director组成一个Director Group以实现高可用。Director自身的实现是分布式的，每个Director中都有运行着多个VM，不同的VM上运行着不同的管理/控制逻辑，这些VM互相配合实现对QFabric的管理与控制。

作为中央管理平面，Director需要为QFabric中的各个组件提供统一的管理接口，包括CLI、SNMP、Syslog等，使这些接口的使用者认为QFabric是一台单一的Chassis交换机。为了实现这一点，Director在管理平面的实现上设计了图1-29所示中的架构：有多个运行着管理功能的VM，北向接口进来以后，Load Balancer会将北向的负载分担到不同的管理VM上去以实现高可用，南向上则通过Scatter将北向接口分发到相应的组件上去，通过Gather将不同组件的状态组合在一起。管理者登录一次Director，就可以直接看到各个Node、Interconnect的信息，能够为运维提供很大的便利。

Director中还有很多的VM运行着QFabric的中央控制逻辑，主要有如下3种。

·Fabric Manager。维护Node和Interconnect的设备清单，为Node和Interconnect分配PFE-ID，维护Node和Interconnect互联形成的网络拓扑。根据网络的拓扑，Fabric Manager会计算出Node间的转发路径以及Node-Node间不同转发路径的权值，并将上述计算结果告诉给Node和Interconnect的RE。Node和Interconnect的RE据此生成Node转发表，以实现tag-based forwarding。

·Fabric Control。在各个SNG RE、RSNG RE、NWNG-VM间处同步MAC路由和IP路由。类似于路由反射器的作用，对NWNG-VM进行的是全反射，对SNG RE/RSNG RE进行有选择地进行反射，只反射那些它们感兴趣的路由，从而减少SNG PFE和RSNG PFE中无用的表项，增强了系统的可扩展性。

·NWNG-VM。为NWNG运行控制逻辑，除了运行着和SNG/RSNG RE相同的协议以外，还运行着OSPF、BGP等路由控制协议，以便和QFabric外界的路由器交换路由信息。另外，NWNG-VM上还维护着VLAN Subscribing信息，用于生成per VLAN的BUM转发树，NWNG-VM会为每个per VLAN的BUM转发树分配Multicast Core Key，作为Tree ID，指导Node、Interconnect对BUM流量进行转发。

图1-29　QFabric Director的架构

这里就需要对QFabric中用到的私有控制协议进行一下介绍了，这些私有的控制协议是QFabric运行的基础，主要分为以下三类。

·VCCPD（Virtual Chassis Control Protocol Daemon）。通过ISIS扩展实现，运行在Out-of-Band的控制网络CPE上。Director、Node RE以及Interconnect RE都会周期性地发送VCCPD Hello，Director中的Fabric Manager即根据VCCPD来维护设备清单。Fabric Manager发现新的设备上线之后，会为该设备分配一个PFE-ID。

·VCCPDf（VCCPD over fabric links）。通过ISIS扩展实现，和VCCPD非常类似，不过VCCPDf运行在Node和Interconnect间的FTE网络上，用于Node和Interconnect间FTE链路的发现。Fabric Manager发现设备后，为其分配一个PFE-ID，然后在FTE端口上使能VCCPDf，发现链路上线后会将该链路信息告诉给Fabric Manager，Fabric Manager通过链路信息形成网络的拓扑。

·FCP（Fabric Control Protocol）。通过MPBGP扩展实现，Fabric Control会与SNG RE、RSNG RE、NWNG-VM建立BGP邻居，然后通过FCP进行MAC、IP路由的收集与反射，运行在Out-of-Band的控制网络CPE上。通过RD可以支持host地址的overlap。

1.9.3　控制与转发过程分析

QFabric的技术架构实际上采用的还是Juniper一贯的路数——BGP/MPLS VPN，Node可以看作PE，Interconnect可以看作P，Director可以看作RR。在控制面上，QFabric为BGP扩展了MAC的地址族，并且还可以传32位的主机IP路由，QFabric的这种设计即为IETF EVPN控制平面的技术原型。在数据面上，QFabric在L2 Header前面封装了私有的Fabric Header。根据公开的资料无法得知Fabric Header具体的封装格式，不过可以确定的是Fabric Header是一个类似于MPLS Stack的结构。Ingress Node至少要向Fabric Header中压两个Label：外层Label为Egress Node的PFE-ID（类似于BGP/MPLS VPN中的transport label）；内层Label为Egress Node上的出端口的Port ID（类似于BGP/MPLS VPN中的service label）。另外，用于标识BUM转发树的Multicast Core Key也会包含在Fabric Header中。

1.控制机制

·通过VCCPD获得设备清单，通过VCCPDf获得Underlay拓扑，Director计算出Node转发表推送给Node和Interconnect。

·每当Node在本地发现一个新的VLAN/VRF时，就会通过FCP告诉给Director，以订阅该VLAN/VRF的转发信息。Director中会记录订阅了某个VLAN/VRF的所有Node。

·Director会将VLAN的订阅信息通过FCP推送给Interconnect和相关的Node，Interconnect/Node会结合Node转发表中的信息，解析出相应的转发端口，形成BUM转发表，用于L2 BUM流量的处理。

·Host向其他host发送ARP时，Ingress Node完成MAC地址的学习并通过FCP告知给Director。Host向默认网关发送ARP时（证明它很可能会发L3的数据包），Ingress Node回复默认网关的MAC地址，并完成/32 IP地址的学习并通过FCP告知给Director。Director会通过FCP将这些MAC路由和/32 IP路由反射给订阅了相关VLAN/VRF的Node，以打通QFabric内部的L2/L3。

·Director与外界的路由器运行OSPF或者BGP等路由协议，学习外界的路由表。

·Director会将FCP学习到的MAC路由/IP路由，与通过路由协议学习到的外界路由，实现重分布，以打通QFabric与外界网络。

2.转发机制

·L2的已知单播包。Ingress Node收到数据包后查找MAC转发表，得到目的地所在的Egress Node的PFE-ID，以及其接入Egress Node的端口Port-ID，并据此封装Fabric Header，然后根据PFE-ID迭代查找Node转发表，选择一个端口送到Interconnect上；Interconnect会根据Fabric Header中外层的PFE-ID Label，查找Node转发表送给Egress Node，并剥掉PFE-ID Label（类似于倒数第二跳弹栈）；Egress Node收到数据包后剥掉Port-ID Label，得到本地的出端口并进行转发（具体的实现机制目前不得而知，可能是类似于Untag，即根据Port-ID直接转发，也可能是类似于Aggregate，即需要迭代查MAC转发表）。

·L2的BUM流量。Ingress Node收到数据包后，根据VLAN得到BUM转发树的Multicast Core Key，并通过Multicast Core Key迭代查找BUM转发表获得相关的端口，对于本地端口就直接发送，对于FTE端口则将Multicast Core Key的信息封装进Fabric Header，然后送到Interconnect上；Interconnect会根据Fabric Header中的Multicast Core Key信息，查找BUM转发表，并送给相关的Node；Egress Node收到数据包后，查找BUM转发表，并从本地相关的端口发送出去。

·L3的已知单播包。Node上有相关VLAN的默认网关，可实现分布式路由。Ingress Node收到数据包后查找IP转发表，得到目的地所在的Egress Node的PFE-ID以及其接入Egress Node的端口Port-ID，TTL减1并封装Fabric Header，然后根据PFE-ID迭代查找Node转发表，选择一个端口送到Interconnect上；Interconnect会根据Fabric Header中外层的PFE-ID Label，查找Node转发表送给Egress Node，并剥掉PFE-ID Label（类似于倒数第二跳弹栈）；Egress Node收到数据包后剥掉Port-ID Label，得到本地的出端口进行转发（具体的实现机制目前不得而知，可能是类似于MPLS VPN中的Untag，即根据Port-ID直接转发，也可能是类似于MPLS VPN中的Aggregate，即需要迭代查IP转发表），并完成MAC地址的改写。

·L3的未知单播包。假设L3数据包的源host属于VLAN 1，目的host属于VLAN 2，而目的host之前是静默的，因此Node A上的IP转发表中没有目的host的信息。此时，Node A需要主动去解析目的host的ARP，不过Node A没有订阅VLAN 2，也就没有VLAN 2的BUM转发表，因此Node A自身无法完成后续的处理。于是，Node A会封装Fabric Header将该数据包单播给NWNG中的Node C，Node C再通过Out-of-Band的CPE网络上报给Director中的NWNG-VM。NWNG-VM收到该数据包后，发现处理该数据包需要解析目的host的ARP，NWNG-VM上有所有VLAN的订阅信息，于是NWNG-VM在本地生成相应的ARP Request，并通过CPE网络向所有订阅了VLAN 2的Node泛洪这个ARP Request。目的host收到后回复ARP Reply，目的host所在的Node B看到这个ARP Reply后，会通过FCP将目的host的/32 IP路由同步给Node A。之后，源host和目的host间的通信就变成了L3的已知单播，处理流程同上。

·L3的组播没有看到公开的资料。

上述就是对QFabric技术架构的介绍。在2011年的时间点来看，QFabric的技术在当时确实有其独到之处，特别是Director所实现的中央式控制，而且QFabric在分布式与集中式间也做了很好的平衡，保证了良好的可扩展性。看起来最亮眼的“1-Tier”，实际上就是一种基于Tag转发的思路，FabricPath中也采用了类似的思路。另外，QFabric中所使用的控制协议，构成了EVPN的雏形，为EVPN后来的发展奠定了直接的基础。关于EVPN的详细介绍，请参见1.14节。

1.10　Brocade私有的大二层——VCS

Brocade是做SAN出身的，在该领域长期占据着超过70%的市场份额。Brocade于2008年收购了交换路由厂商Foundry Networks，开始进军以太网/IP领域。2010年前后，Brocade推出了自己的数据中心Fabric技术VCS（Virtual Cluster Switch），旨在做一款扁平的、智能的、能够良好融合SAN的以太网架构。

1.10.1　整体设计

在1.8节中，曾提到FabricPath的控制面和TRILL有很多类似，但是其数据面没有用TRILL的封装。而VCS的思路恰好和FabricPath相反，在数据平面上保留了TRILL的封装，控制平面却没有用TRILL。TRILL在前面已经介绍过，简单来说就是引入了RBridge Nickname来标识交换机，通过ESADI学习MAC和RBridge Nickname的映射关系，通过ISIS来计算转发路径，Ingress封装端到端的RB Header，然后通过外层的MAC逐跳传输。VCS使用了TRILL的封装，这里不再做重复的介绍，而且与TRILL相同的是，VCS在转发上是拓扑无关的，因此只区分端口角色，而对交换机的角色并不做区分。控制平面上，鉴于Brocade在SAN方面深厚的技术基因，VCS大量借用了Fiber Channel（见本书10.2节）的控制协议，下面就来看看VCS控制平面的设计。VCS中的一些关键概念如图1-30所示。

图1-30　VCS中的一些关键概念

1.10.2　控制与转发过程分析

VDX交换机可以在VCS和Standalone两种模式下工作。当在VCS模式下工作时，交换机上所有的端口上开始跑BLDP（Brocade Link Discovery Protocol），BLDP首先会判断链路两端的交换机是否均为VDX交换机，如果两侧的VDX交换机属于同一个VCS域，就认为协商成功，并将链路两侧的端口均置为fabric port，链路建立为ISL[1]。如果协商失败，则相关的端口被置为edge port。交换机所属的VCS域由VFID来表示。每个交换机只能属于一个VFID，VFID可以由管理员配置，如果不做配置，则默认为1。

有了ISL后，两侧的交换机上开始协商ISL Trunk，ISL Trunk相当于Port Channel，不过VCS中ISL Trunk的协商没有用LACP，用的也是Brocade的私有协议。ISL Trunk中不同的ISL间是通过DWRR（Deficit Weight Round Robin）来进行逐帧的负载均衡，然后由ASIC来保证in-order delivery。相比于Port Channel上基于哈希的负载均衡，DWRR的效果要好很多，不会出现一条ISL有流量，而同属于一个ISL Trunk的另一条ISL上没有流量的情况。

之后，VCS开始进行Fabric Formation。首先，VCS需要保证每个交换机都被分配到一个RBridge ID，为此整个VCS域会选举出一个PS（Principle Switch），PS先为自己分配一个RBridge ID，再通过DIA和RDI逐层地为其他交换机分配RBridge ID。当所有的交换机都有了RBridge ID之后，Fabric Formation就可以进入路由协议的运行阶段了。相比于TRILL中使用ISIS，VCS选择了FC中的路由协议光纤最短路径优先（Fiber Shortest Path First，FSPF）。上述的控制协议和机制都是借鉴于FC，更多关于FC的介绍请参考本书10.2节。

对于已知的单播流量，各个交换机会通过FSPF形成RBridge ID转发表，能够支持到8路ECMP。这里需要说明的是，为了能够尽可能多地利用链路资源，VCS中链路的cost并不是根据带宽计算出来的，而是通过速率计算得到的，比如对于一个3*10GE的ISL Trunk来说，它的带宽是30G，但是速率仍然是10Gbps，因此这个ISL Trunk和10GE的ISL会被FSPF认为具有相同的cost，这使得10GE的ISL上也能够承载流量。为了实现均衡，上述30Gbps的ISL Trunk和10Gbps的ISL权值会被设为3：1。ECMP上是通过L2～L4字段的哈希来分布流量的，这一点和ISL Trunk上通过DWRR逐帧分布流量是不同的。对于BUM流量，FSPF会为VCS域计算出一棵唯一的BUM树，所有的BUM流量都会通过该树进行转发，这和TRILL中的做法是一致的。BUM树上的转发即采用的是TRILL的组播封装，转发过程中会有RPF的检查。

FSPF收敛之后，各个交换机还会通过JMP（Join/Merge Protocol）来同步VCS域内一些配置信息，比如AMPP、Zone等。Zone也是FC的概念，类似于以太网中的VLAN，用于流量的隔离。JMP完成之后，Fabric Formation结束，VCS上面就可以开始跑流量了。

MAC地址学习，VCS没有使用TRILL中的ESADI，而是使用了一种叫作eNS（ethernet Name Server）的MAC分发服务。这个名字也带有鲜明的FC特征，至于其内部的实现是否使用了FC中的协议就不得而知了。对于VCS来说，L2的转发表格式为<VLAN，MAC>：<RBridge ID，Interface>，交换机会学习本地的L2转发信息，远端的L2转发信息即通过eNS来同步。eNS的工作机制如图1-31所示，图中有三个RBridge（RB）和两台主机，MAC-A向MAC-B发起通信，首先发送ARP Request，RB-1本地完成对A的L2学习，然后将这个广播包在BUM树上泛洪，RB-2和RB-3都学习到了A在远端的L2转发信息（不包括A在RB-1上的接入端口）。B收到ARP Request之后单播回复ARP Reply，RB-3在本地完成对B的L2学习，RB-1学习到了B在远端的L2转发信息（不包括B在RB-3上的接入端口），而RB-2此时没有办法学到B的L2转发信息，因为ARP Reply的单播包会直接发给RB-1。然后，eNS开始同步L2转发信息，图中标记灰色底纹部分的信息即是由eNS完成的同步。当然，eNS还会负责L2转发表的老化以及MAC地址移动后的状态更新。

图1-31　eNS的工作机制

对于L3的通信，由于eNS本身是不会处理IP地址同步的，因此需要借助虚拟网关+FHRP来实现的。VCS中虚拟网关被称为VE，和SVI是一个概念，可以完成路由的功能。FHRP方面，VCS可以支持3种，分别为VRRP、VRRP-E和FVG。其中，VRRP-E和FVG是Brocade私有的协议，采用的是类似于FabricPath中Anycast HSRP的做法，可以实现N路多活的网关，解决了VRRP中Standby不会承载流量的问题。

[1] 这里所说的ISL是FC里面的概念，指的是两个交换机间的链路，不是指Cisco私有的ISL。

1.10.3　其他技术细节

上述就是对于VCS的基本介绍。下面再列出一些其他的技术点供读者参考。

·VCS中RBridge ID的取值范围是1～239，也就是说，一个VCS域内交换机的数量不可以超过240。限于实现上的原因，通常一个VCS域内不会超过24台交换机。

·VCS可以通过Virtual Fabric来支持TRILL FGL，理论上可以提供1600万的租户数量，不过现有的VCS只能支持到8000。

·Fabric port间的互联只能支持点对点互联。

·ISL Trunk上DWRR的in-oder delivery可以由ASIC来保证，交换机上ISL Trunk中的fabric port必须在同一块ASIC上，这也是ECMP没有办法使用DWRR而只能使用哈希的原因之一。

·IGMP组播树也要附着于FSPF计算出来的BUM树，可以根据IGMP Snooping的结果对BUM树进行修剪。

·VCS通过Brocade私有的vLAG技术来跨链路聚合，vLAG中最多可以支持4个RBridge，vLAG的实现和FabricPath中的vPC+很类似，即在RBridge后面虚拟出来一个vRBridge，vRBridge会参加路由的计算，vLAG下面的MAC地址都关联到这个vRBridge上。

·可以通过AMPP提供Port Profile，为MAC绑定如下几类策略：VLAN、FCoE、QoS和ACL。当endpoinrt发生移动时，通过AMPP可以自动进行策略的跟随。

·VCS希望对外提供的是TLS（Transparent LAN Service），因此VCS会将STP BPDU看作data plane的流量，通过Fabric进行透传。交换机在Edge Port上收到STP BPDU后，会通过BUM树将STP BPDU泛洪到其他的交换机上，然后这些交换机会将BDPU从自己相应的Edge Ports送出。VCS会为传送BDPU这部分流量预留出Fabric中的VLAN 4095。

作为后来者，Brocade的IP业务并没有能够对市场造成足够的冲击，不过VCS却经常被拿来与FabricPath和QFabric进行比较，本节对VCS的技术进行了简单的介绍，希望能使读者对当时的三大私有Fabric技术有全面的了解。2016年下半年，Brocade开始寻求收购，旗下的业务随之拆解，VCS以及VDX系列交换机被Extreme以5500万美元收购，市场前景仍然不是很明朗。

1.11　跨越数据中心的二层——DCI优化

前几节谈了数据中心内部的大二层技术。为了实现高可用，数据中心需要进行灾备或者多活的部署，这就要求数据中心间也能够实现二层网络的互通，这种场景通常称为DCI（DC Interconnect）。DCI的实现方式有很多种，可以使用MSTP或者WDM等传输方案，也可以使用以VPLS为典型代表的MPLS L2VPN。VPLS能够将数据中心间的网络模拟成一个以太网交换机，可实现多点对多点的二层互联，这是早期主流的DCI技术。不过，VPLS在技术的实现上存在着以下缺陷。

·依赖于泛洪来学习MAC地址，会占用广域网链路上宝贵的带宽资源。

·STP和HSRP需要跨DC收敛，常常会导致出向路由次优。

·Full Mesh的PW隧道维护起来太复杂。

·对多宿主场景的支持不够，不支持PE双活与负载均衡。

这些缺陷使得VPLS难以实现真正意义上的大二层，因此业界出现了一些针对“大二层”进行优化的DCI技术，主要包括Cisco OTV、Huawei EVN以及H3C EVI。

1.11.1　Cisco OTV

OTV（Overlay Transport Virtualization）是Cisco提出的DCI技术，在Nexus 7K中提供了实现。OTV通过为传统的二层VPN增加控制平面来进行MAC地址学习和ARP代理，避免了不必要的跨Internet泛洪；通过在DC间隔离STP BDPU和HSRP Hello，实现了出向路由最优；通过动态封装建立无状态的隧道增强了可扩展性；能够自动完成对多宿主的探测，支持基于VLAN的负载分担和基于vPC的双活机制。

OTV数据平面的封装格式如图1-32所示，外层IP头后面跟着8字节的OTV Shim头，原始以太网帧中的VLAN header也被移到了Shim头中作为租户二层网络的标识，Shim头中还有一个Overlay ID，一个Overlay可以看作一个以太网，一个Overlay中可以传输多个VLAN的流量。

图1-32　OTV封装格式

OVT设备通过IGMP加入OTV组播组，在ED（OTV Edge Device）间建立邻居关系，使用IS-IS在邻居间学习（VLAN，MAC，Remote IP）来转发信息。考虑到Internet对IP组播的限制，OTV支持头端复制的伪广播机制模拟邻居间的3层组播，这种情况下就只能手工配置邻居了。一个OTV网络中典型的单播通信流程如图1-33所示，假设OTV邻居已经建立。

图1-33　一个OTV对单播的处理

VM 1发送ARP Request到ED 1上。ED 1学习MAC 1的本地接入端口，然后封装OTV Shim头通过data组播组发送到ED 2。同时，ED 1通过control组播组传输IS-IS LSP，告知ED 2 VM 1的位置信息。ED 2收到IS-IS更新，得知VM 1连接在ED 1上。ED 2收到data组播组的ARP请求后解封装，本地泛洪到VM 2。VM 2回复ARP Reply到ED 2上，ED 2学习MAC 2的本地连接端口，然后封装OTV Shim头封装好ED 1的IP地址单播给ED 1。同时，ED 2通过control组播组传输IS-IS LSP，告知ED 1 VM 2的位置信息。ED 1收到IS-IS更新，得知VM 2连接在ED 2上。ED 1收到携带ARP Reply的单播后，解封装转发给VM 1。

在上述过程中，ED一旦学习到IS-IS LSP，就会在control组播组中告诉邻居相应的信息，因此在VM跨数据中心迁移场景下，ED间能迅速得知VM的新位置，大大缩短了收敛的时间。除了上述外，OTV还具备以下特性。

·STP隔离。OTV将STP BPDU在ED上进行阻塞，避免了跨站点STP计算。由于站点间通过控制平面进行MAC学习，因此DC间不会出现环路，只要在每个DC站点内部运行生成树协议保证DC内无环即可。这种做法极大地提高了二层的收敛速度和扩展性。

·未知单播隔离。OTV禁止未知单播的跨站点广播，认为IS-IS能够学习到所有合法MAC地址的信息。针对静默主机，可以手工配置静态MAC地址对应远端OTV接口的表项。

·ARP代理。对远端站点返回的ARP回复进行监听，当再收到本地查询同样目的IP的ARP请求时直接代理回复，减少跨站点的泛洪。

·出向/入向路径优化。OTV在ED上阻塞HSRP Hello，避免站点A中的主机到站点B中网关进行路由所引发的次优路径问题。OTV设备支持健康路由注入和LISP（Locator/ID Separation Protocol，RFC 6830），以实现入向路径最优。

·多宿、负载分担和双活机制。ED设备间自动选举AED（Authority Edge Device）作为主设备转发流量，其他ED作为备份。ED间也可以基于VLAN进行AED选举，实现负载均衡。ED间还可以通过vPC进行跨设备链路聚合，以实现ED的双活。

1.11.2　HUAWEI EVN与H3C EVI

EVN（Ethernet Virtual Network）是华为参照EVPN（Ethernet VPN，RFC 7209）提出的DCI技术。其控制平面使用了BGP，数据平面仿照VxLAN用的是MACinUDP的封装。一些DCI的特性，比如STP/VRRP抑制、ARP代理、未知单播隔离等，当时也都是模仿的OTV。由于EVPN主要对多宿主部署做了详细的描述，因此相比于OTV，EVN主要加强了PE双活机制和MP2MP（Multipath to Multipath）机制。它细化了负载分担的策略粒度，支持PE多虚一的GEO Cluster模式。1.14节会专门对EVPN进行介绍，因此这里不再对EVN做过多的分析。

EVI（Ethernet Virtualization Interconnect）是华三的DCI技术，其控制平面上使用ENDP（EVI Neighbor Discovery Protocol）建立邻居，使用IS-IS进行地址学习，数据平面用了GRE的封装。EVI实现的特性主要包括ARP代理和未知流量抑制。没有对STP/VRRP这些的优化机制，多宿PE的负载均衡策略只能通过VLAN来做。EVI有一个特性是它会在邻居间选举出一个DIS做控制信息的分发，DIS类似于OSPF中的DR的角色，此外EVI还支持IS-IS虚拟系统来扩展LSP的分片数量，以增加系统所能发布的MAC地址数量。上述IS-IS+GRE的技术体系针对的是EVI 1.0，华三从EVI 2.0开始也放弃了EVI 1.0中的私有技术，走向了EVPN+VxLAN。

1.12　端到端的二层——NVo3的崛起

跨设备链路聚合、二层多路径、DCI，这些技术都是出于传统网络厂商之手，并没有摆脱“以盒子为中心”的设计思路。云计算的普及使得IT软件厂商获得了更为强势的地位：一方面，他们开始主导云数据中心网络设计的话语权，希望能够使“网络黑盒”变得更加开放，以摆脱对传统网络厂商的依赖。

VMware作为服务器虚拟化领域的老大，开始围绕Overlay的思路来打造基于软件的数据中心网络虚拟化方案，在服务器内部的虚拟交换机上就开始起二层的隧道，利用通用的物理IP网络作为Underlay承载隧道的传输，从而得以摆脱开专用的数据中心硬件交换机，直接在服务器间打通“端到端”的大二层。

上述思路实现起来不仅灵活而且门槛较低，其代表性技术VxLAN在云数据中心迅速普及，直接推动IETF在2014年成立了NVo3（Network Virtualization over Layer 3）工作组。NVo3主要面向数据中心网络虚拟化的场景，基于IP作为L3 Underlay，在其上通过隧道构建Overlay，支撑大规模的租户网络。NVo3的技术模型如图1-34所示，PE设备称为NVE（Network Virtualization Element），VN Context作为Tag标识租户网络并在租户间进行隔离，P设备即为普通的IP路由器。NVo3在设计之初，VxLAN与SDN的联合部署已经成了云数据中心的大趋势，VxLAN作为数据平面解耦租户网络和物理网络，SDN将租户的控制能力集成到云平台中与计算、存储联合调度。因此NVo3的模型中专门画出了NVA（Network Virtualization Authority）作为NVE设备的控制器负责隧道建立、地址学习等控制逻辑。

图1-34　NVo3的技术模型

NVo3的思路主要来源于VxLAN，并汲取了NvGRE和STT中的一些优良特性，未来将以Geneve为集大成者。下面分别对VxLAN、NvGRE、STT和Geneve进行介绍。

1.12.1　VxLAN

VxLAN（Virtual eXtensible LAN，RFC 7348）最早是由VMware和Arista主导联合提出的一种大二层技术，突破了VLAN ID只有4k的限制，通过IP网络进行二层隧道流量的传输，并借助IP网络的ECMP特性获得了多路径的扩展性。VxLAN是一种MACinUDP的隧道，图1-35是标准VxLAN的封装格式。

图1-35　标准VxLAN的封装格式

标准VxLAN（指的是RFC 7348）封装的是以太网帧，外层的报头可以为IPv4，也可以为IPv6。UDP Src Port是外层UDP的源端口号，它的值通过对内层的帧进行哈希得到，可实现传输的ECMP。Dst Port为VxLAN从IANA申请的端口4789。UDP checksum如果不为0，出口VTEP（VxLAN的NVE）必须进行计算，计算结果必须与该字段一致才能进行解封装，不一致则进行丢弃，checksum如果为0，则出口VTEP无条件解封装。VNI是VxLAN中的租户标识，长度为24位，理论上能够支持1600万的租户。Payload是原始的Ethernet帧，由于外层VxLAN报头中VNI已经唯一地标识了租户，考虑到安全因素，Inner VLAN在进行封装时一般会被去掉。

NVo3技术中，NVE上的地址学习包括两类：第一类是学习本地VM的MAC与port的映射关系；第二类是学习远端VM的MAC地址与该VM所在NVE的IP地址的映射关系。标准VxLAN的单播通信流程如图1-36所示，组播过程与此类似，只不过外层的目的IP地址被封装为相应VNI的组播地址。

图1-36　标准VxLAN的单播通信流程

首先，VTEP通过IGMP加入VNI 1的组播组，VNI 1中的BUM流量都将通过该组播组传输。虚拟机A与B间通信的具体转发流程如下：VM A发送ARP请求，VTEP 1学习VM A的本地连接端口，然后将该ARP请求进行VxLAN封装（VNI标记为1），然后向VNI 1的组播组中进行组播。VTEP 2收到后，学习VNI 1中A的MAC地址与VTEP 1的IP地址的映射关系，然后在本地进行泛洪。VMB收到该ARP请求后进行回复，由于VTEP 2已经完成了自学习，因此VTEP 2封装VxLAN后（VNI标记为1）将向VTEP 1进行单播，同时VTEP 2学习VM B的本地连接端口。同理，VTEP 1收到ARP回复后，在VN11中学习该VNI中MAC B与VTEP 2 IP地址的映射关系，然后再交给A。之后VMA和VMB间的数据流将通过单播在VETP 1和VTEP 2间传输。

标准VxLAN不允许VTEP接收IP分片，因此如果Ingress VTEP封装隧道后超过了物理网络的MTU就会被分片，Egress VTEP收到分片后很可能会直接丢弃，而VxLAN本身并没有MTU探测机制。因此，在使用VxLAN时要注意调整VM或物理网络中的MTU。

VxLAN通常用来在虚拟机间建立端到端的隧道，常常被部署在物理服务器的HyperVisor中，目前的vSwitch基本都提供了对VxLAN的支持。不过在Hypervisor中，VxLAN的封装会消耗很多的CPU，考虑到性能问题，很多生产环境会选择用硬件交换机来卸载VxLAN的封装，Hypervisor中的vSwitch只负责打VLAN上联硬件交换机。硬件的VxLAN交换机也被称为Hardware VTEP或者VxLAN L2 Gateway，主要的功能就是做VxLAN和VLAN间的转换，下联口既可以连接vSwitch做VxLAN卸载，也可以连接物理交换机实现VM与物理主机的互通。通常来说，一个VxLAN网络连接的主机都在一个子网中，如果租户需要多个子网可以对应到多个VxLAN网络，不同的VxLAN网络有不同的VNI。在这些子网间实现3层互通，依赖于VxLAN Routing，VxLAN L3 Gateway将终结源VM所在的VxLAN，路由后进入目的VM的VxLAN。

VxLAN核心的东西就这么多了，理解起来也并不复杂，就是通过隧道来模拟交换机间链路上的传输，转发的控制还是MAC自学习，需要泛洪的时候以组播进行模拟。可见，标准VxLAN的转发非常依赖于底层IP网络的组播（标准VxLAN并不支持头端复制的伪广播），这对设备和运维都提出了相当高的要求。

因此，基于标准VxLAN数据平面的封装格式，业界提出了一些对VxLAN控制平面的优化方案，比如通过分布式的MP-BGP主动地同步租户路由，或者通过集中式的SDN控制器（即图1-34中的NVA）对租户路由进行学习和分发，以减少VxLAN流量在IP网络上的组播。除了控制平面的优化外，目前也出现了一些对标准VxLAN数据封装的扩展，比如支持多协议payload的VxLAN-GPE（Generic Protocol Extension for VXLAN，draft-ietf-nvo3-vxlan-gpe），比如通过预留位实现策略控制的VxLAN-GBP（VXLAN Group Policy Option，draft-smith-vxlan-group-policy），还有一些厂商利用VxLAN预留位实现了服务链的功能。

1.12.2　NvGRE

NvGRE（Network virtualization GRE，RFC 7637）是微软提出来的数据中心虚拟化技术，用于其自家的Hyper-V中。如图1-37所示，NvGRE是一种MACinGRE隧道，它对传统的GRE报头进行了改造，增加了24位的VSID字段标识租户，而FlowID可以用于内层流量的精细化识别。由于去掉了GRE报头中的Checksum字段，因此NvGRE不支持校验和检验。NvGRE封装以太网帧，外层的IP头可以为IPv4也可以为IPv6。

图1-37　NvGRE封装格式

虽然是在VxLAN之后提出的技术，但NvGRE变得更简单了，控制平面并没有规定任何机制，就是靠NVA来指挥，是不折不扣的SDN方案。在细节上，NvGRE与VxLAN还是有一定区别的，比如不支持与VLAN Trunk的互通，使用Outer SRC IP+VSID+FlowID做ECMP（要求IP Router的硬件支持），支持头端复制的伪广播，RFC中探讨了MTU发现机制，等等。

1.12.3　STT

STT（Stateless Transport Tunnel，RFC Draft）是Nicira提出的数据中心虚拟化技术，是一种MACinTCP的隧道，其封装格式如图1-38所示。之所以设计STT，是因为希望利用网卡的TSO（TCP Segment Offload）功能在隧道两端进行分片以支持巨型帧的传输，提高端到端的通信效率。

虽然用到了TCP头，但是STT修改了里面的Seq字段和Ack字段的含义，不再用于重传和滑动窗口，是一种无状态的隧道。准确地说，STT使用的是一种类TCP的Header，只是为了伪装成TCP段来进行TSO。Src Port可用来做ECMP，DST Port为7471Ack用来标识STT帧，Seq的高16位用于标识STT帧的长度，低16位用于标识当前帧的偏移量。STT帧被分片后，各片的Ack和Seq的高16位均相同，而Seq的低16位各不相同，隧道出口设备上的网卡用之进行分片重组。TCP Flag和Urgent Pointer都不再具有意义。Version现为0，Flags标识了内层数据的类型与相关信息，L4 Offset标识了STT帧的末位到内层数据Layer 4（TCP/UDP）的偏移量，MSS为最大的分片长度，PCP为传输优先级，VLAN ID用于与VLAN网络互通，租户标识为64位的VN Context。STT的外层的报头可以为IPv4或者IPv6。

图1-38　STT封装格式

和NvGRE一样，STT也没有规定地址学习机制，同样要配合SDN控制器完成转发。STT支持与VLAN Trunk的互通，不支持伪广播，也没有内生的MTU发现机制，RFC中建议了对DSCP和ECN的支持。

STT有一个致命的缺陷——它的TCP是无连接的，并不进行三次握手，也没有对于状态的维护。虽然网卡做TSO时不关心这个问题，但防火墙、负载均衡器这些盒子可就不买账了，因此STT在实际部署时会受到很大的限制。Nicira被VMware收购后，STT在NSX中（在4.1节中会详细介绍）只被用来做Hyper-Hyper的隧道，打通物理网络还是要用VxLAN。在NSX最新的版本NSX-T中，STT已经被Geneve所取代，STT在IETF中Draft的状态也成了Expired，STT就此成为历史，不过其独特的技术思路还是给Geneve提供了一些借鉴。

1.12.4　Geneve

Geneve（Generic Network Virtualization Encapsulation，RFC Draft）是NVo3工作组对VxLAN、NvGRE和STT进行总结后提出的一种网络虚拟化技术，希望形成一种通用的封装格式，以便支持数据中心中隧道机制的后续演化。Geneve是于2015年提出的，仍处于草案阶段，不过从Draft中的设计来看，确实是博采众长，具备相当的潜力。目前，很多的vSwitch已经对Geneve提供了支持，一些SmartNIC也能够实现Geneve的硬件卸载。

Geneve采用了VxLAN的思路，是一种MACinUDP的隧道，其封装格式如图1-39所示。之所以没用MACinGRE或者MACinTCP，估计是工作组考虑到GRE头部的可扩展性比较差，不具备可演进的能力。而用TCP头的话，如果仍保持TCP的特性，则维护有连接的隧道开销过大；如果像STT一样处理为无状态的，那么又会存在Middle Box的穿越问题。相比之下，UDP头就没有那么多问题，Geneve作为一种应用层协议不存在可扩展性的问题，而UDP本身的无连接特性又不会带来开销和兼容的问题。至于分片，Geneve建议使用Path MTU Discovery（RFC 1191）来探测传输路径上的MTU。

图1-39　Geneve封装格式

Geneve封装的是以太网帧，外层的报头可以为IPv4也可以为IPv6。UDP Src Port常通过哈希获得，可用于ECMP；Dst Port为IANA分配给Geneve的6081。对于UDP Checksum，Geneve与VxLAN的处理办法相同，并建议使用NIC去做卸载。Geneve Header中的O位为OAM标识，当O位置1的时候，Geneve携带的为控制信令而非Ethernet payload，VNI为24位的租户标识。Geneve Header还提供了TLV格式的Options，以支持后续的功能扩展。

下面再来谈一些Geneve草案中除了封装格式以外的内容，借以简单地分析一下端到端NVo3隧道未来的演进思路。Geneve可采用头端复制的伪广播，不再要求IP Fabric具备组播的能力，降低要求的同时简化了运维的配置工作。Geneve希望底层IP Fabric的传输能够考虑到隧道传输的需求，这在NvGRE和STT的草案中也都提到过，Overlay应该映射出一些字段给Underlay做参考，而不是死守着对传输透明的原则不放。参考NvGRE的设计，Geneve希望可以将VNI作为ECMP的一个参考字段，未来TLV中的一些Option也可能会被用来细粒度地标识流量，以支持精细化的业务定制。参考STT的设计，Geneve讨论了NIC Offload问题，以提升传输性能。

1.13　新时代的开启——SDN入场

在上一节对于数据中心Overlay技术的介绍中，已经多次提到了SDN。SDN标准的全称为“Software Defined Networking”，即软件定义网络。在其架构中，网络被分为应用、控制和转发三层，转发设备为控制器提供编程接口，控制器为应用提供编程接口，通过编写应用就可以灵活地定义转发设备的行为。图1-40所示的架构图是老图了，不过放在这里很合适，避繁就简。

图1-40　SDN的三层架构

SDN起源于高校。2006年前后，斯坦福大学发起了一个名为Clean-State的项目研究未来网络的架构。2007年，项目组发表了SDN的雏形系统Ethane，旨在通过集中式的控制器来灵活地定义网络中的安全策略。2008年，OpenFlow面世，将转发和控制相分离，并定义了一套控制器和转发设备间交互的标准协议，使得控制器能够灵活、集中地控制设备的转发。OpenFlow的提出极大地降低了网络创新的门槛，SDN在学术界蔚然成风。

Google于2013年发表了B4，它使用OpenFlow在数据中心间进行流量调度，大幅度地提高了广域网的链路利用率。鉴于Google的影响力，B4的落地令OpenFlow名声大噪，也使得SDN首次走入公众的主流视野。不过，Google的工程经验向来是看得见却摸不着，没有任何个人或者公司能够做出一个“商用版B4”。

真正将SDN推向商用的是VMware，场景就是在云数据中心。Martin Casado、Nick Mckeown和Scott Shenker这三位公认的SDN创始人，早在2007年就一起创立了一家名为Nicira的公司，投身于数据中心SDN的产品化。Nicira具有卓越的技术远见和强大的工程能力，OpenFlow、OpenvSwitch、OVSDB和OpenStack Neutron均出自这家公司之手，基于这些开源技术栈，Nicira构建了一款名为NVP的数据中心SDN产品，其主要的设计思路即是通过SDN控制器自动化地管理、控制服务器中的vSwitch，并通过隧道构建租户的Overlay网络。NVP以纯软件来提供虚拟化网络，VMware看中了这一能力，于2012年以12.6亿美元收购了Nicira，并以NVP整合了原有的网络能力形成NSX。

传统客户的这笔天价收购向网络市场释放了明确的信号，一些创业公司开始投身于此，各大厂商也开始布局数据中心SDN。随着Cisco于2013年发布ACI回应NSX，数据中心SDN的商用竞赛正式开始。开源方面，OpenStack Neutron中ML2和neutron_ovs逐渐成熟，OpenDaylight社区也不断地、密切地跟进着数据中心网络。至此，新时代开启，数据中心网络进入SDN时代。

SDN到底是什么？SDN和传统网络有什么区别？SDN为什么受到数据中心青睐？SDN在数据中心里面是怎么用的？后面还有大量的章节来说SDN，这里就不急于展开了。本章的剩余几节将介绍传统派系的网络技术在SDN时代下的最新进展，然后再来说一说数据中心网络接入与融合的问题。

1.14　Overlay最新技术——EVPN

在前面介绍DCI的时候，曾简单地提到了华为的EVN方案。EVN的设计参照的是RFC 7209，RFC 7209的全称为“Requirements for Ethernet VPN（EVPN）”，提出了以太网VPN的技术要求，其诉求是增强VPLS的控制平面，主要优化的目标是ARP的跨站点泛洪以及CE的multi-homed。从这种角度来看，EVPN一词实际上是泛指以太网VPN这种业务场景，而并不指代什么特定类型的技术。不过，目前业界对于EVPN一词的理解是指，基于MP-BGP作为控制平面，来为以太网VPN分发MAC/IP路由的技术。

1.14.1　传统网络对SDN的反击

EVPN控制平面的思路起源于Juniper在2010年提给IETF的MAC-VPN（数据面是MPLS），之后Juniper将其用在了QFabric（数据面是私有的Fabric Header），不过当时Juniper的这两个动作并没有在业界引起什么反响。2012年，VMware NSX引爆了SDN的市场，OpenFlow/OVSDB和VxLAN在数据中心和传统网络展开了正面的交锋。面对着OpenFlow/OVSDB对传统盒子的冲击，各个厂家似乎放下了长久以来对于标准上的争执，在2013～2016这不到3年的时间内，包括Cisco、Juniper、Arista、阿朗、华为、中兴、华三在内的主流厂商，纷纷对BGP-VxLAN EVPN提供了支持，虽然互通性上目前还差得很远，不过能形成这种默契已经是实属罕见了。

EVPN不仅完成了守卫传统网络的使命，还在SDN领域又找到了反攻的突破口。为了实现可扩展性，可以为EVPN引入RR来做路由的反射。RR的部署方式比较多，可以部署在Spine上，可以部署在Leaf上，也可以专门用一台不跑流量的设备做独立的RR，独立的RR既可以是硬件的，也可以是软件的。既然可以用软件实现RR，厂商们就开始琢磨着把RR放到SDN控制器里面去，因此可以看到，目前一些厂商的SDN控制器实际上就是把他们的路由引擎重新包装了一下，然后通过MP-BGP来控制设备上的路由。2016年SDWAN的火热又将EVPN重新推向了数据中心边缘，同时具备L2和L3能力的EVPN几乎成了目前Overlay技术的万金油——“哪里不通点哪里”。

1.14.2　组网与数据模型

EVPN支持多种数据平面上的封装，如MPLS/PBB/VxLAN。本节将以VxLAN-Based EVPN为例进行介绍，而MPLS EVPN和PBB EVPN也是大同小异。作为BGP的骨灰级选手，Juniper直接奠定了EVPN的技术基础，因此下面就用Juniper的材料来对EVPN进行介绍。

图1-41是VxLAN EVPN的组网示意图，需要再次强调的一点是，EVPN最早是为了DC间的互联而设计的，后面才被应用到了DC内部的网络中。客户设备是host/VM（DC互联场景中是CE），边缘设备是Leaf（DC互联场景中是PE），核心设备是Spine（DC互联场景中是P）。控制平面的MP-BGP和数据平面的VxLAN跑在Leaf间，来处理Overlay的流量。而Spine主要负责Underlay的路由，在做RR的时候会跑MP-BGP，做网关的时候会启用VTEP。

图1-41　VxLAN EVPN的组网示意图

由于EVPN面对的是一个庞大的业务场景，因此其数据模型也比较复杂，在介绍具体技术之前，需要先搞清楚EVPN的数据模型。EVI（EVPN Instance）是指一个EVPN实例，一个EVI即可以看作一个以太网。Bridge Domain（BD）是指一个广播域，一个BD中有一个MAC VRF，不同BD的MAC VRF间是隔离的。可以看到，BD之于EVPN就像VLAN之于以太网，不过VLAN在EVPN中的概念却其与在以太网中有所不同，下一段中会对其进行详细的说明。ES（Ethernet Segment）是host/CE做multi-homed时所形成的一个区域，ES使用ESI进行标识。ES在EVPN中的作用难以在这里进行描述，后面会结合具体的场景来说。

EVPN的数据模型中，最难于理解的是EVI、BD和VLAN这几个概念间的关系，图1-42给出了清晰的解释。EVPN将三者的关系分为三种：①VLAN Based。在这种关系下EVI和BD是一一对应的，也就是说，一个EVPN实例中只有一个广播域，EVI/BD和VLAN也是一一对应的，即一个EVI/BD中只能塞一个客户侧的VLAN进去。②VLAN Bundle。在这种关系下EVI和BD仍然是一一对应的，不过EVI/BD和VLAN是一对多的，也就是说，一个EVI/BD中可以塞多个客户侧的VLAN进去，由于这些VLAN会共享BD的MAC VRF，因此这些VLAN的流量就混在一起了而无法实现隔离。③VLAN Aware Bundle。在这种关系下，BD和VLAN是一对一的，而BD/VLAN和EVI是一对多的，一个EVI中可以塞多个客户侧的VLAN进去，这些VLAN对应着不同的BD，使用不同的MAC VRF，因此VLAN间的流量是可以实现隔离的。注意，EVI是个纯逻辑上的概念，对于控制和转发都不会起实际的作用，而BD和VLAN都是设备本地的概念，同一个客户在不同的设备上可以使用不同的BD和VLAN，只要把映射关系做好，就不会影响控制与转发。对于VxLAN EVPN来说，控制面的映射是靠Route Target，数据面的是靠VNI。

图1-42　EVPN中EVI、BD和VLAN间的关系

1.14.3　控制信令的设计

如图1-43所示，EVPN的控制信令共分为5类，从Type 1到Type 5，分别使用了5种扩展的MP-BGP NLRI。其中，Type 2用于在设备间同步客户的MAC和主机IP，是EVPN实现的基础。Type 5用于发布IP前缀。Type 3实现了设备的自动发现和隧道的自动建立，以及BUM流量的处理。Type 1和Type 4主要用于解决双上联场景中引入的一些问题。下面将结合一些图例，来对这些控制信令的工作原理进行介绍。这里要说明的是，VxLAN-based EVPN还没形成RFC，各家在控制面的实现上并不完全一致，下面的图例为Juniper的实现。

图1-43　EVPN的5类控制信令

1.Type 2

Type 2用于在设备间同步Host/VM的MAC和/32的IP地址。Host/VM在上线时通常会发送DHCP和免费ARP，接入的Leaf会据此学习其MAC和/32的IP地址，形成本地的转发表和ARP表，并通过Type 2告诉其他Leaf，将目的地是该Host/VM的流量封装VxLAN发送给自己。相比于标准VxLAN采用的自学习，Type 2的使用可以有效地避免ARP跨Underlay的泛洪，Leaf还可以对ARP进行本地的代答。不过，如果Host上线后一直静默，那么就还是需要泛洪ARP来触发Type 2的同步。对于VxLAN EVPN来说，NLRI中的Ethernet Tag ID和MPLS Label都用VNI来填充，由于图1-44是single-homed的场景，因此ESI填为0。

图1-44　EVPN Type 2的消息格式

只学习到MAC地址是不够的，还需要能够维护MAC地址的状态，防止出现路由黑洞。尤其是在云环境中，虚拟机的迁移是常有的事情，因此必须要解决好MAC地址移动的问题。标准VxLAN使用的是自学习，MAC地址的维护主要有两种方式：①Host移动后通过广播免费ARP对Leaf上的转发表进行更新；②如果Host移动后静默，那么就等待Leaf转发表超时后再来重新学习。上述的第一种方式需要跨Underlay泛洪免费ARP，第二种方式的收敛会很慢，某些Leaf还可能会成为收敛的死角。EVPN使用了MP-BGP去主动维护MAC地址，只要有一个交换机学习到了新的MAC地址，就能在全局范围对其进行同步，避免了ARP泛洪和局部交换机无法收敛的问题。

全局同步中时序的问题很重要，EVPN为Type 2引入了SEQ，解决了时序上的问题。来看图1-45的场景：最开始H1接在L1下面，L1发布H1的地址时指定SEQ=0，L2和L4都学习到了SEQ=0的消息。H1第一次的迁移是从L1到L2，此时L2发现H1，更新本地H1的信息，然后发布H1的地址并指定SEQ=1。L4很快学习到了这个SEQ=1消息，对H1的信息进行了更新，不过由于网络延迟的原因L1还没有学习到这个消息。还没等L1学习到这个消息，H1就立即从L2迁移到了L4上。L4发现H1，更新本地H1的信息，然后发布H1的地址并指定SEQ=2。L2很快就学习到了这个SEQ=2的消息，意识到H1已经不在本地了，于是发布一个Withdraw消息来删除L1和L4上SEQ=1的H1信息。L1陆续收到了L4发布的SEQ=2的消息以及L2发布的SEQ=1的Withdraw消息，得知H1已经不在本地了，于是根据最新版的SEQ=2更新本地H1的信息，并发布Withdraw消息来撤销L2和L4上SEQ=0的H1信息。此时，L2很早之前发布的SEQ=1的消息终于到达了L1，但是L1上现在H1 MAC的SEQ之前已经被更新到了2，因此L1判定该消息已经过期了，就不再做任何的处理。

2.分布式路由机制

二层说完了来说三层。从以太网的传统实现来讲，三层的转发都是集中式完成的，汇聚层的三层交换机上会配好所有VLAN的SVI，以实现Inter-VLAN的路由。EVPN设计Type 2时既携带了MAC又携带了/32的IP，这种设计的初衷是方便Leaf做ARP代答，以避免ARP的泛洪。既然带上了IP，那么Leaf就具备了直接为Overlay做三层转发的可能，以实现L3的分布式转发。对于Overlay的场景来说，用Leaf做分布式网关还需要解决下面三个问题。

1）如何识别出Overlay的三层流量。EVPN规定网关使用任播MAC地址，对于所有的L2，其默认网关都要求使用该任播MAC地址，Leaf上都会预先配好这个MAC地址和所有L2的SVI，Leaf在看到这个MAC地址后就会跳到L3转发的流程上去做路由了。

2）如何选择端到端的路由模型。用术语来说，EVPN是一种IRB（Integrated Routing and Bridging）的技术方案，IRB的路由模型分为对称和不对称两种。不对称IRB中，只有接入侧的Leaf上会做路由，路由的结果是直接跳进目的host/VM所在L2，目的侧的Leaf只做L2转发就好了。不对称IRB要求各个Leaf上有所有L2的信息，会造成硬件资源的浪费。在对称IRB中，接入侧的Leaf做路由的结果是先跳到一个中继的L2，目的侧的Leaf会再做一次路由，从中继的L2路由到目的host/VM所在L2。对称IRB虽然多做了一次路由，但是Leaf不需要了解目的host/VM所在L2的转发信息，可以大大地节约硬件资源。对于VxLAN EVPN而言，对称IRB的实现通常需要为一个路由域使用一个L3 VNI（或者也可以将这个L3 VNI看作那个中继L2的VNI），进行L3转发时就使用这个L3 VNI在Leaf间进行传输。

3）芯片要支持VxLAN Routing。VxLAN Routing需要为Overlay和Underlay做两次IP路由，这通常意味着Recirculation，如果Leaf的芯片不支持VxLAN Routing，那么就没有办法在Leaf上实现分布式L3转发。这时就仍然需要使用集中式路由的方式了，或者通过Spine或者通过额外的路由设备。

图1-45　EVPN使用SEQ来维护控制信令的时序

3.Type 5

由于EVPN对IRB的实现，因此无法简单地将EVPN归为L2VPN或者L3VPN。Type 2的局限在于只能携带/32的IP地址，而不会对IP地址进行聚合。因此，有提案提出通过Type 5来为EVPN增加宣告网段的能力，主要面向DCI中PE和CE跑IGP的场景。在数据中心内部，虚拟机IP较为分散，不宜进行聚合，因此，Type 5通常只用来注入Internet路由。

4.Type 3

EVPN通过Type 3来处理BUM流量。EVPN Type 3的消息格式如图1-46所示，NLRI后面跟着一个PMSI Attribute，PMSI Attribute中的tunnel类型指定了BUM流量的处理方式，EVPN既能够支持Ingress Replication，也可以支持Underlay Replication。如果采用Ingress Replication的方式来处理BUM流量，那么就需要在Leaf间进行水平分割——当一个Leaf收到另一个Leaf发来的BUM流量时，不允许它再将流量复制给任何其他的Leaf，以避免形成环路。

另外，Type 3的另一个作用是完成VNI成员的自动发现以及隧道的自动建立。对于VxLAN EVPN来说，Type 3的NLRI里Ethernet Tag ID和PMSI里的MPLS Label都填为VNI（VLAN based方式下填0）。如果PMSI中的tunnel type指定为Ingress Replication，那么就需要为每个VNI维护对应的头端复制表；如果tunnel type属于Underlay Replication，那么不同的VNI则可以选择对Underlay组播树进行复用。

5.Type 1

实际上，只要有了Type 2和Type 3就可以把流量跑通了。不过如果需要做multi-homed，那么事情就要复杂得多了。前面简单地提了一下ES和ESI，ES是指为Host做multi-homed时的一个Port Group，这个Port Group使用ESI去标识，ESI必须是全局唯一的。属于同一个ES的不同Leaf是对等的关系，它们中的任意一个都可以作为该Host路由的下一跳。为此，EVPN设计了Type 1以实现Leaf间的负载均衡。

图1-46　EVPN Type 3的消息格式

图1-47是EVPN Type 1的消息格式。在LS2和LS3上配置了ES后，它们会向LS1发送Type 1的消息，填充ESI字段以宣告自己所在的ES，Ethernet Tag ID填为保留的MAX-ET（全F）。LS1收到LS2和LS3的Type 1之后，就会知道LS2和LS3是同一个ES中的对等体，发往这个ES的流量可以通过LS2和LS3进行负载均衡。

LS2在向LS1发送H2的Type 2的时候，会对NLRI中的ESI字段进行填充，LS1会将H2的路由与对应的ES关联在一起。既然H2在这个ES下面，LS1进而得知一定也可以通过LS3路由到H2。因此，即使LS3没有向LS1发送Type 2宣告H2（比如H2的免费ARP被LAG分布到了LS2，而LS3并没有收到这个免费ARP），LS1在处理发往H2的流量时也可以在LS2和LS3间进行负载均衡。EVPN将这个机制称为Aliasing。

Type 1还有一个妙用是路由的快速收敛。在图1-48的场景中，LS2和LS3所在的ES下面挂着100个VM，如果LS3接入ES的链路断掉了，它就需要向LS1发送Type 2的Withdraw消息来撤销相关VM的路由。由于MP-BGP在发送Withdraw消息的时候一次只能撤销一条路由，因此LS3就需要发送100条Withdraw，这会严重影响收敛的速度。EVPN设计了一种叫作Mass Withdraw的机制，LS3在探测到接入ES的链路断掉后，会发送Withdraw消息直接撤销LS1上和该ES关联的所有路由，从而大大加快了收敛的速度。

图1-47　EVPN Type 1的消息格式

图1-48　EVPN的Aliasing机制

6.Type 4

Multi-homed的引入使得BUM流量的处理也变得复杂起来，尤其是在Type 3指定了ingress replication方式的情况下。图1-50中的H1～H4属于同一个广播域，其中H3双上联到PE2和PE3。此时H1发出了一个广播包，于是PE1将该广播包复制给PE2和PE3，PE2和PE3收到后在分别在本地进行泛洪，于是H3就收到了两个相同的包。

图1-49　EVPN的Mass Withdraw机制

图1-50　EVPN要解决的Duplicate Packets问题

EVPN通过Type 4解决了这个问题，如图1-51所示。属于同一个ES的Leaf会交互Type 4信息，目的是选举出一个DF（Designated Forwarder），只有DF才能够处理BUM流量，非DF见到BUM流量则直接丢弃，以防出现上述Duplicate Packets的问题。为了实现Leaf间的负载均衡，一个ES下面不同的VLAN会选举出不同的DF。

上述就是EVPN的主要技术特性。作为Inter-DC和Intra-DC的多面手，EVPN可以实现端到端的Overlay控制，DC Edge要通过背靠背在不同封装之间进行切换，如Intra-DC的VxLAN和Inter-DC的MPLS，实现EVPN Stitching，如图1-52所示。如果选择用VxLAN为Inter-DC做Internet OTT，那么就变成了端到端的VxLAN EVPN，可以经过Leaf将VxLAN转VLAN，然后再从Edge上转VxLAN，或者直接在Leaf/Edge上做VxLAN stitching。

图1-51　EVPN Type 4的消息格式

图1-52　EVPN Stitching

1.15　Underlay最新技术——Segment Routing

1.14介绍了“Overlay万金油”EVPN，本节来看看最新的Underlay技术——Segment Routing。入题之前，首先需要介绍一下源路由。

源路由是指由网络中的节点显式地指定流量的传输路径，后续节点将参考这个指定的路径进行转发。相比于直接在源和目的间做最短路径转发，源路由可以有效地满足流量对于时延、带宽等指标的要求。源路由的概念早在网络设计的初期就被提出了，IP的首部里就有源路由的选项，不过从某种角度来说，源路由和IP无连接的本质是有所冲突的，再加上会引入很多的安全问题，因此IP源路由并没有得到广泛的应用。MPLS为IP引入了有连接的部分特性，并将安全边界转移到了运营商的设备上，源路由才得以在MPLS的流量工程中找到了用武之地。不过，实现源路由的代价非常大，比如RSVP-TE需要维护十分复杂的路径状态，配置十分复杂，运维的成本很高，而且流量的调度效率也比较低。不过，由于运营商不愿意随便引入新的技术，因此厂商在广域网上的创新动力不是很足，RSVP-TE也就一直将就着用了。

2012年，SDN开始在商用数据中心落地。虽然在广域网上还不见大的动静，但Google自家B4的成功，使得运营商开始重新考虑广域网的设计，厂商感受到压力后也不得不开始对广域网技术进行更新。2013年，IETF成立了一个名为SPRING（Source Packet Routing in Networking）的工作组，旨在实现一种简单、灵活的源路由技术，用于更好地规划网络中的路径。Cisco推动的Segment Routing迅速地主导了SPRING的技术路线。相比于RSVP-TE，Segment Routing的核心设备上不需要维护复杂的路径状态，使用和维护起来就要简单很多。另外，Ingress Router可以接受由集中式控制器所生成的源路由，能够有效地提高流量调度的效率，因此有的说法将Segment Routing称为“SDN 2.0”。

Segment Routing译为“分段路由”（后面简称为SR），顾名思义，就是将源和目的间的路径分成不同的小段，逐段来进行流量的转发，和源路由大致是一个意思。分段的办法可以很灵活，如果从源A到目的B的路径总共有5跳依次为R1～R5，最简单的办法是将R1～R5看作一段，这样R1～R4都是直接根据R5进行路由，这种分段的办法相当于没有使用源路由。如果将每一跳都看作一段，即对应于严格源路由，R1在做路由时会以R2作为目的，R2做决策时会R3为目的，以此类推。如果在路径中间对应于松散源路由，比如将R1～R3看作一段，R1和R2做路由时以R3作为目的，将R3～R5看作一段，R3和R4做路由时会以R5为目的。

1.15.1　SID与Label

为了更好地实现上述的分段路由机制，SR设计了一套全新的SID（Segment ID）标识机制。网络的组成无非就是节点、链路和编址，节点和链路反映了物理拓扑，编址反映了逻辑拓扑。SR使用Prefix SID来标识IP前缀，不同的IP前缀在SR Domain中的Prefix SID不能有冲突。SR的节点使用Node SID来标识，每个SR节点在SR Domain中有全局唯一的Node SID。实际上Node SID是Prefix SID的一种，通常来说，SR节点上跑IGP/BGP的/32的loopback地址的Prefix SID，就是该节点的Node SID。SR节点间的链路由Adj SID来标识，Adj SID是节点本地唯一的，不同SR节点上很可能有相同的Adj SID。

从网络抽象的角度来说，一个物理的拓扑结构可以通过节点+链路进行完整的描述，因此对Node SID、Adj SID进行有序的排列，就可以对任意路径进行任意的分段。Prefix SID在实际中用的比较少，下面不会讨论Prefix SID的使用。除了Prefix SID、Node SID、Adj SID以外，SR中还有很多其他用途的SID，比如在不同节点间进行保护及负载均衡的Anycast SID，对两个节点间的多条并行链路进行保护及负载均衡的Parallel Adj SID，跨多个SR域并能够减小MPLS Label Stack深度的Binding SID等，这里也不再进行详细的介绍。

SR要求在流量的入口设备（Ingress Router）上指定分段信息，Ingress Router会根据路径的分段要求，将相应的SID列表封装在Header中，后续的设备就可以根据Header中的SID信息沿着目标路径进行转发。SR在其RFC中提到了MPLS Label Stack和IPv6 SRH两种Header，现有SR的实现都是用的MPLS，基于IPv6的商用SR产品目前还比较少见。下面所说的SR，都是指基于MPLS作为数据平面的SR。

SR中的Label是用来在数据平面上标识SID的。在一个SR Domain中，Node SID是全局唯一的，因此Node SID所对应的Node Label也需要是全局唯一的。SRGB（Segment Routing Global Block）可以表示为[start，end]，用于描述Node Label的可用范围。SR中生成Prefix/Node Label的规则，就是用Node SID+SRGB.start来得到。之所以要设计SRGB来为SID做偏移，而不是直接用Node SID作为Label，主要是考虑到厂商现有的MPLS实现，可能难以找到一个足够容量，且各厂商都支持的SRGB。在SR的RFC中，建议一个SR Domain内的各个节点使用相同的SRGB，不过不同厂商SR设备上SRGB通常是不一样的，因此在进行跨厂家SR组网时情况就要复杂一些。考虑图1-53中的情况，PE1/P1/PE3是Juniper的设备，PE2/P2/PE4是Cisco的设备。如果PE 4的Node SID是44，那么PE1/P1/PE3上PE 4的Node Label就是800044，而PE2/P2/PE4上PE 4的Node Label就是16044。所以说，SRGB是SR节点本地的概念，“Node SID所对应的Label也需要是全局唯一的”这个描述实际上并不是十分严谨。在一个SR Domain中，Adj SID是节点本地有效的，Adj SID对应的Label也是节点本地有效的，因此Adj Label和Adj SID通常直接取相等，两者的取值范围默认是从SRGB.end开始递增的。

图1-53　不同的厂商使用不同的SRGB

1.15.2　控制与转发机制

1.基于IGP的信息同步

SR通过IGP在节点间进行信息同步，主要包括OSPF和ISIS两种。由于原生的OSPF/ISIS只能携带IP前缀和下一跳，没有SID的概念，因此需要对OSPF/ISIS进行SR的扩展，以便能够在信令中携带SRGB、Node SID、Adj SID，以及其他SID。通过IGP的同步，各个SR节点上会形成完整的SID拓扑图，假如网络中共有N个节点，每个节点上有L条链路，那么各个节点的LFIB中就会形成N+L条转发表，其中N条是Node Label表项，L条是本地链路的Adj Label表项。Node Label表项中的out interface是节点根据拓扑通过IGP得最短路径算法得到的，而Adj Label表项中的out interface是从SR邻居的IGP宣告消息中直接解析出来的。回过头来再去重新理解SID，实际上每个SID就代表着一个“指令”。Node SID指令的本质是通过IGP路由决定下一跳，属于一种松散的下一跳；而Adj SID指令的本质是通过特定的本地链路转发给下一跳，属于一种严格的下一跳。

2.基于MPLS的流量转发

LFIB中对Label的处理动作规则如下：Ingress Router做PUSH，以Egress Router的Node Label作为栈底（如果需要Service Label，则以Service Label做为栈底），上面是希望经过的节点的Node Label和希望经过的链路的Adj Label。对于Transit Router来说，如果看到的栈顶是和自己没有本地直连的Node Label，则SWAP栈顶，只不过SWAP前后的Label是一样的而已；如果看到的栈顶是Adj Label或者是和自己有本地直连的Node Label，则直接POP栈顶（相当于PHP）。因此，Egress Router收到数据包时，所有SR的Label都已经被POP掉了，然后根据payload（或者Service Label）路由给CE。上述所说的Service Label是一个泛指，或者是VPN Label，或者是CE Label，或者是VM Label，或者是其他与客户侧业务相关的标签，Segment Routing并不负责生成Service Label。

可以结合图1-54中的例子来理解SR数据平面上的转发机制，PE1是Ingress Router，PE3是Egress Router，要求经过P2→P1这条链路进行传输，图中链路的IGP cost都相同。首先在PE1上做PUSH，栈底压Service Label，Service Label上面是PE3的Node Label，再上面是P2本地与P1相连链路的Adj Label，栈顶是P2的Node Label。PE1查LFIB，得知去往远端的P2有两个可用的下一跳PE2和P1，假设PE1选择了PE2作为下一跳。PE2收到后，查LFIB匹配P2的Node Label，进行转发同时POP掉栈顶。P2收到后，查LFIB匹配P2→P1的Adj Label的表项，转发给P1同时POP栈顶。P1收到后，查LFIB匹配PE3的Node Label表项，转发给PE3同时POP栈顶。PE3收到时，就已经只剩下Service Label了，PE3会根据Service Label的处理逻辑来进行后续的转发。图1-51中没有体现出SWAP的操作，如果PE1和PE2间又有一个PE3、PE1和P1间还有一个P3，PE1选择了PE3作为到P2的下一跳，那么PE3收到后，查LFIB匹配P2的Node Label，转发给PE2同时执行SWAP（SWAP之后还是2）。

3.与LDP/RSVP-TE的对比

SR与LDP和RSVP-TE一样，都是作为标签转发的控制面，下面来讨论一下这三者的区别。LDP的作用是在源路由和目的路由间自动生成MPLS路径，它是从“路径”（FEC）的角度看待Label的，每个FEC在每个节点上都有不同的Label，因此节点上LFIB中表项的数量基本上是和网络中的FEC数量成正比的，LDP的控制平面和数据平面都没有体现源路由的思路，无法实现对路径的规划，也没有预留带宽的能力。RSVP-TE的作用是在源路由和目的路由间特定的路径上自动生成MPLS路径，它看待Label的角度和LDP是一致的，数据平面上还是逐跳做SWAP，没有用Label Stack做源路由。不过，RSVP-TE控制平面中的ERO体现了源路由的思路，控制信令才得以预留沿路的带宽，但是其引入的session概念使得设备上需要维护大量的状态信息，而且分布式的路由规划无法充分地利用网络中的带宽资源。

图1-54　SR的转发示例

相比之下，SR弃用了LDP和RSVP-TE，而是使用扩展的IGP去宣告SID和Label，它是从“拓扑”的角度来看待Label的（不考虑Prefix SID），每个节点和物理链路都有自己本身的Label，任意的路径都可以通过组合节点和物理链路的Label来表示，因此节点上LFIB中表项的数目是和网络中FEC的数量无关。SR的数据平面通过Label Stack实现源路由，节点上不需要维护任何和路径相关的状态，源路由的计算可以通过控制器来集中式地完成，能够有效地提高带宽的利用效率，不过SR本身并没有预留带宽的能力。

4.TI-LFA与微环路避免

电信级的网络要求能够在故障出现后的50ms内倒换到保护路径上，由于IGP的收敛速度无法满足此要求，因此通常要借助于FRR（Fast ReRoute）技术，FRR在故障发生前就会得到故障后可用的保护路径，并更新到FIB中，因此PLR在本地检测到故障后即可立即完成切换。IPFRR是一种对IP前缀进行保护的FRR技术，其传统的实现包括LFA（Loop Free Alternate）和RLFA（Remote Loop Free Alternate）。LFA的思路是，以PLR本地直连的、且到达目标前缀的最短路径不经过故障处的节点N作为下一跳，故障时PLR将去往目标前缀的流量转发给N，N再将流量路由至目的地，LFA非常简单但在某些拓扑中无法找到有效的N。RLFA的思路是，取P空间与Q空间的交集P∩Q，其中P/Q空间分别表示到达“PLR/目标前缀”的最短路径不经过故障处的节点集合，然后在P∩Q中选取一个节点N作为下一跳，故障时PLR先通过隧道将去往目标前缀的流量定向到N，接着由N终结隧道并将流量路由至目的地。相比于LFA，RLFA借助隧道提高了拓扑的覆盖率，但是在少数拓扑中，P∩Q仍可能为空集。

而SR所支持的TI-LFA（Topology Independent Remote Loop Free Alternate）可实现100%的拓扑覆盖，思路也是先得到P/Q空间，如果P∩Q不为空，则在P∩Q中找到一个节点N，故障时PLR会压入N的Node Label，引导流量先经过N再流向目的地。如果P∩Q为空，则分别在P/Q空间选择节点NP和NQ，并计算出NQ与NP间不经过故障处的路径L，故障时PLR从栈底到栈顶方向依次压入NQ的Node Label，路径L的Label列表（可根据实际情况由Node Label和Adj Label组合得到），于是流量将先被路由至NP，然后经过路径L引导至NQ，再由NQ流向目的地。可以看到，由于SR具备显示路由能力，能够对流量进行逐跳的引导，因此故障后只要在拓扑上，PLR和目的地仍然是连通的，那么TI-LFA就一定引导流量到达目的地，而且TI-LFA还能够保证通过故障后最优的路径进行引导，这也是LFA和RLFA都无法保证的。

另外值得注意的是，由于IPFRR的定位是一种在故障发生后短时间内对IP前缀进行保护的机制，因此在大部分的实现中，在PLR本地完成IGP收敛后，PLR仍然会使用IGP收敛后生成的新路由进行转发。不过，这可能会导致一个问题，假设PLR本地完成IGP收敛后去往目标前缀的下一跳为C，于是PLR将流量转发给C，但此时C还没有完成IGP收敛，而且不排除C中去往目标前缀的下一跳是PLR的可能，在这种可能下C又将流量送回给了PLR，也就导致了环路的产生，直到C完成IGP收敛后环路才得以解除。

这种由于IGP收敛不同步所导致短暂的环路称为“微环路”，解决微环路问题的一种做法是，当PLR本地完成IGP收敛后不会立即更新转发表，而继续使用保护路径，直到PLR认为IGP已完成全局收敛再更新转发表，按照IGP收敛后的路由进行转发。不过即使如此，由于LFA只能影响PLR的转发行为，因此只能保证PLR和其下一跳间不产生微环路，无法保证保护路径中其他节点间不产生微环路。而SR则允许通过源路由来影响保护路径中每个节点的转发行为，因此采用推迟更新后可以完全地避免微环路的产生。

1.15.3　SDN 2.0？

1.与OpenFlow/OVSDB的对比

上述就是SR的核心技术框架。前面提到过，有的厂商将SR称为SDN 2.0。下面不妨就来比较一下SR和OpenFlow/OVSDB。OpenFlow/OVSDB的特点在于“全面”，ID分配、拓扑发现、路由计算、FIB分发、隧道拆建、流量引导、流量调度以及带宽预留，这些事情它都可以做到。而SR是一个专才，只负责根据SID生成LFIB，然后转发源路由的流量，如果要用SR做SDN，就必须要搭伙其他的协议——发现拓扑要靠IGP/BGP-LS，流量引导要靠PBR/BGP-Flowspec/NETCONT，流量调度要靠PCEP，预留带宽要靠RSVP-TE。那么，相比于OpenFlow/OVSDB，SR的优势在哪里呢？从架构上来说，OpenFlow/OVSDB是纯集中式的，要打通一条路径，需要逐台设备进行控制，而SR在分布式和集中式之间找了一个平衡，先通过分布式的协议把拓扑和路由算好，然后找一个代表同步给控制器，控制器只要计算出目标路径发给Ingress Router，因此SR的可扩展性要强于OpenFlow/OVSDB，可以适用于运营商规模的网络。另外，SR的流量转发并不严格依赖于控制器，且能够实现50ms级别的故障恢复，因此具备更高的可用性。当然，SR相比于OpenFlow/OVSDB最大的优势还是来自于技术的向前兼容性，出于对商用风险的考量以及技术取向的惯性，运营商自然会更加倾向于SR。

2.“端到端”的理想

在大网这块站住脚之后，SR最近又开始做起了数据中心。借着“云网协同”的概念，一些厂商提出了将VM/Container、Host、Leaf、Spine、DC Edge、DC Uplink全部分配上SID，以实现DC-Metro-Core-Metro-Enterprise“端到端SR”的宏伟目标。图1-55是端到端SR的设想。

图1-55　端到端SR的设想

要做进数据中心，首先要搞定服务器端，那么对vRouter进行升级就是必不可少的，另外更彻底的是直接扎入到协议栈中，目前Linux 4.1.0已经开始提供对SR的支持。从服务器出来之后，就进入了DC的Underlay Fabric，鉴于Google、Facebook等大型OTT在近几年将BGP应用于超大规模数据中心的推动，SR拾起了长期以来一直不温不火的BGP-LU（BGP Labeled Unicast，RFC3107）来宣告SID与Label的关系，以拉通Underlay上的传输。到了DC的Edge Router这里，SR设计了BGP-EPE（BGP Egress Peer Engineering，draft-ietf-spring-segment-routing-central-epe-07）的机制，用于优化DC多出口的BGP选路。

如果实现了“端到端SR”，再结合SDN控制器开放出来的北向接口，应用即可主动地规划路径，一些厂商也为此描绘出了“应用驱动网络”的美好愿景。

SR向数据中心的推动，实际上是Underlay对于Overlay的反击。不过，数据中心的网络结构非常规整，网络半径通常都小于7跳，带宽的资源也相对充裕，因此SR做路径规划的优势并不明显。如果是为了调度Elephant Flow，将SR引入物理网络未免有些小题大做，如果是为了做服务链，明显也是在边缘用OpenFlow更适合一些。而TI-LFA所提供的保护能力，也不是特别符合Leaf-Spine“设备热插拔，多路ECMP”的设计哲学。“应用驱动网络”倒是个不错的概念，但是实际上与圈子里一直在提的“Intent Based Networking”也并没有什么本质的区别。

而且，数据中心仍然处于向VxLAN演进的前期阶段，SR想渗透进数据中心，目前来看并不是一个很好的时间点。随着SDWAN的流行，企业入云的场景也开始更多地开始使用VxLAN来做Internet OTT。“端到端SR”还远，“端到端VxLAN”倒是已经近在眼前了。

1.16　本章小结

数据中心网络技术近十年间的演进，在传统IDC向云数据中心转型的大背景下展开，东西向流量的爆发催生了Leaf-Spine架构和各路大二层技术，而对于开放性和自动化的强烈诉求，则拉开了SDN时代的序幕。同时，在与SDN碰撞的过程中，传统网络技术也得到了进一步的升级。本章对这一演进的主线过程进行了回顾，并对其中的一些代表性技术进行了介绍，以作为后续内容的背景知识。

第2章　杂谈SDN

上一章提到过，网络技术当下属于SDN的时代，数据中心则正是SDN的主战场。不过在介绍SDN在数据中心的应用之前，有必要先来好好地聊聊SDN。SDN的入门资料在网上俯拾皆是，所以本书不再赘述。本章主要是谈观点，希望能够引导读者对SDN做一些深入的思考，既能看得到面子，又能瞧得见里子。

2.1　SDN与传统网络——新概念下的老问题

SDN是个时髦的概念，但是它和传统的网络技术究竟有什么区别呢？这是个没有标准答案的问题，一千个人对SDN有一千种理解，但是万变不离其宗，无论是由谁通过怎样的方式来定义网络，其作为通信系统的本质和内核是不会变的。SDN只不过是变革了网络的交付模式，而它在技术上所面临的大部分问题实际上在传统网络中都有迹可循。本节旨在帮助读者认清SDN新概念下的老问题。这是个不小的话题，总要找到些思路上的依托。在较为普遍的认识中，SDN具有三大特征，包括网络可编程、转发与控制相分离以及集中式控制，下面就从这三个特征进行切入。

1.转发与控制分离

路由设计之初，专用的转发芯片尚不成熟，转发通常是基于通用内核来做的。这个阶段的转发和控制都是由CPU来实现的。对于每一个数据包，CPU都要通过复杂的软件查找算法来匹配转发逻辑，因此在转发速度上有很大瓶颈。再加上CPU还要对网络控制消息进行处理，转发和控制对有限CPU资源的竞争时常会导致网络性能出现很大波动。随着ASIC的成熟，业界通通转向了将转发和控制相分离的架构：设备里面ASIC和CPU各司其职，CPU负责计算密集型的控制逻辑的处理，完成计算后通过特定的通道（如PCIe）将转发规则下发给ASIC；ASIC负责按照转发规则进行高速转发。ASIC的灵活性较差，于是又出现了NP、FPGA等，不过转控分离的架构得以保留，目前的网络设备中已经看不到由CPU包办一切的情况了。这和SDN提出的转控分离是形似的。

估计很多的读者要问了，不是吧？传统设备是在盒子内部“1cm”的转控分离，而SDN的控制逻辑可是在远端的。这是个非常普遍的错误理解，笔者在最开始接触SDN的时候也是这么想的，因此曾经长期困惑于一个问题：如果SDN的控制逻辑一定是在远端的，那么控制器下面肯定不能是对着一个设备，那么“集中式控制”是否是“转控分离”一个必然的结果呢？

实际上，SDN“转控分离”的内涵并不在于远端，而在于开放。ForCES作为最早的SDN标准是没有要求CE和FE一定要离得有多远的。传统设备的转发与控制的确是分离的，但是转发的接口可不是标准的，更谈不上是开放的，不同ASIC上同一种控制逻辑是没有办法直接移植的。换句话说，架构上转控是分离了，但逻辑上转控仍然是强绑定的厂商们正是靠着这一点来捆绑了硬件和软件。对于厂商来说，这种捆绑通常意味着高技术门槛、超额的利润以及牢固的用户锁定，因此他们的一切战略都是围绕着盒子的整体交付来铺开的。商业上不可言说的原因为网络的创新铐上了沉重的枷锁，而SDN要求软件和硬件解耦合，这就是厂商们抵触SDN的根本原因。

因此OpenFlow和ASIC的SDK并没有本质的区别，更多情况下只是对相同硬件的不同控制方式而已，抽象程度不同罢了。很多人攻击OpenFlow说PacketIn和FlowMod的性能问题没有办法得到解决，可是传统设备的收敛也是要有一个过程的，转发表总不可能是凭空生成的，ASIC和CPU间的双向通信同样不可避免。如果特别在乎与远端的控制器间通信的时延，那么把OpenFlow控制器放在设备本地就好了。当然，这放弃了OpenFlow集中式控制的能力，但是仍然可以获得开放转发与控制间接口的种种好处。对于OpenFlow的误解还有很多，这里先卖个关子，下一节会为OpenFlow做一个“平反”。

目前业界SDN实现可分为狭义和广义两种，狭义SDN的典型实现包括ForCES和OpenFlow，上面说过其本质在于转发与控制间开放的标准接口。而要理解广泛意义上的SDN，则可以直接从SDN的名称入手：既然称为“软件定义网络”，可编程才是SDN的基本特征，所有允许通过编程控制的网络都属于广义SDN，可以通过编程来控制网络的技术都是SDN技术。概念上有了“可编程”作为基础，厂商们找到了从转控分离逃离出来的理由，从API的角度来切入SDN。实际上，SDN的先驱们在起这个名字的背后可能包含了技术外的多方面考虑——它能够给厂商一个缓冲的机会，或者反过来说这也是SDN至今仍然能够活下来的必要妥协。

2.网络可编程

可编程性自从网络出现以来一直是个热点话题，并不是什么SDN独特的创举。脚本就是最为初级的编程，通过对CLI进行组合与包装来提供网络的自动化。当面对两三台设备的时候，手敲CLI然后show一下是最为稳妥的方式。当面对十几台设备的时候，或许写个简单的脚本是更好的选择。当设备的数量有几十台的时候，脚本几乎就是唯一的选择了。可是，当面对整个数据中心的设备时，简单的脚本可能就不能满足需求了，一边心里念叨着“这该死的IT”，一边祈祷网络不要出现故障。

SDN的出现是希望让网络变得更加智能，SDN“可编程”也并没有要求一定是“转发可编程”，从概念上来说只要是编了码都可以算得上是广义上的SDN。因此，“可编程”可以说是个相当宽泛的概念，这也导致了目前业界SDN技术的百家争鸣。网络运维基于SSH或者RPC做远程集中式的脚本开发，省去了到设备前去配CLI的，这种算不算是SDN呢？网管系统开发基于SNMP来做接口包装、界面呈现，SDN提倡的网络可视化SNMP也有极为丰富的支持，这种又算不算是SDN呢？SNMP有可读性差和不支持事务操作等缺点，于是NETCONF/YANG被提了出来，可是YANG不过是Yet Another Next Generation，而NETCONF在2006年由Juniper提出来的时候也没人提SDN，为什么现在就火了呢？BGP几乎所有的路由器都支持，弄一台服务器做BGP反射，然后通过调用数据库接口来做一些选路策略，为什么大家都认可这很SDN呢？这些都是容易引发争议的话题，不过在新的时代，传统的技术确实是可以被赋予新的概念的。既然先驱们起了SDN这个名字，那么就要辩证地来看，不过这个话题的讨论还是要有底线的，否则SDN就变成了“大锅汇”，会出现很多“看起来很奇怪”的选手。

从脚本到程序，SDN只是为了塑造一个“更好的网络”，而并不是一个“完全不一样的网络”。这原本是一件好事，却让相当一部分搞网络的人心里打了鼓，让程序员这个“具有野心”的行当成了网络界的“阶级敌人”一般的存在。实际上，SDN试图推动的是网络界精细化分工的过程，而编程只是其中的一个环节。IT的各个行业都希望得到既懂编码又懂业务的复合型人才，但这样的人毕竟是有限的，网络的蛋糕足够大，是可以同时容得下只懂网络架构和只懂代码的专才的。

3.集中式控制

与转控分离和可编程不同，对于SDN的另一个特征“集中式控制”的争论，则更多地出于技术方面。传统网络架构上是基于分布式路由协议的，但是并不是没有一点集中式的影子在里面，大家熟知的WLAN中的AC就是典型的例子。诚然，相比于分布式，集中式在可用性和扩展性方面确实有一些不足，但是这些问题实际上并不是无解的，传统网络就是最好的老师。

在可用性方面，传统的设备看起来就那么一个盒子，除了协议一直在打补丁、端口速度不断进化着以外，好像看不出别的什么学问来，但是把盒子拆开了来看，里面的设计架构实际上和SDN是类似的。大型的盒子里面，业务板都是多插槽，背板也很可能有多块，这些业务板和背板上的逻辑都是由引擎来集中式控制的。那么引擎挂了怎么办，是不是业务板和背板就都不能用了？当然不是，想获得高可用性，可以为盒子多准备一块引擎做冗余。如果两块引擎都挂了或者要在线升级，那么可以使用NSF、GR、ISSU这些来保护业务的数据通道。要是盒子整体挂掉了怎么办，那就多买盒子做主备或者多活。

在扩展性方面，传统网络的扩展性是由分布式来保证的，BGP撑起了Internet的基础。然而在大规模网络中，BGP都需要有一个RR作为路由的集中点，否则由于Full-Mesh也是难于实现可扩展的。每个路由器都和这个点去建邻居，这个点统一收集大家的路由然后再去分发。那么，这个还是分布式吗？是的，因为路由的计算还是路由器自己完成的，集中式的RR只负责原封不动地反射。可是不管怎么样，这个点和SDN控制器还是处在相同的位置上。而且多数情况下为保证控制平面的性能，RR是不会跑转发的，这和SDN控制器就更像了。RR不会有扩展性的问题吗？答案是会的，解决的办法是专门对BGP代码进行优化，来把性能做强。可是经过优化过后，一个RR最多也就可以带几百个BGP邻居，如果仍然不能满足需求，那么就再为RR引入分簇做冗余或者分级做层次化。

因此，SDN的集中式为人诟病的可用性和扩展性的问题，并不是传统网络没有遇到，而是它们已经花了多年的时间把问题解决了。但是，传统网络的可用性和扩展性也不是免费的，核心交换机和路由器那么昂贵的价格，每个炫酷的Feature可都是做出了自己的贡献的。当然，如果有一天SDN也演进到和传统网络一样成熟，其成本也不会太低。不过，转控分离和可编程为SDN带来了更为开放的生态以及更低的实现门槛，这些才是传统网络向SDN转型的本质驱动力。另外，控制器的CPU和内存相比于盒子而言可以看作海量的，控制器的性能不再完全受限于代码的实现质量，一定程度上还可以靠“堆资源”来进行弥补。

这一节的讨论粒度是比较粗的，只是希望能帮助读者认识到SDN并不是“网络异教徒”，相反，SDN恰恰是网络界再次开动技术引擎的助燃剂。后面几个小节，将会对SDN的几个特征进行更为深入的讨论。

2.2　转控分离——白盒的曙光

早在2003年，IETF就提出了一种名为ForCES的框架，将转发元件与控制元件分离，然后通过标准的接口实现控制，旨在加速转发平面和控制平面的创新。ForCES中，转发元件被称为FE，控制元件被称为CE，CE通过ForCES协议来控制FE的转发。CE和FE间在物理位置上没有必然的联系，可以位于一个机箱里通过背板互联，也可以通过以太网或者IP进行远端的互联。一个FE可以同时连接多个CE以实现主备或者负载均衡，CE和FE间的对应关系由CE-Manager和FE-Manager进行协商。ForCES框架下的工作流程分为Pre-association和Post-association两个阶段。Pre-association阶段中，首先CE-Manager和FE-Manager间会交换CE、FE的列表，然后CE-Manager告诉CE与之关联的FE的信息，FE-Manager告诉FE与之关联的CE的信息。Post-association阶段中，首先在CE和FE间进行认证、握手、能力协商和初始化配置，然后ForCES协议的通道就建立起来了。通道上的信令主要包括FE向CE发送不知道如何处理的数据报文、FE向CE转发入向的控制报文、CE向FE发送转发表、CE向FE发送出向控制报文、CE向FE请求一些统计数据等。ForCES协议只规范Post-association阶段的交互，Pre-association阶段的交互则不在ForCES协议的范围内。

可以看到，ForCES协议和OpenFlow在基本设计思路上是完全一致的。不过ForCES并没有像OpenFlow一样火起来，为什么？一方面，时机不够成熟，当时通信领域如日中天，OTT们还没有与设备厂商们掰手腕的想法，ForCES想法虽好但太过于超前了。另一方面，找错了切入点，ForCES的倡导者大多来自于高校和Intel，然而面向的却是IP设备，厂商在IP这块有绝对的话语权，当然不会放任ForCES胡来。厂商们为什么抵制ForCES？因为ForCES从根上动摇了厂商的利益。

网络设备这一块，从主板、交换芯片这些硬件到操作系统、协议实现这些软件，向来都是各路厂商自成体系，从硬件到软件一并都做了。这个模式有什么问题吗？试想如果WinTel不是两个公司的联盟，而是一家公司的话，PC肯定做不到今天这样普及，如果浏览器、游戏、即时通信等软件都被操作系统套牢，PC上的应用也不可能像今天一样丰富。

很遗憾，网络设备这一块并没有形成WinTel的格局。软硬件的一体化，意味着用户不仅不清楚各个环节的成本，很多时候还要在买回盒子以后为软件的授权（License）另外付费。对于软件本身，用户也不知道它的实现细节，想要加一些新的功能必须依赖于厂商来做定制化。严密的技术封锁压制了市场竞争，网络设备长期由几个巨头把控，他们在分享着庞大的市场需求的同时，还能够获得高昂的产品利润。这些优势都得益于控制与转发的紧密耦合，也就是说，并不是厂商不接受ForCES转控分离的概念，而是他们不愿意将转控分离进行开放。一旦开放式的分离，就意味着软硬件的进一步分工成为可能，产业链必然面临重构。

不过，任何行业的发展都无法摆脱精细化分工的必然趋势，ForCES搞错了时机与切入点，但是没过几年需求就出现了。2005年前后，Google、Amazon这些OTT巨头的业务开始迅速扩展，对于服务器、存储和网络这些基础设施要进行大规模的建设。这些互联网巨头和电信运营商不同，他们对新技术的态度是更为开放的。首先，他们的技术能力非常强，对厂商不存在什么依赖感，一贯的路数是所有东西都自己来做，如果服务器、存储都能自己来做，网络为什么不行呢？而且，相比于运营商骨干网上的IP设备，数据中心在新建时存量设备的压力也比较小。于是，他们开始着手自己研发数据中心的交换机。

Google摸索出来的思路可以概括为以下三点。

1）从商用芯片公司买芯片，交换机上的软件自己来做。

2）采用CLOS结构进行整体组网，从Scale-Up转向Scale-Out。

3）部分控制逻辑集中化，和计算、存储统一管理编排。

从Google的角度来看，上述思路的最终目的都脱离不开降低成本。

1）和芯片公司谈好合作，根据芯片公司提供的SDK自己来做操作系统和协议栈，不用再为盒子中很多不必要的功能买单。

2）Scale-Out的思路中不再围绕网络中的大盒子进行组网，将一个大盒子分解成多个小盒子，容量不够了就再加一台设备，设备坏掉了就用任何其他设备顶替掉，总体上可以大幅度降低CAPEX。

3）集中式控制可以增强自动化的能力，还可以提供更好的流量调度与可视化，虽然开发集中式控制有一定的前期研发成本，但从长期来看是可以降低OPEX的。

然而，从传统网络设备厂商的角度来看，上述都是对传统网络的巨大冲击，Google的设备上将没有任何厂商的色彩，成了“白盒”，这可绝对不是什么好的兆头。

AWS的思路没有对外公开，但是整体的思路和Google应该是类似的。如果说Google关上门研究“黑科技”，设备厂商们作为“外人”没什么可说的，那么AWS于2012年将10亿美元的大单给了独立的白盒厂家，而没有选择Cisco，这可是公开向市场释放了白盒商用的信号，白盒开始正式走入了人们的视野。可以说2012年就是白盒交换机的“元年”。

相比于Google和AWS，Facebook起步要晚一些，直到2010年才开始自建数据中心，但是FB马上在2011年就有了一个大动作——成立开放计算项目（Open Compute Project，OCP），旨在开源数据中心所有的软硬件设计，想去掉所有传统的服务器和存储厂商，网络厂商自然也“不能幸免”。OCP可谓一鸣惊人，ATT、Verizon、微软、Rackspace、高盛这些体量的用户陆续加入。“白盒”也终于找到了组织，Intel、Broadcom、Mellanox这些商用芯片厂家开始开放芯片的SDK，Cumulus、BigSwitch、Pica8开始推广白盒的软件，Quanta、Accton这些ODM厂商开始跟进白盒的硬件集成与组装。Cisco和Juniper抵挡不过潮流，也在2015年初加入了OCP。随着Google于2016年加入OCP，OCP的影响力已经越来越大。不过AWS目前还没有动静，听闻他们甚至打算开始自己做芯片了，看来技术上的领先优势仍然是比较大的。

既然是分工，那么原来一家通吃的活就要分给不同的人家去做。分工后是什么样子呢？主要拆解成了4个部分，从下向上依次是芯片、机箱、操作系统以及控制与管理软件，如图2-1所示。下面分别从这4个部分对转控分离目前的进展进行具体的介绍。

图2-1　白盒的技术堆栈

2.2.1　芯片级开放

1.OpenNSL与OF-DPA

ASIC主要来讲讲Broadcom。Broadcom在SDN方向上主要供应的是Trident和Tomhawk两款ASIC，为OpenFlow和Overlay专门做了一些设计上的优化，目前大部分的白盒都是基于这两款芯片在做。ASIC的软件接口方面，Broadcom实际上与很多高校和OTT都有合作，允许他们通过Broadcom的SDK来进行开发，只不过要签NDA（NonDisclosure Agreement）。OCP来了之后，Broadcom把SDK进行了二次包装，然后开源出来贡献给了OCP，面向传统的L2/L3协议栈的API叫作OpenNSL，面向OpenFlow的API叫作OF-DPA。图2-2a是交换机本地的应用程序通过调用OpenNSL API来控制芯片的行为，图2-2b是通过JSON-RPC在远端调用OpenNSL API。图2-3是OpenFlow Agent收到远端控制器的OpenFlow消息后，在交换机本地转化成OF-DPA API来控制芯片的行为。

图2-2　交换机本地的应用调用OpenNSL API以及远端通过JSON-RPC调用OpenNSL API

图2-3　OpenFlow到OF-DPA的转换

OF-DPA内部实际上用的还是OpenNSL的库，不过向上提供的API不一样罢了。OpenNSL和OF-DPA提供的接口分别如图2-4和图2-5所示。之所以把OpenNSL和OF-DPA的API列在下面，是想要告诉读者一件事情，就是白盒的能力最后还是受限于芯片提供的API的，并不是控制器让做什么盒子就能做什么。

2.SAI

除了Broadcom以外，ASIC这一领域的选手还有Intel、Melllanox以及Cavium，国内的盛科这几年也逐渐打响了名声。转发的应用为ASIC写Adaptor可不是一件容易的事，有这么多的ASIC厂家，应用如果每移植到一个新的ASIC上去就要重写一次Adaptor，效率就太低了，于是又有人想了一个法子，在各家ASIC的SDK（比如Broadcom的OpenNSL）之上再做一层统一的抽象，ASIC厂商需要根据这层抽象把自家的SDK适配进去，这样转发的应用就可以在不同的ASIC间进行无缝移植了，这就是微软提出的SAI（Switch Abstraction Interface）。

图2-4　Broadcom OpenNSL提供的API

图2-5　Broadcom OF-DPA提供的API

SAI的架构如图2-6所示，其中ASIC SDK被称为Adaptor，一个Adaptor会根据SAI将ASIC中不同的功能适配到不同的Adapter Host中，Adaptor Host可以看作对不同ASIC中特定功能的抽象。Adaptor Host的概念说起来比较抽象，具体一点来说，MAC转发和IP转发是两个不同的Adaptor Host，一个ASIC如果既支持L2又支持L3，那么其SDK就需要把L2功能和L3功能分别适配到MAC Adaptor Host和IP Adaptor Host中，而不同ASIC的L2功能都会适配到MAC Adaptor Host中（熟悉ONOS的读者应该比较容易理解，Adaptor Host和Subsystem是类似的作用）。虽然SAI目前还不是很成熟，但是它对于转控分离的意义十分重大，目前已经有很多ASIC厂商提供了对于SAI的支持。

图2-6　SAI对不同的ASIC SDK进行抽象

2.2.2　操作系统级开放

说完了芯片，再来看操作系统。白盒的操作系统（Network OS，NOS）目前主要的选手有Cumulus、Pica8、Pluribus和BigSwitch。Cumulus的Cumulus Linux是基于Linux+传统的协议栈，主要业务是直接卖交换机OS给ODM。Pica8的PicOS是基于Linux+OVS的，商业模式是与ODM的硬件进行捆绑，不过Pica8自己也在做盒子。Pluribus的Netvisor是基于自己的一套虚拟化平台，主要业务是卖自己的盒子，和ODM也有一些合作。BigSwitch的SwitchLight是基于Linux+Indigo（一个开源的OpenFlow Agent），商业模式是开源交换机OS，其收入主要来自于控制器，BigSwitch自己并不卖盒子，而是选择与ODM进行合作。

这里有必要对白盒的概念做一个澄清。很多人认为白盒就是指OpenFlow交换机，或者认为白盒就是什么控制逻辑都没有的瘦转发设备。其实白盒的定义不是这样的，Wiki上对White-Box的解释是“a white box is a personal computer or server without a well-known brand name”。对于服务器来说，就是卖给用户裸机，用户愿意用Windows还是Linux可以自己去选，但是装不了MAC。类比到交换机上去理解，就是交换机出厂时不会预装其他任何软件，交换机上安装什么操作系统是用户可以自己选择的，不过可选的操作系统只能是白盒操作系统，用户想装Cisco的IOS肯定是不行的。至于白盒的操作系统里是不是支持OpenFlow，实际上是不做要求的，也可能只有传统的协议栈。大家做久了似乎发现，白盒的关键并不在于是不是OpenFlow，这完全是看用户喜好的，因此以往靠OpenFlow起家的BigSwitch和Pica8都在集成传统协议栈，而专注于传统网络的Cumulus和Pluribus也对OVSDB提供了支持。不过，如果是Cisco自己在原有的IOS上集成了一个OpenFlow Agent，那肯定是算不上白盒的。图2-7来自于BigSwitch，它很好地解释了前面这段话的内容。进一步来说，白盒并不一定要和SDN挂钩，它只要求转控分离，可编程是锦上添花，对集中式控制不做要求。

图2-7　白盒交换机与传统网络设备、SDN设备的对比

1.ONL

有人获利就肯定有人想着开源。由于BigSwitch主要的卖点是在自己的控制器，因此为了建立生态就顺手把SwitchLight贡献给了OCP，并起了一个响亮的名字叫作Open Network Linux（ONL）。ONL和普通的Linux发行版又有什么区别呢？可以说ONL是Debian Wheezy的加强版，增加了一些和白盒交换机相关的功能，比如对一些外围器件（风扇、电源、USB、LED）的适配、SFP（SFP+）的管理，对ONIE（后面会提到）的支持等，并且将上述功能抽象出了一层接口叫作ONLP，供基于ONL的平台管理应用进行调用。对于转发应用，虽然BigSwitch自己是一直在推OpenFlow的，但是ONL并没有要求转发应用一定是OpenFlow的，ASIC也没有绑定任何Vendor的ASIC。如果想要用Broadcom的芯片，就在ONL上装OpenNSL或者OF-DPA；如果想要多支持厂商的，就再集成SAI。另外，ONL还希望可以做到CPU架构无关（比如x86、PowerPC或者是ARM）。从ONL的设计理念可以看到，ONL确实是奔着通用的NOS去的。

2.FBOSS

FBOSS是Facebook自家白盒交换机上的应用集，主要包括三部分：第一部分是和芯片打交道的程序，为其他应用程序提供芯片的抽象；第二部分是自动化类的程序，分为配置、监测和排错三大类；第三部分是控制类的程序，比如BGP等。Facebook只开源了FBOSS中很小的一部分，即图2-8中的FBOSS Agent，FBOSS Agent运行在Broadcom的OpenNSL之上，提供对于Trident中L2/L3/VLAN表的进一步抽象，然后向上提供Thrift接口供其他程序调用。另外，FBOSS Agent还包括了对于一些底层控制信令（如ARP/DHCP/LLDP）的处理。对于未开源的那部分，Facebook的说法是自动化和控制类应用都是和自己的网络密切相关的，拿到别的地方也不一定适用。除了FBOSS Agent以外，Facebook还开源了主板的控制程序OpenBMC，BMC是主板上用于做主板管理的专用芯片，和主板上大部分的器件都有连接，通过OpenBMC可以随时获取主板的状态，比如主板温度。

图2-8　Facebook开放出来的FBOSS

3.SONiC

SONiC（Software for Open Networking in the Cloud）是微软在OCP里面提到的应用软件集，据说已经被应用到了Azure的生产网络中。图2-9中绿色的部分是SONiC开放出来的，主要包括基于SAI的和芯片打交道的程序以及外围器件（如风扇、LED等）的管理接口。基于SAI的和芯片打交道的程序，可以理解成和FBOSS Agent中为OpenNSL做的抽象层是一样的。外围器件（如风扇、LED）的管理接口，可以理解成和ONL提供的ONLP是一样的。深色阴影部分是已有的开源项目（如Quagga/lldpd等），SONiC利用这些项目与浅色阴影部分进行了组装。可以看到，和Facebook开源FBOSS的思路一样，微软开放的SONiC也没有涉及自家交换机上的应用，Quagga的功能毕竟有限，而且原生Quagga的性能是否足以胜任大规模生产网络的协议栈，这个事情恐怕还是要打上问号的。实际上，SONiC做了对外围器件的管理，也做了数据库和SNMP，不再单纯是一个应用层面的东西了，虽然SONiC的说法是要运行在ONL之上，但怎么看SONiC都是在往通用NOS的路子上走。

4.OS 10与OpenSwitch

Dell的OS 10也是一个基于原生Linux的通用NOS平台。Dell的策略和微软有些相似，OS 10开源了外围器件的管理接口，还有基于SAI的ASIC的控制接口，但是像自动化、协议栈这种企业级的应用并没有开放出来，如图2-10所示。

图2-9　微软开放出来的SONiC

图2-10　Dell的OS 10

OpenSwitch是HPE贡献给Linux基金会的开源NOS。OpenSwitch和OpenvSwitch仅有一个字母之差。HPE最开始为OpenSwitch做的OPS版本也是基于OpenvSwitch的，不过定位于白盒交换机上的NOS。OPS（图2-11）中为OVS扩展了很多新的东西，包括L2/L3的传统协议栈，ASIC的驱动与抽象，外围器件的适配与管理，不同RESTful、Ansible等不同的北向接口，等等。OPS为OVSDB扩展了很多的Schema，用于传统的协议栈和白盒的管理。扩展后的OVSDB负责各种状态的发布订阅，以驱动不同应用间的通信。OPS中路由协议栈这部分最初用的是Quagga，拓扑发现用的lldpd，Lacp这一块是HPE自己贡献的。2016年10月，HPE宣布不再维护OpenSwitch的代码，将其转交给了Dell和SnapRoute，由Dell负责OS，由SnapRoute负责协议栈。之后，OpenSwitch转向OPX版本（图2-12），架构上开始向Dell的OS 10靠拢，OPS版本不再进行维护。

图2-11　OpenSwitch OPS版本架构

图2-12　OpenSwitch OPX版本架构

2.2.3　应用级开放

有了开源的操作系统，还得有应用才算得上是完整的生态。交换机上的应用主要可以分为两大类：第一类用于对一些外围器件的管理；第二类用于设备转发的控制。第一类应用是基础的能力，是必须要有的，但是它体现不出交换机的特色，因此这类功能一般都集成在操作系统中了。第二类应用与ASIC打交道，负责控制数据包的转发，是交换机的关键应用，能够真正地体现交换机的水平。如果要对操作系统进行继续的分解，那就是把内核和第一类应用抽离出来作为通用的平台，准确地说，NOS指的应该就是这一块，然后第二类应用再由专门做协议实现的厂商来做。

如之前所说，白盒上的转发应用可以分为OpenFlow Agent和传统的协议栈两种，OpenFlow Agent这一块开源的OpenvSwitch已经非常成熟了，而协议栈这块Cumulus、Pica8、Pluribus可都是紧紧攥在手里的。Google没有使用传统的协议栈，Facebook的BGP没有开源，微软和DELL也是犹抱琵琶半遮面。OCP中有一个项目叫作FlexSwitch，就是一个开放的L2/L3协议栈套件，目前可以提供VLAN/STP/LACP/BGP/OSPF/ECMP等基础的功能，后面计划要扩展对IPv6/MLAG/VxLAN/MPLS等的支持。FlexSwitch是个不小的工作量，其背后是一个名叫SnapRoute的创业公司，SnapRoute成立于2015年，是由苹果和思科的一些技术人员跳出来组成的团队，他们用GO语言重写了L2/L3协议栈。2016年6月，SnpaRoute把自己部分的代码作为FlexSwitch贡献给了OCP，然后在同一年的年底和DELL一起从HPE手中接过了OpenSwitch的大旗。

SnapRoute半年内接连在OCP和Linux基金会中占领了高地，在白盒的圈子内激起了不小的波澜。虽然说白盒各个厂家都打着开源的旗号，但是像SnapRoute这种直接开源商用协议栈的思路确实过于激进，不知道Cumulus会作何感想。说到底开源只能作为口号，真动了自家的利益可就不能拿着理想来骗自己了，任何的白盒厂家也都不能例外。

2.2.4　机箱级开放

芯片和软件都有了，白盒还得有ODM来完成组装。传统的网络厂商对自己硬件的设计都是秘而不宣的，SDN来了之后，各个ODM开始有机会进入网络设备领域。一个盒子里面都有什么呢？硬件方面主板上的主要部件有BIOS、CPU、交换芯片、主板管理芯片（BMC），以及SFP、电源、LED、风扇、USB这些外围器件；软件方面是操作系统以及众多的交换机应用程序。

在硬件方面，交换机是不需要服务器中的显卡和声卡的，对于内存、硬盘的要求也远远低于服务器。交换机相比服务器主要多的就是交换芯片，白盒交换机的设计多把交换芯片看作PCIe设备，CPU和交换芯片间往往通过PCIe通道进行通信。一块交换芯片能够连接的端口数量是有限的，核心交换机通常需要很大的端口密度，因此在设计盒子的时候可能需要对多块交换芯片进行互联，芯片互联架构的设计会导致影响时延、信号串扰和散热等诸多问题，将直接决定交换机整体的性能。另外，交换机的端口除了密度高，速率上通常也要比服务器上的网卡高，白盒的诞生伴随着的光模块成本的下降以及40GE/100GE的推广，大量SFP/SFP+/QSFP的使用也会对硬件的设计造成很大的影响。ODM的任务就是做好主板的PCB设计，把交换机中的芯片和外围器件有效地“攒”在一起。目前主流的几家白盒交换机ODM包括Accton、Quanta和EdgeCore等。OCP中很多ODM都开源了自己的设计规范，Facebook也把自家的Wedge（ToR）和6 Pack（Spine）开放了出来。

光有硬件肯定是不行的。白盒出厂的时候是裸机，怎么才能装上操作系统呢？当然，可以把USB启动盘插在交换机上，然后像给服务器装系统一样安装交换机的OS。不过，这种方法对于数据中心管理员来说可就不是什么好事情了，他们要面对的是成百上千的交换机，一台一台插USB手动装系统是不现实的。好一点的办法应该是类似于服务器的PXE启动，交换机插上电之后，自己到远端下载操作系统然后再自动装上。这一技术对于白盒来说很重要，其实现依赖于Cumulus为OCP贡献的ONIE（Open Network Installing Environment）。ONIE是一个集成了Busybox的小型Linux，裸机第一次上电时BIOS从Flash中加载ONIE，ONIE会自动到远端去下载对应的白盒操作系统，将该系统存放在硬盘中，并进行系统的安装，如图2-13所示。第二次上电时，BIOS就会跳过ONIE，直接从硬盘中加载已经下载好的白盒操作系统。Cumulus还向OCP贡献了APD（ACPI Platform Description），以适配不同的BIOS。ODM在完成组装后，都会在裸机的Flash里面预装ONIE，然后交付的才是一个名副其实的“白盒交换机”。

图2-13　白盒交换机通过ONIE自动加载操作系统

2.2.5　白盒的“通”与“痛”

实际上，传统的设备厂商和商用芯片公司也有着密切的合作，不过要自己来做软件和盒子的贴牌，比如Arista向来就是这么做的。Arista可以算作白盒厂家吗？这取决于Arista是否愿意把EOS开放到Accton、Quanta的盒子上去。实际上，早在两年之前就有过传闻Arista会把EOS放到DELL的盒子上，但此事并无下文。Arista和微软合作参与了SONiC的推进，虽然不能代表什么，但是至少说明Arista对于白盒的实际态度是不排斥的。Juniper在市场上已经向白盒示好了，QCX 1100可以装非Junos的OS，而Junos也已经有版本可以运行在其他厂商的白盒裸机上了。Cisco的态度如何呢？其新产品线N9K的设备里面大量地使用了Broadcom的Trident 2，然后基于自家的ALE和ASE做优化，但目前还尚未见到完全基于Trident 2的Cisco设备。

大家都知道白盒是不可逆转的趋势，正如Cisco前CEO钱伯斯所说，“网络设备商的好日子过去了”，厂商们所能做的也只是尽可能地推迟这一天的到来，而实际上他们也都已经在留后手了，Juniper甚至已经成了白盒的主力军。但是网络真正走入白盒时代会是哪一天呢？目前来看这还需要漫长的时间。除了传统厂商方面给的阻力以外，白盒自身也还面临着一些的问题。一方面，采购上的分解就意味着责任的分解，如果盒子出了问题到底该找谁？另一方面，如果用了开源的技术堆栈，那么后期维护成本将会大大增加，而且一旦失去了厂家的保障，可不是什么问题用户都能自己搞定的。白盒到底应该是个什么样的商业模式？恐怕现在还没有一家公司心里面是有底的。

白盒的概念是Google、Facebook引爆的，他们是技术发展的风向标，业界每天都在琢磨着他们的技术。然而，星星之火并没有燎原，这些OTT巨头们有研发、维护自己硬件和软件堆栈的能力，在基础设施上愿意尝试新的东西而且肯投钱，一般的企业难以望其项背。这种技术能力上的巨大差异，使得传统厂商目前仍然可以稳坐钓鱼台。不过，云计算的普及，企业的IT建设逐渐向云端集中，云服务商探索新技术的动力就成了那把白盒打开新世界大门的金钥匙。若干年后，传统设备的成本摊销掉之后，或许就是白盒发起总攻的时候了。

2.3　网络可编程——百家争鸣

可编程是SDN最基本的特征，从某种意义上讲，提供了可编程能力的网络都可以归为广义SDN。可编程能力可以体现在很多的层次上，从下往上说，有芯片可编程、FIB可编程、RIB可编程、设备OS可编程、设备配置可编程、控制器可编程和业务可编程。业界不同方面对于SDN的理解不同，因此不同厂家提供的SDN的开放能力也存在很大的差异。

可编程离不开接口和协议，本节会涉及许多与SDN相关的主流接口或者协议。大多数协议的技术细节复杂而艰深，笔者没有能力通过一节的内容使读者了解它们的技术细节，而这也并不是本书的初衷，仅希望能够帮助读者对各协议技术背后的东西有所了解，也希望给读者解释清楚业界对于这些技术的一些普遍误解。

2.3.1　芯片可编程

芯片可编程可以说是最为彻底的SDN，未来有可能从根本上变革传统数通网络的思维。我们知道数通网络的基础是payload前面各种各样包头的使用，不同的包头往往用于不同的业务，每当有了一个新的网络需求，厂家就在IETF提草案来规划包头字段，再用一套新的协议来规范其控制逻辑。思路想得差不多了，软件上就开始开发新的协议，硬件上就重新设计电路，然后流片再到量产投入商用。这个过程是很漫长的，没有两到三年是做不下来的，除了巨大的人力投入以外，通常还伴有一些天价的授权成本，而且每设计一个新的包头，这样的工作就要重来一次。经过二三十年的积累，包头和协议多到令人眼花，彼此之间的关系错综复杂，增加新东西要付出的代价也越来越高。

1.P4

芯片可编程的代表技术是Nick McKeown教授提出并推动的P4，被学界寄希望于成为OpenFlow 2.0。相比于OpenFlow，P4可以提供对新包头的自定义能力，要求芯片的解析器、匹配字段、处理动作以及流水线是完全可编程的。通过编写P4程序，可以定义包头字段的解析方式、匹配表的数据结构、匹配后的处理动作以及规范各级表间的走向关系。准确地说，P4是一种支持设备可重构的编程语言，使得新的网络需求的实现不用再等待上述漫长的过程。不过，需要明确的是，P4仅仅是一种语言，最终数据包的处理还是要实打实地落到芯片上去，所以为了使P4程序的重构逻辑能够实际部署下去，还需要通过编译器将其转化为芯片的控制逻辑，不同的芯片需要不同的P4编译器。P4是可以运行在CPU/NP/FPGA等不同的芯片架构上，不过CPU/NP/FPGA相比于ASIC，都有着不同程度的性能损失，因此Nick和他的团队提出了协议无关的交换机架构（Protocol Independent Switch Architecture，PISA），宣称可以在兼顾到现有CPU/NP/FPGA灵活性的同时，达到甚至超过现有ASIC的性能。

Nick教授作为公认的SDN之父，他对于SDN的理解显然是要远远地领先于这个时代的。业界目前对OpenFlow仍然是以观望为主，ONF为了OpenFlow的顺利落地提出了TTP（Table Type Pattern，可理解为基于特定硬件的流表），通过增加一个编译层来弥补OpenFlow与现有ASIC架构之间的鸿沟。而Nick的理想显然不在于此，P4实际上只是一个生态上的产物，是“醉翁之酒”，而PISA才是最终的“山水之间”。在OpenFlow命运的十字路口上，TTP向右而P4向左。不过，任何事情追求单方面的极致都会出问题的，性能和灵活性都是需要付出成本的，业界多少年的思路都是用硬件解决性能，用软件解决灵活性。如果PISA真能实现其技术理想，P4再慢慢把生态做起来，网络行业或许真的就要到变天的时候了，不过从目前来看，仍然是向右走是主流的路线。

2.POF

除了P4以外，华为提出的POF也可以看作芯片级的可编程。前面也提到了，CPU/NP/FPGA这些可编程的硬件都可以用来做网络重构，其中CPU和FPGA在性能上的缺点是比较明显的，而NP可以在性能和灵活性间达到一个较为平衡的折中。实际上，业界对于NP的使用已经相当广泛了，在一些核心高端的路由器、防火墙里面经常可以见到NP的影子。NP的可编程能力很强，做到协议无关没有任何问题，但是由于微码的开发门槛过高，即便是开放出来也很难用。于是，POF把华为的NP微码中与协议解析相关的逻辑抽象出来，将其扩展到了OpenFlow中，使得控制器可以基于“偏移量+长度”的方式来自定义新的字段以及制定相应的转发规则。这些规则通过OpenFlow下发给交换机，然后由交换机本地转微码部署到NP当中，从而满足新的网络需求。POF和P4的区别在于：POF依赖的是NP原有的一部分能力，然后为其进行了进一步的抽象，提供的是一种“支持协议无关的OpenFlow”；而P4是一种高级语言，不依赖于任何芯片，旨在“转发通道级别的全面抽象”。相比于P4更多的理想主义色彩，POF要更加务实一些，或者说是“用新瓶装了旧酒”。不过对于华为来说，盒子的市场仍然在提升，开放芯片并没有实际的商业驱动力，因此POF提出后华为也没有花大力气去推。

2.3.2　FIB可编程

FIB可编程就是提供直接对转发表进行控制的能力。FIB可编程的代表技术就是大家所熟知的OpenFlow了，这里指的OpenFlow还是OpenFlow 1.X。从2016年下半年开始，ONF已经在尝试把P4的思路融入OpenFlow里面去了。实际上，OpenFlow是不止于FIB可编程的，但它主要是围绕FIB可编程来做的。

OpenFlow在圈子内毁誉参半：学术界对OpenFlow有着同根同源的情怀，IT出身的人认为OpenFlow使用简单可以满足复杂的需求，运营商对OpenFlow将信将疑，大部分厂商则对OpenFlow嗤之以鼻。不同方面对OpenFlow的说法各执一词，也导致了圈子内出现了很多对OpenFlow片面的批评或者过度的赞誉。下面是笔者经常被问到的一些问题或者曾经听到的一些表述，这些说法某种程度上来说都有一定的道理，但是如果不予进一步的思考，很容易导致技术思维出现混乱。下面就把它们整理在一起集中做一些分析，希望还给读者一个真实的OpenFlow。

先来看看负面的说法。

·好久没更版本了，OpenFlow要不行了。OpenFlow 1.5之后确实很久没出新版了（OpenFlow 1.6只对ONF会员开放），但很大程度上是因为前期推进得太快了，很多不错的增强都还没有落实到硬件交换机上。再追求速度出新的版本也没有意义，而且ONF也在考虑P4和OpenFlow的关系。测试和一致性认证是一致在推动的。

·OpenFlow是为了学术圈发论文用的。学术圈确实靠着OpenFlow出了不少论文，这是件好事，因为OpenFlow把网络的门槛降低了。而且基于这些论文，也有很多思路变为了产品。

·OpenFlow只能在数据中心落地。大网上推OpenFlow遇到的问题确实远远多于数据中心，不过通过对OpenFlow进行一些扩展和定制后，还是可以在大网上找到应用场景的。传输领域的OpenFlow已经得到了不错的扩展，包括PTN和OTN。

·OpenFlow只搭VxLAN。SDN不等于OpenFlow，SDN也不等于Overlay。OpenFlow是集中式实现Overlay的一种方式，但OpenFlow最擅长的不是Overlay而是策略。OpenFlow和VxLAN容易被联系在一起，很大程度上是因为OVS在云里面火了，而OVS恰好对这两个都支持。

·OpenFlow就是ACL。从设备角度来看，可以这样简单地理解，因为OpenFlow流表里两个核心概念match和action，和ACL都能对得上。不过OpenFlow规范中match和action的丰富度要高得多，但是交换机上实现了哪些要看具体的产品。

·OpenFlow只能基于流进行编程。OpenFlow的确具备对细粒度的流进行编程的能力，但没有强制要求必须基于流进行编程。

·OpenFlow就是静态路由。只match目的IP+前缀的流表可以看作类比于由控制器下发的静态路由，这只是OpenFlow很多用法当中的一种。

·OpenFlow在妄想着干掉IGP和BGP。没有任何官方表述有类似意思的表露，OpenFlow控制器向来把IGP和BGP看作与传统网络互通的东西向协议。在设备数量较少、设备自主可控的情况下，OpenFlow具备代替IGP/BGP的潜力。

·OpenFlow交换机只能实现二层功能。控制器可以模拟网关的逻辑，流表可以完成基于IP的转发并改写TTL和MAC，实现路由没有问题。

·控制器必须集中式、端到端控制。OpenFlow控制器可以部署在OpenFlow交换机本地，只负责这一台交换机的流表。虽然损失了集中式控制的一些好处，但是仍然可以获得转发控制分离的所有好处，可用性也有很大的提高。

·控制器挂了，转发就挂了。OpenFlow协议明确要求交换机支持standalone模式，即便控制器挂了，原来的流表仍然可以继续保留在数据通道上。

·首包都要PacketIn，流量大了控制器就崩了。PacketIn只是为控制器提供了一种触发式Reactive编程的可能，控制器知道的信息可以在首包产生之前就预置到交换机中。使用OpenFlow的生产环境中，对于大部分流量采用预置流表的Proactive方式，可以有效地减少流量对控制器的冲击。

·PacketOut和Statistics类的消息，对控制器消耗太大。ARP/LLDP/LACP/ICMP可以卸载到交换机本地，OpenFlow规范没提，不代表OpenFlow交换机不能做。Statistics过于密集地上报控制器确实是有问题的，OpenFlow本身也不是为了这个设计的，如果实在需要，可以通过auxiliary connection来进行优化。

·FlowMod数量太多，交换机资源不够。当FlowMod的粒度比较细的时候，确实面临着这个问题，但是有很多办法可以解决，比如流表分级、流表聚合等。

·传统的ASIC支持不了OpenFlow。ASIC可以支持OpenFlow，只要另外加适配即可，比如OF-DPA。ONF也在积极推动TTP和NDM。目前的OpenFlow交换机大部分是基于ASIC做适配的，不过基于传统ASIC实现OpenFlow肯定是有很多限制的。

·OpenFlow必须依赖TCAM。TACM是OpenFlow需要的硬件资源中的一种，CAM、LPM、RAM在实际的OpenFlow交换机中都扮演着不可或缺的角色。

·OpenFlow没有网管能力。OpenFlow不是专门为网管设计的，不过可以实现网管的一些功能。如通过收集Statistics可以做一些流量统计，通过配合使用PacketIn/PacketOut/FlowMod可以发一些探针的包来监测路径。

·OpenFlow多控制器机制做得不好。OpenFlow只是南向协议，只木能规范交换机看待多控制器的视角，没有权利规范多控制器间的控制。OpenFlow交换机可以连接多个控制器做主备，但是目前协议中没有对负载均衡进行规范。商用OpenFlow可以自己扩展负载均衡机制，比如把不同的消息送给不同的控制器。

·OpenFlow协议扩展性差。厂商可以自己定义Experimenter。而且OpenFlow 1.3就已经有40多个字段了，现有的OpenFlow交换机支持到20个左右的字段是也没有问题的。怎么用这些字段是控制器可以自定义的，对于不需要互通的私有场景，控制器好好规划20个字段基本足够用了，再增加新的字段是否真的有必要？

·OpenFlow无法实现带状态的控制。通过PacketIn，控制器可以实现一些带状态的控制，比如NAPT，但是性能上会有很大问题，如果把OpenFlow控制器拉到交换机本地会有所缓解。另外，OVS已经基于Conntrack扩展了match和action，在内核中即可实现带状态的处理。

下面再来看一些浮夸的说法。

·Nicira、Google用的就是OpenFlow。Nicira和Google确实用了OpenFlow，不过都发现了OpenFlow的一些问题，都基于OpenFlow做了私有的扩展。

·Neutron OVS用的就是OpenFlow。Neutron OVS没有用OpenFlow，虽然数据库是集中式的，但转发的控制逻辑还是分布式的（不考虑l2_pop及其他扩展）。

·OpenFlow能实现所有网络功能。控制器理论上来说确实是万能的，流表不能处理的事情PacketIn和PacketOut控制器都是可以做的，不过这有两个前提条件——为OpenFlow控制器集成其他的协议收集状态；可以忍受时延与性能下降。

·集中式控制就是OpenFlow控制器包办一切。这个说法早期短暂地出现过，不过大家很快意识到这种做法的不合理性。就像前面说过的，部分功能可以卸载到交换机上。

·Counter可以搞定一切运维。Counter只能记录和流相关的参数，不能覆盖和设备本身的状态和配置信息，需要配合使用SNMP/OF-CONFIG/OVSDB。不是所有交换机厂家都支持Counter，号称支持的可能支持得不全面。

·有了Barrier就可以搞定事务。Barrier只是能够保证交换机上执行控制器命令的顺序，做一些简单的状态同步是可以的，但是Barrier远远搞不定事务，比如post-commit和roll-back。

·所有功能都可以通过流表卸载。流表通常只能match到传输层，应用层是无法处理的，因此HTTP、DHCP和DNS流表都是没办法实现的。

·可以进行任意粒度的QoS保障。配合OVSDB在控制器上虽然可以端到端地开通QoS路径，但是控制层面通了不代表数据平面能够正常干活。QoS最后是要落到端口队列上的，而硬件交换机单端口的队列数量是非常有限的，做到为每条流都分配一个队列几乎是不可能的。

·OVS就是专用于OpenFlow。OVS是“原生支持OpenFlow的虚拟交换机”，而不是“专用于OpenFlow的虚拟交换机”。在很多云计算环境中使用了OVS，但没有使用OpenFlow。

·不想用OpenFlow干的事情，Normal掉就可以了。协议中Normal不是必选而是可选，OVS实现Normal没问题，不代表硬件交换机实现Normal就没问题。硬件上支持OpenFlow和传统协议双栈的互通是很困难的，所谓的Hybrid OpenFlow交换机，一般都是基于物理端口或者VLAN把双栈隔离开的。

·OpenFlow适合做安全。没错，OpenFlow适合做安全策略，但是OpenFlow自身又引入了很多新的安全问题。

OpenFlow是幸运的，又是不幸的。幸运的是借“软件定义一切”的东风着实火了一把，不幸的是初出茅庐就被给予了过高的期待，面对着NETCONF、BGP这些“SDN原住民”，多少显得有些稚嫩。不过每种协议都有自己的问题，不应该一棒子打死，用在该用的地方，OpenFlow还是可以所有作为的。

2.3.3　RIB可编程

RIB可编程就是通过对路由表或者其他路由相关信息进行控制，从而间接控制转发。FIB可编程主要面向OpenFlow交换机，RIB可编程则是传统路由器向SDN演化的常见路线。相比于FIB可编程，RIB的演进思路要平滑一些，在大网上具备广泛的部署基础，目前普遍为厂商和运营商所接受。

1.BGP

传统路由中，BGP是绕不开的话题，Internet的成功很大程度上要归功于BGP，几乎所有的路由器都可以提供对BGP的支持。BGP如何与SDN相联系的？首先，IBGP中天然地存在RR这种集中式的角色，域内所有BGP路由器都和RR建立IBGP邻居，然后这些路由器把自己知道的路由告诉给RR，再由RR反射给其他路由器。传统网络中，RR都是部署在硬件的路由器上的。如果把RR的代码移植到SDN控制器上，那么控制器就有了收集BGP路由的能力。如果再把控制器上的RR模块开放一些接口，使得它在反射前可以通过一些安全策略对反射路由进行过滤，或者通过一些算法来优化反射的路由，比如改写下一跳、改变Local Preference等，那么就可以实现集中式的智能选路了，这通常被称为RR+。

将RR引入SDN控制器还可以实现OpenFlow或者其他SDN网络与传统IP网络的互通，控制器一方面通过OpenFlow收集OpenFlow域的状态并为其生成路由信息，一方面通过IBGP收集IP域的路由信息，由控制器作为控制平面的网关将OpenFlow和BGP进行转化，就可以互通OpenFlow和IP网络的路由信息了。除了通过IBGP进行对接的方式，控制器还可以通过将EBGP消息在特定的OpenFlow端口进行PacketIn和PacketOut，通过OpenFlow交换机来中继EBGP与传统路由器来建立邻居，可以打通物理邻接的OpenFlow网络和IP网络。

BGP是网络领域的老江湖了，经过30多年的发展，无论是在性能还是可扩展性、可用性上都已经为业界所广泛认可，因此在看到BGP与SDN的结合点后，业界有很多声音都在呼吁用BGP取代OpenFlow。虽然有一定的道理，但是要辩证地去看待这个问题。首先，BGP与SDN控制器的集成面临着非常现实的问题。成熟的BGP代码都掌握在厂商的手里，他们是否愿意把自己的BGP集成到控制器里面去就是一个问题。而且厂商的BGP实现基本上都是基于C语言开发的，而目前主流的SDN控制器都是基于Python/Java的。如果直接重写，工作量太大，而且由于语言上的效率问题，使用Python/Java来实现BGP，尤其是RR，性能上要有很大的折损。如果直接集成，多语言的混合编程很可能引入新的性能瓶颈，未必能够达到预期的效果。比较现实的做法，是把BGP独立地跑在控制器外面，通过API来进行交互，这样的话数据库也是两套，是一种双引擎的做法。

其次，传统的BGP是专门为路由设计的，对其他网络功能并没有过多考虑。MP-BGP为BGP提供了良好的可扩展性，基于NLRI可以扩充很多的功能，如BGP-LS用于拓扑发现，BGP-LU用于分配MPLS标签，BGP-FS用于安全与策略，以及各种各样BGP-Based的VPN。但是这些BGP的扩展尚未成熟，支持的厂家互通性不够理想，而且限于路由的大框架，其灵活性上也很难有本质上的提升。另外，单纯从选路上来说，BGP主要面向的是面向分组的、无连接的路由场景，对基于流的、有连接特性的路由场景难以提供有效的支持。因此，OpenFlow既没有能力消灭BGP，BGP也没办法完全代替OpenFlow。

2.PCEP

PCEP是另外一种提供RIB可编程能力的协议。和BGP不同，PCEP主要面向的是MPLS域。在传统的MPLS网络中主要有两类场景：一类基本的场景是基于IGP中SPF的计算结果来构建起一条最小cost的标签路径；另一类高级些的场景是基于扩展IGP中CSPF的计算结果来构建起一条满足复杂路径约束的标签路径。SPF和CSPF虽然都是集中式的路由算法，但是各个路由器仍然是独立进行计算的，这可能会产生出现诸多的问题，导致全局最优的无法实现。PCEP的思想很简单，就是在网络中部署一个集中式的角色PCE，由PCE根据全局的视图来计算最优的路径信息，然后通过PCEP把计算的路由结果返回给PCC，由PCC再部署这条路径。也就是说，PCEP更适合做路径的端到端优化，而相比之下BGP只能控下一跳，适合做局部的拥塞缓解。

可以看到，PCEP对网络进行集中式控制的思想和SDN是不谋而合的，但是要清楚，PCEP不是专门为了SDN而设计的。PCEP对PCC和PCE的形态没有进行要求，PCC通常为路由器中的Agent，而PCE既可以部署到网管中，可以部署到OSS中，也可以部署到路由器中。很容易想到，如果将PCE实现在SDN控制器中，再开放出一些选路策略的接口，就能够将SDN可编程的能力引入MPLS网络中了。PCEP本质上就是一种新的RIB控制逻辑，对硬件本身没有新的要求，因此PCEP可以无缝地升级到路由器中。而且以PCEP的工作模式，只要全局视图能够收集上来，算光层的路径也没有任何问题。因此，PCEP目前被广泛地认为是大网向智能调度、甚至IP+光协同的支撑性技术。

不过，PCEP仅仅是一种PCC和PCE间交互转发路径的通道，至于路径是怎么算出来的并不在PCEP的范围内。计算路径要依赖的全局视图，PCPE只能收集路径状态，拓扑要靠BGP-LS，TED要靠OSPF-TE/ISIS-TE，设备和流量状态要靠SNMP/NetFlow。组网上，PCEP一般也只和路径起点的Ingress Router相连，端到端路径的开通还是要靠IGP/LDP/RSVP/BGP-LU这些来分发标签。不过，PCEP中的OBJECT和BGP中的NLRI一样，也具备很强的可扩展性，因此PCEP在IETF的工作组也在提各种各样的PCEP扩展，希望能够在PCEP中原生地支持上述协议的一些功能。不过这些扩展离成熟还差得比较远，目前来看，想拥有一套完整的PCEP调度方案，还是需要配套好多协议的。

PCEP做路径计算确实是很不错的，但是OpenFlow控制器也可以做状态收集，复杂路径的计算，现在对光层的支持也在日渐完善，那么PCEP的调度方案和OpenFlow的调度方案有什么不同呢？功能上，PCEP想做的事情少，可以把路径计算这件事做得更好，这是没错的，后面提出来的Stateful PCEP能够支持维护路径的状态。不过前面说了，想做的事情少意味着PCEP要配合其他的协议来做解决方案，多个协议要良好地配合在一起，增加的开发与维护成本又有多少呢？性能、扩展性、可用性上，如果说BGP的优势在于实现已经趋于成熟，那么PCEP的第一个RFC也是2009年才成稿的，协议本身相比于OpenFlow又能成熟到哪里去呢？目前，还没有一家厂商敢说PCEP可以在运营商现网落地带业务了。

上面对BGP、PCEP发了几句牢骚，并不是说OpenFlow就有多好，而是想说请多给OpenFlow一些耐心。到底是FIB可编程好，还是RIB可编程好？要笔者来说，BGP现网上很成熟，但想在SDN里面用好还要下很大的工夫，PCEP做转发控制更专业，但场景也相对受限，OpenFlow更灵活，但想上规模需要花精力去填坑。抛开场景和技术条件来谈哪个协议好哪个协议不好，是没有任何意义的。没有最好的，只有最合适的。

2.3.4　设备配置可编程

1.CLI

这个话题可算是老生常谈了。CLI于网络非常重要，尤其是对于运维，很多时候都需要逐台设备地show，然后靠经验来定位问题。不过，如果敲CLI非要到设备前去插console口连，那实在是一件没有效率的事情，设备多了，人力的成本太高。最早的远程登录技术是Telnet，运维用自己本地的一台服务器就可以登录到远端的设备上去敲命令行。不过Telnet是明文传输，命令很容易被截获和篡改，后面为了安全提出了SSH。几乎所有的网络设备都支持Telnet和SSH，有了远程登录就可以写一些自动化的工具用于设备的集中式管理了。基于shell来写VLAN、ACL，可以说是网络运维的基础技能了。

远程登录只是个简单的通道，设备上有什么就只能用什么。而RPC（Remote Procedure Call，远程过程调用）可以使得这个远程通道上有一些自定义的能力，在远端可以任意地调用这些能力。相比于SSH+shell，RPC+高级编程语言能够描述更为复杂的运维逻辑，但是RPC需要对设备端的系统进行升级，传统的网络设备都不支持。随着云计算的发展，自动化变得越来越重要，Devops概念日渐盛行，数据中心的网络运维自然也要向更好的自动化方向发展。目前流行的Devops工具，包括基于RPC+Ruby的Puppet/Chef以及基于Python+SSH的Ansible，已经被广泛地用于云中，网络设备为了支持与云中其他资源的联合编排，也开始在设备中集成这些能力。

无论是传统的SSH+shell还是目前的Devops，只是CLI多穿了一件衣服，那么它们算不算是SDN呢？从严格的意义上来讲自然不是，大抵上只能归为自动化运维。不过，目前很多SDN控制器还在大量地使用CLI，这并不是因为SDN控制器的能力不行，而是由于厂商设备对其他接口协议的支持时至今日仍然不够成熟，很多情况下只有用CLI才能解决实际的问题。

CLI用在SDN中，最大的问题有两个：①不支持事务性配置，虽然可以通过RPC来包装一些状态机制，但是没有形成标准化；②机器的可读性很差，程序想要看设备的状态只能用show，返回来一大堆字符串根本不适合机器去做解析。为了在SDN中更好地对设备进行管理和配置，必须要解决CLI存在的这两个问题，目前以NETCONF为OVSDB最为常见。

2.NETCONF/YANG

NETCONF并不是什么新鲜的东西，Juniper从2003年开始提，2006年就出了RFC第一稿。从配置的角度来看，做网络实际上就是在配置一个分布式的数据库，网络的正常运行依赖于不同设备上数据间的配合，因此一个好的网络配置协议需要有良好的机制来维护这些数据的状态。SNMP已经被证明只适合做监测而不适合做大规模的配置，Puppet这些为IT编排而生的工具在网络领域也并不够专业，因此上述机制的实现需要新设计一套专用的、标准化的RPC。NETCONF就是为此而生的，NETCONF的RPC能够实现诸多良好的状态特性，比如配置的分阶段提交、分时生效、回滚、持久化，以及数据的加锁等。有了这些专用的RPC，网络配置的底层语法是做好了，但光有底层是不够的，还需要有合适的高层语言来对网络配置的语义进行统一的建模。相比于SNMP BER的人不可读性以及CLI字符的机器不可读性，NETCONF在数据格式上选用了人类和机器均可读的XML，XML自身有建模语言XSD和DSDL，然而它们都是为静态文档设计的，不能有效地满足NETCONF对语意的要求，于是YANG被提出并成了NETCONF指定的数据建模语言。

NETCONF确实是个非常好的配置协议，良好的事务性设计使得NETCONF有能力集中式地把网络和业务给配起来的。再加上YANG提供的可扩展性，理论上只要厂商愿意做扩展，那么用NETCONF配置BGP路由表、MPLS标签转发表甚至OpenFlow流表都是可行的。不过由于XML解析的速度存在瓶颈，再加上支持事务性对于数据库的影响，因此NETCONF还是更适合实时性要求不高的配置类工作，而不适合对实时性要求较高的控制类工作。

不过，对于YANG实际上是存在很多争议的。YANG第一稿RFC的提出是在2010年，当时业界并没有引起什么轰动，只是知道这是为了NETCONF新设计的一个建模语言。YANG的全称是Yet Another Next Generation，可相比于SMI、XSD和UML，除了改变了语法并做了一些简化以外，在网络领域YANG似乎也没有表现出什么明显的技术优势。被OpenDaylight采纳用于MD-SAL APP的建模，成了YANG的一个重大转折。对于传统网工来说，YANG module和SNMP中的MIB路子类似，相比于OpenFlow中全新的资源模型来说要好接受一些，再加上OpenDaylight提供的YANG-TOOLs能够实现YANG module到Java的自动映射，进一步省去了网工们在编程语言上的学习成本。随着OpenDaylight的推广，YANG顺理成章地火了一把。

不过YANG在一个地方是有优势的，那就是它在IETF的网管专业有一个专门的工作组，负责提出标准的YANG模型来增强各个厂家的互操作性，以便未来对网络进行统一的建模。对于运营商和OTT来说，这自然是求之不得的，但大多数厂商对这件事其实是没有什么感觉的，虽然本质上设备里还是那么一套东西，但真要改造起来可是个巨大的工程。除非用户要求，否则既然之前没有开放给你，为什么现在要主动开放给你？不过，统一YANG模型这件事对于网络的用户来说意义重大，还是需要用户主动去推进，然后从市场层面来带动厂商。总体来说，这件事在大形势上是乐观的，但统一究竟要花上多长的时间，还要看各方的博弈。值得一提的是，OpenConfig作为一个由Google主导、致力于YANG标准化的开源项目，可以进行长期的关注。

NETCONF还有一个小兄弟叫RESTCONF，数据建模还是用的YANG，不过把NETCONF的RPC映射到了RESTful API。其好处是HTTP的通用性更强，缺点是RESTful的操作受限于CRUD，相比于RPC在灵活性上有很大差距，因此在映射RPC的过程中损失了NETCONF的一些优秀机制，如数据加锁等。RESTCONF的流行同样也是受OpenDaylight所提携，当然也得益于它自身操作上的简单性。

3.OVSDB

除了NETCONF/RESTCONF以外，OVSDB也是比较流行的配置协议。实际上这个说法是有一点问题的，OVSDB是OVS的数据库，它的管理协议在RFC中叫作“The Open vSwitch Database Management Protocol”，这里讨论的是后者，下面将其简称为OVSDB MgP。OVSDB是一个关系型的数据库，支持事务，具备ACID的特性，因此OVSDB MgP对于事务也有着良好的支持。OVSDB MgP相比于NETCONF，RPC方面是类似的，数据格式上用的是JSON解析的速度比XML还要快一些，由于OVSDB MgP是专门为了OVSDB设计的，而OVSDB中的表以及表间关系都是固定的，因此OVSDB MgP也没有绑定专用的数据建模语言。

对于OVSDB MgP，同样有几个常见的误解。

·OVSDB MgP只能配虚拟交换机。很多白盒厂家都是基于OVS来做设备的OS的，因此白盒基本上都支持OVSDB MgP。一些传统厂商如Juniper、Arista的设备虽然不是基于OVS的，但也都支持OVSDB MgP，主要是为了和对接虚拟化环境做裸机的接入。

·OVSDB MgP只能配端口、隧道这些，没有办法配流表或者转发表。RFC里面没有对数据模型做限定，通过扩展两端的OVSDB Server和Client，是可以通过OVSDB MgP交互任何信息的。

·OVS就是OpenFlow交换机，OVSDB MgP必须配合OpenFlow使用。不连接控制器的时候，OVS可以当做一个传统的二层交换机来用，然后用OVSDB MgP进行统一配置端口、VLAN，不需要依赖于OpenFlow。而且根据上一条中所说，对OVSDB扩展后OVSDB MgP是可以配置流表的，很多SDN方案里面只有OVSDB MgP，而没有使用OpenFlow。

另外，OF-CONFIG和OVSDB MgP两者都可以用来配置OpenFlow交换机，因此也是比较容易混淆的。OF-CONFIG是ONF为OpenFlow配套提出的管理协议，OF-CONFIG用的是NETCONF的RPC，在上面做了自己的数据模型，和OpenFlow是强绑定的。OVSDB MgP是IETF中为OVS设计的管理协议，有自己的RPC，数据模型依赖于OVSDB，和OpenFlow没有强绑定关系。

关于设备配置可编程，最后再说一点。虽然提到网络配置通常是对交换机、路由器这些进行配置，但上述所提到的脚本、Devops、NETCONF/RESTCONF、OVSDB也是可以用来配置控制器的，数据模型建好了都是可以做的。但也千万不要被一些“NETCONF万能论”的说法搞晕了，真正做控制该用OpenFlow还用OpenFlow，该用BGP还用BGP。

2.3.5　设备OS和控制器可编程

无论是设备OS可编程还是控制器可编程，都是指内部编程接口的开放，设备上OS受限于盒子中的资源一般都是用C/C++，而控制器上的资源通常受的限制较少，考虑到大型框架的集成通常都是选Java。相比于做外部的协议，这一块的争议相对来说要少得多。要么项目本身就是开源的，厂商做些commit占个坑赚个好名声，要么是有人自愿把自己设备或者控制器的部分代码开放出来，这样的话别人家也一点都管不着。设备OS可编程，开源这一块是OCP在推，Facebook部分开源了自己的FBOSS，微软在推动SONiC，厂商的话，Arista的EOS是有开放SDK的。控制器可编程，主要就是开源的了，包括Neutron、OpenDaylight、ONOS和OpenContrail，厂商方面有一些基于OpenDaylight或者ONOS在做商用的版本，给的说法通常都是“可开放集成第三方APP”，但实际上是否行得通，还是只有试过才能知道。

2.3.6　业务可编程

业务可编程是通常所说的北向接口，控制器把底层的一些接口组合在一起进行适当的封装，然后把高层的业务接口暴露给开发者。从编程方式上来讲有“命令式编程”和“声明式编程”，命令式编程关注How，即业务的实施细节，而声明式编程关注的是What，即业务的最终目的。不过，哪些是What，哪些是How呢？实际上也并没有一个明确的界定。封装这件事其实只是为了方便人理解而已，机器是不需要什么封装的。对于不同的人，能够理解、愿意理解到的层面是不同的，因此封装是可以分出来很多层次的，这一层的How有可能就是下层的What。

目前圈子内流行的说法叫作“基于Intent的编程”，比如Cisco推的GBP和华为推的NEMO。从名义上来讲，两者都属于声明式编程，但GBP的封装层次要比NEMO更高一些。GBP的描述逻辑侧重于网络中不同应用间的通信策略，而NEMO的描述逻辑仍然要关注于物理网络的拓扑。当然，封装获得友好性是有代价的，封装层次越高，业务可以掌控的东西就越少。到底用什么呢？主要还是看业务场景。如果是云里面的多租户业务，那么网实际上只是辅助应用的，流量能跑通就可以了；如果是运营商的专线业务，那么网就处在核心地位了，自己做路径的规划就非常有必要了。

目前来看，GBP和NEMO都处在较为初级的阶段，技术上不是很成熟，暂时都还谈不上生态。其实业务可编程这块，实际上还是用户自己最了解需求，而无论是GBP还是NEMO都是SDN自己玩出来的，总是有些泛泛不着边际的感觉。相比之下，以场景为驱动的业务接口正在形成事实上的标准，比如云中的OpenStack Neutron、GBP和NEMO也都在积极地对接Neutron。运营商这边ONAP的轮子也已经开动了，未来不知道是否会发展成ISP的OpenStack。

业务可编程领域也有一些专用的建模语言，比如YANG和TOSCA。YANG前面介绍过了，TOSCA是OASIS为NFV推动的一种建模语言，和YANG相比主要多了编排的能力，YANG擅长描述一个网络业务，而TOSCA可以组合起多个业务。OpenStack已经使用了TOSCA，而YANG主要还是在OpenDaylight这块。

2.4　集中式控制——与分布式的哲学之争

转控分离与可编程都体现了SDN对于网络开放的愿景。除此之外，SDN还有一个衍生的特征是集中式控制，这两个特征的提出可以说都是对传统网络的深刻变革。对于开放性来说，技术实现上不是什么问题，但出于商业利益的考虑，对于究竟应该开放到什么程度的讨论往往是各执一词。而对于集中式控制，大家都承认这在特定的场景下是有商业价值的，但到底该如何实现集中式控制，它在技术上带来的新问题该如何解决，一时间也难于形成统一的业界标准。因此在笔者看来，SDN开放性的痛处在于重构行业的利益分配，而集中式的纠结则在于与分布式长久不衰的哲学之争。

集中式与分布式之争，其实是一个广泛而深刻的命题，小到家庭公司大到社会国家，每个文明系统的组织机制都会面临着在二者之间的抉择，管制带来的是效率与统一，而民主意味的是公平与稳定。管制与民主之间永远存在着博弈，最终势必会趋向于在系统的不同层面上形成不同的组织机制。

作为人类文明中数一数二的大型系统，通信网络的组织机制自然也摆脱不了集中式与分布式之间的争端。Internet的原型是ARPANET，ARPANET最初是美国国防部为军事战争设计的网络，考虑到军网对高可抗毁性的要求，因此基于分组无连接和分布式控制的IP得以发展。随着民用通信网络需求的出现，ARPANET演进为早期的Internet，IP同时要为Internet提供良好的可扩展性，因此IP延伸了分布式控制的思想，以OSPF和BGP为代表的路由协议进一步奠定了分布式控制在传统网络中的基础性地位。尽管如此，历史上实际上并不缺乏对于集中式的尝试，这不仅存在于学术界，比如ATM LANE中使用集中的服务器来做ARP解析以仿真Ethernet，PSTN的七号信令则通过专用的带外控制平面来建立、拆除电路连接，即使是在IP网络中，还是能够看到集中式的一些影子，比如BGP RR和OSPF DR/BDR通过在设备中选举特殊的角色来扮演局部网络中的集中控制点，比如SNMP通过对设备资源模型进行标准化以便在远端进行集中式管理，比如WLAN AC能够在带外对瘦AP的转发进行集中控制，等等。不过这些集中式技术，或者只是为了辅助分布式控制技术，或者只是为了简化网络管理，或者受限于场景和规模，都没有对分布式控制技术的地位产生本质上的影响。

随着互联网应用的爆炸式发展，连通性变成网络最基本的能力，人们开始转而关注网络连接的质量与灵活性，此时分布式控制无法感知网络状态，管理配置复杂的缺点开始显现出来。而集中式恰恰善于解决上述问题，但苦于难以找到合适的替代技术，因此只能采用打补丁的思路来对现网进行改良。在SDN被提出后，人们终于看到了使用集中式代替分布式的理论基础，学术界吹响了使用OpenFlow彻底代替传统网络的号角，创业界似乎普遍找到了“撬动地球的杠杆”，媒体也开始大规模为SDN造势。

然而，理想与现实之间总是存在着巨大的鸿沟。相比于复杂臃肿的传统网络，基于OpenFlow的网络设计确实是简单而且灵活的，非常适合用于小规模网络中的创新。但是由于集中式可扩展性差的老大难问题，另外考虑到传统网络的巨大存量无法直接废弃，因此业界的脚步远远没有跟上学术界吹响的“网络革命”号角，如何基于传统网络向SDN进行逐步的演进则成了业界更为务实的发展战略。

抛开复杂的市场因素，单纯地从技术角度来看，分布式控制带来的好处是稳定和可扩展，缺点是没有办法做到全局最优，相反集中式控制的优点是擅长全局的调度，不足之处在其可扩展性较差。因此，简单地评判二者孰优孰劣是有失偏颇的，客观性上必须要承认二者是互补的。综合考虑，一个好的SDN设计既需要高效率、易管理，又需要稳定和可扩展，这要求兼顾如下两个基础性原则（后续会分别详细讲解）。

1）控制与转发通道相分离，形成逻辑上的集中点，它掌握实时的网络全局视图，能够进行业务的自动部署以及网络的动态调优，并提供整网的单一管理入口。

2）需要为逻辑的集中点引入分布式协作能力，以提高SDN网络的可扩展性与可用性，并支持与传统网络的兼容与互通。

2.4.1　在功能上找到平衡点

先来说第1个基础性原则。

首先，“控制与转发通道相分离，形成逻辑上的集中点”，听起来简单但实际上是有很大的学问的，实际到了工程实现中会遇到很多问题，其中很多问题的答案至少目前还都是需要画问号的。传统的网络设备中控制和转发就是分离的，不过控制逻辑仍然分布在设备的盒子里。那么，要形成SDN的逻辑集中点，是不是一定要将控制功能从各个设备里拖出来？BGP RR和OSPF DR/BDR都是局部网络的集中控制点，但它们都是放在设备的盒子里的，如果开放了BGP RR和OSPF DR的控制权，这算不算是SDN？

其次，如果只拖出来管理功能，转发的控制能力仍然保留在设备盒子里，这算不算SDN？如果管理和控制功能都被拖出来，它们一定要放在一个物理实体中吗？管理功能都适合拖出来吗？比如OAM和LLDP。控制功能都适合拖出来吗？比如ARP/LACP和快速重路由。如果控制功能要拖出来，一定要拖FIB的控制出来吗？把RIB的控制拖出来算不算SDN？把逻辑从设备的盒子里拖出来后，逻辑的集中点怎么部署在网络里？带内，还是带外？如果把转发逻辑拖出来了，而且又要采用带内的部署方式，控制信令本身的转发怎么控制？

要回答上面的问题很不容易，因为不同的场景和需求决定着最终的设计。不过总的来说，还是有以下几点基本的参考原则的。

·管理和控制功能可以放在一起实现，也可以分开实现，具体要看实际的研发能力和存量网络的状态。

·控制信道的能力可能有必要进一步进行切割，一些交互密度高、时延敏感的能力是应该保留在设备中的，比如BFD和FRR。

·大部分情况下，不要企图用控制信道来承载的业务流量的传输。

·尽量使用预下发（Proactive）的方式进行控制，不得已使用触发式（Reactive）的控制方式时要考虑抑制控制信令风暴以及其他形式的控制器DDOS。

·采用带内的部署方式时，要防止出现先有鸡还是先有蛋的问题。

·最后，算不算是SDN的问题不在于集中式控制如何实现，因为它不是SDN最为本质的特征，只要是为网络开放出了某一层次的编程接口，都算是广义上的SDN。

从本书的后续章节可以看到，很多SDDCN的实现，尤其是OpenStack Neutron中的众多解决方案，它们将策略逻辑独立进行了逻辑上的集中式存储，而SDN的控制逻辑仍然被分散到了各个设备本地。其具体的实现放到后面再细谈，在这里先提出来只是为了说明：不同技术背景的团队所实现的SDN千差万别，千万不要狭隘地为技术扣帽子。

另外，“掌握实时的网络全局视图，能够进行业务的自动部署以及网络的动态调优”。网络全局视图的实时搜集是SDN最重要的支撑性技术之一，搜集的信息主要包括转发设备拓扑、终端的位置与网络参数、转发设备的资源状态，转发设备统计信息、流量统计信息等，搜集的手段多种多样，比如通过设计新的协议（如OpenFlow），或者直接通过现有的协议（如SNMP/sFlow/NetFlow），或者对现有协议进行扩展（如NETCONF、BGP-LS），也可以从专用的网管软件中获取处理后的数据。根据实时的网络全局视图，结合业务的需求，SDN控制器可以自动地部署业务，并能够动态地对网络进行调优，业务的部署主要是提供可靠的连接性和一致的策略，动态调优主要就是对网络路径进行重新规划，实现的方式同样多种多样（如OpenFlow/NETCONF/PCEP/BGP），不同的方式各有所长，读者可以回头再看看2.3节中的内容。

“提供整网的单一管理入口”，这一点技术上没有什么争议，但是要注意这个入口在不同场景中可能是不同的，这取决于用户的习惯。比如，各个网络厂商的SDN控制器都会提供自己的GUI/CLI，虽然适用于各个场景的网络管理，但是一旦对接了OpenStack，这个入口就会变成Horizon，毕竟人家才是云的Portal。当然，这也是从一般意义上来说的。对于一些大规模的网络，其网络运维方面的分工非常细致，那么将管理功能可以进一步进行分割，并为不同的分割单独不同的管理入口。

2.4.2　在扩展性和可用性上找到平衡点

再来说第2个基础性原则。

“需要为逻辑的集中点引入分布式协作机制，以提高SDN网络的可扩展性与可用性，并支持与传统网络的互通”。实际上，不只是传统网络中用到了分布式控制技术，任何大规模的IT业务系统都需要通过分布式技术来保证可扩展与高可用。当然，前面曾简单地分析了传统网络中分布式路由技术存在的一些不足，为了能够对网络进行全局化和自动灵活的控制，需要将各个设备中的逻辑提取出来（或者提取一部分），形成一个逻辑上集中的网络控制点。不过，SDN作为一个整体的系统，总需要有一部分来保证扩展性和可用性。因此，逻辑上的集中点并不意味着SDN控制器就是“孤家寡人”，实际上控制平面仍然需要通过分布式协作机制来实现。

这乍一听起来似乎有些荒唐，不是说SDN要解决分布式的缺点吗？难道这些缺点从转发设备中抽离出来，然后转移到控制平面就不存在了吗？虽然有些不好理解，但是这个问题的答案通常是Yes。这是因为，各个SDN控制器掌握着自己控制的本地网络中所有的实时状态（这些信息是在传统的分布式路由协议中各个设备所无法获得的），大家互通一下有无，每个控制器上就有了全局的网络实时状态。可是，控制器间的分布式协作不可避免地要带来时延，A控制器本地网络的实时状态信息转给B不会产生滞后吗？这个确实没办法从根本上进行避免，但所幸SDN控制器在数量上要比转发设备少1～2个数量级，因此大体上来说控制器间分布式协作所产生的开销，对实时性信息造成的滞后效应，是在可接受的范围之内的。

控制平面的分布式协作机制，根据面向的场景不同，分为以下4种实现形式。在对几种形式进行介绍和讨论之前，需要先澄清笔者对于“SDN东西向协议”这一概念的理解——“SDN东西向协议就是运行在异构SDN控制器间的接口协议，用来同步异构SDN控制器中的网络信息”，这里的异构既可以指不同厂家的SDN控制器，也可以指同一厂家不同的SDN控制器。一些容易产生误解的地方在于，有人把同一厂家的定位于不同层次的控制器间的协议理解为“SDN南向协议”，也有人把SDN控制器和传统路由器的通信理解为“SDN东西向协议”。在此做上述概念的澄清，没有哪个对哪个错之分，只是为了方便读者对下面的内容进行理解。

·同构控制器的不同实例间运行集群机制，保证SDN的可用性。

·异构SDN控制器间运行东西向协议，保证SDN的可扩展性。

·SDN和传统网络的兼容与互通，可能需要SDN控制器模拟传统的分布式L2/L3协议。

·以上三种场景的任意组合，需根据实际情况结合集群、东西向协议和传统的分布式路由协议对控制平面进行设计。

单域内的集群早就成了业界的共识，各个商用的SDN控制器和OpenDaylight/ONOS两个开源控制器平台都实现了自己的集群机制。集群的问题本质上是软件设计的问题，这个问题不在本节的讨论范围之内，下一节会对此进行详细的介绍。这里值得注意的是，即使已经具备了相当可观的高可用性，由于一些不可控因素，控制器集群仍然可能会出现问题，这就需要为转发设备设置一定的逃生机制，以避免大规模的控制器瘫痪造成的业务转发中断。

跨异构控制器的东西向协议，尤其是跨厂商控制器的东西向协议，对于行业未来的发展是至关重要的。尽管如此，这件事情目前推进得还比较缓慢，因为这里涉及太多的技术风险与利益互博。对于厂商来说，自家SDN控制器的未来都生死未卜，目前很难有动力去推动东西向协议的标准化，顶多是在自家的多个异构控制器间简单地互通一下而已，笔者个人理解这实际落地的可能性不大，更多是厂商为了进行市场推广的而已。IETF和IEEE实际上是厂商在主导的，对SDN需不需要控制器这个事情一直都是闪烁其词，更不会愿意花大力气来推控制器互通的标准。ONF在推广OpenFlow的心力上都不是很充足，暂时也没有看到对东西向协议有任何的想法。总而言之，大家对于东西向协议都是“叫好不叫座”，于是就出现了上面的“三不管”状态。学术界中倒是有一些尝试，或者基于层次化的控制平面设计，或者基于水平对等的模型来设计控制平面，不过这件事情由学术界来做实际的效果肯定不会太好，业界对此类论文还没有任何的实质反应。

可对接多厂商控制器这件事是一定要做的，尤其是对于运营商这种大体量的网络，否则用户还是得绑定在一家厂商的产品上，这和原来的景象并无二致。要做成这件事，实际上还有另外一个思路，就是在各厂商控制器的上面再做一层编排器，由编排器来规范接口，然后各个厂家控制器去做适配。换了个说法之后，这事做起来就要容易多了，厂商只能选择积极跟进，否则只能面临被淘汰的命运。云这块已经有了OpenStack，运营商稍稍落后，不过大家现在也都把这件事想明白了，目前正在积极地推进ONAP的发展。云提供商和运营商自己做编排器还有一个好处，就是可以把控住业务的入口，毕竟自家的业务还是自己最熟络，把业务交给设备厂商去做，一来不合适，二来也很容易受到制约。编排器通常被看作业务平面而非控制平面，但从架构上来看编排器会作为系统最顶层的集中点，至于编排器自身还要不要分级，就看用户自身的实际需要了，这块属于纯IT问题，争议要小得多。

SDN和传统网络的兼容与互通，和南向协议的选择有很大的关系。对于NETCONF/BGP/PCEP这些广义上的SDN，实际上是可以很好地工作在传统网络的框架下的。而OpenFlow网络和传统网络的互通，目前业界仍然在不断地进行着尝试。这是一个比较大的议题，涉及软硬件不同层面的问题，下面的内容只针对于控制平面的设计思路。以一个简单却比较典型的互通场景为例，该场景中SDN和传统网络覆盖着不同地域的设备，只通过边缘的设备进行互联，此时要求SDN控制器能够和传统网络进行分布式信令的交互。如果传统网络在与SDN互联端口上运行的是L2，那么控制平面就需要模拟MAC Learning/VLAN/xSTP/等二层逻辑；如果运行的是L3，那么控制平面上就需要模拟ISIS/OSPF/BGP等三层协议。

具体的技术实现上，主要有两种思路：第一种思路是直接把L2/L3的控制逻辑作为SDN APP运行在控制器上，由控制器直接与传统设备进行传统控制信令（如STP、OSPF、BGP等）的交互，互通SDN域与传统网络的控制信息，然后控制器做一些协议上的转换（类似于传统网络中路由重分布的概念），将传统网络的控制信息通过南向接口下发给SDN域内的转发设备。第二种思路是在控制器外面独立运行一个协议栈（如Quagga、BIRD等），专门负责与传统网络互通路由，然后控制器与协议栈间再互通控制信息，与前一种的区别在于，SDN控制器不再需要实现复杂的路由逻辑，可以通过一些私有的接口（或者简化版的路由逻辑）来访问协议栈的数据库，这种方式可以有效地减轻控制器端的开发压力。两种实现中，都有一个需要注意的问题：控制信令在网络中的通信路径，如果采用In-Band的部署方式，要把负责控制信令转发的OpenFlow流表写好；如果采用Out-of-Band的部署方式，控制器在注入路由时要注意下一跳的问题，防止路由器把流量直接引向控制器。

分布式与集中式，在SDN领域仍然会是一个长期争论的话题。既然没有绝对的好与坏，那么解决问题的精髓就在于平衡。Juniper内部流传着一句很精辟的话，作为上述内容的一个小结，希望能够引发大家更为深入的思考，摆脱对技术的争执，在工程上找到最适合自己的SDN。

“Centralized what you can，distributed what you must.If something can be centralized，and there are no physical or functional constraints，centralize it；otherwise，it should be left distributed.”

2.5　回归软件本源——从N到D再到S

说到SDN，人们最常联想到的有这么几个词，OpenFlow、数据中心虚拟化、广域网优化。从最开始的喊口号到前几年讨论场景，目前SDN已经开始真正地沉淀技术，商业上也进入了加速落地的阶段。或者也可以这么说，SDN近十年的发展逐步经历了从理解网络需求（Networking）到完善南向协议（Defined），再到打造健壮系统（Software）的过程。

在SDN的框架下，或者说在软件的世界里，实现大部分现有的网络功能都不会是一件太复杂的事情。抛开分布式协议的枷锁后，网络的创新再也不用等IEEE/IETF这些标准组织慢吞吞地输出文档了，用户对网络的需求会普遍转向个性化的定制和敏捷的交付，是时候从厂商主导的传统网络思维转向为用户按需提供网络服务的云计算思维了。

控制器是SDN向用户交付的主要产品，既然控制器在网络功能的实现方面足够灵活，那么SDN控制器的扩展性、可用性与性能往往会超越接口协议和网络业务本身，成为决定SDN能否商用的核心因素。因此，如何构建起一套可扩展、高可用的SDN控制器，才是推动SDN真正走向大规模落地的关键。

2.5.1　模块管理

长久以来，垂直一体化的行业特性使得网络技术的集合变得无比庞杂。虽然有标准协议的规范，但是各大厂商的产品之间，甚至同一厂商不同的产品线之间的设计区别都很大，跨厂商难于互通一直是用户的一块心病，这也导致了更为显著的厂商锁定现象。SDN除了能够提升业务敏捷度以外，用户同时还希望SDN能够帮助他们摆脱对于厂商的依赖，从而在谈判桌上拿到更多的话语权。除了肩负着驱动异构网络设备的重任以外，SDN控制器还要承载各式各样的网络基础服务的运行。因此，SDN控制器的目标应定位于在网络基础服务和设备驱动间构建起一个可扩展的、通用的网络控制平台。

早期的一些轻量级的开源SDN控制器，如Floodlight、POX和RYU等，用户都需要在启动前把需要加载的模块在配置文件中事先指定好，或者在启动的命令行中指定模块。在控制器运行过程中，如果希望启动另外一个模块，需要把控制器关掉后修改配置文件重启，或者在启动命令行中指定新的参数。而且加入一个服务出错了，那么整个控制器也就挂掉了。

上述问题在生产环境中显然是不可接受的，因此OpenDaylight和ONOS在设计之初都采用了OSGi（面向Java的动态模型系统）的框架。在这类框架下，SDN控制器中只保留少数的用于平台管理的核心模块，而将各种网络基础服务和设备驱动看作可插拔的外围模块，这些模块可以动态地加载/卸载到SDN控制器的核心中，各个模块间能够实现良好的隔离。这样的好处在于，新的SDN服务的部署、测试或者上线，并不需要对控制器进行停机升级，以保证现有服务的可用性。如果新的SDN服务的运行出现了问题，通常也不会影响到其他现有服务的工作。相比于Java在企业级应用框架方面不可比拟的优势，C/C++/Python在SDN控制器领域都暂时落在了下风。

2.5.2　模块间通信

有了动态管理模块的框架，SDN控制器接下来面临的一个问题就是如何在模块间进行通信。这里所说的模块间通信，是指发生在控制器内部的模块间通信，该机制设计的优劣会直接影响控制器的可扩展性，甚至影响到控制器核心部分的稳定性。实际上，OSGi的大部分实现中已经提供了模块间的通信机制，不过很多控制器也会自己重写通信机制来优化模块间的通信。

模块间的通信是一个非常复杂的话题，涉及控制器代码实现的方方面面，从通信模式的角度来看，可以分为点对点模式和发布订阅模式。在点对点模式中，源想要和目的通信直接调用模块的目标接口，这种方式实现起来最为简单，代价是增加了模块间的耦合度，一来出了问题容易导致连锁反应，二来模块多了之后接口后期维护的成本也越来越高。在发布订阅模式中，模块间的通信不再直接发生源和目的之间，而是在中间加了一个组件，由这个组件作为中继来协调各个模块间的通信，这样可以获得模块解耦带来的各种好处，而且通信方式更为丰富，中间的组件通过应用级别的路由可以实现点对多点的通信。

点对点虽然听起来没有发布订阅高级，但是从工程的角度来看点对点不受中间组件的约束，它的实现可以更灵活一些。发布订阅虽然提供了解耦合，但是中间组件的引入必然会带来通信效率上的开销，而且这个中间组件本身要足够稳定、强劲，否则它出了问题整个控制器就彻底崩溃了。因此，对于一个好的控制器的设计，两种通信模式都是需要提供出来的，供开发者自己来选择。

回头再来看发布订阅模式下那个中间的组件。它的实现通常是一个消息队列，发布消息的称为生产者，订阅消息的称为消费者。生产者产生了消息后直接把消息投放到消息队列，然后消息队列根据该消息的类型将其推送给相应的消费者，或者由消费者从特定的队列中轮询获得消息。消息队列可以看作一个投递信件的邮差，它对通信的内容实际上是没有任何感知的。除了消息队列以外，中间组件的另一种实现是以数据库为中心的，生产者把它所产生的数据写到数据库中，对这个数据感兴趣的消费者就可以从数据库中读到变化后的数据，或者由数据库将变化后的数据推送给订阅了该数据的消费者。数据库可以看作一个传话的信使，它对通信的内容是可以感知的。

基于消息队列来做发布订阅好，还是基于数据库还做发布订阅好？不同的业务有不同的考虑，而后一种通常来说会更好一些。假设控制器上现在有两个模块，一个是设备的驱动，一个是探测网络拓扑变化的，后一个的工作是要依赖于前一个的。有一个极端点的例子，是一个破坏者反复地插拔着设备上的网线，这个端口的状态就会不断地在通和断两个状态间发生变化。设备的驱动把这个破坏者的行为如实地记录了下来，然后它要通过一种把这些行为发布给拓扑模块。如果是基于消息队列的，那么拓扑模块有可能在收到大量消息的冲击后就挂掉了，但是这个破坏者仍然在不停地插拔网线，那么消息队列只能如实地把消息缓存下来，等拓扑模块恢复后再把消息一起推送给出去，于是拓扑模块就再次挂掉了。如果是基于数据库的，那么拓扑模块恢复后数据库只把当前端口的状态发给拓扑模块即可，防止对拓扑模块的二次冲击。如果状态变化实在过于频繁，还可以将其识别为安全隐患，从而无论实际端口状态如何变化，对于订阅者可以直接将其置位为Down，防止拓扑频繁变化对路由稳定性的影响。本质上，数据库带来的好处是由于它维护了通信上层内容的状态，而消息队列对于通信上层的内容是无状态的。不过，维护状态所带来的开销可能是非常巨大的，对于一个好的控制器的设计，发布订阅的方式也都是需要提供出来的，供开发者自己来选择。

观点听起来比较中庸。其实这主要取决于对控制器的定位，如果是要作为一个通用的平台，就需要给开发者最大的灵活性，那么把底层各种机制都提供出来是要更好一些的。如果是要做一个产品，解决特定的问题，那么就选一个最合适的吧。

2.5.3　接口协议适配

通用平台离不开适配。适配对于SDN的含义，通常就是指把控制器看作网络中的操作系统，对不同的业务或者设备，或者说对北向接口和南向协议，进行统一的抽象。对北向接口的抽象还好，南向的适配目前遇到了巨大的困境。

从日常的经验来看，这对于操作系统来说应该是天经地义的。之所以听起来这么自然，是因为一提到操作系统，大家的认识大多都是在个人电脑领域的，这个领域里的分工很明确了，做鼠标键盘的厂家没有自己来做操作系统的，微软也不会想着自己去做硬件。再加上Windows在市场上具有不可撼动的地位，所有的厂家都要围绕着Windows提供的IO接口来做驱动的开发。

然而，在网络的圈子里面可不是这样的。传统网络里面盒子的软硬件是一体的，全是厂家自己来玩，而且里面是怎么玩的也不会告诉给你，厂家A的引擎是不可能控制厂家B的业务板卡的。SDN来了以后，控制器的角色就好比一个要把所有厂家设备都控制起来的通用引擎，然而现在南向协议五花八门，不同南向协议对设备抽象的模型是完全不一样的，这就给控制器上的适配工作带来了巨大的挑战。试想一下，如果笔记本的触摸板、有线鼠标和无线鼠标，它们的操作方式完全不一样，这会给操作系统带来多大的困扰。而且还有一个问题是，控制器在网络中目前仍然处于十分弱势的地位，即使有一个开源的控制器对南向协议做出了不错的抽象，厂商也不见得愿意把自己的设备适配到上面去，因为厂家是不会用开源控制器和自己商用控制器左右互搏的。

如果想要做适配，从软件实现的角度来看就要在控制器的架构中增加厚厚的一层，屏蔽掉不同南向协议的差异。想要适配的南向协议越多，这一层就要做得越厚，积累到一定程度会严重地影响到控制器的可扩展性。到底是“大且全”，还是“小而美”？这是个见仁见智的话题，没有绝对的优劣。

网络的操作系统最终能顺利地走上PC操作系统的路线吗？只有时间能够告诉我们答案。

2.5.4　数据库

网络实际上是一个巨大的数据库。传统的设备通过交互路由协议来维护邻居表、拓扑表以及路由表等。转发设备中磁盘/硬盘这类资源极少，闪存一般也只用来存储设备的操作系统，像邻居表、拓扑表、路由表以及一些配置参数通常都是存在RAM里面的。RAM的好处是比磁盘/硬盘的读写性能要高很多，对路由协议的性能起到了很好的帮助。RAM的坏处就是设备掉电后这些表就都没有了，路由还要重新开始收敛，只有一些关键的配置才会被存到硬盘里做持久化。另外，转发设备中的RAM本身也不多，这要求做路由协议的开发人员对相关的数据结构要进行非常巧妙的设计。

SDN出现之后，控制器需要维护全局的视图，控制器中数据的存储与维护对于网络的稳定运行至关重要，因此数据库的设计对于控制器可以说是最为关键的。控制器相比于传统的网络设备，在数据库方面的考虑有如下不同。

首先，SDN控制器通常都部署在服务器中，各种存储资源非常丰富，可以摆脱开在设备上进行嵌入式开发的约束。存储资源的丰富，意味着数据库的选择就变多了。如果特别在乎速度，那么还是需要使用内存数据库，另外再进行持久化的设计。如果更在意容量和持久化，控制器则可以选择磁盘式数据库。如果要面对海量数据，甚至还可以考虑使用NAS、SAN等专用的数据存储解决方案。

其次，SDN控制器的实现机制是自己决定的，数据类型可以做得更灵活，数据操作的逻辑也可以做得更加自由了。传统网络设备里面，一条表项都会包含有很多的属性字段，为了完成转发可能需要多张表进行迭代查找，因此数据库的设计多为关系型的。对于SDN控制器来说，要存的数据增加了很多种类，数据格式会变得五花八门，而且在有了全局的视图后，一些传统的协议必须用到的参数很可能在控制器上就被简化掉了。大多数的数据关系按照键–值对（Key-Value）来存可能就足够了，因此非关系型数据库更适合SDN控制器。不过，具体的选型还是要看控制器上数据层面的具体实现，对于数据关系比较固定的，或者对于事务性要求较高的，那么关系型数据库可能仍然是首选。

再次，考虑到不同的应用场景，有可能需要为控制器集成特定的功能型数据库，因此一个控制器中可能会同时存在多种类型的数据库。比如，如果要求在控制器上做Telemetry数据的趋势分析或者实时反馈，可能就会需要将相关的数据存入时间序列数据库。如果要求在控制器上做网络资源的深度关联，可能就会需要将相关的数据存入图数据库中，等等。

控制器上的数据体量变大后，数据库的性能优化也是一个重大的问题。这个问题对于数据库领域而言可以说是老生常谈了，分区、分表、读库写库分离等思路都是SDN控制器可以在工程中借鉴的。

2.5.5　集群与分布式

可用性对于任何的商用系统来说都是首要的考虑因素，对于SDN控制器也不例外。通过集群机制来保证同构SDN控制器间的高可用，已经成为业界的共识。集群，就是为一个SDN控制器部署多个实例，让它们彼此之间进行主备或者负载均衡。实现集群有两个关键的技术：一是虚拟IP，让这些不同的实例在逻辑上对业务表现为同一个SDN控制器；二是集群间通信，使得网络数据能够在多实例间进行同步。

虚拟IP主要是通过在多实例间部署负载均衡器来解决，并不属于控制器本身的设计范畴。而集群间通信机制的设计对于控制器来说就至关重要了，对于一个SDN控制器而言，集群的设计最能体现该控制器的商用能力。目前有很多开源的框架可以作为控制器实现集群的基础，来帮助进行选主以及数据的同步。有一些控制器出于定制化的原因，会选择自己来重做集群的架构，这导致不同控制器的集群实际跑起来的效果差别很大，很多号称可以商用的控制器在主节点挂掉时还会出现多主甚至无主的情况。

集群是必要的，但是它的引入会带来另外一些让人头疼的问题。集群中各个实例在物理上是分散开来的，彼此之间数据的同步依赖于底层网络进行通信。相比之下，单机中多个进程间是被框在一个机器内部的，彼此之间是通过IPC机制进行通信的。由于底层网络相比于IPC机制是非常“不靠谱”的：IPC通信的结果一般来说只有成功和失败，而网络通信还会出现“超时”这种让人摸不着头脑的结果，这会对系统的设计造成很多头疼的问题。考虑这么一个例子，A通过网上银行向B转账，不过由于网络环境太差，账户系统发起的几次尝试都失败了，最后提示A请求超时。此时A也不知道钱到底转出去没有，于是只能查自己账户，或给B打电话确认。如果钱还能找着，那么还好；如果转账的金额“跑丢”了，那么就麻烦了。“钱跑丢了”这件事似乎在生活中很难遇到，但实际上银行的系统在处理转账这个请求时，都是经过严格的设计的，稍有不慎就会导致很多麻烦甚至经济纠纷。

上述现象属于分布式事务中普遍存在的数据一致性问题。根据著名的CAP定理，在强制要求系统分区以实现高可用的前提下，所要求的数据一致性越高，在并发性能上付出的代价就会越高。其中的逻辑简单描述如下：要想系统具备高可用性，数据就要复制出多个副本，放到其他节点上进行备份。写入操作如果使得数据发生变化，那么新的数据就需要在各个副本间进行同步。为了保证写过之后在各个副本中读到的都是新的数据，那么在执行同步时写入操作就必须选择等待。

由于分区对于集群来说是必选的，那么就无法兼得良好的数据一致性和业务的高并发。集群要根据自身所承载业务的特征在两者之间进行选择。数据一致性的强度可分为3种，由弱到强分别为Weak、Eventually和Strong。Weak是指当新的数据被成功写入后，读操作可能成功也可能失败。Eventually是指当新的数据被成功写入后，有可能暂时是读不出来的，但过了一段时间之后一定能被读出来。Strong是指新数据一旦成功写入，是一定可以被立即读出来的。Weak对于集群来说通常是不可接受的，一般只在Eventually和Strong间进行选择。Strong在数据一致性上是优于Eventually的，但代价是成功写入数据需要等待的时间较长，集群处理并发性业务的性能会受到影响。相反，如果选择了Eventually，那么集群处理并发性业务的性能会很强，不过其数据一致性就会比较弱，常常会出现写进去了但读错了的情况。

网络在本质上可以看作一个超大的集群。传统网络中，IGP和BGP主要处理的数据是路由表，由于对分区的要求非常高，它们在设计中都选择了Eventually的数据一致性。不过，对于SDN控制器而言，它所要处理的数据不仅仅局限于路由，因此在数据一致性的选择上也不存在必然的倾向，需要根据数据背后的实际业务需求进行数据一致性的选型。对于通用的SDN控制器来说，应该具备为不同的业务提供不同的数据一致性的能力。

为了保证高可用，数据一致性与并发的性能之间只能取折中，而折中的程度与数据副本在集群中的分布密度是相关的。通常数据副本的分布有两种方式：第一种是镜像，即把新写入数据的副本复制到集群所有的节点上；第二种是分片，即副本仅会被复制到一部分节点中。镜像方式下，为了实现Strong级别的一致性，同步需要发生在数据所在的原始与所有其他节点间，性能的损失很大。分片方式下，为实现Strong级别的一致性，同步仅需要发生在数据所在的原始与少数目标节点间，性能的损失很小。不过，这并不意味着分片方式就要好于镜像方式，因为副本数量的减少，不可避免地要损失一部分可用性。

另外，SDN控制器不仅要通过北向接口对业务，对设备一侧还要操作着五花八门的南向协议。不同的南向协议在集群的实现上区别很大，甚至对于同一个协议内部的不同控制信令来说，集群的具体实现机制也需要具体地去考虑。因此，相比于传统IT业务系统的集群而言，SDN控制器集群的实现难度通常会更高一些，甚至现有的一些面向商用的SDN系统在集群上的表现都不尽如人意，甚至在运行集群后性能还会有所下降，这也是目前制约着SDN大规模落地最重要的因素之一。

分布式和集群的区别在于，分布式是将不同的功能分散到不同的节点上去，各个节点彼此之间通过协作来交付业务，而集群中各个节点的数据、功能都是一样的，彼此之间只是做主备或者负载均衡。相比于集群，分布式在节点物理位置的组织上不受任何限制，节点间的逻辑耦合关系也比较松散，能够支持更好的可扩展性和可用性。

SDN控制器是否需要设计为进行分布式系统？就目前来看，集群基本上就可以满足SDN控制器的要求。不过，控制器上的功能未来会越来越丰富，主要包括业务开通、管理配置、实时控制和数据分析这四类，而这四类功能的软件实现架构以及对于服务器硬件性能的要求是很不一样的，彼此之间对通信时延的要求也不高，是可以考虑进行分布式设计的，将不同类别的功能进行分散，然后同一类的功能内部再进行集群。不过，如果做成了分布式的，这意味着控制平面所面临的后期运维问题将和传统的分布式网络面临的是一样的，控制平面一旦出现问题，故障排查将变得非常困难。

2.5.6　容器与微服务

当下是基础架构的大变革时代。从IT的角度来看，控制器就是一个面向网络控制场景的普通应用，和其他类型的应用并没有什么本质上的区别。SDN控制器是一个非常新的应用场景，架构设计上的讨论最多也就是停留在集群与分布式方面，SDN控制器的设计未来会不会走向容器化，甚至微服务？

容器这几年红透了IT的半边天。容器的出现彻底改变了应用交付的方式，它比虚拟机更轻，比进程更加安全。目前，一些厂商的设备操作系统已经可以支持将特定的服务进行容器化，通常是把成熟的协议栈运行在外面，而把用户或者第三方开发的SDN服务放到容器里面，以同时获得稳定性与扩展性。类似地，SDN控制器的设计也可以借鉴对应的思路，进行容器化的改造，但这种做法是否必要，还要根据产品自身的定位来进行具体的考虑。

和容器相伴相生的还有微服务。微服务实际上是一种理念或者说是系统设计风格，主要是为了敏捷开发而生的。微服务实际上仍然是分布式系统，不过它的粒度更细，会将一个服务拆解成多个更小的原子能力，然后再通过工作流将这些原子能力协调起来。那么，SDN控制器未来需要做成微服务的风格吗？笔者觉得没有必要。虽然大家都在谈论网络敏捷，但在该语境下默认比照的对象都是传统网络，而与上层的应用相比，网络的控制逻辑是不必要也不可能那么多变的，而且控制逻辑的原子能力也很难得到足够精细粒度的抽象，引入微服务架构难免有点牵强。不过，对于电信运营商来说，从传统的、臃肿的单体OSS/BSS转向以微服务架构起来的SDN编排器，倒是一个不错的选择。

2.6　本章小结

转发与控制分离、网络可编程、集中式控制，SDN的几大特征实际上在传统网络中都有迹可循，只不过在新型的网络交付模式下得到了更进一步的诠释与延伸，传统网络中的很多技术仍然值得SDN去参考与借鉴。同时，SDN为网络引入了软件层面上的全新挑战，有效地去应对这些挑战，是SDN未来走向大规模商用的关键。本章对上述方面进行了论述，以澄清SDN的本质，梳理出其内在的脉络。

第3章　SDDCN概述

SDN起源于校园网，不过商用的第一枪却在数据中心打响，究其原因是云计算和SDN在设计思想上的深度契合。从这一章开始，将对SDN在数据中心的应用进行介绍。本章将对SDDCN的设计原则、架构以及关键技术进行介绍，争取为后面章节的展开起到提纲挈领的作用。

3.1　需求

云计算时代的数据中心对网络提出了很多新的需求，首先要解决的是大规模网络的自动化和集中式控制，另外一些新型的IT应用架构也要求网络能够变得更加智能。长期以来，传统网络难以满足上述需求，而SDN架构的几大特征完美地契合了这些需求。

3.1.1　自动化与集中式控制

对于数据中心的网络管理员来说，新业务的开通通常意味着地址规划加上配置VLAN、ACL、路由、防火墙等诸多规则，业务终止后还需要回收相关的资源和规则。这些都需要网管去手动操作，然而手动配置的效率是极为低下的，而且很容易出错。不过传统数据中心中，同一业务的资源分布较为集中，因此这些操作不会涉及过多的设备，手动配置网络在成本上是可以接受的。不过，手动配置还意味着较长的业务交付周期，开通一个新的业务往往需要等待数周的时间。

云计算提出了资源池化的思想，虚拟化技术的发展打破了基础设施的物理边界，管理员人均需要维护的虚拟机数量有了数量级的提升，而且为同一业务分配的虚拟机可能分散在不同的机柜、机房甚至不同的数据中心，管理员很难准确地知道虚拟机某一时刻所在的位置。如果再考虑到虚拟机不断地进行着迁移，那么此时手动配置网络将变成一件不可接受的事情。同时，云计算还深刻地变革了传统数据中心的业务交付模式，用户通过在Web界面上点选一些按钮、输入一些参数就可以自助地开通或者变更业务，其时间要求达到分钟级甚至秒级。另外，随着防火墙、负载均衡、IDS/IPS这些L4～L7设备的虚拟化，业务的组网方式将变得更为复杂多变。“提交变更申请——分解操作流程——手动配置网络”，这样的传统流程不可能实现如此的敏捷性。

上述自动化需求是如此强烈，使得SDN成了云数据中心网络的不二之选。SDN从架构上就原生地具备自动化和集中控制的能力，通过开放转发设备的控制接口，SDN控制器可以自动地探测网络拓扑，并下发VLAN、ACL、路由等诸多规则，做到转发设备的即插即用。SDN控制器还可以向用户暴露业务接口，用户在此接口基础之上进行二次开发，使得网络可以随用户业务动态地进行调整。SDN对于运维自动化能力的提升同样显而易见，通过对流量和网络状态进行综合分析，SDN将有可能对云中的网络故障自动地完成隔离、排查甚至预警。

理论上来说，控制接口、业务接口的开放即提供了网络自动化的基础，不过真正实现一套能够在云数据中心商用的SDN系统并非一件易事。控制器内部的层次设计，同一层次不同接口的统一抽象（或者说不同层次接口间的适配），都是好说不好做的事情，其中涉及太多的技术流派之争和复杂的市场利益纠缠。不过，随着OpenFlow/OVSDB和OpenStack Neutron日渐成熟，SDN在云数据中心中的落地显现出了标准化的前景，OpenFlow/OVSDB作为设备接口所体现的灵活性和Neutron作为业务接口所体现的包容性，都极大地推动了SDN技术在云数据中心网络中的发展，也使得业界深刻体会到了网络开放对于IT系统自动化能力的整体提升。SDN也因而成为云数据中心网络演进的必然趋势。

除了自动化外，云计算所提倡的集约式运营还对集中式调度有着明确的需要。虚拟机作为工作负载经常需要动态地进行迁移，这就需要有一个集中式的控制器来将虚拟机在服务器中进行分布。近几年兴起的软件定义存储（Software Defined Storage，SDS）也提出集中地调度闪存、整列的资源，以提高存储资源利用率和存储节点命中效率。

同样，SDN所具备的对网络进行集中式调度的能力也是云计算所需要的。通过SDN控制器集中地向网络边缘分发网络策略已经广泛地应用在云数据中心网络中，当虚拟机发生迁移时，SDN控制器可以及时地得知虚拟机迁移后的位置，能够将相关的网络策略重新进行部署，以避免迁移过程中由老旧的网络策略导致的业务中断。另外，有一些论文中提出，通过对网络当前状态，如拥塞、丢包率等指标来优化承载关键业务的虚拟机的位置布放，从而达到优化通信效率、节能等目的。

尽管VxLAN等隧道技术可以结合SDN控制器的集中调度能力，但由于SDN的控制权仅存在于网络的边缘，仍无法对网络核心的传输进行调度。云数据中心网络希望能够对东西向流量进行无阻塞传输，虽然OSPF/BGP/ECMP等路由技术已经间接为二层提供了多路径传输的能力，然而它们只能基于流的静态特征对路径进行选择，一来无法感知流的动态特征，二来无法结合网络的实时状态。因此，虽然网络中不存在闲置链路，但各条链路上的负载通常会很不均衡，网络整体的业务吞吐率仍然难以得到有效保障。

若能够使用SDN同时控制网络边缘和网络核心，结合测量、流量调度、QoS等技术，SDN控制器将拥有流量和网络的动态全局视图，可以有效地对网络核心的传输进行调度，提高云数据中心的业务吞吐率。如此一来，网络将能够真正地做灵活智能，与计算、存储资源三维一体，全面迈向软件定义的数据中心，实现IT基础架构的大融合。

3.1.2　应用感知

数据中心的建设是一个大型的系统工程，不考虑电力制冷以及人力上的开销，以每机架为单元，ToR的成本大概只能占到5%～8%，服务器的成本占比大约在25%～35%，而在软件方面所投入的成本，包括操作系统、虚拟化、应用以及管理软件，大概可以占据70%甚至更多。因此，与在电信运营商中的基础性地位不同，网络在数据中心内部应该被定位于业务与应用的辅助，如果让应用围着网络来转，显然就是本末倒置了。

虽然数据中心和电信运营商在网络设备厂商中通常是两条独立的产品线，不过数据中心网络架构的设计至今仍然没有摆脱“以网络为中心”的思路，虽然SDN为网络提供了可编程的能力，但就目前来看，SDN的API基本上都是为了业务的自动化而设计的，应用仍然只能Over the Top。大数据、人工智能等新型应用架构的逐步普及，对未来的数据中心网络将提出更高的要求，网络需要能够为这些应用保障一些严苛的SLA需求，如传输带宽、端到端时延和传输抖动，等等。因此，网络必须重新思考未来在数据中心的定位，SDN需要能够更为深刻地理解应用、更为细致地感知应用以及更为灵活地适应应用，使得应用真正能够实现“play with the network”，而非“play around the network”。

下面以Hadoop为例，看一看SDN为其能够为大数据提供哪些价值。Hadoop的架构是Multi-Stage的，先把数据分布式地存下来，一个Compute Job提出后会被拆分为多个Compute Task并分配给不同的节点，每个节点在本地完成一小部分Task的计算，最后将这些Task的计算结果汇总在一起，作为Job的最终结果进行返回。在Hadoop集群的实现中，分为Client、NameNode、DataNode、JobTracker和TaskTracker五种主要的角色，主要的工作流如下。

1）数据读取进来之后，Client将其分解为不同的Block，然后向NameNode发出请求以获得Block的存储位置。

2）NameNode为每个Block返回一组DataNode，并对此进行记录。

3）Client根据NameNode的返回结果，将Block写入相应的DataNode。

4）当Job被提出后，Client将Job通知给JobTracker，JobTracker会从NameNode处获取存储了相关Block的DataNode。

5）JobTrack将Job拆分为不同的Task（可分为MapTask和ReduceTask），将其发布给不同的TaskTracker进行执行，并对此进行记录。

6）MapTask对本地的数据进行处理，处理完成后将结果存在本地。

7）ReduceTask从JobTracker询问MapTask的执行状态，从已完成的MapTask所在的节点拉取Map的结果。

8）当所有的MapTask都完成后，ReduceTask得到所有的Map结果，并将它们合并为最终结果。

9）此时JobTracker将Job状态置为成功，Client读取最终结果。

以网络的视角来看上述过程：①将数据写入DataNode的过程需要高带宽；②NameNode、JobTracker和其他角色间交互的控制信令需要低时延；③Mapper和Reducer的数量通常是M∶N的（M>N），在Shuffle的过程中，可能会出现大量Many-to-Few的流量，需要能够处理TCP Incast的问题；④只有在所有Task都处理结束后Job才会完成，需要尽可能地缩短Reducer和Mapper间Shuffle流量的传输时间；⑤集群会同时通过处理多个Task/Job，网络中会存在大量的Mice Flow，需要能够处理MicroBurst的问题；⑥为了实现高可用，DataNode间会进行数据的备份，需要放置这种次要流量抢占上述工作流中流量的带宽。

针对上述过程进行优化，整体的目标是降低Job的Completion Time，网络需要能够将大数据流量与其他应用的流量区分开，并识别出大数据的不同流量，保障高带宽型和低延时型流量的QoS，降低次要流量的优先级。SDN控制器需要：

1）将上述理解嵌入到控制算法中；

2）与Hadoop建立接口，获取到集群中不同角色的位置分布信息；

3）获取网络的实时状态，如物理拓扑、链路带宽、拥塞率，等等。通过将角色的分布信息和网络实时状态输入到算法中，计算出优化后的路径并下发到网络当中，或者对当前路径中大数据流量的QoS进行保障。

解决Incast和MicroBurst的问题，只能从流量的源头来想办法，这需要Hadoop自身能够根据网络的状态去优化Block和Task的分布。因此，SDN控制器不仅需要从Hadoop处获得信息，也需要将网络的实时状态提供给Hadoop，比如Server-to-Server流量统计、Rack-to-Rack流量统计、拥塞发生点、端到端时延等，使得JobTracker和NameNode在进行调度时能够结合网络的实际状态来优化Block和Task的分布，优化Job Completion Time。

可以看到的是，针对应用来进行网络优化并不是一件很好做的事，因为不同应用的架构、流量模式和通信需求都不一样，目前仍然只能是case by case地去考虑。关于大数据优化的更多介绍，可以参考本书9.10节中的内容。另一个常见的场景是VDI，可以通过SDN来监测/防止启动风暴，以及保证IP Storage的传输带宽，来优化虚拟桌面的QoE。

不过，通过对流量进行采集，再结合大数据和机器学习等技术，便能分析出应用的行为模式。将这些行为模式告知SDN控制器，SDN控制器即可采取有效的优化措施，从而实现网络对于应用的动态感知与自适应。这种与其他工程领域间的交互能力，完全得益于SDN为网络所带来的开放性，是传统网络所不可能具备的。

另外，抛开以上的技术原因，SDN适合在云数据中心落地的基础还得益于数据中心网络的以下特征：

·静态，网络拓扑和地址规划较为固定；

·统一，设备类型相对单一，网络管理权限较为集中；

·独立，通常不会用于传输过路流量，发生故障不会影响外部网络；

·负担轻，设备和链路成本较低，数据中心新建时无需考虑存量设备。

经过多年的实践，目前业界已经广泛地认可了SDN对于云数据中心竞争力的提升，云数据中心已经成为SDN落地的主战场。因此，云数据中心的管理者们没有必要再怀疑SDN是否具有价值，而应该考虑的是如何量体裁衣，使SDN更为有效地为自身的业务和运维提供价值。

3.2　整体架构

从这里开始切入SDDCN正题，首先，本节将对SDDCN的整体架构及其实现形态和功能设计进行抽象性的介绍。

3.2.1　实现形态

在SDN理想的实现形态中，转发设备中的控制逻辑被完全剥离出来，上移到集中式的控制器中实现，控制器能够直接控制设备的转发表，控制器对应用/编排暴露API，如图3-1所示。

在上述理想形态中，集中式的控制器对网络有着完整的控制权，SDN的优势得到最大程度的体现。但是在SDN实际落地的时候，考虑到扩展性与可用性，其实现形态通常与理想形态有所区别。下面针对SDDCN介绍几种常见的实现形态。

为了解决纯集中式的缺点，可采用集中式和分布式相结合的方式，控制功能在集中式的控制器和转发设备间进行切割，控制器统一对应用/编排暴露API。在图3-2a中，控制器将某些可在本地执行的控制功能卸载到转发设备中实现，转发设备间不会运行分布式协议。而在图3-2b中，转发设备间会运行一些分布式协议，实现基本的转发或者状态监测，而集中式的控制器主要负责对流量进行优化。这种架构的优点是控制器的压力减小，网络对于控制器的依赖性降低，缺点是控制平面实现较为复杂，控制功能在控制器和转发设备间尚无明确的切割原则。

图3-1　纯集中式的实现形态

集中式和分布式相结合，在实际的工程实现中还有一种常见的变形，是以集中式的数据库来代替集中式的控制器，由数据库对应用/编排暴露API。在图3-3a中，转发设备上保留本地控制逻辑，但彼此之间不运行分布式协议，转发设备间的状态同步是以集中式的数据库作为中间件来实现。在图3-3b中，转发设备上保留完整的控制逻辑，彼此之间运行分布式协议进行状态的同步，集中式的数据库负责把配置自动地推给各个转发设备，但是数据库本身不对转发进行控制。这种架构的优点是数据库的集群机制成熟，具备良好的扩展性与可用性，缺点是一些较为复杂的网络优化逻辑难以由数据库来实现。

图3-2　集中式和分布式相结合的实现形态

图3-3　以数据库为逻辑集中点的实现形态

还有一种较为“另类”的SDN架构，是转发设备上保留完整的控制逻辑，设备间运行分布式协议，网络不依赖于任何集中式的控制器或者是数据库。与传统网络不同的是，转发设备会直接向应用/编排暴露API，以实现可视化、自动化或者转发优化，当然通过设备暴露的接口也可以对接第三方控制器。另外在一些实现中，如图3-4b所示，通过一台转发设备暴露的API即可获得全网的状态与数据，实现网络与应用/编排/控制的单点对接。这种架构的优点是彻底消除了集中点，扩展性和可用性最强，缺点是难于对网络的控制逻辑进行优化。

图3-4　以分布式为主的实现形态

目前，多数SDDCN解决方案采用的是图3-2或图3-3中的某种形态，少数解决方案会做混合的实现，而图3-4中的架构多见于白盒交换机厂商的设计中。在本书的4～7章中，将对商用和开源SDDCN解决方案进行深入的介绍，看过这些章节后，读者可以再翻回来重新理解上述的内容，想必会有更好的效果。

3.2.2　功能设计

图3-5是SDDCN的功能架构。数据平面上，L2～L3设备实现基础转发，L4～L7设备提供增值服务。控制平面上，有一块大的逻辑负责对设备上的路由与资源进行控制与配置，另一块大的逻辑是可视化，把设备上的数据采集上来并进行处理与分析，然后反馈给其他模块以实现闭环的自动化。编排与应用层负责实现不同的业务逻辑。安全与高可用贯穿着SDN的三层，为系统的落地提供保障。

图3-5　SDDCN的功能架构

图3-5中的模块和控制流已经很清晰了，这里不再做过多的解释。实际上，图3-5中的架构不仅局限于SDDCN，其他场景中的SDN系统也正在向这种架构进行收敛。在下一节的内容中，将围绕着该架构，对SDDCN中的关键技术进行介绍。

3.3　关键技术

本节将对SDDCN的关键技术，包括网络边缘、网络核心、服务链、可视化、安全和高可用进行概要性的介绍。这些关键技术并不是独立的，彼此之间会有很多的交叉，希望读者能够将它们结合起来进行理解。

3.3.1　网络边缘

网络边缘把控着流量的入口，负责在流量开始传输前对其进行一些预处理。相比传统的数据中心，云数据中心的负载形态和流量特征都发生了深刻的变革，而传统网络中接入层的设计，从各个方面来看都难以满足新的要求，对于网络边缘的改造势在必行。

传统数据的中心里，主要工作负载为服务器，与Internet互通的南北向流量占主导地位，因此通常采用3-Tier的Hierarchy网络设计。接入层是网络的边缘，服务器直接物理上联接入交换机，在接入的布线方式上通常采用EOR（End of Row）或者TOR（Top of Rack）。EOR方式中，接入交换机位于独立的网络机柜，服务器的接入需要经过长距离的走线，EOR的布线管理起来很复杂，但接入设备管理起来较为容易。TOR方式中，接入交换机位于各个服务器机柜的架顶，简化了服务器接入的布线，但是接入设备数量较多难以管理。除了转发以外，接入交换机主要的功能还包括VLAN标记、端口安全和QoS等。

1.虚拟交换机

云数据中心里，虚拟机代替服务器成为主要的工作负载，虚拟机间的东西向流量爆发，传统数据中心的层次化网络设计已经难以有效承载东西向流量，以Leaf-Spine模型为代表的扁平化设计开始流行。由于虚拟机网络的第一跳发生在服务器内部，因此vSwitch成为云数据中心网络边缘的核心技术。不过，在vSwitch中实现过多的网络功能会造成很多的开销，影响服务器中能够容纳虚拟机的数量，因此一些功能需要被卸载到vSwitch下一跳的物理交换机中。在Leaf-Spine的网络架构下，TOR取代EOR成为主要的布线方式，通常由vSwitch和TOR共同来完成网络边缘的接入工作。

随着SDN在云数据中心的广泛流行，vSwitch和TOR上能够执行的功能也变得更为灵活。OpenFlow的使用极大地拓展了ACL的语意，带来了更为多样的匹配条件和处理动作。VxLAN逐渐取代VLAN成为云数据中心网络虚拟化的主流技术，带来了更多的租户数量和不受物理限制的大二层。另一个常见的场景是，虚拟机经常会发生迁移，网络边缘的策略也需要随之进行自动地变更，如VLAN和QoS等，而SDN控制器上的全局视图也有助于实现策略跟随的自动化。此外，如果流量对于L4～L7层的服务有要求，那么在网络边缘上还要支持服务链功能。关于服务链，后面会有一个小节的内容对其进行单独的介绍。

vSwitch的代表性技术是由Nicira发起的，目前承接于Linux基金会的OpenvSwitch（OVS）。OVS同时支持各版本的OpenFlow和丰富的传统网络协议，以OVSDB作为OVS的管理协议基本上已经成为业界的事实标准。当然，要支持灵活的控制必然会带来的是实现的复杂性，因此OVS的虚拟转发通道在效率上是要低于传统的Linux Bridge的，不过随着OVS对于DPDK技术的深度整合，目前OVS在转发性能上也得到了长足的提升。OVS目前已经在各个领域得到了广泛的应用，大多数SDDCN解决方案在网络边缘都采用了OVS作为Hypervisor Switch，以获得对网络边缘的灵活控制能力。不过即使开启了DPDK，由OVS来实现VxLAN等隧道的封装/解封装，开销还是很大的，因此在对于吞吐量有要求的生产环境中，可以通过TOR来卸载隧道的封装/解封装。另外，SmartNIC也可以用于卸载vSwitch的部分功能，但目前仍不足够成熟。相比vSwitch-vSwtich VxLAN，TOR-to-TOR的VxLAN还有另外一个好处，那就是可以有效地减少VxLAN隧道的数量，降低Overlay的维护复杂度。对于一些其他的功能，则可以视情况在OVS和TOR间进行拆解，若使用TOR实现，性能要好一些，但是要考虑到TOR所支持的功能集合以及转发表容量。

2.基于Overlay+SDN的虚拟网络

SDN能够为VxLAN隧道的封装提供全局的视图，包括如何在VTEP间建立隧道、虚拟机的接入位置，VLAN ID和VNI的映射关系，等等。全局视图的来源有两种：一种是由OpenStack这种云管理平台直接将虚拟机的接入位置告知SDN控制器。这种方式下，SDN控制器通过Proactive方式将转发表预置在数据平面。另一种是SDN控制器不通过与CMS交互来学习虚拟机位置，而是当虚拟机上线时触发Hypervisor告知控制器，这种方式下SDN控制器只能通过Reactive的方式动态生成转发表。两种方式互有优缺点：Proactive方式中，流量到来时可以直接在数据平面本地完成匹配转发，控制信道的实时压力较小，但是这种方式会将很多不常用到的转发表推送下去，在使用TOR进行隧道处理时会造成硬件资源的浪费。Reactive方式中，首包会产生延迟或者被丢弃，而且控制信道的实时压力较大，但是数据平面上只会存在需要用到的转发表。实际情况中，有可能需要在两种方式间进行平衡。

SDN还有助于网络边缘对流量进行优化。二层流量的优化体现在对于BUM流量的处理上，借助于控制器中的全局视图，ARP Request得以在本地进行代理，一些情况下DHCP也可以由SDN控制器在本地直接处理。SDN（尤其是OpenFlow）的灵活性对网络的边界造成了极大的冲击，人们发现三层流量也不一定要通过物理路由器来处理了，任播思想的流行和分布式路由技术的发展，使得三层流量能够在vSwitch/TOR本地进行路由，这样不仅可以简化网络路径，还能够避免路由的单点故障。不过，分布式路由技术也可能导致其他的一些问题，比如网络边缘功能过于复杂、故障定位困难等。

通过SDN对流量进行优化时，使用OpenFlow可以获得相当的灵活性。由于VM间的通信仍然不得不依赖于大量的ARP，因此ARP泛洪的抑制是大多数控制器必须要解决的问题。使用标准的OpenFlow需要由控制器来代理回复ARP Reply，当网络规模比较大的时候，控制器处理ARP的压力会急剧增大，因此更好的方式是使用OpenFlow Nicira Extension将ARP Reply的内容预置在OVS中，由OVS本地在数据平面本地对ARP进行代理回复。DHCP同样依赖于广播，不过其数量远不如ARP频繁，因此大多数SDDCN方案中DHCP仍然通过广播交给DHCP服务器去处理。不过，OpenFlow控制器同样可以代理DHCP的回复，但是由于DCHP的构造是基于UDP之上的，因此OVS无法在数据平面本地处理DHCP，必须由控制器进行回复。分布式路由既可以完全由OpenFlow来实现，也可以使用OpenFlow结合vRouter来实现，不同的SDDCN解决方案的设计有所不同。目前业界更接受OpenFlow结合vRouter的实现方式，不过直接使用OpenFlow实现路由，整体性能一般来说会稍好一些。

3.虚拟网络与物理网络的对接

由于很多的原因（如数据库要跑在物理机上、P-V的工作负载迁移、物理防火墙的部署等），虚拟化环境需要对接物理环境实现二层互通，这个对接工作通常也需要由网络边缘来实现。二层的对接，需要vSwitch/TOR来作为L2 Gateway完成异构二层网络间的封装转换，同时L2 Gateway往往还需要运行MAC自学习和xSTP等传统二层网络的控制逻辑。由于二层的对接处会承载大量的东西向流量，因此需要为L2 Gateway设计有效的高可用机制。对接了二层后，仍需要考虑的一个问题是：该二层的IP Gateway是部署在虚拟环境中，还是物理环境中？这要根据企业实际的情况进行择优处理。值得读者注意的是，即使使用了SDN来解决L2的对接，仍然难以避免泛洪的使用，这是因为物理工作负载的MAC地址SDN控制器最初是不得而知的。

与外界的三层对接，需要在网关节点上（可以为ToR，也可以为服务器）运行传统的路由协议，如OSPF/BGP等。TOR通常能够支持这些协议的运行，而服务器中的vSwitch并不具备三层的功能，因此通常需要将vSwitch和协议栈做集成部署，或者将vSwitch直接替换成vRouter来完成路由的工作。SDN控制器在该场景下的功能一个是通过某种方式（一般是在控制器上运行OSPF和BGP）获取到协议栈/vRouter所学习到的外界路由，另一个是将外界路由和DC内部路由进行重分布。如果采用的是vSwitch+协议栈的方案，由于协议栈只有控制平面，转发仍然要依赖于vSwitch的datapath，那么SDN控制器就需要向vSwitch下发流表进行转发。如果采用的是vRouter的方案，则不需要这么做，因为vRouter通常是转控一体的。另外，控制器也可以不依赖于网关节点上的协议栈/vRouter，而是直接与下一跳的物理路由器交互OSPF/BGP，然后通过合适的方式指导vSwitch/TOR进行转发的处理，不过这种做法并不太常见。

当使用分布式路由时，与外界的三层对接情况要复杂一些。通常情况下，分布式路由只处理东西向流量，南北向流量仍然需用集中地进行路由和NAT，此时控制器需要将外界的路由反射给各个Hypervisor，以完成南北向流量的引导。还有一种可能的部署，是将所有的服务器都与外界相连，南北向流量的处理也完全分布式地进行。这种部署的优点是消除了南北向流量的处理瓶颈，缺点是难以对流量进行安全的控制，而且会造成额外的布线成本。另外，实现SNAT时往往需要带状态，而OpenFlow/vSwitch目前还没有形成有效的规范去保证带状态处理的性能。

相比OpenFlow，EVPN使用传统的分布式思路解决了网络边缘的上述问题，同样支持VTEP自动发现、ARP抑制、分布式网关、虚拟机迁移等。EVPN多为传统的设备厂商所支持，其结合SDN的方式主要有两种：一种是在SDN控制器上运行BGP，作为RR反射Overlay的路由；另一种是控制器通过NETCONF或其他配置协议或接口，实现EVPN的自动化开通。混合部署EVPN和OpenFlow也是可行的，此时控制器需要完成两种控制方式间的路由重分布，在EVPN路由和OpenFlow流表间进行转换。

3.3.2　网络传输

云数据中心里，Fabric指的是能够为虚拟机间的通信提供高带宽、无阻塞传输的网络。传统数据中心的3-Tier网络，对于Fabric没有明确的需求。云计算兴起后，数据中心的通信模型发生了巨变，前面章节中反复提到过的一些原因，如虚拟机规模的扩张、东西向流量的激增等，使得Fabric成为支撑云数据中心网络的核心技术。高带宽体现在Leaf-Spine设备的线卡由1GE/10GE向40GE/100GE的快速演进，而无阻塞体现在由xSTP转向更为高效的控制逻辑上。

xSTP是一种有阻塞的协议，网络中的链路利用率极低。为了解决这一问题，以TRILL为代表的二层Fabric技术最先兴起，TRILL为二层网络引入了路由的智能，带来了多路径转发（ECMP）的能力。不过，TRILL的运行需要对传输设备进行升级或者更换，而且协议本身仍不够成熟，跨厂商难于互通，维护起来成本也很高。相比之下，OSPF/BGP等传统的路由协议则要成熟得多，它们天生地就支持ECMP，而且部署维护起来也更为方便。不过，三层网络的问题在于不够灵活，虚拟机无法大范围进行二层的迁移。考虑到虚拟机迁移是云数据中心的刚需，而OSPF/BGP无法独立地支持大二层，因此云数据中心的第一代Fabric技术以TRILL为主。

1.Underlay与Overlay

不过部署TRILL可能会带来产商锁定的问题，人们还是希望可以用更为通用的路由协议来运行Fabric。于是，以VxLAN为代表的隧道技术提出在虚拟机二层流量的外面再封装一层IP包头，使得二层虚拟网络得以与Fabric解耦，这样一来，只要是支持路由的交换机，就都可以用来传输大二层的流量。VxLAN在提出后，迅速得到了业界的认可，隧道+OSPF/BGP代替TRILL成为Fabric的新一代技术。不过，隧道技术更多地关注于封装，控制平面往往比较简单，因此SDN和隧道技术的结合成为新的趋势。如标准的VxLAN只能通过二层的自学习来进行转发，二层的泛洪依赖于Fabric对于组播的支持。通过SDN控制平面的全局视图指导vSwitch/TOR上的VxLAN封装，能够有效地抑制泛洪，从而消除VxLAN对于Fabric组播能力的依赖。

从理论上来讲，隧道里面的内容应该对于Fabric的传输来说是透明的。不过出于对虚拟网络流量更好的支持，Fabric需要感知内层包头的一些特征，以方便进行ECMP和QoS的处理，这就需要提供内层包头字段向外层包头字段映射的机制。VxLAN使用外层UDP源端口号和外层IP DSCP字段实现ECMP和QoS，其他隧道协议也各有各的映射规则。不过，这种映射的控制是发生在vSwitch/TOR中的，Fabric使用这些字段也只是服务于传统的路由协议和DiffServ。ECMP的处理依赖于Fabric设备中的算法，往往是通过<源IP，源端口号，目的IP，目的端口号、协议类型>的五元组进行哈希。而QoS的实现依赖于管理员对Fabric设备进行手工的配置。

2.Underlay的自动化

从上述描述可见，OSPF/BGP+ECMP Fabric只是作为VxLAN等隧道技术的Underlay，SDN的控制逻辑（隧道外层包头的封装、标记策略）主要仍发生在网络边缘。也就是说，转发作为Fabric的主要逻辑是不依赖于SDN控制器的，而依然通过传统的路由协议来支撑。从提供连接的角度来说，传统的路由协议已经做得足够好了，用SDN控制器来定义Fabric的转发，目前还没有看到明确的场景和需求。但是为了实现网络边缘和Fabric的统一管理，简化Fabric上的手工配置，很多厂家开始考虑将Fabric的管理功能从网管软件转移到SDN控制器中，作为云数据中心网络提供单一的管控入口，虽然有别于通过OpenFlow对FIB直接进行编程，但这仍然可以算得上是广义上的SDN。例如，在ZTP（Zero Touch Provisioning）技术中，管理员可以预先为Fabric编写好脚本，为设备分配好角色、ID以及OSPF/BGP参数，设备加电之后会通过DHCP Option获得控制器的地址，从控制器上同步这些配置的脚本，并在本地自动加载这些配置，省去了手动对Fabric进行初始化的工作。对于一些更为复杂的Fabric管理场景，比如涉及事务性的操作，可以通过或者NETCONF作为南向的配置协议，或者使用Puppet、Ansible等自动化部署工具来完成。

3.Underlay SDN

当然，用SDN来定义Fabric的转发完全是可以实现的。相比Internet，数据中心Fabric的网络结构和地址规划都是相对简单和固定的，OSPF/BGP这类分布式路由协议并不会带来明显的优势。相比分布式路由协议，基于SDN的Fabric可以提供更为精细的流量策略（QoS），能实现对拥塞的动态调优（大象流），当网络发生故障时有利于提供精确定位和自动修复，当网络进行割接时也能够更加有效地进行路径切换。当然，少数厂商的转发设备上也实现了拥塞自适应、隧道OAM等特性，不过这些特性需要深度定制化的芯片来提供支持，无法做到跨厂家互通。

如果使用SDN来控制Fabric的转发，那么网络边缘的隧道技术就变得不是那么必要了，从学术角度来讲，通过在边缘对现有字段的重新规划，足以满足云数据中心对于网络的需求，还可以消除由隧道封装所带来的开销。当然，使用SDN来控制Fabric也并不意味着网络边缘就不能使用隧道。

另外，随着光端口成本的降低和光交换技术的成熟，未来的数据中心网络有可能会向IP+光的架构进行演进，那么流量的特征感知、光路的自动开通、IP光的智能协同，这些都需要SDN对网络的传输进行全局的管理与控制才有可能得到实现。

不过，如果从现实的角度来考虑，基于SDN的Fabric需要对数据中心网络进行较大的改造，推动起来存在着很多的阻碍因素。尽管如此，国外还是有一些SDN创业公司选择走上了这条技术路线，希望通过SDN+白盒彻底地改造云数据中心网络。相比之下，国内的环境要保守得多，无论是互联网企业还是运营商，都鲜有魄力承担“对网络动大手术”的风险。

3.3.3　服务链

服务链是指，业务流量需要按照特定的业务逻辑有次序地经过特定的服务节点，如从Internet进入数据中心访问云中Web服务的流量，需要先经过FW进行过滤，然后再经过LB在多个Web服务器实例间进行分流，最后才会发送给某一Web服务器实例。传统的数据中心网络中，FW/LB/IPS均使用专业硬件设备来实现，这些硬件设备的价格十分昂贵，一两个节点就需要负责处理全部的流量，设备的体积通常也很庞大，部署的灵活性较差。在实际部署中，受限于传统的路由机制，这些服务节点或者被旁挂在汇聚设备上，或者被串在汇聚设备和核心设备之间。在旁挂的方式中，需要在汇聚设备上手动配置PBR去实现引流，使得流量能够按照特定的顺序经过这些设备；在串联的方式中，不需要做额外的配置，但是服务节点处理流量的压力太大，很可能会成为性能方面的瓶颈。

随着NFV的发展，服务节点的形态正逐步从专用硬件向VNF转变，形态上的虚拟化使得服务节点的部署不再受物理位置所局限，VNF可以插入到网络边缘的任意位置，有时还会在不同的位置间进行迁移。因此，传统的路由机制无法满足虚拟化环境中服务链的实现需求，而SDN凭借着高度的自动化以及对路由的灵活修饰能力，已经成为数据中心服务链的支撑性技术，而其中流量的分类和引导是SDN实现服务链的基础。

1.流量的分类

流量分类的目的，是识别出来哪些是服务链流量，哪些不属于服务链流量。从概念上来说，流量的分类需要通过专用的流量分类设备来完成，由SDN控制器向流量分类设备下发流量分类策略。但在实际中，分类的功能往往集成在转发设备中进行实现，此时分类策略所能达到的粒度也由转发设备来决定。粗放一点的粒度可以是以端口为单位的，以租户为单位的精细一点的粒度往往需要对L2～L4中的字段进行组合来识别“流”。当然，也可以在转发的设备上集成DPI对流量进行应用层面的分类。

通常来说，流量的分类结果往往是确定的，分类也只需要在服务链的入口设备上进行一次就可以了，但是考虑到某些VNF会对流量的字段进行改写，如NAT会改写源IP地址、LB可能会改写源/目的IP地址，那么经过这些服务节点的改写后，针对原始流量特征的分类结果可能就会失效了，因此就需要控制器向转发设备下发规则对流量重新进行分类。某些VNF会对流量进行更为细致的分类，比如IPS会根据流量的动态特征分析可能的恶意流量源，据此将黑名单上主机所发出的流量引导到堡垒主机上。这时，也需要IPS将分析出来的黑名单告知控制器，控制器向转发设备下发高优先级的分类策略以识别恶意流量。当IPS发现攻击已经结束时，黑名单解除，相应的分类策略控制器要及时地清除。

当然，上面所举的NAT、LB和IPS的例子，都可以看作针对Per Session的动态处理。要求控制器能够动态地获取VNF的处理结果，并维护Session的状态，实现难度较高。相比之下，静态地下发Per Flow的分类策略的实现则要简单得多。

2.流量的引导

有了流量分类作为基础，即可对流量进行引导，使之按需特定的顺序经过VNF。要实现流量的引导，最为核心的就是对数据进行重新封装。封装的思路无外乎有下面几种：

1）在数据包原有字段间插入新的垫层；

2）使用数据包的保留字段或已有的但不会用到的字段；

3）为数据包封装外层包头；

4）改写现有字段。

封装的字段需要有效地携带服务链转发所需的信息，主要包括以下三类：流量所属的服务链ID、流量当前在服务链上所处的位置以及下一跳的目的地址。

除了上述三类基本信息外，某些场景下封装还要携带VNF的处理结果，以便后续的VNF或者转发设备能够根据处理结果调整流量处理或者转发的策略。这些处理结果的信息通常被称为Context，Context的定义与VNF的处理机制密切相关，因此不同的VNF使用的Context的语义可能会非常灵活，这就需要控制器来协调Context的分配，同时要求数据平面的封装可以灵活地进行扩展。

流量的引导通常还需要保证上下行流量所经过的路径是对称的，这不仅要求双向经过的VNF顺序是对称的，还要求必须经过相同的VNF实例，以防某些VNF上出现半开连接而将流量丢弃。当VNF进行在线迁移时，需要动态地调整流量的引导规则，此时应尽量保证数据包不乱序，并且尽可能地减少由于路径调整造成的业务中断时间。

基于SDN的服务链有很多种实现方式。得益于OVS在数据中心的普及，基于OpenFlow来实现服务链是最为常见的，OpenFlow在网络边缘可以精确地识别流量，然后通过流表引导流量在不同的VNF间进行跳转。其他的实现方式还包括VLAN Stitching、PBR、BGP VPN、NSH、SR等。PBR和OpenFlow的原理类似，本质上都属于ACL，相比于传统方式下在汇聚设备上手配策略路由，控制器可以自动化地配置PBR。VLAN Stitching方式中，控制器会为服务链上的每一跳配置专用的VLAN，通过在这些VLAN中进行的泛洪，流量就能够自然地流经各个VNF。BGP VPN方式中，控制器会为服务链分配专用的VPN标识，并根据VNF的分布信息集中地修改BGP的下跳来实现引流。NSH为服务链设计了新的包头，引入了新字段SPI和SI来转发流量NSH作为一种数据面的封装，可以配合OpenFlow来使用。SR原生地支持源路由，控制器会根据VNF的分布生成label stack，直接在header中指定要经过的各个VNF。

3.其他方面的考虑

为了保证VNF工作的性能，同时考虑到安全性的问题，不同的服务链可能会被分配不同的VNF实例，当某种类型的VNF的性能要求较高时，可以分配该类VNF的多个实例。为了实现多个VNF间的负载均衡，SDN控制器需要通过一些监测技术来维护这些VNF实例的负载状态，同时需要运行一些负载均衡算法来保证多个VNF实例的高可用。

在服务链中引入OAM，主要是对服务链路径的状态进行监测，这一块业界目前还没有形成标准，参考IETF相关的Draft，可以看到对于服务链路径状态的监测包括主要以下几个方面。

·连通性。包括可达性、Path MTU、包的乱序和损坏等。

·连续性。包括链路故障、路径故障、VNF故障等，可以通过BFD来实现。

·路径跟踪。包括路径上逐跳的跟踪，当存在多链路时需要跟踪ECMP路径，需要中继设备能够支持对OAM的探针进行回复。

·性能。包括时延、丢包，需要支持时间戳以及时间同步机制（如NTP、GPS）。

基于SDN实现服务链是一个新兴的技术领域，无论是在工程实现还是学术研究中，目前都存在着很多的空白，这里无法逐一而述。另外，服务链的实现不仅依赖于SDN对流量的识别和引导，还依赖于NFV对VNF的管理与监测。SDN与NFV系统间需要进行接口交互和功能切割，这超出了本书的讨论范围，有兴趣的读者请自行了解。

3.3.4　可视化

传统数据中心里，网络运维一直是件“很痛”的事情。网络是数据中心正常运转的基础，网络状态正常时对业务是透明的，一旦网络出了问题，很多技术团队就会找上门来。小至服务器分不到IP、Web访问迟缓，大至数据库连接不稳定，甚至业务的大面积瘫痪，网络运维人员不得不独自对着大量的网络设备进行故障的排查。尽管网络设计已经想尽了一切办法去缩小故障域，但是一个小问题的排查很可能需要花上几个小时的时间，稍有不慎，一条配置出了错，还有可能引发更为严重的问题。有时候花了几个小时去排查网络，但最后发现问题根本就不是出在网络上。在人们的印象中，网络总是“最不靠谱”的，出了问题首先归因于网络也是人之常情。

云数据中心里，虚拟机的数量和网络的规模数倍于传统数据中心，再加上业务对于敏捷性和高可用的要求，网络运维如果不能变得更为智能、自动化，那么一旦发生网络故障，云中的业务可能会受到毁灭性的打击。而且，虚拟交换机的引入会使得网络故障的排查变得更加困难，一方面管理的边界从物理网络延伸进了服务器，另一方面也对网络运维人员的技能提出了新的要求。

传统的网管其实一直就定位于简化网络的运维，不过由于网络一直不够开放，各大厂商设备网管接口区别巨大，网管能采集上来的数据很多，但是难以进行有效的整合，基本上就是出了问题进行告警，并不具备故障的分析、定位和自动纠错的能力。SDN提出后，网络变得更加标准、开放，控制器通过全局的网络视图就能将部分运维的功能自动化。全局视图的形成依赖于控制器的可视化能力，而可视化能力主要包括网络可视化和流量可视化两个方面。

1.网络可视化

网络可视化又可分为网元可视化和组网可视化。网元可视化要求控制器能够与设备进行交互，采集设备的运行时数据，如电源、背板、CPU、内存等基础硬件的状态，Up/Down、收发包速率、丢包率等端口状态，以及MAC表、BGP表等转发表项的状态。另外，控制器作为SDN中最为关键的网元，其自身的状态、性能、异常记录和管理日志也需要进行全面的维护。

和传统网管做网元可视化的目的不同，SDN控制器采集到设备运行时的数据，会将其用于控制的反馈和优化，比如通过buffer当前的载荷情况来优化传输路径，因此对数据采集接口的实时性要求较高。SNMP的通用性较强，但是Polling的效率低，Push的功能弱，无法实现实时数据的采集。NETCONF普遍被看作SNMP的代替者，但实际上其优势在于事务性的配置，在监测和可视化方面相比SNMP并没有明显的优势，尽管通过Subscribe/Notification对Push模型进行了增强，但是由于采用了XML的数据格式，因此在序列化/反序列化性能上存在着明显的瓶颈，同样无法胜任对实时性要求较高的场景。至于OpenFlow/OVSDB，由于二者对于设备的资源建模侧重于转发方面，对设备本身缺乏足够的描述能力，因此并不适合做网元的可视化。目前，业界正在推动采用gRPC来传输实时数据。gRPC采用ProtoBuffer作为数据格式，序列化/反序列化的性能很高，对于Push模型的支持也更好。因此，gRPC已被普遍视为实现Telemetry数据采集的标准传输技术，各个主流厂商的设备和一些SDN控制器都已经对gRPC提供了支持。

组网可视化要求SDN控制器能够全面地了解网络的物理结构和逻辑结构。网络的物理结构主要就是指拓扑。拓扑发现有两种思路：一种是由控制器集中式地进行探测和计算，在这种方式下，控制器能够主动地参与到拓扑发现中，但是控制器上的处理压力较大。另一种是由设备运行分布式的协议获取拓扑，然后再以某种方式将拓扑告知控制器，在这种方式下，控制器只能被动学习拓扑，但是控制器上的处理压力得以减小。网络的逻辑结构主要是指IP编址和路由，对于虚拟化环境而言，还包括租户标识的分配、虚拟机的位置分布、虚拟机间的通信策略、虚拟机间通信所走的路径等。对于网络中任何一个虚拟机或者任何一个IP地址，SDN控制器应该可以明确地指出其所在的服务器；对于网络中任意两个虚拟机或者任何两个IP地址，SDN控制器应该明确知道二者是否可以进行通信，如果可以通信，应该明确两者之间通信的逐跳的路径。这不仅要求控制器能够衔接起南北向接口提供的信息，还需要将Overlay和Underlay进行关联。当底层网络中存在ECMP时，控制器需要对哈希算法的结果进行预判，或者通过探针来探测Underlay中真实的转发路径。

2.流量可视化

网络可视化关注的是网络本身，而流量可视化关注的是用户使用网络的行为，如大象流统计、分类流量统计、流量分时走势等。理论上来说，上述功能都可以通过SDN控制器集中式地实现，但是考虑到流量的数据量级，往往需要通过部署独立的流量分析软件来实现。有很多的商用/开源的产品可供选择，这些软件将处理的结果反馈给SDN控制器，有助于实现更为精准的网络控制与优化。

“流”层面的分析可使用传统的NetFlow、sFlow来实现，设备会对流量进行采样和统计，然后将结果返回给相应的分析软件，这种实现方式中交换机/路由器直接参与到了流量的分析中。gRPC同样可以用于流量的可视化，可以根据五元组或者其他特征来对流量进行实现采集与推送，实现实时的Telemetry。不过“流”仅关注的是L2～L4的特征，如果要看到应用层面的特征，就需要通过部署深度包检测（Deep Packet Inspection，DPI）来实现了。DPI的部署思路有两种：一种是在每个服务器上部署一个专门用于DPI的虚拟机，通过vSwitch将本地的一些流量镜像到这个虚拟机上。这种方式对vSwitch要求较低，但是需要为每个服务器额外维护一个虚拟机。另一种思路是直接在vSwitch中集成DPI的引擎，这种方式不需要额外的虚拟机，但是对vSwitch的要求非常高。在一般情况下，会采用第一种思路来部署DPI，但DPI的使用需要占用服务器上大量的计算资源，会在一定程度上影响服务器承载虚拟机的能力。

如果想要对流量进行更深层次的分析，如安全威胁诊断或用户行为跟踪，就需要借助专业的分析软件来进行处理了，此时交换机/路由器主要负责将流量镜像并导入专业的分析软件中，而并不直接参与流量的分析。导入的方式分为带内和带外两种，带内方式镜像流量与业务流量共存于一张网络中，这就需要SDN控制器对它们进行区分，并对镜像流量进行适当的引导。带内方式的优点是组网简单，缺点是镜像流量会占用业务流量的带宽，需要配合QoS来实现业务流量与镜像流量的隔离。相反，带外方式中镜像流量会有独立的网络进行传输，带外方式的优点是镜像流量不会占用业务流量的带宽，缺点是独立组网会带来额外的成本。

对于政府、电信和金融几类行业的数据中心来说，流量可视化的需求非常强烈，能够接受独立为镜像流量组网的成本，因此可以选择通过带外方式进行流量的导出。此类网络的设计既可以选择传统的方式，也可以选择SDN。使用SDN的好处在于可以方便地模拟NPB（Network Packet Broker）设备的功能，通过对镜像流量进行汇聚、分类、过滤、负载均衡等操作，将流量有针对性地导出到合适的流量分析软件上，能够有效地提高流量分析软件的工作效率。继网络虚拟化之后，NPB很有可能成为SDN的另一个杀手级应用。

做好了上述的可视化工作，就能够得到网络/流量的当前状态和历史数据，这些数据是海量的、细碎的，但是其中蕴含着巨大的价值，为网络的排障和优化提供了基础。网络常见的故障包括网络拥塞、设备/链路故障、路由黑洞/环路/震荡等。通过将这些故障的定位和成因分析返回给控制器的控制逻辑，即可对部分故障进行自动的修复，以实现闭环的自动化运维。进一步地，可以结合大数据和机器学习对原始的数据进行处理，以呈现出更为有价值的信息，比如通过大数据分析出攻击的发生，并反馈给SDN控制器丢弃嫌疑流量，或者通过机器学习分析出用户应用的行为模式，增强网络对于所承载的应用的理解，有针对性地优化资源在应用间的分配。

3.3.5　安全

安全从来是数据中心网络的核心问题。传统的数据中心网络内部往往采用分区部署的方式，或者直接进行物理上的隔离，或者通过VLAN+ACL进行逻辑上的隔离。数据中心与外界网络的互通则由专业的防火墙设备来完成，这些防火墙通常旁挂在汇聚层，集中地把控着数据中心流量的出入。

云数据中心在负载类型和流量模型上有别于传统数据中心网络，对上述的网络安全模型提出了巨大的挑战。

1）虚拟化打破了数据中心网络的物理边界，物理分区的方式再难适应大二层网络。

2）租户数量和虚拟机规模有了数量级的提升，而且虚拟机还经常发生迁移，因此手动配置VLAN和ACL将是不可接受的，网络安全同样需要自动化。

3）东西向流量的爆炸使得数据中心内部的安全问题更为明显，集中式部署的防火墙通常会导致东西向流量的路径迂回。

4）硬件防火墙虚拟化能力较弱，难以适应云数据中心对安全的弹性需求。

1.多维安全模型

云数据中心里，网络安全的设计需要变得更加立体，从数据中心级别，到租户级别，到子网级别，再到端口级别，为云中的业务提供更为可靠的保障。数据中心级别的安全仍然需要在数据中心入口集中式地设置防火墙，由于硬件防火墙不能满足数据中心对安全的弹性需求，因此需要结合虚拟化技术来实现防火墙资源的池化。租户级别的安全可以通过VxLAN等Overlay技术对不同租户进行隔离，防止流量在租户间泄露。子网级别的安全可以通过vRouter实现，也可以通过虚拟化的防火墙（vFW）来实现，不同的租户需要分配不同的vRouter/vFW实例，以获得灵活性和通信隔离。端口级别的安全通常被称为安全组或者微分段（Micro-Segment），需要与虚拟机进行绑定，可在vSwitch上实现，或者将分布式的vFW以inline的方式串在vNIC和vSwitch之间，同时要保证端口安全策略随虚拟机进行迁移。对应于微分段，有的厂商还提出了宏分段（Macro-Segment），以实现虚拟机和裸机间东西向流量的安全。

上述安全模型通常需要结合SDN/NFV进行实现。vRouter/vFW/vIDS/vIPS等VNF实例本身的实现属于NFV技术，并且需要依赖于DPDK等加速技术以保证性能。SDN控制器负责通过NETCONF或者RESTful API对VNF进行自动化的管理配置，包括对安全组规则的下发、安全策略的配置和Session的管理，以及将不同租户的流量引导到不同的VNF实例中。当然，从NFV的视角来看，SDN控制器所做的这些工作大部分也可以通过MANO来实现，这里不去探讨两种视角的区别。

安全组作用于网络边缘，能够实现端口级别的防护，以Iptables为主要的实现机制。SDN控制器可以集中式地分发或配置Iptables规则，不过这通常需要在虚拟机和vSwitch间额外地引入一跳，导致增加了一个潜在的故障点，而且会对性能造成很大的影响，因此并不是理想的选择。如果vSwitch设备支持OpenFlow（如OVS），那么可以通过流表来匹配L2～L4的字段以实现安全组策略，即相当于传统网络中的ACL。不过OpenFlow流表是无状态的，如果希望通过OpenFlow实现有状态的策略，可以将首包PacketIn给控制器去维护状态，但是这种方式会对性能造成很大的影响，还可以使用ovs contrack等扩展功能，在OVS本地完成状态的维护，但是目前这种方式的实现还不是很成熟。

如果考虑到为OpenFlow做硬件卸载，那么ACL规则就会被从vSwitch上转移到TOR上，限于TCAM的容量，此时SDN控制器需要对ACL规则进行适当的聚合，在获得足够灵活性的同时要防止ACL规则的溢出。同时，SDN控制器还需要实时地感知虚拟机的位置，当虚拟机发生迁移时能够及时地将ACL规则进行迁移。

VNF上策略的配置对于SDN控制器的问题不大，其粒度也往往在于租户和数据中心级别，因此策略的变化不会非常频繁。Session的管理则会相对复杂一些，可能需要面临如下的两个挑战。

1）Session的同步方式。如果SDN控制器需要实时同步Session的状态，那么当Session数量很多时，控制信道上的开销会很大。如果SDN控制器定期同步Session状态，那么SDN控制器中全局视图的实时性就会受到影响。OpenFlow控制器还支持对于Session的直接控制，此时Session建立的速率以及OpenFlow控制器的压力的处理可能就会成为瓶颈。因此，Session的同步方式要根据实际场景和需求来进行选择。

2）Session的备份。高可用是防火墙以及其他网络高级服务的基本要求，实现高可用是有代价的，不同层面的高可用代价不同。在路由层面解决防火墙高可用性是最为基本的，其代价较低，但是对于Session的控制能力不强，在切换时很可能会导致业务的中断。如果同时和Session层面进行防火墙状态的热备份，其可用性会很高，但是实现的代价较高。实现热备份有两种方式：第一种是通过SDN控制器在VNF实例间进行同步，控制信道压力大；第二种是直接在VNF实例间进行同步，数据平面带宽的开销会很大。

SDN只能实现网络层面的安全，对于数据安全问题，只能将流量引入专业的数据分析软件进行处理。传统的数据中心里，需要部署SPAN或者TAP来实现端口镜像，SPAN和TAP的实现受限于资源和成本，只能部署在少数的关键位置。SDN可以动态地将镜像点插入网络中的任意位置，为虚拟化环境提供更好的可视性。当数据分析软件完成分析后，可以将分析的结果（如恶意流量的特征）反馈给SDN控制器，然后由SDN控制器下发安全规则将恶意流量丢弃，实现“数据+应用+网络”的闭环安全防护。另外，随着近几年大数据的发展与普及，SDN控制器上也得以直接集成一些数据分析的功能，能更好地服务于数据中心安全。

2.服务链与安全

多租户环境中，不同的租户会获得不同的VNF实例。SDN控制器需要完成流量的引导，即所谓的服务技术。服务链技术通常在vSwitch上实现，当VNF实例采用集中式部署时，服务链的引流策略只需要部署在少数vSwitch上。当VNF实例采用分布式部署，服务链的实现将变得非常复杂。

一般而言，通过服务链将一些安全相关的VNF串在一起，就可以满足绝大部分的安全需求。如果需要实现更为专业的流量安全策略，数据中心运营商可以与流量清洗中心进行合作，将那些难以区分的流量牵引到专业的清洗中心进行处理。攻击流量被清洗中心过滤掉，安全的流量被回注到数据中心进行服务。流量的牵引可以在数据中心入口通过OpenFlow来完成，也可以在城域网入口通过PBR直接对可疑流量进行牵引，清洗过后的流量再通过专线或者隧道回注到数据中心。

3.SDN本身的安全问题

前面概括地说明了SDN对于数据中心安全性的提升。不过在引入SDN后，SDN本身的安全就成了其他网络安全的基础。控制器是SDN的大脑，其安全问题首当其冲，最基本的要求是在控制器的南向和北向接口上做认证、加密和数据校验，防止非法接入、窃听、重放等攻击形式。控制器上某些操作的执行需要进行鉴权，这要求控制器本身能够支持多租户或者RBAC（Role-Based Access Control），同时对一些性关键的操作需要进行审计，以确保其合规性。在网络部署中，如果SDN控制器需要暴露在公网上，就需要对其采取更为全面的安全措施，如DOS和DDoS防护等。

相比IT层面的安全问题，SDN自身的安全性更加值得深入的探讨和研究，尤其是一些新生的网络控制协议。它们在增强了传统网络能力的同时，也会引入很多潜在的安全问题，对于这些问题的研究还仍然停留在学术讨论阶段，目前还没有形成特别成熟的安全防范体系。鉴于SDN仍没有进入大规模的商用，因此SDN自身的安全问题还并不突出，不过这些问题如果不能及时得到解决，那么SDN的大规模落地恐怕就只能是纸上谈兵了。

3.3.6　高可用

和安全一样，高可用对于云数据中心网络来说同样是至关重要的。传统网络中广泛使用了各种高可用技术，如实现多网卡绑定的LACP、实现链路冗余的xSTP、实现网关冗余的VRRP、实现多路径的ECMP、实现数据中心互联的VPLS、实现故障检测的BFD等。总结起来，主要可归为以下几个方面：设备关键组件要有冗余、物理拓扑要有环路保护、路由要快收敛防震荡以及多数据中心间要做双活/主备。

1.网络冗余与监测

传统网络中的冗余机制同样也适用于SDDCN的设计。对于一个SDDCN的产品来说，应尽可能地多提供高可用的功能，这是在生产环境中落地最为重要的加分项。不过需要明确的是，这些冗余机制并不一定都要通过SDN的方式来实现。

·设备关键组件要有冗余，比如多背板、多主控、多电源、多风扇等，SDN白盒交换机在设计中需要考虑上述要求。

·物理拓扑要有环路保护，要求SDN控制器能够处理多网卡绑定（LACP），并在Leaf-Spine间实现负载均衡。在对接传统网络时，还需要能够正确处理STP的BDPU以防止环路，以及出口IGP/BGP以实现多路径。

·路由要快收敛防震荡，要求SDN控制器能够快速发现拓扑变化，计算并开通倒换路径，或者由控制器预先下发逃生路径给设备，发生故障时在本地完成路径切换。

·多数据中心间要做双活/主备，要求SDN控制器提供数据中心间的2层连接通路，该通路上应该支持QoS以区别对待不同的业务，少数场景下还需要在多数据中心间做流量调度。

除了冗余机制的设计外，网络还需要提供实时、全面、准确的监测机制。传统网络中有监测通断BFD、监测性能的NQA等手段，这些手段可以继续在云数据中心中使用，设备间交互不同的探针对网络中不同的状态进行监测，当发生状态异常或者指标波动的时候，设备可以主动上报给SDN控制器，然后由SDN控制器根据一些网络管理的策略执行对应的动作，以保证网络的高可用。当然，SDN控制器上也可以集成探针的生成、接收和参数调整等功能。

2.消除控制器的单点

高可用的天敌是单点，网络中应该尽可能地消除单点。传统数据中心网络中，路由器和防火墙是流量集中经过的地方，最容易成为单点。在SDDCN产品的设计中，应尽可能提供分布式路由、分布式NAT以及分布式防火墙，消除流量的集中点，避免网络中出现单点故障。

由于SDN比传统网络多引入了控制器这一角色，因此还需要考虑控制器的单点问题。对于数据中心而言，控制器通常是部署在intra-net的，受到直接攻击的可能性比较小，只要集群部署得当，控制器的可用性是可以有所保障的。集群是个纯软件架构的话题，可以说是老生常谈了，当然，不同控制器对于集群本身的实现是有好有坏的。如果控制器上的业务允许控制器进行无状态的主备或者负载均衡，那么实现起来较为容易，不过大部分情况下控制器间都需要进行状态同步，此时就需要考虑数据持久化、数据分片、数据一致性、设备控制权主从、脑裂检测等诸多方面的问题。一般来说，控制器可以通过一些成熟的开源分布式产品（如Zookeeper、ETCD、Consul等）来实现集群，一揽子地解决集群的问题，但是考虑到集成后的性能以及功能上的定制化，一些控制器需要分解出更为复杂的技术堆栈，甚至另起炉灶自己来“造轮子”，这就需要根据业务需求以及企业自身的技术水平具体地来考虑了。

不过，即使控制器的集群做得再好，也不可能提供100%的可用性，因此要考虑在集群整体瘫痪的时候，保证数据平面上的通路仍然能够正常完成流量的转发。实现这个目标有几种主要的思路。

1）设备立即切换到传统的分布式控制。这种方式实现复杂而且网络的状态不可控。

2）设备仍然能够按照控制器之前下发的策略继续转发，直到控制器被修复后重新接受新的控制策略。这种方式实现简单但是中间会存在控制的真空期。

3）将基础类的控制策略保留在设备本地，将优化类的控制策略放在控制器上，控制器出现问题时也不会影响基础的转发。这种方式取了前两种方式的优点，代价是控制器失去了一部分控制的权限。

控制器的高可用还会涉及的另外一个问题，那就是升级。理想情况下，控制器应该能够提供做热补丁的能力，不用重启控制器就可以补掉程序中的Bug。在实际情况下，控制器也至少应该提供集群的不间断升级能力，即在升级控制器A时可以将其控制权临时转移给控制器B，控制器A完成升级后切换控制权，然后再升级控制器B，A和B都升级完成后再做负载均衡。

实际上，控制平面的高可用也并不是只有SDN才会面临的问题，传统网络中对设备主控同样提供了很多高可用机制，比如主备控制引擎的倒换技术，数据平面的不中断转发技术、主控的热升级技术等。这些机制和上述提到的SDN控制器的高可用，都是可以一一对应起来的，SDN控制器在做架构设计时不妨向传统网络多多“取经”。

3.负载均衡

应用的高可用通常通过负载均衡器（简称LB）来实现，LB可以看作L4～L7层的转发器，它和防火墙一样属于网络的一部分。对于传统的Hardware LB或者VNF vLB，SDN控制器可以通过NETCONF、RESTful API等方式自动地向其下发配置。对于OpenFlow交换机，SDN控制器也可以向其下发流表来模拟一些简单的负载均衡行为。为了避免单点，LB本身同样也得做高可用，多个LB的前端需要有一个Distributor来分散VIP流量。Disrtibutor的实现既可以使用传统的BGP+ECMP，通过路由收敛来保证高可用，也可以使用多台OpenFlow交换机组成Distributor Fabric来实现。

高可用是个很大的话题，从网络到应用到数据，ICT的任何层面都对高可用有着无止境的追求。但是，越高的可用性通常也意味着越高的成本，而可用性和成本并不是线性的关系，因此在生产环境中到底需要多少的高可用，还是需要数据中心的管理者在需求和成本间进行综合考虑。

3.4　本章小结

经过多年的发展与验证，目前SDN在数据中心的应用已经形成了明确的市场需求，技术上也已经显现了初步的骨架。本章对SDDCN的需求、整体架构和关键技术进行了提炼，其中的一些内容未免有些抽象，读者可以先继续阅读接下来的几章，然后再查看本章所述内容，应该会达到更好的理解效果。

第4章　商用SDDCN解决方案

以2012年VMware收购Nicira为标志，SDN在数据中心落地的旗号正式吹响，众多创业公司开始进行相关的技术创新，各大厂商也着手在产品与市场上进行了布局。经过5年左右时间的发展，商用SDDCN已经度过了早期的探索阶段，目前正处在从先锋客户试水转向大规模商用的关键节点上。本章将对一些具有代表性的商用SDDCN解决方案进行深入的剖析，帮助读者从技术上了解目前商用SDDCN的发展现状。

4.1　VMware NSX

VMware是虚拟化技术的先驱者，其强大的计算虚拟化产品已经深入各行各业的日常使用中。VMware最早的虚拟化产品是VMware Workstation，在PC OS的基础上提供了桌面级的虚拟机，其网络环境比较简单，基本的需求就是虚拟机之间要能够通信。另外，虚拟机也可能需要和外界通信。VMnet作为VMware Workstation中的虚拟交换机，提供了3种网络模式：Host-only、Nat和Bridge。Host-only模式只允许虚拟机间在同一网段中进行通信，不允许虚拟机与外界通信，IP地址由VMware环境来分配；Nat在Host-only的基础上增加了虚拟机与外界通信的功能，虚拟机的网关会指向VMware环境中同网段的某一IP上，由这一IP进行NAPT转换实现与外界的通信；Bridge模式中，虚拟机网卡将与宿主机物理网卡处于同一物理局域网中，虚拟机的IP地址由外界的DHCP服务器来分配，而物理网卡需要开启混杂模式作为VMnet与外界交换机互联的Trunk口。

随着服务器虚拟化在数据中心的普及，VMware推出了旗舰产品vSphere来提供对物理服务器直接进行计算虚拟化的能力。数据中心的网络环境比PC中的网络复杂得多，是承载业务的生产环境，对于网络安全的要求是头等大事，而且数据中心内的流量类型更为多样，不同的流量对于QoS的要求自然也不尽相同。上述需求使得vSphere中集成的虚拟交换机必须支持更为复杂、灵活的网络策略。同时，由于虚拟机迁移这一刚性需求，虚拟交换机上的网络策略还要能够自动地跟随虚拟机进行迁移。另外，为了简化数据中心网络的运维，必须要支持对于海量虚拟交换机的统一管理。VDS（Virtual Distributed Switch）是VMware给出的方案（原来叫作DVS），VDS中能够对VLAN进行集中式、精细化的管理。

可以看到，植根于VMware强大的计算虚拟化基因，VMnet/VDS都是基于x86平台进行的软件实现，这与Cisco这类传统的网络设备厂商基于芯片和网络协议来解决问题的视角是截然不同的。不过，VMnet和VDS都是VMware在服务器内部用来自给自足的，对Cisco并没有任何的影响。为了巩固双方在各自领域的地位，Cisco和VMware还联合研发了虚拟交换机Cisco Nexus 1000v。由于Cisco在网络方面的专业性，N1000v支持比VDS更为高级的网络功能。Cisco获得N1000v License的利润，VMware的客户则能够享受到更好的网络服务，因此Cisco和VMware长期保持着不错的关系。

随着云计算浪潮的来袭，数据中心网络架构发生了巨大的变化。Cisco于2010年左右开始大力布局Nexus系列的硬件产品，包括做接入的N2K和N5K以及做汇聚/核心的N7K，在相关芯片和控制协议的研发上投入了大量的资源，已经为云数据中心网络的转型做足了准备。

4.1.1　从NVP到NSX

Cisco的网络虚拟化联姻VMware的服务器虚拟化，似乎将成为云数据中心的标准打包方案，二者的未来看上去很美。然而，SDN的出现改变了数据中心网络的市场格局。Nicira的NVP平台是最早的基于SDN的数据中心网络解决方案。Nicira是SDN的骨灰级选手，推出了OpenFlow和OVS，该公司三位最主要的创始人Martin Casado、Nick McKeown和Scott Shenker是业界公认的SDN奠基者。NVP所选的技术架构当时让人眼前一亮，使用Overlay构建纯软件的虚拟化网络，SDN只负责网络边缘的控制，而不会对现有网络产生任何影响，可以说是现有数据中心网络向SDN演进的最佳形态。

VMware看到了NVP在商业上具备的巨大价值，一旦将NVP集成到自己的虚拟化解决方案中，那么今后便有机会在网络虚拟化上与Cisco掰一掰手腕，从而开拓自己从未有机会触碰的市场。而Cisco同样敏感地意识到了NVP带来的挑战，这种纯软件的SDN解决方案对于Nexus系列交换机在数据中心市场的布局将形成巨大的挑战，一旦VMware收了NVP，那么肯定要吃不少苦果子，但如果自己拿到NVP，那么操作的空间可就大得多了——既可以选择温和地推进NVP，等到Nexus赚的盆满钵满，再迅速通过NVP来占领数据中心的SDN市场。当然，也可以内部消化掉NVP，坐等SDN自生自灭。于是争端一触即发，2012年年中，双方展开了对Nicira的收购大战。

从最后的结果来看，VMware以12.5亿美元收购了Nicira。VMware收购Nicira后，基于NVP的原型推出了自己的SDN网络虚拟化平台NSX，高调进军数据中心网络市场，也宣告着放弃了与Cisco在网络领域的合作关系。当然，Cisco也不会坐以待毙，于2013年以8.64亿美元收购了SDN创业公司Insieme，同时发布了ACI的架构与产品。此后，NSX和ACI在数据中心展开了正面的交锋，数据中心SDN也正式走上了商用的道路。

不过，鉴于NVP选择开源的技术是其成功的先决条件之一，于是NSX采用了“两条腿走路”的策略：NSX-V将NVP的技术特征嫁接到了VDS上，将其网络虚拟化与服务器虚拟化的产品共同交付；NSX-MH则保留了NVP的开源基因，仍然使用OVS和OpenFlow/OVSDB支撑SDN架构，可以服务于KVM、XEN等非VMware的服务器虚拟化产品。2016年5月，VMware推出NSX-T，NSX-T是对NSX-MH的一次重大的更新，旨在提供更好的Hypervisor Agnostic特性。

4.1.2　NVP控制平面设计

如图4-1所示，NVP的控制平面分为两层，在上面负责和业务打交道是Logical Controller，在下面负责和设备打交道的是Physical Controller。Provider通过CLI/GUI/RESTful API指定租户的逻辑拓扑信息，Logical Controller接收并存储逻辑拓扑信息，并根据声明式的语言nlog自动将其转化为成逻辑转发流表（universal flows）。universal flows对应的是租户层面的逻辑转发资源，如logical port id、logical datapath等，是物理资源无关的。Logical Controller通过RPC将universal flows下发给Physical Controller。Physical Controller中存着逻辑转发资源与物理转发资源间的映射关系，如vNIC和Transport Node的IP的对应关系等，它会根据这些映射关系将universal flows转换为physical flows/configuration，然后通过OpenFlow Nicira Extension/OVSDB发送给设备，以指导设备的转发。

图4-1　NVP的两层控制架构

为了实现控制平面的高可用性，logical controller和physical controller均采用分布式集群进行Master/Hot Standby部署，使用ZooKeeper来进行leader选举、全局logical network id的分配以及RESTful API的信息配置。为了保证策略在分布式集群中的一致性，NVP对API的执行采用了严格的时序同步。另外，NVP还为配置信息设计了快照机制，方便进行回滚。

4.1.3　NVP数据平面设计

NVP的转发设备就是OVS，也称为Transport Node，通过OpenFlow Nicira Extension和OVSDB与Physical Controller Cluster进行通信，每个Transport Node都会指定一个Master Controller和多个Slave Controller。Transport Node进一步分为三类，Hypervisor、Service Node和Gateway。Hypervisor连接着VM，Service Node负责集中地处理广播和组播流量，Gateway负责连接VM与物理主机。图4-2为NVP的数据平面结构。

图4-2　NVP的数据平面结构

Transport Node间的通信建立在隧道的基础上，隧道封装默认采用Nicira私有的STT。STT采用无状态的TCP包头对L2帧进行封装，这样的好处是得以利用硬件网卡的TSO技术对封装后的TCP包进行快速的分片/重组，从而允许VM上的应用产生巨型TCP负载，以提高端到端应用的通信效率。同时，NVP也提供了对VxLAN和GRE封装的支持。

为了实现数据平面的高可用性，Service Node和Gateway均采用了集群式的部署。Service Node集群采用了负载均衡的工作机制，各个Hypervisor通过哈希算法将不同的组播/广播流量分散给不同的Service Node进行处理。Hypervisor与Service Node的隧道上运行着CFM以监测Service Node的状态，一旦心跳信令中断，则将该隧道上的流量切换到另外的隧道上。

不同于Service Node集群，Gateway集群采用主备的工作机制，各个L2 segment在Gateway集群中只能与1个Gateway建立隧道以连接到物理网络，这是为了防止因物理网络中运行的MAC自学习而在多个Gateway间产生环路。为了实现Gateway的主备，Gateway间通过CFM监测彼此的状态，并采用轻量级的leader选举算法，为每个L2 segment选举出活动的Gateway，与各个Hypervisor间建立隧道。另外，当主备Gateway间进行切换时，物理网络是不会进行路径切换的，因此很可能会出现路由黑洞。解决这个问题需要在Gateway上运行特殊的机制以更新物理网络上的MAC Table；或者由新选举出来的主Gateway发送所有VM的免费ARP通告，或者所有的Gateway参与物理网络的STP选举，由新选举出来的主Gateway发送TCN来触发物理网络上的更新。

4.1.4　NVP转发过程分析

NVP的转发采用了Proaltive的模式，因此其转发过程分为两个阶段：第一阶段是控制平面对全局转发信息的学习和分发；第二个阶段是数据包在datapath上的处理。

第一阶段如图4-3所示，其中（1）是Hypervisor/Gateway将MAC地址/vNIC的情况通过OVSDB告知控制平面，（2）是控制平面接收Provider提供的租户信息，（3）是控制平面结合（1）和（2）的输入，通过nlog产生流表并下发给Hypervisor、Service Node和Gateway。

图4-3　控制器学习并分发全局信息

第二阶段如图4-4所示，当VM的数据包从vNIC进入OVS，首先第一级流表会根据vNIC所属的租户并为其标记metadata，然后根据metadata将数据包送到相应的流水线中继续进行匹配。流水线的后续匹配过程将完成对数据包的L2/L3处理，最终会根据egress流表中的逻辑对包进行转发。

图4-4　在OVS中进行逻辑转发和隧道封装

对于单播流量，若目的地在同一个host中，则直接转给目的VM的vNIC；若目的地在另外的host中或者在物理网络中，则需要封隧道转发给相应的Hypervisor或者Gateway。对于广播/组播流量，则需要选择一个Service Node，封装相应的隧道交由该Service Node进行复制，以免占用源Hypervisor上过多的CPU。注意，logical datapath上转发的决策完全发生在源Hypervisor上，决策的结果会放在隧道的header中，目的Hypervisor/Service Node/Gateway只需要根据隧道header中的相关字段将数据包直接转发给相应的vNIC/NIC即可。

论文的最后对于NVP平台的成功做了一些解读，有如下几点。

·Overlay网络的抽象与传统网络一致，NVP的用户可以基于常见的网络策略对Overlay网络进行控制，这使得传统网络的用户可以自然地接受NVP。

·nlog的使用使得控制平面的计算效率提升了一个数量级。

·采用了开源的OVS作为转发设备。

·准备采用DPDK技术对数据平面进行加速。

·没有采用激进的SDN思路，而是只对网络边缘进行了SDN的控制，使得用户能够平滑地完成网络的演进。

4.1.5　NSX-V整体架构

NSX-V用于在纯VMware vSphere环境对网络进行虚拟化，并不支持KVM、XEN等开源服务器虚拟化技术。虽然NSX-V面向的是VMware自家的产品，但还是保留了NVP的大部分SDN技术特征，并提供了强大的NSX Edge以支持更为丰富的NFV组件，为NSX-V的组网提供了更多的可能性。NSX-V的微分段技术中实现了分布式防火墙，能够支持更为灵活、立体的网络安全策略。另外，NSX-V还提供了网络管理平面，方便用户进行集中式的管理。

如图4-5所示，NSX-V的产品架构由上到下分为三个平面：管理、控制与数据。管理平面的主要组件为NSX Manager，它通过UI提供虚拟化网络的视图呈现与集中式的单点配置与管理，通过RESTful API与VMware的管理平台交互、通过RESTful API/私有协议对控制平面/数据平面进行配置与管理。控制平面组件主要为NSX Controller，它向上通过RESTful API接收NSX Manager的配置与管理，向下通过私有协议来管控数据平面的转发，另外DLR（Distributed Logic Router）Control VM也是NSX Controller的一个组件，它接受NSX Manager的配置与管理，并运行传统的路由协议，作为分布式逻辑路由器的控制平面与下一跳路由器（通常为NSX Edge）交换路由信息，以处理南北向流量。数据平面的组件主要包括NSX vSwitch和NSX Edge，通过私有的协议与控制平面进行通信，并根据控制平面提供的全局信息进行转发。

可以看到，NSX-V的这套架构是一套纯软的方案，也就是说，从管理到控制再到设备全是软件实现的，将虚拟网络透明地Overlay在物理网络之上。对于纯软的方案来说，功能从来都不是问题，性能与高可用才是软件产品能否成功的关键，NSX-V作为生产级别的网络虚拟化平台，自然也少不了对这些方面的考虑。下面对NSX-V的技术实现进行介绍。

图4-5　NSX-V整体架构

4.1.6　NSX-V管理平面设计

管理平面的主要组件是NSX Manager，其主要功能有以下几点。

·管理、推送控制平面和数据平面互相认证的安全证书。

·部署、配置NSX Controller。

·配置NSX vSwitch和NSX Edge。

·网络流量的采样与监控。

除了以上管理功能，NSX Manager还会作为NSX vSwitch中DFW（Distributed FireWall，DFW）模块的控制平面，它将绕过NSX Controller，通过RabbitMQ向ESXi主机Hypervisor中的vsfwd进程发分布式防火墙的策略。分布式防火墙的策略可以通过NSX Manager的API来指定，也可以通过vSphere Web Client来生成，此时vCenter Server可以看作分布式防火墙的管理平面。

管理平面的工作流程简要示意如图4-6所示：①NSX Manager自身的部署，②NSX Manager向vCenter注册，③部署、配置控制平面，④⑤配置数据平面。

NSX Manager通过标准虚拟机模板的方式部署在vSphere环境中，可采用HA或者FT的方式进行备份。虽然NSX Manager有自己的UI，不过为了方便管理，NSX Manager还是会注册到vCenter中，用户在vCenter UI中的Network && Security选项卡下可以对NSX网络进行间接的管理。

图4-6　NSX Manager的工作流程

4.1.7　NSX-V控制平面设计

NSX Controller向上通过RESTful API与管理平面进行交互，以获取安全证书和租户的业务策略，向下通过私有协议与ESXi主机中Hypervisor的netcpa进程交互，汇聚数据平面的信息并控制数据包的L2/L3转发，如图4-7所示。NSX Controller上面存储的信息主要包括：ARP表（用于在设备本地抑制ARP的泛洪）、MAC地址表（用于L2的单播）以及VTEP表（用于定向封装隧道解除物理网络上的组播）和路由表（用于分布式路由）。ARP/MAC/VTEP表均从ESXi主机的Hypervisor处学习得到，路由表则从DLR Control VM学习得到。

当采用分布式路由处理南北向流量时，DLR和它下一跳路由器（通常是NSX Edge，也可能是物理路由设备）间的转发逻辑是由传统的分布式路由协议来决策的，DLR的转发平面分布在各个ESXi主机上，如果要为DLR做路由，只能将DLR的控制逻辑从DLR的转发平面抽离出来，与下一跳路由器交互OSPF/BGP等路由协议，以打通南北向流量。DLR Control VM就是承载DLR控制逻辑的实体，它逻辑上属于NSX Controller的一个组件。图4-8给出了DLR Control VM的工作原理：①通过NSX Manager部署、配置DLR Control VM，主要包括DLR的port/MAC/IP和DLR上要运行的路由协议，目前DLR上只支持OSPF和BGP。②NSX Controller Cluster将DLR的port/MAC/IP信息发布给ESXi主机，以便在ESXi主机本地进行L3逻辑转发。③DLR Control VM与下一跳路由器（图中为NSX Edge）交互分布式路由协议，DLR向外发布了租户的IP地址，学习到了通向Internet的路由。④DLR Control VM将学习到的路由告诉给NSX Controller Cluster；⑤NSX Controller Cluster向ESXi主机中的DLR反射Internet路由，使其网关指向下一跳路由器（图4-8中为NSX Edge）；⑥DLR根据Internet路由将VM产生的Internet送给下一跳路由器（图4-8中为NSX Edge）。

图4-7　NSX Controller与管理平面和数据平面的交互

图4-8　通过DLR与外部网络进行路由

控制平面的NSX Controller和DLR Control VM，同样是以虚拟机的形式部署在vSphere环境中的。NSX Controller的工作负载很大，因此需要建立Controller集群，Controller节点间通过Paxos算法实现同步，为了完成Leader的选举，集群中节点的个数需要为奇数。NSX Controller集群通常工作在负载均衡的模式下，不同的ESXi主机可以选择不同的Controller节点作为Master，而且同一个ESXi主机中的VTEP和分布式路由功能也可以选择不同的Controller节点作为Master，后一种负载均衡粒度更为精细，由Cluster Slicing机制来实现。DLR Control VM的负载没有NSX Controller那么大，进行简单的主备部署即可。

4.1.8　NSX-V数据平面设计

NSX-V数据平面的组件包括NSX vSwitch和NSX Edge。NSX vSwitch在VDS的基础上添加了3个内核模块：VxLAN（VTEP）、DLR（分布式逻辑路由器）和DFW（分布式防火墙）。NSX Edge则为NSX网络提供了L2 GW以及L3-L7的高级网络服务，如集中式路由/NAT、集中式防火墙、负载均衡、VPN等。

先来看NSX vSwitch。VDS负责租户的二层转发，VM连接在VDS上。VxLAN模块对目的地位于其他ESXi主机的L2帧封装VxLAN Header。DLR模块负责租户的三层转发，包括东西向流量和南北向流量。DFW与VM的vNIC相关联，能够per VM的细粒度、定制化、带状态的安全防护。由于这些模块都是分布式的，不存在单点故障的问题，因此NSX vSwitch本身就具备了很高的可用性，不需要再设计额外的HA机制。

NSX Edge可以理解为NSX-V提供的NFV产品套件。L2 Gateway用来实现NSX租户网络和物理主机的二层连通，L3 Router负责集中式的路由和NAT，用于连接NSX-V租户网络与Internet。除了二层和三层网关外，NSX Edge还提供了诸多的高级网络服务：防火墙用来提供per VDC级别的安全防护，负载均衡既可以集成在三层网关上，又可以独立于三层网关部署，高版本的NSX-V还支持DLB（Distributed Load Balancer），VPN既可以部署L2 VPN又可以部署L3 VPN，L2 VPN采用私有封装实现跨域二层互通，L3 VPN支持IPSEC和SSL两种。NSX Edge通常放在NFV POD下面采用集中式部署，以连接NSX-V网络与外界网络，因此需要为NSX Edge设计HA机制，以保证其可用性。对于上述不同的NSX Edge功能，它们的HA机制都不尽相同，此处不进行详细的分析。

在vSphere环境中，除了VxLAN所承载的VM通信的数据流量以外，VDS上还通过VMkernel接口承载了很多其他类型的流量，如管理流量、vMotion流量和存储流量，这些流量的特征和优先级截然不同，对物理网络的需求自然也有很大的区别。因此，这4种类型的流量在上联时可以采用不同的VLAN ID，并在上联口使用VLAN Trunking，以便进行复用、隔离和QoS规划。

4.1.9　NSX-V转发过程分析

接下来看NSX-V的转发过程，假设此时控制器已经学到了各个Hypervisor本地的虚拟机信息，形成了全局的ARP/MAC/VTEP表，DLR Control VM也完成了外部路由的学习与注入。

1.VSIP

流量从虚拟机vNIC出来之后，首先进入到内核中的VSIP（VMware internetworking Service Insertion Platform）进行处理。VSIP可以看作一个Service IOChain，第三方的VNF可以部署到VSIP中不同的Slot上，流量进入VSIP后会按照Slot ID由小到大的顺序依次经过各个VNF进行处理，因此VSIP可用于实现服务链的功能，如图4-9所示。默认情况下，Slot 0（DVFilter）负责无状态ACL的实现，Slot 1（vmware-sfw）负责Anti Spoofing，Slot 2即为DFW对vNIC流量进行线速的安全过滤，Slot 4-Slot 12可用于部署第三方的VNF。

图4-9　VSIP可用于实现服务链

2.BUM流量的处理方式

如果没有部署第三方的VNF，那么在DFW过滤后就进入VDS进行二层的处理。如果流量源和目的在同一个ESXi host中，则由VDS直接完成本地的转发；否则，需要通过VxLAN Module封装隧道跨越IP Underlay进行传输。IP Underlay在NSX中被称为Transport Zone，Underlay中不同的子网被称为Transport Subnet。标准的VxLAN依赖于Underlay上的组播来模拟BUM流量的泛洪，相比之下，NSX-V的VxLAN Module还提供了如下两种处理BUM流量的模式：

·Unicast Mode。源VTEP会对BUM流量做头端复制，然后单播到其他相关的目标VTEP上，从而得以完全消除掉组播。在Unicast Mode具体的实现中，如果目标VTEP和源VTEP处在同一个Transport Subnet中，则由源VTEP直接单播给目的VTEP；如果目标VTEP和源VTEP不在同一个Transport Subnet中，那么NSX-V会在目标VTEP所在的Transport Subnet（设为TS X）中选择出一个VTEP作为UTEP，源VTEP发往TS X中目标VTEP的所有流量都会先发给UTEP，并标记VxLAN报头中的REPLICATE_LOCALLY位，UTEP收到后发现REPLICATE_LOCALLY被置位从而得知这是一个泛洪包，于是UTEP会再一次对流量进行复制并单播发送给TS X中其他的VTEP。如图4-10所示，四个VM属于同一个VxLAN Segment，VM 1/VM 2所在的VTEP 1/VTEP 2属于Transport Subnet A，VM 3/VM 4所在的VTEP 3/VTEP 4属于Transport Subnet B，因此VM1发出的BUM流量会被VTEP 1单播发给VTEP 2以及Transport Subnet B中的UTEP（图4-10中为VTEP 3），而不会直接单播给VTEP 4，VTEP 3负责将流量中继给VTEP 4。

·Hybrid Mode。Hybrid Mode是组播和头端复制的结合方式，Underlay只需要做L2组播而不需要做L3组播，减小了组播的部署和维护难度。在Hybrid Mode具体的实现中，对于同一个Transport Subnet中其他相关的VTEP，源VTEP会通过L2组播把流量送过去，对于其他Transport Subnet中的VTEP，源VTEP会为这些Transport Subnet选择出一个MTEP，通过单播把流量送到MTEP上，再由MTEP在其所属的Transport Subnet中做L2组播送到相关的VTEP上。

图4-10　NSX-V VxLAN Module的Unicast Mode

3.L2流量的处理

有了上述介绍作为基础，下面来具体介绍NSX-V对于L2流量的处理。图4-11是虚拟机间ARP Request的处理过程，VM 1向VM 2发送的ARP Request被VDS 1所截获，由于VDS 1本地没有VM 2的ARP信息，于是VDS 1将该ARP Request封装到控制消息中上报给Controller。Controller收到后在全局信息表中进行查找，并将VM 2的信息<MAC 2，IP 2，VTEP 2>返回给VDS 1。VDS 1收到控制器返回的信息，更新本地的ARP/MAC/VTEP表，并生成一个ARP Reply返回给VM 1。如果Controller中也还没有VM 2的信息，就需要对ARP Request进行泛洪了，泛洪的模式可采用Multicast/Unicast/Hybrid中的任意一种。

图4-11　VM 1向VM 2发送ARP Request的处理

收到ARP Reply后，VM 1开始向VM 2发送单播。如图4-12所示，由于VDS 1之前已经从Controller处学习到了VM 2的信息，包括VM 2所在VTEP 2的IP地址，因此VDS 1会直接封装VxLAN单播给VTEP 2，VTEP 2解封装后学习VM 1的信息更新本地的转发表，并将流量发送给VM 2。之后VM 2回复VM 1的流量，VDS 2也直接封装VxLAN单播回给VTEP 1。

图4-12　VM 1向VM 2发送L2单播的处理

虚拟环境与物理网络互通的L2流量，处理过程如图4-13和图4-14所示。图4-13是VM 1向物理主机host 3发送ARP Request的处理，该ARP Request同样被VDS 1所截获，VDS 1本地没有host 3的ARP信息于是上报给Controller。假设MAC 3刚刚接入物理网络，Controller中并没有host 3的信息，于是通知VDS 1进行隧道上的泛洪，VTEP 1将流量送给VTEP 2和VTEP 3。VTEP 2学习VM 1的信息并将流量送给VM 2，VM 2会将该ARP Request丢弃。VTEP 3中有一个Virtual VxLAN Gateway，它通过VLAN与物理网络连接，并存有VNI和VLAN的映射关系，于是它在收到ARP Request后，学习VM 1的信息，并通过相应的VLAN（图4-13中为VLAN 100）送到物理网络中。物理交换机学习VM 1的信息随后进行泛洪，从而物理主机host 3收到了该ARP Request。

图4-13　VM 1向物理主机MAC 3发送ARP Request的处理

图4-14是物理主机MAC 3向VM 1发送ARP Reply的处理。物理交换机学习host 3的信息，根据之前学习的VM 1的信息转发到VTEP 3中的Virtual VxLAN Gateway。Virtual VxLAN Gateway学习到host 3的信息，根据之前学习的VM 1的信息，将VLAN转换为VxLAN并单播给VTEP 1。VTEP 1收到后会学习host 3的信息，并把ARP Reply送给VM 1。之后VM 1发送给host 3的单播流量就会被VTEP 1直接送给VTEP 3。

4.L3流量的处理

下面再来说NSX-V对于L3流量的处理，先来说虚拟机间的东西向流量。图4-15中VM 1和VM 2位于不同的网段，现由VM 1向VM 2发送单播。流量进入后，VDS发现目的MAC地址是分布式网关的任播MAC地址得知这是一个L3的流量，于是交给DLR Module进行处理。NSX-V中，DLR Module采用了非对称的方式来实现分布式路由，因此在匹配目的IP后直接会路由到VM 2所在的网段，解析出VM 2的MAC地址，然后封装VxLAN单播给VTEP 2。VTEP 2解封装，然后转发给本地的VM 2。这里要注意的是，由于采用了非对称的方式，因此VM 2回复VM 1的单播流量会在VTEP 2做路由，直接路由到VM 1所在的网段，解析出VM 1的MAC地址，然后封装VxLAN单播给VTEP 1，VTEP1解封装然后转发给本地的VM 1。

图4-14　物理主机MAC 3向VM 1发送ARP Reply的处理

南北向L3流量的处理如图4-16和图4-17所示，DLR的下一跳为NSX Edge。图4-16是对于External Network访问VM1的流量的处理，流量先到达NSX Edge，根据DLR Controller VM宣告的路由，VM 1所在网段的下一跳是DLR上联NSX Edge网段（图4-16中的Transit Network）的虚拟接口的地址。于是NSX Edge将流量送到本地的DLR Module中，由DLR Module直接路由到VM 1所在的网段，解析VM 1的MAC地址，然后封装VxLAN送给VTEP 1，VTEP 1解封装后转发给VM 1。

图4-17是对于VM1向External Network所回复流量的处理。VM 1的流量被交给本地的DLR Module进行处理，根据从DLR Control VM处学到的路由，External Network的流量的下一跳为NSX Edge下联DLR的虚拟接口（图4-17中的Transit Network），在解析出该虚拟接口的MAC地址后，封装VxLAN送给NSX Edge所接入的VTEP 2，VTEP解封装然后转发给NSX Edge。

图4-15　VM 1向VM 2发送L3单播的处理

图4-16　External Network访问VM1的流量的处理

图4-17　VM1向External Network回复流量的处理

4.1.10　NSX-MH与NSX-T

NSX-MH与NSX-V最大的区别就是NSX-X在数据平面使用了开源的OVS，因此能够支持非vSphere环境，如KVM、XEN等。既然数据平面使用的是OVS，那么南向协议自然也就还是OpenFlow和OVSDB，因此NSX-MH基本保留了NVP已有的技术实现，如用流水线实现L2和东西向的分布式路由，支持用Service Node处理广播/组播，使用STT做Hypervisor到Hypervisor的隧道，等等。同时，为了向NSX-V看齐，NSX-MH在NVP的基础上也增加了很多的新功能。

1）增加了NSX-Manager作为管理平面，为控制平面和数据平面的认证提供证书。

2）在Controller集群中引入了切片机制，实现控制平面的负载均衡。

3）支持集成物理的L2 Gateway，Hypervisor使用VxLAN协议与物理L2 Gateway互通。

4）Service Node支持从STT向其他隧道的转换。

5）通过在OVS上挂vRouter来支持集中式的L3网关，更好地支持VM与Internet间的通信。

6）通过OpenFlow流表为per VM引入L2-L4的安全策略，但不支持有状态的Session。

NSX-T是NSX-MH的升级版，旨在提供更好的Hypervisor Agnostic特性，并增强了对OpenStack和容器的支持。也有VMware的官方说法是，NSX-T将融合NSX-V和NSH-MH的代码库，功能上将作为NSX-MH、NSX-V功能的超集。2017年2月，NSX-T发布了1.1版本，官方给出的一些New Features如下所示。

1）DHCP Server。支持动态分配IP以及静态IP绑定。

2）Metadata Proxy Server。虚拟机实例能够从OpenStack Nova中迅速获得其实例参数。

3）Geneve。使用Geneve代替STT作为Hypervisor到Hypervisor的隧道。

4）MAC Learning。支持一个logical port下存在多个MAC地址。

5）IPFIX。支持logical switch和logical port粒度的IPFIX监测。

6）Port Mirroring。支持在Transport Node上将流量引向sniffer tool。

从最初的NSX-MH到升级版的NSX-T，说明VMware不仅是要NSX服务于VMware的服务器虚拟化，而是希望它可以成为通用的数据中心网络虚拟化平台。要成为通用的平台，技术和生态两个角度都要具备领先之处。技术上NSX无疑是行业领先的，前面也已经详细地进行了介绍。生态方面，VMware已经与众多网络厂商进行了深度的合作，使得NSX能够间接地管理到硬件交换机，以交付完整的数据中心网络。就目前而言，NSX可以说仍然是商用SDDCN的领跑者。

4.2　Cisco ACI

其实在网络厂商的圈子里，SDN早就不是什么新概念了。ForCES（见本书2.2节）作为“SDN上古神兽”在2004年就有了第一版RFC。2006年Juniper向IETF提交NETCONF，希望能够对各厂家设备的CLI进行标准化，同时在远端通过开放API对网络进行自动化配置与管理，至于OpenFlow其实也早在2008年就被提出了，不过当时也没有在业界引起太大的波澜。2004年到2012上半年，面对初现“狼子野心”的SDN，Cisco可谓是镇定自若，策略上并没有采取过分的打压。

事后来看，当时Cisco显然没有预料到SDN近10年蓄势后的爆发力，导火索就是2012年7月VMware对Nicira的12.6亿美元的天价收购，这表明SDN的商用方案正式进军数据中心。Cisco收购Nicira失败后，开始秘密孵化SDN创业公司Insieme，并派出了Cisco技术扛把子MPLS中的MPL三人帮助打造Insieme的软硬件。2013年4月，Cisco领衔各大厂商成立SDN开源项目OpenDaylight，随即于6月发布ONE战略，钱伯斯正式公开承认SDN的未来地位。

2013年8月，Google发布了基于SDN的广域网解决方案B4。2013年10月前后，Awazon放弃与Cisco近10亿美元的设备订单而选择基于SDN的数据中心白盒解决方案。世界上两家Top 5的高科技公司相继投向SDN，抛开技术上的原因不谈，这明显地为市场释放了SDN将加速落地的信号。Cisco没办法再等下去了，在2013年11月初正式收购Insieme，并高调发布其数据中心SDN解决方案ACI（Application Centric Infrastructure），围绕Cisco自己的硬件打造SDN，正式宣布与完全基于软件的NSX开战。2014年4月，Cisco部分开源了ACI的南向协议OpFlex（draft-smith-opflex），向OpenFlow+白盒发起反击。

自从ACI推出以来，业界围绕着NSX和ACI的讨论和比较就从来没有停止过。NSX骨子里是更为正宗的SDN学院派，NSX-MH更是将网络设备进行了彻底的开放。而ACI其实是打了SDN的擦边球，其整体方案仍然是围绕Cisco的硬件来展开的，因此有人说ACI算不得是SDN，而是一种HDN（Hardware Defined Networking）。不过从广义SDN的角度来看，ACI仍然开放出来了一定的网络可编程能力，因此它仍然属于SDN，而且其技术上有很多有趣的地方，本节就来对ACI的实现进行探讨。

4.2.1　整体架构

如图4-18所示，ACI的架构并不复杂，APIC（Application Policy Infrastructure Controller）作为控制平面，向上通过GUI/CLI/RESTful API/Python Scripts向管理员提供配置、管理、策略下发的接口，向下通过OpFlex协议将策略推送给Leaf/vLeaf设备中的PE（Policy Element）模块，PE模块会将策略转换成设备能够理解的配置并部署到设备中。

注意　APIC上的策略都将生效于ACI网络的边缘，因此APIC只会向Leaf/vLeaf推送策略，而并不会向Spine推送策略。另外，Leaf/vLeaf/Spine的转发都不受APIC控制。

图4-18　ACI的架构

为了方便后面介绍ACI的控制平面，这里对ACI使用的南向协议OpFlex进行简单的介绍。OpFlex基于声明式（declarative）的配置管理方式，相比CLI/OVSDB/NETCONF这类命令式（imperative）的配置管理方式，OpFlex将策略实现的复杂性从控制器中剥离，控制器只负责管理和推送策略，策略的实现交由转发设备来完成。这种声明式的配置管理方式在一定程度上可以减轻控制平面的压力。OpFlex的通信模型如图4-19所示，ER（Endpoint Registry）存储着工作负载（EP）的信息并根据PR中的策略将EP分类形成EPG（EndPoint Group），PR（Policy Registry）存储着业务策略，PE（Policy Element）负责监测EP并在转发设备本地执行策略，Observer负责监测转发设备的状态信息。PE分布在各个转发设备本地，而ER、PR和Observer则是逻辑上集中的。对于ACI来说，PE分布在各个Leaf/vLeaf上，ER、PR和Observer则分布在APIC集群中。

图4-19　OpFlex的通信模型

OpFlex承载于RPC之上，提供了双向的通信能力，图4-19中出现的信令如表4-1所示。

表4-1　OpFlex中的一些信令

4.2.2　管理与控制平面设计

相比NSX，ACI并没有设计单独的管理平面。不过实际上，与其将APIC称为控制平面，倒不如称之为管理平面来得贴切，因为OpFlex擅长的是对业务策略和设备的管理，却并不具备对转发设备的转发进行直接控制的能力。

为了实现“以应用为中心”，最为关键的技术就是对应用策略的自动管理和部署。APIC使用了“承诺理论”来管理、部署这些应用策略。“承诺理论”是指控制平面只维护应用策略的状态，而不去真正执行策略，策略完全由转发设备来执行。如果转发设备执行失败，将会导致应用策略的部署失败。ACI中“承诺理论”的执行依赖于策略对象模型（Policy Object Model，POM）的建立，ACI中基础的POM如图4-20所示，EPG（Endpoint Group）代表了一组具有相同通信约束的EP，同一个EPG内的EP间可以无障碍通信，不同EGP的EP间的通信规则由合约（Contract）来进行约束。一个应用可以由多个EPG和这些EPG彼此之间的Contract构成，它们共同形成一个应用策略ANP（Application Network Profile）。EPG的分类规则非常灵活，管理员可以根据应用策略的需要来制订EPG的分类规则，如同一VLAN、同一VxLAN Segment甚至同一DNS后缀，等等。Contract主要包括入向/出现ACL、QoS、重定向和服务链。基于这些多样的EPG和Contract，ACI管理员可以通过CLI/GUI/RESTful API/Python Scripts制订丰富的ANP，由APIC维护并分发这些ANP，由转发设备来完成ANP的执行。

图4-20　ACI中策略对象的基础模型

APIC的POM中，除了通过EPG来对EP进行应用策略级别的分类外，还可以通过Bridge Domain和Network来对EP进行L2和L3的界定。Bridge Domain是一个或者多个subnet的集合，一个Network可以包含多个Bridge Domain，逻辑上是一个VRF。同一EPG下的EP必须属于同一个Bridge Domain，但是不同EPG间的EP可以根据Contract跨越Bridge Domain/Network进行通信，也就是说，ANP的制定逻辑上是不受任何局限的。图4-21是ACI策略中几个关键概念的关系。

图4-21　ACI策略中几个关键概念的关系

APIC向Leaf/vLeaf推送应用策略的方式有3种。

1）策略预装，即所有的应用策略都由APIC预先推送给Leaf/vLeaf，策略在设备本地即刻编译，编译后可在datapath上生效。

2）策略触发式推送，即EP上线时Leaf/vLeaf会通过一条Endpoint Request向APIC请求相关的策略，APIC收到请求后将策略推送给Leaf/vLeaf，策略在设备本地即刻编译，编译后可在datapath上生效。

3）策略预推送，即所有的应用策略都由APIC预先推送给Leaf/vLeaf，但是策略在设备本地不会立刻进行编译，而是等到EP上线时才会开始编译并生效。

第一种方式的优点是APIC的实时开销几乎为零，缺点是设备上的ACL较多；第二种方式与第一种方式相反，优点是设备上不存没有用的ACL，但是APIC的实时开销较大；第三种方式介于前两者之间，是一种折中的方案，也是ACI的默认模式。

除了管理、部署应用策略以外，APIC还需要对转发设备进行管理。APIC有以下8个主要的组件。

·Policy Manager。管理、部署应用策略，作为PR和ER通过OpFlex协议与ACI转发设备中的PE进行交互。

·Topology Manager。通过LLDP维护ACI的物理网络拓扑，通过ISIS维护ACI的L2/L3逻辑拓扑。同时维护设备清单，包括序列号/资产号/port/linecard/switch/chasis等。

·Observer。监测APIC/ACI转发设备/协议/性能等，并提供告警和日志。作为Observer通过OpFlex协议与ACI转发设备中的PE进行交互。

·Boot Director。负责APIC/ACI转发设备的启动，自动发现，IPAM和固件升级。

·Appliance Director。负责APIC Cluster的建立。

·VMM Manager。与虚拟机管理程序（VMware vCenter/Hype-V SCVMM/libvirt）交互，维护Hypervisor上的网络资源信息，如NIC/vNIC/VM names等，能够感知虚拟机迁移。会将相关信息告知Policy Manager。

·Event Manager。记录APIC/ACI转发设备发生的事件和故障。

·Appliance Element。监控上述组件的运行状态。

上述组件所涉及的数据，包括策略和设备的状态信息，是十分复杂的，为了能将这些数据有效地组织起来，APIC设计了一棵MIT（Management Information Tree），如图4-22所示。无论是北向的RESTful API还是南向的OpFlex，对数据进行任何的操作都必须依据MIT上的路径，因此APIC是一种数据驱动的控制器架构。

APIC的高可用建立在APIC集群的基础上。APIC间的自动发现依赖Boot Director组件完成，APIC节点间通过Appliance Director组件维护集群的状态。至少需要3个节点才能形成APIC集群，集群可以根据ACI网络规模进行动态的扩容，集群中节点数上限为31个。不过实际上，由于APIC只做策略不做转发控制，因此即使所有的APIC节点全部挂掉，整个ACI网络的L2/L3转发是不会受到任何影响的。APIC集群间的MIT数据管理采用shard实现，shard技术将MIT上的一份数据复制成多份备份到不同的集群节点中，数据的读写必须发生在原始的节点上，原始节点再向该数据的备份节点进行同步，以保证数据的最终一致性。APIC中需要做shard的组件有4个：Policy Manager、Topology Manager、Observer和Boot Manager，它们的数据在APIC集群上都存有3个备份，随机地分布在各个APIC实例中。

由于APIC集群间的通信以及APIC与ACI转发设备的通信都是通过IP完成的，而ACI网络中的IP连接是不受APIC控制的，因此APIC可以采用In-Band的部署在ACI网络中，而且APIC集群不必位于同一机架下，如图4-22所示。出于商业方面的考虑，Cisco一般会将APIC打包在其UCS刀片服务器进行部署，装有APIC的服务器建议双归到两台冗余的TOR上（N9000 Leaf）。当APIC采用Out-of-Band部署时，应通过至少1G的Ethernet的管理端口与设备进行连接，以保证控制信道足够的带宽。

图4-22　APIC以In-Band方式进行部署

4.2.3　数据平面设计

1.N9300与N9500

ACI转发设备构成的网络称为ACI Fabric，Fabric通过ISIS实现互联。ACI Fabric采用Leaf-Spine的物理拓扑，Leaf与Spine间可采用物理全互联，Leaf间以及Spine间不建议进行物理直连。Cisco专门为ACI Fabric拓展了其Nexus系列的交换机——N9K，通常以N9300系列作为Leaf，用于服务器/防火墙/路由器等设备的接入；以N9500系列作为Spine，为Leaf间的互联提供高速、无阻塞的连接，如图4-23所示。

·Leaf的软件基于Nexus OS进行了裁剪，并增加了PE模块来处理OpFlex协议中APIC推送下来的处理应用策略。Leaf提供了两种模式的OS：Standalone模式，用于兼容之前的Nexus Switch；Fabric模式，用于ACI Fabric。Leaf的硬件设计上，要求支持二层桥接（VLAN/VxLAN/NvGRE）和三层路由（OSPF/BGP），并能够基于原始帧的目的MAC或IP封装隧道，还能够在本地执行应用策略。

·Spine的软件设计中，并不要求支持OpFlex。Spine的硬件设计上要求提供Fabric模块的热插拔、高密度的40E/100GE以太网端口。另外，后面会详细谈到，Spine还需要支持大规模的MAC/IP到VTEP IP间映射的database mapping，以及VxLAN Hardware Proxy。

图4-23　通过N9300和N9500部署Leaf-Spine

2.ACI Fabric的初始化

ACI Fabric的初始化通过LLDP自动完成，实现类似于ZTP的功能，如图4-24所示。首先，Leaf通过LLDP发现直连的In-Band APIC，然后该Leaf会向APIC发送DHCP，获得TEP（Tunnel Endpoint）所用的IP并请求进行固件的下载并请求安装。随后各个Spine通过LLDP发现该Leaf，并向APIC发送DHCP，获得TEP所用的IP并请求进行固件下载与安装。最后，Leaf和Spine间运行ISIS打通Fabric，APIC间完成发现并形成集群。上述初始化工作均在ACI Fabric的Infrastructure VLAN中完成。

图4-24　ACI Fabric的初始化

3.ACI Fabric的接入

通过Hypervisor中的vSwitch可以将VM接入ACI Fabric，ACI支持的vSwitch主要有以下3种：Open vSwitch、VMware VDS和Cisco AVS。对于OVS，VM的发现是由OpenStack直接告知APIC的。而对于VDS，VM的发现是通过在Leaf和VDS之间运行扩展的LLDP来实现的。Leaf发现VM后，通过OpFlex通知APIC新的EP上线并请求应用策略的下发。图4-25和图4-26分别给出了通过OVS和VDS接入ACI Fabric的交互流程，图中表述得非常清晰，就不再逐步骤解释了。

图4-25　ACI中使用OVS接入虚拟机

对于AVS，因为这是Cisco自家的产品，可以支持OpFlex，所以APIC可以通过OpFlex探测到VM的上线，当然由于APIC和AVS通常属于不同的blade/host，因此中间要经过Leaf做一次中继。如图4-27所示。

如图4-28所示，vSwitch上的转发模式有3种可选：①FEX模式，vSwitch收到的所有流量都通过Leaf处理，即使源和目的在同一个vSwitch上。②Local Switching模式，同一个EPG内部的流量在vSwitch本地执行应用策略，跨EPG的流量送给Leaf处理。③Full Switching模式，vSwitch可以在本地处理处理同一个EPG内部的流量以及跨EPG的流量。后面两种模式只有AVS可以采用，因此本地执行应用策略要求vSwitch支持OpFlex。

Leaf上支持通过以不同的封装形式接入ACI，比如VLAN、标准VxLAN和NvGRE，如图4-29所示。与VLAN的对接，Leaf会完成eVxLAN与VLAN间的转换。另外，整个ACI Fabric将以Transparent Mode接入外界VLAN的STP环境，以避免产生环路。如果是与标准VxLAN/NvGRE进行对接，Leaf会完成eVxLAN与标准VxLAN/NvGRE间的转换。因此，ACI也是可以作为NSX的Underlay进行部署的，当然前提是拥有足够的人力和财力。

图4-26　ACI中使用VDS接入虚拟机

图4-27　ACI可使用AVS接入虚拟机

图4-28　vSwitch上的三种转发模式

图4-29　ACI支持以多种封装形式接入

4.eVxLAN

租户网络上的转发是基于VxLAN进行的，Overlay在ACI Fabric之上。为了更好地支持应用策略的部署，ACI拓展了标准的VxLAN Header，形成eVxLAN，如图4-30所示。eVxLAN实际上是对LISP的一种2层扩展，其中比较关键的是记录了源EPG的Source Group字段，策略在datapath上的执行就是通过这个字段完成的，24bit的VNID不再指代标准VxLAN中的Segment ID了，而是根据情况指代Bridge Domain或者Network，VNID后面还有一些扩展位，比如用于实现Flowlet DLB的LB。

5.Overlay组网设计

VxLAN Overlay上的转发是ACI的核心，在本质上体现了Cisco对于SDN的理解。有别于一些SDN控制器直接操作FIB来控制Overlay的转发，APIC只关心应用的策略而并不关心VxLAN Overlay上的转发，VxLAN Overlay上的转发完全由ACI Fabric设备来实现。

在Overlay中转发流量，依赖于虚拟机MAC/IP与其所在的VTEP间的映射关系。通常有三种思路来学习该映射关系：第一种是分布式的自学习（标准VxLAN，只能学MAC）；第二种是由SDN控制器直接从CMS获取映射关系（OpenStack Neutron）；第三种是转发设备自学习本地的MAC/IP，然后将MAC/IP告诉SDN控制器，控制器整理好MAC/IP和VTEP的映射关系再反射给其他的转发设备（VMware NSX）。

图4-30　ACI eVxLAN的报头格式

ACI的思路则有别于上述几种方式。在ACI中，Leaf会对本地EP的MAC/IP采用自学习，但是Leaf不会将该信息告知APIC，而是将该信息通过一种名为COOP（Council of Oracles Protocol）协议随机地同步给某一个Spine，然后该Spine会通过COOP将该信息同步给其他的Spine。经过各个Leaf的同步，Spine会获得ACI网络中所有EP的信息，不过却并不会将该信息反射给其他的Leaf。如果Leaf看到一个未知的目的MAC/IP，默认情况下不会采取泛洪，而是会将数据包直接送给Spine，Spine在查找到目的EP的转发信息后，代为转发给相应的Leaf。目的EP所在的Leaf收到流量后，通过自学习记录下位于远端的源EP的MAC/IP，用于处理后续的返回流量。因此可以看到的是，ACI网络中的转发实际上可以概括为自学习+Spine Proxy，和APIC没有任何的关系，而Spine却在一定程度上对转发进行着控制。

转发表的实现上，Leaf中存在LST（Local Station Table）和GST（Global Station Table）两种。如图4-31所示，LST中存着所有本地的EP的转发信息，主要包括MAC和IP/32与本地端口的对应关系，GST则通过Cache来缓存部分远端EP的转发信息，主要包括MAC和IP/32与远端Leaf的对应关系。Spine存有ACI网络中所有EP的转发信息，主要包括MAC和IP/32与其所在Leaf的对应关系。考虑Spine中要存所有EP的信息，因此ACI为N9500系列设备的线卡设计了专用的芯片，以实现hardware mapping database，通过扩展线卡就可以对容量进行扩充，最多能够提供一百万以上的表项。

由于ACI采用了分布式路由，因此与Internet的L3对接的关键就变成了如何将Internet路由分发给Leaf。如图4-32所示，ACI对此的实现过程为：在ACI Border Leaf以VRF-Lite方式与外界路由器相连，并运行OSPF/BGP以学习Internet路由。经过路由重分布后，ACI Border Leaf会将学习到的Internet路由通过MP-BGP告知Spine，Spine将作为BGP-RR通过MP-BGP将Internet路由反射给其他的Leaf。整个过程同样没有APIC参与，完全由ACI的转发设备完成。由于采用了动态路由协议，因此这部分流量的高可用性可通过HSRP/VRRP以及ECMP等传统方式来实现，这里不再赘述。

图4-31　ACI中的LST与GST

转发表项的实现上，由于External Routes会以聚合的形式存在，因此需要使用传统的LPM Table做最长前缀匹配，而没有用LST或者GST。

图4-32　ACI与Internet的对接

6.服务链机制设计

ACI通过Service Graph来支持服务链（SFC）的自动化，如图4-33所示，开启了Service Graph后，APIC将负责对服务节点的参数以及SFC路径进行配置。SFC是通过Contract起作用的，Contract两端的EPG间的流量将通过服务链进行处理。

图4-33　ACI通过Service Graph来实现服务链

因此，Service Graph中的转发机制与路由的实现位置是密切相关的：若路由实现在服务节点上，则流量在路由的过程中必然会经过该服务节点，因此无须对该服务节点进行引流，这种部署模式被称为“Go-To”；若路由功能不在服务节点实现，而在接入设备上实现，则该服务节点对于流量是完全透明的，因此需要对该服务节点进行引流，这种部署模式被称为“Go-Through”。引流的实现机制如下：一条服务链上会有很多跳，服务链对应一个Network（即VRF），而每一跳都对应与该Network中的不同Bridge Domain，每一跳上两端的接口配置VLAN，并开启该对应Bridge Domain上的广播和泛洪，以保证流量能够流向下一个服务节点。上述引流机制的设计，与正常的二层转发没有区别，是一种VLAN Stitching的方式，但是由于ACI对于普通流量（非SFC流量）的转发，一般是依赖于Hardware Proxy上MAC/IP与VTEP的映射关系来进行的，因此默认会禁止掉广播和泛洪，而服务节点的MAC地址不会出现在流量的目的MAC中，因此只能通过开启广播和泛洪来保证流量流经该服务节点。

4.2.4　转发过程分析

1.ARP的处理

先来看ACI对于ARP的处理。ARP的处理有两种方式：第一种就是泛洪；第二种是通过Spine来做Hardware Proxy。泛洪自不必说，Hardware Proxy中，当Leaf收到未知的ARP后，会封装eVxLAN送给一个Spine，Spine的mapping database中存有MAC/IP的映射关系，因此Spine将重新封装eVxLAN的包头将其送给目的所在的Leaf，从而抑制了ARP的泛洪，如图4-34所示。ACI默认开启的是Hardware Proxy，第一种方式常用于默认网关位于ACI Fabric之外的情况。

2.单播流量的处理

对于单播流量，如果目的MAC为分布式路由器的任播MAC，则进行匹配/32的目的IP地址做三层转发；否则，匹配MAC做二层转发。对于二层的IP流量，实际上也可以通过匹配/32的IP地址来完成转发。对于源和目的在同一个Leaf下的情况，Leaf查LST后直接在本地完成转发，不用封装eVxLAN。对于源和目的在不同Leaf下的情况，如果源所在Leaf的GST中有目的MAC/IP的转发表项，对于二层流量则直接把原始帧包在eVxLAN中送给目的所在的Leaf，其中以VNID指代该二层所对应的Bridge Domain，对于三层流量则先完成MAC的改写，并将改写后的帧包在eVxLAN中送给目的所在的Leaf，其中以VNID指代该三层所对应的Network，这实际上是一种对称IRB的实现。

图4-34　ACI使用Hardware Proxy抑制ARP泛洪

而对于源和目的在不同Leaf下的情况，如果源所在Leaf的GST中没有目的MAC/IP的转发表项，就会通过封装eVxLAN发往一个Anycast VTEP，这个Anycast VTEP由多个Spine组成以实现负载均衡，其中的一个Spine收到后会查mapping database并封装新的eVxLAN定向单播给目的所在的Leaf，如果Spine也不知道目的的位置，就只能在目的所属的EPG内进行泛洪。

图4-35对上述机制进行了解释，其中可以看到的是在源所在的Leaf和目的所在的Leaf上会根据源和目的所在的EPG去执行策略，这也是ACI的eVxLAN相比标准VxLAN在处理机制上的独特之处。

3.组播流量的处理

ACI Fabric在初始化时会通过ISIS生成FTAG转发表，FTAG是ACI Fabric拓扑的生成树，用于实现Underlay上的组播，一个ACI Fabric中可以同时支持16个FTAG。这些FTAG会以不同的Spine作为根，一个Spine可以同时作为多个FTAG的根。APIC会为每个BD分配一个Underlay组播地址（GIPo），这个BD中所有的BUM流量都会使用其GIPo在Underlay上进行传输，不同的GIPo可以使用不同的FTAG实现负载均衡。Leaf会将本地存在的BD通过COOP同步给Spine，Spine会根据此信息为该BD所对应的GIPo来修剪FTAG树，去掉本地不存在该BD的Leaf。

当Leaf收到组播流量后，根据该组播流量所属的BD（设为BD 1）来封装eVxLAN，目的地址为BD 1的GIPo（设为GIPo 1），并通过哈希得到一个FTAG（设为FTAG 1）来转发目的地址为GIPo 1的流量。FTAG 1的根Spine（设为Spine 1）收到目的地址为GIPo 1的流量后，查找GIPo 1在FTAG 1上的转发表，复制流量并转发给其他的本地存在BD 1的Leaf。组播流量转发机制的具体示意见图4-36。

图4-35　ACI对单播流量的处理

图4-36　ACI对组播流量的处理

4.2.5　议ACI与SDN

ACI的技术实现就分析到这里了。概括地说，ACI就是北向ANP+南向OpFlex，ACI的SDN完全是围绕着应用策略来做。“以应用为中心”，说白了就是一个向上开放API、向下能够自动部署的Policy Profile。其实，由于数据中心向来有着自动化管理的诉求，因此绝大多数数据中心网络厂商在其解决方案中都可以提供Policy的编程能力，只不过一般都是针对一个EPG的，并没有向ACI提供ANP这种端到端的Policy Graph。端到端固然吸引人，但是这也必将导致Policy的制定异常复杂。对于网络管理员来说，写好一个ANP不会是件容易的事，甚至开始接触的时候会比敲CLI更让人头疼。

响亮的口号总是让用户很受用的，但是醉翁之意不在酒，这实际上更像是Cisco通过“网络黑盒”提供SDN的一个幌子。不过，在数据中心的场景中，“网络黑盒”确实也不会是一个大问题，毕竟用户更关心的是虚拟机上的应用负载。从用户的角度来看，SDN的最大价值仅仅是为应用提供更灵活的连接。ACI的技术路线确实是打了SDN的擦边球，关于“ACI究竟属不属于SDN”的讨论不绝于耳。“以应用为中心”听起来很Client-Oriented，以此为掩护，Cisco也得以能够继续走封闭设备的路线。不过，Cisco一再强调APIC不仅仅是Super Network Management System，虽然多少有些欲盖弥彰。

从广义的角度来说，ACI仍然属于SDN，只不过其“软件定义”的能力不那么彻底罢了。Cisco当然不会情愿把设备的控制权开放给别人，技术路线的选择从来都是以商业目的为基础的，Cisco走ACI这条路线自然也无可厚非。当然，ACI在技术上有很多的创新，当初Insieme还为N9K的线卡设计了专用的芯片，这也导致ACI不仅不能兼容的厂家的设备，Cisco自家的其他Nexus系列的产品也没办法跑在ACI Fabric中。

自从发布的那一天开始，PK掉NSX就成了ACI最大的目标，不过以目前来看，NSX还是要领先ACI半个身位。技术层面的东西实际上是次要的，最终还是取决于各方在市场上的博弈。前有NSX后有白牌，ACI前方的路可谓是任重而道远。

4.3　Cisco VTS

Cisco这等体量的公司，是不会把所有筹码都压在一个产品上的。实际上，2015年的时候Cisco内部至少有4～5个团队在做数据中心的SDN产品。通过图4-37所示的Cisco数据中心SDN产品线，可以清晰地看到Cisco在数据中心网络产品线的设计与规划思路。

图4-37　Cisco数据中心SDN产品线

ACI被Cisco定位于一站式解决方案，即一个产品就能够把Underlay+Overlay都搞定。ACI在市场上发力较早，也是最为大家所熟知的产品。ACI的优势在于自动化和策略，但是它的问题在于整个解决方案是Cisco-Only的，不仅无法跨厂商，甚至只有N9K才能玩得转。

另外，Cisco还为ACI配套了一个数据中心可视化平台Tetration，Tetration具备感知数据中心每个Packet的能力，集成了Spark InfluxDB、Kafka等大数据工具。并结合私有的机器学习算法，实现了应用状态与行为的可视化。然后再将这些数据注入ACI，即可对应用策略进行动态调整，实现网络对应用的感知与适应。

随着EVPN的兴起，Cisco也开始在Nexus系列中集成EVPN的能力。EVPN的优势在于可扩展性和标准化，但是手动配置起来非常复杂，Cisco为此提供了另一个产品VTS（Virtual Topology System），提供对于Nexus全系列交换机以及ASR 9K上EVPN的控制与自动化配置。VTS是面向Overlay的产品，对Underlay没有独立的管理能力。

目前除了ACI和VTS以外，Cisco在数据中心还有NFM（Nexus Fabric Manager）和DCNM（Data Center Network Manager）两个产品。NFM面向的应用场景是Underlay+Overlay的自动化配置，相比ACI没有做策略的能力，相比VTS来说只能配N9K系列交换机上的EVPN，而且NFM所能够支撑的网络规模也较为受限。DCNM则主要面向Underlay的管理+配置，它配不了EVPN，不过目前也逐渐集成了一些Overlay的可视化功能。

VTS、NFM、DCNM三个产品的定位实际上有一定的重叠，都是通过集中式的Manager来自动化下发设备配置。不过，由于VTS还支持通过MPBGP、RESTCONF来跑控制协议，因此可以将其看作一个SDN方案，而将NFM和DCNM看作传统网管的升级。本节将对VTS进行介绍，对于NFM和DCNM，请读者自行查阅资料。

4.3.1　整体架构

图4-38是VTS的整体架构。控制器由两部分组成：Policy Plane通过RESTful API对接编排器和GUI，除了存放租户的业务数据以外，还负责维护VxLAN EVPN的Resource Pool（Global VNI、Local VLAN、Multicast Group）。Control Plane通过RESTCONF/CLI/MP-BGP等南向接口对设备进行管理、配置与控制。数据平面上，Cisco基于VPP（Vector Packet Processing）技术做了VTF（Virtual Topology Forwarder），用于实现软转发。物理交换机方面，Nexus从2K到9K的交换机VTS都能够支持，ASR 9000也可以被集成到方案中做Border Leaf/DCI。

VTS产品的最大特点就是VxLAN EVPN的auto-provisioning，就是根据租户提供的service information下发相应的CLI给底层的设备。这一思路听起来很土，但实际上要做好是非常难的，原因概括起来主要有如下几点：首先，配CLI不仅是个体力活儿，更是个经验活儿，CLI本身是为了人的可读性来设计的，换机器来干这个事儿很难做到足够灵活。其次，CLI Running Config数据库的状态过于复杂。在业务发生变更时，Running Config的数据库就需要在不同的状态间进行转换，CLI多了之后状态集合的规模将会变得十分巨大，每个转换的过程需要配哪些新的命令，需要去掉哪些旧的命令，都需要逐一地进行考虑，并在程序中进行模板化。另外，控制器和设备间Running Config数据库的状态必须要同步。CLI远程配下去了，哪些成了哪些没成，控制器都要能够记录下来，该回滚的时候需要进行回滚，该重配的时候就得重配。如果网管自己通过设备的console口“偷偷地”配了几条新的命令，控制器需要及时得知这一信息，并判断这几条命令是否合法。最后，不同厂商的设备CLI模型不同，适配不同厂家设备的CLI就相当于要实现不同的南向接口。

图4-38　VTS的整体架构

Tail-F是做auto-provisioning这件事的行家。Tail-F于2005年成立，一直致力于网络的编排与自动化，在IETF主导推动了NETCONF，用来系统地解决auto-provisioning实现中遇到的上述问题。NCS作为Tail-F的拳头产品，主要的应用场景就是各种SP业务的auto-provisioning，主要提供Cisco、Juniper设备CLI的自动化。NCS这种集中向下推配置的思路，十分符合Cisco对SDN技术路线的规划。Cisco于2014年年中以1.75亿收购了Tail-F，NCS也自然划归到了Cisco的名下。VTS的auto-provisioning就是基于Tail-F的NCS（Network Control System）来实现的，VTS控制平面的实现中，很多地方都会看到NCS的影子。

除了auto-provisioning以外，VTS可以外挂IOS-XRv来支持MP-BGP，由此具备了对Overlay路由的控制能力，这使得VTS能够被称为真正意义上的SDN产品。不过，VTS只是具备对Overlay网络的管控能力，对Underlay的管理只能结合其他的产品来做，比如DCNM。

4.3.2　管理与控制平面设计

VTS是一个VxLAN EVPN-based的产品，其主要的功能就是对VxLAN EVPN进行配置，然后对Overlay的转发表进行控制。配置方面就不细说了，实际上就是把VxLAN EVPN的功能加到了NCS的软件架构中，并做了Web上的包装，协议上可以走SSH+CLI，也可以走Nexus自己的NX-API。控制方面，ACI是把RR做在了N9500里面。由于VTS不仅要支持N9K，还要兼容之前的N系列产品，因此提供了两种RR的模式：一种是Inline模式，通过Spine来反射路由；另一种是Global模式，通过运行额外的虚拟机运行IOS-XRv来作为Virtual RR。对于VTF来说，VTS是通过RESTCONF+YANG的方式来控制它的Overlay转发表。从整体的思路来说，就是VTS先根据业务逻辑生成VxLAN EVPN的CLI然后配置下去，然后通过MP-BGP+RESTCONF来学习、同步Overlay转发信息，该过程如图4-39所示。

图4-39　VTS的控制机制

下面通过图4-40来看一个具体的工作流程，以VTS与OpenStack对接为例。

1）租户在OpenStack中创建一个Tenant，在Tenant中创建两个Subnet。

2）VTS生成一个新的Tenant。

3）从资源池中分配可用的L2 VNI给两个Subnet。

4）VM实例启动，nova完成port plug。

5）nova将VM与host的对应关系告诉VTS，VTS更新（VM，host，ToR，VLAN，VNI）的表项。

6）VTS向ToR下发VxLAN EVPN的配置实现auto-provisionning，此时可开始L2通信。

7）host中的L2 Agent向VTS请求为该VM分配的Local VLAN，并对vSwitch进行配置。

8）租户在Tenant中创建一个Router，将两个Subnet连接起来。

9）VTS对IRB进行配置，包括L3 VNI和网关任播地址等，此时可开始L3通信。

图4-40　VTS对接OpenStack的工作流程

对于上述工作流程，还需要做如下说明。

1）（VM，Host，ToR，VLAN，VNI）的映射关系对于VTS来说至关重要，其中：Host和ToR间的对应关系是通过LLDP发现的；VM和Host间的信息是nova告诉给VTS的；VLAN是VTS为不同的Subnet动态分配的，这个VLAN只在ToR/vSwitch本地有效，ToR侧的VLAN是由VTS配置的，vSwitch侧的VLAN需要和ToR侧的VLAN配置一致，不过VTS没有对vSwitch配置的能力，因此需要由L2 Agent（针对VTS修改后的）向VTS请求，得到这一VLAN后在本地进行配置；VNI是VTS动态分配的（全局有效），包括用于Bridging的L2 VNI和用于Routing的L3 VNI，VTS只提供对对称IRB的支持。

2）VTF上的Overlay转发表是VTS通过RESTCONF下发的，而ToR/Border Leaf/DCI上的转发表是通过MPBGP学习到的，两类转发表在VTS上完成结构的转换，这一过程即相当于路由的重分布。如果RR使用了Global的模式，那么VTS直接充当RR反射转发信息。如果使用了Inline的模式，Overlay路由的反射则由Spine完成，此时VTS仍然需要通过MPBGP从Spine处学习EVPN路由，以完成RESTCONF/MPBGP间的路由重分布。

3）虽然OVS/Linux Bridge等vSwitch都具有VTEP的功能，但是在VTS的设计中，考虑到性能问题，VxLAN封装/解封装的功能都上提到了ToR上去完成，只有在使用VTF时才会直接在VTF上做VxLAN的封装/解封装。

4.3.3　数据平面设计

数据平面上，VTS可以管理Nexus从N2K到N9K的交换机和作为Border Leaf/DCI的ASR 9000。从理论上来讲，VTS这样向下推CLI的思路可以用于任何厂家的设备上，如果把不同厂家、或者同一厂家不同版本的CLI看作不同的南向接口，那么VTS就可以看作一个multi-vendor，multi-protocol的SDN解决方案，就像当年Tail-F的NCS一样，这也正是VTS对自身未来的产品定位。

1.VTF与VPP

软转发方面，Cisco基于VPP技术做了自己的VTF。如图4-41所示，VTF是一个工作在用户空间的packet forwarder，可实现很多L2和L3的datapath feature。VTF的部署上有两种：一种是vtf-as-vm，即将VTF包装进一个虚拟机接入到Hypervisor的vSwitch上，这个vSwitch将其他VM的流量标记VLAN并中继给VTF，然后由VTF完成对数据包的处理，包括ACL和VxLAN等。这种vtf-as-vm的部署方式，将vSwitch的大部分功能卸载到了VTF中去做，很像VEPA的hairpin。另一种部署方式是vtf-vhost，即VTF通过vhost-user直接将VM接入，从而省去了vSwitch的环节，提高了VM的IO效率。另外，由于VTF工作在用户空间，因此可以通过DPDK来连接物理网卡，以提高与外界进行IO的性能。

图4-41　VTS可通过用户空间的VTF在用户空间进行软转发

VPP是一个datapath技术，控制平面的功能还是需要有一个Agent来辅助完成，如图4-42所示。如果是RESTCONF/NETCONF的控制接口，可使用Honeycomb Agent来接受管理与配置；如果需要跑BGP和OSPF这类路由协议，可以起一个沙盒来跑Quagga/Bird，然后在通过VPP提供的low level API来注入转发表。

VPP（Vector Packet Processing）并不是一项新技术了，Cisco早在2002年就开始投入商用了。2016年2月，Linux基金会创建FD.io，旨在为NFV提供高性能的IO服务，Cisco将VPP捐了出来撑起了FD.io的技术框架。VPP中datapath上的最小功能模块被称为Node，用户可以开发自己的Node，然后通过NodeGraph将不同的Node串联起来，因此通过VPP的框架能够非常灵活地组织packet processing的pipeline。另外对于新的Node，VPP可以进行动态地插入，而不用中断datapath的转发。VPP的工作机制如图4-43所示。

图4-42　VPP支持多种控制机制

图4-43　VPP的工作机制

那么作为一种datapath技术，VPP的高效体现在哪里呢？除了支持vhost-user和DPDK以外，它还对iCache和dCache的读取顺序进行了优化，简单地说就是各个Node在处理数据包时，都会对一组数据包的相关header（Node所关心的）进行“批处理”（vector packet processing），而不是采用逐个数据包流过NodeGraph的做法（scalar packet processing），这样可以有效地减少内存和iCache/dCache间的数据移动。虽然从个体来看，一些数据包的处理时延可能会增加，但从整体上来看可以大大地提高IO的效率。上面解释得比较浅，有兴趣的读者可以查阅VPP的相关资料，以了解其具体的转发机制。

2.服务链机制的设计

服务链机制的设计方面，VTS支持两种方式，如图4-44所示。第一种是L2 Service Chain，在这种方式中，一个服务链中的各个SF间使用不同的L2 segment（VLAN ID、VNI），VTF只根据二层的信息转发数据包，如果服务链上需要进行路由，则必须由SF完成。第二种是L3 Service Chain，服务链SF只负责它自己本身的业务功能，路由和引流的功能放到了VTF上面来做。第一种方式对于SF的要求较高，路由的实现对于很多SF来说都是非常沉重的负担，可扩展性上很差，而且会占用很多segment；第二种方式把服务链实现的复杂性（包括流量识别、标记、引导、负载均衡、OAM等）都交给了VTF去做，从而把SF解放了出来，可扩展性上有很大的增强。

图4-44　VTS支持两种方式说实现服务链

4.4　Juniper Contrail

相比Cisco ACI和VMware NSX在数据中心SDN领域的高调竞争，Juniper的Contrail显得有些不愠不火。Contrail是Juniper收购来的，收购的时间点在VMware收购Nicira之后、Cisco收购Insieme之前。2012年中旬，VMware和Cisco爆发Nicira收购战，Juniper虽然只是此事的围观者，但是却明白SDN在数据中心落地的时间节点已经到了。于是，J开始筹划对Contrail System的收购。除了Cisco和VMware收购战带来的压力以外，从J家自身在数据中心的境遇来看，此桩收购也是顺理成章的。J的产品在业界被牢牢地定位于SP-Oriented，在数据中心的市场上一直没能获得突破，QFabric推出后也是叫好而不叫座。Contrail和Nicira一样，走的都是服务器端Overlay+SDN的路线，因此通过收购Contrail，J也能够绕开与Cisco、Arista在盒子上的正面竞争。从技术上来看，Contrail控制器的南向用了BGP和类BGP的XMPP，数据平面对的是vRouter而非vSwitch，这对于SP出身的Juniper可谓正中下怀。2012年11月，J家技术元老Kompella出任Contrail CTO。在技术上搞清了路数后，J于同年12月上旬推动Contrail System挂牌，在挂牌仅仅两天后就迅速地敲定了这笔收购，最终以1.76亿美元成交。

2013年，Contrail产品面世，同时Juniper还发布了开源版的OpenContrail。Contrail以其领先的SDN理念以及完整的产品设计架构，在业界造成了不小的轰动，被很多人称赞为“SDN的技术担当”。不过，和QFabric一样，Contrail的市场表现并没有完全匹配得上其精湛的技术，虽然份额上也有着还算不错的占比，但始终不及VMware NSX和Cisco ACI。除了宣传方面的原因，Contrail作为Overlay方案是要与VMware NSX正面交锋的，可是VMware在服务器端有着无可匹敌的市场地位，VMware NSX提供的Overlay可以天然地绑定VMware其他的计算和存储虚拟化产品，而J自己只有网络的虚拟化，无法提供完整的虚拟化解决方案，只能拼着vSphere一起来做。对于用户来说，自然希望由一个vendor来搞定所有环节，因此NSX显然更为用户所认可，尽管Contrail在网络方面的技术不落下风，甚至是更胜一筹。不过，随着开源技术的逐步成熟，产品的分解也正在被业界所接受与认可，Contrail/OpenContrail也借此在一些使用OpenStack的客户环境中找到了集成的突破口。

反观Cisco，ACI继续走着传统的路数，围绕着硬件的盒子来做解决方案，再加上Cisco在服务器端有自己的UCS刀片，对于一些不在乎成本且熟悉Cisco设备的用户来说自然是首选。因此，Contrail在战略上多倾向于与OpenStack进行集成对接，技术上缺少关键环节，就只能在夹缝中求生存了。

实际上，2013年，OpenContrail是J一个很好的生态切入点，当时OpenDaylight还远远未成气候，ONOS还没成立，而OpenContrail在github上已经有了成型的项目，官方一篇近100页的体系架构文档也把方方面面都说得很清楚了。但是后来OpenContrail没有在社区生态上投入足够的力量，因此开源这一块也没有能够做起来，而OpenDaylight和ONOS现在却做得风生水起。

Contrail和OpenContrail实现上是同源的，下面就来看一看Contrail，领略一下“SDN技术担当”的风采[1]。

[1] 为了把事情说得更清楚，这一节中参考了Juniper《MPLS in the SDN Era》一书中的图片，特此声明。

4.4.1　整体架构

Contrail主要分为数据和控制两个平面，上层的管理和编排由云管理平台作为统一的入口。数据平面上，vRouter部署在服务器的host kernel中完成IP/MPLS转发，网关路由器部署在数据中心边缘负责连接外部网络（Internet、VPN、DCI），服务节点部署在服务器或者网络边缘负责实现安全、负载均衡等增值功能。形态上，vRouter是软件实现的，网关路由器一般为硬件大盒子，服务节点可以为硬件也可以为VNF。控制平面分为三类模块：Configuration模块负责接受北向的业务配置信息，将其转化为网络底层信息然后分发给Control。Control模块通过南向接口与数据平面上的各种设备进行交互，控制转发或者部署网络策略。Analytics模块负责搜集Contrail系统中各个模块（包括vRouter和Control）的运行数据，以实施网络监测与分析。为保证高可用，控制平面的三类模块在部署上都是Active-Active的，组合起来构成了一个物理上分布、逻辑上集中的控制器。Contrail的整体架构如图4-45所示。

图4-45　Contrail的整体架构

接口方面，控制平面的Configuration和Analytics模块通过RESTful API向云管理平台提供配置和监测的接口。Control模块通过XMPP控制vRouter，通过BGP/NETCONF与网关节点交互，也可以通过NETCONF来配置服务节点。关于BGP和NETCONF，这里不再讨论，下面主要对XMPP进行简单的介绍。

XMPP（Extensible Messaging and Presence Protocol，RFC 6120-6122）的原身是Jabber。Jabber是一种开源的即时通信协议，Jabber服务器可以维护客户端的在线信息和关系列表等，并在不同的客户端间传递通信消息，因此可以把Jabber的服务器理解为消息分发的中间件。在Jabber.com被Cisco收购后，IETF为了避免歧义将Jabber的核心提炼成了XMPP。XMPP继承了Jabber即时性强的特点，数据格式基于XML，可读性高且易于扩展，被广泛应用在了各种通信场景中。Contrail使用XMPP在控制器和vRouter之间交换网络信息，包括虚拟机、租户网络的参数以及租户路由表等。

传输租户的路由表是XMPP在Contrail中最为关键的任务。XMPP和BGP一样拥有极强的可扩展性，通过自定义XMPP的扩展，可以传输和BGP类似的路由语义。之所以没有直接用BGP，原因可能有两点：①XMPP已经在大型实时系统中得到了充分的验证，适合去支撑控制器向vRouter分发大规模的VM路由。②BGP本身的实现非常复杂，包括了各种各样的属性参数，这些参数在Contrail中是不太需要的，而一些Contrail所需要的非路由信息（如VM ID等），若使用BGP进行传输还需要进行额外的扩展。因此Contrail使用了XMPP在控制器和vRouter间传输路由，而对于网关路由器等就直接使用了标准的MP-BGP。

关于使用XMPP传输租户路由，IETF draft-ietf-l3vpn-end-system给出了详细的规范。这里不再深入介绍。下面给出一个简单的例子，图4-46中有一个租户Red（租户ID=1），该租户有两台虚拟机分别位于两个不同的计算节点上。其中具体的封装后面会详细分析，这里读者只需要直观地感受一下XMPP传路由的格式，后面在分析控制信令交互时不会再列出XMPP的具体消息格式。

图4-46　Contrail使用XMPP收集与分发Overlay路由

计算节点1中的vRouter 1通过XMPP向控制器订阅Red租户的路由：

<?xml version="1.0"?>
<iq type="set" from="vrouter_1"
to="network-control@contrailsystems.com/bgp-peer"
id="subscribe779">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<subscribe node="default-domain:mpls-in-the-sdn-era:Red:Red">
<options>
<instance-id>1</instance-id>
</options>
</subscribe>
</pubsub>
</iq>

下面两段代码给出了Red_1A发布路由所使用的XMPP消息的具体格式，Red_2A发布路由同理。计算节点1中的vRouter 1通过XMPP向控制器发布虚拟机Red_1A的路由：

<?xml version="1.0"?>
<iq type="set" from="vrouter_1"
to="network-control@contrailsystems.com/bgp-peer" id="pubsub20">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<publish node="1/1/default-domain:mpls-in-the-sdn-era: Red:Red/10.2.2.101">
<item>
<entry>
<nlri>
<af>1</af>
<safi>1</safi>
<address>10.2.2.101/32</address>
</nlri>
<next-hops>
<next-hop>
<af>1</af>
<address>10.0.10.11</address>
<label>35</label>
<tunnel-encapsulation-list>
<tunnel-encapsulation>gre</tunnel-encapsulation>
<tunnel-encapsulation>udp</tunnel-encapsulation>
</tunnel-encapsulation-list>
</next-hop>
</next-hops>
<virtual-network>default-domain:mpls-in-the-sdn-era:Red
</virtual-network>
<sequence-number>0</sequence-number>
<local-preference>100</local-preference>
</entry>
</item>
</publish>
</pubsub>
</iq>
<iq type="set" from="vrouter_1"
to="network-control@contrailsystems.com/bgp-peer"
id="collection20">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<collection node="default-domain:mpls-in-the-sdn-era: Red:Red">
<associate
node="1/1/default-domain:mpls-in-the-sdn-era:Red:Red/10.2.2.101" />
</collection>
</pubsub>
</iq>

控制器通过XMPP向vRouter 2更新虚拟机Red_1A的路由，可以看到Contrail的控制器即相当于路由反射器，把类似于EVPN Type 2和Type 5的路由信息封装到3XMPP消息中。

<?xml version="1.0"?>
<message from="network-control@contrailsystems.com"
to="vrouter_2/bgp-peer">
<event xmlns="http://jabber.org/protocol/pubsub">
<items node="1/1/default-domain:mpls-in-the-sdn-era:Red:Red">
<item id="10.2.2.101/32">
<entry>
<nlri>
<af>1</af>
<safi>1</safi>
<address>10.2.2.101/32</address>
</nlri>
<next-hops>
<next-hop>
<af>1</af>
<address>10.0.10.11</address>
<label>35</label>
<tunnel-encapsulation-list>
<tunnel-encapsulation>gre</tunnel-encapsulation>
<tunnel-encapsulation>udp</tunnel-encapsulation>
</tunnel-encapsulation-list>
</next-hop>
</next-hops>
<virtual-network>default-domain:mpls-in-the-sdn-era:Red
</virtual-network>
<sequence-number>0</sequence-number>
<local-preference>100</local-preference>
</entry>
</item>
</items>
</event>
</messages>
</iq>

4.4.2　管理与控制平面设计

Contrail的管理/控制平面包括Configuration、Control和Analytics三个模块，这些模块分别完成不同的功能。下面来具体看看每个模块的设计。

1.模块设计

Configuration模块接收业务的网络配置与策略，然后在本地将业务描述转化为底层的网络描述，并分发给相应的Control模块。Configuration可以看作Contrail控制器中的“编译器”。Contrail最一开始就提出了“SDN作为编译器”（SDN as a Compiler）的设计思想，Configuration模块即是Compiler的核心。如图4-47所示，Configuration模块包含5个组件：RESTful API Server完成业务API的收发；Message Bus是模块中各个组件的消息总线；Schema Transformer完成业务描述和网络描述间的转换；IF-MAP Server通过IF-MAP协议完成网络描述向对应的Control模块的发布；Control模块对配置和策略的订阅，ZooKeeper+Cassandra构建分布式数据库集群以实现高可用。

图4-47　Contrail Configuration Node的设计

如图4-48所示，Control模块通过IF-MAP Client接受Configuration模块分发的网络描述，然后通过不同的南向协议与数据平面不同的设备进行交互。与vRouter的通信通过XMPP Server完成，可以接收vRouter对虚拟机、租户、租户路由信息的订阅，并作为中间件向其他vRouter进行相关信息的发布。与网关路由器间的通信通过NETCONF Client和BGP Peer来完成。NETCONF负责对网关路由器进行配置，对服务节点的配置也可以通过NETCONF来实现。MP-BGP作为东西向协议在Control模块和网关路由器间同步路由信息，Control将XMPP收上来的租户路由转化为MP-BGP有选择地发送给网关路由器，网关路由器将Internet、VPN、DCI路由通过MP-BGP传给Control模块。MP-BGP还用来在多个Control模块间进行路由同步，同时数据平面的设备会与2个或2个以上的控制模块相连，以实现Control模块的高可用，这相当于在多个BGP反射器间做冗余备份。Proxy的作用是在XMPP和其他协议间进行转换，以对接其他的控制逻辑。

Analytics模块监测数据平面设备与其他控制器模块的运行数据与信息：与vRouter、Configuration模块、Control模块间通过Sandesh协议来获取其日志和事件，与网关路由器和服务节点可通过NetFlow和SNMP等传统的协议来进行管理与监控。Analytics模块将这些数据与信息完整地存在数据库中，一些网络分析类的应用可以通过RESTful API来有选择地获取这些数据。如图4-49所示，Analytics模块包含6个组件：RESTful API Server接受应用对数据的请求和响应；Message Bus是模块中各个组件的消息总线；Collector负责收集设备与控制器模块的运行数据与信息；Query Engine一个简单的MapReduce引擎，为网络状态的大数据分析提供基础；Rules Engine按照一些网络监测的策略，由特定事件触发状态的自动收集。ZooKeeper+Cassandra构建分布式数据库集群以实现高可用。

图4-48　Contrail Controller Node的设计

图4-49　Contrail Analytics Node的设计

2.Overlay的控制逻辑

上面简单介绍了Contrail控制器3个模块的设计。下面来详细地介绍一下Control模块中的Overlay控制逻辑。

图4-50是虚拟机上线时的控制信令交互。虚拟机plug到vRouter后，vRouter通过XMPP向控制器请求该虚拟机的信息，控制器上已经通过Configuration模块存下了虚拟机的信息，包括对应的虚拟网卡vNIC的ID、MAC地址、IP地址、所在租户的ID等，并通过XMPP将该信息回复给vRouter。vRouter将根据该信息在本地回复虚拟机的DHCP，完成虚拟机的网络初始化。

图4-50　虚拟机上线时的控制信令交互

前面的图4-46是转发使用L3 mode时，vRouter通过控制器同步远端虚拟机的路由信息时控制信令交互过程。vRouter发现本地新出现的虚拟机后会向控制器发布其虚拟机的租户路由，然后控制器会向其他vRouter发布该虚拟机的租户路由。若该虚拟机属于一个新的租户，vRouter还会向控制器订阅该租户其他虚拟机的路由信息。可以看到该过程中XMPP交互的内容与传统的BGP/MPLS L3 VPN基本一致，控制器可以看作是租户路由的反射器。

图4-51是转发使用L3 mode时，控制器将网关路由器（DC-GW）上的外部路由（包括Internet、VPN、DCI）和vRouter上的租户路由进行相互注入时控制信令的交互过程。控制器作为XMPP和MP-BGP格式转换的网关，和DC-GW间通过MP-BGP进行路由同步。数据平面可采用MPLSoGRE或者MPLSoUDP。

图4-52是转发使用L2_L3 mode时，vRouter通过控制器同步远端虚拟机的MAC路由时控制信令交互过程。vRouter发现本地新出现的虚拟机后会向控制器发布其虚拟机的租户路由，然后控制器会向其他vRouter发布该虚拟机的租户路由。若该虚拟机属于一个新的租户，vRouter还会向控制器订阅该租户其他虚拟机的路由信息。可以看到该过程中XMPP交互的内容与EVPN Type 2基本一致，控制器可以看作租户路由的反射器。该过程与L3 mode中使用MPLSoverX封装时有一点不同，控制器会推送VNI作为数据平面中Segment的标识，而在L3 mode中控制器推送的是MPLS Label作为数据面中目的VM的标识。

图4-51　L3 mode下控制器完成内部租户路由与外部路由的重分布

图4-52　L2_L3 mode下控制器同步远端虚拟机的MAC

图4-53是转发使用L2_L3 mode时，控制器将网关路由器（DC-GW）上的裸机路由和vRouter上的租户路由进行相互注入时控制信令的交互过程。控制器和vRouter间的XMPP交互与图4-52一致，控制器作为XMPP和MP-BGP格式转换的网关，和DC-GW间通过MP-BGP进行路由同步。这一过程需要同时发布BGP/MPLS L3 VPN和EVPN Type 2的路由信息，BGP/MPLS L3 VPN路由用于跨子网的通信，EVPN Type 2用于子网内的通信。

图4-53　L2_L3 mode下控制器完成内部租户路由与外部路由的重分布

图4-54是转发使用L2_L3 mode时，控制器上的租户MAC路由和L2 ToR下的MAC表，通过ToR Service Node（TSN）进行同步。控制器和TSN间通过XMPP交互，L2 ToR和TSN间通过OVSDB RPC交互，TSN可看作XMPP和OVSDB格式转换的网关。由于L2 ToR只能支持VxLAN封装，因此vRouter和VxLAN下虚拟机和逻辑的通信数据平面只能通过VxLAN来进行传输。对于ToR下裸机发送的ARP/DHCP等广播信息，ToR将作为裸机的广播代理对数据包进行复制和转发。

图4-54　L2_L3 mode下控制器通过TSN控制硬件VTEP

4.4.3　数据平面设计

vRouter是Contrail中最重要的数据平面设备，分为vRouter Agent和vRouter Forwa-rding Plane两个部分，如图4-55所示。vRouter Agent运行在用户空间，是控制器在设备本地的XMPP、Sandesh代理，并且实现了ARP/DHCP/DNS。vRouter Forwarding Plane运行在内核空间，负责数据包的高速转发。vRouter支持MAC/IP/MPLS的VRF FIB，支持地址的Overlap。

Contrail是一种Overlay技术，支持多种数据平面的隧道封装，包括MPLSoverGRE、MPLSoverUDP、MPLSoverMPLS、VxLAN等。鉴于Juniper在SP强大的技术背景，而且vRouter原生地提供MPLS的转发，MPLS肯定是Contrail推荐的首选封装。Juniper其实最希望推动的是MPLSoverMPLS的发展，即在Underlay中也使用MPLS进行转发。但是MPLS Underlay在数据中心的推动有着很大的困难：首先，大多数数据中心交换机是不支持MPLS的；其次，数据中心的运维人员对MPLS并不了解；最后，数据中心对MPLS流量工程也没有明确的需求。因此，常用的是MPLSoverGRE和MPLSoverUDP，而MPLSoverMPLS并没有流行起来，虽然现在SR也在试图推向数据中心，但是这个事情未来的前景也并不明朗。

图4-55　Contrail vRouter示意图

在MPLSoverGRE和MPLSoverUDP中，GRE/UDP负责在Underlay上传输到远端的vRouter上，MPLS在vRouter本地唯一标识着虚拟机，vRouter在剥掉外层GRE/UDP封装后根据MPLS转发给本地的虚拟机。MPLSoverGRE和MPLSoverUDP中的内部既可以支持L2流量也可以支持L3流量。在VxLAN中，UDP负责在Underlay上传输到远端的vRouter上，vRouter在剥掉外层VxLAN封装后根据内层的MAC地址转发给本地的虚拟机，VxLAN的内部只用于承载L2流量。计算节点间的连接，以及计算节点和网关路由器的连接，既可以使用MPLSoverGRE和MPLSoverUDP也可以使用VxLAN，取决于流量的模式（L3/L2_L3）。而计算节点和L2 ToR间的连接就只能通过VxLAN了。

图4-56给出了vRouter的Overlay/Underlay FIB和Underlay路径上的MPLSoverGRE封装。

图4-56　vRouter对数据包的处理

4.4.4　转发过程分析

1.L2和L3流量的处理

Contrail的转发分为两种模式L3 mode和L2_L3 mode。L3 mode是完全基于虚拟机的32位IP地址进行转发，无论是跨子网的Inter-Subnet流量还是同一子网内的Intra-Subnet流量，L3 mode的转发采用MPLSoverGRE/MPLSoverUDP的封装。而在L2_L3 mode中，同一子网内的Intra-Subnet流量是基于MAC进行转发，采用VxLAN封装，而跨子网的Inter-Subnet流量是基于虚拟机的32位IP地址进行转发，采用MPLSoverGRE/MPLSoverUDP的封装。

在L3 mode中，vRouter对于Inter-Subnet流量采用32位IP地址转发自然是可以理解的，而对于同一子网内的Intra-Sunbet流量，vRouter则需要进行ARP代理，当虚拟机向同一子网其他虚拟机发送ARP Request时，vRouter会截获此请求并代理回复ARP Reply发布vRouter网关的任播MAC地址00：00：5e：00：01：00，因此当通信开始后，vRouter即可截获所有的通信流量，然后根据目的虚拟机的32位IP地址进行路由，然后将流量的IP报文封装在MPLSoverGRE/MPLSoverUDP中，远端的vRouter收到后解掉GRE/UDP的封装，根据MPLS进行标签路由，转发给虚拟机前重新为流量添加二层包头，其源地址为vRouter网关的任播MAC地址00：00：5e：00：01：00，目的地址为目的虚拟机的MAC地址。因此，在L3 mode中，虚拟机的ARP表中，和自身处于同一网段所有IP地址都被解析为了同一个MAC地址00：00：5e：00：01：00，这也是将L2转发转化为L3转发的一般实现方法。

在L2_L3 mode中，对于Intra-Subnet流量和Inter-Subnet流量，两者的处理办法是有所不同的。对于Intra-Subnet流量，vRouter表现为交换机，vRouter不会代理回复ARP Reply，而是通过头端复制的方式在该子网进行泛洪（这一点的实现其实不是很好，即使vRouter不去代理回复ARP Request，也可以将该ARP Request定向封装单播到目的虚拟机所在的远端vRouter，来避免泛洪造成的开销），但是与传统二层交换机的区别是vRouter会通过XMPP学习MAC路由而非自学习，通信开始后vRouter会根据学习到的租户MAC路由表来进行二层转发，数据平面的封装采用VxLAN。对于Inter-Subnet流量，L2_L3 mode的处理方式与L3 mode的处理相同，都是基于32位IP地址进行转发，由于VxLAN内部只能支持L2，因此L2_L3 mode下Inter-Subnet流量也只能采用MPLSoverGRE或者MPLSoverUDP的封装。

2.服务链流量的处理

对于服务链，Contrail从最开始的版本就开始提供了支持，主要是面向运营商接入网和移动核心网的场景设计的，当然数据中心内部的服务链也是自然可以提供支持的。Contrail使用BGP策略来引导服务链流量（draft-fm-bess-service-chaining），实现方式有两种：

1）在服务链路径上的每一跳都使用不同的SFC VRF，如果服务链上有N个节点，那么它就需要使用N-1个VRF，这种方式称为Transit VN Model。Transit VN Model中，控制器向服务链各跳中的egress VRF下发BGP路由将流量送给该跳的ingress VRF，向各跳的ingress下发BGP路由将流量送给服务节点。

2）在服务链路径上只使用流量源所在的VRF和流量目的所在的VRF，这种方式称为Two VN Model。Two VN Model中vRouter通过两个VRF来识别SFC流量，然后通过修改BGP下一跳来引导流量。

图4-57中，租户Red中的VM Red_1A发向Internet间的通信需要依次经过服务节点VM SI_A和VM SI_B的处理，那么在Two VN Model方式中的转发流程为：Red_1A发出数据包到vRouter 1上，被识别为VRF Red——Red中的流量并从tapX端口送到SI_A的左臂，SI_A完成处理后从右臂送到vRouter 1的tapY端口，被识别为VRF Internet：service<SC_ID>_SI_A中的流量并标记label=37送给vRouter 2，vRouter 2匹配LFIB剥掉label并从tapZ端口送到SI_B的左臂，SI_B完成处理后从右臂送到vRouter 2的tapT端口，被识别为VRF Internet——Internet中的流量并标记label=90送给DC GW，之后DC GW再swap label送到相邻的ABSR上。对于Red_1B来说，它发向Internet间的通信需要依次经过SI_A和SI_B的处理，Red_1B发出数据包到vRouter 2上，被识别为VRF Red：Red中的流量并标记label=35送给vRouter 1，vRouter 1匹配LFIB剥掉label并从tapX端口送到SI_A的左臂，后面的处理就是一样的了，此处不再赘述。

实际上通过修改BGP下一跳的方式对于某些需要精细匹配的服务链场景来说是不够的，因为BGP路由只能匹配目的IP地址，因此需要Contrail需要通过XMPP来模拟BGP FlowSpec的语义，以实现传统服务链的PBR能够支持的流量识别粒度，实际上这也就趋同于OpenFlow中的N Tuple Match和Output Action了。

对Contrail的介绍就到这里了，其技术思想的精华可以概括为以下几点：数据平面使用vRouter而非vSwitch，从而为网络带来了很多特性支持；Contrail的控制器是分布式的，三类控制模块（Configuration、Control、Analytics）的功能切割与实现解耦为系统提供了完整、清晰的架构；控制平面使用了BGP和类BGP的XMPP，获得了良好的兼容性、可用性和扩展性；服务链的功能最开始设计时即被提出，并在各个版本不断进行完善。

Contrail的技术轮廓早在2013年就已经形成了，直至今天来看其设计理念和技术架构仍然是业界领先的，无愧于“SDN的技术担当”，希望在市场方面Contrail和OpenContrail未来能有更好的表现。

4.5　Nuage VCS

Nuage Network是2013年Alcatel-Lucent（现以被Nokia收购）成立的全资子公司，其SDN产品线统称为VSP（Virtualized Service Platform），包括数据中心虚拟化方案VCS（Virtualized Cloud Services）、SDWAN方案VNS（Virtualized Network Soervices）和安全方案VSS（Virtualized Services Security）。除了VSP以外，Nuage还提供了VSAP（Virtualized Service Assurance Platform），可以看作传统设备的增强型网管，以配套VSP产品使用。

图4-57　Contrail可通过Two VN Model来实现服务链

Nuage在2013年成立之初专注于数据中心SDN，和NSX以及Contrail类似，VCS提供的也是Overlay的方案。另外，VSS和VSAP可以配套VCS使用，提供业务开通+安全+运维的完整数据中心网络方案。

4.5.1　整体架构

VCS和VNS、VSS使用的是相同的VSP体系架构，如图4-58所示。VSD（Virtualized Service Director）是管理平面，负责虚拟化、安全、QoS等策略的管理以及网络数据的分析。VSC是控制平面，基于阿朗业务路由器的SR-OS平台进行开发，从VSD接受策略并据此向VRS进行控制。VRS是数据平面，基于OpenvSwitch做了一些增强，接受VSC的控制以完成流量的转发。

接口方面，VSD通过RESTful API接受云管理平台的编排，通过XMPP把策略分发给VSC。VSC和VRS间通过OpenFlow/OVSDB进行通信，由于VSC继承于SR-OS，因此VSC还提供了对BGP的良好支持，用于VSC间以及VSC和DC出口路由器间的状态同步。

图4-58　Nuage VCS体系架构

VSP的架构十分清晰，就是典型的SDN分层。Nuage的另一个平台VSAP和VSP有所不同，VSAP是基于阿朗业务路由器网管——5620 Service Aware Manager进行开发的，可以看作对VSC控制的Overlay网络及对于物理网络的一体化管理系统。VSAP通过IGP、BGP来获取物理网络的路由信息，通过SNMP来收集物理设备的状态。VSAP和VSC间也是通过SNMP进行通信，VSAP从VSC获取虚拟机和虚拟网络的信息，然后将虚拟网络与物理网络的信息进行整合，以提供P+V一体化运维的能力，如图4-59所示。

图4-59　VSAP对物理网络进行管理

4.5.2　管理平面设计

VSD是VSP架构中的管理平面，提供了一个策略与分析引擎，支持以虚拟机的方式进行集群部署。如图4-60所示，计算资源与网络资源的状态、租户的业务策略以及管理策略都存在VSD中，当网络出现异常时可以自动地进行告警。VSD还内置了Hadoop引擎，以支持对网络数据的深度挖掘和性能分析。当然，VSD只是负责对策略和虚拟网络的数据进行管理，对于VSC和物理网络的管理是由VSAP完成的。

图4-60　VSD的设计

4.5.3　控制平面设计

VSC是基于SR-OS来开发的，继承了SNMP、BGP、OSPF等传统网络协议，并补充了XMPP和OpenFlow/OVSDB，分别用于和VSD与VRS的交互，如图4-61所示。

图4-61　VSC的设计

控制的逻辑即为VRS监测到VM上线后，即通过OVSDB通知VSC，VSC再通过OpenFlow向其他的VRS反射该VM的转发流表，VSC没有使用Routing VM，而是直接使用了VRS上的流表实现分布式路由。VSC与HWVTEP间通过OVSDB来同步MAC地址。VSC与DC出口路由器间通过MP-BGP来进行路由同步，VSC会实现OpenFlow和MP-BGP间的转化。VSC间的冗余备份也可以通过MP-BGP来实现。

由于VRS支持OpenFlow/OVSDB，因此VSC可以方便地部署安全、QoS、NAT等策略，如Drop、镜像、限速、端口映射等。服务链的策略通过OpenFlow的引流也很容易实现。为了实现一些有状态的策略，需要对OpenFlow和VRS进行相应的扩展。

安全方面，Nuage基于VSP的架构提供了VSS，VSS可以作为VCS的增值选项。VSS主要有三点功能：第一是防护，可以基于上一段中描述的方式实现网络级或者虚拟机级的细粒度隔离；第二是探测，通过扩展VRS和VSD的功能以实现流量的数据分析，以及安全策略的命中情况；第三是反馈，可提供一些探测后的安全策略告警，通过开放API可以实现应用对网络的闭环安全控制。

4.5.4　数据平面设计

数据平面上，Nuage提供VRS作为虚拟交换机来完成Overlay转发，同时也提供VSG作为HWVTEP。VRS是基于OpenvSwitch进行增强的，通过在单级网桥上构建流水线来实现转发、安全、QoS、NAT等策略，封装上支持VxLAN和MPLSoverGRE，VxLAN用于VRS-VRS或者VRS-HWVTEP间的封装，MPLSoverGRE则可用于VRS-DC出口路由器间的封装。ARP和DHCP的处理也是在VRS本地完成的，以抑制广播。

虽然Nuage在数据中心主推的是基于VRS的Overlay方案，不过VSG也可以作为硬件配套的一种选择。VSG是基于SR-OS的硬件交换机，可以连接VSC作为HWVTEP接入裸机，也可以工作在controller less状态跑OSPF、BGP/MP-BGP等路由协议，VSAP可以通过SNMP对VSG进行管理。VRS的设计如图4-62所示。

图4-62　VRS的设计

虽然说Nuage名义上是一家SDN创业公司，但创立之初背后就有IP大佬坐镇，实力自然不容小觑。不过随着SDWAN的火热，Nuage将重心从VCS转向了VNS，当然使用的也还是类似的Overlay架构。

4.6　Arista EOS与CloudVison

相比Cisco和Juniper，Arista算是网络圈子的“新兵”，不过近几年Arista在数据中心交换机的市场份额上升得非常快，在Gartener近三年的Magic Quadrant中与Cisco并列第一象限。高速上涨的背后是Arista对于数据中心的专注，其交换机的两点核心竞争力——低延时和开放性，成为Arista在云数据中心网络领域脱颖而出的基础。

先来看低延时。某些特定领域的应用，如超算和高频交易，对延时的要求十分苛刻。对于低延时应用来说，InfiniteBand在技术上是最好的网络选择，不过以太网正在靠着简单性、低成本和40GE/100GE端口不断地进行着渗透，低延时以太网+RoCE正在获得低延时网络的市场份额。低延时对以太网要求的不仅是带宽的Non Over-Subscribe，还需综合考虑设备的转发架构、端口的buffer深度、链路的负载均衡、精细的拥塞可视化以及完整的网络数据审计，来构成一整套的低延时解决方案。Arista凭借着直通式转发、大buffer、ECMP+M-LAG以及其LANZ和DANZ技术，在低延时以太网市场上形成了独特的竞争优势，在金融圈子内拥有大量的客户。随着大数据和其他分布式计算应用的大规模爆发，低延时的需求被引入了云计算数据中心，Arista也得以借这股东风找到了新的市场爆发点。

再来谈开放性。Arista做设备秉承的理念是“Merchant Silicon+Open Protocol>>Vendors Proprietary Fabrics”，即通过通用的芯片和标准的协议来组盒子，通过开放性和低成本来抗衡Cisco。“通用的芯片”意味着Arista不会自己玩芯片，而是会选择Broadcom等专业芯片公司的产品，以降低高昂的芯片研发成本，如果对ASIC有特殊需要（如LANZ），也是找芯片公司进行定制。“标准的协议”意味着Arista的组网通常依赖着的都是标准的L2/L3协议，如STP、OSPF、BGP、ECMP等，少数Arista私有的协议，如MLAG和VARP，也是参照开放协议进行修改的。因此，和Cisco的FabricPath以及Juniper的QFabric不同，Arista在2010年前后面对着即将爆发的数据中心虚拟化时，并没有选择自己搞一套大二层的专用Fabric，而是联合VMware和Microsoft推出了VxLAN和NvGRE，希望把二层Overlay在现有设备上。搭上了VMware对于VxLAN生态建设的便车，Arista又一次在网络虚拟化领域占据了有利的位置。

当然，光占据市场位置是没有用的，把技术的内功修炼到位才是产品能够持续卖出去的根本。如果自己不做芯片和协议，技术上想要出彩，就要在软件平台上花心思。Arista显然很早就搞清楚了这个道理，于是在其交换机软件平台EOS上下了很大的工夫。EOS是完全基于开源Linux来做的，相比于WindRiver提供的专用网络操作系统，其开放性和可维护性有着本质上的提升。恰逢其时，EOS的技术路线正好契合了SDN所提倡“把盒子开放出来”的理念，Arista作为传统的设备厂商，得以在SDN的大时代中站稳了脚跟。

自家数据中心的盒子年增长率惊人，Arista就没有动vSwitch的心思，虽然自己也在做SDN控制器但也没有大张旗鼓地去宣传，而是主力在推动自己的Underlay SDN。这里注意，Underlay SDN是为了补充Overlay SDN的，这与Cisco ACI“以硬件为中心”的SDN理念有着本质的不同，虽然Arista自己也支持纯硬件的方案，但是并没有在市场上与VMware NSX直接竞争。Arista与VMware两家一直处于深度合作的状态，并于2014年签订了战略合作关系，图4-63是两家合作的路标。

图4-63　Arista与VMware的合作路标

Arista于2015年推出了CloudVison。CloudVision在架构中所处的是控制器的位置，但它却不是一个正统意义上的SDN控制器，而更像是一个交换机管理的统一入口。由于Arista EOS的可编程能力开放得已经很全面了，因此直接基于EOS做一个Network Wide Dashboard把交换机的状态数据都同步上来，再增加一些对数据的整合与分析，去不去Define转发看实际需要了。Arista对于SDN的理解更倾向于是“Software Driven Networking”，即“软件驱动网络”，一词之差，里面却包含了很多深意。

4.6.1　整体架构

Arista所有型号的转发设备都是基于EOS软件平台的，可以支持传统的L2/L3转发，也可以支持通过OpenFlow来控制转发。EOS是完全基于开源Linux的，因此用户还可以将一些定制化的功能集成到EOS中。EOS北向开放了诸多的接口，如native SDK、eAPI、XMPP、CLI、SNMP、OpenFlow/OVSDB、Python、Puppet等，用户可以调用这些接口直接对交换机进行控制与管理。CloudVision是全网的统一管理面板与控制入口，其核心是基于EOS进行增强扩展的。CloudVison在南向上提供XMPP、eAPI、gRPC等，将底层交换机EOS上的信息同步上来并整理成全局视图，还提供了一些数据分析与自动化控制的功能。CloudVision北向上支持OVSDB、RESTful API和EOS CLI，第三方的控制器（如VMware NSX）或者编排器（如OpenStack）可以通过CloudVison的北向接口与其做单点对接，CloudVsion会作为代理，控制底层所有的物理交换机。这样对于控制器或者编排器来说，CloudVison就是一个Network Wide Enhanced EOS，而不用再和所有的交换机进行互联。图4-64所示即为Arista基于EOS/CloudVison的SDN架构。

图4-64　Arista基于EOS/CloudVison的SDN架构

1.EOS

EOS是Arista最重要的技术，交换机和控制器上的软件都是基于EOS进行构建的。在介绍管控平面和数据平面之前，我们有必要先来介绍一下EOS的设计架构。EOS（Extensible Operating System）是一个完全基于开源Linux的网络操作系统，完全基于开源Linux的意思就是，用户可以像运维服务器一样来运维交换机，Linux诸多的开源工具可以直接集成在EOS中，Bash和Shell也可以用来简化运维，为了实现隔离，虚拟机和容器也可以很容易地就塞到EOS中。EOS作为网络操作系统，上面跑着数百个程序，EOS使用ProcMgr来实现对这些程序的监测与管理，使用SysDB作为中间件来协调程序间的通信，如图4-65所示。

图4-65　EOS以SysDB为核心

SysDB是一种in-memory数据库，能够维护所有程序的原始状态，如VLAN、VxLAN、MAC表、路由表、端口、LED等，以及各种计数器。SysDB作为中间件，支持在程序间对这些状态进行“发布–订阅”，从而可以避免由于引入IPC机制所带来的复杂度和耦合性，这使得EOS获得了很强的可用性以及可扩展性。为实现系统级的高可用，SysDB间需要进行状态的同步，这不仅表现在设备的双引擎之间，还表现在多台交换机间，如M-LAG就依赖交换机间的SysDB同步来支撑跨设备的链路聚合。

对于CloudVison来说，它作为全网的统一面板需要同步所有交换机EOS中的状态信息来维护全网状态的一致性，这个事情的难度是非常大的，对数据库的设计有着近于苛刻的要求。SysDB虽然可以全面地记录并发布状态，但是它的缺点在于没有持久化、不支持事务性、数据查询的支持也不好，而且数据背后的网络逻辑对于SysDB来说是完全透明的。因此Arista以SysDB为核心，重新包装了一层NetDB在外面，如图4-66所示。NetDB有以下两个特点：①同步能力更强，支持事务；②数据量更大，支持中央数据库。Arista还专门设计了Net Table以增强路由的收敛速度和路由表容量，为了支持某些Telemetry数据的实时同步，EOS还增加了对gRPC和ProtoBuffer的支持。

图4-66　CloudVison使用NetDB进行增强

开放性上，除了不涉及芯片以外，EOS几乎在网络的各个层面都进行了开放：交换机操作系统是开源的，可以开放集成Linux Shell和开源工具，可以嵌入VM和Container。数据平面上的FIB可以通过OpenFlow来直接控制，而DirectFlow与OpenFlow类似，能够在兼容现有L2/L3协议栈前提下提供flow-based programming，DirectFlow可以通过CLI或者eAPI来进行配置。EOS SDK是开放的，可以做in-box的协议或者应用开发；支持Python/Go和Puppet/Chef/Ansible以简化运维。EOS还提供了eAPI，可以将EOS CLI封装在JSON-RPC里给CloudVison或者第三方控制器、编排器或者应用来调用。协议栈的部分实现甚至也可以开放给特定的客户。

4.6.2　管理与控制平面设计

CloudVison在设计之初，只是通过XMPP把交换机EOS上的状态数据同步到一个集中的全局数据库中，然后通过Portal把全局视图提供出来。这实际上可以看作一个偏向于网管的入口，除了一些ZTP功能用于配置自动化以外，并没有过多的控制逻辑，因此CloudVison最初的定位并不是作为SDN Controller，而是Network Wide Enhanced EOS。随着这几年的发展，CloudVison逐渐增加了对虚拟网络进行直接控制的能力和对全局数据的监测分析能力，因此CloudVison现在可以看作Arista的SDN控制器。

CloudVison分为CloudVison Portal（CVP）和CloudVison eXchange（CVX）两个部分，CVP和CVX可以采用分布式部署，各自可以支持3个节点的集群。其中，CVP是一个Web平台，是网络的管理入口，负责提供网络的配置以及全局数据的可视化，其主要功能包括以下几点。

·用户安全，支持RBAC。

·基于JSON的RESTful API。

·设备的镜像、快照、智能升级。

·设备自动上线、管理与配置、配置回滚。

·网络的实时状态与历史状态、大数据分析。

CVX是增强的EOS环境，它相当于网络中的控制器，负责业务到网络下行的自动化控制，其主要功能包括以下几点：

·提供EOS的CLI、API以及基于JSON-RPC的eAPI。

·OVSDB接口协议。

·物理网络的拓扑发现。

·VxLAN控制逻辑（VCS，VxLAN Control Service，基于Arista的私有协议）。

·Vmware NSX与OpenStack，对VLAN和VxLAN两种组网模式提供支持。

·宏分段（Macro-Segmentation Services），实现虚拟机和裸机间东西向流量的安全。

Arista一直主打的是Underlay Fabric上的SDN，这种SDN应该理解为软件驱动网络，主要功能包括自动化和遥测两大类。自动化即为对Fabric进行自动的维护，主要包括：配置、回滚与智能升级等。遥测即为网络的可视化，主要包括数据流量分析、时延分析、网络追踪等，这些数据可以通过gRPC实时地推送给CloudVison进行呈现，以及后续的分析与处理。表4-2是对Arista支持的一些遥测功能做一个简单的整理。

表4-2　Arista支持的一些遥测功能

Arista对待Overlay SDN的策略比较复杂，图4-67是Arista对于VxLAN控制平面设计思路的演化。早期Overlay SDN控制器并不成熟，再加上Arista是标准VxLAN的主要推动者，因此Arista当时就实现了“组播+自学习”的基于硬件的VTEP。CloudVison虽然有能力同步VxLAN的转发表，但只是并没有参与到整个的转发控制中。随着NSX、Neutron的发展，Arista第二阶段的思路是CloudVison通过OVSDB与第三方控制器、编排器进行单点对接，然后作为代理来集中地配置交换机。对于VMware NSX来说，大部分的转发逻辑都跑在了Hypervisor上，CloudVison主要负责作为代理控制Arista的VxLAN L2GW上的转发以及为NSX提供Underlay Fabric的可管理性和可视性。对于Neutron来说，CloudVison可以做的事情要多一些，它有自己的ML2 Driver和Service Plugin，接受Neutron的编排信息后向交换机分发VxLAN的转发逻辑，能够完整地支持HWVTEP以及L3 GW。随着EVPN标准的成熟与发展，Arista也像其他厂商一样接受了基于硬件的分布式VxLAN控制方案，交换机上跑EVPN学习自行VxLAN转发表，CloudVison负责通过MP-BGP对接出口的路由器来同步路由表，以打通DC内部与DC外部。

图4-67　Arista对待Overlay的策略演进

服务链方面，由于Arista自己不做虚拟交换机，因此只能从物理组网角度入手。考虑到VMware提出的微分段（Micro-Segment）用于保证虚拟机东西向流量的安全，因此Arista提出了宏分段（Macro-Segment）的概念，将东西向流量的安全引入到虚拟机和物理机间通信的场景，如同一子网内APP虚拟机和DB裸机之间的通信。CloudVison通过与Firewall Controller交互，得到相关的安全策略，包括流量的特征和目标防火墙的位置，然后通过OpenFlow或者其他控制协议向流量源所在的Leaf交换机下发策略，通过Port+SRC_MAC+VLAN的方式识别出相关流量然后送到目标防火墙。防火墙处理后将合法流量返回，防火墙所在的交换机再根据DST_MAC+VLAN将流量发往目的地。

4.6.3　数据平面设计

从EOS的角度来看，Arista交换机的软件平台很有特色，EOS确实在开放性上为其他厂商做出了表率。至于对盒子的整体比较，这个事情见仁见智，就不做比较了。

鉴于盒子在数据中心仍然有着不错的前景，再加上和VMware不错的关系，目前没有听到Arista有打入服务器内部做vSwitch的计划。除了VMware以外，Arista还在2015年和HP进行了合作，甚至有段时间还传闻EOS要移植到HP的盒子中，不过此事似乎也没有了后话。不过，Arista已经提出了EOS+的解决方案，并提供了VM-based的vEOS和Container-based的cEOS，如果未来能够放开盒子一体化的约束，Arista毫无疑问会是白盒圈子的翘楚。

4.7　HUAWEI AC-DCN

看完了国外大厂的SDDCN产品，本节来看华为的相关产品。华为对SDN方向上的探索比较多元化，下面从底层向上来看，梳理一下华为在SDN领域的产业链——芯片方面，华为推出了以太网络处理器ENP（Ethernet Network Processor）来支持Hybrid OpenFlow。交换机软件方面，华为基于VRP 8+Linux Container对多种南向协议接口提供支持。交换机硬件方面，敏捷交换机S12700主要面向园区，CE（Cloud Engine）系列主要面向云数据中心。南向接口方面提出了POF（Protocol Oblivious Forwarding，协议无感知转发），以支持数据平面的可编程。控制器层面方面主要分为SNC（Smart Network Controller）和AC（Agile Controller）两个系列，其中经过3年左右的演化目前AC系列成为了主流，目前已经发布到了3.0版本。编排方面，华为有自己的云管理平台FusionSphere，而NetMatrix则是SDN/NFV编排器。网管方面则是eSight。

之所以要先把华为这些产品线先做一个交代，是因为这些概念都是华为这几年间提出来的，很容易让人混淆。下面主要介绍与数据中心SDN相关的敏捷控制器AC-DCN。

4.7.1　整体架构

华为于2013年正式发布AC 1.0，AC 1.0主要定位在智能园区网的场景，侧重于无线侧的接入与安全策略。可能是考虑到与TRILL产品线的竞争，因此虽然AC 1.0对于数据中心有所涉及，但是在市场上并没有进行过多的宣传。同年，华为的SDN产品线中以ENP芯片、S12700交换机和SNC控制器为主打，因此AC 1.0并没有获得很多的关注。

2015年，华为发布AC 2.0，随着SDN在数据中心落地形势的明朗，AC 2.0开始推广面向数据中心的AC-DCN控制器，并提出了以“SDLAN+SDSAN+SDDCI”为主体技术架构的“敏捷数据中心网络3.0解决方案”，以连接计算资源、存储资源和多数据中心，如图4-68所示。SDLAN主要解决的问题是虚拟网络的自动化部署与物理网络的自动化运维。SDSAN主要解决的问题是FC/FCoE的集中式控制与管理。SDDCI主要解决的问题是跨DC站点的WAN链路调优。

图4-68　AC 2.0中的AC-DCN

2016年年底，华为发布AC 3.0，AC 3.0将三大业务场景正式定位在园区、数据中心和广域网。针对数据中心场景，华为推出了“云数据中心网络5.0解决方案”，鉴于IP超融合概念的兴起，该方案不再关注于SDSAN，而是将重点放在了多类型VxLAN控制与精细化运维等方面。

除了定位的转变，AC还经历了平台级别的变化。前两个版本的AC是基于OpenDaylight进行二次开发的，以MD-SAL为核心提供华为的业务组件，对接华为的硬件设备，并对集群等关键机制进行了增强。由于华为在ONOS社区地位的逐步加强，以及出于一些其他的商业因素，AC 3.0提出了“基于ONOS来统一园区、数据中心和广域网三大场景控制器”的技术转型路线。对于AC-DCN来说，由于前两个版本都是基于Open-Daylight来做的，一些功能模块都已经相对成熟，因此目前AC-DCN的主体仍然是基于Open-Daylight的，这从华为给出的官方说法“AC 3.0基于ONOS平台为核心，同时兼容Open-Daylight应用”中也可知一二。

4.7.2　管理平面设计

华为的视角并不局限于网络，而是拥有完整的数据中心虚拟化产品线，直接与VMware在服务器虚拟化领域进行竞争。对应地，华为还推出了自己的云管理平台FusionSphere，可以作为云编排器直接与AC进行交互。与FusionSphere不同，NetMatrix是华为专门为SDN/NFV设计的协同编排器，与可以作为AC的编排器发放数据中心网络业务。

华为的eSight智能网管可以用于云数据中心的网络管理，在其白皮书中提及了对如下管理功能或特性的支持。

·故障管理：提供IP、IT、业务的实时和远程告警。

·配置管理：预置常用的业务配置模板，提供配置文件的备份、比较与恢复。

·报表管理：提供预定义报表以及报表的定制化能力。

·设备管理：路由器、交换机、防火墙和WLAN设备。

·可视化管理：物理拓扑、IP拓扑、网络流量以及网络性能与SLA。

·虚拟资源管理：虚拟化拓扑、TOR、服务器、虚拟交换机和虚拟机。

·策略管理：安全策略、网卡绑定、VLAN、QoS、ACL和网络策略自动迁移。

4.7.3　控制平面设计

AC-DCN的接口协议如图4-69所示，北向通过RESTful API与云管理平台或者编排器进行交互，处理虚拟网络的业务需求。南向通过多种协议与数据平面设备进行交互，来进行网络控制与监测。东西向主要用于对接计算资源的控制器，用于感知虚拟机信息。

图4-69　AC-DCN的接口协议

1.VxLAN的控制逻辑

由于AC-DCN最初是基于OpenDaylight的，因此它继承了ODL支持多种南向协议的优点，对OpenFlow/OVSDB/NETCONF/BGP的组合使用，使得AC-DCN能够支持多种多样的VxLAN控制逻辑，为底层的Overlay的组网逻辑提供了灵活的选择。

图4-70是基于OpenFlow/OVSDB的纯硬件组网，AC-DCN将隧道表、VxLAN转发表等集中地分送给Leaf ToR，AC-DCN可以代理回复ARP等以消除广播。

图4-70　基于OpenFlow/OVSDB的纯硬件组网

图4-71是基于OpenFlow/OVSDB的纯软件组网，AC-DCN将隧道表、VxLAN转发表等集中地分发给虚拟交换机，虚拟交换机可以在本地代理回复ARP等以消除广播。

图4-71　基于OpenFlow/OVSDB的纯软件组网

图4-72是基于OpenFlow/OVSDB的软硬件混合组网。

图4-72　基于OpenFlow/OVSDB的软硬件混合组网

图4-73是基于EVPN的纯硬件组网，Leaf ToR作为BGP Client发布虚拟网络的路由，Spine作为BGP RR来进行路由反射，以在多个Leaf ToR间同步虚拟网络的路由。Leaf ToR可以代理回复ARP等以消除广播。由于华为还没有能够支持EVPN的vRouter，因此EVPN对于AC-DCN来说没有纯软件的方案。AC-DCN在这种情况下只负责对EVPN进行集中式的配置，不直接控制路由逻辑。

图4-73　基于EVPN的纯硬件组网

图4-74是基于多种协议的多控制平面混合组网。虚拟交换机的控制由AC-DCN通过OpenFlow/OVSDB集中式完成，硬件上的转发逻辑由Leaf ToR/Spine间运行的EVPN分布式完成，AC-DCN运行MPBGP与作为BGP RR的Spine节点交互EVPN路由，并在本地对OpenFlow/OVSDB与MPBGP进行格式转换，以同步虚拟交换机和硬件交换机上的控制逻辑。NETCONF用于下发EVPN配置。

图4-74　基于多种协议的多控制平面混合组网

图4-75由AC-DCN直接作为EVPN中的BGP RR与Leaf ToR进行交互来同步虚拟网络的路由，Spine节点将不再运行BGP RR。这种方式的好处是可以在AC-DCN上自动化地调整BGP的反射策略，从而得以对路径进行优化，这是一种类似于IP骨干网上RR+的设计方案，不过这对控制器上RR实现的性能要求较高。

图4-75　AC-DCN直接作为EVPN中的BGP RR

2.可视化机制

为了能够将Overlay和Underlay网络进行一体化的运维，AC-DCN通过OVSDB、SNMP、sFlow等监测协议获取虚拟交换机集合硬件交换机的原始状态数据，如时延、丢包、缓存等，然后结合大数据平台对这些数据进行二次处理，为上层的网络运维应用提供数据基础，应用基于这些数据可以进行故障定位、故障自动恢复或者在故障发生前进行预判并采取一些措施（如路径优化）来避免故障的发生。如图4-76所示。

图4-76　AC-DCN中运维相关的功能

除了为应用提供网络状态数据以外，AC-DCN还提供了多元的网络可视化能力，如设备可视化、拓扑可视化、租户可视化、路径可视化、流量特征可视、应用状态可视化等。其中，路径可视化是通过NQA（Network Quality Analyze）技术在带内实现的，通过为某些业务流（如五元组）发送一些NQA探针包，可以在该探针包中插入中继节点的标识和时延、带宽等Metadata，从而获得业务流在Underlay上的转发路径信息。

4.7.4　数据平面设计

硬件交换机上，华为CE 7800/8800/12800系列可以提供VxLAN Routing，因此Leaf ToR和Spine都可以作为VxLAN L3 Gateway，分别提供分布式和集中式的路由。另外，CE系列的交换机除了VxLAN以外，还同时支持了一些传统的Fabric技术，如堆叠iStack、横/纵向虚拟化CSS+SVF、TRILL等。这与Cisco将基于VxLAN的N9K和基于FabricPath的N7K产品线独立演进的思路是有所区别的。

如图4-77所示，CE系列交换机的操作系统都采用了华为的VRP 8，VFP 8内嵌了OPS（Open Program System）模块，为交换机的控制提供了开放的接口，允许通过Linux Container来扩展控制与管理功能。

图4-77　CE系列交换机的操作系统

虚拟交换机方面，华为有自己的CE1800V，控制上提供对于OpenFlow 1.0、OpenFlow 1.3和OVSDB的控制器接口，转发上提供对VxLAN和GRE封装的支持。AC-DCN也可以对接开源的OpenvSwitch。不过，CE1800V相比OVS也没有太多的优势可言，因此华为还是为AC-DCN主推CE系列的盒子。

以上就是对华为数据中心SDN的介绍。相比Cisco、VMware和Juniper，华为在SDN数据中心方向上切入较晚，因此在其方案架构上有很多可借鉴的成熟思路，基于众多的开放标准，AC-DCN提供了多种南向协议和可选的Underlay Fabric，因此在技术路线上来看AC-DCN还是比较灵活的，不过AC-DCN目前的版本仍处于早期阶段，未来在市场上会表现如何还是个未知数。

4.8　Bigswitch BCF与BMF

讲过了各大厂商的产品和方案，从本节开始介绍SDN领域中最令人振奋的一股力量——创业公司。2012～2015年，SDN创业的主战场是数据中心虚拟化，但2015年已经开始转向SDWAN。数据中心SDN第一阶段的拼杀基本已经结束了，经过市场的整合，一些创业公司已经投入了大厂商的怀抱，只有少数的几家仍然在市场上独自打拼，其中以BigSwitch最为大家所熟悉。

BigSwitch可以说是根正苗红的SDN玩家，该公司于2010年成立，很多主要创始人都是从Clean State项目组出身的，包括Guido Appenzeller和Rob Sherwood。此二人和Martin Casado属于同一拨选手，几人都是OpenFlow的骨灰级玩家，不过BigSwitch和Nicira走的路线却有着很大的差异，BigSwitch用OpenFlow做的是Underlay，数据平面上做的是白盒的OS，而Nicira用OpenFlow做的是Overlay，数据平面上搞的是Hypervisor上的vSwitch。两者相比较而言，Bigswitch的理念是用纯OpenFlow来做数据中心，偏向于狭义范畴的SDN，而Nicira走的理念则更接地气一点，Overlay一提出来对了很多人的胃口。随着VMware的天价收购，Nicira成了SDN界最为知名的明星，而BigSwitch则至今仍然独立地存在着。

BigSwitch为OpenFlow贡献了非常多的知名开源项目，控制器有FloodLight、协议编译工具Loxigen、虚拟化中间件FlowVisor、交换机代理Indigo以及测试工具OFLOPS等，这些开源项目（尤其是Floodlight）奠定了BigSwitch在开源SDN生态圈中的重要地位。OpenDaylight在成立之初，BigSwitch作为白金会员希望把自己的控制器代码作为OpenDaylight控制器的核心，不过社区最后采纳了将BigSwitch和Cisco One控制器进行融合的方案，以提供对OpenFlow在内的多种南向协议的支持。“提供多种南向协议的支持”听起来很美好，背后实际上是厂商当时对于火热的OpenFlow的围剿，眼看着OpenDayLight社区与OpenFlow背道而驰，BigSwitch在社区仅成立两个月后就立即选择了退出。不过，在Cisco的强大号召下，OpenDaylight还是建立起了广泛的生态，一定程度上可以说是成功地给OpenFlow降了温。

市场策略上，BigSwitch把基于Indigo的交换机操作系统Switch Light也贡献了出来，并和Accton等ODM厂商共同推广OpenFlow白盒，而BigSwitch主要还是卖OpenFlow白盒上游的控制器。目前来看，BigSwitch已经在北美争取到了一些客户。资本方面，BigSwitch在2011和2012两年在A、B两轮一共拿了3880万美元，2016年的C轮有4850万美元，最新的消息是2017年7月份又拿到了3070万美元的资金，融资情况还是很不错的。

4.8.1　整体架构

BigSwitch产品的体系架构就是典型的控制器+OpenFlow交换机。限于OpenFlow的应用场景，BigSwitch的产品是专门为数据中心所设计的，其方案分为两套：Big Cloud Fabric（BCF）和Big Monitoring Fabric（BMF）。BCF用于网络自动化，BMF用于DMZ与网络可视化。针对数据中心安全，BigSwitch还专门提出了BigSecure，不过里面还是用的BCF和BMF的技术，只是换了个说法而已。

要理解BigSwitch产品的体系架构，更好的办法就是直接从公司的名字入手，即将基于白盒设备的Fabric抽象成一台“大的交换机”，通过OpenFlow对其进行集中式的单点控制。从反向的角度还可以理解为，将网络中昂贵的机箱式大盒子换成多个便宜的白盒设备，以获得较低的拥有成本以及更好的可扩展性。如图4-78所示，无论是BCF还是BMF，都贯彻着这一思想。

BigSwitch的产品线如图4-79所示。BCF和BMF都分为两个版本，BCF的P版本用于VMware环境，P+V版本用于OpenStack环境，BMF的Out-of-Band版本用于带外流量分析，Inline版本用于DMZ。

图4-78　BigSwitch的产品设计思维

图4-79　BigSwitch的产品线

4.8.2　BCF控制平面设计

BCF的设计是要将传统的Chassis Switch结构和功能“移植”到Leaf-Spine架构中，其中Leaf相当于线卡，Spine相当于背板，而Controller则相当于控制引擎。那么BCF要实现的主要有以下几点：

·Chassis Switch的线卡是即插即用的，因此Leaf和Spine需要支持ZTP。Chassis线卡上的能够支持L2、L3的转发，因此Leaf上需要实现分布式路由。

·Chassis内背板上的转发不需要复杂的L2、L3协议，因此Controller需要通过OpenFlow直接控制Underlay Fabric上的转发而非通过隧道Overlay在IGP/BGP上。

·Chassis Switch的控制引擎通常都是冗余的，并且是单点管理的，因此Controller需要实现集群形成一个物理上分布、逻辑上统一的控制器。

·Chassis Switch即使双引擎全部失效了，也有GR、NSF等机制来保证数据平面的正常转发，因此Controller集群都挂后数据平面也不能够失效。

·Chassis Switch背板也是冗余的，因此Fabric中的交换机、链路、路由都应该是互为备份的。

BCF与传统Chassis的对比如图4-80所示。

图4-80　BCF与传统Chassis的对比

1.BigSwitch对OpenFlow的理解

可以看到，相比于其他数据中心SDN方案，BCF最大的特点就在于它使用OpenFlow来代替IGP/BGP来运行Underlay，支持在不使用VxLAN封装的情况下实现虚拟化。其优点在于节约了IGP/BGP的运维成本，不需要分别去购买Overlay的Underlay的控制逻辑。不过，BCF需要将Leaf-Spine都替换成OpenFlow白盒，这样一来难以复用现有的设备，而且目前来看使用OpenFlow做Underlay的思路还没有被业界所普遍接受。不过作为OpenFlow的发源地，北美的市场上还是有一些客户愿意对此进行尝试的，包括Verizon这样的Tier-1运营商。

想用OpenFlow来搞Underlay，不可避免地要回答两个问题：①TCAM不够用怎么办？②触发式的逻辑对控制器的冲击怎么解决？

BCF给出的答案是：①OpenFlow虽然对flow-based forwarding有很好的支持，但是不是只能基于流进行转发，OpenFlow本质上只是一个更灵活的FIB RPC而已。对于普通的L2、L3流量，交换机在本地把OpenFlow流表转化成CAM和LPM即可，现有的芯片里面CAM表项轻松上100K，LPM可能少一些不过也要比ACL多得多。需要用flow-based的时候再用，比如一些安全、服务链策略，不过这种策略通常是不会很多的。②同理，OpenFlow只是提供了对PacketIn的支持而已，FlowMod的逻辑是没必要非得和PacketIn绑定在一起的。如果把触发式转为预置式，那么和传统网络中控制引擎提前学好路由表，再同步到线卡里面去是没有区别的。如果一上线就打流量，传统设备达到线速转发也是需要一段时间的，这是无法避免的。另外，BigSwitch对OpenFlow的理解也不是完全狭义的“数据平面没有任何控制逻辑”，一些需要触发的行为可以通过卸载到交换机上去完成，如DHCP、ARP等，这可以进一步减少PacketIn对控制器的冲击。

思路谈完了，开始讲具体的设计。图4-81是BCF的业务网络模型，租户表示为Tenant，一个Tenant可以有多个Segment，每个Segment通常对应于一个VLAN，同一个Tenant下的不同VLAN之间通过Logical Router进行通信，不同的Tenant间通过System Logical Router进行通信，System Logical Router逻辑上与出口路由器进行双上联，以对接非虚拟化环境。

图4-81　BCF的业务网络模型

BCF分为P+V版本和P版本。其中，P+V版本是为了OpenStack设计的，除了白盒以外，BigSwitch还提供了自己的虚拟交换机IVS（也是基于开源的Indigo的），由于不使用VxLAN，因此BCF能够方便地提供Overlay+Underlay的一体化控制，包括虚拟机定位、路径追踪等。

2.P+V版本

图4-82是P+V版本中BCF的转发逻辑。可以看到其转发表并不复杂，本地转发的就直接走目的所在的Port，远端的就通过Group选一个下一跳发出去。这些转发表都是BCF控制器通过OpenFlow流表预置的，DHCP、ARP、ICMP以及NAT/NPAT都是卸载到交换机本地完成的，不需要通过PacketIn上送控制器，以避免流量对控制器的冲击。

对于L3流量，采用的是“任播+分布式路由”的做法，各个Segment中的.1地址即为对应的Logical Router接口，这些Logical Router Interface都使用相同的MAC地址，Logical Router分布在流量接入的vSwitch/Leaf上，识别出L3流量就在直接完成MAC地址的改写并对TTL减1，然后在目的的Segment中进行转发。如果是跨租户的Inter-Tenant L3或者Internet流量，还需要额外在System Router上进行一次路由，为了方便一些安全策略的实现，System Router的转发逻辑被分布在了Spine中。

图4-82　P+V版本BCF的转发逻辑

当然，控制器能够完成上述逻辑的前提是能够得知物理网络的拓扑以及虚拟机、裸机的位置。对于物理网络的拓扑，需要在控制器上事先通过交换机的MAC地址来定义其拓扑角色（vSwitch、Leaf或者Spine），LLDP和LACP被卸载到交换机本地以探测交换机间的连接，以及裸机/虚拟机与交换机间的Bond连接。对于虚拟机的位置，vSwitch可以通过监听vNIC的plug动作来上报。对于裸机的位置，就只能通过事先定义或者通过与OpenStack的接口来获取了。

3.P版本

P版本主要是用于与VMware NSX的对接。在最开始的几个版本，BCF不提供对于VxLAN的支持，因此只能作为单纯的Underlay SDN方案，无法作为HWVTEP将裸机接入NSX环境。DHCP、ARP是由NSX来负责的，拓扑和Bond的发现是通过LLDP/CDP来完成的，BCF为VTEP间的通信提供Transport VLAN的自动化配置，VxLAN的封装与解封装都是由VDS来完成的。不过，虽然BCF不参加VxLAN的控制过程，但是可以通过vCenter间接地从NSX处得到虚拟机与VTEP的对接关系，做一些整合后即将虚拟机和Underlay的位置关系显示出来，如图4-83所示。对于路径的追踪，由于Underlay不具备VxLAN的能力，就只能提供好目标流量所对应的外层包头信息，包括源VTEP IP、目的VTEP IP和源端口号（由内层包头的五元组哈希得到，可以标识内层流量）与目的端口号，因此这是一种这种比较初级的路径追踪方案。

2016年之后，BCF开始在P版本中添加对VxLAN的支持，以提供对裸机接入NSX环境。其原理也很简单，就是在BCF Controller上使能VxLAN，然后配置好HWVTEP上Segment VNI到Port、VLAN的映射关系，然后BCF Controller和NSX直接通过OVSDB进行通信交互MAC与VTEP的映射关系，然后再向HWVTEP同步转发表，裸机发送的ARP也由HWVTEP来代理完成，如图4-84所示。实际上对于NSX来说，BCF Controller就是HWVTEP，可以看作NSX和物理交换机间的中继。BCF有了对VxLAN进行处理的能力以后，路径的追踪就可以直接通过内层流量特征来完成了，而不用再提供外层包头。

图4-83　BCF P版本与NSX的对接

图4-84　BCF P版本支持通过VxLAN将裸机接入NSX

回头再来说说VLAN和VxLAN。Broadcom Trident 2早在2012年就支持了VxLAN，为什么BCF最开始没有选择对VxLAN进行支持呢？原因可能有两点：①VxLAN封装后把内层报文隐藏了起来，Leaf和Spine上的一些转发逻辑写起来比较困难。②Trident 2不支持VxLAN Routing，BigSwitch并不具备改芯片的能力，之所以使用了Trident 2，是没有办法在硬件上打通L3流量的。随着Trident 2+（支持VxLAN Routing）在2015年的上市，BCF也开始提供了对VxLAN的支持，不过VxLAN对于BCF来说主要还是用在P版本中，来为NSX HWVTEP接入裸机的。不过前面说过，VxLAN这种Overlay思路是和BCF的思路有冲突的，因此在对接OpenStack的P+V版本中，BCF可控的环节多了，就仍然使用VLAN来做Fabric了。

当然，使用VLAN做Fabric就面临4K不够用的问题，解决这个问题的做法有两个：一个是VLAN Rewrite，将本地和Port绑定的Local VLAN与传输用的Segment VLAN进行一次映射，虽然转化后还是只有4K，不过这能够解决一定的问题，因为一些租户可以只放在一个vSwitch/Leaf下，不用占用全局的Segment VLAN。不过这是治标不治本的。另一个就是QinQ，也就是VLAN Stack，一个VLAN不够用就再嵌套一个VLAN，QinQ结合VLAN Rewrite，理论上也是可以支持到16million的租户的。

不过，不论是VMware还是OpenStack，基本上都是用在私有云里面的，4K通常来看都是够用的。而且像BCF这种Overlay和Underlay都需要由控制器来控制的方案，规模也是不会做得太大。因此BCF在销售策略上是以POD为规模在卖，客观上讲是算不得大二层了，当然，传统的Chassis也就是在POD内部用一用，如图4-85所示。目前来看，想要二层上规模，主流上还是得用VxLAN。

图4-85　BCF主要面向POD内部的控制

4.8.3　BMF控制平面设计

BMF（最初称为Big Tap）是BigSwitch最早开始推的产品，分为两个版本：Out-of-Band版本用于带外监测，Inline版本用于DMZ。如图4-86所示，BCF左侧的为Inline BMF，实现DC入口处的流量安全；BCF右侧的为Out-of-Band BMF，实现BCF（或者其他生产网络）的流量可视化。

图4-86　Inline BMF和Out-of-Band BMF

1.Out-of-Band BMF

图4-87是Out-of-Band BMF的示意图。一些OpenFlow白盒组成一个Monitoring Fabric，两侧分别连接生产网络和第三方的网络可视化软件，BMF Controller通过OpenFlow对生产网络产生的TAP或者SPAN流量部署一些策略，如过滤、聚合、分流等，然后再有针对性地转发给网络可视化软件，以提高网络可视化软件的处理效率。实际上，这些功能传统的NPB（Network Packet Broker）就可以实现，不过传统NPB都是一些专用的Chassis，这些Chassis虽然功能和性能强大，但是价格昂贵而且可扩展性较差。Out-of-Band BMF的思路就是通过OpenFlow Fabric来替换Chassis，通过Controller的灵活控制来实现类似的NPB功能，优点是可以降低成本、提高可扩展性以及能够开放出来一些监控策略的API，这与BCF的设计思路是基本一致的。

图4-87　Out-of-Band BMF的示意图

不过，由于OpenFlow交换机的NPB功能相比专用的Chassis还是有限的，比如一些去重、加时间戳、分片等，因此BigSwitch还为BMF提供了可选的Service Node，以实现这些“OpenFlow Beyond”的NPB功能。另外，如果策略想匹配一些隧道包的内层特征，标准的OpenFlow也是无法提供支持的。BMF通过对OpenFlow进行扩展解决了这一问题，思路就是匹配时不指定字段名称了，而是通过Anchor+OffSet的方式来自定义报文解析与匹配。该技术被称为DPM（Deep Packet Matching），和POF中的相关机制是类似的，不过DPM是受到长度限制的。

该方案中OpenFlow交换机的端口被划分为几种不同的角色。连接生产网络的TAP/SPAN流量的端口称为Filter Interface，连接网络可视化软件的端口称为Delivery Interface，Fabric内部互联的端口称为Core Interface。Service Node通常通过两个端口来接入Fabric，Pre-Service Interface用于从Fabric接收流量，Post-Service Interface用于处理完流量后重新注入Fabric。

Out-of-Band BMF实际上就是通过OpenFlow来Match特定的流量（如12元组），然后通过Drop、Output、Group等动作在不同的Interface间执行过滤、引流、聚合等策略。当前，前提是首先在BMF Controller上实现定义好交换机端口的角色以及Fabric的拓扑。这个的控制逻辑也没有什么特定的模式，完全是根据监控的策略来实现的，这里不再赘述。

既然是用OpenFlow做策略的，那么又有两个问题了：①两个优先级相同的策略，发生冲突了，怎么办？②策略要是细一点，只能用TCAM来做了。如果策略多了，TCAM不够用怎么办？这两个问题实际上非常偏学术，在工程上只能做一些简单的处理，BMF的做法是：①BMF Controller对新下发的策略和老的策略进行比较，发现发生了冲突，就尽量把Match和Action合并进一条流表。②对流表做优化，主要是对含有inport和ip_prefix的流表进行适当的聚合。当然，无论如何处理，这都必将损失一定的策略精度。

除了部署监控策略以外，BMF Controller本身也提供了一些数据的可视化，比如主机追踪、子网追踪、TAP分析、sFlow数据采集、SNMP监测等。不过，更为精细的可视化就只能通过引流到第三方专业的网络可视化软件来完成了。

2.Inline BMF

图4-88是Inline BMF的示意图。lInline BMF主要面向的场景是在DMZ区域部署服务链，以实现DC入口处的流量策略。与Out-of-Band需要额外的TAP或者SPAN把流量引到带外不同，Inline直接将OpenFlow交换机串在外网和DC入口设备之间，通过BMF Controller识别某些流量并执行服务链的引流，当然这台OpenFlow交换机也把流量SPAN到BMF Out-of-Band上去，进行更进一步的分析与审计。Inline BMF实际上和Out-of-Band BMF是一套软件，技术上还是那些东西，由于Inline BMF通常只涉及一两台交换机，其策略的实现还要比Out-of-Band BMF简单一些，这里就不再赘述了。

图4-88　Inline BMF的示意图

4.8.4　数据平面设计

前面提到过，BigSwitch的商业逻辑是为ODM提供自己的开源交换机OS来捆绑白盒，从而达到销售自己的OpenFlow控制器的目的。BigSwitch开源出来的交换机OS叫作Switch Light，简单地说，可以把Switch Light理解成带有Open Flow Agent的开源Linux。图4-89是对Switch Light结构的介绍，分别来看看图中各个颜色所指代的部分。

图4-89　Switch Light的设计

图4-89中间下方的方框部分是开源Linux，当然这个Linux的发行版并不是我们日常用的Ubuntu，而是基于开源网络操作系统ONL（Open Network Linux）。ONL是专门为bare-metal switch设计的操作系统，它提供了很多开源的、标准化的网络OS模块，如用于对设备的监控的SNMP、用于交换机自动化配置的ZTN Loader、用于记录日志的Syslog等，还包括一些交换机外设（如风扇和LED）的驱动。ONL希望在硬件和交换机的网络程序间提供一个通用的平台，其代码是完全开源的，ONL于2015年被OCP（Open Compute Project）采纳为标准的开源交换机网络操作系统。Baremetal switch只要是支持ONIE（Open Network Install Environment）就可以自动下载ONL Image。

当然，ONL只是一个通用的OS，Switch OS厂商需要基于ONL来提供他们自己的OS，如Cumulus、Pica 8等。Switch Light是BigSwitch提供的基于ONL的OS，其定制化的内容就在于Indigo，即为图4-89中橙红色（CLI和Indigo）的部分。Indigo是BigSwitch开源出来的一个OpenFlow Agent，图4-90是其Indigo_v2内部模块的简单示意。Indigo北向是OpenFlow控制器（图4-90中为BigSwitch开源的Floodlight），LoxiGen（同为BigSwitch开源）为其提供OpenFlow协议的编码。南向是转发通道的SDK。如果转发是ASIC，那么Switch Light经过包装后就变成了白盒交换机；如果转发是Linux Kernel，那么Switch Light经过包装后就是虚拟交换机IVS（Indigo Virtual Switch），IVS同为BigSwitch开源。

图4-90　Indigo_v2的设计

Switch Light不是OpenFlow传统意义上的“Thin Agent”，Switch Light在Indigo之外还增加了很多本地功能，如DHCP、ARP、LLDP、LACP、ICMP等，能够有效地防止这些流量对Controller的冲击，以增强Controller的可靠性。图4-91示意了IVS相比于标准的OpenvSwitch在用户空间增加的部分功能。

图4-91　IVS将部分功能进行了卸载

对于图4-89中的ASIC，目前能够支持OpenFlow的主流ASIC为Broadcom的Trident 2和Tomahawk。Trident 2和Tomahawk都是通过传统的转发表来模拟OpenFlow流表的行为的，Broadcom开放了OF-DPA作为Trident 2和Tomahawk的SDK，以方便OpenFlow Agent对芯片进行操作。实际上，把OF-DPA说成ASIC SDK并不是十分准确，因为出于保密的考虑，Broadcom在ASIC和OF-DPA中间还另外做了一层抽象，所以OF-DPA准确地说应该是“SDK of ASIC SDK”。最开始，BigSwitch并没有把Indigo的OF-DPA Adaptor（见图4-91）开放出来，不过现在从github上来看，还是可以看到OF-DPA Adaptor的。

由于BigSwitch的重点放在了控制器上，因此BigSwitch没有选择自己做盒子，而是通过与ODM（如Accton、Quanta等）合作来建立控制器的生态。BigSwitch为ODM提供交换机的OS，ODM来做盒子的整体集成与包装。BigSwitch自己倒是基于x86为BMF做了Service Node，用于弥补OpenFlow在实现NPB功能上的不足。

上述就是对BigSwitch的介绍了。作为SDN骨灰级的创业公司，BigSwitch一直在坚持“OpenFlow+白盒”的技术路线，虽然在生态方面做出了巨大的努力，但是由于众多因素，OpenFlow目前的进展并没有完全符合预期。不过，BigSwitch在产品上也看得比较准，尤其对Monitoring Fabric和DMZ，可以算是OpenFlow在数据中心的杀手级应用了。BigSwitch对OpenFlow最初所设想的SDN路线的长期坚持，且不谈未来究竟前景如何，这种对执着的技术追求可以说在鱼龙混杂的SDN创业圈子里是一股清流。如果将来有一天BigSwitch也被收购了，那么OpenFlow颠覆传统网络的梦想，或许也就该画上句号了。

4.9　Midokura Midonet

Midonet是日本的Midokura公司开源出来的Neutron组网方案。Midokura早在2010年就开始做云中的网络虚拟化，他们最开始做Midonet是用的Python，后来改用了Java+Scala。2014年年底的OpenStack大会上，Midokura将Midonet开源出来并集成到Neutron plugin中。目前，Midonet已经更新到了5.4版本。

Midokura构建起来的生态比较热闹，既有Eucalyptus、Mirants这些云计算玩家，也不乏Redhat、Suse和Ubuntu等开源IT界的老大哥，Broadcom和Fujitsu等通信巨头也赫然在列。2013年4月，Midokura在A轮融资中拿到了1730万美元，在2016年6月的B轮融资中拿到了2000万美元，在资本市场上的表现只能说是不愠不火。

4.9.1　整体架构

从组件来看，MidoNet以ZooKeeper+Cassandra构建全局数据库Network State DB Cluster，NSDB中存储网络中的各种状态数据。转发的控制逻辑被分布在设备本地——Midolman，转发通过ovs datapath来实现。如图4-92所示，转发设备分为Edge和BGP Gateway两种，Edge负责虚拟机的接入，BGP Gateway负责对接Internet。Edge与Edge间，Edge与Gateway间的Underlay网络（称为tunnel zone）支持VxLAN和GRE两种隧道方式。

从接口来看，租户网络的逻辑拓扑通过RESTful API下发并存于NSDB，NSDB和Midolman间通过RPC进行双向同步，NSDB把租户网络的信息分发给Midolman，而Midolman会将本地的信息上报给NSDB，NSDB再同步到其他的Midolman。Midolman实际上取代了OVS在user space中的vswitchd和ovsdb-server，因此也并没有使用OpenFlow和OVSDB，而是直接通过Linux Netlink来操作kernel space中的odp（ovs datapath）。

图4-93给出了host中Midolman所处的位置。

Midonet典型的组网模型如图4-94所示，vPort、vBridge（图中为Virtual Switch）、Tenant vRouter分别和Neutron中Port、Network和Router相对应。相比Neutron，Midonet还设计了Provider vRouter，对public-network进行了增强。

一般来说，在Neutron的public-network中，各个Tenant Router的External Interface与Physical Router Interface在同一个CIDR下，这就会产生两个问题：①由于Tenant Router没有发布路由的能力，这就使得Tenant间通信（在有FIP的情况下）只能走南北向流量的路由，得绕到Physical Router上再绕回来。②Neutron社区中对public-network的控制机制仍然比较缺乏，Tenant Router不支持做multi-home，尽管很早就有蓝图，但一直也不见什么进展。

图4-92　Midonet的架构

图4-93　Midolman取代了vswitchd和ovsdb-server

图4-94　Midonet典型的组网模型

Midonet解决这一问题的思路是，从逻辑的视角增加了对Provider Router的资源描述，Provider Router在Edge本地即可以直接完成跨Tenant流量的处理，与Internet间的南北向流量送到BGP Gateway，BGP Gateway能够在多个Uplink上进行选路。处理的机制后面再做介绍，下面先通过图4-95给出简单的示意。

图4-95　Neutron的路由模型（左）和Midonet中的路由模型（右）

根据Midokura官网给出的信息，开源版和企业版的Midonet能够支持的上游软件、网络功能以及管理功能，见表4-3。

表4-3　开源版和企业版Midonet支持的特性

以Midonet最新的5.4版本为参考，其支持的一些高级功能包括FWaaS、LBaaS、L2 Gateway、Dynamic Routing、VPNaaS、QoS等。可以说在对于OpenStack网络的支持方面，Midonet是功能最为丰富的SDN产品之一。

4.9.2　控制平面设计

在Midonet控制的平面设计，基于ZooKeeper和Cassandra构建了全局数据库NSDB，分布在各个设备中的Midolman与NSDB双向地同步信息，并执行具体的控制逻辑。根据Midonet自己的说法，这是一种分布式的控制平面设计，但实际上更准确地说，这应该属于一种层次化的控制平面，数据和状态集中在NSDB中，控制逻辑的实现分布在转发设备本地。

1.NSDB中的ZooKeeper与Cassandra

在NSDB的设计中，ZooKeeper主要负责集群，并存储着Overlay和Underlay的信息，包括租户网络的逻辑拓扑、虚拟机MAC/IP地址以及物理节点的Underlay IP地址等。租户网络的逻辑拓扑来源于北向的RESTful API，ZooKeeper会将逻辑拓扑同步给相关的Midolman，而Midolman则会把本地虚拟机MAC/IP地址的Underlay IP和上报给ZooKeeper，ZooKeeper再将该信息同步给相关的其他设备上的Midolman，以指导Midolman上转发逻辑的实现。另外，ZooKeeper还会存储着网络中各种的监测数据，包括集群相关的、租户相关的、流量相关的数据，等等。ZooKeeper是和业务直接相关的，如果ZooKeeper集群挂掉后，虽然已有流量仍然能够进行转发，但是新出现的流量和租户网络拓扑的更新就没有办法做处理了。

Cassandra负责存储Flow-State的相关信息，主要包括NAT Mapping和Connection Tracking两种。之所以要存储这些信息，一方面是为了进行全局的同步，来保证IP/Port的使用不产生冲突，另一方面是为了做持久化，以防重启后流状态的丢失。在Midonet的早期版本中，Flow-State只能通过Cassandra进行同步，新的流量出现时需要Reactive地向Cassandra进行资源的请求。从1.6版本开始设备本地缓存Flow-State，并通过私有的协议Proactive地在Midolman间进行同步。5.2.1版本后，设备在本地即能够对Flow-State进行持久化，这使得系统不再依赖于Cassandra，目前Cassandra在Midonet的部署中默认是不做开启的。

2.Midolman中的Overlay Simulation

下面来对Midolman的设计机制进行介绍。图4-96中netlink channel下方就是Midolman的堆栈。当odp收到一个packet时，如果找不到流表，就要上行向user space的Midolman发出upcall，Midolman首先查找odp flow-table的副本，没有查找wildcard flow-table，再没有就需要进入Overlay Simulation阶段了。之所以称为Simulation，是因为Midolman仿真了Overlay中的各个设备（port traffic filter、vBridge、vRouter），packet会根据port binding的映射关系送入特定的Overlay设备，这些设备存有Overlay转发表（port traffic rule，L2 rule，L3 rule），因此也就具备在Overlay上进行转发的能力，于是packet就会根据Overlay转发表在Midolman中跑一次Overlay Simulation。Overlay Simulation过程中各个Overlay设备的匹配字段和处理动作，会被整合成exact match+action set，然后翻译成一条odp流表项，下行送回odp。之后的相同类型的flow traffic就直接匹配该odp流表项做处理了。

图4-96　Midolman的内部设计

当然，Midolman进行Overlay Simulation的前提是要了解Overlay和Underlay的拓扑以及资源映射关系。Overlay与Underlay的参数（包括Edge IP，Port Binding，MAC，IP、Filter Rule等）、以及网络中的实时状态（包括L3 Connection、NAT Mapping等）都存在于NSDB中，Midolman会通过RPC同步这些数据。

假设Midolman已经拿到了这些数据，Overlay的示例拓扑如图4-97所示，现在VM1通过tap123连接OVS，并第一次开始发包，最一般的过程抽象为DHCP+ARP+Ping。

图4-97　Overlay的示例拓扑

odp第一次看见DHCP/ARP包，送给Midolman，Midolman也还不知道该怎么办，那就跑Simulation。首先，根据Port Binding的映射关系tap123对应着Overlay中的P5，于是就从P5进入其所在的vBridge开始Overlay上的转发。

如果P5上配置了安全策略，在送入vBridge之前还要先经过Filter Rule的处理，包括Port Mirroring、Service Redirection Chain、filtering Chain三个过程，如图4-98所示。对于DHCP/ARP来说，主要做Anti-Spoofing；对于Ping和其他流量，主要做Firewall。

图4-98　vPort对数据包的处理

如图4-99所示，进入vBridge后，就开始了vBridge的处理，vBridge中主要有ARP表和MAC转发表。对于ARP，则直接构造ARP Reply并从P5送出，下行送到odp后将action翻译为output=port 10，返回给VM1。对于DHCP、Ping和其他流量，主要就是Overlay上的L2转发。

图4-99　vBridge对数据包的处理

DHCP、Router Ping以及L3流量会经由vBridge送到vRouter，vRouter中主要就是路由表。对于DHCP/vRouter Ping，构造相应的DHCP/Ping报文然后通过P3再次进入vBridge处理。L3流量的处理逻辑如图4-100所示，还是典型的Iptables的思路，不做过多解释。

图4-100　vRouter对数据包的处理

南北向流量和Tenant间的流量会被Tenant vRouter中的静态路由送往Provider Router，Provider Router对于一个云来说只有一个，由Cloud Operator进行管理。Provider Router的Internet路由表是BGP Gateway上运行的Quagga BGPd形成的（也支持静态路由），Midolman提供API和CLI对Quagga进行BGP的控制，包括配置BGP Peer、发布BGP路由等。Provider Router可以有多条Uplink（默认情况下是3条），相应地映射为不同BGP Gateway上的端口，可以支持ECMP和Failover。

以上就是Overlay Simulation主要的处理逻辑了。可以看到，虽然没有了OpenFlow Pipeline的概念，但Midolman的设计还是体现了Pipeline的思想，而且在实现上与传统的L2/L3设备中的pipeline是很像的。

4.9.3　数据平面设计

Midonet的数据平面上，包括Edge和BGP Gateway，用的是ovs datapath（odp），Midolman直接通过netlink操作。odp中的流表一般都是对流量进行精确匹配的，而且即使odp支持带掩码的Megaflow Cache，Midolman也没有采用这种方式，对此官方博客的解释是：“prefers more granular flows for statistics/counting purposes”。

先来看东西向流量。Edge的odp上连接着Tunnel Port和VM Port。由于Overlay Simulation所有的处理都是发生在源所在的Edge中，因此当发生跨Edge的流量时，为了使得目的所在Edge中的odp能够直接转发到目标VM，需要源所在Edge中的odp为流量标记tunnel id，而目的所在Edge中的odp要预置流表匹配tunnel id并直接转发给相应的目标VM。注意，tunnel id在Midonet中并不表示segment，而是全局唯一地标识vPort（这里对应于VM，还有可能是BGP Gateway上的Uplink），目的所在的odp会匹配tunnel id，并直接从相应的vPort送给目的虚拟机，这一点的设计上是和NVP是一致的。考虑Edge 1中的VM 1发向Edge 2中的VM 2发送流量，图4-101和图4-102分别示意了Edge 1和Edge 2中odp上相关的流表。

图4-101　Edge 1中的流表为流量标记tunnel id并送给Edge 2

图4-102　Edge 2中的流表匹配tunnel id直接送给VM2

再来看南北向流量。如图4-103所示，Midonet通过BGP Gateway来对接Internet，BGP Gateway会通过多条Uplink上联Internet，并运行Quagga BGPd与外界动态地交互路由，能够在不同的Uplink间进行选路。

Edge上的odp通过dstIP识别出Internet流量，通过tunnel port送给BGP Gateway，并使用tunnel id标识BGP Gateway上的某一个Uplink，如图4-104所示。BGP Gateway中的odp连接着tunnel port，Quagga port和Uplink port，需要预置一些流表来处理BGP协议，如图4-105所示。用于处理流量的流表，就是改写MAC地址并从指定的uplink port送出。

另外，Midonet支持通过两种思路来对接二层物理网络的对接：①Edge先走VxLAN到Hardware VTEP，在此终结VxLAN并根据二层的转发表从物理端口送出，图4-106是通过OVSDB配置Cumulus交换机上的映射关系。②物理网络与Edge直连，虚拟机的流量先走到VLAN Agnostic Virtual Bridge标记VLAN，然后通过VLAN Aware Virtual Bridge的Trunk口送到物理网络中，如图4-107所示，相应的流表这里不做深入介绍。

图4-103　Midonet通过BGP Gateway与Internet对接

图4-104　Edge 1中的流表将Internet流量送给Gateway并以tunnel id标记Uplink

图4-105　Gateway上会预置一些流表来处理BGP

图4-106　通过HW VTEP接入物理二层网络

图4-107　通过VLAN Aware Virtual Bridge接入物理二层网络

4.10　PLUMgrid ONS

2011年，Cisco的几位工程师跳出去创办了一家名为PLUMgrid的网络虚拟化公司。PLUMgrid的技术路线十分新颖，它抛开当时风光无二的OpenvSwitch和OpenFlow，以eBPF（extended Berkerly Packet Filter）技术为基础实现了自己的可编程datapath技术——IOVisor，能够提供类型丰富的in kernel VNF，支持更为全面的Overlay组网模型，同时具备更好的处理性能。产品方面，PLUMgrid ONS（Open Networking Suite）用于对接OpenStack，PLUMgrid还有一个称为CloudApex的可视化产品，2016年8月分别发布了ONS 6.0和CloudApex 2.0。开源方面，2015年8月，PLUMgrid将IOVisor贡献给了Linux基金会，希望打造出一个良好的生态系统，Cisco、华为、Intel、Broadcom等通信巨头也都参与到了项目之中。

公司运作方面，PLUMgrid在2011年成立之初就拿到了2700万美元的融资，2014年B轮又融到了1620万美元。之后PLUMgrid在资本市场上的表现不是很活跃，直到2016年年底被VMware收购，又一个SDN Startup消失在了历史的舞台上。尽管PLUMgrid已经被收购了，不过考虑到它在技术的独到之处，所以这里还是把PLUMgrid独立写成了一个小节，希望其技术理念不会因为市场上的表现而被大家所遗忘。

4.10.1　整体架构

图4-108是PLUMgrid的产品架构。CloudApex是PLUMgrid提供的一款偏向于数据采集与分析的工具，它通过IOVisor来追踪网络中每个包的信息（而非抽样），并将这些信息进行关联呈现出租户网络的详细情况，便于管理者进行排障。关于CloudApex，不再进行深入的介绍。

图4-108　PLUMgrid的产品架构

PLUMgrid ONS是一套纯软的网络虚拟化产品，包括Edge、Gateway、Director和LCM（Life Cycle Manager）等部件。数据平面上，Edge和Gateway都是基于IOVisor来实现的，分别部署在计算节点和网关上，它们接受Director的控制，为不同的租户构建出不同的VD（Virtual Domain）。VD中可以部署很多类型的kernel VNF，包括Bridge、Router、NAT、DHCP、DNS、TAP等，通过SIA（Service Insertion Architecture）还可以在集成第三方的VNF。管理平面上，LCM联合ToolBox为ONS提供了三大类的运维功能，包括部署前的环境检查、运行时的状态维护以及发生问题后的网络诊断。

图4-109是ONS中Overlay典型的组网模型。一个租户VD的典型组网模型就是虚拟机通过不同的Bridge接入，Bridge上挂着对应子网的DHCP，Bridge上联到Static Router，Static Router再上联一个NAT接到Connector上，Connector再负责连接到外网（Service VD）。如果租户需要多个子网，可以在自己的Static Router下面挂多个Bridge。如果租户想要对外发布自己的路由信息，则需要使用Dynamic Router。如果租户需要内部的DNS，则可以在自己Virtual Domain的任何位置放一个DNS。

Service VD对应于OpenStack的public network，在OpenStack的public network中tenant间的通信需要进行hairpin，DVR无法直接拉通同一host上不同租户的虚拟机，在Plumgrid中可以通过在Service VD中增加一个Router来解决这一问题。这方面的设计和Midonet中的Provider Router是一致的。

图4-109　Service VD可以直接拉通不同的租户

Underlay的组网方面还是通过VxLAN来拉通Fabric，PLUMgrid还支持通过IPSec对数据平面的流量进行加密。设备分为Edge和Gateway两种角色，Edge负责接入虚拟机，通过IOVisor提供各种kernel VNF的实现，Gateway负责对接物理网络，形态上既可以选择软件的PLUMgrid Gateway，也可以集成其他厂家的硬件VTEP。

4.10.2　数据平面设计

1.BPF与eBPF

PLUMgrid的技术基石是IOVisor，而要了解IOVisor，就必须先了解eBPF。众所周知，网络协议栈的设计和实现都是分层的，数据包经过协议栈时包头会被逐层剥掉，应用层是看不到L2～L4的头的。如果想要实现vSwitch或者vRouter，必须要看到L2/L3的头，此时通常需要对协议栈进行修改（内核旁路技术除外），通过在Linux协议栈中插入hook点，将数据包从通用的协议栈处理流程中“钩”出来，以对数据包进行自定义的处理。BPF（Berkeley Packet Filter，伯克利数据包过滤器）是一种著名的数据包捕获机制，用户可以编写自己的BPF程序，将其hook到协议栈特定的位置，然后将满足某些特征的数据包过滤出来并送到user space中进行处理。BPF可以在3个位置做hook，在raw socket处“钩”出来的是完整的有L2头的数据包，在stream socket处“钩”出来的是以UDP头为起始的数据包，在datagram socket处“钩”出来的是以TCP头为起始的数据包。比如想获得所有HTTP的包，就可以自己写一个BPF程序去匹配80端口，然后将关联到datagram socket的hook点上即可，如图4-110所示。Linux在很早的版本中就提供了对BPF的支持，很多大家耳熟能详的网络程序都是通过BPF来实现的，包括TC、DHCP、nmap、libcap等。

图4-110　BPF可以在内核的不同位置插入hook捕获数据包

不过由于实现上的原因，BPF能够支持的功能十分有限，比如只有2个寄存器，寄存器是32位的，对数据包进行处理的指令也很少，只能对数据包进行解析和匹配，因此BPF主要用来对数据包进行过滤，难以实现更为复杂的网络处理功能。eBPF（extended BPF）的提出解决了BPF的上述问题，主要体现在以下几个方面。

·寄存器资源更为充足，能够提供10+的64位寄存器。

·指令更为丰富，eBPF程序能够对数据包字段进行改写、移位、交换等操作，因此eBPF程序能够实现更为复杂L2～L7的kernel VNF。

·支持key/value的数据结构bpf_map，可以通过用户空间将网络表项（如MAC表、路由表、NAT表等）注入内核的eBPF程序中，eBPF程序会据此进行查找与转发。

·bpf_map中存有一类特殊的映射关系，记录每个eBPF程序的文件描述符，因此不同的eBPF的内部结构如图4-111所示。eBPF程序间能够实现灵活的跳转，可以非常方便地实现服务链。

图4-111　eBPF的内部结构

由于eBPF是一种伪机器代码，其开发的门槛较高，因此eBPF还配套了LLVM等编译工具，支持开发者通过C语言进行eBPF程序的开发。另外，eBPF还提供了代码注入内核前的监测，防止eBPF程序间逻辑冲突或者成环。

2.IOVisor

IOVisor实际上就是PLUMgrid基于eBPF所形成的可编程datapath技术，支持很多类型的kernel VNF（见图4-112），并且可以通过用户空间的VNF Helper来接受外界的控制，为不同的租户组合出不同的VNF Composition Graph，以提供网络连接以及执行其他的网络策略。在IOVisor开源后，PLUMgrid还为其提供了配套的运维和开发工具，不过其自身的kernel VNF并不在开源的范围内。

图4-112　IOVisor架构的简单示意

这里要讨论一个问题，OpenvSwitch也能够提供灵活的pipeline处理能力，其datapath也是工作在kernel中的，通过不同的OpenFlow流表也可以实现很多VNF的功能，那么IOVisor和OpenvSwitch的区别在哪里？

首先，OpenvSwitch只能支持L2、L3以及少数L4的字段，对于一些L4～L7的功能，通常需要转发到用户空间的VNF中去完成，IO context的切换会导致时延的增加，如果VNF采用集中式的部署方式，还会导致hairpin和单点故障等问题。而IOVisor使用的是eBPF技术，它没有对字段的任何限定，可以在内核中直接完成一些L4～L7 VNF的功能，能够大幅地降低时延。同时，VNF可以分布到各个Hypervisor的kernel中，租户虚拟机所在的各个Edge上，都会加载其Virtual Domain中所需的所有kernel VNF，避免了hairpin和单点故障的问题。

不过，OpenvSwitch可以使用用户态datapath来解决上述问题，且结合vhost-user可实现零切换、零拷贝的虚拟机IO。但是eBPF却只能工作在内核态，因此在虚拟机IO这一块不占上风。

其次，即使OpenvSwitch通过P4实现了协议无关，扩展新的字段也需要停止OpenvSwitch，然后对代码进行重新的编译。而IOVisor允许将一个实现了新功能的VNF在线地部署到kernel中，而不用中断datapath中现有VNF的运行。

IOVisor和OpenvSwitch并不构成完全的竞争关系，实际上，在VMware收购PLUMgrid后，OVS社区开始积极地讨论使用eBPF对内核中的odp进行重构，以及使用IOVisor对OVN的数据平面进行整体的重构。总之，eBPF/IOVisor是很酷、很强大的datapath技术，上面所做的介绍十分粗浅，有兴趣的读者可以深入地进行探索。

4.10.3　控制平面设计

Director通过RESTful API与Neutron中的PLUMgrid Driver通信，根据租户不同的需求将kernel VNF发布到相应的Edge/Gateway中，形成Virtual Domain为租户提供连接与服务。Director与Edge/Gateway间的接口是PLUMgrid私有的协议。

当租户的拓扑发生变化时，Director会告诉Edge/Gateway该租户的Virtual Domain中需要哪些kernel VNF（图4-113中的DP），以及这些kernel VNF之间的连接关系是怎样的。当租户启动新的虚拟机实例时，Director会通知相关的Edge/Gateway，将租户Virtual Domain中所需的kernel VNF在线地加载到kernel中，并向对应的Director注册这些kernel VNF。至于kernel VNF中用于处理数据包的表项（如MAC表、路由表、NAT表等），Director会下发给Edge/Gateway的用户空间中的VNF Helper，然后由VNF Helper将表项注入kernel VNF的bpf_map中。

Director还会为不同的kernel VNF生成不同的Control Plane（CP），CP的功能和传统设备中supervisor的功能相同，为kernel VNF提供那些不便于在内核中实现的控制逻辑（比如Bridge CP执行数据包的复制与泛洪，DHCP CP提供DHCP包的处理能力，Dynamic Router CP提供处理路由协议的能力）。某些VNF Helper（如NAT Helper）还可以将Director中VNF CP的部分控制逻辑卸载到Edge/Gateway本地，以减少DP和CP间控制信道上的开销。

图4-113　Director和CP共同作为DP的控制平面

在可用性的设计上，3个Director实例形成一个Director Cluster，3个实例对外表现为一个VIP，一个Director Cluster所控制的区域称为一个PLUMgrid Zone。Director实例间通过keepalived来实现failover，通过数据库来同步配置与状态。当Cluster中1台实例挂掉时，其余的实例会重新启动挂掉的Director服务。当Cluster中有2台（或者3台）Director挂掉时，该Zone即进入Headless Mode，此时DP无法和CP进行交互，不过datapath上仍然能够根据之前的状态继续进行转发。

4.10.4　转发过程分析

数据包在Virtual Domain中转发时，会经过各个kernel VNF的处理，下面会对一些典型的VNF的工作原理进行介绍，帮助读者理解PLUMgrid ONS对于数据包的处理机制。

虚拟机发出的数据包会被kernel中的PEM模块截获，PEM会根据vNIC找到其所属的Virtual Domain，并将其定向给Virtual Domain中与该虚拟相连的第一个kernel VNF进行处理——通常第一个kernel VNF会是Bridge。Bridge VNF由datapath上的Bridge PLUMlet（PLUMlet即为kernel VNF）和Director中的Bridge CP组成，如图4-114所示，Bridge PLUMlet会进行MAC自学习，并根据目的MAC地址进行转发。对于目的MAC地址已知的单播流量，如果目的在本地则直接进行转发，如果目的在远端的Edge上则需要封装VxLAN包，其中tunnel id标识的是目的虚拟机，而不是租户，因此目的虚拟所在的host做完VxLAN解封装后会直接根据tunnel id从相应的vNIC转发出去，这点和NVP/Midonet中的设计是一样的。

对于BUM流量，如果其目的IP地址已知，则转为单播发送（主要针对ARP）；如果目的IP地址未知，则Bridge PLUMlet会将其上报给Bridge CP，由Bridge CP完成数据包的复制，并向其他在同一个Virtual Domain中注册过的Bridge PLUMlet进行泛洪。

图4-114　Bridge VNF对数据包的处理

虚拟机发出的第一个数据包通常会是DHCP Discover的广播。根据上一段中的介绍，Bridge PLUMlet会将这个Discover发送给Bridge CP，Bridge CP会通过这个包学习到源MAC地址和Edge的对应关系，记录下来作为VxLAN的控制信息。然后这个DHCP Discover会被复制并泛洪回Bridge PLUMlet，Bridge PLUMlet发现这是一个从Bridge CP发送的DHCP Discover，认为Bridge Domain已经学习到了VxLAN的控制信息，于是将该DHCP Discover定向到DHCP PLUMlet上，DCHP PLUMlet并不具备处理DCHP包的能力，于是将这个DHCP Discover上报给DHCP CP，DHCP CP将作为DHCP Server将可用的IP地址回复给虚拟机。DHCP后续的交互就是单播了，Bridge将根据目的MAC地址进行转发，如图4-115所示。

跨网段的东西向流量需要经过Router VNF的处理，在两个Bridge的子网间进行路由，如图4-116所示。VM1首先广播ARP请求默认网关的MAC地址，通过对Bridge VNF的介绍可知这个ARP会被转成单播发送给Router PLUMlet，Router PLUMlet不具备处理ARP包的能力，将其上报给Router CP，Router CP单播回复在该子网下接口的MAC地址。收到ARP Reply后，VM1向另一个子网中的VM4发起通信，数据包首先到达Router PLUMlet，然后Router PLUMlet会查询路由表，那么，根据下一跳来改写MAC地址然后发送到下一跳所在的Bridge PLUMlet上。

图4-115　DHCP VNF对数据包的处理

图4-116　Router VNF对数据包的处理

对于发向Internet的南北向流量，通常需要经过NAT的转换。如图4-117所示，NAT PLUMlet会部署在Router PLUMLet的上联口，Router PLUMLet查找路由表后发现这是一个发向Internet的数据包，于是定向给NAT PLUMlet。NAT PLUMlet会查找NAT表，如果源虚拟机配置了Floating IP，那么NAT PLUMlet会直接完成转换并定向给连接了Service VD的Connector。如果源虚拟机没有Floating IP则需要进行端口映射，此时NAT PLUMlet会将数据包上报给user space的NAT Helper，NAT Helper分配好flow资源后更新NAT PLUMlet中的NAT表项，然后就可以处理后续的流量了。这里使用NAT Helper是为了提高NAT的性能，否则从NAT PLUMlet上报Director中的NAT CP上时延太大会严重影响性能。

图4-117　NAT VNF对数据包的处理

如果租户想把路由发布到外网上，就不需要NAT了，此时会通过Dynamic Router CP和外网运行路由协议，Dynamic Router PLUMlets会根据动态学习到的路由表，直接完成Internet流量的转发。

4.11　Plexxi Switch与Control

本节来介绍一家以光SDN为主打的创业公司——Plexxi。Plexxi成立于2010年，和BigSwitch、Pluribus同属于SDN最早的一批探路者。同样，Plexxi也专注于数据中心SDN。和选择Overlay IP进行传输的大部分厂商显著不同的是，Plexxi引入了光传输来处理跨机架的流量，这意味着ToR间的流量是电路交换的，而不用在路径上进行任何的查表转发，因此能够实现真正的“1-Tier”架构。从融资状况来看，截至2015年的D轮，Plexxi一共已经融到了8000多万，2016年年初又收到了Google的投资，未来可期。

4.11.1　整体架构

毫无疑问，Plexxi底层的光技术非常酷，不过其主打的却是“Affinity Driven Network”的概念，控制器会获取到应用的通信模式，并能够据此动态对光通道进行灵活的调整，以满足应用对于网络传输的需求。Affinity一词在IT领域很常见，用于定性地反映两个通信主体间的关系。具体对于网络而言，Affinity可以指对于带宽、时延、安全的需求，实际上就可以理解为应用间通信的网络策略。在Plexxi对于Affinity的设计中，分为Affinity Identifier、Affinity Group、Affinity Link和Affinity Attribute几个基础概念，如图4-118所示，其中Affinity Group是指一组相同类型的应用（如Web、APP、DB等），这些应用通过Affinity Identifier进行标识（如Port、VLAN、MAC、IP等），Affinity Link以及附着在其上的Affinity Attributes代表不同Affinity Group间定性的通信需求（如优化带宽、QoS保障等）。

图4-118　Plexxi对于Affinity的设计

可以看到，Plexxi所提出的Affinity和Cisco ACI中提出的EPG/ANP是类似的，目的都是通过应用间的通信策略来驱动网络。不过相比于EPG/ANP，Affinity更加强调策略的动态性，它可能会随着流量特征的变化而变化。比如，某个ToR Pair间的流量在某一段时间内非常的密集，那么Affinity就会反映出该ToR Pair对带宽需求的提高，并驱动网络为该ToR Pair增加带宽，等到过一段时间后流量下去了，那么Affinity就会反映出该ToR Pair对带宽需求的降低，并驱动网络减少该ToR Pair间的带宽，同时将回收的带宽分配给其他的ToR Pair。

Plexxi的产品架构如图4-119所示。Plexxi Switch作为ToR，通过电口接入服务器，不同Plexxi Switch通过光口互联，以环形拓扑最为简单与常见。Plexxi Control是一个Out-of-Band的控制器，北向上通过Affinity API从外部（如云管理平台、应用可视化分析等）获取应用的通信特征，并将其转化为Affinity Model，南向上通过私有协议与Plexxi Switch交互，获取光连接的拓扑以及MAC的接入位置。结合Affinity Model和底层网络的信息，Plexxi Control中的Algorithmic Fitting Engine能够动态地计算出能满足当前应用需求的光路由，并通过南向接口将其推送给Plexxi Switch。Plexxi Connect能够将应用的API转换为Affinity API，其实现位置没有要求，可以外置也可以放在Plexxi Control本地。

图4-119　Plexxi的产品架构

下面主要对Plexxi Switch和Plexxi Control的关键技术进行解析。至于Plexxi Connect，虽然它对于构建“Affinity Driven Network”的生态而言有重要意义，不过从技术上来讲它只是一个API网关，因此后面不会再对其进行介绍。

4.11.2　数据平面设计

Plexxi Switch目前已经出到了第三代，盒子内部大致的设计模式如图4-120所示。右下角是一块商用Packet Switching ASIC，用于接入服务器，并完成L2/L3的转发。LightRail Interface是光口，通过MTP光纤在交换机间传输流量，CWDM收发器会产生光信号并复用到LightRail Interface上。坐在中间的CrossPoint是光/数字交叉连接器，Packet Switching ASIC的内部端口和CWDM收发器都连接到CrossPoint上，由CrossPoint完成电路交换。另外，Plexxi Switch上还会有一组额外的FlexxPort，这种端口的连接方式很灵活，既可以连接到Packet Switching ASIC，也可以连接到LightRail Interface上。

图4-120　Plexxi Switch的大致设计

对于同一个Plexxi Switch下不同服务器间的流量，直接由Packet Switching ASIC完成本地的L2/L3转发。对于不同Plexxi Switch下服务器间的流量，源交换机在自己的Packet Switching ASIC上完成L2/L3并送到CrossPoint，CrossPoint再将流量送到收发器上，收发器产生某种波长的光信号，然后通过LightRail Interface进行L1的传输。光信号在传输的过程中，可能是一跳的也可能是多跳的，如果是多跳的，那么过路交换机在收到光信号后会直接在不同的LightRail Interface间直接传输，不需要再经过Packet Switching ASIC的处理。目的地所在的交换机从LighRail收到光信号后，会通过CrossPoint送给Packet Switching ASIC，再做L2转发给目的地。

这里需要强调的是，Plexxi Switch并不是有光纤口的L2/L3交换机，而是同时具备L2/L3分组交换和L1电路交换能力的混合式交换机。跨交换机流量的传输路径，决定于CrossPoint上交叉连接的规则以及收发器上波长的使用。多跳路径中的过路设备并不会终结物理层的光信号，因此环形的物理拓扑虽然看起来很简单，但在逻辑上能够提供丰富的物理层光路径。

下面通过图4-121中的例子来做进一步的介绍。Plexxi Switch中1个LightRail Interface上可以跑12个波长，在初始条件下（即在控制器还没有重构光路径时），会有4个波长分给1跳可达的邻居交换机，这些波长被称为Local Channel，剩余的8个波长会以2/2/2/2的方式分配给2/3/4/5跳可达的交换机，这些波长被称为Express Channel。Express Channel即相当于两个非物理直连交换机间的“直达通道”。这种初始的分配方式被称为“5 Degree Chordal Ring”，图4-121是“5 Degree Chordal Ring”的简单示意，图中每个交换机各有两个LightRail Interface，多个交换机连接成一个环形的物理拓扑，两个LightRail Interface分别用于环中east-torwards和west-towards流量的传输，若LightRail Interface上单波长带宽为10Gbps，那么east-torwards方向上与邻居交换机的带宽就是40Gbps，与2/3/4/5跳可达的交换机间的带宽就都是20Gbps，west-towards方向上是完全对称的。

图4-121　“5 Degree Chordal Ring”方式中的波长/带宽分配

当然，除了简单的环形物理拓扑以外，如果Plexxi Switch的LightRail Interface比较多，那么交换机间可以形成非常丰富的物理拓扑。比如图4-122中，左上角和右上角的Multi-Dimensional（右上角可扩展为2D-Torus），左下角和右下角的拓扑叫不上具体的名字，均可用于POD间的互联，每个POD内部则是Leaf-Spine的结构。这些物理拓扑本身就具备了多个维度的连接，再加上逻辑上highly-meshed的光路径，为跨交换机的流量提供了丰富的传输路径。

图4-122　Plexii Switch形成的一些典型物理拓扑

4.11.3　控制平面设计

有了这些传输路径做基础，再加上Packet Switching ASIC、CrossPoint和收发器所提供的可编程能力，控制平面能够对光路径进行非常灵活的调度。不过，Plexxi并没有采用纯集中式的控制，如图4-123所示，其控制平面的架构是层次化的，除了Out-of-Band的Plexxi Control外，还有分布于各个Plexxi Switch本地的Co-Controller。

图4-123　Plexxi层次化的控制平面

1.Co-Controller

Co-Controller的作用是实现分布式的基础转发，而并不负责对光路径进行优化。Co-Controller的主要功能包括邻居的发现、Reactive式的二层转发、路径负载均衡、路径监测以及快速重路由。下面来具体说一下基础转发的过程，考虑一个多跳传输的场景，A向B发起通信：①当A所在的ToR收到ARP Request的广播后，会向所有位于相同VLAN的本地端口以及所有的Local Channel（包括east-towards和west-towards两个方向）进行泛洪，并将该数据包上送给Co-Controller，Co-Controller完成MAC A的学习，并通过一种私有的协议在交换机间发布该MAC地址。②East-towards方向的过路交换机收到该ARP Request后，同样也会向所有和A位于相同VLAN的本地端口以及所有east-towards方向的Local Channel进行泛洪，不过对于从光口传进来的数据包并不会上报给Co-Controller进行学习，而是通过A所在的ToR所宣告的MAC路由来学习。West-towards方向上同理。该过程一直持续下去，最终B会收到该ARP Request。③B把ARP Reply单播出来以后，B所在的ToR会上报给Co-Controller，Co-Controller会通过散列在多条光路径间进行负载均衡，这些路径包括east-towards和west-towards中距离A较近的方向上的Local Channel以及可以直达A所在ToR的Express Channel。

2.Plexxi Control

对应地，Plexxi Control只负责对光路径进行优化，也就是说，即使它宕掉了，也不会影响到基本的互通性。Plexxi Control对光路径进行优化的工作流程为：①收集所有交换机中的信息，主要包括Local Channel和Express Channel的分配以及MAC的接入位置，并根据上述信息形成网络的全局视图。②将Affinity Model和全局视图输入Algorithmic Fitting Engine中，为Affinity Group间的通信计算出符合Affinity Attributes的光路径，这被称为FSAT。③完成FSAT的计算后，剩余的光路径可用于传输那些并没有指定Affinity的流量，Algorithmic Fitting Engine同样会为这部分流量计算出加权的光路径，这被称为PSAT。实际上可以将PSAT理解为对于Co-Controller所实现的基础转发的优化。④FSAT和PSAT是由Plexxi Control自动生成的，对于用户来说属于一种隐式的路径优化。Plexxi Control还提供了一种UDT的方式，允许用户显式地指定传输特定流量的光路径。⑤如果现有的光路径不能满足上述的优化需求，那么Plexxi Controll还会调整CrossPoint和收发器，以对光路径进行重构。⑥上述步骤都完成后，Plexxi Control会将结果主动地推给交换机，并通过2-phase commit来保证一致性。

以光传输为技术主体来构建数据中心网络，这种思路多见于学术界的研究中，Plexxi将其产品化的做法可以说是革命性的，超越了当时甚至是目前工业界对于数据中心网络的理解。不过，与应用的紧密关联使得Plexxi在网络优化领域形成了独到的优势，吸引了一些有相关需求的用户。同时，光传输所带来的高带宽、低时延和稳定性，也使得Plexxi在存储的场景中找到了一些机会。超融合的兴起为Plexxi提供了汇聚自身优势的抓手，于是Plexxi已经开始将重心转向了超融合网络架构的构建。

4.12　Pluribus

本章的内容是关于商用SDDCN解决方案的，不过到目前为止，还并没有对白盒厂家做介绍（BigSwitch主要是卖控制器，不算是白盒厂商），这是因为白盒和SDN实际上并没有必然的联系，虽然印象上两者总是一同出现，实际上白盒只要求转控分离，可编程属于锦上添花，对集中控制不做要求，设备形态上一般是硬件的盒子，协议方面一般都会集成传统的L2/L3协议栈。白盒如果连上控制器就算是SDN的方案，否则通过传统的分布式协议把网络跑通也没有任何问题，很多大型OTT买了（或者自己做了）白盒，主要是看中了白盒在成本和可控性方面的优势，而并不一定是为了要做SDN。

白盒这一块的创业选手有很多，最为人所熟知的是Cumulus和Pica8。Cumulus的侧重点在于传统的协议栈，Pica8的侧重点在于OpenFlow，目前两者发展的都还不错，值得长期的关注。不过从技术上来说，Cumulus和Pica8都只能算是中规中矩，相比之下Pluribus就比较有意思了，虽然它也是只做盒子不做控制器，不过这里还是要花上一节的篇幅，对Pluribus独特的设计理念进行一下介绍。

Pluribus成立于2010年，三个联合创始人中有两个来自Sun，植根于在服务器端强大的软硬件基因，Pluribus提出了“Server Switch”的概念，改变了传统交换机的硬件设计，并通过操作系统Netvisor实现了对Server Switch特色的管理与控制。不过从Server Switch+Netvisor的角度来看，Pluribus严格意义上并不能算是一家白盒厂商，后来Pluribus部分开放了Netvisor形成ONVL（Open Netvisor Linux），支持把ONVL跑在ODM的盒子上，从而正式走进了白盒的圈子。

新颖的技术理念和深厚的技术功底使得Pluribus在资本市场上得到了广泛而持续的关注，2012/2013年C轮4400万美元，2015年D轮5000万美元，2017年年初E轮又得到资方追投的2100万美元。无论从技术还是从市场上来说，Pluribus已经成为一股不可忽视的力量。

4.12.1　Server Switch设计

下面来看技术，首先要对Server Switch的设计进一步的介绍。图4-124展示了传统交换机和Server Switch的内部设计。在传统交换机的架构设计中，通常只看重ASIC的性能和容量，投入CPU、RAM和Disk上的成本十分有限，因此传统的交换机只适合于高速转发，而无法对数据包进行业务层面的处理，也无法对流量的特征产生认识。比如要实现可视化，交换机中ASIC to CPU的带宽有限，数据上来了之后，也没有足够富裕的CPU去做深入的处理，即使做过一些处理，同样没有足够的存储空间能记录下来。因此，在传统的可视化方案中，要么损失精度通过sFlow做采样，要么提高成本通过TAP和Broker把流量引到专用的分析软件中做处理。

Pluribus转换了看待交换机的视角，重新设计了交换机的架构，在网络、计算和存储资源间进行了平衡。交换机中会使用高性能的服务器CPU以及大容量的RAM，以提高交换机内部的计算能力，同时还会配置SSD以及SSD扩展槽，提供T级别的数据存储能力。Broadcom或者Intel商用的Switch ASIC，通过多通道10Gbps PCIe与CPU相连，CPU通过PCIe接口直接控制ASIC上的硬件资源，如L2/L3 Table和TCAM等，从而对转发进行高速、高效、灵活的控制。因此，Pluribus的交换机就可以看作增加了商用ASIC，拥有大量网卡的服务器，不仅能够进行高速转发，同时具备密集型计算以及大容量存储的能力。

图4-124　传统交换机和Server Switch的内部设计

简单归纳一下，Server Switch的设计理念主要包括以下两点：①增强交换机的CPU、RAM和Disk，把一些提供网络附加值的功能植入交换机中。②通过CPU对Switch ASIC进行直接的控制，就像直接控制网卡一样。表4-4给出了Pluribus Freedom数据中心F系列交换机的硬件配置，可以看到基本上是对照着服务器的标准来配备资源的。

表4-4　Pluribus Freedom数据中心F系列交换机的硬件配置

4.12.2　Netvisor设计

硬件之上的操作系统Netvisor是Pluribus最核心的技术，图4-125是Netvisor的设计架构，从下向上依次可分为基础OS层、虚拟化层和应用层。图中绿色的部分是Netvisor自己的东西，基础OS层上主要增加了对于Switch ASIC的控制逻辑，虚拟化层上增加了Fabric-Cluster的机制，应用层上增加了一些二层的逻辑（主要是vLAG实现跨机箱链路聚合）并开放了Netvisor的接口。图中可以看到，Netvisor能够通过KVM/QEMU来提供虚拟机，利用Server Switch中丰富的计算资源，这些虚拟机可以做很多事情，比如OpenStack Control Node、L4～L7 VNF、L2～L7 User App等，实现InNetwork Applications。

图4-125　Netvisor的设计架构

1.基础OS层

Netvisor的基础OS层，即图4-125中的Netvisor OS Independent部分，负责与硬件直接打交道，主要是对Switch ASIC的控制进行了优化。传统交换机中的Network OS往往都是通过芯片厂商提供的SDK来间接控制Switch ASIC，而不是直接对ASIC进行控制，因为经过SDK的抽象后能够降低开发的难度。不过，ASIC SDK的底层实现通常是单线程的，这使得ASIC-to-CPU的实际性能受到了很大的损失。Netvisor则绕过了SDK，将Switch ASIC上的硬件资源直接映射到了OS中，因此得以通过PCIe PIO对ASIC进行直接的控制，包括读写Register、L2/L3 Table和TCAM等，并通过Multi Thread的设计进一步优化了性能，从而大幅度地提高了ASIC-to-CPU的实际性能。上述技术的原型是Sun提出的一种NIC虚拟化技术——Crossbow，Netvisor对其进行了发展，使得Switch ASIC能够被虚拟化并整合进OS中，有兴趣的读者可以参考Crossbow的文献。

凭借高配的CPU和RAM以及高性能的ASIC-to-CPU通道，Pluribus在其交换机中大大地提升了CPU流量处理方面的地位。在Pluribus的交换机中，网络中所有的转发表都存在于CPU中，ASIC只是作为CPU中软转发表的Hardware Cache。流量的首包过来之后，先上送CPU查找软转发表，找到对应的表项后CPU再将其写入ASIC。也就是说，ASIC并不会进行硬件的MAC自学习，而是完全将控制权交给了CPU，这也彰显了Server Switch的设计理念。另外，在进行可视化的时候，Multi Thread的设计使得Netvisor可以实时、全面的读取ASIC的状态，这也为Netvisor中丰富的Analytics特性提供了基础。

Netvisor为应用层提供了丰富的接口形式，比如C、Java、RESTful、Ansible等。Netvisor抽象出了一套与ASIC厂商无关的FDK（Freedom Development Kit），应用不仅能读取网络的状态，还能够通过Netvisor来控制ASIC的行为，这对于防火墙、VPN这些网络应用来说很关键，通过Netvisor FDK就能够在ASIC上实现Hardware Acceleration。另外，Netvisor中还会提供OpenStack和OpenFlow/OVSDB的接口，用于对接外部的第三方SDN控制器。

2.虚拟化层

Netvisor的虚拟化层是最有特色的，它基于Pluribus自研的VCF（Virtualization Centric Fabric）进行实现。VCF在功能上主要可以分为Fabric Automation、Fabric Visibility和Fabric Virtualization三大部分，其中：Fabric Automation能够自动地把Leaf-Spine的Underlay Fabric打通，这依赖于Netvisor中所集成的L2/L3协议栈，包括VLAN、STP、VLAG、OSPF、BGP、ECMP等，以及类似于ZTP的一类自动化技术，实现交换机的Plug与Play。基于Underlay的连通性之上，交换机间会通过一种类似于Server Clustering的技术，Peer-to-Peer地同步网络中所有的状态信息，登录到任何一个交换机上进行show，都可以看到网络中所有的状态信息。同样，在任何一台交换机上所进行的Fabric-Wide的配置或者API调用，都会被同步到Fabric中的其他交换机上，增量的配置将被看作一个Atomic Transaction，以3-phase commit来保证一致性，如果在任意一台交换机上的同步失败，那么所有其他以同步的交换机都会进行回滚。

FabricVisibility能够提供精细的网络可视化，主要包括以下方面。

1）Application Telemetry：通过读取ASIC上的状态，Netvisor可以全面地记录网络中Flow/Connection的信息，比如流量经过的路径、端到端的延时、生存时间等，对原始的信息进行加工后，Netvisor还会提取出更直观的信息，比如应用的Top Talker、Client-Server的流量源分布等。这些实时以及历史的信息，可以被无损地存入SSD或者Fusion IO中，为Trouble Shooting和审计提供可靠的数据基础，Pluribus将其称为“Time Machine”。

2）vPort：Netvisor会将Fabric中所有的endpoint都抽象为vPort，为vPort记录Switch_ID、MAC、VLAN、IP、Status、Hostname、Migration_Counter等方面的信息，并在所有交换机间进行同步vPort Table，因此每一个交换机都知道任意一个vPort的信息，也就得以通过软件形成L2/L3的转发表。当流量首包上报给CPU后，查询vPort Table并将相应的表项更新到ASIC中，Leaf还会进行ARP代答以消除ARP广播。

3）vFlow：通过TCAM提供flow-based forwading的能力，支持匹配In_Port、MAC、IP、IP、掩码、端口号等常用的L1-L4字段，提供drop、to_port、to_cpu、copy_to_cpu、set_vlan、set_tunnel等处理动作。通过vFlow可以实现很多功能，比如把某些可疑的流量copy_to_cpu，然后在Netvisor中用wireshark逐包地去分析，服务链也可以通过vFlow来实现。vFlow可以在本地或者全局范围生效，如果是全局生效那么完成同步后，所有的交换机都能够执行该vFlow的策略。

Fabric Virtualization实现的是网络的虚拟化，主要包括以下方面。

4）VNET：Netvisor通过VNET来隔离不同的逻辑网络，不同的VNET可以看作不同的Fabric Container。VNET间不仅是一个控制层面的概念，VSphere对服务器的硬件资源进行虚拟化并在此基础之上提供虚拟机，而Netvisor则能够对Switch ASIC上的硬件资源，包括Port、VLAN、VxLAN和L2/L3 Table等，进行虚拟化并在不同的VNET间进行分配并隔离。

5）Netvisor Machine：利用Netvisor中集成的KVM/Qemu在交换机上开通虚拟机，实现InNetwork Application，可提供vRouter、NAT、Load Balancer等VNF，以及Open-Stack Control Node、OpenFlow Controller等。另外，也可以通过vFlow把流量mirror到这些虚拟机中，在交换机本地进行应用的可视化。和交换机外接的endpoint一样，Netvisor Machine也可以被划分到某个VNET中。

3.VCF-IA

Pluribus还为Netvisor设计了专用的监控软件VCF-IA，它通过Netvisor提供的API把数据读取出来，实现了数据呈现、模糊搜索、用户自定义分组、报表和告警等功能。由于Netvisor会在所有的交换机间同步网络信息，因此VCF-IA只需要对接任意一台交换机就可以拿到所有的数据，如图4-126所示。

图4-126　Pluribus VCF-IA的部署

4.12.3　再议数据中心SDN

Pluribus作为一家白盒厂商，在其产品线中并没有提供控制器，而且在Pluribus所提倡的纯分布式SDN的架构下，确实也找不到控制器的位置。在前面关于Netvisor设计的内容中，处处体现了Pluribus对于数据中心SDN的独到理解。

1）在大家普遍接受的SDN架构中，控制器位于设备的远端，通过TCP/IP与设备交互一些控制信令来控制转发，这些信令先到设备本地的Agent，Agent再转换为ASIC SDK，这一交互过程中的时延很大，甚至会超过数据中心内部一些短流的生存时间，此时再精巧的控制逻辑都没有实际的意义了。因此，Pluribus的想法就是把SDN的控制逻辑放在设备本地，依靠对ASIC-to-CPU性能的优化，为控制提供极限的反应速度。

2）集中式的SDN控制器通常需要连接大量的设备，因此在扩展性方面存在一定的问题，把控制逻辑回归到设备中，再通过Peer-to-Peer机制来做集群，可以完全消除网络中的单点。如果需要外接第三方SDN控制器，控制器也只需要和一台交换机进行连接即可，当然这只是理论上的设想，控制器中的绝大部分应用在这种部署方式下是没有办法正常工作的。

3）通过Netvisor FDK为应用提供了OS BareMetal的编程能力，应用可以通过Netvisor来控制Switch ASIC的行为，实现InNetwork的SDN Programmability。vFlow提供了类似于OpenFlow的能力，通过Netvisor的API操作vFlow，其效果和通过FlowMod操作OpenFlow流表并没有本质区别。

技术就介绍到这里了。Pluribus做Server Switch的灵感来自于Sun，可以算得上是最早将Server Switch产品化的公司了，这个理念放在今天来看，和超融合有异曲同工之处，不论市场未来究竟如何，在技术创新上来说是要竖大拇指的。如果要笔者来评DataCenter Networking的魔力象限，Pluribus毫无疑问应该在Visionaries中榜上有名。

4.13　本章小结

商用SDDCN的竞赛中，仍然是VMware NSX和Cisco ACI领跑，其他厂商目前还是处于跟随的状态。虽然都是围绕着Overlay的思路在做，不过有的厂家偏向于软件，有的则偏向于硬件。相比之下，一些创业公司的技术方案则会更加新颖，但技术只是实力的一部分，最终能否成功上位主要还得看市场方面的运作。商用方案要考虑的方面太多，本章只选择了各方案中的关键性设计来做介绍。另外，本章的内容中涉及了很多笔者的个人观点，仅供参考。

第5章　开源SDDCN：OpenStack Neutron的设计与实现

开源在IT界早已蔚然成风，优秀的开源软件正在被越来越多地应用到生产环境当中。对于开源云计算领域，OpenStack的网络组件经过多年打磨，在功能上已趋于收敛，OpenStack Neutron得到广泛应用。SDN领域的两大开源控制器（OpenDaylight和ONOS）也正在逐步实现数据中心网络的基础功能，虽然在稳定性上有所欠缺，不过未来的发展是乐观的。开源部分的内容比较多，需要拆开来说，本章先来介绍OpenStack Neutron。

本章所涉及的代码以neutron-stable-ocata版本为准。

5.1　网络基础

OpenStack是主流的开源云计算平台，目前已经有了很多大规模的商用案例，所有与云相关的，无论是商用软件还是开源平台都在积极寻求与OpenStack的对接。在网络这一块，OpenStack经历了由Nova-network到Quantum再到Neutron的演进过程，各个版本网络的特征如下：

1）Nova-network是隶属于Nova项目的网络实现，它利用了Linux Bridge（早期，目前也支持OVS）作为交换机，具备Flat、Flat DHCP、VLAN 3种组网模式。

优点与缺点：优点是性能出色，工作稳定，支持多主机部署以实现HA；缺点是网络模块不独立，功能不够灵活，组网模式也比较受限。

2）Quantum作为独立的网络管理项目出现在F版本中，除了Linux Bridge外还支持OVS，以及其他商业插件，组网模式增加了对GRE和VxLAN两种Overlay技术的支持。

优点与缺点：优点是功能灵活，支持大二层组网；缺点是集中式的网络节点缺乏HA，而且各厂商插件无法同时在底层网络中运行。

3）Neutron出现在H版本中，它是因为Quantum和一家公司的名称冲突而改名的。Neutron对Quantum的插件机制进行了优化，将各个厂商L2插件中独立的数据库实现提取出来，作为公共的ML2插件存储租户的业务需求，使得厂商可以专注于L2设备驱动的实现，而ML2作为总控可以协调多厂商L2设备共同运行。Neutron继承了Quantum对大二层的支持，还支持L2 PoP、DVR、VRRP、HA等关键功能，集成了很多L4～L7的网络服务。

优点与缺点：优点是开始引入SDN思想，功能上更为丰富，网络兼容性强；缺点是不够稳定，HA机制仍缺乏大规模商用的检验。

从应用场景来看，Nova-network组网模式较为简单，一些复杂的网络需求无法实现（比如两个公司合并，有大量IP地址重叠的VM要迁移到一个平台上，而且要求迁移后都要用原来的IP）。不过由于其简单稳定的特点，仍然可用于中小型企业的生产环境中。Quantum属于一个过渡版本，实际的部署较为少见。Neutron可以支持大二层，在多个版本的演进后，功能上也更为丰富，不过原生Neutron的稳定性和HA还不够成熟，在生产环境中仍然需要进行功能裁剪和二次开发。

5.1.1　网络结构与网络类型

下面开始介绍Neutron的网络结构，对于Nova-network和Quantum本节暂不讨论。首先需要对Neutron中的3类节点和3种网络进行介绍，如图5-1所示。

（1）3类节点

控制节点实现镜像、块存储、身份认证、前端等服务，运行Nova-compute的调度模块以及Nova api-server。计算节点实现Nova-compute，以及Neutron的一些Agent。网络节点实现Neutron的一些Agent。注意，由于OpenStack为分布式架构实现，因此Neutron-server既可以运行在控制节点中，也可以运行在网络节点中。

（2）3种网络

OpenStack内部模块之间的交互发生在管理网络，虚拟机之间的通信发生在数据网络，而External Network/API Network网络是连接外网的，无论是用户调用Openstack API，还是虚拟机与外网间的互通都需要经过这个网络。目前OpenStack通常采用Out-of-Band方式进行部署，管理网络与另外两个网络是独立的，也不涉及复杂的组网需求，本章后续内容的分析只针对数据网络与External Network/API Network网络。

Neutron的数据网络支持3种常见的网络类型，包括Flat、VLAN和Overlay。

（1）Flat类型

Flat类型最为简单，所有的虚拟机共用一个私有IP网段，IP地址在虚拟机启动时完成注入，虚拟机间的通信直接通过HyperVisor中的vBridge/vSwitch进行转发，公网流量在该网段的网关上进行NAT。

图5-1　Neutron中的3类节点和3种网络

（2）VLAN类型

VLAN类型引入了多租户机制，虚拟机可以使用不同的私有IP网段，一个租户可以拥有多个IP网段。虚拟机IP通过DHCP获取。网段内部虚拟机间的通信直接通过HyperVisor中的vBridge/vSwitch进行转发，同一租户跨网段通信可以通过vRouter做路由，公网流量在该网段的网关上进行NAT。

（3）Overlay类型

Overlay类型利用隧道技术进行隔离和传输，相比于VLAN类型有以下改进。

1）租户数量从4096增加到1600万；

2）租户内部的二层通信可以跨越底层的IP网络，虚拟机能够迁移到任意位置；

3）一般情况下，不需要对底层网络进行任何额外的配置。

5.1.2　VLAN网络类型中流量的处理

有了以上知识作为基础，就可以分析虚拟机的通信过程了。以下将通过两张图来分析Neutron中的VLAN网络类型，HyperVisor中的网络设备以OpenvSwitch为例。Overlay类型与VLAN类型的区别只在于将图5-2中的br-eth1替换成br-tun即可，具体Overlay网络中的转发如何完成，后面的小节中会做更为细致的介绍。

图5-2所示为计算节点上的网络实现。以虚拟机发出流量的方向为例，从源虚拟机开始分析。

图5-2　VLAN网络类型在计算节点的实现

1）虚拟机发出的流量，经过QEMU注入到TAP设备中。TAP设备是一种软件实现的以太网卡，此处配合QEMU模拟了虚拟机网卡的硬件功能。

2）TAP设备并不是直接连接到OVS上的，而是通过Linux Bridge中继到OVS br-int上的，其原因在于OVS无法实现Linux Bridge中一些带状态的安全规则，而这些规则往往用于以虚拟机为单位的安全组功能的实现。qbr是quantum bridge的缩写，Neutron中沿用了Quantum的叫法。

3）qbr与br-int间的连接通过Linux的veth-pair技术实现，qvb代表quantum veth bridge，qvo代表quantum veth ovs。veth-pair用于连接两个虚拟网络设备，它们总是成对出现以模拟虚拟设备间的数据收发，其原理是反转通信数据的方向，需要发送的数据会被转换成需要收到的数据重新送入内核网络层进行处理。veth-pair与TAP的区别可以简单理解为veth-pair是软件模拟的网线，而TAP是软件模拟的网卡。

4）br-int是计算节点本地的虚拟交换设备，完成流量在本地的处理：本地虚拟机送入的流量标记为本地VLAN，送到本地虚拟机的流量去掉本地VLAN，本地虚拟机间的二层流量直接在本地转发，本地虚拟机到远端虚拟机，网关的流量由int-br-eth1送到OVS br-eth1上（在Overlay模型中送到OVS br-tun上）。无论是VLAN模型还是Overlay模型，由于br-int上VLAN数量的限制，计算节点本地最多支持4096个的租户。

5）br-int与br-eth1间的连接在图5-2中通过veth-pair技术来实现。从Juno版本开始，使用OVS patch代替veth-pair作为默认的网桥连接方式，以便提高性能。

6）br-eth1将该计算节点与其他计算节点、网络节点连接起来，根据Neutron-server中OVS Plugin的指导，完成流量送出、送入本地前的处理：根据底层物理网络租户VLAN与本地租户VLAN间的映射关系进行VLAN ID的转换（在Overlay网络类型中此处进行隧道封装，并进行VNI与本地租户VLAN ID间的映射）。由于受底层物理网络中VLAN数量的限制，所以VLAN模型最多支持4096的租户，而Overlay网络类型中，24位的VNI最多支持1600万的租户。

7）br-eth1直接关联物理宿主机的硬件网卡eth1，通过eth1将数据包送到物理网络中。在Overlay模型中br-tun通过TUN设备对数据包进行外层隧道封装并送到HyperVisor中，内核根据外层IP地址进行路由，从硬件网卡eth1上将数据包送到物理网络中。TUN与TAP的实现机制类似，区别在于TAP工作在二层，而TUN工作在三层。

图5-3所示为网络节点上的网络实现。以流入网络节点的流量方向为例，从底层物理网络流量通过eth1进入br-eth1（在Overlay模型中为br-tun）开始分析。

图5-3　VLAN网络类型在网络节点的实现

1）br-eth1将网络节点与计算节点连接起来，完成流量送入网络节点前的处理：根据底层物理网络租户VLAN与本地租户VLAN间的映射关系进行VLAN ID的转换（在Overlay网络类型，此处进行解封装，并进行VNI与本地租户VLAN ID间的映射）。注意，虽然同一租户在底层物理网络上的VLAN ID（在Overlay模型中为VNI）是唯一的，但是在网络节点与计算节点，以及不同计算节点中同一租户对应的本地VLAN ID并不要求不同。另外由于网络节点也要在br-int上使用本地VLAN，而租户跨网段流量与公网流量都要经过网络节点，因此使用单个网络节点时，Neutron最多能支持4096租户，可采用部署多个网络节点的方式来解决这一问题。

2）送入网络节点的流量，由br-eth1（br-tun）通过veth-pair送给br-int，br-int连接了本地不同的namespace，包括实现DHCP Agent功能的dnsmasq，以及实现路由功能的vRouter。dnsmasq负责给对应租户的虚拟机分配IP地址，而vRouter负责处理租户内跨网段流量以及公网流量。不同的租户有不同的dnsmasq和vRouter实例，因此不同租户间可以实现IP地址的复用。

3）vRouter namesapce通过qr（quantum router）接口接收租户内跨网段流量以及公网流量，在namespace的协议栈中对跨网段流量进行路由，改写MAC地址并通过相应的qr接口向br-int送出数据包。公网流量在vRouter namespace的协议栈中进行NAT，并通过qg（quantum gateway）接口发送给OVS br-ex。

4）br-ex通过关联物理宿主机的硬件网卡eth1将流量送至Internet路由器。

5）br-int与br-ex间的连接，主要是保证在vRouter出现问题时流量能够不中断转发，不过实际上很少遇到这一问题。

5.2　软件架构

上一节交代了Neutron基本的组网情况，这一节来看一看Neutron的软件架构。

5.2.1　分布式组件

在架构设计上，Neutron沿用了OpenStack的分布式思想，各组件之间通过消息队列进行通信，使得Neutron中的各个组件甚至各个进程都可以运行在任意节点上，如图5-4所示。目前Neutron提供了众多的插件与驱动，基本上可以满足各种部署的需要，如果这些还难以支撑实际所需的环境，那么可以方便地在Neutron框架下扩展插件或驱动。

图5-4　Neutron的分布式组件

在图5-4中，除了消息机制以外还涉及5类Neutron组件，Neutron Server、Neutron Agent、Neutron Plugin、Neutron Database、Neutron Provider，下面先对这几个组件的作用进行简单介绍，再根据这几个组件间的交互来介绍Neutron主体代码的实现。

1）Neutron Server可以理解为一个专门用来接收Neutron RESTful API调用的服务器，然后负责将不同的RESTful API分发到不同的Neutron Plugin上。

2）Neutron Plugin可以理解为不同网络功能实现的入口，各个厂商可以开发自己的插件。Neutron Plugin接收Neutron Server分发过来的RESTful API，向Neutron Database完成一些信息注册，然后将具体要执行的业务操作和参数通知给自身对应的Neutron Agent。

3）Neutron Agent可以直观地理解为Neutron Plugin在设备上的代理，接收相应Neutron Plugin通知的业务操作和参数，并转换为具体的Neutron Provider操作。当Neutron Provider发生问题时，Neutron Agent会将情况通知给Neutron Plugin。

4）Neutron Provider，即为实际执行功能的网络设备，一般为虚拟交换机（OVS或者Linux Bridge），少数方案中使用虚拟路由器。

5）Neutron Database，顾名思义就是Neutron的数据库，一些业务相关的参数都存在这里。

5.2.2　Core Plugin与Service Plugin

在开始分析代码之前，还需要先对Neutron Plugin进行一些更为详细的介绍，Neutron Plugin分为Core Plugin和Service Plugin两类。

（1）Core Plugin

在Neutron中即为ML2（Modular Layer 2），负责管理L2的网络连接。ML2主要包括Network、Subnet、Port 3类核心资源，对这3类资源进行操作的RESTful API被Neutron Server看作Core API，由Neutron原生提供支持，见表5-1。

表5-1　Core Plugin中的核心资源

（2）Service Plugin

Service Plugin即为除Core Plugin以外其他的插件，包括l3 router、firewall、load balancer、VPN、metering等，主要实现L3～L7的网络服务。这些插件要操作的资源比较丰富，对这些资源进行操作的RESTful API被Neutron-server看作扩展API，需要厂家自行进行扩展。

上一节曾经提到，Neutron对Quantum的插件机制进行了优化，将各个厂商的L2插件中独立的数据库实现提取出来，作为公共的ML2插件存储租户的业务需求，使得厂商可以专注于L2设备驱动的实现，而ML2作为总控可以协调多厂商的L2设备共同运行。在Quantum中，厂商都开发各自的Service Plugin，它们不能兼容而且开发重复度很高，于是在Neutron中就设计了ML2机制，使得各厂商的L2插件完全变成了可插拔的，方便L2中network资源的扩展与使用。

ML2作为L2的总控，其实现包括Type和Mechanism两部分，每部分又分为Manager和Driver，如图5-5所示。Type指的是L2网络的类型（如Flat、VLAN、VxLAN等），与厂商实现无关。Mechanism Manager是ML2规范的，而Mechanism Driver则是各个厂商根据自己设备机制来实现的。

图5-5　ML2结构示意

Neutron最早出现在Havana版本中，这一版只有少数厂商将L2驱动迁移到了neutron.plugins.ml2.driver目录下，其余大部分厂商仍然留在neutron.plugins下，L2和其他的Service Plugin都混在一个文件中。到了Kilo版的时候，大部分厂商都将L2的驱动转移到了neutron.plugins.ml2.driver下。不过从Liberty版开始，厂商又开始将ML2 Mechanism Driver开始向外迁出，和自己其他的Service Plugin并列，一起形成和Neutron级别平行的networking-XXX。因此，在目前的版本中，neutron.plugins.ml2.driver目录下只剩下了几个通用的Mechanism Driver，而不见了厂商驱动的影子。表5-2所示为Ocata版中neutron.plugins.ml2.driver目录下的几个Mechanism Driver，以及它们各级所具备的能力。

表5-2　Ocata中ML2原生的Mechanism Driver

除了ML2以外，Neutron中还有其他很多的Service Plugin，比如L3、LBaaS、FWaaS、QoS等，从目录结构上来看，这些Service Plugin通常都会包括一些开源实现的driver，而厂商的私有实现目前都放在Neutron外面的networking-XXX中了，如图5-6所示。

图5-6　一些典型的Neutron Service Plugin

5.3　WSGI与RPC的实现

一般而言，Neutron Server和各Neutron Plugin部署在控制节点或者网络节点上，而Neutron Agent则部署在网络节点上和计算节点上。本节先来介绍控制端Neutron Server和Neutron Plugin的实现，在本章的5.5节将对设备端OVS Agent的实现进行介绍。

从Neutron Server的启动开始说起。Neutron Server的启动入口在neutron.cmd.eventlet.server.__init__中，main（）启动了两个服务，第一是启动WSGI服务器作为Neutron API Server，用于监听Neutron RESTful API；第二是启动Core Plugin和各个Service Plugin的RPC listener，用于Plugin与Agent间的通信，两类服务作为“绿色线程”并发运行。从SDN的角度来看，WSGI负责Neutron的北向接口，而Neutron的南向通信机制主要依赖于RPC来实现。当然，不同厂商的Plugin也可能使用其他的南向通信机制。

==================== neutron.cmd.eventlet.server.__init__ ======================
def main(): // 由命令行neutron-server start触发
server.boot_server(_main_neutron_server)
def _main_neutron_server():
if cfg.CONF.web_framework == 'legacy':
wsgi_eventlet.eventlet_wsgi_server()
============= neutron.server.wsgi_eventletdef eventlet_wsgi_server =============
def eventlet_wsgi_server():
neutron_api = service.serve_wsgi()// 启动WSGI服务器
start_api_and_rpc_workers() // 将API server、Core/Service
// Plugin作为“绿色线程”运行

5.3.1　Neutron Server的WSGI

首先来介绍WSGI的机理。启动WSGI服务器，会执行到neutron.service的_run_wsgi方法，该方法中加载了各类Neutron资源，并实例化WSGI服务器，然后启动WSGI的socket，这样Neutron Sever就开始监听Neutron RESTful API了。

=============================== neutron.service ================================
def _run_wsgi():
app = config.load_paste_app() // 加载Neutron资源
return run_wsgi_app()
def run_wsgi_app():
server = wsgi.Server() // 实例化WSGI服务器
server.start() // 启动WSGI的Socket
return server

Load_paste_app这个方法属于WSGI的paste deployment，用于发现、配置、连接WSGI应用和服务器。对于Neutron来说就是在Neutron Server和各类Neutron资源间建立关系，使得Neutron Server能够将对A plugin中资源进行操作的RESTful API分发给A plugin，将对B plugin中资源进行操作的RESTful API分发给B plugin。Neutron的paste deployment配置文件为etc.neutron.api-paste.ini，如下所示。

========================= etc.neutron.api-paste.ini ==========================
[filter:extensions]
paste.filter_factory = neutron.api.extensions:plugin_aware_extension_middleware_factory
[app:neutronapiapp_v2_0]
paste.app_factory = neutron.api.v2.router:APIRouter.factory

Extension API中的URL与Extension资源间的关联由neutron.api.extensions.plugin_aware_extension_middleware_factory方法来完成。Filter_factory返回neutron.api.extensions中的ExtensionMiddleware，ExtensionMiddleware负责生成Extension API的URL和Extension资源的Application化。

Core API中的URL与Core资源间的关联由neutron.api.v2.router.APIRouter来完成。APIRouter类在实例化过程中，首先实例化并加载Core Plugin和各个Service Plugin（注意，Service Plugin的加载也是在APIRouter实例化时完成的），然后生成Core API的URL，最后将Core资源Application化为Controller实例，Controller实例负责RESTful资源的增删改查。这样当WSGI服务器收到RESTful API请求后，就能够根据请求中的URL找到资源的Controller，然后Controller会自动拼接字符串，得到并调用相应的Core Plugin方法，如所请求操作的资源是network，Action是“Create”，则应该调用的Core Plugin方法就是“create_network”。

默认配置中Neutron的Core Plugin为ML2（neutron.plugins.ml2.plugin）。ML2在执行create_network方法时，首先向Neutron-database注册，然后发布一个资源为network，event为AFTER_CREATE的事件（neutron.plugins.ml2.rpc.AgentNotifierApi会订阅这个事件，并向L2的各个Agent发送create_network的RPC），通过Mechanism Manager将该network的参数传给底层各个Mechanism Driver，Mechanism Driver再具体进行执行create操作。

========================= neutron.plugins.ml2.plugin ==========================
class Ml2Plugin()
def create_network():
result, mech_context = self._create_network_db()
// 向DB进行注册，为该network分配一些参数，并完成precommit验证
registry.notify(resources.NETWORK, events.AFTER_CREATE, …)
// 发布事件，触发agent上进行create_network
try:
self.mechanism_manager.create_network_postcommit()
// 调用各个mechanism driver的create_network_postcommit方法
return result

关于Neutron-server北向的WSGI部分就介绍到这里，RESTful API中业务请求在网络设备中的落实主要通过RPC机制来完成。

5.3.2　Neutron Plugin与Neutron Agent间的RPC

控制端Neutron Plugin与设备端相应Neutron Agent间的通信一般由RPC机制实现。在开始进入Neutron RPC服务代码分析前，需要先简单地了解一下RPC。

在OpenStack组件内部RPC机制的实现默认是以AMQP作为通信模型的。AMQP是用于异步消息通信的中间件协议，有4个重要的概念。

1）Exchange：根据Routing key转发消息到不同的Message Queue中。

2）Routing key：用于Exchange中，判断哪些消息需要发送对应的Message Queue。

3）Publisher：消息发布者，将消息发送给Exchange并指明Routing Key。

4）Consumer：消息接收者，从Message Queue中获取消息，并在本地执行。

总体来说，消息发布者将Message发送给Exchange并且说明Routing Key。Exchange根据Message的Routing Key进行路由，将Message正确地转发给相应的Message Queue。Consumer将会从Message Queue中读取消息。

Publisher可以分为4类：Direct Publisher发送点对点的消息，Topic Publisher采用“发布订阅”模式发送消息，Fanout Publisher发送广播消息，Notify Publisher同Topic Publisher，发送与通知相关的消息。类似地，Exchange可以分为3类：Direct Exchange根据Routing Key进行精确匹配，只有对应的Message Queue会接收到消息；Topic Exchange根据Routing Key进行模式匹配，只要符合模式匹配的Message Queue都会收到消息；Fanout Exchange将消息转发给所有绑定的Message Queue。

了解了基础知识以后，再来看代码。之前提到过，启动Core Plugin和各个Service Plugin的RPC listener也是在neutron.cmd.eventlet.server.__init__的main（）方法中触发完成的。

===================== neutron.cmd.eventlet.server.__init__ =====================
def main(): // 命令行neutron-server start触发
server.boot_server(_main_neutron_server)
def _main_neutron_server():
if cfg.CONF.web_framework == 'legacy':
wsgi_eventlet.eventlet_wsgi_server()
======================= neutron. server.wsgi_eventlet =========================
def eventlet_wsgi_server():
start_api_and_rpc_workers()
def start_api_and_rpc_workers():
try:
worker_launcher = service.start_all_workers()
=============================== neutron.service ================================
def start_all_workers():
workers = _get_rpc_workers() + _get_plugins_workers()
// 分别为Core Plugin和各个Service Plugin实例化rpc_worker
return _start_workers() // 该方法中调用oslo_service中的ProcessLauncher启动
// 各个Plugin的rpc listener，这里不做深入的代码分析

Neutron中控制端Neutron Plugin和设备端相应的Neutron Agent间的RPC通信是单向异步的，在plugin和agent上都要开启Publisher和Consumer。由于同一类Agent往往不止一个，因此Neutron的RPC采用“发布订阅”模式在Plugin和Agent间传递消息，在Publisher和Consumer实例化时需要指定全局唯一的Topic（在neutron.common.topics中定义）。

Neutron中Publisher类的命名规范为**AgentNotifierApi，它们的实例可以向特定的Consumer发送消息，Consumer接收到消息后，通过dispatcher解封装，调用**RpcCallBacks在本地执行消息体。Core Plugin和Service Plugin在构造函数执行过程中（在APIRouter的实例化时调用）实例化了AgentNotifierApi，其RpcCallBacks的实例化由Plugin的start_rpc_listener完成，start_rpc_listener是在neutron.service执行start_all_workers时调用的，见上面的代码。

5.4　虚拟机启动过程中网络的相关实现

正确地接入虚拟机，是网络能够发挥作用的前提。因此在介绍Neutron所提供的各种网络服务之前，这里需要插入一个小节，介绍一下虚拟机在启动过程中，是如何获取MAC、IP地址，以及如何接入网络的。

5.4.1　虚拟机的启动流程

虚拟机的启动命令通常来自于控制节点命令行的nova boot，该命令被组装成RESTful API发送到Nova api server。Nova api server与Neutron Server做的是一样的事情：接收RESTful请求，运行一些调度机制，计算出虚拟机部署的位置，然后通过RPC与相应计算节点上的Nova-compute进行通信，而启动虚拟机的实际工作由Nova-compute完成。当然，以上过程与网络并没有什么关系，这里不再分析具体的实现机制。

假定Nova-compute已经通过RPC收到了开始工作的命令，那么就从这里开始进行代码分析。在此之前，先看一看OpenStack组件层面的调用流程，如图5-7所示，图中①～⑤的工作内容依次为：①Nova请求Port资源；②Neutron生成Port资源；③Neutron将Port相关信息通知给dhcp agent；④dhcp agent将Port相关信息通知给dhcp server（dnsmaq）；⑤虚拟机被拉起并通过相应的Port接入网络。

虚拟机启动后就会发送DHCP Discover，dnsmaq会分配给虚拟机一个IP地址，这样就可以开始通信了。下面来看具体实现过程。在开始具体的代码解读之前，先给出代码的主体思路。

图5-7　虚拟机启动的工作流

1）Nova-compute向Neutron Server发送create_port的RESTful API请求，生成新的Port资源。

2）Neutron Server收到该RESTful请求，通过APIRouter路由到ML2的create_port方法。在该方法中，获得了Neutron-database新生成的Port，并通知ML2 Mechanism Driver该Port的生成。

3）Nova-compute向Neutron发送update_port的RESTful API请求。

4）Neutron Server收到该RESTful请求，通过APIRouter路由到ML2的update_port方法。在该方法中，Neutron-database更新该Port的状态，并根据ML2 Mechanism Driver的不同，决定后续的处理：若Mechanism Driver为hyperv/linuxbridge/ofagent/openvswitch，则需要通过ML2的update_port方法执行RPC远程调用update_port；对于其余的Mech-anism Driver，ML2的update_port方法调用其自身的update_port_postcommit方法进行处理。这些Mechanism Driver可能使用非RPC方式与自身的Agent通信（如RESTful API、NETCONF等）。

5）ML2执行完update_port方法后，Port资源在WSGI中对应的Controller实例通过DhcpAgentNotifyAPI实例RPC通知给网络节点上的DHCP Agent（也可能通过一些调度机制通知给分布在计算节点上的DHCP Agent）。

6）DHCP Agent收到该RPC通知后，通过call_driver方法将虚拟机中MAC与IP的绑定关系传递给本地的DHCP守护进程dnsmaq。

7）Nova-compute通过Hypervisor Driver的spawn方法将虚拟机接入网络，然后启动虚拟机。

5.4.2　Nova请求Port资源

首先，Nova-compute接收远端的RPC调用，入口方法为nova.compute.manager的build_and_run_instance方法。其主要逻辑是先调用_build_resource方法为虚拟机分配网络和存储资源，然后调用相关hypervisor driver的spawn方法启动虚拟机，为虚拟机分配网络资源，它是以_build_networks_for_instance方法为入口的。在这个方法中会得到虚拟机在宿主机中的MAC地址集合，生成虚拟机的DHCP选项，然后调用_allocate_network方法获取虚拟机的网络参数。

从_allocate_network方法跳入_allocate_network_async方法，尝试获取虚拟机的网络参数。进入nova.network.neutronv2.api的allocate_for_instance方法负责与Neutron-server进行RESTful API通信，以获取虚拟机相应的网络资源。主要逻辑是，先create_port，并过滤掉已经被使用过的MAC地址，然后再update_port，最后包装成一个NetworkInfo返回其网络参数。

======================== nova.network.neutronv2.api. ==========================
class API():
def allocate_for_instance()
// create_port
requests_and_created_ports = self._create_ports_for_instance()
available_macs = _filter_hypervisor_macs() // 过滤已经用过的MAC地址
ordered_nets, ordered_ports, preexisting_port_ids, update port, created_port_ids = self._update_ports_for_instance()

到这里，Nova-compute就完成了Port资源的申请，总结起来就是接收业务请求的虚拟机参数，然后将涉及网络的请求通过RESTful API发送给Neutron Server。

5.4.3　Neutron生成Port资源

Port是3种核心资源中的一种，Neutron Server通过APIRouter将create_port或者update_port请求路由到ML2 plugin中（参考5.3.1节），假设Port是第一次申请，那么要先进行create操作再更新。

1.ML2对create_port的处理

收到create_port的RESTful API后，由ML2（neutron.plugins.ml2.plugin）执行create_port方法。该方法的主要工作为：①在Neutron-database中新建该Port的信息；②将请求交给Mechanism Driver去完成Port在网络中的部署；③进行port_binding。

========================= neutron.plugins.ml2.plugin. =========================
class Ml2Plugin()
def create_port():
result, mech_context = self._create_port_db()
registry.notify(resources.PORT, events.AFTER_CREATE, …)
try:
self.mechanism_manager.create_port_postcommit()
try:
bound_context = self._bind_port_if_needed()

首先，来看ML2调用的数据库方法create_port_db。Ml2Plugin继承了neutron.db.db_base_plugin_v2.NeutronDbPluginV2，并会执行NeutronDbPluginV2类中的create_port_db方法，该方法主要做了两个事情：①分配可用的MAC地址，如果参数中的MAC地址可用，那么为其分配该MAC地址，如果不可用则DB生成一个可用的MAC地址。②在虚拟机所在租户子网的地址池中获取IP地址。然后会通过mechanism_manager的create_port_precommit方法将create_port操作提交给各个Mechanism Driver。precommit相当于两阶段提交的第一阶段，校验各个Mechanism Driver是否有能力进行create_port操作，如果不能进行该操作，则需要对数据库进行回滚。

========================= neutron.plugins.ml2.plugin. =========================
class Ml2Plugin()
def _create_port_db():
session = context.session
with session.begin(subtransactions=True):
port_db = self.create_port_db()
// 为虚拟机分配MAC和IP，并将虚拟机信息写入数据库
self.mechanism_manager.create_port_precommit()
// 提交create_port给各个mechanism driver，如果出错需进行回滚

Precommit成功后，会跳出来发布一个resource为Port，event为AFTER_CREATE的事件，neutron.plugins.ml2.rpc.AgentNotifierApi会订阅这个事件，并向底下的各个L2 agent发送create_network的RPC。

发布事件后会进行create_port_postcommit，Mechanism Manager遍历各个Mechanism Driver，从而完成Port在网络中的创建。这里要注意的是，在ML2中几个常见的Mechanism Driver（如linuxbridge和OpenvSwitch）都没有重写抽象类的create_port_postcommit，而是直接会pass掉，它们下面对应的agent是通过上一段介绍的机制来实现create_port接收的。

然后是进行bind_port处理。Port的属性（host/vnic_type/profile/vif_type等）会更新到数据结构中，然后通过Mechanism Manager的bind_port方法，遍历各个Mechanism Driver，从而执行bind_port方法。bind_port方法有的Driver实现了（如linuxbridge/openvswitch等），但要进行一些信息校验，有的则直接过滤掉了。

2.ML2对update_port的处理

收到update_port的RESTful API后，由ML2（neutron.plugins.ml2.plugin）执行update_port方法。这个方法和create_port的主体逻辑类似：首先更新数据库中该Port的信息，然后是update_port_precommit，发布事件，update_port_postcommit，最后重新处理bind_port以更新之前的数据结构。在bind_port_if_needed方法中，有个名为need_port_update_notify的布尔型参数，用于判断Port的信息是否有更新。如果有更新，bind_port_if_needed会调用_notify_port_updated方法发送一个update_port事件给相应的Agent。Neutron OVS-agent执行update_port事件的代码分析请参考5.5.2节。

========================= neutron.plugins.ml2.plugin. =========================
class Ml2Plugin()
def update_port():
with session.begin(subtransactions=True):
updated_port = super().update_port() // 更新数据库
if port_db['device_owner'] == const.DEVICE_OWNER_DVR_INTERFACE:
for dist_binding in dist_binding_list:
self.mechanism_manager.update_port_precommit()
registry.notify(resources.PORT, events.AFTER_UPDATE, …)
// 发布resource为Port，event为AFTER_UPDATE的事件
try:
for mech_context in bound_mech_contexts:
self.mechanism_manager.update_port_postcommit()
bound_context = self._bind_port_if_needed()
// 之前会判断是否需要做port_update_notify

5.4.4　Neutron将Port相关信息通知给DHCP Agent

Port资源生成好了，还需要向DHCP Agent更新MAC地址与IP地址的绑定关系，这依赖于RPC两端的通信工作。

1.Neutron Server发出RPC

DhcpAgentNotifyAPI的实例负责与DHCP Agent进行RPC通信，它在实例化的时候会订阅关于Port的事件，然后触发_native_event_send_dhcp_notification方法来与DHCP Agent进行交互。前面曾提到，ML2 Plugin在update_port的过程中会发布一个resource为Port，event为AFTER_UPDATE的事件，因此DhcpAgentNotifyAPI会看到这个事件，然后发送一个方法为port.update.end的消息触发DHCP Agent上对应的回调函数。

2.DHCP Agent接收RPC

DHCP Agent（neutron.agent.dhcp_agent）在初始化过程中实例化了RPC的Publiser，可以主动向Neutron Server同步DHCP的数据库状态，当然DHCP Agent也可以被动地监听Neutron发出的RPC通知。当收到port_update_end的消息后，会执行port_update_end方法。

========================== neutron.agent.dhcp.agent ==========================
class DhcpAgent():
def port_update_end():
self.call_driver()

5.4.5　DHCP Agent将Port相关信息通知给DHCP Server

接着上面向下走。call_driver方法会找到DHCP Server，即dnsmaq（neutron.agent.linux.dhcp），然后执行dnsmaq的reload_allocations方法，以便对DHCP参数进行更新。

5.4.6　Nova拉起虚拟机并通过相应的Port接入网络

转了一大圈，回到了起点Nova-compute（nova.compute.manager），在5.4.2节中提到了在build_and_run_instance方法的最后，会调用Hypervisor Driver（如libvirt）的spawn方法来启动虚拟机。Libvert（nova.vert.libvert.driver.LibvirtGenericVIFDriver）的spawn方法会创建虚拟机的镜像和网络，创建网络时会调用到nova.virt.vif中的plug_ovs_hybrid方法，然后该方法会调用_plug_bridge_with_port方法。在_plug_bridge_with_port方法中，先调用nova.network.linux_net的_create_veth_pair方法在qvb和qvo间建立连接，然后通过ip link启动Linux Bridge，最后通过brctl和ovs-vsctl将qvb和qvo添加到Linux Bridge和OVS上。

=========================== nova.virt.libvirt.vif =============================
class LibvirtGenericVIFDriver():
def _plug_bridge_with_port():
if not linux_net.device_exists(v2_name):
linux_net._create_veth_pair(v1_name, v2_name, mtu)
// 建立连接，v1_name和v2_name分别是qvbxxx和qvoxxx
utils.execute('ip', 'link', 'set', br_name, …)
// 启动Linux Bridge
utils.execute('brctl', 'addif', br_name, v1_name, …)
// 将qvbxxx添加到Linux Bridge
if port == 'ovs':
linux_net.create_ovs_vif_port(self.get_bridge_name(vif), v2_name, …)
// 将qvoxxx添加到OVS

这些工作完成后，虚拟机启动过程中的网络处理就都结束了。启动虚拟机，通过DHCP获取IP地址，然后就可以进行通信了。

5.5　OVS Agent的实现

接着来介绍设备端的Neutron Agent。控制端APIRouter通过WSGI接收北向的RESTful API请求，Neutron Plugin通过RPC与设备端进行南向通信。设备端Agent则向上通过RPC与控制端进行通信，向下则直接在本地对网络设备进行配置。Neutron Agent的实现有很多，彼此之间也没什么共性，本节选取比较有代表性的Neutron OVS Agent，对其实现进行框架性介绍。

OVS Agent的启动入口为neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent.py中的main方法，其中实例化了OVSNeutronAgent类，然后启动这个实例与Neutron Server间的RPC通信。这个OVSNeutronAgent的实例即负责在本地配置并监测OVS。

======= neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent =======
def main():
try:
agent = OVSNeutronAgent() // 实例化ovs_neutron_agent
agent.daemon_loop() // 使能agent与Neutron-server间的RPC

5.5.1　网桥的初始化

OVSNeutronAgent的实例化过程主要包含如下几个步骤，其中各个网桥的作用请参考上一小节的介绍，流表的具体逻辑在5.6节和5.7节中介绍。

======== neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent =======
class OVSNeutronAgent()
def __init__():
self.setup_integration_br()
self.setup_rpc()
self.setup_physical_bridges()
if self.enable_tunneling:
self.setup_tunnel_br()
self.setup_tunnel_br_flows()
self.dvr_agent = ovs_dvr_neutron_agent.OVSDVRNeutronAgent()
if self.enable_distributed_routing:
self.dvr_agent.setup_dvr_flows()
self.sg_agent = agent_sg_rpc.SecurityGroupAgentRpc()
self.run_daemon_loop = True
self.connection.consume_in_threads()
def setup_integration_br():
self.int_br.create() // 创建br-int网桥
self.int_br.set_secure_mode() // 配置安全模式
self.int_br.setup_controllers() // 配置控制器
if self.conf.AGENT.drop_flows_on_start:
// 判断是否需要在启动时清空网桥中的数据
self.int_br.delete_port(self.conf.OVS.int_peer_patch_port)
// 删除patch口
self.int_br.delete_flows() // 清空现有的流表
self.int_br.setup_default_table() // 配置默认流表
def setup_rpc(self):
self.plugin_rpc = OVSPluginApi() // 这个Publisher实现为空
self.sg_plugin_rpc = sg_rpc.SecurityGroupServerRpcApi()
// 安全组的Publisher
self.dvr_plugin_rpc = dvr_rpc.DVRServerRpcApi() // DVR的Publisher
self.state_rpc = agent_rpc.PluginReportStateAPI()
// 本地OVS状态的Publisher
consumers = [[topics.PORT, topics.UPDATE],
[topics.PORT, topics.DELETE],
[constants.TUNNEL, topics.UPDATE],
[constants.TUNNEL, topics.DELETE],
[topics.SECURITY_GROUP, topics.UPDATE],
[topics.DVR, topics.UPDATE],
[topics.NETWORK, topics.UPDATE]]
if self.l2_pop:
consumers.append([topics.L2POPULATION, topics.UPDATE])
self.connection = agent_rpc.create_consumers()
// 创建agent本地的各个Consumer
def setup_physical_bridges():
for physical_network, bridge in six.iteritems():
br.create()
br.set_secure_mode()
br.setup_controllers()
if cfg.CONF.AGENT.drop_flows_on_start: // 判断是否需要在启动时清空流表
br.delete_flows()
br.setup_default_table()
… // 省略掉的代码主要完成br-ex和br-int间的连接，
// 其代码层次较深，这里不做展开
def setup_tunnel_br():
self.tun_br.create()
self.tun_br.setup_controllers()
… // 省略掉的代码主要完成br-tun和br-int间的连接，
// 其代码层次较深，这里不做展开
if self.conf.AGENT.drop_flows_on_start: // 是否在启动时清空流表
self.tun_br.delete_flows()
def setup_tunnel_br_flows():
// 这个方法中的流表逻辑很复杂，这里不做代码层面
// 相关流表的分析，可参考本章5.6节和5.7节中相关内容
self.tun_br.setup_default_table()

1）通过setup_intergration_br方法启动br-int网桥。该方法通过ovs-vsctl在本地创建网桥，配置安全模式，配置控制器，并对网桥中的流表进行初始化。

2）通过setup_rpc方法启动RPC。在该方法中实例化了一些RPC Publisher，以及代理本地的RPC Consumer。OVS-agent的RPC Consumer监听7类topic：update_port、delete_port、update_tunnel、delete_tunnel、sg_update、dvr_update、network update。L2pop update可选监听。

3）通过setup_physical_bridge方法启动br-ex网桥。该方法通过ovs-vsctl在本地创建网桥并对其进行初始化，并建立与br-int间的连接。

4）通过setup_tunnel_br方法启动br-tun。该方法通过ovs-vsctl在本地创建网桥并对其进行初始化，建立与br-int间的连接。然后，通过setup_tunnel_br_flows方法初始化br-tun上的流表。

5）实例化OVSDVRNeutronAgent，初始化相应的RPC Publisher，如果开启了DVR，则向各个网桥下发DVR的流表。

6）实例化OVSSecurityGroupAgent，初始化相应的RPC Publisher，初始化防火墙。

7）把run_daemon_loop变量置为True，并初始化Agent中的各个RPC listener。

5.5.2　使能RPC

实例化OVSNeutronAgent之后，main方法调用daemon_loop方法，之后调用rpc_loop。rpc_loop的主要工作就是轮询一些状态，根据这些状态，进行相应的操作。如一旦探测到本地的OVS重启了，就重新创建本地的网桥并添加连接。另外，一旦RPC监听到update_port事件，则在本地使能相应的Port。

OVSNeutronAgent的process_network_ports方法在这里有必要说明一下。对Port的增加和更新的处理调用了treat_devices_added_or_updated方法，对Port的删除调用了treat_devices_removed方法。treat_devices_added_or_updated方法又会调用treat_vif_port方法，由treat_vif_port方法再调用port_bound方法。在port_bound方法中，会首先调用provision_local_vlan方法，然后下发流表打通网桥间对应segment的流量，最后会更改数据库中br-int网桥的属性，为端口绑定VLAN。

========= neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent =======
class OVSNeutronAgent()
def port_bound():
if net_uuid not in self.vlan_manager or ovs_restarted:
self.provision_local_vlan() // 打通网桥间对应segment的流量
self.int_br.set_db_attribute() // 为端口绑定VLAN

上述就是对OVS-agent的框架性介绍，启动完毕后，便开始了与Neutron Server间的RPC通信。在本节的介绍中，跳过了所有和流表设计相关的内容，接下来就来看一看OVS-agent为Overlay流量设计的转发机制。

5.6　OVS Agent对Overlay L2的处理

5.6.1　标准转发机制

本节来看看OVS Agent对于Overlay中二层流量的处理。在Overlay模型中，二层的通信涉及ovs br-int和ovs br-tun，br-int负责接入虚拟机并实现节点本地的通信，br-tun负责实现跨节点的通信。如图5-8所示，对于两个同网段的虚拟机间通信，经过的路径为：源所在的br-int、源所在的br-tun、目的所在的br-tun、目的所在的br-int。

图5-8　Overlay中二层流量的路径

br-int可以看作普通的二层交换机，即完成VLAN标签的增删和正常的二层自学习与转发。br-tun采用多级流表实现节点间的转发，并负责隧道的封装/解封装。br-int上的流表不必细说，图5-9给出了标准处理流程，对于br-tun上流水线的设计，更详细的流表规划信息可在neutron.plugins.openvswitch.agent.common目录下的constants.py文件中查看。

所有流经br-tun的数据包首先进入Table 0进行处理。Table 0对数据包的来源进行判断，从与br-int相连的patch-int进入的数据包交给Table 2处理，从GRE或者VxLAN端口（不同节点间的隧道有不同的Port_ID）进入的分别交给Table 3、Table 4处理。Table 2根据数据包的目的MAC地址判断是否为单播，若是则送往Table 20，否则送往Table 22。Table 20根据〈VLAN_ID，MAC〉到〈PORT_ID，TUNNEL_ID〉的映射关系将单播包送到特定的隧道，Table 22将非单播包复制后送到所有隧道。进入Table 3或者Table 4的数据包，首先判断TUNNE_ID是否合法，若合法则添加本地VLAN_ID并送往Table 10，否则丢弃。Table 10记录数据包的VLAN_ID，MAC、入端口以及TUNNEL_ID，将〈VLAN_ID，MAC〉到〈PORT_ID，TUNNEL_ID〉的映射关系写入Table 20，然后将数据包从与br-int相连的patch-int送出。上述流表的具体表示如下：

图5-9　br-tun上流水线的设计

============================= Tables in br-tun =================================
Table 0
table=0, priority=1, in_port=patch_int, actions=resubmit(,2)
table=0, priority=1, in_port=<gre-port>, actions=resubmit(,3)
table=0, priority=1, in_port=<vxlan-port>, actions=resubmit(,4)
table=0, priority=0, actions=drop
Table 2
table=2, priority=0, dl_dst=00:00:00:00:00:00/01:00:00:00:00:00, actions=resubmit (,20)
table=2, priority=0, dl_dst=01:00:00:00:00:00/01:00:00:00:00:00, actions=resubmit (,22)
Table 3
table=3, priority=1, tun_id=xxx, actions=mod_vlan_vid: xxx, resubmit(,10)
table=3, priority=0, actions=drop
Table 4
table=4, priority=1, tun_id=xxx, actions=mod_vlan_vid: xxx, resubmit(,10)
table=4, priority=0, actions=drop
Table 10
table=10, priority=1 actions=learn(table=20,hard_timeout=300,priority=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:0->NXM_OF_VLAN_TCI[],load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],output:NXM_OF_IN_PORT[]),output:patch-int
Table 20（其他的流表项由Table 10的learn action触发生成）
table=20, priority=0, actions=resubmit(,22)
Table 22
table=22, priority=1, dl_vlan=xxx, actions=strip_vlan, set_tunnel:xxx, output->[gre/vxlan ports]
table=22, priority=0, actions=drop

对上述流表的语法进行两点介绍。

1）流表间的跳转并没有用标准OpenFlow规范中的指令GOTO_TABLE，而是使用了Nicira的扩展动作resubmit。

2）Table 10中的〈VLAN_ID，MAC〉到〈PORT_ID，TUNNEL_ID〉，映射关系的自学习使用了Nicira扩展的寄存器机制，使得数据平面具备了一定程度的智能，这能够大大减少控制信道的负担。在OpenFlow 1.5中也提出了类似的机制。这里分析一下Table 10流表中的learn action。

·table=20，表示将学习到的流表放入Table 20。

·NXM_OF_VLAN_TCI[0..11]，记录当前数据包的VLAN_ID（VxLAN数据包中本来没有VLAN，是由Table 3根据tunnel_id映射后添加上去的）作为match中的VLAN_ID。

·NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[]，记录当前数据包的源MAC地址作为match中的目的MAC地址。

·load：0->NXM_OF_VLAN_TCI[]，表示action中要去掉VLAN_ID。

·load：NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[]，表示action中要封装隧道，隧道ID为当前隧道ID。

·output：NXM_OF_IN_PORT[]，表示action中的输出，输出端口为当前数据包的输入端口。

可见，上述过程就是标准MAC自学习在隧道中的扩展，无非就是将<VLAN_ID，MAC>到PORT_ID的映射变为了<VLAN_ID，MAC>到<PORT_ID，TUNNEL_ID>的映射。这种自学习仍然要依赖于泛洪来完成。

5.6.2　arp_responder

在传统网络中ARP依赖于广播泛洪的原因在于没有一个集中式的控制平面，而Neutron中的数据库存有所有虚拟机MAC地址与IP地址间的映射，可以说是一个天然原生的控制平面。因此有人提出将该映射关系注入OVS本地，在本地处理ARP广播，以避免隧道上的泛洪，这就是arp_responder。

arp_responder的实现并不复杂，就是在介绍的流水线中增加一个ARP Table（Table 21）去处理ARP Request。Table 21中会事先存好MAC与IP的映射关系，如果ARP Table匹配ARP Request消息中的目的IP，则构造一个ARP Reply，从ARP Request的入端口返回给虚拟机。如果匹配失败，则跳转到Table 22继续泛洪。上述过程如图5-10所示，之所以保留ARP Table到Table 22的跳转，一方面是考虑与外界L2进行对接时，Neutron无法得知其MAC/IP信息，仍然需要依赖于泛洪，另一方面是考虑控制平面的延迟或其他不可控因素，如果状态同步发生了滞后，则同样也要依赖于泛洪去解决问题。

图5-10　ARP Responder通过Table 21来实现

可以看到，对于Table 2的后续处理增加了ARP Table一个分支，所有的ARP Request都发往了Table 21（在Table 2中匹配ARP Request流表项的优先级高于单纯匹配广播流表项的优先级）。Table 21中流表项匹配字段包括：以太网类型（ARP）、VLAN ID和ARP请求中的目的IP地址，动作的伪代码如下：

其中：

1）move：NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[]，表示将ARP Request数据包的源MAC地址作为ARP Reply数据包的目的MAC地址；

2）mod_dl_src：%（mac）s，表示将ARP Request的目的虚拟机的MAC地址作为ARP Reply数据包的源MAC地址；

3）load：0x2->NXM_OF_ARP_OP[]，表示将构造的ARP包的类型设置为ARP Reply；

4）move：NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[]，表示将Request中的源MAC地址作为Reply中的目的MAC地址；

5）move：NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[]，表示将Request中的源IP地址作为Reply中的目的IP地址；

6）load：%（mac）#x->NXM_NX_ARP_SHA[]，表示将Request的目的虚拟机的MAC地址作为Reply中的源MAC地址；

7）load：%（ip）#x->NXM_OF_ARP_SPA[]，表示将Request的目的虚拟机的IP地址作为Reply中的源IP地址；

8）in-port，表示将封装好ARP Reply从ARP Request的输入端口送出，返回给源虚拟机。

arp_responder的工作机制很简单，却有效地减少了隧道上的泛洪。其实DHCP也存在类似的问题，如果只在网络节点上放置DHCP Server，那么所有的DHCP DISCOVER消息都要靠隧道泛洪发送到网络节点上。当然，DHCP消息的数量和产生频率远远赶不上ARP，问题也不会那么明显。

解决DHCP存在的上述问题，一种思路是在Table 22上专门写一条高优先级的DHCP流表项去匹配DHCP广播消息，并将其封装隧道送到网络节点进行处理。另外，也可以在Table 2上用一条高优先级的DHCP流表项去匹配DHCP消息，这条流表项只需要将DHCP消息通过相应的端口转交给dhcp namespace即可。之所以用namespace实现，是因为DHCP消息封装在应用层，OpenFlow流表无法直接支持DHCP消息的封装，因此这个工作得由分布在计算节点上的DHCP namespace来完成。

第一种思路的优点是实现简单，但是一旦网络节点发生单点故障，虚拟机便无法正常使用DHCP获取IP，不过在K版中就已经有人在多个网络节点中实现了dhcp_loadbalance。第二种思路的实现复杂一些，但能够避免网络节点中单点故障带来的问题，实现分布式DHCP。实际上，在第二种思路中也可以将namespace换成是本地的轻量级SDN控制器，然后动作换成是PacketIn，由控制器PacketOut完成DHCP的代理。

5.6.3　l2_population

arp_responder只能处理ARP，在处理未知单播时仍然需要泛洪。那么，既然Neutron的数据库中有虚拟机的完整信息，包括虚拟机所在服务器的信息，不妨直接在Neutron Server侧起一个Driver，预先把虚拟机的分布信息推送给Agent，这样Agent在封装tunnel的时候就可以有的放矢，而不是盲目地泛洪了。因此，l2_population的Mechanism Driver在发现Port Up了之后，会将对应虚拟机的信息以fdb_entry的形式同步给OVS-agent，OVS-agent上的处理逻辑如下所示：

1）首先判断本地是否存在和目标虚拟机在同一个segment的虚拟机。如果存在，则和目标虚拟机所在的服务器间自动建立隧道。如果不存在，则不进行后续的处理。

2）建立隧道后，向Table 22下发Flood流表，将新的隧道端口加入到泛洪的出端口中。

3）然后，向Table 21下发Arp Responder流表。

4）最后，向Table 20下发Unicast流表，将后续发往目的虚拟机的流量单播到其所在的服务器上。

可以看到l2_population在同步FDB的时候，并没有引入新的流表，只是改变了几个相关流表的生成来源，并且在这一过程中将arp_responder的流表也一同下发给了agent。实际上，l2_population的Mechanism Driver就相当于SDN控制器，只不过它可以完成的功能比较单一，只用于同步L2的控制信息。

5.7　OVS Agent对Overlay L3的处理

5.7.1　标准转发机制

上一节介绍了二层流量的处理，本节来看看OVS Agent对三层流量的处理，包括东西向流量和南北向流量两类。

东西向流量，是指同一租户不同网段的虚拟机间的通信，具体可以分为两种：

1）在同一个计算节点上不同网段内虚拟机之间的通信；

2）在不同计算节点上不同网段内虚拟机之间的通信。

南北向流量，是指租户虚拟机与外网间的通信，具体也可分为两种：

1）虚拟机主动访问外网的流量，需要进行SNAT处理；

2）外网通过Floating IP访问租户虚拟机的流量，需要进行DNAT处理。

在Neutron较早的版本中，上述流量都需要通过网络节点上的vRouter来处理。如图5-11所示，同一租户不同网段的两台虚拟机间的通信，经过的路径为：源所在的br-int、源所在的br-tun、网络节点中的br-tun、网络节点中的br-int、租户vRouter的namespace、网络节点中的br-int、网络节点中的br-tun、目的所在的br-tun、目的所在的br-int。

图5-11　Overlay中三层流量的路径

Neutron OVS对租户三层流量的上述处理，实际上和传统网络对于三层流量的处理方式是一样的。虽然这种设计非常经典，但它存在两个明显的问题，一是路径上的跳数太多增加了时延，二是vRouter会成为单点降低了可用性。解决这两个问题的思路包括以下几种。

1）部署多个网络节点，并通过算法将三层流量在不同的vRouter间进行调度和负载均衡，这种方式实现复杂而且路由的状态也很难同步。

2）在vRouter间通过VRRP来同步Router状态。这种方式很成熟，常见于传统网络中，不过只能工作在主备模式，而且三层流量仍然需要绕行到网络节点上进行处理。

3）把vRouter的功能分布在各个计算节点中实现。在这种方式中，三层流量的通信路径都可以得到最优的处理，分布式的架构也自然消除了单点故障的问题。

Neutron中第三种思路的实现称为DVR（Distributed Virtual Router）。DVR在J版中提出，增强了Neutron OVS三层网络的可用性。不过，DVR与Neutron其他功能的兼容性不是很好，后续的版本仍在不断地修复DVR的问题。下面将着重介绍DVR处理东西向流量的机制，最后简要给出DVR处理南北向流量的模型。

5.7.2　DVR对东西向流量的处理

DVR对同一租户不同网段内虚拟机之间通信的处理的组网结构如图5-12所示。DVR的使用对于租户来说是透明的，不过隐藏在其背后的实现比较复杂，涉及的处理机制比较多，需要依赖于计算节点上OVS Agent和L3-agent的相互配合。OVS Agent负责在br-int和br-tun上增加一些DVR流表，而L3 Agent则负责在本地完成路由工作。

图5-12　DVR组网结构示例

接下来的讲解发生在图5-13所示的场景中：某租户有红、绿两个网段，两台虚拟机vm1、vm2分属两个网段，分别位于计算节点CN1、CN2上，租户拥有一个DVR路由器r1，分布在两个计算节点之上。假定vm1已经通过ARP获得了CN 1中r1在红色网段接口的MAC地址r1 red mac，现在vm1发起向vm2的ping request。

图5-13　DVR转发过程示例

抛开流表的格式与下发的过程，先按照图中序号来看一看DVR流表下发后进行通信的各个阶段的数据包特征。这里规定<源MAC，目的MAC，源IP，目的IP地址>为数据包的特征四元组。

1）vm1发出的ping包特征为<vm1 mac，r1 red mac，vm1 ip，vm2 ip>，该数据包送至br-int-cn1。

2）br-int-cn1在之前ARP过程中学到了r1 red mac所在端口，将ping包直接转发给CN1中的r1。

3）r1进行路由，得知目的虚拟机连接在绿色网段上，而且r1中存有目的虚拟机的静态ARP表项，不需要进行ARP解析。于是CN1中的r1通过其绿色网段接口将ping包重新送回br-int-cn1。此时ping包的特征为<r1 grn mac，vm2 mac，vm1 ip，vm2 ip>，br-int-cn1还不知道vm2连在何处，进行泛洪。

4）br-tun-cn1由br-int-cn1收到ping包，将源MAC地址修改为全局唯一的dvr cn1 mac，封装好隧道，标记绿色网段的TUNNEL_ID，直接送往CN2。此时ping包封装在外层包头内，其特征为<dvr cn1 mac，vm2 mac，vm1 ip，vm2 ip>。

5）br-tun-cn2收到后去掉外层包头，打上绿色网段本地的VLAN标签，送给br-int-cn2，此时ping包的特征仍为<dvr cn1 mac，vm2 mac，vm1 ip，vm2 ip>。

6）br-int-cn2识别出这是CN1经过绿色网段送过来的流量，于是将源MAC地址改回r1 grn mac并剥掉VLAN标签。br-int-cn2还不知道vm2连在哪里，于是就将ping包泛洪。此时ping包的特征为<r1 grn mac，vm2 mac，vm1 ip，vm2 ip>。

7）vm2收到ping request，回复ping echo，反向的通信过程和上述基本一致。

上述步骤给出了通信的外在特征，下面还需进一步说明某些步骤内在的实现原理。

在3）中，“r1中存有目的虚拟机的静态ARP表项”，是因为在各个部署了DVR的计算节点中，l3-agent事先从Neutron数据库中获取了虚拟机的网络信息，直接注入了r1中。这是为了防止r1跨隧道泛洪获取vm2的MAC地址（可以通过l2_population来实现）。

在4）中，“将源MAC地址修改为全局唯一的dvr cn1 mac”，是因为在所有计算节点上，r1位于相同网段的接口MAC地址是一致的，即CN1上的r1 red/grn mac与CN2上的r1 red/grn mac一致。因此为了防止对端br-tun的混乱，Neutron为每个部署了DVR的计算节点分配了全局唯一的dvr mac地址，br-tun在进行隧道传输前都需要对源MAC地址进行改写。“封装好隧道，标记绿色网段的TUNNEL_ID，直接送往CN2”，是因为DVR要求开启l2_population事先学习<VLAN_ID，MAC>到<PORT_ID，TUNNEL_ID>的映射，以避免隧道上的泛洪。

在5）中，br-tun-cn2解封装后，判断流量若是由dvr cn1送过来的，则不进行自学习，直接将流量送给br-int-cn2。

在6）中，br-int-cn2实现了存有所有部署了DVR计算节点的全局唯一的MAC地址，因而可以识别dvr cn1发送过来的流量，完成源MAC地址的回写后进行转发。

通过上述描述，应该对整个过程有了一定的认识，下面来具体看看流表的情况。由于CN 1和CN 2是对等的，所以将只针对CN 1进行介绍，CN 2可类比得到。

1.br-int-cn1上的流表

前面介绍过，在未开启DVR之前br-int-cn1上只有Table 0，而在开启DVR后，OVS Agent将为其增加DVR流表Table 1。Table 0在进行正常的二层转发之前会做如下判断：入端口是否为与br-tun-cn1相连的patch-tun，以及源MAC地址是否属于dvr MAC地址（由于示例场景比较简单，图5-13中示例流表只匹配了CN2的dvr-cn2-mac）。如果满足这两个条件，Table 0会将数据包送到Table 1中去处理。Table 1根据VLAN判断目的虚拟机所在的网段，将源MAC地址改为r1位于该网段接口的MAC地址，剥掉VLAN并根据目的虚拟机的MAC地址进行转发。下面给出各个流表项，并对新增的流表项进行了标注。

============================= br-int-cn1中的流表 ================================
Table 0
# 入端口是否为与br-tun-cn1相连的patch-tun？源MAC地址是否属于dvr MAC地址？若是则转给Table 1
table=0, priority=2, in_port=patch-tun, dl_src=dvr-cn2-mac, actions=resubmit(,1)
table=0, priority=1, actions=NORMAL
Table 1
# 根据VLAN_ID判断目的虚拟机所在网段，将源MAC地址改为r1位于该网段接口的MAC地址，剥掉VLAN
并根据目的虚拟机的MAC地址进行转发
table=1, priority=4, dl_vlan=2, dl_dst=vm1-mac, actions=strip_vlan, mod_dl_src: r1-red-mac, output: vm1-port
# 根据VLAN_ID判断目的虚拟机所在网段，将源MAC地址改为r1位于该网段接口的MAC地址，剥掉VLAN，
并根据目的IP所在网段，向所有属于该网段的端口转发。正常情况下，数据包由上面priority=4的表项进行处
理，该表项可能是为了防止上面表项未下发时流量能够送到目的虚拟机而设置的
table=1, priority=2, ip, dl_vlan=red, nw_dst=red-subnet, actions=strip_vlan, mod_dl_src:r1-red-mac, output:[vm1-port,…]
# 丢弃未能匹配上的流量
table=1, priority=1, actions=drop

2.br-tun-cn1上的流表

再来看br-tun-cn1上的流表。Table 0对数据包的来源进行判断：从与br-int相连的patch-int进入的数据包交给Table 1处理，从VxLAN端口（以VxLAN为例）进入的交给Table 4处理。Table 1判断数据包是否为发向r1的ARP，或者其他发给r1的二层帧，如果是则丢弃（为了保证虚拟机送到r1的数据包只在本地转发）。如果Table 1判断数据包是由r1发出来的，则将源MAC地址改为CN1的dvr MAC地址（为了避免对br-tun的混乱），然后送往Table 2。Table 2根据数据包目的MAC地址判断是否为单播，若是则送往Table 20，否则送往Table 22。Table 20根据<VLAN_ID，MAC>到<PORT_ID，TUNNEL_ID>的映射关系将单播包送到特定的隧道，该映射关系可事先通过l2_populaiton学习而得到，也可以通过Table 10的触发学习而得到。Table 22将非单播包复制后送到所有隧道。进入Table 4的数据包，首先判断TUNNE_ID是否合法，若是则标记本地VLAN_ID并送往Table 9，否则丢弃。Table 9判断数据包源MAC地址是否属于dvr MAC地址（由于示例场景比较简单，图5-13中示例流表只匹配了CN2的dvr-cn2-mac），如果是则直接送给br-int-cn1处理，否则转给Table 10进行学习。Table 10记录数据包的VLAN_ID，MAC以及TUNNEL_ID，将<VLAN_ID，MAC>到<PORT_ID，TUNNEL_ID>的映射关系写入Table 20，然后从与br-int相连的patch-int送出去。下面给出各个流表项，并对新增的流表项进行了标注。

============================= br-tun-cn1中的流表 ================================
Table 0
table=0, priority=1, in_port=patch_int, actions=resubmit(,1)
table=0, priority=1, in_port=<VxLAN-port>, actions=resubmit(,4)
table=0, priority=0, actions=drop
Table 1
# 判断数据包是否为发向r1的ARP，或者其他发给r1的二层帧，如果是则丢弃。这保证了虚拟机送到r1的数据包只在本地转发
table=1, priority=4, arp, dl_vlan=red-vlan, arp_tpa=r1-red-ip, actions=drop
table=1, priority=4, arp, dl_vlan=grn-vlan, arp_tpa=r1-grn-ip, actions=drop
table=1, priority=2, dl_vlan=red-vlan, dl_dst=r1-red-mac, actions=drop
table=1, priority=2, dl_vlan=grn-vlan, dl_dst= r1-grn-mac, actions=drop
# 判断数据包是否由r1发出，若是则将源MAC地址改为CN1的dvr MAC地址（为了避免对br-tun的混乱），然后送往Table 2
table=1, priority=1, dl_vlan=red-vlan, dl_src= r1-red-mac, actions=mod_dl_src:dvr-cn1-mac, resubmit(,2)
table=1, priority=1, dl_vlan=grn-vlan, dl_src= r1-grn-mac, actions=mod_dl_src: dvr-cn1-mac, resubmit(,2)
table=1, priority=0, actions=resubmit(,2)
Table 2
table=2, priority=0, dl_dst=00:00:00:00:00:00/01:00:00:00:00:00, actions= resubmit(,20)
table=2, priority=0, dl_dst=01:00:00:00:00:00/01:00:00:00:00:00, actions= resubmit(,22)
Table 4
table=4, priority=1, tun_id=red-vni, actions=mod_vlan_vid: red-vlan, resubmit(,9)
table=4, priority=1, tun_id=grn-vni, actions=mod_vlan_vid: grn-vlan, resubmit(,9)
table=4, priority=0, actions=drop
Table 9
# 判断源MAC地址是否属于dvr MAC地址，若是则直接交给br-int-cn1，不再进行地址学习
table=9, priority=1, dl_src=dvr-cn2-mac, actions=output:patch-int
# 其余提交给table 10 处理，进行地址学习
table=9, priority=0, actions=resubmit(,10)
Table 10
table=10, priority=1 actions=learn(table=20,hard_timeout=300,priority=1,NXM_OF_VLAN_TCI[0..11],NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],load:0->NXM_OF_VLAN_TCI[],load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],output:NXM_OF_IN_PORT[]),output:patch-int
Table 20
table=20, priority=0, dl_vlan=grn-vlan, dl_dst=vm2-mac, actions=strip_vlan, set_tunnel:grn-vni, output->cn2-vxlan-port
table=20, priority=0, dl_vlan=red-vlan, dl_dst=vm1-mac, actions=strip_vlan, set_tunnel:red-vni, output->cn1-vxlan-port
table=20, priority=0, actions=resubmit(,22)
Table 22
table=22, priority=1, dl_vlan=red-vlan, actions=strip_vlan, set_tunnel:red-vni, output->[cn2-vxlan-port, networknode-vxlan-port]
table=22, priority=1, dl_vlan=grn-vlan, actions=strip_vlan, set_tunnel:grn-vni, output->[cn2-vxlan-port, networknode-vxlan-port]
table=22, priority=0, actions=drop

3.DVR的流水线

DVR流水线如图5-14所示，图中未表示l2_population。

图5-14　DVR的流水线

以上的示例即为DVR对不同计算节点中同一租户不同网段虚拟机之间通信的处理，如果vm1和vm2位于同一计算节点中，则只有br-int参与转发，br-tun将只起到一个作用，即将虚拟机发给r1的包限制在节点本地。

5.7.3　DVR对南北向流量的处理

DVR对于南北向流量的处理有两种模式，第一种是对于Floating IP，路由后直接在节点本地完成NAT。这种纯分布式的代价是，计算节点本地需要和external network有直连，而且需要一个FIP的namespace去为本地的Floating IP做ARP Proxy，FIP上还需要消耗一个额外的external IP，如图5-15所示。第二种是对于其他非Floating IP业务的流量，路由后仍然需要走到网络节点上进行集中式的SNAT，如图5-16所示。

图5-15　在计算节点本地处理Floating IP的流量

图5-16　在网络节点上集中进行SNAT

5.8　Security-Group与FWaaS

关于网络安全，OpenStack的实现有Nova-Security-Group、Neutron-Security-Group、Neutron-FWaaS。其中，Nova-Security-Group的作用点在虚拟机内部，是虚拟机内部的防火墙。Neutron-Security-Group的作用点在虚拟机的接入设备上，是一组虚拟机的防火墙。Neutron-FWaaS分为v1和v2两个版本，v1作用于租户的router，为租户网络提供集中式保护；v2可作用于所有Neutron Port，为网络中的各类端口提供保护。这些不同粒度的安全策略为OpenStack网络提供了多层次的防护体系，本节来介绍一下Neutron-Security-Group，Neutron-FWaaS放到下一节介绍。

5.8.1　Neutron-Security-Group

在G版本之前，OpenStack只能提供Nova-Security-Group，而Nova-Security-Group存在几个问题，如只能处理入向的流量，只能作用于一个虚拟机实例，在实现上对IP Overlapping的支持也有一些问题。在G版本中，OpenStack开始同时支持Nova-Security-Group和Quantum-Security-Group，Quantum-Security-Group可独立地实现安全组功能，能够处理入向和出向的流量，而且能够作用于一组虚拟机。随着Quantum迁移到Neutron，Quantum-Security-Group延续下来自然就成为了Neutron-Security-Group。

一个Neutron-Security-Group中包含1或多条规则，可以匹配流向和5元组，允许多个虚拟机端口关联到该Neutron-Security-Group中，也允许一个虚拟机端口同时关联多个Neutron-Security-Group。安全组中的规则会在虚拟机启动时生效，启动后更改的规则也能够动态地生效，能够匹配某条规则的数据包允许通过，不能匹配任何规则的数据包将被丢弃。在默认情况下，每个虚拟机端口都会自动地受到一个Default Security Group的保护，其中包含如下规则。

1）对于虚拟机发出的出向流量——允许通过DHCP请求IP地址，不允许对外提供DHCP服务，只允许以虚拟机自身的MAC和IP作为源地址（防止欺骗攻击），允许状态为已建立连接的数据包，禁止状态为无效连接的数据包。

2）对于发向虚拟机的入向流量——允许DHCP Server返回流量，允许状态为已建立连接的数据包，禁止状态为无效连接的数据包。

在Neutron-Security-Group的实现中，通常使用Linux Bridge作为provider，即使在使用Open vSwitch进行转发时，虚拟机端口和OVS端口间往往也会接入一个Linux Bridge作为过渡。这是因为Neutron-Security-Group需要进行带状态的防护，而早期版本的OVS中流表并不支持带状态，想要实现带状态的规则只能由PacketIn给控制器，这在性能上存在很大问题，因此要以Linux Bridge作为过渡，通过Iptables来实现带状态规则。

后面随着OVS对contrack的支持，社区中有人提出通过OVS来直接实现带状态规则，从而去掉Linux Bridge，以简化组网结构。这种思路无疑是正确的，但是现在的成熟度和性能都有待验证。目前来说，仍然有必要保留Linux Bridge作为安全组的实现。在本书6.6.2节的内容中，会介绍通过OVS contrack实现安全组的实例。

5.8.2　FWaaS v1

从H版本开始，Neutron提供了FWaaS v1服务，其默认的实现是在网络节点上的router namespace中部署Iptables规则，对进出租户网络的流量进行过滤。FWaaS v1中有3个主要的概念：firewall rule是防火墙规则，能够匹配五元组，支持allow/deny/reject 3个动作；firewall policy是一组firewall rule的集合，可提供策略的审计功能；firewall是防火墙的逻辑概念，一个firewall只能有一个firewall policy，一个租户只能有一个firewall，不同租户可以共用一个firewall（即一个firewall可以关联多个router）。

可以看到，FWaaS v1的设计是从传统网络中借鉴过来的，它主要提供的是网络级别的防护。不过在虚拟化环境中，这种级别的防护是不够的，比如租户内部的东西向流量不会走到router上，因此FWaaS v1无法提供对东西向流量的保护。Neutron-Security-Group能够为虚拟机提供保护，但是它的设计初衷是为了保护虚拟机上的应用，因此它匹配的通常是源IP+目的端口（对于ingress方向），或者目的IP+目的端口（对于egress方向），而不支持同时匹配五元组。

5.8.3　FWaaS v2

从M版本开始，Neutron提供了FWaaS v2。FWaaS v2将FWaaS v1和Neutron-Security-Group两者的功能和特点综合到了一起，既能提供灵活的匹配规则，又能提供虚拟机级别的防护。从设计上来看，FWaaS v2引入firewall_group的概念代替了FWaaS v1中的firewall，一个firewall_group中包含ingress和egress两条firewall policy，一个firewall_group可以关联多个Port（而FWaaS v1中firewall要关联的是router）。而firewall rule和firewall policy的概念并没有大的变化，只是在firewall rule中去掉了reject这个动作。

FWaaS v2的上述设计，提供了一种通用的安全策略描述，连带着把FWaaS v1和Neutron-Security-Group中一些其他的限制也都解决掉了。FWaaS v2相比于FWaaS v1，增强了如下特点。

1）提供了端口级别的安全策略，可作用于router端口、虚拟机端口或者VNF端口等。

2）一个策略中可以为ingress和egress两个方向关联不同的规则。

3）一个Neutron Port可以关联多个firewall_group。

4）有计划地引入address group和service group，将IP地址和端口号解耦合。

FWaaS v2相比于Neutron-Security-Group，增强了如下特点。

1）支持显式的deny操作，rule的组合可以更加灵活。

2）可以同时匹配五元组。

3）firewall policy可以在firewall_group间复用。

4）有计划地引入address group和service group，将IP地址和端口号解耦合。

5）未来service group可能会支持L7的语义。

尽管FWaaS v2综合了FWaaS v1和Neutron-Security-Group的功能，但是FWaaS v1和Neutron-Security-Group目前仍然独立于FWaaS v2存在。FWaaS v1已经被标记为了Depreciated，但是目前社区仍然会继续维护其API，未来在R版本中可能会终结FWaaS v1的历史使命。而Neutron-Security-Group在实际部署中会和FWaaS v2混合使用，这个原因在于FWaaS v2和Neutron-Security-Group的使用者是不同的，网络管理员倾向于使用FWaaS v2，而用户或者应用管理员则更习惯于Neutron-Security-Group。

FWaaS v2希望提供的是一个通用的Port保护机制，因此除了L3端口以外，L2端口也在FWaaS v2的计划之内，这时Iptables就不是一个好的选择了，因此FWaaS v2默认的backend driver正在向OVS倾斜，流表添加在了br-int里面，具体的流表设计就不做介绍了，有兴趣的读者请自行了解。

5.9　LBaaS

负载均衡是实际生产环境中不可或缺的一个环节。从G版本开始，Neutron通过子项目LBaaS来提供负载均衡的服务，默认使用HAProxy作为backend driver。这一阶段的LBaaS称为LBaaS v1，从功能和可扩展性上来讲，LBaaS v1存在着很大的局限。因此从K版本开始，社区推进了LBaaS v2，在Liberty版中将LBaaS v1标记为Depreciated，并在Newton版本中下架了LBaaS v1。本节先来介绍一下LBaaS v1和LBaaS v2，最后再简单地看一看OpenStack中负载均衡的新势力Octavia。

5.9.1　LBaaS v1

LBaaS v1的设计架构如图5-17所示，其中包括4个基础性的概念。VIP是业务对外表现的IP地址，一般来说VIP对于Client是公网可见的。Pool可以看作业务实例所形成的资源池，一个Pool只能对应一个VIP，一个Pool对应一个独立的HAProxy（默认）进程。HAProxy进程存在于网络节点独立的namespace中并拥有VIP，它收到目的地址为VIP的流量后会分配给后端不同的业务实例，可支持的算法包括ROUND_ROBIN、LEAST_CONNECTIONS、SOURCE_IP 3种，支持基于cookie的会话保持。业务实例称为Member，一个Member只能属于一个Pool，一个Pool中会有多个Member，一个Member只能监听一个业务端口，同一个虚拟机的不同业务端口属于不同的Member。Health-Monitor用来监测Pool中不同Member的状态，多次轮询后没有响应即认为Member处于不可用状态，HAProxy将不再向该Member分配流量，Member恢复响应后置为可用状态，之后HAProxy分配流量时会重新考虑该Member，一个Pool中可以没有/有一个/多个Health-Monitor。

图5-17　LBaaS v1的设计架构

LBaaS的后端通常是通过专用的负载均衡软件来实现的，开源的主要有HAProxy、Nginx以及LVS，OpenDaylight的Netvirt项目中通过OVS contrack简单地实现了LBaaS v1，可参考本书6.6.2节中的相关介绍。

LBaaS v1的架构很清晰，但是存在以下几个问题：

1）HAProxy的namespace部署在网络节点上，会出现绕路、单点等通用的问题。

2）一个VIP只能绑定一个端口号，无法同时对多种业务流量进行负载均衡。

3）不支持应用层的负载均衡，若如URL重定向。

4）无法支持TLS Termination。

5.9.2　LBaaS v2

为解决上述问题，LBaaS v2重构了LBaaS v1的数据结构，形成了图5-18中的设计架构。Pool/Member/Health-Monitor的概念没有大的变化，而LBaaS v1的VIP结构中IP和端口号解耦合为LoadBalancer和Listener，一个LoadBalancer对应一个虚拟IP地址，一个LoadBalancer可以有一个或多个Listener，每个Listener对应一个HAProxy进程，负责监听一个业务端口。另外，Listener还可以通过绑定容器来终结TLS，一个Listener可以绑定多个L7Policy来实现L7的负载均衡。一个L7Policy可以有多个L7Rule，L7Rule支持与hostname/path/file type/header/cookie进行比较。

图5-18　LBaaS v2的设计架构

不过，无论数据结构怎么变化，都无法改善HAProxy部署在网络节点上所产生的问题。解决的思路就是将HAProxy从网络节点中拿出来，将其放到独立的虚拟机里面，一方面位置上不再受到限制，另一方面可以在多个HAProxy虚拟机实例间进行高可用。

5.9.3　Octavia

Octavia位于OpenStack的Big Tent下，专门用于实现OpenStack环境中的负载均衡。Octavia旨在提供对LB VNF的全生命周期进行管理，因此也可以看作是一个NFV项目。Octavia内部逻辑的流程控制用的是TaskFlow，盛放HAProxy的虚拟机实例称为Amphorae，Amphorae间运行KeepAlived，在O版本中还不能实现多活，只能支持主备，其设计架构如图5-19所示。

Octavia出现于L版本中，在项目级别上和Neutron是平行的，和Neutron做独立的版本演进。Octavia有自己一套独立的API，通过在内部做一些转换即可以作为LBaaS v2的一个driver，目前基本上可以完全兼容LBaaS v2。Octavia自出现就得到了广泛的关注，从Roadmap来看未来的features会很丰富，是一个值得持续关注的项目。目前社区已经决定将LBaaS v2合并到Octavia中，不过短时间来看，LBaaS v2仍然会继续进行维护。

图5-19　Octavia的设计架构

5.10　TaaS

Neutron有个子项目叫作TaaS（Tap as a Service），它能够把流量镜像到特定的、运行有流量分析软件的虚拟机中，以实现流量的可视化。TaaS在Neutron中仍然属于比较新的子项目，目前还没有正式纳入Neutron的标准API中。不过，鉴于流量可视化对于OpenStack网络运维和安全的重要性，本节会对TaaS进行介绍。

在TaaS的设计中，有几个主要的概念：Destination Port是镜像流量的目的地，即运行有流量分析软件的虚拟机；Source Port是指镜像的流量源，通常是租户的某台虚拟机；Tap Service是指一个镜像流量服务的实例；Tap Flow是指一个流量镜像服务的规则。在O版的TaaS中，一个Tap Service需要关联一个Destination Port，一个Tap Service可以包含一个或多个Tap Flow，每个Tap Flow需要关联一个Source Port。因此，租户开通一个TaaS需要两个步骤。

1）创建一个Tap Service并指定一个虚拟机作为Destination Port。

2）然后创建一个Tap Flow，关联上一步中创建的Tap Service并指定一个虚拟机作为Source Port。为了防止租户间的流量泄露，一个Tap Service只能属于一个租户，相应的Destination Port和Source Port也都必须属于该租户。

从架构上来看，TaaS Plugin负责处理TaaS API，分析其合法性并通过RPC分发给相应的TaaS Agent，TaaS Agent控制本地的Driver对流量镜像进行底层实现。O版中TaaS的Driver只有OVS，br-int和br-tun中的流水线均进行了相应的扩展，另外TaaS还设计了一个br-tap，串在br-int和br-tun中间，便于提供更为灵活的镜像策略，如图5-20所示。

图5-20　TaaS中增加了br-tap实现流量镜像

TaaS所涉及的流表很多，流水线画起来比较复杂，下面概括地介绍一下其实现思路。首先要明确的是，Source Port和Destination Port在分布位置上并没有必然的联系，可能位于同一个host中，不过更多的时候位于不同的host中。针对两种不同的情况，数据的流向分别如下所示。

（1）第一种情况

Source Port和Destination Port位于同一个host中，此时镜像流量不需要经过br-tun处理。Source Port的流量流入br-int之后：

1）br-int识别出这是某个Source Port的流量，于是将其镜像到br-tap中，同时为镜像流量标记其所属Tap Service的专用VLAN。

2）由于Destination Port就在本地，因此br-tap在收到从br-int发过来的镜像流量后，直接通过in_port将其反射回br-int。

3）br-int发现这是从br-tap发送过来的流量，去掉Tap Service的VLAN，并将流量送给本地的Destination Port。

（2）第二种情况

Source Port和Destination Port位于不同host，此时镜像流量需要经过br-tun在不同的host间进行传输。Source Port的流量流入br-int之后。

1）br-int识别出这是某个Source Port的流量，于是将其镜像到br-tap中，同时为镜像流量标记其所属Tap Service的专用VLAN。

2）由于Destination Port不在本地，因此br-tap在收到从br-int发送过来的镜像流量后，将其传送给br-tun。

3）br-tun发现这是从br-tap发送过来的流量，知道要将其送到远端的Destination Port，不过br-tun此时并不知道Destination Port位于哪个host中，因此只能进行隧道泛洪，tunnel_id标记为Tap Service的专用VNI以便和业务流量进行区分。

4）Destination Port所在host的br-tun收到该镜像流量后，去掉封装并标记Tap Service的VLAN，然后将其发送给本地的br-tap。其他host的br-tun收到该镜像流量后会直接丢弃。

5）br-tap从br-tun上收到镜像流量后，得知Destination Port就在本地，于是透传给本地的br-int。

6）br-int发现这是从br-tap送过来的流量，去掉Tap Service的VLAN，并将流量送给本地的Destination Port。

对于第二种情况：在3）中第一次处理镜像流量的时候需要经过隧道泛洪，为了能够在处理后续流量时避免泛洪，因此4）中Destination Port所在host的br-tun收到该镜像流量后，在送给本地br-tap的同时，还会复制一次并通过in_port反射回给Source Port所在host的br-tun。Source Port所在host的br-tun收到这个反射回来的流量后，即可得知Destination Port所在的host，于是后续的镜像流量就可以直接进行隧道单播了。

TaaS支持将Source Port的入向流量和出向流量都导向Destination Port。由于TaaS是实现在OVS上的，因此它和虚拟机间还隔着Linux Bridge上的Security Group。对于Destination Port而言，需要禁止其入向SG规则从而放行所有的镜像流量。对于Source Port而言，目前TaaS对入向流量的处理发生在入向SG规则之前，对出向流量的处理发生在出向SG之后。与之相反，理想状态下对入向流量的处理应该发生在入向SG规则之后，出向流量的处理应该发生在出向SG规则之前。目前社区正在OVS中集成SG功能，完成后这一顺序问题即可得到解决。

TaaS对于生产环境来说是个非常好的功能，不过由于TaaS的设计是基于In-Band的，因此镜像流量会占据业务流量的带宽，而且将多个Source Port的流量都镜像到一个Destination Port上，Destination Port的压力也太大了，这些都需要TaaS进行优化，比如通过QoS做镜像流量限速，通过Policy Filtering筛选流量，或者通过Quota做Source Port的配额。

5.11　SFC

Neutron在L版本中提供了一个处理服务链的Service Plugin，叫作Networking-SFC。这个插件提供了描述SFC的能力，提供了一个基于OVS的sfc_driver的实现。SFC的概念就不再重复了，直接来介绍一下Networking-SFC Service Plugin为用户提供的API。

为了能把各个SF串起来，API需要描述的基本内容包括：SF通过哪些端口接入网络，筛选出什么样的流量送到SFC路径上进行处理，SFC路径上都需要经过哪些SF。对应到Networking-SFC Service Plugin中的数据结构，分别是Port_Pair（一个SF有出向、入向两个Port）、FlowClassifier和Port_Chain，用户操作的逻辑就是先创建好SF对应的Port_Pair，然后创建一个FlowClassifier描述流量特征，最后创建一个Port_Chain并关联一个FlowClassifier和多个Port_Pair。

还有另外一个概念是Port_Pair_Goup，一个Port_Pair_Group中包括一个或多个Port_Pair。用户可以创建一个Port_Pair_Group，把同一类型SF的不同实例所对应的Port_Pair，纳入这个Port_Pair_Group中，底层的Driver读到这一语义后，会自动在组内的SF实例间进行负载均衡。

下面再来看基于OVS的sfc_driver的实现。Sfc_driver需要干的第一件事，是把SFC流量识别出来，实现上就是向br-int下发FlowClassifier流表，根据L1～L4层的特征对流量进行分类。从SFC路径端到端的角度来考虑，这里面会有两个问题。

1）流量在入口处匹配后如何进行标记，一旦入口处做了标记（如MAC/VLAN/MPLS/NSH等），那么路径的后续节点就只需要匹配标记字段了，而不需要再进行L1～L4的匹配。目前，基于OVS的sfc_driver只能支持通过MPLS进行标记，MPLS的标签在SFC路径上会逐跳发生变化，相当于NSH中SI的逐跳递减。

2）如果中间某个SF不支持对标记的处理，那么在发送给该SF前需要把标记去掉，而对于经过该SF处理结束后发送出来的流量，需要重新进行分类并标记新的MPLS。

第二件事，是将SFC流量转发给某个SF。这又分为两种情况。

1）目标SF在本地，这依赖于向br-int下发ingress和egress流表，匹配MPLS直接转发给本地目标SF，如果目标SF不支持MPLS的话，则需要去掉MPLS标签。

2）目标SF在远端，此时br-int会匹配MPLS并将流量送进br-tun（如果目标是一个Port_Pair_Group，则使用group_table进行负载均衡），Networking SFC Plugin会通过L2 pop向br-tun下发fdb_entry指导tunnel的建立与封装。基于OVS的sfc_driver目前只能支持通过VxLAN作为隧道的封装。

因此，基于OVS的sfc_driver在数据平面上采用的是MPLSoverEthernetoverVxLAN的封装，VxLAN用于在OVS间进行传输，而MPLS则用于OVS本地的SF转发，如图5-21所示。

图5-21　基于OVS的sfc_driver所使用的封装

社区上有一个Symmetric Port Chain的蓝图，它让流量能够双向对称地经过SF实例，保证SF上状态的完整性，在O版本中这基本上已经得到了实现。另外还有一个蓝图也比较有意思，对于少数类型的SF（如IDS），它们接收了流量之后就不会再把流量吐出来，因此当SFC路径上有IDS存在时，就需要在特定的位置对流量进行镜像，而不是直接把流量转发给IDS。

5.12　L2-Gateway

如果租户的VxLAN网络想要和外界物理二层进行对接，那么需要有一个VxLAN Gateway对VxLAN和VLAN间进行转换。Neutron有一个子项目叫作L2-Gateway，负责实现VxLAN Gateway的自动化管理与控制。L2-Gateway的架构如图5-22所示，其中的L2 GW agent实际上是一个适配器，它通过RPC和L2 GW Plugin中的Driver进行通信，通过OVSDB和VxLAN Gateway中的OVSDB Server通信，并在两侧进行信息同步。

图5-22　L2 Gateway的架构

之所以没有在L2 GW Plugin和VxLAN Gateway间进行直接通信，而是加了个L2 GW Agent做适配，出发点应该是不想在Plugin中直接集成OVSDB这种重量级的南向协议。因此，与其说L2 GW Agent是个适配器，倒不如把它看作是一个功能比较单一的SDN控制器，北向通过RPC接收L2 GW Plugin的编排，南向通过OVSDB控制VXLAN Gateway。L2 GW Agent南北向的通信都遵守着OVSDB的hardware_vtep schema中所定义的数据结构。

OVSDB的hardware_vtep schema，是OVS中的一个spec，它是VMware为了用NSX来管理其他厂商硬件的VxLAN Gateway，从而实现裸机的集成。这个schema提出来之后得到了厂商广泛的响应，目前Arista、Juniper、Brocade、Dell、HP、华为都提供了相应的支持，可以说这个schema已经成为了厂商交换机（尤其是ToR）SDN化的事实标准，因此L2 GW Agent是能够控制多厂商的硬件VxLAN Gateway的。虽然schema前面扣的是hardware的帽子，不过也很早开始就得到OVS的支持，因此OVS也可以作为软的VxLAN Gateway被L2 GW Agent统一控制。

hardware_vtep schema中的数据结构，这里不做深入介绍。L2 GW Plugin侧能提供的信息主要包括：虚拟机的MAC地址及其所在服务器中VTEP的IP地址（创建虚拟机时获得，用于形成schema中的Ucast_Remote_Mac和Mcast_Remote_Mac），所属租户使用的Segment ID（即VNI）及VNI/VLAN的映射关系（通过L2 GW的API获得，用于形成schema中的Logical_Switch和Physical_Port）。VxLAN Gateway侧能提供的信息主要包括：接口和VLAN的绑定关系（从本地配置中获得，用于形成schema中的Physical_Port），物理网络后面的MAC地址（自学习得到，用于形成schema中的Ucast_Local_Mac和Mcast_Local_Mac），VTEP的IP地址（从本地配置中获得，用于形成schema中的Physical_Switch和Physical_Locator）。

L2 GW Agent收集到两侧所提供的信息后，会进行接口转换并完成信息同步。VxLAN Gateway侧，是设备在本地监听到OVSDB Server的变化后自动生效的，L2 GW Plugin侧是RPC consumer收到L2 GW Agent发布的事件后写Neutron DB的，并调用L2 pop的RPC通知给相关hypervisor上的vSwitch。实际上，上述过程也是MAC路由重分布的过程。

目前，L2-Gateway可以支持L2 GW Agent的HA，但是无法实现VxLAN Gateway的主备或者双活，当然，OVSDB hardware_vtep schema本身也没有提供相应的描述机制。

5.13　Dynamic Routing

上一节介绍了Overlay网络与外界的二层对接，本节将介绍Overlay网络与外界的三层对接。Neutron中的租户网络通常作为一个Stub，与外界网络进行三层通信，通常这依赖于vRouter上的默认路由和NAT。不同租户的vRouter通常都上联到同一个二层的Provider Network中，Floating IP也都是在这个Provider Network的地址池中进行分配的。不过，如果租户不希望做NAT，而是希望和外界形成一个扁平的路由域，那么就只能在Provider Router上手动地配置静态路由，指定到该租户路由的下一跳。

导致这种情况的原因在于，vRouter的实现通常是一个轻量的namespace，因此并不具备动态路由的能力。Neutron中有一个子项目叫做Dynamic Routing，它的思路是在OpenStack环境中部署BGP Agent，由BGP Agent驱动本地的BGP Speaker把某些租户路由动态地发布给external gateway。如图5-23所示，BGP Speaker和external gateway（图中表示为Provider Router）建立BGP邻居，发布路由时会把租户网络（self-service network）的IP前缀作为Prefix，把租户router在Provider Network中的接口作为下一跳。对于Floating IP的处理也是一个道理，只不过prefix会变成/32而已。

图5-23　Dynamic Routing示意图

上述过程对于BGP协议本身而言是非常简单的，但是如果想要从上到下拉通Dynamic Routing，还要解决两个关键的问题——大部分租户通常还是希望通过NAT出去，那么数据平面上的Router怎么知道哪些流量走NAT，哪些不走NAT呢？控制平面上的BGP Agent怎么知道哪些租户的路由需要发布给external gateway呢？

这里需要先介绍一下Subnet Pool和Address Scope两个概念。Subnet Pool是在K版中提出的，对于一些不太在意IP地址规划的租户来说，使用Subnet Pool可以简化建立Subnet的流程，Neutron会在Subnet Pool中自动选择可用的IP地址分配给租户，而不用在创建Subnet的时候明确指定CIDR。一个Subnet Pool可以有多个Subnet，在一个Subnet Pool中各个Subnet使用的IP地址不会发生重叠。Address Scope是在M版中出现的，一个Address Scope可以包含多个Subnet Pool，这些Subnet Pool间的IP地址是不可以重叠的，因此一个Address Scope中的所有IP地址都不会出现重叠的情况。

Address Scope的实际作用在于，在同一个Address Scope中所有的IP地址都是可以直接路由的，而不需要进行NAT。也就是说，Address Scope会直接影响到L3 Agent中的Iptables规则。L3 Agent上通常会有一个Provider Network侧的接口，以及多个tenant network侧的接口。若tenant network侧的接口和Provider Network侧的接口在同一个Address Scope中，则对于相关流量直接路由转发不进行NAT，否则路由后还是需要进行NAT的。这就解决了刚才提到的数据平面上的问题。为了解决控制平面上的问题，Dynamic Routing的Plugin会选择出所有跟Provider Network侧接口在同一个Address Scope下的tenant network侧接口，并将它们的路由通过RPC推给BGP Agent，BGP Agent再驱动本地的BGP Speaker，并将这些路由发布给external gateway。

可以看到，在Dynamic Routing设计中，以BGP Speaker作为集中式的控制平面，而以各个vRouter作为分布式的数据平面。Dynamic Routing为什么选择BGP，而不是IGP呢？原因大概可以想到如下两点。

1）IGP作为链路状态协议，需要依赖于汇聚拓扑和本地路由的计算，而BGP可以显式地指定下一跳，实现和维护起来相对简单。

2）IGP的信令依赖于二层的组播，控制平面和数据平面解耦起来相对困难，而BGP是一种应用层协议，因此BGP Speaker的部署位置会更加灵活。目前，Dynamic Routing只支持Ryu的一种BGP Speaker。

另外，Dynamic Routing只能对租户路由进行advertise或者withdraw，还不支持将BGP Speaker学习到的外部路由注入到vRouter中的操作，因此也无法在多个external gateway间进行multi-homed。实际上，Dynamic Routing在最初设计时是把multi-homed排在用例的首位的，不过在Neutron中对于multiple provider_network的支持一直都不是很好，而且Router上multiple external_gateway的功能也很弱，目前来看Dynamic Routing距离实现multi-homed或者更高级一点的uplink policy差距还比较大。

如果Dynamic Routing和DVR同时使用，那么在进行宣告的时候，对于Floating IP而言其下一跳指向计算节点本地的FIP namespace的external IP，而对于非Floating IP的网段，其下一跳会指向网络节点上的SNAT namespace的external IP（划分Address Scope之后，DVR SNAT namespace上不会进行NAT）。对于非Floating IP的网段，下一跳之所以没有直接指到计算节点上，是因为目前DVR通常会把非Floating IP流量送到网络节点上，进行集中式的SNAT，而计算节点本地只负责处理Floating IP的流量。

5.14　VPNaaS

目前很多企业都会选择混合云的组网方式，以求在成本和安全上获得平衡，而企业站点和公有云之间的点对点VPN连接，则以IPSEC VPN最为常见。早在H版本，Neutron中就成立了子项目VPNaaS，提供了对于IPSEC VPN的支持。顾名思义，这个子项目最初提出来的时候是希望构建一个通用框架，提供各种形式的VPN，如IPSEC、SSL、L2TP、MPLS等，但是这些VPN的组网架构和数据模型完全不同，很难统一纳入到同一个项目下。虽然社区中出现过SSL VPNaaS和MPLS VPNaaS两个blueprint，但是它们都没有代码层面的实现，后面成立了BGP VPN项目，但是它并不属于VPNaaS，而是和Neutron平级。因此从实际使用的角度来说，VPNaaS可以等同地看作是IPSEC VPNaaS。

IPSEC VPN的原理这里不做介绍，由于涉及的协议比较多，因此IPSEC在配置上会显得非常冗长。在设备使用命令行开通一个IPSEC VPN时，大概需要如下步骤：建立一个IKE策略（可以看作是IPSEC控制平面的参数），建立一个IPSEC策略（可以看作是IPSEC数据平面的参数），建立一个兴趣流（哪些流量送进IPSEC VPN），最后建立一个点对点的IPSEC连接，并将之前建立好的IKE策略、IPSEC策略，以及兴趣流关联到这个IPSEC连接上来。

VPNaaS的数据模型和API的设计也是照着上述思路来设计的：Ike_policy用于描述IKE策略，ipsec_policy用于描述IPSEC策略，endpoint_group用于描述兴趣流，ipsec_connection用于描述IPSEC连接。一个Ipsec_connection会关联一个ike_policy、一个ipsec_policy和local/peer endpoint_group。这些数据结构涉及的参数非常多（比如加密用什么算法、SA的生命周期是多少，等等），而相当一部分用户实际上并不是很关心这些参数的选取过程，因此在VPNaaS的API中将很多参数都设成了可选项，以方便用户开通。

上述信息把一个点对点IPSEC VPN的所有基本逻辑特征都交代清楚了，最后再为ipsec_connection关联一个ipsec_service，进而和某个vRouter进行绑定，以明确隧道的端点。然后，VPNaaS中的各个Driver就负责把上述信息推送给vRouter，vRouter会在本地进行相应配置，这样一个IPSEC VPN就建立好了。需要注意的是，在vRouter处理流量时，NAT通常会发生在IPSEC之前，如果NAT修改了流量的IP地址，那么IPSEC的兴趣流就无法发挥作用了，因此vRouter上的NAT规则通常要进行一些调整，以放行IPSEC的兴趣流量。

目前VPNaaS所支持的开源IPSEC实现，主要就是OpenSwan和StrongSwan。两者的代码是同源的，目前都有商业公司在支撑，其特性上的优与劣，在这里不进行对比。

5.15　Networking-BGPVPN与BagPipe

5.15.1　Networking-BGPVPN

由于IPSEC自身缺少路由的控制平面，因此它更多地使用在企业点对点入云的场景中，而并不适合进行full-mesh或者partial full-mesh的连接。对于多点互联的DCI场景，基于BGP的VPN无疑是更好的选择。最开始的时候，Neutron中相关的blueprint叫做MPLSVPNaaS，侧重于用MPLS跨骨干网实现IPVPN，BGP只作为一个实现手段。随着EVPN的发展，技术的侧重点开始转向BGP，独立出了一个与Neutron平级的Service Plugin——Networking-BGPVPN。Networking-BGPVPN在API层面提供了对IPVPN和EVPN两种连接类型的支持，用例也不仅仅面向于inter DC，而是更多地开始考虑intra-dc的需求，比如在计算节点间跑BGP同步租户路由，实现租户内的互通和租户间的隔离，或者在计算节点和DC Edge Router间跑BGP，然后依赖于DC Edge Router上已有的广域网VPN方案来拉通多个DC间的流量。

Networking-BGPVPN的API非常简单，对于用户来说就是创建一个BGP VPN，然后把这个VPN和现有的network/router关联在一起。用户在创建BGP VPN时，甚至都不用指定任何参数。之后VPN的开通，是driver驱动后端的Agent来完成的，目前Networking-BGPVPN支持3个driver/agent，包括OpenDayLight、OpenContrail和BagPipe，下面来对BagPipe进行介绍。

5.15.2　BagPipe

BagPipe的agent拿到了Networking-BGPVPN的外面，独立作为一个项目叫作Networking-BagPipe。Networking-BagPipe在实现上分为BagPipe-BGP-Agent和BagPipe-BGP两个部分。BagPipe Agent可以看作是一个接口的适配器，它通过RPC与BGPVPN中的BagPipe Driver交互，解析出BGP VPN中所包含的本地端口，然后转化成RESTful接口发送给BagPipe-BGP。BagPipe-BGP收到RESTful请求后，针对每个本地端口进行plug，plug操作有两个主要的动作：①将端口的信息包装成BGP路由表项并通过BGP Speaker发布给所有的MP-BGP Peer；②向datapath下发本地端口的转发信息。BGP Speaker收到Peer发布的路由后，会向datapath注入远端端口的转发信息。

在O版中，BGP Speaker只支持ExaBGP，ExaBGP能够同时支持IPVPN和EVPN的MP-BGP地址族，datapath支持linux_bridge和OVS两种，其中linux_bridge可以用作IPVPN和EVPN的datapath，而OVS目前只实现了IPVPN的datapath。发布路由不需要多说，下面说一下基于OVS的IPVPN datapath的实现。

在图5-24中可以看到，BagPipe引入了一个新的网桥br-mpls来实现IPVPN，叫做br-mpls，目前它在上联方向上可以提供3种类型的数据封装，包括MPLSoverEthernet、MPL-SoverEthernetoverGRE和VxLAN：①使用MPLSoverEthernet时，通过目的MAC进行Underlay路由找到Peer节点，通过mpls_label标记本地端口。②使用MPLSover-Ether-netover-GRE时，通过GRE进行Overlay找到Peer节点，通过mpls label标记本地端口。③使用VxLAN时，通过外层目的IP进行overlay找到Peer，tunnel_id用于标记虚拟机端口而非指代VNI。使用MPLSoverEthernet和MPLSoverEthernetoverGRE的时候，br-mpls的上联端口统一称为mpls_interface，使用VxLAN的时候，br-mpls的上联端口称为VxLAN interface。

图5-24　BagPipe的架构

再来介绍一下br-mpls上的流表设计，如图5-25所示。Br-mpls上共有4个Table，Table 0负责对流入br-mpls的流量进行分类，如果是从本地端口进入的流量，那么标记端口所属的VRF并送给Table 2；如果是从MPLS/VxLAN interface进来的则发送给Table 1。Table 2匹配VRF，如果是发给网关的流量则从与gateway namesapce连接的patch口送出，如果不是发给网关的流量则根据目的IP前缀进行负载均衡并送给Table 3。Table 3匹配目的IP前缀，如果目的地在本地，则push mpls label或者set vxlan tunnel_id给Table 1，如果目的地在远端则封装MPLSoverEthernet/MPLSoverEthernetoverGRE/VxLAN，并从对应的mpls_interface/vxlan interface送出。在Table 1中，对于从mpls_interface进入的流量，匹配mpls label，修改源MAC和目地MAC地址，然后剥掉标签并发送给本地端口，对于从vxlan_interface进入的流量，匹配tunnel_id，修改源MAC和目地MAC地址然后发送给本地端口。

抛开match和action，简单来说，Table 0用于流量分类，区分从本地流入和从远端流入的流量。Table 1用于解封装流量，并发送给本地端口。Table 2用于将发给网关的流量牵引到gateway namespace上。Table 4用于封装流量，完成MAC的改写，并发向本地或者远端端口。其中，Table 1的流表是在plug操作的执行过程中写进入的，而Table 2和Table 3上的流表是由BGP Speaker学习到新路由时触发写入的。

实际上，BagPipe除了可以作为BGPVPN的后端以外，还可以运行EVPN作为ML2的后端，在每次监听到update_port的时候就可以把端口的信息注入到本地的fdb中，再通过Type 2宣告给远端。在目前Ocata的版本中，BagPipe只能支持以Linux Bridge作为EVPN datapath，还不支持OVS。相比于l2_population，BagPipe的好处在于分布式的部署，而且BGP Peer间只可以发布同一个租户的路由，而l2_population的Driver通过MQ会把所有的端口发给所有的Agent，因此BagPipe在性能、可扩展性和可用性上都有不小的提升。

图5-25　br-mpls上的流表设计

5.16　DragonFlow

DragonFlow是华为以色列技术团队创立的，在OpenStack的K版开始提交代码。DragonFlow最初的目标是通过可插拔、无状态、轻量级的SDN控制器来实现分布式路由，它是为了解决DVR中存在的一些问题而提出的，主要是DVR会造成计算节点上资源和性能的一些损耗。

1.架构演进

早期，DragonFlow的设计架构以及控制流如图5-26和图5-27所示。L3 Controller-plugin处理L3 Extension API，然后通过RPC将L3信息同步给网络节点上的L3 Controller-agent。L3 Controller-agent会向计算节点中的br-int发送分布式路由表，而SNAT/DNAT则在网络节点上进行集中式处理。实现方面，L3 Controller-agent中实际上就是Ryu，通过下发OpenFlow流表在OVS中直接实现路由。

图5-26　DragonFlow的早期设计架构

图5-27　DragonFlow早期架构中的控制流

经过一段时间的发展后，DragonFlow不再局限于DVR的实现，其整体架构也有了很大的变化，如图5-28所示。可以看到DragonFlow在功能上已经集成了对L3 Extension API以及L2 Core API的支持，架构也由早期网络节点上的单点Controller变为了计算节点上分布式的Controller。Controller通过本地的SB DB Driver操作OVS，通过本地的NB DB Driver与Distributed DB交互业务数据。Distributed DB是可插拔的数据库框架，存有业务与资源的全局视图。

可以看到，DragonFlow在宏观架构上和Midonet很像，都是DB Driven的。这种架构介于集中式和分布式之间，以DB作为逻辑上的集中点。分布式的Controller以DB为中间件来同步状态。相比于纯集中式的SDN架构，这种架构的优势是能够借助成熟的DB集群机制有效地消除单点的问题，分布式的Controller在南向通道上的性能会更好一些。劣势在于数据库中不适合有复杂的算法，因此难以实现一些集中式的网络优化。

图5-28　DragonFlow目前的架构

DragonFlow目前已经支持的功能，主要包括：分布式DHCP、arp_responder、l2_population、分布式路由（Reactive/Proactive两种方式）、分布式DNAT、分布式/集中式SNAT、带状态Security Group、Anti Spoofing、Ryu BGP、QoS等。

2.流水线设计

图5-29所示为DragonFlow流水线中的主要逻辑，其中浅灰色的是完全Proactive的，而天蓝色的（即DHCP与L3Lookup）是部分Reactive的。

图5-29　DragonFlow流水线中的主要逻辑

Table 0是Ingress Classfication与Dispatch to Ports流表，图5-30所示为它的几种情况：1）对于本地VM产生的流量，通过metadata标记好租户ID，然后送入Ingress Port Security流表对源MAC/IP进行监测，以防止欺骗。2）对于隧道传入的流量，匹配tunnel_id并直接发给目的VM。3）对于Floating IP的南北向流量，送入Ingress Processing流表，完成NAT和FloatingIP的ARP代答。

图5-30　Ingress Classification Table对进入的流量进行分流

通过Ingress Port Security流表的反欺骗检查后，送入Service流表，它的作用是过滤出ARP和DHCP信令以便进行特殊处理，其余流量则送入L2 Lookup流表。ARP Request送入ARP Table，ARP Table的设计采用了Proactive模式，控制器事先预置好ARP Reply的流表项，在数据平面本地直接实现ARP代答。DHCP消息送入DHCP Table，DHCP Table的设计采用了Reactive模式，由控制器完成分布式的DHCP代答。操作过程如图5-31所示。

L2 Lookup流表处理L2流量，如图5-32所示。它根据目的MAC地址判断是否为L3流量，如果是则送入L3 Lookup Table。如果判断是L2的广播/组播，则标记租户的网络ID，然后送入Egress Security流表。如果判断是L2单播，则标记目的虚拟机的ID，然后送入Egress Security流表。

L3 Lookup流表处理L3流量，在图5-33中以Reactive的实现方式为例。它根据目的子网判断流量是否为东西向流量，如果是则将首包发送给控制器，控制器下发用于路由的流表，改写MAC减TTL并标记目的虚拟机的ID，然后送给Egress Security流表进行处理。如果是南北向流量则送入Egress Processing流表，Egress Processing会判断源虚拟机是否申请了Floating IP，若是则将其源IP/MAC地址转换为Floating IP/MAC地址并直接从相应端口发送出去完成转发，如果没有申请Floating IP则进行SNAT并送入Egress Security流表进行处理。

图5-31　Service Table用于分流控制信令和业务流量

图5-32　L2 Lookup Table对于二层流量的处理

Egress Security流表处理后，将合法流量送入Egress流表。Egress流表将本地流量直接转发，远端流量则用tunnel_id标记目的虚拟机，远端收到后匹配tunnel_id即直接发给目的虚拟机。如图5-34所示。

图5-33　L3 Lookup Table对于三层流量的处理

图5-34　Egress Table完成最后的转发

5.17　OVN

OVN（Open Virtual Network）由OVS社区孵化，其目标是为OVS提供原生的网络虚拟化能力，在社区对其未来的定位上，有可能会成为OVS在OpenStack中的缺省后端（default backend）。OVN在2015年初成为OVS的子项目，在OVS 2.6.0中首次提供正式版中，紧接着在OpenStack的N版中进行了集成。

1.架构设计

图5-35所示为OVN的整体架构，Local OVN Controller分布在计算节点或者网络节点上，向下通过OpenFlow指挥本地OVS的pipeline，向上通过OVSDB与全局数据库进行同步。相比于DragonFlow中可插拔DB的框架，OVN目前只提供了OVSDB Server，在集群的实现上只能通过Pacemaker进行主从，还不支持A/A，社区有考虑在未来使用etcd。相比于现有的Neutron OVS，对于OVN可以理解为把多个功能的Agent聚到了OVN Controller中进行实现，可以有效地减少了Agent的数量。

图5-35　OVN的整体架构

OVN的DB又分为Northbound DB和Southbound DB，两者之间是通过守护进程OVN-Northd联系起来的。Northbound DB存储上层的业务信息，而Southbound DB存储底层网络的状态，属于南北向数据库分离的设计。目前Northbound DB和Southbound DB都在同一个OVSDB Server实例中实现。

具体说，Northbound DB负责把Neutron中的数据结构转换为OVN中的数据结构，OVN-Northd根据OVN的数据结构产生logical flows（租户网络视图中的流表，与底层OVS中的流表不同）存入Southbound DB。一方面，Southbound DB会把logical flows推送给相关的OVN Controller，另一方面，各个OVN Controller会把本地信息（如VIF的信息、MAC地址和接入位置）同步到Southbound DB中，Southbound DB再把这些信息同步给其他的OVN Controller。最后，OVN Controller将logical flows映射为physical flows，通过OpenFlow+OVSDB来部署本地的OVS。这种在logical flows/physical flows间进行转化的思路，实际上是从Nicira NVP中延续下来的，这也并不难理解，因为OVN/OVS的几个主要开发者，都出身于当年的Nicira。

OVN目前已经支持的功能，主要包括：分布式DHCP、ARP Responder、L2_pop、分布式路由（Proactive）、分布式DHCP、分布式DNAT、分布式/集中式SNAT、带状态安全组、Anti、L2 Gateway、QoS、TraceRoute等。

OVN在Northbound DB中保存着租户的逻辑拓扑，其模型如图5-36所示，图中只给出了比较主要的逻辑组件。其中，Switch上有多个Port并负责这些Port所连的虚拟机间的二层通信。Distributer连接多个SW，实现不同网段虚拟机间的通信。同时，Distributer Router还会连接Gateway，通过Gateway与外界实现三层互联。

图5-36　OVN Northbound DB中的逻辑网络模型

OVN-Northd根据北向DB中的模型，生成租户网络的转发逻辑logical flows，并将logical flows推送给相关的OVN Controller。logical flows的生成是OVN中最为关键的步骤，为Switch、Distributed Router和Gateway生成logical flows的逻辑是不同的。从整体设计的角度来看，logical对于流量的处理可分为Ingress和Egress两个阶段，当源和目的位于同一个主机中时，Ingress阶段和Egress阶段的处理是在相同的OVS上，如果源和目的位于不同的主机中，那么两个阶段位于不同OVS上。除了一些入向的处理以外，目的地的路由也在Ingress阶段完成，Egress阶段不会再进行路由，而是根据Ingress处理的结果直接将流量转发给目的地。

2.logical flow datapath的设计

图5-37所示为在Switch中logical flow datapath的简要示意图，考虑VM1发向同网段VM3的流量。ingress阶段，VM1发出的流量通过Port Security过滤掉非法的源MAC/IP，之后进入Egress ACL进行安全组的Egress Filtering，然后筛选出ARP/DHCP作为本地代答，业务流量进入Destination做MAC转发。如果VM1和VM3在相同的主机上，则直接跳到VM3接入的Port上进行处理，先在Ingress ACL中进行安全组的Ingress Filtering，最后通过Port Security过滤非法的目的MAC/IP。如果VM1和VM3在不同的主机上，那么Ingress阶段处理结束后根据转发结果封装隧道，送到VM3所在主机中进行Egress阶段的处理。

图5-37　OVN Switch logical flow datapath

图5-38所示为Gateway中logical flow datapath的简要示意图，考虑VM1发向外网的流量。在Ingress阶段，VM1的流量先通过Port Security过滤掉非法的源MAC/IP，然后进入IP Input处理发给Gateway本身的流量（Gateway Port不需要安全组的保护），之后UNSNAT负责处理SNAT的下行流量，DNAT负责处理Floating IP的转换，IP Routing负责路由改源MAC地址减TTL，Next Hop Resolver接在IP Routing后面根据目的IP改写目的MAC并进行转发。至此Ingress阶段处理结束，然后进入Egress阶段，SNAT处理SNAT的上行流量，然后通过OUTPUT送到External Network中。

图5-38　OVN Gateway logical flow datapath

上面简单地介绍过了logical flow，Southbound DB会把这些logical flow发给相应的OVN Controller，然后由OVN Controller转换成OpenFlow流表并下发到OVS的pipeline中生效。OVN Controller要完成这一步的转换，必须要结合物理网络中的相关信息，这主要包括Port实际接入的in_port，以及隧道在Underlay中的IP地址。Southbound DB负责从各个OVN Controller处收集这些信息并进行全局同步。

3.流水线设计

OVS pipeline的设计思路大致如下：Table 0匹配in_port并送到相应logical flow datapath的入口。Logical flow datapath对应Northd生成的logical flow的逻辑，这由OVS pipeline中一连串的Table来实现。在logical flow datapath处理结束之后，会跳入OVS pipeline中最后一个Table，这个Table会根据在Ingress阶段所形成的路由结果，映射到相应的物理端口（包括隧道）上进行实际的转发。

5.18　本章小结

Neutron是OpenStack为云计算网络搭出来的一个框架，或者说是构建起来的一个生态环境，其原生的OVS plugin/agent是目前最为常见的SDDCN开源方案，它已经能够满足基本业务功能的需求，以及一部分增值类的业务，如FWaaS、LBaaS、TaaS、SFC等，仍然需要进一步的完善，或者依赖于第三方的provider来提供支持。本章介绍了Neutron相关的设计机制，在实际部署中，可有针对性地对其进行裁剪和二次开发。

第6章　开源SDDCN：OpenDaylight相关项目的设计与实现

云和虚拟化方向的整合，一直伴随着OpenDaylight的发展。在最早的Hydrogen版本中，就有了OpenStack Service、OVSDB Neutron和VTN这3个云相关的项目。发展到Carbon版本，目前与云/虚拟化相关的有Neutron No rthBound、Netvirt、VTN、GBP、LISP、SFC、FaaS等诸多项目。本章先简单地介绍一下ODL的整体架构，然后对ODL中OpenFlow Plugin的实现进行介绍，并对Neutron NorthBound、Netvirt、VTN、SFC几个项目进行代码导读。

本章所涉及的代码都是以Stable-Carbon版本为准的。

6.1　架构分析

OpenDaylight社区成立于2013年4月，从2014年2月发布第一代的Hydrogen（氢版本）开始，每半年发布一个版本，2017年年底已经发布到了第7代的Nitrogen（氮版本）了。从项目成员来看，ODL几乎囊括了全球ICT界的精英与翘楚；从社区活跃度来看，ODL在项目数、贡献人数、代码提交次数等方面相比于其他SDN开源项目都处于遥遥领先的地位；从实际应用来看，基于ODL的解决方案在云、NFV、广域网、移动核心网等多种场景中都已经有了实际的案例，很多厂商用SDN控制器也是基于ODL的内核进行二次开发的。

ODL的主体采用Java开发，为了实现Plugin的生命周期管理，使用了开源的Karaf项目（一种OSGI架构的实现），使用者可以根据自己的需求，动态地加载或者卸载Plugin。ODL的架构设计经历了两个阶段，从以适配思想为主导的AD-SAL，演变为以模型驱动思想为主导的MD-SAL。

6.1.1　AD-SAL架构

AD-SAL（API Driven Service Abstraction Layer，API驱动的业务抽象层）的架构如图6-1所示。

图6-1　AD-SAL的架构

AD-SAL的核心在于SAL，即通过中间的业务抽象层屏蔽底层南向协议的差异，为北向应用提供统一的南向适配接口，北向应用不可以直接跨越AD-SAL调用南向协议提供的接口。AD-SAL提供的适配接口包括拓扑、流表、数据包、设备等。

显然，这是一种典型的分层设计思想。目前ONOS的架构与AD-SAL十分类似，可以把ONOS Core中各个Subsystem看作是AD-SAL中提供的各类适配服务。图6-2给出了ONOS的架构，方便读者与AD-SAL进行对照。

图6-2　ONOS的架构

AD-SAL希望通过统一的抽象层吸收南向协议的差异，为控制器上的应用开发提供统一的接口。这个出发点毋庸置疑是好的，但是在具体的实现中却碰到了两难的问题：

1）由于各类南向协议的差异实在是太大了，如果要进行统一的抽象，就需要去差异化，那么就会损失南向协议中一些功能的灵活性，开发者也就失去了使用这些功能的可能。

2）如果要保证功能的完整性，则所有南向协议中的所有功能都需要在AD-SAL中定义相应的API，这反过来又造成了AD-SAL的复杂化，会直接影响整个架构的可扩展性和可维护性。

6.1.2　MD-SAL架构

为了摆脱这个两难问题带来的窘迫性，ODL在第二个版本Helium中将AD-SAL改为了MD-SAL。MD-SAL（Model Driven Service Abstraction Layer，模型驱动的业务抽象层）的架构如图6-3所示。

图6-3　MD-SAL的架构

虽然名字中还带着SAL的字样，但实际上MD-SAL的核心在于模型驱动，它放弃了为北向应用统一适配南向协议的思路，转而将南向协议和北向应用平等看待，通通看作是附着在MD-SAL上的Plugin。MD-SAL位于各个Plugin的中间，为Plugin提供通信能力，而MD-SAL自身则不提供任何业务逻辑。MD-SAL提供的3类通信机制，如图6-4所示，DataStore提供数据的存储与读写，并支持数据的发布订阅；RPC提供Plugin间的API调用；Notification则提供事件的发布订阅。

从IT的角度来理解MD-SAL，实际上就是消息总线加上数据库。虽然ODL中的Plugin数量繁多，但是MD-SAL的存在使得各个Plugin间得以高度解耦，使得平台具有了通用性以及良好的可扩展性，因此如果以操作系统的角度去理解，MD-SAL是一种类似于微内核的设计。有好处就会有代价，MD-SAL既然放弃了统一适配的尝试，其实也就是基本放弃了抽象所带来的好处，MD-SAL的应用开发者仍然不得不去关心底层南向协议的具体机制。当然，这反过来也提供了相当的灵活性，应用可以更好地利用南向协议不同的特色，而不用再受到适配层的约束。

图6-4　MD-SAL提供的3种通信机制

6.1.3　YANG和YANG-Tools

MD-SAL的模型驱动体现在YANG和YANG-Tools上。YANG（RFC 6020）是一种标准化的网络建模语言。基于MD-SAL开发的Plugin都可以使用YANG语言对自身进行描述，包括数据结构、RPC和Notification。YANG-Tools是一个有特殊用途的Plugin，类似于UML工具，能根据类图自动生成代码，即能够将YANG文件自动转化为Java代码。开发者在编写Plugin代码之前，首要的工作就是为模块编写YANG文件，声明该Plugin的数据结构，以及要提供的RPC和Notification。然后通过YANG-Tools处理这个YANG文件，为该Plugin生成Java代码的骨架，开发者可以基于这个骨架进行业务逻辑的开发。在业务逻辑当中，可以通过MD-SAL访问DataStore，监听DataStore的变化，调用其他Plugin的RPC，或者发布/订阅Notification。开发完成后，通过Maven将Plugin转化成OSGi Bundle，将其导入到Karaf之后就可以运行起来了。其流程如图6-5所示。

图6-5　基于YANG的开发流程

目前，ODL中的Plugin大多数都使用blueprint完成OSGi中的注册。通过Plugin中的features.pom，可以看到所有需要运行的模块，各个模块中的blueprint文件会指定初始入口类、初始化时所执行的方法，以及暴露给框架的引用，等等。因此，在分析Plugin业务逻辑的时候从features.pom和各模块的blueprint入手能够迅速地找到切入点。

6.1.4　MD-SAL的内部设计

上面两个小节所讲的是MD-SAL作为黑盒子对外表现出来的样子。图6-6所示的虚框中是MD-SAL内部架构的简要示意图，蓝色方框都是属于MD-SAL的功能。这个图里面有两个关键词，Binding-Aware（以下简称BA）和Binding-Independent（以下简称BI），BA指的是与语言相关的MD-SAL操作，BI指的是与语言无关的MD-SAL操作。当然这只是从字面上来理解，如果要深入理解的话，需要先介绍一下DOM的概念。

图6-6　MD-SAL的内部实现

DOM（Document Object Model，文档对象模型）是W3C规范的一种用于访问XML数据的标准。DOM是与语言无关的，其实现机制将XML文档映射成一个节点对象树，这个节点对象树在逻辑上表示XML文档的内容。通过DOM提供的API对这些节点对象进行读或写，即可以访问XML文档的内容。

出于设计之初对NETCONF/YANG的重视，ODL选择DOM作为构建MD-SAL中DataStore的核心技术，一方面NETCONF传输使用的编码格式就是XML，另一方面YANG中的数据结构通常就是以树形的结构组织起来的，而且YANG和XML还可以进行无损转换，因此基于DOM来构建ODL的DataStore就再合适不过了。是什么导致ODL在设计之初对于NETCONF/YANG如此重视呢？NETCONF/YANG在技术上固然有一些特色，不过其背后主要的推动力还是源自一些非技术的因素，简单地说就是当时厂商需要一种南向技术来制衡日益崛起的OpenFlow，这里就不展开去说明了。

虽然YANG和XML是可以相互转化的，不过为了更好地服务于YANG，MD-SAL定义了一套NormalizedNode模型作为DOM DataStore的数据结构，通过DOM API可以直接对DOM DataStore中的NormalizedNode进行操作。由于DOM是与语言无关的，因此Plugin直接通过DOM API进行开发的方式，即BI。与BI相对的就是与语言相关的BA，具体到MD-SAL来说就是开发者基于YANG-Tools生成的Java代码骨架来编写Plugin的业务逻辑，并在业务逻辑中通过Java DTO来间接描述DOM NormalizedNode，通过Java API来间接地操作DOM DataStore。之所以说间接，是因为实际上可以把DOM DataStore看作是一种BI Plugin，它只能理解DOM NormalizedNode和DOM API，因此在BA Plugin和DOM DataStore（也包括其他BI Plugin）进行交互时，需要进行Java DTO/API到DOM NormalizedNode/API的转换。在这个转换过程中会损失掉一些性能，有厂商测试的结果显示，基于BI开发Plugin相比于BA可提高30%～40%的性能。

回头再来看图6-6。BA、BI两类Plugin分别使用不同的接口进行MD-SAL开发，两类Broker实现相应RPC/Notification/DataStore的路由功能，BA Plugin和BI Plugin间进行通信，或者BA Plugin和DOM DataStore间进行通信时，需要由BA-BI Connector来完成转换，其中对DataStore操作的转换依据是由YANG-Tools处理YANG文件时生成的。

DOM DataStore目前采用的是in memory的存储方式，结构上分为configuration和operational两棵树，configuration树存储的是北向数据，包括业务上的一些需求或者配置，而operational树存储的是南向数据，包括底层网络的各种运行时状态。每当谈起MD-SAL的DataStore时，南北向数据分离总被看作是ODL的一大特色，其实MD-SAL的这种设计思路也是源自于NETCONF/YANG中RFC的要求，并非由MD-SAL首创。实际上大多数网络操作系统的数据库也都是这么设计的。

6.1.5　MD-SAL的集群机制

MD-SAL的集群是基于Akka实现的，主要提供了Distributed DataStore和Remote RPC Connector两个功能，如图6-7所示。

图6-7　ODL通过Akka实现集群

Distributed DataStore的实现，一是依赖于akka-persistence的持久化，DateStore的操作日志会写入LevelDB中，还会定期对DataStore的状态进行快照以存入本地文件系统中；二是依赖于akka-remoting进行节点间的通信；三是依赖于akka-clustering进行集群管理。Distributed DataStore采用Raft算法实现了数据的强一致性。

由于使用了强一致的做法，为提高Distributed DataStore的性能，ODL实现了数据分片的机制。数据分片也称为Shard，即对DOM树进行切分，切分出来的子树作为一个Shard。Shard不必在每个集群节点上都有Replica（副本），但是至少需要在一个节点上存有Replica。图6-8给出了ODL Distributed DataStore中Shard的简要示意图。一个Shard不管有多少Replica，都只有一个Leader，当调用DataStore接口写Shard时，需要Leader先处理写操作，然后再由Leader同步给各个Replica，等到所有的Replica都完成同步后写操作才算完成。

图6-8　ODL的DataStore与数据分片

Remote RPC Connector负责跨节点RPC通信，它没有持久化的问题，因此只使用了akka-remoting和akka-clustering，RpcRegistry的同步采用的是Gossip协议。

RPC Provider在实例化的时候，会向MD-SAL中的RpcRegistry进行注册，MD-SAL会记录Provider所在的位置。RPC Consumer在调用RPC时会指明Context，据此MD-SAL通过RpcRegistry判断被调用RPC的Provider是否在本地，如果是则直接在本地执行RPC Callback，否则MD-SAL会发送跨节点的RPC调用消息，目标收到后在本地执行RPC回调函数。ODL中跨节点的RPC调用如图6-9所示。

图6-9　ODL跨节点RPC

6.1.6　其他

从AD-SAL到MD-SAL，ODL在架构设计上相比于其他SDN控制器有其独到之处，具有很好的通用性和扩展性。不过，虽然模型驱动听起来很高大上，但是YANG和DOM DataStore的引入使得MD-SAL中internal APP的开发变得无比艰难，不花上几个月的时间来消化MD-SAL本身，稍微复杂一点的功能估计是很难搞定的。还有一种开发方式，是通过RESTCONF来开发外部APP，但是RESTCONF在设计上是CS模式的，而且交互上会受到RESTful的限制，因此适用于业务应用，并不适合进行网络基础服务的开发。

前面提到过，MD-SAL的核心在于模型驱动，而不在于适配。但是，这并不意味着ODL就没有办法实现适配了，在Boron版本中出现了一个新的Plugin叫作Genius，这个Plugin带着一些适配的影子。所以说存在即为合理，任何的技术架构都没有绝对的好坏之分，关键是看场景和需求。由于Genius比较新，只影响到极少数Plugin的设计与开发，它后面会在ODL的整个架构中扮演一个什么样的角色现在还很难说，因此本节就不做深入介绍了，本章的6.5.2节中会再次提到Genius。

以目前情况来看，MD-SAL还达不到商用的要求。

一方面，DOM DataStore会占用很大的内存，而且BA-BI转换过程会损失很多性能，对于侧重于实时控制功能的服务来说，它基于MD-SAL实现起来会遇到明显的瓶颈。

另一方面，虽然ODL从Helium版本就开始为MD-SAL引入了集群机制，但是ODL集群的实现一直饱受诟病，不仅是稳定性的问题，有的时候集群之后性能甚至会下降。Beryllium版本后，社区加强了对集群的重视程度，但是考虑到MD-SAL实现上的复杂性，因此估计在短期的几个版本内也难以有根本性的改善。

另外，ODL社区的未来发展也面临着很多不确定因素，社区高管的离任，ONOS和ONF的联手，白金会员中Brocade被收购，Cisco投入的力量渐弱，这些都难免会让人心里画上问号。不过，鉴于很多公司都已经加入进来了，它肯定还是会一直向前发展的，至于能走多远，有待后几个版本的观察。

了解了MD-SAL后，就要开始对ODL的代码进行分析了。后面的分析将着重介绍代码的逻辑，对于MD-SAL相关的内容不再进行过多介绍。

6.2　OpenFlow的示例实现

OpenFlow和OVSDB是实现云网络业务的基础协议，本节将对ODL OpenFlow的实现进行介绍。假设网络中有一台OF交换机连接两个主机，该OF交换机由一个ODL示例控制，示例中开启了一个内部APP——l2switch实现reactive forwarding。现在先从代码的角度来看看以下几个问题：

1）OF交换机如何连上ODL；

2）l2switch如何获得PacketIn；

3）l2switch如何下发PacketOut；

4）l2switch如何下发FlowMod。

OpenFlow在ODL中的实现依赖于OpenFlowJava和OpenFlow Plugin。其中OpenFlowJava工作在最底层，它通过Netty实现socket收发，并对OpenFlow消息进行封装、解封装。OpenFlowPlugin工作在OpenFlowJava之上，负责对OpenFlow消息的具体处理，并提供RPC方法，其他Plugin可以调用这些RPC方法来使用OpenFlow协议。

在ODL中OpenFlowJava和OpenFlow Plugin的实现非常复杂，下面在讲解代码时，主要会注重代码的实现逻辑，对一些具体的调用和设计模式不进行展开讨论。

6.2.1　OF交换机的上线

查看openflowplugin-impl下的blueprint文件，得知OpenFlowPlugin的入口类为Open-FlowPluginProviderFactoryImpl。该入口类负责生成OpenFlowPluginProviderImpl，并执行OpenFlowPluginProviderImpl的initialize方法。initialize方法逻辑示意如下，主要是实例化一些Manager，并通过startSwitchConnections方法使能OpenFlow的底层信道。

================= openflowplugin.impl.OpenFlowPluginProviderImpl ===============
public class OpenFlowPluginProviderImpl
public void initialize()
connectionManager = new ConnectionManagerImpl();
deviceManager = new DeviceManagerImpl();
rpcManager = new RpcManagerImpl();
statisticsManager = new StatisticsManagerImpl();
startSwitchConnections();
private void startSwitchConnections() // 使能OpenFlow的底层信道
return switchConnectionProvider.startup(); // 在OpenFlowJava中实现

上述StartSwitchConnections方法中的switchConnectionProvider是在OpenFlowJava中实现的。查看openflowjava-protocol-impl下的blueprint文件，得知OpenFlowJava的入口类为core.SwitchConnectionProviderFactoryImpl。SwitchConnectionProviderFactoryImpl负责生成SwitchConnectionProviderImpl，在SwitchConnectionProviderImpl的构造方法中，主要就是实例化了SerializerRegistryImpl和DeserializerRegistryImpl，并通过这两个实例的init方法注册了OpenFlow v1.0和OpenFlow v1.3中各类数据结构。

SwitchConnectionProviderFactoryImpl的startup方法被OpenFlowPluginProviderImpl中startSwitchConnections方法调用，它通过Netty进行与OpenFlow socket相关的操作。socket收到消息后，会最终调用到core.connection.ConnectionAdapterImpl的consume-DeviceMessage方法，这里面的调用层次比较深，就不进行代码的分析了。

===== openflowjava.protocol.impl.core.SwitchConnectionProviderFactoryImpl ======
public class SwitchConnectionProviderImpl
public SwitchConnectionProviderImpl()
serializerRegistry = new SerializerRegistryImpl(); // Outbound编码器
serializerRegistry.init();
deserializerRegistry = new DeserializerRegistryImpl(); // Inbound解码器
deserializerRegistry.init();
public ListenableFuture<Boolean> startup() // 被OpenFlowPluginProviderImpl中的
// startSwitchConnections方法调用，通过netty进行socket操作
try
serverFacade = createAndConfigureServer();
private ServerFacade createAndConfigureServer()
if (transportProtocol.equals(TransportProtocol.TCP) ||
transportProtocol.equals(TransportProtocol.TLS)) // 使用TCP或TLS传输
server = new TcpHandler(connConfig.getAddress(), connConfig.getPort());
connectionInitializer = new TcpConnectionInitializer();

ConnectionAdapterImpl的consumeDeviceMessage方法，会将OpenFlow中各类Symm-etric消息和Asynchronous消息都交给实现了OpenFlowProtocolListener接口的实例去处理（注意，ConnectionAdapterImpl的consumeDeviceMessage方法不会处理Controllerto-Switch类消息，如FlowMod、PacketOut等），实现OpenFlowProtocolListener接口的类存在于OpenFlow Plugin中，有以下两个。

1）openflowplugin.impl.connection.listener.OpenflowProtocolListenerInitialImpl，处理握手阶段的Symmetric消息，包括Hello、EchoRequest和Error 3种。

2）openflowplugin.impl.device.listener.OpenflowProtocolListenerFullImpl，处理非握手阶段的Symmetric消息和Asynchronous消息，其中Symmetric消息仍包括Hello、EchoRequest和Error 3种，Asynchronous消息主要包括PacketIn、PortStatus等。

OpenflowProtocolListenerInitialImpl实例的onHelloMessage方法调用了handshakeStepWrapper的run方法，run方法调用了handshakeManager的shake方法，shake方法的主体逻辑如下所示。若判断需要由控制器发起Hello，则通过sendHelloMessage方法发送Hello消息，若收到了交换机发出来的Hello，则进行OF版本协商。协商好版本后，会通过postHandshake方法协商Features，之后就认为连接已经建立成功了。

============= openflowplugin.openflow.md.core.HandshakeManagerImpl =============
public class HandshakeManagerImpl
public synchronized void shake()
try
if (receivedHello == null) // 若控制器首先发起hello
sendHelloMessage(); // 向设备发送hello
if (useVersionBitmap && remoteVersionBitmap != null) // 如果有可用版本
handleVersionBitmapNegotiation(); // 调用postHandshake方法
protected void postHandshake()
Future<RpcResult<GetFeaturesOutput>> featuresFuture = connectionAdapter
.getFeatures(); // Features请求
Futures.addCallback()
public void onSuccess()
if (rpcFeatures.isSuccessful())
handshakeListener.onHandshakeSuccessful(); // 连接建立成功

OnHandshakeSuccessful的执行过程，会通过SalRegistrationManager的onSessionAdded方法实例化一个ModelDrivenSwitchImpl作为该交换机在控制器上的代理，通过这个实例可以对该交换机进行OpenFlow的相关操作，如发送FlowMod、GroupMod等。onSessionAdded方法紧接着会发布一个nodeAdded的Notification，相关的MD-SAL模块可以订阅这一Notification。上述过程的调用层次太深，因此这里不做代码分析了。

至此，该交换机就完成了上线。

6.2.2　l2switch获得PacketIn

交换机已经连接上了控制器，此时一台主机向另一台主机发送数据包，交换机包装PacketIn消息发送给控制器。OpenFlowjava对该消息进行解封装，通过ConnectionAdapterImpl的consumeDeviceMessage方法将该消息交给OpenflowProtocolListenerFullImpl实例的onPacketInMessage方法进行处理，由onPacketInMessage方法再调用DeviceContextImpl实例的processPacketInMessage方法进行处理。DeviceContextImpl实例的processPacketInMessage方法会发布一个packetReceived的Notification，l2switch模块中的EthernetDecoder实例通过onPacketReceived方法订阅这一事件。

================= openflowplugin.impl.device.DeviceContextImpl =================
public class DeviceContextImpl
public void processPacketInMessage()
final ListenableFuture<?> offerNotification =
notificationPublishService.offerNotification(packetReceived);
// 发布一个packetReceived的Notification
================ l2switch.packethandler.decoders.EthernetDecoder ===============
public class EthernetDecoder
public void onPacketReceived(PacketReceived packetReceived)
decodeAndPublish(packetReceived); // 订阅packetReceived的Notification

这里需要特别注意的是，ODL对Notification的处理采用了最为原始的“发布—订阅”模式，这意味着ODL并没有设计一种机制对多个订阅了packetReceived事件的模块进行约束，因此订阅了packetReceived事件的模块对PacketIn消息的处理是没有明确先后顺序的。这种对PacketIn的处理机制，会使开发存有潜在的安全问题。后面在介绍ONOS时，会分析在ONOS中保证多个模块对PacketIn有序处理的相关机制。

EthernetDecoder实例的onPacketReceived方法调用了decodeAndPublish方法对PacketIn消息进行进一步的处理，通过decode方法将PacketIn消息格式化，然后再发布一个ethernetPacketReceived事件。ArpDecoder实例、Ipv4Decoder实例和Ipv6Decoder实例都通过onEthernetPacketReceived方法订阅了ethernetPacketReceived，这些实例的onEthernetPacketReceived方法，会判断Ethernet的帧类型是否与自身类型相符，若符合则通过decode方法对格式化后的PacketIn消息进行进一步的格式化，然后再发布相应的ArpPacketReceived/Ipv4PacketReceived/Ipv6PacketReceived事件。

在L2switch模块中，订阅ArpPacketReceived事件的子模块有4个，订阅Ipv4PacketReceived事件的子模块有2个，订阅Ipv6PacketReceived事件的子模块也有2个，如表6-1所示。

表6-1　L2switch模块中子模块对事件的订阅情况

6.2.3　l2switch下发PacketOut和FlowMod

经过上面的调用过程，现在数据包进入了各个子模块的视野。下面通过arphandler来介绍PacketOut的实现，通过l2switch-main来介绍FlowMod的实现。

1.PacketOut

ArpHander通过onArpPacketReceived方法接收ARP包，调用PacketDispatcher的dispatchPacket方法进行处理，dispatchPacket方法获得ARP目的主机的接入端口，然后调用sendPacketOut方法将已知目的的ARP直接发送给目的主机，这样可以避免泛洪不必要的开销。sendPacketOut方法使用OpenFlowPlugin中PacketProcessingServiceImpl提供的RPC——transmitPacket方法，transmitPacket调用handleServiceCall方法将PacketOut消息通过socket下发给目的主机的接入交换机。可以看到，l2switch中的ArpHandler并没有完成ArpProxy的功能，只是将ARP消息经过控制平面进行了转发。

================== l2switch.arphandler.core.ArpPacketHandler ====================
public class ArpPacketHandler
public void onArpPacketReceived()
packetDispatcher.dispatchPacket(packetReceived.getPayload(),
rawPacket.getIngress(), ethernetPacket.getSourceMac(),
ethernetPacket.getDestinationMac());
================== l2switch.arphandler.core.PacketDispatcher ====================
public class PacketDispatcher
public void dispatchPacket()
if (srcConnectorRef != null)
if (destNodeConnector != null) // 若ARP的目标主机已知
sendPacketOut(); // 直接通过目标主机的接入交换机发送PacketOut
else // 若ARP的目标主机未知
floodPacket(); // 在点前交换机上泛洪PacketOut
public void sendPacketOut()
packetProcessingService.transmitPacket();

2.FlowMod

前面提到，控制器和交换机握手结束后，会为交换机生成一个ModelDrivenSwitchImpl实例。ModelDrivenSwitchImpl的祖先接口为ModelDrivenSwitch，它继承了OpenFlow Plugin中各种各样的服务，因此这个实例可以代理交换机的OpenFlow相关操作。

=============== openflowplugin.api.openflow.md.ModelDrivenSwitch ===============
public interface ModelDrivenSwitch extends SalGroupService, SalFlowService,
SalMeterService, SalTableService, SalPortService, ...

要下发FlowMod的话，调用ModelDrivenSwitchImpl的RPC的addFlow方法即可。不过，l2switch下发流表直接调用了OpenFlowPlugin中SalFlowService的RPC方法addFlow。其逻辑为：每当收到ARP时，l2switch便认为ARP的源主机和目的主机就要开始通信了，于是l2switch-main子模块中的ReactiveFlowWriter实例就通过onArp-PacketReceieved方法监听ArpPacketReceieve事件，方法不会处理ARP，而是通过FlowWriterServiceImpl实例的addBidirectionalMacToMacFlows方法下发L2双向流表。addBidirectionalMacToMacFlows方法通过loopremove模块获得源和目的间的通信路径，最后调用到SalFlowService的addFlow方法向相关的交换机下发流表。

==================== l2switch.flow. FlowWriterServiceImpl =====================
public class FlowWriterServiceImpl
public void addBidirectionalMacToMacFlows()
addMacToMacFlow(destMac, sourceMac, sourceNodeConnectorRef); // 从目的到源
addMacToMacFlow(sourceMac, destMac, destNodeConnectorRef); // 从源到目的
public void addMacToMacFlow()
writeFlowToConfigData();
private Future<RpcResult<AddFlowOutput>> writeFlowToConfigData()
return salFlowService.addFlow(); // 通过SalFlowService的方法下发流表

以上就是对ODL中OpenFlow通信实例的介绍，当然这中间省略了很多复杂的调用逻辑。有了这部分内容作为基础，接下来开始对ODL中与云业务相关的Plugin的实现进行介绍。

6.3　OpenStack Networking-ODL

OpenStack网络通过networking-odl项目与OpenDaylight进行对接，目前支持的Plugin包括ML2、L3、FWaaS、LBaaS、SFC、BGPVPN、SFC、L2GW、QoS等，其功能非常丰富。关于ODL对这些功能的后端实现，会放到本章后面的内容中进行具体介绍，本节先来看看networking-odl的设计。

6.3.1　v1

Networking-odl经历了两个版本。在v1版本中networking-odl的主要工作就是将Neutron的RESTful API转化为ODL的RESTful API，然后直接进行透传，networking-odl本身是没有任何状态的。由于ODL在本地有自己的数据库，因此在使用networking-odl v1时，尤其是在Neutron HA的部署方式下，Neutron和ODL间的数据库同步存在很大的问题。例子有很多，下面举一个最简单的。

现有Neutron A和Neutron B，首先发生的操作是Neutron A收到create_port的API，Neutron A写入Neutron DB，然后发create_port给ODL，紧接着发生的操作是Neutron B收到delete_port的API删除刚刚经由Neutron A添加的Port，Neutron B写入Neutron DB，然后发送delete_port给ODL。此时，Neutron数据库中已经没有这个Port了，但是由于某种网络原因，Neutron发给ODL的delete_port先于create_port到达，导致ODL会对先到的delete_port报错，并对后到的create_port执行相应的操作，因此在ODL数据库中这个Port是存在的。这种状态的不一致很可能会导致业务的崩溃，在生产环境中这是不可接受的。

6.3.2　v2

从M版本开始，networking-odl提出了v2版本，希望能解决数据库同步问题。Networking-odl v2中的做法是增加一个Syncer，它在收到Plugin北向的RESTful API后，不是直接透传给ODL，而是通过Syncer来同步Neutron和ODL间的状态。Networking-odl v2的架构如图6-10所示，图中ODL也表示成了集群的形式。

图6-10　通过networking-odl v2与ODL对接

具体来说，整个由上及下的工作流如图6-11所示。用户发送一个Neutron API，由Neutron-Server路由给相应的ODL v2 Driver，ODL v2 Driver不会直接发送给ODL，而是将该API作为一个journal entry存入Journal DB中，标记其状态为PENDING。Networking-odl v2会在后台运行一个Journal Thread，Journal Thread会读取Journal DB中最先存入的journal entry，将其状态置为PROCESSING，然后转化为相应的RESTful API发送给ODL，并阻塞等待ODL的执行结果。如果ODL执行成功，则Journal Thread将该journal entry状态置为COMPLETED，表示执行成功，否则将该journal entry状态重新置回PENDING，并将其Retry Counter（重试计数器）加1。当某个journal entry的Retry Counter超过一定次数时，Journal Thread会将其状态置为FAILED，表示执行失败。除了Journal Thread以外，后台还会有MaintenanceThread去处理COMPLETED和FAILED的journal entry，如图6-12所示。

图6-11　networking-odl v2的工作流

图6-12　networking-odl v2中Journal的状态机

数据库同步出现问题，主要源于操作顺序的混乱。而Journal的作用实际上就是为Neutron和ODL间的操作引入顺序执行的能力，从而有效解决同步过程中遇到的问题。不过，Journal Thread的阻塞必然会降低Neutron与ODL间的并发能力，为了不影响Neutron北向业务API的并发能力，因此networking-odl v2在收到Neutron北向API并成功存入Journal DB后会直接返回，这从图6-11中也能够看得出来。

实际上，Neutron和SDN控制器间数据库的同步问题是相当普遍的，这一部分如果做得不好基本上就意味着无法在生产环境中应用。Networking-odl v2也只能说是一个初步的探索，未来还需要不断地优化，以平衡一致性和并发性的要求。

6.4　Neutron-Northbound的实现

ODL中有一个专门用来接收、分发networking-odl的RESTful API的Plugin，在Hydrogen和Helium两个版本中，实现这一功能的Plugin叫作OpenStack Service，后面改名为Neutron-Northbound Plugin。目前Neutron-Northbound Plugin在github上的项目名为neutron-master。

6.4.1　对接Networking-ODL

Neutron和ODL对接的架构如图6-13所示，networking-odl下发RESTful给Neutron-Northbound Plugin，Neutron-Northbound Plugin在实现上分为3个模块，包括Northbound-API、Transcriber和Neutron-SPI。其中，Nort-h-b-ound-API模块接收RESTful API，并将其中Neutron的标准数据结构交给Transcriber模块。Transcriber模块负责将Neutron的标准数据结构转化为MD-SAL中为Neutron设计的YANG Model，然后对DataStore进行相应的操作，其他一些Plugin（如Netvirt、VTN等）监听DataStore上的数据变化，并完成相应的后端操作。Neutron-SPI模块在Boron之前的版本中，能够从Northbound-API处接收Neutron的数据结构，并直接发送给后端的Plugin。从Boron版本开始，Neutron-SPI模块的功能被废弃掉了，Plugin采用监听DataStore的方式来触发后端操作。

图6-13　Neutron和ODL对接的架构

在下面的代码分析中，将以Create Port为例来介绍Neutron-Northbound Plugin对RESTful API的具体处理。由于Neutron-SPI模块目前已经被弃用了，因此不再对其进行介绍。

6.4.2　RESTful请求的处理示例

Northbound-API模块中的NeutronPortsNorthbound负责对标准Port资源的RESTful请求进行处理，其中Create_Port请求由createPorts方法处理。createPorts方法主要调用了create方法，然后由create方法将标准的Create Port请求交给Transcriber模块中NeutronPortInterface的add方法。

================ neutron.northbound.api.NeutronPortsNorthbound ==================
public final class NeutronPortsNorthbound
public Response createPorts()
return create();
================ neutron.northbound.api.AbstractNeutronNorthbound ===============
public abstract class AbstractNeutronNorthbound
protected Response create()
if (input.isSingleton())
neutronCRUD.add(); // 调Transcriber中NeutronPortInterface的add方法

Transcriber模块中NeutronPortInterface的add方法，经过一系列的调用来到updateMd方法，该方法首先通过toMd方法将标准的Create Port请求转化为MD-SAL中为Neutron Port定义的数据结构，然后通过put操作将转换后的数据结构写入数据库。Database的更新会触发相应的Neutron事件，一些实现Neutron业务的Plugin可以通过监听Neutron事件来触发相关的业务逻辑。

================ neutron.transcriber.AbstractNeutronInterface =================
public abstract class AbstractNeutronInterface
private void updateMd()
final T item = toMd(); // 转化为MD-SAL中为Neutron Port定义的数据结构
final InstanceIdentifier<T> iid = createInstanceIdentifier();
// 创建Neutron Port在DOM树上的标识
tx.put(LogicalDatastoreType.CONFIGURATION, iid, item, true);
// 使用PUT操作将新的Neutron Port更新到configuration树上
final CheckedFuture<Void, TransactionCommitFailedException> future = tx.submit();
// 提交transaction写数据库

6.5　Netvirt简介

OVSDB-Netvirt和VPNService可以说是ODL中功能最丰富的两个Plugin。从字面上来理解，OVSDB-Netvirt是通过OVSDB来实现网络虚拟化的，VPNService提供VPN服务而没有限定南向协议，两者都能作为OpenStack网络的后端，在功能上有很多重叠。在Beryllium版本中，OVSDB-Netvirt从OVSDB中抽离出来，成为一个独立的Netvirt Plugin。在Boron版本中，社区开始推进将VPNService合并入Netvirt。下文为了便于区别，合并前的Netvirt还是称为OVSDB-Netvirt，而Netvirt特指合并后的Netvirt Plugin。

在合并前，OVSDB-Netvirt和VPNService的功能对比如表6-2所示。

表6-2　OVSDB-Netvirt和VPNService的功能对比

6.5.1　OVSDB-Netvirt和VPNService的合并

OVSDB-Netvirt早在ODL的Hydrogen版本中就已经出现了，经过几个版本的发展后，对OpenStack网络的支持已经很完善，可以说是ODL中的“明星项目”，在合并前其项目结构如图6-14所示。北向上依赖Neutron-Northbound来处理RESTful API，而所有后端的实现都是由Netvirt-provider目录下的Application来完成的，南向上通过OpenFlow和OVSDB来操作pipeline。

图6-14　OVSDB-Netvirt的项目结构（合并前）

同样作为OpenStack网络的后端，虽然VPNService的功能也很多，但是VPNService的名气一直没有OVSDB-Netvirt大，借着这次和OVSDB-Netvirt的合并，VPNService才得以走入主流视野。VPNService是一个十分庞大的项目，在合并前主要分为三大块。一是Neutron VPN Manager，它监听DataStore中由Neutron Northbound Transcriber生成的数据结构，并将其转化为VPNService的数据结构，再存入DataStore中。二是各类业务逻辑模块，它们监听DataStore中VPNService的数据结构，并提供相应的业务实现，主要包括负责二层网络连通性的ELAN Manager，负责三层网络连通性的VPN Manager，负责NAT的NATService，等等。三是各类通用的资源管理，如负责隧道管理的Tunnel Manager，负责接口管理的Interface Manager，等等。

和OVSDB-Netvirt合并后，社区的计划是将VPNService现有的代码分散到3个Plugin中。Neutron VPN Manager和各类业务逻辑模块合并到新的Netvirt中，各类通用的资源管理提取到Genius中，而与BGP VPN直接相关的模块仍然保留在VPNService项目中。合并后的代码结构如图6-15所示。

对于合并的具体操作方式，社区计划将其分为3个阶段，如图6-16所示，图中Netvirt Project指的是合并后的Netvirt，图中的Netvirt指的则是原有的OVSDB-Netvirt。

Carbon版本处于第二个阶段。由于OVSDB-Netvirt和Genius不能兼容，因此对OVSDB-Netvirt和VPNService进行代码上的合并是很困难的，两者目前是Netvirt下两种独立的Provider，Netvirt Plugin现在的项目结构如下所示。在ODL的后续版本中，Netvirt Plugin将以VPNService为代码基础进行后续功能的开发和迭代，而OVSDB-Neutron的代码会继续进行维护，但不会再提供新的功能。

netvirt-master
openstack/ // 这个子模块是原来的OVSDB-Netvirt
vpnservice/ // 这个子模块是原来的VPNService

图6-15　合并后的代码结构

图6-16　合并的阶段计划

6.5.2　Genius

说到这里，就需要介绍一下Genius这个Plugin了。ODL中有很多能够提供Neutron后端服务的Plugin，每个Plugin都包含很多的Application，这些Application在实现上都会用到OpenFlow pipeline。OpenFlow Pipeline可以看作是一种网络资源，不过由于ODL缺乏编排OpenFlow pipeline的机制，因此不同的Application想要协作起来，在开发上会造成很大的耦合。一旦协作不好，如不同的Application向同一个Table下发了彼此冲突的流表，那么流量在pipeline上的后续处理也就全都混乱了。

OVSDB-Neutron和VPNService合并为Netvirt，推动了这一资源编排机制的出现。OVSDB-Netvirt和VPNService中的Application都非常多，两者的pipeline设计也都非常复杂，如果没有一种编排机制来协调，那么开发新功能的困难会越来越大。OVSDB-Neutron和VPNService的合并是在Boron版本开始的，Genius也在这一版本中应运而生。图6-17所示为Genius的架构示意图，它目前能够协调的资源包括Interface、Tunnel、OpenFlow pipeLine等，Genius还提供一些维护资源可用性的功能，比如监测隧道通断。实际上，Genius目前具有的大部分功能，都是直接从VPNService中迁移出来的。VPNService和Genius的原生兼容，也正是社区选择VPNService而非OVSDB-Netvirt进行演进的一个最重要的原因。

图6-17　Genius示意图

图6-17中还体现了一个信息，即Genius可以作为南向协议的适配，为北向Plugin提供统一操作底层资源的API，从而使北向的Plugin和南向协议解耦。换一句话说，Genius所希望实现的目标和AD-SAL是非常类似的。目前Genius只提供极少数资源的适配，如果Genius未来想要做大，也势必和当初的AD-SAL面临同样的问题。不过，Genius的主要作用在于资源的编排，这一点应该是大势所趋，但是适配会借着Genius重新在ODL中拾回其基础性的地位吗？这一点笔者也不得而知，只能看后面版本的发展了。

6.6　Netvirt-OVSDB-Neutron的实现

Netvirt-OVSDB-Neutron现在所在的代码路径是netvirt-master-openstack，其目录结构如下所示。其中，net-virt分支提供了各个Application的业务逻辑，它会对接Neutron-Northbound，并触发net-virt-providers分支中相应Application对Pipeline进行操作；net-virt-it分支提供了integration-test框架；net-virt-providers分支提供了Application对Pipeline的操作逻辑；Netvirt2是在Beryllium版本中出现的，从目前Carbon版本的代码来看，它会对net-virt的代码结构进行重构，将net-virt、net-virt-it和net-virt-provider收入到同一个目录下，并将hwvtep的功能集成进来。

netvirt-master
openstack
net-virt/ // 提供各个Application的Manager，并对接Neutron-Northbound
net-virt-it/ // 提供integration-test框架
net-virt-providers/ // 提供各个Application对Pipeline的操作逻辑
net-virt-sfc/ // 提供服务链的实现
netvirt2/ // Beryllium版本出现，重构net-virt的代码结构
sfc-translator/ // Boron版本出现，重构net-virt-sfc的代码结构

net-virt-sfc分支提供了服务链的实现，但是它只能处理ODL SFC中的API，不能对接在Boron版本中出现的Neutron SFC的API Sfc-translator，从Carbon版本的代码来看，它提供了对于Neutron SFC的RESTful API的适配，功能上没有看到什么新的变化。

下面将主要介绍net-virt、net-virt-providers两个分支的代码逻辑。由于net-virt-sfc目前在实现上还不完整，所以不会对其进行介绍，关于SFC的相关内容可以参考本章的6.8节。

6.6.1　net-virt分支

net-virt分支的目录结构如下所示，其中省去了冗长的Package前缀。net-virt对接Neutron-Northbound Plugin有两种方式，第一种是通过实现Neutron-SPI中的INeutron***Aware接口，直接处理RESTful API；另一种是监听DataStore中由Transcriber写入的数据结构，间接地处理RESTful API。由于从Boron版本开始，Neutron-Northbound不再支持Neutron-SPI，因此目前net-virt只能通过监听DataStore的方式来间接地处理RESTful API。RESTful API会先由***Handler进行处理，然后调用impl目录下相应Application的业务逻辑，在业务逻辑的实现中再通过net-virt-providers分支中相应的Application去操作OpenFlow pipeline，最终实现数据平面上相应的处理。

net-virt
openstack
api/ // 提供net-virt-providers中各个Applcation的接口
impl/ // 提供了各类Application业务逻辑的实现
translator/ // 提供了两种对接Neutron-Northbound方式的实现
***Handler //

1.***Handler对RESTful API的处理

以PortHandler为例进行分析，其他的***Handler模块虽然在业务逻辑上互有区别，但是在代码逻辑上与PortHandler基本是一致的。RESTful API发布一个Create_Port后，无论是哪种对接Neutron-Northbound的方式，都会使用PortHandler的neutronPortCreated方法。该方法会将Port资源的描述封装成一个NorthboundEvent然后发布出去，同时PortHandler会通过processEvent方法监听该事件，并调用doNeutronPortCreated方法进行处理。

doNeutronPortCreated方法主要干了两件事情。一是判断该Port是否已经在线，如果已经在线则调用netvirt.providers.openflow13.OF13Provider中的handleInterfaceUpdate，下发二层相关的流表。二是通过neutronL3Adaptor的handleNeutronPortEvent方法来实现与该Neutron Port三层相关的处理逻辑。

=========================== netvirt.PortHandler ============================
public class PortHandler
public void neutronPortCreated()
enqueueEvent(new NorthboundEvent(neutronPort, Action.ADD)); // 发布事件
public void processEvent()
NorthboundEvent ev = (NorthboundEvent) abstractEvent;
switch (ev.getAction())
case ADD:
doNeutronPortCreated(); // 处理事件
break;
private void doNeutronPortCreated()
for (Node node : nodes) // 遍历所有Node
OvsdbTerminationPointAugmentation port = findPortOnNode();
if (port != null) // 如果在Node上找到了Port
if (network != null && !network.getRouterExternal())
if (bridgeConfigurationManager.createLocalNetwork())
networkingProviderManager.getProvider().handleInterface-Update();
// 调用netvirt.providers.openflow13.OF13Provider中
// 的handleInterfaceUpdate，下发二层相关的流表
neutronL3Adapter.handleNeutronPortEvent(neutronPort, Action.ADD);
// 实现该Neutron Port三层相关的处理逻辑

OF13Provider的handleInterfaceUpdate方法会调用programLocalRules方法向该端口所在的交换机下发流表，从该端口流入的流量标记为VLAN，流向该端口的流量剥掉VLAN并进行转发。另外，programLocalRules方法还会下发流表，使能与该端口相关的Fixed和Custom安全组规则。接下来会判断计算节点间的网络类型：如果是VLAN类型，则调用programVlanRules方法来处理计算节点间的VLAN流量；如果是隧道类型，则会遍历除该新建端口所在的交换机（称为源交换机）以外的，所有其他远端交换机，在源交换机和远端交换机间建立隧道，并通过programTunnelRules方法处理计算节点间的隧道流量。

=================== netvirt.providers.openflow13.OF13Provider ==================
public class OF13Provider
public boolean handleInterfaceUpdate()
programLocalRules();
// 向该新建端口所在的交换机下发流表，流入新建端口的流量标记VLAN，
// 流出新建端口的流量剥掉VLAN并转发，使能相关的安全组规则
if (isVlan(networkType)) // 若计算节点间通过VLAN组网
programVlanRules(); // 处理计算节点间的VLAN流量
else if (isTunnel(networkType)) // 若计算节点间通过隧道组网
for (Node dstNode : nodes.values())
// 遍历除该新建端口所在的交换机以外的，所有其他的远端交换机
if ((src != null) && (dst != null))
sourceTunnelStatus = addTunnelPort();
// 若源交换机尚未与远端交换机间建立隧道，
// 则在源交换机上新建该隧道
if (dstBridgeNode != null)
destTunnelStatus = addTunnelPort();
// 若远端交换机尚未与源交换机间建立隧道，
// 则在目的交换机上新建该隧道
if (sourceTunnelStatus)
if (isSrcinNw && isDestinNw)
programTunnelRules();
// 向源交换机上下发BUM流表
programTunnelRules();
// 向远端交换机上下发BUM流表
else if (configurationService.isRemoteMacLearnEnabled())
programTunnelRules();
// 可通过OVS的learn action支持MAC地址自学习
if (destTunnelStatus)
programTunnelRules(); // 向远端交换机下发流表，用于处理
// 发向该新建端口的L2单播流量
if (…)
programTunnelRulesInNewNode();
// 向该新建端口所在的交换机下发流表，用于处理发向
// 与该新建端口在同一二层的其他端口的，L2单播流量

neutronL3Adaptor的handleNeutronPortEvent方法会根据Port的类型跳入不同的if-else逻辑分支。如果Port是连接到external network的ROUTER_GATEWAY，则调用net-virt-providers分支中GatewayMacResolverService实例的resolveMacAddress方法定期地获取external gateway的MAC地址（这个方法会放到后面net-virt-providers部分再分析）；如果Port是ROUTER_INTERFACE，则会调用handleNeutronRouterInterfaceEvent方法去处理；若上述两个判断都不能满足，那么Port就是连接虚拟机的Port，则会调用updateL3ForNeutronPort方法去处理。

======================= netvirt.impl.NeutronL3Adapter =======================
public class NeutronL3Adapter
public void handleNeutronPortEvent()
if (neutronPort.getDeviceOwner().
equalsIgnoreCase(OWNER_ROUTER_GATEWAY))
// 如果端口是连接到external network的ROUTER_GATEWAY
if (!isDelete)
this.triggerGatewayMacResolver();
// 定期地解析external gateway的MAC地址
if (neutronPort.getDeviceOwner().
equalsIgnoreCase(OWNER_ROUTER_INTERFACE) ||
neutronPort.getDeviceOwner().
equalsIgnoreCase(OWNER_ROUTER_INTERFACE_DISTRIBUTED))
// 如果该端口是ROUTER_INTERFACE
if (neutronPort.getFixedIPs() != null)
for (Neutron_IPs neutronIP : neutronPort.getFixedIPs())
this.handleNeutronRouterInterfaceEvent();
else // 若上述条件都不满足，则该端口是虚拟机的接入端口
this.updateL3ForNeutronPort();

如果Port是ROUTER_INTERFACE，则handleNeutronRouterInterfaceEvent方法首先会调用programFlowsForNeutronRouterInterface方法。programFlowsForNeutronRouterInt-erface方法的主要逻辑如下一段代码所示，具体作用见其中注释，被注释的方法同样会调用net-virt-providers分支中相应的Application来操作pipeline。然后，handleNeutronRout-erInterfaceEvent方法会调用updateL3ForNeutronPort方法，由updateL3ForNeutronPort方法遍历所有的neutronPort并调用net-virt-providers分支中l3ForwardingProvider实例的programForwardingTableEntry方法，对发送到ROUTER_INTERFACE的L3流量进行目的MAC地址的改写。

====================== netvirt.impl.NeutronL3Adapter ========================
public class NeutronL3Adapter
private void programFlowsForNeutronRouterInterface()
for (Node node : nodes)
for (Neutron_IPs neutronIP : ipList)
for (NeutronRouter_Interface srcNeutronRouterInterface :
subnetIdTo-Router-InterfaceCache.values())
// 遍历租户所有的ROUTER_INTERFACE
programFlowsForNeutronRouterInterfacePair);
// 改写L3流量的源MAC地址，实现各个租
// 户网络所连ROUTER_INTERFACE间的路由
if (! isExternal)
programFlowForNetworkFromExternal();
// 改写L3流量的源MAC地址，实现由租户网络所
// 连的ROUTER_INTERFACE到external_network，
// 以及所连的ROUTER_GATEWAY的路由
distributedArpService.programStaticRuleStage1();
// 下发流表代理ROUTER_INTERFACE回复ARP
programIcmpEcho();
// 下发流表代理ROUTER_INTERFACE回复ICMP
programIpRewriteExclusionStage1();
// 没有分配Floating IP的虚拟机实例的流量跳过NAT处理

如果Port是连接虚拟机的Port，则会直接通过updateL3ForNeutronPort方法，调用net-virt-providers分支中l3ForwardingProvider实例的programForwardingTableEntry方法，从而对发送到新建虚拟机端口的L3流量进行目的MAC地址的改写。

到这里，PortHandler对Create Port的处理就结束了，Update Port的处理也是类似的，在执行过程中会对Security Group进行处理。除了PortHandler以外，处理Network、Subnet、Router、FloatingIP这类资源的**Handler结构都是类似的，不过处理的逻辑都比较简单，主要就是更新ODL中相关的数据结构，FloatingIPHandler的逻辑复杂一些，要通过net-virt-providers分支中的InboundNatService、OutboundService模块处理FloatingIP实例中的南北向流量，以及通过net-virt-providers分支中的ArpResponderService模块完成Floating IP的静态ARP Reply。FwaasHandler没有具体的实现，LBaaS***Handler会通过net-virt-providers分支中的LoadBalancerService模块对流量L2～L4字段进行改写以实现负载均衡。

2.OVSDB Event的触发逻辑

与Neutron从上向下发送RESTful API不同，还有另外一大类业务触发逻辑，它们是由设备通过OVSDB从面上报给ODL的，net-virt分支中的SouthboundHandler负责对这类OVSDB事件进行处理。SouthboundHandler通过ovsdbUpdate方法将OVSDB事件转换为Southbound Event，对Southbound Event的处理逻辑如下所示。

======================== netvirt.SouthboundHandler =========================
public class SouthboundHandler
public void processEvent()
switch (ev.getType())
case NODE:
processOvsdbNodeEvent(); // 处理物理节点上线，创建br-int和br-ex
case BRIDGE:
processBridgeEvent(); // 处理br-int和br-ex的状态更新
case PORT:
processPortEvent(); // 处理Port的状态更新，虚拟机首次
// 上线后由此触发流表的下发
case OPENVSWITCH:
processOpenVSwitchEvent(); // 处理OVS守护进程的状态更新

当在物理节点上执行ovs-vsctl set-manager命令完成和ODL中的OVSDB连接时，SouthboundHandler会接收到ovsdbNodeEvent，紧接着触发processOvsdbNodeEvent方法的执行。processOvsdbNodeEvent方法最终调用BridgeConfigurationManagerImpl实例的prepareNode方法从而创建br-int和br-ex，后续的流水线设计大多发生在br-int上，br-ex只负责连通br-int和external以区别南北向流量的传输路径。

当bridge创建好时，SouthboundHandler会接收到ovsdbBridgeEvent，紧接着触发processBridgeEvent方法的执行，由processBridgeEvent方法调用NodeCacheManagerImpl的nodeAdded方法，然后发布节点上线的事件。该事件由processNodeUpdate方法来处理，在processNodeUpdate方法中会回调SouthboundHandler的notifyNode方法，notifyNode方法会调用netvirt.providers.openflow13.OF13Provider的initializeOFFlowRules方法初始化br-int和br-ex上的流表。

======================= netvirt.SouthboundHandler ==========================
public class SouthboundHandler
private void processBridgeCreate()
if (datapathId != null)
nodeCacheManager.nodeAdded();
// 调用NodeCacheManagerImpl的nodeAdded方法
public void notifyNode (Node node, Action action)
if ((action == Action.ADD) && (southbound.getBridge(node) != null))
networkingProviderManager.getProvider().initializeOFFlowRules();
// 初始化br-int和br-ex上的流表
================== netvirt.impl.NodeCacheManagerImpl ======================
public class NodeCacheManagerImpl
public void nodeAdded()
enqueueEvent(new NodeCacheManagerEvent(node, Action.UPDATE)); // 发布事件
public void processEvent()
switch (ev.getAction())
case UPDATE:
processNodeUpdate(); // 处理事件
private void processNodeUpdate()
for (NodeCacheListener handler : handlers.values())
try
handler.notifyNode(); // 回调SouthboundHandler的notifyNode方法

Bridge的初始化完成后，就等着虚拟机的接入了。虚拟机的接入是由Nova通过ovs-vsctl add-port命令完成的，此时SouthboundHandler会收到ovsdbPortEvent，紧接着触发processPortEvent方法的执行。processPortEvent方法最终通过handleInterfaceUpdate方法来处理主机的上线，包括为主机下发L2流表、ARP Responder流表和L3流表。这些的具体实现会在6.6.2节中进行介绍。

======================== netvirt.SouthboundHandler =========================
public class SouthboundHandler
private void processPortUpdate()
if (network != null)
if (!(Action.UPDATE.equals() && isMigratedPort()))
if (!network.getRouterExternal())
handleInterfaceUpdate();
private void handleInterfaceUpdate()
if (network != null && !network.getRouterExternal())
if (bridgeConfigurationManager.createLocalNetwork())
networkingProviderManager.getProvider().handleInterfaceUpdate();
// 下发L2的流表
if (action.equals(Action.UPDATE))
distributedArpService.processInterfaceEvent();
// 下发流表，代理该端口回复ARP
neutronL3Adapter.handleInterfaceEvent(); // 下发L3的流表

3.一个时序机制的设计

这里有一点要说明一下。本节最开始在对PortHandler的介绍中，提到了它也会执行OF13Provider的handleInterfaceUpdate方法，下发与该端口相关的流表。Southbound Handler和PortHandler为什么要下发重复的流表呢？

之所以在代码中要进行这种设计，其根源在于Nova和ODL间状态的不一致。在前面5.4节中曾介绍过，Nova会先调用Neutron的RESTful API，等到Create_Port和Update_Port都结束后才会执行plug操作。另一方面，Neutron中的Networking-odl为了保证北向RESTful API的并发性能，采用了异步设计，它在接收到Nova发出来的RESTful API后会立即返回OK，而不会等到ODL执行完Create_Port和Update_Port再向Nova返回相关的消息。

因此，很可能出现的一种情况是，Nova认为Update_Port已经成功了，然后就执行了端口的插入操作。于是，OVS会通过OVSDB将Port已上线这一状态告诉给ODL的OVSDB Plugin，接着OVSDB Plugin会发布一个PortEvent，不过Southbound Handler在收到这个PortEvent的时候，却不知道该如何操作，因为此时ODL还没有完成Create_Port，甚至连Create_Network还都没有来得及执行，因此它很有可能不知道如何下发和该端口相关的流表。这时，对于与该端口相关流表的下发，就需要等到PortHandler在处理Create_Port时，重新判断一下之前发生的情况：若Southbound Handler先处理了OVSDB插件发布的PortEvent，那么虽然Southbound Handler不会下发流表，但是数据库里一定有该端口已上线的记录。如果PortHandler发现了该端口上线的记录，就会认为之前出现了时序错乱的问题，此时PortHandler就会“代替”Southbound Handler下发和该端口相关的流表。

有了上面的铺垫，回头再去看本节开头PortHandler中的处理逻辑，就会得到一种更为深入的理解了。按照正常的时序，PortHandler在处理Create_Port时，端口应该还没有执行Plug，那么遍历已上线端口的结果应该是找不到该新建端口的，此时就不会执行OF13Provider的handleInterfaceUpdate方法下发流表，而是等到Southbound Handler监听到端口上线的事件后再去下发流表。

之所以花了一些篇幅去讲这个问题，是希望读者能够了解到，对于一个分布式系统来说，如果想保证业务逻辑的健壮性，需要把状态和时序梳理得非常清楚。这种问题需要具体问题具体分析，因此在后面的代码分析中，就不再对类似设计进行说明了。读者在自己啃代码的时候，如果遇到一些看似冗余的设计，那么多半就是出于对状态和时序的考虑了。

6.6.2　net-virt-providers分支

在net-virt-providers分支的目录结构下，各类Service负责实现Pipeline上各级流表具体的Match和Action，而PipelineOrchestratorImpl主要用于规范Pipeline流表间的跳转逻辑。

1.流水线设计

Pipeline中的各级Table如下所示。其中，Table 0对入向流量进行标记，并将某些控制信令交给控制器，Table 20负责实现分布式的ARP代答，Table 30负责为浮动IP的南北向流量做入向NAT，Table 31负责将流量导入Table 39和Table 40，Table 39+Table 40可实现基于Learn的ACL，Table 40还能够独立实现基于Conntrack的出向ACL规则，Table 50负责实现负载均衡，Table 60在各个节点实现分布式路由，完成对L3流量改写SRC MAC、改Segment ID以及减TTL这3个工作。Table 70负责回复Virtual Router的ping request，以及对L3流量改写DST MAC，Table 80在目前版本中没什么作用，Table 90负责实现Security Group中的入向ACL规则，Table 100为浮动IP的南北向流量做入向NAT，Table 105负责完成MAC地址的自学习，Table 110负责完成L2流量的转发。

==================== netvirt.providers.openflow13.Service ======================
public enum Service
CLASSIFIER ((short) 0, "Classifier"), // 对入向流量进行分类
GATEWAY_RESOLVER((short) 0, "External Network Gateway Resolver"),
// 解析External Network Gateway的MAC地址
DIRECTOR ((short) 10, "Director"), // 目前暂时没有用到
SFC_CLASSIFIER ((short) 10, "SFC Classifier"), // net-virt-sfc中会用到
ARP_RESPONDER ((short) 20, "Distributed ARP Responder"), // 分布式的ARP代答
INBOUND_NAT ((short) 30, "DNAT for inbound floating-ip traffic"),
// 为浮动IP的南北向流量做入向NAT
RESUBMIT_ACL_SERVICE ((short) 31, "Resubmit service for Learn ACL"),
// 将流量导入Table 39和Table 40
ACL_LEARN_SERVICE ((short) 39, "ACL Learn Service"),
// 配合Table 40可实现基于Learn的ACL
EGRESS_ACL ((short) 40, "Egress Acces-control"),
// 配合Table 39可实现基于Learn的Egress ACL，
// 也可以独立实现基于Conntrack的Egress ACL
LOAD_BALANCER ((short) 50, "Distributed LBaaS"), // 负载均衡
ROUTING ((short) 60, "Distributed Virtual Routing (DVR)"),
// 分布式路由，根据目的IP，改写源MAC，
// 改Segment ID，减TTL
ICMP_ECHO ((short) 70, "Distributed ICMP Echo Responder"),
// 分布式的ICMP代答
L3_FORWARDING ((short) 70, "Layer 3 forwarding/lookup service"),
// 根据目的IP改写目的MAC
L2_REWRITE ((short) 80, "Layer2 rewrite service"), // 目前暂时没有用到
INGRESS_ACL ((short) 90, "Ingress Acces-control"),
// 配合Table 39可实现基于Learn的Ingress ACL，
// 也可以独立实现基于Conntrack的In gress ACL
OUTBOUND_NAT ((short) 100, "DNAT for outbound floating-ip traffic"),
// 为浮动IP的南北向流量做入向NAT
L2_LEARN ((short) 105, "Layer2 mac remote tep learning"),// 远端MAC地址自学习
L2_FORWARDING ((short) 110, "Layer2 mac,vlan based forwarding");
// 根据目的MAC进行转发

pipeline上Table间的跳转逻辑如图6-18所示，其逻辑是：分类→入向NAT→出向ACL→负载均衡→IP转发→入向ACL→出向NAT→MAC转发。其中，为了实现有状态的ACL，设计了好几个表，这比较难以理解，这一部分在后面会详细介绍。

图6-18　Netvirt-OVSDB-Neutron中的流水线设计

2.转发相关的流表

ClassifierService负责对进入br-int的流量进行标记。ClassifierService中的方法比较多，这里不具体分析代码了，表6-3列出了各个方法的作用。

表6-3　OVSDB-Netvirt中ClassifierService中的方法

由于在ODL中下发流表，Match和Action的写法十分冗长，这里Match和Action的代码就不放上来了，请读者自行参考源代码。

ArpResponderService负责处理虚拟机和Router Interface的ARP请求，通过预置的流表自动进行回复。这和Neutron的arp_responder做的事情是一样的。这一工作由programStaticArpEntry方法实现。

InboundNatService/OutboundNatService分配了Floating IP中虚拟机实例的南北向流量，通过programIpRewriteRule方法对这类流量进行目的/源IP地址的改写。由于InboundNat/OutboundNat Table是串在Pipeline里面的，因此还需要通过programIpRewriteExclusion方法来放行东西向的三层流量。

GatewayMacResolverService模块在netvirt.providers.services.arp目录下，负责定期获取external router的MAC地址。前面在net-virt分支中曾讲到，当利用neutronL3Adaptor的handleNeutronPortEvent方法判断Neutron Port是否连接到external的ROUTER_GATEWAY时，则会调用GatewayMacResolverService实例的resolveMacAddress方法，这一方法会通过init方法定期向external router发送ARP Request。当然，为了能够收到external router的ARP Reply，还需要sendGatewayArpRequest方法在发送ARP Request前，向Table 0下发一条流表对预期的ARP Reply进行收集。

当external router回复的ARP Reply送到控制器后，GatewayMacResolverService通过onPacketReceived方法进行订阅，并通过ArpResolverMetadata实例的setGateway-Mac-Address方法更新external router的MAC地址。更新动作会触发一个类型为SUBTYPE_EXTERNAL_MAC_UPDATE的NeutronL3AdapterEvent，NeutronL3Adaptor监听到这一事件后会通过updateExternalRouterMac方法更新OutboundService下发的SNAT流表，保证external router的MAC地址变更后仍能正常转发南北向的流量。

RoutingService负责模拟L3流量的路由过程，通过programRouterInterface方法可以对这类流量改写源MAC地址、减TTL、改写tunnel_id等。减TTL好理解，改写源MAC地址可理解成是从路由器的一个接口路由到了另外一个接口，改写tunnel_id实际上就是改变了流量当前所在网段的Segment ID（可以理解为这是一种非对称的IRB的实现）。

IcmpEchoResponderService也向Routing Service Table（Table 70）下发流表，它通过programIcmpEchoEntry方法代理Router Interface回复ICMP，该方法在逻辑上分为虚拟机ping同一租户中同网段的Router Interface，以及虚拟机ping同一租户中不同网段的Router Interfaces两部分。

L3ForwardingService通过programForwardingTableEntry将目的MAC地址改写为目的虚拟机的MAC地址。L3Forwarding Table在Pipeline中是紧跟在Routing Table后面的，之所以目的MAC地址的改写要与源MAC地址的改写分开，主要是为了节约资源，避免出现N2级别的流表开销。

L2ForwardingService提供了控制L2转发的API，L2ForwardingService中的方法有很多，表6-4列出了一些主要方法的作用。

表6-4　OVSDB-Netvirt中L2ForwardingService中的方法

3.与安全组相关的流表

Ingress ACL和Egress ACL，是OVSDB-Neutron中最复杂的模块，它负责实现Security Group的功能。由于Security Group的实现要求是有状态的，因此不能通过Proactive方式静态下发流表，OVSDB-Neutron提供了两种Security Group的实现方式，一种是基于学习的semi stateful ACL，另一种是基于conntrack的full stateful ACL。

基于learn的实现是由Table 31、Table 39和Table 40共同完成的。Table 31中有一个默认的流表，其逻辑是通配所有的流量，动作是两个连续的Resubmit。第一个Resubmit把流量送到Table 39，Table 39的名字为ACL Learn Table，顾名思义其中的流表不是由控制器下发的，而是由其他流表通过OVS learn同步得到的，Ingress ACL Table（Table 90）筛选出合法的入向流量后，即会向Table 39同步流表，匹配对称的出向流量并标记Reg6=0x1。在Table 31中默认流表的第二个Resubmit会把流量送到Table 40，Table 40的名字为出向ACL Table，它会匹配（由Table 39标记的）Reg6=0x1，即与合法入向流量对称的出向流量，将其放行到Pipeline上的下一个（Table 50）进行后续处理，其他没有标记Reg6=0x1的流量，则会看作非法流量而丢弃。

基于conntrack的实现可以由Table 40单独完成。Table 40中有几条默认的Fixed Security Group流表：第1条是如果数据包没有被netfilter处理过，则交给netfilter来处理，netfilter处理后recircle重新进入Table 0；第2/3条是如果数据包已经被netfilter处理过，且状态为ESTABLISHED/RELATED，则认为流量合法，Resubmit给Pipeline上的下一阶段（Table 50）进行后续处理；第4/5条是如果数据包已经被netfilter处理过，且状态为NEW/INVALID，则认为流量合法并丢弃。当用户新建一个Security Group Rule的时候，会向Table 40下发一条流表，匹配Security Group Rule所允许的合法流量，如果其中某个数据包已经被netfilter处理过，且状态为NEW，则进行Commit，后续的数据包经过netfilter处理后状态会变为ESTABLISHED或者RELATED，因此它会被第2条或者第3条默认流表所匹配，从而被Table 40放行，开始进行Pipeline的后续处理。

4.与负载均衡相关的流表

LoadBalancerService对于负载均衡的处理逻辑分为两部分，包括在inbound方向对发给VIP流量的处理，以及在outbound方向对从member返回流量进行的处理：

1）对于由inbound方向发给VIP的流量，manageLoadBalancerMemberVIPRulesFirstPass方法会下发流表，匹配发向VIP流量的首数据包，该流表会通过OVS的multipath基于5元组来做哈希，然后将哈希结果存在Reg1里面，再把首数据包Resubmit回Load Balancer Table。ManageLoadBalancerMemberVIPRulesSecondPass方法会下发流表，来处理被Resubmit回Load Balancer Table的数据包，从而匹配Reg1中的哈希结果，目的IP和MAC被改写为相应member的IP和MAC。

2）对于由outbound方向从member返回的流量，manageLoadBalancerMemberReve-rseRules方法会下发流表，匹配member返回的流量，源IP和MAC改写为VIP的IP和MAC。

受限于OpenFlow的能力，LoadBalancerService无法实现对member的健康监控，也无法实现L7的负载均衡。Carbon版本中，OVSDB-Netvirt没有提供对于Neutron LBaaS v2的处理逻辑。

6.7　Netvirt-VPNService的实现

OVSDB-Netvirt与VPNService的合并是从Boron版本开始推进的，目前Carbon版本处于第二个阶段，与BGP VPN直接相关的模块还没有独立出去。Netvirt-master下有openstack和vpnservice两个子目录，其中openstack是原有的OVSDB-Netvirt Plugin中的模块，仍然处于维护状态但不已再提供新的功能，vpnservice是从原有的VPNService Plugin中迁移过来的模块。NetVirt-VPNService的架构如图6-19所示。

在Carbon版本中，Netvirt-VPNService的主要模块及其功能如表6-5所示。

上述功能的实现都依赖于OpenFlow Plugin和OVSDB Plugin。VPNService将上述功能都串在了一个Pipeline里面，如图6-20所示。表间的跳转关系非常复杂，从宏观逻辑上来看：Table Classifier（Table 0）根据流量的入端口类型（如虚拟机端口、隧道端口、Provider Network接口）进行分类，其设计流程如图6-20所示。Table Dispatcher（Table 17）根据流量的特征进行分类，需要经过ACL走Ingress ACL这条路径（Table 211～213），L2的流量走MAC这条路径（Table 48～55）。在MAC这条路线上完成已知单播的转发和BUM的泛洪，L3的流量走IP这条路径（Table 19～47），在IP这条路线上完成转发和NAT。进行端口转发之前都会送到Egress Dispatcher（Table 220）中，Egress Dispatcher会将一些流量送到Egress ACL线上进行处理（Table 241～243）。

图6-19　Netvirt-VPNService的架构

表6-5　Netvirt-VPNService中的主要模块

图6-20　VPNService的流水线设计

VPNService的实现非常复杂，模块多，各个模块中的文件也很多，而且代码调用的层次也很深。本节不会对VPNService的代码进行分析，只针对其中最主要的两个模块，完成L2功能的elanmanager和完成L3功能的vpnmanager的业务逻辑进行简单介绍。

6.7.1　elanmanager

elanmanager模块可提供Intra-DC租户L2网络的转发功能，它的基本设计思路是当发生create_network的时候创建一个ELAN Instance，当发生create_port的时候创建一个ELAN Interface，当Port状态变为Up后更新相关的流表。

发生create_network时，elanmanager主要的工作流如图6-21所示。

1）Neutronvpn模块监听DataStore，发现Neutron-Northbound创建了一个Network。

2）Neutronvpn模块创建一个新的ELAN Instance，写入DataStore中。

3）Elanmanager模块监听DataStore，发现neutronvpn创建了一个新的ELAN Instance，于是向Genius中的ID Manager请求一个ELAN Tag，这个ELAN Tag是ELAN Instance的唯一标识，用于数据平面的封装，泛洪的时候将匹配ELAN Tag进行转发。

发生create_port时，elanmanager主要的工作流如图6-22所示。

图6-21　elanmanager处理create_network的工作流

图6-22　elanmanager处理create_port的工作流

1）Neutronvpn模块监听DataStore，发现Neutron-Northbound创建了一个Port。

2）Neutronvpn模块调用Genius中的Interface Manager，创建一个新的Interface。

3）Neutronvpn模块创建一个新的ELAN Interface，并写入Config DataStore中。

4）Elanmanager模块监听DataStore，发现neutronvpn创建了一个新的ELAN Interface。

5）Elanmanager向DataStore注册，以便监听该ELAN Interface的状态。

6）Genius中的Interface管理器向Genius中的ID管理器请求该ELAN Interface的Lport Tag，这个Lport Tag是ELAN Interface的唯一标识，用于数据平面上的封装，单播的时候将匹配ELAN Tag进行转发。

7）可以通过elanmanager提供的RESTful API，将MAC地址与该ELAN Interface进行绑定，这会被写入Config DataStore中。它的用处是指定Static MAC Entry，这类表项是不会老化的。

Port状态变为Up时，elanmanager主要的工作流（下发流表的操作，包括6.9.10.11，在代码实现中并不是严格按照下面的顺序来执行的）如图6-23所示。

图6-23　elanmanager处理Port Up的工作流

1）OpenFlow Plugin上报Port_Status消息，Genius中的interface manager会得知Open-Flow Port的状态变为Up，同时会得知该OpenFlow Port所在交换机的DPN ID（即为DPID），更新DataStore。

2）Elanmanager模块监听DataStore，得知该ELAN Interface状态变为Up，同时得知该ELAN Interface所在交换机的DPN ID，更新DataStore。

3）Elanmanager维护某个ELAN Instance中所有相关交换机的DPN ID，并写入Opera-tional DataStore中。

4）Elanmanager通过bind service将该ELAN Interface的Lport Tag，以及它所属的ELAN Instance的ELAN Tag，并写入Data-Store中。

5）Genius中的interface manager监听DataStore，发现需要为Interface进行bind service，于是向该Interface所在交换机的Dispatcher Table（现为Table 17）下发流表，该流表会使用metadata标记流量所属的ELAN Tag（相当于标记VLAN）。

6）Elanmanager模块向该Interface所在交换机的SMAC Learing Table（为Table 50）下发流表，声明该Interface的MAC地址为已知的MAC地址。

7）Elanmanager模块通过Genius中的tunnel manager获得交换机间VxLAN隧道的接口名称。

8）Elanmanager模块通过Genius中的interface manager获得VxLAN隧道接口对应的OpenFlow Port ID。

9）Elanmanager模块向该ELAN Interface所属ELAN Instance中相关交换机的DMAC Table（现为Table 51）下发流表。

·对于该ELAN Interface所在的交换机而言，匹配metadata中该ELAN Instance的ELAN Tag（相当于VLAN）以及该ELAN Interface的MAC地址，或者入端口为VxLAN的隧道接口以及tunnel_id为该ELAN Interface的Lport Tag，动作是output到本地OpenFlow Port。

·对于其他交换机而言，匹配metadata中该ELAN Instance的ELAN Tag（相当于VLAN），以及该ELAN Interface的MAC地址，动作是output到VxLAN隧道接口对应的OpenFlow Port，并将tunnel_id设为该ELAN Interface的Lport Tag。

10）对于该ELAN Interface所在的交换机而言，如果之前没有任何其他相关的ELAN Interface（相关是指和该ELAN Interface属于同一个ELAN Instance）接入，那么Elanmanager模块会向该交换机的Unknown DMAC Table（现为Table 52）下发流表，为该ELAN Interface所属的ELAN Instance建立本地泛洪表，以及VxLAN隧道泛洪的头端复制表。

11）Elanmanager模块会向该ELAN Interface所在交换机的Filter Equal Table（现为Table 55）下发流表，过滤掉从某个接口的子接口A进入，经过pipeline处理后从该接口的子接口B送出的流量，以实现水平分割。

Elanmanager对二层功能的实现，有如下几个特色。

1）SMAC Learing Table中MAC地址会老化，如果MAC发生了迁移，SMAC Learing Table中默认流表会将PacketIn给控制器，控制器能及时学习到MAC的新位置并对DMAC Table进行同步。

2）能对hwvtep提供支持，把hwvtep中的local_mac重分布到各个计算节点中。关于Hwvtep，请参考5.12节中的介绍。

3）支持通过RESTful API下发静态MAC表项。

4）隧道通过Lport Tag和ELAN Tag标识目的地，目的交换机上不用再查MAC地址可以进行转发，对单播流量直接查Lport Tag，对BUM流量直接查ELAN Tag即可。

5）Filter Equal Table提供水平分割功能，支持VLAN Trunk、VLAN子接口和Transparent VLAN。

6.7.2　vpnmanager

vpnmanager可提供Intra-DC租户L3网络的转发功能，也可以通过BGP来提供计算节点和DC Edge Router间的路由，从而同步实现Inter-DC的三层互通。Vpnmanager的基本设计思路是，当发生create_router时创建一个VRF Instance，当发生create_port时将port关联到VRF Instance中，并通过bgpmanager将该Port的路由宣告出去，通过fibmanager更新相关的流表。

发生create_router时，vpnmanager主要的工作流如图6-24所示。

图6-24　vpnmanager处理create_router的工作流

1）Neutronvpn模块监听DataStore，发现Neutron-Northbound创建了一个Router。

2）Neutronvpn模块创建一个新的VRF Instance，写入DataStore。

3）Vpnmanager模块监听DataStore，发现neutronvpn创建了一个新的VRF Instance，于是向Genius中的ID Manager请求一个VRF Tag，这个VRF Tag是ELAN Instance的唯一标识，用于数据平面上的封装。

发生create_port时，vpnmanager主要的工作流如图6-25所示。

1）Neutronvpn模块监听DataStore，发现Neutron-Northbound创建了一个Port。Neutronvpn模块判断该Port的类型，如果该Port是一个VM Port，则直接对其执行后续操作，如果该Port是个Router Port，则遍历与其在同一Subnet中的所有VM Port，分别执行后续操作。

图6-25　vpnmanager处理create_port的工作流

2）Neutronvpn模块调用Genius中的Interface Manager，创建一个新的Interface。

3）Neutronvpn模块创建一个新的VPN Interface，写入Configration DataStore。

4）Vpnmanager模块监听DataStore，发现neutronvpn创建了一个新的VPN Interface。

5）Vpnmanager模块监听DataStore，得知该VPN Interface状态变为Up。

6）Vpnmanager模块通过bgpmanager模块操作外部BGP引擎，并发布该VPN Interface的路由。

7）Vpnmanager模块通过fibmanager模块下发流表，从而完成数据平面的转发与封装（VxLAN或者MPLSoverGRE的封装）。

8）外部BGP引擎收到BGP Peer（比如DC Edge Router）同步的远端路由，通过bgpmanager模块反馈给vpnmanager模块。

9）Vpnmanager模块通过fibmanager模块下发流表，通过BGP Peer同步到远端路由，从而完成数据平面的转发与封装（VxLAN或者MPLSoverGRE的封装）。

这里要说明一个问题，就是elanmanager和vpnmanager并不是冲突的，两者可以配合起来实现分布式的L2/L3转发。从最新版的代码来看，elanmanager也在考虑通过bgpmanager操作外部BGP引擎中的EVPN来宣告MAC路由。如果这样的话，elanmanager和vpnmanager加起来实际上和BagPipe就很像了，区别在于VPNService只在控制器上起一个BGP Speaker，然后控制器向所有的计算节点进行BGP RIB和流表的重分布，而BagPipe需要在每个相关的计算节点上起BGP Speaker，BGP RIB和流表的重分布也是在本地完成的。所以说，本质上还是集中式和分布式的区别，因此，也不能说哪个方案好哪个方案坏。VPNService的优势在于功能比较丰富，但是它的框架太重，相反Bagpipe虽然功能较少，但是框架较轻，上手起来会很容易。

6.8　SFC的实现

ODL中SFC Plugin是在Helium版本开始推进的，主要目的是在各类网络环境下支持SFC的编排和部署，经过多个版本的发展，SFC Plugin已经提供了多元化的服务链的实现方式。下面列出了其根目录下一些典型的模块，对于网络而言，SFC最关键的是classifier和SFF的实现，本节将以sfc-classifier.sfc-scf-openflow和sfc-renders.sfc-openflow-renderer为例，介绍基于OpenFlow的服务链的实现机制。

sfc-master
sfc-classifier/ // classifer的实现，目前提供openflow和vpp两种
sfc-netconf/ // 通过NETCONF配置SFF部署SFC
sfc-ovs/ // 通过OVSDB配置OVS SFF部署SFC
sfc-renders/ // SFF的实现，目前提供OpenFlow、VPP、NETCONF等方式
sfc-vnfm-tacker/ // 对OpenStack Tacker的支持

6.8.1　sfc-openflow-renderer分支

sfc-renders.sfc-openflow-renderer分支下的目录结构如下所示，其中listeners/下的模块负责监听DataStore中与SFC相关的数据结构，主要包括RSP（Rendered Service Path）和SFG（Service Function Group），并触发processors/下相应模块的处理逻辑。Processers/下相应的模块会通过openflow/下SfcOfFlowProgrammerImpl提供的方法来部署SFC的Pipeline。

sfc-renderer
listeners/ // 监听RSP与SFG，触发相应的处理逻辑
openflow/ // 处理SFC业务相关的PacketIn和FlowMod
processors/ // 提供对RSP的处理
SfcOfRender // 入口类

sfc-openflow-renderer的启动会实例化入口类SfcOfRender，SfcL2Render的构造函数实例化了listeners/下的SfcOfRendererDataListener（监听配置参数）、SfcL2SfgDataListener（监听SFG）和SfcL2RspDataListener（监听RSP），openflow/下的SfcIpv4PacketInHandler（负责处理PacketIn）和SfcOfFlowProgrammerImpl（负责部署SFC Pipeline），以及processors/下的SfcOfRspProcessor。SfcOfRspProcessor是实现SFC业务逻辑的主体类。后面的介绍主要会围绕SfcOfRspProcessor和SfcOfFlowProgrammerImpl对RSP的创建来展开。

======================== sfc.ofrenderer.SfcOfRenderer =========================
public class SfcOfRenderer
public SfcOfRenderer()
this.sfcOfFlowProgrammer = new SfcOfFlowProgrammerImpl();
this.sfcOfRspProcessor = new SfcOfRspProcessor();
this.openflowRspDataListener = new SfcOfRspDataListener();
this.sfcOfSfgDataListener = new SfcOfSfgDataListener();
this.sfcOfRendererListener = new SfcOfRendererDataListener();
this.packetInHandler = new SfcIpv4PacketInHandler();

1.RSP的创建

当用户通过SFC的北向接口提出新建一个RSP的方案时，MD-SAL Datastore中的SFC.RSP数据会更新并发出通知，SfcL2RspDataListener监听到这个通知并调用SfcOfRspProcessor实例的processRenderedServicePath方法，该方法负责完成RSP的构建，其主体为一个try语句。该try语句的主要逻辑是，首先为该RSP生成SFF Graph并设置RSP的传输参数，然后在更新结束后向SFF下发流表并开通该RSP，最后将SFF Graph写入DataStore。

================ sfc.ofrenderer.processors.SfcOfRspProcessor =================
public class SfcOfRspProcessor
public void processRenderedServicePath()
try
SffGraph sffGraph = populateSffGraph(); // 为该RSP生成SFF Graph
transportProcessor.processSffDpls(); // 为RSP中的各个SFF端口
// 分配Dataplane Locator
transportProcessor.setRspTransports(); // 为RSP中的各个Hop分配传输
// 网络上的路径标识
Iterator<SffGraph.SffGraphEntry> sffGraphIter
= sffGraph.getGraphEntryIterator();
while (sffGraphIter.hasNext()) // 遍历SFF Graph，下发流表开通SFC路径
entry = sffGraphIter.next();
initializeSff();
configureTransportIngressFlows();
configurePathMapperFlows();
configureNextHopFlows();
configureTransportEgressFlows();
transportProcessor.updateOperationalDSInfo();
// 将SFF Graph写DataStore

为RSP生成SFF Graph是通过populateSffGraph方法完成的。一个RSP对应着一个SFF Graph，在这个SFF Graph中有一个SFF Graph Entry列表，这些SFF Graph Entry依次对应RSP各跳（PathHop）的信息。populateSffGraph方法中的while循环用于更新该RSP上除最后一跳外其他PathHop的信息，while后紧接的两行代码用来更新RSP上最后一跳的信息。其中，EGRESS这个变量需要解释一下，它并不指代任何SFF，而是指RSP中最后一跳的目的设备，通常为接入目的主机的classifier，同理源代码中的INGRESS也不指代任何SFF，而是指RSP中第一跳的源设备，通常为流量的ingress classifier。当然，classifier的实现也可能使用OF交换机，还有可能与first SFF实现在同一台OF交换机上，这一点到了后面sfc-scf-openflow部分再讲解。

================= sfc.ofrenderer.processors.SfcOfRspProcessor ==================
public class SfcOfRspProcessor
private SffGraph populateSffGraph()
Iterator<RenderedServicePathHop> servicePathHopIter =
rsp.getRenderedServicePathHop().iterator();
while (servicePathHopIter.hasNext()) // 遍历该RSP上的PathHop，并更新信息
entry = sffGraph.addGraphEntry();
entry.setPrevSf();
entry = sffGraph.addGraphEntry(); // 更新RSP上最后一跳的信息
entry.setPrevSf();
====================== sfc.ofrenderer.processors.SffGraph ======================
public class SffGraph
public SffGraphEntry addGraphEntry()
SffGraphEntry entry = new SffGraphEntry();
public class SffGraphEntry
public SffGraphEntry()
this.srcSff = srcSff; // 该PathHop的源SFF
this.dstSff = dstSff; // 该PathHop的目的SFF
this.sf = sf; // 该PathHop要发往的目的SF
this.sfg = sfg; // 目的SF所在的SFG
this.pathId = pathId; // 该PathHop所属RSP的SPI
this.serviceIndex = serviceIndex; // 该PathHop在RSP中的SI
this.prevSf = null; // 进入该PathHop前流量所经过的SF

这里还要再说一说PathHop（即SffGraphEntry）的数据结构，以便分析后续代码。每一个PathHop主要包括以下7个成员变量：srcSff是该PathHop的源SFF，dstSff是该PathHop的目的SFF，prevSf是进入该PathHop前流量所经过的SF，sf是该PathHop要发往的目的SF，sfg是目的SF所在的SFG，pathId是该PathHop所属RSP的Service Path Index（SPI），SPI是RSP的全局唯一标识，serviceIndex是该PathHop在RSP中的Service Index（SI），在RSP中每经过一个PathHop，SI就会减1。

接着说processRenderedServicePath方法。populateSffGraph方法更新了SFF Graph Entry的信息后，还需要更新RSP上各个SFF的dataplane locator（processSffDpls方法），以及传输网络中各个PathHop的参数（setRspTransports方法）。dataplane locator是SFF在传输网络上的身份标识，用于指导SFF下发流表。

2.流水线的设计

各种信息都更新完毕后，就到了向各个SFF下发流表的时候了。这里先简单地看一下sfc-openflow-renderer的流表设计，完成业务逻辑的分析后，会详细对Pipeline进行介绍。

Table 0是流量分类表，用于对SFC流量进行识别，它所包含的流表项主要在sfc-scf-openflow中完成下发；Table 1是转发入口表，用于区分RSP的传输方式；Table 2是路径映射表，通过包头中传输标识来识别RSP；Table 3是路径映射ACL表，识别流经TCP Proxy SF的RSP；Table 4是下一跳表，用于决定RSP下一跳SFF/SF的dataplane locator；Table 5是出口转发表，根据RSP下一跳SFF/SF的dataplane locator进行转发。

=============== sfc.ofrenderer.openflow.SfcOfFlowProgrammerImpl ================
public class SfcOfFlowProgrammerImpl
public static final short TABLE_INDEX_CLASSIFIER = 0;
public static final short TABLE_INDEX_TRANSPORT_INGRESS = 1;
public static final short TABLE_INDEX_PATH_MAPPER = 2;
public static final short TABLE_INDEX_PATH_MAPPER_ACL = 3;
public static final short TABLE_INDEX_NEXT_HOP = 4;
public static final short TABLE_INDEX_TRANSPORT_EGRESS = 10;

回到processRenderedServicePath方法，来看Pipeline相关的业务逻辑。Pipeline流表的下发是以PathHop为单位进行循环的，其逻辑体现在processRenderedServicePath方法try语句块的while循环中。循环会依次拿到RSP上的各个PathHop，并向该PathHop的srcSff和dstSff下发流表。

================== sfc.ofrenderer.processors.SfcOfRspProcessor =================
public class SfcOfRspProcessor
public void processRenderedServicePath()
try
while (sffGraphIter.hasNext()) // 遍历SFF Graph
entry = sffGraphIter.next(); // 每个entry对应RSP上的一个Hop
initializeSff(); // 初始化SFF上的流表
configureTransportIngressFlows();
// 向Transport Ingress Table下发流表
configurePathMapperFlows(); // 向Path Mapper Table下发流表
configureNextHopFlows(); // 向Nexthop Table下发流表
configureTransportEgressFlows();
// 向Transport Egress Table下发流表

InitializeSff方法的作用是依次向dstSff中的各级流表下发table-miss，对通配的流量进行处理，当dstSff为EGRESS时则直接返回。通过观察SfcOfFlowProgrammerImpl中对应的方法，得知只有Table 1（转发入口表）和Table 10（出口转发表）会对不能识别的流量采取丢弃的策略，具体原因后面会讲到。

================= sfc.ofrenderer.processors.SfcOfRspProcessor ==================
public class SfcOfRspProcessor
private void initializeSff()
if (entry.getDstSff().equals(SffGraph.EGRESS))
return; // 当dstSff为EGRESS时则直接返回
if (!getSffInitialized())
transportProcessor.configureClassifierTableMatchAny();
if (entry.usesLogicalSFF()) // 此分支与Genius相关，此处不做介绍
…
else
this.sfcOfFlowProgrammer.configureTransportIngressTableMatchAny();
this.sfcOfFlowProgrammer.configurePathMapperTableMatchAny();
this.sfcOfFlowProgrammer.configurePathMapperAclTableMatchAny();
this.sfcOfFlowProgrammer.configureNextHopTableMatchAny();
this.sfcOfFlowProgrammer.configureTransportEgressTableMatchAny();

ConfigureTransportIngressFlows方法会向PathHop的dstSff中的Transport Ingress Table下发流表。它首先会判断PathHop的dstSff是否为EGRESS，如果是则直接返回。如果dstSff不是EGRESS，则向dstSff的Transport Ingress Table下发流表，具体方式为：一是处理从prevSff流入dstSff的流量，二是处理由目的SF处理后流入dstSff的流量。

================= sfc.ofrenderer.processors.SfcOfRspProcessor ==================
public class SfcOfRspProcessor
private void configureTransportIngressFlows()
if (entry.getDstSff().equals(SffGraph.EGRESS))
return;
transportProcessor.configureSffTransportIngressFlow();
// 处理从prevSFF流入dstSFF的流量
if (entry.getSf() != null)
transportProcessor.configureSfTransportIngressFlow();
// 处理由目的SF处理过后流入dstSFF的流量

ConfigurePathMapperFlows方法会向PathHop的dstSff中的Path Mapper Table下发流表。其逻辑和configureTransportIngressFlows方法完全一致。

================= sfc.ofrenderer.processors.SfcOfRspProcessor ==================
public class SfcOfRspProcessor
private void configurePathMapperFlows()
if (entry.getDstSff().equals(SffGraph.EGRESS))
return;
transportProcessor.configureSffPathMapperFlow();
// 处理从prevSFF流入dstSFF的流量
if (entry.getSf() != null)
transportProcessor.configureSfPathMapperFlow();
// 处理由目的SF处理过后流入dstSFF的流量

ConfigureNextHopFlows方法会向PathHop的srcSff/dstSff中的Nexthop Table下发流表，对流量的目的地进行标记。其逻辑如下所示。

1）对于srcSff，它需要判断srcSff与dstSff是否相同（即prevSF与目的SF是否连接在同一个SFF上），若相同则srcSff将会把从prevSff发过来的流量目的地标记为Sf，如果不相同则srcSff将会把从prevSff发过来的流量目的地标记为SF所在的dstSff。另外，如果dstSff是EGRESS，则srcSff会把prevSf发来的流量的目的地标记为EGRESS。

2）对于dstSff，只需要将从srcSff发来的流量目的地标记为Sf。

=================== sfc.ofrenderer.processors.SfcOfRspProcessor ==================
public class SfcOfRspProcessor
private void configureNextHopFlows()
if (sfDstDpl != null)
if (entry.getSrcSff().equals(SffGraph.INGRESS))
transportProcessor.configureNextHopFlow();
// 如果srcSff是INGRESS，则dstSff把INGRESS
// 送过来的流量的目的地，标记为Sf
else
transportProcessor.configureNextHopFlow();
// 如果srcSff不是INGRESS，则dstSff把srcSff
// 送过来的流量的目的地，标记为Sf
if (entry.getDstSff().equals(SffGraph.EGRESS))
transportProcessor.configureNextHopFlow();
// 如果dstSff是EGRESS，则srcSff会把prevSf处理
// 后的流量的目的地，标记为EGRESS
if (sfSrcDpl != null)
if (entry.getSrcSff().getValue().equals(entry.getDstSff().getValue()))
transportProcessor.configureNextHopFlow();
// 如果prevSf和Sf在同一个SFF上（即srcSff==dstSff），
// 则srcSff会把prevSf处理后的流量的目的地，标记为Sf
else
transportProcessor.configureNextHopFlow();
// 如果prevSf和Sf不在同一个SFF上（即srcSff!=dstSff），
// 则srcSff会把prevSf处理后的流量的目的地，标记为Sf所在的dstSFF

ConfigureTransportEgressFlows方法会向PathHop的srcSff/dstSff中的Transport Egress Table下发流表，根据Nexthop Table标记的目的地，通过传输网络进行转发。其逻辑如下。

1）对于srcSff，如果dstSff是EGRESS，则srcSff会把目的地被标记为EGRESS的流量，通过传输网络发送给EGRESS。如果dstSff不是EGRESS，则srcSff会把目的地被标记为dstSff的流量，通过传输网络送给dstSff。

2）对于dstSff，只需要将目的地被标记为Sf的流量，通过传输网络送给Sf。

================== sfc.ofrenderer.processors.SfcOfRspProcessor ===================
public class SfcOfRspProcessor
private void configureTransportEgressFlows()
if (entry.getDstSff().equals(SffGraph.EGRESS))
transportProcessor.configureSffTransportEgressFlow();
return;
// 如果dstSff是EGRESS，则srcSff会把目的地标记
// 为EGRESS的流量发送给EGRESS，方法直接返回
if (sfDstDpl != null)
transportProcessor.configureSfTransportEgressFlow();
// 目的地标记为Sf的流量，则dstSff会把流量送给Sf
if (entry.getSrcSff().equals(SffGraph.INGRESS))
return; // 如果srcSff是INGRESS，则不需要做后面的处理
if (!entry.getSrcSff().getValue().equals(entry.getDstSff().
getValue()) || entry.isIntraLogicalSFFEntry() &&
!entry.getSrcDpnId().getValue().equals(entry.getDstDpnId().getValue()))
transportProcessor.configureSffTransportEgressFlow();
// 目的地标记为dstSff的流量，则srcSff会把流量送给dstSff

到此，processRenderedServicePath方法就完成了RSP的开通。

3.相关流表的介绍

上面所讲的内容是sfc-openflow-renderer业务逻辑的设计，下面来看看Pipeline实际的样子，如表6-6所示。

表6-6　SFC的流水线

各个Table中表项的设计与RSP使用的传输方式密切相关。在目前版本的sfc-openflow-render中，RSP可以支持以下几种传输方式：VLAN、MPLS和NSHoverVxLANGPE（NSH-over-Ethernet支持的还不完整），不过一个RSP只能选择一种方式在SFF间进行传输。SF和SFF间目前只支持通过VLAN方式进行传输。

Processers下面为不同类型的传输方式提供了不同的SfcRspProcessor***，它们都继承了抽象类SfcRspTransportProcessorBase，针对传输网络的不同特点对configure***Flow方法进行了不同的实现。结合各个SfcRspProcessor***和SfcOfFlowProgrammerImpl，下面介绍一下在几个Table中，Match和Instruction的设计逻辑。

Table 0（分类表）在sfc-openflow-render中的实现十分简单，就是通配所有的流量然后GOTO Table 1，这一级流表的存在看似没有必要，实际上它考虑了sfc-openflow-render与sfc-scf-openflow的配合使用。sfc-openflow-render只实现了SFF的转发逻辑，classifier逻辑的实现还要依赖于sfc-scf-openflow，而对于sfc-scf-openflow来说Table 0就比较重要了，因此SFF上的分类表被保留了。到了sfcscfopenflow部分会提到Table 0中的流表逻辑。

Table 1（转发入口表）做的工作也并不复杂，对于使用VLAN/MPLS进行传输的SFC流量，GOTO Table 2，对于使用NSHoverVxLANGPE/NSHoverEthernet进行传输的SFC流量，GOTO Table4。如果SFC流量不属于以上传输网络的类型，则丢弃掉。使用NSHoverVxLANGPE/NSHoverEthernet进行传输的SFC流量，之所以旁路掉Table 2而直接GOTO Table4，是因为Table 2存在的作用是根据包头中的传输标签来识别RSP，而NSH封装中的SPI字段本身就标识着RSP，因此不需要再用Table 2进行标记。

Table 2（路径映射表），根据流量流入时携带的传输标签来识别RSP，并通过metadata在Pipeline中标记RSP，以便Table 4决定下一跳。其match与instruction的逻辑如下：Match域与RSP使用的传输方式有关，若使用VLAN方式则匹配VLAN ID，若使用MPLS则匹配mpls label，如果使用NSHoverVxLANGPE或者NSHoverEthernet进行传输时，Table 2不会有机会匹配。从SF进入dstSff的流量也要被Table 2处理，目前大部分的SF不支持MPLS和NSH，因此只能通过IP DSCP来标记RSP，因此对于从SF出来的流量，Table 2还要匹配DSCP字段。Instruction如下：1）标记metadata。2）剥掉传输标签（VLAN ID/mpls label），3）GOTO Table 3。Table 2中的通配流量会直接GOTO Table 3。

Table 3（路径映射ACL表）完成的工作是相对独立的，它负责匹配与TCP Proxy SF相关的流量。由于TCP Proxy SF作为中间件会产生新的包（包括Proxy向客户端回复的TCP SYN_ACK和向服务器发送的TCP SYN），而这些包并不会被classifer所标记，因此需要Table 3根据srcip+dstip为PacketIn上的包标记metadata，以维持RSP ID的信息。在之前介绍的configureSffEgress方法中，如果目的SF是TCP Proxy SF，则dstSff会在Table 10中将TCP_SYN包PacketIn上去（只有OpenFlow 1.5支持匹配TCP_SYN），然后SfcIpv4PacketInHandler实例会向Table 3下发双向的流表项，匹配TCP socket的srcip+dstip以及流量的metadata，然后GOTO Table 4。Table 3对于通配流量，直接GOTO Table 4。

Table 4（下一跳表）负责标记流量的目的地，通配流量GOTO Table 10。Match域与RSP使用的传输方式有关，VLAN/MPLS方式下匹配metadata+srcmac（metadata标识RSP，相当于SPI，srcmac标识所在RSP的位置，相当于SI），NSHoverVxLANGPE/NSHoverEthernet方式匹配SPI和SI。Instruction如下：1）改写dstmac（VLAN/MPLS），或者外层包头的dstip（NSHoverVxLANGPE），或者外层包头的dstmac（NSHoverEthernet），作为下一跳的dataplane locator，以标记目的地。2）GOTO Table 10。

Table 10（出口转发表）根据下一跳的dataplane locator来选择转发的端口，对于通配流量直接丢弃。Match域与RSP使用的传输方式相关，VLAN/MPLS网络匹配metadata+dstmac，NSHoverVxLANGPE/NSHoverEthernet匹配SPI+SI。另外对于TCP Proxy SF的SYN包还要PacketIn。Instruction如下：1）对于VLAN/MPLS网络修改srcmac并打上新的传输标签，如果是送给SF的流量还要标记DSCP；对于NSHoverVxLANGPE/NSHover-Ethernet需要修改NSH Context。2）当dstSff为EGRESS时，要剥掉所有标签或者包头。3）从outport进行转发。

从源码上来看，各种传输方式的实现在SfcOfFlowProgrammerImpl中都混在了一起，非常容易产生混乱。表6-7是一个对各种传输方式的总结，可以与表6-6结合起来进行理解。

表6-7　各种传输方式的总结

6.8.2　sfc-scf-openflow分支

SFC中classifier主要完成两个工作，第一是识别SFC流量，目前sfc-scf-openflow能够支持I2元组中的大部分字段，以及SCTP与IPv6，如果用户没有提供SFC流量的特征字段，将只匹配inport来识别SFC流量。第二是对SFC流量进行封装并送往first SFF，目前sfc-scf-openflow还不支持VLAN和MPLS，只能提供对NSHoverVxLANGPE的支持。

这部分的代码逻辑相对而言要简单一些，入口类renderders.SfcScfOfRenderer实例化了listeners.SfcScfOfDataListener，SfcScfOfDataListener会监听DataStore中的Service-Fu-n-c-tion-Classifier，在add方法中会调用processors.SfcScfOfProcessor的created-Service-Fun-ctionClassifier方法，这个方法经过多个层次的调用会走到processors.Nsh-Pro-cessor的pro-cessAceByProcessor方法。processAceByProcessor方法的主体逻辑，是先根据Classifier的特征生成Match，然后依次向Table 0下发流表，处理从INGRESS流向Classifier的流量，以及其对称方向上返回给INGRESS的流量。

================== sfc.scfofrenderer.processors.NshProcessor===================
public class NshProcessor
public List<FlowDetails> processAceByProcessor()
Optional<Long> inPort = classifierInterface.getInPort();
Match match = inPort.map().map().map().map().orElseThrow().build();
// 根据Classifier的特征生成Match，如果没有指
// 定流量特征，则默认只匹配入端口
If (addClassifier) // 处理从INGRESS流向Classifier的流量
theFlows.add(classifierInterface.initClassifierTable());
// 初始化Table 0，通配流量GOTO Table 1
theFlows.add(classifierInterface.createClassifierOutFlow();
// 对从INGRESS流向Classifier的流量进行
// NSHoverVxLAN封装，发向firstSff
List<FlowDetails> theReverseRspFlows = processReverseRsp();
// 处理对称方向上返回给INGRESS的流量
protected List<FlowDetails> processReverseRsp()
if (addClassifier)
Optional.ofNullable(classifierInterface.createClassifierInFlow()).ifPresent();
// 对于对称方向上返回给INGRESS的流量，
// 解封装NSHoverVxLAN，发给INGRESS

对于从INGRESS流向Classifier的流量，Table 0中的流表会通过ApplyActions立即执行动作，因此数据包的一个副本将跳出Pipeline，立即封装NSHoverVxLANGPE，并送往firstSFF的VxLAN端口。那么Sfc-scf-openflow实现的classifier与sfc-openflow-renderer实现的first Sff是怎么串在一起工作的呢？可分为如下两种情况来考虑。

1）如果classifier与first Sff分开部署，那么first Sff的Table 0中就只有通配的流表项，收到数据包后进入Table 1，若判定为NSHoverVxLANGPE的包，则送到Table 4进行后续处理。

2）如果classifier与first Sff为同一台OF交换机，那么first Sff的Table 0即为classifier的Table 0，first Sff和classifier也共用一个VxLAN端口，所以classifier发送出来的NSHoverVxLANGPE包会从内核绕回来，再次送入Pipeline。不过此时的数据包因为已经加了一层封装，因此在Table 0中通配流表项后送入Table 1，若Table 1判定为NSHoverVxLANGPE，则发送到Table 4进行后续处理。

6.9　VTN Manager的实现

VTN在本质上，是一个利用OpenFlow实现网络虚拟化的Plugin，它希望在直观上给网络使用者提供一个逻辑上的网络。使用者可以通过VTN Manager提供的RESTful API或者Web UI给出逻辑网络的拓扑连接，如图6-26所示，图中的使用者希望得到处于不同网段的两台主机。它们分别连接在两个网段的逻辑交换机上，逻辑交换机间通过逻辑路由器实现网段间的通信。VTN会根据这些逻辑拓扑的连接向物理的OpenFlow网络下发流表，以实现两台主机间的通信，并且隔离不同使用者所拥有的主机。

图6-26　VTN原理示意图

上述描述的是在SDN网络中泛化的虚拟化场景，而云则是这一场景的典型应用。VTN Plugin早在ODL的第一个版本Hydrogen中就有了，是最开始对OpenStack提供支持的Plugin。目前，VTN Plugin中这部分逻辑叫做VTN Manager。本节将对VTN Plugin中的VTN Manager进行代码导读。

除了VTN Manager以外，目前VTN Plugin还包括VTN Application和VTN Coordinator。VTN Application会向VTN Manager发送REST GET，将逻辑网络所在的实时状态信息以JSON格式呈现出来，避免用户通过curl一条一条地发送状态请求。VTN Coordinator使用C语言编写，它独立于ODL控制器运行，用来控制多个VTN Manager跨域协同工作。

VTN Manager主要有两个子模块：neutron分支用于VTN Manager和OpenStack的对接，触发相应的虚拟化逻辑；implementation分支则负责虚拟化逻辑的实现。

6.9.1　neutron分支

neutron分支的入口类是NeutronProvider，它会实例化NeutronNetworkChangeListener和PortDataChangeListener，它分别监听DataStore中由Neutron-Northbound更新的Neutron-Network和NeutronPort。NeutronProvider还会实例化OvsdbDataChangeListener，用于监听OpenStack节点的上线以及虚拟机实例的接入。这些***ChangeListener都会调用implementation中的模块来实现对应的业务逻辑。

在OpenStack环境中，OvsdbDataChangeListener首先会通过监听DataStore得知OpenStack节点的上线。OvsdbDataChangeListener对该事件进行处理，最终会走到neutron.impl.OVSDBEventHandler的nodeAdded方法。nodeAdded方法主要做了两件事情：写DataStore驱动OVSDB Plugin，在节点上建立br-int，然后写DataStore将节点的eth0添加到br-int上。这里要注意的是，由于VTN Manager的Underlay网络可以都是OpenFlow设备，因此在nodeAdded方法中没有必要为br-int添加隧道端口和隧道逻辑。可以看到，后面implementation中VBridge的转发逻辑也并没有用到隧道。

==================== neutron.impl.OVSDBEventHandler =======================
public final class OVSDBEventHandler
public void nodeAdded() // 探测到节点上线
if (key != null)
if (!isBridgeOnOvsdbNode())
if (addBridge()) // 在节点上建立br-int
addPortToBridge(); // 将节点上的eth0绑定到br-int上

上述工作结束后，OpenStack节点的初始化就完成了。接下来，OpenStack用户会建Network、Subnet和Router。目前VTN的实现仍然是比较简单的，并不支持L3的通信，Subnet API和Router API在VTN Manager中都还没有相应的代码实现。

NeutronNetworkChangeListener监听到由Neutron-Northbound更新的NeutronNetwork后，会调用NetworkHandler的neutronNetworkCreated方法，这个方法再调用VTN-M-ana-gerImpl的updateBridge方法，该方法会通过RPC调用implementation分支中相应的VBridgeManager模块，为这个NeutronNetwork创建对应的VBridge。VTN目前并不支持L3的通信，Neutron Network和VBridge在实现中也因此为一一对应的关系。

同样，PortDataChangeListener监听由Neutron-Northbound更新的NeutronPort后，调用createPort方法，createPort方法会获得NeutronPort所在的NeutronNetwork和该Neutron-Netowork对应的VBridge，然后调用VTNManagerImpl的updateInterface方法，该方法会通过RPC调用implementation分支中相应的VInterfaceService模块，生成一个VInterface并添加到对应的VBridge上。

======================= neutron.impl.NetworkHandler ========================
public final class NetworkHandler
public void neutronNetworkCreated()
if (result != HTTP_OK)
…
else
result = vtnManager.updateBridge();
===================== neutron.impl.PortDataChangeListener ====================
public final class PortDataChangeListener
private void createPort()
if (port == null)
…
else
int result = vtnManager.updateInterface();

到这里，VTN Manager主要的初始化工作就完成了。可能有读者会觉得很奇怪，前面介绍的Netvirt Plugin中的OVSDB-Neutron和VPNService在初始化时，业务逻辑可是相当复杂的，会下发很多的流表以保证虚拟机的通信，为什么VTN Manager的初始化工作却这么简单呢？

在本节的开始提到过，VTN Manager的设计目标是通过OpenFlow实现泛化的网络虚拟化。它的实现思路是，首包都PacketIn给控制器，控制器会仿真流量在租户网络中的处理，然后根据仿真结果下发流表，后续的流量直接匹配流表进行转发。因此，当VTN Manager与OpenStack进行对接时，仍然保留了这种Reactive的设计思路，初始化的工作实际上就是把租户网络的视图保存在控制器中，然后等着PacketIn把首包送上来，再触发后续的处理逻辑。

VTN Manager的这种实现思路和Midokura是很类似的，只不过VTN Manager实现仿真是通过集中式的ODL来完成的，而Midokura实现仿真是通过分布式的Midolman来完成的。VTN Manager是由NEC公司创建并维护的，Midokura也是日本的创业公司研发出来的产品，看来日本的同行们做SDN虚拟化的思路还挺一致的。

6.9.2　implementation分支

implementation分支的目录结构如下所示，config下的VTNConfigManager负责VTN的配置管理，flow下的VTNFlowManager负责流表的下发，inventory下的VTNInventoryManager负责监听Node、Port和Topology的状态变化，packet下的VTNPacketService负责处理PacketIn和PacketOut，provider下的VTNManagerProviderImpl负责整个VTN Manager Plugin的管理，routing下的VTNRoutingManager负责物理路径的计算。逻辑网络的通信都要依赖于VTNRoutingManager将逻辑路径转化为物理网络中的流表，util下有很多小的工具模块，vnode是VTN元素的实现逻辑，如Vtenant、VBridge、Vinterface等。

implementation
vtn.manager.impl
config/ // VTN的配置管理
flow/ // VTN相关流表的下发
inventory/ // 监听Node、Port和Topology的状态变化
packet/ // PacketIn和PacketOut的处理
provider/ // VTN Manager的管理
routing/ // 负责物理路径的计算
util/ // 一些工具模块
vnode/ // 各类VTN的数据结构，如VBridge/Vinterface

Implementation分支的入口类，是provider下的VTNManagerProviderImpl，VTNManager-ProviderImpl会实例化各个子模块，如VTNConfigManager、VTNFlowManager、VTNInventory-Manager、VTNPacketService和VTNRoutingManager，等等。

在neutron分支的最后说到，若VBridge、VInterface准备完毕后就可以等着虚拟机发包了。假设现在有如下场景，用户有一个两台虚拟机的逻辑二层网络，物理网络中host 1连在s1-eth1，host 4连在s4-eth1。现在host 1开始向host 4发包，s1发送PacketIn给控制器，如图6-27所示。

VTNPacketService中的onPacketReceived方法收到了这个PacketIn，然后调用VTNManagerProviderImpl实例中的post方法发布一个PacketInEvent，这个PacketInEvent的发布是通过MD-SAL来完成的，发布的实现逻辑十分复杂。VTenantManager订阅了这一事件，并通过notifyPacket对其进行处理，notifyPacket方法会先判断流量是否为控制器产生的流量（如LLDP），如果是则直接忽略，否则判断为虚拟机产生的通信流量，调用receive方法对流量进行后续的处理。receive方法会通过getMapping方法获得s1-eth1所对应的VInterface，然后动态生成一个VTenant实例并调用其receive方法。

图6-27　VTN示例场景

================= vtn.manager.internal.packet.VTNPacketService ==================
public final class VTNPacketService
public void onPacketReceived()
if (it.hasNext())
if (ev != null)
vtnProvider.post(); // 发布一个事件
================== vtn.manager.internal.vnode.VTenantManager ==================
public final class VTenantManager
public void notifyPacket()
if (src.equals(pctx.getControllerAddress()))
… // 如果是控制器产生的包，则不做任何处理
else
try
receive(); // 处理由虚拟机发出的包
private void receive()
TenantNodeIdentifier<?, ?> ref = getMapping();
if (ref != null)
new VTenant().receive();

对于VTenant的receive方法，会通过evaluate方法判断数据包是否需要丢弃（drop）或者重定向（redirect）。如果evaluate认为需要丢弃或者重定向，就抛出相应的异常，如果是丢弃的异常则什么都不做，如果是重定向，就调用redirect方法，redirect方法会再调用VBridge的redirect方法进行重定向的相关处理。Evaluate的实现中用到了FlowFilter的概念，这是用户指定的转发策略，重定向的策略可以用来实现SFC的功能。如果不需要丢弃或者重定向，则调用VBridge的receive方法，receive方法调用VBridge的receive方法转发相关的处理。

==================== vtn.manager.internal.vnode.VTenant =======================
public final class VTenant
public void receive()
try
pctx.evaluate(); // 判断该数据包是否属于某个FlowFilter，
// 如果是，则抛出异常进行丢弃或者重定向
VirtualBridge<?> bridge = reader.getBridge(); // 获得相关的VBridge
if (bridge == null)
…
else
bridge.receive(); // 通过VBridge的receive方法进行转发相关的处理
catch (DropFlowException e) // 需要丢弃的话，控制器什么都不用做
catch (RedirectFlowException e)
redirect(); // 需要重定向的话，执行VBridge的receive方法

VBridge的receive方法，先通过match方法得到数据包所对应的VInterface，然后通过forward方法转发的相关逻辑。首先通过learnMacAddress方法进行MAC地址的学习，接着进行特定流量的过滤，再通过getDestination方法返回目的虚拟机的接入端口。若流量为目的地已知的单播则重载另外一个forward方法，在源、目的交换机间选择一条传输路径，并向支路径相关的交换机下发流表，首包则直接从目的交换机进行PacketOut。若流量为BUM类型则在getDestination方法中直接调用broadcast方法进行处理，broadcast方法会在该VBridge上除入口VInterface以外的所有VInterface上进行泛洪。

==================== vtn.manager.internal.vnode.VirtualBridge ===================
public abstract class VirtualBridge
public final void receive()
VirtualMapNode vnode = match(); // 得到数据包所对应的VInterface
if (vnode == null)
…
else if (vnode.isEnabled())
forward(); // forward方法完成转发的相关逻辑
===================== vtn.manager.internal.vnode.VBridge ======================
public final class VBridge
protected void forward()
if (pctx.getFirstRedirection() == null)
learnMacAddress(); // MAC地址学习
if (pctx.isToController())
return; // 忽略掉发给控制器的包
MacEntry ment = getDestination(); // BUM流量直接泛洪并返回null，
// 已知单播流量返回虚拟机的接入端口
if (ment != null)
forward(); // 重载另一个forward方法，对已知单播进行转发
protected final void forward() // 重载的forward方法，负责完成最终的转发
List<LinkEdge> path = rr.getRoute(); // 获得源和目的交换机间的转发路径
if (path == null)
…
else
pctx.transmit(); // 首包直接在目的交换机上做PacketOut
pctx.installFlow(); // 向转发路径上相关的交换机下发流表

虽然VTN的VBridge采用的是Reactive的方式，但是通过上面的描述我们可以看到，对于已知目的地的单播包来说Reactive只发生在第一跳交换机上，一旦第一跳送PacketIn给控制器，控制器就会一次性选好后续的传输路径并依次向相应的交换机下发流表，第一跳后还是相当于是Preactive的。BUM流量没有办法，就只能逐跳PacketIn了。

6.10　本章小结

OpenDaylight是一盘大杂烩，如果说其中有比较聚焦的场景，那么就是数据中心这一块了。本章介绍了OpenDaylight本身的架构，以及在社区中与数据中心业务相关的项目设计与实现，随着OVSDB-Netvirt与VPNService的合并，社区这边陆续又有了很多新的大动作，具体的实现仍然需要逐个版本地进行跟进。

第7章　开源SDDCN：ONOS相关项目的设计与实现

ONOS在云这一块，主要是CORD和SONA两个use case，其中CORD主要面向运营商端局，而SONA主要面向于云数据中心。实际上，还有一个面向云数据中心的项目叫做ONOSFW，它是为了OPNFV的集成而设计的。本章将先对ONOS的架构进行介绍，再对ONOS中OpenFlow的实现进行介绍，然后进行SONA和ONOSFW的代码导读。CORD所涉及的业务与本书的侧重点并不是十分吻合，但其中的某些设计具备一定的参考价值，因此本章最后会对CORD进行一个概要性的介绍。

本章所涉及的代码都是以Kingfisher版本为准的。

7.1　架构分析

ONOS是由On.Lab推动的SDN控制器，致力于提供Carrier-Grade的SDN基础平台。OnLab是由斯坦福发起的SDN开源组织，自On.Lab成立以来，ONOS的发展非常迅猛。相比于OpenStack和OpenDayLight的半年一个版本，ONOS的第一只鸟Avocet是在2014年12月份出生的，这只鸟每隔3个月就进化一次，目前已经到了第二代MagPie。相比于OpenDaylight广泛的应用场景，ONOS专注于运营商领域，从成负来看不乏全球顶级的大型运营商，一些拥有网络基础设施的高校与科研机构也名列其中。

由于ONOS的方向比较专一，因此它在项目驱动方式上和ODL也有所不同。ODL是以应用来驱动用例的，而ONOS则是通过用例去驱动应用的，一个用例下面往往会有多个应用进行支撑。ONOS的Wiki上列出了很多的用例，其中一些主要的如下：CORD（端局云化）、SONA（数据中心网络虚拟化）、Packet Optical Convergence（IP+光协同）、IP RAN（接入网）、SDN-IP（SDN与传统IP域互通）、VPLS（L2VPN）、Seamless MPLS（端到端MPLS）、Carrier Ethernet（电信级以太网）、PCE+（基于PCEP的路径优化）、RR+（基于BGP的流量调度）。

能够看出，ONOS确实围绕着运营商做了很多事情，不过大多数用例还都处于原型设计阶段，实际能投入商用甚至PoC的并不多。数据中心这块也只有少数几个，本章的后面会逐一介绍，本节先来看看ONOS的架构。

7.1.1　分层架构

ONOS的主体采用Java开发，和ODL一样ONOS也使用了Karaf，使用者可以根据自己的需求，动态地加载或者卸载ONOS的各种模块。ONOS在架构设计上，一直保持分层的结构，中间层负责在应用和南向协议间进行适配，以屏蔽掉南向协议的差异性，这和ODL早期版本所用的AD-SAL架构是类似的。

ONOS的架构，如图7-1所示，从上到下分为App、Core、Providers+Protocls三层。App层是ONOS集成的一些应用，向上通过RESTful API、GUI和CLI对控制器外部应用和管理员开放接口，向下调用Core层提供的接口，以实现网络应用的逻辑。Core层负责收集底层网络的状态，向上与App层交互执行网络应用的逻辑，向下经Providers适配，通过Protocols对网络设备进行操作，水平方向会通过集群机制，在多个ONOS实例间进行状态的同步或者信息的交互。Protocols层是各类南向协议的实现，向上通过Providers适配到Core层，向下到网络设备进行管理或者控制。

图7-1　ONOS的分层架构

App消费Core提供的接口来实现不同的网络应用逻辑。注意区别这些App与控制器的外部应用，外部应用只能通过RESTful API调用这些App提供的API来进行二次开发，而不能直接调用Core所提供的内部接口。

Core将App与Protocols进行了隔离，它将南向协议中各异的网络资源抽象为通用的网络资源，使得App的开发者不用关心底层网络的接口特征，这降低了App的开发难度，尤其是当底层网络中存在多个厂家或者异构类型设备的时候。目前Core层对资源抽象如图7-2所示，红色部分为Core网络资源，灰色部分为ONOS中通用的平台资源。

图7-2　ONOS Core层对于资源的抽象

这些不同资源，在ONOS中分别对应着不同的Subsystem。Subsystem一词多见于Linux领域，指代某一类功能（比如进程管理、内存管理等）的实现集合，对于ONOS这种网络OS来说，Subsystem可理解为，围绕着某一类Core资源所进行的垂直实现，比如说Device Subsystem就包括Core中对Device资源的定义，以及所有消费了Core Device接口的App，以及适配了Core Device模型的Providers。多个ONOS实例间会以Subsystem为单位，通过Store机制进行状态的同步或者信息的交互，以实现集群协作。

Protocols是南向协议的实现，由于不同的南向协议对网络的抽象方式不同，因此在对接Core的时候需要通过Providers进行适配。另外，由于不同的南向协议所描述的资源也各不相同，因此在进行适配的时候要针对不同的Subsystem来进行，一些主要的南向协议所能适配的Subsystem如表7-1所示。

表7-1　ONOS中一些主要的南向协议所能适配的Subsystem

7.1.2　分层架构的实现

图7-3所示为ONOS对于分层的实现机制，分为Core和App之间的通信、Core内部的处理，以及Provider和Core之间的通信。这种实现机制也是以Subsystem为单位的。

图7-3　ONOS分层架构的实现

App在启动时，会订阅一些Core资源产生的事件，订阅的函数通常会实现App的网络应用逻辑。于是当这些Core资源发布这些事件时，就能够触发App网络服务逻辑的执行。

Core资源的ProviderService实例，将Provider资源的描述转化为Core资源的模型，并由该Core资源的Store发布一个事件，Core层中的EventDeliveryService会将该事件投递给各个订阅者。订阅者可能为该实例上的App，也可能为其他实例上的Store。Core层内部的实现如图7-4所示。

图7-4　Core层内部的实现

每个Provider都有不同的Provider资源（比如OpenFlow Provider会分OpenFlow DeviceProvider和OpenFlow FlowProvider等）。这些资源在ONOS中是独立启动的，启动时会去相应Core资源的ProviderRegistry（分别对应于DeviceProviderRegistry和Flow-Provider-Registry）注册。注册后会获得对应Core资源的ProviderService实例，Provider资源就通过调用这个实例的方法与对应的Core资源交互，传递的参数为该Provider资源的描述。

7.1.3　模块的开发

ONOS采用Java开发，为了实现模块的动态加载，使用了开源的Karaf项目（一种OSGI架构的实现）对ONOS各层中的模块进行管理。Karaf通过识别annotations（注解）去管理ONOS模块，表7-2介绍了在ONOS中用到的几个重要的annotations，Karaf实现annotations的内在机制就不介绍了。

表7-2　ONOS用到的几个重要的Karaf annotations

ONOS的分层结构，使其模块的开发是以API为驱动的。在ONOS中开发App时，只需要用Reference引用Core模块实例，然后直接通过这个实例就可以获取相关的数据，或者调用相关的方法，而在ODL中则需要对Datastore和RPC进行复杂的操作。可以直观地看到，相同的功能在ONOS中实现，相比于在ODL中实现，代码量上会有大幅度减少。

不过，ODL存在的上述问题实际上并不能归结为模型驱动本身的原因，而是由其DOM和BA/BI的设计所导致的。而模型驱动本身是有很多好处的，比如业务和代码间可以解耦，模块的代码间也可以解耦，平台的扩展性会大大提高。随着YANG的逐渐成熟，ONOS也提供了基于YANG的模型驱动的开发方式。但是目前来看，YANG对于ONOS来说只是一个辅助的开发手段，基于API驱动的开发方式仍将是ONOS的主流。

7.1.4　分层架构存在的问题

分层的核心在于Core对多种南向协议的适配，应用可以不关心底层设备的细节，直接对网络进行管理与控制。但是从另外一方面来讲，在适配过程中必然要放弃南向协议中一些有特色的功能，因为如果对每个功能点都进行适配，Provider就会越做越多，那么Core层将会变得无比臃肿，这样会降低平台的可扩展性。所以说，这是一个两难的问题。

ODL解决这一问题的办法是直接放弃了AD-SAL而转向MD-SAL。而ONOS在意识到这一问题后，采取的办法是仍然保留Core层对核心资源与功能的适配，而对于私有的资源或者功能，则采用Extensions Framework，允许App绕过Core直接调用设备的功能，如图7-5所示。实际上，ONOS这种兼而有之的思路是对的，毕竟一刀切的做法会损失掉能力的多样性，这对于通用平台来说并不是一个件事。前面提到过，ODL从Boron版本开始，重新通过Genius来提供对某些网络资源的抽象与适配。ONOS和ODL在思路上趋同，也从侧面说明了SDN控制器正在一步一步地变得更加成熟。

图7-5　ONOS Extensions Framework

7.1.5　数据存储与集群

ONOS在成立之初，就立志于成为一个Carrier-Grade的SDN控制器，因此ONOS非常重视与扩展性和可用性相关的设计，在系统的Prototype阶段，ONOS就开始考虑对分布式与集群机制进行集成与优化。最开始在Prototype 1中，ONOS使用了ZooKeeper进行集群管理，使用Casandra进行分布式数据库，虽然这个组合已经证明是足够成熟的，但是由于ZK和CA都需要外接于ONOS，因此在性能上并不尽如人意。随后在Prototype 2中，ONOS用RamCloud替换掉了Casandra，提高了数据的读写效率。在Prototype 2中，ONOS还引入了Hazelcast以提供基于发布/订阅事件的通知机制，缓解了轮询给CPU造成的巨大压力。

Hazelcast的一个问题在于，它使用了复杂的Paxos算法，但是在实现上却不够成熟。从Blackbird版本开始，ONOS开始将目光投向Raft（Paxos的简化版），并在Drake版本弃用了Hazelcast，随后集成了开源的Atomix作为Raft的实现。不过，Raft只能实现数据的强一致性，为了给业务提供灵活的数据存储，ONOS自己开发了一套Anti Entrophy Gossip的实现，为数据提供最终一致性，开发者可根据业务的实际需求来对数据一致性进行选择。

在持久化方面，ONOS用的是Java的嵌入式数据库MapDB。在节点间通信方面，ONOS基于Netty实现了一个异步的MessagingService，实例间所有的东西通信都会使用该服务提供的接口，基于该接口可以很容易实现分布式RPC。

综上所述，目前ONOS在数据存储与集群这一块，所用的技术堆栈就是：

1）基于RamCloud的内存数据库；

2）基于MapDB的持久化；

3）基于Atomix的集群管理、数据分片与强一致性；

4）基于Gossip的数据最终一致性；

5）基于Netty的节点间通信。

虽然在业界普遍的印象中，ONOS的集群要比ODL的集群做得更好，但实际上两者在集群设计的考虑上有很大的差别，所以这里也不好直接进行对比。

7.1.6　其他

ONOS出身于高校，相比于ODL来说，ONOS具有更为纯正的SDN血统。但是，ONOS比ODL晚出生了一年，这一年的时间意味着很多的事情，虽然ONOS版本迭代的速度比ODL要快上一倍，但是目前ONOS在影响力上和OpenDayLight仍存在一定的差距。不过，2016年ONOS圈子出现了下面几个大事件，从市场的角度来看，ONOS未来的发展还是值得期待的。

开源这边，先是CORD从ONOS中剥离出来，成立了独立的开源组织Open CORD，希望加速运营商端局云化的落地。不过Open CORD至今还是依赖于ONOS作为SDN控制器的，因此这对于ONOS来说实际上是一个利好的消息。随后，Google正式宣布加入Open CORD，Google的作风一向都是闭起门来鼓捣自家的黑科技，加入Open CORD的举措可算是给On.Lab和ONOS做了个大广告。厂商这边，华为的商用SDN控制器Agile Controller，从其3.0版本开始，就正式宣布其内核从ODL转向ONOS。标准组织这边，随着ONF与On.Lab的正式合并，“嫡出”的ONOS也得以进一步加强了其正统SDN的地位。

业内一直有声音在讨论ONOS和ODL会不会走到一起。目前看来这种可能性很小，虽然在某些技术点上有趋同之势，但鉴于技术背后复杂的集团利益，短期内应该很难找到一个人去推动双方的合并。而且，有竞争才会有创新，大一统对于行业的发展来说也不见得是什么好事情。

7.2　OpenFlow的示例实现

本节会对ONOS中OpenFlow的实现进行介绍，为后面介绍云相关业务的实现打一个基础。假设网络中有一台OF交换机连接两个主机，该OF交换机由一个ONOS实例控制，实例开启了一个App——fwd实现reactive forwarding。仍然和在ODL中讲解OpenFlow Plugin的思路一样，从代码的角度来看看以下几个问题：

1）OF交换机如何连上ONOS并被Device Subsystem检测到；

2）fwd如何获得PacketIn；

3）fwd如何下发PacketOut；

4）fwd如何下发FlowMod。

由于ONOS的分层结构，使得fwd和OpenFlow中间还隔着一个Core层，因此这部分代码的逻辑相比于ODL来说还要复杂一些。

7.2.1　OF交换机的上线

这部分依次涉及两件事：一是ONOS对OpenFlow信道的实例化，二是在信道上交互Hello和FeaturesReply后OF交换机上线，并被Device Subsystem检测到。

1.OpenFlow信道的实例化

启动Karaf会自动加载ONOS模块的OpenFlowControllerImpl，执行activate方法，执行Controller实例的start方法，在start方法中执行run，run生成了OpenFlow信道（Open-flowPipelineFactory实例化），并开启了对OpenFlow端口的监听。Openflow-Pipeline-Factory实例的getPipeline方法，生成了OFChannelHandler实例，负责接收OpenFlow消息。

然后，交换机开始连接控制器，OFChannelHandler实例与交换机开始交互，经过了Hello、FeaturesRequest、SetConfig、DescriptionStatsRequest后，收到OFStatisticsReply消息。在该消息的处理方法中，获得默认的OpenFlowSwitchDriver——DefaultSwitchHandshaker的实例，调用该实例的connectSwitch方法，然后connectSwitch方法会回调OpenFlow-ControllerImpl私有类OpenFlowSwitchAgent实例中的addConnectedSwitch方法。在add-ConnectedSwitch方法中，会遍历所有集合ofSwitchListener中的OpenFlow-SwitchListener实例，并执行其switchAdded方法。

========== onos.protocols.openflow.ctl.src.main.java.org.onosproject ============
================== .openflow.controller.impl. OFChannelHandler ==================
class OFChannelHandler
enum ChannelState
WAIT_DESCRIPTION_STAT_REPLY(false)
void processOFStatisticsReply()
h.sw = h.controller.getOFSwitchInstance();
// 获得默认的OpenFlowSwitchDriver——
// DefaultSwitchHandshaker的实例
if (h.sw.isDriverHandshakeComplete())
if (!h.sw.connectSwitch()) // 调用DefaultSwitchHandshaker
// 实例connectSwitch方法
disconnectDuplicate(h);
=========== onos.protocols.openflow.ctl.src.main.java.org.onosproject ===========
=============== .openflow.controller.impl.OpenFlowControllerImpl ===============
@Component(immediate = true)
@Service
public class OpenFlowControllerImpl
public class OpenFlowSwitchAgent
public boolean addConnectedSwitch()
if (connectedSwitches.get(dpid) != null)
…
else
for (OpenFlowSwitchListener l : ofSwitchListener)
l.switchAdded(dpid);
// 遍历集合ofSwitchListener中的OpenFlowSwitchListener
// 实例，并执行其switchAdded方法

从代码中可以看到，在OpenFlowControllerImpl实例初始化的时候集合ofSwitch-Listener是空的，OpenFlowSwitchListener实例是从哪里来的呢？通过对Open-Flow-Con-t-rollerImpl方法的查看，得知一定是有一些地方调用了OpenFlowControllerImpl的add-Listener方法，对ofSwitchListener这个集合进行了填充。

这就涉及了ONOS架构中最为核心的地方，即Protocols通过Providers对接Core。从图7-6中可以看出，应该是OFDeviceProvider调用了OpenFlowControllerImpl的addListener方法。

图7-6　Protocols通过Providers对接Core

来到ONOS模块OpenFlowDeviceProvider的activate方法，发现它果然调用了OpenFlowControllerImpl实例的addListener方法，而OpenFlowDeviceProvider的私有类InternalDeviceProvider被实例化了，InternalDeviceProvider实现了OpenFlowSwitchListener，它作为addListener的参数填充进了ofSwitchListener。

=========== providers.openflow.device.src.main.java.org.onosproject =============
=============== .provider.of.device.impl.OpenFlowDeviceProvider ================
@Component(immediate = true)
public class OpenFlowDeviceProvider
@Activate
public void activate()
controller.addListener(listener);
// 参数listener是一个InternalDeviceProvider实例，
// 此方法将该实例填充进了ofSwitchListener

2.OF交换机被Device Subsystem检测到

来看InternalDeviceProvider实例的switchAdded方法。方法中先后调用了provider-Service的deviceConnected方法和updatePorts方法，而providerService则是Core中的模块DeviceManager。

=========== providers.openflow.device.src.main.java.org.onosproject ============
=============== .provider.of.device.impl.OpenFlowDeviceProvider ================
@Component(immediate = true)
public class OpenFlowDeviceProvider
private class InternalDeviceProvider
public void switchAdded()
providerService.deviceConnected();
providerService.updatePorts();

这个DeviceManager正是ProviderService的实现，是ONOS在Core层的一个模块，其activate方法启动了Store中与Device相关事情的发布机制。在DeviceManager的deviceConnected和updatePorts方法中，可以看到事件的生成，以及通过post方法发布相应的事件。

===== core.net.src.main.java.org.onosproject.net.device.impl.DeviceManager =====
@Component(immediate = true)
@Service
public class DeviceManager
@Activate
public void activate()
store.setDelegate();
eventDispatcher.addSink(); // 启动Store中与Device相关的事情的发布机制
private class InternalDeviceProviderService
public void deviceConnected()
DeviceEvent event = store.createOrUpdateDevice();
if (event != null)
post();
public void updatePorts()
List<DeviceEvent> events = store.updatePorts();
if (event != null)
for (DeviceEvent event : events) // 遍历Device中的Port进行事件发布
post();

后面的事情就顺理成章了，有通过addListener方法订阅Device事件的模块就能收到该OF设备上线的通知了（事件类型为DEVICE_ADDED）。

7.2.2　fwd获得PacketIn

信道已经建立好了，fwd获得PacketIn的过程即为packetService捕获到OF信道上的PacketIn消息，并发布packet事件的过程。不过，fwd模块并没有直接订阅Device事件，因此上一小节产生的事件并不会直接触发FWD的逻辑。fwd模块在启动时会订阅packet，对应OpenFlow也就是PacketIn消息的后续处理。AddProcessor方法还要求传入一个整型参数，关于这个整型参数的作用后面会介绍。

======== apps.fwd.src.main.java.org.onosproject.fwd.ReactiveForwarding =========
@Component(immediate = true)
@Service(value = ReactiveForwarding.class)
public class ReactiveForwarding
@Activate
public void activate()
packetService.addProcessor(processor, PacketProcessor.director(2));
// 订阅packet事件

交换机连接控制器后，OFChannelHandler将信道置为Active状态，此时一台主机向另一台主机发送数据包，交换机包装PacketIn消息送给控制器。OFChannelHandler对PacketIn消息的处理和上面介绍过的对OFStatisticsReply消息的处理，在逻辑上是类似的，首先获得默认的OpenFlowSwitchDriver——DefaultSwitchHandshaker的实例，然后调用该实例的handleMessage方法。handleMessage方法会回调OpenFlowControllerImpl私有类OpenFlowSwitchAgent实例的processMessage方法。

========== onos.protocols.openflow.ctl.src.main.java.org.onosproject ============
================== .openflow.controller.impl. OFChannelHandler ==================
class OFChannelHandler
enum ChannelState
ACTIVE(true)
void processOFPacketIn()
h.dispatchMessage();
private void dispatchMessage()
sw.handleMessage();
=========== onos.protocols.openflow.ctl.src.main.java.org.onosproject ===========
=============== .openflow.controller.impl.OpenFlowControllerImpl ===============
@Component(immediate = true)
@Service
public class OpenFlowControllerImpl
public class OpenFlowSwitchAgent
public void processMessage()
processPacket();
for (OpenFlowMessageListener listener : ofMessageListener)
listener.handleIncomingMessage();

ProcessMessage方法调用processPacket方法，经过switch的判断后进入PACKET_IN分支。在PACKET_IN分支中，先获得解析PacketIn的消息以获得原始pktCtx，pktCtx含有原始数据包的特征。接下来又出现了熟悉的桥段：遍历所有集合ofPacketListener中的PacketListener实例并执行其handlePacket方法。ofPacketListener初始为空，那一定是有地方调用了addListener方法填充了ofPacketListener。

=========== onos.protocols.openflow.ctl.src.main.java.org.onosproject ===========
=============== .openflow.controller.impl.OpenFlowControllerImpl ===============
@Component(immediate = true)
@Service
public class OpenFlowControllerImpl
public void processPacket()
switch (msg.getType())
case PACKET_IN:
OpenFlowPacketContext pktCtx = DefaultOpenFlowPacketContext
.packetContextFromPacketIn();
for (PacketListener p : ofPacketListener.values())
p.handlePacket();

上述过程和上一节ofSwitchListener遇到的情况完全一样。对应地，找到OpenFlow-Packet-Provider模块，果然是它调用了OpenFlowControllerImpl实例的addPacketListener方法，将OpenFlowPacketProvider的私有类InternalPacketProvider实例化，并作为addPac-ketListener的参数填充进了ofPacketListener。

=========== providers.openflow.packet.src.main.java.org.onosproject ============
================ .provider.of.packet.impl.OpenFlowPacketProvider ===============
@Component(immediate = true)
public class OpenFlowPacketProvide
@Activate
public void activate()
providerService = providerRegistry.register();
controller.addPacketListener(20, listener); // 监听PacketIn

回到OpenFlowControllerImpl的processPacket方法，该方法会遍历ofPacketListener得到InternalPacketProvider的实例，并执行其handlePacket方法。该方法的参数pktCtx，之前提到过，包含了原始数据包的特征。handlePacket方法首先通过pktCtx构造出inPkt和outPkt两个副本，随同pktCtx一并构造出一个OpenFlowCorePacketContext实例的corePktCtx。corePktCtx作为参数传给providerService的processPacket方法，而该provid-er-Service则是Core中的模块PacketManager。

=========== providers.openflow.packet.src.main.java.org.onosproject ============
================ .provider.of.packet.impl.OpenFlowPacketProvider ===============
@Component(immediate = true)
public class OpenFlowPacketProvider
private class InternalPacketProvider
public void handlePacket()
DefaultInboundPacket inPkt = new DefaultInboundPacket();
// packet的Inbound副本
if (!pktCtx.isBuffered())
outPkt = new DefaultOutboundPacket(); // packet的Outbound副本
OpenFlowCorePacketContext corePktCtx =
new OpenFlowCorePacketContext(); // packet的Core副本
providerService.processPacket(corePktCtx); // 处理Core副本

与DeviceManager一样，PacketManager也是ProviderService的实现，在其activate方法里则启动了与Packet相关事情的发布机制。要注意的是，不要被activate方法所迷惑，PacketManager向App投递Packet事件并不是通过post这类机制的。

===== core.net.src.main.java.org.onosproject.net.packet.impl.PacketManager =====
@Component(immediate = true)
@Service
public class PacketManager
@Activate
public void activate()
store.setDelegate();
deviceService.addListener();

PacketManager的processPacket方法背后的工作机制就比较讲究了。可以看到，这个方法的主体是个循环，要遍历processers中各个ProcessEntry实例并执行其process方法。可是processers初始也为空，也是有地方调用了addProcesser方法对processers进行了填充。

===== core.net.src.main.java.org.onosproject.net.packet.impl.PacketManager =====
@Component(immediate = true)
@Service
public class PacketManager
private class InternalPacketProviderService
public void processPacket()
for (ProcessorEntry entry : processors)
// 各个App有序地对packet进行处理
try
long start = System.nanoTime();
entry.processor().process();
entry.addNanos();

回到本节最开始对fwd介绍的部分，可以看到调用PacketManager的addProcesser方法的正是fwd这个App。Fwd在调用addProcesser方法时，将自身私有类Reactive-Packet-Processor的实例添加到了PacketManager的processers中，并指定自己处理packet的优先级为2。

重要的部分来了：addProcesser方法在执行时，会根据优先级将App放入processers的特定位置。这个processers是个列表，是有序的，各个订阅了packetService的App按优先级从小到大排序。这样就使得PacketManager的processPacket方法在遍历过程中，实际上就相当于将corePktCtx投给优先级最小的App，这个App通过process方法处理完再给优先级第二小的App，依此类推直至全部App处理完。

========= apps.fwd.src.main.java.org.onosproject.fwd.ReactiveForwarding =========
@Component(immediate = true)
@Service(value = ReactiveForwarding.class)
public class ReactiveForwarding
@Activate
public void activate()
packetService.addProcessor(processor, PacketProcessor.director(2));
// 订阅packet，自身的优先级为2
===== core.net.src.main.java.org.onosproject.net.packet.impl.PacketManager =====
@Component(immediate = true)
@Service
public class PacketManager
public void addProcessor(PacketProcessor processor, int priority)
ProcessorEntry entry = new ProcessorEntry(processor, priority);
int i = 0;
// 根据priority将App放入processers的特定位置
for (; i < processors.size(); i++)
if (priority < processors.get(i).priority())
break;
processors.add(i, entry);

使用这个优先级的意义是什么呢？主要是对多个订阅了packet的App进行编排，规定谁先处理谁后处理。可能有的App会在控制器中直接对packet进行修改，后面的App看到的Core Packet就随之发生了变化，如一个NAT（假设优先级=1）的App把目的IP地址改了，然而另一个路由的App（假设优先级=2）处理的就是改写目的IP后的数据包，它不需要了解NAT App的存在就可以做出正确的转发决策。

如果没有优先级这种机制，使用原始的发布订阅去通知App，那么这些App间的编排就需要依赖于数据平面的Pipeline。不同的App向不同的Table下发流表，然后由GOTO来决定数据包的处理顺序。可是，如果底层设备不支持Pipeline（比如OpenFlow 1.0只支持单级流表），那么对数据包的处理就很可能乱了套。

7.2.3　fwd下发PacketOut和FlowMod

经过上面的调用过程，现在数据包pktCtx终于进入了fwd的视野。fwd通过私有类ReactivePacketProcessor的process方法执行转发的处理逻辑，主要是根据全局视图选路，没有目的主机的信息就泛洪，拓扑改变后重新铺路，具体的逻辑实现细节这里不谈，只看PacketOut和FlowMod是如何下发的。

1.PacketOut

PacketOut的分发是通过packetOut方法完成的。参数中context即为pktCtx，port-Number是出端口。treatmentBuilder类似于OpenFlow的Action，setOput类似于设好Action中的output。方法最后调用pktCtx的send方法，send方法中又跳转到Default-OpenFlowPacketContext的send方法中。

========= apps.fwd.src.main.java.org.onosproject.fwd.ReactiveForwarding ========
@Component(immediate = true)
@Service(value = ReactiveForwarding.class)
public class ReactiveForwarding
private void packetOut()
context.treatmentBuilder().setOutput(); // 设定发出PacketOut的端口
context.send();

DefaultOpenFlowPacketContext的send方法，会调用DefaultSwitchHandshaker实例的sendMsg方法，进而又跳转到sendMsgsOnChannel方法。sendMsgsOnChannel方法会通过目标交换机对应的Netty channel，执行其write方法，这样OFPacketout消息就被发送出去了。

一般而言，上述调用过程发生于不改变数据包payload的情况，如果要自己封一个新包发出去（比如ARP Reply），那么就要使用packetService提供的emit方法了。因为emit发的包是控制器生成出来的，不是交换机传来的，所以这个包就有可能要通过别的ONOS实例下发。因此emit方法的实现要比上述过程的实现复杂一些，要先使用Store将生成出来的包以事件的方式通知出去，相应的ONOS实例收到该事件后回调localEmit方法，找到目标交换机的channel把OFPacketout消息写下去。

由于Fwd模块不会封新的包发出去，这里就不详细介绍emit方法的执行过程了，不过下面将介绍的FlowMod处理过程倒是与其比较类似。

2.FlowMod

在fwd中流表的下发是通过installRule方法完成的，该方法最终会调用FlowObjective-Manager模块的forward方法来下发流表。

======== apps.fwd.src.main.java.org.onosproject.fwd.ReactiveForwarding =========
@Component(immediate = true)
@Service(value = ReactiveForwarding.class)
public class ReactiveForwarding
private void installRule()
flowObjectiveService.forward();

FlowObjectiveManager是FlowObjectiveService的实现，是Core层的主要模块，向其他模块提供转发的接口。除了FlowObjectiveManager以外，ONOS还有另外一个提供流表服务的模块FlowRuleManager。两个模块的区别在于FlowObjectiveManager提供业务层面的转发逻辑，FlowRuleManager提供的是表项级别的转发规则，实际上FlowObjectiveManager中forward方法的实现最终也是通过FlowRuleManager来完成的。

Forward方法启动了一个ExecutorService，入口函数为FlowObjectiveManager内部类ObjectiveInstaller的run方法，该方法首先根据deviceid获取设备的Pipeliner，然后调用Pipeliner的forward方法。

=========== core.net.src.main.java.org.onosproject.net.flowobjective ============
====================== .impl.FlowObjectiveManager ==========================
@Component(immediate = true)
@Service
public class FlowObjectiveManager
public void forward()
executorService.execute(new ObjectiveInstaller());
private class ObjectiveInstaller implements Runnable
public void run()
Pipeliner pipeliner = getDevicePipeliner();
// 根据deviceid获取设备的Pipeliner
if (pipeliner != null)
if (objective instanceof NextObjective)
…
else if (objective instanceof ForwardingObjective)
pipeliner.forward(()); // 调用Pipeliner的forward方法转发数据包

Pipeliner是设备对流表的具体实现，一般来说只有OpenFlow交换机才具备Pipeliner，不同厂家OF交换机的Pipeliner也各不相同，主要体现在流水线上各级流表功能的不同。来看看OpenVSwitchPipeline中的forward方法，首先通过processForward方法对转发的业务逻辑进行适配，转换为表项逻辑，然后调用flowRuleService的实现FlowRuleManager中的apply方法。

============ drivers.default.src.main.java.org.onosproject.driver ===============
====================== .pipeline.OpenVSwitchPipeline ========================
public class OpenVSwitchPipeline
public void forward()
flowRuleService.apply(); // 调用FlowRuleManager的apply方法

apply方法启动了一个ExecutorService，入口函数为FlowRuleManager的内部类Flow-OperationsProcessor的run方法，run方法调用了process方法。Process方法最后又启动了一个ExecutorService，入口函数为FlowRuleStore实现（SimpleFlowRuleStore或者Distri-butedFlowRuleStore）中的storeBatch方法。

===== core.net.src.main.java.org.onosproject.net.flow.impl.FlowRuleManager =====
@Component(immediate = true)
@Service
public class FlowRuleManager
public void apply()
operationsService.execute(new FlowOperationsProcessor());
private class FlowOperationsProcessor implements Runnable
public synchronized void run()
if (!stages.isEmpty())
process();
private void process()
for (DeviceId deviceId : perDeviceBatches.keySet())
deviceInstallers.execute(() -> store.storeBatch());

StoreBatch方法会将表项规则以FlowRule Event的形式发布出去，Distributed-Flow-RuleStore事件发布的代码如下所示。之所以ONOS选择用这种较为复杂的方式来下发FlowMod，而不是直接交给交换机的socket，原因和前一小节提到的emit方法的处理是一样的，这个FlowMod有可能要通过别的ONOS实例下发给目标交换机。代码首先通过MastershipService获得deviceid所属的主控制器，如果主控制器即为当前控制器，则继续调用storeBatchInternal方法发布事件，否则通过ClusterCommunicationService提供的集群通信服务向主控制器单播FlowRule事件。

=========== core.store.dist.src.main.java.org.onosproject.store.flow ============
======================= .impl.DistributedFlowRuleStore =======================
@Component(immediate = true)
@Service
public class DistributedFlowRuleStore
public void storeBatch()
DeviceId deviceId = operation.deviceId();
NodeId master = mastershipService.getMasterFor(deviceId);
// 通过Core层的mastership服务解析
// 目标交换机的master controller
if (Objects.equals(local, master)) // 如果master controller就是自己
storeBatchInternal(); // 发布FlowRule Event
return; // 返回
clusterCommunicator.unicast().whenCompleted();
// 如果master controller是其他的ONOS实例，
// 则会执行至此，向主控制器单播FlowRule Event

FlowRuleManager通过内部私有类InternalStoreDelegate来订阅FlowRule事件，有FlowRule Event过来时回调notify方法，该方法进而调用OpenFlowRuleProvider的executeBatch方法，该方法最终根据deviceid找到设备对应的OF信道，将OFFlowMod消息写下去。

====== core.net.src.main.java.org.onosproject.net.flow.impl.FlowRuleManager =====
@Component(immediate = true)
@Service
public class FlowRuleManager
private class InternalStoreDelegate
public void notify() // 通过notify方法来处理事件
switch (event.type())
case BATCH_OPERATION_REQUESTED:
request.ops().forEach(
ops -> {
switch (op.operator())
case ADD:
post(new FlowRuleEvent());
break;
});
if (flowRuleProvider != null)
flowRuleProvider.executeBatch();
============= providers.openflow.flow.src.main.java.org.onosproject ===========
================== .provider.of.flow.impl.OpenFlowRuleProvider ==================
@Component(immediate = true)
public class OpenFlowRuleProvider
public void executeBatch()
for (FlowRuleBatchEntry fbe : batch.getOperations())
sw.sendMsg();

7.3　ONOSFW的实现

ONOS中有一个专门用于集成OPNFV环境的项目叫做ONOSFW（ONOS FrameWork），它通过ONOS App——VTN来支持虚拟化网络的开通，能够对接networking-onos作为OpenStack后端网络的实现。VTN实现的功能，包括分布式ARP、分布式L2/L3，以及浮动IP的SNAT和DNAT（不支持NAPT）。其特色是支持SFC，不过这个项目在完成OPNFV Brahmaputra中的Demo Test后，VTN就不再更新功能了。

VTN的目录结构如下所示，vtnmgr负责提供二三层网络功能，sfcmgr负责提供服务链功能，vtnweb负责接收二三层网络和与服务链相关的RESTful API，sfcweb提供了服务链的GUI，vtnrsc则定义了各种二三层网络和服务链的数据结构。

vtn/
sfcmgr // 提供服务链的功能
sfcweb // 服务链的GUI
vtnmgr // 提供二三层网络功能
vtnsrc // 接收二三层网络和服务链相关的RESTful API
vtnweb // 二三层网络和服务链的数据结构

Vtnrsc、vtnweb和sfcweb没什么好说的，本节主要来看看sfcmgr和vtnmgr的业务逻辑。

7.3.1　vtnmgr分支

在vtnmgr的目录中，manager分支下的VtnManager有个ONOS模块，它负责实现二三层的业务逻辑，impl分支下的**Impl提供各个功能点的具体实现，utils则提供一些边边角角的API。目前vtnmgr能提供的功能包括分布式的L2/L3、FloatingIP的双向NAT。

1.VtnManager

VtnManager是一个ONOS模块，在其activate方法中会启动impl分支下的各个功能，并监听一些事件，包括Device事件、Host事件和VtnRsc事件，以触发相应的业务逻辑。处理业务逻辑的主要方法如下所示。

============= apps.vtn.vtnmgr.src.main.java.org.onosproject.vtn ================
======================== .manager.impl.VtnManager =========================
public class VtnManager
public void onControllerDetected() // 探测到OVSDB节点上线
public void onControllerVanished() // 探测到OVSDB节点离线
public void onOvsDetected() // 探测到OVS上线
public void onOvsVanished() // 探测到OVS离线
public void onHostDetected() // 探测到虚拟机上线
public void onHostVanished() // 探测到虚拟机离线

当收到Device_ADDED Event时，VtnManager会判断Device是Switch类型的，还是Controller（指OVSDB Server）类型的，如果是Controller类型的会调用onController-Detected方法，如果是Swtich类型的会调用onOvsDetected方法。一般而言，会先收到Controller device的Device_ADDED，然后是Switch device的Device_ADDED。

OnControllerDetected方法，会创建br-int并建立该br-int上的默认隧道端口。相反，onControllerVanished方法则从相关数据结构中移除该OVSDB服务器。

============== apps.vtn.vtnmgr.src.main.java.org.onosproject.vtn ===============
======================= .manager.impl.VtnManager ==========================
public class VtnManager
public void onControllerDetected()
if (mastershipService.isLocalMaster())
if (exPortVersioned != null)
VtnConfig.applyBridgeConfig(); // 创建br-int
programTunnelConfig(); // 为br-int建立默认的隧道端口

OnOvsDetected方法调用了applyTunnelOut方法，applyTunnelOut方法遍历现有的虚拟机（这些虚拟机都位于其他节点上），得到这些虚拟机所在交换机的IP，然后建立<Segment ID，MAC>到<Port，Remote IP>的转发流表。相反，OnOvsVanished方法会移除这些转发流表。

=============== apps.vtn.vtnmgr.src.main.java.org.onosproject.vtn ==============
========================= .manager.impl.VtnManager ========================
public class VtnManager
public void onOvsDetected()
applyTunnelOut(); // 向该新上线OVS下发流表，转发到
// 其他OVS上已有虚拟机的流量
Iterable<RouterInterface> interfaces = routerInterfaceService.getRouter-Interfaces();
interfaces.forEach(routerInf -> {
applyL3ArpFlows(); // 下发为router interface代答ARP的流表
});
private void applyTunnelOut()
Iterable<Host> allHosts = hostService.getHosts();
// 得到所有已经上线的虚拟机，由于该OVS是新上线的，
// 因此该方法得到的是位于其他已上线的OVS上的虚拟机
if (allHosts != null)
Sets.newHashSet(allHosts).forEach(host -> { // 遍历所有的已上线虚拟机
Device remoteDevice = deviceService.getDevice();
// 获得该虚拟机的接入OVS
String remoteControllerIp = VtnDat.getControllerIpOfSwitch();
// 获得该虚拟机接入OVS的IP
ports.stream().filter(p -> p.name().equalsIgnoreCase())
.forEach(p -> {
l2ForwardService.programTunnelOut();
// 下发流表，用于转发去往同一个二层中、
// 位于远端的虚拟机的流量
});
});

OnHostDetected方法处理虚拟机的上线。该方法会进一步执行3个方法。ProgramSffAndClassifierHost方法判断虚拟机是否为SFI（Service Function Instance，如FW、LB等），若是则将所在交换机的类型设为sff_ovs，否则设为classifier_sff，具体作用到了sfcmgr部分再讲。

============== apps.vtn.vtnmgr.src.main.java.org.onosproject.vtn ===============
======================= .manager.impl.VtnManager ==========================
public class VtnManager
public void onHostDetected()
programSffAndClassifierHost();
applyHostMonitoredL2Rules();
applyHostMonitoredL3Rules();

ApplyHostMonitoredL2Rules方法的主体实现如下，这个方法里面涉及了较多的处理过程。programGroupTable方法使用ALL类型的Group来建立隧道的组播端口，用于处理本地产生的广播，然后调用多个下发流表的方法，各个方法的作用参见注释。

============== apps.vtn.vtnmgr.src.main.java.org.onosproject.vtn ===============
======================= .manager.impl.VtnManager ==========================
public class VtnManager
private void applyHostMonitoredL2Rules()
for (PortNumber p : localTunnelPorts)
programGroupTable(); // 建立隧道组播的Group
if (type == Objective.Operation.ADD)
l2ForwardService.programLocalBcastRules();
// 在新增虚拟机所在的交换机上，处理本地产生的广播
classifierService.programTunnelIn();
// 在新增虚拟机所在的交换机上，标记从隧道流入的流量
l2ForwardService.programLocalOut();
// 在新增虚拟机所在的交换机上，处理发往远端虚拟机的流量
l2ForwardService.programTunnelBcastRules();
// 在新增虚拟机所在的交换机上，处理从隧道流入的广播流量
programTunnelOuts();
// 在远端交换机上，处理发向该新增虚拟机的单播流量
classifierService.programLocalIn();
// 在新增虚拟机所在的交换机上，标记虚拟机流入的流量

ApplyHostMonitoredL3Rules方法的主体实现如下，在逻辑上主要分为2块：通过apply-East-WestFlows处理同一租户内部跨网段的流量，通过applyNorthSouthFlows方法处理虚拟机与Internet间的流量。

============== apps.vtn.vtnmgr.src.main.java.org.onosproject.vtn ===============
======================= .manager.impl.VtnManager ==========================
public class VtnManager
private void applyHostMonitoredL3Rules()
hostInterfaces.forEach(routerInf -> {
// 遍历router中属于同一租户的interfaces
if (count > 0)
if (operation == Objective.Operation.ADD)
if (routerInfFlagOfTenantRouter.get(tenantRouter) != null)
applyEastWestL3Flows(); // 处理同一租户内部跨网段的流量
});
if (floatingIp != null) // 若新建的虚拟机分配了浮动IP地址
applyNorthSouthL3Flows(); // 处理该新建虚拟机与Internet间的流量
private void applyEastWestL3Flows()
if (operation == Objective.Operation.ADD)
sendEastWestL3Flows();
private void sendEastWestL3Flows()
classifierService.programL3InPortClassifierRules(); // 标记流入的L3流量
Sets.newHashSet(devices).stream().filter().forEach(d -> {
l3ForwardService.programRouteRules();
// 为东西向流量做路由，改写MAC和VNI
});
private void applyNorthSouthL3Flows()
classifierService.programL3ExPortClassifierRules();
// 识别从External Network发向浮动IP的流量
dnatService.programRules(); // 入方向上的DNAT
snatService.programSnatSameSegmentUploadControllerRules();
if (operation == Objective.Operation.ADD)
sendNorthSouthL3Flows();
l2ForwardService.programExternalOut();
// SNAT后向External Network转发
private void sendNorthSouthL3Flows()
l3ForwardService.programRouteRules();
// 为NAT后的南北向流量做路由，改写MAC
classifierService.programL3InPortClassifierRules();
// 标记由该分配了浮动IP的虚拟机流入的流量

以上就是VtnManager的主体业务逻辑。除了上述以外，router interface和Floating Ip还可以单独触发业务逻辑的执行，onRouterInterfaceDetected方法会执行applyEast-WestFlows，onFloatingIpDetected方法就执行applyNorthSouthFlows。

2.***ServiceImpl

在上一节的openstacknetworking部分讲过ARP和Floating IP了，VTN相应的实现区别也不大。L2ForwardServiceImpl和L3ForwardServiceImpl的实现在上面有关VtnManager的讲解过程中已经覆盖了，此处不再赘述。VTN使用的Pipeline如下所示，可以很容易地将上面对VtnManager的讲解对应到各个表中，Table 4和Table 7用于SFC的实现，下面就来介绍VTN中对SFC的实现。

============= drivers.default.src.main.java.org.onosproject.driver ==============
====================== .pipeline.OpenVSwitchPipeline ========================
public class OpenVSwitchPipeline
private static final int CLASSIFIER_TABLE = 0;
private static final int ENCAP_OUTPUT_TABLE = 4; // PortPair转发表，SFC相关
private static final int TUN_SEND_TABLE = 7; // 隧道转发表，SFC相关
private static final int ARP_TABLE = 10;
private static final int DNAT_TABLE = 20;
private static final int L3FWD_TABLE = 30;
private static final int SNAT_TABLE = 40;
private static final int MAC_TABLE = 50;

7.3.2　sfcmgr分支

在sfcmgr的目录中，manager.impl.SfcManager实现了SFC的业务逻辑，它会调用installer.impl.SfcFlowRuleInstaller中提供的方法对Pipeline进行操作。

在介绍SfcManager的业务逻辑前，需要先搞清楚SfcManager中各种数据结构的含义。其中，PortChain是指服务链，FlowClassifier是流量分类器（根据五元组识别），PortPair对应一个VNF实例，PortPairGroup是同一类功能的PortPair组成的VNF资源池。一个PortChain中会有一个或多个FlowClassifier，以及一个或多个PortPairGroup。

SfcManager是实现SFC业务逻辑的ONOS模块，它主要就是通过onPortChainCreated方法去监听服务链的建立，然后开通服务链上的路径，onPortChainCreated方法会调用SfcFlowRuleInstallerImpl的installFlowClassifier方法下发FlowClassifier流表。Flow-Classifier流表的逻辑是，首包需要上报给控制器，如果FlowClassifier与PortChain要经过的第一个PortPair不在同一台OVS上，则封装NSHoverVxLANGPE将流量送到第一个PortPair所在的OVS上，同时向下一个PortPair所在的OVS下发流表，从而指导其接收该流量并从相应端口送出。如果FlowClassifier与PortChain要经过的第一个PortPair在同一台OVS上，则GOTO PortPair转发表。

============== apps.vtn.sfcmgr.src.main.java.org.onosproject.sfc ================
======================= .manager.impl.SfcManager ==========================
@Component(immediate = true)
@Service
public class SfcManager
public void onPortChainCreated()
flowRuleInstaller.installFlowClassifier();
=============== apps.vtn.sfcmgr.src.main.java.org.onosproject =================
================ .sfc.installer.impl.SfcFlowRuleInstallerImpl =================
public class SfcFlowRuleInstallerImpl
public ConnectPoint installFlowClassifier()
return installSfcClassifierRules();
public ConnectPoint installSfcClassifierRules()
while (flowClassifierListIterator.hasNext())
// 遍历PortChain中所有的FlowClassifier
if (fiveTuple == null)
sendSfcRule(); // 将首包发给控制器
continue;
if (deviceId != null && !deviceId.equals(deviceIdfromPortPair))
// 如果FlowClassifier与PortChain中要经过的
// 第一个PortPair不在同一台OVS上
sendSfcRule(); // 流量会GOTO隧道转发表（TUN_SEND_TABLE）
sendSfcRule(); // 控制器发出的PacketOut也GOTO隧道转发表
installSfcTunnelSendRule(); // 将流量送到第一个PortPair所在的OVS
installSfcTunnelReceiveRule();
// 指导下一个PortPair所在的OVS接收该流量，并从相应端口送出
else // 如果FlowClassifier与PortChain中要经过的
// 第一个PortPair在同一台OVS上
sendSfcRule();
// 流量会GOTO PortPair转发表（ENCAP_OUT_TABLE）
sendSfcRule();
// 控制器发出的PacketOut也会GOTO PortPair转发表

上述的FlowClassifier流表，完成的只是流表的识别，以及识别后到路径第一跳OVS的转发。对于后续路径上流量的引导，要等到流量的首包进来之后（上面的installSfcClassifierRules方法中有一条“将首包发给控制器”的流表），SfcManager内部类SfcPacketProcessor中的process方法会对其进行处理，该方法首先根据数据包的包头提取出5元组，从而获得5元组对应的PortChain，并分析出PortChain中负载最轻的路径，然后下发流表引导目标流量流经该路径。

============== apps.vtn.sfcmgr.src.main.java.org.onosproject.sfc ================
======================== .manager.impl.SfcManager =========================
@Component(immediate = true)
@Service
public class SfcManager
private class SfcPacketProcessor
public void process()
LoadBalanceId id = loadBalanceSfc(); // 获得PortChain中负载最轻的路径
flowRuleInstaller.installLoadBalancedFlowRules();
// 下发流表对流量进行路径上的引导
sendPacket(); // 通过PacketOut将首包重新送回数据平面

LoadBalanceSfc方法会选择当前工作负载最小的VNF实例。其实现逻辑为遍历该PortChain中的PortPairGroup，然后再遍历当前PortPairGroup中的各个PortPair，找到工作负载最小的PortPair加入最优路径。

============== apps.vtn.sfcmgr.src.main.java.org.onosproject.sfc ================
======================== .manager.impl.SfcManager =========================
@Component(immediate = true)
@Service
public class SfcManager
private LoadBalanceId loadBalanceSfc()
for (final PortPairGroupId portPairGroupId : portPairGroups)
// 遍历PortChain中的各个PortPairGroup
for (final PortPairId portPairId : portPairs)
// 遍历PortChainGroup中的各个PortPair，
if (load == 0)
…
else
if (load < minLoad)
minLoad = load;
minLoadPortPairId = portPairId;
// 选择出当前负载最小的PortPair
if (minLoadPortPairId != null)
loadBalancePath.add(minLoadPortPairId);
// 将负载最小的PortPari添加到路径中

InstallLoadBalancedForwardingRule方法，会调用installSfcFlowRules方法来引导流量在负载均衡的服务链路径上进行的转发。InstallSfcFlowRules方法会先向第一个PortPair所在的OVS下发PortPair转发表，PortPair转发表会采用NSHoverEthernet封装。然后InstallSfcFlowRules方法会从第二个PortPair遍历到倒数第二个PortPair，如果发现当前PortPair和下一个PortPair在同一个OVS上，就直接在本地完成中继，否则指导当前PortPair所在的OVS将流量通过NSHoverVxLANGPE隧道送到下一个PortPair所在的OVS上，并指导下一个PortPair所在的OVS从相应端口送给下一个OVS。对于最后一个PortPair，则需要下发流表终结NSH，并进行后续非服务链的处理，最终将流量送到目的地。

============== apps.vtn.sfcmgr.src.main.java.org.onosproject.sfc ================
======================== .manager.impl.SfcManager =========================
@Component(immediate = true)
@Service
public class SfcManager
public ConnectPoint installLoadBalancedFlowRules()
return installSfcFlowRules();
public ConnectPoint installSfcFlowRules()
installSfcEncapOutputRule(); // 向第一个PortPair所在的OVS下发PortPair
// 转发流表，采用NSHoverEthernet的封装
while (portPairListIterator.hasNext())
installSfcForwardRule();
// 向当前PortPair所在的交换机下发流表，若下一个
// PortPair与当前PortPair在同一个OVS上，则GOTO
// PortPair转发流表，否则GOTO隧道转发表，并向隧道
// 转发表下发流表将流量送给下一个PortPair所在的OVS，
// 封装采用NSHoverVxLANGPE
installSfcEncapOutputRule();
// 向下一个PortPair所在的OVS下发PortPair转发流表
installSfcEndRule(); // 向最后一个PortPair所在的OVS下发流表，将NSH头
// 剥掉，并做Resubmit，进行后续非服务链的处理

7.4　SONA的实现

SONA是ONOS中OpenStack的后端实现，其架构如图7-7所示。除了运行虚拟机的计算节点外，图中还画出了网关节点，这个网关节点是用来处理南北向流量的，多个网关节点可形成网关组以支持数据平面的HA。在网关节点上，还会集成Quagga与外界网络进行动态路由的交互。ONOS中与SONA的相关模块有3个，分别是负责处理节点初始化配置的OpenStackNode，负责监听networking-onos并完成后端网络开通的OpenStackNetworking，以及负责外部路由重分布的vRouter。vRouter实际上是从SDN-IP这个use case中借鉴过来的，它在下一节CORD的内容中再做介绍。本节先来看看OpenStackNode和OpenStackNetworking。

图7-7　ONOS SONA架构

7.4.1　openstacknode分支

openstacknode通过OVSDB对计算节点和网关节点上的OVS进行初始化。open-s-t-a-cknode的目录结构如下所示。OpenstackNode是对计算、网关节点的抽象，Opens-tackNodeConfig能够根据配置文件中的OpenStack节点信息返回OpenstackNode的实例集合，OpenstackEvent规定了根据节点状态处理节点事件的方法，OpenstackNodeManager是处理节点事件的主模块，SelectGroupHandler通过Group提供多个网关节点间的负载均衡。

openstacknode/
OpenstackNode // 描述计算节点和网关节点的数据结构
OpenstackNodeConfig // 负责读取配置文件中的节点信息
OpenstackEvent // 根据节点状态选择处理节点事件的方法
OpenstackNodeManager // 处理节点事件的主模块
SelectGroupHandler //通过Group实现了网关节点间的负载均衡

OpenstackNodeManager是ONOS模块，其activate方法如下所示。该方法会实例化一个SelectGroupHandler，并在最后通过readConfiguration方法调用OpenStackNodeConfig的openstackNodes方法，读入配置文件中OpenStack的节点信息，并通过nodeStore发布OpenStack节点的UPDATE事件。这个事件会被OpenstackNodeManager的私有类InternalMapListener的实例所监听到，触发其event方法，该方法判断出该事件为UPDATE类型并调用initNode方法。initNode方法会根据节点当前所处的状态执行相应的处理逻辑。

============== apps.openstacknode.src.main.java.org.onosproject ================
=================== .openstacknode.OpenstackNodeManager =====================
@Component(immediate = true)
@Service
public final class OpenstackNodeManager
private final MapEventListener<String, OpenstackNode> nodeStoreListener =
new InternalMapListener();
private ConsistentMap<String, OpenstackNode> nodeStore;
@Activate
protected void activate()
readConfiguration();
private void readConfiguration()
OpenstackNodeConfig config = configRegistry.getConfig();
config.openstackNodes().forEach(node -> {
// 通过OpenstackNodeConfig读取配置文件中的节点信息
addOrUpdateNode();
});
public void addOrUpdateNode()
nodeStore.put(node.hostname(),OpenstackNode.getUpdatedNode());
// 通过nodeStore来发布节点的UPDATE事件
private class InternalMapListener
public void event()
switch (event.type())
case UPDATE: // 监听到节点的UPDATE事件
eventExecutor.execute(() -> initNode(newNode));
private void initNode()
NodeState state = node.state();
state.process();

在OpenstackNodeEvent中制定了4种节点状态，每一种在OpenstackNodeManager中都有对应的处理方法：INIT状态的节点在下一步需要创建Bridge，DEVICE_CREATED状态的节点在下一步需要创建端口，COMPLETED状态的节点在下一步需要处理和网关节点相关的逻辑，IMCOMPLETED状态的节点视为非法，需要移除与之相关的一些数据结构。

============== apps.openstacknode.src.main.java.org.onosproject ===============
===================== .openstacknode.OpenstackNodeEvent =====================
public class OpenstackNodeEvent
public enum NodeState
INIT
public void process()
nodeService.processInitState();
DEVICE_CREATED
…
COMPLETED
…
INCOMPLETE
…
=============== apps.openstacknode.src.main.java.org.onosproject ==============
==================== .openstacknode.OpenstackNodeManager ====================
@Component(immediate = true)
@Service
public final class OpenstackNodeManager
public void processInitState()
if (!isOvsdbConnected()) // 先确认节点和控制器间的OVSDB已建立
connectOvsdb();
return;
createBridge(); // 建立br-int
if (node.type().equals(NodeType.GATEWAY))
createBridge();
// 如果是网关节点则还需要建立br-router

这样OpenstackNodeManager就完成了初始化工作。对Device事件的监听会触发一些逻辑，这主要是根据交换机的状态变化进行的一些工作，如更新交换机的端口信息等。

7.4.2　openstacknetworking分支

openstacknetworking主要包括web和impl两块，web负责接收networking-onos向ONOS发出的RESTful请求，并调用impl中的相关逻辑对后端网络进行开通。在Kingfisher版本中，openstacknetworking支持的功能包括控制器代答ARP/DHCP、Security Group、分布式的L2/L3、基于控制器的NAPT，以及Floating IP的双向NAT。

Openstacknetworking使用的Pipeline如下所示，Table 0用于标记流量，Table 1用于Security Group，Table 2用于二三层流量分流，Table 3用于不同Subnet间的路由，Table 4用于Subnet内部的转发。可以看到，由于openstacknetworking目前所支持的功能仍然比较单一，因此相比于OVS_Agent和ODL Netvirt来说，Pipeline的设计要简单很多。

============= drivers.default.src.main.java.org.onosproject.driver ==============
======================= .pipeline.OpenstackPipeline ==========================
public class OpenstackPipeline
private static final int SRC_VNI_TABLE = 0;
private static final int ACL_TABLE = 1;
private static final int JUMP_TABLE = 2;
private static final int ROUTING_TABLE = 3;
private static final int FORWARDING_TABLE = 4;

OpenstackSwitchingArpHandler通过requestPacket方法在br-int上下发流表，将ARP Request上交给控制器。收到ARP Request的PacketIn后，它会通过Core层hostService提供的方法获取目标的MAC地址，据此构造ARP Reply并通过Core层packetService提供的emit方法在本地代理回复ARP，这避免了ARP Request的泛洪。如果目标IP地址为网关或者FloatingIP，则由OpenstackRoutingArpHandler使用GATEWAY_MAC常量fe：00：00：00：00：01进行回复。OpenstackSwitchingDhcpHandler的实现同理于Openstack-Swit-ching-ArpHandler。

OpenstackSecurityGroupHandler通过setSecurityGroupRule方法向OVS下发流表，实现了Neutron的安全组功能，这样就不需要启动Linux Bridge了，目前不支持带状态的安全组。

OpenstackSwitchingHostProvider会监听Core Device发布的Port事件，如果是PORT_ADDED或者PORT_UPDATED，则会将该Port对应的虚拟机实例更新到Core Host中。然后Core Host会发布OPENSTACK_INSTANCE_PORT_DETECTED或者OPENSTACK_INSTANCE_PORT_UPDATED事件，这一事件会触发OpenstackSwitchingHandler中的instPortDetected方法，该方法调用setNetworkRules方法下发与该虚拟机相关的L2流表。

OpenstackSwitchingHandler的setNetworkRules方法做了两件事情，一是通过setTunnelTagFlowRules方法向虚拟机所在的本地交换机下发流表，在虚拟机流量流入时为其标记VNI。二是通过setForwardingRules方法向该虚拟机的本地交换机和远端交换机下发流表，处理发往该虚拟机的单播流量。setForwardingRules方法的流表逻辑，是匹配流量当前的VNI与目的IP地址，在动作中会改写目的MAC地址并进行转发。不过从设计的角度来说，Swiching流表的逻辑应该是匹配目的MAC地址然后进行转发，根据目的IP映射目的MAC的工作属于Router上的逻辑，OpenstackSwitchingHandler的setForwardingRules的实现方式还是比较奇怪的。

============ apps.openstacknetworking.src.main.java.org.onosproject ============
============= .openstacknetworking.impl.OpenstackSwitchingHandler ==============
@Component(immediate = true)
public final class OpenstackSwitchingHandler
private void setNetworkRules()
setTunnelTagFlowRules(); // 对流量在入口处标记VNI
setForwardingRules(); // 下发和该虚拟机相关的转发流表
private void setForwardingRules()
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType().matchIPDst()..matchTunnelId().build();
// 匹配目的IP及当前所属的VNI
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setEthDst().setOutput().build();
// 对于本地交换机，改写目的MAC， 并从该虚拟机的端口直接转发
… // 此省略处，会遍历其他远端交换机，改写目的MAC并从对应隧道转发

OpenstackRoutingHandler是openstacknetworking中设计较为复杂的一个模块，它会监听OPENSTACK_ROUTER和OPENSTACK_ROUTER_INTERFACE两类事件。对OPENSTACK_ROUTER_CREATED和OPENSTACK_ROUTER_UPDATED事件的处理是通过routerUpdated方法完成的，其逻辑是将去往外网的流量通过Group均衡到不同的网关节点上去，网关节点会将去往外网的流量PacketIn给控制器，该PacketIn流表的优先级为25000，是所有流表类型中优先级最低的，因此不会覆盖掉其他流表的处理逻辑。

============ apps.openstacknetworking.src.main.java.org.onosproject ============
=============== .openstacknetworking.impl.OpenstackRoutingHandler ===============
@Component(immediate = true)
public class OpenstackRoutingHandler
private void routerUpdated()
if (exGateway == null) // 如果router和External Network没有连接
…
else // 如果router和External Network有连接
osRouterService.routerInterfaces(osRouter.getId()).forEach(iface -> {
setSourceNat(); // 处理router上所有interface的SNAT
}));
private void setSourceNat()
osNodeService.completeNodes().stream()
.filter(osNode -> osNode.type() == COMPUTE)
.forEach(osNode -> {
setRulesToGateway();
}); // 遍历所有计算节点，将发向External Network的
// 流量通过Group负载均衡给网关节点
osNodeService.gatewayDeviceIds()
.forEach(gwDeviceId -> setRulesToController());
// 网关节点将发向External Network的流量上交
// 控制器（优先级为25000）

对OPENSTACK_ROUTER_INTERFACE_ADDED事件的处理是通过routerIface-Updated方法完成的，该方法主要做了3件事，1）通过setInternalRoutes方法完成新增router interface和同一router上其他router interface间的路由。2）将虚拟机ping该新增interface的流量并通过Group负载均衡给网关节点，再有网关节点将该流量PacketIn给控制器。3）处理该新增interface的SNAT。其中，1）处理的是东西向流量，它是源交换机所在的本地交换机分布式地完成的，2）和3）处理的是南北向流量，它们在网关节点集中式地进行处理。

============ apps.openstacknetworking.src.main.java.org.onosproject ============
=============== .openstacknetworking.impl.OpenstackRoutingHandler ===============
@Component(immediate = true)
public class OpenstackRoutingHandler
private void routerIfaceAdded()
setInternalRoutes();
setGatewayIcmp();
if (exGateway != null && exGateway.isEnableSnat())
setSourceNat(); // 处理该新增interface的SNAT
private void setInternalRoutes()
… // 此省略处，会完成该新增interface所在的subnet
// 和同一router上的其他interface所在的subnet间
// 的路由，匹配的是源VNI、源IP、目的IP，动作是
// 改写流量当前所属的VNI
private void setGatewayIcmp()
… // 此省略处，会将虚拟机ping该新增interface的流量
// 通过Group负载均衡给网关节点，网络节点会将该流
// 量上交控制器

OpenstackFloatingIPHandler负责分配FloatingIP的虚拟机实例到Internet的流量，以及分配FloatingIP的虚拟机实例到其他虚拟机实例的流量。OpenstackFloatingIPHandler会通过setFloatingIpRules方法下发双向流表，流表的逻辑就是进行静态NAT，改写相关的MAC和IP。

OpenstackIcmpHandler通过request方法截获网关节点上的ICMP包，通过process-Icmp-Packet方法处理ICMP包，包括虚拟机ping router interface或者ping Internet两类。Process-IcmpPacket方法通过handleEchoRequest方法处理ICMP Request，通过handleEchoReply方法处理ICMP Reply。HandleEchoRequest方法，通过processRequestForGateway方法代答ping router interface的流量，通过sendRequestforExternal方法路由ping Internet的流量。handle-EchoReply方法通过processReplyFromExternal方法路由Internet返回给虚拟机的ICMP Reply。

=========== apps.openstacknetworking.src.main.java.org.onosproject =============
============ .openstacknetworking impl.OpenstackRoutingIcmpHandler ==============
@Component(immediate = true)
public class OpenstackRoutingIcmpHandler
private void processIcmpPacket()
switch (icmp.getIcmpType())
case ICMP.TYPE_ECHO_REQUEST:
handleEchoRequest();
case ICMP.TYPE_ECHO_REPLY:
handleEchoReply();
private void handleEchoRequest()
if (isForSubnetGateway()
processRequestForGateway(); // 控制器代答ping Router的流量
else
sendRequestForExternal(); // 路由ping External Network的流量
private void handleEchoReply()
processReplyFromExternal();
// 路由External Network返回给虚拟机的ICMP Reply

OpenstackRoutingSnatHandler负责处理未分配FloatingIP的虚拟机实例到Internet的流量。ProcessSnatPacket方法会得到可用的L4端口号，然后下发虚拟机实例与Internet通信的双向流表，流表的逻辑是基于端口NAT（即NAPT）的。与OpenstackFloatingIPHandler中setFloatingIpRules方法在处理上不同的是，ProcessSnatPacket方法在下发完流表后会发一个PacketOut出去，这个区别产生的原因在于FloatingIP流表的下发是Proactive的，而NAPT流表的下发是Reactive的，因此下发流表后需要把首包再扔回到数据平面中去，这个首包和其后续的数据包就会匹配NAPT流表直接进行转发了。

============ apps.openstacknetworking.src.main.java.org.onosproject ============
============ .openstacknetworking.impl.OpenstackRoutingSnatHandler =============
@Component(immediate = true)
public class OpenstackRoutingSnatHandler
private void processSnatPacket()
int patPort = getPortNum(); // 获得可用的L4端口号
populateSnatFlowRules(); // 下发双向流表实现NAPT
packetOut(); // 把首包PacketOut

7.5　CORD简介

运营商的本地网中有大量的传统电信机房，也称为本地端局，本地端局承载了宽带、专线、语音等不同业务，部署这些业务需要采购大量的专用硬件设备，如OLT/CPE/BNG、CE/PE、SGSN/GGSN等，因此本地端局的构建会形成大量的成本，而其灵活性和可编程性却都十分受限。2015年初，ATT在ONOS中成立了一个use case名为CORD（Central Office Re-Architected as Data-Center），CORD旨在构建电信级的边缘数据中心，对本地端局进行云化重构，以降低成本同时增强其开放性。CORD的技术思路，就是通过云提供虚拟化能力，通过NFV控制虚拟化网元、编排虚拟化服务，再通过SDN为这些虚拟化网元进行灵活的、开放的连接。在强力需求的驱动下，CORD很快完成了PoC，并取得了不错的效果。为了加速CORD的落地与推广，CORD于2016年3月从ONOS中独立出来，形成了一个新的开源组织Open Cord，包括ATT、Verizon、中国联通、韩国SK电讯、日本NTT在内的多家知名大型运营商纷纷加入Open Cord。2016年7月，Google也宣布加入Open Cord，在业内引起了不小的轰动。

Open Cord目前提出了3个主要的应用场景，一是面向光宽带的R-CORD（Residential CORD），二是面向企业的E-CORD（Entreprise CORD），三是面向5G的M-CORD（Mobility CORD）。根据社区的未来计划，还会尝试将上述三者综合为Multi-Access CORD，以支持多业务混合接入的云化端局。R-CORD的原型即为ONOS中的CORD use case，下面将对R-CORD的设计进行简单介绍。

7.5.1　R-CORD的架构

R-CORD主要提供光宽带用户的接入功能，它实现了OLT/CPE/BNG的虚拟化与控制逻辑，R-CORD的设计如图7-8所示。用户的光信号由物理OLT完成终结，而对用户的认证（802.1x+Radius），以及为用户分配VLAN的功能则由vOLT模拟完成。vOLT完成处理后，用户流量用ONU封好C-VLAN并送至OLT，OLT会对用户流量标记S-VLAN，根据S-VLAN，流量转发给vSG。vSG完成的是传统CPE的部分功能，包括DHCP和NAT等，vSG会剥掉QinQ，并在完成NAT后将发往Internet的流量送到上联骨干网的物理交换机上。这个物理交换机与Metro-Router相连，vRouter会与Metro-Router交互动态路由协议，然后下发流表控制发往Internet流量的转发。传统端局中三层结构的物理网络，在R-CORD中会演进为扁平化的Leaf-Spine Fabric。

图7-8　R-CORD示意图

R-CORD的软硬件架构如图7-9所示。软件方面，XOS对虚拟化网元提供服务编排，ONOS提供网络转发层面的控制。XOS可以看作是ESTI的NFV架构中的NFVO，通过不同的Service Profile（服务配置文件）可以实现不同应用场景的服务编排，比如在R-CORD中就是vOLT-vSG-vRouter。ONOS是SDN控制器，负责实现与网络转发相关的控制逻辑。硬件方面，虚拟化网元由x86服务器承载，而底层的Leaf-Spine Fabric则使用了白盒交换机（由ONOS控制）进行部署，这在业界，尤其是在运营商的SDN圈中，是十分罕见并具有技术突破性的做法。

图7-9　R-CORD的软硬件架构

7.5.2　R-CORD的控制与转发机制

在ONOS中与R-CORD相关的App包括vOLT、Cord VTN、SR Fabric、Multicast和vRouter 5个，这5个App组合在一起称为Trellis。其中，各个App的功能如下：

1）vOLT负责接收用户发出来的802.1x报文并向Radius进行认证，认证成功后为用户分配VLAN。

2）Cord VTN负责对OVS Pipeline进行控制，完成Overlay的转发、节点间的转发，通过VxLAN进行传输。

3）SR Fabric负责对白盒交换机进行控制，完成Underlay的传输，传输的过程借鉴了Segment Routing的思路。

4）vRouter负责同步，Quagga从Metro Router处学习到的Internet单播路由，并通过SR Fabric和Cord VTN控制单播的转发。

5）Multicast负责监听接入侧的IGMP，并和Metro Router交互PIM-SIM，学习组播的路由并通过SR Fabric控制组播流的转发，它主要面向的是IPTV业务。

下面主要来说SR Fabric。Segment Routing在本书的第1章介绍过，它采用的是源路由的思路，通过在入口设备上封装路径的信息，指导后续的设备按照该路径进行转发。Segment Routing中最重要的两个概念是Node SID和Adj SID，其中Node SID标识设备，Adj SID标识链路。SR Fabric采用MPLS作为叶节点SID，源OVS封装VxLAN出来以后，源口会通过MPLS Label标记目的Leaf，然后通过Group负载均衡给Spine，然后Spine直接根据MPLS Label转发给目的Leaf，并剥掉MPLS Label，目的Leaf再转发给目的OVS。具体过程如图7-10所示。

图7-10　Trellis SR Fabric中数据平面上的转发

如果SR Fabric只推一层MPLS Label进去，那么实际上MPLS Label和IP/32是等价的，如果能把Spine的Node SID也推进去，就可以实现简单的流量工程了。CORD未来计划通过sFlow探测大象流，并反馈给ONOS进行动态的路径调度。

与在OVS上可以自定义Pipeline不同的是，白盒交换机上的Pipeline是受限于芯片实现的。R-CORD中的白盒交换机大多数使用的是Broadcom Trident系列的芯片，需要按照OFDPA提供的API对Pipeline进行操作。OFDPA的3.0版本，R-CORD也在积极地进行跟进。图7-11所示为OFDPA Pipeline的一个简单示意图，各个表的作用在名字上都体现得很清楚。

图7-11　OFDPA Pipeline的简单示意

下面针对R-CORD中不同的流量类型来介绍Pipeline相应的处理逻辑，括号中是Group的类型，Indirect是指定多个中的一个，Select是通过哈希选择多个中的一个，All是选择全部。

1）ARP：Phy Port→Ingress Port Table→VLAN Table→Termination MAC Table→ACL Policy Table→Controller。

2）L2 Unicast：Phy Port→Ingress Port Table→VLAN Table→Termination MAC Table→Bridging Table→ACL Policy Table→L2 Interface Group（Indirect）→Phy Port。

3）L2 Broadcast：Phy Port→Ingress Port Table→VLAN Table→Termination MAC Table→Bridging Table→ACL Policy Table→L2 Flood Group（All）→L2 Interface Group（Indirect）→Phy Port。

4）L3 Unicast（源Leaf）：Phy Port→Ingress Port Table→VLAN Table→Termination MAC Table→Unicast Routing Table→L3 ECMP Group Table（Select）→MPLS Label Group（Indirect）→L2 Interface Group（Indirect）→Phy Port。

5）L3 Unicast（Spine）：Phy Port→Ingress Port Table→VLAN Table→Termination MAC Table→MPLS Table→L3 ECMP Group Table（Select）→L3 Unicast Group（Indirect）→L2 Interface Group（Indirect）→Phy Port。

6）L3 Unicast（目的Leaf）：Phy Port→Ingress Port Table→VLAN Table→Termination MAC Table→Unicast Routing Table→L3 Unicast Group Table（Indirect→L2 Interface Group（Indirect）→Phy Port。

7）L3 Multicast：Phy Port→Ingress Port Table→VLAN Table→Termination MAC Table→Multicast Routing Table→L3 Multicast Group Table（All）→L2 Interface Group（Indirect）→Phy Port。

虽然CORD和本书所关注的商用云数据中心有较大的差别，但是无论从其设计架构，还是实现上来看，都有很多有意思的地方。之所以上面只讲解了SR Fabric，而没有讲解Trellis中其他的App，是希望读者能够了解到SDN并不是只能在DC Overlay中应用，DC Underlay同样有很多值得SDN去挖掘的地方。OpenCord的官网上还给出了SR Fabric的Roadmap，包括Leaf和Quagga的HA、IPv6、VxLAN Gateway、VxLAN Routing、组播LSP、In-Band控制等，有兴趣的读者可深入地探索与研究。

7.6　本章小结

ONOS最开始的定位是面向运营商大网的控制器，因此其项目多关注于运营商的业务，对于数据中心这块目前仍多用于功能性的Demo。本章介绍了ONOS本身的架构，以及社区中与数据中心业务相关的项目设计与实现，另外在最后一节中还简单地介绍了与CORD相关的内容。

第8章　学术界相关研究

自从2016年SDWAN的概念兴起之后，SDDCN的话题热度就迅速冷却下来了，很多人觉得未来几年SDDCN基本也就是现在这个样子了，未来或生或灭等待市场来裁决就好了。实际上抛开已经明确的市场需求，单从网络的设计角度来看，目前SDDCN在功能上仍有很大的发展空间。本章会介绍学术界对数据中心网络的主流构想，会有与SDN相关的，也有非SDN的，希望能够与读者一同来拓展技术视野。

8.1　拓扑

8.1.1　FatTree

UCSD大学的一些学者在2008年的Sigcomm上发表了论文[1]，论文基于FatTree拓扑对数据中心网络进行了重构，使用了大量低端的商用交换机（COTS，Commodity Off The Shelf）来代替传统网络中少数昂贵的核心交换机进行组网。FatTree的组网结构示意如图8-1所示，图中拓扑分为Edge、Aggregation和Core 3层，3个层次上的交换机均采用完全相同的COTS交换机（如48×1GE），为了利用这些COTS交换机来实现数据的无阻塞交换，该论文对于3个层次上设备的数量和连接规则都提出了一定的约束，另外提出了相应的路由算法。下面只讨论FatTree在拓扑层面的设计，具体的路由算法见本章8.2.2节。

Edge和Aggregation层组成了服务器接入的网络基本单元（称为POD），Core组成了网络交换的核心，将各个POD连接起来。FatTree组网的设计原则如下：整个网络包括K个POD，每个POD都由K/2个Edge交换机和K/2个Aggregation交换机组成。每个Edge和Aggregation交换机都有K个端口，其中Edge层交换机使用K/2个端口下联服务器，Aggregation层交换机使用K/2个端口上联Core层交换机，Edge和Aggregation交换机中剩余的K/2个端口用于POD中Edge和Aggregation层间的全互联。因此，有K个端口、K个POD的FatTree网络可容纳K×（K/2）×（K/2）共计K^3/4台服务器（K个POD，每个POD中有K/2个Edge交换机，每个Edge交换机通过K/2个端口连接服务器）。

图8-1　FatTree的拓扑结构

Core层由（K/2）×（K/2）共计K^2/4个K个端口交换机组成。这些Core交换机分为K/2组，每组有K/2个。第一组的K/2台Core交换机用于连接各个POD中Aggregation层的一号交换机（从左向右依次排序），第二组的K/2台Core交换机用于连接各个POD中Aggregation层的二号交换机，依此类推，第K/2组的K/2台Core交换机用于连接各个POD中Aggregation层的K/2号交换机。每一组中的K/2台Core交换机间互为冗余备份，K/2组Core交换机可以为任意两个服务器间的通信提供（K/2）×（K/2）共计K^2/4条冗余路径。

可以看到，由于Edge层和Aggregation层的交换机数量是一样的，每台Edge交换机下联的端口数和每台Aggregation交换机上联的端口数也是一样的，而且Edge和Aggregation层交换机端口的带宽也是一样的，因此该FatTree网络的收敛比是1：1。再配合论文中配套提出的FatTree路由算法，Aggregation层的交换机可以在与之相连的K/2个Core层交换机间进行负载均衡，因此在理论上可以实现无阻塞的数据中心网络。

该论文提出的FatTree拓扑，在物理结构上与图论中给出的标准FatTree不同，不过由于设计了1：1的收敛比，因此在逻辑上可以归类为FatTree。实际上，图8-1是一个Multi-root的树状网络，从每一个Core层交换机向下看，都是一个3层的完全二叉树。或者把每个POD看作是一个虚拟的交换机，那么该论文提出的FatTree拓扑也可以看作是3-Stage Folded CLOS。

这种FatTree拓扑最大的好处就是可以使数据中心网络摆脱掉高端口密度的、昂贵的核心交换机。这样一来容错率提高了，一台Core交换机出现故障了只损失掉了1/（K^2/4）的带宽，而在两台核心交换机组成的虚拟机框中，一个核心交换机出现故障了就会损失掉一半的带宽。另外，使用了大量低端的COTS交换机来代替传统网络中少数昂贵的核心交换机进行组网，可以摆脱交换机厂商的锁定并降低网络的整体成本。图8-2所示为论文中对传统网络和FatTree的成本对比图。

图8-2　传统网络和FatTree的成本对比

FatTree的设计看起来是Scale-out的，貌似多增加POD和Core层交换机就可以无限地扩展服务器的接入数量。但实际上由于拓扑本身的特殊性，FatTree能够接入的服务器数量受限于交换机的端口密度，目前低端COTS交换机的端口密度通常只能做到48个，因此FatTree能够接入的服务器数量一般只能做到48^3/4=27648，想扩展的话只能增加交换机的端口密度，这样通常会导致成本的非线性增加。而且FatTree中交换机的数量相比于传统网络增加了很多，Core层交换机的数量将随着K平方级别增长，随之带来的设备管理成本和布线成本是不可忽视的。

这篇论文在后续几年中，引发了学术界对于数据中心内部网络拓扑设计的广泛而深刻的讨论。

[1] Al-Fares M,Loukissas A,Vahdat A.A scalable,commodity data center network architecture[C]//ACM,2008:63-74.

8.1.2　VL2

2009年的Sigcomm上，微软的研究人员提出了一种名为VL2的网络架构[1]。VL2采用的网络拓扑是典型的CLOS结构，如图8-3所示。VL2中的网络可以分为ToR、Aggregate、Intermediate 3层。ToR交换机通过2个端口上联到不同的Aggregate交换机，其余的端口连接服务器。Aggregate层交换机有DA个端口，其中DA/2个端口连接DA/2个ToR交换机，剩余的DA/2个端口上联DA/2个Intermediate交换机。如果VL2网络中有DI台Aggregate交换机，则对应有（DA/2/2）×DI共计DA×DI/4台ToR交换机。若ToR上联端口为10G，服务器使用1GE接入，则为了保证1∶1的收敛比，则VL2网络最多可以容纳（DA×DI/4）×20台服务器。

VL2和FatTree的设计理念有所不同，它并没有要求全网都采用相同型号的低端交换机，因此VL2中对于拓扑的约束相比于FatTree要宽松一些。VL2对Aggregate交换机的数量DI并没有进行明确的要求，只要不超过Intermediate交换机的端口数就可以了。对于Intermediate交换机的端口数VL2也没有明确要求，Intermediate交换机上还可以有一些高带宽的端口，用来做Internet接入，或者继续上联更高层次的交换机（如Super层），用以形成5-Stage CLOS来支持更大规模的数据中心网络。VL2相比于FatTree在演进上也更为平滑一些，路由方面采用了VLB+OSPF+ECMP的组合，对传统设备有很好的兼容性（见本章8.2.3节）。

图8-3　VL2的拓扑结构

实际上，当前业界流行的Leaf-Spine和VL2的拓扑结构是基本一致的。Leaf相当于VL2中的Aggregate，而Spine则相当于VL2中的Intermediate，Hypervisor中的vSwitch（或者ToR）则相当于VL2中的ToR。在实际生产环境中，Leaf-Spine网络一般不需要设计成1：1的收敛比，考虑到40GE/100GE的普及，Leaf-Spine能够轻松地支持几千台10GE服务器，如果考虑增加Super层来作为5-Stage Folded CLOS，几万台甚至上百万台10GE服务器的规模也是可以支撑的。

[1] Greenberg A,Hamilton J R,Jain N,et al.VL2:a scalable and fl exible data center network[C]//ACM SIGCOMM 2009 Conference on Data Communication.ACM,2009:51-62.

8.1.3　DCell

DCell是微软亚研院在2008年Sigcomm上提出的一种数据中心网络结构[1]。与FatTree、VL2不同的是，DCell是一种以服务器为中心的组网结构，服务器上需要部署多块网卡用于服务器间的直连，服务器还上拥有路由转发的逻辑用于中转来源于其他服务器的数据包。

DCell的基本单元是DCell_0，DCell_0由一台T个端口的mini交换机用于连接T个服务器。DCell_0内部服务器的互联由mini交换机完成，而在DCell_1中不同DCell_0间的互联则需要借助服务器间的直连链路完成。DCell_1的典型结构如图8-4所示，图中DCell_0由4个服务器和1个4端口的mini交换机组成（T=4），mini交换机只负责DCell_0内部的4个服务器间转发的数据包。DCell_1是由5个DCell_0组成的，5个DCell_0间的互联没有交换机参与，在每个DCell_0中4个服务器分别使用一个额外的端口，依次与其他DCell_0中的服务器直连，从而在5个DCell_0间形成全互联的结构。DCell_1中的服务器可以用（m，n）坐标来表示，其中m标识服务器位于第m个DCell_0，n标识服务器在第m个DCell_0中的序号。同一个DCell_0中的服务器进行通信时（m相同），如（4，0）和（4，1），由该DCell_0中的mini交换机中继。不同DCell_0中的服务器进行通信时（m不同），DCell需要某些服务器对数据包进行中转。如（4，0）和（1，3）通信时，可先通过源服务器（4，0）所在的DCell_0中的mini交换机中继到（4，1），再由（4，1）中继到（1，3）。

图8-4　DCell1的典型拓扑结构

对于DCellk来说，其包括的DCellk-1的数量为DCellk-1中总共所包含的服务器数量+1，DCellk-1中的每一个服务器都通过一块额外的网卡，依次与其他DCellk-1中的服务器直连，从而在DCellk-1间形成全互联的结构。从递推的公式来看，设DCellk中包括DCellk-1的数量为Gk，DCellk中服务器的数量为Tk个，则有：

上述递推公式的约束条件为k≥1，初始条件为T_0=DCell_0中服务器的数量。当DCell_0中有4个服务器时，DCell_1由5个DCell_0组成（G_1=5），DCell_1共有服务器20个（T_1=20）。DCell_2由21个DCell_1组成（G_2=21）时，DCell_2共有服务器420个（T_2=420）。DCell_3由421个DCell_2组成（G_3=421），DCell_3共有服务器176820个（T_3=176820）。可见DCell拓扑可以容纳的服务器数量是按照指数级上升的，当T_0=6时，DCell_3可以容纳的服务器数量可达到3260000个。

由于DCellk中不同的DCellk-1间采用了全连接的结构，因此DCell拓扑中服务器间两两通信的可用路径的数量也很多。DCell这种全连接的对称结构，使得它能以不算很大的连接度（DCell_3中每个服务器需要4块网卡）获得很高的冗余度，当然这需要服务器对数据包进行中转。但是正是因为如此，也导致跨DCell的某些服务器间的通信转发路径可能会很长，不适合用于传输某些时延敏感的流量。另外，某些服务器间的直连需要使用长距离的链路，这也会带来很高的链路管理成本。

虽然在DCell中每个服务器需要的网卡数量并不多，但是考虑到服务器数量指数级增长，DCell中的总网卡数量也是相当惊人的。相比于FatTree，虽然DCell可以大幅降低交换机的采购成本，但是额外的网卡所带来的开销同样也是不可忽视的。而且，高层次DCell间的通信会占用较低层次DCell中的网卡带宽，如在之前的例子中，DCell_1网络中（4，0）和（1，3）的通信需要等量地占用（4，1）网卡的带宽，导致在较低层次的DCell单元中常常会出现链路拥塞。也就是说虽然花费了大量成本来采购网卡，但是却无法获得其全部的带宽能力，因此DCell实际上是一种有阻塞的网络结构，这是DCell拓扑（以及其他以服务器为中心进行转发的拓扑，如后面的FiConn和BCube，等等）最大的一个缺点。

[1] Guo C,Wu H,Tan K,et al.Dcell:a scalable and fault-tolerant network structure for data centers[C]//ACM,2008:75-86.

8.1.4　FiConn

顺着DCell的思路，微软亚研院在2009年的Infocomm上又提出了FiConn[1]。DCellk中各个服务器都要求有k+1块网卡，这不仅增加了网卡的成本，而且服务器间繁多的链路也给布线带来了极大的困难。FiConn可以看作是低连接度的DCell，无论k取多少，FiConnk中的每个服务器都限用两块网卡，为了在连接度较小时能够保持住DCell中全连接的特性，FiConn对FiConnk中所包含FiConnk-1的数量进行了约束。

FiConn的基本单元是FiConn_0，FiConn_0由一台T个端口的mini交换机连接T个服务器。FiConn_0内部服务器使用主网卡与mini交换机连接，而FiConn_1中不同FiConn_0间的互联则需要借助服务器中另外一块备用网卡来完成。图8-5所示为T=4时FiConn_2的示意图，从中可以看到，在由FiConn_0构建FiConn_1时，并没有将FiConn_0中4个服务器的4块备用网卡都用光，而是只用了2块备用网卡连接2个其他的FiConn_0。这使得由3个FiConn_0所组成的FiConn_1拥有了2×3=6块备用网卡，用来构建FiConn_2。当然，在由FiConn_1构建FiConn_2时，也没有将FiConn_1中的6块备用网卡都用光，而是只用了3块备用网卡连接3个其他的FiConn_1。这使得由4个FiConn_1所组成的FiConn_2拥有了（6-3）×4=12块备用网卡，用来构建FiConn_3。

图8-5　FiConn2的拓扑结构（T=4）

从上述规律中可以归纳出FiConn使用备用网卡的约束条件：如果在FiConnk-1中仍有Bk-1个备用端口，那么FiConnk就由Bk-1/2+1个FiConnk-1构成。这样一来，每个FiConnk-1都使用了Bk-1/2个备用端口与其他FiConnk-1进行全互联，FiConnk中就剩下了（Bk-1-Bk-1/2）×（Bk-1/2+1）个备用端口，用于构建FiConnk+1。从递推的公式来看，设FiConnk中包含的FiConnk-1数量为Gk，FiConnk中可以容纳的服务器数量为Tk，FiConnk中剩余可用的备用网卡数量为Bk，则有：

上述递推公式的约束条件为k≥1，初始条件为B_0=T_0=FiConn_0中服务器的数量。当FiConn_0中有4个服务器时，FiConn_1由3个FiConn_0组成（G_1=3）时，FiConn_1共有服务器12个（T_1=12），可用的备用网卡有6个（B_1=6）。FiConn_2由4个FiConn_1组成（G_2=4）时，FiConn_2共有服务器48个（T_2=48），可用的备用网卡有12个（B_2=12）。FiConn_3由7个FiConn_2组成（G_3=7）时，FiConn_3共有服务器336个（T_3=336），可用的备用网卡有42个（B_3=42）。可见当初始值T_0相同时，FiConnk中可以容纳的服务器数量要比DCellk少得多。当T_0=16时，FiConn_3可以容纳的服务器数量为3553776，大体相当于T_0=6时DCell_3可以容纳的服务器数量（3260000）。

T_0变大对于网络成本的影响在于，要多使用多端口的mini交换机，这相比于DCell需要使用更多的服务器网卡，到底哪一个的成本更低论文中没有给出明确的分析。不过，FiConn相比于DCell倒是有几点明显的好处：在容纳相同数量的服务器时，FiConn的网络直径相比于DCell缩小了将近一半（此处不对此结论进行证明），这可以更好地传输时延敏感的流量；由于多端口mini交换机的使用，DCell中多数的长链路变为了FiConn_0服务器到mini交换机的短链路，节约了链路的管理成本；服务器节点上连接度的变小，降低了布线的难度。

[1] Li D,Guo C,Wu H,et al.FiConn:Using Backup Port for Server Interconnection in Data Centers[C]//INFOCOM.IEEE,2009:2276-2285.

8.1.5　BCube

在2009年8月份的Sigcomm上，微软亚研院紧接着又发表了BCube[1]，用于解决DCell中如下几个问题：①DCell中服务器的指数级增长适合于超大规模数据中心（mega datacenter）的组网，并不适用于规模相对来说较小的集装箱数据中心（container datacenter）。②在DCell较低层次的单元中链路经常会发生拥塞。③DCell可以为服务器间的通信提供很多条路径，但是这些路径在路径长度上有着较大的差异，这可能会影响多路径转发的实际效率。

BCube的基本单元是BCube_0，BCube_0和DCell_0的组成是完全一样的，也是由一台T个端口的mini交换机连接T个服务器。BCube_0内部服务器的互联由mini交换机完成，而在BCube_1中不同的BCube_0间也同样需要借助mini交换机完成互联。BCube_1的典型结构如图8-6所示，图中level 0的方框内即为BCube_0，每个BCube_0中的4个服务器通过4个端口的mini交换机进行连接，这些mini交换机定义为level 0交换机。BCube_1由4个BCube_0互联组成，BCube_0间的通信由额外的4个4端口的mini交换机来协助转发，这些交换机定义为level 1交换机。各组BCube_0中的服务器由（m，n）来表示，其中m标识服务器所属的BCube_0，n标识服务器在第m个BCube_0中的序号。将BCube拓扑中的mini交换机由<M，N>来表示，M标识mini交换机所在的level，N标识mini交换机在level M中的序号。BCube_1使用mini交换机<1，N>来连接各个BCube_0中序号n=N的服务器。

BCube是一种递归结构。对于BCubek来说，它包括T个BCubek-1，以及Tk-1个level k的T端口mini交换机，其中T为BCube_0中服务器的个数。BCubek-1中的每一个服务器都通过一块额外的网卡与level k的mini交换机相连，从而实现BCubek-1间的互联。从递推公式来看，设BCubek中包括的BCubek-1的数量为Gk，包括level k的mini交换机的个数为Lk，BCubek中服务器的数量为Tk个，则有：

图8-6　BCube的递归结构（左）与BCube1的结构（n=4）

上述递推公式的约束条件为k≥0。当BCube_0中有4个服务器时（T=4），Bcube_1由4个BCube_0组成（G_1=4），DCell_1共有服务器16个（T_1=16）。BCube_2由4个BCube_1组成（G_2=4）时，BCube_2共有服务器64个（T_2=64）。BCube_3由4个BCube_2组成（G_3=4）时，BCube_3共有服务器256个。可见BCube拓扑中服务器数量是按照幂次上升的。当T=6时，BCube_3可容纳服务器1296个，相比于T_0=6时DCell_3可以容纳百万级别的服务器而言，它有着数量级的降低。

BCube的设计实际上是在FatTree和DCell间进行了折中。

1）BCube在k层和k-1层的连接，在结构上可以看作是FatTree中Aggregation层和Core层连接的简化版——FatTree的各个POD中同一组序号的Aggregation层交换机通过多个Core层交换机进行冗余备份，而各个BCubek-1中同一序号的服务器只通过一个level k的mini交换机进行互联。相比于FatTree，BCube节约了交换机间的级联链路，而且减少了交换机的数量，代价是增加了服务器端的网卡数，而且同DCell一样无法实现无阻塞交换。

2）BCube仍然沿用了DCell中以服务器为中心进行转发的思路。相比于DCell，BCube节约了不同DCellk服务器间互联所需要的长链路，改为增加了BCubek服务器到level k mini交换机间的短链路，这可以降低布线的复杂度。同时level k mini交换机与服务器的直连，可以有效地缓解DCell拓扑中低层次单元的链路拥塞，提高网络转发的效率。另外，level k mini交换机也可以有效地减小转发路径的长度，有利于降低传输的时延。但是，BCube相比于DCell付出的代价是增加了交换机的数量，而且在网络连接度相同的情况下，可容纳的服务器数量也大大减少了。

[1] Guo C,Lu G,Li D,et al.BCube:a high performance,server-centric network architecture for modular data centers.[J].Acm Sigcomm Computer Communication Review,2009,39(4):63-74.

8.1.6　MDCube

如上面所述，BCube看作是一种小型的集装箱数据中心（container datacenter）。为了方便部署，Bcube的网络半径和可以容纳的服务器数量都控制在了一个较小的范围之内。在2009年12月，微软亚研院又在CoNEXT上提出了MDCube[1]，用于互联多个BCube的container，以满足超大规模数据中心（mega datacenter）网络对于服务器数量的要求。

BCube_1中的连接都发生在服务器和交换机之间，交换机和交换机间并没有连接，而MDCube的思路就是将多个container的交换机连接起来。由于container的互联网络对于带宽有着很高的要求，因此MDCube提出在container间使用带有少数高速率（10GE/40GE/100GE）光端口的mini交换机，container内的通信仍然使用mini交换机中的低速率电端口，而container间的通信则使用mini交换机中的高速率光端口。

MDCube的拓扑设计规则如下：将各个BCube container看作是不同的虚拟节点，将container中mini交换机的高速率端口视为该虚拟节点的虚拟端口，如4个10Gbps的光端口绑定为一个40Gbps的虚拟光端口，然后通过虚拟端口来实现虚拟节点的全连接。假设需连接的container数目为N，全连接要求每两个container之间均存在一条链路，因此每一个container至少需要N-1个虚拟端口。当MDCube中需要连接的container数目较多时，可以通过增加全连接拓扑的维数实现扩展，图8-7和图8-8分别示意了一维和二维MDCube拓扑的连接。在一维的MDCube拓扑中，连接了5个BCube1 container，其中BCube中mini-switch的低速率端口数量为T=2，在二维的MDCube拓扑中，连接了9个BCube_1 container。

图8-7　一维的MDCube

理论上MDCube拓扑可以以任意多的维度进行container间的互连，一般情况下二维MDCube即可满足超大规模数据中心对于扩展性的要求。对于一维的MDCube，可实现T=48，k=1的BCube_1 container互连，各个container内服务器的数量为Tk+1=2304，mini交换机的数量为T×（k+1）=96个。因此一维的MDCube可以连接T×（k+1）+1=97个BCube_1 container，可容纳97×2304约220000个服务器，而二维的MDCube则可连接[T×（k+1）/2+1]2=49^2=2401个container，可支持5500000个服务器。因此MDCube可以实现与DCell同数量级的服务器容量。

如果把每个container看作是一个节点，那么MDCube实际上就是标准的GHC（Generalized HyperCube）结构，这种结构实际上很早就被应用在并行计算的网络中了。在构建同等量级的超大规模数据中心时，MDCube的结构相比于DCell来说简单得多，服务器节点的连接度较小，可以大大地降低布线的复杂度。MDCube利用了BCube中原有mini交换机中的高速端口，并不需要增加交换机的数量，相比于FatTree可以节约交换机的数量。MDCube的问题在于BCube container的内部仍然有阻塞，从这一点上来说MDCube是不如FatTree的，而DCell的阻塞问题则更加严重。

图8-8　二维的MDCube

[1] Wu H,Lu G,Li D,et al.MDCube:a high performance network structure for modular data center interconnection[C]//Interna tional Conference on Emerging NETWORKING Experiments and Technologies.ACM,2009:25-36.

8.1.7　CamCube

CamCube[1]是微软研究院在2013年的HPDC上提出的。CamCube借鉴了HPC（High Performance Computing，高性能计算）网络中的设计思路，采用了3D-Torus的拓扑，这是一种不需要任何交换机，完全通过服务器实现的网络互联结构。

图8-9　CamCube采取的3D-Torus拓扑

3D-Torus拓扑如图8-9所示（图中为3×3），服务器摆放在立方体各个整数的坐标点上（当然只是在逻辑上，在物理距离上并没有严格的要求），每个服务器都有6块网卡，分别连接6个相邻服务器（每一个维度上有两个邻居，3个维度上就有6个）。位于立方体表面或者立方体顶点的服务器，将在三个维度上把和自己形成对称的服务器看作是邻居并进行连接。或者简单地说，就是通过在每个维度上增加一条连接，把线性结构变成了环形结构，从而补齐了原有线性结构中端点缺失的连接度。上面的表述实际上不是很贴切，倒是直接看图更直观一些。

3D-Torus能容纳的服务器数量为N^3，如果希望容纳百万级别的服务器则需要构建一个100×100×100的服务器阵列。对于1个100×100×100的3D-Torus，两个服务器间的路径最大为150跳左右（对角的顶点到立方体中心）。

CamCube的样子看起来和三维的MDCube很像，不过要注意的是它是一种nearest neighbor的mesh结构，每个服务器的连接度都恒定是6，而MDCube在各个维度上都是一种full-mesh结构，每个container的连接度和container内部的结构有关系。

3D-Torus的拓扑广泛地应用于HPC集群的互联，因为在HPC集群中通常每个服务器上面都运行着相同的服务，因此可以最有效地利用其3D-Torus的最近邻mesh特性。虽然CamCube的作者提出了可将3D-Torus拓扑应用到非HPC的商用数据中心中，并提出了一些配套的路由机制。不过，目前来看3D-Torus还只适用于超算场景，没有像FatTree一样在商用数据中心中得到普及。

上面这些论文讨论的是比较经典的数据中心网络拓扑，还有很多很有意思的数据中心拓扑设计，如对FatTree中的连接进行简单变换从而获得更好的容错性[2]，通过对无标度网络的生成算法进行一定的连接度约束用于构建数据中心网络拓扑[3]，通过在CamCube拓扑的基础上增加一些随机的连接构建小世界网络[4]，通过在ToR间构建有限连接度的随机图（Degree-Bounded Random Graph）来构成随机网络结构[5]，等等。限于篇幅这里就不展开介绍了，有兴趣的读者可以自己到网上去搜索。

[1] Costa P,Donnelly A,O'Shea G,et al.CamCubeOS:a key-based network stack for 3D torus cluster topologies[C]//International Symposium on High-Performance Parallel and Distributed Computing.2013:73-84.

[2] Liu V,Halperin D,Krishnamurthy A,et al.F10:a fault-tolerant engineered network[C]//Usenix Conference on Networked Systems Design and Implementation.2013:399-412.

[3] Gyarmati L,Trinh T A.Scafi da:a scale-free network inspired data center architecture[J].Acm Sigcomm Computer Communication Review,2010,40(5):4-12.

[4] Shin J Y,Wong B.Small-world datacenters[C]//ACM Symposium on Cloud Computing.ACM,2011:1-13.

[5] Godfrey P B.Jellyfi sh:Networking Data Centers Randomly[J].2012:17-17.

8.2　路由

8.2.1　Seattle

在传统以太网中，为了解决MAC自学习、VLAN和STP所导致的扩展性差的问题，同时保留其平化、即插即用等良好特性，论文[1]中Seattle对以太网的路由架构进行了重新设计。在传统网络中，由于MAC的转发强烈地依赖于ARP广播包的泛洪，而泛洪会导致带宽浪费，环路中的信令风暴会严重限制以太网的规模，泛洪还会导致MAC地址分散到所有的交换机上，过度消耗了交换机上的转发资源。Seattle的思路是加强二层的控制平面，通过分布式存储技术DHT（Distributed Hash Table）来维护网络中的MAC转发表，从而规避掉ARP在网络中的泛洪。

DHT广泛地应用在应用层的P2P网络中。比如使用迅雷进行下载时，会以目标文件的关键信息作为种子，然后解析出目标文件存储位置的信息（可能是通过一台中央种子服务器），从而找到peer进行下载。Seattle利用了DHT思想的，将MAC地址看作是种子，将MAC地址所接入的交换机看作是peer。为了实现高可用，Seattle并没有设计中央式的服务器，而是由网络中不同的交换机来维护不同的MAC地址和接入交换机的映射关系。上述表述比较抽象，下面通过一个具体的例子来介绍Seattle的路由机制。

图8-10中有两台主机，其中a连接在交换机Sa上，b连接在交换机Sb上，以b向a发起通信为例。首先，Sa需要通过某种机制发现a是连接在自己本地端口上的，这可以通过监听a发送的免费ARP或者DHCP消息来完成。当Sa发现了a之后，Sa所做的不仅仅是学习a的MAC地址MACa与入端口的映射关系，还会通过哈希算法F计算出F（MACa）=ra，其中ra是网络中的一台交换机（一般来说Sa和ra不是同一台交换机，这和F的选择有关）。然后，Sa会把（MACa，Sa）的映射关系单播给ra，等到b想要和a通信时，Sb会使用相同的算法来哈希MACa，从而得知和MACa相关的信息一定是存放在了ra上，于是Sb发往a的数据包通过隧道先传给ra，然后再由ra重新封装到隧道发送到Sa上（之所以使用隧道，是因为其他交换机很可能都不知道MACa的信息），Sa通过本地的（MACa，inport）映射关系最终转发给a。可以看到在整个过程里面，网络中是没有泛洪存在的。

图8-10　Seattle的路由机制示意图

注意，图8-10中的拓扑只是一个示意，Seattle对底层网络的拓扑没有任何要求，这意味着Seattle不仅可以应用于拓扑高度结构化的数据中心网络，而且也可以用于运营商的网络。

当然上一段的描述粒度太粗，其中有很多问题还需要进行进一步的解释。

（1）Sa发给ra的单播是怎么处理的

Seattle需要使用link-state协议来发现交换机的拓扑，交换机通过最短路径（Shortest Path Forwarding，SPF）算法，在本地计算出到其他交换机的单播路由。这和传统的三层路由协议OSPF、ISIS是一样的，不过Seattle只算到交换机switch-id的路由，不会计算主机MAC地址的路由。就好比OSPF只算到router-id的路由，而不去计算到子网IP的路由，这可以大大减少link-state协议所带来的各种开销，如链路带宽、CPU、RIB、FIB等。

（2）DHCP是怎么处理的

DHCP服务器也是网络中的一台主机，其位置也是通过某种哈希算法得到的，比如D（“DHCP_SERVER”）=tdchp，Sa收到a发送的DHCP后计算出tdhcp，然后通过DHCP Relay将DHCP DISCOVER的广播包转化成单播发送给tdhcp。对于DNS来说也是同理。

（3）b发送的ARP是怎么处理的

为了能够在不泛洪的条件下获得（IPa，MACa）的映射关系，实际上还要求Sa在发现a之前做另外一个工作，就是通过哈希算法G（MACa）找到一个交换机pa，将（IPa，MACa）的映射关系单播给pa进行存储。如果G=F，那么ra和pa就是同一台交换机，如果G≠F，那么ra和pa很可能是不同的交换机。因此，Sb在截获b请求IPa的ARP消息后，会通过G（MACa）定位到pa，然后发送单播给pa，解析IPa对应的MACa，然后Sb向b回复ARP并在本地缓存（IPa，MACa）。当然，Sb针对MACb进行同样的哈希处理，找到对应的交换机存下（MACb，Sb）以及（IPb，MACb）的映射关系。

（4）Sb发给Sa的流量一定要经过ra吗

如果Sb发给Sa的流量都经过ra转发，这意味着c、d、e、f等到a的流量也要经过ra，而且哈希的结果rc和ra有可能是同一个交换机，这样该交换机就可能成为网络中的瓶颈。另外，这种路径也不是流量的最优路径。Seattle提出的优化思路是，（IPa，MACa）的地址解析和（MACa，Sa）的位置解析存在同一个交换机上，即上一节提到的G=F（pa=ra），该交换机在收到Sb单播ARP解析时，不仅仅回复（IPa，MACa），也同时向Sb回复（MACa，Sa），使得Sb也学习到了MACa的转发信息，之后就可以直接将a的数据包封装给Sa了。实际上，Seattle也是一种Overlay的方案。

（5）a迁移到另外的地方后怎么重新收敛

a迁移到Sa'后，通常来说MAC地址是不会变化的，那么F算法和G算法的结果就没有变化。Sa'发现a后向ra宣告新的位置信息（MACa，Sa'），然后再由ra向Sa发送一条单播来将Sa之前的位置信息从（MACa，Sa）变为（MACa，Sa'）。不过此时，其他已经学习到（MACa，Sa）的交换机仍然会将目的是a的数据包发给Sa，为了避免形成路由黑洞，Seattle的做法是在Sa上维护额外的状态，当收到其他交换机发给它的目的是a的数据包后，Sa会把数据包中继给Sa'，并通知源交换机将位置信息从（MACa，Sa）变为（MACa，Sa'）。

（6）拓扑发生改变后会产生什么影响

链路的增删，只会影响link-state中SPF算法对Underlay路由的计算，而并不会影响到F算法和G算法，对Overlay的DHT控制平面没有直接的影响。如果是交换机的增加或者删除，就会直接影响到DHT控制信息的分布，Seattle通过一致性哈希尽可能地减少了交换机的增删（尤其是删除）对控制平面的影响。关于一致性哈希，这里就不进行详细介绍了，有兴趣的读者可自行参阅相关论文。

以上是Seattle对路由的主要设计。如果仔细阅读Seattle的论文，会发现其中还有很多有意思的设计，比如把物理交换机分为多个virtual switch来影响哈希的结果，间接地调整DHT控制平面在不同交换机上的负载。再比如提出了group概念，group类似于传统以太网中的VLAN，通过将不同的MAC分为不同的group来影响转发策略，但是group相比于VLAN要更为灵活一些，可实现组播、广播、安全组以及子网内的隔离，等等。

论文[2]和论文[3]也同样使用了DHT来重新设计以太网的路由。论文是在Seattle之前提出的，在使用DHT去做MAC地址位置解析上的设计和Seattle基本上是一致的，不过它主要是为了兼容传统网络而设计的，没有设计自己的Underlay路由机制，而Seattle的设计则更为全面一些。论文在Seattle之后提出了AIR，在Seattle的基础上把用于拓扑发现和计算Underlay的link-state协议也改成了利用DHT来实现，这进一步降低了link-state带来的开销。

[1] Kim C,Caesar M,Rexford J.Floodless in seattle:a scalable ethernet architecture for large enterprises[C]//Acm Sigcomm Conference on Data Communication.ACM,2008:3-14.

[2] Ray S,Guerin R,Sofi a R.A Distributed Hash Table based Address Resolution Scheme for Large-Scale Ethernet Networks[C]//IEEE International Conference on Communications.IEEE,2007:6446-6453.

[3] Sampath D,Agarwal S,Garcialunaaceves J J.'Ethernet on AIR':Scalable Routing in very Large Ethernet-Based Networks[C]//IEEE,International Conference on Distributed Computing Systems.IEEE Computer Society,2010:1-9.

8.2.2　FatTree

论文[1]中针对FatTree拓扑提出了一个两阶段路由算法，该路由算法和论文中提出的编址规则有很强的依赖关系。论文要求交换机按照图8-11所示的规则进行编址（图中拓扑参数为k=4，关于FatTree的拓扑介绍请参见本章8.1.1节）：POD中交换机IP为10.pod.switch.1，其中pod为交换机所在的POD号（POD号从0开始由左至右依次增加至k-1），switch为交换机所在POD中的编号（Edge层从0开始由左至右依次增加至k/2-1，Aggregation层从k/2开始由左至右依次增加至k-1）；Core层交换机IP为10.k.j.i，k为拓扑对应的参数，j为交换机在Core层所属的组号，i为交换机在该组中的序号。服务器的编址规则为10.pod.switch.ID，其中ID为服务器所在Edge交换机下的序号，由于其上联的Edge交换机占用了10.pod.switch.1，因此服务器的序号从2开始由左至右增加至k/2+1。

在两阶段路由算法中，对POD中的交换机，以及Core层的交换机采用了不同的处理办法。对于POD中的Edge交换机（以图8-11中的10.2.0.1为例），第一阶段直接匹配10.2.0.2～10.2.0.3两个服务器的32位地址，如果匹配成功则从本地端口直接送给目的服务器，如果第一阶段没有匹配成功（即匹配到0.0.0.0/0），则说明目的服务器不在同一个Edge下，于是进入第二阶段的匹配。由于Edge交换机通过任意上联的Aggregation层交换机都能到达任意的目的服务器，因此为了在多条路径上进行负载均衡，第二阶段的Edge匹配将根据目的服务器的后8位（即ID号）来选择路由到哪个上联的Aggregation层交换机（因为服务器ID号的范围是[2，k/2+1]，Aggregation层交换机的switch号的范围是[k/2，k-1]，正好形成k/2个一一对应的关系），其两阶段路由表如图8-12所示。如果目的服务器为10.0.1.2，则数据包就通过2端口转发给10.2.2.1。

图8-11　FatTree中的编址

图8-12　Edge交换机的两阶段路由表

对于POD中的Aggregation交换机（以图8-11中POD2的10.2.2.1为例），第一阶段匹配本POD中不同Edge交换机下服务器所在的网段10.2.0.0/24和10.2.1.0/24。如果匹配成功则说明目的服务器在本POD中，于是直接交给对应的Edge交换机，如果没有匹配成功（即匹配到0.0.0.0/0），则说明目的服务器不在本POD中，于是进入第二阶段的匹配。由于Aggregation交换机通过任意上联的Core层交换机都能到达任意的目的服务器，因此为了在多条路径上进行负载均衡，Aggregation第二阶段匹配将根据目的服务器的后8位（即ID号）来选择路由到哪个上联的Core层交换机中（因为服务器ID号的范围是[2，k/2+1]，与每个Aggregation相连的Core交换机的序号i的范围是[1，k/2]，正好形成k/2个一一对应的关系），如果目的服务器为10.0.1.2，则数据包就通过2端口转发给10.4.1.1。其两阶段路由表如图8-13所示。

图8-13　Aggregation交换机的两阶段路由表

对于Core层的交换机（以图8-11中的10.4.1.1为例）。由于到目的服务器只有一条路径，因此Core层交换机的匹配只有一个阶段，对目的服务器可能在的POD（10.0.0.0/16～10.3.0.0/16）进行判断，并据此选择下行的端口进行转发，如果目的服务器为10.0.1.2，则数据包就转发给10.0.2.1。其路由表如图8-14所示。

图8-14　Core交换机中的路由表

在容错性设计上，交换机运行BFD来探测链路的通断。

1）当Edge交换机A和Aggregation交换机B间的链路L1断掉以后，A将选择POD中其他Aggregation交换机转发原来L1上的流量，B会向与其相连的其他Edge交换机发送广播，告诉它们到A中服务器的流量请选择其他的Aggregation交换机进行转发，B还会向与其相连的Core层交换机发送广播，告诉它们到A中服务器的流量请选择POD中其他的Aggregation交换机进行转发。不过由于与B相连的Core层交换机与B所在POD中的任何其他Aggregation交换机都没有连接，所以这意味着与B相连的Core层交换机将没有任何路径到达A中的服务器，于是这些Core层的交换机会向与其相连的除B以外的其他Aggregation交换机发送广播，告诉它们A中服务器的流量请选择其他的Core层交换机进行转发。

2）当Aggregation交换机B和Core层交换机C间的链路L2断掉以后，B将选择其他与其相连的Core层交换机转发原来L2上的流量，由于C与B所在POD的任何其他Aggregation交换机都没有连接，所以这意味着与C相连的任何Aggregation交换机都无法再通过C来到达B所在POD中的服务器，于是这些Core层的交换机会向与其相连的除B以外的其他Aggregation交换机发送广播，告诉它们到B所在POD中服务器的流量，请选择其他的Core层交换机进行转发。

上述路由机制，要求服务器按照特定的规则进行IP编址，每个二层都限制在同一个Edge交换机下，虚拟机是没有办法跨Edge进行迁移的。而且，由于交换机的IP编址也有着特定的规则，所以需要根据交换机在拓扑中所处的位置对IP地址进行静态配置。另外，两阶段的路由算法需要对交换机的硬件进行修改，如需要按照IP的后8位对目的IP地址进行匹配。

文章中还提到了Flow Classification和Flow Scheduling。Flow Classification作为两阶段路由的一个替代方案，思路是将最拥塞的上联链路中的一些流重新分配到负载最轻的上联链路上，这是一种动态的多路径转发，相比于两阶段路由中以目的IP后8位的服务器ID为依据的静态多路径转发，在理论上有更好的效果。Flow Scheduling通过Edge交换机来监测大象流，然后将新探测到的大象流告诉给集中式控制器，然后由控制器来调度大象流。

[1] Al-Fares M,Loukissas A,Vahdat A.A scalable,commodity data center network architecture[C]//ACM,2008:63-74.

8.2.3　VL2

VL2[1]提出了一种通过OSPF进行Underlay路由，然后通过Overlay隧道来支持大二层的思路。VL2网络中有两个地址族AA（Application Address）和LA（Locator Address）。集中式的Direct System拥有服务器AA和其所在ToR交换机LA的映射关系（文章中并没有提到Direct System如何维护这一关系），每个服务器都需要安装一个VL2 Agent，该Agent负责截获ARP请求并上报给Direct System，Direct System返回给VL2 Agent目的服务器的（AA，LA）映射，以指导VL2 Agent对后续发往该目的服务器的数据包进行封装。

在图8-15中，两个服务器S和D分别位于ToR 10.0.0.4和ToR 10.0.0.6下，S和D处于同一个二层中，S的IP地址为20.0.0.55，D的IP地址为20.0.0.56。当S向D发起通信时，首先由S中的VL2 Agent截获ARP请求，然后通过单播发送给Direct System，Direct System在本地查找服务器D的AA–LA映射关系，并将结果（20.0.0.56，10.0.0.6）返回给S，S中的VL2 Agent将这一关系缓存下来，当收到S后续发给D的IP报文时，VL2 Agent将根据缓存来封装隧道，外层IP的源地址为H（ft）（稍后介绍），目的地址为10.0.0.6。这里要注意的一点是，虽然S和D间发生的是二层通信，但是S并不需要知道D的MAC信息，因为VL2 Agent发起的是IPinIP的隧道，内层封装并不需要D的MAC地址。因此，ARP对于VL2来说只是Agent向Direct System解析（AA，LA）的一个触发机制，Direct System在Reply中并不需要携带D的MAC地址。

图8-15　VL2使用IPinIP的隧道

LA地址族所在的是CLOS网络，为了利用CLOS网络中丰富的路径，VL2使用了ECMP+VLB方式进行多路径的负载均衡。ECMP是通用的多路径转发机制，通过哈希来在多个等价路径上（在VL2中为OSPF计算出来的路径）进行流量分布，为了使封装隧道后ECMP可以更有效地分布流量，外层头部的源IP地址采用了对内层包头哈希之后的结果H（ft），这与在VxLAN中使用源端口号承载哈希的结果是不同的，因为VL2所使用的COTS交换机并不一定按照五元组进行ECMP。

VLB（Valiant Load Balance）通过在源和目的之间指定一个中间节点来转发中继，同样用在多条路径间调节负载。如图8-15中所示，S发往D的数据包实际上是有3个IP头的，VL2 Agent在封装完一层目的ToR的LA地址（10.0.0.6）后，还要再封装一层用于VLB的IP头，指定到目的ToR需要经过Intermediate交换机的LA地址。Intermediate交换机的LA地址，是Direct System接收到ARP请求后，和目的ToR的LA地址一并返回给VL2 Agent的。为了防止intermediate交换机挂掉之后对相关VL2 Agent的转发造成影响，VL2为intermediate层交换机分配了一个任播LA地址（图8-15中为10.1.1.1），所有VL2 Agent在进行封装时，在VLB的IP头中目的地址都采用该任播地址，这样保证在一个intermediate交换机挂掉后ECMP可以找到其他具有10.1.1.1的intermediate交换机进行中继。

实际上，VLB的应用场景更多是在于骨干网的调优，因为骨干网上多条路径的消耗通常是不一样的，路由协议会把大部分的流量导到少数的链路上，从而导致拥塞。使用VLB来指定一个转发的中继节点可以有效地疏导网络中的拥塞，结合一些算法（如网络中的动态带宽）可以实现比较高级的负载均衡。不过VL2用的是CLOS拓扑，不同路径的消耗都是一样的，使用VLB+ECMP相比于ECMP有什么好处呢？文中提到，当时的COTS交换机通常只能支持16路的ECMP，这意味着一旦Intermediate交换机的数量超过了16个，单纯依靠ECMP来进行负载均衡是难以线性地获得新增交换机的链路带宽的。如果使用VLB+ECMP的话，可以以16个Intermediate交换机为单位作为一组从而采用一个任播地址。Agent在转发时可以采用不同组Intermediate的任播地址进行封装，这样ECMP就可以利用所有的intermediate交换机了。

论文[2]中提到的路由方案Monsoon也和VL2的思路大同小异，都是采用隧道来实现大二层的，通过最外层的VLB头来进行转发调度。在实现上Monsoon和VL2还是有较大区别的，Monsoon采用的是MACinMAC的封装，服务器上的Monsoon Agent将ARP上报给集中式的Directory Service，Directory Service将目的服务器的MAC地址、目的服务器所在ToR交换机的MAC地址，以及一组可用的intermediate交换机的MAC地址返回给Agent，然后Agent再进行封装。具体过程如图8-16所示。另外一点要注意的是，由于Monsoon的底层网络也是以太网，而Monsoon并没有提供自己的二层转发机制，所以只能依赖于其他二层转发，如果底层还是MAC自学习+STP的话，即使采用了VLB也很难达到VL2中VLB+L3 ECMP的效果。

图8-16　Monsoon使用MACinMAC

[1] Greenberg A,Hamilton J R,Jain N,et al.VL2:a scalable and fl exible data center network[C]//ACM SIGCOMM 2009 Conference on Data Communication.ACM,2009:51-62.

[2] Greenberg A,Lahiri P,Maltz D A,et al.Towards a next generation data center architecture:scalability and commoditization[C]//ACM Workshop on Programmable Routers for Extensible Services of Tomorrow.ACM,2008:57-62.

8.2.4　PortLand

论文[1]中针对FatTree拓扑设计了一个大二层的网络PortLand，解决了论文[2]中虚拟机没有办法跨Edge交换机迁移的问题。由于PortLand面向的也是结构高度规范化的FatTree拓扑，不过相比于FatTree中使用层次化的IP编址来实现三层，PortLand选用了层次化的MAC编址来支持大二层，PortLand中每个服务器/虚拟机除了自身的实际MAC（AMAC）地址以外，都有一个由PortLand规范的伪MAC（PMAC）地址，PorLand网络中的转发机制也和PMAC编址有着很强的依赖关系。PMAC的编址规则为pod.position.port.vmid，其中（FatTree拓扑中的术语请参见本章8.1.1节）：pod（2字节）代表服务器/虚拟机在FatTree拓扑中所处的POD号，position（1字节）代表服务器/虚拟机所连接的Edge交换机在POD中的编号，port（1字节）代表服务器/虚拟机连接的端口在Edge交换机上的本地编号，vmid（2字节）代表服务器在Edge下挂的以太网交换机上的编号，或者虚拟机在服务器vswitch上的编号，如果Edge向下直连的只有一个物理服务器（没有虚拟化），那么该Edge交换机下的vmid号就只能为1。图8-17所示左边标黄的物理服务器（没有虚拟化）10.5.1.2对应的PMAC地址为00：00：01：02：00：01（pod=0，position=1，port=2，vmid=1），而右边标黄的物理服务器（没有虚拟化）10.2.4.5对应的PMAC地址为00：02：00：02：00：01（pod=2，position=0，port=2，vmid=1）。

由于二层网络是要求即插即用的，所以PortLand设计了一套拓扑发现机制LDP（Location Discovery Protocol），PortLand中所有的交换机都在各个端口上发送LDP的报文LDM（Location Discovery Message）来识别自己所处的位置。LDM携带的信息包括以下几种：switch_id（交换机自身的MAC地址，与PMAC无关），pod（交换机所属的pod号），pos（交换机在所处POD中的编号），level（Edge为0、Aggregation为1、Core为2），dir（端口上联时为1，下联时为-1）。

网络刚开始启动时，各个交换机只知道自己的switch_id，其他信息都是未知的。Edge层交换机首先发现有一些端口（连接服务器的端口）是收不到LDM的，于是Edge层交换机得知自己的level为0，那么所有能够收到LDM的端口即为上联Aggregation交换机的端口，这些端口再发送LDM时就指定了level=0，dir=1。Aggregation交换机发现有一些端口收到了level=0，dir=1的LDM，于是得知自己的level为1。所有能够收到level=0，dir=1的LDM端口就是连接Edge层交换机的下连端口，这些端口再发送LDM时就可以指定level=1，dir=-1。一直没有收到level=0，dir=1的LDM端口就是连接Core层交换机的上联端口，这些上联端口再发送LDM时就可以指定level=1，dir=1。对于Core层的交换机而言，它们只能连接Aggregation层的交换机，所以当一个交换机发现自己所有的端口都收到了level=1，dir=1的LDM的时候，它就可以判定自己的level=2，其端口在发送LDM时将标记为level=2，dir=-1。至此，所有交换机的level和各个端口的dir就都确定下来了。

图8-17　PortLand的架构示意图

下面再来说pos和pod。Pos是由Edge Switch随机在[0，k/2-1]中选取的一个数R（每个POD中都有k个Edge交换机），然后向其上联的Aggregation交换机发送一个proposal，如果Aggregation交换机是第一次看到这个数R，那么就向该Edge回复一个verified，如果该Edge交换机收到了超过一半的Aggregation交换机（≥k/4+1）所回复的verified，则该Edge交换机就认为自己的pos为R，否则重新生成一个R1然后再次进行判断，最后POD中的Edge交换机都会分配到不同的pos号。至于pod，是由pos=0的Edge交换机向一个集中式的控制器Fabric Manager申请得到的，Fabric Manager会根据switch_id识别出该交换机所属的pod号然后返回给该交换机，然后该交换机在发送LDM时就会携带这个pod号，于是逐跳地扩散到POD中其他的交换机上。Core层的交换机发送LDM时不携带任何pod号。这样一来，所有的交换机就都学习到了自己在FatTree拓扑中的位置了。

Fabric Manager是一个集中式的PortLand控制器，除了向Edge交换机回答pod号以外，它还负责ARP的解析、路由重收敛以及组播。先来说ARP的解析。由于二层网络是可以任意迁移的，因此同一个服务器/虚拟机可能出现在PortLand网络中任何一台Edge交换机下，因此AMAC和PMAC的映射关系是没有办法静态配置的，只能靠Edge交换机自学习。当Edge交换机首次看到一个AMAC时，它会计算出对应的PMAC，记录下（IP，AMAC，PMAC）的映射关系，然后向Fabric Manger更新该映射关系。下次有其他服务器或者主机向该IP发起ARP请求时，Fabric Manager会直接回复对应的PMAC地址（而非AMAC地址），于是双方开始通信，之后使用的目的MAC地址即为对端的PMAC地址，PortLand中的交换机就根据PMAC来进行路由了。

下面来介绍PortLand的路由机制。在发现了自己在拓扑中的位置后，PortLand中的各个交换机就可以在本地计算不同PMAC的路由了。对于Edge交换机来说，它会判断收到的数据包中的目的MAC地址（即目的服务器/虚拟机的PMAC地址）中的pod和pos是否与自己的pod和pos相同，若均相同则根据port号从本地对应的端口进行转发，否则说明流量的目的不是本地直连，于是通过ECMP选择一个上联端口送给Aggregation交换机。对于Aggregation交换机来说，它会判断收到的数据包中目的MAC地址（即目的服务器/虚拟机的PMAC地址）中的pod是否和自己的pod号相同，若相同则根据pos号选择对应的Edge交换机进行转发，否则就通过ECMP选择一个上联端口发送给Core层交换机。对于Core层的交换机来说，它将直接根据收到的数据包中目的MAC地址（即目的服务器/虚拟机的PMAC地址）中的pod选择对应的Aggregation交换机进行转发。

当数据包被路由到目的服务器/虚拟机所在的Edge交换机后，该Edge交换机还要负责把PMAC地址写回到原来的AMAC地址（已经学习过该PMAC和AMAC的映射关系了），这样做是为了防止Edge交换机下面的vswitch，或者普通的交换机产生混乱。当虚拟机发生迁移时，如果按照交换机中原来学习到的AMAC和PMAC间的映射关系就会产生路由黑洞，此时虚拟机会发送一个免费ARP来宣告自己新的位置，这个免费ARP会被Fabric Manager接收到，然后Fabric Manger会找到虚拟机原来所在的Edge交换机Soriginal，并向其发送一个无效的消息从而废掉该虚拟机AMAC和PMAC间的映射关系。不过，其他的Edge交换机Sothers仍然会将目的地为该虚拟机的数据包发送给Soriginal，此时Soriginal会将数据包中继到迁移后的交换机上，然后向Sothers发送一个免费ARP来更新Sothers上该虚拟机AMAC和PMAC间的映射关系。

至于组播，Fabric Manager会监听IGMP来维护组播树，计算组播的转发表然后下发给相关的交换机。至于路由重收敛，交换机一旦发现LDM超时就认为链路断掉了，然后将该事件上报给Fabric Manager，Fabric Manager分析出将会受到影响的交换机，然后更新其相关的转发表。图8-18的左半部分示意了组播，右半部分示意了链路断掉后组播树的重收敛，限于篇幅这里就不进行详细的介绍了。

图8-18　PortLand的组播机制

PortLand是一种很典型的集中式控制方案，其中一些设计思路也是很巧妙的。论文[3]提出的Moose思路和PortLand十分类似，也是通过集中式的控制器ELK（Enhanced Lookup）来处理ARP的，回复的是层次化编址的Moose MAC。不过由于Moose没有绑定任何拓扑，因此Moose MAC的结构相对简单，即为switch_id（3字节）+host_id（3字节）。其中的switch_id是松散组织的，交换机可以使用任意的switch_id，交换机通过MAC地址前缀匹配switch_id来路由数据包，具体的MAC学习与转发机制仍然依赖于自学习和STP。路由到目的交换机后，再通过一些方法把Moose MAC写回到原来的MAC地址。论文[4]提出的Diverter也采用了MAC地址重写技术，实际上MAC地址重写技术还有很广泛的应用，有兴趣的读者可以再深入地研究一下这些论文。

路由整体架构的设计是一个非常庞大的工程，以上是关于数据中心路由整体架构设计的几篇最为经典的论文，因此花了非常多的篇幅对它们进行了详细的分析。还有很多其他经典的文献也都讨论了数据中心的路由架构问题[5]，[6]，[7]，[8]，[9]，以DCell[10]和BCube[11]为代表的一些论文还提出了以服务器为主、以交换机为辅进行路由的数据中心网络，论文[12]甚至提出了只有服务器、没有交换机的数据中心组网架构与相应的路由机制，限于篇幅这里就不再一一介绍了。

路由的框架可以分解出来很多子问题，有很多论文都选择了其中的一点进行讨论与设计。下面从源地址路由、多路径转发、流量调度、路由重收敛、组播几个方面来介绍一些相关的论文。

[1] Mysore R N,Pamboris A,Farrington N,et al.PortLand:a scalable fault-tolerant layer 2 data center network fabric[C]//ACM SIGCOMM 2009 Conference on Data Communication.ACM,2009:39-50.

[2] Al-Fares M,Loukissas A,Vahdat A.A scalable,commodity data center network architecture[C]//ACM,2008:63-74.

[3] Scott M,Moore A,Crowcroft J.Addressing the Scalability of Ethernet with MOOSE[J].Dc Caves Workshop,2010.

[4] Edwards A,Fischer A,Lain A.Diverter:a new approach to networking within virtualized infrastructures[C]//ACM SIGCOMM 2009 Workshop on Research on Enterprise Networking,Wren 2009,Barcelona,Spain,August.DBLP,2009:103-110.

[5] Tu C C,Chiueh T C.Cloud-Scale Data Center Network Architecture[J].2011

[6] Hu Y,Zhu M,Xia Y,et al.GARDEN:Generic Addressing and Routing for Data Center Networks[C]//IEEE Fifth International Conference on Cloud Computing.IEEE Computer Society,2012:107-114.

[7] Cohen R,Barabash K,Rochwerger B,et al.DOVE:Distributed Overlay Virtual nEtwork Architecture[J].Ibm Corporation,2007.

[8] Mudigonda J,Yalagandula P,Mogul J,et al.NetLord:a scalable multi-tenant network architecture for virtualized datacenters[J].Acm Sigcomm Computer Communication Review,2011,41(4):62-73.

[9] Kempf J,Zhang Y,Mishra R,et al.Zeppelin-A third generation data center network virtualization technology based on SDN and MPLS[C]//IEEE,International Conference on Cloud NETWORKING.IEEE,2013:1-9.

[10] Guo C,Wu H,Tan K,et al.Dcell:a scalable and fault-tolerant network structure for data centers[C]//ACM,2008:75-86.

[11] Guo C,Lu G,Li D,et al.BCube:a high performance,server-centric network architecture for modular data centers.[J].Acm Sigcomm Computer Communication Review,2009,39(4):63-74.

[12] Abu-Libdeh H,Costa P,Rowstron A,et al.Symbiotic Routing in Future Data Centers[J].Acm Sigcomm Computer Communication Review,2010,40(4):51-62.

8.2.5　SecondNet

论文[1]中提出了SecondNet，它设计了端口源路由（PSSR，Port Switching based Source Routing）来保证某些流量带宽的SLA。PSSR是指源在发出流量时会将各个交换机在该路径上的转发端口编码发到数据包的header中，然后交换机直接根据最外层的端口号进行转发，并把最外层的端口号剥掉，直到最后一台交换机把这个header去掉。如图8-19所示，用户将某两个主机间VDC VM_0和VDC VM_1通信所需要的带宽告诉给一个集中式的控制器VDC Manager，VDC Manager根据网络拓扑和网络中实际的带宽情况为VDC VM_0到VDC VM_1计算出一条通信路径（图8-19中黑色加粗的路径），然后通知给VDC VM_0所在的虚拟交换机，由虚拟交换机把各交换机对应于该路径的转发端口封装到MPLS Stack中（图8-19黑色路径上的带底纹的端口），然后各个交换机根据当前数据包中最外层的MPLS Label直接转发到对应的端口上。注意，这里并不需要有MPLS Label到端口号的映射表，因为MPLS Label本身封装的就是端口号。在流量转发到VDC VM_1所在的虚拟交换机前，MPLS Stack恰好弹空，虚拟交换机根据MAC地址将流量送给VDC VM_1。

图8-19　SecondNet架构示意图

实际上MPLS Stack在数据平面上是很简单、很清晰的封装，只不过它的控制平面LDP和RSVP太过于复杂，才导致在数据中心中难以实施。SecondNet只保留了MPLS在数据平面上的封装，而使用VDC Manager产生MPLS Stack进行端口源路由，所以中间的交换机也不用再去维护复杂的MPLS状态了。虽然SecondNet的思路比较简单，但却是一个很实用的方案。

[1] Guo C,Lu G,Wang H J,et al.SecondNet:a data center network virtualization architecture with bandwidth guarantees[C]//International Conference.ACM,2010:15.

8.2.6　SiBF

论文[1]中提出了SiBF（Switching with in-packet Bloom filters），其思路是使用Bloom Filter将属于某个路径上的信息映射到header中，然后据此进行路由。使用Bloom Filter算法可以判断出一个元素是否存在于集合中。一般而言对于一个算法来说时间和空间往往是不可兼得的，但是Bloom Filter通过引入错误率来同时保证时间和空间的高效性，其代价是判断的结果有一定几率会出错，也就是说可能会把一个不属于集合的元素判定为属于集合，但是不会将一个属于集合的元素判断为不属于集合。Bloom Filter被学术界广泛地应用在网络领域中，比如将一条路径上的节点信息通过特定的Bloom Filter算法映射到某个header中，然后路径上的节点就可以使用相同的Bloom Filter算法来判定与自己相邻的节点是否存在于header中，从而完成转发的决策。为了不增加额外的包头开销，SiBF选择通过改写MAC地址来承载Bloom Filter的结果，然后在出口处将MAC地址改回到原来的MAC地址。

在图8-20中，虚线是SiBF中的控制平面交互流程，实线是数据包的转发流程。示例的拓扑为典型的ToR、Aggregation、Core三层结构，RM是SiBF网络中的OpenFlow控制器。首先RM通过RDP（一种扩展的LLDP）来发现网络拓扑，并检测。当ARP从源主机IPsrc发出来之后先到达它所在的ToRsrc，然后ToRsrc将其上报给RM，RM作为ARP代理将Reply返回给IPsrc。通信正式开始以后，ToRsrc再次将数据包上报给RM，RM根据网络拓扑找到目的地IPdst的接入位置ToRdst，然后根据拓扑计算出一条路径（使用的VLB，见本节对VL2的介绍），将该路径的交换机信息通过Bloom Filter映射出一个96位的二进制序列。然后，RM向ToR下发一条规则，将这个二进制序列重写到96位的MAC地址中（源+目的）并进行转发。在Aggregation和Core上，通过匹配源、目的MAC地址+对应的掩码来进行基于iBF的转发。转发到ToRdst后，再由ToRdst将MAC地址改写回去并交给IPdst。

图8-20　SiBF的信令交互与转发过程

相比于传统的MAC地址学习，SiBF实际上就是一种集中式控制+源路由的方案。相比于SecondNet通过MPLS Stack来承载源路由，SiBF的好处在于，交换机不要求支持MPLS Stack，使用Bloom Filter可以将任意层数的MPLS Label都转化为对MAC地址的重写，然后通过MAC地址+对应的掩码即可实现和匹配同MPLS Stack一样的作用。另外，如果交换机本地可以实现Bloom Filter算法的话（如一些带支持Bloom Filter的FPGA芯片的交换机），那么实际上不需要控制器向交换机分发Bloom Filter流表。

[1] Rothenberg C E,Macapuna C A B,Verdi F L,et al.Data center networking with in-packet Bloom filters[J].2010.

8.2.7　SPAIN

论文[1]中提出了一种将数据中心的拓扑划分为多个子树，然后通过VLAN Tag将流量分散到不同的子树中来提供多路径转发，从而提高链路的利用率。这一思路与传统网络中的PVST（Per VLAN Spanning Tree）类似，PVST通过为每个VLAN维护不同的生成树来提高链路利用率，不过PVST受限于STP协议有着诸多缺点，如收敛慢、规模小、VLAN配置复杂，而且生成树的根往往只能是几个核心节点。而在SPAIN设计中，集中式节点通过SNMP来收集网络拓扑并计算不同的子树（有别于覆盖全部节点的生成树，子树只需覆盖一部分节点），然后再通过SNMP为不同的子树配置不同的VLAN它的收敛速度很快，规模可以做到很大，无需复杂的手工配置。使用集中式服务器计算子树的拓扑，不受限于任何根节点，因此这些子树能够最大程度地利用网络中的不同链路。以图8-21为例，左边的网络拓扑有7个节点以及多条节点间的链路，右边是该拓扑对应的不同子树（图中仅展示了能够覆盖所有7个节点的生成树）。

图8-21　SPAIN通过不同的VLAN子树来实现多路径

这里要注意的一点是，SPAIN网络中的VLAN是用于区分子树的，而不是用来虚拟化的，一个服务器可以使用多个VLAN传输自己的流量。服务器通过DHCP下载这些VLAN的信息表（需要新的Option），论文中将这个表称为“VLAN reachability map”，VLAN reachability map中包括各个MAC地址在网络中的接入位置、各个VLAN可达的交换机、当前可用的VLAN子树，等等。服务器根据这些信息就能得到与目的MAC地址通信时可用的VLAN标签，根据自身流量的特征，为不同的流量标记不同的VLAN，然后将去往同一个目的MAC地址的流量分布到不同的子树上去，从而提高链路的利用率。

SPAIN对于各个子树的状态维护，是通过名为chirp packet的探针来完成的。VLAN 1保留用作default tree，其所对应的子树要覆盖到所有节点，用来承载chirp packet。chirp packet具体的工作机制请读者参考SPAIN的论文进行学习。

简单地说，SPAIN的思路就是集中式地计算出多个拓扑的子树，然后由各个服务器自行将流量分散到不同的子树上去。论文[2]中所提出的PAST，与其在思路上类似，PAST通过OpenFlow控制器来收集拓扑，为不同的目的MAC地址计算出不同的生成树，然后向相关的COTS交换机下发流表以匹配该MAC地址，从而在该MAC地址对应的生成树上进行转发。不同的MAC地址会对应到不同的生成树上去，以提高网络链路的利用率。PAST和SPAIN的不同点在于：1）PAST计算的生成树是针对于MAC地址的，而SPAIN是针对于子树的。2）PAST是完全的集中式控制，而SPAIN中的服务器具有分布式的控制器逻辑可以自行选择如何分布流量。

PAST相比于SPAIN有一些优势：1）MAC地址并不一定是针对于服务器的，虚拟机或者交换机都可以，因此PAST可以作为单纯的Underlay fabric为其他Overlay逻辑提供服务。2）PAST没有使用VLAN，因此VLAN可以用来进行虚拟化。3）在PAST中交换机的转发是由OpenFlow控制的，但SPAIN仍然通过VLAN+自学习进行转发，这样可以扩展其他更为灵活的功能。

[1] Mudigonda J,Yalagandula P,Al-Fares M,et al.SPAIN:COTS data-center Ethernet for multipathing over arbitrary topologies[C]//Usenix Symposium on Networked Systems Design and Implementation,NSDI 2010,April 28-30,2010,San Jose,Ca,Usa.DBLP,2010:265-280.

[2] Stephens B,Cox A,Felter W,et al.PAST:scalable ethernet for data centers[C]//International Conference on Emerging NETWORKING Experiments and Technologies.2012:49-60.

8.2.8　WCMP

论文[1]针对CLOS网络，提出了一种对ECMP进行动态加权的机制，并将其称为WCMP（Weighted Cost MultiPath）。WCMP设计的出发点是，数据中心网络的整体结构在设计上虽然是高度对称的，但是受限于一些实际情况，实际的网络连接通常是不对称的，导致“Imbalanced Stripping”。“Balanced Stripping”的一个例子存在于FatTree网络中，需要（k^2/2）个Core层交换机。不过在实际生产网络中，通常是难以满足如此苛刻的连接条件的，下面给出一个简单的“Imbalanced Stripping”的例子。如图8-22所示，每个S1交换机都有4个上联口，而S2交换机总共只有3个，这样就导致在S1交换机上必然有2个上联口连接到同一个S2交换机上。对于这样的连接，使用ECMP就很有可能使S2层交换机产生拥塞。如在图8-22中，S1_0和S1_2间共产生了12GB的流量（假设不同端口上的流量是均衡的），这时ECMP在4条上联链路上将分别打3GB的流量，由于S1_0和S2_0间有两条上联链路，因此与S1_2间只有一条链路的S2_0就承载了6GB的流量，而与S1_2间有两条链路的S2_2只承载了3GB的流量。这样一来，S2_0就可能产生拥塞，同时S2_2上却有很多的链路带宽被闲置了。另外，当网络发生故障时，ECMP所期望的连接对称性也同样会遭到破坏。

WCMP的提出就是为了解决由“Imbalanced Stripping”和网络故障导致的问题。产生这些问题的根源就是ECMP只考虑了路径静态的cost，而没有考虑拓扑的实际连接情况。WCMP的思路就是通过集中式的控制器来监测网络拓扑和流量的特征，然后通过一些算法为各个上联链路分配最优的权重，从而尽量减轻流量在下一级交换机上产生拥塞。如图8-22所示，控制器通过对拓扑的分析，使得S1_0在处理到S1_2的流量时，4条上联链路的权重分别设为2：2：4：4（从左到右），从而使得3个S2交换机均收到了4G的流量，缓解了S2_0上的负担。当然，2：2：4：4的权重也不一定就是最优的，不过相比于ECMP的1：1：1：1是要好很多的。

图8-22　WCMP（右）与ECMP（左）的区别

论文中还给出了通过OpenFlow控制器来实现WCMP的设计原型。如图8-23所示，NIB模块维护底层网络的信息，如拓扑结构以及网络中现存的转发表与WCMP表。NIB将拓扑结构告诉给Path Calculator，Path Calculator通过WCMP算法来确定各条链路的权重。当拓扑结构或者流量的特征发生改变时，Path Calculator如果发现权重需要进行调整，从而调用Forward Manager的接口将新的转发表和WCMP表更新到对应的交换机上去，从而在多条上联链路间实现动态均衡。

图8-23　WCMP的实现架构

在Path Calculator的实现上，论文针对不同的情况提出了不同的算法，考虑到交换机中有限的WCMP硬件资源，对其进行了一些优化。这些算法和优化机制请读者自行查阅论文。

[1] Zhou J,Tewari M,Zhu M,et al.WCMP:weighted cost multipathing for improved fairness in data centers[C]//European Conference on Computer Systems.ACM,2014:5.

8.2.9　OF-based DLB

论文[1]针对FatTree网络提出了一种适应性的多路径转发算法。这篇文章的思路是放弃使用ECMP这种静态的且与流量无关的哈希算法，而使用一种动态的负载均衡算法进行代替，在可用的上联链路中选择剩余带宽最大的链路来转发当前的流量。思路没有什么太多需要解释的了，在实现上使用了OpenFlow控制器，控制器通过LLDP收集拓扑，通过定期发送Stats消息记录链路可用的带宽，当交换机发现一个新的Flow后通过PacketIn上报给控制器。控制器根据流量的源和目的地址，判断流量是需要继续上行，还是直接下行，如果是上行流量，就选择剩余带宽最大的且可用的上联端口；如果是下行流量，对于特定的FatTree拓扑来说就只有一个可用的下行端口。控制器选择好转发端口后就下发FlowMod指导交换机处理后续的流量。

相比于WCMP考虑全局的流量和拓扑状态，论文采用的是局部最优的贪心算法，有可能会导致链路整体利用率的低下。另外，在实现上它严重地依赖于PacketIn来驱动流量的转发，这会给控制器造成不小的处理压力。

[1] Li Y,Pan D.OpenFlow based load balancing for Fat-Tree networks with multipath support[C]//Proc.12th IEEE International Conference on Communications (ICC’13), Budapest,Hungary. 2013:1-5.

8.2.10　Flowlet与CONGA

为了防止数据包乱序而造成TCP重传，ECMP等机制实现的都是per-flow的负载均衡，即对同一个5元组（源IP、目的IP、IP负载类型、源端口号、目的端口号）选择相同的转发路径。不过由于ECMP难以有效地处理大象流，因此很多论文都从flow-splitting的角度重新设计多路径的转发机制。

论文[1]提出了flowlet的概念，路由器或者交换机通过将一个TCP连接中（即同一个五元组）的不同burst识别不同的flowlet，允许这些不同的flowlet从不同的路径进行转发。顾名思义，burst的概念就是突发的一段流量，一个burst内部的数据包间隔时间非常小，而不同burst间数据包的间隔时间相对较长。假设判断burst的门限时间为Tb，则在一个TCP连接中，各个数据包的间隔时间均小于Tb的一段流量认为是一个flowlet，当在这个TCP连接中经过时间T（T>Tb）后再次出现数据包，后面的数据包就被识别为另外一个flowlet。由于两个flowlet的间隔时间较长，这段时间通常足够前一个flowlet完成端到端的传输，因此即使后一个flowlet被路由到其他路径上去，产生乱序的可能性也非常小。

对于数据中心来说，burst的流量很多，而且由于数据中心内部端到端的延时很低，一个足够小的Tb就可以区分开不同的flowlet。因此Cisco的工程师们提出将flowlet用于数据中心交换机的设计中，并在Cisco的Nexus系列部分交换机的芯片上进行了实现，并称其为CONGA[2]（Congestion Aware Balancing）。CONGA提出了根据网络中端到端的拥塞状况来为flowlet选择转发路径，从而能够获得更好的负载均衡效果。考虑到当时以VxLAN为代表的Overlay方案在数据中心里面已经得到了广泛的认可，因此CONGA基于VxLAN的隧道封装扩展了用于负载均衡的字段，包括4bit的LBTAG用于标识CONGA选择的转发路径，3bit的CE用来记录转发路径的拥塞程度，4bit的FB_LBTAG和FB_Metric用于反馈路径拥塞的程度。源Leaf交换机在转发flowlet时被标记为LBTAG，路径沿途的交换机收到数据包后会判断链路的拥塞值是否大于数据包中携带的CE值，如果大于就用当前的拥塞值来更新CE字段。目的Leaf交换机收到数据包后会将该flowlet对应的LBTAG和当前的CE值保存下来，等到有数据包回复给该源交换机时，就把之前的记录值作为FB_LBTAG和FB_Metric返回给源交换机。然后源交换机将会以链路的拥塞状态为依据，来影响后面flowlet的转发。可以看到CONGA的实现对于Leaf交换机来说是有状态的，因此Leaf需要在本地维护一些新的表来支撑CONGA。图8-24所示为CONGA的一个简单转发示例。

图8-24　CONGA转发机制示意图

区别于flowlet通过数据包的时间间隔Tb来进行flow-splitting，论文[3]提出LocalFlow，它通过TCP SEQ号来切分flow。local_flow在每个ToR交换机的本地部署一个OpenFlow控制器，控制器可以匹配某些flow中的TCP SEQ号，并且通过一些算法来划分不同的subflow，然后对不同的subflow采用不同的转发路径。

另外，还有很多论文都讨论或设计了per-packet multipath在数据中心应用的可能性。以传统的角度来看，per-packet做multipath的问题在于，在同一条流中不同的数据包会走到不同的路径上去，由于不同路径有不同的延时，因此数据包很可能会出现乱序的问题，TCP的重传可能会降低流量的传输效率。不过论文[4]认为，数据中心的网络结构是高度对称的，而且端到端的跳数也很少，如果采用per-packet的多路径转发不会存在大象流的问题，这样可以保证大部分链路的拥塞程度都差不多，因此在任何路径上端到端的延时都趋于一致，那么数据包乱序的情况会大大减少。论文[5]使用交换机对少数乱序包进行重排，论文[6]提出了一种per-packet的DRB路由算法，相比于随机的路由方式降低了时延。因此，学术界有声音认为per-packet multipath是可以改善数据中心网络的转发效率的。这个说法的逻辑听起来是合理的，不过不同数据中心的流量特征是完全不一样的，per-packet multipath是否可以用于具有不同流量特征的数据中心，仍需要进行深入的研究。

[1] Kandula S,Katabi D,Sinha S,et al.Dynamic load balancing without packet reordering[J].ACM SIGCOMMComputer Communication Review,2007,37(2): 51-62.

[2] Alizadeh M,Edsall T,Dharmapurikar S,et al.CONGA:Distributed congestion-aware load balancing fordatacenters[C]//ACM SIGCOMM Computer Communication Review.ACM,2014,44(4):503-514.

[3] Sen S,Shue D,Ihm S,et al.Scalable,optimal fl ow routing in datacenters via local link balancing[C]//Proceedings of the ninth ACM conference on Emerging networking experiments and technologies.ACM,2013:151-162.

[4] Dixit A,Prakash P,Hu Y C,et al.On the impact of packet spraying in data center networks[C]//INFOCOM,2013 Proceedings IEEE.IEEE,2013:2130-2138.

[5] Rottenstreich O,Li P,Horev I,et al.The switch reordering contagion:Preventing a few late packets from ruining the whole party[J].IEEE Transactions on Computers,2014,63(5):1262-1276.

[6] Cao J,Xia R,Yang P,et al.Per-packet load-balanced,low-latency routing for clos-based data center networks[C]//Proceedings of the ninth ACM conference on Emerging networking experiments and technologies.ACM,2013:49-60.

8.2.11　Hedera

论文[1]中提出了Hedera，它可以看作是对FatTree[2]中Flow Scheduler的详细实现，可以看作是最早提出将OpenFlow控制器部署在数据中心用于调度大象流的。Hedera在初始状态下，对于流量一律采用基于十元组<src MAC，dst MAC，src IP，dst IP，EtherType，IP protocol，src port，dst port，VLAN tag，input port>的ECMP转发。然后每隔5s通过Flow统计量来探测一下Edge交换机上各个流的情况，如果发现某个流的速率已经超过某个阈值，如对于1GE的端口的链路来说超过了100Mbps（10%），那么控制器就判别该流为大象流，然后通过一些算法为这条流计算出一条合适的路径，通过下发相应的流表进行流量调度。

在调度算法上，Hedera评估了GFF（Global First Fit）和Simulated Annealing（模拟退火）两种。GFF是一种贪心算法，控制器发现一个大象流后，根据当前的网络状态对网络中的路径进行线性搜索，如果发现一条路径，并且其中任意一段链路都能够满足大象流带宽的需要，那么就选中该路径来传输这个大象流，并通过向路径上的交换机下发流表来引导这个大象流的转发。Simulated Annealing的算法中，控制器首先汇总所有的大象流，然后评估将这些大象流放置在网络中的所有方式（即解空间），从中选择出一个近似于全局最优的放置方式，最后根据这个放置方式中的放置方法协调网络中所有的大象流（假设在某种放置方式中，第一条大象流的带宽为M，放置这条大象流的路径的链路的带宽为N。如果M>N，那么第一条大象流就超过了M-N的带宽，将所有超过带宽的大象流加在一起，就是这个放置方式中总共超过的带宽为S，选取S最小的放置方式即可保证近似的全局最优）。

由于在FatTree拓扑中，Core层交换机到所有的目的地都只有一条可用的路径，因此Hedera对Simulated Annealing做了一个相应的优化，即为具有相同目的地址的大象流指定同一个Core层交换机，然后以大象流所在的Edge交换机作为路径起点，该大象流所对应的Core层交换机为路径终点。这能够大大地缩小待评估的解空间范围，从而有效地减少算法的收敛时间。

[1] Al-Fares M,Radhakrishnan S,Raghavan B,et al.Hedera:Dynamic Flow Scheduling for Data Center Networks[C]//NSDI.2010,10:19-19.

[2] Al-Fares M,Loukissas A,Vahdat A.A scalable,commodity data center network architecture[C]//ACM,2008:63-74.

8.2.12　DevoFlow

Hedera为了探测大象流，控制器需要每隔5s给交换机发送一个Flow统计消息。论文[1]提出了DevoFlow，它认为Hedera发送Flow统计所导致的开销太大，因此需要将监测大象流的功能卸载到OpenFlow交换机本地来完成。为了在交换机本地对流量进行精细的监测，DevoFlow为OpenFlow中的FlowMod消息扩展了一个Clone标志位，流量在匹配了带有Clone标志位的FlowMod后，会自动生成一条新的流表，这条流表会将原来FlowMod中的通配项都转化为对流量的精确匹配，然后通过扩展的local multipath动作在交换机本地实现负载均衡。可以认为带有Clone标志位的FlowMod是一个粗粒度的流表（可能由一些非TE的APP下发），而由该FlowMod克隆出来的新流表则是有更细粒度的流，克隆出来的流表负责维护对应的counter，实现对流量的精细化统计，以便对流量的转发进行更细粒度的控制。

当交换机发现某些流量的速率超过了阈值后，会触发一条report消息给控制器，使控制器意识到在该交换机上有一个大象流，然后控制器就会对这个大象流计算路径并下发对应的流表。为了解决Hedera的问题，DevoFlow付出的代价是需要维护大量的counter，这对于ASIC来说有巨大的困难，因此DevoFlow提到可使用交换机本地的CPU来对counter进行维护。

[1] Curtis A R,Mogul J C,Tourrilhes J,et al.DevoFlow:scaling fl ow management for high-performance networks[C]//ACM SIGCOMM 2011 Conference.ACM,2011:254-265.

8.2.13　MicroTE

论文[1]提出了MicroTE，根据ToR-ToR的流量可预测性特征来控制网络中的转发。设两个ToR间的平均速率是V，门限值为T，则流量是否可预测的判断条件为：若两个ToR间的流量速率较为平稳，瞬时速率相比于V的波动值小于T，即认为流量是可预测的。对这部分可预测的流量MicroTE采用OpenFlow控制器来为它们计算路径，并下发流表进行转发调度。若两个ToR间流量的瞬时速率相比于V的波动值大于T，则认为流量是不可预测的，这部分流量则采用加权ECMP的方式在交换机本地进行负载均衡。

MicroTE为每一个Rack指定一台Designated Sever，由它收集Rack下所有的流量情况，发现流量特征有所变化时通知给控制器，然后控制器汇总所有Rack下的流量情况，形成ToR-ToR的流量矩阵，以此作为依据来进行流量调度。相比于Hedera控制器周期性地从Edge交换机中查询状态，这种使用服务器来监测流量，以及触发式的状态更新，能够避免在控制平面上频繁交互Flow Statics消息所造成的巨大开销。

[1] Benson T,Anand A,Akella A,et al.MicroTE:fine grained traffic engineering for data centers[C]//Conference on Emerging NETWORKING Experiments and Technologies.ACM,2011:1-12.

8.2.14　Mahout

相比于在MicroTE中使用Designated Sever来监测ToR下的流量，论文[1]提出Mahout，直接在End-Host操作系统的协议栈中嵌入一个垫层，这个垫层监测各个socket的buffer，当有socket堆积了大量buffer时，它就会将其识别为大象流，然后对该socket的流量标记特定的DSCP字段再发出。OpenFlow的控制器提前下发流表，将特定DSCP字段的流量PacketIn上去，这样在控制器和交换机都无需付出任何开销的情况下，即可迅速、准确地探测到大象流。这里要注意的是，Mahout只是避免了监测大象流的开销，为了能够调度大象流，控制器还是需要对网络拓扑和链路实时带宽的信息进行监测的。

[1] Curtis A R,Kim W,Yalagandula P.Mahout:Low-overhead datacenter traffic management using end-host-based elephant detection[C]//INFOCOM,2011 Proceedings IEEE.IEEE,2011:1629-1637.

8.2.15　F10

论文[1]中对FatTree拓扑中的连接进行了一些简单的改动，形成了AB FatTree，使得网络在发生故障时能够获得更好的容错性。图8-25所示的叉的为发生故障的交换机，带阴影的为受影响的交换机，而边框加粗的部分是受影响但是能够找到其他可用路径的交换机。在图8-25中左边是FatTree（k=4）的示意图，受影响的交换机共17个，其中具有其他可用路径的仅有8个，AB FatTree的拓扑如右边所示，受影响的交换机共24个，其中具有其他可用路径的有18个。相比之下，在AB FatTree中受到影响的交换机更多，不过大部分都能够找到其他的替代路径进行传输，因而从拓扑的设计来说，AB FatTree的容错性是要好于FatTree的。简单的理解如下：FatTree拓扑中的高度对称的连接，实际上被分成了很多个等价组，一条链路或设备的故障会影响到所有等价组中接近一半的链路和设备。而AB FatTree则在交换机数、端口数不变的情况下，通过兑换不同分组中的某些连接，将连接分为了A类、B类两类等价组，从而降低了在FatTree分组中只有一个等价组所造成的强耦合，使得网络获得了更好的容错性。

F10在路由层面上也针对容错性提出了更为全面的设计，以便发挥出AB FatTree拓扑的优势。F10从下面几个角度，对容错性路由进行了分层次的考虑：

1）交换机本地需要具备快速重路由的能力，在探测到故障后，应自主、尽快地选择可用的下一跳进行转发，即使要以增加路径条数为代价。

图8-25　FatTree（左）与F10提出的AB Fat Tree（右）

2）交换机应该把本地探测到的故障宣告给其他受到间接影响的交换机，从而使它们选择其他交换机进行转发。

3）使用集中式的控制器来调度流量，因为1）的设计主要考虑的是为了尽可能地减少数据包的丢失，而选择在本地进行重路由，但是本地重路由的结果很可能会导致其他链路的拥塞，因此探测到故障恢复后，应该切换回原来的转发路径。

图8-26　F10重路由示例

在图8-26的示例中，v发生了故障，此时u在收到流量后（z-y-u）是没办法直接转给w的（以及左侧POD中的任意目的地）。F10的路由算法（分为three-hop rerouting和five-hop rerouting两种）是让u选择将流量“反射”给x，然后x再通过粗实线标记路径进行迂回式转发。同时u会告诉x和y，下次再收到去往左侧POD的流量时就不要再发给u了。这里面不仅仅是要绕路的问题，如果x继续把左侧POD的流量发给u，由于u不允许从入端口进行转发，那么u就要把流量“反射”给y，这实际上导致（y，u）和（x，u）两条链路，在上下行两个方向上都要承载双倍的负载。当（u，v）链路恢复后，u同样应该把这一情况告诉给x和y，让它们重新利用和u之间的链路。限于篇幅，具体的算法和机制这里就不再深入介绍了。

最后，对于那些长期存在的故障，F10需要依赖于集中式的控制器进行处理。这意味着网络的拓扑结构会发生变化，而F10提出的本地重路由算法+故障宣告都是基于固定拓扑结构的，这就需要有一个集中式的控制器在全局层面上对流量进行调度，从而适应新的拓扑结构。

[1] Liu V,Halperin D,Krishnamurthy A,et al.F10:a fault-tolerant engineered network[C]//Usenix Conference on Networked Systems Design and Implementation.2013:399-412.

8.2.16　DDC

论文[1]中提出了一种新颖的网络路由思想DDC（Data Driven Connectivity），全网中各个节点到某个目的地址的路由库（RIB）可以看作是一个有向无环图（Directed Acyclic Graph，DAG），当DAG收敛时，即意味着对于网络中的任意一条链路来说，去往某个目的地址的流量在该链路上的流量都是单向的流向。当网络发生故障时，无法按照原来的路由进行转发的交换机就将数据包直接“反射”，从而调整有向链路的方向，这样一来通过数据包本身就可以驱动网络重新收敛了。这种Data Driven的路由收敛相比于传统的Link-State协议，不用维护控制平面的复杂状态，大大降低了实现路由重收敛的代价。

图8-27　DDC中的DAG收敛

以图8-27为例，在最初网络中DAG的收敛状态为左半部分，对于目的地址为v的流量，以2的视角来看，链路（2，v）是出向链路，（4，2）和（3，2）是入向链路。由于2上只有（2，v）一条出向链路，因此当（2，v）发生故障后，2会收到4发往v的数据包，之后发现所有到v的可用链路都是入向链路了，因此2认为没有办法帮4转发该数据包，就将数据包“反射”回4（注意这里要和AB FatTree中不反射给入端口进行区别），并将2和4之间链路的状态从“（4，2）入向”转化为“（2，4）出向”。当4收到反射回来的数据包后，它发现在从（4，2）这条到v的出向链路（4本地的视角）上收到了到v的数据包，因此它在本地即可判定2出了问题，于是将2和4之间的链路从“（4，2）出向”转化为“（2，4）入向”。然后4发现自己到v还有另外一个出向链路（4，3），于是4就把这个数据包再发给3，然后以此递推，最后重新收敛出新的DAG。

因此直白地说，DDC是一种link reversal routing链路反向路由的机制。实际上，上面一段的描述只是为了便于读者对DDC的理解，实际上论文是从端口的角度来看待链路状态的，文中为交换机的端口设计了很复杂的状态机来维护DDC。另外，关于DAG初始的生成方法，并没有在论文中进行介绍，读者可参考论文[2]对DAG进行深入研究。

[1] Liu J,Yan B,Shenker S,et al.Data-driven network connectivity[C]//Proceedings of the 10th ACM Workshop on Hot Topics in Networks.ACM,2011:8.

[2] Liu J.Routing Along DAGs[M].University of California,Berkeley,2011.

8.2.17　SlickFlow

在论文[1]提出的SlickFlow中，它利用了OpenFlow控制器来收集拓扑，并集中式地计算出到某个目的地的主路径和备份路径，以源路由的形式将所有的路径信息都封装在header中，然后预先向主路径和备份路径的交换机下发流表，来支持数据平面上的快速路由收敛。

思路很简单，不过实现起来并不容易。图8-28所示为SlickFlow header的示例结构，S_1到Sk是转发路径上“各段”的编码序列，S是Segment的缩写（而非Switch），每个S都表示路径上的“一段”。在编码结构上，每个S的组成都是一样的。以S_1为例，p是主路径的下一跳，length代表该跳上备用路径a的长度，a_1到ak是备用路径上“隔断”的标识序列，若length为0则说明当前节点到目的地没有备用主路径。数据包到来之后，要匹配S_1中的p_1当前是否可用，这里假设p_1可用，那么就将S_1头剥掉然后向p_1转发。p_1收到的数据包最外层暴露的是S_2，于是要匹配S_2中的p_2当前是否可用，这里假设不可用，那么就判断S_2中的length是否为0。如果length为0说明p_1没有可用的备份路径，于是p_1就将数据包丢掉；这里假设length不为0，p_1就可以根据a_1进行转发，并将当前数据包的编码序列（S_2…Sk）替换为（a_2…ak）。a_1收到后再查找a_2中的pa2，并依次向下迭代。

图8-28　SlickFlow header的结构示意图

可以看到，在SlickFlow中最为关键的就是编码序列的生成，和DDC一样SlickFlow在生成编码序列时也利用了DAG的理论。不过和DDC区别的是，SlickFlow是集中式计算DAG结果的，然后以源路由的形式放在了header中，而DDC则是分布式地计算和处理DAG的，并不用添加新的header。

[1] Ramos R M,Martinello M,Rothenberg C E.Slickfl ow:Resilient source routing in data center networks unlocked by openfl ow[C]//Local Computer Networks(LCN),2013 IEEE 38th Conference on.IEEE,2013:606-613.

8.2.18　COXCast

论文[1]提出了COXcast，COXcast是一种基于显式路由+CRT（Chinese Remainder Theorem，）算法的转发框架，能够对组播进行良好的支持。其思路是由源在数据包的某个header里面封装好组播树转发信息，然后交换机根据这个header进行转发。COXCast的运行需要3个参数，第一个是MCID，即为不同的组播组（当组播组里面只有一个时，对于COXCast来说相当于单播）生成的ID。第二个Key是为网络中每个交换机分配的身份标识。第三个是OPB，可以理解为是交换机本地端口在组播组中的分布信息。COXCast使用CRT算法保证了3个参数具有如下的对应关系：

上述恒等式为对于任意一个组播组MCID，假设该组播对应的组播树上有n个交换机，那么对于每个交换机来说MCID对其Key取余数，一定等于其OPB。可以理解为如下形式：源通过某种方式拥有了组播组的全局视图，了解了组播组用户的分布情况，据此计算出了该组播树的对应形态，并记录了组播树中每个交换机的身份（Key），以及这些交换机分别是通过哪些端口接入该组播树的（即OPB），然后根据这些交换机的Key和OPB，通过CRT算法生成了该组播组需要使用的MCID值。

当组播源在转发时，会为每个数据包增加一个COXcast header来携带自己的MCID值，交换机收到数据包后会使用自己的Key值来对MCID值取余，根据上面的公式即可得到OPB，而OPB里面正好携带了本地端口在组播组的分布情况，于是交换机就可以根据取余的结果来进行转发了。在图8-29中有两个组，蓝色虚线为单播，红色的虚线为组播组（host 1，148665）。当host 1发出数据包时将COXCast中的MCID值指定为148665，SW3从端口2收到数据包后使自己的Key=19来对148665取余数得到9（01001）。这个余数即为本地的0号端口和3号端口是组播组的下游端口，于是SW3就可以从端口0和端口3转发数据包，从端口3就可以直接转发给host 16了，而从端口0就会转发给SW1，然后SW1再继续执行相同的逻辑，直到遍历整个组播树。对于单播（host 1，7811）来说，每一跳交换机取余后得到的余数肯定是2的整数次幂，因为OPB中只能有一位为1，这可以保证只从一个端口对单播进行转发。

图8-29　COXCast的转发机制示意图

可以看到，COXcast的工作原理实际上和基于Bloom Filter进行转发的机制类似（见本节中的SiBF）。就是由源在数据包的某个header里面封装好组播树转发信息，然后交换机根据这个header进行转发。不过从交换机的视角来看，COXCast基于显示路由+CRT的实现和基于Bloom Filter的实现机制实际上是两个逆向过程，COXCast中的交换机通过计算可以直接得到需要执行的转发端口，而基于Bloom Filter机制的中交换机要对每个端口依次进行判断，如果判定属于某个集合就进行转发，不属于就不转发。

[1] Jia W K.A scalable multicast source routing architecture for data center networks[J].IEEE Journal on Selected Areas in Communications,2014,32(1):116-123.

8.2.19　Avalanche

论文[1]基于BFS提出了组播树生成算法AvRA，以优化数据中心内组播树的形态。传统的分布式组播协议PIM-SM使用了RP来汇聚组播流量，因此组播树的生成要依赖于RP的单播路由，这往往会导致组播树远远偏离最优的形态。Avalanche使用集中式的OpenFlow控制器来感知组播组用户的接入位置，并且集中式地计算出用户接入组播树的最短路径，据此下发流表来指导组播流量的转发，从而提高组播的效率。

在图8-30中左半部分是PIM-SM以C为RP，为红蓝两个组播组形成的组播树，右半部分是Avalanche为红蓝两个组播组维护的组播树。可以看到，右图中树的形态更为灵活，控制器可以将新加入的组播组用户就近接入到组播树上，组播路径的平均长度得以减小，网络中有更多的链路被选择参与到组播的转发中，而且组播流量也不再以C为单一的汇聚点，这些都有助于减小拥塞发生的可能。

图8-30　PIM-SM中的组播树（左）与Avalanche中的组播树（右）

IBM基于OpenDaylight对Avalanche进行了实现，其模块如图8-31所示。其中，IGMP模块通过PacketIn监听组播组用户的IGMP以便获取用户的接入位置，根据Policy来确定该用户是否可以加入该组播组，加入成功之后向AvRouter更新用户的接入位置，AvRouter结合底层物理拓扑以及当前组播树的形态来计算出用户所在位置到组播树的最短路径，然后通过路由模块生成相应的流表并下发给OpenFlow交换机。

Avalanche主要是从拓扑层面上关注了用户接入组播组时的tree-formulation问题，实际上使用OpenFlow的框架还可以对组播进行很多其他优化，如根据网络中的链路负载，以及流量矩阵的变化对组播树的形态进行动态优化，等等。

图8-31　Avalanche的实现架构

[1] Iyer A,Kumar P,Mann V.Avalanche:Data center multicast using software defined networking[C]//Communication Systems and Networks(COMSNETS),2014 Sixth International Conference on.IEEE,2014:1-8.

8.3　虚拟化

8.3.1　NetLord

目前基于Overlay的虚拟化技术已经广泛地应用于数据中心中，这是业界的一次重大创新。VxLAN向IETF提交第一版草案的时间是2011年8月，同年学术界也有两篇论文，即IBM和HP也都提出了类似的思路，分别称为DOVE[1]和NetLord[2]。DOVE的Overlay实际上就是VxLAN，不过是对标准VxLAN中的保留字段进行了一些私有化，实际上DOVE的特色在于策略。而NetLord对于Overlay的设计则要有趣一些，下面就来看看NetLord的Overlay。

NetLord的数据平面设计，十分新颖地使用了MACin（MAC+IP）的封装，Hypervisor需要部署NetLord Agent（NLA）来提供支持，NLA相当于VxLAN中的VTEP。实际上，基于Overlay来实现多租户，最重要的就是在header中携带两个信息：一个是租户的标识，另一个是目标虚拟机所在的服务器地址。VxLAN用VNI来标识租户，用外层的目的IP来标识目的服务器，而NetLord则使用外层的以太网和IP共同承载了这两个信息，NetLord外层header的设计如图8-32所示。源MAC地址和目的MAC地址分别为源虚拟机所在ToR和目的虚拟机所在ToR的MAC地址，它们用于定位目的虚拟机所在的ToR，其作用相当于VxLAN中的外层源IP和目的IP。也就是说，NetLord要求Underlay是L2的Fabric，为了保证Fabric上的转发效率，NetLord依赖于SPAIN（见本章8.2.7节）作为Underlay的路由机制。

图8-32　NetLord外层header的设计

不过，SPAIN只能把数据包路由到目标虚拟机所在的ToR，而想送到目标虚拟机，还差ToR到NLA这一跳，这一跳的路由SPAIN是不管的，因此NetLord还需要在外层header中告诉ToR从哪个端口可以转发到目标虚拟机所在的NLA，这个端口的信息放在了外层header中目的IP地址的高8位，也就是说NetLord要求ToR具备IP LPM转发的能力（NLA会被分配周知的IP地址port.0.0.1）。目的IP的后24位，NetLord用于存放租户标识Tenant_ID，因此NetLord可以支持1600万租户，这和VxLAN是一样的。外层header中的源IP地址用于存放MAC_AS_ID的信息，一个虚拟机在NetLord中被<Tenant_ID，MAC_AS_ID，MAC>唯一标识，也就是说一个租户可以有两个相同MAC地址的虚拟机，只要它们的MAC_AS_ID是不同的。因此，NetLord允许租户自己去规划MAC地址，这一点是VxLAN所不具备的，不过在实际中这种需求似乎是很少见的。假设ToR已经通过IP LPM把数据包转发给了NLA，NLA会剥掉外层的header，提取其中的Tenant_ID和MAC_AS_ID，然后结合内层数据帧的目的MAC地址转发给目的虚拟机。上面的表述比较抽象，可以结合图8-33进行理解。

除了MAC和IP以外，图8-32中的另外几个字段也都需要介绍一下。由于NetLord依赖于SPAIN作为底层的承载网络，因此外层header中的VLAN ID可以实现SPAIN的多路径，类似于VxLAN使用源端口号在ECMP上进行负载均衡。另外，由于出口需要进行IP LPM routing来向NLA转发，所以在此之前会终结掉外层header中的VLAN，而这个VLAN对于NLA来说还是有用的（用于监测SPAIN的状态），因此NetLord使用IP Identification来存放外层header中VLAN的信息，使得NLA收到数据包后仍能够提取到该信息。由于使用了IP Identification来承载VLAN信息，NetLord要求网络不能对数据包分片，因此DF字段需要置位。

图8-33　NetLord的转发示意图

NetLord对分布式路由也进行了设计。NetLord会维护虚拟路由器的IP和MAC间的对应关系，当发现数据包的目的MAC地址是虚拟路由器的MAC地址时，NLA会先完成路由再封装外层的header。外网的Tenant_ID被规定为2，租户可以通过它来访问Internet，租户间的通信也需要通过外网进行中继。

在控制平面上，NL-ARP Subsystem维护着所有虚拟机的位置信息，即<Tenant_ID，MAC_AS_ID，MAC>和<IP，Egress Switch MAC，Port>的映射关系。默认采用的是预置式的同步方式，当有新的虚拟机上线或者虚拟机发生迁移时，NL-ARP Subsystem会将新的映射关系推送给各个NLA。NLA会将这些关系缓存在本地的NL-ARP表中，以完成ARP代理和隧道封装的功能。

[1] Cohen R,Barabash K,Rochwerger B,et al.DOVE:Distributed Overlay Virtual nEtwork Architecture[J].Ibm Corporation,2007.

[2] Mudigonda J,Yalagandula P,Mogul J,et al.NetLord:a scalable multi-tenant network architecture for virtualized datacenters[J].Acm Sigcomm Computer Communication Review,2011,41(4):62-73.

8.3.2　FlowN

论文[1]提出了FlowN，在OpenFlow的控制器里面做了一层Application Virtualization，不同的租户可以把自己的OpenFlow应用运行在Application Virtualization之上。对于租户的OpenFlow应用来说，它们只能看到自己的虚拟网络，这个虚拟网络是由Application Virtualization对物理网络抽象得到的。因此，FlowN本质上是一个基于多租户框架的OpenFlow控制器。

都是对多租户进行抽象，FlowN和OpenStack Neutron有什么区别呢？OpenStack Neutron为租户应用提供的是北向的业务API，如什么时候创建一个subnet，什么时候创建一个vRouter，什么时候创建一个FW，至于subnet、vRouter、FW是如何工作的，对于租户来说是完全透明的。而FlowN为租户应用提供的则是原生的OpenFlow API，租户可以开发自己的应用来控制subnet怎么连通，vRouter如何路由，FW上的ACL怎么写，甚至可以定制自己的逻辑网络拓扑。如果一些租户不想关心底层的网络是如何实现的，FlowN会为它们运行一个默认的，来保证虚拟网络的基础连通性，此时从租户的角度来看，FlowN扮演的角色和Neutron就是一样的了。

因此，Application Virtualization可以理解为是OpenFlow的一个Hypervisor，它只负责对OpenFlow信令中的一些字段进行映射，以保证租户通过OpenFlow API只能看到自己虚拟网络中的vDpid、vPort，等等；而交换机看到的则是物理网络上的pDpid，pPort，等等。映射的工作原理这里先绕过去不讲，请参阅本节后面对FlowVisor、ADVisor、OpenVirtex的相关介绍。FlowN相比于它们的主要区别是通过VLAN来标识租户，而且同一个租户的VLAN标识在路径上是逐跳变化的，以解决VLAN只能支持4K租户的问题，这十分类似于MPLS在不同链路上对标签进行复用。

在实现上，租户的OpenFlow应用以container的形态运行在FlowN上，每个租户都有自己的线程池。Application Virtualization收到物理网络的OpenFlow消息后，首先完成映射，找到触发这条OpenFlow消息的租户，调用对应线程的接口，再把映射后的消息传递给租户的应用。为了实现分布式与持久化，FlowN使用了关系型数据库来存储租户虚拟网络和物理网络间的映射关系，多个FlowN实例间通过数据库连通，不同租户的应用可以运行在不同的FlowN实例上以实现负载均衡。由于Application Virtualization每次在映射时，都要读取数据库中的映射关系，因此FlowN在数据库旁边会挂一个memcached，以实现映射关系的读优化。其实现架构如图8-34所示。

图8-34　FlowN的实现架构

[1] Drutskoy D,Keller E,Rexford J.Scalable network virtualization in software-defined networks[J].IEEE Internet Computing,2013,17(2):20-27.

8.3.3　FlowVisor

论文[1]提出的FlowVisor也是OpenFlow的Hypervisor，和FlowN将租户的应用嵌在控制器里面不同，在FlowVisor的架构中租户有自己独立的控制器。FlowVisor是最早提出SDN HyperVisor设计思路的，实际上FlowN也是在FlowVisor之后提出的。之所以把FlowN放在FlowVisor前介绍，是因为FlowN在架构上更接近于业界已经落地的SDN方案，读者可能更容易理解一些。

FlowVisor作为透明的代理，工作在OF交换机和租户的OpenFlow控制器之间。对于OF交换机来说，FlowVisor是一个特殊的控制器；从各个租户控制器的视角来看，FlowVisor则相当于一个物理的OpenFlow网络。FlowVisor负责截获OF物理交换机产生的信令，并根据流量的特征和各个租户的匹配规则，将信令送给相应的控制器。另外，租户控制器产生的信令也会首先经过FlowVisor，FlowVisor对信令进行修改和约束后再分发给相应的物理OF交换机。FlowVisor通过对OpenFlow消息的劫持、修改、分发等操作，使租户的控制器只能看到FlowVisor向它提供的虚拟网络视图，从而实现了租户间的隔离。其工作原理示意图如图8-35所示。

图8-35　FlowVisor的工作原理简要示意

OpenFlow v1.0提出了基于12元Tuple的匹配方式，包括从一层的物理端口到四层的应用端口等12个字段都可以用来匹配数据流，而且提供了相对丰富的处理动作。FlowVisor允许将这些字段（除了VLAN优先级字段）开放给管理员灵活地制订流量识别策略，其粒度可粗可细。一个策略称为一个Flowspace，一个租户可能对应着多个不连续的Flowspace。与VxLAN基于VNI专用字段来标识租户的方式不同，FlowVisor直接根据流量的特征识别租户，并不对流量进行附加标识。FlowVisor的Flowspace机制虽然灵活，但存在着以下两个问题：①租户的地址空间无法复用，如当不同租户需要重叠IP地址时很可能会发生匹配规则冲突。②租户的控制器很可能会改变Flowspace中的某些字段，FlowVisor是不会对这些动作进行检查的，这样一来租户的流量很可能在某个地方发生了泄露，即租户A的流量被识别为租户B的流量，导致租户网络安全性的降低。

在实现租户标识的基础上，FlowVisor会对虚拟节点上的资源包括端口、CPU、转发表进行隔离。控制器与交换机握手时，FlowVisor对SwitchFeatures消息中物理OF交换机上的端口号进行合理的过滤后送给控制器，控制器只能看到属于该虚拟网络的端口。控制器在下发包括泛洪动作在内的流表时，FlowVisor会将该动作映射为多条流表，只从属于该虚拟网络的端口上进行转发。CPU的隔离实现起来相对复杂，FlowVisor给出了节约物理交换机上CPU资源的一些思路，希望通过保证CPU有轻负载来间接地保证虚拟节点间的CPU隔离。转发表的隔离对于FlowVisor来说，是自然实现的，只要各个租户的Flowspace不发生冲突，转发表就不会出现冲突的情况。FlowVisor在一定程度上实现了虚拟资源的隔离，但是FlowVisor并没有对虚拟节点进行显式的抽象。一方面，这使得租户无法直接管理其虚拟节点，另一方面租户控制器看到的dpid和port id很可能是不连续的，这不符合虚拟化中透明性的原则。虽然FlowVisor并没有显式地抽象虚拟节点实例，但在一定程度上可以说它支持了“一虚多”的模型，但对于“多虚一”的场景FlowVisor并不支持。

租户被分配到不同的虚拟节点上，在物理上可能是部署在不同物理交换机上的，也可能是部署在相同物理交换机上的，虚拟链路技术负责连接这些分布式的虚拟节点。当不同的虚拟节点部署到不同的物理交换机时，需要通过实际的物理链路来模拟虚拟链路。FlowVisor的一个明显缺点是一条虚拟链路只能对应一条物理链路，无法跨越多个物理交换机实现虚拟链路，也意味着租户的逻辑拓扑只能是物理拓扑的一个子集，从而无法实现拓扑的任意映射。比如说在租户的逻辑拓扑中相邻的虚拟交换机VOF1和VOF2，分别部署到OF1和OF2中，则FlowVisor要求OF1与OF2要在物理上是直连的，这极大地限制了部署的灵活性，也会导致FlowVisor无法实现虚拟链路的备份和重收敛。当不同的虚拟节点部署在相同的物理交换机中时，虚拟链路的实现要求设备能够抽象出一个类似于“环回”的逻辑端口以便在不同的虚拟节点间进行连接，FlowVisor并不支持这种机制。

虽然FlowN在架构上“更接地气”，不过学术界却更青睐FlowVisor这种更为新颖的虚拟化架构，后续很多的论文都延续FlowVisor的思路，对SDN HyperVisor进行了逐步深入的研究。

[1] Sherwood R,Gibb G,Yap K K,et al.Can the production network be the testbed?[C]//OSDI.2010,10:1-6.

8.3.4　ADVisor

论文[1]提出了ADVisor（Advanced FlowVisor），它解决了FlowVisor不能支持拓扑任意映射的问题。拓扑任意映射场景如图8-36所示，物理拓扑中有6台交换机，在左边这种映射机制下，逻辑拓扑只是物理拓扑的一个子集，逻辑拓扑中相邻的虚拟节点在物理上必须相邻；而右边的映射机制则没有这种限制关系，虚拟链路的实现依赖于沿途交换机的透明转发。相比较而言后者能够更加灵活地调度底层的网络资源，如租户需要一个由3节点形成的环状拓扑，左边的机制便无法实现，右边的机制便可以虚拟出物理网络中不存在的虚拟链路来满足该需求。FlowVisor没有这么灵活的虚拟链路技术，它只能实现左边这种映射，而ADVisor针对这一点进行了改进，对于实现右边场景中虚拟化需求提供了支持。不过ADVisor只允许通过手动配置来指定映射关系，仍然不够灵活。

图8-36　拓扑的任意映射

ADVisor同样位于控制器和交换机间作为透明代理，通过网络管理者手动配置的Virtual topology configuration来指导各个租户网络中虚拟节点和虚拟链路的生成与维护。相比于FlowVisor，ADVisor增加了3个子模块：Port Mapper、Topology Monitor和Link Broker，它们根据Virtual topology configuration的映射信息，共同实现了虚拟化的映射工作。

ADVisor没有沿用FlowVisor的Flowspace机制来识别租户，而是要求将主机（虚拟机）的接入端口与某一租户绑定，该主机的所有流量在网络入口处会打上slice_tag_si的标签，在传输过程中该标签就替代了FlowVisor的Flowspace规则，唯一地标识了该流量所属的租户，流量被送到目的地前需要剥掉该标签。考虑到OF协议的版本限制，ADVisor采用了12位VLAN ID中的一部分位作为slice_tag_si来标识租户网络。这种做法虽然不如FlowVisor灵活，但是有效地解决了在FlowVisor中租户间流量可能会发生泄露的问题，其代价就是在单级VLAN技术条件下，不允许租户通过VLAN字段来标识业务，另外租户网络的上限数量也必然受到VLAN字段位数的限制。

ADVisor通过租户拓扑的配置文件来记录物理交换机与虚拟节点端口号之间的映射关系，租户的控制器不会看到不连续的端口号。这实现起来并不复杂，但是相比于FlowVisor而言已经有了明显的提升。ADVisor也没有支持虚拟节点的“多虚一”模型。

虚拟链路技术的实现，依赖于对经过虚拟链路的流量的标识，以便沿途的交换机能够直接转发流量，而不再上报给租户的控制器。同样由于OpenFlow v1.0协议的限制，ADVisor中使用VLAN字段的一些位作为slice_tag_vl去标识虚拟链路上的流量。VLAN字段总共有12位，网络的管理者需要合理地分配给slice_tag_vl和用于标记租户网络的slice_tag_si，slice_tag_si和slice_tag_vl组合在一起形成slice_tag，它唯一地标识了某个租户网络中的某一条虚拟链路。只有虚拟链路两端连接了虚拟节点才能够与租户的控制器进行交互。物理交换机将流量上报时，ADVisor中的Topology Mionitor根据slice_tag判断该物理交换机是虚拟节点还是虚拟链路上的沿途交换机，前者经Port Mapper映射后交给租户的控制器来处理，后者则由Link Broker直接完成透明转发。这样一来在对租户控制器透明的情况下，模拟了虚拟节点间的邻接关系，实现了虚拟链路的功能。ADVisor中的这些映射关系都需要手动进行配置，这会导致网络状态发生改变时，虚拟链路的调整不够灵活，难以实现动态优化和路径的Fail-Over机制。

[1] Salvadori E,Corin R D,Broglio A,et al.Generalizing virtual network topologies in OpenFlow-based networks[C]//Global Telecommunications Conference(GLOBECOM 2011),2011 IEEE.IEEE,2011:1-6.

8.3.5　VeRTIGO

VeRTIGO[1]是在ADVisor的基础上开发的一款SDN HyperVisor。相比于ADVisor，VeRTIGO能通过算法动态地进行映射，支持虚链路的动态优化和Fail-Over机制。VeRTIGO还支持节点虚拟化的“多虚一”模型，允许将多台物理设备抽象为租户网络中的一台逻辑设备，如图8-37所示。

图8-37　VeRTIGO能够支持拓扑的任意映射以及节点的“多虚一”模型

VeRTIGO虽然是基于ADVisor进行开发的，但是其流量识别和租户网络标识技术却使用了FlowVisor的Flowspace机制，并未实现ADVisor的slice_tag_si。

VeRTIGO实现了虚拟节点的“多虚一”模型，当租户提出对于逻辑网络的链路带宽等指标需求后，VT Planner会进行自动映射，并通过Node Virtualizer模块自动地封装出一个虚拟的Abstract Node。这个唯一的虚拟节点将代替底层的物理设备与控制器进行交互，包括握手、SwitchFeatures等消息都由其代理，控制器向Abstract Node下发FlowMod和PacketOut消息，也将经过映射并分发给对应的物理OF交换机。一个Abstract Node往往对应多个物理交换机，跨越多条物理链路，它们可分别看作Abstract Node的接口线卡和内部背板走线。基于上述思路，VeRTIGO实现了虚拟节点的“多虚一”模型。“多虚一”模型的部署，涉及流表的放置问题，本文不再对这些算法进行分析，感兴趣的读者可以参考论文[2]和论文[3]。

相比于ADVisor手动配置进行映射，VeRTIGO采用了VT Planner进行自动化映射。ADVisor采用VLAN字段中的部分比特位作为slice_tag_vl去标记虚拟链路上的流量，这限制了用户对于VLAN字段的使用。当VeRTIGO在VT Planner中映射了租户的逻辑拓扑后，会存储流经虚拟链路的Header Sequence，当这些流量从沿途交换机中进行上报时，VeRTIGO识别出Header Sequence并直接代理租户的控制器向该交换机下发流表以进行透明的转发。通过算法动态地进行映射的另一个好处，就是能够增强HyperVisor的健壮性，通过检测链路的实时负载，VeRTIGO可以自动进行虚拟链路映射的动态优化，它也支持Fail-Over机制。

[1] Corin R D,Gerola M,Riggio R,et al.Vertigo:Network virtualization and beyond[C]//Software Defined Networking(EWSDN),2012 European Workshop on.IEEE,2012:24-29.

[2] Kanizo Y,Hay D,Keslassy I.Palette:Distributing tables in software-defined networks[C]//INFOCOM,2013 Proceedings IEEE.IEEE,2013:545-549.

[3] Kang N,Liu Z,Rexford J,et al.Optimizing the one big switch abstraction in software-defined networks[C]//Proceedings of the ninth ACM conference on Emerging networking experiments and technologies.ACM,2013:13-24.

8.3.6　OpenVirteX

论文[1]提出的OpenVirteX，也是一个基于FlowVisor研发的SDN Hypervisor，其实现架构如图8-38所示。OpenVirtex具备以下特点：①为租户提供彼此隔离的虚拟SDN网络（vSDN），允许租户自定义拓扑与网络编址方案。②显式地为vSDN抽象出虚拟节点，允许租户直观地对虚拟节点进行管理。③借助后端数据库对网络状态进行实时的记录，具备了对租户网络执行快照的能力。

OpenVirteX在接入交换机时，根据端口号和MAC地址识别主机所属的租户，流量经过交换机后，其源MAC地址将被OpenVirteX重写。重写后的源MAC地址的前24位为OpenVirteX从IEEE中申请得到的保留OUI，后24位将携带着租户网络的标识信息在数据平面上传输。传输途中的虚拟节点将根据数据包的源MAC地址来识别租户的流量，并送给相应的租户控制器。在目的主机所在的出口交换机上，源MAC地址将回写源主机的MAC地址。相比于FlowVisor的Flowspace机制，OpenVirteX通过重写MAC地址解决了租户地址空间无法复用的问题。相比于ADVisor使用VLAN的部分位来标识租户的机制，OpenVirteX则开放了租户在逻辑网络内部使用VLAN区分业务的能力。另外，在接入/出口交换机上，OpenVirteX还会重写/回写源IP地址和目的IP地址以显式地支持IP地址的复用。

从租户的角度来看，逻辑网络中的设备应该拥有独立的管理方式，这需要SDN Hypervisor能够对虚拟交换机进行完整的抽象，实现模拟数据包缓存和虚拟流表匹配等功能。OpenVirteX首次对虚拟节点进行了显式的抽象，租户能够直接对其进行管理，这就形成了较为完整的虚拟网络子层。在此基础上，OpenVirteX将租户的控制器投放用于拓扑探测的LLDP包限制在了虚拟网络子层内部，这可以避免将这些数据包投放到真实的设备中，节约了数据平面的带宽资源。OpenVirtex对“一虚多”和“多虚一”两种模式都提供了良好的支持。在“一虚多”模型中，一台物理交换机可以抽象为多个不同的虚拟节点，包括虚拟设备的标识和虚拟端口的端口号，这些对于租户来说都是连续而完整的。在“一虚多”模型中，OpenVirteX支持将多个物理交换机模拟到一个逻辑OpenVirtexBigSwitch中，通过手动配置或者最短路径计算来模拟OpenVirtexBigSwitch的内部背板走线。

图8-38　OpenVirtex的实现架构

与租户网络标识技术类似，OpenVirteX通过对目的MAC地址进行重写来标识虚拟链路以及该虚拟链路所承载的流量。具体重写规则如下：重写后的目的MAC地址的前24位为OpenVirteX从IEEE中申请得到的保留OUI，后24位将携带着vLink ID和Flow ID的信息在数据平面上传输。这里的Link ID即为该租户网络中虚拟链路的标识，而Flow ID不会对应某一个流表，而是记录着该次通信的源主机和目的主机的MAC地址，以便在出口交换机上进行主机MAC地址的回写。接入交换机重写后，源MAC地址可以用来区分不同租户的流量，这在传输过程中不会改变。目的MAC地址中的vLink ID用来标识流量传输到了哪一条虚拟链路上，它每经过一个中继虚拟节点后改变一次。Flow ID则标识了该流量的源主机、目的主机的MAC地址，以便在出口交换机处进行主机MAC地址的回写。OpenVirteX可以支持虚拟链路的Fail-Over机制。在实现虚拟链路时，OpenVirteX可以备份一条次优路径，当主用虚拟链路经过的物理节点或者物理链路失效时，OpenVirtex自动地将该虚拟链路映射到备份路径上。当失效点恢复时，OpenVirtex将重新切换到原来的最优路径。尽管OpenVirtex对于虚拟节点进行了较为完整的抽象，也有着新颖的虚拟链路技术，但是它也没有支持在同一租户中不同的虚拟节点部署在同一物理交换机下的场景。

[1] Al-Shabibi A,De Leenheer M,Gerola M,et al.OpenVirteX:Make your virtual SDNs programmable[C]//Proceedings of the third workshop on Hot topics in software defined networking.ACM,2014:25-30.

8.3.7　CoVisor

论文[1]提出的CoVisor，是以OpenVirtex为原型开发的SDN Hypervisor，设计架构如图8-39所示。其中不同租户的控制器负责实现不同的网络服务，租户通过编写CoVisor API制订其逻辑网络的拓扑。当由不同租户控制器下发的流表分发到同一台物理OF交换机中时，很可能出现无法协同工作的情况，CoVisor的设计目标是在提供虚拟拓扑的基础上，对由不同租户控制器下发的流表进行重新编译，以协调它们对网络的控制逻辑，同时监测各控制器的行为以防止其操作越界。

CoVisor同样支持使用FlowVisor的Flowspace机制，可以根据流量的特征对租户网络进行识别。在虚拟链路技术上，CoVisor也利用了OpenVirtex的现有机制。由于CoVisor实现了协调策略，因此具备对流表进行聚合的能力，支持将同一租户网络中的多个虚拟节点部署到同一台物理交换机中，这相比于OpenVirteX是一个明显的改进。下面通过一个实例对此进行分析。如图8-39a中所示，在一台物理OF交换机S中为一个租户网络抽象出了3台虚拟交换机A、B、C，彩色的链路和端口反映了映射关系，黑色端口和链路是CoVisor虚拟出来的，不对应任何底层的资源，它连接了AB与BC。图8-39b展示了这3台虚拟交换机中的路由流表，为了在S上实现正确的转发，CoVisor要对这些流表项进行聚合。以端口1进入S的流量为例，如果其目的地为2.0.0.0/16，聚合后将由端口2送出；如果其目的地为1.0.0.0/24，聚合后将由S的端口3送出；如果其目的地为1.0.0.0/8，聚合后将修改目的IP为2.0.0.0后由S的端口4送出。为了在S中实现这一转发逻辑，CoVisor将模拟出由端口1进入，送往不同目的地IP地址的包，并由A、B、C中的虚拟流表进行处理从而得到完整的传输路径，并据此对虚拟流表进行聚合，得到最终需要下发到S中的3条流表，如图8-39c所示。

CoVisor对于虚拟节点中流表的隔离也做了改进性的工作，通过对不同租户控制器开放不同的权限，限制了它们对匹配域的匹配（如负责MAC自学习控制器下发的流表只允许其匹配MAC地址），以防止租户控制器的行为越界。

除了CoVisor外，论文[2]提出的AutoSlice，也支持将同一租户网络中的多个虚拟节点部署到同一台物理交换机中。不过，AutoSlice是利用交换机的本地环回端口+recursive Pipeline来实现的，这和CoVisor通过控制平面实现流表的聚合有着很大的不同。

图8-39　CoVisor支持将多个虚拟节点部署到同一台物理交换机中

本节选取的论文都偏向于工程实现，除此之外还有很多论文从理论上对网络虚拟化进行了研究与探讨。网络虚拟化中一个很重要的问题，是关于如何形成虚拟网络资源和物理网络资源间的映射关系，学术界称其为VNE（Virtual Network Embedding），可以参见相关的经典论文。由于VNE相关论文都过于算法化，因此本小节不再对这部分论文进行介绍。

[1] Jin X,Gossels J,Rexford J,et al.CoVisor:A Compositional Hypervisor for Software-Defined Networks[C]//NSDI.2015,15:87-101.

[2] Bozakov Z,Papadimitriou P.Towards a scalable software-defined network virtualization platform[C]//Network Operations and Management Symposium(NOMS),2014 IEEE.IEEE,2014:1-8.

8.4　服务链

8.4.1　pSwitch

论文[1]提出的pLayer，其主要思路是将服务节点旁挂在改造后的二层交换机（pSwitch）上，并通过控制器来集中地分发服务链策略，引导pSwitch中流量的转发。pSwitch的设计如图8-40所示，它有两个主要的模块：Switch Core保留了普通交换机MAC学习转发的能力，Policy Core为新增模块负责服务链策略的执行。其中，Rule Table存储着服务链流量的匹配规则（主要支持IP五元组），对于每个连接主机或者服务节点的接口，Policy Core还设计了InP、OutP和Fail Detect组件用于执行服务链的转发。

图8-40　pSwitch的设计

pLayer对于服务链流量的典型转发流程如下所示：当流量通过inf A进入pSwitch时，inf A的InP组件将流量与Rule Table中的规则进行匹配，以识别流量所属的服务链（可以理解为NSH中的SPI），并根据流量的源MAC地址（或者Port/VLAN）来判定流量当前在服务链中的位置（可理解为NSH中的SI），然后根据上述两个信息封装MACinMAC，其外层目的MAC地址为下一跳服务节点的MAC地址，并发送给Switch Core。Switch Core进行正常的MAC自学习，若下一跳服务节点旁挂在本地的inf B中，则inf B完成解封装并送出，若下一跳服务节点不在本地则不经过解封装处理，直接发往下一跳服务节点所在的pSwitch。服务链路上的pSwitch依次迭代上述过程，直到最后一个服务节点处理结束，其所在的pSwitch会去掉外层封装并送给目的主机。

上述转发机制还需要进行如下说明。

1）各个服务节点的MAC地址需要集中进行分配。

2）工作在三层的服务节点（如路由器、负载均衡器），在处理完流量后源地址自然被改写为服务节点的MAC地址，可用于InP的判定。

3）另外一些服务节点（防火墙、IPS）对于流量是透明的，本身不会改写MAC地址。当这些服务节点直接旁挂在pSwitch上时，InP可通过Port/VLAN来判定，如果它们通过普通交换机中继到pSwitch时，InP只能通过源MAC地址来判定，此时需要有一个SRCMACREWRITER模块代理它们进行源MAC地址的改写。

4）每一种服务节点可以有多个实例，不同流之间可通过哈希实现负载均衡，对同一条流的哈希需要保持双向一致，避免出现路径不对称的情况。

5）当路径上存在LB/NAT这些会改写IP地址的服务节点时，同样可能会导致上下行路径不对称，因此pSwitch规定Rule Table中的规则不可以携带可能会被服务节点改写的IP地址。

6）Fail Detect组件通过ICMP探针、TCP状态检查、SNMP Trap等方式监视服务节点的状态，当服务节点出现问题时能够及时进行路径切换。

除了设计上述转发机制以外，由于采用了集中式控制，pLayer还考虑了一些pSwitch上服务链策略一致性的问题。为保证同一服务链上各个pSwitch策略的一致性，控制器需要先确认所有相关的pSwitch已经收到了该服务链的策略，然后再下发一个adpot信令使得pSwitch的策略在数据平面上生效。另外，考虑到Service Sequence互为回环的新老策略可能造成产生的Bypass问题，pLayer规定Service Sequence互为回环的新老策略必须使用不同的服务节点实例。

[1] Joseph D A,Tavakoli A,Stoica I.A policy-aware switching layer for data centers[C]//ACM SIGCOMM Computer Communication Review.ACM,2008,38(4):51-62.

8.4.2　FlowTags

论文[1]提出的FlowTags，主要为了解决服务链实现中的一些问题：一些服务节点会改写流量的字段（如LB、NAT会改写IP和端口），很有可能会造成交换机无法识别流量特征；一些服务节点对于流量的处理，依赖于之前服务节点的处理结果；交换机的转发可能依赖于之前服务节点的处理结果（如经Light IPS初步确定可疑的流量后需要转发给Heavy IPS，安全流量则不必转发给Heavy IPS）。产生上述情况的根本原因在于SDN控制器无法动态地获知服务节点的处理结果，因此FlowTags汲取了OpenFlow集中式控制交换机的思想，提出了一套服务节点的primitive API（FlowTags API），并将OpenFlow和FlowTags API集成在控制器中实现。控制器作为服务链实现的大脑，通过集中、动态、逐跳地为服务链流量分配Flow Tag，服务节点和交换机将根据Flow Tag对流量进行处理和转发，其架构如图8-41所示。

图8-41　FlowTags的架构示意图

Flow Tag作为服务链处理的唯一标识，需要承载的信息包括：流量的原始特征、之前服务节点的处理结果（可看作context）、流量的SFC路径信息。前两者用于指导后续服务节点的处理，最后一个用于指导OpenFlow交换机转发流量。可见，Flow Tag对于整个系统的实现至关重要，相比于pSwitch通过隧道的外层包头来承载服务链信息，Flow Tag选择改写已有字段（如MAC），或者使用已有的保留字段（如TOS、VLAN等）来承载上述信息，文章中并没有规定如何对这些字段进行编码。

FlowTags API的设计类似于OpenFlow，主要以触发式的方式实现对服务节点的控制。当服务节点MB1初次看到流量（尚未携带Flow Tag）并完成处理后，通过FT GENERATE QRY消息向控制器请求生成Flow Tag，控制器记录流量的原始特征、MB1的处理结果，并通过FT GENERATE RSP回复消息给Flow Tag，该服务节点封装Flow Tag字段，并发送给OpenFlow交换机S1。S1初次看到流量，通过PacketIn将消息上交控制器，控制器根据Flow Tag解析记录流量的原始特征和之前服务节点的处理结果并决定S1的转发行为，然后下发FlowMod消息匹配Flow Tag并发向下一跳服务节点MB2。MB2初次看到携带有Flow Tag的流量，通过FT CONSUME QRY消息请求消费Flow Tag，控制器返回FT CONSUME RSP消息回复给Flow Tag对应流量的原始特征和之前MB1的处理结果，MB2根据FT CONSUME RSP返回的信息进行处理，然后通过FT GENERATE QRY消息向控制器请求生成新的Flow Tag，依次迭代，直至最后一跳的交换机将Flow Tag“清零”，然后送给目的节点。其生成和使用过程如图8-42所示。

图8-42　Flow Tag的生成（左）和使用（右）

Flow Tag是一套十分动态、灵活的SFC控制机制，但是由于触发式设计，会导致控制平面有很大的开销，所以流量首包的延时会很明显。实际上FlowTags在2013年最初被提出时[2]，采用的是预置方式（proactive）来端到端地分配Flow Tag。不过由于在预置方式中Flow Tag无法携带动态的context信息，从而难以解决之前提到的一些问题，后来便主要采用了触发式设计。不过FlowTag API仍然为用户保留了预置式控制的能力。

[1] Fayazbakhsh S K,Chiang L,Sekar V,et al.Enforcing Network-Wide Policies in the Presence of Dynamic Middlebox Actions using FlowTags[C]//NSDI.2014,14:533-546.

[2] Fayazbakhsh S K,Sekar V,Yu M,et al.Flowtags:Enforcing network-wide policies in the presence of dynamic middlebox actions[C]//Proceedings of the second ACM SIGCOMM workshop on Hot topics in software defined networking.ACM,2013:19-24.

8.4.3　Simple

论文[1]提出了Simple，其设计也着眼于解决以下问题：IP五元组无法携带流量在当前服务链中的位置信息；在考虑服务节点CPU和交换机可用TCAM的前提下，在多服务节点实例间进行负载均衡；服务节点修改包头后可能会使得数据平面策略发生混淆。Simple的架构如图8-43所示，数据平面使用标准的OpenFlow交换机，服务节点也不用进行任何改动，控制器主要包括3个模块：Resource Manager负责接收服务链策略，并根据策略的约束，以及当前的资源状态（如各服务节点CPU、各交换机可用TCAM）等运行负载均衡算法，得到当前最优的服务节点实例和转发路径；Dynamics Handler用于分析某些服务节点对包头的改写；Rule Generator根据Resource Manager和Dynamics Handler的处理结果，下发流表标记、转发服务链流量。

Simple通过Rule Generator向OpenFlow交换机下放流表，它主要基于预置的方式，通过运行负载均衡算法提前生成服务链路径，并向相关交换机下发流表。以图8-44为例分析流量在服务链“FW-IDS-Proxy”上的转发流程：流量从S2的入端口流入服务链路径，S2通过基础字段（如IP五元组）识别流量并转发给FW，FW完成处理后送回S2中，S2为流量打ProcState标签标记上一跳为FW（VLAN/TOS），并封装S2与S5间的隧道转发给S4。S4根据外层包头直接转发给S5，S5收到后识别标签为FW，转发给下一跳的IDS，IDS处理后送回S5，S5为流量更新标签并标记上一跳为IDS，并通过隧道送回S2。S2识别标签为IDS，转发给Proxy，Proxy处理后送回S2，S2为流量更新标签并标记上一跳为Proxy，封装隧道发送给S5。S5识别标签，发现Proxy已经处理完毕，直接从出端口将流量发向最终目的地。

图8-43　Simple的架构

图8-44　Simple的转发机制

对于上述转发过程，需要进行如下说明。

1）对于数据平面的处理都发生在交换机上，服务节点不需要进行任何修改。

2）文中没有说明如何获取服务节点的状态，这可通过SNMP等传统协议完成。

3）上述例子中的流量在IDS-Proxy时发生了迂回，这是由于Simple的负载均衡算法有多个维度的参数，不是单纯基于最短路径的。

4）虽然Hop By Hop也是可行的，但是使用隧道使得S4不用维护服务链的策略，从而节约了S4上的流表资源。

5）示例中的ProcState标签仅用于标记上一跳服务节点，不携带服务链ID，因此可能需要通过IP五元组来识别流量所属的服务链ID。不过文章中并没有限定ProcState标签的生成算法，也有可能通过ProcState的部分位来承载服务链ID等信息。

由于LB、NAT等服务节点可能会重写包头，所以通过五元组来识别服务链ID，则改写后交换机很有可能无法识别流量。Simple通过Dynamic Handler模块来处理这种情况，控制器向此类服务节点的接入交换机下发流表，通过PacketIn收集一段时间内流入、流出此类服务节点流量的首包，并通过算法从payload层面分析流入、流出流量的相似度，从而近似地得到流量经过服务节点处理前后的对应关系。Dynamic Handler会将这种对应关系通知给Rule Generator，用于生成流表使得交换机能够将改写前后的流量识别为同一服务链ID。当然，Dynamic Handler的这种近似处理其精确度是有一定限制的，其根本原因是Simple将服务节点当作了黑盒，而FlowTags能够精确处理的代价是需要对服务节点进行改造以支持FlowTags API。

[1] Qazi Z A,Tu C C,Chiang L,et al.SIMPLE-fying middlebox policy enforcement using SDN[J].ACM SIGCOMM computer communication review,2013,43(4):27-38.

8.4.4　StEERING

论文[1]中提出的StEERING，主要是为了在运营商的服务交付网络（service delivery network）中实现服务链，同时也可以用于数据中心内部。Service delivery network一侧是接入、汇聚网络引入用户（Subscriber）的流量，另一侧为核心网连接互联网服务（Application）。Service delivery network中有多类服务节点，每类服务节点可以有多个实例，各实例都需要通过OpenFlow交换机接入service delivery network。

StEERING采用多级流表来引导流量在服务链路上进行转发，并使用metadata来标记流量在服务链路上的状态。其中，metadata的最高位表示流量的流向，其余位则表示各类服务节点是否已完成了流量的处理。Metadata总共有64位，StEERING并没有规定metadata各位的分配规则，可以使用一位代表一类服务节点，这样在service delivery network中最多可以支持64-1=63类服务节点，但是每类节点只能有一个实例。考虑到需要在各类服务节点的多个实例间进行负载均衡，所以metadata中可以用N位表示2N-1个实例，因为其中1位需要表示该类服务节点是否已经完成了对流量的处理。其使用过程如图8-45所示。

图8-45　StEERING对Metadata的使用

StEERING流表的设计如图8-46所示。Direction Table根据入端口判断流量的方向，若为上行（从用户流向应用），则置metadata的最高位为1，若为下行（从应用流向用户）则置metadata的最高位为0。Subscriber Table、Application Table分别匹配源IP、目的IP和端口号来判断流量所属的服务链ID，并根据依次流经的服务节点，把它们在metadata中对应的位置1。Path Status Table则用于标记当前流量在服务链中所处的位置，根据流量的入端口修改metadata，将已完成处理的上一跳服务节点在metadata中对应的位置0。Next Dst Table则根据当前metadata中最高的“1”位，判定下一跳服务节点并改写目的MAC地址，并交给MAC Table进行转发。

图8-46　StEERING中流表的设计

上述机制仍需要进行如下说明。

1）上述各个流表中的表项都是静态生成的，服务链流量的源、目的特征分别由Subscriber Table、Application Table进行识别，之所以拆分成两张表是为了节约表项。

2）某些服务链流量可能无法从IP和端口号层面识别出来，可能需要通过DPI对流量进行高层payload的分析，因此这类流量的识别流表只能动态下发，Microflow Table就是用于存放此类流表的，后续流量如果匹配了Microflow Table的表项则会直接跳过Subscriber Table、Application Table的处理。

3）Path Status Table匹配上一跳服务节点接入的入端口，并将该服务节点在metadata中对应的位置0，当该类服务节点有多个实例时，则将该类服务节点在metadata中所占N位中的最高位置0。

StEERING通过metadata来标记流量在服务链路径上的状态，这是一个比较新颖的思路，metadata直接记录了流量需要经过服务节点的顺序，而非服务链ID和当前流量所处服务链的位置。不过由于metadta只能在交换机的多级流表间传递，无法在多个交换机间传递，因此流量在每一跳交换机上都需要生成metadata。

[1] Zhang Y,Beheshti N,Beliveau L,et al.Steering:A software-defined networking for inline service chaining[C]//Network Protocols(ICNP),201321st IEEE International Conference on.IEEE,2013:1-10.

8.4.5　OpenSCaaS

论文[1]中提出的OpenSCaaS，其基本的思想是把服务链路上的识别策略、转发控制和服务节点管理，分别放在不同的控制器中实现，然后将统一的服务链编排器作为管理平面，其架构如图8-47所示。用户通过编排器输入服务链策略，编排器分解策略分发给policy controller、SDN controller和NFV controller，三者分别控制Classifier识别流量、SDN Switch转发流量、服务节点处理流量，三者配合起来完成服务链的开通。

图8-47　OpenSCaaS的架构

对于服务链的标记，OpenSCaaS提出在Classifier上改写源MAC地址来标记服务链，后续的SDN Switch都参照此标记来转发流量。文中提到的标记机制比较简单，改写后的源MAC地址只携带了服务链ID的信息，没有携带当前流量在服务链中所处的位置信息，更没有考虑FlowTags和Simple提出的其他复杂场景。而且改写后的源MAC地址端到端地进行传输，很可能会导致一些服务节点无法正常工作，如执行MAC Anti-Spoofing等。其转发示意图如图8-48所示。

图8-48　OpenSCaas的转发示意图

[1] Ding W,Qi W,Wang J,et al.OpenSCaaS:an open service chain as a service platform toward the integration of SDN and NFV[J].IEEE Network,2015,29(3):30-35.

8.4.6　SPFRI

论文[1]中提出了SPFRI，其控制器架构如图8-49所示，Policy DB存储服务链策略，MB DB存储与服务节点相关的信息，Policy Enforcer负责从Policy DB中解析流量所属的服务链ID，Path Finder负责解析从当前服务节点到下一跳服务节点的路径，SPFRI Engine结合Policy Enforcer和Path Finder的处理结果，通过Flow Rule Injector向相关交换机下发转发规则。

图8-49　SPFRI中控制器的实现架构

在数据平面上，SPFRI的思路是通过QinQ标记服务链流量的，其中Inner Tag标记服务链ID，Outer Tag标记下一跳服务节点。服务链路上的一跳被为Segment，Inner Tag由ingress segment的接入交换机压栈，由egress segment的出口交换机弹栈，intermediate segment上的交换机根据Outer Tag进行标签转发。Inner Tag端到端唯一，Outer Tag逐跳变化。可见，SPFRI在数据平面上的封装和转发与MPLS VPN有些类似，区别在于SPFRI使用了集中式的控制器来控制标签的分发和压栈/弹栈。在文章中也讨论了使用MPLS标签协议栈代替VLAN协议栈的可行性。

SPFRI要求ingress segment的接入交换机和出口段的出口交换机（图8-49中的SWI和SWE），以及连接intermediate segment上连接MB的交换机为OpenFlow交换机。在图8-50中，以H1发往H5的流量方向为例：SWI收到H1发出的流量，压栈Inner Tag（Blue）和Outer Tag（X），然后转发给SW1。SW1收到后剥掉Outer Tag，将MAC地址改写为MB1对应的MAC地址，然后转发给MB1。MB1处理后送回SW1，SW1打上新的Outer Tag（Y）发给SW2。SW2根据Outer Tag地址将流量直接转发给SW3。SW3收到后剥掉Outer Tag，并将目的MAC地址改写为MB2对应的MAC地址，然后送给MB2。MB2处理后送回SW3，此时SW3发现所有的MB都已经完成了处理，因此剥掉Inner Tag，并将目的MAC地址重新写为H5的MAC地址，然后发送给SWE。SWE收到后根据MAC地址转发给H5。

图8-50　SPFRI的转发机制示意

SPFRI结合了预置式和触发式两种方式，流量的首包会被送到控制器，触发控制器下发本segment的转发规则，控制器还会预置下发当对称路径的转发规则，这样流量在返回时就不必再上报控制器进行处理了。

除了上面所讨论的流量分类与引导以外，服务链中还有很多问题，论文[2]中讨论了服务节点状态的维护机制，并随即在论文[3]中提出一种类似于OpenFlow的服务节点标准控制机制OpenNF。另外，当网络中同一类服务节点存在多个实例的时候，这时就会涉及到服务节点的摆放问题，以及如何将流量引导到不同实例上进行负载均衡的问题，可参考论文[4]和论文[5]中的讨论。

[1] Pawar P B,Kataoka K.Segmented proactive flow rule injection for service chaining using SDN[C]//NetSoft Conference and Workshops(NetSoft),2016 IEEE.IEEE,2016:38-42.

[2] Gember A,Prabhu P,Ghadiyali Z,et al.Toward software-defined middlebox networking[C]//Proceedings of the 11th ACM Workshop on Hot Topics in Networks.ACM,2012:7-12.

[3] Gember-Jacobson A,Viswanathan R,Prakash C,et al.OpenNF:Enabling innovation in network function control[C]//ACM SIGCOMM Computer Communication Review.ACM,2014,44(4):163-174.

[4] Gember A,Krishnamurthy A,John S S,et al.Stratos:A network-aware orchestration layer for middleboxes in the cloud[R].Technical Report,2013.

[5] Huang H,Guo S,Wu J,et al.Joint middlebox selection and routing for software-defined networking[C]//Communications(ICC),2016 IEEE International Conference on.IEEE,2016:1-6.

8.5　服务质量

8.5.1　NetShare

论文[1]中提出的Netshare，可保证在满足各租户对网络带宽的最低要求的同时，使用统计复用的机制来分配实际处于闲置状态的带宽，将其分配给需要额外带宽的流量，以提高网络带宽的利用率。也就是说，Netshare是一种minimum bandwidth+work-conserving的QoS机制。其思路是，Netshare会根据Edge设备间的带宽需求，以及网络中实际的流量情况，动态地将物理链路资源映射为Edge设备间的虚拟链路资源，然后对这些虚拟链路进行分解与组合，以保证在任意时刻都能够满足各个租户对带宽的最低要求。

在实现上，Edge设备会周期性地进行本地监测，获得自己与其他Edge设备间的实时流量特征，并通过算法估算出下个监测周期内的流量特征，然后通过改进后的OSPF协议把这些情况扩散到其他Edge设备上。OSPF收敛后，Edge就得到了网络拓扑以及Edge设备间的流量矩阵。在通过Dijkstra算法计算路由的同时，Edge还会根据Weight Max-Min（加权最大最小）算法来处理流量矩阵，为去往不同Edge设备的流量分配带宽。于是，每对Edge设备间可以看作存在一条虚拟链路，在该虚拟链路的带宽中Weight Max-Min为其动态分配带宽。若Edge间的流量超过了其对应的虚拟链路的带宽，Edge本地的基于令牌桶（Token Bucket）的限速器就会直接丢弃掉超额的部分，防止这部分流量送到网络上造成拥塞。在为租户生成虚拟网络时，Netshare会根据租户的物理分布以及带宽需求，对虚拟链路进行分解与组合，然后通过CBFQ（基于类的公平队列）来实现租户在虚拟链路上的QoS。

论文中为Netshare定位的应用场景是帮助电信运营商实现预留带宽+统计复用，因此考虑更多的是VPN的需求，这时Edge设备主要是PE节点，Weight Max-Min算法在计算虚拟链路的带宽时参考的依据是Site-Site的流量特征。如果将NetShare应用在数据中心，那么Edge设备就是ToR或者是Hypervisor，Weight Max-Min算法在计算虚拟链路的带宽时参考的依据就是ToR-ToR（或者Hypervisor-Hypervisor）的流量特征。不过，数据中心和广域网的区别在于，VPN租户的物理位置可以看作是和Site绑定的。因此先基于Site-Site的流量特征划分虚拟链路，然后在虚拟链路中为租户进行QoS是可行的，而数据中心租户的虚拟机物理位置是在不断移动的，虚拟机和ToR或者Hypervisor的映射关系是经常发生变化的，如果基于ToR-ToR（或者Hypervisor-Hypervisor）的流量特征划分虚拟链路，然后在虚拟链路上隔离租户带宽，其实现的复杂度会远远高于VPN场景，在算法的可行性上也有待商榷。

[1] Lam T,Varghese G.Netshare:Virtualizing bandwidth within the cloud[J].UCSD,San Diego,CA,USA,Tech.Rep,2009:2013-2015.

8.5.2　Seawall

论文[1]提出了Seawall，它通过Hypervisor为不同的流量源分配加权带宽，并借鉴TCP的inband control对流量进行拥塞控制。这里所说的流量源，指的是可以产生流量的逻辑主体，虚拟机、应用或者进程都可以看作是流量源。Seawall不像Netshare针对每一对Hypervisor，也不针对租户，只针对上述的流量源进行带宽分配和拥塞控制，是一种“per source”的QoS机制。Seawall设计的思路是，网络管理者可以为每个流量源分配一定的权重，由这些权重来决定流量源如何分配NIC上的带宽，如果流量源当前所使用的带宽小于按照权重可以分得的带宽，那么可以通过Weight Max-Min算法将剩余的带宽分配给其他流量源，因此Seawall也是一种minimum bandwidth+work-conserving的QoS机制。如果接收端发现了拥塞，那么就反馈给发送端以限制其发送速度这可以避免网络带宽的不必要浪费，也可以起到阻止流量恶意侵占网络带宽的作用。

从实现上来看，Seawall是一种分布式架构，每个Hypervisor中都有自己的数据平面和控制平面。在数据平面上，流量从vswitch发出来进入NIC之前，必须要经过Seawall的shim层，它负责为数据包封装一个Seawall header，这个header携带着用于实现QoS的metadata，主要就是Sequence Number。接收端的shim层负责剥掉Seawall header，一旦发现Sequence Number出现了明显的不连续情况，就认为网络已经发生了拥塞，于是接收端的shim层就把收到的最后一个Sequence Number和已经收到的字节数反馈给源端的shim层，然后源端的shim层就根据拥塞的情况降低相应流量源的发送速率，并将剩余的带宽通过Weight Max-Min算法分配给其他流量源。Seawall的实现架构如图8-51所示。

图8-51　Seawall的实现架构

可以看到，Seawall实际上和TCP的思路是完全一样的。不过由于Seawall在Hypervisor中实现，因此它可以在降低某个流量源速率的同时，结合不同流量源的权重实现带宽分配。Seawall这种通过分布式的拥塞反馈来实现网络中QoS整体收敛的思路是具有开创性的，后续的很多论文都在此基础上进行了优化和深入的探索。

[1] Shieh A,Kandula S,Greenberg A G,et al.Sharing the Data Center Network[C]//NSDI.2011,11:23-23.

8.5.3　GateKeeper

论文[1]中提出了GateKeeper，GateKeeper的思路和Seawall一样，也是在Hypervisor中进行带宽分配，并通过接收端反馈的方式来限速。不过，GateKeeper并没有使用Seawall中per source的带宽分配视角，因为per source的带宽分配可能会造成如下情况：假设租户A有10个流量源，分别运行在10台服务器上，租户B只有1个流量源，现在A和B的流量源同时向某一台服务器的两个虚拟机发送流量。假设每个流量源的权重都一样，流量源的速率也一样，那么在接收端租户A就会占用带宽的10/11，而租户B就只能得到1/11的带宽。因此在GateKeeper中，接收端会根据拥塞情况来显式地决定发送端的速率，而不是把拥塞的情况反馈回去然后由发送端自行决定。GateKeeper架构如图8-52所示。

图8-52　GateKeeper的架构

在实现上，GateKeeper通过OVS来记录流量的速率，然后通过HTB实现带宽分配和限速，不过论文中并没有介绍接收端和发送端间是如何进行交互的。论文[2]延续了GateKeeper的思路并提出了EyeQ，它细化了接收端和发送端的控制机制。如在上一段提到的场景中，A的10个流量源以及B的1个流量源同时向某一台服务器的两个虚拟机发送流量，此时该服务器Hypervisor中的接收模块REM就会发现有10个流量源是租户A的，有1个是租户B的。如果租户A和租户B被分配了相同的权重，也就是说无论A有多少流量源，它们加在一起只能占用该服务器1/2的带宽，那么REM就会显式地通知A的各个流量源，限定它们的发送速率只能为带宽的1/20，这样就能够保证B可以得到1/2的带宽。如果过了一段时间，B的流量源减小了发送速率，那么REM就会显式地通告A的流量源可以提高发送速率，此时B闲置下来的带宽会平均地分给A的各个流量源。

不过，GateKeeper和EyeQ在接收端实现显式控制的机制，并不意味着这就一定优于Seawall中发送端收到拥塞通知后的本地决策机制。因为如果租户A多买了虚拟机，那么通常也意味着A会付出成比例的费用，那么A相比于B占用更多的带宽也自然是合情合理的。如果租户B愿意为更有保障的网络带宽另外付费，那么Seawall是没有办法保证B的通信速率的，这时候GateKeeper和EyeQ就更为合适一些。

[1] Rodrigues H,Santos J R,Turner Y,et al.Gatekeeper:Supporting Bandwidth Guarantees for Multi-tenant Datacenter Networks[C]//WIOV.2011.

[2] Jeyakumar V,Alizadeh M,Mazières D,et al.EyeQ:Practical network performance isolation at the edge[J].REM,2013,1005(A1):A2.

8.5.4　ElasticSwitch

论文[1]中提出了ElasticSwitch，它较好地融合了Netshare、Seawall、GateKeeper三者的思路。和Netshare类似的地方是，ElasticSwitch根据租户的需求计算出VM-VM的最低带宽需求，然后本地会进行周期性的监测以获得当前的流量特征，并据此预测下个周期的流量特征，从而使用Weighted Max-Min算法来分配带宽，以实现minimum bandwidth+work-conserving的QoS机制。和Seawall类似的地方是，ElasticSwitch也采用了inband control来实现拥塞的监测。和GateKeeper类似的地方是，ElasticSwitch的接收端也会通过显式通知的方式来优化发送端的带宽分配。其实现架构如图8-53所示。

图8-53　ElasticSwitch的实现架构

在实现上，ElasticSwitch在Hypervisor中会部署GP和RA两个模块。其中GP（Guara-ntee Partitioning）会将租户通过Hose模型所指定的带宽需求映射为VM-VM间的最小带宽需求，然后指导RA进行带宽分配。RA（Rate Allocation）负责监测当前流量的实际状态，并结合网络中的反馈信息，实现Weighted Max-Min的带宽分配。GP和RA的配合，可在满足最小带宽的同时，将闲置下来的带宽公平地分配给需要额外带宽的流量。在拥塞控制方面，ElasticSwitch假设数据中心不会出现数据包分片的情况，因此就采用了IPv4 header中的Identification字段作为Sequence Number，如果接收端发现Sequence Number出现明显的不连续情况，就认为网络中已经发生了拥塞，然后通过基于UDP的控制报文告知发送端丢失报文的数量，发送端收到拥塞通知后，RA就会减小下个周期发往接收端流量的带宽。ElasticSwitch中还有另外一种基于UDP的控制报文，用于计算发送端和接收端间同步GP的结果，ElasticSwitch规定选择两端GP计算结果中的较小值，以提前预防拥塞的发生。

[1] Popal,Yalagandula P,Banerjee S,etal.Elastic Switch:Practical work-conserving bandwidth guarantees for cloud computing[J].Computer Communication Review,2013,43(4):351-362.

8.5.5　SecondNet

论文[1]中提出了SecondNet，它通过集中式的控制器VDC Manager来实现QoS。SecondNet将流量分为3类：Type 0的流量对带宽的要求较高（如3-Tier应用和MapReduce等），这样的流量需要针对VM-VM进行端到端的带宽预留。Type 1的流量具有一定的优先级（如Email和FTP），但是并不要求端到端的预留，这样的流量只在第一跳和最后一跳提供有保证的带宽，防止被其他流量冲击掉。另外还有一种流量就是Best Effort（如备份类的流量），这类流量对带宽没有要求，不过却也是多多益善，这样的流量优先级最低，如果发生拥塞时它们会被丢弃，如果没有发生拥塞可以将闲置的带宽分配给它们，以提高带宽的利用率。

在实现上，VDC Manager会接收用户针对Type 0和Type 1流量所提出的QoS需求，然后通知Hypervisor中的SecondNet Driver来识别、标记Type 0和Type 1的流量，SecondNet Driver会基于漏桶（Leaky Bucket）算法来保证Type 0和Type 1的流量在入口、出口上的带宽。对于Type 0的流量，还会通过源路由的方式来显式地指定传输路径，路径上的交换机将根据MPLS Stack进行源路由转发，并通过基于优先级的队列，逐跳地保证Type 0流量的带宽。

相比于前面介绍的Netshare、Seawall等，SecondNet和它们有着很大的区别。首先，SecondNet是一种集中式的控制架构，由VDC Manager统一控制QoS的实现。其次，SecondNet除了对Hypervisor进行控制以外，还会影响交换机的行为，能够在路由层面提供QoS保障。另外，SecondNet使用的是IntServ+DiffServ的QoS机制，追求的是流量的差异性，而minimum bandwidth+work-conserving的QoS机制则追求的是流量间的公平。IntServ+DiffServ的QoS机制是网络中最为经典的QoS机制。不过传统网络中IntServ的实现代价太高（如RSVP需要在交换机上维护复杂的状态），难以在数据中心落地。而SecondNet中集中式+源路由的方式很好地解决了这一问题，可以看作是简化版的PCEP+Segment Routing。

SecondNet对于Type 0的QoS需求采用的是VM-VM的保证，它可以看作是一种Pipe模型。不过对于数据中心的用户来说，Pipe模型太过于精细了，因为大部分的用户对于VM间的流量模型都没有足够清晰的概念。

[1] Guo C,Lu G,Wang H J,et al.SecondNet:a data center network virtualization architecture with bandwidth guarantees[C]//International Conference.ACM,2010:15.

8.5.6　Oktopus

论文[1]提出了Oktopus，它针对数据中心网络规范了两种QoS需求模型，一种是VC（Virtual Cluster），如图8-54所示。一种是VOC（Virtual Oversubscribed Cluster），如图8-55所示。VC模型是指，在同一个租户中所有的虚拟机（设数量为N）都通过相同的带宽（设带宽为B）接入网络。在租户看来这些虚拟机好像都连在一个共有N个端口带宽为B的虚拟交换机上，这样的租户在VC模型中即可表示为<N，B>。VC模型适合数据密集型的应用（如MapReduce），但是它对底层网络提出的带宽需求较高。考虑到数据中心内有很多应用是按照不同的组别组织起来的，所以组间的通信量远远小于组内的通信量，VOC模型通过超购的方式可以满足这种网络需求，即为组内通信提供VC模型，而组间通信则以一定的超购比来实现。设租户共有N个虚拟机，所有虚拟机都以带宽B接入Group Virtual Switch，每一组都有S个虚拟机，因此共有N/S个Group Virtual Switch，这些Group Virtual Switch的下行带宽是B×S，上行以超购比O接入Root Virtual Switch，这样的租户在VOC模型中表示为<N，S，B，O>。

图8-54　VC模型

图8-55　VOC模型

针对VC和VOC两种模型，Oktopus提出了相应的VM Allocation算法，按照其QoS需求将租户的网络嵌入物理网络。或者说，虚拟机的分布不再随机或者轮询了，而是根据QoS需求来计算出分布位置。VM Allocation算法的实现依赖于集中式的控制器NM（Network Manager），NM维护物理网络拓扑、物理链路的可预留带宽、服务器上虚拟机的分配情况、租户的逻辑拓扑信息等。当一个新租户申请虚拟机资源的时候，NM会将上述已知信息，以及该租户提供的VC（VOC）模型参数，输入到VM Allocation算法中，VM Allocation算法可计算出满足该租户QoS需求的虚拟机分布信息，然后在相应的服务器上为租户分配虚拟机。

当VM Allocation分配好虚拟机后，理论上就可以满足各个租户的QoS需求了。不过在实际应用中，由于很多虚拟机会“不守规矩”甚至“恶意”地多发流量，因此Oktopus还是要依赖于Hypervisor中的Rate Limiter来限制流量的速率。不过和Netshare、Seawall、GateKeeper等不同的是，Octopus的Rate Limiter是通过集中式的架构实现的，每个租户都有一个指定VM作为Rate Limit Controller，Hypervisor中的Rate Limiter会监测各个虚拟机发送、接收流量的速率信息，并周期性地上报给对应租户的Controller，然后Controller会计算出VM-VM的限速参数并回复给Rate Limiter，实现相应的限速逻辑。如果为某个QoS租户预留的带宽发生了闲置，Oktopus的做法是将其平均分配给那些没有申请QoS的租户，而非在其他QoS租户间进行分配。

Oktopus和SecondNet的设计目标都是实现带宽的预留，两者相同的地方在于都采用了集中式的控制架构，不同的地方在于SecondNet采用的是传统InterServ做法，通过控制物理交换机的转发来实现端到端的QoS（Type 0），而Oktopus不会去控制交换机的转发，而是通过Rate Limiter来阻止那些可能会导致拥塞的流量进入物理网络，并结合集中式的VM Allocation来保证在物理网络上有充足的带宽来满足租户的QoS需求。

VC模型和VOC模型相比于SecondNet中所使用的Pipe模型，符合用户对于传统企业网络的简单认识，因此对于用户来说是更加友好的。除了Oktopus提出这两种模型以外，还有很多其他文献从不同的角度对数据中心用户的QoS需求进行了建模。论文[2]提出了TIVC（TimeInterleaved Virtual Clusters）模型，当用户的流量模型随时间不断变化时它能够对QoS需求进行时变的建模。论文[3]提出了TAG（Tenant Application Graph）模型，针对租户网络中不同应用它具有不同流量模式，并进行了基于应用的QoS建模。论文[4]将层次化的VC模型应用在了租户间通信的QoS建模中。

QoS涉及到网络本身如何盈利的问题，这是网络技术中十分复杂的一个分支。QoS在传统的电信运营商网络中已经得到了广泛的研究与部署，但是在数据中心内部，网络一直是作为“附属品”提供给用户的，一直没能形成独立的、合理的收费机制。因此，数据中心网络的QoS在学术界一直是十分热门的话题，本小节选取了几篇相关的经典论文进行了粗浅的介绍，有兴趣的读者可以继续进行深入的挖掘。

[1] Ballani H,Costa P,Karagiannis T,et al.Towards predictable datacenter networks[C]//ACM SIGCOMM Computer Communication Review.ACM,2011,41(4):242-253.

[2] Xie D,Ding N,Hu Y C,et al.The only constant is change:Incorporating time-varying network reservations in data centers[J].ACM SIGCOMM Computer Communication Review,2012,42(4):199-210.

[3] Lee J,Lee M,Popa L,et al.CloudMirror:Application-Aware Bandwidth Reservations in the Cloud[C]//HotCloud.2013.

[4] Ballani H,Jang K,Karagiannis T,et al.Chatty Tenants and the Cloud Network Sharing Problem[C]//Nsdi.2013,13:171-184.

8.6　传输层优化

8.6.1　MPTCP

ECMP解决了在多路径上实现负载均衡的问题，其实现非常成熟，在生产网络中得到了广泛的应用。ECMP通常是基于IP五元组进行负载均衡的，因此一个socket中的流量是无法利用多路径进行传输的，这样设计的原因是为了防止数据包乱序会导致的TCP重传，不过其代价是某些路径可能会被大象流所占据，导致这些路径上的拥塞。在本章9.2节中，有一些多路径转发和流量调度的论文，它们的思路大多数都是从路由的层面去解决ECMP中的上述问题，论文[1]则从TCP socket本身入手提出了MPTCP。既然对于ECMP来说只能为一个TCP socket分配一条路径，那么不妨在源端将这一个MPTCP socket分解为多个具有不同ECMP特征的子socket（源/目的IP地址相同，源/目的TCP端口号不同），然后再送入ECMP网络。这样一来ECMP网络就能够将这些子socket分散到不同的路径中，然后再由接收端对这些子socket进行重新组合。这种方案称为MPTCP（Multipath TCP），MPTCP将原始的TCP socket称为flow，将其分解出来的子socket称为subflow。

图8-56所示为MPTCP工作原理的简单示意图，图中某个TCP应用要发送长度为300的字节流，经过MPTCP的调度，将1～100字节，以及201～300字节通过subflow 2传输，将101～200字节通过subflow 1传输。从subflow 2的角度来看，应用中的原始字节流SEQ为1～100的字节对应自身的SEQ仍为1～100，而应用中的原始字节流SEQ为201～300的字节则对应着自身SEQ为101～200的字节。同理，从subflow 1的角度来看，应用中的原始字节流SEQ为101～200的字节对应的是自身SEQ为1～100的字节。因此，MPTCP通过TCP Option在subflow中保留了应用原始字节流的SEQ（DSN，Data Sequence Number），以便接收端能够还原出应用字节流最初的顺序。当subflow 2发生丢包需要重传时，可以将丢失的字节重新分配到subflow 1中，以提高成功传输该字节的可能性，此时需要对SEQ和DSN间的映射关系进行相应的调整。

图8-56　MPTCP工作原理示意图

为了兼容TCP，MPTCP复用了TCP的控制信道，协商时通过使用特定的TCP Option来实现MPTCP的控制逻辑。这些MPTCP的TCP Option只对主机有意义，对于网络中的转发设备和Middle Box是没有任何意义的，因此MPTCP对于网络来说是完全透明的。图8-57所示为MPTCP建立连接的过程，假设在某场景中，MPTCP主机A向MPTCP主机B发起TCP连接，MPTCP第一个使用的subflow为<A.1，B.1>（A.1代表着IP为A，端口号为1）。A.1在向B.1发送SYN时会在TCP Option中携带MP_CAPABLE，表明自己支持MPTCP。B.1回复SYN+ACK时也会在TCP Option中携带MP_CAPABLE，同时返回给A.1一个token B。然后A.1回复ACK，在TCP Option中携带MP_CAPABLE，同时将token A返回给B.1。MPTCP中的token是基于A和B双方的身份进行哈希计算出来的，用于标识原始的TCP socket。后续的subflow在建立新连接时，需要使用第一个subflow协商好的token。因此，MPTCP相比于传统的三次握手机制需要一个额外的ACK，来确认B.1收到了token A，然后A和B即可通过<A.1，B.1>传输数据。

图8-57　MPTCP建立连接的过程

在通信过程中，A决定通过A.2和B.1建立新的subflow<A.2，B.1>，于是A.2向B.1发送SYN，在TCP Option中携带MP_JOIN，并附上之前协商得知的B为初始socket所生成的token B。B.1收到后回复SYN+ACK，在TCP Option中携带MP_JOIN。A.1收到后回复ACK，在TCP Option中携带MP_JOIN，同时附上token B。B.1收到后回复ACK进行最后的确认，然后A和B即可通过新的subflow<A.2，B.1>传输数据了。为了利用B上的端口号资源，MPTCP还设计了一种新的信令ADD_ADDR，如图8-57右半边所示，B可以主动地将端口号2宣告给A，于是就可以通过新的subflow<A.2，B.2>协助A与B间的通信了。

MPTCP的设计从多路径的角度增强了TCP。对于网络来说它并不能感知到MPTCP的存在，subflow的生成也无法结合底层网络的实际情况，论文[2]提出了A-MPTCP，将MPTCP与LISP进行跨层的结合，在multihome场景中优化了DC入向流量的传输。

[1] BarréS.Implementation and assessment of modern host-based multipath solutions[D].UCL-UniversitéCatholique de Louvain,2011.

[2] Coudron M,Secci S,Pujolle G,et al.Cross-layer cooperation to boost multipath TCP performance in cloud networks[C]//Cloud Networking(CloudNet),2013 IEEE 2nd International Conference on.IEEE,2013:58-66.

8.6.2　DCTCP

TCP通过拥塞窗口来调节网络中的拥塞，防止过多的数据注入到网络中。由于TCP定位为端到端的协议，不会去尝试感知底层网络的状态，因此TCP是没有办法提前避免拥塞的，只能通过重传定时器的超时来判定丢包已经发生，然后将拥塞窗口回归为1，门限值降为一半，开始新一轮的慢启动。设计的上述机制，保证了TCP在拥塞控制实现上的简单性，但是很可能会导致网络在轻载和超载间来回地振荡，而且一旦出现这种情况，网络的利用率就会大幅度下降。为了解决上述问题，IETF提出了ECN（RFC 3168）。路径沿途的路由器发现即将需要发生丢包的可能后，就会将IP包头中的ECN置位。接收端发现CE置位后即可得知网络发生了“早期的”拥塞，于是在接下来的ACK中置位TCP首部的ECE。发送端发现ECE被置位后，就将拥塞窗口减半（无需等待RTO超时触发），降低慢启动门限值，并在下一个数据包中置位TCP首部的CWR，通知接收端主动降低了发送速率。ECN通过携带网络状态的metadata，使发送端可以在发生丢包之前就降低速率，有效地减少了TCP的重传，从而增强了应用的性能。

不过TCP对于ECN的处理也是“一刀切”，在RTT内只要发现一个ACK中有ECN，就直接将拥塞窗口减半，而不管网络中到底是“仅有一点拥塞”还是“已经非常拥塞了”。换句话说，使用了ECN之后，网络负载振荡的问题还是没有得到很好的解决。因此，论文[1]提出了DCTCP（Data Center TCP），其思路是加强发送端在收到ECE时的控制逻辑，根据一个RTT所收到的置位ECE的ACK占全部ACK的比例来判断拥塞的程度，平滑地减小下一个RTT中的拥塞窗口，从而避免网络负载的大幅度振荡，提高网络资源的利用率。图8-58所示为根据拥塞程度调整的窗口。

图8-58　DCTCP能够根据拥塞程度调整TCP窗口

在实现上，DCTCP对支持ECN的交换机、接收端和发送端都提出了一些要求。在传统的ECN中交换机通过平均长度来判断是否需要置位CE，然而对于数据中心来说，很多流量都是突发性的，而且交换机的buffer都很浅，如果根据一段时间内队列的平均长度来判断是否需要置位的话，发送端很有可能还没来得及调整拥塞窗口，交换机就已经开始丢包了。因此，DCTCP要求交换机根据当前队列长度来判断是否需要置位CE，从而快速地响应网络状态的变化。在传统的ECN中，接收端在收到CE置位的数据包后，在该RTT内会不断地在ACK中置位ECE，直到收到发送端置位CWR的数据包。而DCTCP要求接收端在收到CE被置位的数据包后，只在下一个ACK中置位ECE，从而保证发送端能够准确地知道在一个RTT内，置位ECE的ACK占所有ACK的比例。在传统的ECN中，发送端在收到ECE被置位的ACK后，会直接将拥塞窗口减半，DCTCP的发送端则会维护在一个RTT内所收到的ECE被置位的ACK占全部ACK的比例F，并据此计算出一个系数α，再根据α的值来确定如何调整下一个RTT的拥塞窗口。在DCTCP发送端，α和拥塞窗口的计算公式如下所示。α可以看作是交换机当前队列长度超过门限值K的概率，当它趋近1的时候，DCTCP发送端的行为与传统ECN中的相同，即减半拥塞窗口，但是当α很小时，拥塞窗口则不会受到太大的影响。

DCTCP的设计非常简单，论文中提到DCTCP相比于TCP只需要改动30行的代码，交换机也不需要新的硬件，只需要配置好队列长度的门限值K即可。不过，DCTCP有一个主要的局限，就是无法支持多路径转发。由于DCTCP在实现和部署上的简单性，因此很多后续的论文都沿用了DCTCP的思路。如论文[2]中所提出的D2TCP，通过对α进行伽马校正，可以对时延敏感型流量的传输进行优化。如论文[3]中所提出的HULL，在DCTCP拥塞窗口计算的基础上，通过Phantom Queue来优化交换机对拥塞的判断，通过网卡的Packet Pacing功能来缓解由于LSO造成的流量突发对网络的冲击。

[1] Alizadeh M,Greenberg A,Maltz D A,et al.Data center tcp(dctcp)[C]//ACM SIGCOMM computer communication review.ACM,2010,40(4):63-74.

[2] Vamanan B,Hasan J,Vijaykumar T N.Deadline-aware datacenter tcp(d2tcp)[J].ACM SIGCOMM Computer Communication Review,2012,42(4):115-126.

[3] Alizadeh M,Kabbani A,Edsall T,et al.Less is more:trading a little bandwidth for ultra-low latency in the data center[C]//Proceedings of the 9th USENIX conference on Networked Systems Design and Implementation.USENIX Association,2012:19-19.

8.6.3　D3

在数据中心中，不同的流量对于网络传输的要求是不一样的，比如在一些分布式计算的应用架构中（如MapReduce），对服务器间的时延有着苛刻的要求，如果某两台服务器间的时延超过了阈值，很可能就会造成整个系统性能的下降，这个时延的阈值通常称为deadline。不过，由于网络的转发设备是无法感知应用的deadline的，而且传统的TCP，以及DCTCP都是追求公平传输机制的，所以对待所有流量都一视同仁，不会去区分流量的传输需求。针对上述情况，论文[1]提出了一种新的传输控制机制D3，应用的发送端将自身的deadline发布给网络，网络结合实际情况为其分配一定的资源，接收端将分配结果反馈给发送端，发送端再根据预留的结果来调整发送流量的速率。因此，D3是一种deadline aware的传输机制，对于deadline要求越高的流量会得到更多的网络资源。

图8-59　D3的控制流程

在实现上，D3设计了一种新的封装congestion header来承载与deadline相关的控制信息，图8-59所示为D3的控制流程。首先，发送端向接收端发出SYN请求建立连接，同时将自己希望得到的速率r封装在congestion header中，如果流的大小为s，deadline为d，则r=s/d。这个SYN相当于是D3的探针，路由器根据自身的情况将可以分配出来的速率封装在congestion header中。沿路的路由器会依次执行该操作，因此接收端在收到SYN时，在congestion header中会存有一个各个路由器能够分配的速率列表。接收端在回复SYN+ACK时，会携带上这个列表以将其反馈给发送端。发送端得到这个列表后，会选择其中的最小值作为下一个RTT中发送流量的速率。另外，发送端还会重新评估下一个周期希望得到的速率，然后在ACK（或者下一个RTT的某个数据包中）中会重新请求下一个RTT中能够使用的流量速率。当发送端要结束这次传输时，在发送FIN时会携带之前RTT分配到的速率，然后路由器会释放掉为其分配的资源。

可以看到，D3对TCP的控制逻辑进行了较大的改动，因此D3是无法兼容TCP的。论文中提出，由于大多数有deadline需求的流量都是短流，以per-flow的方式来维护短流的状态代价过大，因此在D3中路由器以per-link的方式维护所有经过该链路的deadline flow的加总状态，然后采用贪心的方式为这些deadline flow分配带宽。如果在满足所有deadline flow后仍然有剩余带宽，则这些剩余带宽将会平均分给所有流量，如果剩余的带宽无法满足某些deadline flow的要求，那么将会为这些deadline flow保留一个base rate，这个base rate只能够发送congestion header，确保在未来有足够的带宽时能够分配给该deadline flow。

尽管D3提出了deadline aware的设计，但是由于路由器上没有per-flow粒度的状态信息，因此对于D3的资源分配只能采用先到先得的机制，后进入网络的流量是无法抢占那些已经存在于网络中的流量的带宽的，尽管后进入网络的流量对deadline的要求可能更高。另外，与DCTCP一样，D3也无法支持多路径转发。论文[2]提出了PDQ，它针对D3的上述问题进行了改进，允许对deadline有更高要求的流量抢占已经分配给网络中其他流量的带宽资源，并为多路径转发提出了M-PDQ。

[1] Wilson C,Ballani H,Karagiannis T,et al.Better never than late:Meeting deadlines in datacenter networks[C]//ACM SIGCOMM Computer Communication Review.ACM,2011,41(4):50-61.

[2] Hong C Y,Caesar M,Godfrey P.Finishing flows quickly with preemptive scheduling[J].ACM SIGCOMM Computer Communication Review,2012,42(4):127-138.

8.6.4　pFabric

论文[1]提出了pFabric，它认为D3和PDQ实现deadline aware的代价太大了，都需要对路由器进行复杂的带宽分配和状态维护，在软硬件上都需要大量的修改，难以得到广泛的应用。因此，pFabric的思路是削弱交换机上rate allocation，发送端根据自己对deadline的需求计算出一个对应的优先级放在header中，然后沿路的交换机就根据这个优先级来进行flow scheduling，以保证对deadline有较高要求的流量得到优先转发。另外论文中还提到，pFabric的一个insight就是将flow scheduling和rate limit解耦，因为pFabric认为主机上rate limit的控制逻辑通常是糟糕的、低效的，所以pFabric并没有尝试去优化rate limit的逻辑，而是简单地对传统TCP的逻辑进行了裁剪。

优先级是pFabric中的核心概念，论文中评估了主机上3种生成优先级的算法：①已发送字节数；②总共需要发送的字节数；③剩余待发送的字节数。得出的结论是：基于①来生成优先级是最简单的，但是效果最差，基于③生成优先级是最困难的，但是效果最好，而基于②生成优先级难度适中，而且在大多数场景下可以获得接近于③的效果。

在实现上，pFabric要求交换机支持基于优先级的队列调度机制，其出队的逻辑是选择当前缓存数据包中优先级最高的数据包，而入队的逻辑是丢弃当前缓存包中优先级最低的数据包（如果队列已满），因此在pFabric中交换机的处理是属于贪心的。传统的交换机并不支持上述的队列调度机制，pFabric提出了一种新的设计如下所示：端口上有两类队列，一类是数据包在RAM中真正进行排队的队列actual queue，另一类是用于快速查找具有最高（最低）优先级数据包的虚拟队列（metadata queue）。Metadata queue不会保存数据包的payload，只是记录数据包的flow-id（五元组或者基于5元组的散列值）和其优先级值，然后通过二叉树比较器来快速地找到具有最高（最低）优先级的数据包，然后将该数据包在actual queue出队（丢弃），并更新metadata queue。pFabric交换机中的队列设计如图8-60所示。

图8-60　pFabric交换机中的队列设计

在论文中提到，在某些情况下，上述的队列调度机制可能会导致“饿死”（starvation）的情况发生：如果优先级的生成采用的是基于剩余待发送的字节数，那么同一个flow-id中任意两个数据包的优先级都是不一样的，先到达的数据包优先级较低（因为当时剩余的字节数还较多，所以认为优先级较低），而后到达的数据包优先级较高，如果单纯根据优先级进行队列的调度，那么就会出现先到达的数据包永远无法得到出队的机会。为了解决starvation的问题，在查找到优先级最高的数据包后，交换机需要在metadata-queue中找到与该数据包具有相同flow-id的所有数据包，并选择其中优先级最低的数据包进行出队，以保证同一个flow中数据包按顺序传输。

pFabric的设计思路是非常简洁的，通过将flow-schedule和rate-limit解耦，可以大大降低网络设备实现的复杂性。论文[2]提出的NumFabric沿用了这一解耦的思路：xWI作为flow-schedule的模块，根据管理员指定的最大效用函数来计算流量的权重。Swift作为rate-limit模块根据xWI计算出来的权重实现Weighted Max-Min的带宽分配。相比于pFabric，NumFabric的优势在于提供了flow-schedule的统一框架，它能够支持更为丰富的最大效用函数，而不仅针对于deadline。

[1] Alizadeh M,Yang S,Sharif M,et al.pfabric:Minimal near-optimal datacenter transport[C]//ACM SIGCOMM Computer Communication Review.ACM,2013,43(4):435-446.

[2] Nagaraj K,Bharadia D,Mao H,et al.Numfabric:Fast and flexible bandwidth allocation in datacenters[C]//Proceedings of the 2016 conference on ACM SIGCOMM 2016 Conference.ACM,2016:188-201.

8.6.5　Fastpass

论文[1]提出了Fastpass，它通过引入一个集中式的Arbiter来为网络中所有的流量规划传输的timeslot，并为其选择特定的路由，以实现“nearly zero queue”的网络，从而起到降低时延，提高吞吐量，减少TCP重传等效果。文中提到，Fastpass的这一思路借鉴了交通控制系统的设计，系统里有一个全局的交通规划者（Arbiter），它不仅会告诉每辆车去向目的地应该选择哪一条路线（路由），而且还会告诉它们该何时出发（timeslot），从而在根本上避免了堵车的发生（zero-queue）。因此，Fastpass是一种集中仲裁的时分方案，这和TCP的公平竞争+统计复用有着本质上的区别。

在实现上，Fastpass设计了一种新的传输控制协议FCP（Fastpass Control Protocol），用于主机和Arbiter之间的交互，如图8-61所示。考虑到实现和部署的难度，Fastpass没有对协议栈进行任何修改，它需要在主机的协议栈和NIC之间开启Fastpass qdisc，Fastpass qdisc会截获socket发出的流量，将其入队缓存，并提取其目的地址以及数据包的长度，然后由FCP Client向Arbiter发起传输的请求。Arbiter会根据网络的实际情况为其分配用于传输的timeslot和路由，然后通过FCP协议将分配好的timeslot和路径信息返回给FCP Client，Fastpass qdisc会根据Arbiter返回的信息在指定的timeslot发送之前缓存的流量。网络需要支持ECMP Spoofing以实现显式的路由控制。对于主机上的入向流量，NIC会直接送给协议栈，FCP Client不会做任何处理。Fastpass基于时分控制的特征，要求系统进行精确的时钟同步，一旦某一环节的时钟没有同步，那么很可能就会导致交换机出端口上的排队。因此，Fastpass要求在主机上开启Linux hrtimer，交换机则需要支持PTP。

图8-61　Fastpass通过FCP实现Arbiter与主机间的交互

FCP的思路是很新颖的，文中指出在启用Fastpass后，网络很少出现丢包的情况，因此可以关掉TCP的拥塞控制机制。不过，由于FCP是一种触发式的带外控制，再加上Arbiter中的计算开销，因此Fastpass所带来的时延可能会对一些时延敏感型的流量造成巨大的影响。

[1] Perry J,Ousterhout A,Balakrishnan H,et al.Fastpass:A centralized zero-queue datacenter network[J].ACM SIGCOMM Computer Communication Review,2015,44(4):307-318.

8.6.6　OpenTCP

论文[1]提出了OpenTCP，其思路是引入一个SDN控制器来收集网络的状态信息，然后周期性地调整主机端的TCP参数，使得TCP能够始终处在优化后的状态，比如在网络轻载的时候增加拥塞窗口的初始值（init_cwnd），在网络中流并发较低时开启TCP Pacing，等等。在实现上，OpenTCP采用了OpenFlow，然后通过PacketOut将TCP的参数控制信息发送给主机，在主机上需要植入OpenTCP的Agent。不过，由于OpenTCP是一种集中式的架构，所以控制信道上的延时导致它无法像其他in-datapath方式一样，以RTT为单位对TCP进行调控。另外，考虑到控制器容易成为单点，所以OpenTCP也不适合进行太过频繁的控制动作。因此，其控制周期相比于RTT来说通常会有数量级的增加，它不适合对一些时延敏感的流量进行控制。OpenTCP架构如图8-62所示。

图8-62　OpenTCP的架构

[1] Ghobadi M,Yeganeh S H,Ganjali Y.Rethinking end-to-end congestion control in software-defined.networks[C]//Proceedings of the 11th ACM Workshop on Hot Topics in networks.ACM,2012:61-66.

8.6.7　vCC

专门为数据中心而设计的拥塞控制机制有很多，它们相比于传统TCP中的拥塞控制机制而言，都能够有效地提升流量的传输效率。然而出于安全性的考虑，用户通常不会允许云服务商对VM中传统TCP的代码进行修改，所以这些良好的拥塞控制机制在现实中是难以派上用场的。另外，如果一些用户在VM中自行开启了新型拥塞控制机制（如ECN），那么这些VM的流量很可能会在带宽的竞争中获得巨大的优势，导致其他VM流量的starvation。论文[1]提出了vCC（Virtual Congestion Control），它在Hypervisor中实现了传统TCP到新型拥塞控制机制的转换，在保证用户VM安全的前提下，可以获得新型拥塞控制机制对传输效率的提升。

在实现上，vCC需要维护per-flow的状态。在图8-63中，以ECN为例简单地介绍了vCC是如何在传统TCP（不支持ECN）和ECN-aware TCP间进行转换的。首先，用户的VM发送SYN请求建立连接，vCC收到这个SYN后会置位ECE和CWR，然后再发送出去。服务器端收到带有ECE和CWR标志的SYN后，会回复SYN+ACK并置位ECE。vCC收到带有ECE标志的SYN+ACK后会去掉ECE标志，然后将SYN+ACK回发给VM，此时TCP连接建立成功。对于VM来说，并没有感知到ECN的存在，随后双方开始交互数据。当网络发生拥塞时，服务器端在回复ACK时会置位ECE。vCC收到带有ECE标志的ACK后可以得知网络中已经发生了拥塞，ECN发送端收到带有ECE的ACK后，窗口自动减半，但是由于VM无法处理ECN，所以vCC会去掉ACK中的ECE标志，并将RWND置为原来的一半以达到相同的效果。VM调整窗口后发出下一个数据包，vCC会为其置位CWR，告诉服务器发送端已经针对拥塞进行了调整。

图8-63　vCC代理ECN的工作原理

与vCC的思路类似，论文[2]提出了AC/DC TCP，它基于Open vSwitch在传统TCP和DCTCP间实现了转换，在收到带有ECE的ACK的比例计算出拥塞窗口后，通过RWND来对VM进行相应的调整，并给出了详细的设计与实现。

[1] Cronkite-Ratcliff B,Bergman A,Vargaftik S,et al.Virtualized congestion control[C]//Proceedings of the 2016 conference on ACM SIGCOMM 2016 Conference.ACM,2016:230-243.

[2] He K,Rozner E,Agarwal K,et al.Ac/dc tcp:Virtual congestion control enforcement for datacenter networks[C]//Proceedings of the 2016 conference on ACM SIGCOMM 2016 Conference.ACM,2016:244-257.

8.7　测量与分析

8.7.1　Pingmesh

论文[1]所介绍的Pingmesh，是微软在其数据中心中使用的一种测量工具。Pingmesh的目的是通过ping来测量数据中心网络中的各种时延指标，包括intra-DC中的server-to-server以及rack-to-rack，和inter-DC中的DC-to-DC，这些为网络问题的排障提供了数据基础。

Pingmesh的架构如图8-64所示，其中Pingmesh Controller是控制器，Network Graph记录了数据网络中完整的拓扑，拓扑输入到Pingmesh Generator中使用特定的算法进行处理，产生ping的源目的列表Pinglist，并通过Web发布Pinglist。Pingmesh在微软数据中心中是一种“always-on”服务，为了实现服务的高可用，Pingmesh Controller会进行集群式部署并通过VIP来发布服务。Pingmesh Agent存在于所在服务器中，它通过RESTful API从Controller VIP处获取Pinglist，并向Pinglist中的所有目的服务器发送TCP ping与HTTP ping，为了防止Pingmesh占用过多的服务器资源，Pingmesh Agent对ping包的间隔和负载都进行了限制。Pingmesh Agent会将探测到的时延信息进行聚合，并定期地送到微软的数据存储与分析平台（Cosmos/SCOPE）中进行时延数据的存储、关联与挖掘。

图8-64　Pingmesh的架构

Pingmesh是一种Server-based的测量方案，相比于Network-based的测量方式，其优点在于测量方式通用简单，并不依赖于任何的网络设备，而且它所测量到的性能指标最接近应用所感知的实际情况的。不过，由于需要在服务器上安装Pingmesh Agent，因此Pingmesh并不能适用于第三方的虚拟机以及第三方虚拟网络的测量。

[1] Guo C,Yuan L,Xiang D,et al.Pingmesh:A large-scale system for data center network latency measurement and analysis[J].ACM SIGCOMM Computer Communication Review,2015,45(4):139-152.

8.7.2　OpenNetMon

论文[1]中的OpenNetMon提出了测量OpenFlow网络中吞吐量、丢包率和时延的方法，为流量工程的实现提供了基础。OpenNetMon采用的是主动测量的方式，即由控制器主动获取底层网络的状态，而不是被动地等待网络将状态的变化上报给控制器，其手段主要包括向交换机发送一些状态请求，或者构造并发送一些Probe。

通过向源目的主机通信路径上的Last Hop交换机定期发送Flow Statistics，并且使用Packet Counter或者Byte Counter的变化量除以时间的变化量就可以得到吞吐率。通过向源目的主机通信路径上的First Hop和Last Hop交换机分别发送Flow Statistics，通过Last Hop交换机上的Packet Counter/Byte Counter减去First Hop交换机上的Packet Counter/Byte Counter，就得到了某条流在该路径上的丢包数，除以First Hop交换机上的Packet Counter/Byte Counter就可以得到丢包率。

OpenNetMon使用Probe来测量时延，控制器通过PacketOut向目标路径的First Hop交换机注入Probe，这些Probe沿目标路径进行转发，Last Hop交换机收到Probe后PacketIn给控制器，收到PacketIn和发送PacketOut的时间差即是路径时延的粗略估计。为了得到更加精确的时延，还需要减去Probe在控制信道上的传输时间，即控制器将PacketOut推送给First Hop的时间，以及Last Hop将PacketIn上报给控制器的时间。因此，控制器还会向First Hop和Last Hop交换机PacketOut特殊的Probe，处理这些Probe的动作并不是沿路径转发的而是立即PacketIn给控制器的，计算这些特殊Probe收与发的时间差，即可获得在控制信道上的传输时间。

主动测量可以有很高的测量精度，但是其代价是控制器和交换机间需要交互大量的信令，在控制器上会产生大量的开销。为了减小定期发送Flow Statistics造成的开销，OpenNetMon动态地调整发送Flow Statistics的时间间隔，对于新上线或者吞吐率不够平稳的流量增加间隔，对于已经存在的、平稳的流量则减小间隔。另外，OpenNetMon还会动态调整发送Probe的速率，吞吐率变高就增加发送Probe的频率，吞吐率变低就减小发送Probe的频率。

论文[2]提出的OpenTM也和OpenNetMon一样，都采用了主动探测的机制。OpenTM只关注于吞吐率的测量，方法也是向源目的主机通信路径上的交换机发送Flow Statistics。与OpenNetMon不同的是，OpenTM会通过一定的算法选择路径上的某一个交换机去发送Flow Statistics，而不是固定地向Last Hop交换机发送Flow Statistics，这样可以减轻Last Hop交换机上的测量负载。

[1] Van Adrichem N L M,Doerr C,Kuipers F A.Opennetmon:Network monitoring in openflow software-defined networks[C]//Network Operations and Management Symposium(NOMS),2014 IEEE.IEEE,2014:1-8.

[2] Tootoonchian A,Ghobadi M,Ganjali Y.OpenTM:traffic matrix estimator for OpenFlow networks[C]//International Conference on Passive and Active Network Measurement.Springer Berlin Heidelberg,2010:201-210.

8.7.3　FlowSense

论文[1]中提出的FlowSense是用来测量OpenFlow网络中链路利用率的，其思路和OpenNetMon有所区别。它采用的是被动测量的方式，控制器不会主动向交换机发送Flow Statistics，而是通过监听交换机上报的FlowRemoved以获得流量的特征。由于FlowRemoved消息中会携带流表的信息，包括duration time（流表生存的时间）和packet/byte counters等，通过byte counters除以流的活跃时间即可大致得到该流的速率。对于流的活跃时间的计算方法是，判断FlowRemoved的原因（论文中仅考虑了超时），如果是soft_timeout则活跃时间等于duration_time-soft_timeout，如果是hard_timeout就没有办法获得准确的活跃时间了，只能通过duration_time来近似。

为了从流的速率推算出链路的利用率，需要将某段特定时间内存在于某条链路上的流量的流速加总起来。FlowSense的做法是，当流量首包PacketIn上来的时候将该流加入一个Active列表同时记录in_port，当收到该流的FlowRemoved时将其从Active列表中移除，并找到和该流有相同in_port并且活跃时间有重叠的其他流，将这些流的流速相加即可近似得出in_port所在链路的利用率。

FlowSense这种被动的“感知式”测量方法，能够将测量带来的额外控制负载降到最低，不过由于FlowRemoved只能在流消失后才上报，因此FlowSense所给出的测量结果的时效性很差。一条流在其活跃时间内流速的峰谷值有可能相差很大，但FlowSense只能给出流在活跃时间内的平均流速，其测量结果的准确性也较差。

为了在主动测量的准确性和被动测量的低开销间取得平衡，论文[2]提出了PayLess。它的思路就是结合FlowRemoved和Flow Statistics，控制器在下发流表时会启动一个定时器，如果在一定时间间隔内收到了FlowRemoved则采用类似于FlowSense的办法进行处理。如果在一定时间间隔内没有收到FlowRemoved则向交换机下发Flow Statistics，并采用类似于OpenNetMon的措施，通过一种适应性的算法来调整下一个定时的时间间隔，如果流的流速很快就减小间隔，如果流的流速较低则增加间隔。

[1] Yu C,Lumezanu C,Zhang Y,et al.Flowsense:Monitoring network utilization with zero measurement cost[C]//International Conference on Passive and Active Network Measurement.Springer,Berlin,Heidelberg,2013:31-41.

[2] Chowdhury S R,Bari M F,Ahmed R,et al.Payless:A low cost network monitoring framework for software defined networks[C]//Network Operations and Management Symposium(NOMS),2014 IEEE.IEEE,2014:1-9.

8.7.4　Dream

在OpenFlow网络中，为了完成一些有明确目的性的流统计任务，控制器可以下发一些专门用于测量的流表，匹配目标流量并直接GOTO下一级Table，通过Flow Statistics读取这些测量流表的counter，就能够得到目标流量的统计信息。对于硬件交换机来说，TCAM资源通常都比较有限，这就对测量流表的数量有了一定的限制。

论文[1]能够在TCAM数量受限的前提下，实现对HHH（Hierarchy Heavy Hitters）的动态测量。HH（Heavy Hitters）是指流速占链路带宽比值超过阈值的源IP前缀，网络中的HH有很多，不同的HH间存在着大量的包含关系，消除冗余的HH后剩余的就是HHH。图8-65所示为通过二叉树给出了某一条链路上不同源IP前缀的流速占比，若以10%作为判定阈值，则图中双圈的节点均为HH，而只有3个带底纹的节点才是HHH。其中：0000前缀占比达到11%，而且它所对应的节点为叶子节点，即前缀不需要再进行细分，因此0000前缀是一个HHH；0100前缀和0101前缀的占比均小于10%，不过两者占比相加超过了10%，因此聚合后的010*形成了一个HHH；将0000的占比（11%）从00**的占比（19%）中刨除剩余8%，将010*的占比（12%）从01**的占比（21%）中刨除剩余9%，两个剩余均未超过10%但相加超过了10%，因此聚合后的0***也是一个HHH。

图8-65　HH与HHH示例

可以看到在已知HHH的情况下，控制器可以只下发3条流表，分配匹配源IP为0000*，010*和0***，即可完成对HHH的测量。不过控制器没有办法预先知道哪些前缀是HHH的，而且随着流量的变化HHH也是动态变化的，因此控制器需要能够感知流量，并对流表进行适应性的调整。论文中提出了一种算法，在满足测量HHH的同时，能够最小化流表的数量。具体的算法很难三言两语说清楚，有兴趣的读者可以看看论文中的介绍。

通过优化TCAM的使用，可以更好地支持OpenFlow的测量，还可以参考论文[2]中提出的Dream。相比之下，Dream考虑了对HH和CD（Change Detector）的测量方法，并能够同时利用多个交换机中的TCAM资源服务于测量。另外，Dream侧重于在不同的测量任务间协调TCAM资源的分配，以尽可能地满足不同测量任务对于准确性的要求。

[1] Jose L,Yu M,Rexford J.Online Measurement of Large Traffic Aggregates on Commodity Switches[C]//Hot-ICE.2011.

[2] Moshref M,Yu M,Govindan R,et al.DREAM:dynamic resource allocation for software-defined measurement[J].ACM SIGCOMM Computer Communication Review,2015,44(4):419-430.

8.7.5　OpenSample

相比于OpenFlow，业界还有一些更为成熟的测量手段，比如常见于交换机中的sFlow。sFlow交换机会对流量进行采样，经过本地sFlow Agent分析后送给集中式的Collector进行呈现。在Collector中通常用采样的频率乘以采到的数据包数量，对流速进行统计意义上的估计，这种方式称为simple scaling。Simple scaling的优点是简单，而且从统计学的角度来讲它是无偏的，但是它需要大量的采样数据才能消除估计值的误差。扩大采样样本的方法，一是增加采样时间，这会影响测量的时效性；二是提高采样频率，但是这会对交换机的CPU产生很大的压力。

论文[1]提出的OpenSample，通过对采样数据包中的TCP SEQ进行记录与分析，增强了sFlow对TCP流量测量的准确性。具体来说，如果采样能够得到属于同一条流中的两个数据包，那么就可将这两个数据包的TCP SEQ相减，然后乘以数据包的平均长度，再除以这两个数据包时间戳的间隔，就能够得到该流的近似流速。这种方式更接近于实际情况，而非统计意义上的估算，因此相比于小样本的simple scaling，在流速计算的准确性方面它会有很大的提高。

图8-66　OpenSample的实现架构

OpenSample的实现架构如图8-66所示。sFlow Agent上报来的流数据会送到Flow Analyzer中进行处理，通过上述对TCP SEQ的处理计算出流速，另外Flow Analyzer还会判断流量是否为大象流。判断为大象流的条件是某个流的数据包在采样中出现了两次或者以上，这种判断的准确性实际上还有待商榷。Port Analyzer负责计算链路的利用率，它会使用端口上的采样频率乘以采到的数据包数量，再乘以数据包的平均长度来估算链路上带宽的使用情况。实际上Port Analyzer的这种做法也属于simple scaling，但是由于它针对的是经过某个端口的所有流量，因此其统计特性相比与针对某个流的simple scaling的统计特性要好一些，OpenSample认为是其准确性在可以接受的范围内。

另外，Flow Analyser和Port Analyzer计算出的数据每隔100ms会保存快照，一些测量应用可以通过API来获取这些快照。对于SDN控制器来说，从OpenSample获得这些数据后即可支撑流量调度、虚拟机调度等SDN应用，就可以对网络进行实时优化。

[1] Suh J,Kwon T T,Dixon C,et al.Opensample:A low-latency,sampling-based measurement platform for commodity sdn[C]//Distributed Computing Systems(ICDCS),2014 IEEE 34th International Conference on.IEEE,2014:228-237.

8.7.6　Planck

上面提到过，提高sFlow的采样频率会对交换机的CPU产生很大的压力，一方面的原因是ASIC和CPU间的通道不能承受太快的采样，另一方面的原因是交换机本地CPU的能力并不足够强，过快的采样会淹没CPU对控制信令的处理。论文[1]提出的Planck，通过镜像端口把待采样的数据流引到专门的Collector服务器上，旁路掉交换机本地的CPU，图8-67所示的左半部分示意了Planck在交换机上的工作原理。不同交换机的镜像端口都直连到同一个Collector服务器上，Collector服务器的CPU处理能力可以很强，专门负责对镜像流量进行高速采样和分析，其分析结果可以输出到SDN控制器上，以便SDN控制器对网络进行优化。

图8-67　Planck的工作原理（左）与系统部署（右）

在实现方面，目前大多数的交换机都支持端口镜像，采样的目标可以是全部业务端口上的全部流量，也可以是部分业务端口上的部分流量，只需要下发相应的配置或者流表就可以了。不过，由于多个端口的流量都汇聚到了镜像端口上，因此镜像端口很有可能会出现丢包的现象，不过由于Collector本身也会对镜像流量进行采样，因此即使镜像端口上产生了丢包，对统计出来的结果影响也不是很大。

Collector服务器使用netmap将镜像流量送到用户空间，并在用户空间中为每个镜像端口都分配了独立的处理进程。和sFlow送到本地CPU不同的是，镜像流量就是原始的流量没有任何的metadata，Collector无法得知流量的inport和outport，也就没有办法将流量与链路相关联了。为了解决这一问题，Collector需要从SDN控制器处得知网络拓扑和转发规则，自己来判断流量的inport和outport。如果网络中使用了ECMP，那么Collector还需要预先知道ECMP的映射算法。由于采样过后的流速分析，Planck使用了和OpenSample相同的方法，即通过TCP SEQ来计算，链路利用率的分析则使用了和FlowSense类似的方法，通过加总相关流的流速得到它。Collector上的数据，SDN控制器可以主动地轮询，也可以被动地监听以降低控制器的开销。

[1] Rasley J,Stephens B,Dixon C,et al.Planck:Millisecond-scale monitoring and control for commodity networks[C]//ACM SIGCOMM Computer Communication Review.ACM,2014,44(4):407-418.

8.7.7　OpenSketch

论文[1]提出的OpenSketch，旨在分离网络测量的数据平面和控制平面，数据平面具备通用的测量能力，并由控制器可以灵活地定义数据平面的测量行为。从名字上可以看出，OpenSketch的关键在于Sketch，Sketch是指一些能够压缩并存储流量特征的数据结构，与NetFlow、sFlow这些依赖于软件的sample-based的测量技术不同，Sketch通常用于streaming-based的测量技术，Sketch的硬件能够实时地处理、分析流量的特征，比如heavy hitters detection、traffic change detection、flow size distribution estimation等。不过，Sketch的硬件设计需要具体问题具体分析，所以测量不同流量特征的Sketch需要不同的、专用的ASIC，这使得Sketch的实现成本极高。

为了解决这个问题，OpenSketch为测量设计了一种3-stage pipeline的数据平面，如图8-68所示。从逻辑上来说测量可以分为两个阶段，第一个阶段是选择何种流量进行测量，第二是测量并记录、输出测量得到的数据。针对第一阶段，OpenSketch设计了Hash和Classification两个阶段，Hash负责对流特征进行压缩，Classification用于对Hash之后的值，以及流的字段进行匹配，针对第二阶段OpenSketch设计了Counting这一Stage，用于对第一阶段筛选出来的流量进行测量，并为测量到的结果提供查询和上报能力。

图8-68　OpenSketch的三阶段Pipeline

OpenSketch的三阶段pipeline是可编程的，针对不同的Sketch、Hash、Classification和Couting会有不同的实现。OpenSketch控制器负责对三阶段pipeline进行编程，应用可以编写测量的程序，说明需要测量何种流量的何种特征。OpenSketch控制器中存有一些常用Sketch的实现库，通过对测量任务进行分析，控制器会选择出相应的库并向数据平面下发配置，将三阶段pipeline配置成对应的Sketch。假设现有某个测量任务，它要统计白名单中各个源IP访问192.168.0.1的频率，控制器经过分析得知可以通过BloomFilter判断源IP是否在白名单上，于是控制器通过函数h处理白名单计算出的BloomFilter列为0001101101，那么在Hash阶段就会对数据包进行h函数的处理，Classification阶段就会匹配源IP为000**0**0*、目的IP为192.168.0.1的流量，然后在中对相关资源匹配上的流量进行计数。通过Countering资源为该任务分配的索引值，控制器就可以定期地读取该任务的测量数据了。OpenSketch架构如图8-69所示。

图8-69　OpenSketch的架构

OpenSketch使用NetFPGA对数据平面进行了实现，在Hash阶段中它预置了一些常用的哈希函数，Classification阶段使用的是TCAM，而Couting Stage使用的是SRAM。Classification阶段匹配上后会得到一个索引值，通过索引值可以找到相应的SRAM资源并进行更新。由于不同的测量任务需要共享SRAM资源，因此OpenSketch使用了Logical table去隔离不同任务的SRAM资源。控制器提供了7个Sketch的实现库，并将一些常见的测量任务，如heavy hitters detection、traffic change detection、flow size distribution estimation等，与它们所需要使用的Sketch预先进行了关联。控制器会解析测量任务，关联对应的Sketch，在不同的Sketch间分配pipeline资源，然后下发Sketch配置。控制器还会定期收集测量结果，并根据测量结果动态地调整资源的分配。

[1] Yu M,Jose L,Miao R.Software Defined Traffic Measurement with OpenSketch[C]//NSDI.2013,13:29-42.

8.8　安全

8.8.1　SOM

论文[1]中提出了一种轻量级的DDoS监测系统SOM，其思路是通过SDN来收集数据，然后通过数据挖掘来监测DDoS攻击，其架构如图8-70所示。具体来说，是通过Flow Statistics来收集网络中流表的状态，主要包括receive packets、receive bytes和duration time，并根据这些状态信息提取出用于评估各个flow的DDoS特征数据集，然后通过一种无监督的人工神经网络算法SOM（Self Organizing Map）来对各个flow进行分类，以判断flow是否为DDoS攻击流量。

图8-70　SOM的架构

该系统的关键在于，如何将Flow Statistics收集上来的原始统计信息转化为有用的DDoS特征数据集。实际上，入侵检测的本质就是设计出一个分类器，通过一些复杂的算法将异常的数据流区分出来，从而实现对攻击流量的报警，这通常需要一个数据集对分类器进行识别异常的训练。因此，数据集的建立对于入侵检测来说至关重要，针对数据集的建立已经有了很多的研究工作，其中最典型的是KDD-99数据集。KDD-99数据集通过41种特征来描述一个连接，这41种特征可以分为4大类，包括TCP连接基本特征（9种）、TCP连接的内容特征（13种）、基于时间的网络流量统计特征（9种），以及基于主机的网络流量统计特征（10种）。不过，KDD-99数据集中的特征过于复杂，算法想要精确地处理这些特征是十分耗时的，这对于一些实时在线的入侵检测来说是难以接受的。本小节介绍的这篇论文简化了特征的设计，提出了如下6个用于DDoS监测的流量特征。

1）Average of Packets per flow（APf）：DDoS会建立大量的无效连接，它们建立后（通常需要3个数据包）不会承载真实的流量，因此APf用来监测流中的数据包数量，如果某条流中的数据包非常少，那么这条流很可能就是DDoS的攻击流量。

2）Average of Bytes per flow（ABf）：为了提高攻击的效率，DDoS攻击者通常会使用尺寸非常小的数据包。因此ABf用来监测流的字节数，如果某条流中的数据包非常少，那么这条流很可能就是DDoS的攻击流量。

3）Average of Duration per flow（ADf）：APf可能会将某些交互式流量（如telnet）误判为DDoS流量。ADf用来监测流的生存时间，如果某条流的数据包较少但是生存时间较长，那么这条流可能就不是DDoS的攻击流量。因此，ADf相当于是对APf的一个纠正。

4）Percentage of Pair-flow（PPf）：DDoS的攻击者可能会伪造大量的IP地址，导致网络中出现大量的单向流（single-flow），pair-flow指的是网络中五元组完全对称的两条流，根据pair-flow的数量和流的总数量即可计算出single-flow的数量。因此，PPf可以用来监测pair-flow的百分比，如果PPf过低那么很可能就是由DDoS攻击导致的。

5）Growth of Single-flows（GSf）：和PPf类似，GSf是对single-flow的另一种监测特征，计算的是single-flow的数量在网络中的增长率，如果GSf过高那么很可能就出现了DDoS攻击。

6）Growth of Different Ports（GDP）：除了伪造IP地址以外，DDoS的攻击者还会随机地生成大量的端口号。因此GDP可以用来监测网络中出现端口号数量的增长率，如果GDP过高很可能就出现了DDoS攻击。

论文中使用了SOM作为分类器的算法，SOM的思路就是通过拓扑排序将n维的数据集进行分类，其算法这里就不再进行详细的介绍了。通过使用基于上述6个特征的数据集对SOM分类器进行训练，就可以用一个较小的代价识别出DDoS攻击。即使如此，通过训练的方式来识别DDoS还是太慢了，论文[2]提出了通过分析网络中流量目的IP的熵值来预判DDoS的方法，如果熵值在几个时间窗口内都低于阈值则说明一些目的IP出现得过于频繁，可能正在被DDoS攻击。

[1] Braga R,Mota E,Passito A.Lightweight DDoS flooding attack detection using NOX/OpenFlow[C]//Local Computer Networks(LCN),2010 IEEE 35th Conference on.IEEE,2010:408-415.

[2] Mousavi S M,St-Hilaire M.Early detection of DDoS attacks against SDN controllers[C]//Computing,Networking and Communications(ICNC),2015 International Conference on.IEEE,2015:77-81.

8.8.2　FloodGuard

对于OpenFlow网络来说，流量出现Table miss时通常会以PacketIn上报给控制器，因此攻击者可以伪造大量不存在的流，以触发大量的PacketIn对控制器进行DoS攻击。如果控制器对这些恶意的PacketIn都下发了流表，那么交换机的流表资源很快就会被消耗光了，这也变相地对交换机形成了DoS攻击。这种攻击方式也称为Data-to-Control Plane Saturation Attack，是OpenFlow中众所周知的，却最为严重的一个安全隐患。

图8-71　FloodGuard的架构

论文[1]提出的FloodGuard，能够缓解上述Saturation Attack，其思路是当发现疑似的攻击时，根据OpenFlow控制器其与应用的当前状态，获得所有潜在需要下发的流表（Proactive Flow Rule）并预先推送给交换机，保证在发生Saturation Attack时交换机仍然能够正确地处理合法流量，其架构如图8-71所示。另外，FloodGuard还设计了一种叫做Packet Migration的机制，当发现疑似攻击时PacketIn不再直接上报控制器，而是先缓存在Data Plane Cache中，Data Plane Cache再将这些PacketIn限速地发给控制器，以缓解PacketIn对控制器的冲击。

在实现上，FloodGuard主要分为Proactive Flow Rule Analyzer和Migration Agent两个模块。图8-72所示为FloodGuard的状态机，最开始处于Idle的状态，Migration Agent在后台会监测PacketIn的速率以及控制器的CPU、内存等资源的利用率，当超过阈值的时候就认为发生了饱和攻击并转入Init状态。Init状态完成两个动作：①Proactive Flow Rule Analyzer会根据OpenFlow应用的当前状态（如MAC转发表里面已经学习到的MAC地址）生成Proactive Flow Rule，②Migration Agent会向交换机下发一条流表将Table Miss流量引入Data Plane Cache。两个动作完成后Init状态转为Defense状态，Proactive Flow Rule Analyzer下发Proactive Flow Rule，并跟踪应用状态以对Proactive Flow Rule进行动态更新。一旦Migration Agent认为攻击结束了，就从Defense状态转为Finish状态，在Finish状态下先需要对在Data Plane Cache中缓存的PacketIn进行处理，如果在处理的过程中再次探测到了攻击就转为Init状态，否则等到Data Plane Cache为空后即重新转为Idle状态。

FloodGuard实际上是一种被动的应对方式，只能缓解受到攻击后所产生的负面效果，无法从根本上使OpenFlow控制器避开攻击。与FloodGuard思路类似的还有论文[2]。DoS的攻击形式多种多样，FloodGuard也只是对Saturation Attack有一定效果，实际上很有可能也并没有一种万金油式的解决办法。网络安全攻防永远是见招拆招，这里无法逐一而述。

图8-72　FloodGuard状态机

[1] Wang H,Xu L,Gu G.Floodguard:A dos attack prevention extension in software-defined networks[C]//Dependable Systems and Networks(DSN),201545th Annual IEEE/IFIP International Conference on.IEEE,2015:239-250.

[2] Kotani D,Okabe Y.A packet-in message filtering mechanism for protection of control plane in openflow networks[C]//Proceedings of the tenth ACM/IEEE symposium on Architectures for networking and communications systems.ACM,2014:29-40.

8.8.3　TopoGuard

拓扑发现与维护是SDN控制器中全局视图的基础，如果拓扑出现了问题，那么相当一部分SDN应用就失去了正常工作的能力。拓扑的主要元素无外乎是主机、转发设备和链路。对于OpenFlow来说，一般通过ARP监测主机，一般通过LLDP发现链路。如果攻击者通过ARP和LLDP来做一些手脚，那么就可以达到类似于传统网络中ARP欺骗或者STP欺骗的效果。ARP攻击的一般形式，就是攻击者以非法的MAC地址绑定攻击目标的IP地址，操纵控制器中的MAC转发表，使得攻击目标的IP无法正常通信，甚至能够对其流量进行劫持或者嗅探。LLDP攻击的一般形式，就是攻击者伪造LLDP包发送给接入的交换机，使得控制器误认为攻击者的主机是一台OpenFlow交换机，误导控制器生成错误的拓扑，从而影响所有流量的转发。

论文[1]提出的TopoGuard，解决了OpenFlow网络中和拓扑相关的安全问题。TopoGuard引入了Port Property的概念，端口分为HOST、ANY和SWITCH3种角色。最开始端口为ANY角色，端口一旦收到合法的ARP/DNS就认为该端口是主机，于是角色从ANY转变为HOST，如果收到合法的LLDP就认为该端口是交换机间的互联端口，于是角色从ANY转变为SWITCH。如果端口Down了，那么角色将从HOST或者SWITCH转变为ANY。如果HOST端口收到了LLDP，或者SWITCH端口收到了ARP/DNS，则认为发生了拓扑攻击。其端口角色转换如图8-73所示。

图8-73　TopoGuard中端口角色的状态机

图8-74所示为TopoGuard的具体实现。Port Manager用于维护上述端口的状态，这里有一个问题是ARP/DNS也有可能出现在SWITCH端口上，因此Port Manager会维护一个Host List，保存着已知主机的MAC地址，只有在MAC第一次出现时才会将入端口转变为HOST。Host Prober会不断地在Host端口上发送PacketOut，以检测主机是否在线。

图8-74　TopoGuard的实现架构

Topology Update Checker负责对拓扑的更新进行检测，主要包括两个功能：①如果发现已知的MAC/IP出现在了别的端口上，那么会判断该MAC/IP原来所在的端口是否出现了Port_Down，以及在原来的端口上向该MAC/IP发送的Probe是否超时，如果Port_Down再加上Probe超时，则认为该MAC/IP发生了位置迁移并进行主机端口的更新，否则认为出现了ARP攻击并进行告警。②对收到的LLDP进行检测，判断该LLDP是否出现在SWITCH端口上，同时还要判断LLDP TLV中的signature是否等于DPID和Port ID的哈希，如果端口角色为SWITCH再加上signature校验通过，则认为该LLDP合法并进行SWITCH端口的更新，否则认为出现了LLDP攻击并进行告警。

由于依赖于主机第一次上线时发送ARP/DNS，因此如果攻击者在静默的前提下直接发送LLDP，那么TopoGuard就没有办法检测该攻击了。

[1] Hong S,Xu L,Wang H,et al.Poisoning Network Visibility in Software-Defined Networks:New Attacks and Countermeasures[C]//NDSS.2015.

8.8.4　FortNox

论文[1]提出的FortNox旨在解决，由于不同SDN APP间控制逻辑的冲突，所导致的安全规则失效的问题。关于安全规则的失效，可考虑下面一个简单的场景，在一个OpenFlow交换机上旁挂了一个防火墙，服务链的应用会将特定的流量引导到防火墙上进行处理，而另外一个应用会用这种流量的header进行改写以便用于重定向。如果重定向的流表在服务链流表前得到了匹配，那么服务链的流表可能就识别不出来改写后的流量了，于是防火墙就被旁路掉了，导致网络中出现了安全隐患。FortNox的思路是，截获所有SDN APP下发的流表，将其与网络中现有的流表进行比较。如果待下发的流表A与现有流表B发生了冲突，就判断A与B间的优先级，如果A的优先级高，则删除B然后下发A，否则向产生A的SDN APP返回错误。FortNox为SDN APP规定了3种优先级：管理员手动下发的Administration Flow Rule优先级最高，Security APP产生的Secure Flow Rule优先级次之，而普通的APP产生的Non-Privileged Flow Rule优先级最低。

图8-75　FortNox的实现架构

在实现上，FortNox对NOX的内核进行了升级，添加了Role-based Source Authorization、State Table Manager、Conflict Analyzer和Flow Rule Timeout Callback几个模块，如图8-75所示。其中State Table Manager和Flow Rule Timeout Callback负责维护网络中现有的流表，Conflict Analyzer负责检查待下发流表与现有流表是否存在冲突，Role-based Source Authorization负责冲突发生时优先级的检测。对于冲突检测，FortNox使用了一种名为ARR（Alias Rule Reduction）的算法。对于优先级检测，FortNox使用的是基于角色的数字签名。另外FortNox还对OpenFlow进行了进一步的抽象，形成了8种安全策略原语（block、deny、allow、redirect、quarantine、undo、constrain以及info），这降低了安全应用开发的难度。

除了FortNox以外，还有很多的论文讨论了流表的冲突检测[2]，[3]，[4]。准确地说，这些论文应该属于SDN Debugging领域，由于FortNox主要将其思路用在了安全方面，因此本小节着重对其进行了介绍。另外，论文[5]在HyperVisor中实现了SDN控制器的冲突检测机制，解决了虚拟化环境下多控制器的安全问题，感兴趣的读者可以自行阅读。

[1] Porras P,Shin S,Yegneswaran V,et al.A security enforcement kernel for OpenFlow networks[C]//Proceedings of the first workshop on Hot topics in software defined networks.ACM,2012:121-126.

[2] Al-Shaer E,Al-Haj S.FlowChecker:Configuration analysis and verification of federated OpenFlow infrastructures[C]//Proceedings of the 3rd ACM workshop on Assurable and usable security configuration.ACM,2010:37-44.

[3] Khurshid A,Zhou W,Caesar M,et al.Verifl ow:Verifying network-wide invariants in real time[J].ACM SIGCOMM Computer Communication Review,2012,42(4):467-472.

[4] Son S,Shin S,Yegneswaran V,et al.Model checking invariant security properties in OpenFlow[C]//Communications(ICC),2013 IEEE International Conference on.IEEE,2013:1974-1979.

[5] Shin S,Yegneswaran V,Porras P,et al.Avant-guard:Scalable and vigilant switch fl ow management in softwaredefined networks[C]//Proceedings of the 2013 ACM SIGSAC conference on Computer & communications security.ACM,2013:413-424.

8.8.5　AVANT GUARD

论文[1]提出了AVANT GUARD，它通过对OpenFlow数据平面的逻辑进行加强，使OpenFlow交换机变成了一个SYN Cookie Firewall，能够有效地检测及阻隔SYN Flood。SYN Flood是网络中最为常见的攻击，攻击者会伪造大量的IP地址，并利用这些IP地址在短时间内向TCP服务器发送大量的SYN，TCP服务器会为这些SYN所请求的连接分配系统资源，并等待后续的TCP处理，而攻击者则不会继续后续的TCP处理，TCP服务器上会存在着大量的“半开连接”，导致大量的系统资源被闲置，从而达到了DoS的攻击效果。SYN Cookie是解决SYN Flood的一种简单办法，TCP服务器在收到SYN后不会立即分配资源，并在回复SYN+ACK时根据数据包的特征计算出一个特定的SEQ（并非随机生成），直到收到ACK并确认SEQ合法的情况下，TCP服务器才会为该连接分配资源。

SYN Cookie Firewall利用SYN Cookie的原理，实现TCP三次握手过程的代理（proxy）。AVANT GUARD通过对OpenFlow交换机进行改造，使其具备了SYN Cookie Firewall的功能。如图8-76所示，AVANT GUARD的工作原理可分为几个阶段，包括Classification［（1）～（3）］，Report［（4）～（5）］、［（9）～（10）］，Migration［（6）～（8）］，以及Relay［（10）～（12）］。在阶段中，TCP客户端A首先向TCP服务器B发起SYN，OpenFlow交换机收到后判断是否有流表能够匹配，如果能够匹配则按照流表处理，如果不能匹配并不会送给控制器，而是基于SYN Cookie向A回复带有特定SEQ的SYN+ACK，然后等待A回复合法的ACK，以进入Report阶段。在Report阶段中，交换机把该连接的信息上报给控制器，控制器判定连接合法后进入Migration阶段。在Migration阶段中，交换机代理A向B发起连接（映射到新的SEQ），等到三次握手完成后再次进入Report阶段，将代理完成的情况告知控制器。此时，控制器会通知交换机将该连接转入Relay阶段，开始数据报文的传输。对于控制器来说，它会定期收集网络中存在的TCP流的总数量，以及合法的TCP流的数量，并计算出非法TCP流的占比，如果该占比过高则很有可能出现了攻击。

图8-76　AVANT GUARD对SYN Cookie Firewall的实现

相比于SYN Flood通过伪造IP地址来发起攻击，Connection Flood的攻击原理则是利用真实的IP地址向TCP服务器发起大量的连接，在三次握手之后不再传输数据包，并且在很长时间内不释放连接，导致服务器上的无效连接过多，直至资源耗尽无法响应其他合法客户所发起的连接。为了解决Connection Flood，AVANT GUARD还支持一种Delayed模式，在Classification阶段三次握手后，必须再接收到A发送出的真实数据包，才会进入Report阶段，如图8-77所示。

图8-77　AVANT GUARD提供的Delayed模式

除了防止TCP的一些攻击，AVANT GUARD的另一个着眼点在于规避大量的PacketIn对控制器造成的DoS/DDoS攻击。其一在于，通过在数据平面对SYN Flood和Connection Flood进行防护，本身就可以避免它们触发PacketIn。其二，AVANT GUARD对OpenFlow协议进行了扩展，允许交换机在本地维护一些状态，当超过阈值时它会主动通知给控制器，控制器也主动地将某些处理逻辑卸载到数据平面上，当满足某些条件时交换机会自动执行这些控制逻辑。

AVANT GUARD的思路实际上是将Stateful Firewall的功能扩展到了OpenFlow交换机上，不过这本身也会引入一些额外的安全问题。比如，攻击者得知AVANT GUARD的工作原理后，仍然会大量地发送SYN Flood和Connection Flood，尽管TCP服务器得到了保护，但是交换机上的处理资源却会很容易就被耗尽了（交换机的CPU和内存通常都很小），导致网络中大量连接直接中断，引发更为严重的网络事故。

[1] Jafarian J H,Al-Shaer E,Duan Q.Openfl ow random host mutation:transparent moving target defense using software defined networking[C]//Proceedings of the first workshop on Hot topics in software defined networks.ACM,2012:127-132.

8.8.6　OF-RHM

论文[1]提出了OF-RHM，它利用OpenFlow对IP地址进行随机改写，以保护目标主机的真实IP地址。OF-RHM的这种防御机制，属于典型的MTD（Moving Target Defense，移动目标防御），有别于传统的安全机制，MTD不需要用复杂的手段来检测、阻挡攻击，而是通过对网络环境进行快速的变化，从而将攻击限制在一个较短的时间段内。MTD的思路非常简单，却能够显著地增加攻击的成本和难度，从而提升网络的安全性。

OF-RHM具体的工作原理如图8-78所示，网络中有host1和host2两台主机（host2是一台HTTP服务器，域名为name2），其真实的IP地址分别为r1和r2，控制器通过一定的算法为它们生成了虚拟IP地址v1和v2。现在由host1向host2发起通信，①host1发起对name2的DNS Request，该请求被PacketIn上传给控制器，控制器作为中继向DNS服务器发送该请求。②DNS服务器将r2的对应关系Reply给控制器。③为了实现MTD，控制器使用v2代替DNS Reply中的r2。④host1使用r1作为源IP地址，v2作为目的IP地址发送数据包，首包被PacketIn发给控制器。⑤控制器下发流表，在host1的接入交换机上将host1发出的数据包进行改写，将其源IP地址改写为v1，在host2的接入交换机上将该数据包再次改写，将其目的IP地址改写为r2。⑥数据包转发到host2的接入交换机上。⑦数据包被host2的接入交换机改写，然后转发给host2。在⑤中，控制器实际下发的是双向对称的流表，host2回复给host1的数据包也会被类似的逻辑处理，见图8-78中的⑧～⑩。

对于某些授权用户，他们有权看到目标主机的真实IP地址，此时OF-RHM的工作原理如图8-79所示。①host1通过DNS解析到host2的真实IP地址r2。②host1使用r1作为源IP地址，r2作为目的IP地址发送数据包，首包被PacketIn发给控制器。③控制器下发流表，在host1的接入交换机上将host1发出的数据包改写，将其源IP地址改写为v1，将其目的地址改写为v2，在host2的接入交换机上将该数据包再次改写，将其源IP地址改写为v1，目的IP地址改写为r2。④数据包被转发到host2的接入交换机上。⑤数据包被host2的接入交换机改写，然后转发给host2。在③中，控制器实际下发的是双向对称的流表，host2回复给host1的数据包也会被类似的逻辑处理，见图8-79中的⑥～⑧。

图8-78　OF-RHM的工作原理

图8-79　OF-RHM的授权用户流量的处理

关于上述OF-RHM的工作机制，仍需要进行如下的说明。

1）DNS的payload需要传给控制器。

2）控制器上有一个虚拟IP的地址池，虚拟IP地址的分配是通过SMT算法完成的。

3）控制器需要维护真实IP和虚拟IP间的映射关系，一个虚拟IP地址在某一时间段内只能分配给一台主机。真实IP和虚拟IP间的映射关系，是以host pair为粒度的，也就是说host1看到host2的IP地址是v2，而其他主机看到host2的IP地址就不会是v2了。

4）在DNS Reply中TTL会置为一个较小的值，超时后host1会重新发送DNS请求。此时，控制器会为host2分配一个新的虚拟IP地址v2'。此时，host1之前针对v2的攻击都将失效。

5）控制器需要记录某一时间段内，曾经为host分配过的所有的虚拟IP地址（v2，v2'，v2''等），新分配的虚拟IP地址不能与它们任何一个相同。

6）真实的IP只能出现在接入交换机的下联口，在上联口上不会出现任何主机的真实IP地址，防止攻击者通过其他手段截获到真实的IP地址。

OF-RHM是一种简单有效的安全防护机制。不过，攻击者一旦得知其工作机制后，也能够很容易地攻破OF-RHM的防护，只要能够动态地获取目标主机当前的IP地址，并随之变换攻击的目标IP地址即可。

[1] Jafarian J H,Al-Shaer E,Duan Q.Openfl ow random host mutation:transparent moving target defense using software defined networking[C]//Proceedings of the first workshop on Hot topics in software defined networks.ACM,2012:127-132.

8.8.7　Fresco

论文[1]提出的Fresco是一种安全编排框架，可提供多个模块化的安全服务，以及对应的编排能力。用户可根据自身的安全需求自行组合这些安全服务。图8-80所示为Fresco的架构图，包括Application Layer和Security Enforcement Kernel两个部分。

图8-80　Fresco的架构

Application Layer由RC（Resource Controller）和DE（Development Environment）组成。RC是流表资源的管理器，它收集网络中的流表，处理后存放在DE中的DB。RC还会监测交换机表项的利用率，当交换机中的流表资源快要被耗尽的时候，就会主动地回收不活跃或者优先级较低的流表（可理解为垃圾回收）。DE是安全应用的开发环境，开发者通过编写Fresco Script，可以将多个Security Module组织成一个Security Instance，当某些事件发生时Fresco会将对应的Security Instance加载到内存中执行，事件可以由DB来触发，也可以由第三方安全设备或者应用调用接口来触发。

Security Enforcement Kernel内嵌在SDN控制器中，实现了和FortNox相同的功能，它包括安全策略原语的转换、流表的冲突监测、基于优先级的流表冲突仲裁（Fresco APP优先级较高、非Fresco APP的优先级较低），等等。

图8-81所示为一个使用Fresco编排生成的Scan Deflector Service Instance，它由3个模块协作组成，3个模块分别为源IP的黑名单（Blacklist Evaluator），端口扫描检测器（Scanner Detector）和重定向器（Redirector）。Blacklist Evaluator通过DB发现有新的TCP连接建立后，会判断源IP是否合法并将结果输出给Scanner Detector。如果源IP非法，Scanner Detector直接下发流表丢弃该流量，如果源IP是合法的，Scanner Detector会执行端口扫描的检测逻辑，并将检测的结果输入给Redirector。如果没有发现端口扫描的迹象，Redirector下发流表转发流量，如果发现了端口扫描迹象，Redirector下发流表对流量进行重定向，将其引导到专业的安全设备上以便进行精确的检测。

图8-81　使用Fresco编排生成的安全服务

网络和安全之间有着密不可分的关系，同时两者又各自拥有独立的体系。论文中的实现是将Fresco部署在SDN控制器中的，那么这样来看Fresco就是一个SDN应用的运行环境。如果把Fresco Application Layer独立出来实现，也可看作是一个安全的控制器，通过东西向接口与SDN控制器通信，协同实现“软件定义的网络安全”。

[1] Shin S,Porras P A,Yegneswaran V,et al.FRESCO:Modular Composable Security Services for Software-Defined Networks[C]//NDSS.2013.

8.9　高可用

8.9.1　ElastiCon

论文[1]提出了一种OpenFlow信道的迁移机制ElastiCon，它为OpenFlow控制平面的负载均衡提供了基础。OpenFlow 1.2中提出了多控制器机制，如果控制器向交换机发送Role-Request请求转换为Master角色，那么交换机会立即将该控制器切换为主控制器，这样会使已有控制器上的负载无法实现平滑迁移。ElastiCon在多控制器和交换机间设计了一种信令交互机制，当某个控制器负载过重时可以将负载平滑地转移到其他控制器上，如果控制器集群整体负载过重还可以拉起一个新的控制器，实现控制器集群的弹性扩缩容。

图8-82所示为ElastiCon设计的信令交互机制，假设控制器A是交换机X当前的Master，现需要将控制器B平滑地切换为Master。首先在Phase 1中，A会向B发送一个Start migration表示需要将自己的负载进行迁移，B收到这个信令后会向X发送一个Role-Request请求转换为和A对等的Equal角色。X回复Role-Reply确认B的Equal角色，然后B会向A发送一个Ready for migration的信令，表示A可以开始进行负载迁移了。

图8-82　ElastiCon的信令交互机制

Phase 2中，A会向X发送一个FlowMod消息增加一个dummy流表，然后再删除掉这个dummy流表。顾名思义，这个dummy流表没有任何的实际意义，它不会匹配任何的数据流量，写入后并删除这个dummy流表的作用是触发交换机发送一个FlowRemoved消息，以确定一个在A、B间切换控制负载的时间点。由于B的角色在Phase 1中已经转变为Equal，因此可以收到交换机发送的FlowRemoved，当B收到该消息的时候即为切换控制负载的时刻——所有在FlowRemoved之前的控制消息都由A来处理，而FlowRemoved之后的控制消息则都由B来处理。为了保证dummy流表的FlowMod add先于FlowMod delete得到执行，增加和删除dumny流表之间还需要插入一个Barrier。

Phase 3中，A在收到FlowRemoved之后还需要向X发送一个额外的Barrier。设计这个Barrier是考虑到，在A发送dummy流表的FlowMod delete和A收到FlowRemoved消息的这段时间内，A有可能又和X交互了其他的控制消息，Phase 3中的Barrier就是为了保证这些消息仍然由A来进行处理的。收到这个Barrier的Reply后，A立即向B发送一个End migration消息，表示所有和自己相关的控制消息都已经得到了处理，负载可以完全迁移给B了。

于是在Phase 4中，B向X发送Role-Request消息请求转变为Master角色，X收到这一消息后将B切换为Master，并将A切换为Slave。A作为Slave仍然可以读取X的部分状态，但无法再下发FlowMod等Controller-to-Switch消息，这样控制负载就平滑地迁移到了B上。

[1] Dixit A,Hao F,Mukherjee S,et al.Towards an elastic distributed SDN controller[C]//ACM SIGCOMM Computer Communication Review.ACM,2013,43(4):7-12.

8.9.2　Ravana

一般对于控制器集群中同步机制的讨论，多见于业务层面的北向接口，以及对于OpenFlow流表的版本控制，而对于由交换机主动上报的异步事件的同步机制，相关的研究则很少。以PacketIn为例，如果多个控制器都对同一个PacketIn进行了处理，那么就有可能会多次下发相同的PacketOut，导致Duplicate Packet，如果主控制器收到PacketIn后挂掉了，而新选举出来的主控制器并没有收到这个PacketIn，那么就可能会导致在转发上出现异常。

类似于上述问题还有很多，论文[1]提出了Ravana，主要就是为了解决多控制器网络中OpenFlow消息同步的问题，其解决的思路是对OpenFlow进行修改，使得交换机具备了缓存/重传OpenFlow消息的能力，其设计原则有如下几点：①同一个交换机上报控制器集群的异步事件只能（同时必须）被处理1次，②需要保证不同异步事件的顺序处理，③控制器集群和应用需要保持一致的状态，④向同一个交换机下发相同的命令只能（同时必须）生效1次。

Ravana的工作原理如图8-83所示。如果数据包进入交换机后触发了异步事件，那么在上报控制器的同时会在本地对该事件进行缓存。Master控制器收到该异步事件后，记录其发生的时间顺序，并与Slave控制器进行同步，同步完成后向交换机确认已经收到该事件。交换机收到确认后，即可清除缓存中相应的事件。Master控制器将事件交给Master控制器中的应用进行处理，处理完成后向相关的交换机下发指令。交换机收到指令后向Master控制器返回确认，然后执行指令。控制器在收到同一批次中的全部确认指令后，将该事件置为“已处理”状态。当Slave控制器发现“已处理”事件后，此时才会将事件交给Slave控制器中的应用，以同步Slave控制器和应用中的状态，不过Slave控制器中的应用所生成的指令并不会真正地下发给交换机，这是因为之前Master控制器中的应用已经下发过相同的指令了。

图8-83　Ravana的工作原理

可以看到的是，在Ravana中控制器间通过Two Phase Replicatio实现了OpenFlow消息的Transaction，增强了OpenFlow信道的健壮性，在实现上主要采用了ZooKeeper。不过，在增强了健壮性的同时，也不可避免地会降低OpenFlow信道的并发性能。针对于此，论文中提到了一些性能的优化思路，有兴趣的读者可自行查阅。

[1] Katta N,Zhang H,Freedman M,et al.Ravana:Controller fault-tolerance in software-defined networking[C]//Proceedings of the 1st ACM SIGCOMM Symposium on Software Defined Networking Research.ACM,2015:4.

8.9.3　BFD for OpenFlow

SDN控制器失效后可以通过集群和角色切换来保证控制平面的高可用，如果数据平面上的链路或者节点失效了，可用性就需要通过快速切换路由来实现了。在本章9.2节中讨论了一些十分复杂的快速重路由机制，而对于OpenFlow来说实现快速重路由则相对简单，大概可以分为Reactive和Proactive两种实现方式。在Reactive方式中，控制器通常会通过PacketOut/PacketIn LLDP来探测拓扑，一旦发现了拓扑的变化就为相关流量计算新的路径，并下发新的FlowMod实现重路由[1]。在Proactive方式中，控制器可以通过Failover Group来卸载上述的处理工作，控制器会预先计算并下发备份路径，交换机在探测到端口Down之后可在本地快速地切换到备份路径上。

不过一般来说，Failover Group也只能监测到端口Down，而对于路径的通断一无所知。在传统网络中路径的通断通常是由BFD监测的，BFD是一种特殊的Hello消息，路径两端的交换机需要为BFD Session维护状态，如果在几个interval内没有收到BFD消息则认为该路径已断。将OpenFlow结合BFD，需要在交换机中增加BFD的控制机制，这虽然有悖于OpenFlow的设计原则，但是由于一些关键路径对切换有50ms的时间要求，而控制器从发现拓扑变化到重新下发路径需要100ms以上[2]，因此BFD可能是生产网络中唯一的选择。

论文[3]中介绍了在OpenFlow交换机中集成Per LSP BFD的思路，实现如图8-84所示，左半部分是BFD发送端的处理流程，右半部分是BFD接收端的处理流程。BFD数据包由一种特殊类型的OpenFlow Virtual Port产生，控制器可以控制这些Virtual Port产生的BFD数据包类型，以及生成BFD数据包的速率。BFD数据包生成后送入Pipeline中进行处理。通过ALL Group对其进行处理，可为BFD数据包插入一些用于OAM的Metadata，最后标记OAM Tag并发送出去。接收端匹配OAM Tag识别出是一个BFD数据包，然后剥掉OAM Tag并终结BFD数据包，终结BFD数据包需要一个新的动作，这个动作能够根据OAM Metadata来更新BFD Session的状态，如果发现BFD超时则立即切换到备用路径上，并通过一种类型为Notification的OpenFlow Error消息来通知控制器。

图8-84　BFD发送端的处理流程（左）和接收端的处理流程（右）

另外，在论文[4]和论文[5]中也讨论了类似的思路，一旦BFD监测到通断状态发生了变化就在本地切换到备份路径上，并通知给控制器。有所区别的是，前者针对的是Per-Path BFD，而后者针对的是Per-Link BFD。

[1] Sharma S,Staessens D,Colle D,et al.Enabling fast failure recovery in OpenFlow networks[C]//Design of Reliable Communication Networks(DRCN),20118th International Workshop on the.IEEE,2011:164-171.

[2] Sharma S,Staessens D,Colle D,et al.OpenFlow:Meeting carrier-grade recovery requirements[J].Computer Communications,2013,36(6):656-665.

[3] Kempf J,Bellagamba E,Kern A,et al.Scalable fault management for OpenFlow[C]//Communications(ICC),2012 IEEE international conference on.IEEE,2012:6606-6610.

[4] Sharma S,Staessens D,Colle D,et al.OpenFlow:Meeting carrier-grade recovery requirements[J].Computer Communications,2013,36(6):656-665.

[5] Van Adrichem N L M,Van Asten B J,Kuipers F A.Fast recovery in software-defined networks[C]//Software Defined Networks(EWSDN),2014 Third European Workshop on.IEEE,2014:61-66.

8.9.4　In-Band Control Recovery

SDN控制平面的组网，可以分为Out-of-Band和In-Band两种模式。在Out-of-Band模式下，交换机通过额外的端口与控制器进行通信，数据流和控制信令的传输路径是彼此独立的。而在In-Band模式下，交换机复用业务端口与控制器进行通信，数据流和控制信令的传输路径会有重合，这意味着一旦网络中的某条链路断掉了，控制器与部分交换机的控制信道有可能就随之断掉了，那么这部分交换机也就不再可控了。因此对于In-Band SDN网络而言，需要设计良好的可用性机制，当链路断掉后能够快速地对受影响的控制信道进行恢复。

对于OpenFlow来说，交换机在没有连接控制器前是没有转发能力的，但是OpenFlow的信令却又需要通过交换机进行传输，因此OpenFlow的In-Band组网，是一个“先有鸡还是先有蛋”的问题。大多数的Hybrid OpenFlow交换机是通过正常来转发In-Band的OpenFlow信令的，或者出厂时嵌入一些高优先级、对控制器不可见的流表专门处理In-Band的OpenFlow信令。其结果是会形成一棵以控制器为根且包含所有交换机的生成树，控制信令在这棵生成树上进行传输。

图8-85　一种OpenFlow In-Band Failover机制

论文[1]中提出了一种OpenFlow In-Band网络的Failover机制，如图8-85所示。控制器通过交换机S1和S2接入，粗实线代表承载控制信令的生成树，一旦S4和S5间的链路断开，那么S5、S6、S7与控制器间的连接就会全部断开。此时控制器发现S4仍然在线，而S5、S6、S7都下线了，根据拓扑进行分析就可以知道是S4和S5间的链路发生了故障，因此控制器会向S3下发规则，使能S3和S5间的链路传输控制信令，这改变了生成树的形态从而恢复与S5、S6和S7间的通信。在与S5、S6和S7重新建立控制信道后，控制器再对数据流的路径进行调整，使得原来需要经过S4、S5间链路传输的流量，绕行到其他可用链路上进行传输。

对于SDN In-Band组网模式可用性问题，目前仍然还不够成熟，有很多问题都需要进行更加深入的研究。论文[2]提出通过BFD来检测控制信道的通断。论文[3]提出在一个多控制器的In-Band网络中，通过Reliable Flooding来保证控制信道高可用。论文[4]讨论了在In-Band组网方式下控制器的摆放问题，目标是使得尽可能多的交换机在失去与控制器的联系后，能够主动切换到备用的控制信道上。

[1] Lee S S W,Li K Y,Chan K Y,et al.Software-based fast failure recovery for resilient OpenFlow networks[C]//Reliable Networks Design and Modeling(RNDM),20157th International Workshop on.IEEE,2015:194-200.

[2] Sharma S,Staessens D,Colle D,et al.Fast failure recovery for in-band OpenFlow networks[C]//Design of reliable communication networks(drcn),20139th international conference on the.IEEE,2013:52-59.

[3] Akella A,Krishnamurthy A.A highly available software defined fabric[C]//Proceedings of the 13th ACM Workshop on Hot Topics in Networks.ACM,2014:21.

[4] Beheshti N,Zhang Y.Fast failover for control traffic in software-defined networks[C]//Global Communications Conference(GLOBECOM),2012 IEEE.IEEE,2012:2665-2670.

8.9.5　OF-based SLB

论文[1]中提出了一种基于OpenFlow的负载均衡机制，它采用的思路是用二叉树对源IP进行前缀归并，对于具有相同源IP前缀的流量，使用一条Wildcard流表匹配并转发给特定的后端服务器。思路非常简单，下面通过一个例子来具体说明一下。

假定OpenFlow交换机后面有R1、R2、R3？3个后端服务器，它们的权值为3：4：1（即3/8：4/8：1/8），由于8是2的整数次幂，因此可以通过完全二叉树来表示三者间源IP前缀的分配关系。在图8-86中，左半部分是通过叶子节点来表示的，源IP前缀为000*、001*、010*的流量分配给R1，011*、100*、101*、110*的流量分配给R2，111*的流量则会分配给R3。聚合之后的结果是源IP前缀为00*和010*的流量分配给R1，011*、10*、110*的流量分配给R2，111*的流量分配给R3，因此控制器需要向交换机下发6条Wildcard流表来实现该分配机制。在图8-86的右半部分是经过优化后的分配机制，此时树左分支上的前缀为0*的流量完全分配给R2，右分支上10*和110*分配给R1，111*仍然分配给R3，这样一来只需要4条Wildcard流表，节约了交换机上的资源。

图8-86　利用二叉树对IP地址进行前缀归并

论文的另一部分内容，讨论了如何动态地对分配关系进行调整，而且尽可能地不破坏已有连接的亲和性。亲和性是指同一个连接中的流量应该分配到相同的后端服务器中，以保持会话的完整。论文中提出了两种调整方式，一种方式是将待调整的流量PacketIn给控制器，控制器来判断流量是否为SYN，如果为SYN则认为是一个新的连接，此时控制器会直接下发一条精确匹配流量的Microflow流表（匹配时优先于Wildcard流表），将新连接的后续流量分配到新的后端服务器上；如果不是SYN则认为是原有的连接，此时控制器会直接下发一条精确匹配流量的Microflow流表，流量仍然会送到原先为其分配的后端服务器上。在第一种方式中，调整的速度较快，但是会对控制器产生一定的冲击，第二种方式是向交换机下发一条低优先级的、送往新后端服务器的Wildcard流表，当原有的Wildcard流表超时60s后（60s内无数据包即可认为连接中断了），流量就会自动匹配低优先级的Wildcard流表，从而在不经过控制器处理的条件下，实现分配关系的调整。

该论文使用了OpenFlow实现服务器负载均衡的早期探索，当时OpenFlow仍处于1.0版本，还没有Group机制，也不能匹配TCP Flag，因此可以看到上面所述的处理办法都是比较原始的。在OpenFlow 1.1中设计了Group的原语，使用Select Group可以非常方便地对流量进行负载均衡，不过在OpenFlow交换机上实现Group是很难做到有状态的，动态增加一个bucket后很难保证连接的亲和性。在OpenFlow 1.5中增加了TCP Flag的匹配能力，交换机可以在本地识别连接的状态，利用Group和TCP Flag就可以通过OpenFlow实现还不错的四层负载均衡了。

[1] Wang R,Butnariu D,Rexford J.OpenFlow-Based Server Load Balancing Gone Wild[J].Hot-ICE,2011,11:12-12.

8.9.6　Anata

论文[1]介绍了微软在Azure中使用的软件负载均衡架构Anata，如图8-87所示，Anata架构可分为3层。第一层是DC Edge Router，负责将VIP流量路由给第二层的MUX，MUX对VIP流量进行负载均衡选择出后端的VM，并封装IPinIP将流量送到该VM所在的服务器上。第三层的Host Agent负责去掉隧道首部，并做NAT将VIP转换为VM实际的DIP，然后通过vSwitch转发给目标VM。实际上，在Anata中还有一个集中式的控制器Anata Manager（图8-87中没有画出），负责分配资源和同步状态等功能，从这个角度来说Anata也可以看作是一种SDN的架构。

图8-87　Anata的架构

对于负载均衡器来说，主要完成的工作包括两个：一个选择出后端的服务器，另一个是做NAT，把目的IP地址从VIP转化为后端目标服务器的DIP（Direct IP）。在Anata中，第一个工作由MUX负责，第二个工作由Host Agent负责。之所以要将NAT分布到Host Agent上实现，是为了缓解MUX上的处理压力，Host Agent也只需要处理与本地后端服务器相关的NAT即可。Host Agent还实现了对DIP的监控，如果DIP的状态出了问题，本地的Host Agent会上报给Anata Manager，Anata Manager再中继给各个MUX，以同步MUX中后端服务器的状态。MUX和Host Agent之间之所以使用IPinIP隧道进行传输，是为了支持跨越三层进行组网，为MUX的部署提供了灵活的位置选项。

为了提供可用性，MUX本身也是分布式的，多个MUX组成了负载均衡器的资源池。那么随之而来的问题就是，MUX实例间的负载均衡如何实现呢？如果继续在MUX中前挂负载均衡器，那么MUX前的负载均衡器就又成为了单点，没有从根本上解决问题。Anata首次提出了通过网络层来解决这一问题的方案，MUX和Router间会运行BGP，MUX会将VIP的/32主机路由通过BGP宣告给Router。一个MUX可以宣告多个VIP，不同的MUX可以宣告相同的VIP，MUX下线时会Withdraw掉VIP的主机路由。在Router上可以看到VIP等价地存在于多个MUX上，因此通过ECMP即可将VIP流量均衡给不同的MUX。不过ECMP是无状态的，而且无法保证把相同源IP的流量都均衡给同一个MUX，因此在不同的MUX上需要采用一致性哈希，来保证相同源IP的流量最终会送给同一个后端服务器。

下面来具体看看流量的转发，图8-88所示为对于从Internet访问VIP流量的处理。首先是Router通过ECMP转发给某个MUX（Step 1），MUX进行负载均衡处理、记录连接的状态（Step 2）并封装IPinIP（Step 3），Host Agent收到后去掉IPinIP的封装（Step 4），做NAT送给目标后端服务器并记录NAT的状态（Step 5）。后端服务器回复的流量被Host Agent拦截（Step 6），Host Agent对其进行反向的NAT（Step 7），然后直接返回给Router而不再经过MUX（Step 8）。Step 8这种处理称为DSR（Direct Server Return），它可以有效地减轻MUX的处理负担。

图8-88　Anata对Internet访问VIP的流量的处理

图8-89所示为从后端服务器主动访问Internet流量的处理。首先后端服务器发出来的流量被Host Agent拦截（Step 1），由于这是一个SNAT的过程，Host Agent并不知道哪些源IP和源端口号是可用的，因此Host Agent会将数据包存入队列，并向Anata Manager请求可用的源IP和源端口号（Step 2）。Anata Manager会将为这次SNAT分配源IP和源端口号，并同步给MUX（Step 3）并回复给Host Agent（Step 4）。Host Agent收到Anata Manager的回复后完成NAT，并以DSR的方式直接发送给Router（Step 5）。流量返回后，Router通过ECMP转发给某个MUX（Step 6），MUX根据Step 3中记录的SNAT状态，封装IPinIP将流量返回给流量源（Step 7），Host Agent收到后解封装做NAT并送给流量源（Step 8）。

图8-89　Anata对后端服务器主动访问Internet流量的处理

为了获得更高的可用性，Anata还设计了一种叫做FastPath的机制，即在不同服务器间VIP互访的流量可以不经过MUX直接进行访问。实现上MUX会判断流量的类型，如果是不同服务器间VIP互访的流量则会对流量源进行重定向，使得后续流量能够直接访问目标后端服务器的DIP。

Google的Maglev[2]和Anata使用了近似的设计，Maglev在实现上的一个亮点是在NIC和User Space Forwarder间实现了共享内存，从而能够旁路掉内核以提高处理的性能。不过文章中只提到了入向流量的负载均衡，并没有介绍对SNAT的处理方式。

[1] Patel P,Bansal D,Yuan L,et al.Ananta:Cloud scale load balancing[C]//ACM SIGCOMM Computer Communication Review.ACM,2013,43(4):207-218.

[2] Eisenbud D E,Yi C,Contavalli C,et al.Maglev:A Fast and Reliable Software Network Load Balancer[C]//NSDI.2016:523-535.

8.9.7　Duet

在论文[1]中，微软又提出了一种名为Duet的负载均衡架构，对Anata进行了优化。优化的出发点是，基于软件的负载均衡器虽然在灵活性、扩展性和可用性上都很好，但是它很耗CPU而且处理的时延较为明显，因此Duet提出了一种利用现有硬件交换机实现HMUX的方法，并利用SMUX作为HMUX的补充，以实现兼具灵活、可扩展、高可用、高性能的负载均衡架构。

图8-90　Duet HMUX中记录VIP-DIP的映射关系

HMUX的设计Forwarding表中存的是/32的VIP，ECMP Table中存的是VIP流量哈希后的Index，每个Index指向Tunneling Table中不同的DIP，如图8-90所示。这种设计要求交换机支持ECMP和Tunnel，并提供对于ECMP和Tunnel表的API，以便对负载均衡进行自动化控制，另外就是交换机需要支持BGP来向Router宣告/32的VIP。对于COTS交换机来说，上述的要求都是可以满足的，但是用COTS实现HMUX的限制在于容量，微软所使用的COTS中/32 Forwarding表的容量只有16K，Tunneling表的容量只有512，这意味着VIP只能支持到16K，而DIP则只能支持到512，这是无法满足Azure的需求的，因此必须通过一些办法来解决HMUX的容量问题。

解决VIP数量受限的思路，就是做分区，将不同的VIP映射到不同的HMUX上，在如图8-91中VIP 1分配到HMUX C2上，VIP 2分配到HMUX A6上。不过，一个VIP所对应的所有后端DIP仍然都需要保存到相应的HMUX上。这种做法实现起来很容易，但有一个问题是如果一个VIP所对应的HMUX出现了问题，那么其他的HMUX就无法起到保护作用了。Duet解决这个问题的思路是通过SMUX进行HMUX的备份，在SMUX上会宣告所有VIP的/32路由，不过其优先级较低，当HMUX正常工作时Router会将VIP流量送给HMUX，当HMUX不可用的时候，路由就会收敛到SMUX。为了保证连接的亲和性，SMUX在选择后端服务器时需要使用和HMUX相同的哈希算法。

图8-91　Duet结合使用SMUX和HMUX

解决了VIP的问题，还要解决DIP的问题。DIP数量的限制，根源在于一个交换机上Tunnel Table的容量太小，如果能够使用多个HMUX进行级联的话，那么就能够解决DIP数量限制的问题了。图8-92是HMUX级联部署的示意图，HMUX 1在封装IPinIP时，外层的目的IP指向二级HMUX的TIP（Transient IP），HMUX 2和HMUX 3收到后解封装，然后再次封装IPinIP，此时外层的目的IP指向目标后端服务器。在这种二级级联架构下，HMUX 1上的VIP后端可以拥有多达512×512个DIP，这就可以充分地满足后端服务器的数量要求了。

图8-92　Duet HMUX的级联部署

[1] Gandhi R,Liu H H,Hu Y C,et al.Duet:Cloud scale load balancing with hardware and software[J].ACM SIGCOMM Computer Communication Review,2015,44(4):27-38.

8.10　大数据优化

8.10.1　BASS

论文[1]提出的BASS，是一种能够根据网络带宽对Task进行调度的算法，Task调度器会从SDN控制器处获得网络中可用带宽的信息，并结合该信息来优化Task的分布。以TKi表示Job的某个任务i，NDj表示集群中的某个节点j，文章首先对Task的调度问题进行了如下建模：

其中，SZi表示Task i所需处理的数据量，BWsrc，j表示数据源节点和目的节点间的带宽，TPi，j表示Task i在Node j进行本地处理所需的时间，ΥIj表示Task i在Node j中做处理前的等待时间，ΥCi，j表示Task i的完成时间。上述等式的意义即为，完成某个Task的时间，为相关数据在网络中的传输时间、数据处理前的等待时间、数据的处理时间三者之和。

以上述模型为基础，简单地介绍一下BASS的思路。如果本地的ΥIj足够小，就意味着Task在本地可以马上得到处理，那么最好的方式就是直接在本地进行处理，以省去数据在网络中的传输时间。如果本地的ΥIj较大，就意味着Task需要在本地等待很长的时间后才能进行处理，那么将Task调度到其他节点（Node）上进行处理可能会好一些。由于其他参数都可以看作是固定的，因此带宽就成为了是否需要调度到其他节点进行处理的决定性因素：如果可用的带宽足够大，很快就可以把数据传输到其他节点，处理可以先于本地节点开始，那么这时就应该调度到其他节点上去。如果带宽不够，传输的时间会很长，那么就不如在本地等待处理。链路上的带宽按照Time Slot进行切分，一旦决定要把Task调度到其他节点上，那么在该Task传输的时间段内，路径上带宽的Time Slot就会预先分配给这个Task，对其他的Task进行调度时这些带宽就变为不可用了。

上面介绍的是使某一个Task完成时间最短的调度方法，这也是对Job中所有的Task进行整体调度，是使得Job完成时间最短方法的基础。由于Job需要等到所有相关Task都完成后才能完成，因此最小化Job Completion Time的目标函数为：

其中，ΥCi，j表示Job所涉及各个Task的完成时间，ΥCi′，j′表示在某种整体调度方式中完成得最慢的Task的完成时间，那么使得ΥCi′，j′最小的那个整体调度方式即为最优。

[1] Qin P,Dai B,Huang B,et al.Bandwidth-aware scheduling with sdn in hadoop:A new trend for big data[J].IEEE Systems Journal,2015.

8.10.2　OFScheduler

论文[1]提出的OFScheduler，旨在通过OpenFlow解决MapReduce中次要流量抢占主要流量带宽的问题。Shuffle流量会直接影响Job Completion Time，是MapReduce的主要流量，MapReduce中还会存在一些为了可用性而设计的replication流量（文中称为load balancing流量），它们属于次要流量。Load balancing流量和shuffle流量都是带宽密集型的，如果load balancing流量占据了过多的链路带宽，就会影响到shuffle流量的传输。OFScheduler解决这一问题的思路是：①MapReduce应用在将流量送入网络前，先进行load balance或者shuffle标记；②如果某条链路出现了拥塞的兆头，就将该链路上的load balancing流量转移到其他轻载链路上，以避免影响该链路上shuffle流量的传输；③如果无法找到合适的轻载链路，就对该链路上的load balancing流量进行限速。

具体来说，与上述所对应的是以下几个内容：

1）OFScheduler要求应用使用ToS对流量进行标记，在流表中额外匹配ToS来对load流量和shuffle流量进行区分。

2）OFScheduler会轮询交换机中的counter以获得链路的实时带宽，对于链路利用率超过阈值的busy link，下发流表将通过该busy link进行传输的部分或者全部load balancing流量转移走，使得该busy link的链路利用率降低到网络中链路的平均水平，并且不允许引入新的拥塞。

3）如果2）中条件不能实现，那么OFScheduler会对busy link上的load balancing流量进行限速，默认会限制到10KB/s以下，等到该busy link上的shuffle流量下降之后，再取消对这些load balancing流量的限速。

[1] Li Z,Shen Y,Yao B,et al.OFScheduler:a dynamic network optimizer for MapReduce in heterogeneous cluster[J].International Journal of Parallel Programming,2015,43(3):472-488.

8.10.3　Phurti

论文[1]提出了Phurti，其目标是在多租户的大数据集群中调度网络资源，以提高集群整体的工作效率。多租户是指一个集群中同时存在不同的Job，此时这些Job彼此会竞争带宽，不合意的竞争结果会大幅度地降低集群整体的工作效率。针对于此，Phurti设计了一套对接Hadoop的API，以获取各个Job的信息，并提出了一种叫做SMSF的网络调度算法，以优化带宽资源在Job间的分配，这与OFScheduler调度同一个Job中不同类型流量的思路是不同的。

Phurti设计的Hadoop API分为四种消息类型：①Job的注册与注销。当一个Job即将开始的时候Hadoop会发送一个Job Start的消息给Phurti，并携带Job ID，相反Job Complete消息表示一个Job已经完成。②Task通知。Hadoop会把MapTask和Reduce Task的Task ID、它所属的Job ID以及它所在的Host传递给Phurti。③数据量发布。当MapTask处理结束后，会把intermediate output的数据量大小发布给Phurti。④Flow的注册与注销。当shuffle流量开始的时候，Hadoop会通过Flow Request消息把shuffle流量的信息传递给Phurti，包括流量的特征、ID以及所属的Job，shuffle流量的结束是以Flow Complete消息来标识的。

通过上述API，Phurti会维护集群中所有Job的信息，并采用SMSF算法为这些Job分配带宽资源。该算法的核心思路是，优先为工作量最小的Job进行服务。为实现SMSF，Plurti会以各个Job中数据量最大的Task的数据量作为Job的Maximum Sequential-Traffic，并根据Maximum Sequential-Traffic为Job分配优先级，Maximum Sequential-Traffic越小Job优先级越高。当一个新的shuffle要开始的时候，Phurti会：①通过SDN控制器得到该shuffle流量的传输路径，以及该路径上所涉及的其他shuffle流量；②判断新shuffle流量所属的Job与路径上其他已有shuffle流量所属Job的优先级，如果新shuffle流量所属Job的优先级最高，那么对路径上其他已有的shuffle流量进行限速，以保证新shuffle得到优先传输，否则新shuffle流量本身会被限速；③当一个shuffle完成的时候，重新判断其传输路径上剩余shuffle流量所属Job的优先级，并通过限速来保证优先级最高者的传输。

可以看到，SMSF是一种允许抢占的算法，为了保证一些低优先级的流量不被饿死，Phurti会定期查看各个Job的状态，如果一个Job中所有的流量一直处于限速的状态，且该状态的持续时间超过了阈值，那么就用接下来的一小段时间优先传输该Job的流量。

[1] Cai C X,Saeed S,Gupta I,et al.Phurti:Application and Network-Aware Flow Scheduling for Multi-Tenant MapReduce Clusters[C]//Cloud Engineering(IC2E),2016 IEEE International Conference on.IEEE,2016:161-170.

8.10.4　Application-Aware Networking

论文[1]提出了一种数据中心网络架构，它能够实现对大数据流量的优化，如图8-93所示。在该架构中，接入层的ToR采用OpenFlow交换机，支持光和电两种上联方式。汇聚层设备分为光交换机和分组交换机，光交换机用于传输带宽型的流量，分组交换机则用于传输非带宽型流量。SDN控制器能够从大数据集群的控制节点处获取流量的特征，通过OpenFlow流表对跨ToR的流量进行分流，将带宽型流量，主要为shuffle流量，分流到光交换机上，其余对带宽要求不高的流量进行分组转发。

图8-93　光电混合的Application-Aware Networking架构

在该架构中，ToR通常会通过多条链路上联光交换机，光交换机会开放控制接口给SDN控制器，允许SDN控制器对光连接进行动态重构，以适应shuffle流量的不同模式。对于many-to-one的模式，可将光连接重构为以reducer所在ToR为根的树形结构，mapper intermediate data size较大的ToR会放在离根较近的位置，mapper intermediate data size较小的ToR会放在离根远一些的位置。对于many-to-many的模式，可将光连接重构为以2D-Torus结构，以利用该结构中丰富的多路径特性进行路由。对于partially overlapping的模式，则可将其分解为多个独立的many-to-one和many-to-many模式，并对分解后得到的模式分别进行相应的处理。它们的拓扑重构如图8-94所示。

图8-94　many-to-one（左）和many-to-many（右）模式下的拓扑重构

类似地，论文[2]也提出了光电混合的数据中心网络架构，通过SDN对光连接的重构来优化大数据集群中流量不同的*cast模式，如unicast（one-to-on）、broadcast（one-to-many）、incast（many-to-one）以及all-to-all cast（many-to-many）。

[1] Wang G,Ng T S,Shaikh A.Programming your network at run-time for big data applications[C]//Proceedings of the first workshop on Hot topics in software defined networks.ACM,2012:103-108.

[2] Samadi P,Calhoun D,Wang H,et al.Accelerating cast traffic delivery in data centers leveraging physical layer optics and SDN[C]//Optical Network Design and Modeling,2014 International Conference on.IEEE,2014:73-77.

8.10.5　CoFlow

论文[1]对不同的大数据架构进行了分析，抽象出了图8-95所示的6种通信模式，并设计了一套通用的CoFlow API，用于描述集群中不同角色间的通信模式与需求。CoFlow的概念可以对应地理解为，不同角色间的通信流量所组成的集合，CoFlow API提供了5种原语：Create表示一个CoFlow初始化的同时指定其通信模式，Update用于更新某个CoFlow的参数，Put表示发送方要将数据发送到接收方同时指定流量的特征，Get表示接收方要从发送方请求数据，Terminate表示一个CoFlow的结束。SDN控制器可以接收CoFlow API，进而了解通信的模式与需求，然后在不同的CoFlow间协调网络资源的分配。

论文[2]提出的PANE，是一种SDN网络为应用提供的通用API框架，主要用于保证最小带宽、为路由增加策略，以及对某些流量进行限速，等等。文章给出了多类应用使用PANE来控制网络的示例，其中包括Hadoop为shuffle流量进行带宽的预留。

图8-95　大数据架构下的6种通信模式

[1] Chowdhury M,Stoica I.Cofl ow:A networking abstraction for cluster applications[C]//Proceedings of the 11th ACM Workshop on Hot Topics in Networks.ACM,2012:31-36.

[2] Ferguson A D,Guha A,Liang C,et al.Participatory networking:An API for application control of SDNs[C]//ACM SIGCOMM computer communication review.ACM,2013,43(4):327-338.

8.11　本章小结

网络是门系统的、复杂的工程技术，虽然数据中心网络相比于运营商网络要简单一些，但是仍然会涉及诸多方向的问题。除了本章所介绍的一些方向以外，还包括节能、运维、Debug、一致性等，限于篇幅未能够进行介绍。通过本章的介绍，笔者所期待的，是数据中心网络产品的设计者们，可以不囿于现有的市场需求，从中选择出一些有实际意义，并且可操作的思路进行实现，让技术创新在数据中心网络中生根发芽。

第9章　番外——容器网络

坦白地说，网络从来都不是数据中心和云计算的核心，可见的未来也不会是。从物理服务器到虚拟机，再到眼下红得发紫的容器，计算资源的形态之争，才是真正的主战场。本章来谈一谈容器世界的网络。

9.1　容器网络概述

容器这两年可谓风生水起，相比虚拟机来说，容器更轻，一台服务器上可以运行成百上千个容器，这意味着可以有更为密集的计算资源。因此基于容器运行工作负载的模式深受云服务提供商的青睐。

然而对于云管理员来说，管理容器确是一件相当头疼的事情，容器的生命周期更短了，容器的数量更多了，容器间的关系更复杂了。为了简化大规模容器集群的运维，各路容器管理与编排平台应运而生，Docker社区开发了Swarm+Machine+Compose的集群管理套件，Twitter主推Apache的Mesos，Google则开源了自己的Kubernetes。这些平台为大规模的容器集群提供了资源调度、服务发现、扩容缩容等功能，然而这些功能都是策略性的，真正要实现大规模的容器集群，网络才是最基础的一环。

相比于虚拟机网络，容器网络主要具有以下特点，以及相应的技术挑战：

1）虚拟机拥有完善的隔离机制，虚拟网卡与硬件网卡在使用上没有什么区别，而容器则使用network namespace提供网络在内核中的隔离，因此为了保证容器的安全，容器网络的设计需要更为慎重的考虑。

2）出于安全考虑，很多情况下容器会部署在虚拟机内部，这种嵌套部署（nested deployment）需要设计新的网络模型。

3）容器的分布不同于虚拟机，一个虚拟机运行的业务可能要拆分到多个容器上运行。根据不同业务的需要，这些容器有时候必须放在一台服务器中，有时候可以分布在网络的各个位置，两种情况对应的网络模型很可能不尽相同。

4）容器的迁移速度更快，网络策略的更新要能够跟得上其速度。

5）容器数量更多了，多主机间的ARP泛洪会造成大量的资源浪费。

6）容器生命周期短，重启非常频繁，网络地址的有效管理（IPAM）将变得非常关键。

不过，由于容器自身的特征使得它与应用的绑定更为紧密。从交付模式来看，更倾向于PaaS而非IaaS，因此容器网络并没有成为业界最初关注的焦点。它起步较晚，再加上上述诸多的技术挑战，使得容器网络相比于OpenStack Neutron来说发展的情况要落后不少。Docker在开始的很长一段时间内只支持使用Linux Bridge+Iptables进行single-host的部署，自动化方面也只有Pipework这类shell脚本。

幸运的是，目前业界已经意识到了可扩展、自动化的网络对于大规模容器环境的重要性：Docker收购了容器网络的创业公司socketplane，随即将网络管理从docker daemon中独立出来形成了libnetwork，并在Docker 1.9中提供了多种network driver，并支持了multi-host。一些专业的容器网络（如Flannel、Weave、Calico等）也开始与各个容器编排平台进行集成。OpenStack社区也成立了专门的子项目Kuryr，它提供Neutron network driver（如DragonFlow、OVN、Midonet等）与容器对接。

9.2　容器网络模型

本节来介绍容器网络的基础，包括容器的接入、容器间的组网，以及几种容器网络的通用模型。

9.2.1　接入方式

1.和host共享network namespace

在这种接入模式下，不会为容器创建网络协议栈，即容器没有独立于host的network namespace，但是容器的其他namespace（如IPC、PID、Mount等）还是和host的namespace独立的。容器中的进程处于host的网络环境中，与host共用L2～L4网络资源。该方式的优点是，容器能够直接使用host的网络资源与外界进行通信，没有额外的开销（如NAT），如图9-1所示；缺点是网络的隔离性差，容器和host所使用的端口号经常发生冲突。

图9-1　容器和host共享network namespace

2.和host共享物理网卡

这种方式与上一种方式的区别在于，容器和host共享物理网卡，如图9-2所示，但容器拥有独立于host的network namespace，容器有自己的MAC地址、IP地址、端口号。这种接入方式主要使用了SR-IOV技术，每个容器分配一个VF，直接通过硬件网卡与外界通信，优点是旁路了内核不占任何计算资源，而且IO速度较快，缺点是VF数量有限且对迁移的支持不足。

图9-2　容器和主机共享物理网卡

3.和另外一个容器共享network namespace

在这种方式中，容器没有独立的network namespace，但是以该方式新创建的容器将与一个已经存在的容器共享其network namespace（包括MAC、IP以及端口号等），如图9-3所示。从网络角度上两者将作为一个整体对外提供服务，不过两个容器的其他namespace（如IPC、PID、Mount等）是彼此独立的。这种方式的优点是，network namespace相关的容器间的通信高效便利，缺点是由于其他的namespace仍然是彼此独立的，因此容器间无法形成一个业务逻辑上的整体。

图9-3　容器和其他容器共享network namespace

4.通过vSwitch/vRouter接入

在这种方式中，容器拥有独立的network namespace，通过veth-pair连接到vSwitch/vRouter上。这种方式对于网络来说是最为直接的，在vSwitch/vRouter看来，通过这种方式连接的容器与虚拟机并没有任何区别。vSwitch的实现有很多，如Linux Bridge，Open vSwitch等，可用于容器间二层流量的互通，能够对VLAN、Tunnel、SDN Controller等高级功能进行支持。vRouter可用于容器间三层流量的路由，能够对BGP、Tunnel、SDN Controller等提供高级功能的支持。

5.Macvlan/IPvlan

通过vSwitch或者vRouter来接入容器，虽然可以实现丰富的功能，但是却引入了相当的复杂性。Macvlan/IPvlan是由Linux原生提供的网络虚拟化方案，由于二者在部署和使用上的简单性，因此在容器网络中得到了广泛的使用。

Macvlan将一个物理网卡虚拟出多个虚拟网卡，并提供多个虚拟的MAC地址，使用macvlan接入的容器即会获得虚拟的MAC地址。物理网卡在收到流量后，在发送到协议栈前，会在RX函数中判断目的MAC地址，如果为本地容器的虚拟MAC则直接发给相应的namespace。容器的虚拟网卡在发出流量时，会在TX函数中判断目的MAC地址，如果为本地容器的虚拟MAC则直接发给相应的namespace，否则交给物理网卡来处理。Macvlan提供了Bridge、VEPA、Private、Pass Through等多种工作模式。IPvlan在一个物理网卡上虚拟出多个IP地址，使用IPvlan接入的容器会复用物理网卡的MAC地址，并获得虚拟的IP地址。物理网卡在收到流量后，在发送到协议栈前，会在RX函数中判断目的IP地址，如果为容器的IP则直接发给相应的namespace。容器的虚拟网卡在发出流量时，会在TX函数中判断目的IP地址，如果为本地容器的虚拟IP则直接发给相应的namespace，否则交给宿主机的协议栈进行处理。IPvlan提供了L2和L3两种工作模式，其中L3模式可以实现不同网段间的路由。

6.嵌套部署在VM中

图9-4所示为容器嵌入在虚拟机中的情形，这种方式在生产环境也比较常见，由于一台host中往往部署着多方的容器，所示存在安全隐患，因此许多用户会选择先启动自己的虚拟机，然后在自己的虚拟机上运行容器。这种方式其实是一种嵌套虚拟化。从本质上来说，在这种方式下容器的接入对于host可以是完全透明的，容器在虚拟机内部的接入可以采用上述其他方法。不过这对于云平台来说，这就意味着失去了对容器接入的管理能力，为了保留这一能力，往往需要在虚拟机内部和host中分别部署vswitch并实现级联，由虚拟机内部的vswitch来接入容器并对其进行特定的标记（云平台分配），以便host中的vswitch对其进行识别。一种常见的方式是使用Open vSwitch对容器标记VLAN ID。

图9-4　容器嵌入在虚拟机中

9.2.2　跨主机通信

1.Flat

Flat可分为L2 Flat和L3 Flat。L2 Flat指在各个host中所有的容器都在虚拟+物理网络形成的VLAN大二层中，容器可以在任意host间进行迁移而不用改变其IP地址。L3 Flat指在各个host中所有的容器都在虚拟+物理网络中可路由，且路由以/32的形式存在，使得容器在host间迁移时不需要改变IP地址。在L2/L3 Flat下，不同租户的IP地址不可以Overlap，在L3 Flat下容器的IP编址也不可与物理网络Overlap。L3 Flat简单示意如图9-5所示。

图9-5　L3 Flat示意图

2.Hierarchy

在L3 Hierarchy的各个host中所有的容器都在虚拟+物理网络中可路由，且路由在不同层次上（VM/Host/Leaf/Spine）以聚合路由的形式存在，即处于相同CIDR的容器需要在物理位置上组织在一起，因此容器在host间迁移时需要改变IP地址。在L3 Hierarchy下，不同租户的IP地址不可以Overlap，容器的IP编址也不可与物理网络Overlap。图9-6所示为L3 Hierarchy中的IP地址规划示例。

图9-6　L3 Hierarchy示例

3.Overlay

Overlay主要的实现方式有L2overL3和L3overL3。在L2overL3中，容器可以跨越L3 Underlay进行L2通信，容器可以在任意主机间进行迁移而不用改变其IP地址。在L3overL3中，容器可以跨越L3 Underlay进行L3通信，容器在主机间进行迁移时可能需要改变IP地址（取决于Overlay是L3 Flat还是L3 Hierarchy）。在L2/L3 Overlay下，不同租户的IP地址也可以Overlap，容器的IP编址也可以与Underlay网络Overlap。L2 over L3（VxLAN实现）如图9-7所示。

图9-7　L2 over L3示意图

9.2.3　通用数据模型

1.CNM

CNM（Container Network Model）是Cisco的一位工程师提出的一个容器网络模型，Docker 1.9在Libnetwork中实现了CNM，现在CNM已经被Kuryr、OVN、Calico、Weave和Contiv等公司和项目所采纳。CNM的示意如图9-8所示，它主要建立在3类组件上：Sandbox、Endpoint和Network。

1）Sandbox：一个Sandbox对应一个容器的网络栈，能够对该容器的接口、路由、DNS等参数进行管理。一个Sandbox中可以有多个Endpoint，这些Endpoint可以属于不同的Network。Sandbox的实现可以为Linux网络命名空间、FreeBSD Jail或其他类似的机制。

图9-8　CNM模型

2）端点：Sandbox通过端点接入网络，一个端点只能属于一个Network。端点的实现可以是veth pair、Open vSwitch内部端点或者其他类似的设备。

3）网络：一个网络由一组端点组成，这些端点彼此间可以直接通信，不同网络间端点的通信彼此隔离。网络的实现可以是Linux Bridge、Open vSwitch等。

Libnetwork对于CNM的实现包括以下5类对象。

1）NetworkController：每创建一个网络对象时，就会相应地生成一个Network-Controller对象，NetworkController对象将网络对象的API暴露给用户，以便用户对libnetwork进行调用，然后驱动特定的Driver对象实现网络对象的功能。NetworkController允许用户绑定网络对象所使用的Driver对象。NetworkController对象可以看作是网络对象的分布式SDN控制器。

2）Network：Network对象是CNM Network的一种实现。NetworkController对象通过提供API对Network对象进行创建和管理。NetworkController对象需要操作Network对象的时候，Network对象所对应的Driver对象会得到通知。一个Network对象能够包含多个端点对象，一个Network对象中包含的各个端点对象间可以通过Driver完成通信，这种通信支持可以是同一主机的，也可以是跨主机的。不同Network对象中的端点对象间彼此隔离。

3）Driver：Driver对象能真正实现Network功能（包括通信和管理），它并不直接暴露API给用户。Libnetwork支持多种Driver，其中包括内置的bridge，host，container和overlay，也支持remote driver（即第三方或用户自定义的网络驱动）。

4）Endpoint：Endpoint对象是CNM Endpoint的一种实现。容器通过Endpoint对象接入Network，并通过Endpoint对象与其他容器进行通信。一个Endpoint对象只能属于一个Network对象，Network对象的API对于Endpoint对象提供了创建与管理。

5）Sandbox：Sandbox对象是CNM Sandbox的一种实现。Sandbox对象代表了一个容器的网络栈，它拥有IP地址、MAC地址、路由、DNS等网络资源。一个Sandbox对象可以有多个Endpoint对象，这些Endpoint对象可以属于不同的Network对象，Endpoint对象使用Sandbox对象中的网络资源与外界进行通信。Sandbox对象的创建发生在Endpoint对象创建后，（Endpoint对象所属的）Network对象所绑定的Driver对象为该Sandbox对象分配网络资源并返回给libnetwork，然后libnetwork使用特定的机制（如linux netns）配置Sandbox对象中对应的网络资源。

2.CNI

CNI（Container Networking Interface）是CoreOS为Rocket（Docker之外的另一种容器引擎）提出的一种基于插件的容器网络接口规范。CNI十分符合Kubernetes中的网络规划思想，Kubernetes采用CNI作为默认的网络接口规范，目前CNI的实现有Weave、Calico、Romana、Contiv等。

CNI没有像CNM一样规定模型的术语，CNI的实现依赖于两种插件：CNI Plugin负责将容器与host中的vBridge/vSwitch进行连接，IPAM Plugin负责配置容器namespace中的网络参数。

CNI使用起来很简洁，仅要求CNI Plugin支持容器的增加/删除操作，操作所需的参数规范如下。

1）Version：使用的CNI Spec版本。

2）Container ID：容器在全局（管理域内）唯一的标识，容器被删除后可以重用。Container ID是可选参数，CNI建议使用。

3）Network namespace path：netns要被添加的路径，如/proc/[pid]/ns/net。

4）Network configuration：一个JSON文件，描述了容器要加入的网络参数。

5）Extra arguments：针对特定容器要进行细粒度的配置。

6）Name of the interface inside the container：容器接口在容器namespace内部的名称。

其中，网络配置的schema如下。

1）cniVersion：使用的CNI Spec版本。

2）name：网络在全局（管理域内）唯一标识。

3）type：CNI Plugin的类型，如bridge/OVS/macvlan等。

4）ipMasq：布尔类型，host是否需要对外隐藏容器的IP地址。CNIPlugin可选支持。

5）ipam：网络参数信息。

·type：分为host-local和dhcp两种。

·routes：一个路由列表，每一个路由表项包含dst和gw两个参数。

6）DNS：DNS相关参数包括nameservers、domain、search domains、options等。

为了减轻CNI Plugin的负担，ipam由CNI Plugin调用IPAM Plugin来实现，IPAM Plugin负责配置容器namespace中的网络参数。IPAM的实施分为两种：一种是host-local，在subnet CIDR中选择一个可用的IP地址作为容器的IP，路由表项（可选）在host本地配置完成。另一种是DHCP，容器发送DHCP消息请求网络参数。

执行增加操作后，会返回接口的IP地址以及DNS信息的两个结果，删除就不说了。

9.3　Docker网络

Docker是最为普及的容器引擎，为实现大规模集群，Docker推出了Swarm+Machine+Compose的集群管理套件。然而，Docker的原生网络在很长一段时间内都是基于Linux Bridge+Iptables实现的，在这种方式下容器的可见性只存在于主机内部，这严重地限制了容器集群的规模以及可用性。其实，社区很早就意识到了这个问题，不过由于缺乏专业的网络团队支持，因此Docker的跨主机通信问题始终没有得到很好的解决。另外，手动配置Docker网络是一件很麻烦的事情，尽管有Pipework这样的shell脚本工具，但是就脚本的自动化程度而言，用来运维大规模的Docker网络还是不现实的。

2015年3月，Docker收购了一家SDN初创公司socketplane，随即于5月宣布将网络管理功能从libcontainer和docker daemon中抽离出来作为一个单独的项目libnetwork，由原socketplane团队成员接手，基于GO语言进行开发。2015年11月发布的docker 1.9中，libnetwork架构已初步形成，它支持多种nework driver并提供跨主机通信，并在后续的1.10、1.11两个版本中修复了大量Bug。目前，libnetwork处于0.9版本。

9.3.1　docker0

在Docker 1.9之前，网络的实现主要由docker daemon来完成，当docker daemon启动时默认情况下它会创建docker0，为docker0分配IP地址，并设置一些iptables规则。然后通过docker run命令启动容器，该命令可以通过-net选项来选择容器的接入方式，在docker 1.9之前的版本支持如下4种接入方式。

1）bridge：新建容器有独立的network namespace，并通过以下步骤将容器接入docker0中。

①创建veth pair；

②将veth pair的一端置于host的root network namespace中，并将其关联docker0；

③将veth pair的另一端置于新建容器的network namespace中；

④从docker0所在的subnet中选一个可用的IP地址赋予veth pair在容器的一端。

2）host：新建容器与host共享network namespace，该容器不会连接到docker0中，直接使用网络的网络资源进行通信。

3）container：新建容器与一个已有的容器共享network namespace，该容器不会连接到docker0中，直接使用已有容器的网络资源进行通信。

4）none：新建容器有独立的网络namespace，但是不会配置任何网络参数，也不会接入docker0中，用户可对其进行任意的手动配置。

图9-9　Docker的Bridge模式

后三种没什么好说的，下面介绍一下Bridge方式，如图9-9所示。Docker0由Linux Bridge实现，容器通过veth设备接入docker0，本地容器都处于同一子网中，彼此间通过docker0交换通信，与外界通信以docker0的IP地址作为网关。Docker0的IP地址可以看作是连接在Linux Bridge上的内置设备，位于host的root namespace中。容器与外界的通信要依赖于host中的Iptables MASQUERADE规则来做SNAT，容器对外提供服务要依赖于host中的Iptables dnat规则来暴露端口。因此在这种方案中，容器间的跨主机通信使用的都是主机的socket，容器本身的IP地址和端口号对其他host上的容器来说都是不可见的。

这个方案非常原始，除了不能支持直接可见的跨主机通信以外，NAT还会导致很多其他不满意的结果，如端口冲突等。另外，对于一些复杂的需求，如IPAM、多租户、SDN等均无法提供支持。

9.3.2　pipework

容器就是namespace，docker0就是Linux Bridge，再加上一些Iptables规则，实际上容器组网就是调用一些已有的命令行而已。不过，当容器数量很多，或者频繁启动、关闭时，一条条命令行去配置就显得不是很合适了。于是，Docker公司的一个工程师就写了一个shell脚本来简化容器网络的配置，主要就是对docker/ip nets/ip link/brctl这些命令行的二次封装。虽然Pipework在形式上比较原始，但是从实用性的角度来看，确实倒也可以满足一些自动化运维的需要。

当然Pipework相比于docker0，除了提供了命令行的封装以外，还是具备一些其他优势的，比如支持多样的network driver如OVS和Macvlan，支持在host上使用dhcp-server为容器自动分配IP地址，支持免费ARP，等等。

9.3.3　libnetwork

socketplane作为一家做容器网络的初创公司，于2014年第4季度创建，2015年3月份就被Docker收购了，可以看到当时Docker对于原生的网络管理组件的需求是有多么迫切，而且socketplane团队的人是SDN科班出身的，Docker也总算有了搞网络的正规军。不过，socketplane和Libnetwork的设计在架构上还是有很大不同的，下面通过图9-10先来看看socketplane的设计。

图9-10　socketplane的架构

在架构上，数据平面是OVS VxLAN，南向协议是OVSDB，控制平面是基于Consul的分布式Key/Value存储，北向是socketplane CLI。在控制平面的部署细节上，Consul是放在一个socketplane容器中的，该容器通过host模式与host共享network namespace，Consul通过eth0去进行服务发现和状态同步，状态主要就是指容器与host IP的映射关系。数据平面的流表情况，就是匹配MAC+IP，动作就是送到本地的容器或者隧道远端上，有点奇怪的是socketplane没有使用tunnel_id，而是用了vlan_id标识vnet。根据为数不多的资料来看，socketplane在被收购前只完成了L2的东西向流量，还没有考虑路由和南北向流量。

可以看到的是，socketplane的设计并不复杂。但是收购进Docker后，要考虑的事情就多了：首先，数据平面不可以演化为OVS一家独大的情况，Linux Bridge要有，第三方driver也得玩得转。其次，控制平面Key/Value存储也要可插拔，起码要支持ZooKeeper和Etcd，最好还要把自家的集群工具Swarm集成进来。另外，要考虑老用户的习惯，原有的网络设计该保留的还要保留。最后，还要遵循社区提出的容器网络模型CNM。

于是，Docker网络在1.9版本时变成了图9-11中的架构，libkv提供Swarm的服务发现，以及Overlay网络的Key-Value Store，每个主机上开启docker daemon并加入Swarm集群，libcontainer负责管理容器，libnetwork负责管理网络。libnetwork支持5种网络模式：none/host/bridge/overlay/remote，在图9-11中从左到右依次显示了后4种，其中overlay和一些remote可以支持multi-host。

图9-11　Docker 1.9之后的网络

Overlay是libnetwork默认的multi-host网络模式，通过VxLAN完成跨主机的工作。Libnetwork会把overlay driver放在单独的network namespace中，默认的overlay driver为Linux Bridge。当容器（Sandbox）接入overlay（Network）时，会分到两个网卡（Endpoint）上，eth0连在vxlan_driver上，eth1连在docker_gwbridge上。Vxlan_driver主要负责L2的通信，包括本地流量和跨主机流量，docker_gwbridge的实现原理和docker0一样，负责处理应用服务的通信，包括不同网络容器间，以及容器与Internet间两类流量。Eth0和eth1各有一个IP地址，分属于不同网段，eth0默认以10开头，eth1默认以172开头，L2和L3的通信直接通过容器内部的路由表分流，送到不同的设备上进行处理。

Remote是libnetwork为了支持其他的networking driver而设计的一种可插拔的框架，这些driver不一定要支持multi-host。除了一些第三方的driver外（如Weave、Calico等），目前libnetwork还提供了对Macvlan driver和IPvlan driver的支持。当然，就像Neutron的ML2一样，为了打造生态，plugin driver的接口还是要libnetwork自己来规范的。

既然说是引入SDN，那么API的规范对于libnetwork来说就十分重要了，不过目前libnetwork的接口封装还处于相当初级的阶段，基本上就是对Network和Endpoint的创建、删除以及连接，并没有提供很友好的API业务。

对于libnetwork的介绍就是这些了。尽管libnetwork实现了原生千呼万唤的multi-host，也为Docker的原生网络带来了一定的灵活性与自动化。但就目前来说，它的API还不够友好，Driver的生态还不够成熟，而且并不具备任何高级的网络服务。相比于OpenStack Neutron而言，Docker的libnetwork目前仍然存在着较大的差距。

9.4　Kubernetes网络

Kubernets是Google开源出来的Container Orchestrator，通常被简称为K8S。K8S的前身是Google内部使用的容器集群管理平台Borg，K8S凝聚了Borg多年来所积累的设计经验，能够支撑大规模、高可用的容器部署与管理，目前已经在很多公司的容器生产环境中得到了落地。虽然没有自己的容器引擎，但是K8S对Docker、Rkt的支持却做得十分优秀。经过短短的三四年时间，K8S已经成长为和OpenStack处于同一级别的云操作系统。相比之下，Docker原生的编排器Swarm在技术底蕴上就很难和K8S相提并论了，目前Swarm在功能和可用性上和K8S都存在着不小的差距。

K8S提供了众多的功能，如资源调度、服务发现、健康监控、负载均衡、弹性扩缩容、容灾恢复，等等。在网络方面，K8S的设计也非常有特色，相比于Docker早期基于NAT的docker0，K8S一开始就提出了以下的网络设计原则。

1）容器间的两两通信不能使用NAT。

2）容器和物理节点间的通信不能使用NAT。

3）容器看到的自己的地址，和别的容器/物理节点看到的地址是同一个地址（实际上和前两点是重复的）。

9.4.1　基于POD的组网模型

K8S之所以抛弃了NAT，一方面是因为NAT本身会影响容器的跨节点通信，另一方面是由于在大规模容器环境中，维护NAT的端口映射关系也是非常麻烦的。基于上述指导思想，K8S提出了“Per POD Per IP”的概念。POD是K8S中可以被创建、销毁、调度的最小单元，一个POD中有一个基础容器（pause container）以及一个或一组应用容器。基础容器对应一个独立的network namespace，应用容器间则共享基础容器的network namespace（包括MAC、IP以及端口号等），还可以共享基础容器的其他namespace（如IPC、PID、Mount等）。POD作为一个整体，对外表现出的IP地址即为基础容器的IP地址，这个IP称为POD IP。POD IP对集群中所有其他的POD都是可见的，容器间的访问直接通过POD IP进行，基础容器会完成到应用容器的端口转换。K8S中基于PoD的组网模型如图9-12所示。

图9-12　K8S中基于POD的组网模型

为了便于理解，可以将一个POD看作是一个抽象的“虚拟机”，POD里面运行的不同容器就相当于若干个不同的进程。一些在业务上密切相关的容器，如APP和DB，可以部署在同一个POD中，共享POD的全部资源，同时又不会产生资源冲突。这些容器间的通信可直接通过localhost或者IPC完成，非常高效。

K8S中将物理节点称为Minion，一个Minion中会有一个docker 0，这个docker 0将作为该Minion中不同POD IP的默认网关，不过docker0不会做NAT，而是会直接完成POD IP间的路由，因此同一Minion中的POD处于同一个子网中，不同Minion中的POD将处于不同子网中。至于docker0间的通信方式，在K8S中就比较多样化了。K8S原生提供了一种叫做Kubenet的Dummy网络插件，不过它只实现了容器的接入，并不能解决路由的问题。在v1.1中，K8S开始支持CNI的数据模型，提供了可插拔的框架，所有实现了CNI的Network Driver都可以集成到K8S中。

9.4.2　Service VIP机制

相比于虚拟机，容器更加重视应用的交付，因此K8S中提供了强大的Service机制。在K8S中，POD是一组container的集合，而Service是一组提供相同应用的POD集合，K8S通过Service来支持应用的交付。每个Service都会分配或者指定一个VIP，这个VIP是Service的前端IP地址，访问这个VIP的流量即会被均衡到Service后端的POD IP上。

K8S通过kube-proxy来实现VIP的负载均衡，每个物理节点都有一个kube-proxy，其实现分为userspace kube-proxy和Iptables kube-proxy两种。如图9-13所示，上面的是userspace方式，下面的是Iptables方式。在Userspace方式中，API的监听、Iptables的配置以及数据流的负载均衡均由kube-proxy实现，属于on path模式。在Iptables方式中，kube-proxy只负责监听API、配置Iptables，而不需要处理数据流，属于off path模式。比较两种方式，由于userspace kube-proxy需要在userspace中处理流量，因此CPU的利用率和流量的处理延迟都比较高，而且如果kube-proxy挂掉了流量就没办法正常转发了，而在iptables kube-proxy中则不存在这些问题，K8S在v1.2以后默认采用的是Iptables kube-proxy。

图9-13　kube-proxy实现负载均衡的两种模式

K8S还提供了多种Service的访问方式（Service Type）：

1）ClusterIP：通过Cluster IP来访问Service，Cluster IP只对K8S集群内部可见，是默认的Service Type。

2）NodePort：在K8S集群外可通过物理节点的IP+物理节点上的端口号来访问Service。

3）LoadBalancer：通过第三方的LB，提供K8S集群外到Service的负载均衡。

4）ExternalName：为Service映射一个外部域名，对负载均衡机制不会产生影响。

上面的描述比较抽象，具体来说：Cluster IP的主要作用是方便POD到POD之间的调用，ClusterIP会在每个物理节点上使用Iptables，将发向ClusterIP对应端口的流量，转发给本地的kube-proxy，kube-proxy再通过负载均衡将流量转发给后端的POD。NodePort会在物理节点上开一个端口（可以用户自己指定），集群外部发向该物理节点IP对应端口的流量，会被导入kube-proxy，然后同样由kube-proxy均衡给后端的POD。LoadBalancer相比于NodePort多做了一步，在多个物理节点前再挂上一个第三方的LB，流量先访问LB提供的ingress IP，第三方的LB会将流量均衡给不同的物理节点，后面的处理和NodePort就一样了。

综合上面的介绍，K8S中数据流的处理方式如下所示：

1）POD-to-POD的通信，直接通过POD IP进行路由，不需要做NAT。

2）POD-to-Service的通信，POD通过ClusterIP访问Service，流量被POD本地的kube-proxy截获，并负载均衡到Service后端相应的POD上。

3）External-to-Internal的通信，外部流量可访问NodeIP+NodePort，或者访问LB前端的IP+端口，最后仍然是由kube-proxy截获，并负载均衡到Service后端相应的POD上。

另外，K8S还通过Network Policy为Service提供访问控制机制。每个Service在创建时可以为其分配一个Label，其他Service可以通过selector来限制入向流量的Label。Label也可以针对容器进行分配，提供更细粒度的访问控制策略，实现容器网络的“微分段”。Network Policy是一种白名单的机制，所有没有明确允许的流量都会被阻止。要注意的是，K8S只提供了Network Policy的描述机制，它需要依赖于第三方的Network Driver去做Network Policy的后端实现。

9.5　第三方组网方案

其实，早在Docker社区将Libnetwork提上日程之前，就已经有不少支持multi-host的容器网络方案了。除了socketplane以外，如CoreOS为K8S设计的Flannel，通过P2P的控制平面构建Overlay的Weave Net，通过BGP构建Flat L3的Calico，通过优化IPAM逻辑来构建Hierarchy L3的Romana，Cisco派系侧重于策略的Contiv。当然，在网络规模不大时，直接手配OVS也是个可行的方案。本节就来介绍上述的容器网络。

9.5.1　Flannel

K8S的网络以POD为单位，每个POD的IP地址，容器通过POD方式接入网络，一个POD中可包含多个容器，这些容器共享该POD的IP地址。另外，K8S要求容器的POD IP地址都是可直接路由的，那么显然docker0+Iptables的NAT方案是不可行的。

实现上述要求其实有很多种组网方法。Flat L3是一种，Hierarchy L3是一种，另外L3 Overlay也是可以的，Flannel主要采用的是L3 Overlay的方式，并规定每个host下各个POD属于同一个子网，不同的host/VM下的POD属于不同子网。如图9-14所示，在Flannel的架构中，控制平面上的host本地的flanneld负责从远端的Etcd集群同步本地和其他host上的子网信息，并为POD分配IP地址。数据平面的Flannel通过UDP封装来实现L3 Overlay，这样既可以选择一般的TUN设备又可以选择VxLAN设备。Flannel的控制平面如图9-14所示。Flannel的数据平面如图9-15所示。

Flannel做得比较早，技术选型也十分成熟，已经可以用于大规模部署。Flannel还有一种host-gw模式，可以直接路由实现Flat L3。

图9-14　Flannel的控制平面

图9-15　Flannel的数据平面

9.5.2　Weave

Weave是Weaveworks公司的容器网络产品，业界习惯叫做Weave，实际上目前该产品的名字叫做Weave Nets。因为Weaveworks现在并不是一家只做网络的公司，最近它又做了两款其他的容器管理产品：GUI和集群。不过，为大家所熟悉的还是它网络这一块的产品。

不同于其他multi-host方案，Weave可以支持去中心化的控制平面，如图9-16所示。各个host上的wRouter间通过建立Full Mesh的TCP链接，可通过Gossip来同步控制信息。这种方式省去了集中式的K/V Store，在一定程度上能够降低部署的复杂性，Weave将其称为“data centric”，而非RAFT或者Paxos的“algorithm centric”。

不过，考虑到Docker libnetwork是用集中式的K/V Store作为控制平面的，因此Weave为了集成docker，它也提供了对集中式控制平面的支持，作为remote driver与libkv通信。

图9-16　Weave去中心化的控制平面

在数据平面上，Weave通过UDP封装实现L2 Overlay。封装支持两种模式，一种是运行在用户空间的sleeve mode，如图9-17所示。另一种是运行在内核空间的fastpath mode，如图9-18所示。Sleeve mode通过pcap设备在Linux bridge上截获数据包并由wRouter完成UDP封装，支持对L2流量进行加密，还支持Partial Connection，但是性能会损失明显。Fastpath mode通过OVS的odp封装VxLAN并完成转发，wRouter不直接参与转发，而是通过下发odp流表的方式控制转发，这种方式可以明显提升吞吐量，但是不支持加密等高级功能。

这里要说一下Partial Connection的组网。在多数据中心场景下一些站点无法直连，如图9-19中Peer 1与Peer 5间的隧道通信，中间势必要经过Peer 3，那么Peer 3就必须要支持隧道的中间转发。sleeve mode的实现是通过多级封装来完成的，在fastpath上还没有实现。

图9-17　Weave数据平面的sleeve mode

图9-18　Weave数据平面的fastpath mode

图9-19　Partial Connection拓扑示意

上面主要介绍的是Weave对multi-host L2的实现。关于Service的发布，Weave做得也比较完整。首先，wRouter集成了DNS功能，能够动态地进行服务发现和负载均衡，另外，与libnetwork的overlay driver类似，Weave要求每个POD有两个网卡，一个连在Linux Bridge或者OVS上处理L2流量，另一个则连在docker0上处理Service流量，docker0后面仍然是Iptables做NAT。Weave中DNS的实现如图9-20所示。

图9-20　Weave中DNS的实现

9.5.3　Calico

Calico是一个专门做DC网络的开源项目。当业界都痴迷于Overlay的时候，Calico实现multi-host容器网络的思路的确可以说是返璞归真——Flat L3，通过单纯的IP路由来完成容器间的组网。这是因为Calico认为L3更为健壮，且对网络人员更为熟悉，而L2网络由于控制平面太弱会导致太多问题，排错起来也更加困难。那么，如果能够利用好L3去设计DC的话就完全没有必要用L2了。不过对于某些分布式应用来说，考虑到各类*cast的需求，L2无疑是更好的网络。业界普遍给出的答案是L2overL3，不过Calico认为Overlay技术带来的性能开销太大，如果能用L3去模拟L2是最好的，这样既能保证性能，又能满足应用需求，也免去了维护各类隧道的复杂性，看上去是件一举多得的事情。

用L3模拟L2的关键在于打破传统的Hierarchy L3概念，IP不再以前缀收敛，而是把容器的IP以32位主机路由的形式发布到网络中，那么Flat L3对于应用来说就和L2一模一样了。实际上，一个用Flat L3技术形成的大二层，和一个用L2技术形成的大二层也并没有本质上的区别。而且，L3有成熟的、完善的、被普遍认可的控制平面，以及丰富的管理工具，运维起来要容易得多。

于是，Calico给出了如下Flat L3的设计，见图9-21和图9-22。L3选择的是BGP，控制平面是开源的Bird作为BGP RR，Etcd+Felix进行业务数据同步，数据平面直接是用Linux内核做datapath，FIB是/32的v4或者/128的v6。具体来说，Etcd接收业务数据，Felix对Etcd同步后向host本地的路由表注入32/128位的主机路由，以及Iptables的ACL规则，然后Bird BGP Client将host的本地路由发送给Bird BGP RR，然后再由RR发布给其他host。

图9-21　Calico的整体架构

图9-22　Calico通过/32主机路由实现Flat L3

Calico的技术堆栈是经过了考验的，有着良好的稳定性、可扩展性和可维护性，而且由于没有了隧道，因此Calico能够获得接近于Linux原生的网络性能，它普遍被认为是容器网络生产环境的最佳选择。但是，采用Flat L3后就没法实现多租户地址的Overlap了，L2和L3间模糊的界限也使得网络级别的安全策略变得难以实现。

另外，在Calico控制平面的设计中，物理网络最好是用L2 Fabric，这样vRouter间都是直接可达的，路由不需要把物理设备当作下一跳。如果是L3 Fabric，控制平面的问题马上就来了：是否需要在host和物理设备间起BGP？物理设备支持多少的32位路由？Container-to-Container的BGP Only说起来容易，真正部署起来还是要解决很多问题的。为了支持在L3 Fabric上的部署，Calico提供了IPinIP隧道模式，但是这种做法明显属于左右互搏，如果要用隧道倒还不如直接用VxLAN实在一些。

9.5.4　Romana

说完了Calico的Flat L3，再来看看Romana给出的Hierarchy L3的方案。Romana是Panic Networks在2016年新提出的开源项目，旨在解决Overlay方案给网络带来的开销，虽然目标和Calico基本一致，但是采取的思路却截然相反，Romana希望用Hierarchy L3来组织DC的网络，但这样一来也就谈不上大二层了。

当然，Romana想要的是SDN的Hierarchy L3，因此控制平面的路由比较好控制，不用使用BGP和RR，不过IPAM的问题就比较关键了。IP地址有32位，Leaf-Spine、Host、VM、POD都需要进行规划，如果要多租户，还需要规划Tenant和Segment。如果能够做好这些规划，而且都可以动态调整，那么Romana将会是个“很SDN”的方案。

不过，想要实现灵活的规划并不容易。首先，在DC中网络资源的分布并不是对称的，32位的地址空间还是比较紧张的，图9-23所示为Romana给出的IP规划实例，255 Hosts、255 Tenants、255 Endpoints，多多少少显得有些局促。其次，在不使用Overlay的情况下，想要IPAM能够SDN化，边缘的Host没问题，但在物理网络上进行调整就比较困难了。另外，大二层不要了，虚拟机迁移和很多关键的业务就很难进行了。

图9-23　Romana中Hierarchy L3的地址规划

下面来看一看Romana的架构。在图9-24中，Managers是控制端，agent在设备端。控制端几个组件的功能，看了名字也就能够知道了，这里不再解释。设备端接收调度，给容器配IP，在host上配路由，也没什么好说的了。

图9-24　Romana的架构

9.5.5　Contiv

Contiv是Cisco在2015年成立的开源项目，希望能够把ACI中的策略概念引到容器网络中来。Contiv的架构如图9-25所示，Netmaster为集中式的控制器，负责分配IP地址、同步容器网络信息、同步网络策略等。Netplugin部署在设备端，接收Netmaster的控制，并对vSwitch和vRouter进行配置。Contiv的集群管理可通过Etcd/Serf/Consul等实现。因此，Contiv是一个典型的SDN架构。

图9-25　Contiv的架构

Contiv支持多种组网方式，包括基于VLAN的Flat L2、基于BGP的Flat L3、基于VxLAN的Overlay，以及支持EPG的ACI模式。在策略方面，支持隔离和带宽控制，可通过ACI GW与ACI的控制器APIC进行对接。

9.6　Neutron网络与容器的对接

眼看着容器一步一步火起来了，OpenStack也开始对容器进行集成。有Magnum作为Swarm、K8S这些COE（Container Orchestration Engine，容器编排引擎）的前端，OpenStack就有了编排大规模容器集群的入口，而除了编排以外，网络侧的集成也是很重要的一部分。其实从network driver的角度来看，容器和虚拟机倒也没什么特别大的差别，那么再设计一套Neutron容器显然是没有必要的。于是，Kuryr项目应运而生，旨在将现有Neutron的network driver衔接到容器网络中。

Kuryr是捷克语“信使”的意思，顾名思义，就是要把容器网络的API转化成Neutron API传递给Neutron，然后仍然由Neutron来调度后端的network driver来为容器组网。要做成这件事情，主要解决3个问题。

1）建立容器网络模型（如CNM和CNI）和Neutron网络模型的映射关系。

2）处理容器和Neutron network driver的端口绑定。

3）容器不同于虚拟机，可能会对现有Neutron网络方案的实现造成影响。

第一个问题，通俗点说就是要做好翻译工作。以Docker libnetwork为例，用户调用了libnetwork的API要新建一个（CNM模型中的）Network对象，那Kuryr就得翻译成Neutron能听得懂的API——新建一个Neutron Subnet。这要求Kuryr作为remote driver，于是Neutron和Neutron driver对于libnetwork来说就是完全透明的了。共流程如图9-26所示。

图9-26　Kuryr在Docker和Neutron间扮演翻译的角色

上面举了一个比较容易理解的例子，不好办的是当两侧的模型不一致，尤其是左边有新概念的时候。比如，现在要为部署在VM中的Nested Container设计一个Security Group，但是Neutron目前只能管到host，是看不见这个藏起来的容器的，那这时就要对Neutron进行扩展了，思路就是为Neutron Port扩展一个新属性来标记VM中这个Nested Container，这样识别的时候带上这个标记就行了。Kuryr处理端口绑定的示意如图9-27所示。

图9-27　Kuryr处理端口绑定

从实现上来讲，Kuryr要负责管理两侧资源实例ID的映射关系，以保证操作的一致性，否则会直接带来用户间的网络入侵。另外，由于IPAM在两侧都被独立出来了，因此IPAM的API也要能够衔接上。

至于第二个问题，按常理来想似乎是不应该存在的。但是，目前绝大多数Neutron network在绑定端口时，其动作只会更新数据库，并不会为容器执行plug。其原因在于，之前在处理虚拟机的时候，plug被看作是虚拟机启动时自带的动作，因此plug就放在了Nova的poweron函数里面。改network driver自然是不好的，于是Kuryr就负责处理这个历史遗留问题了。

第三个问题可就是学问了。按照道理来讲，业务的API没问题了，容器也都接入网络了，而转发的逻辑都是network driver写好的，这与连接的是容器还是虚拟机也就没有任何关系了，那不就应该万事大吉了吗？可是现实很有可能不是这样的。比如由于容器作为工作负载，其特征与虚拟机完全不同，因此业务对二者的需求也是大相径庭。容器都是批量的，而且它们的生命周期可能很短——需要来回反复地启动，而Neutron的API都是通过消息总线传输的，而过于密集的API操作很有可能会造成消息总线崩溃。一个新建容器的API等了1分钟失败了，那么可能的业务需求就过去了，这个损失自然是不可接受的。类似的问题都在潜伏着，如果Kuryr要走上生产环境中，还需要多多开动脑筋。

虽然Kuryr是OpenStack中比较新的项目，但目前Kuryr的进展还不错，对Docker libnetwork和K8S的集成都有demo出来了。一旦Kuryr成熟后，这意味着Neutron下面的各家vendor都可以不费吹灰之力直接集成容器了，这对于Weave、Calico这些靠容器起家的vendor可不算是个好消息。kuryr与K8S的对接如图9-28所示。

图9-28　Kuryr与K8S的对接

9.7　本章小结

容器是一种新兴的负载形态，但实际上只要通过适当的方式完成接入，那么对于网络本身的控制与转发而言，容器与虚拟机并没有任何本质上的区别。本章对容器的组网模型进行了抽象，并介绍了一些典型方案的设计思路，其中所涉及的绝大部分技术在之前的章节中都已覆盖，本章未进行重复性的介绍。

第10章　番外——异构网络与融合

除了以太网和TCP/IP以外，历史上曾出现过很多种类型的交换网络。广域网重视服务质量的问题，因此出现过X.25、帧中继、ATM等面向连接的网络架构，而数据中心网络中则出现了以Fiber Channel（以下简称FC）和InfiniBand为代表的网络架构，分别用于满足存储资源间和计算资源间的通信需求。本章将对Fiber Channel和InfiniBand，以及基于以太网的异构网络融合技术进行简单介绍。

10.1　融合以太网基础

网络转发设备用于传输流量，不同类型的流量对网络的需求是不同的。在数据中心中有3大类资源：计算、存储和网络。之前讲过的数据网络都是用来传输数据业务流量的，这类流量对于网络的容忍度比较高，丢包多一点、时延高一点或者抖动大一点都没什么关系，以太网+TCP/IP的协议栈基本上统治了数通网络领域，而这套协议栈用于存储资源间或者计算资源间的通信却很不合适。存储应用对于由丢包所导致的IO延时抖动非常敏感，而传统的以太网/IP都是尽力而为的，可靠性需要依靠高层的TCP来保证，端到端的重传显然不能满足存储对丢包率的要求。同样，一些高性能计算（High Performance Computing，HPC）的应用对于延时有着严苛的要求，底层的互联网络需要达到机内总线级别的性能，传统的以太网/IP同样难以支撑HPC流量的传输。

因此，在数据中心中往往需要为存储和计算流量专门布网，运行专用的协议栈。对于HPC流量，通常使用IB网络（Infinite Band，无限带宽网络）进行高带宽、低时延的传输。对于存储流量，则通常使用FC网络（Fibre Channel，光纤通道网络）进行高带宽、无丢包的传输。图10-1给出了数据中心中业务网络组网的简化模型。

图10-1　以太网、光纤存储和高性能计算网络共存

3种独立的网络往往意味着大于3倍的CAPEX/OPEX，整合势在必行。而3种网络的协议栈不同，要实现整合就需要使用一个通用的承载协议。由于部署的广泛性和丰富的整合经验，以太网成为了承载协议的理想选择。DCB（Data Center Bridge，802.1）协议集的发展，使以太网的特性得到了扩充，PFC（Priority Flow Control，802.1Qbb）提供了基于通道的无丢包机制，ETS（Enhanced Transmission Selection，802.1Qaz）提供了灵活的带宽分配与调度机制，DCBX（DCB eXchange）提供了DCB的自动化配置，QCN（Quantized Congestion Notification，802.1Qau）提供了路径拥塞通知机制。具备这些优良的特性，再加上10GE端口的普及，通过以太网来融合网络已经成为了业界的普遍共识。本节先来简单地介绍一下DCB，在本章后面两小节的内容中将介绍如何使用DCB以太网来承载存储和HPC流量。

10.1.1　PFC

802.3x是一种基于PAUSE帧的以太网流量控制机制。如果接收端口出现了拥塞，不能再接收更多的数据包，它将会发送PAUSE帧，PAUSE帧中会有一个暂停时间，在收到PAUSE帧后，发送设备在暂停时间内将不会向接收端口发送任何数据包，以免丢失数据包。暂停时间过后，发送设备重新开始向接收端口发送数据包，如果接收端口仍然拥塞，则继续反馈PAUSE帧，如此循环直到拥塞解除。不过802.3x这种流控的粒度比较粗，它不具备在链路上区分不同业务的能力，如果一类非关键业务挤满了接收端口的缓存，那么PAUSE帧的反馈将会暂停关键业务的转发。

PFC可以看作是802.3x和802.1p的结合体，图10-2给出了PFC的工作机制示意图。它引入了优先级的概念，一条链路上具有不同VLAN CoS值的业务分别拥有不同的转发通道，PFC最多支持8个优先级，在不同优先级的通道中PAUSE帧是彼此独立的，一个通道上的PAUSE帧不能控制另一个通道上的业务流量，不同通道中PAUSE帧的暂停时间也是独立的，因此流控的策略能够做得非常灵活。如果每个通道都不需要流控，也可以关闭PFC功能。PFC提供了不丢包的流控机制，同时保证了关键业务与非关键业务的流控隔离，它是DCB实现网络融合最为关键的技术。

图10-2　PFC通过PAUSE帧对不同优先级的流量进行流控

10.1.2　ETS

PFC实现了链路上的拥塞控制，但是它并没有涉及链路带宽的分配。ETS在PFC的基础上，为不同优先级的业务流量分配不同的链路带宽，保证业务流量能够获得它所需要的带宽，同时不影响其他业务流量。ETS定义了一些带宽配置参数，包括端口支持的流量优先级的数量（最小为3，最大为8），为每一个优先级分配的流量类型，以及为每一种流量分配的带宽。需要注意的是，ETS只是一种规范，它自身并不会产生实际的控制帧，ETS需要依赖于DCBX在链路两侧进行协商，从而自动调整链路上的带宽分配。

ETS将不同类型的业务流量纳入不同的PG（Priority Group，优先级组），每个PG有一个PGID，PGID可以在0～7间取值，这些PGID都会对应一个带宽的百分比，调度器将按照该百分比来分配带宽。PGID另外一个可能的取值是15。当PGID为15时，代表该流量需要得到严格的优先级保证，该流量不归ETS管理，只要该流量出现就需要无条件地优先进行转发，剩余的带宽将作为总量，按照百分比在PGID为0～7的流量间进行分配。如图10-3所示，有一类关键业务在PG15中传输，它们对于带宽是可以抢占的，有5类优先级较低的业务流量被合并纳入PG0，一起获得了除PG15所占用带宽之外的60%，有一类优先级较高的业务流量被纳入PG1，获得了除PG15所占用带宽之外的40%。

图10-3　ETS分配链路带宽

10.1.3　QCN

PFC的拥塞控制作用于链路级别，只能逐跳地对上游交换机进行反馈，不能直接控制产生流量的源头，而且PFC的反馈机制是定性的，上游交换机收到PAUSE后就会暂停发送，而不是根据拥塞的程度来减小发送的速率。对应地，QCN是一种可直接溯源、可量化调整的拥塞控制机制，交换机可以对出端口设置拥塞监测点，当其发现拥塞后会发送CNM（Congestion Notification Control，拥塞通知控制）消息给流量源的网卡。该网卡中会有一个限速器RL（Rate Limiter），它会参考CNM消息中的参数，并通过算法定量地减小发送流量的速率，如图10-4所示。另外，如果在一段时间内没有再收到CNM，那么该网卡就会通过另外的算法恢复发送的速率，保证在拥塞解除后能够充分地利用带宽。

图10-4　QCN对流量源进行拥塞反馈

想法是好的，不过QCN的一个明显问题在于它是一种二层控制协议，因此没有办法跨越IP网络，另外它需要服务器中的网卡提供专用的支持，因此实际上QCN在数据中心的应用比较少。

10.1.4　DCBX

PFC和ETS都是链路层面的控制机制，只有当链路两端的相关参数能够匹配的时候才能够正常工作。手动去配置PFC和ETS是个艰巨的任务，DCB为此设计了一种自动协商的协议DCBX，用于自动地协商链路两端的PFC和ETS参数，如有多少种流量类型可以同时支持PFC、每种流量类型是否使能PFC、支持ETS优先级组的数量、每个ETS PGID对应的带宽百分比，等等。另外，QCN也可以通过DCBX得到自动配置。DCBX协商完成后，就可以建立起一条DCB链路了。

DCBX的实现是通过扩展LLDP来完成的，因此DCB交换机可以兼容传统的以太网交换机，不过DCBX需要工作在点到点链路上，如果链路的一端发现有多个DCBX对端，那么它就将忽略掉所有收到的DCBX，直到发现只存在一个DCBX对端为止。

10.2　存储网络及其融合

存储在企业级网络和数据中心中都占有基础性的地位，它提供数据的管理、复制、快照、迁移、容灾等诸多的功能与事务，存储架构的选择对于整体IT系统的效率有着至关重要的影响。DAS（Direct Attached Storage，直连式存储）能将外部存储设备直接挂在服务器内部总线上，能够实现高性能的数据读写，不过DAS附属于服务器的架构中，存储设备的资源利用率较低，服务器间数据的共享会受到严重的限制。NAS（Network Attached Storage，网络连接存储）通过专门的文件服务器来提供文件的远程操作，存储不再作为服务器的附属，而是作为独立节点存在于网络之中，文件可以通过网络在服务器间自由地流动，不过NAS没有自己专用的网络，网络传输的低效率会对性能造成很大的制约，而且NAS只能提供文件形式的访问，不能提供对数据块（Block）的直接访问，因此无法适用于某些数据库系统。

SAN结合了DAS和NAS的优点，提供了高速的、Block级别的数据操作，又提供了组网的可扩展性和灵活性。SAN起源于FC（Fiber Channel，光纤通道），在20世纪80年代，存储的架构多是以SCSI作为存储接口的DAS，随着对存储容量的要求越来越高，SCSI设备需要越来越多的空间，这就要求SCSI连接能够从主机延伸出来以在组网上获得可扩展性，FC在这个背景下脱颖而出。FC将SCSI指令封装后在专用的底层网络中进行传输，FC专用的底层网络具有高带宽、不丢包等特点，并针对大块数据的传输进行了优化，因此FC的性能在当时远远超过了以太网，在存储领域形成了巨大的优势，FC成为了SAN的核心技术。经过二十多年的发展，SAN逐步形成了寡头市场，主要就是Brocade和Cisco两家，其他厂家大多数都是这两家的OEM。

FC是一套独立的协议栈，是无法和以太网/IP进行原生互通的。随着DCB的提出，以及以太网端口速率的快速发展，FC相比于以太网的性能优势已经不再明显，因此通过以太网/IP来整合存储流量也就顺理成章了。整合的方案主要有iFCP、FCIP、iSCSI和FCoE几种，其中：iFCP是FC into IP，目前基本上看不到什么应用。FCIP是FC over TCP/IP，思路是通过IP隧道将两个FC网络跨域Internet互连起来，主要用于存储流量的异地传输与灾备。iSCSI是SCSI over TCP/IP，也称为IP-SAN，其成本相对FC较低但性能稍差，它占领了中端存储市场。FCoE是FC over Ethernet，思路是通过DCB以太网来承载FC，主要用于数据中心内部的IO、网络整合。本节先来介绍一下FC，然后再来看一看FCoE，最后介绍曾经出现过的一些SDSAN方案网络。

10.2.1　FC的协议栈

FC协议栈的层次分为FC0～FC4，如图10-5所示。FC-0和FC-1是物理层规范，最新一代的GEN6 FC产品可以提供128（为4×32）G的端口速率。FC-2分为FC-2P、FC-2M、FC-2V 3个子层，其中FC-2P负责帧的定界与封装，并使用了Buffer-to-Buffer Credit的流控机制来保证不丢包。主机通过HBA（Host Bus Adaptor）来支持FC，HBA上会有多个FC端口（N_port），每个N_port在接入FC网络时会分配一个24位的FCID，之后即可使用在FC网络上和其他N_port进行通信，FC交换机会根据FCID进行转发。FC-2V实现HBA上不同的N_port，FC-2M实现一个HBA上的多个N_port对于FC-2P的复用。FC-4是FC-2和应用间的映射层，允许应用在不改变程序原有语义的情况下使用FC进行传输，不同的应用需要不同的FC-4来映射。要注意的是，FC-3是FC网络的通用服务层，它并不对应OSI中的网络层，不具备路由的功能，而是为节点提供注册和查询等服务。FC-3提供的服务，比如Login Service，Name Service等，都具有周知的FCID。它们分布于各个FC交换机上，交换机会将这些服务的本地信息与其他交换机进行同步。可以看出，FC的协议栈和OSI标准模型并没有强对应关系。

图10-5　FC协议栈的分层

10.2.2　FC的控制与转发机制

在FC Fabric的初始化阶段，会从各个交换机中选举出一个PS（Principle Switch），这个PS角色就是负责为其他交换机分配Domain ID。PS的选举过程比较复杂，这里可以简单地将其理解为和STP选举root是类似的过程。选举出PS后，PS首先为自己分配一个Domain ID，然后向其他直连的交换机发送一个DIA（Domain ID Assignment），其他交换机看到DIA后，知道可以开始申请Domain ID了，于是向PS发送一个RDI（Request for Domain ID）。PS收到RDI后，会返回一个可用的Domain ID。分配了Domain ID的交换机会向下游的交换机再次发送DIA，以触发下游交换机向PS发送RDI。这里注意，DIA只能传一跳，而RDI是可以透传的。换句话说，Domain ID的分配过程，是沿着以PS为根的树逐层进行的。

当所有交换机都有了Domain ID之后，Fabric就开始进入路由协议的运行阶段了。FC中的路由协议叫做FSPF（Fiber Shortest Path First，光纤最短路径优先），FSPF和OSPF差不多，也是一种链路状态协议，工作原理就是通过Hello发现邻居，通过扩散LSDB来学习拓扑，然后根据D算法来计算cost最小的路径，可以支持多路的ECMP。在下一段中会看到，在为节点分配FCID时，交换机会用自己的Domain ID作为节点FCID的高8位，也就是说同一个交换机下面的节点会拥有相同的FCID前缀，因此FSPF在扩散和计算路由时都是以Domain ID为目的地的。

初始化阶段和路由阶段过后，FC Fabric即可开始处理FC节点间的流量了。每个FC节点都有64位的WWN（World Wide Name），节点上的每个端口都有64位的WWPN（World Wide Port Name），对FC网络中的元素进行全球唯一标识，WWN/WWPN相当于以太网中的MAC地址。在FC的设计中，交换机会使用FCID来进行转发，之所以没有使用WWN/WWPN，是因为64位太长了会影响转发效率。由于节点需要一个FCID来进行通信，因此节点首先会向周知FCID地址为0xFFFFFE的Login Sever发送FLOGI（Fabric Login）消息来申请FCID，Login Server会返回一个24位的、层次化编址的FCID。FCID的编址规则为8位的Device ID+8位的Area ID+8位的Port ID。其中，Device ID用于标识FC交换机，在FC的实现中通常选择Domain ID作为Device ID；Area ID用于标识交换机本地的一组Port，比如可以为同一个Slot中的各个Port使用同一个Area ID；Port ID用于标识某个特定的Port。从编址的规则来看，节点分配到的FCID在它所在的FC Fabric中是唯一的，如果在FC Fabric中使用了VSAN（类似于以太网中的VLAN，用于隔离流量），那么在不同的VSAN中可能会分配相同的FCID。虽然FC为FCID设计了层次化编址，而且使用了类似于OSPF的FSPF来计算FCID的路由，但是由于在FC协议栈中不存在独立的网络层，路由的功能都是在链路层展开的，因此FCID仍然属于链路层的概念。

节点在获得FCID后，还要向周知FCID为0xFFFFFC的Name Server发送PLOGI（Port Login）消息，上报自己的WWN/WWPN、FCID等参数，Name Server记录下这些参数后回复确认。之后，作为Initiator的节点继续向Name Server发送PLOGI消息来请求在线的节点列表，然后从列表中找出可用的Target，并向这些Target发送PLOGI消息与其协商通信参数。对于SCSI应用而言，在PLOGI协商过后，Initiator和Target间还需要进行PRLI（Process Login）协商，然后才能开始数据通信。数据包从HBA送出之后就进入了FC Fabric，交换机就会根据FSPF计算出来的路由表，配置数据包的目的FCID来进行转发。FC中的FLOGI和PLOGZ如图10-6所示。

图10-6　FC中的FLOGI和PLOGI

10.2.3　FCoE的控制与转发机制

上面是对FC技术的框架性介绍，下面再来看一看FCoE。FCoE的做法是使用以太网的帧头代替FC-2P和FC-2M，上层的FC-2V、FC-3和FC-4仍然保留，FCoE协议栈如图10-7所示。FCoE将服务器的FC节点称为ENode，FCoE交换机称为FCF。

在FCoE网络中，服务器通过一块CNA网卡同时支撑IP和FC两套协议，相当于HBA和以太网NIC的合体。FCoE的以太网类型是0x8906，其外层MAC地址的写法比较讲究，后续通过具体的通信流程会进行介绍。外层的VLAN对于FCoE来说同样非常关键，其原因主要有两个：首先，FCoE流量必须在无损无丢包的以太网链路上进行传输，这完全依赖于PFC和ETS机制，而这两种机制都需要根据VLAN标签来对流量进行分类，因此FCoE流量必须承载在特定的VLAN中。其次，FCF要想实现存储网络内部的虚拟化，需要使用不同的VLAN来承载不同VSAN（Virtual Storage Area Network）的流量。那么封装外层以太网时具体该使用哪个VLAN呢？这就要看FCoE的控制平面了。

图10-7　FCoE的协议栈

FCoE使用FIP（FCoE Initialization Protocol）作为控制平面协议，其以太网类型为0x8914。FIP主要负责以下3个工作。

1）VLAN发现。FIP在原先VLAN中通过VLAN发现报文，并与邻居协商后续FIP信令和FCoE流量所使用的VLAN，其缺省值为1002。

2）FCF发现。FCF在所有FCoE VLAN内定期组播发现通告报文，使得当前VLAN内的所有的ENode发现自己。

3）FLOGI/PLOGI。与FC中相应过程一样，FCF作为Login Server为ENode分配FCID，同时作为Name Server记录ENode的登录信息。

经过上述3个阶段后，FCoE网络的初始化工作就完成了，FCoE流量得以无损地在以太网中传输。下面来看一个在多跳FCoE网络中典型的报文转发流程，以图10-8中的场景为例。

图10-8　多跳FCoE网络中转发报文示例

由于FCID是端到端的，因此FCoE报文在经过FCF转发时FCID不会发生变化，而外层的MAC地址会逐跳改写。我们知道IP和MAC是通过ARP协议联系在一起的，那么FCID和MAC该如何映射呢？FCoE为ENode规定了如下的映射方法：使用FC-MAP填充MAC地址的高24位，低24位填充为FCID，得到FPMA作为自己的以太网地址，而弃用CAN网卡出厂时的MAC地址。其中FC-MAP为在FIP的FCF发现阶段中，FCF告诉ENode的信息，每个VSAN内部的ENode都使用相同的FC-MAP，不同的VSAN使用不同的FC-MAP。而对于FCF来说，不进行这种转换，直接使用本机MAC地址FCF-MAC进行外层以太网封装。同一个VSAN内的报文都在同一个VLAN内传输，FCF进行VLAN的MAC地址学习，保证了VSAN间的隔离，不同VLAN的优先级不同，通过PFC和ETS进行差异化的传输控制。

单跳FCoE的转发更为简单，负责接入的FCF收到FCoE流量后，根据FCID进行寻址，然后直接转换成FC-2的帧格式在FC网络中进行传输，这里不再赘述。

有一个问题就是，当在服务器中部署虚拟机的时候，FCF不再是FCoE接入网络的第一跳，很多FIP的交互过程就实现不了了，而FC网络也面临着这个问题。FC网络给出的解决办法是NPIV/NPV，NPIV部署在服务器中作为ENode和FCF之间的代理，为下挂多个虚拟机的ENode完成FLOGI/PLOGI过程，而NPV则将NPIV的功能放到了以太网交换机上。NPIV/NPV的示意如图10-9和图10-10所示。同样，FCoE也可以配合NPIV的工作实现虚拟机的FCoE接入。

图10-9　虚拟机通过NPIV完成FC的接入

图10-10　NPV通过以太网交换机实现NPIV的功能

FCoE在2008年提出后，以太网看到了进军存储领域的契机，各路厂商极为罕见地、迅速地达成了共识，纷纷在自家的产品中增加了对FCoE的支持。不过由于存储领域相对来说比较保守，因此FCoE在市场上的推进并没有达到预期的效果。尽管如此，在当时来看，FCoE仍然将会是未来数据中心中IO整合的首选。然而就在近两年，超融合的概念迅速崛起，存储重新开始向计算靠拢，端的地位逐渐强化，存储领域正在摆脱以网络为核心的思路，FCoE还没有等到市场的普及，就需要开始面临着可能被新技术浪潮所吞没的挑战。另外一个方面，SSD在企业级市场的快速发展，将低延迟引入到了存储网络的评价指标当中，HPC网络也开始走入了主流存储界的视野。FCoE的前景已经变得很模糊了，而随着Brocade的SAN业务在2016年被Broadcom收购，SAN的市场格局未来会走势如何，现在也成为一个问号。

10.2.4　昙花一现的SDSAN

在2015年左右，业界曾经有过一些尝试希望将SDN引入SAN网络，当时的主流思路就是通过控制器实现对FC/FCoE的自动部署与集中控制。简单来说，存储设备会在通信前发一些控制信令向网络进行注册，通信开始后网络根据设备的位置进行路由。从SDN实现的宏观角度来分析，无非就是控制器收集信令，形成存储网络的全局视图，再下发转发表指导存储流量的路由。不过，由于SAN网络所承载的多为极关键的流量，技术上翻新的动力并不是很强，而且通过SDN来改造SAN网络所带来的收益也并不明显，因此SDSAN的概念并没有得到推广。在超融合等新兴存储热点出现后，SDSAN的声音基本上就彻底消失掉了。本节会简单地介绍一些SDSAN的设计思路，有兴趣的读者不妨了解一下。

1.NEC Advanced FCoE

NEC提出过一种叫做AFCoE的方案，希望解决当时单跳FCoE存在的两个不足：①所有的流量都经过FCF，很容易成为流量的瓶颈。②DCB不支持丢包数据重传（目前的DCB已经支持）。于是AFCoE通过部署FCC（FC Controller）解决了FCF的扩展问题，通过在主机端部署gateway的方式实现快速重传。Gateway的实现这里不去关注，来看一看FCC在AFCoE中的使用，AFCoE的组网和通信机制如图10-11中左右两部分所示。

其实AFCoE的原理非常简单，主机或者存储设备送出FIP信令（包括FLOGI和PLOGI）后，交换机直接泛洪给FCC，FCC负责模拟FCF向主机或者存储设备回复FIP消息，与之建立virtual link，并记录PORT、MAC、FCID间的映射关系。通信开始后，FCC根据映射关系，模拟FCF的功能并进行FCoE流量的转发以及源目MAC地址的改写。实际上，FCC可看作以InBand方式部署在网络中的一个FCF软件代理。

AFCoE虽然将FCoE的控制平面从FCF的盒子中分离到了FCC上进行实现，但是由于只涉及了一个FCF，所以全局视图和存储流量调度都无从谈起。如图10-12所示，NEC还曾要结合AFCoE与OpenFlow实现端到端的FCoE，以真正地出体现SDN的特色，不过后面并未见到相应的进展。

2.Fujitsu SD-FCoE

与AFCoE类似，Fujitsu也是提出了一个边缘FCoE组网的SDN解决方案，其原理如图10-13所示。Fujitsu对OpenFlow的匹配域进行了扩展，使得OF交换机能够识别FCoE的相关字段（如FCID），从而据此进行存储流量的转发。

图10-11　AFCoE的组网结构（左）和通信机制（右）

图10-12　AFCoE与OpenFlow结合示意图

图10-13　SD-FCoE的方案示意

在该方案中，网络中仍然存在FCF，主机和存储设备直连的浅蓝色盒子为扩展后的OF交换机，通过一些预置的流表充当一个FIP Snooper。看到主机或者存储设备发出的FLOGIN Request后，OF交换机将其上报给控制器，控制器会记录下端口，Enode_MAC和FCF MAC间的映射关系可以构建LOGIN Table，并指导OF交换机将该FLOGIN Request传给FCF。看到FCF回复的FLOGIN Accept后，OF交换机将其交给控制器，控制器会记录下FCID和FPMA，结合LOGIN Table中的信息形成流表，并下发给OF交换机指导存储流量的转发，图10-14给出了LOGIN Table以及流表的形成规则，图10-15则给出了控制器中的处理逻辑。

图10-14　SD-FCoE中LOGIN Table以及流表的形成规则

FIP过程结束后，存储流量开始传输。对于不同OF交换机下的主机或存储设备间的通信，OF交换机转发给FCF，再由FCF转发到另一台OF交换机上。对于同一OF交换机下的主机或存储设备间的通信，该OF交换机直接按照控制器下发的流表完成转发，不再迂回到FCF，这在一定程度上解决了FCF的流量瓶颈问题。

这个SD-FCoE的方案相比于AFCoE，已经向真正的SDSAN迈出了一大步，不过该方案仍存在以下几个问题：

1）FIP需要通过Keep Alive消息来维持主机和存储设备间virtual link的状态，这部分信令如果都交给控制器处理的话是一个很大的开销，因此当网络中节点较多时，可能需要将FIP Keep Alive的处理offload到交换机本地进行实现。

2）虽然基于FCoE的帧格式对OF交换机进行了扩展，然而OF交换机仍然不支持DCB的某些无丢包特性，因此数据平面离真正的SDSAN还有差距。

图10-15　SD-FCoE中控制器的处理逻辑

3）实现了FCoE的边缘接入，然而对存储流量的端到端传输并未进行集中式的控制。

3.HUAWEI CSN

相比于SD-FCoE，更为彻底的SDSAN其实应该是SD-FC。华为曾联合发表过一篇论文对SD–FC进行了初步的探讨与设计，其架构如图10-16所示，论文中将其命名为CSN（Controller-based FC Storage Network）。

图10-16　华为针对SD-FC提出的CSN

在CSN架构中，通过对FC交换机的软件进行升级，使得其上电后可以立即与FFC（FC Fabric Controller）建立TCP连接，并交互FC的控制信令。控制信令的消息类型如图10-17的左半部分所示，图10-17的右半部分是控制信道的建立和传输过程。其中第一类消息维护控制信道的状态，第二类消息处理主机或存储设备的登录，第三类消息用来收集FC网络拓扑，第四类消息用来更新FC网络路由表。

图10-17　CSN中的控制信令类型（左）与交互流程（右）

FC交换机连接上FFC后，将主机或存储设备的FLOGI和PLOGI请求通过控制信道上报给FFC，FFC进行代理回复，并记录下主机或存储设备的位置和身份信息。另外，FC网络的拓扑信息对于控制器同样也很重要。为实现该信息的收集，FC交换机会在本地进行邻居探测，新检测到的和断掉的FC链路将分别通过EPort UP和EPort Down请求发送给FFC，FFC发送相应的确认消息进行确认。拥有了全局信息后，FFC即可通过FSPF算法计算存储流量的端到端路径，并通过Routing Table消息通知FC交换机。

CSN架构的通用性能够使其应用到任意的SAN网络中，对数据平面也不需要任何的修改，可以说SDSAN是最理想的一个方案了。华为在其Agile Controller 2.0中，也曾明确地将SDSAN作为SDN一个重要的应用场景，不过后来随着3.0版本重点的转移（见本书4.7节），SDSAN这一块也就不了了之了。

10.3　高性能计算网络及其融合

网络的本质是互联，低时延是网络永恒的追求。广域网上的时延较高，传输流量需要经过很多跳的路由器，每一跳上的处理都会带来一些延时，而网络结构的不对称，也会导致在少数路由器上出现严重的拥塞，丢包后的TCP重传会倍增通信的时延。而对于数据中心来说，网络直径有限，在高度对称的结构中路由也会更为均匀，丢包重传的情况相对较少，因此时延不会很高。随着大数据和人工智能的兴起，云化的数据中心开始承载越来越多的计算密集型业务，这些业务对于延时有着很高的要求，网络需要提供更低的延时才能保证不拖集群的后腿。因此就在这几年，HPC（High Performance Computing，高性能计算）中的一些低延时网络技术，开始得到云数据中心的关注。

HPC网络通常用来承载超算业务。对于超算业务来说时延就是生命线，在网络传输上多省出来几个μs，系统的整体计算能力可能就会得到很大的提升。InfiniBand是HPC网络的主要技术，相比于以太网/IP，InfiniBand有着更高的带宽、更优的流控、更细的QoS、原生地支持RDMA（Remote Direct Memory Access，远程直接内存访问），端到端的时延能够达到10μs以下，甚至可以迈入ns级。不过，目前以太网也逐步具备了承载超算业务的能力，凭借着成本的优势，以太网在超算Top 500中得以和InfiniBand分庭抗礼，而RoCE、RoCEv2也已经有了在云数据中心落地的案例。本小节，就来对InfiniBand和RoCE/RoCEv2进行简单的介绍。

InfiniBand其实并不是什么很新的技术。1999年左右，当时IT界的七家翘楚Compaq、HP、IBM，Dell、Intel、Microsoft、Sun联合起来成立了IBTA（InfiniBand Trade Association），将InfiniBand定位于高速的I/O互联网络，旨在通过机外的长连接提供机内总线的IO性能，同时保证机外互联的可扩展性。不过出于各种原因，各家巨头在IBTA成立后都纷纷离开，InfiniBand没能得到良好的市场推广，只是在超算和高性能数据库领域有所应用。Cisco在2005年收购Topspin后也拥有了InfiniBand的产品线，但是Cisco后面就没怎么继续研发了。Intel在收购QLogic之后，也没有继续研发InfiniBand，而是推出了自己的OmniPath。目前，InfiniBand的厂商主要就是Mellonax一家，基本上可以算是小圈子里面绝对的霸主了。

10.3.1　InfiniBand的协议栈

InfiniBand的协议栈如图10-18所示。InfiniBand的传输层首部叫做BTH（Base Transport Header），通过Queue Pair来定位远端的目标内存，通过Partition Key实现内存的访问控制，通过Sequence Number实现可靠传输。InfiniBand提供了对多种（包括可靠/不可靠、基于连接/基于报文）传输类型的支持，不同的传输类型使用不同的ETH（Extended Transport Header），ETH紧跟在BTH的后面。传输层和传输层以下都能够卸载到InfiniBand网卡中进行硬件加速。传输层之上是InfiniBand传输层和用户应用间的映射层ULP（Upper Level Protocol），允许用户应用在不改变程序原有语义的情况下使用底层的InfiniBand网络进行传输（不同类型的应用需要不同的ULP，如TCP/IP应用可通过IPoIB这一ULP来进行适配），而InfiniBand的原生应用则可以调用InfiniBand所提供的Verbs API直接对InfiniBand网卡进行操作。

图10-18　Infi niBand协议栈

10.3.2　InfiniBand的控制与转发机制

1.RDMA与CA

InfiniBand的超低时延得益于以下几点：

1）InfiniBand交换机都会采用直通式的转发，减小了串行化延迟；

2）使用了先进的、基于Credit的链路层流控机制，能够有效地防止丢包与死锁；

3）提供了对于RDMA的原生支持，从而大幅地提高了端的性能。

这里对RDMA进行一下简单的介绍。传统服务器网卡的工作依赖于中断或者轮训，数据包从网卡到达应用程序需要经过内核协议栈的处理以及内核态与用户态间的切换，在上述过程中还要对数据包进行反复的拷贝，这些操作不仅对CPU资源造成了巨大的消耗，而且严重地制约了应用的IO性能。相比之下，RDMA首先作为一种DMA机制，能够直接在网卡的缓冲区和应用内存间进行数据移动，显著降低了中断或者轮询的频率，旁路掉了内核协议栈，并实现了数据的零拷贝，因此应用的IO性能得以大幅提升，而被解放出来的CPU则可以用于处理应用本身。另外，RDMA提供了一组标准的数据操作接口，使得本端应用能够直接操作远端应用在内存中的数据，即字面所述的“远程DMA”。InfiniBand在设计之初即提供了对于RDMA的支持，是InfiniBand实现超低延时的重要基础。

InfiniBand的网卡称为CA（Channel Adaptor），CA又可分为服务器端的HCA（Host Channel Adaptor）和交换机/存储端的TCA（Target Channel Adaptor）。前面提到过，CA不仅实现了InfiniBand的物理层与链路层，而且能够对InfiniBand的网络层和传输层进行硬件加速，CA的Driver工作在内核中，为ULP或者InfiniBand原生应用提供操作接口。

QP（Queue Pair）是CA提供RDMA能力的基础，它可完成应用层的虚拟地址到CA上的物理地址的自动映射，如果两端的应用需要通信，那么双方都要申请CA上的物理资源，并通过相应的QP来完成对CA的操作。如图10-19所示，每个QP中都包括Receive和Send两个Work Queue分别用于数据的收和发。应用在进行网络通信前，首先需要向CA请求建立一个QP，需要发送数据时通过Work Request将数据作为WQE（Work Queue Entry）投入QP的Send Queue，应用需要为WQE指定AV（Address Vector），AV中携带着通信目标的地址信息，以及在通信路径上进行传输所需要的参数。CA根据AV开始进行L4～L2层的处理，封装好包头并从相应的物理端口送出。远端的目标CA收到数据包后，会根据传输层的QP字段将数据放入相应QP的Receive Queue中，然后将数据直接移动到相应的应用内存。每当一个WQE处理完毕之后，CA会通过Completion Queue发送CQE（Completion Queue Entry）给应用，通知其可以继续进行后面的处理。

2.InfiniBand Fabric

CA将数据包从物理端口送出后，就进入到了InfiniBand Fabric。子网是InfiniBand Fabric中最重要的概念，子网内部是二层通信，InfiniBand Switch根据链路层LRH中的DLID完成转发，子网间通信需要进行三层的路由，InfiniBand Router通过网络层GRH中的DGID完成。GRH中的SGID和DGID是端到端不变的，而每经过一次路由，链路层LRH中的SLID和DLID都会逐跳发生变化。InfiniBand对子网采用了集中式的管理与控制方式，每个子网中至少有一个SM（Subnet Manager），多个SM间可以互为备份。SM既可以硬件实现也可以软件实现，可以实现在子网的任何一个节点上，包括CA、Switch和Router。子网中每个节点上都有一个SMA（SM Agent），SM和SMA间通过接口SMI（Subnet Management Interface）来实现子网的管理与控制，SMI主要提供Get、Set、Trap3种类型的操作。SMI信道上的控制信令称为SMP（Subnet Management Packet），SMP的传输需要使用专用的QP0（QP0不可用于传输用户应用的数据），并且要求放在逻辑链路VL 15上（QoS优先级最高），保证子网的管理和控制流量能够得到优先传输，而不会被数据流量所阻塞。其子网、交换机与路由器的连接如图10-20所示。

图10-19　InfiniBand中的CA与QP

图10-20　InfiniBand的子网、交换机与路由器

SM的主要功能及相关说明如下所示。

1）获取节点信息发现并维护拓扑。SM和SMA间以带内的方式交互SMP，在拓扑发现过程中，交换机上还没有任何转发信息，SMP是没有办法进行传输的。为了解决这个问题，InfiniBand设计了一种专用的控制信令Directed Routed SMP，Directed Routed SMP携带了自身的一次性转发信息，因此可以在交换机上尚未形成LID转发表时在InfiniBand Fabric中进行传输。

2）为节点分配LID、GID。对每个CA来说，在出厂时会分配一个64位全球唯一的GUID（Global Unique Identifier），不过这个GUID只是用来标识CA的，并不用于二层和三层的转发。LID和GID是由SM为CA集中分配的，分别用于二层和三层的转发。LID的长度为16位，只在子网本地有效，每个CA可以分配一个或者一段连续的LID（通过LMC实现），发送数据包时CA可以采用不同的LID，以实现Fabric上的多路径转发。GID的长度为128位，由64位的子网前缀+GUID组成，实际上SM为CA分配的是子网前缀，然后CA自己在本地组合出GID。SM还会维护GUID和LID/GID间的映射关系，用于地址解析。

3）根据拓扑计算路由。InfiniBand在计算路由时通常都是以Up/Down算法为基础的，Up/Down算法会为链路指定Up或者Down两种方向，路径只允许链路方向从Up转为Down，而不允许从Down转为Up，从而可以避免形成路由环路。

4）形成LID转发表，并将转发表配置给相应的Switch。Switch会根据数据包的DLID查找LID转发表，然后找到对应的端口进行转发。由于对转发表采用集中式控制方式，为了防止子网内部产生路由环路，InfiniBand通常的做法是等到所有Switch上的转发表都形成之后，再来激活子网中流量的转发。

除了SM以外，每个子网还都需要有一个SA（Subnet Administration），SA在逻辑上是SM的一部分（物理上两者没有必然的联系），可以看作是子网的数据库，CA间通信所需的信息（如DLID/DGID、Path MTU等）都由SA完成解析。InfiniBand中还有一些其他的子网管理组件，比如负责维护端到端QP连接的CM（Connection Management），负责性能检测的PM（Performance Manager），负责板上器件检测的BM（Baseboard Manager），等等。除了SM以外，InfiniBand中其余的管理组件统称为GSM（General Services Manager），子网中每个节点上都有相应的GSA（General Services Agent），GSM和GSA间的接口称为GSI（General Services Interface），GSI信道上的控制信令称为GMP（General Services Management Packet），GMP的传输也需要使用专用的QP1（QP1不可用于传输用户应用的数据），不过和SMP不同的是，GMP不可以放在逻辑链路VL 15上，也就是说GMP需要和数据流量一起接受流控。

10.3.3　RoCE与RoCEv2

相比于以太网，InfiniBand的优势主要在于以下几点：带宽总是能够领先一步，链路层具有流控能力，RDMA可以通过旁路内核来加速。不过，InfiniBand的价格要比以太网贵很多，对运维的要求也非常高。随着10GE的普及，40GE/100GE的推广，以太网的带宽资源变得相对可观，而DCB协议族的发展则使得以太网具备了不丢包的传输能力，如果再能够提供对RDMA的支持，那么以太网就会有能力和InfiniBand在HPC领域一较高下了。2010年，IBTA制定了RoCE（RDMA over Converged Ethernet），使用以太网代替了IB的链路层，保留了网络层以及传输层对于RDMA的支持，结合DCB（要求至少10G的端口速率）即可以获得微秒级的传输延迟。不过，RoCE由于在网络层仍保留着IB的GRH，因此是不能进行IP路由的，因此IBTA在2014年又制订了RoCEv2，在RoCE的基础上将IB的网络层替换为IP，RoCEv2的流量获得了跨越广域网进行传输的能力，因此RoCEv2又称为RRoCE（Routable RoCE）。图10-21所示为RoCE和RoCEv2。

图10-21　RoCE和RoCEv2

相比于FCoE使用FIP协议作为专有的控制平面，RoCE和RoCEv2并没有专门设计自己的控制协议。在InfiniBand中管理和控制的组件多是围绕InfiniBand子网来进行的，由于RoCE、RoCEv2中使用了以太网来替换InfiniBand的链路层，因此自然就没有了InfiniBand子网的概念，SM、SA、PM、BM等组件也就失去了存在的意义，只有传输层上用于端到端协商QP的CM保留了下来。在InfiniBand中，地址分配和解析工作是由SM和SA完成的，去掉了SM和SA后，需要由以太网/IP中相应的机制来进行地址分配和解析。三者地址分配和解析机制如表10-1所示。

表10-1　InfiniBand、RoCE、RoCEv2中地址分配与解析机制

除了RoCE和RoCEv2以外，还有一种融合的方案是iWARP（RDMA over TCP/IP），通过将TCP/IP卸载到网卡中也可以实现良好的延时性能，iWARP是一种纯应用层的实现，这里就不再深入介绍了。

10.4　本章小结

作为本书的收尾，本章中对数据中心存储网络与高性能计算网络进行了简要的介绍。虽然从现状来看异构网络的共存仍然是较为普遍的现象，但是融合已成大势，数据中心网络何时能够实现架构一统？仍需拭目以待。