乐趣区

SONIC-VLAN配置流程

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 Bridge
sudo 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 Ethernet32
sudo ip link set Ethernet32 master Bridge
#配置网卡 Ethernet32 以 tagged 模式加入 vlan6
sudo bridge vlan add vid 6 dev Ethernet32 tagged

查看 vlan 配置

admin@switch:~$ sudo bridge vlan
port    vlan ids
docker0 None
Bridge   6

Ethernet32       6

admin@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 端口最大的不同。

退出移动版