精华内容
下载资源
问答
  • 移植性优良的匿名飞控上位机协议源码,支持V6版本上位机,稍加改动可支持其他任意版本。注释清楚,程序清晰易懂,包含使用例程。
  • 上层协议在计算checksum时早就不需要IP层字段作为伪头参与了,但是不管TCP,UDP还是ICMP都是古老的协议,它们设计时就如此,没有办法,即便是IPv6还是要支持! 如果在协议转换的集中化节点去重新计算上层协议的...

    在IPv6时代,是不是可以用本地链路质量信息编码源地址的主机标识符从而指导服务器端拥塞控制策略呢,是不是也可以把自己是谁编码进去呢?比如自己是Android,自己是一台PC,或者说自己是一双智能皮鞋?以此来指导数据发送端的定制化动作呢?

    IPv6的地址空间足够大,且留下了可达64位的主机标识符可供任意发挥,如此长度的主机标识符可以藏匿很多信息啊!

    可以先看一下我很久之前在2012年写的一篇文章:
    IPv6的NAT原理以及MAP66: https://blog.csdn.net/dog250/article/details/7799398
    很有意思。

    这种 利用IPv6地址空间远大于IPv4地址空间的特性,在IPv4报文转换为IPv6报文实现IPv4和IPv6之间互访的时候,通过解一个一元一次方程来保证协议checksum无需重新计算 的技术,其实还有很多玩法。

    关键不在于什么解一元一次方程,而是在于IPv6的地址空间比IPv4地址空间足够大,在IPv4地址嵌入到IPv6地址中后,剩余的空间仍然可以存储校验码矫正值。

    之前说过,IPv4和IPv6之间存在联通的必要,因为要平滑过渡就必然需要某种兼容,那么这种联通就可以分为两类:

    横向联通: IPv4海洋中,IPv6孤岛之间的互访,此时需要IPv4隧道,参见6to4以及ISATAP等。
    纵向联通: IPv4直接访问IPv6资源或者反过来。此时就需要协议转换,协议转换必然涉及到checksum的重新计算问题。
    以上纵向联通方面,有一个超猛支撑技术,那就是DNS64,但是这种DNS技术更加侧重于管理平面和配置技巧,不是我的菜,所以我也不想多聊,分享一篇文章:
    支持IPv6 DNS64/NAT64 网络<- 网络概述: https://www.jianshu.com/p/37b8c006cd2d

    为了防止链接失效,盗图一张,解释DNS64:


    逻辑是比较简单的,但细节却足够繁琐,超级烦人。

    本文不想谈DNS64,本文谈谈当IPv4报文转换为IPv6报文以及IPv6报文转换为IPv4报文时,上层协议checksum的计算问题。

    上层协议在计算checksum时早就不需要IP层字段作为伪头参与了,但是不管TCP,UDP还是ICMP都是古老的协议,它们设计时就如此,没有办法,即便是IPv6还是要支持!

    如果在协议转换的集中化节点去重新计算上层协议的checksum,那么资源的消耗将会是集中式的,为此,我们希望这些相关的计算尽量在边缘进行。此外,由于IPv6没有NAT或者至少说不提倡NAT,且地址足够长,没有哪台设备有足够的内存可以承受海量的连接状态跟踪的维护,所以需要某种stateless机制去维护conntrack!

    就像TCP的Syncookie一样,我们 可以把conntrack信息存放在IPv6报文本身,因为它的地址空间足够大!

    先看IPv4报文转换为IPv6报文时,如何保持上层checksum的不变性。

    按照RFC6052的规范:
    IPv6 Addressing of IPv4/IPv6 Translators: https://tools.ietf.org/html/rfc6052

    IPv4地址会嵌入到IPv6地址空间的低位,具体就是下面这个规则了(参见2.2节):


    注意后面的suffix,这些后缀空间是可以供我们自由发挥的。

    除却96位的prefix实在是没有空间,其它的情况,至少可以在suffix低位取2个字节来存放checksum矫正值,计算这个checksum矫正值的问题可以描述为:
    求解一个16bit的数字,请问它是多少时,当IPv4头按照RFC6052规范换成IPv6头时,TCP的checksum可以保持不变?

    这不就是一个一元一次方程嘛…

    给出一段代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    //以下2个函数就是计算校验码的,具体的原理请参见RFC1071/RFC1624/RFC1141
    static inline u_int16_t add16(
        u_int16_t a,
        u_int16_t b)
    {
        a += b;
        return a + (a < b);
    }

    static inline u_int16_t csum16(const u_int16_t *buf, int len)
    {
        u_int16_t csum = 0;
        while(len--) csum = add16(csum, *buf++);
        return csum;
    }

    unsigned char sip[4] = {192, 168, 1, 1};
    unsigned char dip[4] = {172, 16, 2, 2};

    // 转换后的源IPv6地址,即将源IPv4地址嵌入到IPv6地址后64位的高32位,sip6已经完成嵌入
    unsigned char sip6[16] = {0x20, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
                             192, 168, 1, 1, 0x00, 0x00, 0x00, 0x00};
    // 1. 转换后的目标IPv6地址,即将目标IPv4地址嵌入到IPv6地址后64位的高32位,sip6已经完成嵌入
    // 2. 目标IPv4地址2.2.2.2,是由DNS64机制通告给IPv4-only的客户端的,到达NAT64网关后,将其转换为下面的IPv6地址
    unsigned char dip6[16] = {0x20, 0x01, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00,
                             172, 16, 2, 2, 0x00, 0x00, 0x00, 0x00};

    // 简版IPv4头
    struct ipv4hdr {
        unsigned char sip[4];
        unsigned char dip[4];
    };

    // 简版IPv6头
    struct ipv6hdr {
        unsigned char sip6[16];
        unsigned char dip6[16];
    };

    // 报文格式:|0~31 IPv4/6头部|32~45 payload|46~47 校验码|

    int main(int argc, char **argv)
    {

        u_int16_t csum, *c2;
        // 完整的数据报文:0~31bit可以装下简版IPv6头,24~31bit可以装下简版IPv4头.保持payload不变,方便操作
        unsigned char  packet[48] = {0};
        // 暂存IPv6头
        unsigned char  hdr6buf[32] = {0};
        struct ipv4hdr *hdr4;
        struct ipv6hdr *hdr6;

        hdr4 = (struct ipv4hdr *)&packet[24];
        memcpy(hdr4->sip, sip, 4);
        memcpy(hdr4->dip, dip, 4);
        // 10个字节的payload
        memcpy((unsigned char *)hdr4 + sizeof(struct ipv4hdr), "1234567890abcd", 14);
        // 最后2个字节保存校验码
        memset((unsigned char *)hdr4 + sizeof(struct ipv4hdr) + 14, 0, 2);

        // 一共校验23个16位组,总共46个字节
        csum = csum16((u_int16_t *)(&packet[0]), 23);
        c2 = (u_int16_t *)(&packet[46]);
        *c2 = csum;
        printf("IPv4报文数据检验码(可模拟包含伪头的TCP校验码):%.2X\n", csum);

        {
            int i = 0;
            printf("begin IPv4 packet:\n");
            for (i = 24; i < 48; i++) {
                printf ("%.2X ", packet[i]);
            }
            printf("\nend IPv4 packet\n");
        }

        hdr6 = (struct ipv6hdr *)&hdr6buf[0];
        memcpy(hdr6->sip6, sip6, 16);
        memcpy(hdr6->dip6, dip6, 16);
        u_int16_t csum_hdr6 = csum16((u_int16_t *)hdr6, 16);

        //定位固定修改后的动态修改的初始地址,我将IPv6头的源地址hostID的最低2个字节用于检验码矫正!
        u_int16_t* pcsum = (u_int16_t*)(&hdr6buf[14]);

        //计算校验码矫正值!即已知h,p,c,求x:h + p + x = c
        *pcsum = ~add16(
            add16(
                ~(*pcsum),
                ~csum16((u_int16_t*)(&packet[0]), 16)
            ),
            csum_hdr6
        );

        printf("校验码矫正值为:%X\n", *pcsum);
        memcpy(&packet[0], &hdr6buf[0], 32); //完成修改

        printf("当前IPv6报文的校验码(模拟在IPv4头转换为IPv6头之后,TCP协议的校验码不需要改变):%.2X\n", csum16((u_int16_t*)(&packet[0]), 23));

        {
            int i;
            printf("begin IPv6 packet:\n");
            for (i = 0; i < 48; i++) {
                printf ("%.2X ", packet[i]);
            }
            printf("\nend IPv6 packet\n");
        }

        return 0;
    }

    编译执行之:

    [root@localhost DESTHDR]# ./a.out
    IPv4报文数据检验码(可模拟包含伪头的TCP校验码):883E
    begin IPv4 packet:
    C0 A8 01 01 AC 10 02 02 31 32 33 34 35 36 37 38 39 30 61 62 63 64 3E 88
    end IPv4 packet
    校验码矫正值为:EEB0
    当前IPv6报文的校验码(模拟在IPv4头转换为IPv6头之后,TCP协议的校验码不需要改变):883E
    begin IPv6 packet:
    20 01 00 01 02 03 04 05 C0 A8 01 01 00 00 B0 EE 20 01 05 04 03 02 01 00 AC 10 02 02 00 00 00 00 31 32 33 34 35 36 37 38 39 30 61 62 63 64 3E 88
    end IPv6 packet

    是不是很好玩?解一元一次方程也能解出实际用途来。

    这可不是我自己说的,这可是RFC上说的,我只是照着试试做一下而已:
    https://tools.ietf.org/html/rfc6052#section-4.1

    现在反过来,IPv6报文如果转换为IPv4报文呢?

    我们知道IPv4报文本身就有一个针对于IPv4协议头的校验码字段,如果数据始发于IPv6栈,那么当它需要转换为IPv4报文时,看样子这个IPv4的校验码计算是躲不过。

    确实躲不过,但问题是在哪里进行这个计算。是在边缘节点还是在协议转换的节点来做呢?

    我认为可以在边缘节点来做这个计算,然后把值藏匿于IPv6地址的 可以自由发挥的主机标识符里 就是了。

    假设从IPv6栈发起一个去往IPv4地址10.18.19.2的数据报文,按照RFC6052的规范,这个IPv4地址肯定被编码进了IPv6栈的源地址的主机标识符里,那么是不是可以在数据始发的时候,就直接按照IPv4地址来计算TCP/UDP/ICMP的校验码呢,然后继续计算IPv4头的校验码,将IPv4头的校验码藏匿于源IPv6地址的suffix即可。

    下面是一个例子:

    IPv6始发:2001: 1234: 1234: 1234:192.168.12.2::0/64到2001:4321:4321:4321:172.16.12.2::/64。
    始发站直接按照192.168.12.2和172.16.12.2作为源和目标计算4层协议checksum保存在报文checksum字段。
    始发站自行组装源和目标分别为192.168.12.2和172.16.12.2的IPv4报头,计算IPv4的checksum,保存于源IPv6地址2001: 1234: 1234: 1234:192.168.12.2::0/6的最后2个字节。
    协议转换网关收到报文,按照嵌入的IPv4地址组装IPv4头,取出IPv6源地址的低2字节作为checksum装入IPv4头的checksum字段。
    IPv4报文发出到IPv4网络。
    这便解放了协议转换网关的算力资源。

    遗留的问题是,IPv6始发站如何识别一个报文是不是发往IPv4网络的,如何触发它去按照内嵌IPv4地址去生成伪头以及去计算一个IPv4头的校验码,这除了RFC6052之外,就看应用层的配置了,一个sockopt规则灌下去,非常容易做到!

    既然IPv4和IPv6要互联互通,肯定是需要协议转换设备了,我本来就是一个设备迷而不是很care什么软件,所以,本文也是看了下面的新闻才有感而发的:
    国内首个IPv6翻译设备认证出炉 北京英迪瑞讯IVI通过IPv6 认证: http://www.qianjia.com/zhike/201904/031822517170.html

    我可能思想过于老套了,但我依然觉得设备才是重要的,软件灌入设备卖出去才有效。最关键的,我不喜欢互联网软件的原因是,互联网软件的代码一般都很low,毕竟服务器都在这些互联网公司自己的机房,出了问题远程登录即可排错,而设备是卖出去到客户那里的,排错成本高昂,所以必须精雕细琢不断测试。

    转自blog.csdn.net/dog250/article/details/89039395

    展开全文
  • 双光 一个可同时控制RGBW和RGBWW灯泡的Android应用... 仅与Milight v6协议兼容(!) 由于当前版本完全适合我,因此我暂停了开发。 但是,请随时提交拉取请求,或者直接给我下一行。 可在 2017年1月29日版本的屏幕截图
  • 3GPP V6协议说明

    2009-03-30 16:18:49
    3GPPV6标准包括:3GPPV6Multirate等协议说明。
  • OMA CP协议 V6

    2012-12-13 15:54:25
    OMA Client Provisoning 协议 V6
  • IPv6 协议基础

    2020-06-15 12:38:01
    IPv6 协议基础 IPv6介绍 IPv6地址 IPv6地址格式 IPv6地址压缩 IPv6地址分类 IPv6组播地址 IPV6通信建立 IPv6地址自动配置 小实验 IPv6介绍 IPv6 概括: IPv6(Internet Protocol Version 6)是网络层协议的第二代...

    IPv6介绍

    IPv6 概括:
    IPv6(Internet Protocol Version 6)是网络层协议的第二代标准协议,也被称为IPng(IP Next Generation)。它是Internet工程任务组IETF(Internet Engineering Task Force)设计的一套规范,是IPv4(Internet Protocol Version 4)的升级版本。
    目的:

    IPv6 目的:
    IPv4协议是目前广泛部署的因特网协议。在因特网发展初期,IPv4以其协议简单、易于实现、互操作性好的优势而得到快速发展。但随着因特网的迅猛发展,IPv4设计的不足也日益明显,IPv6的出现,解决了IPv4的一些弊端。相比IPv4,IPv6具有如下优势:

    IPV4协议目前存在的缺陷大致归为以下几点:

    • IP地址空间
    • 自动配置机制
    • 头部字段处理负担
    • 安全机制

    自动配置机制
    IPv4缺乏自动配置机制:

    • 要想让IPv4主机能够接入到网络当中,少不了需要具备一定网络技术的专业人士对实现通信所需的参数进行手动配置。

    IPv6需要实现网络插即用的机制:

    • 主机所在子网中不需要任何提供地址配置的服务器就可以自行完成地址配置,做到真正意义上的即插即用。

    安全机制
    IPV4协议缺乏安全机制:

    • IPv4协议缺乏有效的安全认证和保密机制。需要依赖其他技术(如IPsec)来保证IP数据报文在网络上的安全。

    IPv6协议所具备的安全机制:

    • 在网络层加入身份认证和加密等安全机制,这种当时为IPV6定义的安全标准就是IPSec。

    IPv4和IPv6头部封装字段对比
    在这里插入图片描述在这里插入图片描述
    分片与重组是相当消耗转发设备资源的处理行为。为了节省转发设备的处理资源,IPV6环境中的路由器不再对数据包执行分片。
    当IPV6路由器接收到大于最大传输单元的数据包时,它会直接丢弃这个数据包,然后把因过大而丢弃数据包的情况通告给数据包的始发设备。为了避免因为数据包过大而被路由器丢包,IPV6环境中的终端设备必须承担起控制数据包大小的工作,它们会通过路径MTU发现机制来判断可以发送的最大数据包长度,然后由上层协议来限制数据的规模。

    IPv6地址

    IPv6地址格式

    IPv6地址=前缀+接口ID口

    • 前缀:相当于v4地址中的网络ID(前缀由IANA、ISP和各组织分配)
    • 接口标识:相当于v4地址中的主机ID(接口标识符目前定义为64比特,可以由本地链路标识生成或采用随机算法生成以保证唯一性)
    • 128位长,用冒号将128比特分割成8个16比特的部分,每个部分包括4位的16进制数字。

    接口标识可通过三种方法生成:手工配置、系统通过软件自动生成或IEEE EUI-64规范生成。其中,EUI-64规范自动生成最为常用。
     

    IPv6地址压缩

    为了使用方便,IPV6定义了两种地址压缩规则:

    • 每组十六进制数的前导0可以省略
    • 如果地址中包含连续两个或多个全0组,那么这些全0组可以压缩为双冒号(::)。但是,一个IPV6地址只能使用一次冒号

    IPv6地址分类

    IPv6地址分为单播地址、任播地址(Anycast Address)、组播地址三种类型。和IPv4相比,取消了广播地址类型,以更丰富的组播地址代替,同时增加了任播地址类型。

    IPv6单播地址标识了一个接口,由于每个接口属于一个节点,因此每个节点的任何接口上的单播地址都可以标识这个节点。发往单播地址的报文,由此地址标识的接口接收。

    IPv6定义了多种单播地址,目前常用的单播地址有:未指定地址、环回地址、全局单播地址、链路本地地址、唯一本地地址ULA(Unique Local Address)。

    全局单播地址
    全局单播IPv6地址的有效范围是全局有效的。这也就是说,全局单播地址是那种可以部署在公共网络环境中的、全网可路由的IPV6地址。
    全局单播IPv6地址的前3位固定为001。
    在这里插入图片描述全局单播地址:001
    全局路由前缀可以理解为网络号
    接口ID可以理解为主机号

    未指定地址
    IPv6中的未指定地址即 0:0:0:0:0:0:0:0/128 或者::/128(相当于IPv4中的0.0.0.0/32)。该地址可以表示某个接口或者节点还没有IP地址,可以作为某些报文的源IP地址(例如在NS报文的重复地址检测中会出现)。源IP地址是::的报文不会被路由设备转发。

    环回地址
    IPv6中的环回地址即 0:0:0:0:0:0:0:1/128 或者::1/128。环回与IPv4中的127.0.0.1作用相同,主要用于设备给自己发送报文。该地址通常用来作为一个虚接口的地址(如Loopback接口)。实际发送的数据包中不能使用环回地址作为源IP地址或者目的IP地址。

    唯一本地地址
    是另一种应用范围受限的地址,它仅能在一个站点内使用。由于本地站点地址的废除(RFC3879),唯一本地地址被用来代替本地站点地址。
    唯一本地地址的作用类似于IPv4中的私网地址

    在这里插入图片描述

    IPv6组播地址

    IPv6的组播与IPv4相同,用来标识一组接口,一般这些接口属于不同的节点。一个节点可能属于0到多个组播组。发往组播地址的报文被组播地址标识的所有接口接收。例如组播地址FF02::1表示链路本地范围的所有节点,组播地址FF02::2表示链路本地范围的所有路由器。

    前8位固定为1
    在这里插入图片描述
    在这里插入图片描述

    IPV6通信建立

    邻居发现协议NDP(Neighbor Discovery Protocol)是IPv6协议体系中一个重要的基础协议。邻居发现协议替代了IPv4的ARP(Address Resolution Protocol)和ICMP路由器发现(Router Discovery),它定义了使用ICMPv6报文实现地址解析,跟踪邻居状态,重复地址检测,路由器发现以及重定向等功能。

    ARP报文是直接封装在以太网报文中,以太网协议类型为0x0806,普遍观点认为ARP定位为第2.5层的协议。ND本身基于ICMPv6实现,以太网协议类型为0x86DD,即IPv6报文,IPv6下一个报头字段值为58,表示ICMPv6报文,由于ND协议使用的所有报文均封装在ICMPv6报文中,一般来说,ND被看作第3层的协议。在三层完成地址解析,主要带来以下几个好处:

    • 地址解析在三层完成,不同的二层介质可以采用相同的地址解析协议。
    • 可以使用三层的安全机制避免地址解析攻击。
    • 使用组播方式发送请求报文,减少了二层网络的性能压力。

    地址解析过程中使用了两种ICMPv6报文:邻居请求报文NS(Neighbor Solicitation)和邻居通告报文NA(Neighbor
    Advertisement)。

    • NS报文:Type字段值为135,Code字段值为0,在地址解析中的作用类似于IPv4中的ARP请求报文。
    • NA报文:Type字段值为136,Code字段值为0,在地址解析中的作用类似于IPv4中的ARP应答报文。

    在这里插入图片描述
    在这里插入图片描述
    地址解析
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

     

    IPv6地址自动配置

    IPv4使用DHCP实现自动配置,包括IP地址,缺省网关等信息,简化了网络管理。IPv6地址增长为128位,且终端节点多,对于自动配置的要求更为迫切,除保留了DHCP作为有状态自动配置外,还增加了无状态自动配置。无状态自动配置即自动生成链路本地地址,主机根据RA报文的前缀信息,自动配置全球单播地址等,并获得其他相关信息。

    IPv6主机无状态自动配置过程:

    1.根据接口标识产生链路本地地址。
    2.发出邻居请求,进行重复地址检测。
    3.如地址冲突,则停止自动配置,需要手工配置。
    4.如不冲突,链路本地地址生效,节点具备本地链路通信能力。
    5.主机会发送RS报文(或接收到设备定期发送的RA报文)。
    6.根据RA报文中的前缀信息和接口标识得到IPv6地址。
    

    在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

    小实验

    在这里插入图片描述

    展开全文
  • ANO匿名上位机V7协议&STM32 说明:以下程序为自己编写,若有误欢迎各位指出。 基于ANO匿名V7上位机的通信协议编写的代码 文章目录ANO匿名上位机V7协议&STM32前言一、Ano V7上位机功能介绍1,基本数据接收2,...

    ANO匿名上位机V7协议&STM32

    说明:以下程序为自己编写,若有误欢迎各位指出。

    基于ANO匿名V7上位机的通信协议编写的代码


    前言

    提示:以下内容需用到C语言中的指针、结构体、枚举、及大小端储存方式,需提前知晓其中的基本用法&高级用法。


    一、Ano V7上位机功能介绍

    1,基本数据接收

    注:使用基本收发时需开启显示功能
    在这里插入图片描述

    2,协议通信

    界面如下所示,会显示出协议数据中的所有数据
    在这里插入图片描述

    a,格式设置

    点击右边的边框,展开格式设置界面,用于配置用户数据,可用于波形显示
    在这里插入图片描述
    其中用户帧是从F1~FA共10帧
    在这里插入图片描述
    而每一帧里面可以携带10个数据,数据类型可为"uint8_t"、“uint16_t”、“int16_t”、“uint32_t"和"int32_t”
    在这里插入图片描述
    要使用那个帧就勾上使能该帧,否则该帧的数据无效
    若要传输小数则需改变后面的传输缩放"1.0E+0"表示没缩放,"1.0E+1"表示缩放10倍

    数据容器配置界面中,一共有20个用户的数据容器,可用于显示波形,只需要设置容器对应的数据位就行,后面会显示当前数据位的数据值
    在这里插入图片描述

    b,协议通信

    协议通信界面可以进行发送命令、发送读取命令、发送写入命令
    在这里插入图片描述

    3,数据波形显示

    在这里插入图片描述
    在波形显示界面要配置需要显示的数据,右击波形名称,选择用户数据,找到对应的用户数据,并且在需要显示的数据前打勾在这里插入图片描述
    其他详细操作请自行体会。

    4,飞控参数

    在这里插入图片描述
    其中我们需要用到PID参数,Ano给我们留了18组PID
    在这里插入图片描述

    a,读取设备信息

    在ANO V7上位机中,飞控参数界面需要指定的设备才能使用
    在这里插入图片描述
    所以,我们在连接串口后点击“读取信息”,会读取设备信息,此处我们只是借用“拓空者飞控”的ID
    在这里插入图片描述
    在这里插入图片描述

    b,读取参数

    点击读取参数,会显示出PID参数,直接点击参数值可进行更改,然后点击写入参数即可
    在这里插入图片描述

    二、程序

    1,协议解析程序

    MyAno.c

    #include "MyAno.h"
    #include "string.h"
    #include "usart.h"
    #include "math.h"
    
    _ano MyAno = {0};			//发送的数据
    _Para MPara = {0};			//参数
    
    /** 	
     *	函数名:	参数初始化
     *	形	参:	无
     *	返回值:	无
    **/
    void Ano_Init(void) 
    {
    	MyAno.Head = 0xAA;
    	MyAno.Addr = 0xFF;
    	MyAno.Lenth = 0;
    }
    
    /**	
     * 函数名:	发送ID对应的参数
     * 形	参:	Id - 参数ID
     * 			para - 参数数据
     *	返回值:	无
    **/
    static void SendParaData(uint8_t Id,int32_t para)
    {
    	Ano_Set_Mdata(0xE2,(uint16_t *)&Id,2,1);	//加载ID
    	Ano_Set_Mdata(0xE2,(int32_t *)&para,4,2);	//加载参数数据
    	Ano_SendMdata();	//发送数据
    }
    
    /**
     * 函数名:	发送数据校验帧0x00
     * 形	参:	id - 帧的ID	
     * 			sumcheck 和校验数据
     * 			addcheck 附加校验数据
     * 返回值:无
    **/
    static void SendCheckAnalysis(uint8_t id,uint8_t *sumcheck,uint8_t *addcheck)
    {
    	Ano_Set_Mdata(0x00,(uint8_t *)&id,1,1);
    	Ano_Set_Mdata(0x00,(uint8_t *)sumcheck,1,2);
    	Ano_Set_Mdata(0x00,(uint8_t *)addcheck,1,3);
    	Ano_SendMdata();
    }
    
    /**	
     *	函数名:	接收数据校验检测
     *	形	参:	_da数据
     *	返回值:	1校验成功	0校验失败 
    **/
    static uint8_t Receive_CheckData(uint8_t *_da)
    {
    	uint8_t i = 0;
    	uint8_t sumcheck = 0,addcheck = 0;
    	for(i = 0;i < _da[3] + 4;i++)
    	{
    		sumcheck += _da[i];
    		addcheck += sumcheck;
    	}	
    	if((sumcheck == _da[_da[3] + 4]) && (addcheck == _da[_da[3] + 5]))		//校验通过
    		return 1;
    	else
    		return 0;
    }
    
    /**	
     *	函数名:	发送数据和校验&附加校验计算
     *	形	参:	ano结构体
     *	返回值:	1->校验成功 0->校验失败
    **/
    static uint8_t Send_CheckData(_ano *ano)	
    {
    	uint8_t i = 0;
    	uint8_t sumcheck = 0,addcheck = 0;
    	for(i = 0;i < ano->Lenth + 4;i++)
    	{
    		sumcheck += ano->SendBuff[i];
    		addcheck += sumcheck;
    	}	
    	memcpy(ano->SendBuff + 4 + ano->Lenth,(uint8_t*)&sumcheck,sizeof(sumcheck));
    	memcpy(ano->SendBuff + 5 + ano->Lenth,(uint8_t*)&addcheck,sizeof(addcheck));
    	/* 其中 ano->SendBuff[3] 表示数据长度 */
    	if(sumcheck == ano->SendBuff[ano->SendBuff[3] + 4] && addcheck == ano->SendBuff[ano->SendBuff[3] + 5])
    		return 1;
    	else
    		return 0;
    }
    
    /**
     * 函数名:	控制指令
     * 形 参:	_Ord命令
     * 返回值:	无
    **/
    __weak void ControlOrder(uint8_t _Ord)
    {
    	switch (_Ord)	//命令
    	{
    		case Stop:			//程序停止
    			Ano_SendString("程序停止运行!\r\n",Color_Red);
    		break;
    
    		case Operation:		//运行程序
    			Ano_SendString("程序开始运行!\r\n",Color_Green);
    		break;
    
    		case Store:			//储存参数
    			Ano_SendString("参数储存成功!\r\n",Color_Green);
    		break;
    	}
    }
    
    /**
     * 函数名:	参数回传设置
     * 形 参:	_id参数的ID
     * 返回值:	无
    **/
    __weak void ParaOfReturn_Set(uint16_t _id)
    {
    	memset(MyAno.SendBuff,0x00,sizeof(MyAno.SendBuff));
    	MyAno.Lenth = 0;
    	switch (_id)
    	{
    		case 1:		SendParaData((uint8_t )_id,(int32_t)HWTYPE);	break;	//参数回传硬件版本(此处借用拓空者)
    		case PID_1_P:	SendParaData((uint8_t )_id,(int32_t)MPara.PID_Par1_P);	break;	
    		case PID_1_I:	SendParaData((uint8_t )_id,(int32_t)MPara.PID_Par1_I);	break;	
    		case PID_1_D:	SendParaData((uint8_t )_id,(int32_t)MPara.PID_Par1_D);	break;	//参数回传(第一组PID)
    
    		case PID_2_P:	SendParaData((uint8_t )_id,(int32_t)MPara.PID_Par2_P);	break;	
    		case PID_2_I:	SendParaData((uint8_t )_id,(int32_t)MPara.PID_Par2_I);	break;	
    		case PID_2_D:	SendParaData((uint8_t )_id,(int32_t)MPara.PID_Par2_D);	break;	//参数回传(第二组PID)
    
    		case PID_3_P:	SendParaData((uint8_t )_id,(int32_t)MPara.PID_Par3_P);	break;	
    		case PID_3_I:	SendParaData((uint8_t )_id,(int32_t)MPara.PID_Par3_I);	break;	
    		case PID_3_D:	SendParaData((uint8_t )_id,(int32_t)MPara.PID_Par3_D);	break;	//参数回传(第三组PID)
    
    		default: SendParaData((uint8_t )_id,(int32_t)0x00);	break;	//若为其他参数则返回0	
    	}
    }
    
    /**
     * 函数名:	参数数据读取设置
     * 形 参:	_id参数的ID
     * 			_val参数数据
     * 返回值:	无
    **/
    __weak void ParaRead_Set(uint16_t _id,int32_t _val)
    {
    	switch (_id)
    	{
    		case PID_1_P:	MPara.PID_Par1_P = _val;	break;	//参数回传硬件版本(此处借用拓空者)
    		case PID_1_I:	MPara.PID_Par1_I = _val;	break;	//参数回传硬件版本(此处借用拓空者)
    		case PID_1_D:	MPara.PID_Par1_D = _val;	break;	//参数回传硬件版本(此处借用拓空者)
    
    		case PID_2_P:	MPara.PID_Par2_P = _val;	break;	//参数回传硬件版本(此处借用拓空者)
    		case PID_2_I:	MPara.PID_Par2_I = _val;	break;	//参数回传硬件版本(此处借用拓空者)
    		case PID_2_D:	MPara.PID_Par2_D = _val;	break;	//参数回传硬件版本(此处借用拓空者)
    
    		case PID_3_P:	MPara.PID_Par3_P = _val;	break;	//参数回传硬件版本(此处借用拓空者)
    		case PID_3_I:	MPara.PID_Par3_I = _val;	break;	//参数回传硬件版本(此处借用拓空者)
    		case PID_3_D:	MPara.PID_Par3_D = _val;	break;	//参数回传硬件版本(此处借用拓空者)
    	}
    }
    
    /**	
     *	函数名:	接收数据解析
     *	形	参:	_da数据(需为完整的一帧数据)
     *	返回值:	无
    **/
    void Ano_DataAnalysis(uint8_t *_da)
    {
    	uint16_t HeadID = 0;
    	int32_t DataVal = 0;
    	MPara.OrderState = 1;	//在接收命令
    	
    	if(_da[0] == 0xAA && (_da[1] == 0xFF || _da[1] == 0x05))	//判断帧头、和目标地址
    	{
    		if(_da[2] == 0xE0)	//判断功能码(命令帧)
    		{
    			if(_da[4] == 0x10 && _da[5] == 0x00)		//判断CID和CMD0
    				ControlOrder(_da[6]);	//控制指令(通过CMD1来控制程序)
    			SendCheckAnalysis((uint8_t )0xE0,(uint8_t *)&(_da[_da[3] + 4]),(uint8_t *)&(_da[_da[3] + 5]));	//回传数据校验帧
    			MPara.OrderState = 0;	//命令回传结束
    		}
    		else if(_da[2] == 0xE1)	//判断功能码(读参数)
    		{
    			if(Receive_CheckData(_da))
    			{
    				HeadID = *(uint16_t*)(&_da[4]);//(uint8_t)(_da[4] + (_da[5] << 8));
    				ParaOfReturn_Set(HeadID);	//参数回传
    				MPara.OrderState = 0;	//命令回传结束
    			}
    		}
    		else if(_da[2] == 0xE2)	//判断功能码(写参数)
    		{
    			if(Receive_CheckData(_da))
    			{
    				HeadID =  *(uint16_t*)(&_da[4]);// (uint8_t)(_da[4] + (_da[5] << 8));		//(*(uint16_t*)(&_da[4]))
    				DataVal = *(int32_t*)(&_da[6]);
    				ParaRead_Set(HeadID,DataVal);	//参数数据读取设置
    				SendCheckAnalysis((uint8_t )0xE2,(uint8_t *)&(_da[_da[3] + 4]),(uint8_t *)&(_da[_da[3] + 5]));	//回传数据校验帧
    				MPara.OrderState = 0;	//命令回传结束
    			}
    		}
    	}	
    }
    
    /**	
     *	函数名:	多数据配置函数(结合Ano_SendMdata多数据发送函数使用)
     *	形	参:	id->功能码(0xF1-0xFA) *data->发送的数据 len->数据长度(sizeof) num->用于指示第几个数据(可去除)
     *	返回值:	无
    **/
    void Ano_Set_Mdata(uint8_t id,void *data,uint8_t len,uint8_t num)
    {
    	MyAno.ID = id;
    	memcpy(MyAno.SendBuff,(uint8_t*)&MyAno,3);
    	memcpy(MyAno.SendBuff + 4 + MyAno.Lenth,(uint8_t*)data,len);
    	MyAno.Lenth += len;
    	memcpy(MyAno.SendBuff + 3,(uint8_t*)&MyAno.Lenth,1);
    }
    
    /**	
     *	函数名:	多数据发送(结合Ano_Set_Mdata多数据配置函数使用)
     *	形	参:	id->功能码(0xF1-0xFA) *data->发送的数据 len->数据长度(sizeof) num->用于指示第几个数据(可去除)
     *	返回值:	无
    **/
    void Ano_SendMdata(void)
    {
    	uint8_t check;
    	check = Send_CheckData(&MyAno);
    	if(check)
    	{
    		HAL_UART_Transmit(&huart2,MyAno.SendBuff,MyAno.Lenth + 6,100);
    		memset(MyAno.SendBuff,0,sizeof(MyAno.SendBuff));
    		MyAno.Lenth = 0;
    	}
    	else
    	{
    		memset(MyAno.SendBuff,0,sizeof(MyAno.SendBuff));
    		MyAno.Lenth = 0;
    	}
    }
    
    /**	
     *	函数名:	发送数据函数
     * 	形	参:	id->功能码(0xF1-0xFA) *Data->发送的数据 lenth->数据长度(sizeof)
     *	返回值:	无
    **/
    void Ano_Send_Data(uint8_t id, void *Data, uint8_t lenth)	//发送函数
    {
    	static uint8_t check;
    	MyAno.ID = id;
    	MyAno.Lenth = lenth;
    	
    	memcpy(MyAno.SendBuff,(uint8_t*)&MyAno,4);
    	memcpy(MyAno.SendBuff + 4,(uint8_t*)Data,lenth);
    	
    	check = Send_CheckData(&MyAno);
    	if(check)	//如果校验成功则发送数据,校验失败就丢弃此包
    	{
    		HAL_UART_Transmit(&huart2,MyAno.SendBuff,MyAno.Lenth + 6,100);
    		memset(MyAno.SendBuff,0x00,sizeof(MyAno.SendBuff));
    		MyAno.Lenth = 0;
    	}	
    }
    
    /**
     * 函数名:	发送LOGO信息
     * 形 参:	字符串\数字
     * 返回值:	无
    **/
    void Ano_SendString(const char *str,uint8_t color)
    {
    	uint8_t i = 0;
    	Ano_Set_Mdata(0xA0,(uint8_t*)&color,1,1);	//加载颜色数据
    	while (*(str + i) != '\0')		//判断结束位
    	{
    		Ano_Set_Mdata(0xA0,(uint8_t*)(str+i++),1,2);	//加载字符串数据
    	}
    	Ano_SendMdata();	//发送数据
    }
    
    /**
     * 函数名:	发送LOGO信息+数据信息
     * 形 参:	字符串\数字
     * 返回值:	无
    **/
    void Ano_SendStringVal(const char *str,int32_t Val)
    {
    	uint8_t i = 0;
    	Ano_Set_Mdata(0xA0,(int32_t*)&Val,4,1);	//加载数据信息
    	while (*(str + i) != '\0')		//判断结束位
    	{
    		Ano_Set_Mdata(0xA1,(uint8_t*)(str+i++),1,2);	//加载字符串数据
    	}
    	Ano_SendMdata();	//发送数据
    }
    
    int16_t Ano_Show_Sin(float x)	//SIN函数
    {
    	return 100*sin(2*x);
    }
    
    float Ano_Show_Cos(float x)	//COS函数
    {
    	return 100*cos(2*x);
    }
    
    float Ano_Show_SQU(float x)	//方波
    {
    	if(x > 0 && x < 3.14f)
    		return 10;
    	else
    		return -10;
    }
    	
    void Show_Test(void)	//测试显示
    {	
    	int16_t y1,y2,y3;
    	uint8_t nul = 0;
    	static float x = 0;
    	x += 0.01f;
    	if(x > 3.14f)
    		x = -3.14f;
    	
    	y1 = Ano_Show_Sin(x);		//数据*10发送
    	y2 = Ano_Show_Cos(x);		//数据*10发送
    	y3 = Ano_Show_SQU(x);
    	
    	Ano_Set_Mdata(0xF1,(int16_t*)&y1,sizeof(y1),1);
    	Ano_Set_Mdata(0xF1,(int16_t*)&y2,sizeof(y2),2);
    	Ano_Set_Mdata(0xF1,(int16_t*)&y3,sizeof(y3),3);
    	Ano_Set_Mdata(0xF1,(uint8_t*)&nul,sizeof(nul),4);		//加载数据到对应的数据位
    	Ano_SendMdata();	//发送数据
    }
    
    

    MyAno.h

    #ifndef __MYANO_H
    #define __MYANO_H
    
    #include "stm32f3xx_hal.h"
    
    #define Color_Black  	0
    #define Color_Red  		1
    #define Color_Green  	2
    
    enum AnoID
    {
    	Stop = 0x00,		//停止运行
    	Operation,			//开始运行
    	Store,				//储存
    
    	HWTYPE = 0x05,	//硬件种类
    
    	PID_1_P = 11,
    	PID_1_I,
    	PID_1_D,		//第一组PID
    
    	PID_2_P,
    	PID_2_I,
    	PID_2_D,		//第二组PID
    
    	PID_3_P,
    	PID_3_I,
    	PID_3_D			//第三组PID
    };
    
    typedef struct
    {
    	uint8_t Head;
    	uint8_t Addr;
    	uint8_t ID;
    	uint8_t Lenth;
    	uint8_t SendBuff[1024];	//发送缓存数组
    	uint8_t ReceiveBuf[10];	//接收缓存数组
    }_ano;
    
    typedef struct 
    {
    	uint8_t OrderState;		//命令接收状态
    
    	int16_t PID_Par1_P;
    	int16_t PID_Par1_I;
    	int16_t PID_Par1_D;		//第一组PID
    
    	uint16_t PID_Par2_P;
    	uint16_t PID_Par2_I;
    	uint16_t PID_Par2_D;	//第二组PID
    
    	uint16_t PID_Par3_P;
    	uint16_t PID_Par3_I;
    	uint16_t PID_Par3_D;	//第三组PID
    	/* data */
    }_Para;
    
    extern _ano MyAno;		//声明外部变量
    extern _Para MPara;		//参数
    
    /****** 用户不可调用函数 ******/
    static void SendParaData(uint8_t Id,int32_t para);		//发送ID对应的参数
    static void SendCheckAnalysis(uint8_t id,uint8_t *sumcheck,uint8_t *addcheck);		//发送数据校验帧0x00
    static uint8_t Receive_CheckData(uint8_t *_da);		//接收数据校验检测
    static uint8_t Send_CheckData(_ano *ano);			//发送数据和校验&附加校验计算
    
    /****** 用户可调用函数 ******/
    void Ano_Init(void); 												//参数初始化
    void Ano_Send_Data(uint8_t id, void *Data, uint8_t lenth);			//发送数据函数
    void Ano_Set_Mdata(uint8_t id,void *data,uint8_t len,uint8_t num);	//多数据配置
    void Ano_SendMdata(void);											//多数据发送
    void Ano_DataAnalysis(uint8_t *_da);								//接收数据解析(放串口回调函数里面)
    void Ano_SendString(const char *str,uint8_t color);					//发送字符串
    void Ano_SendStringVal(const char *str,int32_t Val);				//发送字符串+数据值
    
    void Show_Test(void);	//测试显示函数
    
    /****** 用户可调用&重写函数 ******/
    __weak void ControlOrder(uint8_t _Ord);					//控制指令
    __weak void ParaOfReturn_Set(uint16_t _id);				//参数回传设置
    __weak void ParaRead_Set(uint16_t _id,int32_t _val);	//参数数据读取设置
    
    #endif 
    
    

    2,调用说明

    注:需先调用"Ano_Init"参数初始化函数,在确定回传参数与实际参数的关系,然后把"Ano_DataAnalysis"扔进串口回调函数中,传入一个完整的一帧数据进行解析。

    串口接收回调函数的示例:

    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
      if(huart->Instance == huart2.Instance)  //判断是否是USART1
      {
        MPara.OrderState = 1;	        //在接收命令数据
        if(UsartBuffer[0] == 0xAA)    //判断帧头
        {
          UsartBuffer[Usart1_RxCnt++] = RxBuffer;
          if((Usart1_RxCnt == 8 && UsartBuffer[2] == 0xE1) || (Usart1_RxCnt == 12 && UsartBuffer[2] == 0xE2) || (Usart1_RxCnt == 17 && UsartBuffer[2] == 0xE0))
          {
            Ano_DataAnalysis((uint8_t *)&UsartBuffer);
            Usart1_RxCnt = 0;			
    			  memset(UsartBuffer,0x00,sizeof(UsartBuffer)); //清空数组
          }
        }
        else
        { 
          Usart1_RxCnt = 0;			
    			memset(UsartBuffer,0x00,sizeof(UsartBuffer)); //清空数组
          UsartBuffer[Usart1_RxCnt++] = RxBuffer;
        }
        HAL_UART_Receive_IT(&huart2, (uint8_t *)&RxBuffer, 1);   //再开启接收中断	
      }
    }
    

    注:在发送数据的时候写一个判断语句,进行判断接收命令的状态

    if(MPara.OrderState == 0) //命令接收结束
    {
    	//发送数据语句
    }
    
    展开全文
  • VxLAN协议详解

    千次阅读 2020-05-23 23:20:38
    版权声明:本文为Heriam博主原创文章,遵循CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://jiang-hao.com/articles/2020/networking-vxlan-in-depth.html 文章目录VxLAN简介背景定义...

    版权声明:本文为Heriam博主原创文章,遵循CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
    原文链接:https://jiang-hao.com/articles/2020/networking-vxlan-in-depth.html


    VxLAN简介

    背景

    任何技术的产生,都有其特定的时代背景与实际需求,VXLAN正是为了解决云计算时代虚拟化中的一系列问题而产生的一项技术。那么我们先看看 VXLAN 到底要解决哪些问题。

    • 虚拟机规模受网络设备表项规格的限制

    对于同网段主机的通信而言,报文通过查询MAC表进行二层转发。服务器虚拟化后,数据中心中VM的数量比原有的物理机发生了数量级的增长,伴随而来的便是虚拟机网卡MAC地址数量的空前增加。一般而言,接入侧二层设备的规格较小,MAC地址表项规模已经无法满足快速增长的VM数量。

    • 传统网络的隔离能力有限

      虚拟化(虚拟机和容器)的兴起使得一个数据中心会有动辄上万的机器需要通信,而传统的 VLAN 技术在标准定义中只有12比特,也就只能支持 4096 个网络上限,已经显然满足不了不断扩展的数据中心规模。

    • 虚拟机迁移范围受限

      虚拟机迁移,顾名思义,就是将虚拟机从一个物理机迁移到另一个物理机,但是要求在迁移过程中业务不能中断。要做到这一点,需要保证虚拟机迁移前后,其IP地址、MAC地址等参数维持不变。这就决定了,虚拟机迁移必须发生在一个二层域中。而传统数据中心网络的二层域,将虚拟机迁移限制在了一个较小的局部范围内。此外,解决这个问题同时还需保证二层的广播域不会过分扩大,这也是云计算网络的要求。

    传统“二层+三层”的网络在应对这些要求时变得力不从心,虽然通过很多改进型的技术比如堆叠、SVF、TRILL等可以构建物理上的大二层网络,可以将虚拟机迁移的范围扩大。但是,构建物理上的大二层,难免需要对原来的网络做大的改动,并且大二层网络的范围依然会受到种种条件的限制。

    为了解决这些问题,有很多方案被提出来,VxLAN就是其中之一。VxLAN 是 VMware、Cisco 等一众大型企业共同推出的,目前标准文档在 RFC7348

    定义

    在介绍完VxLAN要解决的问题也就是技术背景之后,接下来正式阐述一下VxLAN的定义,也就是它到底是什么。

    VXLAN 全称是 Virtual eXtensible Local Area Network,虚拟可扩展的局域网。它是一种 Overlay 技术,采用L2 over L4(MAC-in-UDP)封装方式,是NVO3(Network Virtualization over Layer 3)中的一种网络虚拟化技术,将二层报文用三层协议进行封装,可实现虚拟的二层网络在三层范围内进行扩展,同时满足数据中心大二层虚拟迁移和多租户的需求。RFC7348上的介绍是这样的:

    A framework for overlaying virtualized layer 2 networks over lay 3 networks.

    意义

    针对大二层网络,VxLAN技术的出现很好的解决了云计算时代背景下数据中心在物理网络基础设施上实施服务器虚拟化的隔离和可扩展性问题:

    • 通过24比特的VNI可以支持多达16M的VXLAN段的网络隔离,对用户进行隔离和标识不再受到限制,可满足海量租户。
    • 除VXLAN网络边缘设备,网络中的其他设备不需要识别虚拟机的MAC地址,减轻了设备的MAC地址学习压力,提升了设备性能。
    • 通过采用MAC in UDP封装来延伸二层网络,实现了物理网络和虚拟网络解耦,租户可以规划自己的虚拟网络,不需要考虑物理网络IP地址和广播域的限制,大大降低了网络管理的难度。

    VxLAN组网模型

    VxLAN主要用于数据中心网络。VxLAN技术将已有的三层物理网络作为Underlay网络,在其上构建出虚拟的二层网络,即Overlay网络。Overlay网络通过Mac-in-UDP封装技术、利用Underlay网络提供的三层转发路径,实现租户二层报文跨越三层网络在不同的站点间传递。对于租户来说,Underlay网络是透明的,同一租户的不同站点就像是工作在一个局域网中。同时,在同一个物理网络上可以构建多个VxLAN网络,每个VxLAN网络由唯一的VNI标识,不同VxLAN之间互不影响,从而实现租户网络之间的隔离。

    如上图所示,VxLAN的典型网络模型中主要包含以下几个基本元素:

    • VM (Virtual Machine): 虚拟机。在一台服务器上可以创建多台虚拟机,不同的虚拟机可以属于不同的 VXLAN。处于相同VxLAN的虚拟机处于同一个逻辑二层网络,彼此之间二层互通;属于不同VxLAN的虚拟机之间二层隔离。
    • VxLAN Tunnel: VxLAN隧道。“隧道”是一个逻辑上的概念,它并不新鲜,比如大家熟悉的GRE。说白了就是将原始报文“变身”下,加以“包装”,好让它可以在承载网络(比如IP网络)上传输。从主机的角度看,就好像原始报文的起点和终点之间,有一条直通的链路一样。而这个看起来直通的链路,就是“隧道”。顾名思义,“VXLAN隧道”便是用来传输经过VXLAN封装的报文的,它是建立在两个VTEP之间的一条虚拟通道。Vxlan 通信双方(图中的虚拟机)认为自己是通过二层VSI直接通信,并不知道底层网络的存在。
    • VTEP (VxLAN Tunnel Endpoints): VXLAN隧道端点。VXLAN网络的边缘设备,是VXLAN隧道的起点和终点,VXLAN报文的封装和解封装处理均在这上面进行。VTEP可以理解为Overlay网络立足于Underlay物理网络之上的支脚点,分配有物理网络的IP地址,该地址与虚拟网络无关。VXLAN报文中源IP地址为隧道一端节点的VTEP地址,目的IP地址为隧道另一端节点的VTEP地址,一对VTEP地址就对应着一个VXLAN隧道。VTEP 可以是一个独立的网络设备(比如交换机),也可以是一台物理服务器(比如虚拟机所在的宿主机)。
    • VNI (VXLAN Network Identifier): VXLAN 网络标识符。以太网数据帧中VLAN只占了12比特的空间,这使得VLAN的隔离能力在数据中心网络中力不从心。而VNI的出现,就是专门解决这个问题的。VNI是一种类似于VLAN ID的用户标示,一个VNI代表了一个租户,即使多个终端用户属于同一个VNI,也表示一个租户。VNI
      由24比特组成,支持多达16M的租户。属于不同VNI的虚拟机之间不能直接进行二层通信。VXLAN报文封装时,给VNI分配了足够的空间使其可以支持海量租户的隔离。
    • IP核心设备/隧道中间设备: 网络中普通的路由/转发设备,不参与VxLAN处理,仅需根据封装后的VxLAN报文的目的VTEP IP地址沿着VxLAN隧道路径进行普通的三层转发。
    • VSI (Virtual Switch Instance): 虚拟交换实例。VTEP上为每个VxLAN提供二层交换服务的虚拟交换实例。VSI可以看做是VTEP上的一台针对某个VxLAN内的数据帧进行二层转发的虚拟交换机,它具有传统以太网交换机的所有功能,包括源MAC地址学习、MAC地址老化、泛洪等。VSI与VxLAN一一对应。
    • VSI-Interface: VSI的虚拟三层接口。类似于Vlan-Interface,用来处理跨VNI即跨VXLAN的流量。VSI-Interface与VSI一一对应,在没有跨VNI流量时可以没有VSI-Interface。

    VxLAN报文格式

    VXLAN是MAC in UDP的网络虚拟化技术,所以其报文封装是在原始以太报文之前添加了一个UDP头及VXLAN头封装:VTEP会将VM发出的原始报文封装成一个新的UDP报文,并使用物理网络的IP和MAC地址作为外层头,对网络中的其他设备只表现为封装后的参数。也就是说,网络中的其他设备看不到VM发送的原始报文。

    如果服务器作为VTEP,那从服务器发送到接入设备的报文便是经过封装后的报文,这样,接入设备就不需要学习VM的MAC地址了,它只需要根据外层封装的报文头负责基本的三层转发就可以了。因此,虚拟机规模就不会受网络设备表项规格的限制了。

    当然,如果网络设备作为VTEP,它还是需要学习VM的MAC地址。但是,从对报文进行封装的角度来说,网络设备的性能还是要比服务器强很多。

    下图是 VxLAN 协议的报文,白色的部分是虚拟机发出的原始报文(二层帧,包含了 MAC 头部、IP 头部和传输层头部的报文),前面加了VxLAN 头部用来专门保存 VxLAN 相关的内容,再前面是标准的 UDP 协议头部(UDP 头部、IP 头部和 MAC 头部)用来在物理网路上传输报文。

    从这个报文中可以看到三个部分:

    1. 最外层的 UDP 协议报文用来在底层物理网络上传输,也就是 VTEP 之间互相通信的基础;
    2. 中间是 VXLAN 头部,VTEP 接受到报文之后,去除前面的 UDP 协议部分,根据这部分来处理 VxLAN 的逻辑,主要是根据 VNI 发送到最终的虚拟机;
    3. 最里面是原始的二层帧,也就是虚拟机所见的报文内容。

    VxLAN报文各个部分解释如下:

    • Outer Ethernet/MAC Header: 外层以太头。14字节,如果有VLAN TAG则为18字节。

      • SA:发送报文的虚拟机所属VTEP的MAC地址。
      • DA:到达目的VTEP的路径上下一跳设备的MAC地址。
      • VLAN Type:可选字段,当报文中携带VLAN Tag时,该字段取值为0x8100。
      • Ethernet Type:以太报文类型,IP协议报文该字段取值为0x0800。
    • Outer IP Header: 外层IP头。20字节。其中,源IP地址(Outer Src. IP)为源VM所属VTEP的IP地址,目的IP地址(Outer Dst. IP)为目的VM所属VTEP的IP地址。IP协议号(Protocol)为17(0x11),指示内层封装的是UDP报文。

    • Outer UDP Header: 外层UDP头。8字节。其中,UDP目的端口号(UDP Destination Port)固定为4789,指示内层封装报文为VxLAN报文。UDP源端口号(UDP Source Port)为原始以太帧通过哈希算法计算后的随机任意值,可以用于VxLAN网络VTEP节点之间ECMP负载均衡。

    • VxLAN Header: VxLAN头。8字节。

      • Flags: 8比特,RRRRIRRR。“I”位为1时,表示VXLAN头中的VXLAN ID有效;为0,表示VXLAN ID无效。“R”位保留未用,设置为0。
      • VxLAN ID (VNI): 24比特,用于标识一个单独的VXLAN网络。这也是 VxLAN 能支持千万租户的地方。
      • Reserved: 两个保留字段,分别为24比特和8比特。
    • Original L2 Frame: 原始以太网报文。

    从报文的封装可以看出,VXLAN头和原始二层报文是作为UDP报文的载荷存在的。在VTEP之间的网络设备,只需要根据Outer MAC Header和Outer IP Header进行转发,利用UDP Source Port进行负载分担,这一过程,与转发普通的IP报文完全相同。这样,除了VTEP设备,现网的大量设备无需更换或升级即可支持VXLAN网络。

    VxLAN协议比原始报文多出50字节的内容,这会降低网络链路传输有效数据的比例。此外,新增加的VXLAN报文封装也引入了一个问题,即MTU值的设置。一般来说,虚拟机的默认MTU为1500 Bytes,也就是说原始以太网报文最大为1500字节。这个报文在经过VTEP时,会封装上50字节的新报文头(VXLAN头8字节+UDP头8字节+外部IP头20字节+外部MAC头14字节),这样一来,整个报文长度达到了1550字节。而现有的VTEP设备,一般在解封装VXLAN报文时,要求VXLAN报文不能被分片,否则无法正确解封装。这就要求VTEP之间的所有网络设备的MTU最小为 1550字节。如果中间设备的MTU值不方便进行更改,那么设置虚拟机的MTU值为1450,也可以暂时解决这个问题。

    VxLAN头部最重要的是VNID字段,其他的保留字段主要是为了未来的扩展,很多厂商都会加以运用来实现自己组网的一些特性。

    VxLAN运行机制

    隧道建立

    网络中存在多个VTEP,那么这其中哪些VTEP间需要建立VXLAN隧道呢?如前所述,通过VXLAN隧道,“二层域”可以突破物理上的界限,实现大二层网络中VM之间的通信。所以,连接在不同VTEP上的VM之间如果有“大二层”互通的需求,这两个VTEP之间就需要建立VXLAN隧道。换言之,同一大二层域内的VTEP之间都需要建立VXLAN隧道。

    一般而言,隧道的建立不外乎手工方式和自动方式两种。

    ####手工方式

    这种方式需要用户手动指定VXLAN隧道的源和目的IP地址分别为本端和对端VTEP的IP地址,也就是人为的在本端VTEP和对端VTEP之间建立静态VXLAN隧道。以华为CE系列交换机为例,以上配置是在NVE(Network Virtualization Edge)接口下完成的。配置过程如下:

    #
    interface Nve1   //创建逻辑接口NVE 1
     source 1.1.1.1   //配置源VTEP的IP地址(推荐使用Loopback接口的IP地址)
     vni 5000 head-end peer-list 2.2.2.2  
     vni 5000 head-end peer-list 2.2.2.3   
    #
    

    其中,vni 5000 head-end peer-list 2.2.2.2和vni 5000 head-end peer-list 2.2.2.3的配置,表示属于VNI 5000的对端VTEP有两个,IP地址分别为2.2.2.2和2.2.2.3。根据这两条配置,VTEP上会生成如下所示的一张表:

    <HUAWEI> display vxlan vni 5000 verbose
        BD ID                  : 10
        State                  : up
        NVE                     : 288
        Source                 : 1.1.1.1
        UDP Port               : 4789
        BUM Mode               : head-end
        Group Address         : - 
        Peer List              : 2.2.2.2 2.2.2.3 
    

    根据上表中的Peer List,本端VTEP就可以知道属于同一VNI的对端VTEP都有哪些,这也就决定了同一大二层广播域的范围。当VTEP收到BUM(Broadcast&Unknown-unicast&Multicast,广播&未知单播&组播)报文时,会将报文复制并发送给Peer List中所列的所有对端VTEP(这就好比广播报文在VLAN内广播)。因此,这张表也被称为“头端复制列表”。当VTEP收到已知单播报文时,会根据VTEP上的MAC表来确定报文要从哪条VXLAN隧道走。而此时Peer List中所列的对端,则充当了MAC表中“出接口”的角色。在后面的报文转发流程中,你将会看到头端复制列表是如何在VXLAN网络中指导报文进行转发的。

    ####自动方式

    自动方式下VXLAN隧道的建立需要借助于其他的协议,例如通过BGP/EVPN(Ethernet Virtual Private Network)或ENDP(Enhanced Neighbor Discovery Protocol)发现远端VTEP后,自动在本端和远端VTEP之间建立VXLAN隧道。

    二层MAC学习

    通过上节的内容,我们大致了解 VxLAN 报文的发送过程。概括地说就是虚拟机的报文通过 VTEP 添加上 VxLAN 以及外部的UDP/IP报文头,然后发送出去,对方 VTEP 收到之后拆除 VxLAN 头部然后根据 VNI 把原始报文发送到目的虚拟机。

    这个过程是双方已经知道所有通信所需信息的情况下的转发流程,但是在第一次通信之前还有很多问题有解决:

    • VTEP是如何对报文进行封装?
    • 发送方虚拟机怎么知道对方的 MAC 地址?
    • VTEP怎么知道目的虚拟机在哪一台宿主机上?

    要回答这些问题,我们还是回到 VxLAN 协议报文上,看看一个完整的 VxLAN 报文需要哪些信息。

    • 内层报文:通信的虚拟机双方要么直接使用 IP 地址,要么通过 DNS 等方式已经获取了对方的 IP 地址。因此网络层的源和目的地址已经知道。同一个网络的虚拟机需要通信,还需要知道对方虚拟机的 MAC 地址,VxLAN需要一个机制来实现类似传统网络 ARP 的功能
    • VxLAN 头部:只需要知道 VNI,这一般是直接配置在 VTEP 上的,要么是提前规划固定的,要么是根据内部报文自动生成的,也不需要担心。
    • UDP 头部:最重要的是源端口和目的端口,源端口是系统生成并管理的,目的端口也是固定的,比如 IANA 规定的 4789 端口,这部分也不需要担心。
    • 外层IP头部:外层IP头部关心的是隧道两端VTEP的IP地址,源地址可以很简单确定,目的地址是目的虚拟机所在宿主机关联的VTEP IP 地址,这个也需要由某种方式来确定。
    • 外层MAC头部:如果目的VTEP 的 IP 地址确定了,根据路由表查找到下一跳的MAC 地址可以通过经典的 ARP 方式来获取,毕竟 VTEP 网络在同一个三层,经典网络架构那一套就能直接用了。

    总结一下,一个 VxLAN 报文需要确定两个地址信息:目的虚拟机的 MAC 地址和目的 VTEP 的 IP 地址,如果 VNI 也是动态感知的,那么 VTEP 就需要一个三元组:

    (内层目的虚机MAC, VNI, 外层目的VTEP IP)

    组成为控制平面的表来记录对端地址可达情况。VXLAN有着与传统以太网非常相似的MAC学习机制,当VTEP接收到VXLAN报文后,会记录源VTEP的IP、虚拟机MAC和VNI到本地MAC表中,这样当VTEP接收到目的MAC为此虚拟机的MAC时,就可以进行VXLAN封装并转发。VXLAN学习地址的时候仍然保存着二层协议的特征,节点之间不会周期性的交换各自的转发表。对于不认识的MAC地址,VXLAN一般依靠组播或控制中心来获取路径信息。组播的概念是同个 VxLAN 网络的 VTEP 加入到同一个组播网络,如果需要知道以上信息,就在组内发送多播来查询;控制中心的概念是在某个集中式的地方保存了所有虚拟机的上述信息,自动化告知 VTEP 它需要的信息。

    ####组播方式

    每个多播组对应一个多播IP地址,vtep 建立的时候会通过配置加入到多播组(具体做法取决于实现),往这个多播IP地址发送的报文会发给多播组的所有主机。为什么要使用多播?因为vxlan的底层网络是三层的,广播地址无法穿越三层网络,要给vxlan 网络所有vtep发送报文只能通过多播。 通过组播的方式承载ARP的广播报文可以实现整个VxLAN网络下的地址解析以及VSI的MAC地址学习,在这个过程中,只需要有一次多播,因为VTEP有自动学习的能力,后续的报文都是通过单播直接发送的。也可以看到,多播报文非常浪费,每次的多播其实只有一个报文是有效的,如果某个多播组的 vtep 数量很多,这个浪费是非常大的。但是多播组也有它的实现起来比较简单,不需要中心化的控制,只要底层网络支持多播,只需配置好多播组就能自动发现了。因为并不是所有的网络设备都支持多播,再加上多播方式带来的报文浪费,在实际生产中这种方式很少用到。综上,VXLAN和传统VLAN网络数据平面一样,数据经过未知单播泛洪->MAC表项及ARP表项建立->单播转发的过程,我们称之为自学习模式。但自学习方式过于简单,其大量的泛洪报文以及无法智能调整的缺点,使得这样的控制平面构建方式不适合SDN网络。

    ####控制器方式

    VTEP发送报文最关键的就是知道对方虚拟机的 MAC 地址和虚拟机所在主机的 VTEP IP 地址,如果实现知道这两个信息,那么就不需要多播了。SDN最大的特点就是转控分离,集中控制。按照这个指导思想,将控制功能单独剥离出来成为一个单独的设备便是很自然的事了。这个设备就是 Controller。Controller可以是一个或者一组硬件设备,也可以是一套软件。Controller与网络中所有设备建立连接,整个VXLAN网络的数据转发都由Controller来管理。Controller与设备连接的接口称为南向接口,可以使用OpenFlow、Netconf等协议;对用户提供服务的接口称为北向接口,也可以提供API以便与其他管理平台对接或进行深度开发。基于Controller的南向接口,可以通过OpenFlow或OVSDB协议的方式向VTEP设备下发远端MAC地址表项。具体不在这里进行展开讲述。

    BUM报文转发

    前面描述的报文转发过程都是已知单播报文转发,如果VTEP收到一个未知地址的BUM报文如何处理呢。与传统以太网BUM报文转发类似,VTEP会通过泛洪的方式转发流量。BUM(Broadcast, Unknown-unicast, Multicast)即广播、未知单播、组播流量。根据对泛洪流量的复制方式不同可分为单播路由方式(头端复制)和组播路由方式(核心复制)两种。

    单播路由方式泛洪(头端复制)

    在头端复制方式下,VTEP负责复制报文,采用单播方式将复制后的报文通过本地接口发送给本地站点,并通过VXLAN隧道发送给VXLAN内的所有远端VTEP。

    如下图所示,当VTEP 1上的VM 1发出BUM报文后,VTEP 1判断数据所属的VXLAN,通过该VXLAN内所有本地接口和VXLAN Tunnel转发报文。通过VXLAN Tunnel转发报文时,封装VXLAN头、UDP头和IP头,将泛洪报文封装于单播报文中,发送到VXLAN内的所有远端VTEP。

    远端VTEP收到VXLAN报文后,解封装报文,将原始数据在本地站点的VXLAN内泛洪。为避免环路,远端VTEP从VXLAN隧道上接收到报文后,不会再将其泛洪到其他的VXLAN隧道。

    通过头端复制完成BUM报文的广播,不需要依赖组播路由协议。

    组播路由方式泛洪(核心复制)

    组播路由方式的组网中同一个VXLAN内的所有VTEP都加入同一个组播组,利用组播路由协议(如PIM)在IP网络上为该组播建立组播转发表项,VTEP上相应生成一个组播隧道。

    与头端复制方式不同,当VTEP 1上的VM 1发出BUM报文后,VTEP 1不仅在本地站点内泛洪,还会为其封装组播目的IP地址,封装后的报文根据已建立的组播转发表项转发到IP网络。

    在组播报文到达IP网络中的中间设备时,该设备根据已建立的组播表项对报文进行复制并转发。

    远端VTEP(VTEP 2和VTEP 3)接收到报文后,解封装报文,将原始的数据帧在本地站点的指定VXLAN泛洪。为了避免环路,远端VTEP从VXLAN隧道上接收到报文后,不会再将其泛洪到其他的VXLAN隧道。

    由于泛洪流量使用了组播技术,所以整个组网中的网络设备需要支持组播路由协议(如PIM等)来建立组播路径以便组播报文转发。

    参考文献

    1. vxlan 协议原理简介
    2. 华为悦读汇技术发烧友:认识VXLAN
    3. 华为VxLAN技术白皮书
    展开全文
  • TL-WR740N V5V6V7_130704 支持802.1x协议 固件
  • c语言实现IPV6的服务端程序与客户端程序,直接编译可使用,亲测服务端与客户端正常,并且包含与IPV4实现的具体区别详解。
  • IPv6邻居发现协议ND学习笔记

    千次阅读 2020-08-19 22:58:27
    ND(邻居发现协议协议是IPv6非常重要的基础协议协议使用ICMPv6报文,综合了IPv4中一些协议功能,如:ARP,ICMP路由器发现和ICMP重定向等,在第三层上实现的,可以独立于数据链路层协议不受这层协议的影响 ...
  • 匿名上位机通信协议

    万次阅读 多人点赞 2018-03-11 11:30:59
    @桢头--功能字--长度--数据(一个或多个,具体看协议说明)-校验 @前2个字节为帧头0xAAAA @第3个字节为帧ID,也就是功能字,应设置为0xF1~0xFA中的一个 @第4个字节为报文数据长度(dlc) @第5个字节开始...
  • 网络中的节点同时支持IPv4和IPv6协议栈,源节点根据目的节点的不同选用不同的协议栈,而网络设备根据报文的协议类型选择不同的协议栈进行处理和转发。 4. 隧道技术 隧道技术是通过将一种IP 协议数据包嵌套在另一种IP...
  • IPv6将成为5G和物联网基础协议

    千次阅读 2017-07-04 14:57:00
    去年11月在韩国召开的IETF大会上发布了一个声明,希望未来的互联网协议标准全部基于IPv6来制定,新设备和新的扩展协议不再兼容IPv4。 据预测,未来两到三年内全球发达国家的IPv6用户数将会超过IPv4用户。我国在“十...
  • IPv4&IPv6双重协议

    千次阅读 2016-05-21 12:07:05
    IPV4 TCP客户与IPV6服务器之间的通信: 1 启动IPV6服务器,创建套接监听...IPV4客户不能调用Connect或sendto指定IPV6的地址,因为在IPV4的sockaddr_in结构里的4字节的in_addr结构中,放不下16字节的v6地址
  • IPv6协议栈安装方法

    千次阅读 2013-09-29 09:20:20
    IPv6协议栈安装方法 注:转载自清华大学IPv6 http://ipv6.tsinghua.edu.cn/guide/ipv6-an-zhuang/ Windows Vista 和 Windows 7 以上版本 无须配置,自动支持IPv6。 Windows XP 在命令行窗口下,输入下列命令:...
  • Linux内核关闭IPv6协议

    2015-07-08 10:47:00
    即使在grub.conf文件中不添加ipv6的任何信息,向/sys/module/ipv6/parameters/disable_ipv6文件中写入也不能控制IPv6协议,建议使用proc目录下的变量控制。 第三种方式: 在/proc/sys/net/ipv6/conf/目录下有下面...
  • KEPServerEX-V6中文说明书包含所有驱动配置以及窗口菜单应用讲解bacnet、opc、odbc、modbus、自定义协议
  • 在IPv4向IPv6过渡过程中,PPPoE现有标准对IPv6协议栈的支持还不够完善,NCP在对网络层IPv6协议配置方面,无法完成IPv6前缀下传、IPv6地址、DNS地址等参数的配置。
  • 3.2 OMA DM 协议与OMA CP 协议 3.4 开发能同时支持OMA DM 和OMA CP 的软件产品 3.5 手机供应商对OMA DM的支持 8.10 OMA DM 的主要业务与客户需求 8.11 OMA DM 客户端软件及其部署难题 8.12 OMA DM 的服务器性能...
  • nfcPro_kgf_v6_2020081803.exe

    2020-08-26 09:32:51
    nfcPro_kgf_v6_2020081803:一款图形化NFC协议安全分析工具,整体程序基于libnfc完成。研究水卡、饭卡、校园一卡通、公交卡的利器。
  • 针对IPv6协议DNS服务器的剖析

    千次阅读 2011-03-11 13:31:00
    因为这个原因,所以我们急切需要IPv6协议来改善这个情况。那么IPv6如何进行DNS服务器的分配呢?下面我们就此来深入的剖析一下其中的原理。希望能对大家有所帮助。   IPv6协议域名系统的体系结构是什么样...
  • KEPServerEX_V6的BACnet IP驱动连接文档,KEPServerEX实现数据库表格实时刷新案例,KEPServerEX-使用Datalogger和数据库通信操作文档V6,里面都是bacnet协议连接kepserver文档
  • IPv6协议详解(一)

    2009-08-22 23:58:06
    IPv6协议详解(一)    IPv6协议是IP协议的第6版本,于1992年开始开发,1998年12月发布标准RFC2460。  在运营领域,国外部分电信运营商已经建立IPv6网络,并开始提供接入服务以及一些基于IPv6的增值业务。我国...
  • IPV6

    千次阅读 2018-11-10 20:29:02
    【3】双栈—一台设备同时连接IPV4/IPV6网络,若目标IP为V6地址使用V6的源ip,通过V6的路由表传递;V4同理; 当进行DNS查询时,优先查询V6网络中的DNS 服务器; IPV6路由 IPV6静态路由协议 (1)普通静态路由 ...
  • HTTP.Analyzer.Full.Edition.v6 注册版 协议分析利器

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,146
精华内容 4,458
关键字:

v6协议