精华内容
下载资源
问答
  • MAC首部 IP首部 TCP首部介绍

    千次阅读 2020-07-24 12:12:46
    了解数据传输过程中的MAC首部,IP数据包首部,TCP首部。了解结构是基本,为了直观显示,本文采用抓包的方式逐层验证这些首部结构。同时,列出这些首部结构在系统中C语言结构体的实现。

    0. 前言

    因为原生套接字编程,需要了解数据传输过程中的MAC帧首部,IP数据包首部,TCP首部。了解结构是基本,为了直观显示,本文采用抓包的方式逐层验证这些首部结构。同时,列出这些首部结构在系统中C语言结构体的实现。

    抓包工具采用:tcpdump + wireshark

    工具使用方法:超详细的网络抓包神器 tcpdump 使用指南实战!我用 Wireshark 让你“看见“ TCP

    (“实战!我用 Wireshark 让你“看见TCP” 是一篇很好的文章,介绍了TCP的三次握手;握手过程中包丢失的几种情况等)

    1. 使用tcpdump抓取数据包

      # 数据准备
      sudo tcpdump -nn tcp and host www.baidu.com -w http.pcap
      curl www.baidu.com
      
    2. 使用wireshark展示数据包内容
      在这里插入图片描述


    1. 背景介绍

    了解主机之间的数据传输过程

    在这里插入图片描述

    了解数据封装和解封装过程,下面图片传输层使用TCP进行举例。

    在这里插入图片描述

    这里值得特别注意的是,在每一层,有不同的英文术语来对应包的概念,比如在 TCP 层的包叫做 Segment,在 IP 层的叫做 Packet,在链路层的叫做 Frame,另外和 TCP 位于同一层的 UDP 包我们一般叫做 Datagram


    3. MAC首部

    以太帧有很多种类型。不同类型的帧具有不同的格式和MTU值。

    如何选择使用哪种格式的帧我不知道。下面仅仅看Ethernet_II 帧格式

    在这里插入图片描述

    字段含义
    目的地址接收帧的网络适配器的物理地址(MAC 地址),为 6 个字节(48 比特)。作用是当网卡接收到一个数据帧时,首先会检查该帧的目的地址,是否与当前适配器的物理地址相同,如果相同,就会进一步处理;如果不同,则直接丢弃。
    源地址发送帧的网络适配器的物理地址(MAC 地址),为 6 个字节(48 比特)。
    类型上层协议的类型。由于上层协议众多,所以在处理数据的时候必须设置该字段,标识数据交付哪个协议处理。例如,字段为 0x0800 时,表示将数据交付给 IP 协议。
    数据也称为效载荷,表示交付给上层的数据。以太网帧数据长度最小为 46 字节,最大为 1500 字节。如果不足 46 字节时,会填充到最小长度。最大值也叫最大传输单元(MTU)。 在 Linux 中,使用 ifconfig 命令可以查看该值,通常为 1500。
    帧检验序列 FCS检测该帧是否出现差错,占 4 个字节(32 比特)。发送方计算帧的循环冗余码校验(CRC)值,把这个值写到帧里。接收方计算机重新计算 CRC,与 FCS 字段的值进行比较。如果两个值不相同,则表示传输过程中发生了数据丢失或改变。这时,就需要重新传输这一帧。

    在这里插入图片描述

    // 结构体位置:/usr/include/net/ethernet.h
    struct ether_header
    {
      uint8_t  ether_dhost[ETH_ALEN];	/* destination eth addr	*/
      uint8_t  ether_shost[ETH_ALEN];	/* source ether addr	*/
      uint16_t ether_type;		        /* packet type ID field	*/
    } __attribute__ ((__packed__));
    

    4. IP首部

    IP首部分为IPv4首部和IPv6首部,下面仅仅列出IPv4首部。

    IPv4 数据报头字段如图所示。
    在这里插入图片描述

    IP 报头的最小长度为 20 字节,上图中每个字段的含义如下:

    1) 版本(version)

    占 4 位,表示 IP 协议的版本。通信双方使用的 IP 协议版本必须一致。

    2) 首部长度(网际报头长度IHL)

    占 4 位,可表示的最大十进制数值是 15。这个字段所表示数的单位是 32 位字长(1 个 32 位字长是 4 字节)。因此,当 IP 的首部长度为 1111 时(即十进制的 15),首部长度就达到 60 字节。当 IP 分组的首部长度不是 4 字节的整数倍时,必须利用最后的填充字段加以填充。

    数据部分永远在 4 字节的整数倍开始,这样在实现 IP 协议时较为方便。首部长度限制为 60 字节的缺点是,长度有时可能不够用,之所以限制长度为 60 字节,是希望用户尽量减少开销。最常用的首部长度就是 20 字节(即首部长度为 0101),这时不使用任何选项。

    3) 区分服务(tos)

    也被称为服务类型,占 8 位,用来获得更好的服务。这个字段在旧标准中叫做服务类型,但实际上一直没有被使用过。1998 年 IETF 把这个字段改名为区分服务(Differentiated Services,DS)。只有在使用区分服务时,这个字段才起作用。

    4) 总长度(totlen)

    首部和数据之和,单位为字节。总长度字段为 16 位,因此数据报的最大长度为 2^16-1=65535 字节。

    5) 标识(identification)

    用来标识数据报,占 16 位。IP 协议在存储器中维持一个计数器。每产生一个数据,计数器就加 1,并将此值赋给标识字段。当数据报的长度超过网络的 MTU,而必须分片时,这个标识字段的值就被复制到所有的数据报的标识字段中。具有相同的标识字段值的分片报文会被重组成原来的数据报

    6) 标志(flag)

    占 3 位。第一位未使用,其值为 0。第二位称为 DF(不分片),表示是否允许分片。取值为 0 时,表示允许分片;取值为 1 时,表示不允许分片。第三位称为 MF(更多分片),表示是否还有分片正在传输,设置为 0 时,表示没有更多分片需要发送,或数据报没有分片。

    7) 片偏移(offsetfrag)

    占 13 位。**当报文被分片后,该字段标记该分片在原报文中的相对位置。**片偏移以 8 个字节为偏移单位。所以,除了最后一个分片,其他分片的偏移值都是 8 字节(64 位)的整数倍。

    8) 生存时间(TTL)

    表示数据报在网络中的寿命,占 8 位。该字段由发出数据报的源主机设置。其目的是防止无法交付的数据报无限制地在网络中传输,从而消耗网络资源。

    路由器在转发数据报之前,先把 TTL 值减 1。若 TTL 值减少到 0,则丢弃这个数据报,不再转发。因此,TTL 指明数据报在网络中最多可经过多少个路由器。TTL 的最大数值为 255。若把 TTL 的初始值设为 1,则表示这个数据报只能在本局域网中传送。

    9) 协议

    表示该数据报文所携带的数据所使用的协议类型,占 8 位。该字段可以方便目的主机的 IP 层知道按照什么协议来处理数据部分。不同的协议有专门不同的协议号。

    例如,TCP 的协议号为 6,UDP 的协议号为 17,ICMP 的协议号为 1。

    10) 首部检验和(checksum)

    用于校验数据报的首部,占 16 位。数据报每经过一个路由器,首部的字段都可能发生变化(如TTL),所以需要重新校验。而数据部分不发生变化,所以不用重新生成校验值。

    11) 源地址

    表示数据报的源 IP 地址,占 32 位。

    12) 目的地址

    表示数据报的目的 IP 地址,占 32 位。该字段用于校验发送是否正确。

    13) 可选字段

    该字段用于一些可选的报头设置,主要用于测试、调试和安全的目的。这些选项包括严格源路由(数据报必须经过指定的路由)、网际时间戳(经过每个路由器时的时间戳记录)和安全限制。

    14) 填充

    由于可选字段中的长度不是固定的,使用若干个 0 填充该字段,可以保证整个报头的长度是 32 位的整数倍。

    15) 数据部分

    表示传输层的数据,如保存 TCP、UDP、ICMP 或 IGMP 的数据。数据部分的长度不固定。

    我不进行标注了,标注比较麻烦,可自行对比数据,我从网上找了张图。

    在这里插入图片描述

    IP头部结构体实现有两种定义方式,如下所示。这两种有什么区别?分别在什么场合下使用?

    我目前仅仅看到这一篇:Difference between struct ip and struct iphdr

    struct ip and struct iphdr are two different definitions of the same underlying structure, brought in from different places.

    struct ip is defined in <netinet/ip.h>, which is a reasonably standard header on UNIX systems.

    struct iphdr is defined in <linux/ip.h>. This header (and structure) are Linux-specific, and will not be present in other operating systems.

    If you’re not sure which one to use, use struct ip; code which uses this structure is more likely to be portable to non-Linux systems.

    // 结构体位置 /usr/include/netinet/ip.h
    struct iphdr
      {
    #if __BYTE_ORDER == __LITTLE_ENDIAN
        unsigned int ihl:4;
        unsigned int version:4;
    #elif __BYTE_ORDER == __BIG_ENDIAN
        unsigned int version:4;
        unsigned int ihl:4;
    #else
    # error	"Please fix <bits/endian.h>"
    #endif
        uint8_t tos;
        uint16_t tot_len;
        uint16_t id;
        uint16_t frag_off;
        uint8_t ttl;
        uint8_t protocol;
        uint16_t check;
        uint32_t saddr;
        uint32_t daddr;
        /*The options start here. */
      };
    
    struct ip
      {
    #if __BYTE_ORDER == __LITTLE_ENDIAN
        unsigned int ip_hl:4;		/* header length */
        unsigned int ip_v:4;		/* version */
    #endif
    #if __BYTE_ORDER == __BIG_ENDIAN
        unsigned int ip_v:4;		/* version */
        unsigned int ip_hl:4;		/* header length */
    #endif
        uint8_t ip_tos;			/* type of service */
        unsigned short ip_len;		/* total length */
        unsigned short ip_id;		/* identification */
        unsigned short ip_off;		/* fragment offset field */
    #define	IP_RF 0x8000			/* reserved fragment flag */
    #define	IP_DF 0x4000			/* dont fragment flag */
    #define	IP_MF 0x2000			/* more fragments flag */
    #define	IP_OFFMASK 0x1fff		/* mask for fragmenting bits */
        uint8_t ip_ttl;			/* time to live */
        uint8_t ip_p;			/* protocol */
        unsigned short ip_sum;		/* checksum */
        struct in_addr ip_src, ip_dst;	/* source and dest address */
      };
    

    5. TCP首部

    在这里插入图片描述

    1. 源端口和目的端口:各占2个字节.端口是传输层和应用层的服务接口。传输层的复用和分用功能都有要通过端口才能实现。

    2. 序号:占4个字节,序号范围是(0,2^32 - 1),共2^32 (即4294967296)个序号。序号增加到2^32-1后,下一个序号就又回到0。也就是说,序号使用mod 2^32运算。TCP是面向字节流的。在一个TCP连接中传送的字节流中的每一个字节都按顺序编号。整个要传送的字节流的起始序号必须在连接建立时设置。首部中的序号字段值则是指的是本报文段所发送的数据的第一个字节的序号。例如,一报文段的序号是301,而接待的数据共有100字节。这就表明:本报文段的数据的第一个字节的序号是301,最后一个字节的序号是400。显然,下一个报文段(如果还有的话)的数据序号应当从401开始,即下一个报文段的序号字段值应为401。这个字段的序号也叫“报文段序号”。

    3. 确认号:占4字节,是期望收到对方下一个报文段的第一个数据字节的序号。例如,B正确收到了A发送过来的一个报文段,其序号字段值是501,而数据长度是200字节(序号501~700),这表明B正确收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701。注意,现在确认号不是501,也不是700,而是701。

      若确认号为N,则表明,起始序号到序号N-1为止的所有数据都已正确收到,期望的序号是N。

    4. 数据偏移(即首部长度):占4位,它指出TCP报文段的数据起始处距离TCP报文段的起始处有多远。这个字段实际上是指出TCP报文段的首部长度。由于首部中还有长度不确定的选项字段,因此数据偏移字段是必要的,但应注意,“数据偏移”的单位是32位字(即以4字节的字为计算单位)。由于4位二进制数能表示的最大十进制数字是15,因此数据偏移的最大值是60字节,这也是TCP首部的最大字节(即选项长度不能超过40字节)。

    5. 保留:占6位,保留为今后使用,但目前应置为0。

    6. 紧急URG(URGent):占1位, 当URG=1时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快发送(相当于高优先级的数据),而不要按原来的排队顺序来传送。例如,已经发送了很长的一个程序要在远地的主机上运行。但后来发现了一些问题,需要取消该程序的运行,因此用户从键盘发出中断命令。如果不使用紧急数据,那么这两个字符将存储在接收TCP的缓存末尾。只有在所有的数据被处理完毕后这两个字符才被交付接收方的应用进程。这样做就浪费了很多时间。当URG置为1时,发送应用进程就告诉发送方的TCP有紧急数据要传送。于是发送方TCP就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍然是普通数据。这时要与首部中紧急指针(Urgent Pointer)字段配合使用。

    7. 确认ACK(ACKnowledgment):占1位, 仅当ACK = 1时确认号字段才有效,当ACK = 0时确认号无效。TCP规定,在连接建立后所有的传送的报文段都必须把ACK置为1。

    8. 推送PSH(Push):占1位,当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下,TCP就可以使用推送(push)操作。这时,发送方TCP把PSH置为1,并立即创建一个报文段发送出去。接收方TCP收到PSH=1的报文段,就尽快地(即“推送”向前)交付接收应用进程。而不用再等到整个缓存都填满了后再向上交付。

    9. 复位RST(ReSet):占1位,当RST=1时,表名TCP连接中出现了严重错误(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立传输连接。RST置为1还用来拒绝一个非法的报文段或拒绝打开一个连接。

    10. 同步SYN(SYNchronization):占1位,在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使SYN=1和ACK=1,因此SYN置为1就表示这是一个连接请求或连接接受报文。

    11. 终止FIN(FINish):占1位,用来释放一个连接。当FIN=1时,表明此报文段的发送发的数据已发送完毕,并要求释放运输连接。

    12. 窗口:占2个字节,窗口值是(0,2^16-1)之间的整数。窗口指的是发送本报文段的一方的接受窗口(而不是自己的发送窗口)。窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。总之,窗口值作为接收方让发送方设置其发送窗口的依据。

      例如,发送了一个报文段,其确认号是701,窗口字段是1000.这就是告诉对方:“从701算起,我(即发送方报文段的一方)的接收缓存空间还可接受1000个字节数据(字节序号是701~1700),你在给我发数据时,必须考虑到这一点。”

      窗口字段明确指出了现在允许对方发送的数据量。窗口值经常在动态变化。

    13. 检验和:占2个字节,检验和字段检验的范围包括首部和数据这两部分。和UDP用户数据报一样,在计算检验和时,要在TCP报文段的前面加上12字节的伪首部。伪首部的格式和UDP用户数据报的伪首部一样。但应把伪首部第4个字段中的17改为6(TCP的协议号是6);把第5字段中的UDP中的长度改为TCP长度。接收方收到此报文段后,仍要加上这个伪首部来计算检验和。若使用TPv6,则相应的伪首部也要改变。

    14. 紧急指针:占2个字节,紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据) 。因此,在紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时,TCP就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为0时也可以发送紧急数据。

    15. 选项:长度可变,最长可达40字节。当没有使用“选项”时,TCP的首部长度是20字节。其最大长度可根据TCP首部长度进行推算。TCP首部长度用4位表示,那么选项部分最长为:(2^4-1)*4-20=40字节。

      • MSS最大报文段长度(Maxium Segment Size):TCP最初只规定了一种选项,即最大报文段长度MSS(Maximum Segment Szie)。注意MSS这个名词含义。MSS是每一个TCP报文段中的数据字段的最大长度。数据字段加上TCP首部才等于整个的TCP报文段。所以MSS并不是整个TCP报文段的最大长度,而是“TCP报文段长度减去TCP首部长度”。

      • 为什么要规定一个最大报文长度MSS呢?

        • 这并不是考虑接受方的接收缓存可能存放不下TCP报文段中的数据。实际上,MSS与接收窗口值没有关系。我们知道,TCP报文段的数据部分,至少要加上40字节的首部(TCP首部20字节和IP首部20字节,这里还没有考虑首部中的可选部分)才能组装成一个IP数据报。若选择较小的MSS长度,网络的利用率就降低。设想在极端情况下,当TCP报文段只含有1字节的数据时,在IP层传输的数据报的开销至少有40字节(包括TCP报文段的首部和IP数据报的首部)。这样,对网络的利用率就不会超过1/41。到了数据链路层还要加上一些开销。但反过来,若TCP报文段非常长,那么在IP层传输时就有可能要分解成多个短数据报片。在终点要把收到的各个短数据报片组成成原来的TCP报文段,当传输出错时还要进行重传,这些也都会使开销增大。
        • 因此,MSS应尽可能大些,只要在IP层传输时不需要分片就行。由于IP数据报所经历的路径是动态变化的,因此在这条路径上确定的不需要的分片的MSS,如果改走另一条路径就可能需要进行分片。因此最佳的MSS是很难确定的。在连接过程中,双方都把自己能够支持的MSS写入这一字段,以后就按照这个数值传输数据,两个传送方向可以有不同的MSS值。若主机未填写这一项,则MSS的默认值是536字节长。因此,所有在互联网上的主机都应该接受的报文段长度是536+20(固定首部长度)=556字节。
      • 其他选项:

        • 窗口扩大选项

          (Windows Scaling):是为了扩大窗口。我们知道,TCP首部中窗口字段长度是16位,因此最大的窗口大小为64K字节。虽然这对早期的网络是足够用的,但对于包含卫星信道的网络,传播时延和宽带都很大,要获得高吞吐量需要更大的窗口大小。

          • 窗口扩大选项占3字节,其中有一个字节表示移位值S。新的窗口值等于TCP首部中的窗口位数从16增大到(16+S)。移位值允许使用的最大值是14,相当于窗口最大值增大到2(16+14)-1=230-1。
          • 窗口扩大选项可以在双方初始建立TCP连接时进行协商。如果连接的某一端实现了窗口扩大,当它不再需要扩大其窗口时,可发送S=0选项,使窗口大小回到16。
        • 时间戳选项(Timestamps):占10字节,其中最主要的字段是时间戳字段(4字节)和时间戳回送回答字段(4字节)。时间戳选项有以下两个概念:

          • 用来计算往返时间RTT。发送方在发送报文段时把当前时钟的时间值放入时间戳字段,接收方在确认该报文段时把时间戳字段复制到时间戳回送回答字段。因此,发送方在收到确认报文后,可以准确地计算出RTT来。
          • 用于处理TCP序号超过232的情况,这又称为防止序号绕回PAWS。我们知道,TCP报文段的序号只有32位,而每增加232个序号就会重复使用原来用过的序号。当使用高速网络时,在一次TCP连接的数据传送中序号很可能被重复使用。例如,当使用1.5Mbit/s的速度发送报文段时,序号重复要6小时以上。但若用2.5Gbit/s的速率发送报文段,则不到14秒钟序号就会重复。为了使接收方能够把新的报文段和迟到很久的报文段区分开,则可以在报文段中加上这种时间戳。
        • SACK选择确认项(Selective Acknowledgements):用来确保只重传缺少的报文段,而不是重传所有报文段。比如主机A发送报文段1、2、3,而主机B仅收到报文段1、3。那么此时就需要使用SACK选项来告诉发送方只发送丢失的数据。那么又如何指明丢失了哪些报文段呢?使用SACK需要两个功能字节。一个表示要使用SACK选项,另一个指明这个选项占用多少字节。描述丢失的报文段2,是通过描述它的左右边界报文段1、3来完成的。而这个1、3实际上是表示序列号,所以描述一个丢失的报文段需要64位即8个字节的空间。那么可以推算整个选项字段最多描述(40-2)/8=4个丢失的报文段。

        • NOP(NO-Operation):它要求选项部分中的每种选项长度必须是4字节的倍数,不足的则用NOP填充。同时也可以用来分割不同的选项字段。如窗口扩大选项和SACK之间用NOP隔开。

    1. 填充:为了使整个首部长度是4字节的整数倍。

    在这里插入图片描述

    // 结构体位置:/usr/include/netinet/tcp.h
    // union中有两个结构体,给目标赋值的时候,可以采用两种方式
    // tcph->dest = htons (80); or tcph->th_dest = htons (80);
    // 这两个变量在同一位置
    // 应为过程中会有类型强制转换,便于取内容;所以对字节序用# if
    struct tcphdr
      {
        __extension__ union
        {
          struct
          {
    	uint16_t th_sport;	/* source port */
    	uint16_t th_dport;	/* destination port */
    	tcp_seq th_seq;		/* sequence number */
    	tcp_seq th_ack;		/* acknowledgement number */
    # if __BYTE_ORDER == __LITTLE_ENDIAN
    	uint8_t th_x2:4;	/* (unused) */
    	uint8_t th_off:4;	/* data offset */
    # endif
    # if __BYTE_ORDER == __BIG_ENDIAN
    	uint8_t th_off:4;	/* data offset */
    	uint8_t th_x2:4;	/* (unused) */
    # endif
    	uint8_t th_flags;
    # define TH_FIN	0x01
    # define TH_SYN	0x02
    # define TH_RST	0x04
    # define TH_PUSH	0x08
    # define TH_ACK	0x10
    # define TH_URG	0x20
    	uint16_t th_win;	/* window */
    	uint16_t th_sum;	/* checksum */
    	uint16_t th_urp;	/* urgent pointer */
          };
          struct
          {
    	uint16_t source;
    	uint16_t dest;
    	uint32_t seq;
    	uint32_t ack_seq;
    # if __BYTE_ORDER == __LITTLE_ENDIAN
    	uint16_t res1:4;
    	uint16_t doff:4;
    	uint16_t fin:1;
    	uint16_t syn:1;
    	uint16_t rst:1;
    	uint16_t psh:1;
    	uint16_t ack:1;
    	uint16_t urg:1;
    	uint16_t res2:2;
    # elif __BYTE_ORDER == __BIG_ENDIAN
    	uint16_t doff:4;
    	uint16_t res1:4;
    	uint16_t res2:2;
    	uint16_t urg:1;
    	uint16_t ack:1;
    	uint16_t psh:1;
    	uint16_t rst:1;
    	uint16_t syn:1;
    	uint16_t fin:1;
    # else
    #  error "Adjust your <bits/endian.h> defines"
    # endif
    	uint16_t window;
    	uint16_t check;
    	uint16_t urg_ptr;
          };
        };
    };
    

    附录

    涉及但未介绍内容

    • 从网上拼凑而来,皆是二手来源。

    • 所有的代码/文档见:github

    • 这些首部的应用,因为还需要更多的背景支持,这里暂不介绍。

    • 暂不介绍循环冗余码校验,首部校验和,字节序。

    参考文章

    超详细的网络抓包神器 tcpdump 使用指南

    实战!我用 Wireshark 让你“看见“ TCP

    展开全文
  • 原文链接:MAC首部 IP首部 TCP首部介绍 文章目录 0. 前言1. 背景介绍3. MAC首部4. IP首部5. TCP首部附录涉及但未介绍内容 参考文章 0. 前言 因为原生套接字编程,需要了解数据传输过程中的MAC帧首部,IP...

    原文链接:MAC首部 IP首部 TCP首部介绍

    0. 前言

    因为原生套接字编程,需要了解数据传输过程中的MAC帧首部,IP数据包首部,TCP首部。了解结构是基本,为了直观显示,本文采用抓包的方式逐层验证这些首部结构。同时,列出这些首部结构在系统中C语言结构体的实现。

    抓包工具采用:tcpdump + wireshark

    工具使用方法:超详细的网络抓包神器 tcpdump 使用指南实战!我用 Wireshark 让你“看见“ TCP

    (“实战!我用 Wireshark 让你“看见TCP” 是一篇很好的文章,介绍了TCP的三次握手;握手过程中包丢失的几种情况等)

    1. 使用tcpdump抓取数据包

      # 数据准备
      sudo tcpdump -nn tcp and host www.baidu.com -w http.pcap
      curl www.baidu.com
      
         
      • 1
      • 2
      • 3
    2. 使用wireshark展示数据包内容
      在这里插入图片描述


    1. 背景介绍

    了解主机之间的数据传输过程

    在这里插入图片描述

    了解数据封装和解封装过程,下面图片传输层使用TCP进行举例。

    在这里插入图片描述

    这里值得特别注意的是,在每一层,有不同的英文术语来对应包的概念,比如在 TCP 层的包叫做 Segment,在 IP 层的叫做 Packet,在链路层的叫做 Frame,另外和 TCP 位于同一层的 UDP 包我们一般叫做 Datagram


    3. MAC首部

    以太帧有很多种类型。不同类型的帧具有不同的格式和MTU值。

    如何选择使用哪种格式的帧我不知道。下面仅仅看Ethernet_II 帧格式

    在这里插入图片描述

    字段含义
    目的地址接收帧的网络适配器的物理地址(MAC 地址),为 6 个字节(48 比特)。作用是当网卡接收到一个数据帧时,首先会检查该帧的目的地址,是否与当前适配器的物理地址相同,如果相同,就会进一步处理;如果不同,则直接丢弃。
    源地址发送帧的网络适配器的物理地址(MAC 地址),为 6 个字节(48 比特)。
    类型上层协议的类型。由于上层协议众多,所以在处理数据的时候必须设置该字段,标识数据交付哪个协议处理。例如,字段为 0x0800 时,表示将数据交付给 IP 协议。
    数据也称为效载荷,表示交付给上层的数据。以太网帧数据长度最小为 46 字节,最大为 1500 字节。如果不足 46 字节时,会填充到最小长度。最大值也叫最大传输单元(MTU)。 在 Linux 中,使用 ifconfig 命令可以查看该值,通常为 1500。
    帧检验序列 FCS检测该帧是否出现差错,占 4 个字节(32 比特)。发送方计算帧的循环冗余码校验(CRC)值,把这个值写到帧里。接收方计算机重新计算 CRC,与 FCS 字段的值进行比较。如果两个值不相同,则表示传输过程中发生了数据丢失或改变。这时,就需要重新传输这一帧。

    在这里插入图片描述

    // 结构体位置:/usr/include/net/ethernet.h
    struct ether_header
    {
      uint8_t  ether_dhost[ETH_ALEN];	/* destination eth addr	*/
      uint8_t  ether_shost[ETH_ALEN];	/* source ether addr	*/
      uint16_t ether_type;		        /* packet type ID field	*/
    } __attribute__ ((__packed__));
    
     
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4. IP首部

    IP首部分为IPv4首部和IPv6首部,下面仅仅列出IPv4首部。

    IPv4 数据报头字段如图所示。
    在这里插入图片描述

    IP 报头的最小长度为 20 字节,上图中每个字段的含义如下:

    1) 版本(version)

    占 4 位,表示 IP 协议的版本。通信双方使用的 IP 协议版本必须一致。

    2) 首部长度(网际报头长度IHL)

    占 4 位,可表示的最大十进制数值是 15。这个字段所表示数的单位是 32 位字长(1 个 32 位字长是 4 字节)。因此,当 IP 的首部长度为 1111 时(即十进制的 15),首部长度就达到 60 字节。当 IP 分组的首部长度不是 4 字节的整数倍时,必须利用最后的填充字段加以填充。

    数据部分永远在 4 字节的整数倍开始,这样在实现 IP 协议时较为方便。首部长度限制为 60 字节的缺点是,长度有时可能不够用,之所以限制长度为 60 字节,是希望用户尽量减少开销。最常用的首部长度就是 20 字节(即首部长度为 0101),这时不使用任何选项。

    3) 区分服务(tos)

    也被称为服务类型,占 8 位,用来获得更好的服务。这个字段在旧标准中叫做服务类型,但实际上一直没有被使用过。1998 年 IETF 把这个字段改名为区分服务(Differentiated Services,DS)。只有在使用区分服务时,这个字段才起作用。

    4) 总长度(totlen)

    首部和数据之和,单位为字节。总长度字段为 16 位,因此数据报的最大长度为 2^16-1=65535 字节。

    5) 标识(identification)

    用来标识数据报,占 16 位。IP 协议在存储器中维持一个计数器。每产生一个数据,计数器就加 1,并将此值赋给标识字段。当数据报的长度超过网络的 MTU,而必须分片时,这个标识字段的值就被复制到所有的数据报的标识字段中。具有相同的标识字段值的分片报文会被重组成原来的数据报

    6) 标志(flag)

    占 3 位。第一位未使用,其值为 0。第二位称为 DF(不分片),表示是否允许分片。取值为 0 时,表示允许分片;取值为 1 时,表示不允许分片。第三位称为 MF(更多分片),表示是否还有分片正在传输,设置为 0 时,表示没有更多分片需要发送,或数据报没有分片。

    7) 片偏移(offsetfrag)

    占 13 位。**当报文被分片后,该字段标记该分片在原报文中的相对位置。**片偏移以 8 个字节为偏移单位。所以,除了最后一个分片,其他分片的偏移值都是 8 字节(64 位)的整数倍。

    8) 生存时间(TTL)

    表示数据报在网络中的寿命,占 8 位。该字段由发出数据报的源主机设置。其目的是防止无法交付的数据报无限制地在网络中传输,从而消耗网络资源。

    路由器在转发数据报之前,先把 TTL 值减 1。若 TTL 值减少到 0,则丢弃这个数据报,不再转发。因此,TTL 指明数据报在网络中最多可经过多少个路由器。TTL 的最大数值为 255。若把 TTL 的初始值设为 1,则表示这个数据报只能在本局域网中传送。

    9) 协议

    表示该数据报文所携带的数据所使用的协议类型,占 8 位。该字段可以方便目的主机的 IP 层知道按照什么协议来处理数据部分。不同的协议有专门不同的协议号。

    例如,TCP 的协议号为 6,UDP 的协议号为 17,ICMP 的协议号为 1。

    10) 首部检验和(checksum)

    用于校验数据报的首部,占 16 位。数据报每经过一个路由器,首部的字段都可能发生变化(如TTL),所以需要重新校验。而数据部分不发生变化,所以不用重新生成校验值。

    11) 源地址

    表示数据报的源 IP 地址,占 32 位。

    12) 目的地址

    表示数据报的目的 IP 地址,占 32 位。该字段用于校验发送是否正确。

    13) 可选字段

    该字段用于一些可选的报头设置,主要用于测试、调试和安全的目的。这些选项包括严格源路由(数据报必须经过指定的路由)、网际时间戳(经过每个路由器时的时间戳记录)和安全限制。

    14) 填充

    由于可选字段中的长度不是固定的,使用若干个 0 填充该字段,可以保证整个报头的长度是 32 位的整数倍。

    15) 数据部分

    表示传输层的数据,如保存 TCP、UDP、ICMP 或 IGMP 的数据。数据部分的长度不固定。

    我不进行标注了,标注比较麻烦,可自行对比数据,我从网上找了张图。

    在这里插入图片描述

    IP头部结构体实现有两种定义方式,如下所示。这两种有什么区别?分别在什么场合下使用?

    我目前仅仅看到这一篇:Difference between struct ip and struct iphdr

    struct ip and struct iphdr are two different definitions of the same underlying structure, brought in from different places.

    struct ip is defined in <netinet/ip.h>, which is a reasonably standard header on UNIX systems.

    struct iphdr is defined in <linux/ip.h>. This header (and structure) are Linux-specific, and will not be present in other operating systems.

    If you’re not sure which one to use, use struct ip; code which uses this structure is more likely to be portable to non-Linux systems.

    // 结构体位置 /usr/include/netinet/ip.h
    struct iphdr
      {
    #if __BYTE_ORDER == __LITTLE_ENDIAN
        unsigned int ihl:4;
        unsigned int version:4;
    #elif __BYTE_ORDER == __BIG_ENDIAN
        unsigned int version:4;
        unsigned int ihl:4;
    #else
    # error	"Please fix <bits/endian.h>"
    #endif
        uint8_t tos;
        uint16_t tot_len;
        uint16_t id;
        uint16_t frag_off;
        uint8_t ttl;
        uint8_t protocol;
        uint16_t check;
        uint32_t saddr;
        uint32_t daddr;
        /*The options start here. */
      };
    
    struct ip
      {
    #if __BYTE_ORDER == __LITTLE_ENDIAN
        unsigned int ip_hl:4;		/* header length */
        unsigned int ip_v:4;		/* version */
    #endif
    #if __BYTE_ORDER == __BIG_ENDIAN
        unsigned int ip_v:4;		/* version */
        unsigned int ip_hl:4;		/* header length */
    #endif
        uint8_t ip_tos;			/* type of service */
        unsigned short ip_len;		/* total length */
        unsigned short ip_id;		/* identification */
        unsigned short ip_off;		/* fragment offset field */
    #define	IP_RF 0x8000			/* reserved fragment flag */
    #define	IP_DF 0x4000			/* dont fragment flag */
    #define	IP_MF 0x2000			/* more fragments flag */
    #define	IP_OFFMASK 0x1fff		/* mask for fragmenting bits */
        uint8_t ip_ttl;			/* time to live */
        uint8_t ip_p;			/* protocol */
        unsigned short ip_sum;		/* checksum */
        struct in_addr ip_src, ip_dst;	/* source and dest address */
      };
    
     
    • 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

    5. TCP首部

    在这里插入图片描述

    1. 源端口和目的端口:各占2个字节.端口是传输层和应用层的服务接口。传输层的复用和分用功能都有要通过端口才能实现。

    2. 序号:占4个字节,序号范围是(0,2^32 - 1),共2^32 (即4294967296)个序号。序号增加到2^32-1后,下一个序号就又回到0。也就是说,序号使用mod 2^32运算。TCP是面向字节流的。在一个TCP连接中传送的字节流中的每一个字节都按顺序编号。整个要传送的字节流的起始序号必须在连接建立时设置。首部中的序号字段值则是指的是本报文段所发送的数据的第一个字节的序号。例如,一报文段的序号是301,而接待的数据共有100字节。这就表明:本报文段的数据的第一个字节的序号是301,最后一个字节的序号是400。显然,下一个报文段(如果还有的话)的数据序号应当从401开始,即下一个报文段的序号字段值应为401。这个字段的序号也叫“报文段序号”。

    3. 确认号:占4字节,是期望收到对方下一个报文段的第一个数据字节的序号。例如,B正确收到了A发送过来的一个报文段,其序号字段值是501,而数据长度是200字节(序号501~700),这表明B正确收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701。注意,现在确认号不是501,也不是700,而是701。

      若确认号为N,则表明,起始序号到序号N-1为止的所有数据都已正确收到,期望的序号是N。

    4. 数据偏移(即首部长度):占4位,它指出TCP报文段的数据起始处距离TCP报文段的起始处有多远。这个字段实际上是指出TCP报文段的首部长度。由于首部中还有长度不确定的选项字段,因此数据偏移字段是必要的,但应注意,“数据偏移”的单位是32位字(即以4字节的字为计算单位)。由于4位二进制数能表示的最大十进制数字是15,因此数据偏移的最大值是60字节,这也是TCP首部的最大字节(即选项长度不能超过40字节)。

    5. 保留:占6位,保留为今后使用,但目前应置为0。

    6. 紧急URG(URGent):占1位, 当URG=1时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快发送(相当于高优先级的数据),而不要按原来的排队顺序来传送。例如,已经发送了很长的一个程序要在远地的主机上运行。但后来发现了一些问题,需要取消该程序的运行,因此用户从键盘发出中断命令。如果不使用紧急数据,那么这两个字符将存储在接收TCP的缓存末尾。只有在所有的数据被处理完毕后这两个字符才被交付接收方的应用进程。这样做就浪费了很多时间。当URG置为1时,发送应用进程就告诉发送方的TCP有紧急数据要传送。于是发送方TCP就把紧急数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍然是普通数据。这时要与首部中紧急指针(Urgent Pointer)字段配合使用。

    7. 确认ACK(ACKnowledgment):占1位, 仅当ACK = 1时确认号字段才有效,当ACK = 0时确认号无效。TCP规定,在连接建立后所有的传送的报文段都必须把ACK置为1。

    8. 推送PSH(Push):占1位,当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下,TCP就可以使用推送(push)操作。这时,发送方TCP把PSH置为1,并立即创建一个报文段发送出去。接收方TCP收到PSH=1的报文段,就尽快地(即“推送”向前)交付接收应用进程。而不用再等到整个缓存都填满了后再向上交付。

    9. 复位RST(ReSet):占1位,当RST=1时,表名TCP连接中出现了严重错误(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立传输连接。RST置为1还用来拒绝一个非法的报文段或拒绝打开一个连接。

    10. 同步SYN(SYNchronization):占1位,在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使SYN=1和ACK=1,因此SYN置为1就表示这是一个连接请求或连接接受报文。

    11. 终止FIN(FINish):占1位,用来释放一个连接。当FIN=1时,表明此报文段的发送发的数据已发送完毕,并要求释放运输连接。

    12. 窗口:占2个字节,窗口值是(0,2^16-1)之间的整数。窗口指的是发送本报文段的一方的接受窗口(而不是自己的发送窗口)。窗口值告诉对方:从本报文段首部中的确认号算起,接收方目前允许对方发送的数据量(以字节为单位)。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。总之,窗口值作为接收方让发送方设置其发送窗口的依据。

      例如,发送了一个报文段,其确认号是701,窗口字段是1000.这就是告诉对方:“从701算起,我(即发送方报文段的一方)的接收缓存空间还可接受1000个字节数据(字节序号是701~1700),你在给我发数据时,必须考虑到这一点。”

      窗口字段明确指出了现在允许对方发送的数据量。窗口值经常在动态变化。

    13. 检验和:占2个字节,检验和字段检验的范围包括首部和数据这两部分。和UDP用户数据报一样,在计算检验和时,要在TCP报文段的前面加上12字节的伪首部。伪首部的格式和UDP用户数据报的伪首部一样。但应把伪首部第4个字段中的17改为6(TCP的协议号是6);把第5字段中的UDP中的长度改为TCP长度。接收方收到此报文段后,仍要加上这个伪首部来计算检验和。若使用TPv6,则相应的伪首部也要改变。

    14. 紧急指针:占2个字节,紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据) 。因此,在紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时,TCP就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为0时也可以发送紧急数据。

    15. 选项:长度可变,最长可达40字节。当没有使用“选项”时,TCP的首部长度是20字节。其最大长度可根据TCP首部长度进行推算。TCP首部长度用4位表示,那么选项部分最长为:(2^4-1)*4-20=40字节。

      • MSS最大报文段长度(Maxium Segment Size):TCP最初只规定了一种选项,即最大报文段长度MSS(Maximum Segment Szie)。注意MSS这个名词含义。MSS是每一个TCP报文段中的数据字段的最大长度。数据字段加上TCP首部才等于整个的TCP报文段。所以MSS并不是整个TCP报文段的最大长度,而是“TCP报文段长度减去TCP首部长度”。

      • 为什么要规定一个最大报文长度MSS呢?

        • 这并不是考虑接受方的接收缓存可能存放不下TCP报文段中的数据。实际上,MSS与接收窗口值没有关系。我们知道,TCP报文段的数据部分,至少要加上40字节的首部(TCP首部20字节和IP首部20字节,这里还没有考虑首部中的可选部分)才能组装成一个IP数据报。若选择较小的MSS长度,网络的利用率就降低。设想在极端情况下,当TCP报文段只含有1字节的数据时,在IP层传输的数据报的开销至少有40字节(包括TCP报文段的首部和IP数据报的首部)。这样,对网络的利用率就不会超过1/41。到了数据链路层还要加上一些开销。但反过来,若TCP报文段非常长,那么在IP层传输时就有可能要分解成多个短数据报片。在终点要把收到的各个短数据报片组成成原来的TCP报文段,当传输出错时还要进行重传,这些也都会使开销增大。
        • 因此,MSS应尽可能大些,只要在IP层传输时不需要分片就行。由于IP数据报所经历的路径是动态变化的,因此在这条路径上确定的不需要的分片的MSS,如果改走另一条路径就可能需要进行分片。因此最佳的MSS是很难确定的。在连接过程中,双方都把自己能够支持的MSS写入这一字段,以后就按照这个数值传输数据,两个传送方向可以有不同的MSS值。若主机未填写这一项,则MSS的默认值是536字节长。因此,所有在互联网上的主机都应该接受的报文段长度是536+20(固定首部长度)=556字节。
      • 其他选项:

        • 窗口扩大选项

          (Windows Scaling):是为了扩大窗口。我们知道,TCP首部中窗口字段长度是16位,因此最大的窗口大小为64K字节。虽然这对早期的网络是足够用的,但对于包含卫星信道的网络,传播时延和宽带都很大,要获得高吞吐量需要更大的窗口大小。

          • 窗口扩大选项占3字节,其中有一个字节表示移位值S。新的窗口值等于TCP首部中的窗口位数从16增大到(16+S)。移位值允许使用的最大值是14,相当于窗口最大值增大到2(16+14)-1=230-1。
          • 窗口扩大选项可以在双方初始建立TCP连接时进行协商。如果连接的某一端实现了窗口扩大,当它不再需要扩大其窗口时,可发送S=0选项,使窗口大小回到16。
        • 时间戳选项(Timestamps):占10字节,其中最主要的字段是时间戳字段(4字节)和时间戳回送回答字段(4字节)。时间戳选项有以下两个概念:

          • 用来计算往返时间RTT。发送方在发送报文段时把当前时钟的时间值放入时间戳字段,接收方在确认该报文段时把时间戳字段复制到时间戳回送回答字段。因此,发送方在收到确认报文后,可以准确地计算出RTT来。
          • 用于处理TCP序号超过232的情况,这又称为防止序号绕回PAWS。我们知道,TCP报文段的序号只有32位,而每增加232个序号就会重复使用原来用过的序号。当使用高速网络时,在一次TCP连接的数据传送中序号很可能被重复使用。例如,当使用1.5Mbit/s的速度发送报文段时,序号重复要6小时以上。但若用2.5Gbit/s的速率发送报文段,则不到14秒钟序号就会重复。为了使接收方能够把新的报文段和迟到很久的报文段区分开,则可以在报文段中加上这种时间戳。
        • SACK选择确认项(Selective Acknowledgements):用来确保只重传缺少的报文段,而不是重传所有报文段。比如主机A发送报文段1、2、3,而主机B仅收到报文段1、3。那么此时就需要使用SACK选项来告诉发送方只发送丢失的数据。那么又如何指明丢失了哪些报文段呢?使用SACK需要两个功能字节。一个表示要使用SACK选项,另一个指明这个选项占用多少字节。描述丢失的报文段2,是通过描述它的左右边界报文段1、3来完成的。而这个1、3实际上是表示序列号,所以描述一个丢失的报文段需要64位即8个字节的空间。那么可以推算整个选项字段最多描述(40-2)/8=4个丢失的报文段。

        • NOP(NO-Operation):它要求选项部分中的每种选项长度必须是4字节的倍数,不足的则用NOP填充。同时也可以用来分割不同的选项字段。如窗口扩大选项和SACK之间用NOP隔开。

    1. 填充:为了使整个首部长度是4字节的整数倍。

    在这里插入图片描述

    // 结构体位置:/usr/include/netinet/tcp.h
    // union中有两个结构体,给目标赋值的时候,可以采用两种方式
    // tcph->dest = htons (80); or tcph->th_dest = htons (80);
    // 这两个变量在同一位置
    // 应为过程中会有类型强制转换,便于取内容;所以对字节序用# if
    struct tcphdr
      {
        __extension__ union
        {
          struct
          {
    	uint16_t th_sport;	/* source port */
    	uint16_t th_dport;	/* destination port */
    	tcp_seq th_seq;		/* sequence number */
    	tcp_seq th_ack;		/* acknowledgement number */
    # if __BYTE_ORDER == __LITTLE_ENDIAN
    	uint8_t th_x2:4;	/* (unused) */
    	uint8_t th_off:4;	/* data offset */
    # endif
    # if __BYTE_ORDER == __BIG_ENDIAN
    	uint8_t th_off:4;	/* data offset */
    	uint8_t th_x2:4;	/* (unused) */
    # endif
    	uint8_t th_flags;
    # define TH_FIN	0x01
    # define TH_SYN	0x02
    # define TH_RST	0x04
    # define TH_PUSH	0x08
    # define TH_ACK	0x10
    # define TH_URG	0x20
    	uint16_t th_win;	/* window */
    	uint16_t th_sum;	/* checksum */
    	uint16_t th_urp;	/* urgent pointer */
          };
          struct
          {
    	uint16_t source;
    	uint16_t dest;
    	uint32_t seq;
    	uint32_t ack_seq;
    # if __BYTE_ORDER == __LITTLE_ENDIAN
    	uint16_t res1:4;
    	uint16_t doff:4;
    	uint16_t fin:1;
    	uint16_t syn:1;
    	uint16_t rst:1;
    	uint16_t psh:1;
    	uint16_t ack:1;
    	uint16_t urg:1;
    	uint16_t res2:2;
    # elif __BYTE_ORDER == __BIG_ENDIAN
    	uint16_t doff:4;
    	uint16_t res1:4;
    	uint16_t res2:2;
    	uint16_t urg:1;
    	uint16_t ack:1;
    	uint16_t psh:1;
    	uint16_t rst:1;
    	uint16_t syn:1;
    	uint16_t fin:1;
    # else
    #  error "Adjust your <bits/endian.h> defines"
    # endif
    	uint16_t window;
    	uint16_t check;
    	uint16_t urg_ptr;
          };
        };
    };
    
     
    • 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

    附录

    涉及但未介绍内容

    • 从网上拼凑而来,皆是二手来源。

    • 所有的代码/文档见:github

    • 这些首部的应用,因为还需要更多的背景支持,这里暂不介绍。

    • 暂不介绍循环冗余码校验,首部校验和,字节序。

    参考文章

    超详细的网络抓包神器 tcpdump 使用指南

    实战!我用 Wireshark 让你“看见“ TCP

    展开全文
  • 添加MAC首部的过程

    2021-02-24 18:08:22
    因为要用到MAC首部,细细研究了仿真中帧首部的添加及使用环节。只写出结果,找到这个结果耗费了很长时间,过程就不写了。 一、添加环节 1.https://blog.csdn.net/zhang1806618/article/details/107753611中写到...

    因为要用到MAC帧首部,细细研究了仿真中帧首部的添加及使用环节。只写出结果,找到这个结果耗费了很长时间,过程就不写了。

    一、添加环节

    1. https://blog.csdn.net/zhang1806618/article/details/107753611中写到了调用QueueUpIpFragmentForMacLayer发送数据。在该函数中调用NetworkIpOutputQueueInsert()函数,将一个消息插入输出队列,然后调用MAC_NetworkLayerHasPacketToSend()发送消息。

    static void 
    QueueUpIpFragmentForMacLayer(
        Node *node,
        Message *msg,
        int interfaceIndex,
        NodeAddress nextHop,
        int incomingInterface)
    {
        NetworkDataIp *ip = (NetworkDataIp *) node->networkData.networkVar;
        Scheduler *scheduler = ip->interfaceInfo[interfaceIndex]->scheduler;
        IpHeaderType* ipHeader = (IpHeaderType *) MESSAGE_ReturnPacket(msg);
        NetworkDataIcmp *icmp = (NetworkDataIcmp*) ip->icmpStruct;
        BOOL queueIsFull;
        BOOL queueWasEmpty;
        NetworkType netType = NETWORK_IPV4;
        {
            queueWasEmpty = NetworkIpOutputQueueIsEmpty(node, interfaceIndex);
        }
        NetworkIpOutputQueueInsert(node,
                                   interfaceIndex,
                                   msg,
                                   nextHop,
                                   ipHeader->ip_dst,
                                   NETWORK_PROTOCOL_IP,
                                   &queueIsFull);
        if (queueWasEmpty)
        {
            {
                if (!NetworkIpOutputQueueIsEmpty(node, interfaceIndex))
                {
                    MAC_NetworkLayerHasPacketToSend(node, interfaceIndex);
                }
            }
        }
    }

    2. 函数NetworkIpOutputQueueInsert()调用NetworkIpQueueInsert()函数将消息插入队列。

    void
    NetworkIpOutputQueueInsert(
        Node *node,
        int outgoingInterface,
        Message *msg,
        NodeAddress nextHopAddress,
        NodeAddress destinationAddress,
        int networkType,
        BOOL *queueIsFull)
    {
        NetworkDataIp *ip = (NetworkDataIp *) node->networkData.networkVar;
        Scheduler *scheduler = NULL;    
        scheduler = ip->interfaceInfo[outgoingInterface]->scheduler;
        NetworkIpQueueInsert(node,
                             scheduler,
                             msg,
                             nextHopAddress,
                             destinationAddress,
                             outgoingInterface,
                             networkType,
                             queueIsFull,
                             ANY_INTERFACE,
                             TRUE);
    }

    3.  函数NetworkIpQueueInsert()调用IPv4AddressToHWAddress()函数,将下一跳的NodeAddress地址转换成MacHWAddress地址,然后存入消息附属的QueuedPacketInfo信息结构。

    void
    NetworkIpQueueInsert(
        Node *node,
        Scheduler *scheduler,
        Message *msg,
        NodeAddress nextHopAddress,
        NodeAddress destinationAddress,
        int outgoingInterface,
        int networkType,
        BOOL *queueIsFull,
        int incomingInterface,
        BOOL isOutputQueue)
    {
        IpHeaderType *ipHeader = NULL;
        QueuedPacketInfo *infoPtr;
        BOOL isResolved = FALSE;
        ipHeader = (IpHeaderType*) MESSAGE_ReturnPacket(msg);
        MacHWAddress hwAddr ;
        if (isOutputQueue && (outgoingInterface != CPU_INTERFACE))      
            isResolved = IPv4AddressToHWAddress(node,outgoingInterface,msg,nextHopAddress,&hwAddr);
        MESSAGE_InfoAlloc(node, msg, sizeof(QueuedPacketInfo));
        infoPtr = (QueuedPacketInfo *) MESSAGE_ReturnInfo(msg);
        infoPtr->nextHopAddress = nextHopAddress;
        infoPtr->destinationAddress.ipv4DestAddr = destinationAddress;   
        memcpy(infoPtr->macAddress,hwAddr.byte,hwAddr.hwLength);
        infoPtr->hwLength = hwAddr.hwLength;
        infoPtr->hwType = hwAddr.hwType;
        infoPtr->outgoingInterface = outgoingInterface;
        infoPtr->networkType = networkType;
        infoPtr->userTos = IpHeaderGetTOS(ipHeader->ip_v_hl_tos_len);
        infoPtr->incomingInterface = incomingInterface;
    }

    4.  IPv4AddressToHWAddress()函数根据MAC协议调用IPv4AddressToDefaultHWAddress()函数,将IPv4地址转换成默认硬件地址。

    BOOL IPv4AddressToHWAddress(
        Node *node,
        int interfaceIndex,
        Message* msg,
        NodeAddress ipv4Address,
        MacHWAddress* macAddr)
    {
        switch (node->macData[interfaceIndex]->macProtocol)
        {
            case MAC_PROTOCOL_CSMA:
            case MAC_PROTOCOL_MACA:
            case MAC_PROTOCOL_802_3:
            case MAC_PROTOCOL_ALOHA:
            case MAC_PROTOCOL_GENERICMAC:
            case MAC_PROTOCOL_SWITCHED_ETHERNET:
            case MAC_PROTOCOL_TDMA:
            case MAC_SWITCH:
            case MAC_PROTOCOL_DOT11:
            case MAC_PROTOCOL_DOT16:
            case MAC_PROTOCOL_LINK:
            {
                (*macAddr).byte = (unsigned char*) MEM_malloc(
                    sizeof(unsigned char)*MAC_ADDRESS_DEFAULT_LENGTH);
                IPv4AddressToDefaultHWAddress(node, interfaceIndex,
                                              ipv4Address, macAddr);
                break;
            }
        }
    }

    5.  IPv4AddressToDefaultHWAddress()函数,由节点IP地址获取节点ID号和接口号,并调用SetNodeIdInMacAddress()和SetInterfaceIndexInMacAddress()函数,将节点ID号和接口号写入接口默认硬件地址。

    IPv4AddressToDefaultHWAddress(
        Node *node,
        int index,
        NodeAddress ipv4Address,
        MacHWAddress *macAddr)
    {
        macAddr->hwLength = MAC_ADDRESS_LENGTH_IN_BYTE;
        macAddr->hwType = HW_TYPE_ETHER;
        Address interfaceAddr;
        interfaceAddr.interfaceAddr.ipv4 = ipv4Address;
        interfaceAddr.networkType = NETWORK_IPV4;
    
        unsigned int nodeId = MAPPING_GetNodeIdFromInterfaceAddress(node,interfaceAddr);
        unsigned int interfaceIndex = MAPPING_GetInterfaceIndexFromInterfaceAddress(node,interfaceAddr);
        SetNodeIdInMacAddress(macAddr->byte, nodeId);
        macAddr->byte[4] = 0;
        SetInterfaceIndexInMacAddress(macAddr->byte, interfaceIndex);
    }
    inline void SetNodeIdInMacAddress (unsigned char byte[], NodeId nodeId)
    {
        byte[0] = ((unsigned char )(nodeId));
        byte[1] = ((unsigned char )(nodeId >> 8));
        byte[2] = ((unsigned char )(nodeId >> 16));
        byte[3] = ((unsigned char )(nodeId >> 24));
    }
    inline void SetInterfaceIndexInMacAddress (unsigned char byte[],
                                               unsigned int interfaceIndex)
    {
        byte[5] = ((unsigned char )(interfaceIndex));
    }

    二、使用环节

    1. 如添加环节1所述,调用MAC_NetworkLayerHasPacketToSend()发送消息,区分MAC层协议类型进行分别调用。以802.11为例,调用MacDot11NetworkLayerHasPacketToSend()函数,在该函数中调用MacDot11StationMoveAPacketFromTheNetworkLayerToTheLocalBuffer()函数将消息由网络层传至缓存,在该函数中调用MAC_OutputQueueTopPacket()函数取出网络层输出队列最顶端的消息,并获取该消息的Mac802Address地址及网络类型、优先级等参数。MacDot11ClassifyPacket()函数用于检查消息的场合,如是否用于接入点AP场合,是否DCF、是否QoS等。在我的场景中都为否,可略过。

    static 
    BOOL MacDot11StationMoveAPacketFromTheNetworkLayerToTheLocalBuffer(
        Node* node,
        MacDataDot11* dot11)
    {
        BOOL dequeued = FALSE;
        BOOL peek = FALSE;
        BOOL isACHasMsg = FALSE;
        int interfaceIndex = dot11->myMacData->interfaceIndex;
        Message* tempMsg = NULL;
        Mac802Address tempNextHopAddress;
        TosType priority = 0;
        int networkType = 0; 
    	{
    		tempMsg = NULL;
    		dequeued =
    			MAC_OutputQueueTopPacket(
    				node,
    				interfaceIndex,
    				&(tempMsg),
    				&(tempNextHopAddress),
    				&networkType,
    				&priority);		
    		{
    			if (dequeued)
    			{
                   MacDot11ClassifyPacket(node,dot11,tempMsg,
                                    priority,tempNextHopAddress,FALSE);
    			}
    		}
    	}  
        isACHasMsg = MacDot11IsACsHasMessage(node,dot11);
        {        
            if (isACHasMsg == TRUE && dot11->currentMessage == NULL)
                MacDot11SetCurrentMessage(node,dot11);
            else
                return FALSE;
        }      
        return TRUE;
    }

     2. Mac802Address参数的MAC_OutputQueueTopPacket()函数调用MacHWAddress参数的同名函数MAC_OutputQueueTopPacket()获取MacHWAddress地址,再调用ConvertVariableHWAddressTo802Address()函数转换成Mac802Address地址,返回上一层函数调用。

    BOOL MAC_OutputQueueTopPacket(
        Node *node,
        int interfaceIndex,
        Message **msg,
        Mac802Address* destMacAddr,
        int* networkType,
        TosType *priority)
    {
        MacHWAddress destHWAddr;
        BOOL dequeued = MAC_OutputQueueTopPacket(node, interfaceIndex, msg,
                                 &destHWAddr, networkType, priority);
        if (dequeued)
        {
            ConvertVariableHWAddressTo802Address(node, &destHWAddr, destMacAddr);
        }
        return dequeued;
    }

    在MacHWAddress参数函数MAC_OutputQueueTopPacket()中调用NetworkIpOutputQueueTopPacket()。

    BOOL MAC_OutputQueueTopPacket(
        Node *node,
        int interfaceIndex,
        Message **msg,
        MacHWAddress* destHWAddr,
        int* networkType,
        TosType *priority)
    {
        BOOL aPacket = FALSE;
        *networkType = NETWORK_PROTOCOL_IP;
        NodeAddress nextHopAddress;
        {
            aPacket =
                NetworkIpOutputQueueTopPacket(
                    node,
                    interfaceIndex,
                    msg,
                    &nextHopAddress,
                    destHWAddr,
                    networkType,
                    priority);
        }
        return aPacket;
    }

    3.  NetworkIpOutputQueueTopPacket()函数调用NetworkIpQueueTopPacket()函数。

    BOOL NetworkIpOutputQueueTopPacket(
        Node *node,
        int interfaceIndex,
        Message **msg,
        NodeAddress *nextHopAddress,
        MacHWAddress* nexthopmacAddr,
        int* networkType,
        QueuePriorityType *priority)
    {
        NetworkDataIp *ip = (NetworkDataIp *) node->networkData.networkVar;
        Scheduler *scheduler = NULL;
        int outgoingInterface;
        scheduler = ip->interfaceInfo[interfaceIndex]->scheduler;
        return NetworkIpQueueTopPacket(node,
                                       scheduler,
                                       msg,
                                       nextHopAddress,
                                       nexthopmacAddr,
                                       &outgoingInterface,
                                       networkType,
                                       priority);
    }

    4. NetworkIpQueueTopPacket()函数将消息附属信息结构QueuedPacketInfo中的下一跳NodeAddress和MacHWAddress地址取出,赋值给nextHopAddress,nexthopmacAddr,返回上一层函数调用。

    BOOL NetworkIpQueueTopPacket(
        Node *node,
        Scheduler *scheduler,
        Message **msg,
        NodeAddress *nextHopAddress,
        MacHWAddress *nexthopmacAddr,
        int *outgoingInterface,
        int *networkType,
        QueuePriorityType *priority,
        int posInQueue)
    {
        QueuePriorityType notUsed = ALL_PRIORITIES;
        QueuePriorityType queuePriority = ALL_PRIORITIES;
        BOOL isPktRetrieved = FALSE;
        QueuedPacketInfo *infoPtr;
        isPktRetrieved = (*scheduler).retrieve(notUsed,
                                               posInQueue,
                                               msg,
                                               &queuePriority,
                                               PEEK_AT_NEXT_PACKET,
                                               getSimTime(node));
        if (isPktRetrieved)
        {
            infoPtr = (QueuedPacketInfo *) MESSAGE_ReturnInfo((*msg));
            *priority = queuePriority;
            *nextHopAddress = infoPtr->nextHopAddress;
            nexthopmacAddr->hwLength = infoPtr->hwLength;
            nexthopmacAddr->hwType = infoPtr->hwType;
            if (nexthopmacAddr->byte == NULL)
            {
                nexthopmacAddr->byte = (unsigned char*) MEM_malloc(
                                  sizeof(unsigned char)*infoPtr->hwLength);
            }
            memcpy(nexthopmacAddr->byte,infoPtr->macAddress,infoPtr->hwLength);
            *outgoingInterface = infoPtr->outgoingInterface;
            *networkType = infoPtr->networkType;
        }
        return isPktRetrieved;
    }

     

    展开全文
  • 文章目录一:网络传输基本流程(1)数据包(2)网络传输的基本流程(3)具体处理过程A:发送数据B:路由转发C:接受数据二:网络中的地址(1)MAC地址(2)IP地址 一:网络传输基本流程 (1)数据包 前文说过,发送...

    一:网络传输基本流程

    (1)数据包

    前文说过,发送“你好”这样的信息给另一台主机,发送时并不是简简单单就这样直接传递过去,而是会封装一些其他信息,最终在以太网电缆中传输。

    在这里插入图片描述
    每个分层中,都会对所发送的数据附加一个首部,在这个首部中包含了该层的必要信息。我们知道每一层都有相应的协议,所以为协议提供的信息是包的首部,所要发送的内容为数据。因此上图中,从下一层角度看,从上一层受到的包全部都认识是本层的数据
    在这里插入图片描述
    其中,包,帧,数据包,段,消息的区别如下
    在这里插入图片描述

    网络中传输的数据包由两部分组成:一部分是协议所要用到的首部,另一部分是上层传过来的数据。首部的结构由协议的具体规范定义。比如识别上一层协议的域应该从包的哪一位开始取多少比特位,以及如何计算校验和插入包的哪一位等等。所以相互通信的两端计算机如果在识别协议的序号以及校验的计算方法等上出现不一致,就无法完成通信。

    (2)网络传输的基本流程

    以发送“早上好”这样的信息为例,下图展示了大致的传输流程

    在这里插入图片描述
    可以看出最终在以太网电缆中传输的数据相较于原生数据是“庞大的”。
    如下,包流动时从前往后依次附加以太网包首部,IP包首部,TCP包首部以及应用程序自己的包首部和数据,包的最后则追加了以太网包尾
    在这里插入图片描述
    每个包首部中至少会包含两个信息:一个是发送端和接收端的地址,一个是识别上一层协议类型
    在这里插入图片描述
    经过每个协议分层时,都必须有识别包发送端和接收端的信息:以太网会用MAC地址(MAC地址具有唯一性),IP则会用IP地址,TCP/IP则会用端口号等。
    在这里插入图片描述

    (3)具体处理过程

    上面的流程图左半部分称之为封包,右半部分称之为解包。所对应的操作分贝就是发送接受。封包时,上一层封好之后只需将数据传递给下一层(也就是调用某个接口)进行封包,但是解包时存在一些问题:比如下一层分离完自己的包首部后,如何确定自己的数据移交的对象等等(这个过程称为分用)。当然这一切都是协议规定好的.

    A:发送数据

    1:应用程序处理

    键盘输入“早上好”之后,点击某个应用程序的发送按钮,之后应用程序会进行编码(比如utf-8)。应用程序在发送那一刻建立TCP连接,从而利用TCP连接发送数据,它的过程首先是将应有的数据发送给下一层的TCP。
    在这里插入图片描述
    2:TCP模块处理

    TCP根据应用的知识,负责建立连接,发送数据及断开连接。TCP提供可靠传输保证数据顺利发送
    TCP首部包括源端口号和目标端端口号(用于是被发送主机和接受主机上的应用),序号(用于判断包中那部分是数据)还有校验(判断数据是否损坏)。随后再发送给IP

    3:IP模块处理

    IP模块在TCP首部前添加IP首部,IP首部包含接收端IP地址和以及发送端IP地址。IP包生成之后,参考路由表决定接受此IP包的路由和主机,随后IP包将被发送给连接这些路由器或主机网络接口的驱动程序,以实现真正发送数据。只要知道了对方MAC地址就可以将MAC地址和IP地址交给以太网驱动程序实现数据传输

    4:以太网驱动添加首部通过物理层实现传输

    B:路由转发

    数据已经封装好了,准备进行发送,如果对方和自己不在同一个局域网内就要进行路由器的转发,如下图。

    有两个局域网,中间的路由器可以作为左边局域网的主机也可以作为右边局域网内的主机。路由器接受到左侧发送过来的数据后,进行解包,发现MAC地址不在本局域网内,然后去除之前的以太网首部,通过查询路由表确定新的MAC地址,再次添加以太网首部,然后发送到另一个局域网内。

    在这里插入图片描述
    所以我们在百度搜索输入框输入一些关键词回车之后,就会发生上述的数据发送过程,由于我们自己的IP地址是固定的,百度服务器的IP地址也是相对固定的,由于距离较远,所以要经过多个路由器进行转发,每次转发时只需更换以太网首部即可
    在这里插入图片描述

    C:接受数据

    4:以太网驱动处理

    所以主机收到以太网包后,首先会从以太网的包首部找到MAC地址进行判断,如果和本机MAC地址相同则认定为是发送给自己的包,然后接着查找以太网包首部中的类型域以确定是什么类型的包(比如说IP包);反之如果不相同则直接丢弃数据
    在这里插入图片描述
    3:IP模块

    IP模块收到以太网驱动传回的数据后,也分析IP包首部,比如判断包首部中的IP地址是否与本机的IP地址匹配,如果匹配就继续查找上层协议,如果上层是TCP就传递给TCP处理。对于有路由器的情况下,接收端地址往往不是自己的地址,所有就要进行路由转发等操作

    2:TCP模块

    1:应用程序

    在这里插入图片描述

    二:网络中的地址

    (1)MAC地址

    MAC地址用于识别数据链路中互连的结点。
    在这里插入图片描述
    MAC地址有48个比特位,地址一般会被烧入到ROM中,因此MAC地址在世界上都是独一无二的(也有例外)
    在这里插入图片描述

    (2)IP地址

    MAC地址是用来标识同一个链路中不同计算机的标识码。但是在网络传输中,也必须要有一个地址信息,一般叫做IP地址,用于在连接到网络中的所有主机中识别出进行通信的目标地址
    在这里插入图片描述
    无论一台主机与哪种数据链路连接(比如以太网电缆或者无线局域网),其IP地址都将保持不变。也就是IP地址其实是和之前的进程地址空间,“Linux下一切皆文件”采用了相同的思想——抽象,虚拟化

    IP地址是一个4个字节,32位的整数,IP地址在计算机内部将以二进制方式处理,但是这样的表示方法不太好,所以一般采用点分十进制的方式表示,也就是将32位IP地址每8位为一组,分为4组,每组以"."隔开,再将每组数转化为十进制。比如192.168.0.1

    引用此老铁的图片:一枚快乐的野指针
    在这里插入图片描述

    在这里插入图片描述

    展开全文
  • Mac地址 8.23

    2021-08-23 20:24:33
    只有在剥去MAC帧的首部和尾部后把数据上传给网络层后,网络层在IP数据报的首部中找到源IP地址和目的IP地址。 数据从主机1发送到主机2时,源IP地址和目的IP地址始终不变,而mac地址在变化。路由器会根据目的IP地址...
  • HTTP首部字段:给浏览器...按实际用途划分:通用首部字段、请求首部字段、响应首部字段、实体首部字段 按是否缓存代理的行为划分:端到端首部(End to end Header)、逐跳首部(Hop by hop Header) 端到端首部:此类
  • http首部字段详解与cookie

    千次阅读 2018-11-16 03:25:20
    HTTPHTTP报文首部1.General 通用首部2.响应头部3.请求头部4. Cache-control(缓存设置)5.Cookie为cookie服务的首部字段:1、 Set-Cookie...通用首部,请求首部,响应首部,实体首部。 HTTP首部有很多字段,常见就那...
  • 计算机网络 『MAC帧格式、MAC地址』

    千次阅读 2020-12-30 23:19:17
    2、MAC帧最小为64字节,目标地址+源地址+FCS(帧校验序列)占了18字节,所以IP数据报最小为46个字节 3、无效的MAC帧 ① 帧的长度不是整数个字节 ② 用收到的帧序列FCS查到有差错 ③ 数据段的长度不在46~1500字节之间...
  • 计算机网络中MAC地址与IP地址

    万次阅读 多人点赞 2018-08-13 21:51:40
    1、IP地址和物理地址(mac或硬件地址)的区别: 物理地址是数据链路层和物理层使用的...只有在剥去MAC帧的首部和尾部后把数据上传给网络层后,网络层在IP数据报的首部中找到源IP地址和目的IP地址。 (2)应用...
  • ARP/RARP首部

    2019-03-17 13:24:02
    ARP (Address Resolution Protocol)地址解析协议 RARP (Reverse Address Resolution Protocol) 反向地址...可以看到ARP报文是由14位的以太网首部和28位的ARP请求/应答构成的 wireshark抓包分析 以太网首部 ...
  • 以太网首部 目地MAC地址(8字节) 源MAC地址(8字节) 类型(2字节) 转载于:https://www.cnblogs.com/classics/p/10417390.html
  • 我们在前一小节谈到 MAC 的封装,那么 IP 封包的封装也得要来了解一下,才能知道 IP 到底是如何产生的啊! IP 封包可以达到 65535 bytes 这么大,在比 MAC 大的情况下,我们的操作系统会对 IP 进行拆解的动作。至于 ...
  • MAC地址

    千次阅读 2018-03-23 16:53:21
    硬件地址又称为物理地址或MAC地址(因为这种地址用在MAC帧中),“MAC地址”,实际上就是是适配器地址,当这块适配器插入(或嵌入)到某台计算机后,适配器上的标识符EUI-48就成为这台计算机的MAC地址。 MAC帧的...
  • TCP/IP/ARP/ICMP首部分析

    2018-03-06 14:36:43
    2、各协议首部 2.1 以太网首部 2.2 IP(网络协议)首部 2.3 TCP(传输控制协议)首部 2.4 ARP(地址解析协议)首部 2.5 UDP(用户数据报协议)首部 2.6 ICMP(网络控制报文协议)首部 3、常见ICMP报文 ...
  • 介绍了IP协议的概念和IP首部的内容,以及相关知识点,ICMP、ARP、port、DNS。
  • 为什么要在IP首部在添加一个检验和,明明在MAC层的时候会有一个CRC校验,误码率极低,已经能够保证ip数据包的正确性,为何在ip首部还要设计一个检验和,这个的目的是啥?一直很困惑!总不至于为了降低一个包在路由...
  • MAC帧封装

    千次阅读 2018-11-19 18:04:44
    通过控制台输入一段文字,输出MAC帧的2进制和16进制的字符串,主要是求FCS。这里只考虑单帧的情况,即只考虑输入数据在1字节~1500字节之间的情况,对于更长的数据暂不考虑。 1、MAC帧基本格式  表1 802.3标准的.....
  • Mac OS

    千次阅读 2011-10-18 23:23:20
    mac os 求助编辑百科名片 Mac OS是一套运行于苹果Macintosh系列电脑上的操作系统。Mac OS是首个在商用领域成功的图形用户界面。现行的最新的系统版本是Mac OS X 10.6.x版。 目录 1、MAC简介
  • 1--ip首部

    2016-04-16 10:59:51
    2. Hdr len:IP首部的长度,IP首部的长度是可变的,因为有一些选项(options)选项填与不填可能使得IP首部变化; 3. Type of Service:服务类型,虽然都是发送报文,但是对报文的要求是可靠到达、优先到达还是快
  • 2.1MAC协议概述

    千次阅读 2020-03-27 23:14:22
    数据链路层的分析 1.1 可能提供的服务 组帧: 在一段数据的前后部分添上首部和尾部,这样就构成了一个帧。...)帧首部采用MAC地址识别源和目的地。 链路接入: 共享信道需要有接入控制机制–MAC; ...
  • 以太网首部字段含义

    千次阅读 2012-10-25 19:12:17
    typedef struct _ETHeader //以太网数据帧... //目的MAC地址 UCHAR shost[6]; //源MAC地址 USHORT type; //下层协议类型,如IP(ETHERTYPE_IP),ARP(ETHERTYPE_ARP)等 }ETHeader,*PETHeader; typedef struct _IPH
  • 以太网MAC帧格式

    万次阅读 2018-05-06 18:48:10
    在FPGA有关的以太网设计中,FPGA实现的代码工 能工作在mac层。因此,设计FPGA网口代码时常需要对网络包进行打包和解包,这就需要相关网络协议包格式的知识。 常用的以太网MAC帧格式有两种标准 :DIX Ethernet II...
  • 1.如果两台主机或多台主机不接入因特网,不经过路由和网络,那么...在链路层首部封装由源MAC地址和目的MAC地址。 4-2:MAC地址与IP地址在网络中的传输。 IP地址在传输过程中是没有进行改变的,改变的是传输的MAC地址。
  • 如果 A (192.168.1.1 )向 B (192.168.1.2 )发送一个数据包,那么需要的条件有 ip、port、使用的协议(TCP/UDP)之外还需要 MAC 地址,因为在以太网数据包中 MAC 地址是必须要有的。那么怎样才能知道对方的 MAC ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 19,734
精华内容 7,893
关键字:

mac首部