乐于分享
好东西不私藏

跨平台底层网络库libdnet源码分析系列(十二)

跨平台底层网络库libdnet源码分析系列(十二)

源码分析mettle后门工具学习 所使用的依赖库

 libdnet 以太网与防火墙模块源码深入分析

目录

  1. 以太网模块概述
  2. Linux平台以太网实现
  3. BSD/macOS平台以太网实现
  4. Solaris平台以太网实现
  5. 防火墙模块概述
  6. BSD IPFW防火墙实现
  7. Linux ipchains防火墙实现
  8. BSD IPF防火墙实现
  9. OpenBSD PF防火墙实现
  10. Windows PktFilter实现
  11. 模块对比与最佳实践

以太网模块概述

1. 模块架构

libdnet的以太网模块提供了跨平台的二层网络访问接口,主要功能包括:

函数
功能描述
eth_open()
打开以太网设备
eth_get()
获取接口MAC地址
eth_set()
设置接口MAC地址
eth_send()
发送以太网帧
eth_close()
关闭以太网设备

2. 以太网帧结构

  1. // include/dnet/eth.h:29-33
  2. struct eth_hdr {
  3. eth_addr_t eth_dst;// 目标MAC地址(6字节)
  4. eth_addr_t eth_src;// 源MAC地址(6字节)
  5. uint16_t eth_type;// 以太类型(2字节)
  6. };

帧格式说明

  • 标准以太网帧:14字节头部 + 46-1500字节载荷 + 4字节CRC
  • 最小帧:64字节(包括CRC)
  • 最大帧:1518字节(包括CRC)

3. 常见以太类型

  1. // include/dnet/eth.h:38-52
  2. #define ETH_TYPE_IP        0x0800// IPv4协议
  3. #define ETH_TYPE_ARP       0x0806// 地址解析协议
  4. #define ETH_TYPE_REVARP    0x8035// 反向ARP
  5. #define ETH_TYPE_8021Q     0x8100// IEEE 802.1Q VLAN
  6. #define ETH_TYPE_IPV6      0x86DD// IPv6协议
  7. #define ETH_TYPE_MPLS      0x8847// MPLS
  8. #define ETH_TYPE_PPPOE     0x8864// PPPoE
  9. #define ETH_TYPE_LOOPBACK  0x9000// 回环测试

4. MAC地址操作

  1. // include/dnet/eth.h:74-79
  2. #define eth_pack_hdr(h, dst, src, type)do{      \
  3. struct eth_hdr *eth_pack_p =(struct eth_hdr *)(h);   \
  4.     memcpy(&eth_pack_p->eth_dst,&(dst), ETH_ADDR_LEN);  \
  5.     memcpy(&eth_pack_p->eth_src,&(src), ETH_ADDR_LEN);  \
  6.     eth_pack_p->eth_type = htons(type);          \
  7. }while(0)

Linux平台以太网实现

1. 核心数据结构

  1. // src/eth-linux.c:36-40
  2. struct eth_handle {
  3. int                 fd;// PF_PACKET套接字描述符
  4. struct ifreq        ifr;// 接口请求结构
  5. struct sockaddr_ll  sll;// 链路层套接字地址
  6. };

2. eth_open() 实现分析

  1. // src/eth-linux.c:42-67
  2. eth_t*
  3. eth_open(constchar*device)
  4. {
  5. eth_t*e;
  6. int n;
  7. if((= calloc(1,sizeof(*e)))!= NULL){
  8. // 步骤1: 创建PF_PACKET原始套接字
  9. if((e->fd = socket(PF_PACKET, SOCK_RAW,
  10.              htons(ETH_P_ALL)))<0)
  11. return(eth_close(e));
  12. // 步骤2: 允许广播
  13. #ifdef SO_BROADCAST
  14.         n =1;
  15. if(setsockopt(e->fd, SOL_SOCKET, SO_BROADCAST,&n,
  16. sizeof(n))<0)
  17. return(eth_close(e));
  18. #endif
  19. // 步骤3: 保存设备名称
  20.         strlcpy(e->ifr.ifr_name, device,sizeof(e->ifr.ifr_name));
  21. // 步骤4: 获取接口索引
  22. if(ioctl(e->fd, SIOCGIFINDEX,&e->ifr)<0)
  23. return(eth_close(e));
  24. // 步骤5: 配置链路层地址
  25.         e->sll.sll_family = AF_PACKET;
  26.         e->sll.sll_ifindex = e->ifr.ifr_ifindex;
  27. }
  28. return(e);
  29. }

实现细节

  1. PF_PACKET套接字创建c socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL))
  • PF_PACKET: Linux特有的协议族,用于链路层访问
  • SOCK_RAW: 原始套接字模式
  • ETH_P_ALL: 接收所有协议类型的以太网帧
  1. ioctl调用流程mermaid graph TD A[SIOCGIFINDEX]-->B[获取接口索引]B-->C[绑定到特定接口]A-->D[SIOCGIFHWADDR]D-->E[获取MAC地址]E-->F[eth_get调用]A-->G[SIOCSIFHWADDR]G-->H[设置MAC地址]H-->I[eth_set调用]

3. eth_send() 实现

  1. // src/eth-linux.c:69-78
  2. ssize_t
  3. eth_send(eth_t*e,constvoid*buf,size_t len)
  4. {
  5. struct eth_hdr *eth =(struct eth_hdr *)buf;
  6. // 从以太网头中提取协议类型
  7.     e->sll.sll_protocol = eth->eth_type;
  8. // 通过sendto发送数据包,指定链路层地址
  9. return(sendto(e->fd, buf, len,0,(struct sockaddr *)&e->sll,
  10. sizeof(e->sll)));
  11. }

发送流程

  1. 1.解析以太网头,提取eth_type
  2. 2.设置sll_protocol字段
  3. 3.调用sendto()发送
  4. 4.内核根据sll_ifindex路由到指定接口
  5. 5.硬件发送帧

4. eth_get() 实现

  1. // src/eth-linux.c:91-104
  2. int
  3. eth_get(eth_t*e,eth_addr_t*ea)
  4. {
  5. struct addr ha;
  6. // 通过ioctl获取硬件地址
  7. if(ioctl(e->fd, SIOCGIFHWADDR,&e->ifr)<0)
  8. return(-1);
  9. // 转换地址格式
  10. if(addr_ston(&e->ifr.ifr_hwaddr,&ha)<0)
  11. return(-1);
  12. // 复制MAC地址
  13.     memcpy(ea,&ha.addr_eth,sizeof(*ea));
  14. return(0);
  15. }

5. eth_set() 实现

  1. // src/eth-linux.c:106-118
  2. int
  3. eth_set(eth_t*e,consteth_addr_t*ea)
  4. {
  5. struct addr ha;
  6.     ha.addr_type = ADDR_TYPE_ETH;
  7.     ha.addr_bits = ETH_ADDR_BITS;
  8.     memcpy(&ha.addr_eth, ea, ETH_ADDR_LEN);
  9. // 转换地址格式
  10.     addr_ntos(&ha,&e->ifr.ifr_hwaddr);
  11. // 通过ioctl设置硬件地址
  12. return(ioctl(e->fd, SIOCSIFHWADDR,&e->ifr));
  13. }

注意事项

  • 修改MAC地址需要 CAP_NET_ADMIN权限
  • 某些网卡驱动可能不支持MAC地址修改
  • 修改后需要重启接口才能生效

BSD/macOS平台以太网实现

1. 核心数据结构

  1. // src/eth-bsd.c:34-37
  2. struct eth_handle {
  3. int     fd;// BPF文件描述符
  4. char    device[16];// 设备名称
  5. };

2. BPF (Berkeley Packet Filter) 简介

BPF是BSD系统的数据包捕获和过滤机制,特点:

  • 基于设备文件 /dev/bpf*
  • 支持高效的BPF字节码过滤
  • 提供原始数据包访问

3. eth_open() 实现分析

  1. // src/eth-bsd.c:39-74
  2. eth_t*
  3. eth_open(constchar*device)
  4. {
  5. struct ifreq ifr;
  6. char file[32];
  7. eth_t*e;
  8. int i;
  9. if((= calloc(1,sizeof(*e)))!= NULL){
  10. // 步骤1: 尝试打开可用的BPF设备
  11. for(=0; i <128; i++){
  12.             snprintf(file,sizeof(file),"/dev/bpf%d", i);
  13. /* O_RDWR: Mac OS X 10.6有bug,使用O_RDWR而不是O_WRONLY */
  14.             e->fd = open(file, O_RDWR);
  15. if(e->fd !=-1|| errno != EBUSY)
  16. break;
  17. }
  18. if(e->fd <0)
  19. return(eth_close(e));
  20. // 步骤2: 将BPF绑定到指定网络接口
  21.         memset(&ifr,0,sizeof(ifr));
  22.         strlcpy(ifr.ifr_name, device,sizeof(ifr.ifr_name));
  23. if(ioctl(e->fd, BIOCSETIF,(char*)&ifr)<0)
  24. return(eth_close(e));
  25. // 步骤3: 设置BIOCSETIF标志
  26. #ifdef BIOCSHDRCMPLT
  27.         i =1;
  28. if(ioctl(e->fd, BIOCSHDRCMPLT,&i)<0)
  29. return(eth_close(e));
  30. #endif
  31.         strlcpy(e->device, device,sizeof(e->device));
  32. }
  33. return(e);
  34. }

BPF ioctl命令说明

命令
功能
参数
BIOCSETIF
绑定到网络接口
struct ifreq
BIOCSHDRCMPLT
设置头部完成标志
int
BIOCGBLEN
获取缓冲区长度
int
BIOCSBLEN
设置缓冲区长度
int

4. eth_send() 实现

  1. // src/eth-bsd.c:76-80
  2. ssize_t
  3. eth_send(eth_t*e,constvoid*buf,size_t len)
  4. {
  5. return(write(e->fd, buf, len));
  6. }

发送流程

  1. 1.应用程序调用write()
  2. 2.数据写入BPF设备
  3. 3.内核通过绑定的接口发送
  4. 4.数据包进入网络栈

5. eth_get() 实现(sysctl方式)

  1. // src/eth-bsd.c:94-138
  2. int
  3. eth_get(eth_t*e,eth_addr_t*ea)
  4. {
  5. struct if_msghdr *ifm;
  6. struct sockaddr_dl *sdl;
  7. struct addr ha;
  8.     u_char *p,*buf;
  9. size_t len;
  10. int mib[]={ CTL_NET, AF_ROUTE,0, AF_LINK, NET_RT_IFLIST,0};
  11. // 步骤1: 获取所需缓冲区大小
  12. if(sysctl(mib,6, NULL,&len, NULL,0)<0)
  13. return(-1);
  14. // 步骤2: 分配缓冲区
  15. if((buf = malloc(len))== NULL)
  16. return(-1);
  17. // 步骤3: 获取接口列表
  18. if(sysctl(mib,6, buf,&len, NULL,0)<0){
  19.         free(buf);
  20. return(-1);
  21. }
  22. // 步骤4: 遍历接口列表查找目标接口
  23. for(= buf; p < buf + len; p += ifm->ifm_msglen){
  24.         ifm =(struct if_msghdr *)p;
  25.         sdl =(struct sockaddr_dl *)(ifm +1);
  26. if(ifm->ifm_type != RTM_IFINFO ||
  27. (ifm->ifm_addrs & RTA_IFP)==0)
  28. continue;
  29. if(sdl->sdl_family != AF_LINK || sdl->sdl_nlen ==0||
  30.             memcmp(sdl->sdl_data, e->device, sdl->sdl_nlen)!=0)
  31. continue;
  32. if(addr_ston((struct sockaddr *)sdl,&ha)==0)
  33. break;
  34. }
  35.     free(buf);
  36. if(>= buf + len){
  37.         errno = ESRCH;
  38. return(-1);
  39. }
  40.     memcpy(ea,&ha.addr_eth,sizeof(*ea));
  41. return(0);
  42. }

sysctl MIB树结构

  1. CTL_NET (4)
  2. └── AF_ROUTE (17)
  3. └──0(协议族)
  4. └── AF_LINK (18)
  5. └── NET_RT_IFLIST (3)
  6. └──0(接口索引)

6. eth_set() 实现

  1. // src/eth-bsd.c:148-164
  2. int
  3. eth_set(eth_t*e,consteth_addr_t*ea)
  4. {
  5. struct ifreq ifr;
  6. struct addr ha;
  7.     ha.addr_type = ADDR_TYPE_ETH;
  8.     ha.addr_bits = ETH_ADDR_BITS;
  9.     memcpy(&ha.addr_eth, ea, ETH_ADDR_LEN);
  10.     memset(&ifr,0,sizeof(ifr));
  11.     strlcpy(ifr.ifr_name, e->device,sizeof(ifr.ifr_name));
  12.     addr_ntos(&ha,&ifr.ifr_addr);
  13. // 使用SIOCSIFLLADDR设置链路层地址
  14. return(ioctl(e->fd, SIOCSIFLLADDR,&ifr));
  15. }

Solaris平台以太网实现

1. 核心数据结构

  1. // src/eth-dlpi.c:42-45
  2. struct eth_handle {
  3. int     fd;// DLPI流文件描述符
  4. int     sap_len;// SAP长度
  5. };

2. DLPI (Data Link Provider Interface) 简介

DLPI是Solaris和其他System V系统的数据链路接口规范:

  • 基于STREAMS机制
  • 支持多种介质类型(以太网、FDDI等)
  • 使用putmsg/getmsg进行消息交换

3. DLPI消息处理

  1. // src/eth-dlpi.c:47-72
  2. staticint
  3. dlpi_msg(int fd,union DL_primitives *dlp,int rlen,int flags,
  4. int ack,int alen,int size)
  5. {
  6. struct strbuf ctl;
  7.     ctl.maxlen =0;
  8.     ctl.len = rlen;
  9.     ctl.buf =(caddr_t)dlp;
  10. // 发送DLPI请求
  11. if(putmsg(fd,&ctl, NULL, flags)<0)
  12. return(-1);
  13.     ctl.maxlen = size;
  14.     ctl.len =0;
  15.     flags =0;
  16. // 接收DLPI响应
  17. if(getmsg(fd,&ctl, NULL,&flags)<0)
  18. return(-1);
  19. // 验证响应类型和长度
  20. if(dlp->dl_primitive != ack || ctl.len < alen)
  21. return(-1);
  22. return(0);
  23. }

4. eth_open() 实现分析

  1. // src/eth-dlpi.c:132-205
  2. eth_t*
  3. eth_open(constchar*device)
  4. {
  5. union DL_primitives *dlp;
  6. uint32_t buf[8192];
  7. char*p, dev[16];
  8. eth_t*e;
  9. int ppa;
  10. if((= calloc(1,sizeof(*e)))== NULL)
  11. return(NULL);
  12. #ifdef HAVE_SYS_DLPIHDR_H
  13. // OSF1方式
  14. if((e->fd = open("/dev/streams/dlb", O_RDWR))<0)
  15. return(eth_close(e));
  16. if((ppa = eth_match_ppa(e, device))<0){
  17.         errno = ESRCH;
  18. return(eth_close(e));
  19. }
  20. #else
  21. // 标准DLPI方式
  22.     e->fd =-1;
  23.     snprintf(dev,sizeof(dev),"/dev/%s", device);
  24. if((= dev_find_ppa(dev))== NULL){
  25.         errno = EINVAL;
  26. return(eth_close(e));
  27. }
  28.     ppa = atoi(p);
  29. *='\0';
  30. // 尝试多个可能的设备路径
  31. if((e->fd = open(dev, O_RDWR))<0){
  32.         snprintf(dev,sizeof(dev),"/dev/%s", device);
  33. if((e->fd = open(dev, O_RDWR))<0){
  34.             snprintf(dev,sizeof(dev),"/dev/net/%s", device);
  35. if((e->fd = open(dev, O_RDWR))<0)
  36. return(eth_close(e));
  37. }
  38. }
  39. #endif
  40. // 步骤1: 发送DL_INFO_REQ获取提供商信息
  41.     dlp =(union DL_primitives *)buf;
  42.     dlp->info_req.dl_primitive = DL_INFO_REQ;
  43. if(dlpi_msg(e->fd, dlp, DL_INFO_REQ_SIZE, RS_HIPRI,
  44.         DL_INFO_ACK, DL_INFO_ACK_SIZE,sizeof(buf))<0)
  45. return(eth_close(e));
  46.     e->sap_len = dlp->info_ack.dl_sap_length;
  47. // 步骤2: 如果是STYLE2,需要附加到PPA
  48. if(dlp->info_ack.dl_provider_style == DL_STYLE2){
  49.         dlp->attach_req.dl_primitive = DL_ATTACH_REQ;
  50.         dlp->attach_req.dl_ppa = ppa;
  51. if(dlpi_msg(e->fd, dlp, DL_ATTACH_REQ_SIZE,0,
  52.             DL_OK_ACK, DL_OK_ACK_SIZE,sizeof(buf))<0)
  53. return(eth_close(e));
  54. }
  55. // 步骤3: 绑定到服务接入点
  56.     memset(&dlp->bind_req,0, DL_BIND_REQ_SIZE);
  57.     dlp->bind_req.dl_primitive = DL_BIND_REQ;
  58. #ifdef DL_HP_RAWDLS
  59.     dlp->bind_req.dl_sap =24;// HP-UX特殊值
  60.     dlp->bind_req.dl_service_mode = DL_HP_RAWDLS;
  61. #else
  62.     dlp->bind_req.dl_sap = DL_ETHER;
  63.     dlp->bind_req.dl_service_mode = DL_CLDLS;
  64. #endif
  65. if(dlpi_msg(e->fd, dlp, DL_BIND_REQ_SIZE,0,
  66.         DL_BIND_ACK, DL_BIND_ACK_SIZE,sizeof(buf))<0)
  67. return(eth_close(e));
  68. // 步骤4: 启用原始模式
  69. #ifdef DLIOCRAW
  70. if(strioctl(e->fd, DLIOCRAW,0, NULL)<0)
  71. return(eth_close(e));
  72. #endif
  73. return(e);
  74. }

DLPI握手流程

  1. 1. DL_INFO_REQ -> DL_INFO_ACK (获取提供商信息)
  2. 2. DL_ATTACH_REQ -> DL_OK_ACK (STYLE2:附加到PPA)
  3. 3. DL_BIND_REQ -> DL_BIND_ACK (绑定到SAP)
  4. 4. DLIOCRAW ioctl (启用原始模式)

5. eth_send() 实现

  1. // src/eth-dlpi.c:207-257
  2. ssize_t
  3. eth_send(eth_t*e,constvoid*buf,size_t len)
  4. {
  5. #if defined(DLIOCRAW)
  6. // 简单模式:直接写入
  7. return(write(e->fd, buf, len));
  8. #else
  9. // 复杂模式:构建DL_UNITDATA_REQ消息
  10. union DL_primitives *dlp;
  11. struct strbuf ctl, data;
  12. struct eth_hdr *eth;
  13. uint32_t ctlbuf[8192];
  14.     u_char sap[4]={0,0,0,0};
  15. int dlen;
  16.     dlp =(union DL_primitives *)ctlbuf;
  17. #ifdef DL_HP_RAWDATA_REQ
  18.     dlp->dl_primitive = DL_HP_RAWDATA_REQ;
  19.     dlen = DL_HP_RAWDATA_REQ_SIZE;
  20. #else
  21.     dlp->unitdata_req.dl_primitive = DL_UNITDATA_REQ;
  22.     dlp->unitdata_req.dl_dest_addr_length = ETH_ADDR_LEN;
  23.     dlp->unitdata_req.dl_dest_addr_offset = DL_UNITDATA_REQ_SIZE;
  24.     dlp->unitdata_req.dl_priority.dl_min =
  25.         dlp->unitdata_req.dl_priority.dl_max =0;
  26.     dlen = DL_UNITDATA_REQ_SIZE;
  27. #endif
  28.     eth =(struct eth_hdr *)buf;
  29. *(uint16_t*)sap = ntohs(eth->eth_type);
  30. // 构建控制消息
  31.     ctl.maxlen =0;
  32.     ctl.len = dlen + ETH_ADDR_LEN + abs(e->sap_len);
  33.     ctl.buf =(char*)ctlbuf;
  34. if(e->sap_len >=0){
  35.         memcpy(ctlbuf + dlen, sap, e->sap_len);
  36.         memcpy(ctlbuf + dlen + e->sap_len,
  37.             eth->eth_dst.data, ETH_ADDR_LEN);
  38. }else{
  39.         memcpy(ctlbuf + dlen, eth->eth_dst.data, ETH_ADDR_LEN);
  40.         memcpy(ctlbuf + dlen + ETH_ADDR_LEN, sap, abs(e->sap_len));
  41. }
  42. // 构建数据消息
  43.     data.maxlen =0;
  44.     data.len = len;
  45.     data.buf =(char*)buf;
  46. // 使用putmsg发送
  47. if(putmsg(e->fd,&ctl,&data,0)<0)
  48. return(-1);
  49. return(len);
  50. #endif
  51. }

6. ethget() 和 ethset() 实现

  1. // src/eth-dlpi.c:270-287
  2. int
  3. eth_get(eth_t*e,eth_addr_t*ea)
  4. {
  5. union DL_primitives *dlp;
  6.     u_char buf[2048];
  7.     dlp =(union DL_primitives *)buf;
  8.     dlp->physaddr_req.dl_primitive = DL_PHYS_ADDR_REQ;
  9.     dlp->physaddr_req.dl_addr_type = DL_CURR_PHYS_ADDR;
  10. if(dlpi_msg(e->fd, dlp, DL_PHYS_ADDR_REQ_SIZE,0,
  11.         DL_PHYS_ADDR_ACK, DL_PHYS_ADDR_ACK_SIZE,sizeof(buf))<0)
  12. return(-1);
  13.     memcpy(ea, buf + dlp->physaddr_ack.dl_addr_offset,sizeof(*ea));
  14. return(0);
  15. }
  16. // src/eth-dlpi.c:289-304
  17. int
  18. eth_set(eth_t*e,consteth_addr_t*ea)
  19. {
  20. union DL_primitives *dlp;
  21.     u_char buf[2048];
  22.     dlp =(union DL_primitives *)buf;
  23.     dlp->set_physaddr_req.dl_primitive = DL_SET_PHYS_ADDR_REQ;
  24.     dlp->set_physaddr_req.dl_addr_length = ETH_ADDR_LEN;
  25.     dlp->set_physaddr_req.dl_addr_offset = DL_SET_PHYS_ADDR_REQ_SIZE;
  26.     memcpy(buf + DL_SET_PHYS_ADDR_REQ_SIZE, ea,sizeof(*ea));
  27. return(dlpi_msg(e->fd, dlp, DL_SET_PHYS_ADDR_REQ_SIZE + ETH_ADDR_LEN,
  28. 0, DL_OK_ACK, DL_OK_ACK_SIZE,sizeof(buf)));
  29. }

防火墙模块概述

1. 模块架构

防火墙模块提供跨平台的防火墙规则管理接口:

  1. // include/dnet/fw.h
  2. struct fw_rule {
  3. char    fw_device[INTF_NAME_LEN];// 网络接口
  4. uint8_t fw_op;// 操作:允许/拒绝
  5. uint8_t fw_dir;// 方向:入站/出站
  6. uint8_t fw_proto;// 协议:TCP/UDP/ICMP
  7. struct addr fw_src;// 源地址
  8. struct addr fw_dst;// 目标地址
  9. uint16_t fw_sport[2];// 源端口范围
  10. uint16_t fw_dport[2];// 目标端口范围
  11. };

2. 核心函数

函数
功能描述
fw_open()
打开防火墙子系统
fw_add()
添加防火墙规则
fw_delete()
删除防火墙规则
fw_loop()
遍历防火墙规则
fw_close()
关闭防火墙子系统

3. 平台支持矩阵

平台
防火墙系统
实现文件
FreeBSD
ipfw
fw-ipfw.c
Linux (2.2)
ipchains
fw-ipchains.c
Solaris/HP-UX/IRIX
ipfilter
fw-ipf.c
OpenBSD/FreeBSD
pf
fw-pf.c
Windows
PktFilter
fw-pktfilter.c
其他
fw-none.c

BSD IPFW防火墙实现

1. 核心数据结构

  1. // src/fw-ipfw.c:28-30
  2. struct fw_handle {
  3. int     fd;// 原始套接字描述符
  4. };

2. 规则转换:fwrule -> ipfw

  1. // src/fw-ipfw.c:42-109
  2. staticvoid
  3. fr_to_ipfw(conststruct fw_rule *fr,struct ip_fw *ipfw)
  4. {
  5. int i;
  6.     memset(ipfw,0,sizeof(*ipfw));
  7. // 方向和接口
  8. if(fr->fw_dir == FW_DIR_IN){
  9. if(*fr->fw_device !='\0'){
  10.             fr_to_ipfw_device(fr->fw_device,
  11.                 ipfw->fw_in_if.fu_via_if.name,
  12. &ipfw->fw_in_if.fu_via_if.unit);
  13.             ipfw->fw_flg |= IP_FW_F_IIFNAME;
  14. }
  15.         ipfw->fw_flg |= IP_FW_F_IN;
  16. }else{
  17. if(*fr->fw_device !='\0'){
  18.             fr_to_ipfw_device(fr->fw_device,
  19.                 ipfw->fw_out_if.fu_via_if.name,
  20. &ipfw->fw_out_if.fu_via_if.unit);
  21.             ipfw->fw_flg |= IP_FW_F_OIFNAME;
  22. }
  23.         ipfw->fw_flg |= IP_FW_F_OUT;
  24. }
  25. // 操作
  26. if(fr->fw_op == FW_OP_ALLOW)
  27.         ipfw->fw_flg |= IP_FW_F_ACCEPT;
  28. else
  29.         ipfw->fw_flg |= IP_FW_F_DENY;
  30. // 协议和地址
  31.     ipfw->fw_prot = fr->fw_proto;
  32.     ipfw->fw_src.s_addr = fr->fw_src.addr_ip;
  33.     ipfw->fw_dst.s_addr = fr->fw_dst.addr_ip;
  34.     addr_btom(fr->fw_src.addr_bits,&ipfw->fw_smsk.s_addr, IP_ADDR_LEN);
  35.     addr_btom(fr->fw_dst.addr_bits,&ipfw->fw_dmsk.s_addr, IP_ADDR_LEN);
  36. // 端口处理
  37. switch(fr->fw_proto){
  38. case IP_PROTO_TCP:
  39. case IP_PROTO_UDP:
  40.         i =0;
  41. if(fr->fw_sport[0]!= fr->fw_sport[1]){
  42.             ipfw->fw_flg |= IP_FW_F_SRNG;
  43.             ipfw->fw_uar.fw_pts[i++]= fr->fw_sport[0];
  44.             ipfw->fw_uar.fw_pts[i++]= fr->fw_sport[1];
  45.             IP_FW_SETNSRCP(ipfw,2);
  46. }elseif(fr->fw_sport[0]>0){
  47.             ipfw->fw_uar.fw_pts[i++]= fr->fw_sport[0];
  48.             IP_FW_SETNSRCP(ipfw,1);
  49. }
  50. if(fr->fw_dport[0]!= fr->fw_dport[1]){
  51.             ipfw->fw_flg |= IP_FW_F_DRNG;
  52.             ipfw->fw_uar.fw_pts[i++]= fr->fw_dport[0];
  53.             ipfw->fw_uar.fw_pts[i++]= fr->fw_dport[1];
  54.             IP_FW_SETNDSTP(ipfw,2);
  55. }elseif(fr->fw_dport[0]>0){
  56.             ipfw->fw_uar.fw_pts[i++]= fr->fw_dport[0];
  57.             IP_FW_SETNDSTP(ipfw,1);
  58. }
  59. break;
  60. case IP_PROTO_ICMP:
  61. if(fr->fw_sport[1]){
  62.             ipfw->fw_uar.fw_icmptypes[fr->fw_sport[0]/32]|=
  63. 1<<(fr->fw_sport[0]%32);
  64.             ipfw->fw_flg |= IP_FW_F_ICMPBIT;
  65. }
  66. break;
  67. }
  68. }

3. fw_open() 实现

  1. // src/fw-ipfw.c:184-194
  2. fw_t*
  3. fw_open(void)
  4. {
  5. fw_t*fw;
  6. if((fw = calloc(1,sizeof(*fw)))!= NULL){
  7. // 创建原始套接字用于防火墙操作
  8. if((fw->fd = socket(AF_INET, SOCK_RAW, IPPROTO_IP))<0)
  9. return(fw_close(fw));
  10. }
  11. return(fw);
  12. }

4. fw_add() 实现

  1. // src/fw-ipfw.c:196-207
  2. int
  3. fw_add(fw_t*fw,conststruct fw_rule *rule)
  4. {
  5. struct ip_fw ipfw;
  6.     assert(fw != NULL && rule != NULL);
  7. // 转换规则格式
  8.     fr_to_ipfw(rule,&ipfw);
  9. // 使用setsockopt添加规则
  10. return(setsockopt(fw->fd, IPPROTO_IP, IP_FW_ADD,
  11. &ipfw,sizeof(ipfw)));
  12. }

5. fw_delete() 实现

  1. // src/fw-ipfw.c:222-273
  2. int
  3. fw_delete(fw_t*fw,conststruct fw_rule *rule)
  4. {
  5. struct ip_fw *ipfw;
  6. struct fw_rule fr;
  7. int nbytes, nalloc, ret;
  8.     u_char *buf,*new;
  9.     assert(rule != NULL);
  10. // 步骤1: 获取所有规则
  11.     nbytes = nalloc =sizeof(*ipfw);
  12. if((buf = malloc(nbytes))== NULL)
  13. return(-1);
  14. while(nbytes >= nalloc){
  15.         nalloc = nalloc *2+200;
  16.         nbytes = nalloc;
  17. if((new= realloc(buf, nbytes))== NULL){
  18. if(buf)
  19.                 free(buf);
  20. return(-1);
  21. }
  22.         buf =new;
  23. if(getsockopt(fw->fd, IPPROTO_IP, IP_FW_GET,
  24.                buf,&nbytes)<0){
  25.             free(buf);
  26. return(-1);
  27. }
  28. }
  29. // 步骤2: 查找匹配的规则
  30.     ret =-1;
  31. // 65535是ipfw的默认规则
  32. for(ipfw =(struct ip_fw *)buf; ipfw->fw_number <65535; ipfw++){
  33.         ipfw_to_fr(ipfw,&fr);
  34. if(fw_cmp(&fr, rule)==0){
  35. if(setsockopt(fw->fd, IPPROTO_IP, IP_FW_DEL,
  36.                 ipfw,sizeof(*ipfw))<0)
  37.                 ret =-2;
  38. else
  39.                 ret =0;
  40. break;
  41. }
  42. }
  43.     free(buf);
  44. if(ret <0){
  45. if(ret ==-1)
  46.             errno = ESRCH;
  47. return(-1);
  48. }
  49. return(0);
  50. }

6. fw_loop() 实现

  1. // src/fw-ipfw.c:275-313
  2. int
  3. fw_loop(fw_t*fw, fw_handler callback,void*arg)
  4. {
  5. struct ip_fw *ipfw;
  6. struct fw_rule fr;
  7. int i, cnt, nbytes, nalloc, ret;
  8.     u_char *buf,*new;
  9. // 获取规则列表
  10.     nbytes = nalloc =sizeof(*ipfw);
  11. if((buf = malloc(nbytes))== NULL)
  12. return(-1);
  13. while(nbytes >= nalloc){
  14.         nalloc = nalloc *2+200;
  15.         nbytes = nalloc;
  16. if((new= realloc(buf, nbytes))== NULL){
  17. if(buf)
  18.                 free(buf);
  19. return(-1);
  20. }
  21.         buf =new;
  22. if(getsockopt(fw->fd, IPPROTO_IP, IP_FW_GET,
  23.                buf,&nbytes)<0){
  24.             free(buf);
  25. return(-1);
  26. }
  27. }
  28.     cnt = nbytes /sizeof(*ipfw);
  29.     ipfw =(struct ip_fw *)buf;
  30.     ret =0;
  31. // 遍历规则
  32. for(=0; i < cnt; i++){
  33.         ipfw_to_fr(&ipfw[i],&fr);
  34. if((ret = callback(&fr, arg))!=0)
  35. break;
  36. }
  37.     free(buf);
  38. return(ret);
  39. }

Linux ipchains防火墙实现

1. 核心数据结构

  1. // src/fw-ipchains.c:40-42
  2. struct fw_handle {
  3. int     fd;// 原始套接字描述符
  4. };
  5. #define PROC_IPCHAINS_FILE    "/proc/net/ip_fwchains"

2. 规则转换:fwrule -> ipfwchange

  1. // src/fw-ipchains.c:44-78
  2. staticvoid
  3. fr_to_fwc(conststruct fw_rule *fr,struct ip_fwchange *fwc)
  4. {
  5.     memset(fwc,0,sizeof(*fwc));
  6. // 接口名称
  7.     strlcpy(fwc->fwc_rule.ipfw.fw_vianame, fr->fw_device, IFNAMSIZ);
  8. // 操作和方向标签
  9. if(fr->fw_op == FW_OP_ALLOW)
  10.         strlcpy(fwc->fwc_rule.label, IP_FW_LABEL_ACCEPT,
  11. sizeof(fwc->fwc_rule.label));
  12. else
  13.         strlcpy(fwc->fwc_rule.label, IP_FW_LABEL_BLOCK,
  14. sizeof(fwc->fwc_rule.label));
  15. if(fr->fw_dir == FW_DIR_IN)
  16.         strlcpy(fwc->fwc_label, IP_FW_LABEL_INPUT,
  17. sizeof(fwc->fwc_label));
  18. else
  19.         strlcpy(fwc->fwc_label, IP_FW_LABEL_OUTPUT,
  20. sizeof(fwc->fwc_label));
  21. // 协议和地址
  22.     fwc->fwc_rule.ipfw.fw_proto = fr->fw_proto;
  23.     fwc->fwc_rule.ipfw.fw_src.s_addr = fr->fw_src.addr_ip;
  24.     fwc->fwc_rule.ipfw.fw_dst.s_addr = fr->fw_dst.addr_ip;
  25.     addr_btom(fr->fw_src.addr_bits,&fwc->fwc_rule.ipfw.fw_smsk.s_addr,
  26.         IP_ADDR_LEN);
  27.     addr_btom(fr->fw_dst.addr_bits,&fwc->fwc_rule.ipfw.fw_dmsk.s_addr,
  28.         IP_ADDR_LEN);
  29. // 端口范围
  30.     fwc->fwc_rule.ipfw.fw_spts[0]= fr->fw_sport[0];
  31.     fwc->fwc_rule.ipfw.fw_spts[1]= fr->fw_sport[1];
  32.     fwc->fwc_rule.ipfw.fw_dpts[0]= fr->fw_dport[0];
  33.     fwc->fwc_rule.ipfw.fw_dpts[1]= fr->fw_dport[1];
  34. }

3. fw_open() 实现

  1. // src/fw-ipchains.c:114-124
  2. fw_t*
  3. fw_open(void)
  4. {
  5. fw_t*fw;
  6. if((fw = calloc(1,sizeof(*fw)))!= NULL){
  7. // 创建原始套接字
  8. if((fw->fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))<0)
  9. return(fw_close(fw));
  10. }
  11. return(fw);
  12. }

4. fwadd() 和 fwdelete() 实现

  1. // src/fw-ipchains.c:126-146
  2. int
  3. fw_add(fw_t*fw,conststruct fw_rule *rule)
  4. {
  5. struct ip_fwchange fwc;
  6.     fr_to_fwc(rule,&fwc);
  7. // 使用IP_FW_APPEND追加规则
  8. return(setsockopt(fw->fd, IPPROTO_IP, IP_FW_APPEND,
  9. &fwc,sizeof(fwc)));
  10. }
  11. int
  12. fw_delete(fw_t*fw,conststruct fw_rule *rule)
  13. {
  14. struct ip_fwchange fwc;
  15.     fr_to_fwc(rule,&fwc);
  16. // 使用IP_FW_DELETE删除规则
  17. return(setsockopt(fw->fd, IPPROTO_IP, IP_FW_DELETE,
  18. &fwc,sizeof(fwc)));
  19. }

5. fw_loop() 实现(通过/proc解析)

  1. // src/fw-ipchains.c:148-216
  2. int
  3. fw_loop(fw_t*fw, fw_handler callback,void*arg)
  4. {
  5. FILE*fp;
  6. struct ip_fwchange fwc;
  7. struct fw_rule fr;
  8. char buf[BUFSIZ];
  9.     u_int phi, plo, bhi, blo, tand, txor;
  10. int ret;
  11. // 打开/proc/net/ip_fwchains文件
  12. if((fp = fopen(PROC_IPCHAINS_FILE,"r"))== NULL)
  13. return(-1);
  14. // 解析每一行
  15. while(fgets(buf,sizeof(buf), fp)!= NULL){
  16. if(sscanf(buf,
  17. "%8s %X/%X->%X/%X %s %hX %hX %hu %u %u %u %u "
  18. "%hu-%hu %hu-%hu A%X X%X %hX %u %hu %s\n",
  19.             fwc.fwc_label,
  20. &fwc.fwc_rule.ipfw.fw_src.s_addr,
  21. &fwc.fwc_rule.ipfw.fw_smsk.s_addr,
  22. &fwc.fwc_rule.ipfw.fw_dst.s_addr,
  23. &fwc.fwc_rule.ipfw.fw_dmsk.s_addr,
  24.             fwc.fwc_rule.ipfw.fw_vianame,
  25. &fwc.fwc_rule.ipfw.fw_flg,
  26. &fwc.fwc_rule.ipfw.fw_invflg,
  27. &fwc.fwc_rule.ipfw.fw_proto,
  28. &phi,&plo,&bhi,&blo,
  29. &fwc.fwc_rule.ipfw.fw_spts[0],
  30. &fwc.fwc_rule.ipfw.fw_spts[1],
  31. &fwc.fwc_rule.ipfw.fw_dpts[0],
  32. &fwc.fwc_rule.ipfw.fw_dpts[1],
  33. &tand,&txor,
  34. &fwc.fwc_rule.ipfw.fw_redirpt,
  35. &fwc.fwc_rule.ipfw.fw_mark,
  36. &fwc.fwc_rule.ipfw.fw_outputsize,
  37.             fwc.fwc_rule.label)!=23)
  38. break;
  39. // 过滤不支持的规则类型
  40. if(strcmp(fwc.fwc_rule.label, IP_FW_LABEL_ACCEPT)!=0&&
  41.             strcmp(fwc.fwc_rule.label, IP_FW_LABEL_BLOCK)!=0&&
  42.             strcmp(fwc.fwc_rule.label, IP_FW_LABEL_REJECT)!=0)
  43. continue;
  44. if(strcmp(fwc.fwc_label, IP_FW_LABEL_INPUT)!=0&&
  45.             strcmp(fwc.fwc_label, IP_FW_LABEL_OUTPUT)!=0)
  46. continue;
  47. // 转换地址格式(从网络字节序到主机字节序)
  48. if(strcmp(fwc.fwc_rule.label,"-")==0)
  49. (fwc.fwc_rule.label)[0]='\0';
  50. if(strcmp(fwc.fwc_rule.ipfw.fw_vianame,"-")==0)
  51. (fwc.fwc_rule.ipfw.fw_vianame)[0]='\0';
  52.         fwc.fwc_rule.ipfw.fw_src.s_addr =
  53.             htonl(fwc.fwc_rule.ipfw.fw_src.s_addr);
  54.         fwc.fwc_rule.ipfw.fw_dst.s_addr =
  55.             htonl(fwc.fwc_rule.ipfw.fw_dst.s_addr);
  56.         fwc.fwc_rule.ipfw.fw_smsk.s_addr =
  57.             htonl(fwc.fwc_rule.ipfw.fw_smsk.s_addr);
  58.         fwc.fwc_rule.ipfw.fw_dmsk.s_addr =
  59.             htonl(fwc.fwc_rule.ipfw.fw_dmsk.s_addr);
  60.         fwc_to_fr(&fwc,&fr);
  61. if((ret = callback(&fr, arg))!=0){
  62.             fclose(fp);
  63. return(ret);
  64. }
  65. }
  66.     fclose(fp);
  67. return(0);
  68. }

BSD IPF防火墙实现

1. 核心数据结构

  1. // src/fw-ipf.c:46-49
  2. struct fw_handle {
  3. int     fd;// IPF设备文件描述符
  4. int     kfd;// 内核内存文件描述符
  5. };
  6. #define KMEM_NAME    "/dev/kmem"

2. 规则转换:fw_rule -> frentry

  1. // src/fw-ipf.c:51-100
  2. staticvoid
  3. rule_to_ipf(conststruct fw_rule *rule,struct frentry *fr)
  4. {
  5.     memset(fr,0,sizeof(*fr));
  6. // 接口名称
  7. if(*rule->fw_device !='\0'){
  8.         strlcpy(fr->fr_ifname, rule->fw_device, IFNAMSIZ);
  9.         strlcpy(fr->fr_oifname, rule->fw_device, IFNAMSIZ);
  10. }
  11. // 操作和方向
  12. if(rule->fw_op == FW_OP_ALLOW)
  13.         fr->fr_flags |= FR_PASS;
  14. else
  15.         fr->fr_flags |= FR_BLOCK;
  16. if(rule->fw_dir == FW_DIR_IN)
  17.         fr->fr_flags |= FR_INQUE;
  18. else
  19.         fr->fr_flags |= FR_OUTQUE;
  20. // 协议和地址
  21.     fr->fr_ip.fi_p = rule->fw_proto;
  22.     fr->fr_ip.fi_saddr = rule->fw_src.addr_ip;
  23.     fr->fr_ip.fi_daddr = rule->fw_dst.addr_ip;
  24.     addr_btom(rule->fw_src.addr_bits,&fr->fr_mip.fi_saddr, IP_ADDR_LEN);
  25.     addr_btom(rule->fw_dst.addr_bits,&fr->fr_mip.fi_daddr, IP_ADDR_LEN);
  26. // 端口和ICMP类型
  27. switch(rule->fw_proto){
  28. case IPPROTO_ICMP:
  29.         fr->fr_icmpm = rule->fw_sport[1]<<8|
  30. (rule->fw_dport[1]&0xff);
  31.         fr->fr_icmp = rule->fw_sport[0]<<8|
  32. (rule->fw_dport[0]&0xff);
  33. break;
  34. case IPPROTO_TCP:
  35. case IPPROTO_UDP:
  36.         fr->fr_sport = rule->fw_sport[0];
  37. if(rule->fw_sport[0]!= rule->fw_sport[1]){
  38.             fr->fr_scmp = FR_INRANGE;
  39.             fr->fr_stop = rule->fw_sport[1];
  40. }else
  41.             fr->fr_scmp = FR_EQUAL;
  42.         fr->fr_dport = rule->fw_dport[0];
  43. if(rule->fw_dport[0]!= rule->fw_dport[1]){
  44.             fr->fr_dcmp = FR_INRANGE;
  45.             fr->fr_dtop = rule->fw_dport[1];
  46. }else
  47.             fr->fr_dcmp = FR_EQUAL;
  48. break;
  49. }
  50. }

3. fw_open() 实现

  1. // src/fw-ipf.c:178-191
  2. fw_t*
  3. fw_open(void)
  4. {
  5. fw_t*fw;
  6. if((fw = calloc(1,sizeof(*fw)))!= NULL){
  7.         fw->fd = fw->kfd =-1;
  8. // 打开IPF设备文件
  9. if((fw->fd = open(IPL_NAME, O_RDWR,0))<0)
  10. return(fw_close(fw));
  11. // 打开内核内存
  12. if((fw->kfd = open(KMEM_NAME, O_RDONLY))<0)
  13. return(fw_close(fw));
  14. }
  15. return(fw);
  16. }

4. fwadd() 和 fwdelete() 实现

  1. // src/fw-ipf.c:193-215
  2. int
  3. fw_add(fw_t*fw,conststruct fw_rule *rule)
  4. {
  5. struct frentry fr;
  6.     assert(fw != NULL && rule != NULL);
  7.     rule_to_ipf(rule,&fr);
  8. // 使用ioctl添加规则
  9. return(ioctl(fw->fd, SIOCADDFR,&fr));
  10. }
  11. int
  12. fw_delete(fw_t*fw,conststruct fw_rule *rule)
  13. {
  14. struct frentry fr;
  15.     assert(fw != NULL && rule != NULL);
  16.     rule_to_ipf(rule,&fr);
  17. // 使用ioctl删除规则
  18. return(ioctl(fw->fd, SIOCDELFR,&fr));
  19. }

5. fw_loop() 实现(通过内核内存)

  1. // src/fw-ipf.c:217-268
  2. int
  3. fw_loop(fw_t*fw, fw_handler callback,void*arg)
  4. {
  5. struct friostat fio;
  6. struct friostat *fiop =&fio;
  7. struct frentry *frp, fr;
  8. struct fw_rule rule;
  9. int ret;
  10.     memset(&fio,0,sizeof(fio));
  11. // 获取防火墙统计信息
  12. #ifdef __OpenBSD__
  13. if(ioctl(fw->fd, SIOCGETFS, fiop)<0)
  14. #else
  15. if(ioctl(fw->fd, SIOCGETFS,&fiop)<0)/* XXX - OpenBSD差异 */
  16. #endif
  17. return(-1);
  18. // 遍历出站规则
  19. for(frp = fio.f_fout[(int)fio.f_active]; frp != NULL;
  20.         frp = fr.fr_next){
  21. if(fw_kcopy(fw,(u_char *)&fr,(u_long)frp,sizeof(fr))<0)
  22. return(-1);
  23.         ipf_to_rule(&fr,&rule);
  24. if((ret = callback(&rule, arg))!=0)
  25. return(ret);
  26. }
  27. // 遍历入站规则
  28. for(frp = fio.f_fin[(int)fio.f_active]; frp != NULL;
  29.         frp = fr.fr_next){
  30. if(fw_kcopy(fw,(u_char *)&fr,(u_long)frp,sizeof(fr))<0)
  31. return(-1);
  32.         ipf_to_rule(&fr,&rule);
  33. if((ret = callback(&rule, arg))!=0)
  34. return(ret);
  35. }
  36. return(0);
  37. }

内核内存复制

  1. // src/fw-ipf.c:217-232
  2. staticint
  3. fw_kcopy(fw_t*fw, u_char *buf,off_t pos,size_t n)
  4. {
  5. int i;
  6. if(lseek(fw->kfd, pos,0)<0)
  7. return(-1);
  8. while((= read(fw->kfd, buf, n))< n){
  9. if(<=0)
  10. return(-1);
  11.         buf += i;
  12.         n -= i;
  13. }
  14. return(0);
  15. }

OpenBSD PF防火墙实现

1. 核心数据结构

  1. // src/fw-pf.c:67-69
  2. struct fw_handle {
  3. int     fd;// PF设备文件描述符
  4. };

2. PF版本兼容性处理

  1. // src/fw-pf.c:29-65
  2. /*
  3.  * XXX - 处理不断变化的PF API
  4.  */
  5. #if defined(DIOCRCLRTABLES)
  6. /* OpenBSD 3.3+ - 3.6 */
  7. #define HAVE_PF_CHANGE_GET_TICKET    1
  8. #define PFRA_ADDR(ra)(ra)->addr.v.a.addr.v4.s_addr
  9. #define PFRA_MASK(ra)(ra)->addr.v.a.mask.v4.s_addr
  10. #define pfioc_changerule   pfioc_rule
  11. #define oldrule    rule
  12. #define newrule    rule
  13. #elif defined(DIOCBEGINADDRS)
  14. /* OpenBSD 3.2 */
  15. #define PFRA_ADDR(ra)(ra)->addr.addr.v4.s_addr
  16. #define PFRA_MASK(ra)(ra)->addr.mask.v4.s_addr
  17. #elif defined(PFRULE_FRAGMENT)
  18. /* OpenBSD 3.1 */
  19. #define PFRA_ADDR(ra)(ra)->addr.addr.v4.s_addr
  20. #define PFRA_MASK(ra)(ra)->mask.v4.s_addr
  21. #elif defined (PF_AEQ)
  22. /* OpenBSD 3.0 */
  23. #define PFRA_ADDR(ra)(ra)->addr
  24. #define PFRA_ADDR(ra)(ra)->mask
  25. #endif

3. 规则转换:fwrule -> pfrule

  1. // src/fw-pf.c:71-115
  2. staticvoid
  3. fr_to_pr(conststruct fw_rule *fr,struct pf_rule *pr)
  4. {
  5.     memset(pr,0,sizeof(*pr));
  6. // 接口名称
  7.     strlcpy(pr->ifname, fr->fw_device,sizeof(pr->ifname));
  8. // 动作和方向
  9.     pr->action =(fr->fw_op == FW_OP_ALLOW)? PF_PASS : PF_DROP;
  10.     pr->direction =(fr->fw_dir == FW_DIR_IN)? PF_IN : PF_OUT;
  11.     pr->proto = fr->fw_proto;
  12. // 地址
  13.     pr->af = AF_INET;
  14.     PFRA_ADDR(&pr->src)= fr->fw_src.addr_ip;
  15.     addr_btom(fr->fw_src.addr_bits,&(PFRA_MASK(&pr->src)), IP_ADDR_LEN);
  16.     PFRA_ADDR(&pr->dst)= fr->fw_dst.addr_ip;
  17.     addr_btom(fr->fw_dst.addr_bits,&(PFRA_MASK(&pr->dst)), IP_ADDR_LEN);
  18. // 端口和ICMP类型
  19. switch(fr->fw_proto){
  20. case IP_PROTO_ICMP:
  21. if(fr->fw_sport[1])
  22.             pr->type =(u_char)(fr->fw_sport[0]&
  23.                 fr->fw_sport[1])+1;
  24. if(fr->fw_dport[1])
  25.             pr->code =(u_char)(fr->fw_dport[0]&
  26.                 fr->fw_dport[1])+1;
  27. break;
  28. case IP_PROTO_TCP:
  29. case IP_PROTO_UDP:
  30.         pr->src.port[0]= htons(fr->fw_sport[0]);
  31.         pr->src.port[1]= htons(fr->fw_sport[1]);
  32. if(pr->src.port[0]== pr->src.port[1]){
  33.             pr->src.port_op = PF_OP_EQ;
  34. }else
  35.             pr->src.port_op = PF_OP_IRG;
  36.         pr->dst.port[0]= htons(fr->fw_dport[0]);
  37.         pr->dst.port[1]= htons(fr->fw_dport[1]);
  38. if(pr->dst.port[0]== pr->dst.port[1]){
  39.             pr->dst.port_op = PF_OP_EQ;
  40. }else
  41.             pr->dst.port_op = PF_OP_IRG;
  42. break;
  43. }
  44. }

4. fw_open() 实现

  1. // src/fw-pf.c:186-196
  2. fw_t*
  3. fw_open(void)
  4. {
  5. fw_t*fw;
  6. if((fw = calloc(1,sizeof(*fw)))!= NULL){
  7. // 打开PF设备
  8. if((fw->fd = open("/dev/pf", O_RDWR))<0)
  9. return(fw_close(fw));
  10. }
  11. return(fw);
  12. }

5. fw_add() 实现

  1. // src/fw-pf.c:198-235
  2. int
  3. fw_add(fw_t*fw,conststruct fw_rule *rule)
  4. {
  5. struct pfioc_changerule pcr;
  6.     assert(fw != NULL && rule != NULL);
  7.     memset(&pcr,0,sizeof(pcr));
  8. #ifdef HAVE_PF_CHANGE_GET_TICKET
  9. // 检查规则是否已存在
  10. {
  11. struct fw_rule fr;
  12. if(ioctl(fw->fd, DIOCGETRULES,&pcr)<0)
  13. return(-1);
  14. while((int)--pcr.nr >=0){
  15. if(ioctl(fw->fd, DIOCGETRULE,&pcr)==0&&
  16.                 pr_to_fr(&pcr.rule,&fr)==0){
  17. if(_fw_cmp(rule,&fr)==0){
  18.                     errno = EEXIST;
  19. return(-1);
  20. }
  21. }
  22. }
  23. }
  24. #endif
  25. #ifdef DIOCBEGINADDRS
  26. // 获取地址池票据
  27. {
  28. struct pfioc_pooladdr ppa;
  29. if(ioctl(fw->fd, DIOCBEGINADDRS,&ppa)<0)
  30. return(-1);
  31.         pcr.pool_ticket = ppa.ticket;
  32. }
  33. #endif
  34. // 添加规则到规则集尾部
  35.     pcr.action = PF_CHANGE_ADD_TAIL;
  36.     fr_to_pr(rule,&pcr.newrule);
  37. return(ioctl(fw->fd, DIOCCHANGERULE,&pcr));
  38. }

6. fw_delete() 实现

  1. // src/fw-pf.c:237-279
  2. int
  3. fw_delete(fw_t*fw,conststruct fw_rule *rule)
  4. {
  5. struct pfioc_changerule pcr;
  6.     assert(fw != NULL && rule != NULL);
  7.     memset(&pcr,0,sizeof(pcr));
  8. #ifdef HAVE_PF_CHANGE_GET_TICKET
  9. // 查找要删除的规则
  10. {
  11. struct fw_rule fr;
  12. int found =0;
  13. if(ioctl(fw->fd, DIOCGETRULES,&pcr)<0)
  14. return(-1);
  15. while((int)--pcr.nr >=0){
  16. if(ioctl(fw->fd, DIOCGETRULE,&pcr)==0&&
  17.                 pr_to_fr(&pcr.rule,&fr)==0){
  18. if(_fw_cmp(rule,&fr)==0){
  19.                     found =1;
  20. break;
  21. }
  22. }
  23. }
  24. if(!found){
  25.             errno = ENOENT;
  26. return(-1);
  27. }
  28. }
  29. #endif
  30. #ifdef DIOCBEGINADDRS
  31. {
  32. struct pfioc_pooladdr ppa;
  33. if(ioctl(fw->fd, DIOCBEGINADDRS,&ppa)<0)
  34. return(-1);
  35.         pcr.pool_ticket = ppa.ticket;
  36. }
  37. #endif
  38. // 删除规则
  39.     pcr.action = PF_CHANGE_REMOVE;
  40.     fr_to_pr(rule,&pcr.oldrule);
  41. return(ioctl(fw->fd, DIOCCHANGERULE,&pcr));
  42. }

7. fw_loop() 实现

  1. // src/fw-pf.c:281-310
  2. int
  3. fw_loop(fw_t*fw, fw_handler callback,void*arg)
  4. {
  5. struct pfioc_rule pr;
  6. struct fw_rule fr;
  7. uint32_t n, max;
  8. int ret =0;
  9.     memset(&pr,0,sizeof(pr));
  10. // 获取规则数量
  11. if(ioctl(fw->fd, DIOCGETRULES,&pr)<0)
  12. return(-1);
  13. // 遍历规则
  14. for(=0, max = pr.nr; n < max; n++){
  15.         pr.nr = n;
  16. if((ret = ioctl(fw->fd, DIOCGETRULE,&pr))<0)
  17. break;
  18. #ifdef PF_TABLE_NAME_SIZE
  19. // 跳过表地址规则
  20. if(pr.rule.src.addr.type == PF_ADDR_TABLE ||
  21.             pr.rule.dst.addr.type == PF_ADDR_TABLE)
  22. continue;
  23. #endif
  24. if(pr_to_fr(&pr.rule,&fr)<0)
  25. continue;
  26. if((ret = callback(&fr, arg))!=0)
  27. break;
  28. }
  29. return(ret);
  30. }

Windows PktFilter实现

1. 核心数据结构

  1. // src/fw-pktfilter.c:52-55
  2. struct fw_handle {
  3.     IP_ADAPTER_INFO *ifinfo;// 适配器信息列表
  4. /* XXX - 规则缓存用于删除查找? */
  5. };
  6. #define PKTFILTER_PIPE    "\\\\.\\pipe\\PktFltPipe"
  7. #define MAX_RULE_LENGTH  256

2. PktFilter简介

PktFilter是Windows上的防火墙实现,特点:

  • 基于命名管道通信
  • 通过PktFlt服务实现
  • 提供简单的规则语法

3. 规则格式化

  1. // src/fw-pktfilter.c:214-275
  2. staticint
  3. format_rule(conststruct fw_rule *rule,char*buf,int len)
  4. {
  5. char tmp[128];
  6. // 操作和方向
  7.     strlcpy(buf,(rule->fw_op == FW_OP_ALLOW)?"pass ":"block ", len);
  8.     strlcat(buf,(rule->fw_dir == FW_DIR_IN)?"in ":"out ", len);
  9. // 接口
  10.     snprintf(tmp,sizeof(tmp),"on %s ", rule->fw_device);
  11.     strlcat(buf, tmp, len);
  12. // 协议
  13. if(rule->fw_proto !=0){
  14.         snprintf(tmp,sizeof(tmp),"proto %d ", rule->fw_proto);
  15.         strlcat(buf, tmp, len);
  16. }
  17. // 源地址
  18. if(rule->fw_src.addr_type != ADDR_TYPE_NONE){
  19.         snprintf(tmp,sizeof(tmp),"from %s ",
  20.             addr_ntoa(&rule->fw_src));
  21.         strlcat(buf, tmp, len);
  22. }else
  23.         strlcat(buf,"from any ", len);
  24. // 源端口
  25. if(rule->fw_proto == IP_PROTO_TCP || rule->fw_proto == IP_PROTO_UDP){
  26. if(rule->fw_sport[0]== rule->fw_sport[1])
  27.             snprintf(tmp,sizeof(tmp),"port = %d ",
  28.                 rule->fw_sport[0]);
  29. else
  30.             snprintf(tmp,sizeof(tmp),"port %d >< %d ",
  31.                 rule->fw_sport[0]-1, rule->fw_sport[1]+1);
  32.         strlcat(buf, tmp, len);
  33. }
  34. // 目标地址
  35. if(rule->fw_dst.addr_type != ADDR_TYPE_NONE){
  36.         snprintf(tmp,sizeof(tmp),"to %s ",
  37.             addr_ntoa(&rule->fw_dst));
  38.         strlcat(buf, tmp, len);
  39. }else
  40.         strlcat(buf,"to any ", len);
  41. // 目标端口
  42. if(rule->fw_proto == IP_PROTO_TCP || rule->fw_proto == IP_PROTO_UDP){
  43. if(rule->fw_dport[0]== rule->fw_dport[1])
  44.             snprintf(tmp,sizeof(tmp),"port = %d",
  45.                 rule->fw_dport[0]);
  46. else
  47.             snprintf(tmp,sizeof(tmp),"port %d >< %d",
  48.                 rule->fw_dport[0]-1, rule->fw_dport[1]+1);
  49.         strlcat(buf, tmp, len);
  50. }elseif(rule->fw_proto == IP_PROTO_ICMP){
  51. if(rule->fw_sport[1]){
  52.             snprintf(tmp,sizeof(tmp),"icmp-type %d",
  53.                 rule->fw_sport[0]);
  54.             strlcat(buf, tmp, len);
  55. if(rule->fw_dport[1]){
  56.                 snprintf(tmp,sizeof(tmp)," code %d",
  57.                     rule->fw_dport[0]);
  58.                 strlcat(buf, tmp, len);
  59. }
  60. }
  61. }
  62. return(strlen(buf));
  63. }

PktFilter规则语法

  1. passin on eth0 proto tcp from192.168.1.0/24 to any port 80
  2. block out on eth0 proto udp from any to any port 53
  3. passin on eth0 proto icmp from any to any icmp-type echo

4. 管道通信

  1. // src/fw-pktfilter.c:277-313
  2. staticchar*
  3. call_pipe(constchar*msg,int len)
  4. {
  5.     HANDLE *pipe;
  6.     DWORD i;
  7. char*reply, status;
  8. // 等待命名管道
  9. if(!WaitNamedPipe(PKTFILTER_PIPE, NMPWAIT_USE_DEFAULT_WAIT)||
  10. (pipe =CreateFile(PKTFILTER_PIPE, GENERIC_READ | GENERIC_WRITE,
  11. 0, NULL, OPEN_EXISTING,0, NULL))== INVALID_HANDLE_VALUE){
  12. return(NULL);
  13. }
  14.     reply = NULL;
  15. // 发送命令
  16. if(WriteFile(pipe, msg, len,&i, NULL)){
  17. // 接收响应
  18. if(ReadFile(pipe,&status,sizeof(status),&i, NULL)){
  19. if(status == FILTER_FAILURE){
  20. ReadFile(pipe,&status,sizeof(status),
  21. &i, NULL);
  22. }elseif(status == FILTER_MESSAGE){
  23. // 获取消息长度
  24. if(ReadFile(pipe,&len,4,&i, NULL)){
  25. // 获取消息内容
  26.                     reply = calloc(1, len +1);
  27. if(!ReadFile(pipe, reply, len,
  28. &i, NULL)){
  29.                         free(reply);
  30.                         reply = NULL;
  31. }
  32. }
  33. }elseif(status == FILTER_SUCCESS)
  34.                 reply = strdup("");/* XXX */
  35. }
  36. }
  37. CloseHandle(pipe);
  38. return(reply);
  39. }

5. fw_open() 实现

  1. // src/fw-pktfilter.c:315-352
  2. fw_t*
  3. fw_open(void)
  4. {
  5. fw_t*f;
  6.     IP_ADAPTER_INFO *ifinfo;
  7.     ULONG size;
  8. if((= calloc(1,sizeof(*f)))== NULL)
  9. return(NULL);
  10. // 获取适配器信息
  11.     size =sizeof(*f->ifinfo);
  12.     f->ifinfo = malloc(size);
  13. if(GetAdaptersInfo(f->ifinfo,&size)!= ERROR_SUCCESS){
  14.         free(f->ifinfo);
  15.         f->ifinfo = malloc(size);
  16. GetAdaptersInfo(f->ifinfo,&size);
  17. }
  18. // 规范化接口名称
  19. for(ifinfo = f->ifinfo; ifinfo != NULL; ifinfo = ifinfo->Next){
  20. char*fmt;
  21. if(ifinfo->Type== MIB_IF_TYPE_ETHERNET)
  22.             fmt ="eth";
  23. elseif(ifinfo->Type== MIB_IF_TYPE_PPP)
  24.             fmt ="ppp";
  25. elseif(ifinfo->Type== MIB_IF_TYPE_SLIP)
  26.             fmt ="sl";
  27. elseif(ifinfo->Type== MIB_IF_TYPE_LOOPBACK)
  28.             fmt ="lo";
  29. elseif(ifinfo->Type== MIB_IF_TYPE_TOKENRING)
  30.             fmt ="tr";
  31. elseif(ifinfo->Type== MIB_IF_TYPE_FDDI)
  32.             fmt ="fd";
  33. else
  34.             fmt ="if";
  35.         sprintf(ifinfo->AdapterName,"%s%lu", fmt, ifinfo->ComboIndex);
  36. }
  37. return(f);
  38. }

6. fw_add() 实现

  1. // src/fw-pktfilter.c:354-366
  2. int
  3. fw_add(fw_t*f,conststruct fw_rule *rule)
  4. {
  5. char*p, buf[MAX_RULE_LENGTH];
  6. int len;
  7. // 格式化规则
  8.     len = format_rule(rule, buf,sizeof(buf));
  9. // 通过管道发送规则
  10. if((= call_pipe(buf, len))== NULL)
  11. return(-1);
  12.     free(p);
  13. return(0);
  14. }

7. fw_delete() 实现

  1. // src/fw-pktfilter.c:368-405
  2. int
  3. fw_delete(fw_t*f,conststruct fw_rule *rule)
  4. {
  5. struct fw_rule tmp;
  6. char*p,*line,*msg, cmd[128], buf[MAX_RULE_LENGTH];
  7. int n, ruleno, len;
  8.     format_rule(rule, buf,sizeof(buf));
  9. // 列出规则
  10.     len = snprintf(cmd,sizeof(cmd),"List on %s", rule->fw_device);
  11. if((msg = call_pipe(cmd, len))== NULL)
  12. return(-1);
  13. // 查找匹配的规则
  14. for(ruleno =0, p = msg;(line = strsep(&p,"\r\n"))!= NULL;){
  15. if(strncmp(line,"rule ",5)==0){
  16.             line +=5;
  17.             n = atoi(strsep(&line,":"));
  18. if(parse_rule(line +1,&tmp)==0&&
  19.                 memcmp(&tmp, rule,sizeof(tmp))==0){
  20.                 ruleno = n;
  21. break;
  22. }
  23. }
  24. }
  25.     free(msg);
  26. if(ruleno ==0){
  27.         errno = ENXIO;
  28. SetLastError(ERROR_NO_DATA);
  29. return(-1);
  30. }
  31. // 删除规则
  32.     len = snprintf(cmd,sizeof(cmd),"delete %d on %s",
  33.         ruleno, rule->fw_device);
  34. if((= call_pipe(cmd, len))== NULL)
  35. return(-1);
  36.     free(p);
  37. return(0);
  38. }

模块对比与最佳实践

1. 以太网模块对比

特性
Linux (PF_PACKET)
BSD (BPF)
Solaris (DLPI)
Windows
实现方式
原始套接字
设备文件
STREAMS
N/A
设备绑定
SIOCGIFINDEX
BIOCSETIF
DLATTACHREQ
获取MAC
SIOCGIFHWADDR
sysctl
DLPHYSADDR_REQ
GetAdaptersInfo
设置MAC
SIOCSIFHWADDR
SIOCSIFLLADDR
DLSETPHYSADDRREQ
发送帧
sendto()
write()
putmsg()
性能

2. 防火墙模块对比

特性
IPFW
ipchains
IPF
PF
PktFilter
操作方式
setsockopt
setsockopt
ioctl
ioctl
管道
规则存储
内核
/proc
/dev/kmem
内核
服务
规则获取
getsockopt
/proc解析
读取内存
ioctl
管道
状态跟踪
性能

3. 使用最佳实践

以太网模块

  1. // 1. 正确的错误处理
  2. eth_t*eth = eth_open("eth0");
  3. if(eth == NULL){
  4. if(errno == EPERM){
  5.         fprintf(stderr,"需要root权限\n");
  6. }else{
  7.         perror("eth_open失败");
  8. }
  9.     exit(EXIT_FAILURE);
  10. }
  11. // 2. 构造以太网帧
  12. struct eth_hdr hdr;
  13. eth_pack_hdr(&hdr, dst_mac, src_mac, ETH_TYPE_IP);
  14. // 3. 发送数据包
  15. ssize_t sent = eth_send(eth,&hdr,sizeof(hdr));
  16. if(sent <0){
  17.     perror("eth_send失败");
  18. }
  19. // 4. 清理资源
  20. eth_close(eth);

防火墙模块

  1. // 1. 构造规则
  2. struct fw_rule rule;
  3. memset(&rule,0,sizeof(rule));
  4. strcpy(rule.fw_device,"eth0");
  5. rule.fw_op = FW_OP_ALLOW;
  6. rule.fw_dir = FW_DIR_IN;
  7. rule.fw_proto = IP_PROTO_TCP;
  8. addr_aton("192.168.1.0/24",&rule.fw_src);
  9. rule.fw_dport[0]= rule.fw_dport[1]=80;
  10. // 2. 打开防火墙
  11. fw_t*fw = fw_open();
  12. if(fw == NULL){
  13.     perror("fw_open失败");
  14.     exit(EXIT_FAILURE);
  15. }
  16. // 3. 添加规则
  17. if(fw_add(fw,&rule)<0){
  18.     perror("fw_add失败");
  19.     fw_close(fw);
  20.     exit(EXIT_FAILURE);
  21. }
  22. // 4. 清理资源
  23. fw_close(fw);

4. 性能优化建议

以太网发送优化

  1. 批量发送:使用writev而非多次write
  2. 缓冲区调整:设置适当的SO_SNDBUF
  3. 零拷贝:考虑使用mmap或sendfile

防火墙规则优化

  1. 规则顺序:将最常用的规则放在前面
  2. 规则聚合:使用CIDR聚合相似规则
  3. 状态检测:优先使用状态跟踪

5. 安全注意事项

  1. 权限管理
  • 以最小权限原则运行
  • 完成特权操作后立即降级
  1. 输入验证
  • 验证MAC地址格式
  • 检查接口名称合法性
  • 验证防火墙规则参数
  1. 错误处理
  • 不泄露敏感信息
  • 记录安全事件
  • 优雅地处理失败

总结

本文档深入分析了libdnet以太网和防火墙模块的跨平台实现:

以太网模块

  • Linux使用PF_PACKET套接字
  • BSD使用BPF设备文件
  • Solaris使用DLPI STREAMS接口
  • 每个平台都提供了统一的API

防火墙模块

  • 支持多种防火墙系统(IPFW、ipchains、IPF、PF、PktFilter)
  • 通过转换层统一不同防火墙的规则格式
  • 使用平台特定的通信机制(setsockopt、ioctl、管道)

通过理解这些底层实现,开发者可以:

  1. 更好地调试网络问题
  2. 优化性能瓶颈
  3. 选择合适的平台特性
  4. 编写更可靠的代码
  • 公众号:安全狗的自我修养

  • vx:2207344074

  • http://gitee.com/haidragon

  • http://github.com/haidragon

  • bilibili:haidragonx