SONIC VLAN配置流程

sonic vlan配置通过订阅config_db的键空间事件完成vlan配置信息从config_db到内核和硬件。config_db.json格式如下:

"VLAN": {        "Vlan1000": {            "vlanid": "1000"        }    },     "VLAN_MEMBER": {        "Vlan1000|Ethernet16": {            "tagging_mode": "untagged"        }     },     "VLAN_INTERFACE": {        "Vlan1000|192.168.0.1/27": {}    }, 

在swss容器中存在一个vlanmgrd进行用于订阅config_db的vlan键空间,响应vlan配置,解析后将其同步到内核和app_db。由portsorch订阅app_db vlan事件,将其同步到asic-db,最终将其同步到硬件。VLAN_INTERFACE接口信息由intfmgr和intforch进行处理。

vlanmgr

该进程运行在swss容器中,用于订阅config_db的vlan键空间,响应vlan配置,解析后将其同步到内核和app_db。

该部分涉及文件:

vlanmgrd.cpp

vlanmgr.cpp

vlanmgr.h

class VlanMgr : public Orch{public:    VlanMgr(DBConnector *cfgDb, DBConnector *appDb, DBConnector *stateDb, const vector<string> &tableNames);    using Orch::doTask;private:    ProducerStateTable m_appVlanTableProducer, m_appVlanMemberTableProducer;    Table m_cfgVlanTable, m_cfgVlanMemberTable;    Table m_statePortTable, m_stateLagTable;    Table m_stateVlanTable;    std::set<std::string> m_vlans;    void doTask(Consumer &consumer);//主任务    void doVlanTask(Consumer &consumer);//处理vlan事件    void doVlanMemberTask(Consumer &consumer);//处理vlan成员事件    void processUntaggedVlanMembers(string vlan, const string &members);//处理vlan内untag成员,暂时没用    bool addHostVlan(int vlan_id);//将vlan同步到host,即创建vlan    bool removeHostVlan(int vlan_id);//删除host中的vlan    bool setHostVlanAdminState(int vlan_id, const string &admin_status);//设置hostvlan状态    bool setHostVlanMtu(int vlan_id, uint32_t mtu);//设置host vlan接口的mtu    //添加成员    bool addHostVlanMember(int vlan_id, const string &port_alias, const string& tagging_mode);    bool removeHostVlanMember(int vlan_id, const string &port_alias);//删除host vlan成员    bool isMemberStateOk(const string &alias);//从state db中获取成员端口是否ok    bool isVlanStateOk(const string &alias);//从state db中获取vlan是否ok    bool isVlanMacOk();//判断该vlan的mac地址是否ok};

实现

int main(int argc, char **argv){    Logger::linkToDbNative("vlanmgrd");    SWSS_LOG_ENTER();    SWSS_LOG_NOTICE("--- Starting vlanmgrd ---");    try    {        vector<string> cfg_vlan_tables = {//订阅了两个config db table            CFG_VLAN_TABLE_NAME,            CFG_VLAN_MEMBER_TABLE_NAME,        };        //连接了三个数据库        DBConnector cfgDb(CONFIG_DB, DBConnector::DEFAULT_UNIXSOCKET, 0);//读和订阅config-db数据        DBConnector appDb(APPL_DB, DBConnector::DEFAULT_UNIXSOCKET, 0);//将配置希尔app-db        DBConnector stateDb(STATE_DB, DBConnector::DEFAULT_UNIXSOCKET, 0);//获取vlan端口是否准备好        /*         * swss service starts after interfaces-config.service which will have         * switch_mac set.         * Dynamic switch_mac update is not supported for now.         * 获取全局的mac地址作为vlan-if的mac地址         */        Table table(&cfgDb, "DEVICE_METADATA");        std::vector<FieldValueTuple> ovalues;        table.get("localhost", ovalues);        auto it = std::find_if( ovalues.begin(), ovalues.end(), [](const FieldValueTuple& t){ return t.first == "mac";} );        if ( it == ovalues.end() ) {            throw runtime_error("couldn't find MAC address of the device from config DB");        }        gMacAddress = MacAddress(it->second);        //构造vlanmgr        VlanMgr vlanmgr(&cfgDb, &appDb, &stateDb, cfg_vlan_tables);        std::vector<Orch *> cfgOrchList = {&vlanmgr};        swss::Select s;        for (Orch *o : cfgOrchList)        {            s.addSelectables(o->getSelectables());        }        SWSS_LOG_NOTICE("starting main loop");        while (true)        {            Selectable *sel;            int ret;            ret = s.select(&sel, SELECT_TIMEOUT);            if (ret == Select::ERROR)            {                SWSS_LOG_NOTICE("Error: %s!", strerror(errno));                continue;            }            if (ret == Select::TIMEOUT)            {                vlanmgr.doTask();                continue;            }            auto *c = (Executor *)sel;            c->execute();        }    }    catch(const std::exception &e)    {        SWSS_LOG_ERROR("Runtime error: %s", e.what());    }    return -1;}

VlanMgr::doTask(Consumer &consumer)

//vlanmgr主任务,vlan没有sync进程监听内核的netlink事件,内核与硬件的vlan事件都//由vlanmgr负责,vlanmgr负责在linux内核创建vlan和vlan-member,同时往app-db中写入//vlan事件。同时vlanif的IP地址在intfmgr中进行添加,并同步到内核void VlanMgr::doTask(Consumer &consumer){    SWSS_LOG_ENTER();    //获取事件表格名    string table_name = consumer.getTableName();    if (table_name == CFG_VLAN_TABLE_NAME)    {        doVlanTask(consumer);//vlan添加创建    }    else if (table_name == CFG_VLAN_MEMBER_TABLE_NAME)    {        doVlanMemberTask(consumer);//vlan成员添加创建    }    else    {        SWSS_LOG_ERROR("Unknown config table %s ", table_name.c_str());        throw runtime_error("VlanMgr doTask failure.");    }}

VlanMgr::doVlanTask(Consumer &consumer)

//执行vlan事件void VlanMgr::doVlanTask(Consumer &consumer){    if (!isVlanMacOk())//vlanmac是否准备好了,没有的话等待,这里就是个判断,vlanmac在vlanmgr起来时已经获取了    {        SWSS_LOG_DEBUG("VLAN mac not ready, delaying VLAN task");        return;    }    auto it = consumer.m_toSync.begin();    while (it != consumer.m_toSync.end())    {        auto &t = it->second;        string key = kfvKey(t);        /* Ensure the key starts with "Vlan" otherwise ignore */        if (strncmp(key.c_str(), VLAN_PREFIX, 4))        {            SWSS_LOG_ERROR("Invalid key format. No 'Vlan' prefix: %s", key.c_str());            it = consumer.m_toSync.erase(it);            continue;        }        int vlan_id;        vlan_id = stoi(key.substr(4));        string vlan_alias, port_alias;        string op = kfvOp(t);        if (op == SET_COMMAND)        {            string admin_status;            uint32_t mtu = 0;            vector<FieldValueTuple> fvVector;            string members;            /* Add host VLAN when it has not been created. */            //添加vlan            if (m_vlans.find(key) == m_vlans.end())            {                addHostVlan(vlan_id);            }            /* set up host env .... */            //设置host vlan相关信息            for (auto i : kfvFieldsValues(t))            {                /* Set vlan admin status 设置管理状态*/                if (fvField(i) == "admin_status")                {                    admin_status = fvValue(i);                    setHostVlanAdminState(vlan_id, admin_status);                    fvVector.push_back(i);                }                /* Set vlan mtu 设置mtu */                else if (fvField(i) == "mtu")                {                    mtu = (uint32_t)stoul(fvValue(i));                    /*                     * TODO: support host VLAN mtu setting.                     * Host VLAN mtu should be set only after member configured                     * and VLAN state is not UNKNOWN.                     */                    SWSS_LOG_DEBUG("%s mtu %u: Host VLAN mtu setting to be supported.", key.c_str(), mtu);                    fvVector.push_back(i);                }                else if (fvField(i) == "members@") {                    members = fvValue(i);                }            }            /* fvVector should not be empty */            if (fvVector.empty())            {                FieldValueTuple a("admin_status",  "up");                fvVector.push_back(a);            }            //写入app-db            m_appVlanTableProducer.set(key, fvVector);            m_vlans.insert(key);            //写入state-db(6)             fvVector.clear();            FieldValueTuple s("state", "ok");            fvVector.push_back(s);            m_stateVlanTable.set(key, fvVector);            it = consumer.m_toSync.erase(it);            /*             * Members configured together with VLAN in untagged mode.             * This is to be compatible with access VLAN configuration from minigraph.             * untag模式配置vlan时,会跟随着vlan一起下发,所以在这里解决vlan成员添加问题。             * tag vlan则在单独的mem表中添加。             */            if (!members.empty())            {                processUntaggedVlanMembers(key, members);            }        }        else if (op == DEL_COMMAND)//删除        {            if (m_vlans.find(key) != m_vlans.end())            {                removeHostVlan(vlan_id);//找到删除                m_vlans.erase(key);                m_appVlanTableProducer.del(key);//通知app-db                m_stateVlanTable.del(key);//删除状态            }            else            {                SWSS_LOG_ERROR("%s doesn't exist", key.c_str());            }            SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str());            it = consumer.m_toSync.erase(it);        }        else        {            SWSS_LOG_ERROR("Unknown operation type %s", op.c_str());            SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str());            it = consumer.m_toSync.erase(it);        }    }}

void VlanMgr::doVlanMemberTask(Consumer &consumer)

//处理vlan-member事件void VlanMgr::doVlanMemberTask(Consumer &consumer){    auto it = consumer.m_toSync.begin();    while (it != consumer.m_toSync.end())    {        auto &t = it->second;        string key = kfvKey(t);        /* Ensure the key starts with "Vlan" otherwise ignore */        if (strncmp(key.c_str(), VLAN_PREFIX, 4))        {            SWSS_LOG_ERROR("Invalid key format. No 'Vlan' prefix: %s", key.c_str());            it = consumer.m_toSync.erase(it);            continue;        }        key = key.substr(4);        size_t found = key.find(CONFIGDB_KEY_SEPARATOR);        int vlan_id;        string vlan_alias, port_alias;        if (found != string::npos)        {            vlan_id = stoi(key.substr(0, found));            port_alias = key.substr(found+1);        }        else        {            SWSS_LOG_ERROR("Invalid key format. No member port is presented: %s",                           kfvKey(t).c_str());            it = consumer.m_toSync.erase(it);            continue;        }        //获取vlan名字        vlan_alias = VLAN_PREFIX + to_string(vlan_id);        string op = kfvOp(t);       // TODO:  store port/lag/VLAN data in local data structure and perform more validations.        if (op == SET_COMMAND)        {            /* Don't proceed if member port/lag is not ready yet */            if (!isMemberStateOk(port_alias) || !isVlanStateOk(vlan_alias))            {                SWSS_LOG_DEBUG("%s not ready, delaying", kfvKey(t).c_str());                it++;                continue;            }            string tagging_mode = "untagged";            for (auto i : kfvFieldsValues(t))            {                if (fvField(i) == "tagging_mode")                {                    tagging_mode = fvValue(i);                }            }            if (tagging_mode != "untagged" &&                tagging_mode != "tagged"   &&                tagging_mode != "priority_tagged")            {                SWSS_LOG_ERROR("Wrong tagging_mode '%s' for key: %s", tagging_mode.c_str(), kfvKey(t).c_str());                it = consumer.m_toSync.erase(it);                continue;            }            //添加vlan成员            if (addHostVlanMember(vlan_id, port_alias, tagging_mode))            {                key = VLAN_PREFIX + to_string(vlan_id);                key += DEFAULT_KEY_SEPARATOR;                key += port_alias;//写入app-db数据库                m_appVlanMemberTableProducer.set(key, kfvFieldsValues(t));            }            it = consumer.m_toSync.erase(it);        }        else if (op == DEL_COMMAND)        {            removeHostVlanMember(vlan_id, port_alias);            key = VLAN_PREFIX + to_string(vlan_id);            key += DEFAULT_KEY_SEPARATOR;            key += port_alias;            m_appVlanMemberTableProducer.del(key);            SWSS_LOG_DEBUG("%s", (dumpTuple(consumer, t)).c_str());            it = consumer.m_toSync.erase(it);        }        else        {            SWSS_LOG_ERROR("Unknown operation type %s", op.c_str());            it = consumer.m_toSync.erase(it);        }    }}

同步app_db数据到asic_db

app_db的vlan事件由portsorch进行同步,该部分涉及文件:

portsorch.cpp

portsorch.h

app_db中数据样例:

127.0.0.1:6379> KEYS *VLAN*1) "VLAN_TABLE:Vlan1000"2) "VLAN_MEMBER_TABLE:Vlan1000:Ethernet16"127.0.0.1:6379> HGETALL "VLAN_MEMBER_TABLE:Vlan1000:Ethernet16"1) "tagging_mode"2) "untagged"127.0.0.1:6379> HGETALL "VLAN_TABLE:Vlan1000"1) "admin_status"2) "up"127.0.0.1:6379> 

void PortsOrch::doTask(Consumer &consumer)

void PortsOrch::doTask(Consumer &consumer){    SWSS_LOG_ENTER();    string table_name = consumer.getTableName();    if (table_name == APP_PORT_TABLE_NAME)    {        doPortTask(consumer);    }    else    {        /* Wait for all ports to be initialized */        if (!isInitDone())        {            return;        }        if (table_name == APP_VLAN_TABLE_NAME)//处理vlan        {            doVlanTask(consumer);        }        else if (table_name == APP_VLAN_MEMBER_TABLE_NAME)//处理vlan成员        {            doVlanMemberTask(consumer);        }        else if (table_name == APP_LAG_TABLE_NAME)        {            doLagTask(consumer);        }        else if (table_name == APP_LAG_MEMBER_TABLE_NAME)        {            doLagMemberTask(consumer);        }    }}

void PortsOrch::doVlanTask(Consumer &consumer)

void PortsOrch::doVlanTask(Consumer &consumer){    SWSS_LOG_ENTER();    auto it = consumer.m_toSync.begin();    while (it != consumer.m_toSync.end())    {        auto &t = it->second;        string key = kfvKey(t);        /* Ensure the key starts with "Vlan" otherwise ignore */        if (strncmp(key.c_str(), VLAN_PREFIX, 4))        {            SWSS_LOG_ERROR("Invalid key format. No 'Vlan' prefix: %s", key.c_str());            it = consumer.m_toSync.erase(it);            continue;        }        int vlan_id;        vlan_id = stoi(key.substr(4)); // FIXME: might raise exception        string vlan_alias, port_alias;        vlan_alias = VLAN_PREFIX + to_string(vlan_id);        string op = kfvOp(t);        if (op == SET_COMMAND)        {            /*             * Only creation is supported for now.             * We may add support for VLAN mac learning enable/disable,             * VLAN flooding control setting and etc. in the future.             * 创建一个vlan             */            if (m_portList.find(vlan_alias) == m_portList.end())            {                if (!addVlan(vlan_alias))                {                    it++;                    continue;                }            }            it = consumer.m_toSync.erase(it);        }        else if (op == DEL_COMMAND)        {            Port vlan;            getPort(vlan_alias, vlan);            if (removeVlan(vlan))                it = consumer.m_toSync.erase(it);            else                it++;        }        else        {            SWSS_LOG_ERROR("Unknown operation type %s", op.c_str());            it = consumer.m_toSync.erase(it);        }    }}

void PortsOrch::doVlanMemberTask(Consumer &consumer)

void PortsOrch::doVlanMemberTask(Consumer &consumer){    SWSS_LOG_ENTER();    auto it = consumer.m_toSync.begin();    while (it != consumer.m_toSync.end())    {        auto &t = it->second;        string key = kfvKey(t);        /* Ensure the key starts with "Vlan" otherwise ignore */        if (strncmp(key.c_str(), VLAN_PREFIX, 4))        {            SWSS_LOG_ERROR("Invalid key format. No 'Vlan' prefix: %s", key.c_str());            it = consumer.m_toSync.erase(it);            continue;        }        key = key.substr(4);        size_t found = key.find(':');        int vlan_id;        string vlan_alias, port_alias;        if (found != string::npos)        {            vlan_id = stoi(key.substr(0, found)); // FIXME: might raise exception            port_alias = key.substr(found+1);        }        else        {            SWSS_LOG_ERROR("Invalid key format. No member port is presented: %s",                           kfvKey(t).c_str());            it = consumer.m_toSync.erase(it);            continue;        }        //vlan名字        vlan_alias = VLAN_PREFIX + to_string(vlan_id);        string op = kfvOp(t);        assert(m_portList.find(vlan_alias) != m_portList.end());        Port vlan, port;        /* When VLAN member is to be created before VLAN is created */        if (!getPort(vlan_alias, vlan))        {            SWSS_LOG_INFO("Failed to locate VLAN %s", vlan_alias.c_str());            it++;            continue;        }        if (!getPort(port_alias, port))        {            SWSS_LOG_DEBUG("%s is not not yet created, delaying", port_alias.c_str());            it++;            continue;        }        if (op == SET_COMMAND)        {            string tagging_mode = "untagged";            for (auto i : kfvFieldsValues(t))            {                if (fvField(i) == "tagging_mode")                    tagging_mode = fvValue(i);            }            if (tagging_mode != "untagged" &&                tagging_mode != "tagged"   &&                tagging_mode != "priority_tagged")            {                SWSS_LOG_ERROR("Wrong tagging_mode '%s' for key: %s", tagging_mode.c_str(), kfvKey(t).c_str());                it = consumer.m_toSync.erase(it);                continue;            }            /* Duplicate entry */            if (vlan.m_members.find(port_alias) != vlan.m_members.end())            {                it = consumer.m_toSync.erase(it);                continue;            }            //加入vlan的端口将会创建一个桥接口,桥接口是一个二层口的抽象,屏蔽lag,phy等接口的差异            if (addBridgePort(port) && addVlanMember(vlan, port, tagging_mode))                it = consumer.m_toSync.erase(it);            else                it++;        }        else if (op == DEL_COMMAND)        {            if (vlan.m_members.find(port_alias) != vlan.m_members.end())            {                if (removeVlanMember(vlan, port))                {                    if (port.m_vlan_members.empty())                    {                        removeBridgePort(port);                    }                    it = consumer.m_toSync.erase(it);                }                else                {                    it++;                }            }            else                /* Cannot locate the VLAN */                it = consumer.m_toSync.erase(it);        }        else        {            SWSS_LOG_ERROR("Unknown operation type %s", op.c_str());            it = consumer.m_toSync.erase(it);        }    }}

配置host vlan步骤

sudo ip link add Bridge type bridge up #创建一个网桥,等价于sudo brctl addbr Bridgesudo bridge vlan add vid 6 dev Bridge self #给网桥创建一个vlan表项,vid为6  #创建一个vlan虚接口,其附着在Bridge接口上,设置其mac为00:00:00:00:00:01,这是一个vlan-if接口sudo ip link add link Bridge up name Vlan6 address 00:00:00:00:00:01 type vlan id 6 #将网卡添加到Bridge中,等价于命令sudo brctl addif Bridge Ethernet32sudo ip link set Ethernet32 master Bridge#配置网卡Ethernet32以tagged模式加入vlan6sudo bridge vlan add vid 6 dev Ethernet32 tagged

查看vlan配置

admin@switch:~$ sudo bridge vlanport    vlan idsdocker0 NoneBridge   6Ethernet32       6admin@switch:~$ 

从上面可以看出,有两个设备加入了vlan6:Bridge和Ethernet32

VLAN基本知识补充

TAG 字段由4个字节组成,前两个字节为TPID(TAG Protocol Identifier),其值固定为0x8100,表示是dot1q标准的TAG。后两个字节为TCI(TAG Control Information),它又分为三个部分,图示如下:

1)user_priority:用户优先级,占3个比特,可以表示8种不同的优先级,不同优先级的报文可以得到不同级别的服务;

2)CFI:规范格式指示符(Canonical Format Indicator),占一个比特,它通常设置为0,表示报文中的MAC地址格式为规范格式(遵循IEEE 802标准,即字节中的比特位顺序与标准一致),如果该位设置为1,则根据网络类型的不同具有不同的意义,具体可以参考dot1q标准文档。

3)VID:即VLAN的标志符,占12个比特,表示一个无符号整数,范围从0~4095,其中0表示报文没有指定的VID(priority tagged),4095按规定是保留的,不能使用,交换机收到这样的报文不会进行转发,因此可用的VID范围为1~4094,这其中VID 1又规定为默认VLAN,在用户没有配置的情况下,设备的出厂配置是所有端口属于VLAN 1,端口的默认VID也是VLAN 1。vid为0的帧(即priority tagged帧)用于指定优先级,但不指定vid,当接口收到这种报文后,会将vid设置为pvid

4)pvid 不是加在帧头的标记,而是端口的属性。
5)pvid 是 用来标识端口接收到的未标记的帧。
也就是说,当端口收到一个未标记的帧时,就把该帧打上vlan id,这个id值等于pvid的值,然后转发到VID和PVID相等的VLAN 中。
6)帧从端口出去时,如果帧头中的VID和端口的PVID值相同,就把这个标识去掉,再送出去。
7)如果端口为access类型,并加入了一个vlan ,那么这端口的PVID属性值变成和VID值一样,如果不一样就会把帧送到错误的vlan中,导致通行中断。但在trunk型的端口里,pvid的值是默认的1,注意交换机默认的vid也是1,所以交换机vlan id 为1的端口向trunk端口转发数据包时,trunk端口的把帧头的标识去掉再发出去,到了对方交换机trunk口时,由于帧没有带标识,所以会加上个vid,其值为1(因为trunk端口默认的pvid为1),这样数据包就 会被正确的送到vlan id为1的端口中。
所以没有划分vlan的端口发出的数据包也能通过trunk端口发出去,可以正常通行。而不是不能通过trunk口。

8 )Hybrid的输入输出端口对数据包的处理:

接收端口:同时都能够接收VID=PVID和VID=PVID的taggedframe,不改变TAG;对于untagedframe则加上端口的PVID和 defaultpriority再进行交换转发,对于priority only taggedframe则添加PVID再进行转发。接收方向与trunk口行为一致。

发送端口: 1、判断该VLAN在本端口的属性
2、如果是untag则剥离VLAN信息,再发送,如果是tag则比较端口的PVID和将要发送报文的VLAN信息,如果两 者相等则剥离VLAN信息,再发送,否则报文将携带原有的VLAN标记进行转发。

Hybrid接口可以配置多个untagged vlan,这是与trunk端口最大的不同。