TAPaaS,全称为Tap as a Service,其主要功能是将流量镜像到特定的、运行有流量分析软件的虚拟机中,以实现租户流量的可视化。

数据类型

TaaS中主要定义了两个数据类型,如下:

  • Tap Service:指的是一个流量镜像服务的实例,其中一个Tap Service需要关联一个Destination Port(流量需要镜像到的目的地,即:镜像目的地),同时,一个Tap Service可以包含多个Tap Flow,对于Tap Flow的解释参见如下。Tap Service中定义了若干个字段值,具体如下所示,其中port_id即为关联的Destination Port。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    'tap_services': {
    'id': {'allow_post': False, 'allow_put': False,
    'validate': {'type:uuid': None}, 'is_visible': True,
    'primary_key': True},
    'tenant_id': {'allow_post': True, 'allow_put': False,
    'validate': {'type:string': None},
    'required_by_policy': True, 'is_visible': True},
    'name': {'allow_post': True, 'allow_put': True,
    'validate': {'type:string': None},
    'is_visible': True, 'default': ''},
    'description': {'allow_post': True, 'allow_put': True,
    'validate': {'type:string': None},
    'is_visible': True, 'default': ''},
    'port_id': {'allow_post': True, 'allow_put': False,
    'validate': {'type:uuid': None},
    'is_visible': True},
    }
  • Tap Flow:指的是一个流量镜像服务的规则,其中一个Tap Flow需要关联一个Source Port(流量从哪里镜像,即:镜像源)。Tap Flow中定义了若干个字段值,具体如下所示,其中direction可以包含IN、OUT、BOTH三个方向值,即分别对应入方向、出方向、出入结合。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    'tap_flows': {
    'id': {'allow_post': False, 'allow_put': False,
    'validate': {'type:uuid': None}, 'is_visible': True,
    'primary_key': True},
    'tenant_id': {'allow_post': True, 'allow_put': False,
    'validate': {'type:string': None},
    'required_by_policy': True, 'is_visible': True},
    'name': {'allow_post': True, 'allow_put': True,
    'validate': {'type:string': None},
    'is_visible': True, 'default': ''},
    'description': {'allow_post': True, 'allow_put': True,
    'validate': {'type:string': None},
    'is_visible': True, 'default': ''},
    'tap_service_id': {'allow_post': True, 'allow_put': False,
    'validate': {'type:uuid': None},
    'required_by_policy': True, 'is_visible': True},
    'source_port': {'allow_post': True, 'allow_put': False,
    'validate': {'type:uuid': None},
    'required_by_policy': True, 'is_visible': True},
    'position': {'allow_post': True, 'allow_put': False,
    'validate': {'type:values': position_enum},
    'is_visible': True}
    'direction': {'allow_post': True, 'allow_put': False,
    'validate': {'type:values': direction_enum},
    'is_visible': True}
    }

为了防止租户间的流量泄露,一个Tap Service只能属于一个租户,相应的关联的Destination Port和Source Port都应该属于该租户。

总体框架

TAPaaS总体框架

TaaS总体框架如上图所示,主要分为两大部分,即:Service端和Agent端。通常Service端部署在控制节点上,而Agent端部署在计算节点上,二者之间通过RPC通道进行交互信息。图中的Service Driver和Agent Driver都是设计成可插式的,可以替换成其他Driver。

代码结构

主要分为如下几个部分:

  • CLI
    这部分主要是提供一些CLI供使用者使用TaaS服务。这部分的代码主要在taas_client目录下,该目录下有两个.py文件,分别为tapflow.py和tapservice.py,分别提供了tapflow和tapservice的增、删、改、查CLI。
  • DB
    这部分主要是定义了tapflow和tapservice的数据库存储格式及其相应的增、删、改、查等数据库操作。这部分的代码主要在db目录下对应的taas_db.py文件。
  • Service Plugin
    这部分主要的功能是处理CLI传下来的操作请求,在数据库中维护tapservice和tapflow的状态,并将请求投递给相应的Service Plugin Driver。这部分的代码在services/taas目录下的taas_plugin.py文件,该文件定义的TaasPlugin即为Service Plugin。
  • Service Plugin Driver
    接收Service Plugin发下来的请求,通过RPC通道转送给相应的Agent。这部分代码在services/taas/service_drivers目录下的taas_rpc.py文件,该文件定义的TaasRpcDriver即为Service Plugin Driver。
  • Agent
    通过RPC通道接收Service Plugin Driver发来的请求,并调用相应的Agent Driver来处理这些请求。这部分代码在services/taas/agents/ovs目录下的taas_ovs_agent.py文件,该文件定义的TaasOvsAgentRpcCallback即为对应的Agent。
  • Agent Driver
    接收Agent下发的请求,并处理,在本地OVS设备上增加或删除一些tap相关的流表。除此之外,该Agent Driver负责在本地OVS设备初始化一些tap相关的流表。这部分代码在services/taas/drivers/linux/ovs_taas.py文件,该文件定义的OvsTaasDriver即为对应的Agent Driver。

实现原理

TaaS设计中引入了br-tap桥,串接在br-int和br-tun中间,便于提供更为灵活的镜像策略,如下图所示。
图中分别给出了三种流量类型,即:租户流量、本地镜像流量、远程镜像流量。

流量示意图.png

初始化产生的流表

TaaS中存在若干个流表项,如下表所示:

Table 含义 位置
1 处理从br-int 上送过来的流量 br-tap
2 处理从br-tun上过来的流量 br-tap
30 处理发送给br-tun 的流量 br-tun
31 处理送出br-tun的流量, 广播泛洪到所有的vxlan端口 br-tun
35 分类表:
1.发送方指定端口发出,接收方的br-tun收到后直接送给br-tap,无需在返送给发送方;
2.发送方并不知道流量要从那个端口发送出, 因此只能够通过广播泛洪到所有的端口, 接收的br-tun收到后除了直接送给br-tap,还需要反射流量给发送方;
3.反射流量给发送方,发送方的br-tun收到保温后学习到了一条单播表项
br-tun
36 destination port 所在的host 的br-tun检查流量合法性 br-tun
37 source port所在host的br-tun上检查由destination port反射回的流量合法性 br-tun
38 负责将流量送给br-tap,如果是广播泛洪过来的,还需要将流量反射给发送方 br-tun
39 发送方根据接收方反射回的流量通过自学习方式产生单播流表项 br-tun

创建tap service

创建tap service,其关联的destination port为MON虚拟机对应的neutron port。
创建好tap service之后,br-int、br-tap、br-tun上会动态添加一些与taas相关的流表项,具体如下:

  • br-int
    下面流表中,port_vlan_id为MON虚拟机关联的neutron port所属的内部vlan id值,ovs_port_id为MON虚拟机关联的neutron port对应在br-int桥上的ofport值。taas_id为tap serivce关联的专用ID值,在内部用作VLAN值,在外部用作VNI使用。下面这条流表意义:从br-tap收到流量后,将vlan值修改成MON虚拟机关联的内部VLAN值,再送给MON虚拟机。

    1
    table=0,priority=25,in_port=patch-int-tap,dl_vlan=taas_id,actions=mod_vlan_vid:port_vlan_id,output:ovs_port_id
  • br-tap

    1
    2
    3
    4
    # table=1用于指示本地接收到了镜像流量,需要反射回br-int,由br-int再送给MON虚拟机
    table=1,priority=1,dl_vlan=taas_id,actions=output:in_port
    # 从外部经过br-tun接收到的流量,需要送给br-int做远程镜像
    table=2,priority=1,dl_vlan=taas_id,output:patch_tap_int
  • br-tun

    1
    2
    3
    4
    # 从vxlan端口接收到镜像流量,先把报文的VLAN值暂存在寄存器reg0,用于在table=35中进一步做分类。接着把报文的VLAN修改成VNI值,最后丢给分类表table=35
    table=4,priority=1,tun_id=taas_id,actions=move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_REG0[0..11],move:NXM_NX_TUN_ID[0..11]->NXM_OF_VLAN_TCI[0..11],resubmit(,35)
    # 在destination port所在host上的br-tun执行合法性判断.如果合法,则将流量继续丢给table=38作进一步的处理。 */
    table=36,priority=1,tun_id=taas_id,resubmit(,38)

对应的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Add flow(s) in br-int
self.int_br.add_flow(table=0,
priority=25,
in_port=patch_int_tap_id,
dl_vlan=taas_id,
actions="mod_vlan_vid:%s,output:%s" %
(str(port_vlan_id), str(ovs_port_id)))
# Add flow(s) in br-tap
self.tap_br.add_flow(table=taas_ovs_consts.TAAS_RECV_LOC,
priority=1,
dl_vlan=taas_id,
actions="output:in_port")
self.tap_br.add_flow(table=taas_ovs_consts.TAAS_RECV_REM,
priority=1,
dl_vlan=taas_id,
actions="output:%s" % str(patch_tap_int_id))
# Add flow(s) in br-tun
for tunnel_type in ovs_consts.TUNNEL_NETWORK_TYPES:
self.tun_br.add_flow(table=ovs_consts.TUN_TABLE[tunnel_type],
priority=1,
tun_id=taas_id,
actions=(
"move:NXM_OF_VLAN_TCI[0..11]->"
"NXM_NX_REG0[0..11],move:NXM_NX_TUN_ID"
"[0..11]->NXM_OF_VLAN_TCI[0..11],"
"resubmit(,%s)" %
taas_ovs_consts.TAAS_CLASSIFY))
self.tun_br.add_flow(table=taas_ovs_consts.TAAS_DST_CHECK,
priority=1,
tun_id=taas_id,
actions="resubmit(,%s)" %
taas_ovs_consts.TAAS_DST_RESPOND)

创建 tap flow

创建tap flow,其关联的source port为VM1虚拟机对应的neutron port。创建好tap flow之后,br-int、br-tun上会动态添加一些与taas相关的流表项,具体如下:

  • br-int

    1
    2
    3
    4
    # 下面这条流表是出方向的匹配.其中ovs_port_id为source port在br-int上对应的ofport值,source port出来的流量执行normal动作完成普通业务流的转发.除此之外,修改报文VLAN值为taas_id,之后送给br-tap做流量镜像策略判断
    # table=0,priority=20,in_port=ovs_port_id,actions=normal,mod_vlan_vid:taas_id,output:patch-int-tap
    # 下面这条流表是入方向的匹配.其中port_mac为source port对应的mac地址.需要送给source port的流量执行normal动作完成普通业务流程.除此之外,修改报文VLAN为taas_id,之后送给br-tap做流量镜像策略判断
    table=0,priority=20,dl_dst=port_mac,actions=normal,mod_vlan_vid:taas_id,output:patch-int-tap
  • br-tun

    1
    2
    3
    # 从vxlan端口接收到镜像流量,先把报文的VLAN值暂存在寄存器reg0,用于在table=35中进一步做分类。接着把报文的VLAN修改成VNI值,最后丢给分类表table=35 */table=4,priority=1,tun_id=taas_id,actions=move:NXM_OF_VLAN_TCI[0..11]->NXM_NX_REG0[0..11],move:NXM_NX_TUN_ID[0..11]->NXM_OF_VLAN_TCI[0..11],resubmit(,35)
    # 在source port所在host上的br-tun执行合法性判断.如果合法,则将流量继续丢给table=39作进一步的处理:学习单播表项
    table=37,priority=1,tun_id=taas_id,resubmit(,39)

对应的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# Add flow(s) in br-int
if direction == 'OUT' or direction == 'BOTH':
self.int_br.add_flow(table=0,
priority=20,
in_port=ovs_port_id,
actions="normal,mod_vlan_vid:%s,output:%s" %
(str(taas_id), str(patch_int_tap_id)))


if direction == 'IN' or direction == 'BOTH':
port_mac = tap_flow['port_mac']
#
# Note: The ingress side flow (for unicast traffic) should
# include a check for the 'VLAN id of the Neutron
# network the port belongs to' + 'MAC address of the
# port', to comply with the requirement that port MAC
# addresses are unique only within a Neutron network.
# Unfortunately, at the moment there is no clean way
# to implement such a check, given OVS's handling of
# VLAN tags and Neutron's use of the NORMAL action in
# br-int.
#
# We are therefore temporarily disabling the VLAN id
# check until a mechanism is available to implement
# it correctly. The {broad,multi}cast flow, which is
# also dependent on the VLAN id, has been disabled
# for the same reason.
#
# Get VLAN id for tap flow port
# port_dict = self.int_br.get_port_tag_dict()
# port_vlan_id = port_dict[ovs_port.port_name]
self.int_br.add_flow(table=0,
priority=20,
# dl_vlan=port_vlan_id,
dl_dst=port_mac,
actions="normal,mod_vlan_vid:%s,output:%s" %
(str(taas_id), str(patch_int_tap_id)))
# self._add_update_ingress_bcmc_flow(port_vlan_id,
# taas_id,
# patch_int_tap_id)
# Add flow(s) in br-tun
for tunnel_type in ovs_consts.TUNNEL_NETWORK_TYPES:
self.tun_br.add_flow(table=ovs_consts.TUN_TABLE[tunnel_type],
priority=1,
tun_id=taas_id,
actions=(
"move:NXM_OF_VLAN_TCI[0..11]->"
"NXM_NX_REG0[0..11],move:NXM_NX_TUN_ID"
"[0..11]->NXM_OF_VLAN_TCI[0..11],"
"resubmit(,%s)" %
taas_ovs_consts.TAAS_CLASSIFY))
self.tun_br.add_flow(table=taas_ovs_consts.TAAS_SRC_CHECK,
priority=1,
tun_id=taas_id,
actions="resubmit(,%s)" %
taas_ovs_consts.TAAS_SRC_RESPOND)
return

流量的流动方式

内部的镜像流量

内部的镜像流量

  1. VM1 发出的流量,在br-int 中匹配到taas的流表(table=0,priority=20,in_port=ovs_port_id,actions=normal,mod_vlan_vid:taas_id,output:patch-int-tap)
  2. br-tap 从patch-int-tap接收到VM1的流量 (table=0, priority=1,in_port=”patch-tap-int” actions=resubmit(,1))
  3. br-tap将报文的 table=1,priority=1,dl_vlan=taas_id,actions=output:in_port
  4. br-int 接收到该流量 将其送到MON 虚机中 (table=0,priority=25,in_port=patch-int-tap,dl_vlan=taas_id,actions=mod_vlan_vid:port_vlan_id,output:ovs_port_id)

远程的镜像流量

远程的镜像流量

  1. VM2 发出的流量,在br-int 中匹配到相应的流表(table=0,priority=20,in_port=ovs_port_id,actions=normal,mod_vlan_vid:taas_id,output:patch-int-tap)
  2. br-tap 从patch-int-tap接收到VM1的流量 (table=0, priority=1,in_port=”patch-tap-int” actions=resubmit(,1)), 发现 dport 不在本地中,于是将流量从patch-tap-tun发出
  3. br-tun收到VM2的镜像流量,匹配流表,送到table=30的流表项继续匹配。table=30表项是通过学习动态生成的单播表项,如果没有学习到任何的单播表项,则会转到table=31(泛洪表项)进行广播泛洪。这里假设没有学习到任何的单播table=30流表项,因此选择广播泛洪,封装报文为VXLAN,VNI为报文的VLAN ID值,而报文原有的VLAN ID值修改成1,接着从所有的VXLAN端口中泛洪出去;
  4. br-tun收到镜像流量后,走到table=4匹配,将报文的VLAN值暂存到寄存器reg0中(有分类表table=35作进一步分类),将报文的VLAN值修改为VNI值,送入table=35作进一步分类。这一步流程详细可以参考下图的Pipeline(只给出与taas相关的匹配表)。

pipeline

  1. 将流量反射给source port,具体参考上图的Pipeline;
  2. source port所在host的br-tun接收到反射流后,学习单播表项,具体参考上图的pipeline;
  3. br-tap接收到VM2的镜像流后,从patch-tap-int端口送出;
  4. br-int接收到VM2的镜像流后,修改报文的VLAN值为MON虚拟机关联的内部VLAN值,最后送入MON虚拟机。

参考链接

openstack doc: https://docs.openstack.org/developer/dragonflow/specs/tap_as_a_service.html
Github: https://github.com/openstack/tap-as-a-service