手写TCP/IP——第3节以太网数据包收发实现2——以太网驱动封装

🌈hello,你好鸭,我是Ethan,西安电子科技大学大三在读,很高兴你能来阅读。

✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。
🏃人生之义,在于追求,不在成败,勤通大道。加油呀!

🔥个人主页:Ethan Yankang
🔥推荐:史上最强八股文||一分钟看完我的几百篇博客

🔥温馨提示:划到文末发现专栏彩蛋   点击这里直接传送

🔥本篇概览:详细讲解了手写TCP/IP的第4节——实现对以太网驱动的封装,进行简单处理,后面我们在做以太网输入输出的时候,就会调用这里定义的输入输出函数。🌈⭕🔥


【计算机领域一切迷惑的源头都是基本概念的模糊,算法除外】


🔥 手写底层系列

🔥 手写TCP/IP系列

【OSI与课程讲解】


🌈章节引出

前一篇章:手写TCP/IP——第3节以太网数据包收发实现1——数据包结构定义-CSDN博客

🌈章节速览


1.项目原始数据包捕获库文件依赖

项目是依赖两个库文件的,分别是:

其中,pcap.h的函数头文件如下:

/**
 * 使用pcap来模拟一个网卡
 * 本部分代码可独立编译,不依赖xnet
 */
#ifndef PCAP_DRIVER_H
#define PCAP_DRIVER_H 

#include <pcap.h>
#include <stdint.h>

// 主-次版本号
#define NPCAP_VERSION_M             0
#define NPCAP_VERSION_N             9986

typedef void (*irq_handler_t)(void* arg, uint8_t is_rx, const uint8_t* data, uint32_t size);

//数据包捕获:PCAP 驱动能够从网络接口(如以太网接口、无线网卡等)捕获经过的网络数据包。
//本函数是对pcap驱动进行封装,以适用于本项目。
// 含义:pcap_device_open函数根据给定的 IP 地址、MAC 地址和轮询模式打开一个网络设备。
pcap_t* pcap_device_open(const char* ip, const uint8_t *mac_addr, uint8_t poll_mode);

void pcap_device_close(pcap_t* pcap);
uint32_t pcap_device_send(pcap_t* pcap, const uint8_t* buffer, uint32_t length);
uint32_t pcap_device_read(pcap_t* pcap, uint8_t* buffer, uint32_t length);

#endif //PCAP_DRIVER_H

之中的pcap是一段网络数据包捕获程序pcap驱动能够从网络接口(如以太网接口、无线网卡等)捕获经过的网络数据包。

而本章节的目的就是实现对pcap驱动进行封装,以适用于本项目的开发。


2.port_pcap.c函数的编写,封装pcap.c驱动对以太网的操作。

后面我们在做以太网输入输出的时候,就会调用这里定义的输入输出函数

2.1设置虚拟机ip

//这里是虚拟机的IP
const char* ip_str = "192.168.254.1";

2.2给虚拟机网卡设置MAC地址

//这里是给虚拟机上的 网卡设置的一个mac地址。MAC地址就相当于网卡的身份证号,唯一标识
const char my_mac_addr[] = { 0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88 };

拓展:MAC地址:

        全称MAC(Media Access Control)地址,也称为物理地址、硬件地址,是网络设备制造商烧录在网卡(NIC)里的一串数字,用于在网络中唯一标识一个网络设备。MAC 地址通常由 48 位(6byte)二进制数组成,一般表示为 12 个十六进制数,如 00-1A-2B-3C-4D-5E。


2.3封装pcap的打开以太网驱动函数,xnet_driver_open

以上面刚刚定义的MAC地址传入,进行打开pcap驱动

//这里的传入的MAC地址参数是由自己的驱动返回的,之后会被上层调用  
xnet_err_t xnet_driver_open(uint8_t* mac_addr) {
	 memcpy(mac_addr, my_mac_addr, sizeof(my_mac_addr));//将虚拟机上的网卡地址传入进来
	 //实现设备的打开——需要传入的参数有:虚拟机上的ip与其网卡的地址,还有工作模式的选择(此处为查找模式,固定填1,实现数据包的读取与发送)
	 pcap_device_open(ip_str, my_mac_addr, 1);
	 pcap = pcap_device_open(ip_str, my_mac_addr, 1);
	 if (pcap==(pcap_t*)0){
		 exit(-1);//直接调用stdlib的库函数实现退出,简单
	 }
	 return XNET_ERR_OK;
}

2.4封装pcap的发送包函数,xnet_driver_send

 //发送包函数封装
 xnet_err_t xnet_driver_send(xnet_packet_t* packet) {
	 return pcap_device_send(pcap, packet->data, packet->size);

 }

2.5封装pcap的读取包函数,xnet_driver_read

这里调用的是前一节课时我们定义的最底层的分配网络数据包函数用于最底层的网络数据帧读取的。这里就接收到了!

 //读取包函数封装,将读取到的数据封装到二级指针packet包里面
 xnet_err_t xnet_driver_read(xnet_packet_t** packet) {
	 uint16_t size;
	 //接收到包, 这里是前一节课时我们定义的最底层的分配网络数据包函数,用于最底层的网络数据帧读取的。这里就接收到了!
	 xnet_packet_t* r_packet = xnet_alloc_for_read(XNET_CFG_PACKET_MAX_SIZE);
	 size = pcap_device_read(pcap, r_packet->data, XNET_CFG_PACKET_MAX_SIZE);
	 if (size){
		 r_packet->size = size;
		 *packet = r_packet;
		 return XNET_ERR_OK;
	 }
	 return XNET_ERR_IO;
 }

2.5.1pcap_device_read网络接口包读取源函数

其中,pcap_device_read(pcap, r_packet->data, XNET_CFG_PACKET_MAX_SIZE)函数如下:

作用是从网络接口处读取数据包【这里是最底层的直接和网卡交互了】。

/**
 * 从网络接口读取数据包
 */
uint32_t pcap_device_read(pcap_t* pcap, uint8_t* buffer, uint32_t length) {
    int err;
    struct pcap_pkthdr* pkthdr;
    const uint8_t* pkt_data;

    err = pcap_next_ex(pcap, &pkthdr, &pkt_data);
    if (err == 0) {
        return 0;
    } else if (err == 1) {     // 1 - 成功读取数据包, 0 - 没有数据包,其它值-出错
        memcpy(buffer, pkt_data, pkthdr->len);
        return pkthdr->len;
    }

    fprintf(stderr, "pcap_read: reading packet failed!:%s", pcap_geterr(pcap));
    return 0;
}

3.源文件:

3.1pcap devce.h

/**
 * 使用pcap来模拟一个网卡
 * 本部分代码可独立编译,不依赖xnet
 */
#ifndef PCAP_DRIVER_H
#define PCAP_DRIVER_H 

#include <pcap.h>
#include <stdint.h>

// 主-次版本号
#define NPCAP_VERSION_M             0
#define NPCAP_VERSION_N             9986

typedef void (*irq_handler_t)(void* arg, uint8_t is_rx, const uint8_t* data, uint32_t size);

//数据包捕获:PCAP 驱动能够从网络接口(如以太网接口、无线网卡等)捕获经过的网络数据包。
//本函数是对pcap驱动进行封装,以适用于本项目。
// 含义:pcap_device_open函数根据给定的 IP 地址、MAC 地址和轮询模式打开一个网络设备。
pcap_t* pcap_device_open(const char* ip, const uint8_t *mac_addr, uint8_t poll_mode);

void pcap_device_close(pcap_t* pcap);
uint32_t pcap_device_send(pcap_t* pcap, const uint8_t* buffer, uint32_t length);
uint32_t pcap_device_read(pcap_t* pcap, uint8_t* buffer, uint32_t length);

#endif //PCAP_DRIVER_H

3.2pcap device.c

#include <memory.h>
#include "pcap_device.h"

#if defined(WIN32)

#include <winsock.h>
#include <tchar.h>
#include <time.h>

#pragma comment(lib, "ws2_32.lib")  // 加载win32的网络库

// 加载pcap的lib,根据32位或64位平台来加
#ifdef _WIN64
#pragma comment(lib, "..\\lib\\npcap\\Lib\\x64\\Packet.lib")  
#pragma comment(lib, "..\\lib\\npcap\\Lib\\x64\\wpcap.lib") 
#else 
#pragma comment(lib, "..\\lib\\npcap\\Lib\\Packet.lib")  
#pragma comment(lib, "..\\lib\\npcap\\Lib\\wpcap.lib") 
#endif

static const char* read_num(const char* str, int * num) {
    const char* pstr = str;

    while ((*pstr < '0') || (*pstr > '9')) { 
        if (*pstr == '\0') {
            return '\0';
        }

        pstr++;  
    }

    *num = 0;
    while (*pstr) {
        char c = *pstr++;
        if ((c >= '0') && (c <= '9')) {
            *num = *num * 10 + c - '0';
        } else {
            break;
        }
    }

    return pstr;
}


/**
 * 调整npcap的搜索路径:默认安装在系统的dll路径\npcap目录下
 * 设置该路径,以避免使用其它已经安装的winpcap版本的dll
 * 注意:要先安装npcap软件包
 */
static int load_pcap_lib() {
    static int dll_loaded = 0;
    _TCHAR  npcap_dir[512];
    int size;
    DWORD dwAttrib;
    int m_version, n_version;

    if (dll_loaded) {
        return 0;
    }

    size = GetSystemDirectory(npcap_dir, 480);
    if (!size) {
        goto error_end;
    }

    _tcscat_s(npcap_dir, 512, _T("\\Npcap"));
    if (SetDllDirectory(npcap_dir) == 0) {
        goto error_end;
    }

    _tcscat_s(npcap_dir, 512, _T("\\npcap.dll"));
    dwAttrib = GetFileAttributes(npcap_dir);
    if ((INVALID_FILE_ATTRIBUTES != dwAttrib) && (0 == (dwAttrib & FILE_ATTRIBUTE_DIRECTORY))) {
        goto error_end;
    }

    // 检查版本号,要求必须比工程所用的高
    const char * v_str = pcap_lib_version();
    v_str = read_num(v_str, &m_version);
    read_num(v_str, &n_version);
    if ((m_version < NPCAP_VERSION_M) || ((m_version == NPCAP_VERSION_M) && (n_version < NPCAP_VERSION_N))) {
        wchar_t title[256];

        wsprintf(title, _T("npcap版本号太老: %d.%d < %d.%d"), m_version, n_version, NPCAP_VERSION_M, NPCAP_VERSION_N);
        MessageBox(0,
            _T("1.请卸载所有已安装的npcap或者winpcap. \n2. 请安装最新版本npcap,或者安装课程提供的wireshark, 安装过程中安装其附带的npcap."),
            title,
            MB_ABORTRETRYIGNORE);
        return -1;
    }

    dll_loaded = 1;
    return 0;

    
error_end:
    MessageBox(0,
        _T("请安装课程提供的wireshark,并确认wireshark提供的npcap安装."),
        _T("npcap驱动加载失败"),
        MB_ABORTRETRYIGNORE);
    return -1;
}

#else   // Mac或者linux

#include <netinet/in.h>
#include <arpa/inet.h>

static int load_pcap_lib() {
    return 0;
}

#endif

/**
 * 找到指定IP地址的网卡名
 * @param ip 物理网卡或者由虚拟软件生成的虚拟刚卡, 字符串形式,如"192.168.1.1"
 * @param name_buf 找到的对应网卡名称
 */
static int pcap_find_device(const char* ip, char* name_buf) {
    char err_buf[PCAP_ERRBUF_SIZE];
    pcap_if_t* pcap_if_list = NULL;
    struct in_addr dest_ip;
    pcap_if_t* item;

    inet_pton(AF_INET, ip, &dest_ip);

    int err = pcap_findalldevs(&pcap_if_list, err_buf);
    if (err < 0) {
        pcap_freealldevs(pcap_if_list);
        return -1;
    }

    for (item = pcap_if_list; item != NULL; item = item->next) {
        if (item->addresses == NULL) {
            continue;
        }

        for (struct pcap_addr* pcap_addr = item->addresses; pcap_addr != NULL; pcap_addr = pcap_addr->next) {
            struct sockaddr_in* curr_addr;
            struct sockaddr* sock_addr = pcap_addr->addr;

            if (sock_addr->sa_family != AF_INET) {
                continue;
            }

            curr_addr = ((struct sockaddr_in*)sock_addr);
            if (curr_addr->sin_addr.s_addr == dest_ip.s_addr) {
                strcpy(name_buf, item->name);
                pcap_freealldevs(pcap_if_list);
                return 0;
            }
        }
    }

    pcap_freealldevs(pcap_if_list);
    return -1;
}

/*
 * 显示所有的网络接口列表
 */
static int pcap_show_list(void) {
    char err_buf[PCAP_ERRBUF_SIZE];
    pcap_if_t* pcapif_list = NULL;
    int count = 0;

    // 查找所有的网络接口
    int err = pcap_findalldevs(&pcapif_list, err_buf);
    if (err < 0) {
        fprintf(stderr, "pcap_show_list: find all net list failed:%s\n", err_buf);
        pcap_freealldevs(pcapif_list);
        return -1;
    }

    printf("pcap_show_list: card list\n");

    // 遍历所有的可用接口,输出其信息
    for (pcap_if_t* item = pcapif_list; item != NULL; item = item->next) {
        if (item->addresses == NULL) {
            continue;
        }

        for (struct pcap_addr* pcap_addr = item->addresses; pcap_addr != NULL; pcap_addr = pcap_addr->next) {
            char str[INET_ADDRSTRLEN];
            struct sockaddr_in* ip_addr;

            struct sockaddr* sockaddr = pcap_addr->addr;
            if (sockaddr->sa_family != AF_INET) {
                continue;
            }

            ip_addr = (struct sockaddr_in*)sockaddr;
            printf("card %d: IP:%s name: %s, \n\n",
                count++,
                item->description == NULL ? "" : item->description,
                inet_ntop(AF_INET, &ip_addr->sin_addr, str, sizeof(str))
            );
            break;
        }
    }

    pcap_freealldevs(pcapif_list);

    if ((pcapif_list == NULL) || (count == 0)) {
        fprintf(stderr, "pcap_show_list: no available card!\n");
        return -1;
    }

    return 0;
}

/**
 * 打开pcap设备接口
 * @param ip 打开网卡的指定ip
 * @param 给网卡设置mac
 */
pcap_t* pcap_device_open(const char* ip, const uint8_t * mac_addr, uint8_t poll_mode) {
    char err_buf[PCAP_ERRBUF_SIZE];
    struct bpf_program fp;
    bpf_u_int32 mask;
    bpf_u_int32 net;
    char filter_exp[256];
    char name_buf[256];
    pcap_t* pcap;

    if (load_pcap_lib() < 0) {
        fprintf(stderr, "pcap_open: load pcap dll failed! install it first\n");
        return (pcap_t*)0;
    }

    if (pcap_find_device(ip, name_buf) < 0) {
        fprintf(stderr, "pcap_open: no net card has ip: %s, use the following:\n", ip);
        pcap_show_list();
        return (pcap_t*)0;
    }

    if (pcap_lookupnet(name_buf, &net, &mask, err_buf) == -1) {
        printf("pcap_open: can't find use net card: %s\n", name_buf);
        net = 0;
        mask = 0;
    }

    pcap = pcap_open_live(name_buf,    // 设置字符串
                          65536,  // 要捕获的最大字节数
                          1, // 混杂模式
                          0, // 读取超时(以毫秒为单位)
                          err_buf);
    if (pcap == NULL) {
        fprintf(stderr, "pcap_open: create pcap failed %s\n net card name: %s\n", err_buf, name_buf);
        fprintf(stderr, "Use the following:\n");
        pcap_show_list();
        return (pcap_t*)0;
    }

    // 非阻塞模式读取,程序中使用查询的方式读
    if (pcap_setnonblock(pcap, 1, err_buf) != 0) {
        fprintf(stderr, "pcap_open: set none block failed: %s\n", pcap_geterr(pcap));
        return (pcap_t*)0;
    }

    // 只捕获输入,不要捕获自己发出去的
    // 注:win平台似乎不支持这个选项
    if (pcap_setdirection(pcap, PCAP_D_IN) != 0) {
        // fprintf(stderr, "pcap_open: set direction not suppor: %s\n", pcap_geterr(pcap));
        
    }

    // 只捕获发往本接口与广播的数据帧。相当于只处理发往这张网卡的包
    sprintf(filter_exp,
            "(ether dst %02x:%02x:%02x:%02x:%02x:%02x or ether broadcast) and (not ether src %02x:%02x:%02x:%02x:%02x:%02x)",
            mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5],
            mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
    if (pcap_compile(pcap, &fp, filter_exp, 0, net) == -1) {
        printf("pcap_open: couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(pcap));
        return (pcap_t*)0;
    }
    if (pcap_setfilter(pcap, &fp) == -1) {
        printf("pcap_open: couldn't install filter %s: %s\n", filter_exp, pcap_geterr(pcap));
        return (pcap_t*)0;
    }

    return pcap;
}

/**
 * 关闭Pcapif接口
 */
void pcap_device_close(pcap_t* pcap) {
    if (pcap == (pcap_t *)0) {
        fprintf(stderr, "pcap = 0");
        pcap_show_list();
        return;
    }
    pcap_close(pcap);
}

/**
 * 向网络接口发送数据包
 */
uint32_t pcap_device_send(pcap_t* pcap, const uint8_t* buffer, uint32_t length) {
    if (pcap_sendpacket(pcap, buffer, length) == -1) {
        fprintf(stderr, "pcap send: send packet failed!:%s\n", pcap_geterr(pcap));
        fprintf(stderr, "pcap send: pcaket size %d\n", length);
        return 0;
    }

    return 0;
}

/**
 * 从网络接口读取数据包
 */
uint32_t pcap_device_read(pcap_t* pcap, uint8_t* buffer, uint32_t length) {
    int err;
    struct pcap_pkthdr* pkthdr;
    const uint8_t* pkt_data;

    err = pcap_next_ex(pcap, &pkthdr, &pkt_data);
    if (err == 0) {
        return 0;
    } else if (err == 1) {     // 1 - 成功读取数据包, 0 - 没有数据包,其它值-出错
        memcpy(buffer, pkt_data, pkthdr->len);
        return pkthdr->len;
    }

    fprintf(stderr, "pcap_read: reading packet failed!:%s", pcap_geterr(pcap));
    return 0;
}

 3.3port_pcap.c

#include<string.h>
#include<stdlib.h>
#include "xnet_tiny.h"
#include "pcap_device.h"

static pcap_t* pcap;
//这里是虚拟机的IP
const char* ip_str = "192.168.254.1";
//这里是给虚拟机上的 网卡设置的一个mac地址。MAC地址就相当于网卡的身份证号,唯一标识
const char my_mac_addr[] = { 0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88 };


//打开pcap以太网驱动函数的封装
 xnet_err_t xnet_driver_open(uint8_t* mac_addr) {//这里的MAC地址是由自己的驱动返回的,之后会被上层调用 
	 memcpy(mac_addr, my_mac_addr, sizeof(my_mac_addr));//将虚拟机上的网卡地址传入进来
	 //实现设备的打开——需要传入的参数有:虚拟机上的ip与其网卡的地址,还有工作模式的选择(此处为查找模式,固定填1,实现数据包的读取与发送)
	 pcap_device_open(ip_str, my_mac_addr, 1);
	 pcap = pcap_device_open(ip_str, my_mac_addr, 1);
	 if (pcap==(pcap_t*)0){
		 exit(-1);//直接调用stdlib的库函数实现退出,简单
	 }
	 return XNET_ERR_OK;
}

 //发送包函数封装
 xnet_err_t xnet_driver_send(xnet_packet_t* packet) {
	 return pcap_device_send(pcap, packet->data, packet->size);

 }

 //读取包函数封装,将读取到的数据封装到二级指针packet包里面
 xnet_err_t xnet_driver_read(xnet_packet_t** packet) {
	 uint16_t size;
	 //接收到包, 
	 xnet_packet_t* r_packet = xnet_alloc_for_read(XNET_CFG_PACKET_MAX_SIZE);
	 size = pcap_device_read(pcap, r_packet->data, XNET_CFG_PACKET_MAX_SIZE);
	 if (size){
		 r_packet->size = size;
		 *packet = r_packet;
		 return XNET_ERR_OK;
	 }
	 return XNET_ERR_IO;
 }

本来想推送到github的,但是这的VS2022抽风,直接报错推送不了,节约时间,之后在搞。


💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖

热门专栏推荐

🌈🌈计算机科学入门系列                     关注走一波💕💕

🌈🌈CSAPP深入理解计算机原理        关注走一波💕💕

🌈🌈微服务项目之黑马头条                 关注走一波💕💕

🌈🌈redis深度项目之黑马点评            关注走一波💕💕

🌈🌈JAVA面试八股文系列专栏           关注走一波💕💕

🌈🌈JAVA基础试题集精讲                  关注走一波💕💕   

🌈🌈代码随想录精讲200题                  关注走一波💕💕


总栏

🌈🌈JAVA基础要夯牢                         关注走一波💕💕  

🌈🌈​​​​​​JAVA后端技术栈                          关注走一波💕💕  

🌈🌈JAVA面试八股文​​​​​​                          关注走一波💕💕  

🌈🌈JAVA项目(含源码深度剖析)    关注走一波💕💕  

🌈🌈计算机四件套                               关注走一波💕💕  

🌈🌈数据结构与算法                           ​关注走一波💕💕  

🌈🌈必知必会工具集                           关注走一波💕💕

🌈🌈书籍网课笔记汇总                       关注走一波💕💕         



📣非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤收藏✅ 评论💬,大佬三连必回哦!thanks!!!
📚愿大家都能学有所得,功不唐捐!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值