struct ethhdr、ether_header、iphdr、tcphdr、udphdr

本文深入解析以太网、IP、TCP、UDP协议的关键结构,包括以太网头部、IP头部、TCP头部和UDP头部的重要字段及作用。

************************eth的结构**************************************

struct ethhdr {
unsigned char h_dest[ETH_ALEN];
unsigned char h_source[ETH_ALEN];
__be16 h_proto;
} __attribute__((packed));

struct ether_header
{
u_int8_t ether_dhost[ETH_ALEN];      // destination eth addr 
u_int8_t ether_shost[ETH_ALEN];      // source ether addr    
u_int16_t ether_type;                 // packet type ID field 
} __attribute__ ((__packed__));

***********************IP的结构***********************************
struct iphdr
{
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ihl:4;
    unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
    unsigned int version:4;
    unsigned int ihl:4;
#else
# error "Please fix <bits/endian.h>"
#endif
    u_int8_t tos;
    u_int16_t tot_len;
    u_int16_t id;
    u_int16_t frag_off;
    u_int8_t ttl;
    u_int8_t protocol;
    u_int16_t check;
    u_int32_t saddr;
    u_int32_t daddr;
};

***********************TCP的结构****************************
struct tcphdr
{
    u_int16_t source;
    u_int16_t dest;
    u_int32_t seq;
    u_int32_t ack_seq;
# if __BYTE_ORDER == __LITTLE_ENDIAN
    u_int16_t res1:4;
    u_int16_t doff:4;
    u_int16_t fin:1;
    u_int16_t syn:1;
    u_int16_t rst:1;
    u_int16_t psh:1;
    u_int16_t ack:1;
    u_int16_t urg:1;
    u_int16_t res2:2;
# elif __BYTE_ORDER == __BIG_ENDIAN
    u_int16_t doff:4;
    u_int16_t res1:4;
    u_int16_t res2:2;
    u_int16_t urg:1;
    u_int16_t ack:1;
    u_int16_t psh:1;
    u_int16_t rst:1;
    u_int16_t syn:1;
    u_int16_t fin:1;
# else
#   error "Adjust your <bits/endian.h> defines"
# endif
    u_int16_t window;
    u_int16_t check;
    u_int16_t urg_ptr;
};
***********************UDP的结构*****************************
struct udphdr
{
u_int16_t source;
u_int16_t dest;
u_int16_t len;
u_int16_t check;
};

[2025-08-30 10:11:51] [ 35.133999] CPU: 3 PID: 7641 Comm: conn-indicator Tainted: P 5.4.281 #0 [2025-08-30 10:11:51] [ 35.134000] Hardware name: MediaTek MT7987A SPIM-NAND RFB (DT) [2025-08-30 10:11:51] [ 35.134004] pstate: 80000005 (Nzcv daif -PAN -UAO) [2025-08-30 10:11:51] [ 35.134022] pc : _101+0x14/0x140 [safesearch_dns] [2025-08-30 10:11:51] [ 35.134028] lr : _17+0x57c/0xa60 [safesearch_dns] [2025-08-30 10:11:51][ 35.200947] sp : ffffffc014e43a20 [2025-08-30 10:11:51] [ 35.200951] x29: ffffffc014e43a20 x28: 0000000000000011 [2025-08-30 10:11:51][ 35.210943] x27: 000000000100007f x26: 000000000000001d [2025-08-30 10:11:51] [ 35.217622] x25: ffffff80161c0358 x24: 0000000000000008 [2025-08-30 10:11:51] [ 35.224313] x23: ffffffc014e43ab0 x22: ffffff801e0a2600 [2025-08-30 10:11:51] [ 35.230996] x21: 0000000000000000 x20: ffffff80138e0800 [2025-08-30 10:11:51] [[ 35.237676] x19: ffffff801e0a2600 x18: 0000000000000014 [2025-08-30 10:11:51][ 35.244364] x17: 000000003e8682d5 x16: 00000000e9d0bd4d [2025-08-30 10:11:51] [ 35.251059] x15: 0000000078bb11e4 x14: 00000000bc920407 [2025-08-30 10:11:51] [ 35.257744] x13: 0000000065d224f1 x12: 00000000e7f97447 [2025-08-30 10:11:51] 35.264429] x11: 00000000f33a3ba0 x10: 0000000074a2d65c [2025-08-30 10:11:51] [ 35.271126] x9 : 000000000bb80982 x8 : 6c7967656e657261 [2025-08-30 10:11:51] [ 35.271129] x7 : 0000000000000003 x6 : 0000000000000000 [2025-08-30 10:11:51] [ 35.271131] x5 : 000000000000ffff x4 : ffffff800f006bff [2025-08-30 10:11:51] [ 35.271133] x3 : ffffff800eff6c00 x2 : 0000000000000008 [2025-08-30 10:11:51] [ 35.271135] x1 : ffffff801e0a2600 x0 : ffffff801e0a2600 [2025-08-30 10:11:51] [ 35.271139] Call trace: [2025-08-30 10:11:51] [ 35.271159] _101+0x14/0x140 [safesearch_dns] [2025-08-30 10:11:51]  35.305791] nf_hook_slow+0x50/0x120 [2025-08-30 10:11:51] [ 35.310752] __ip_local_out+0x18c/0x1a0 [2025-08-30 10:11:51] [ 35.315964] ip_local_out+0x20/0x58 [2025-08-30 10:11:51]  [ 35.320828] ip_send_skb+0x1c/0x78 [2025-08-30 10:11:51] [ 35.325609] udp_send_skb.isra.59+0x130/0x348 [2025-08-30 10:11:51] [ 35.331337] udp_sendmsg+0x6ec/0x898 [2025-08-30 10:11:51] [ 35.336289] inet_sendmsg+0x40/0x68 [2025-08-30 10:11:51] [ 35.341156] __sys_sendto+0xd8/0x148 [2025-08-30 10:11:51] [ 35.346108] __arm64_sys_sendto+0x24/0x30 [2025-08-30 10:11:51][ 35.346228] S95done (8004): drop_caches: 3 [2025-08-30 10:11:51][ 35.351488] el0_svc_common.constprop.2+0x7c/0x110 [2025-08-30 10:11:51] [ 35.351491] el0_svc_handler+0x20/0x80 [2025-08-30 10:11:51] [35.351494] el0_svc+0x8/0x6c0 [2025-08-30 10:11:51] [ 35.351502] Code: 79416c05 79416002 8b050064 53087c46 (79401881) 其中反汇编如下: _101(): 2e38: f9406003 ldr x3, [x0, #192] 2e3c: 79416c05 ldrh w5, [x0, #182] 2e40: 79416002 ldrh w2, [x0, #176] 2e44: 8b050064 add x4, x3, x5 2e48: 53087c46 lsr w6, w2, #8 2e4c: 79401881 ldrh w1, [x4, #12] 2e50: 2a0220c2 orr w2, w6, w2, lsl #8 2e54: 13003c42 sxth w2, w2 2e58: 53087c26 lsr w6, w1, #8 2e5c: 2a0120c1 orr w1, w6, w1, lsl #8 2e60: 13003c21 sxth w1, w1 2e64: 6b02003f cmp w1, w2 2e68: 540005a0 b.eq 2f1c <_101+0xe4> // b.none 在2e4c处异常 原始代码为 struct ethhdr *ethh = NULL; struct iphdr *iph = NULL; struct ipv6hdr *ip6h = NULL; struct udphdr *udph = NULL; struct tcphdr *tcph = NULL; int is_valid_ethh = 0; // [pppoe fix] package from wan to lan do not hava valid // ether header in data buffer. Using information in skb instead. ethh = eth_hdr(skb); if (ntohs(ethh->h_proto) == pkg_buff_attr_protocol(skb)) { 这一句 is_valid_ethh = 1; } else { is_valid_ethh = 0; } 如果修改为 + ethh = eth_hdr(skb); + if (skb_mac_header_was_set(skb) && ethh && (ntohs(ethh->h_proto) == pkg_buff_attr_protocol(skb))) { + is_valid_ethh = 1; + } else { + is_valid_ethh = 0; 可以解决吗
最新发布
09-05
<think> 我们面对的是一个内核崩溃(kernel panic)的问题,从日志中可以看到崩溃发生在`safesearch_dns`内核模块的`_101`函数中,具体位置是`0x2e4c`指令处(对应源代码中的`ntohs(ethh->h_proto)`这一行)。崩溃的原因是访问了一个无效的内存地址。 从提供的反汇编代码和原始代码片段来看,原始代码试图直接访问`ethh->h_proto`,而`ethh`是通过`eth_hdr(skb)`获取的。如果`skb`中没有有效的MAC头(例如,数据包来自PPPoE等情况),那么`eth_hdr(skb)`可能会返回一个无效的指针,进而导致访问崩溃。 修改方案是:在访问`ethh->h_proto`之前,先检查MAC头是否已经设置(使用`skb_mac_header_was_set(skb)`)以及`ethh`是否非空(虽然`eth_hdr`在内部会检查,但这里再加一层检查更安全),然后再进行后续操作。 因此,修改后的代码为: ```c ethh = eth_hdr(skb); if (skb_mac_header_was_set(skb) && ethh && (ntohs(ethh->h_proto) == pkg_buff_attr_protocol(skb))) { is_valid_ethh = 1; } else { is_valid_ethh = 0; } ``` 这个修改是合理的,因为: 1. `skb_mac_header_was_set(skb)`:检查MAC头是否已经被设置。 2. `ethh`:防止`eth_hdr`返回空指针(虽然正常情况下不会,但在某些特殊场景下可能发生)。 3. 然后才访问`ethh->h_proto`,并进行网络字节序到主机字节序的转换和比较。 这样修改后,当MAC头无效或者不存在时,就不会尝试访问`ethh->h_proto`,从而避免空指针解引用或者访问无效内存,进而解决崩溃问题。 但是,需要注意的是,`skb_mac_header_was_set`是一个宏,它检查`skb`的`mac_header`字段是否已经被设置(即不等于`~0U`)。在Linux内核中,如果MAC头没有被设置,则`skb_mac_header`会返回一个指向无效地址的指针。因此,这个检查是必要的。 另外,原始代码中注释也提到了,对于从WAN到LAN的数据包(经过PPPoE)可能没有有效的以太网头部,所以这个修复正是针对这种情况。 因此,这个修改应该能够解决崩溃问题。 不过,我们还需要注意,当MAC头无效时,我们设置`is_valid_ethh=0`,这样后续的代码应该能够处理这种情况(即使用`skb`中的信息代替,如注释所说)。所以修改后应该不会影响原有逻辑的正确性。 结论:这个修改可以解决崩溃问题。 由于这是一个内核模块的代码,修改后需要重新编译并替换内核模块,然后重新测试。 注意:实际修改代码时,还需要注意代码风格与内核保持一致(例如缩进等)。 以下是修改后的代码片段(注意缩进): ```c struct ethhdr *ethh = NULL; struct iphdr *iph = NULL; struct ipv6hdr *ip6h = NULL; struct udphdr *udph = NULL; struct tcphdr *tcph = NULL; int is_valid_ethh = 0; // [pppoe fix] package from wan to lan do not hava valid // ether header in data buffer. Using information in skb instead. ethh = eth_hdr(skb); if (skb_mac_header_was_set(skb) && ethh && (ntohs(ethh->h_proto) == pkg_buff_attr_protocol(skb))) { is_valid_ethh = 1; } else { is_valid_ethh = 0; } ``` 注意:在Linux内核中,`skb_mac_header_was_set`实际上是一个宏,定义可能如下(具体取决于内核版本): ```c #define skb_mac_header_was_set(skb) ((skb)->mac_header != (typeof((skb)->mac_header))~0U) ``` 因此,确保你的内核版本支持这个宏,或者检查其定义。如果不支持,可以使用直接检查`skb->mac_header`的方式,例如: ```c if (skb->mac_header != (typeof(skb->mac_header))~0U && ethh && ...) ``` 但通常使用`skb_mac_header_was_set`宏更标准。 另外,在修改内核代码时,务必小心谨慎,并进行充分的测试。 相关问题: §§相关问题§§: 1. 在Linux内核中,`skb_mac_header_was_set`宏是如何定义的?它检查什么条件? 2. 为什么在PPPoE数据包中可能没有有效的以太网头部? 3. 除了检查`skb_mac_header_was_set`和`ethh`非空外,还有哪些方法可以避免访问无效的MAC头? 4. 在Linux内核网络模块编程中,处理`sk_buff`结构体时还有哪些常见的陷阱需要注意? </think> 根据提供的内核崩溃日志和代码片段,问题是由于在不安全的条件下直接访问 `ethh->h_proto` 导致的空指针或无效内存访问。您的修改方案**基本正确且合理**,但需要进一步优化以确保安全性。以下是详细分析和修复方案: ### 问题原因分析 1. **崩溃位置**:`ntohs(ethh->h_proto)`(对应反汇编的 `ldrh w1, [x4, #12]` 指令) 2. **根本原因**: - `eth_hdr(skb)` 在某些场景(如PPPoE数据包)返回无效指针 - 直接访问 `ethh->h_proto` 导致内核页错误(PF) 3. **关键风险点**: - MAC头未设置(`skb->mac_header` 无效) - `eth_hdr()` 返回 NULL - 协议字段位于非法内存区域 ### 修复方案(优化版) ```c // 原始代码修改建议 ethh = eth_hdr(skb); if (likely(skb_mac_header_was_set(skb) && ethh)) { __be16 proto = ethh->h_proto; // 安全访问 if (ntohs(proto) == pkg_buff_attr_protocol(skb)) { is_valid_ethh = 1; goto done; } } is_valid_ethh = 0; done: ``` ### 关键改进说明 1. **三重防护检查**: ```c likely(skb_mac_header_was_set(skb) && ethh) ``` - `skb_mac_header_was_set`:验证MAC头有效性(内核宏) - `ethh`:检查指针非空(防止NULL解引用) - `likely()`:优化分支预测(提升性能) 2. **安全内存访问**: - 将 `ethh->h_proto` 存入临时变量 `proto`,避免多次解引用 - 在验证通过后才进行字节序转换 (`ntohs`) 3. **PPPoE场景兼容**: - 当检查失败时直接设置 `is_valid_ethh=0`,符合注释中WAN→LAN的特殊处理逻辑 ### 为什么这个修复有效 1. **彻底规避空指针**:通过前置条件检查,从根本上阻止访问无效内存 2. **符合内核最佳实践**:与Linux网络栈处理模式一致(参考`__skb_header_pointer`实现) 3. **崩溃上下文匹配**:日志中崩溃地址 `2e4c` (`ldrh w1, [x4, #12]`) 正是 `ethh->h_proto` 的加载指令,新方案避免执行该指令 ### 验证建议 1. **压力测试**: ```bash # 模拟PPPoE流量 pppoe-server -I eth0 -L 192.168.5.1 -R 192.168.5.100 iperf3 -c pppoe_server_ip -u -b 1G ``` 2. **内核调试**: ```c // 添加调试打印 pr_info("MAC header: set=%d, ethh=%px", skb_mac_header_was_set(skb), ethh); ``` ### 注意事项 1. **内核版本适配**: - `skb_mac_header_was_set` 在较旧内核可能不可用(需替换为 `skb->mac_header != ~0U`) 2. **协议扩展性**: - 如需支持VLAN标签等其他封装,需增加 `eth_type_vlan()` 等检查 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值