🌈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!!!
📚愿大家都能学有所得,功不唐捐!