Linux网络协议栈5--ovs收发包

ovs,全名openvswitch,是一个高质量的、多层虚拟交换机,相对于bridge的一些优势:

1)方便网络管理与监控。OVS 的引入,可以方便管理员对整套云环境中的网络状态和数据流量进行监控,比如可以分析网络中流淌的数据包是来自哪个 VM、哪个 OS 及哪个用户,这些都可以借助 OVS 提供的工具来达到。
2)加速数据包的寻路与转发。相比 Bridge 单纯的基于 MAC 地址学习的转发规则,OVS 引入流缓存的机制,可以加快数据包的转发效率。
3)基于 SDN 控制面与数据面分离的思想。上面两点其实都跟这一点有关,OVS 控制面负责流表的学习与下发,具体的转发动作则有数据面来完成。可扩展性强。
4)隧道协议支持。Bridge 只支持 VxLAN,OVS 支持 gre/vxlan/IPsec 等。
5)适用于 Xen、KVM、VirtualBox、VMware 等多种 Hypervisors。

不过这些年,openflow明显热度降低,SDN网络可以有很多实现方式,如segment routing技术,结合传统的mpls/bgp,也能很好的实现SDN,相比于基于openflow协议的SDN网络,设备厂商支持的更好,更稳定。

同bridge一样,在向ovs bridge中添加成员接口的时候,会在成员接口的dev->rx_handler 上挂载收包处理函数,如下,ovs_vport_add 中会根据加入接口类型的不同,调用接口对应的create函数,而所有create函数都会调用ovs_netdev_link,其中注册了netdev_frame_hook 作为ovs成员口的收包处理函数。
以vxlan_create为例,vxlan本身的创建和linux vxlan一样,核心函数也是vxlan_dev_configure,其次是创建ovs成员口的私有数据 vport,最后在ovs_netdev_link 函数中间vxlan和vport关联,以及挂载接口收报函数和私有数据(netdev_frame_hook,vport)

struct vport *ovs_vport_add(const struct vport_parms *parms)
{
    struct vport_ops *ops;
    struct vport *vport;

    ops = ovs_vport_lookup(parms);
    if (ops) {
        struct hlist_head *bucket;

        if (!try_module_get(ops->owner))
            return ERR_PTR(-EAFNOSUPPORT);

        vport = ops->create(parms);
        ......
}

struct vport *ovs_netdev_link(struct vport *vport, const char *name)
{
    ......
    err = netdev_rx_handler_register(vport->dev, netdev_frame_hook,
                     vport);
    ......
}
EXPORT_SYMBOL_GPL(ovs_netdev_link);
image.png

网卡收到包后,走到__netif_receive_skb_core后,剥完vlan找到vlan子接口(如果有的话),如果skb->dev是ovs成员口,就会走到netdev_frame_hook处理函数。

static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
......
    // ovs挂载的 netdev_frame_hook 函数。
    rx_handler = rcu_dereference(skb->dev->rx_handler);
    if (rx_handler) {
        if (pt_prev) {
            ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = NULL;
        }
        switch (rx_handler(&skb)) {
        case RX_HANDLER_CONSUMED:  // 报文已经被消费,结束处理
            ret = NET_RX_SUCCESS;
            goto out;
        case RX_HANDLER_ANOTHER:  // skb->dev 被修改,重新走一次
            goto another_round;
        case RX_HANDLER_EXACT: /* 精确传递到ptype->dev == skb->dev */
            deliver_exact = true;
        case RX_HANDLER_PASS:
            break;
        default:
            BUG();
        }
    }

    ......
}

struct vport 是ovs成员端口的核心数据结构,它挂载在net_device 的rx_handler_data 上成员上,ovs模块中用的更多的是这个结构,但对外部模块不可见(私有数据结构)。

struct vport {
    struct net_device *dev;        
    struct datapath *dp;                // 对应一个bridge,ovs中可以添加多个bridge。
    struct vport_portids __rcu *upcall_portids;
    u16 port_no;

    struct hlist_node hash_node;
    struct hlist_node dp_hash_node;
    const struct vport_ops *ops;         // 对应不同接口类型的操作处理函数。

    struct list_head detach_list;
    struct rcu_head rcu;
};

netdev_frame_hook --> netdev_port_receive -->ovs_vport_receive-->ovs_dp_process_packet 流程。在进入 ovs_dp_process_packet 之前,从tun_info和 skb,提取了流的key信息,包含 tunnel、二层、三层、四层的报文头信息,为ovs dp匹配流表提供依据。

static void netdev_port_receive(struct sk_buff *skb)
{
    struct vport *vport;

    vport = ovs_netdev_get_vport(skb->dev);
    if (unlikely(!vport))
        goto error;

    if (unlikely(skb_warn_if_lro(skb)))
        goto error;

    skb = skb_share_check(skb, GFP_ATOMIC);
    if (unlikely(!skb))
        return;
    // ovs 是 switch,所以智能加二层口,向一些三层的tunnel口是无法加入的
    skb_push(skb, ETH_HLEN);
    skb_postpush_rcsum(skb, skb->data, ETH_HLEN);
    // 一些tunnel口,如vxlan、gre,会在skb的dst_entry中缓存tunnel key
    ovs_vport_receive(vport, skb, skb_tunnel_info(skb));
    return;
error:
    kfree_skb(skb);
}

int ovs_vport_receive(struct vport *vport, struct sk_buff *skb,
              const struct ip_tunnel_info *tun_info)
{
    struct sw_flow_key key;
    int error;

    OVS_CB(skb)->input_vport = vport;
    OVS_CB(skb)->mru = 0;
    OVS_CB(skb)->cutlen = 0;
    if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
        u32 mark;

        mark = skb->mark;
        skb_scrub_packet(skb, true);
        skb->mark = mark;
        tun_info = NULL;
    }

    /* Extract flow from 'skb' into 'key'. */
    // 这里从tun_info和 skb,提取了流的key信息,sw_flow_key 包含 tunnel、二层、三层、四层的报文头信息,为ovs dp匹配流表提供依据。
    error = ovs_flow_key_extract(tun_info, skb, &key);
    if (unlikely(error)) {
        kfree_skb(skb);
        return error;
    }
    ovs_dp_process_packet(skb, &key);
    return 0;
}

ovs_dp_process_packet 函数会根据报文key match流表:
1、如果没找到,走upcall处理,调用 queue_userspace_packet 将报文各层协议头 (OVS_PACKET_ATTR_KEY )、skb本身数据(OVS_PACKET_ATTR_PACKET)信息上送用户态(upcall.cmd=OVS_PACKET_CMD_MISS),用户态会有线程监听消息,在udpif_start_threads中创建了处理upcall的线程,处理handler = udpif_upcall_handler,udpif_upcall_handler通过fd poll的方式等待触发,如果有upcall上送,则进入recv_upcalls的处理函数中
1)从Device接收Packet交给事先注册的event handler进行处理;
2)接收Packet后识别是否是unknown packet,是则交由upcall处理;
3)vswitchd对unknown packet找到flow rule进行处理,其中最重要的调用是通过rule_dpif_lookup_from_table查找到匹配的流表规则,进而生成actions
rule_dpif_lookup_from_table又会通过流表的级联一个个顺序查找,每单个流表都会调用rule_dpif_lookup_in_table;
4)经过process_upcall流程后,已经为upcall的流生成对应的缓存流表信息了,缓存流表的key信息以及actions动作保存在struct upcall里,接下去就是要将缓存流表put到datapath了,通知内核态ovs处理put(对应OVS_FLOW_CMD_NEW)及execute(OVS_PACKET_CMD_EXECUTE)流程;
https://www.codenong.com/cs109398201/

2、存在匹配的流表,根据流表的action,执行相应的操作。action 类型很多,但最终一般需要是output action,从一个接口发送出去。

void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
{
    const struct vport *p = OVS_CB(skb)->input_vport;
    struct datapath *dp = p->dp;
    struct sw_flow *flow;
    struct sw_flow_actions *sf_acts;
    struct dp_stats_percpu *stats;
    u64 *stats_counter;
    u32 n_mask_hit;

    stats = this_cpu_ptr(dp->stats_percpu);

    /* Look up flow. */
    // 流表查询,根据前面从报文提取的信息
    flow = ovs_flow_tbl_lookup_stats(&dp->table, key, &n_mask_hit);
    if (unlikely(!flow)) {
        struct dp_upcall_info upcall;
        int error;
        // 未查找到流表,做upcall 处理
        memset(&upcall, 0, sizeof(upcall));
        upcall.cmd = OVS_PACKET_CMD_MISS;
        upcall.portid = ovs_vport_find_upcall_portid(p, skb);
        upcall.mru = OVS_CB(skb)->mru;
        error = ovs_dp_upcall(dp, skb, key, &upcall, 0);
        if (unlikely(error))
            kfree_skb(skb);
        else
            consume_skb(skb);
        stats_counter = &stats->n_missed;
        goto out;
    }
    // 查找到了流表,根据流表的action,执行相应的操作。
    ovs_flow_stats_update(flow, key->tp.flags, skb);
    sf_acts = rcu_dereference(flow->sf_acts);
    ovs_execute_actions(dp, skb, sf_acts, key);

    stats_counter = &stats->n_hit;

out:
    /* Update datapath statistics. */
    u64_stats_update_begin(&stats->syncp);
    (*stats_counter)++;
    stats->n_mask_hit += n_mask_hit;
    u64_stats_update_end(&stats->syncp);
}

int ovs_execute_actions(struct datapath *dp, struct sk_buff *skb,
            const struct sw_flow_actions *acts,
            struct sw_flow_key *key)
{
    int err, level;

    level = __this_cpu_inc_return(exec_actions_level);
    if (unlikely(level > OVS_RECURSION_LIMIT)) {
        net_crit_ratelimited("ovs: recursion limit reached on datapath %s, probable configuration error\n",
                     ovs_dp_name(dp));
        kfree_skb(skb);
        err = -ENETDOWN;
        goto out;
    }

    OVS_CB(skb)->acts_origlen = acts->orig_len;
    err = do_execute_actions(dp, skb, key,
                 acts->actions, acts->actions_len);

    if (level == 1)
        process_deferred_actions(dp);

out:
    __this_cpu_dec(exec_actions_level);
    return err;
}

// action 类型很多,但最终一般需要是output action,从一个接口发送出去
static int do_execute_actions(struct datapath *dp, struct sk_buff *skb,
                  struct sw_flow_key *key,
                  const struct nlattr *attr, int len)
{
    /* Every output action needs a separate clone of 'skb', but the common
     * case is just a single output action, so that doing a clone and
     * then freeing the original skbuff is wasteful.  So the following code
     * is slightly obscure just to avoid that.
     */
    int prev_port = -1;
    const struct nlattr *a;
    int rem;

    for (a = attr, rem = len; rem > 0;
         a = nla_next(a, &rem)) {
        int err = 0;

        if (unlikely(prev_port != -1)) {
            struct sk_buff *out_skb = skb_clone(skb, GFP_ATOMIC);

            if (out_skb)
                do_output(dp, out_skb, prev_port, key);

            OVS_CB(skb)->cutlen = 0;
            prev_port = -1;
        }

        switch (nla_type(a)) {
        case OVS_ACTION_ATTR_OUTPUT:
            prev_port = nla_get_u32(a);
            break;

        case OVS_ACTION_ATTR_TRUNC: {
            struct ovs_action_trunc *trunc = nla_data(a);

            if (skb->len > trunc->max_len)
                OVS_CB(skb)->cutlen = skb->len - trunc->max_len;
            break;
        }

        case OVS_ACTION_ATTR_USERSPACE:
            output_userspace(dp, skb, key, a, attr,
                             len, OVS_CB(skb)->cutlen);
            OVS_CB(skb)->cutlen = 0;
            break;

        case OVS_ACTION_ATTR_HASH:
            execute_hash(skb, key, a);
            break;

        case OVS_ACTION_ATTR_PUSH_MPLS:
            err = push_mpls(skb, key, nla_data(a));
            break;

        case OVS_ACTION_ATTR_POP_MPLS:
            err = pop_mpls(skb, key, nla_get_be16(a));
            break;

        case OVS_ACTION_ATTR_PUSH_VLAN:
            err = push_vlan(skb, key, nla_data(a));
            break;

        case OVS_ACTION_ATTR_POP_VLAN:
            err = pop_vlan(skb, key);
            break;

        case OVS_ACTION_ATTR_RECIRC:
            err = execute_recirc(dp, skb, key, a, rem);
            if (nla_is_last(a, rem)) {
                /* If this is the last action, the skb has
                 * been consumed or freed.
                 * Return immediately.
                 */
                return err;
            }
            break;

        case OVS_ACTION_ATTR_SET:
            err = execute_set_action(skb, key, nla_data(a));
            break;

        case OVS_ACTION_ATTR_SET_MASKED:
        case OVS_ACTION_ATTR_SET_TO_MASKED:
            err = execute_masked_set_action(skb, key, nla_data(a));
            break;

        case OVS_ACTION_ATTR_SAMPLE:
            err = sample(dp, skb, key, a, attr, len);
            break;

        case OVS_ACTION_ATTR_CT:
            if (!is_flow_key_valid(key)) {
                err = ovs_flow_key_update(skb, key);
                if (err)
                    return err;
            }

            err = ovs_ct_execute(ovs_dp_get_net(dp), skb, key,
                         nla_data(a));

            /* Hide stolen IP fragments from user space. */
            if (err)
                return err == -EINPROGRESS ? 0 : err;
            break;
        }

        if (unlikely(err)) {
            kfree_skb(skb);
            return err;
        }
    }

    if (prev_port != -1)
        do_output(dp, skb, prev_port, key);
    else
        consume_skb(skb);

    return 0;
}

接口的output操作,流程是 do_output -->ovs_vport_send --> vport 注册的send函数,都是直接调用的 dev_queue_xmit 函数,因为ovs进来的是二层包,流表只是对报头做了修改,再转发出去,不需要走协议栈。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342