51学通信论坛2017新版

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 1270|回复: 0
打印 上一主题 下一主题

Open vSwitch源码解析之基于VxLAN实现NSH解析功能

[复制链接]

 成长值: 15613

  • TA的每日心情
    开心
    2022-7-17 17:50
  • 2444

    主题

    2544

    帖子

    7万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    74104
    跳转到指定楼层
    楼主
    发表于 2017-9-17 14:52:40 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    有关SFC的某些基本概率就不在这里做过多阐述,相关内容可以参看其他小伙伴写的文章.SDNLAB技术分享(一):ODL的SFC入门和Demo。
    本文所有源码不是OVS主线版本,目前OVS主线版本还没有实现NSH解析功能。该版本的OVS为私有版本,github网址见下方:https://github.com/tfherbert/ovs_Pritesh_V8/tree/nsh-v8
    </p>1. NSH-SFC概述

    当前SFC的实现方案主要分为两种:一种基于NSH(network service header)。数据封装时,在L2或者L3数据后添加NSH头,然后进行L3或L4的封装。转发时,根据nshheader去转发SFC中的数据,整个过程都是依据同一个SPI(service path id)和递减的SI(service index);另一种无NSH头,在转发过程中,SFF需要不停对新来的数据包进行判断,确定是否属于某个SFC。ODL的子项目SFC就是第一种的实现。

    1.1 NSH-SFC组件简述

    Classifier:分类器,根据controller来的policy来决策是否给数据进行nsh封装,并且把数据发送至相应的sff。 Service Function(SF):网络中的Middleware,常见的有loadbalance,firework,NAT等。值得注意的是,在NSH方案下,如果SF没有NSH解析功能,那么需要在SF和其对应的SFF之间增加一个proxy。该proxy用于解析NSH,并且帮助完成nshheader中SI的自减算法(如果SI不能成功自减,那么SFF会认为这个数据没有被forward到该SF,从而再次forward向该SF,最后的结果就是产生网络风暴)。
    Service Function Forwarder(SFF):SFC中的数据中转站,负责数据的转发。当SI=1的时候,SFF负责把nsh头部去掉。

    1.2 NSH-SFC(with VxLAN)工作流程


      控制层面定义SFC:用户利用SDN提供的北向应用,创建sfc1,controller会做相应的配置工作,产生对应的数据通道(sfc1本身是一个chain,可以和vxlan的tunnel完美匹配。那么,定义了sfc1,同时可以对应一个vxlan。所以,可以理解为sfc1同时绑定了vxlan和NSH中的path信息)。数据首先通过classifier,classifier为sfc1的数据打上nsh头信息,然后转交sff1。sff1将数据发送至sf1,sf1将数据处理完后,为nshheader中的si执行自减操作,然后返回到sff1.sff1将数据交到sff2,sff2重复sff的职责...对于没有nsh解析能力的sf2,proxy会帮其完成nsh相关工作.在si=0(有文档说si=1)的时候,sff会给数据包去nsh,那么整个sfc1流程就结束


    1.3 VxLAN存在的意义

    首先,NSH不一定需要依托于VxLAN,GRE + NSH、VXLAN-gpe + NSH、Ethernet + NSH都是可行的方案。其实,NSH只是一个封装技术,起了一个标签的作用,本身并不会影响传输。 在SFC中,很重要的一点是SFF能够尽快、准确地把带NSH头的数据包转发出去。那么,SFF需要做的工作实际根据nshheader去择路,而SFF中的路是controller通过流表已经创建好了的。所以,进一步说,SFF只需要把NSH头和某个流表统一起来即可。
    Shell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36

    struct sw_flow {
    struct rcu_head rcu;
    struct hlist_node hash_node[2];
    u32 hash;
    int stats_last_writer; /* NUMA-node id of the last writer on
    * 'stats[0]'.
    */
    struct sw_flow_key key; //流表匹配时候用到的key值
    struct sw_flow_key unmasked_key;
    struct sw_flow_mask *mask;
    struct sw_flow_actions __rcu *sf_acts;
    struct flow_stats __rcu *stats; /* One for each NUMA node. First one
    * is allocated at flow creation time,
    * the rest are allocated on demand
    * while holding the 'stats[0].lock'.
    */
    };
    struct sw_flow_key {
    u8 tun_opts[255];
    u8 tun_opts_len;
    struct ovs_key_ipv4_tunnel tun_key; //该流表对应的tunnel信息
    .......
    }
    struct ovs_key_ipv4_tunnel {
    __be64 tun_id;
    __be32 nsp; /* it contains (nsp - 24 bits | nsi - 8 bits) here */
    __be32 nshc1; /* NSH context headers */
    __be32 nshc2;
    __be32 nshc3;
    __be32 nshc4;
    __be32 ipv4_src;
    __be32 ipv4_dst;
    __be16 tun_flags;
    u8 ipv4_tos;
    u8 ipv4_ttl;
    } __packed __aligned(4); /* Minimize padding. */

    每个openflow流表去匹配的时候,sw_key结构体是匹配的关键,该结构体包含了隧道信息。而nshheader本身的SPI就可以看成一种隧道信息,那么用vxlan便很轻易的统一起来了。
    综合起来,VxLAN的tunnel信息和openflow流表就可以统一起来了。


    上图其实也是sff去匹配流表的过程。

    2. OVS源码解读。

    首先是NSH和VxLAN的header结构体。
    2.1NSHheader和VxLANheader结构体
    NSH
    Shell
    struct nshhdr {
    struct nsh_base b;
    struct nsh_ctx c;
    };
    很清楚的表明:包含base和ctx部分,其中base尤为重要,包含了路径信息;ctx用于sf之间传递metadata。
    Shell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    struct nsh_base {
    #if defined(__LITTLE_ENDIAN_BITFIELD) //小端序
    __u8 res1:4;
    __u8 c:1;
    __u8 o:1;
    __u8 ver:2;
    __u8 len:6;
    __u8 res2:2;
    #elif defined(__BIG_ENDIAN_BITFIELD) //大端序
    __u8 ver:2;
    __u8 o:1;
    __u8 c:1;
    __u8 res1:4;
    __u8 res2:2;
    __u8 len:6;
    #else
    #error "Bitfield Endianess not defined."
    #endif
    __u8 mdtype; //mdtype决定了ctx的格式
    __u8 proto; //next protocol
    union {
    struct {
    __u8 svc_path[3]; //spi
    __u8 svc_idx; //si
    };
    __be32 b2;
    };
    };

    base的结构体
    Shell
    struct nsh_ctx {
    __be32 c1;
    __be32 c2;
    __be32 c3;
    __be32 c4;
    };
    ctx结构体


    协议图
    VxLANheader
    Shell
    struct vxlanhdr {
    __be32 vx_flags;
    __be32 vx_vni;
    };
    vni是重点
    Shell
    #define VXLAN_HLEN (sizeof(struct udphdr) + sizeof(struct vxlanhdr))
    #define NSH_HLEN (sizeof(struct udphdr) + \
    sizeof(struct vxlanhdr) + \
    sizeof(struct nshhdr))
    这部分代码说明了数据包封装过后的格式,如下图(请无视gpe)

    2.2 VxLAN的创建和数据的监听和处理

    首先介绍下linux内核中sock和socket还有skb。
    socket和sock的区别和联系:大家都是socket通信中的socket,不过应用的网络层次有区别。socket主要作用于Linux系统中的BSD层(L6层),INET层很少应用, BSD和INET层分别对应ISO中的表示层和会话层,其结构体相对sock而言比较简单;而sock贯穿了硬件层和设备接口层和IP层INET层,贯穿了L2-L5层,而且是各层之间相互联系的重要纽带。
    skb:sk_buff 是网络数据报在内核中的表现形式,数据包在内核协议栈中是通过skb的数据结构来实现的。
    Shell
    union {
    struct tcphdr *th; //传输层tcp,指向首部第一个字节位置
    struct ethhdr *eth; //链路层上,指向以太网首部第一个字节位置
    struct iphdr *iph; //网络层上,指向ip首部第一个字节位置
    struct udphdr *uh; //传输层udp协议,
    unsigned char *raw; //随层次变化而变化,链路层=eth,网络层=iph
    unsigned long seq; //针对tcp协议的待发送数据包而言,表示该数据包的ACK值
    } h;
    这部分代码是sk_buffer结构体中的联合体,很明显可以看出来skb贯穿了协议栈的各层。
    下面是VxLAN的创建过程: 1、vport.c定义了实现了vport最基本的或者共有的一些功能, 如添加、删除等功能,还有数据的收发功能。
    2、vport-XXX.c是vport基于其他的一些个性化实现,如vport-vxlan.c实现了一些vxlan的功能:tunnel的创建、删除等。

    2.2.1 OVS相关socket结构体

    Shell
    /* per UDP socket information */
    struct vxlan_sock {
    struct hlist_node hlist;
    vxlan_rcv_t *rcv; //数据处理函数,在收到数据后回调该函数
    void *data;
    struct work_struct del_work; //调用linux内核工作队列需要用到的结构体, 该结构体定义了新启线程需要完成的工作的抽象
    struct socket *sock;
    struct rcu_head rcu; //linux线程锁
    };
    上面是VxLAN对应的socket,注意其中的struct socket *sock; 他名字叫做sock,其实是socket结构体的实例。
    接着看struct socket *sock对应的结构体(linux内核中的函数)
    Shell
    struct socket
    {
    socket_state state;
    unsigned long flags;
    struct proto_ops *ops;
    struct inode *inode;
    struct fasync_struct *fasync_list; /* Asynchronous wake up list */
    struct file *file; /* File back pointer for gc */
    struct sock *sk; //指向sock
    wait_queue_head_t wait;
    short type;
    unsigned char passcred;
    };
    注意struct sock *sk; 指向了sock。sock的结构体就不贴了,但是要注意的是sock中也有指向socket的指针,还有指向数据处理函数的指针。
    综合起来,VxLAN中的几个socket联系如下:



    2.2.2 VxLAN的创建过程,packet和流表的匹配

    在用ovs-vsctl工具下命令创建VxLAN的时候,会去调用vport-vxlan.c中的ovs_vxlan_vport_ops.create命令,对应函数如下:
    Shell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45

    static struct vport *vxlan_tnl_create(const struct vport_parms *parms)
    {
    struct net *net = ovs_dp_get_net(parms->dp);
    struct nlattr *options = parms->options;//vxlan
    struct vxlan_port *vxlan_port;
    struct vxlan_sock *vs;
    struct vport *vport;
    struct nlattr *a;
    u16 dst_port;
    int err;
    if (!options) { //option数据不能为空
    err = -EINVAL;
    goto error;
    }
    a = nla_find_nested(options, OVS_TUNNEL_ATTR_DST_PORT); //从option中提取dst port
    if (a && nla_len(a) == sizeof(u16)) {
    dst_port = nla_get_u16(a);
    } else {
    /* Require destination port from userspace. */
    err = -EINVAL;
    goto error;
    }
    vport = ovs_vport_alloc(sizeof(struct vxlan_port), //初始化vport,&ovs_vxlan_vport_ops是 vport相关的操作,此处为默认的几种,可以自己添加
    &ovs_vxlan_vport_ops, parms); /*struct vport *ovs_vport_alloc(int priv_size, const struct vport_ops *ops,
    const struct vport_parms *parms)*/
    if (IS_ERR(vport)) //初始化是否成功
    return vport;
    vxlan_port = vxlan_vport(vport); //为vxlan_vport分配私有数据区
    strncpy(vxlan_port->name, parms->name, IFNAMSIZ);
    vs = vxlan_sock_add(net, htons(dst_port), vxlan_rcv, vport, true, false); //为vport创建 vxlan_sock
    if (IS_ERR(vs)) { //判断是否创建成功
    ovs_vport_free(vport);
    return (void *)vs;
    }
    vxlan_port->vs = vs;
    return vport;
    error:
    return ERR_PTR(err);
    }

    这个函数中比较重要的是vxlan_port = vxlan_vport(vport)和vxlan_sock_add(net, htons(dst_port), vxlan_rcv, vport, true, false);
      vxlan_port = vxlan_vport(vport)为该VxLAN的port申请私有数据区,该数据区可以存放任何数据。这也为逻辑端口vport和物理端口net_device提供绑定的空间。事实上,vport和net-device之间还隔着一个结构体netdev_vport,该数据区就可以用在存放vport对应的netdev_port。vs = vxlan_sock_add(net, htons(dst_port), vxlan_rcv, vport, true, false)把vxlan_rcv数据处理函数注册到了vs中。

    为了思路的连续性,我们先接着看主线vxlan_sock_add,最后回头再看数据处理部分。
    Shell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61

    struct vxlan_sock *vxlan_sock_add(struct net *net, __be16 port,
    vxlan_rcv_t *rcv, void *data,
    bool no_share, bool ipv6)
    {
    return vxlan_socket_create(net, port, rcv, data);
    }
    static struct vxlan_sock *vxlan_socket_create(struct net *net, __be16 port,
    vxlan_rcv_t *rcv, void *data)
    {
    struct vxlan_sock *vs; //最后返回的vs
    struct sock *sk;
    struct sockaddr_in vxlan_addr = {
    .sin_family = AF_INET,
    .sin_addr.s_addr = htonl(INADDR_ANY),
    .sin_port = port,
    };
    int rc;
    vs = kmalloc(sizeof(*vs), GFP_KERNEL); //为vs分配内存
    if (!vs) {
    pr_debug("memory alocation failure\n");
    return ERR_PTR(-ENOMEM);
    }
    INIT_WORK(&vs->del_work, vxlan_del_work); //调用linux内核中工作队列,去执行del工作
    /* Create UDP socket for encapsulation receive. */
    rc = sock_create_kern(AF_INET, SOCK_DGRAM, IPPROTO_UDP, &vs->sock);//创建用于接收udp包的socket
    if (rc < 0) {
    pr_debug("UDP socket create failed\n");
    kfree(vs);
    return ERR_PTR(rc);
    }
    /* Put in proper namespace */
    sk = vs->sock->sk;
    sk_change_net(sk, net);
    rc = kernel_bind(vs->sock, (struct sockaddr *) &vxlan_addr, //将刚刚创建的socket和正在创建的vxlan绑定 , 其中vs->sk指向该socket kernel_bind为Linux内核里面的绑定函数
    sizeof(vxlan_addr));
    if (rc < 0) {
    pr_debug("bind for UDP socket %pI4:%u (%d)\n",
    &vxlan_addr.sin_addr, ntohs(vxlan_addr.sin_port), rc);
    sk_release_kernel(sk);
    kfree(vs);
    return ERR_PTR(rc);
    }
    vs->rcv = rcv;
    vs->data = data;
    /* Disable multicast loopback */
    inet_sk(sk)->mc_loop = 0;
    rcu_assign_sk_user_data(vs->sock->sk, vs);
    /* Mark socket as an encapsulation socket. */
    udp_sk(sk)->encap_type = 1; //将sk转换成udp内部使用的sk类型,
    udp_sk(sk)->encap_rcv = vxlan_udp_encap_recv; //绑定vxlan收到udp包后的处理函数
    udp_encap_enable;
    return vs;
    }

      vs->rcv = rcv;sk = vs->sock->sk;udp_sk(sk)->encap_rcv = vxlan_udp_encap_recv;

    主线到这里就算结束了,最后的结果是创建了一个vxlan_sock和一个socket和一个sock,中间的socket只是一个索引,vxlan_sock->socket->sock。其中vxlan_sock和sock注册了各自的数据处理函数。 一个很重要的点是:在linux内核中,无论收发数据,都会被缓存到sock 结构中的缓冲队列中。
    整理下主线结构:
    创建vxlan的流程
    ovs_vport_add->vport_ops_list.create->ovs_vxlan_vport_ops.create->
    vxlan_tnl_create->vxlan_sock_add->vxlan_socket_create.
    Shell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66

    /* Callback from net/ipv4/udp.c to receive packets */
    static int vxlan_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
    {
    struct vxlan_sock *vs;
    struct vxlanhdr *vxh;
    struct udphdr *udp;
    bool isnsh = false;
    __be32 nsp = 0;
    __be32 c1 = 0;
    __be32 c2 = 0;
    __be32 c3 = 0;
    __be32 c4 = 0;
    udp = (struct udphdr *)udp_hdr(skb);
    if (udp->dest == htons(NSH_DST_PORT)) //根据udp目的端口(6633)来判断是否含有nsh
    isnsh = true; //htons HBO to NBO
    /* Need Vxlan and inner Ethernet header to be present */
    if (!pskb_may_pull(skb, isnsh ? NSH_HLEN : VXLAN_HLEN)) // 检查skb长度是否能够满足把后面的头部去掉 如果不够返回-1 报错
    goto error;
    /* Return packets with reserved bits set */
    vxh = vxlan_hdr(skb); //提取vxlanheader
    if (vxh->vx_flags != htonl(VXLAN_FLAGS) ||
    (vxh->vx_vni & htonl(0xff))) {
    pr_warn("invalid vxlan flags=%#x vni=%#x\n",
    ntohl(vxh->vx_flags), ntohl(vxh->vx_vni));
    goto error;
    }
    if (isnsh) {
    struct nshhdr *nsh = nsh_hdr(skb);
    if (unlikely(nsh->b.svc_idx == 0 || nsh->b.ver || //如果 si == 0 sfc结束,直接drop
    nsh->b.len != 6 || nsh->b.mdtype != 0x01 ||
    nsh->b.proto != NSH_P_ETHERNET)) {
    pr_warn("NSH service index reached zero or not supported\n");
    goto drop;
    }
    nsp = nsh->b.b2; /* same as svc_path | htonl(svc_idx) */
    c1 = nsh->c.c1; /* NSH Contexts */
    c2 = nsh->c.c2;
    c3 = nsh->c.c3;
    c4 = nsh->c.c4;
    }
    if (iptunnel_pull_header(skb, isnsh ? NSH_HLEN : VXLAN_HLEN,
    htons(ETH_P_TEB)))
    goto drop;
    vs = rcu_dereference_sk_user_data(sk);
    if (!vs)
    goto drop;
    vs->rcv(vs, skb, vxh->vx_vni, nsp, c1, c2, c3, c3); //调用vs->rcv,即
    return 0;
    drop:
    /* Consume bad packet */
    kfree_skb(skb);
    return 0;
    error:
    /* Return non vxlan pkt */
    return 1;
    }

    上面这个为sock注册过的处理函数,主要工作是一层层的剥开skb的封装(当然,里面会判断是否含有nshheader),提取udp,vxlan 和nshheader的头部信息。
      sock在之前被强制转换成了udp的socket,该函数由linux内核中net/ipv4/udp.c回调.if (udp->dest == htons(NSH_DST_PORT)) nsh的判断依据si=0 的时候,sfc结束,drop packet核心调用 vs->rcv(vs, skb, vxh->vx_vni, nsp, c1, c2, c3, c3);

    Shell
    static void vxlan_rcv(struct vxlan_sock *vs, struct sk_buff *skb,
    __be32 vx_vni, __be32 nsp, __be32 nshc1, __be32 nshc2,
    __be32 nshc3, __be32 nshc4)
    {
    struct ovs_tunnel_info tun_info;
    struct vport *vport = vs->data;
    struct iphdr *iph;
    __be64 key;
    /* Save outer tunnel values */
    iph = ip_hdr(skb);
    key = cpu_to_be64(ntohl(vx_vni) >> 8);
    ovs_flow_tun_info_init(&tun_info, iph, key, nsp, nshc1, nshc2,
    nshc3, nshc4, TUNNEL_KEY | TUNNEL_NSP | TUNNEL_NSI | TUNNEL_NSHC,
    NULL, 0);
    ovs_vport_receive(vport, skb, &tun_info); //回到vport.c中处理
    }0
    该函数最重要的一句无疑是ovs_vport_receive(vport, skb, &tun_info).
    之前的工作实际是进一步整合传入的header信息,用ovs_flow_tun_info_init去初始化tun_info,然后调用vport中数据处理函数ovs_vport_receive进一步处理。
    事实上,不论vxlan还是gre亦或lisp网络,最后的数据处理都会回到ovs_vport_receive,然后在ovs_vport_receive去调用ovs_dp_process_received_packet,回到datapath.c进行统一处理;在ovs_dp_process_received_packet进行流表查询,然后去查表执行对应的action。
    在查询流表失败的时候,由ovs_dp_upcall发送upcall到用户空间(ovs-vswitchd),此后处理过程交给ovsd 处理。具体的流表匹配就不展开了。


    以上数据监听到处理的流程解读实际上说明了OVS已经把VxLAN的tunnel信息和NSH信息整合起来了,流表和数据的对应关系如上图。

    2.3 OVS作为classifier对数据进行的NSH封装

    OVS作为classifier最重要的功能就是对数据的封装和发送。
    Shell

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72

    int vxlan_xmit_skb(struct vxlan_sock *vs,
    struct rtable *rt, struct sk_buff *skb,
    __be32 src, __be32 dst, __u8 tos, __u8 ttl, __be16 df,
    __be16 src_port, __be16 dst_port, __be32 vni, __be32 nsp,
    __be32 nshc1, __be32 nshc2, __be32 nshc3, __be32 nshc4)
    {
    bool isnsh = (dst_port == htons(NSH_DST_PORT));
    struct vxlanhdr *vxh;
    struct udphdr *uh;
    int min_headroom;
    int err;
    min_headroom = LL_RESERVED_SPACE(rt_dst(rt).dev) + rt_dst(rt).header_len
    + (isnsh ? NSH_HLEN : VXLAN_HLEN) + sizeof(struct iphdr)
    + (vlan_tx_tag_present(skb) ? VLAN_HLEN : 0);//最小长度
    /* Need space for new headers (invalidates iph ptr) */
    err = skb_cow_head(skb, min_headroom); //验证skb长度是否足够
    if (unlikely(err))
    return err;
    if (vlan_tx_tag_present(skb)) {
    if (unlikely(!__vlan_put_tag(skb,
    skb->vlan_proto,
    vlan_tx_tag_get(skb))))
    return -ENOMEM;
    vlan_set_tci(skb, 0);
    }
    skb_reset_inner_headers(skb);
    if (isnsh) {
    struct nshhdr *nsh;
    uint8_t nsi = ntohl(nsp) & NSH_M_NSI;
    nsh = (struct nshhdr *) __skb_push(skb, sizeof(*nsh));//把nsh头push到skb中 nshheader封装
    memset(&nsh->b, 0, sizeof nsh->b); //初始化nsh_b
    nsh->b.len = 6;
    nsh->b.mdtype = NSH_M_TYPE1;
    nsh->b.proto = NSH_P_ETHERNET;
    /* b2 should precede svc_idx, else svc_idx will be zero */
    nsh->b.b2 = nsp & htonl(NSH_M_NSP);
    nsh->b.svc_idx = nsi ? nsi : 0x01;
    nsh->c.c1 = nshc1;
    nsh->c.c2 = nshc2;
    nsh->c.c3 = nshc3;
    nsh->c.c4 = nshc4;
    }
    vxh = (struct vxlanhdr *) __skb_push(skb, sizeof(*vxh)); //vxlanheader封装
    vxh->vx_flags = htonl(VXLAN_FLAGS);
    vxh->vx_vni = vni;
    __skb_push(skb, sizeof(*uh)); //udp header封装
    skb_reset_transport_header(skb);
    uh = udp_hdr(skb);
    uh->dest = dst_port;
    uh->source = src_port;
    uh->len = htons(skb->len);
    uh->check = 0;
    vxlan_set_owner(vs->sock->sk, skb); //把skb 绑定的到某个vxlan的sk, 实际是为vxlan设置skb
    err = handle_offloads(skb);
    if (err)
    return err;
    return iptunnel_xmit(vs->sock->sk, rt, skb, src, dst, IPPROTO_UDP, //iptunnel封装
    tos, ttl, df, false);
    }

    linux内核中vxlan_xmit_skb函数的功能是对skb进行vxlan封装,在OVS中对该函数进行了重写,增加了NSH封装功能。 该函数在vxlan的send操作被调用,即在classifier发数据前进行了封装。另外,该版本OVS添加了NSH对应的action,主要是一些attribute动作,用于添加NSH信息。

    3. 总结

      OVS在SFC-NSH中,可以是Classifier,SFF和SF对应的proxy.该版本OVS添加了NSH对应的action,主要是attribute,用于添加NSH,这部分功能主要用于classifier去打NSHheader。NSH只是一个封装技术,便于SFC中的数据传输。

    作者简介:张萌 2014.9-今在北邮计算机学院网络技术中心学习
    --------------华丽的分割线------------------
    本文系《SDNLAB原创文章奖励计划》投稿文章,该计划旨在鼓励广大从业人员在SDN/NFV/Cloud网络领域创新技术、开源项目、产业动态等方面进行经验和成果的文字传播、分享、交流。有意向投稿的同学请通过官方唯一指定投稿通道进行文章投递,投稿细则请参考《SDNLAB原创文章奖励计划》
    声明:本文转载自网络。版权归原作者所有,如有侵权请联系删除。
    扫描并关注51学通信微信公众号,获取更多精彩通信课程分享。

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    Archiver|手机版|小黑屋|51学通信技术论坛

    GMT+8, 2025-1-31 20:05 , Processed in 0.091162 second(s), 33 queries .

    Powered by Discuz! X3

    © 2001-2013 Comsenz Inc.

    快速回复 返回顶部 返回列表