BFD 作为 Neutron 的服务

https://bugs.launchpad.net/neutron/+bug/1907089

添加在 Neutron 中定义 BFD 监控器并将其与路由关联的可能性。

问题描述

BFD(双向转发检测)可用于检测 Neutron 路由器与任意目标之间的链路故障,例如外部非 Neutron 路由器。

BFD 可用于提供快速、低开销的链路故障检测,在相邻转发引擎之间 ([1]),例如检查额外的路由(下一跳-目标对)是否存活,并相应地更改路由。

注意

本规范不涵盖其他用例,如隧道监控和 HA 或 DVR 路由器监控,或 BGP 或其他路由协议监控。

提议的变更

本规范的目标是添加 API 以管理 bfd_monitor 实例,获取其状态,将监控器与额外的路由关联。目标是覆盖 Neutron 管理的 bfd_monitor 实例进入 ACTIVE 角色,从而发送 BFD 控制包的情况(参见 [2])。

这使得操作员能够监控路由器的路由(目标 - 下一跳对)。

监控场景

  • 相同目标的两个不同的下一跳,例如 ecmp 路由分支

dst1

nexthop1

bfd_dst1

bfd1

dst1

nexthop2

bfd_dst1

bfd2

____nexthop1 ____
                 \ ____dst1
____nexthop2____ /
  • 通过相同的下一跳监控两个不同的目标,当 bfd3 和 bfd4 的 dst_ip 相同的情况

dst3

nexthop3

bfd_dst3

bfd3

dst4

nexthop3

bfd_dst4

bfd4

                   ____dst3
_____nexthop3_____/
                  \____dst4

REST API 影响

新的 bfd 监控器 API

  • 创建 bfd_monitor

    POST /v2.0/bfd_monitors
    {
        "bfd_monitor": {
            "name": "bfd1",
            "mode": "asynchronous",
            "min_rx": 1000,
            "min_tx": 100,
            "multiplier": 3,
            "dst_ip": "10.0.0.13",
            "auth_type": "",
            "auth_keys": {1: "secret_1", 2: "secret_2"},
        }
    }
    
  • 列出 bfd_monitors

    GET /v2.0/bfd_monitors
    
  • 显示 bfd_monitor

    GET /v2.0/bfd_monitors/{monitor_id}
    {
        "bfd_monitor": {
            "name": "bfd1",
            "project_id": "{project_id}",
            "description": "",
            "mode": "asynchronous",
            "min_rx": 1000,
            "min_tx": 100,
            "multiplier": 3,
            "dst_ip": "10.0.0.13",
            "src_ip": "169.254.112.144",
            "auth_type": "",
            "auth_keys": {1: "secret_1"},
        }
    }
    
  • 更新 bfd_monitor

    PUT /v2.0/bfd_monitors/{monitor_id}
    {
        "bfd_monitor": {
            "name": "bfd_monitor1"
            "description": "foo",
            "min_rx": 2000,
            "min_tx": 200,
            "multiplier": 4
        }
    }
    
  • 删除 bfd_monitor

    DELETE /v2.0/bfd_monitors/{monitor_id}
    

注意

只有未关联的 bfd_monitor 才能被删除。

bfd_monitor 的 API 字段和描述

属性

类型

必需

CRUD

描述

id

uuid-str

R

bfd_monitor 的 ID

name

字符串

CRU

bfd_monitor 的人类可读名称(限制为 255 个字符)。不必唯一。

description

字符串

CRU

bfd_monitor 的人类可读描述(限制为 255 个字符)。

project_id

字符串

R

bfd_monitor 的所有者。

mode

字符串

CR

可以是 asynchronous(默认的常见 BFD 回显模式)或 demand(使用其他机制检测链路状态),并且可以接受未来的模式,如 one-arm-echo,参见 [4]

dst_ip

字符串

CR

要监控的目标 IP 地址。对于单跳 BFD,这是路由的下一跳 IP,对于一般情况(如多跳 BFD),这是一个任意 IP(IPv4 或 Ipv6),可以用作 BFD 邻居。

src_ip

字符串

CR

用于发送 BFD 数据包的源 IP 地址。可选字段,如果未指定,则设置为接口上的 IP,例如 qg-xyz。可以在远程端设置以进行配置。

min_rx

Integer

CRU

此 BFD 会话提供接收 BFD 控制消息的最短间隔,以毫秒为单位。至少为 1。默认值为 1000。

min_tx

Integer

CRU

此 BFD 会话愿意传输 BFD 控制消息的最短间隔,以毫秒为单位。至少为 1。默认值为 100。

multiplier

Integer

CRU

BFD 检测乘数。端点如果给定的连续 BFD 控制消息未能到达,则发出连接故障信号。默认值为 3。

status

字符串

N/A

R

这表明 BFD 监控器是否已在后端成功创建,但没有关于会话状态的信息,可以使用 session_status API 端点获取会话状态。

auth_type

字符串

CR

身份验证类型,可以是 passwordMD5MeticulousMD5SHA1MeticulousSHA1,如果为空,则不使用身份验证。

auth_key

字典

CR

一个身份验证密钥链的字典,其中键是 Auth Key ID 的整数,值是 PasswordAuth Key 的字符串。

注意

要使用 BFD 的身份验证,请查看 [5]

bfd_monitors 的 API 扩展提案

ALIAS = 'bfd-monitor'
IS_SHIM_EXTENSION = False
IS_STANDARD_ATTR_EXTENSION = False
NAME = 'BFD monitors for Neutron'
DESCRIPTION = "Provides support for BFD monitors"
UPDATED_TIMESTAMP = "2021-02-12T11:00:00-00:00"
BFD_MONITOR = 'bfd_monitor'
BFD_MONITORS = 'bfd_monitors'
BFD_SESSION_STATUS = 'bfd_session_status'

BFD_MODE_ASYNC = 'asynchronous'
BFD_MODE_DEMAND = 'demand'
BFD_MODE_ONE_ARM = 'one_arm_echo'

RESOURCE_ATTRIBUTE_MAP = {
    BFD_MONITORS: {
        'id': {'allow_post': False, 'allow_put': False,
               'validate': {'type:uuid': None},
               'is_visible': True,
               'primary_key': True,
               'enforce_policy': True},
        'name': {'allow_post': True, 'allow_put': True,
                 'validate': {'type:string': db_const.NAME_FIELD_SIZE},
                 'default': '', 'is_filter': True, 'is_sort_key': True,
                 'is_visible': True},
        'description': {'allow_post': True, 'allow_put': True,
                        'is_visible': True, 'default': '',
                        'validate': {
                            'type:string': db_const.DESCRIPTION_FIELD_SIZE}},
        'project_id': {'allow_post': True, 'allow_put': False,
                       'validate': {
                           'type:string': db_const.PROJECT_ID_FIELD_SIZE},
                       'required_by_policy': True,
                       'is_visible': True, 'enforce_policy': True},
        'mode': {'allow_post': True, 'allow_put': False,
                 'validate': {'type:string': db_const.STATUS_FIELD_SIZE},
                 'default': BFD_MODE_ASYNC, 'is_filter': True,
                 'is_sort_key': True, 'is_visible': True},
        'dst_ip': {'allow_post': True, 'allow_put': False,
                   'validate': {'type:ip_address': None},
                   'is_sort_key': True, 'is_filter': True,
                   'is_visible': True, 'default': None,
                   'enforce_policy': True},
        'src_ip': {'allow_post': True, 'allow_put': False,
               'validate': {'type:ip_address_or_none': None},
               'is_sort_key': True, 'is_filter': True,
               'is_visible': True, 'default': None,
               'enforce_policy': True},
        'min_rx': {'allow_post': True, 'allow_put': True,
                   'validate': {'type:non_negative': None},
                   'convert_to': converters.convert_to_int,
                   'default': 1000,
                   'is_visible': True, 'enforce_policy': True},
        'min_tx': {'allow_post': True, 'allow_put': True,
                   'validate': {'type:non_negative': None},
                   'convert_to': converters.convert_to_int,
                   'default': 100,
                   'is_visible': True, 'enforce_policy': True},
        'multiplier': {'allow_post': True, 'allow_put': True,
                       'validate': {'type:non_negative': None},
                       'convert_to': converters.convert_to_int,
                       'default': 3,
                       'is_visible': True, 'enforce_policy': True},
        'status': {'allow_post': False, 'allow_put': False,
                   'is_filter': True, 'is_sort_key': True,
                   'is_visible': True},
        'auth_type': {'allow_post': True, 'allow_put': False,
                      'validate': {'type:string_or_none':
                                   db_const.NAME_FIELD_SIZE},
                      'default': constants.ATTR_NOT_SPECIFIED,
                      'is_visible': True},
        'auth_key': {'allow_post': True, 'allow_put': False,
                     'validate': {'type:dict_or_none': None},
                     'default': constants.ATTR_NOT_SPECIFIED,
                     'is_visible': True},
    },
}
SUB_RESOURCE_ATTRIBUTE_MAP = {}
ACTION_MAP = {
    BFD_MONITOR: {
        'bfd_session_status': 'GET',
        'bfd_monitor_associations': 'GET',
    }
}
ACTION_STATUS = {}
REQUIRED_EXTENSIONS = [l3.ALIAS]
OPTIONAL_EXTENSIONS = []
  • 显示 BFD 会话状态(有关详细信息,请参阅 rfc5880,状态变量部分)

    GET /v2.0/bfd_monitors/{monitor_id}/session_status
    {
        "bfd_session_status": {
            "remotes": [{
                "type": "extra_route",
                "router": {router_id},
                "extra_route": {
                    "destination": "10.0.3.0/24",
                    "nexthop": "10.0.0.1"
                },
                "src_ip": "169.254.112.144",
                "status": {
                    "SessionState": "Up",
                    "RemoteSessionState": "Up",
                    "LocalDiagnostic": "",
                    "RemoteDiagnostic": "",
                    "LocalDiscriminator": "",
                    "RemoteDiscriminator": "",
                    "Forwarding": "",
                }
            },
            {
                "type": "extra_route",
                ...
            }]
        }
    }
    
  • 显示 bfd_monitor 关联

    GET /v2.0/bfd_monitors/{monitor_id}/bfd_monitor_associations
    {
         "bfd_monitor_associations": {
             "remotes": [{
                 "type": "extra_route",
                 "router": {router_id},
                 "extra_route": {
                     "destination": "10.0.3.0/24",
                     "nexthop": "10.0.0.1"
                 },
                 "src_ip": "169.254.112.144",
             },
             {
                 "type": "extra_route",
                 ...
             }]
         }
    }
    

注意

当前提案仅适用于“type”:“extra_route”,因此将显示 extra_route 的详细信息(目前为下一跳、目标)。

注意

一个 bfd_monitor 实例可以关联到多个 extra_route。

注意

src_ip 是用于发送 BFD 数据包的源 IP 地址。一个只读字段,实际上显示了接口上设置的 IP,例如 qg-xyz。可以在远程端设置。

状态字段的简短描述

属性

描述

SessionState

BFD 会话的状态,可以是 admin_downdowninitup

RemoteSessionState

远程端点 BFD 会话的状态,可以是 admin_downdowninitup

LocalDiagnostic

一个诊断代码,指定本地系统上次更改会话状态的原因。错误消息在 RFC 5880 的第 4.1 节中定义(参见 [3])。

RemoteDiagnostic

一个诊断代码,指定远程系统上次更改会话状态的原因。错误消息在 RFC 5880 的第 4.1 节中定义(参见 [3])。

LocalDiscriminator

此 BFD 会话的本地区分器,用于唯一标识它。

RemoteDiscriminator

此 BFD 会话的远程区分器。这是远程系统选择的区分器。

Forwarding

报告 BFD 会话是否认为此接口可用于转发流量。可以是 truefalse

注意

以上字段是可选的,因此根据后端,并非全部都会填充,在最坏的情况下,只会显示累积的 Forwarding 结果。

对路由器 extra routes API 的更改

  • 更改以添加或删除路由器上的 extra routes,或更新路由器 API

    PUT /v2.0/routers/{router_id}
    {
        "router": {
            "routes": [
                {
                    "destination": "179.24.1.0/24",
                    "nexthop": "172.24.3.99"
                    "bfd_monitor": {bfd_monitor_uuid}
                },
            ]
        }
    }
    
    PUT /v2.0/routers/{router_id}/add_extraroutes
    {
        "router" : {
            "routes" : [
               { "destination" : "10.0.3.0/24", "nexthop" : "10.0.0.13", "bfd_monitor": {bfd_monitor_uuid} },
               { "destination" : "10.0.4.0/24", "nexthop" : "10.0.0.14" }
            ]
        }
    }
    
    PUT /v2.0/routers/{router_id}/remove_extraroutes
    {
        "router" : {
            "routes" : [
               { "destination" : "10.0.3.0/24", "nexthop" : "10.0.0.13", "bfd_monitor": {bfd_monitor_uuid} }
            ]
        }
    }
    

注意

API 允许删除与路由关联的 bfd_monitor,而无需删除和重新创建整个下一跳-目标元组。

  • 获取路由状态

    GET /v2.0/routers/{router_id}/routes_status
    {
        "router": {
            "routes": [
                { "destination" : "10.0.3.0/24",
                  "nexthop" : "10.0.0.13",
                  "status": "UP",
                  "bfd_monitor": {bfd_monitor_uuid}
                },
            ]
        }
    }
    

允许 bfd_monitor 关联到 extra_routes 的 API 扩展提案

ALIAS = 'bfd-for-extraroutes'
IS_SHIM_EXTENSION = False
IS_STANDARD_ATTR_EXTENSION = False
NAME = 'BFD monitors for extraroutes'
DESCRIPTION = ('Provides the possibility to associate a bfd_monitor to'
               'extra_routes')
UPDATED_TIMESTAMP = '2021-01-29T00:00:00+00:00'
RESOURCE_NAME = l3.ROUTER
COLLECTION_NAME = l3.ROUTERS
ROUTES = 'routes'
RESOURCE_ATTRIBUTE_MAP = {
    COLLECTION_NAME: {
        ROUTES: {
            'allow_post': False, 'allow_put': True,
            'validate': {'type:hostroutes': ['destination', 'nexthop',
                                             'bfd_monitor_id']},
            'convert_to': converters.convert_none_to_empty_list,
            'is_visible': True,
            'default': constants.ATTR_NOT_SPECIFIED},
    }
}
SUB_RESOURCE_ATTRIBUTE_MAP = None
ACTION_MAP = {}
REQUIRED_EXTENSIONS = [l3.ALIAS, extraroute.ALIAS]
OPTIONAL_EXTENSIONS = []
ACTION_STATUS = {}

注意

[6] 提出将 hostroutes 验证器更改为接受一个可能的字段列表以进行验证,例如:destinationnexthopbfd_monitor_id

BFD 本身以及获取 bfd_monitor 会话状态信息可能是一项非常消耗资源的操作(必须从后端获取信息),因此建议新的 API 端点来获取监控状态,以避免过度负载或影响普通的路由器 API 操作。

routes_status API 端点向用户提供有关被监控链路是否处于活动状态的反馈。

如果 bfd_monitor 的 session_status 为 DOWN(BFD 检测到给定的链路已断开),则应在后端删除给定的路由(在命令行上:ip r delete…),并在监控器变为 UP 时将其设置回去。

当 bfd_monitor 与任何路由关联时,后端会创建 bfd_monitor 实例,bfd_monitor 的 statusDOWN 设置为 ACTIVE。当更新 extra route 并且 bfd_monitor 值设置为空时,bfd_monitor 的 status 设置为 DOWN,因此 bfd_monitor 会在后端被删除。

由于 bfd_monitor 可以关联到许多路由,如果它关联到至少一个路由并且在后端创建成功,则 bfd_monitor 的 status 字段为 ACTIVE,当从最后一个 extra_route 删除关联时,它将更改为 DOWN(bfd_monitor 从最后一个 extra_route 中删除)。

后端

OVS 可以在接口上处理 BFD 并检查其状态。它的问题是 ovs 可以为每个端口拥有 1 个 BFD 会话,并且为了管理它,ovsdb 是最简单的方法,但正如 LIU Yulong 在 [7] 中所述,从 l3-agent 接触 ovsdb 可能会很奇怪。

使用 OVS 的一个可行方案是创建一个 BFD 桥接,例如 br-bfd,并向其中添加 veth 端口,并在这些接口上启用 BFD。

 --------
| br-int |
 --------
   | veth-xy-br-int
   |
   | veth-xy
 --------
| br-bfd |
 --------

另一种可能性是使用 os-ken,但在我的实验中,os-ken 中的 BFD 实现不够成熟,并且无论如何都需要 ovsdb 连接,所以我建议不要将 os-ken 用于 BFD。

数据模型影响

为 bfd_monitors 创建新表,更改 routers 表以能够将 bfd_monitor 与 extra route(nexthop - destination 对)关联起来。

安全影响

性能影响

BFD 和获取 bfd_monitor 状态信息可能是一项非常消耗资源的操作,因为它可能从后端获取,必须仔细记录。

实现

负责人

主要负责人

Lajos Katona <katonalala@gmail.com> (IRC: lajoskatona)

工作项

  • REST API 更新。

  • DB 模式更新。

  • L3 代理更新以处理 BFD

    • RPC 更改以将 BFD 数据发送到 l3 代理。

    • RPC 更改以从 l3 代理获取 BFD 状态信息。

  • CLI 更新。

  • 文档。

  • 测试和 CI 相关更改。

测试

  • 单元测试

  • 功能测试

  • API 测试

也许这可以通过全栈测试端到端轻松测试。需要进一步调查。

文档影响

用户文档

新的 API 和对 legacy API(如 routers)的更改必须记录。

参考资料