[云原生网络]Open vSwitch

988 阅读11分钟

Open vSwitch

Open vSwitch 是一种产品质量的多层虚拟交换机,使用开源 Apache 2.0许可证授权。它旨在通过编程扩展实现大规模网络自动化,同时仍然支持标准的管理接口和协议(例如 NetFlow、 sFlow、 IPFIX、 RSPAN、 CLI、 LACP、802.1 ag)。

  • 通过 NetFlow、sFlow(R)、IPFIX、SPAN、RSPAN 和 GRE 隧道镜像查看 VM 间通信

  • LACP (IEEE 802.1AX-2008)

  • 具有中继的标准 802.1Q VLAN 模型

  • 组播侦听

  • IETF 自动附加 SPBM 和基本所需的 LLDP 支持

  • BFD 和 802.1ag 链路监控

  • STP (IEEE 802.1D-1998) 和 RSTP (IEEE 802.1D-2004)

  • 细粒度的 QoS 控制

  • 支持 HFSC qdisc

  • 每个虚拟机接口流量监管

  • 具有源 MAC 负载平衡、主动备份和 L4 散列的 NIC 绑定

  • OpenFlow 协议支持(包括许多虚拟化扩展)

    • IPv6 支持
  • 多种隧道协议(GRE、VXLAN、STT 和 Geneve,支持 IPsec)

  • 具有 C 和 Python 绑定的远程配置协议

  • 内核和用户空间转发引擎选项

  • 带有流缓存引擎的多表转发管道

  • 转发层抽象,便于移植到新的软件和硬件平台

介绍来自Open vSwitch 官方文档,可能会有更新,最新文档可以通过官网查看。

ovs architecture

ovs-architecture.jpg

ovs的架构如上图所示,主要由内核datapath和用户空间的vswitchdovsdb组成

  • datapath:是负责数据交换的内核模块,从网卡读取数据,并快速匹配flowtable中的流表项,成功的直接转发,失败的上交vswtichd处理。它在初始化和 port binding的时候注册hook函数,把端口保温处理接管到内核模块,。。。。。 属于快速转发平面,主要负责流表匹配、保温修改、隧道封装、转发或者上送。并且维护底层转发表。在原始ovs中,报文首先经过该组件完成报文解析和封装、转发规则匹配,若找到转发规则不再经过用户空间,直接转发。否则转交用户空间的ovs-vswitchd组件进行处理。ovs-vswitchd组件与openvswitch.ko组件之间采用netlink执行进程间的通信。netlink是一种进程间通信机制,可用于处理用户态和内核态的通信。

  • vswitchd:一个守护进程,是ovs的管理和控制服务,通过unix socket将配置保存至ovsdb,并通过netlink和内核模块交互,主要负责基本的转发逻辑、地址学习、外部物理端口绑定、还可以运行ovs-ofctl工具,采用openflow协议对交换机进行远程配置和管理

  • ovsdb:ovs的数据库,保存了ovs的配置信息

Open vSwitch 内部分为用户态和内核态,用户态为守护程序实现了交换机和流表,是Open vSwitch的核心,提供了一些组件去管理交换机,实现数据库,对内核进行管理

数据流通过ovs的转发流程

1 数据包进入ovs
2 匹配流表
3 流表无匹配,向vswitchd中匹配转发规则
4 规则无匹配,向上传递个openflow 控制器判断是否转发,
5 无转发决策或者判断为否,将数据包drop
6 判断为可以转发的话,将数据包正常转发,并缓存流表

流表

每条流表规则由一些列字段组成,可以分为**基础字段** 、**匹配字段动作字段**三部分。 在打印流表时,在流表中还存在一些显示字段,如duration,idle_age等,此处把这些字段也暂时归之于基础字段之中.

基础字段

  • cookie=value 流表标识字段,cookie字段有两种书写方式:cookie=valuecookie=value/maskmask中对应位为1时cookie中值相应的位须严格匹配,为0时cookie中值对应的位通配,当mask为-1时,必须严格匹配cookie值。
  • duration=value 流表生效时间,标识流表从下发到现在所持续的时间
  • table=tableid 流表所属表项,标识流表所属的表,默认为0
  • priority=priority 标识流表的优先级,范围为0-65535,值越大,优先级越高
  • n_packets 标识流表匹配包数
  • n_bytes 标识流表匹配字节数
  • idle_timeout=sec 流表空闲超时时间,流表会在空闲时间达到给定的时间时被删除。设置为0(默认值)时,流表不会因空闲时间被删除。
  • hard_timeout=sec 流表可存在的时间。设置此值后,流表会在到达给定时间后被删除。
  • idle_age=sec 流表空闲时间
  • hard_age=sec 流表存在时间。此字段与duration字段的区别在当流表被修改后,会重新设置hard_timer但是不会重置duration
  • ip_frag=frag_typedl_type指定为IP或者IPv6,frag_type指定匹配的IP分片包或者非分片包的匹配 frag_type支持的值为: no: 仅匹配非分片报文 yes:匹配所有分片报文 first:仅匹配offset为0的分片报文 later: 仅匹配offset非0的分片报文 not_later:匹配非分片报文和offset为0的分片报文

匹配字段

  • in_port=port 标识匹配接收数据包的端口号
  • dl_type=ethertype 匹配数据包的二层协议类型,IP数据包为0x0800,IPv6数据包为0x86dd,ARP数据包为0x0806
  • dl_src=xx:xx:xx:xx:xx:xx
  • dl_dst=xx:xx:xx:xx:xx:xx 匹配指定的链路层源或者目的MAC地址
  • dl_src=xx:xx:xx:xx:xx:xx/xx:xx:xx:xx:xx:xx
  • dl_dst=xx:xx:xx:xx:xx:xx/xx:xx:xx:xx:xx:xx 匹配指定的链路层MAC地址,MAC地址格式为ADDR/MASK,当MASK值为01:00:00:00:00:00时,仅匹配多播位。当dl_dst=01:00:00:00:00:00/01:00:00:00:00:00时,匹配所有的组播报文和广播报文。dl_dst=00:00:00:00:00:00/01:00:00:00:00:00匹配所有的单播报文。
  • nw_src=ip[/mask]
  • nw_dst=ip[/mask]dl_type=0x0800或指定ip时,匹配数据包的源、目的IP地址 当dl_type=0x0806或指定arp时,匹配ARP数据包的ar_spa或者ar_tpa字段
  • dl_vlan=vlan 匹配802.1Q类型(即vlan)数据包
  • nw_proto=proto 匹配数据包协议类型。当dl_type=0x0800时,匹配IP协议族的协议,例如tcpudpicmp
  • nw_tos=tos 匹配IP Tos/DSCP或者IPv6的tos字段,值为0-255
  • nw_ecn=ecn 匹配IP或者IPv6的ecn字段,值为0~3
  • nw_ttl=ttl 匹配TTL值
  • tp_src=port
  • tp_dst=port 若指定了udp或者tcp协议,则匹配udp/tcp的端口号
  • icmp_type=type
  • icmp_code=code 若指定了icmp或者icmpv6协议,则匹配对应的icmp 类型或者code字段
  • arp_sha=xx:xx:xx:xx:xx:xx
  • arp_tha=xx:xx:xx:xx:xx:xx 当设置dl_typeARP或者RARP,则arp_shaarp_tha匹配数据包的源、目的MAC地址

动作字段

  • output:port 将数据包从port接口发送
  • enqueue:port:queue 将数据包入队到指定端口的指定队列里
  • normal 将数据包按照设备上的正常L2/L3层处理方式进行处理
  • flood 将数据包发送到交换机上除接收接口和禁止flood的接口外的所有接口
  • all 将数据包发送到除接收接口外的所有接口
  • controller(key=value…) 将数据包作为PACKET IN消息发送到OpenFlow控制器。 支持的键值对: max_len=nbytes:限制发送到控制器的数据包长度字节数,默认情况是发送整个数据包;reason=reason:在PACKET IN消息中指明发送消息的原因,支持的reasonaction(default),no_matchinvalid_ttl; id=controller-id:指明控制器ID
  • in_port 将数据包从接收的接口发送出去
  • drop 丢弃数据包
  • mod_vlan_vid:vlan_vid 修改数据包的vlan id
  • mod_vlan_pcp:vlan_pcp 修改数据包的vlan priority
  • strip_vlan 如果数据包中存在vlan tag,则剥离vlan tag
  • push_vlan:ethertype 为数据包添加新的vlan tag
  • mod_dl_src:mac 设置数据包的源MAC地址
  • mod_dl_dst:mac 设置数据包的目的MAC地址
  • mod_nw_src:ip 设置数据包的源IP地址
  • mod_nw_dsp:ip 设置数据包的目的IP地址
  • mod_tp_src:port 设置TCP或者UDP的源端口
  • mod_tp_dst:port 设置TCP或UDP的目的端口

ovs的相关命令

ovs-vsctl

ovs-vsctl:用于控制ovs db

ovs-ofctl:用于管理OpenFlow Switch的flow

ovs-dpctl:用于管理ovs的datapath

ovs-appctl:用于管理ovs daemon

ovs-vsctl

bridge相关

创建 bridge

ovs-vsctl add-br br0

删除 bridge

ovs-vsctl del-br br0

查看 bridge

ovs-vsctl list-br
# 固定网桥MAC地址
ovs-vsctl set bridge boc0 other-config:hwaddr=02:42:a9:28:9c:a7                              
# 设置网桥支持的OF协议
ovs-vsctl set bridge boc0 protocols=OpenFlow13        
# 设置网桥ARP老化时间(最小15s)
ovs-vsctl set bridge br0 other_config:mac-aging-time=1                           
# 查看MAC缓存表
ovs-appctl fdb/show boc0
# 设置fail-mode
ovs-vsctl set-fail-mode bridge standalone|secure
ovs-vsctl get-fail-mode bridge
ovs-vsctl del-fail-mode bridge
ovs-vsctl set-fail-mode bridge standalone|secure
# 修改隧道协议
ovs-vsctl set Interface tun0 type=gre

port 相关

添加端口

添加端口eth1 到bridge中

ovs-vsctl add-port br0 eth1

创建 bond

在br0上创建一个bond了eth0,eth1和eth2的bond端口bond0

ovs-vsctl add-bond br0 bond0 eth0 eth1 eth2  

移除端口

ovs-vsctl  del-port br0 eth1  

列出端口

ovs-vsctl list-ports br0  

查看端口详细数据

列出ovs中端口eth1的详细数据

ovs-vsctl list interface eth1
# 添加数据口
ovs-vsctl add-port boc0 eth1 -- set port eth1 vlan_mode=trunk
# 添加隧道端口
ovs-vsctl add-port boc0 tun0 -- set interface tun0 type=vxlan options:remote_ip=flow options:key=flow
# 清除vlan tag
ovs-vsctl clear port eth1 tag
# 设置interface编号
ovs-vsctl  add-port boc0 p1 -- set interface p1 type=internal ofport_request=10001
# 设置interface options 协议
ovs-vsctl set Interface tun0 type=vxlan options:remote_ip=flow options:csum="true" options:key=flow

控制器相关

添加控制器

ovs-vsctl set-controller br0 tcp:1.2.3.4:6633

# 添加多个控制器
ovs-vsctl set-controller br0 tcp:1.2.3.4:6633 tcp:4.3.2.1:6633

# 添加使用unix socket通信的controller
ovs-vsctl set-controller br0 unix:/var/run/xx/xx.sock  

移除控制器

ovs-vsctl del-controller br0  

查询bridge上配置的控制器

ovs-vsctl get-controller br0  

vlan相关

配置端口为Access

# 设置br0中的端口eth0为VLAN 10的access口
ovs-vsctl set port eth0 tag=10  

# 添加eth1到指定bridge br0中,同时将其配置成指定VLAN 10的access端口
ovs-svctl add-port br0 eth1 tag=10 

配置端口为Trunk口

# 在br0上添加port eth1为VLAN 9,10,11的trunk
ovs-vsctl add-port br0 eth1 trunk=9,10,11  

VXlan相关

在bridge ovs0中添加远端IP为10.10.10.1的VXLAN endpoint端口vxlan0

# key=100表示设置vni为100,不设置默认为0
ovs-vsctl add-port ovs0 vxlan0 -- set interface vxlan0 type=vxlan options:remote_ip=10.10.10.1 options:key=100

# 不设key值,vni默认为0
ovs-vsctl add-port ovs0 vxlan0 -- set interface vxlan0 type=vxlan options:remote_ip=10.10.10.1

# key=flow的话,表示该port的vni可以通过openflow的actions来进行设置
# 如: actions=set_field:100->tun_id
# 或: actions=set_tunnel:100
ovs-vsctl add-port ovs0 vxlan0 -- set interface vxlan0 type=vxlan options:remote_ip=10.10.10.1 options:key=flow  

其它

Atomic operation

一条命令创建bridge br0的,并添加eth0到br0中

ovs-vsctl add-br br0 -- add-port br0 eth0  

创建 internal port

OVS internal port 可以配置IP地址,普通 port 上配置的IP地址是不起作用的。在 br0 上创建一个internal port in0:

ovs-vsctl add-br br0 in0 -- set interface in0 type=internal  
ip addr add 10.10.10.10/24 dev in0

# 创建internal port的同时将其设置为VLAN 10的access port
ovs-vsctl add-br br0 in1 tag=10 -- set interface in1 type=internal  
ip addr add 20.20.20.20/24 dev in1  

设置 OpenFlow port id

# 将已在ovs中的端口veth1的OpenFlow端口设置成100
ovs-vsctl set interface veth1 ofport_request=100

# 将端口veth1添加到bridge br0中,并将veth1的OpenFlow端口设置成200
ovs-vsctl add-port br0 veth1 -- set interface veth1 ofport_request=200  

{% admonition note Note %} OpenFlow的端口 id 在设置 flow 的匹配字段 in_port 以及 actions 字段的 output 中都会用到。 可以通过命令ovs-ofctl show br0来查看 br0 中各端口的 OpenFlow 端口 id,该 id 并不求是按顺序的。 {% endadmonition %}

设置OpenFlow版本

ovs-vsctl set bridge br0 protocols=OpenFlow10,OpenFlow12,OpenFlow13  

给ovs端口配置IP

ovs−vsctl add−port br-ex port tag=10 −− set Interface port type=internal # default is access
ifconfig port 192.168.100.1

配置流镜像

ovs-vsctl -- set Bridge br-int mirrors=@m -- --id=@tap6a094914-cd get Port tap6a094914-cd -- --id=@tap73e945b4-79 get Port tap73e945b4-79 -- --id=@tapa6cd1168-a2 get Port tapa6cd1168-a2 -- --id=@m create Mirror name=mymirror select-dst-port=@tap6a094914-cd,@tap73e945b4-79 select-src-port=@tap6a094914-cd,@tap73e945b4-79 output-port=@tapa6cd1168-a2
# clear
ovs-vsctl remove Bridge br0 mirrors mymirror
ovs-vsctl clear Bridge br-int mirrors

利用mirror特性对ovs端口patch-tun抓包

ip link add name snooper0 type dummy
ip link set dev snooper0 up
ovs-vsctl add-port br-int snooper0
ovs-vsctl -- set Bridge br-int mirrors=@m  \
-- --id=@snooper0 get Port snooper0  \
-- --id=@patch-tun get Port patch-tun  \
-- --id=@m create Mirror name=mymirror select-dst-port=@patch-tun \
select-src-port=@patch-tun output-port=@snooper0
# capture
tcpdump -i snooper0
# clear
ovs-vsctl clear Bridge br-int mirrors
ip link delete dev snooper0

如何配置QoS

# egress
$ ovs-vsctl -- \
add-br br0 -- \
add-port br0 eth0 -- \
add-port br0 vif1.0 -- set interface vif1.0 ofport_request=5 -- \
add-port br0 vif2.0 -- set interface vif2.0 ofport_request=6 -- \
set port eth0 qos=@newqos -- \
--id=@newqos create qos type=linux-htb \
  other-config:max-rate=1000000000 \
  queues:123=@vif10queue \
  queues:234=@vif20queue -- \
--id=@vif10queue create queue other-config:max-rate=10000000 -- \
--id=@vif20queue create queue other-config:max-rate=20000000
$ ovs-ofctl add-flow br0 in_port=5,actions=set_queue:123,normal
$ ovs-ofctl add-flow br0 in_port=6,actions=set_queue:234,normal
# ingress
ovs-vsctl set interface vif1.0 ingress_policing_rate=10000
ovs-vsctl set interface vif1.0 ingress_policing_burst=8000
# clear
ovs-vsctl clear Port vif1.0 qos
ovs-vsctl list qos
ovs-vsctl destroy qos _uuid
ovs-vsctl list qos
ovs-vsctl destroy queue _uuid

ovs-ofctl

# 查看流表
ovs-ofctl dump-flows <table name>

# 清空流表
ovs-ofctl del-flows <table name>

# 添加表项让所有包本地转发
ovs-ofctl add-flow <table name> "table=0 priority=800 actions=NORMAL"

# in_port表示入端口,匹配到之后的actions是output:2,意思是从2端口转发出去
ovs-ofctl add-flow br0 in_port=1,actions=output:2