sonic acl守护进程

内容分享12小时前发布
0 0 0

从acl的守护进程类 – AclOrch,进一步看orchagent应用的订阅与监听、event处理。

1. 初始化

1.1 constructor

首先看一下AclOrch类的构造函数,在构造对象时执行了订阅动作。
sonic acl守护进程

如上图所示,构建一个AclOrch对象可以分为三步:

成员变量赋值: 调用父类构造函数,执行订阅操作init(): 根据平台设置镜像能力;获取交换机对于acl的支持能力;与相关进程如ports、mirror守护进程对象构建观察者模式配置数据平面遥测技术

前两个步骤比较重要,接下来分别从前两个步骤分析acl守护进程的构建。

1.1.1 成员变量赋值

成员变量的赋值主要是对5个相关的守护进程对象赋值,其中比较重要的是调用父类的构造函数进行初始化。

1.1.2 父类构造函数

sonic acl守护进程


AclOrch
继承
Orch
以及
Observer
类,
Orch
类是所有守护进程类都需要继承的,
Observer
类是该对象与其相关对象形成观察者模式需要的,表示该类对象是一个观察者,需要观察端口状态、mirror状态的变化(通过notify)等。
主要看父类
Orch()
的构造函数。构造所有的守护进程类对象时,都需要调用父类
Orch
的构造函数,并且重载了多个构造函数,但作用都是订阅相关通知。
选择
AclOrch
类对象调用的父类构造函数:
sonic acl守护进程

传入一组表连接对象


    vector<TableConnector> acl_table_connectors = {
        confDbAclTableType, //CONFIG_DB|ACL_TABLE
        confDbAclTable, //CONFIG_DB|ACL_TABLE_TYPE
        confDbAclRuleTable, //CONFIG_DB|ACL_RULE
        appDbAclTable, //APPL_DB|ACL_TABLE_TABLE
        appDbAclRuleTable, //APPL_DB|ACL_TABLE_TYPE_TABLE
        appDbAclTableType, //APPL_DB|ACL_RULE_TABLE
    };

添加消费者,针对上述每一个table,创建消费者: 采用模式订阅,订阅表的keyspace事件


void Orch::addConsumer(DBConnector *db, string tableName, int pri) //pri有默认值
{
    if (db->getDbId() == CONFIG_DB || db->getDbId() == STATE_DB || db->getDbId() == CHASSIS_APP_DB)
    {
        addExecutor(new Consumer(new SubscriberStateTable(db, tableName, TableConsumable::DEFAULT_POP_BATCH_SIZE, pri), this, tableName));
    }
    else
    {
        addExecutor(new Consumer(new ConsumerStateTable(db, tableName, gBatchSize, pri), this, tableName));
    }
}

针对不同的数据库,初始化为不同的订阅对象:


CONFIG_DB

STATE_DB

CHASSIS_APP_DB
中的table使用
SubscriberStateTable
类初始化其他使用
ConsumerStateTable
初始化该守护进程需要订阅
CONFIG_DB
以及
APPL_DB
中table的keyspace事件,所以需要分别使用不同的订阅者类初始化,下一小节详细介绍两种初始化的不同。
Consumer类图
sonic acl守护进程

Selectable

一个订阅者/消费者的基类,保存订阅的events的处理优先级(默认为0),以及一个上次处理该订阅事件的时间戳,每次处理过都需要更新,为了保证在相同优先级下,每个订阅者或者消费者能够被轮询到,进行events处理。还提供一些可被覆写的虚函数:
获取与数据库连接之后的socket的fd判断当前对象是否有等待处理的events读取内核中的events
Executor

保存当前的订阅者对象,是
SubscriberStateTable
还是
ConsumerStateTable
保存当前的守护进程对象保存当前订阅的table_name覆写父类的虚函数,相当于对
SubscriberStateTable
中覆写的
Selectable
中的虚函数再次封装提供可供子类覆写的虚函数: events产生时的处理函数
Consumer

覆写
Executor
的虚函数同步操作: 将从内核中获取的events信息同步到处理队列
m_toSync

1.1.2.1 SubscriberStateTable

使用
cli
创建
acl table/rule
时是写入
CONFIG_DB
中的,所以目前先主要看一下
SubscriberStateTable

1.1.2.1.1 类图

sonic acl守护进程

上图是
SubscriberStateTable
的类图,分别介绍一下每个类的大概作用:


Selectable

一个订阅者/消费者的基类,保存订阅的events的处理优先级(默认为0),以及一个上次处理该订阅事件的时间戳,每次处理过都需要更新,为了保证在相同优先级下,每个订阅者或者消费者能够被轮询到,进行events处理。还提供一些可被覆写的虚函数:
获取与数据库连接之后的socket的fd判断当前对象是否有等待处理的events读取内核中的events
RedisSelect

较父类增加一个指针变量
m_subscribe
,指向一个与redis的连接(DBConnector()),该连接执行了订阅功能覆写
Selectable
中的虚函数,有实现subscribe/psubscribe/punsubscribe方法: 频道订阅/模式订阅/取消模式订阅,实际调用的是DBConnector中实现的
TableEntryPoppable

一个抽象类,为继承它的子类提供一个虚函数
pop()
,该虚函数基本作用是: 从获取的events队列中取出最前面的一个,并且将其拆解成
key

operation

information
三个部分。
TableBase

保存并且构造表的基本信息
table_nametable_separator: 只有
:
或者
|
构造table中一条记录在redis中的key:
table_name + table_separator + record_key
构造table的channel名称:
table_name + "_CHANNEL"
或者
table_name + "_CHANNEL@" + db_index
,指的是订阅数据库db_index中的table
TableConsumable

只是组合
TableBase

RedisSelect
两个父类的中介,构造时需要调用两个父类的构造函数。


TableEntryPoppable

TableBase

TableConsumable
都定义在
table.h
中。


RedisTransactioner

这个类主要用来处理redis事务的,使用hiredis库向redis发送
multi

exec
等事务相关命令。
但是redis事务只是用于帮助用户在一个步骤中执行多个命令,并不具有原子性,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。
redis使用queue按顺序保存每一条命令,也会使用queue保存每一条命令执行之后的响应。
ConsumerTableBase

构造对象时调用两个父类的构造函数覆写子类虚函数
pop()
增加
m_buffer
队列,保存子类table初始化时获取的记录
SubscriberStateTable

构造对象时: 调用父类的构造函数,使用模式订阅,订阅table的keyspace事件覆写
Selectable
类中虚函数,读取events、判断连接状态的最终实现增加buffer队列
m_keyspace_event_buffer
,保存的是从内核中需要被处理的events
注意:先从内核中获取events存到
m_keyspace_event_buffer
,由于每一个连接需要被轮询处理,所以不是立即处理,等待轮询到该连接时,才将
m_keyspace_event_buffer
中的events同步到处理队列。

另外还有
Table
的类图,
SubscriberStateTable
中有一个
Table
成员:


TableEntryEnumerable
: 提供两个虚函数,一个实际上是需要执行
HGETALL KEY
,获取一条记录的所有
<field,value>
信息;一个实际上是需要执行
KEYS ACL_TABLE|*
,获取table的所有记录对应的key值
Table
: 实现table的crud操作

HMSET KEY_NAME FIELD1 VALUE1 ...FIELDN VALUEN
: 创建或者更新记录的字段值
DEL KEY_NAME
: 删除table中的一条记录
HDEL KEY_NAME FIELD1.. FIELDN
: 删除table中对应记录的字段
HGETALL KEY_NAME
: 获取一条记录的所有
<field,value>
信息
HGET KEY_NAME FIELD
: 获取记录中对应
FIELD
的值
HSET KEY_NAME FIELD VALUE
: 创建或者更新记录中对应
FIELD
的值
KEYS PATTERN
: 获取符合
PATTERN
的hash值,如
KEYS ACL_TABLE|*
,会得到ACL_TABLE中的所有记录key值

1.1.2.1.2 constructor

sonic acl守护进程

一个保存被订阅表的信息,以及订阅连接信息的类:

DBConnector *db: 传入的是一个与数据库的连接,其中在订阅过程中比较重要的信息是连接的fd

调用父类:按照上一小节的类图连续调用父类构造函数,保存被订阅table的信息,以及订阅连接的基本信息

构建与table的连接
m_table
:

向redis发布订阅命令

使用模式订阅:
__keyspace@4__:ACL_TABLE|*
;CONFIG_DB的index/id为4,表示订阅CONFIG_DB中ACL_TABLE的所有记录的SET以及DEL事件。 – keyspace事件表示table的所有变化都需要监听,其实就只有两种操作:SET以及DEL
m_subscribe->psubscribe()

sonic acl守护进程

当前数据库连接还会有其他操作,所以根据当前连接信息创建一个新的连接,专门用来实现订阅与监听功能。最终的订阅操作实际上就是调用
hiredis
库向redis发布订阅命令,比如:
PSUBSCRIBE __keyspace@4__:ACL_TABLE|*

获取table所有的记录信息,每一条记录都初始化为一个SET操作,构建成事件通知形式存储到
m_buffer
中 – 在前面介绍过的orchagent应用启动过程中,会处理这些SET操作,然后将
m_buffer
清空

1.1.2.2 ConsumerStateTable

有待补充…

1.2 init()

根据不平的平台设置不同的镜像能力,比如barefoot:


m_mirrorTableCapabilities =
    {
        { TABLE_TYPE_MIRROR, true },
        { TABLE_TYPE_MIRRORV6, true },
    }

将镜像能力写入
STATE_DB
中的
SWITCH_CAPABILITY
表的
switch
记录中:
HMSET

sonic acl守护进程

调用
sai_switch_api
接口获取交换机支持的
acl
能力,获取asic支持的规则最大优先级与最小优先级 – 调用sai接口

调用
sai_switch_api
接口获取asic支持的
acl action
能力,写入
STATE_DB
中的
ACL_STAGE_CAPABILITY_TABLE
– INGRESS STAGE & EGRESS STAGE

初始化ACL Table Type,如L3、L3V6等,初始化各类table支持的规则属性

“L3”:支持IPv4协议的基本以及高级ACL“L3V6”:支持IPv6协议的基本以及高级ACL“MIRROR”:barefoot中支持IPv4以及IPv6的镜像“MIRRORV6”:支持IPv6的镜像“MIRROR_DSCP”:仅仅支持dscp标志位的匹配“PFCWD”:“CTRLPLANE”“DTEL_FLOW_WATCHLIST”“MCLAG”“MUX”“DROP”
默认不支持L2层ACL,但是sonic提供了可添加acl table type功能。

与Ports以及Mirror守护进程对象绑定,形成观察者模式,acl守护进程对象是观察者

2. doTask()

在OrchDaemon::start()章节中会轮询各个守护进程订阅的事件通知,看一下AclOrch对于events的处理。

2.1 AclTable

首先看一下对于AclTable的处理:

void AclOrch::doAclTableTask(Consumer &consumer);

2.1.1 事件通知预处理

初始的event:
"pmessage","__keyspace@4__:ACL_TABLE|*","__keyspace@4__:ACL_TABLE|TEST","set"


pmessage
: 表示是模式订阅消息,如果是
message
,表示是频道订阅消息
__keyspace@4__:ACL_TABLE|*
: 订阅模式
__keyspace@4__:ACL_TABLE|TEST
: 订阅频道,如
__keyspace@4__:ACL_TABLE|TEST

set
: 对应频道的操作,只有
SET
以及
DEL
操作

预处理之后的
event={"type":"pmessage","pattern":"__keyspace@4__:ACL_TABLE|*","channel":"__keyspace@4__:ACL_TABLE|TEST","data":"set"}

epoll取到的events存储在一个队列中,但是因为是按照时间戳进行排序,然后对每一个订阅的事件通知进行轮询处理,所以需要从队列中读取 –
SubscriberStateTable::pops()
,然后转存到待处理事件的队列–
Consumer::addToSync()
。在这个过程中存在一次对事件的预处理: –
consumer->refillToSync()

sonic acl守护进程

最终形式为:
<key, "set/del", <<field1,value1>,<field2,value2>,...>
,其中
key
是表的一条记录的name

2.1.2 doTask()

最终传入
doTask()
方法的event形式为:
<key, "set/del", <<field1,value1>,<field2,value2>,...>
,如“,并且保存在
m_toSync
队列中,轮询队列处理每一个事件通知。

2.1.2.1 SET

set操作又分为update以及create,下图是set操作的一些验证过程。
sonic acl守护进程

上述流程是处理SET操作的基本流程:

创建一个AclTable对象,根据通知信息设置table属性
descriptiontable typeports: PortsOrch初始化时会调用sai获取ports信息
判断需要绑定的port是否处于ready状态,如果不是则跳过,如果是则进一步获取port id将port id加入table对象的port成员 stage: 仅支持INGRESS&EGRESS 验证table类型是否支持针对table类型添加相关stage的必要属性针对stage类型添加必要的acl action再次验证table的stage信息以及acl action信息获取object id
如果table id(table name)之前已经创建过,那么将直接获取到对应的oid如果创建一个新的table,那么object id为table返回SAI_NULL_OBJECT_ID,等于0 table是否存在,即object id是否等于SAI_NULL_OBJECT_ID:
object id!=SAI_NULL_OBJECT_ID,存在,则采取update操作object id==SAI_NULL_OBJECT_ID,不存在,则采取create操作

2.1.2.2 CREATE

对应
AclOrch::addAclTable()
,create的大致流程:
sonic acl守护进程

获取object id
如果table id(table name)之前已经创建过,那么将直接获取到对应的oid如果创建一个新的table,那么object id为table返回SAI_NULL_OBJECT_ID,等于0 table是否存在,如果object id!=SAI_NULL_OBJECT_ID,存在,则先删除这个table检查当前的mirror table信息,因为mirror table在ingress以及egress只能有一个表 – ???创建表的实际操作:
create tablebind ports 如果创建失败,则返回false如果创建成功,那么将得到oid,保存在成员
m_AclTable
中 – 该成员保存创建过的table信息,(table_oid,table)如果是mirror类型,更新对应stage的mirror table为当前创建的table

继续分析create table部分:
sonic acl守护进程

构建与sai对应的属性信息


SAI_ACL_TABLE_ATTR_ACL_BIND_POINT_TYPE_LIST
:


typedef struct _sai_s32_list_t
{
    uint32_t count;
    int32_t *list;
} sai_s32_list_t;


key
对应的
value
类型为上述代码所示,假设是
L3
类型,那么其中:


count
: 2 – 表示可绑定的point类型的数量
list
:
{SAI_ACL_BIND_POINT_TYPE_PORT, SAI_ACL_BIND_POINT_TYPE_LAG}
– 表示可绑定的point类型


SAI_ACL_TABLE_ATTR_ACL_ACTION_TYPE_LIST
:


typedef struct _sai_s32_list_t
{
    uint32_t count;
    int32_t *list;
} sai_s32_list_t;


key
对应的
value
类型为上述代码所示,其中:


count
: 表示可绑定的action类型的数量
list
: 表示可绑定的action类型
如果是
L3
类型,不存在
action list
,所以该属性不会发送给syncd应用


SAI_ACL_TABLE_ATTR_ACL_STAGE
:


typedef int32_t sai_int32_t;

可设置为:
ingress

egress

每一种类型可macth的字段是不一样的,假设是
L3
类型,那么:


typedef struct _sai_attribute_t
{
    /** @passparam meta */
    sai_attr_id_t id;

    /** @passparam meta */
    sai_attribute_value_t value;
} sai_attribute_t;

每一种可匹配类型转换成为上述格式,分为两种匹配类型,一种是基本匹配信息,其中
id
表示属性
id

value
选择
bool
类型,表示是否支持该属性值,全部设置为
true
:


SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE

SAI_ACL_TABLE_ATTR_FIELD_OUTER_VLAN_ID

SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE

SAI_ACL_TABLE_ATTR_FIELD_SRC_IP

SAI_ACL_TABLE_ATTR_FIELD_DST_IP

SAI_ACL_TABLE_ATTR_FIELD_ICMP_TYPE

SAI_ACL_TABLE_ATTR_FIELD_ICMP_CODE

SAI_ACL_TABLE_ATTR_FIELD_IP_PROTOCOL

SAI_ACL_TABLE_ATTR_FIELD_L4_SRC_PORT

SAI_ACL_TABLE_ATTR_FIELD_L4_DST_PORT

SAI_ACL_TABLE_ATTR_FIELD_TCP_FLAGS

一种是范围匹配信息,其中
id
表示属性
id

value
选择
sai_s32_list_t
类型,对应
key

SAI_ACL_TABLE_ATTR_FIELD_ACL_RANGE_TYPE
,其中:


count
: 2 – 表示支持的
range type
的数量
list
:
{SAI_ACL_RANGE_TYPE_L4_SRC_PORT_RANGE, SAI_ACL_RANGE_TYPE_L4_DST_PORT_RANGE}
– 表示支持的
range type
最终实现是
RedisRemoteSaiInterface::create()
– 下面的步骤是
RedisRemoteSaiInterface
对象的处理生成oid –
VirtualObjectIdManager::allocateNewObjectId()

检查object type是否位于范围内,否则抛出错误检查switch id是否有效,否则抛出错误生成oid: 调用redis的incr命令增加asic_db中VIDCOUNTER的值如果oid超过object id的最大值,抛出错误构建最终oid: switch_id, oid, guid 序列化oid – “oid:0x%”序列化属性列表,循环属性列表
根据objectType以及attr id获取对应的详细的属性信息 – sdk如果不存在,抛出错误存在则获取attr name根据attr.value的类型进行序列化构成pair: <attr_name,serialized_attr_value> 序列化object type: 根据object type获取对应的详细的类型信息,然后得到type name – sdk组成key:
serialized_object_type:oid:0x%
调用
m_communicationChannel->set()
,参数信息为:
{"key":"", "entry":"", "op":"create"}
– RedisChannel Async Mode

LPUSH ASIC_STATE_KEY_VALUE_OP_QUEUE key value op


key
: 如
SAI_OBJECT_TYPE_ACL_TABLE:oid:0x%

value

op
: 如
REDIS_ASIC_STATE_COMMAND_CREATE

create

PUBLISH ASIC_STATE_CHANNEL G
等待response
如果成员
m_syncMode
为false,是异步模式,直接返回success如果成员
m_syncMode
为true,是同步模式,开启对频道
ASIC_STATE_CHANNEL@asic_db_index
的监听,获取通知信息输出LOG

继续分析bind ports部分:
最终调用的是Port的守护进程进行绑定 –
gPortsOrch->bindAclTable()
,分为几个大步骤:

创建table group
构建table group属性

SAI_ACL_TABLE_GROUP_ATTR_ACL_STAGE
:
SAI_ACL_STAGE_INGRESS/SAI_ACL_STAGE_EGRESS

SAI_ACL_TABLE_GROUP_ATTR_ACL_BIND_POINT_TYPE_LIST
:
{count:1, list:SAI_ACL_BIND_POINT_TYPE_PORT}

SAI_ACL_TABLE_GROUP_ATTR_TYPE
:
SAI_ACL_TABLE_GROUP_TYPE_PARALLEL
调用
sai_acl_api->create_acl_table_group()
创建 将table group与port绑定
属性信息:
{attr.id=SAI_PORT_ATTR_INGRESS_ACL/SAI_PORT_ATTR_EGRESS_ACL, atr.value.oid=m_ingress_acl_table_group_id/m_egress_acl_table_group_id}
调用
sai_port_api->set_port_attribute()
设置port对应的table group 创建group member
属性信息

SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_GROUP_ID
:
group_oid

SAI_ACL_TABLE_GROUP_MEMBER_ATTR_ACL_TABLE_ID
:
table_oid

SAI_ACL_TABLE_GROUP_MEMBER_ATTR_PRIORITY
: 100 调用
sai_acl_api->create_acl_table_group_member()
创建

sonic acl守护进程
创建一个acl table并绑定到端口的过程,实际上需要通过acl table group进行连接。如上图所示,table1和table2是ingress的一个group的member,而该group又与port1/2/3相连,即table1与table2的规则会应用到port1/2/3的入口流量,而table3的规则会应用到port1/2/3的出口流量。

UPDATE

只能更改ports,有待补充…

DEL

有待补充…

GET

get操作不在doTask()中,因为所有get操作都是同步模式,需要等待syncd的反馈。

© 版权声明

相关文章

暂无评论

none
暂无评论...