kernel使用skb打印ip地址和tcp端口号

本文介绍了在Linux内核代码中使用`printk_ratelimit()`结合宏`NIPQUAD()`来限制打印频率并优雅地显示IP地址的方法。当网络包为IP协议时,代码会提取并打印源和目标IP地址以及TCP信息,如端口号。此外,还展示了一个新的内核打印IP地址的简洁方式。

if(printk_ratelimit())

{

#define NIPQUAD(addr) \

((unsigned char *)&addr)[0],

\ ((unsigned char *)&addr)[1],

\ ((unsigned char *)&addr)[2],

\ ((unsigned char *)&addr)[3]

      if((skb ->protocol) == htons(ETH_P_IP ))

    {

            struct iphdr *nh;

            struct tcphdr *th;

           nh = ip_hdr(skb);

           //printk("src: %u.%u.%u.%u, dst: %u.%u.%u.%u\n", NIPQUAD(nh->saddr), NIPQUAD(nh->daddr));

          if(nh->protocol == IPPROTO_TCP)

         {

               th = tcp_hdr(skb);

              printk(" src: %u.%u.%u.%u, sport: %d dst: %u.%u.%u.%u dport: %d \n", NIPQUAD(nh->saddr), th->source, NIPQUAD(nh->daddr), th->dest);

           }

       }

}

最近编写内核代码,有发现一个新的内核打印ip地址的方法:

if (printk_ratelimit()) {

       

        printk("%s fun: IP: %pI4\n", __func__, &iph->saddr);

}

Linux 内核网络子系统中,`sk_buff`(简称 `skb`)是表示网络数据包的核心结构体。要从 `skb` 中提取 **端口号**(TCP/UDP 源或目的端口),你需要: 1. 确保 skb 包含传输层头部(TCP/UDP) 2. 正确获取 IP 头部传输层头部指针 3. 提取源/目的端口字段 --- ## ✅ 基本原理 - `struct sk_buff *skb`:指向数据包缓冲区 - 使用 `skb->network_header` 获取 IP 头起始位置 - 使用 `skb->transport_header` 获取 TCP/UDP 头起始位置 - 强制类型转换为 `struct tcphdr` 或 `struct udphdr` - 读取 `.source` `.dest` 字段(注意字节序) --- ## ✅ 示例代码:判断 UDP/TCP 端口号 ```c #include <linux/skbuff.h> #include <linux/ip.h> #include <linux/tcp.h> #include <linux/udp.h> #include <linux/in.h> // 判断是否匹配特定目的端口(例如 80) bool is_dest_port(struct sk_buff *skb, __be16 port) { struct iphdr *ip_header; struct tcphdr *tcp_header; struct udphdr *udp_header; // 1. 检查是否为 IPv4 协议 if (!pskb_may_pull(skb, sizeof(struct iphdr))) return false; ip_header = ip_hdr(skb); if (ip_header->version != 4) return false; // 2. 根据协议判断 switch (ip_header->protocol) { case IPPROTO_TCP: // 确保 TCP 头可访问 if (!pskb_may_pull(skb, ip_header->ihl * 4 + sizeof(struct tcphdr))) return false; tcp_header = tcp_hdr(skb); return tcp_header->dest == port; case IPPROTO_UDP: // 确保 UDP 头可访问 if (!pskb_may_pull(skb, ip_header->ihl * 4 + sizeof(struct udphdr))) return false; udp_header = udp_hdr(skb); return udp_header->dest == port; default: return false; } } ``` --- ### 🔍 关键函数解释 | 函数 / 宏 | 说明 | |----------|------| | `ip_hdr(skb)` | 获取 IP 头指针 | | `tcp_hdr(skb)` | 获取 TCP 头指针(基于 transport_header) | | `udp_hdr(skb)` | 获取 UDP 头指针 | | `pskb_may_pull(skb, len)` | 安全地检查是否有足够空间读取指定长度的数据(防止越界) | | `__be16` | 表示“大端 16 位整数”,即网络字节序 | > ⚠️ 所有端口号都是 **网络字节序(大端)**,所以比较时不要用 `htons()` 转换 `port` 参数! --- ## ✅ 使用方式示例 ```c // 判断是否是发往 80 端口的包 if (is_dest_port(skb, htons(80))) { printk(KERN_INFO "HTTP traffic detected!\n"); } // 或直接写常量(推荐使用 htons 明确转换) if (is_dest_port(skb, htons(53))) { // DNS printk(KERN_INFO "DNS packet captured.\n"); } ``` --- ## ✅ 扩展:同时判断源端口协议 ```c bool is_src_port_proto(struct sk_buff *skb, __be16 port, u8 proto) { struct iphdr *ip = ip_hdr(skb); if (ip->version != 4 || ip->protocol != proto) return false; if (!pskb_may_pull(skb, ip->ihl * 4)) return false; switch (proto) { case IPPROTO_TCP: { if (!pskb_may_pull(skb, ip->ihl * 4 + sizeof(struct tcphdr))) return false; return tcp_hdr(skb)->source == port; } case IPPROTO_UDP: { if (!pskb_may_pull(skb, ip->ihl * 4 + sizeof(struct udphdr))) return false; return udp_hdr(skb)->source == port; } default: return false; } } ``` --- ## ✅ 注意事项 1. **确保 `skb` 已经解析过头部** 如果你在 netfilter hook(如 `NF_INET_PRE_ROUTING`)中使用,通常没问题; 2. **避免未对齐访问** - 使用 `pskb_may_pull()` 是安全做法; - 不要直接强制转换指针而不检查; 3. **支持分片包?** - 上述代码不处理 IP 分片; - 若需处理,请先调用 `ip_defrag()` 或跳过分片包; 4. **IPv6 支持?** - 需额外判断 `ETH_P_IPV6` 使用 `ipv6_hdr()`、`ipv6_find_hdr()`; --- ## ✅ IPv6 版本简略示例 ```c #include <linux/ipv6.h> if (skb->protocol == htons(ETH_P_IPV6)) { struct ipv6hdr *ip6h = ipv6_hdr(skb); unsigned int off = sizeof(struct ipv6hdr); u8 nexthdr = ip6h->nexthdr; // 解析扩展头(简化版) struct tcphdr _tcph; struct tcphdr *th; th = skb_header_pointer(skb, off, sizeof(_tcph), &_tcph); if (th) { if (nexthdr == IPPROTO_TCP && th->dest == htons(443)) { printk("IPv6 HTTPS packet\n"); } } } ``` --- ## ✅ 总结 你可以在内核模块、Netfilter 钩子、TC BPF 等场景中使用上述代码来判断 `skb` 的端口号。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值