• 二层转发及C/C++代码实现


    数据链路层是开放系统互连 (OSI) 模型中的第二层,该层用于通过 LAN 等单一网络进行通信的节点,第二层数据包不能从一个网络传输到另一个网络。而二层转发是根据报文的目的MAC直接进行转发,转发过程中不用对报文的头部做任何的修改。

    OSI 第 2 层

    在这里插入图片描述在这里插入图片描述
    前两个字段分别是目的地址和源地址字段。第3个字段是2字节的类型字段,用来标识上一层是什么协议。

    数据链路层有两个子层:逻辑链路控制 (LLC) 子层和媒体访问控制 (MAC) 子层。

    媒体访问控制 (MAC):MAC 子层处理硬件标识号的分配,称为 MAC 地址,它唯一地标识网络上的每个设备。 任何两个设备都不应具有相同的 MAC 地址。 MAC 地址是在制造时分配的。 大多数网络都会自动识别它。 MAC 地址位于网卡 上。
    在这里插入图片描述

    交换机跟踪网络上的所有 MAC 地址。

    逻辑链路控制 (LLC):LLC 子层处理成帧寻址和流量控制。 速度取决于节点之间的链接,例如以太网或 Wifi。

    第 2 层上的数据单元是一个帧。每个帧都包含一个帧头、正文和一个帧尾:

    Header:通常包括源节点和目标节点的 MAC 地址。
    Body:由正在传输的位组成。
    Trailer:包括错误检测信息。 当检测到错误时,根据网络或协议的实现或配置,帧可能会被丢弃,或者可能会将错误报告给更高层以进行进一步的纠错。
    error detection mechanisms::循环冗余校验 (CRC) 和帧校验序列 (FCS)。

    在这里插入图片描述

    通常有一个最大帧大小限制,称为最大传输单元,MTU。 巨型帧超过标准 MTU.

    通过ARP解析出目标 MAC 地址?

    传统交换在 OSI 模型的第 2 层运行,其中数据包根据目标 MAC 地址发送到特定的交换机端口。第 2 层网段中的设备不需要路由即可到达本地对等点。 然而,需要的是可以通过地址解析协议 (ARP) 解析的目标 MAC 地址,如下所示:

    在这里插入图片描述
    在这里,PC A 想要将流量发送到 IP 地址为 192.168.1.6 的 PC B。 然而,它不知道唯一的 MAC 地址,直到它通过 ARP 发现它,该 ARP 在整个第 2 层网段中广播。

    然后将数据包发送到适当的目标 MAC 地址,交换机将根据其 MAC 地址表将正确的端口转发出去。

    什么是MAC地址表

    MAC地址表是在交换机中记录局域网主机和对应接口关系的表,交换机就是根据这张表负责将数据帧传输到指定的主机上的。

    MAC地址表可以动态的学习数据帧中的原MAC地址。在MAC地址表中,交换机的一个接口可以对应多个MAC地址。一个MAC地址只能对应在一个接口上。下面是MAC地址表形成的具体过程,如下:

    在这里插入图片描述

    二层转发C/C++代码实现

    cethping:

    void ethping(char *destination, char* interface){
        //创建原始套接字。 指定接口名称
        struct RawSocket* rawsocket = new_RawSocket(interface);
    
        //数据包数据缓冲区
        unsigned char buf[1024];
        //定义数据包。 投射以匹配以太网帧的格式
        struct ethhdr_frame* eth_packet = (struct ethhdr_frame*)buf;
        //存储发送方和接收方的 MAC 地址。 协议类型是可选的,并且指定了 0x0806。
        //根据指定的接口名称获取发送者的MAC地址
        memset(buf, 0x0, sizeof(eth_packet));
        set_macaddr_from_string(destination, eth_packet->h_dest);
        set_macaddr_from_ifname(interface, eth_packet->h_source);
    	eth_packet->h_proto = 0x0806;
        //在payload中设置字符串“Hello”
        char* data = "Hello";
        memcpy(eth_packet->payload, data, sizeof(data)); 
    
        //绑定指定接口上的socket
        rawsocket->bind_rawsocket(rawsocket);
        int send_size = send(rawsocket->socket, &buf, sizeof(buf), 0);
        printf("%dbyte send.\n", send_size);
    
        //关闭原始套接字
        rawsocket->close_rawsocket(rawsocket);
    }
    
    
    int main(int argc, char *argv[]){
        if(argc != 3){
            printf("usage: %s <destination> <interface>", argv[0]);
            exit(0);
        }
    
        char *destination = argv[1];
        char *if_name = argv[2];
        ethping(destination, if_name);
    
        return 0;
    }
    
    
    • 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

    cethpingd:

    void start_daemon(char *interface){
        //创建原始套接字(指定接收器接口名称)
        struct RawSocket* rawsocket = new_RawSocket(interface);
        int len;
        //使用 bind 绑定到接口
        rawsocket->bind_rawsocket(rawsocket);
        while(1){
            int len = rawsocket->recv_rawsocket(rawsocket);
            struct ethhdr_frame *data = (struct ethhdr_frame*)(rawsocket->buf);
            fflush(stdout);
            //显示接收到的数据包的内容
            if(len > 0){
                printf("src: ");
                print_macaddr(data->h_source);
                printf(", ");
                printf("dst: ");
                print_macaddr(data->h_dest);
                printf(", ");
                printf("type: ");
                printf("%02x", (uint16_t)data->h_proto);
                printf(", ");
                printf("payload: ");
                printf("%s", data->payload);
                printf("\n");
            }
        }
    }
    
    int main(int argc, char *argv[]){
        if(argc != 2){
            printf("usage: %s <interface>", argv[0]);
            exit(0);
        }
        char *if_name = argv[1];
        start_daemon(if_name);
        return 0;
    }
    
    
    • 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

    编译:
    在这里插入图片描述

    增加两个虚拟网卡:
    在这里插入图片描述
    运行:

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

    转发是将连接到网络交换机一个端口的设备的网络流量传递到连接到交换机上另一个端口的另一个设备的过程。

    当第 2 层以太网帧到达网络交换机上的端口时,交换机会读取以太网帧的源 MAC 地址作为学习功能的一部分,它还会读取目标 MAC 地址作为转发功能的一部分。目标 MAC 地址对于确定连接目标设备的端口号很重要。如果在 MAC 地址表中找到目的 MAC 地址,则交换机通过 MAC 地址对应的端口转发以太网帧。

    欢迎关注微信公众号【程序猿编码】,需要二层转发完整源码的添加本人微信号(c17865354792)

    参考:
    《TCP IP详解卷一》

  • 相关阅读:
    mybatis-spring注解MapperScan的原理
    【CSS】transition、transform以及animation
    Docker安装启动Mysql
    Vue前端框架10 组件的组成、组件嵌套关系、组件的注册方式、组件传递数据(props $emit)、数组传递多种数据类型、组件传递props校验、组件事件
    Vue---8种组件传值方式总结,总有一款适合你
    SpringBoot - 实现启动时执行指定任务(CommandLineRunner、ApplicationRunner)
    Java21 LTS版本
    springboot电气与信息类书籍网上书店 java ssm书籍借阅归还系统
    html写一个table表
    井水,矿泉水等饮用水去除氟离子树脂技术
  • 原文地址:https://blog.csdn.net/chen1415886044/article/details/124853000