上一篇:Gannicus Guo的DIY TCP/IP之旅
5. 网络设备模块的实现
本章介绍DIY TCP/IP的网络设备模块的实现。网络设备层的逻辑包括,接收逻辑和发送逻辑。接收逻辑:DIY TCP/IP通过PF_PACKET域的socket接收链路层数据帧,模拟硬件网卡的接收。接收队列异步处理链路层接收的数据帧,解析以太网头部,根据以太网头部类型,将数据帧交给DIY TCP/IP的上层模块处理。发送逻辑:接收上层模块发送的数据帧,交给发送队列异步处理,同样通过PF_PACKET域的socket模拟硬件网卡的发送。本章基于第4章(从0开始)device.c的实现,继续模块化device.c,定义网络设备数据结构,封装pcap_loop接收的数据帧,实现接收队列和接收线程,在接收线程中异步处理pcap_loop接收的数据帧,解析数据帧,为调用上层模块的接口函数预留代码位置。后续章节实现DIY TCP/IP上层模块的接口函数后,替换预留的代码位置即可。
5.1 网络设备结构体
本节基于4.5节device.c的实现,继续模块化device.c,将与网络设备模块相关的逻辑放在device.c中,无关的逻辑放在其他模块文件中,先来看device.h中网络设备结构体的定义。
device.h
#ifndef _DEVICE_H_
#define _DEVICE_H_
#include
#define MAX_NETWORK_SEGMENT_SIZE 65535
#define PROMISC_ENABLE 1
#define PROMISC_DISABLE 0
#define TIMEOUT_MS 512
#define FILTER_BUFFER_SIZE 256
typedef struct _net_device {
pcap_t *pcap_dev;
} net_device_t;
net_device_t *netdev_init(char *ifname);
void netdev_deinit(net_device_t *ndev);
void netdev_start_loop(net_device_t *ndev);
void netdev_stop_loop(net_device_t *ndev);
net_device_t *netdev_get(void);
#endif
Line 4-9: 与4.6节一致。
Line 11-12: 定义网络设备结构体net_device_t。便于描述,将pcap_t结构体称为pcap设备,网络设备结构体目前只是封装了pcap设备,尚未加入发送队列和接收队列数据结构的定义。
Line 15-19: 声明网络设备模块暴露给其他模块的接口函数。netdev_init初始化网络设备;netdev_deinit销毁网络设备;netdev_start_loop,开始从链路层接收数据帧;netdev_stop_loop,结束从链路层接收数据帧;netdev_get获取网络设备结构体的指针。根据这些函数接口的定义,重写device.c,实现这些接口函数,分离与网络设备层无关的代码逻辑。
device.c
#include
#include
#include
#include
#include "init.h"
#include "common.h"
#include "device.h"
static net_device_t *gndev = NULL;
...
Line10: 定义静态net_device_t指针,用于保存netdev_init初始化的网络设备结构体的指针。
Line11 之后省略掉的代码是init_packet_filter与4.5节一致,pcap_callback与4.6节一致。
net_device_t * netdev_init(char *ifname)
{
net_device_t *ndev = NULL;
char pcap_packet_filter[FILTER_BUFFER_SIZE];
char err_buf[PCAP_ERRBUF_SIZE];
struct bpf_program filter_code;
ndev = (net_device_t *)malloc(sizeof(net_device_t));
if (ndev == NULL) {
printf("No memory for net device, %s (%d)\n",
strerror(errno), errno);
return NULL;
}
gndev = ndev;
printf("Network device init\n");
/* obtain packet capture handle */
ndev->pcap_dev = pcap_open_live(DEFAULT_IFNAME,
MAX_NETWORK_SEGMENT_SIZE, PROMISC_ENABLE, TIMEOUT_MS, err_buf);
if (ndev->pcap_dev == NULL) {
printf("pcap_open_live failed, %s (%d)\n",
strerror(errno), errno);
goto out;
}
/* set pcap filter */
memset(pcap_packet_filter, 0, FILTER_BUFFER_SIZE);
init_packet_filter(pcap_packet_filter, FILTER_BUFFER_SIZE);
if (pcap_compile(ndev->pcap_dev, &filter_code,
pcap_packet_filter, 1, IPV4_NETWORK_MASK) < 0) {
pcap_perror(ndev->pcap_dev, "pcap_compile");
goto out;
}
if (pcap_setfilter(ndev->pcap_dev, &filter_code) < 0) {
pcap_perror(ndev->pcap_dev, "pcap_setfilter");
goto out;
}
return ndev;
out:
netdev_deinit(ndev);
return NULL;
}
Line 8-14: 申请内存,创建网络设备。将网络设备结构体的指针保存在静态指针gndev中。
Line 16-39: 网络设备结构体net_device_t封装了pcap设备。后续章节用到的libpcap库函数,包括本节的pcap_open_live,,pcap_compile和pcap_setfilter均通过网络设备结构体访问pcap设备。在netdev_init函数中,打开pcap设备,设置帧过滤条件,返回网路设备结构体的指针。如果line55-71的libpcap库函数返回出错,调用netdev_deinit销毁网络设备,返回NULL。
void netdev_deinit(net_device_t *ndev)
{
if (ndev == NULL)
return;
printf("Network device deinit\n");
if (ndev->pcap_dev)
pcap_close(ndev->pcap_dev);
free(ndev);
gndev = NULL;
}
void netdev_start_loop(net_device_t *ndev)
{
if (ndev == NULL || ndev->pcap_dev == NULL)
return;
pcap_loop(ndev->pcap_dev, -1, pcap_callback, NULL);
printf("pcap_loop ended\n");
}
void netdev_stop_loop(net_device_t *ndev)
{
if (ndev == NULL || ndev->pcap_dev == NULL)
return;
pcap_breakloop(ndev->pcap_dev);
}
net_device_t *netdev_get(void)
{
return gndev;
}
Line 1-10: 销毁网络设备,调用pcap_close关闭pcap设备,然后释放netdev_init中为网络设备申请的内存。
Line 12-25: netdev_start_loop和netdev_stop_loop,是对pcap_loop和pcap_breakloop的封装,用于开始接和结束接收链路层数据帧。
Line 27-30: netdev_get返回网络设备结构体的指针,gndev供其他模块获取网络设备。
5.2 初始化模块
5.1节重写了device.c,目前device.c只包含与网络设备相关的逻辑。从零开始代码中的信号处理函数和入口main函数,放在DIY TCP/IP的初始化文件init.c中,init.c是DIY TCP/IP执行的入口。
#include
#include
#include "init.h"
#include "device.h"
void signal_handler(int sig_num)
{
net_device_t *ndev = NULL;
ndev = netdev_get();
if (ndev)
netdev_stop_loop(ndev);
}
int main(int argc, char *argv[])
{
int ret = 0;
net_device_t *ndev = NULL;
signal(SIGINT, signal_handler);
ndev = netdev_init(DEFAULT_IFNAME);
if (ndev == NULL)
return -1;
netdev_start_loop(ndev);
out:
netdev_deinit(ndev);
return ret;
}
Line 7-13: 信号处理函数,调用netdev_get获取网络设备结构体的指针,再调用netdev_stop_loop结束主循环。
Line 16-32: main函数是DIY TCP/IP的入口函数,先注册新号处理函数用于捕获SIGINT信号,netdev_init初始化网络设备模块,调用netdev_start_loop开始主循环。netdev_start_loop是对pcap_loop的封装,开始从链路层接收数据帧,直到终端键入ctrl+c结束循环。netdev_start_loop返回后,main函数继续执行到netdev_deinit销毁网络设备层,确保整个过程无内存泄漏。
把DIY TCP/IP初始化模块init.c加入到Makefile中,与device.c一起编译链接。Makefile修改如下:
tcp_ip_stack:device.o init.o
gcc -o tcp_ip_stack device.o init.o -lpcap
device.o:device.c device.h init.h common.h
gcc -c device.c
init.o:init.c init.h
gcc -c init.c
clean:
rm -rf *.o
rm -rf tcp_ip_stack
Line 1-2: 目标tcp_ip_stack,加入对init.o的依赖,命令行gcc链接tcp_ip_stack时加入init.o。
Line 3-4: 与4.6节一致。
Line 5-7: 加入init.o目标的依赖init.c和init.h,命令行gcc –c编译生成目标文件init.o
下一篇:DIY TCP/IP 网络设备模块2