乐于分享
好东西不私藏

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

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

  • 网:http://securitytech.cc

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

    第 3 章:原始 Socket 封装实现

    3.1 概述

    libdnet 提供了跨平台的原始 Socket 封装,主要用于发送和接收原始 IP 数据包。本章深入分析其内部实现细节,对比 Linux、macOS 和 Windows 平台的不同实现方式。

    核心模块架构

    
    

    支持的平台

    平台
    实现文件
    底层技术
    特点
    Linux ip.c

     + eth-linux.c
    Raw Socket + PF_PACKET
    权限要求高,性能最优
    macOS/BSD ip.c

     + eth-bsd.c
    Raw Socket + BPF
    Berkeley Packet Filter,灵活强大
    Windows ip-win32.c
    WinSocket Raw Socket
    需要 IP_HDRINCL,权限要求高
    Cooked ip-cooked.c
    用户态以太网发送
    无需特殊权限,跨平台

    3.2 Linux 平台实现 (ip.c + eth-linux.c)

    3.2.1 数据结构定义

    
    

    设计特点:

    3.2.2 ip_open – 打开原始 Socket

    源码分析 (ip.c:25-61):

    
    

    关键点解析:

    步骤
    功能
    说明
    socket 创建
    socket(AF_INET,SOCK_RAW,IPPROTO_RAW)
    创建原始套接字,需要 root 权限
    IP_HDRINCL
    应用层构造 IP 头
    内核不会自动添加 IP 头
    SO_SNDBUF
    发送缓冲区优化
    动态调整以提高性能
    SO_BROADCAST
    支持广播
    允许发送到广播地址

    权限要求:

    3.2.3 ip_send – 发送原始 IP 数据包

    源码分析 (ip.c:63-93):

    
    

    执行流程:

    
    

    Linux 内核处理过程:

    3.2.4 ip_close – 关闭 Socket

    源码分析 (ip.c:95-104):

    
    

    3.3 Windows 平台实现 (ip-win32.c)

    3.3.1 数据结构定义

    
    

    设计特点:

    3.3.2 ip_open – 打开原始 Socket

    源码分析 (ip-win32.c:24-49):

    
    

    Windows 特有特点:

    特性
    说明
    WSAStartup
    Windows 必须显式初始化 WinSocket 库
    INVALID_SOCKET
    Windows 使用宏而非 -1 表示错误
    setsockopt 参数
    第三个参数需要 (constchar*) 强制转换
    端口设置
    虽然不使用,但需要初始化避免未定义行为

    权限要求:

    常见错误:

    错误代码
    含义
    解决方案
    WSAEACCES (10013)
    权限不足
    以管理员身份运行
    WSAEPROTONOSUPPORT (10043)
    不支持原始 socket
    Windows Home 版本可能不支持
    WSAENETRESET (10052)
    连接重置
    需要重新打开 socket

    3.3.3 ip_send – 发送原始 IP 数据包

    源码分析 (ip-win32.c:51-63):

    
    

    ⚠️ 重要差异:Windows 使用源地址作为发送目标!

    这是一个非常容易出错的差异:

    
    

    原因分析:

    Windows 内核处理流程:

    
    

    3.3.4 ip_close – 关闭 Socket

    源码分析 (ip-win32.c:65-75):

    
    

    清理顺序:


    3.4 macOS/BSD 平台实现 (ip.c + eth-bsd.c)

    3.4.1 概述

    macOS 和其他 BSD 系统(FreeBSD、OpenBSD、NetBSD)使用 Berkeley Packet Filter (BPF) 进行原始数据包发送。BPF 是一个强大的内核级数据包捕获和过滤框架,提供了比 Linux PF_PACKET 更灵活的接口。

    3.4.2 数据结构定义

    
    

    设计特点:

    3.4.3 eth_open – 打开 BPF 设备

    源码分析 (eth-bsd.c:39-74):

    
    

    BPF 设备特点:

    特性
    说明
    设备文件 /dev/bpf0

    /dev/bpf1, …
    并发控制
    每个设备一次只能被一个进程打开 (EBUSY)
    权限要求
    需要 root 权限
    接口绑定
    通过 BIOCSETIF ioctl 绑定到网络接口
    自动补全 BIOCSHDRCMPLT

     控制是否自动补全以太网头

    3.4.4 eth_send – 发送以太网帧

    源码分析 (eth-bsd.c:76-80):

    
    

    设计特点:

    与 Linux 对比:

    
    

    3.4.5 eth_get – 获取 MAC 地址

    源码分析 (eth-bsd.c:94-138):

    
    

    sysctl 获取接口信息的原理:

    
    

    3.4.6 macOS 特有问题与解决

    问题 1: macOS 10.6 的 O_WRONLY Bug

    问题描述:macOS 10.6 (Snow Leopard) 有一个 bug,如果以 O_WRONLY 模式打开 BPF 设备,会导致其他进程无法接收网络流量。

    解决方案:

    
    

    问题 2: BPF 设备数量限制

    问题描述:系统默认创建的 BPF 设备数量有限,可能不够多个进程同时使用。

    解决方案:

    
    

    问题 3: 设备命名差异

    平台
    设备命名示例
    Linux
    eth0, wlan0, enp3s0
    macOS
    en0, en1, bridge0
    FreeBSD
    em0, igb0, re0

    3.5 用户态 IP 实现 (ip-cooked.c)

    3.4.1 实现动机

    在某些平台或场景下,原始 socket 不可用或受到限制:

    libdnet 提供了 ip-cooked.c,在用户态实现 IP 层功能,通过以太网 socket 发送数据包。

    3.4.2 数据结构定义

    
    

    设计特点:

    3.4.3 ip_open – 初始化用户态 IP

    源码分析 (ip-cooked.c:67-93):

    
    

    _add_ip_intf 回调函数 (ip-cooked.c:42-65):

    
    

    3.4.4 _lookup_ip_intf – 查找出口接口

    源码分析 (ip-cooked.c:95-124):

    
    

    路由查找技巧详解:

    
    

    优势:

    3.4.5 _request_arp – 发送 ARP 请求

    源码分析 (ip-cooked.c:126-138):

    
    

    ARP 请求帧格式:

    
    

    3.4.6 ip_send – 用户态 IP 发送

    源码分析 (ip-cooked.c:140-220):

    
    

    发送流程详解:

    
    

    IP 分片算法:

    
    

    3.4.7 ip_close – 清理资源

    源码分析 (ip-cooked.c:222-246):

    
    

    3.6 平台对比总结

    3.6.1 数据结构对比

    平台
    IP 句柄结构
    Eth 句柄结构
    成员
    说明
    Linux structip_handle structeth_handle intfd

    structifreq ifrstructsockaddr_ll sll
    socket + PF_PACKET
    macOS/BSD structip_handle structeth_handle intfd

    chardevice[16]
    socket + BPF 设备
    Windows structip_handle structeth_handle WSADATA wsdata

    SOCKET fdstructsockaddr_in sin
    WinSocket + 无实现
    Cooked structip_handle structeth_handle arp_t*

    intf_t*route_t*intfdLIST_HEAD
    集成多个模块

    3.6.2 ip_open 对比

    特性
    Linux (ip.c)
    macOS/BSD (ip.c)
    Windows (ip-win32.c)
    Cooked (ip-cooked.c)
    Socket 创建 socket(AF_INET,SOCK_RAW,IPPROTO_RAW)
    同 Linux
    socket(AF_INET,SOCK_RAW,IPPROTO_RAW) socket(AF_INET,SOCK_DGRAM,0)
    权限要求
    root / CAPNETRAW
    root
    Administrator
    无特殊权限
    IP_HDRINCL
    设置 IP_HDRINCL
    设置 IP_HDRINCL
    设置 IP_HDRINCL
    不使用
    缓冲区优化
    调整 SO_SNDBUF
    调整 SO_SNDBUF
    不调整
    不适用
    额外初始化
    WSAStartup()
    遍历接口、打开 arp/intf/route
    依赖模块
    eth, arp, intf, route

    3.6.3 以太网发送对比

    特性
    Linux (PF_PACKET)
    macOS/BSD (BPF)
    Windows (无实现)
    Cooked
    打开方式 socket(PF_PACKET,SOCK_RAW) open("/dev/bpf0")
    eth_open()
    发送函数 sendto() write()
    eth_send()
    需要地址结构
    ✅ structsockaddr_ll
    ❌ 直接写入
    ❌ 直接写入
    自动补全头部
    ❌ (BIOCSHDRCMPLT)
    设备绑定
    ✅ bind() + SIOCGIFINDEX
    ✅ ioctl(BIOCSETIF)
    ✅ 设备文件
    BPF 过滤支持

    3.6.4 ip_send 对比

    特性
    Linux (ip.c)
    macOS/BSD (ip.c)
    Windows (ip-win32.c)
    Cooked (ip-cooked.c)
    目标地址 sin_addr=ip->ip_dst
    同 Linux
    sin_addr=hdr->ip_src

     ⚠️
    N/A (以太网发送)
    ARP 解析
    内核自动处理
    内核自动处理
    内核自动处理
    用户态实现(最多 3 次重试)
    分片处理
    内核自动处理
    内核自动处理
    内核自动处理
    用户态实现
    校验和
    内核计算
    内核计算
    内核计算
    用户计算 ip_checksum()
    以太网头
    内核添加
    内核添加
    内核添加
    用户构造 eth_pack_hdr()
    发送方式 sendto() sendto() sendto() eth_send()

    3.6.5 使用场景

    场景
    Linux
    macOS/BSD
    Windows
    Cooked
    需要 root 权限
    ❌ (需要 Admin)
    需要管理员权限
    发送广播/组播
    支持
    支持
    支持
    支持
    自定义 IP 头
    支持
    支持
    支持
    支持
    自定义以太网头
    精确控制 ARP
    手动分片
    BPF 过滤
    性能
    最高(内核处理)
    最高(内核处理)
    最高(内核处理)
    较低(用户态处理)
    适用场景
    Linux 服务器
    macOS 开发
    Windows 环境
    无权限环境、精确控制

    3.7 实战案例:构建自定义 IP 数据包

    3.6.1 完整示例代码

    
    

    3.6.2 编译与运行

    Linux:

    
    

    Windows (MSVC):

    
    

    Cooked 模式(无需 root/管理员权限):

    
    

    3.8 校验和计算实现 (ip-util.c)

    3.7.1 IP 校验和算法

    源码分析 (ip-util.c:131-182):

    
    

    IP 校验和计算原理:

    
    

    3.7.2 TCP/UDP 伪首部校验和

    TCP 校验和 (ip-util.c:101-109):

    
    

    TCP 伪首部结构:

    
    

    伪首部校验和计算:

    
    

    3.7.3 高性能校验和计算 (Duff’s Device)

    源码分析 (ip-util.c:184-238):

    
    

    Duff’s Device 原理:

    
    

    性能优势:


    3.9 常见问题与调试

    3.8.1 Linux 常见问题

    问题
    错误信息
    原因
    解决方案
    权限不足
    Operationnotpermitted
    非 root 用户
    使用 sudo 或设置 CAPNETRAW
    Socket 创建失败
    Sockettypenotsupported
    内核未编译 RAW socket 支持
    重新编译内核
    缓冲区不足
    Nobuffer space available
    发送速率过快
    增加 SO_SNDBUF 或降低发送速率
    路由失败
    Networkisunreachable
    目标不可达
    检查路由表 ip route

    3.8.2 Windows 常见问题

    问题
    错误代码
    错误信息
    解决方案
    权限不足
    WSAEACCES (10013)
    Permission denied
    以管理员身份运行
    协议不支持
    WSAEPROTONOSUPPORT (10043)
    Protocol not supported
    使用 Professional/Enterprise 版本
    无效参数
    WSAEINVAL (10022)
    Invalid argument
    检查 IP 头格式
    网络不可达
    WSAENETUNREACH (10051)
    Network is unreachable
    检查网络连接

    3.8.3 调试技巧

    1. 启用详细日志

    
    

    2. 使用 tcpdump 抓包

    
    

    3. 检查内核日志

    
    

    3.10 总结

    3.9.1 核心要点

    3.9.2 最佳实践


    • 使用条件编译处理平台差异
    • 统一错误处理机制
    • 提供平台特定的回退方案
    • 复用 socket 句柄
    • 批量发送数据包
    • 使用适当大小的缓冲区
    • 确保调用 ip_close() 释放资源
    • 使用 goto 进行错误清理
    • 避免资源泄漏
    • 始终检查 ip_open() 返回值
    • 处理 errno 和 GetLastError()
    • 提供清晰的错误信息
    • Raw socket 需要 root/管理员权限
    • Cooked 模式无权限限制
    • 非特权环境考虑使用 cooked 模式
    • IP 校验和:覆盖 IP 头部
    • TCP/UDP 校验和:包含伪首部
    • 使用 Duff’s Device 优化性能
    • Windows 使用 ip_src 作为 sendto 目标地址 ⚠️
    • Windows 需要 WSAStartup/WSACleanup
    • Linux 可以动态调整发送缓冲区
    • Cooked 模式需要手动处理 ARP 和分片
    • Linux/Unix (ip.c):使用内核 raw socket,性能最高
    • Windows (ip-win32.c):使用 WinSocket raw socket
    • Cooked (ip-cooked.c):用户态实现,无需特殊权限
    • 减少循环条件判断次数
    • 在处理长数据包时效果明显
    • 一次处理 16 个 16 位字
    • 无需 root 权限
    • 利用内核的路由表
    • 准确找到出口接口
    • 集成了多个底层模块(eth、arp、intf、route)
    • 维护一个接口链表,缓存所有可用的网络接口
    • 使用 UDP socket 进行路由查找(Linux 特有的技巧)
    • 权限限制:非 root 用户无法创建 raw socket
    • 平台限制:某些平台(如 Windows Home)不支持原始 socket
    • 性能需求:需要更精细的控制(如 ARP 解析、分片处理)
    • BPF 设备使用标准的 write() 系统调用发送数据
    • 用户必须提供完整的以太网帧(14 字节头部 + 数据)
    • 简洁高效,不需要复杂的 sendto 参数
    • IP 层与 Linux 共享相同的 ip.c 实现
    • 以太网层使用 BPF 设备,而非 socket
    • 设备名称为 en0、 en1 等(macOS 风格)
    • Windows 的原始 socket 实现,sendto 的目标地址是绑定的本地地址
    • IP 头中的目标地址 ( ip_dst) 由内核解析
    • 因此需要将 socket 绑定到源地址 ( ip_src)
    • Windows: 需要管理员权限
    • 否则会失败并返回 WSAEACCES (10013)
    • 包含 WinSocket 初始化数据(WSADATA)
    • 预先分配目标地址结构,避免重复构造
    • Windows 的 SOCKET 类型实际上是 unsignedint,不同于 Linux 的 int
    • Linux: 需要 root 权限或 CAPNETRAW 能力
    • 创建 raw socket 时会检查 /proc/sys/net/core/wmem_max
    • 极简设计,只包含一个 socket 文件描述符
    • Linux 的 raw socket 功能强大,无需额外状态维护
    1. 跨平台兼容
    1. 性能优化
    1. 资源管理
    1. 错误处理
    1. 权限要求
    1. 校验和计算
    1. 平台差异
    1. 三种实现方式
    1. # Linux
    2. sudo dmesg | grep -"raw\|socket"
    3. # Windows
    4. # 使用事件查看器 (Event Viewer) 查看系统日志
    1. # Linux
    2. sudo tcpdump -i eth0 --'ip host 192.168.1.100'
    3. # Windows (使用 Wireshark)
    4. # 安装 WinPcap 或 Npcap 后使用 Wireshark 抓包
    1. void log_packet(struct ip_hdr *ip,size_t len)
    2. {
    3. char src[16], dst[16];
    4.     ip_ntop(&ip->ip_src, src,sizeof(src));
    5.     ip_ntop(&ip->ip_dst, dst,sizeof(dst));
    6.     printf("IP Packet:\n");
    7.     printf("  Version: %u\n", ip->ip_v);
    8.     printf("  Header Length: %u bytes\n", ip->ip_hl *4);
    9.     printf("  TOS: 0x%02X\n", ip->ip_tos);
    10.     printf("  Total Length: %u\n", ntohs(ip->ip_len));
    11.     printf("  ID: %u\n", ntohs(ip->ip_id));
    12.     printf("  Flags: %s%s\n",
    13. (ntohs(ip->ip_off)& IP_DF)?"DF ":"",
    14. (ntohs(ip->ip_off)& IP_MF)?"MF":"");
    15.     printf("  Fragment Offset: %u\n", ntohs(ip->ip_off)& IP_OFFMASK);
    16.     printf("  TTL: %u\n", ip->ip_ttl);
    17.     printf("  Protocol: %u\n", ip->ip_p);
    18.     printf("  Checksum: 0x%04X\n", ntohs(ip->ip_sum));
    19.     printf("  Source: %s\n", src);
    20.     printf("  Destination: %s\n", dst);
    21.     printf("  Payload Length: %zu\n", len -(ip->ip_hl *4));
    22. }
    1. /* 传统循环 (16 次迭代) */
    2. for(int i =0; i <16; i++){
    3.     cksum +=*sp++;
    4. }
    5. /* Duff's Device (减少循环控制开销) */
    6. switch(%16){
    7. case0:do{ cksum +=*sp++;
    8. case15:      cksum +=*sp++;
    9. case14:      cksum +=*sp++;
    10. /* ... */
    11. case1:       cksum +=*sp++;
    12. }while(-->0);
    13. }
    1. int
    2. ip_cksum_add(constvoid*buf,size_t len,int cksum)
    3. {
    4. uint16_t*sp =(uint16_t*)buf;
    5. int n, sn;
    6. /* 单字节特殊处理 */
    7. if(len ==1){
    8. constuint8_t*= buf;
    9. return htons(((uint16_t)*b)<<8);
    10. }
    11.     sn = len /2;/* 16 位字数 */
    12.     n =(sn +15)/16;/* 循环次数 */
    13. /* Duff's Device - 展开循环 */
    14. switch(sn %16){
    15. case0:do{
    16.         cksum +=*sp++;
    17. case15:
    18.         cksum +=*sp++;
    19. case14:
    20.         cksum +=*sp++;
    21. case13:
    22.         cksum +=*sp++;
    23. case12:
    24.         cksum +=*sp++;
    25. case11:
    26.         cksum +=*sp++;
    27. case10:
    28.         cksum +=*sp++;
    29. case9:
    30.         cksum +=*sp++;
    31. case8:
    32.         cksum +=*sp++;
    33. case7:
    34.         cksum +=*sp++;
    35. case6:
    36.         cksum +=*sp++;
    37. case5:
    38.         cksum +=*sp++;
    39. case4:
    40.         cksum +=*sp++;
    41. case3:
    42.         cksum +=*sp++;
    43. case2:
    44.         cksum +=*sp++;
    45. case1:
    46.         cksum +=*sp++;
    47. }while(-->0);
    48. }
    49. /* 处理奇数长度 */
    50. if(len &1)
    51.         cksum += htons(*(u_char *)sp <<8);
    52. return(cksum);
    53. }
    54. /* 进位处理 */
    55. #define ip_cksum_carry(x) \
    56. (=(>>16)+(&0xffff),(~(+(>>16))&0xffff))
    1. uint16_t compute_tcp_pseudo_checksum(uint32_t src_ip,uint32_t dst_ip,
    2. uint8_t protocol,uint16_t tcp_len,
    3. uint8_t*tcp_data,size_t data_len)
    4. {
    5. uint32_t sum =0;
    6. /* 源 IP (拆分为两个 16 位) */
    7.     sum +=(src_ip >>16)&0xFFFF;
    8.     sum += src_ip &0xFFFF;
    9. /* 目的 IP (拆分为两个 16 位) */
    10.     sum +=(dst_ip >>16)&0xFFFF;
    11.     sum += dst_ip &0xFFFF;
    12. /* 协议 + 长度 */
    13.     sum +=(protocol <<8)|(tcp_len >>8);
    14.     sum += tcp_len &0xFF;
    15. /* TCP 数据 */
    16.     sum += ip_cksum_add(tcp_data, data_len,0);
    17. /* 取反 */
    18. return(uint16_t)~sum;
    19. }
    1. ┌─────────────┬─────────────┬──────────┬─────────────┐
    2.  IP 地址目的 IP 地址协议  TCP 长度
    3. 4 bytes   4 bytes   1byte2 bytes    
    4. └─────────────┴─────────────┴──────────┴─────────────┘
    5. 12字节(伪首部)
    1. void
    2. tcp_checksum(struct ip_hdr *ip,struct tcp_hdr *tcp,size_t len)
    3. {
    4. int sum;
    5.     tcp->th_sum =0;
    6. /* 伪首部:源IP + 目的IP + 协议 + TCP长度 */
    7.     sum = ip_cksum_add(tcp, len,0)+ htonl(ip->ip_p + len);
    8.     sum = ip_cksum_add(&ip->ip_src,8, sum);/* 源IP + 目的IP (8 bytes) */
    9.     tcp->th_sum = ip_cksum_carry(sum);
    10. }
    1. /* IP 校验和算法 (RFC 1071) */
    2. uint16_t compute_ip_checksum(uint16_t*buf,int len)
    3. {
    4. uint32_t sum =0;
    5. /* 16 位累加 */
    6. while(len >1){
    7.         sum +=*buf++;
    8.         len -=2;
    9. }
    10. /* 处理奇数长度 */
    11. if(len ==1){
    12.         sum +=*(uint8_t*)buf;
    13. }
    14. /* 将高 16 位加到低 16 位 */
    15. while(sum >>16){
    16.         sum =(sum &0xFFFF)+(sum >>16);
    17. }
    18. /* 取反 */
    19. return(uint16_t)(~sum);
    20. }
    1. void
    2. ip_checksum(void*buf,size_t len,int flags)
    3. {
    4. struct ip_hdr *ip;
    5. int hl, off, sum;
    6. if(len < IP_HDR_LEN)
    7. return;
    8.     ip =(struct ip_hdr *)buf;
    9.     hl = ip->ip_hl <<2;/* IP 头部长度 */
    10.     ip->ip_sum =0;
    11.     sum = ip_cksum_add(ip, hl,0);
    12.     ip->ip_sum = ip_cksum_carry(sum);
    13. /* 如果只计算分片校验和,则返回 */
    14. if(flags & IP_CHECKSUM_FRAGMENT)
    15. return;
    16.     off = htons(ip->ip_off);
    17. /* 如果是分片,不计算上层校验和 */
    18. if((off & IP_OFFMASK)!=0||(off & IP_MF)!=0)
    19. return;
    20.     len -= hl;
    21. /* 根据上层协议计算相应的校验和 */
    22. if(ip->ip_p == IP_PROTO_TCP){
    23. struct tcp_hdr *tcp =(struct tcp_hdr *)((u_char *)ip + hl);
    24. if(len >= TCP_HDR_LEN){
    25.             tcp_checksum(ip, tcp, len);
    26. }
    27. }elseif(ip->ip_p == IP_PROTO_UDP){
    28. struct udp_hdr *udp =(struct udp_hdr *)((u_char *)ip + hl);
    29. if(len >= UDP_HDR_LEN){
    30.             udp_checksum(ip, udp, len);
    31. }
    32. }elseif(ip->ip_p == IP_PROTO_ICMP || ip->ip_p == IP_PROTO_IGMP){
    33. struct icmp_hdr *icmp =(struct icmp_hdr *)((u_char *)ip + hl);
    34. if(len >= ICMP_HDR_LEN){
    35.             icmp_checksum(icmp, len);
    36. }
    37. }elseif(ip->ip_p == IP_PROTO_SCTP){
    38. struct sctp_hdr *sctp =(struct sctp_hdr *)((u_char *)ip + hl);
    39. if(len >= SCTP_HDR_LEN){
    40.             sctp->sh_sum =0;
    41.             sctp->sh_sum = htonl(_crc32c((u_char *)sctp, len));
    42. }
    43. }
    44. }
    1. # 编译时定义 USE_COOKED
    2. gcc -DUSE_COOKED -o ip_sender ip_sender.-I include -.-ldnet
    3. # 可以普通用户运行
    4. ./ip_sender 192.168.1.100192.168.1.1
    1. REM 编译
    2. cl ip_sender./I include libdnet.lib ws2_32.lib iphlpapi.lib
    3. REM 需要管理员权限运行
    4. ip_sender.exe 192.168.1.100192.168.1.1
    1. # 编译
    2. gcc -o ip_sender ip_sender.-I include -.-ldnet
    3. # 需要 root 权限运行
    4. sudo ./ip_sender 192.168.1.100192.168.1.1
    1. /*
    2.  * 自定义 IP 数据包发送器
    3.  * 支持跨平台编译和运行
    4.  */
    5. #include<stdio.h>
    6. #include<stdlib.h>
    7. #include<string.h>
    8. #include"dnet.h"
    9. /* 构造自定义 IP 数据包 */
    10. void build_ip_packet(struct ip_hdr *ip,
    11. uint32_t src_ip,
    12. uint32_t dst_ip,
    13. uint8_t protocol,
    14. constvoid*data,
    15. size_t data_len)
    16. {
    17. /* IP 头部字段 */
    18.     ip->ip_v =4;/* IPv4 */
    19.     ip->ip_hl =5;/* 头部长度 20 字节 (5 * 4) */
    20.     ip->ip_tos =0;/* 服务类型 */
    21.     ip->ip_len = htons(IP_HDR_LEN + data_len);/* 总长度 */
    22.     ip->ip_id = htons(12345);/* 标识 */
    23.     ip->ip_off = htons(IP_DF);/* 不分片 */
    24.     ip->ip_ttl =64;/* TTL */
    25.     ip->ip_p = protocol;/* 协议 */
    26.     ip->ip_sum =0;/* 校验和(先清零) */
    27.     ip->ip_src = src_ip;
    28.     ip->ip_dst = dst_ip;
    29. /* 复制数据 */
    30.     memcpy((uint8_t*)ip + IP_HDR_LEN, data, data_len);
    31. /* 计算 IP 校验和 */
    32.     ip_checksum(ip, IP_HDR_LEN + data_len);
    33. }
    34. /* 构造 ICMP Echo Request 数据包 */
    35. void build_icmp_packet(uint8_t*packet,size_t len,
    36. uint32_t src_ip,uint32_t dst_ip)
    37. {
    38. struct ip_hdr *ip =(struct ip_hdr *)packet;
    39. struct icmp_hdr *icmp;
    40. /* ICMP 数据 */
    41. char data[64];
    42.     memset(data,0xAA,sizeof(data));
    43. /* 构造 IP 头 */
    44.     build_ip_packet(ip, src_ip, dst_ip, IP_PROTO_ICMP,
    45.                     data,sizeof(data));
    46. /* 构造 ICMP 头 */
    47.     icmp =(struct icmp_hdr *)((uint8_t*)ip + IP_HDR_LEN);
    48.     icmp->icmp_type = ICMP_ECHO;/* Echo Request */
    49.     icmp->icmp_code =0;
    50.     icmp->icmp_cksum =0;
    51.     icmp->icmp_seq = htons(1);
    52.     icmp->icmp_id = htons(54321);
    53. /* 计算 ICMP 校验和 */
    54.     icmp_checksum(icmp,sizeof(*icmp)+sizeof(data));
    55. }
    56. int main(int argc,char*argv[])
    57. {
    58. ip_t*ip;
    59. uint8_t packet[1500];
    60. uint32_t src_ip, dst_ip;
    61. ssize_t ret;
    62. if(argc <3){
    63.         printf("Usage: %s <src_ip> <dst_ip>\n", argv[0]);
    64. return1;
    65. }
    66. /* 解析 IP 地址 */
    67. if(ip_pton(argv[1],&src_ip)<0){
    68.         fprintf(stderr,"Invalid source IP: %s\n", argv[1]);
    69. return1;
    70. }
    71. if(ip_pton(argv[2],&dst_ip)<0){
    72.         fprintf(stderr,"Invalid destination IP: %s\n", argv[2]);
    73. return1;
    74. }
    75.     printf("Opening IP socket...\n");
    76. if((ip = ip_open())== NULL){
    77.         fprintf(stderr,"ip_open failed: %s\n", strerror(errno));
    78. return1;
    79. }
    80. /* 构造 ICMP Echo Request */
    81.     build_icmp_packet(packet,sizeof(packet), src_ip, dst_ip);
    82.     printf("Sending ICMP Echo Request:\n");
    83.     printf("  Source: %s\n", argv[1]);
    84.     printf("  Destination: %s\n", argv[2]);
    85.     ret = ip_send(ip, packet, ntohs(((struct ip_hdr *)packet)->ip_len));
    86. if(ret <0){
    87.         fprintf(stderr,"ip_send failed: %s\n", strerror(errno));
    88.         ip_close(ip);
    89. return1;
    90. }
    91.     printf("Sent %zd bytes\n", ret);
    92.     ip_close(ip);
    93. return0;
    94. }
    1. ip_t*
    2. ip_close(ip_t*ip)
    3. {
    4. struct ip_intf *ipi,*nxt;
    5. if(ip != NULL){
    6. /* 1. 释放接口链表 */
    7. for(ipi = LIST_FIRST(&ip->ip_intf_list);
    8.             ipi != LIST_END(&ip->ip_intf_list); ipi = nxt){
    9.             nxt = LIST_NEXT(ipi, next);
    10. if(ipi->eth != NULL)
    11.                 eth_close(ipi->eth);
    12.             free(ipi);
    13. }
    14. /* 2. 关闭其他模块 */
    15. if(ip->fd >=0)
    16.             close(ip->fd);
    17. if(ip->route != NULL)
    18.             route_close(ip->route);
    19. if(ip->intf != NULL)
    20.             intf_close(ip->intf);
    21. if(ip->arp != NULL)
    22.             arp_close(ip->arp);
    23. /* 3. 释放句柄 */
    24.         free(ip);
    25. }
    26. return(NULL);
    27. }
    1. /* 分片参数 */
    2. int ip_hl = iph->ip_hl <<2;/* IP 头部长度 */
    3. int fraglen = mtu - ip_hl;/* 每个分片的数据长度 */
    4. int offset =0;/* 分片偏移(8 字节为单位) */
    5. u_char *data_start = buf + ip_hl;
    6. u_char *data_end = buf + len;
    7. for(u_char *= data_start; p < data_end;){
    8. /* 计算当前分片的实际长度 */
    9. int current_len =(+ fraglen < data_end)? fraglen :(data_end - p);
    10. /* 设置 IP 头 */
    11.     iph->ip_len = htons(ip_hl + current_len);
    12.     iph->ip_off = htons(offset | IP_MF);/* 设置 MF 标志 */
    13. /* 最后一个分片清除 MF 标志 */
    14. if(+ current_len >= data_end){
    15.         iph->ip_off = htons(offset);
    16. }
    17. /* 发送分片 */
    18.     send_packet(iph, ip_hl + current_len);
    19. /* 更新偏移 */
    20.     offset +=(current_len >>3);/* 偏移以 8 字节为单位 */
    21.     p += current_len;
    22. }
    1. ┌─────────────────────────────────────────────────────────┐
    2. 1.查找出口接口
    3. -使用 UDP socket + connect + getsockname           
    4. -确定出口 IP 和对应的网卡
    5. └─────────────────────────────────────────────────────────┘
    6. ┌─────────────────────────────────────────────────────────┐
    7. 2. ARP 解析
    8. -查询 ARP 缓存
    9. -如果不在缓存中,发送 ARP 请求(最多3次)
    10. -获取目标 MAC 地址
    11. -如果有网关,查询网关的 MAC 地址
    12. └─────────────────────────────────────────────────────────┘
    13. ┌─────────────────────────────────────────────────────────┐
    14. 3.分片判断
    15. -检查数据包大小是否超过 MTU                          
    16. -如果超过,进行 IP 分片
    17. -设置 MF 标志和分片偏移
    18. └─────────────────────────────────────────────────────────┘
    19. ┌─────────────────────────────────────────────────────────┐
    20. 4.构造以太网帧
    21. ┌────────┬────────┬────────────┐
    22. 目标MAC MAC  类型=0x0800
    23. └────────┴────────┴────────────┘
    24. └─────────────────────────────────────────────────────────┘
    25. ┌─────────────────────────────────────────────────────────┐
    26. 5.发送数据包
    27. -通过以太网 socket 发送
    28. -使用 sendto()系统调用
    29. └─────────────────────────────────────────────────────────┘
    1. ssize_t
    2. ip_send(ip_t*ip,constvoid*buf,size_t len)
    3. {
    4. struct ip_hdr *iph;
    5. struct ip_intf *ipi;
    6. struct arp_entry arpent;
    7. struct route_entry rtent;
    8.     u_char frame[ETH_LEN_MAX];
    9. int i, usec;
    10.     iph =(struct ip_hdr *)buf;
    11. /* 1. 查找出口接口 */
    12. if((ipi = _lookup_ip_intf(ip, iph->ip_dst))== NULL){
    13.         errno = EHOSTUNREACH;
    14. return(-1);
    15. }
    16. /* 2. 准备 ARP 查询 */
    17.     arpent.arp_pa.addr_type = ADDR_TYPE_IP;
    18.     arpent.arp_pa.addr_bits = IP_ADDR_BITS;
    19.     arpent.arp_pa.addr_ip = iph->ip_dst;
    20.     memcpy(&rtent.route_dst,&arpent.arp_pa,sizeof(rtent.route_dst));
    21. /* 3. 尝试获取目标 MAC 地址(最多 3 次) */
    22. for(=0, usec =10; i <3; i++, usec *=100){
    23. /* 查询 ARP 缓存 */
    24. if(arp_get(ip->arp,&arpent)==0)
    25. break;
    26. /* 检查是否需要通过网关 */
    27. if(route_get(ip->route,&rtent)==0&&
    28.             rtent.route_gw.addr_ip != ipi->pa.addr_ip){
    29.             memcpy(&arpent.arp_pa,&rtent.route_gw,
    30. sizeof(arpent.arp_pa));
    31. if(arp_get(ip->arp,&arpent)==0)
    32. break;
    33. }
    34. /* 发送 ARP 请求 */
    35.         _request_arp(ipi,&arpent.arp_pa);
    36.         usleep(usec);
    37. }
    38. /* 4. 如果 3 次都失败,使用广播 MAC */
    39. if(==3)
    40.         memset(&arpent.arp_ha.addr_eth,0xff, ETH_ADDR_LEN);
    41. /* 5. 构造以太网头 */
    42.     eth_pack_hdr(frame, arpent.arp_ha.addr_eth,
    43.         ipi->ha.addr_eth, ETH_TYPE_IP);
    44. /* 6. 分片处理 */
    45. if(len > ipi->mtu){
    46.         u_char *p,*start,*end,*ip_data;
    47. int ip_hl, fraglen;
    48.         ip_hl = iph->ip_hl <<2;
    49.         fraglen = ipi->mtu - ip_hl;
    50.         iph =(struct ip_hdr *)(frame + ETH_HDR_LEN);
    51.         memcpy(iph, buf, ip_hl);
    52.         ip_data =(u_char *)iph + ip_hl;
    53.         start =(u_char *)buf + ip_hl;
    54.         end =(u_char *)buf + len;
    55. /* 分片发送 */
    56. for(= start; p < end;){
    57.             memcpy(ip_data, p, fraglen);
    58.             iph->ip_len = htons(ip_hl + fraglen);
    59.             iph->ip_off = htons(((+ fraglen < end)? IP_MF :0)|
    60. ((- start)>>3));
    61.             ip_checksum(iph, ip_hl + fraglen);
    62.             i = ETH_HDR_LEN + ip_hl + fraglen;
    63. if(eth_send(ipi->eth, frame, i)!= i)
    64. return(-1);
    65.             p += fraglen;
    66. if(end - p < fraglen)
    67.                 fraglen = end - p;
    68. }
    69. return(len);
    70. }
    71. /* 7. 不需要分片,直接发送 */
    72.     memcpy(frame + ETH_HDR_LEN, buf, len);
    73.     i = ETH_HDR_LEN + len;
    74. if(eth_send(ipi->eth, frame, i)!= i)
    75. return(-1);
    76. return(len);
    77. }
    1. ┌──────────────┬──────────────┬────────────┐
    2. 以太网头    ARP 填充
    3. 14 bytes    28 bytes  18 bytes 
    4. └──────────────┴──────────────┴────────────┘
    5. 以太网头:
    6. ┌────────┬────────┬────────────┐
    7. 目标MAC MAC  类型=0x0806
    8.  FF:FF...│本机MAC(ARP)
    9. └────────┴────────┴────────────┘
    10. ARP 头:
    11. ┌────┬────┬────┬────┬────────┬────┬────────┬────┐
    12.  HW  PRO  HW  PL  OPCODE  SM  SPA     TM 
    13.  TY  TY  SL  SL 1=Req6IP   6
    14. 1800642=Rep
    15. └────┴────┴────┴────┴────────┴────┴────────┴────┘
    1. staticvoid
    2. _request_arp(struct ip_intf *ipi,struct addr *dst)
    3. {
    4.     u_char frame[ETH_HDR_LEN + ARP_HDR_LEN + ARP_ETHIP_LEN];
    5. /* 1. 构造以太网头 */
    6.     eth_pack_hdr(frame, ETH_ADDR_BROADCAST, ipi->ha.addr_eth,
    7.         ETH_TYPE_ARP);
    8. /* 2. 构造 ARP 头 */
    9.     arp_pack_hdr_ethip(frame + ETH_HDR_LEN, ARP_OP_REQUEST,
    10.         ipi->ha.addr_eth,/* 发送方 MAC */
    11.         ipi->pa.addr_ip,/* 发送方 IP */
    12.         ETH_ADDR_BROADCAST,/* 目标 MAC (广播) */
    13.         dst->addr_ip);/* 目标 IP */
    14. /* 3. 发送 ARP 请求 */
    15.     eth_send(ipi->eth, frame,sizeof(frame));
    16. }
    1. /* 利用 UDP socket 进行路由查找 */
    2. int fd = socket(AF_INET, SOCK_DGRAM,0);
    3. /* 目标地址 */
    4. struct sockaddr_in target;
    5. target.sin_family = AF_INET;
    6. target.sin_addr.s_addr = dst_ip;
    7. /* connect() 不会发送数据,但会触发路由查找 */
    8. connect(fd,(struct sockaddr *)&target,sizeof(target));
    9. /* getsockname() 返回出口地址(本地地址) */
    10. struct sockaddr_in local;
    11. socklen_t len =sizeof(local);
    12. getsockname(fd,(struct sockaddr *)&local,&len);
    13. /* local.sin_addr 就是出口 IP */
    14. exit_ip = local.sin_addr.s_addr;
    1. staticstruct ip_intf *
    2. _lookup_ip_intf(ip_t*ip,ip_addr_t dst)
    3. {
    4. struct ip_intf *ipi;
    5. int n;
    6. /* 1. 设置目标地址 */
    7.     ip->sin.sin_addr.s_addr = dst;
    8.     n =sizeof(ip->sin);
    9. /* 2. connect() 用于路由查找
    10.      * 这是一个巧妙的技巧:
    11.      * - UDP socket 的 connect() 不会实际发送数据
    12.      * - 内核会执行路由查找,确定出口接口
    13.      * - 我们可以通过 getsockname() 获取出口 IP
    14.      */
    15. if(connect(ip->fd,(struct sockaddr *)&ip->sin, n)<0)
    16. return(NULL);
    17. /* 3. 获取本地地址 (出口 IP) */
    18. if(getsockname(ip->fd,(struct sockaddr *)&ip->sin, n)<0)
    19. return(NULL);
    20. /* 4. 在接口链表中查找对应的接口 */
    21.     LIST_FOREACH(ipi,&ip->ip_intf_list, next){
    22. if(ipi->pa.addr_ip == ip->sin.sin_addr.s_addr){
    23. /* 延迟打开以太网 socket */
    24. if(ipi->eth == NULL){
    25. if((ipi->eth = eth_open(ipi->name))== NULL)
    26. return(NULL);
    27. }
    28. /* LRU 优化:将使用的接口移到链表头部 */
    29. if(ipi != LIST_FIRST(&ip->ip_intf_list)){
    30.                 LIST_REMOVE(ipi, next);
    31.                 LIST_INSERT_HEAD(&ip->ip_intf_list, ipi, next);
    32. }
    33. return(ipi);
    34. }
    35. }
    36. return(NULL);
    37. }
    1. staticint
    2. _add_ip_intf(conststruct intf_entry *entry,void*arg)
    3. {
    4. ip_t*ip =(ip_t*)arg;
    5. struct ip_intf *ipi;
    6. /* 过滤条件 */
    7. if(entry->intf_type == INTF_TYPE_ETH &&
    8. (entry->intf_flags & INTF_FLAG_UP)!=0&&
    9.         entry->intf_mtu >= ETH_LEN_MIN &&
    10.         entry->intf_addr.addr_type == ADDR_TYPE_IP &&
    11.         entry->intf_link_addr.addr_type == ADDR_TYPE_ETH){
    12. if((ipi = calloc(1,sizeof(*ipi)))== NULL)
    13. return(-1);
    14. /* 保存接口信息 */
    15.         strlcpy(ipi->name, entry->intf_name,sizeof(ipi->name));
    16.         memcpy(&ipi->ha,&entry->intf_link_addr,sizeof(ipi->ha));
    17.         memcpy(&ipi->pa,&entry->intf_addr,sizeof(ipi->pa));
    18.         ipi->mtu = entry->intf_mtu;
    19. /* 添加到链表头部 */
    20.         LIST_INSERT_HEAD(&ip->ip_intf_list, ipi, next);
    21. }
    22. return(0);
    23. }
    1. ip_t*
    2. ip_open(void)
    3. {
    4. ip_t*ip;
    5. if((ip = calloc(1,sizeof(*ip)))!= NULL){
    6.         ip->fd =-1;
    7. /* 1. 打开底层模块 */
    8. if((ip->arp = arp_open())== NULL ||
    9. (ip->intf = intf_open())== NULL ||
    10. (ip->route = route_open())== NULL)
    11. return(ip_close(ip));
    12. /* 2. 创建 UDP socket 用于路由查找
    13.          * 创建 SOCK_DGRAM socket,内核会自动路由
    14.          */
    15. if((ip->fd = socket(AF_INET, SOCK_DGRAM,0))<0)
    16. return(ip_close(ip));
    17. /* 3. 初始化地址结构 */
    18.         memset(&ip->sin,0,sizeof(ip->sin));
    19.         ip->sin.sin_family = AF_INET;
    20.         ip->sin.sin_port = htons(666);
    21. /* 4. 初始化接口链表 */
    22.         LIST_INIT(&ip->ip_intf_list);
    23. /* 5. 遍历所有接口,添加符合条件的接口到链表
    24.          * 条件:
    25.          *   - 类型为以太网
    26.          *   - 接口 UP
    27.          *   - MTU >= 64 字节
    28.          *   - 有 IP 地址
    29.          *   - 有 MAC 地址
    30.          */
    31. if(intf_loop(ip->intf, _add_ip_intf, ip)!=0)
    32. return(ip_close(ip));
    33. }
    34. return(ip);
    35. }
    1. /* 接口链表元素 (ip-cooked.c:23-30) */
    2. struct ip_intf {
    3. eth_t*eth;/* 以太网句柄 */
    4. char             name[INTF_NAME_LEN];/* 接口名称 */
    5. struct addr      ha;/* 硬件地址 (MAC) */
    6. struct addr      pa;/* 协议地址 (IP) */
    7. int              mtu;/* 最大传输单元 */
    8.     LIST_ENTRY(ip_intf) next;/* 链表指针 */
    9. };
    10. /* IP 句柄 (ip-cooked.c:32-40) */
    11. struct ip_handle {
    12. arp_t*arp;/* ARP 模块 */
    13. intf_t*intf;/* 接口模块 */
    14. route_t*route;/* 路由模块 */
    15. int              fd;/* UDP socket 用于路由查找 */
    16. struct sockaddr_in sin;/* 地址结构 */
    17.     LIST_HEAD(, ip_intf) ip_intf_list;/* 接口链表 */
    18. };
    1. # 增加 BPF 设备数量
    2. sudo sysctl -w kern.bpfcache_max=128
    1. /* 使用 O_RDWR 而非 O_WRONLY */
    2. e->fd = open(file, O_RDWR);
    1. /* sysctl 参数详解 */
    2. int mib[]={
    3.     CTL_NET,/* 网络 (NET) */
    4.     AF_ROUTE,/* 路由域 (PF_ROUTE) */
    5. 0,/* 协议族 (0 = 任意) */
    6.     AF_LINK,/* 地址族 (AF_LINK = 链路层) */
    7.     NET_RT_IFLIST,/* 操作 (获取接口列表) */
    8. 0/* 地址 (0 = 所有接口) */
    9. };
    1. int
    2. eth_get(eth_t*e,eth_addr_t*ea)
    3. {
    4. struct if_msghdr *ifm;
    5. struct sockaddr_dl *sdl;
    6. struct addr ha;
    7.     u_char *p,*buf;
    8. size_t len;
    9. int mib[]={ CTL_NET, AF_ROUTE,0, AF_LINK, NET_RT_IFLIST,0};
    10. /* 1. 获取接口列表所需缓冲区大小 */
    11. if(sysctl(mib,6, NULL,&len, NULL,0)<0)
    12. return(-1);
    13. /* 2. 分配缓冲区 */
    14. if((buf = malloc(len))== NULL)
    15. return(-1);
    16. /* 3. 获取接口列表
    17.      * sysctl 返回所有网络接口的信息
    18.      */
    19. if(sysctl(mib,6, buf,&len, NULL,0)<0){
    20.         free(buf);
    21. return(-1);
    22. }
    23. /* 4. 遍历接口列表,查找指定设备 */
    24. for(= buf; p < buf + len; p += ifm->ifm_msglen){
    25.         ifm =(struct if_msghdr *)p;
    26.         sdl =(struct sockaddr_dl *)(ifm +1);
    27. /* 过滤条件 */
    28. if(ifm->ifm_type != RTM_IFINFO ||
    29. (ifm->ifm_addrs & RTA_IFP)==0)
    30. continue;
    31. /* 检查接口名称是否匹配 */
    32. if(sdl->sdl_family != AF_LINK || sdl->sdl_nlen ==0||
    33.             memcmp(sdl->sdl_data, e->device, sdl->sdl_nlen)!=0)
    34. continue;
    35. /* 转换 MAC 地址 */
    36. if(addr_ston((struct sockaddr *)sdl,&ha)==0)
    37. break;
    38. }
    39.     free(buf);
    40. if(>= buf + len){
    41.         errno = ESRCH;
    42. return(-1);
    43. }
    44.     memcpy(ea,&ha.addr_eth,sizeof(*ea));
    45. return(0);
    46. }
    1. /* Linux (eth-linux.c) */
    2. sendto(e->fd, buf, len,0,(struct sockaddr *)&e->sll,sizeof(e->sll));
    3. /* macOS/BSD (eth-bsd.c) */
    4. write(e->fd, buf, len);
    1. ssize_t
    2. eth_send(eth_t*e,constvoid*buf,size_t len)
    3. {
    4. return(write(e->fd, buf, len));
    5. }
    1. eth_t*
    2. eth_open(constchar*device)
    3. {
    4. struct ifreq ifr;
    5. char file[32];
    6. eth_t*e;
    7. int i;
    8. if((= calloc(1,sizeof(*e)))!= NULL){
    9. /* 1. 打开 BPF 设备
    10.          * BPF 设备文件路径: /dev/bpf0, /dev/bpf1, ...
    11.          * 尝试从 0 开始,最多尝试 128 次
    12.          */
    13. for(=0; i <128; i++){
    14.             snprintf(file,sizeof(file),"/dev/bpf%d", i);
    15. /* 注意:macOS 10.6 有一个 bug
    16.              * 使用 O_WRONLY 会导致其他进程无法接收流量
    17.              * 因此必须使用 O_RDWR
    18.              */
    19.             e->fd = open(file, O_RDWR);
    20. if(e->fd !=-1|| errno != EBUSY)
    21. break;
    22. }
    23. if(e->fd <0)
    24. return(eth_close(e));
    25. /* 2. 绑定到指定网络接口
    26.          * 使用 BIOCSETIF ioctl 绑定 BPF 设备到网络接口
    27.          */
    28.         memset(&ifr,0,sizeof(ifr));
    29.         strlcpy(ifr.ifr_name, device,sizeof(ifr.ifr_name));
    30. if(ioctl(e->fd, BIOCSETIF,(char*)&ifr)<0)
    31. return(eth_close(e));
    32. /* 3. 设置完整标志 (BIOCSHDRCMPLT)
    33.          * BIOCSHDRCMPLT: BPF 将不自动补全以太网头
    34.          * 这意味着用户必须提供完整的以太网帧
    35.          */
    36. #ifdef BIOCSHDRCMPLT
    37.         i =1;
    38. if(ioctl(e->fd, BIOCSHDRCMPLT,&i)<0)
    39. return(eth_close(e));
    40. #endif
    41. /* 4. 保存设备名称 */
    42.         strlcpy(e->device, device,sizeof(e->device));
    43. }
    44. return(e);
    45. }
    1. /* IP 层句柄结构 (ip.c:21-23) - 与 Linux 相同 */
    2. struct ip_handle {
    3. int fd;/* Raw socket 文件描述符 */
    4. };
    5. /* 以太网层句柄结构 (eth-bsd.c:34-37) */
    6. struct eth_handle {
    7. int     fd;/* BPF 文件描述符 */
    8. char    device[16];/* 设备名称 (如 en0) */
    9. };
    1. 调用 WSACleanup() 释放 WinSocket 资源
    2. 关闭 socket 句柄
    3. 释放内存
    1. ip_t*
    2. ip_close(ip_t*ip)
    3. {
    4. if(ip != NULL){
    5. WSACleanup();/* 清理 WinSocket 资源 */
    6. if(ip->fd != INVALID_SOCKET)
    7.             closesocket(ip->fd);/* 关闭 socket */
    8.         free(ip);
    9. }
    10. return(NULL);
    11. }
    1. sendto(..., sin_src,...)
    2. ├─ socket 绑定到源地址(sin.sin_addr = ip_src)
    3. ├─解析用户提供的 IP 
    4. ├─ ip_src: IP 地址
    5. ├─ ip_dst:目标 IP 地址(实际目标)
    6. └─ ip_p:上层协议
    7. ├─路由查找(基于 ip_dst)
    8. └─发送到网络
    1. /* Linux: 使用目标地址 */
    2. sin.sin_addr.s_addr = ip->ip_dst;// 正确
    3. /* Windows: 使用源地址 */
    4. ip->sin.sin_addr.s_addr = hdr->ip_src;// 注意这里!
    1. ssize_t
    2. ip_send(ip_t*ip,constvoid*buf,size_t len)
    3. {
    4. struct ip_hdr *hdr =(struct ip_hdr *)buf;
    5. /* 1. 设置目标地址
    6.      * 注意:Windows 使用 ip_src 作为目标地址!
    7.      * 这与 Linux 不同(Linux 使用 ip_dst)
    8.      */
    9.     ip->sin.sin_addr.s_addr = hdr->ip_src;
    10. /* 2. 发送数据包 */
    11. if((len = sendto(ip->fd,(constchar*)buf, len,0,
    12. (struct sockaddr *)&ip->sin,sizeof(ip->sin)))!= SOCKET_ERROR)
    13. return(len);
    14. return(-1);
    15. }
    1. ip_t*
    2. ip_open(void)
    3. {
    4.     BOOL on;
    5. ip_t*ip;
    6. /* 1. 分配句柄内存 */
    7. if((ip = calloc(1,sizeof(*ip)))!= NULL){
    8. /* 2. 初始化 WinSocket
    9.          * WSAStartup(MAKEWORD(2, 2), &ip->wsdata):
    10.          * - 请求 WinSocket 2.2 版本
    11.          * - 将版本信息存储在 wsdata 中
    12.          */
    13. if(WSAStartup(MAKEWORD(2,2),&ip->wsdata)!=0){
    14.             free(ip);
    15. return(NULL);
    16. }
    17. /* 3. 创建原始 Socket
    18.          * AF_INET: IPv4 协议族
    19.          * SOCK_RAW: 原始 socket 类型
    20.          * IPPROTO_RAW: 原始 IP 协议
    21.          */
    22. if((ip->fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))==
    23.             INVALID_SOCKET)
    24. return(ip_close(ip));
    25. /* 4. 设置 IP_HDRINCL 选项
    26.          * 在 Windows 上,这个选项是必须的
    27.          * 如果失败,设置错误代码为 ERROR_NETWORK_ACCESS_DENIED
    28.          */
    29.         on = TRUE;
    30. if(setsockopt(ip->fd, IPPROTO_IP, IP_HDRINCL,
    31. (constchar*)&on,sizeof(on))== SOCKET_ERROR){
    32. SetLastError(ERROR_NETWORK_ACCESS_DENIED);
    33. return(ip_close(ip));
    34. }
    35. /* 5. 初始化地址结构
    36.          * sin_family = AF_INET
    37.          * sin_port = 666 (任意端口,IP 层不使用)
    38.          */
    39.         ip->sin.sin_family = AF_INET;
    40.         ip->sin.sin_port = htons(666);
    41. }
    42. return(ip);
    43. }
    1. /* Windows 平台的 IP 句柄结构 (ip-win32.c:18-22) */
    2. struct ip_handle {
    3.     WSADATA         wsdata;/* WinSocket 初始化数据 */
    4.     SOCKET          fd;/* Socket 句柄 */
    5. struct sockaddr_in sin;/* 目标地址结构 */
    6. };
    1. ip_t*
    2. ip_close(ip_t*i)
    3. {
    4. if(!= NULL){
    5. if(i->fd >=0)
    6.             close(i->fd);/* 关闭 socket 文件描述符 */
    7.         free(i);/* 释放句柄内存 */
    8. }
    9. return(NULL);
    10. }
    1. 路由查找:根据目标地址确定出口接口
    2. 分片判断:如果数据包超过 MTU,进行分片
    3. IP 头校验:内核会重新计算 IP 校验和(虽然用户已计算)
    4. 传输:通过网络接口发送
    1. 用户态数据包
    2. ├─解析 IP 头,获取目标地址 ip->ip_dst
    3. ├─填充struct sockaddr_in
    4. ├─ sin_family = AF_INET
    5. ├─ sin_addr.s_addr = ip->ip_dst
    6. └─ sin_port =0(IP 层不需要端口)
    7. ├─ sendto()系统调用
    8. └─内核网络栈
    9. ├─路由查找(查找出口网卡)
    10. ├─分片处理(如果需要)
    11. └─发送到网络
    1. ssize_t
    2. ip_send(ip_t*i,constvoid*buf,size_t len)
    3. {
    4. struct ip_hdr *ip;
    5. struct sockaddr_in sin;
    6.     ip =(struct ip_hdr *)buf;
    7. /* 1. 构造目标地址结构 */
    8.     memset(&sin,0,sizeof(sin));
    9. #ifdef HAVE_SOCKADDR_SA_LEN
    10.     sin.sin_len =sizeof(sin);/* BSD 特有:地址长度字段 */
    11. #endif
    12.     sin.sin_family = AF_INET;
    13.     sin.sin_addr.s_addr = ip->ip_dst;/* 使用 IP 头中的目标地址 */
    14. /* 2. 某些平台的特殊处理
    15.      * HAVE_RAWIP_HOST_OFFLEN: 表示平台在传输前需要将字节序转换为主机序
    16.      * 主要是某些 BSD 系统
    17.      */
    18. #ifdef HAVE_RAWIP_HOST_OFFLEN
    19. /* 转换为主机序 */
    20.     ip->ip_len = ntohs(ip->ip_len);
    21.     ip->ip_off = ntohs(ip->ip_off);
    22. /* 发送数据包 */
    23.     len = sendto(i->fd, buf, len,0,
    24. (struct sockaddr *)&sin,sizeof(sin));
    25. /* 转换回网络序 */
    26.     ip->ip_len = htons(ip->ip_len);
    27.     ip->ip_off = htons(ip->ip_off);
    28. return(len);
    29. #else
    30. /* 标准 Linux 实现:直接发送 */
    31. return(sendto(i->fd, buf, len,0,
    32. (struct sockaddr *)&sin,sizeof(sin)));
    33. #endif
    34. }
    1. ip_t*
    2. ip_open(void)
    3. {
    4. ip_t*i;
    5. int n;
    6. socklen_t len;
    7. /* 1. 分配句柄内存 */
    8. if((= calloc(1,sizeof(*i)))== NULL)
    9. return(NULL);
    10. /* 2. 创建 IPv4 原始 socket
    11.      * AF_INET: IPv4 协议族
    12.      * SOCK_RAW: 原始 socket 类型
    13.      * IPPROTO_RAW: 原始 IP 协议,允许用户完全控制 IP 头部
    14.      */
    15. if((i->fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW))<0)
    16. return(ip_close(i));
    17. /* 3. 设置 IP_HDRINCL 选项
    18.      * IP_HDRINCL: 告诉内核 IP 头部由应用程序提供
    19.      * 某些系统(如 OpenBSD)不支持此选项
    20.      */
    21. #ifdef IP_HDRINCL
    22.     n =1;
    23. if(setsockopt(i->fd, IPPROTO_IP, IP_HDRINCL,&n,sizeof(n))<0)
    24. return(ip_close(i));
    25. #endif
    26. /* 4. 调整发送缓冲区大小
    27.      * Linux 默认的发送缓冲区可能较小,通过循环测试找到最大可用大小
    28.      */
    29. #ifdef SO_SNDBUF
    30.     len =sizeof(n);
    31. if(getsockopt(i->fd, SOL_SOCKET, SO_SNDBUF,&n,&len)<0)
    32. return(ip_close(i));
    33. /* 从当前缓冲区大小开始,每次增加 128 字节
    34.      * 直到达到 1MB 或失败为止
    35.      */
    36. for(+=128; n <1048576; n +=128){
    37. if(setsockopt(i->fd, SOL_SOCKET, SO_SNDBUF,&n, len)<0){
    38. if(errno == ENOBUFS)/* 缓冲区耗尽 */
    39. break;
    40. return(ip_close(i));
    41. }
    42. }
    43. #endif
    44. /* 5. 允许发送广播包 */
    45. #ifdef SO_BROADCAST
    46.     n =1;
    47. if(setsockopt(i->fd, SOL_SOCKET, SO_BROADCAST,&n,sizeof(n))<0)
    48. return(ip_close(i));
    49. #endif
    50. return(i);
    51. }
    1. /* Linux 平台的 IP 句柄结构 (ip.c:21-23) */
    2. struct ip_handle {
    3. int fd;/* Raw socket 文件描述符 */
    4. };
    1. ┌─────────────────────────────────────────────────────┐
    2.               libdnet IP Module
    3. ├─────────────────────────────────────────────────────┤
    4.   ip_open()      ip_send()      ip_close()
    5. ├─────────────────────────────────────────────────────┤
    6.   ip-cooked.c    ip.c          ip-win32.c             
    7. (用户态实现)(Linux/BSD)(Windows)
    8. └─────────────────────────────────────────────────────┘
    9. ┌───────────────┐┌──────────────┐┌──────────────────┐
    10.    eth_open() socket()WSASocket()
    11.    eth_send() sendto() sendto()
    12. (用户态以太网)│(PF_PACKET)(AF_INET, RAW)
    13. (BPF on BSD)(RawSocket)(WinSocket)
    14. └───────────────┘└──────────────┘└──────────────────┘
  • 公众号:安全狗的自我修养

  • vx:2207344074

  • http://gitee.com/haidragon

  • http://github.com/haidragon

  • bilibili:haidragonx

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 跨平台底层网络库libdnet源码分析系列(三)

猜你喜欢

  • 暂无文章