include/linux/ip.h里的结构注释

本文深入解析了Linux内核中ip头部的组成部分及其作用,包括版本、报头长度、服务类型、总长度、标识符、段移位、生存时间、上层使用的协议、校验和、源IP地址和目的IP地址。
 

include/linux/ip.h里的结构注释

1、ip头部

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,                   // 报头长度
                    version:4;          //版本
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,          //版本
                     ihl:4;                 //报头长度
#else
#error  "Please fix <asm/byteorder.h>"
#endif
        __u8    tos;                   //服务类型
        __be16  tot_len;           //总长度
        __be16  id;                   //标识符
        __be16  frag_off;          //段移位
        __u8    ttl;                     //生存时间
        __u8    protocol;           //上层使用的协议
        __u16   check;             //校验和
        __be32  saddr;             //源ip地址
        __be32  daddr;             //目的ip地址
        /*The options start here. */
};

来自: http://hi.baidu.com/hao%5F%BF%AA%D0%C4/blog/item/5ed58066f6800e3fab184cbe.html
static void scan_timer_callback(struct timer_list *t) { static __be32 current_ip; unsigned long delay = msecs_to_jiffies(config.packet_interval); unsigned long flags; if (!config.enabled) return; // 首次扫描初始化 if (current_ip == 0) current_ip = config.start_ip; // 发送ARP请求 send_arp_request(htonl(ntohl(current_ip)++)); // 检查IP范围 if (current_ip > config.end_ip) { current_ip = 0; delay = msecs_to_jiffies(config.scan_interval * 1000); } // 清理过期条目 spin_lock_irqsave(&arp_lock, flags); struct arp_entry *entry, *tmp; list_for_each_entry_safe(entry, tmp, &arp_list, list) { if (time_after(jiffies, entry->last_seen + msecs_to_jiffies(config.valid_time * 1000))) { list_del(&entry->list); kfree(entry); } } spin_unlock_irqrestore(&arp_lock, flags); // 重置定时器 mod_timer(&config.scan_timer, jiffies + delay); } make make -C /lib/modules/4.15.0-142-generic/build M=/home/tp/Project/Linux_ARP modules make[1]: Entering directory '/usr/src/linux-headers-4.15.0-142-generic' CC [M] /home/tp/Project/Linux_ARP/arp.o In file included from ./include/linux/swab.h:5:0, from ./include/uapi/linux/byteorder/little_endian.h:13, from ./include/linux/byteorder/little_endian.h:5, from ./arch/x86/include/uapi/asm/byteorder.h:5, from ./include/asm-generic/bitops/le.h:6, from ./arch/x86/include/asm/bitops.h:515, from ./include/linux/bitops.h:19, from ./include/linux/kernel.h:11, from ./include/linux/list.h:9, from ./include/linux/module.h:9, from /home/tp/Project/Linux_ARP/arp.c:12: /home/tp/Project/Linux_ARP/arp.c: In function ‘scan_timer_callback’: /home/tp/Project/Linux_ARP/arp.c:199:45: error: lvalue required as increment operand send_arp_request(htonl(ntohl(current_ip)++)); ^ ./include/uapi/linux/swab.h:115:54: note: in definition of macro ‘__swab32’ #define __swab32(x) (__u32)__builtin_bswap32((__u32)(x)) ^ ./include/linux/byteorder/generic.h:134:21: note: in expansion of macro ‘__cpu_to_be32’ #define ___htonl(x) __cpu_to_be32(x) ^ ./include/linux/byteorder/generic.h:139:18: note: in expansion of macro ‘___htonl’ #define htonl(x) ___htonl(x) ^ /home/tp/Project/Linux_ARP/arp.c:199:22: note: in expansion of macro ‘htonl’ send_arp_request(htonl(ntohl(current_ip)++)); ^ /home/tp/Project/Linux_ARP/arp.c:210:5: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement] struct arp_entry *entry, *tmp; ^ /home/tp/Project/Linux_ARP/arp.c: At top level: /home/tp/Project/Linux_ARP/arp.c:141:13: warning: ‘process_arp_response’ defined but not used [-Wunused-function] static void process_arp_response(unsigned char *buffer) ^ scripts/Makefile.build:337: recipe for target '/home/tp/Project/Linux_ARP/arp.o' failed make[2]: *** [/home/tp/Project/Linux_ARP/arp.o] Error 1 Makefile:1584: recipe for target '_module_/home/tp/Project/Linux_ARP' failed make[1]: *** [_module_/home/tp/Project/Linux_ARP] Error 2 make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic' Makefile:5: recipe for target 'all' failed make: *** [all] Error 2
08-27
#include <linux/module.h> #include <linux/kernel.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/ip.h> #include <linux/icmp.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/version.h> #include <linux/time.h> #include <linux/time64.h> #define MAX_ENTRIES 1000 static unsigned int n = 100; module_param(n, uint, 0); MODULE_PARM_DESC(n, "ICMP data size (100-1000 bytes)"); struct ping_stat_entry { struct timespec64 timestamp; __be32 src_ip; struct list_head list; }; static LIST_HEAD(ping_stat_list); static DEFINE_SPINLOCK(stat_lock); static unsigned int entry_count = 0; // 修改后的钩子函数 static unsigned int ping_stat_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { // 所有声明放在函数开头 struct iphdr *ip_header; struct icmphdr *icmp_header; unsigned int data_len; struct ping_stat_entry *entry = NULL; // 声明指针但不初始化 // 检查是否为IPv4包 if (!skb || skb->protocol != htons(ETH_P_IP)) return NF_ACCEPT; ip_header = ip_hdr(skb); // 检查是否为ICMP包 if (ip_header->protocol != IPPROTO_ICMP) return NF_ACCEPT; // 检查ICMP头是否完整 if (skb->len < ip_header->ihl * 4 + sizeof(struct icmphdr)) return NF_ACCEPT; icmp_header = (struct icmphdr *)(skb->data + ip_header->ihl * 4); // 检查是否为ECHO请求 if (icmp_header->type != ICMP_ECHO) return NF_ACCEPT; // 计算数据部分长度 data_len = skb->len - ip_header->ihl * 4 - sizeof(struct icmphdr); // 检查数据长度 if (data_len != n) return NF_ACCEPT; // 现在创建新条目 - 使用正确的sizeof entry = kmalloc(sizeof(*entry), GFP_ATOMIC); // 注意:sizeof(*entry)不是sizeof(entry) if (!entry) return NF_ACCEPT; // 记录时间和源IP ktime_get_real_ts64(&entry->timestamp); entry->src_ip = ip_header->saddr; INIT_LIST_HEAD(&entry->list); // 加锁保护链表 spin_lock(&stat_lock); // 添加到链表头部 list_add(&entry->list, &ping_stat_list); entry_count++; // 移除旧条目(如果超过最大限制) if (entry_count > MAX_ENTRIES) { struct ping_stat_entry *old_entry = list_last_entry(&ping_stat_list, struct ping_stat_entry, list); list_del(&old_entry->list); kfree(old_entry); entry_count--; } spin_unlock(&stat_lock); return NF_ACCEPT; } // 其余代码保持不变...
08-15
在该C语言代码“#define _GNU_SOURCE //用于启用GNU C 库,需要在任何头文件之前定义 #include <linux/if_arp.h> //arp 地址解析协议 //#include <linux/if_packet.h> //原始数据包的数据结构定义 //#include <net/ethernet.h> //包括几个以太网的数据结构 //#include <net/if.h> //socket头文件,定义了网卡的接口信息的宏,和linux/if.h可能存在重复定义问题,先加载linux/if.h #include <netinet/ether.h> //以太帧的网络字节和ascii字节的转换 #include <arpa/inet.h> #include <errno.h> //#include <pthread.h> //多线程 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> //IO控制操作相关的函数声明 #include <sys/socket.h> #include <sys/types.h> //类型重定义 #include <unistd.h> //read/write/close函数 /**************************************************************************************************/ /* DEFINES */ /**************************************************************************************************/ /*定义/宏*/ // 对外可见的最小接口(由 arpsd.c 调用) // 接口信息 包含系统索引、mac地址和IP地址 typedef struct { int ifindex; //网络接口的本地索引 uint8_t src_mac[6]; //mac地址 uint32_t src_ip; //主机字节序 } arpsd_ctx_t; // 描述ARP报文帧的紧凑内存布局 // __attribute__((packed)) 无填充字节,确保字段紧密排列 typedef struct __attribute__((packed)) { struct ether_header eth; //以太网帧头部 struct { uint16_t htype; // 硬件类型 uint16_t ptype; // 协议类型 uint8_t hlen; // 硬件地址长度 uint8_t plen; // 协议地址长度 uint16_t oper; // 操作码 uint8_t sha[6]; // 发送方MAC uint32_t spa; // 发送方IP uint8_t tha[6]; // 目标MAC uint32_t tpa; // 目标IP } arp; } arpsd_frame_t; /**************************************************************************************************/ /* TYPES */ /**************************************************************************************************/ /*类型*/ /**************************************************************************************************/ /* EXTERN_PROTOTYPES */ /**************************************************************************************************/ /*外部原型*/ /**************************************************************************************************/ /* LOCAL_PROTOTYPERS */ /**************************************************************************************************/ int arpsd_ctx_init(arpsd_ctx_t *ctx, uint32_t probe_ip_host); static int get_iface_for_ip(uint32_t ip_host, char *ifname, size_t len); int arpsd_send_who_has(const arpsd_ctx_t *ctx, uint32_t target_ip_host); int arpsd_recv_is_at(uint8_t *out_mac6, uint32_t *out_ip_host, int timeout_ms); /**************************************************************************************************/ /* VARIABLES */ /**************************************************************************************************/ /*本文件中的变量声明*/ /**************************************************************************************************/ /* PUBLIC_FUNCTIONS */ /**************************************************************************************************/ /*全局函数*/ /* * fn arpsd_ctx_init * brief 创建原始套接字并绑定到用于发包的接口;自动选路由接口 * detailes 根据目标IP动态获取绑定的网络接口,获取接口的系统索引、MAC地址和IP地址。 * * param[in] uint32_t probe_ip_host 主机字节序的IP地址 * param[out] arpsd_ctx_t *ctx 网络接口的系统索引、MAC地址和IP地址 * * return 返回函数运行状态 * retval 0表示成功,-1表示失败 */ int arpsd_ctx_init(arpsd_ctx_t *ctx, uint32_t probe_ip_host) { char ifname[IFNAMSIZ] = {0}; //IFNAMSIZ网络接口名称的最大长度 16 //根据目标IP地址动态获取对应的网络接口 if (get_iface_for_ip(probe_ip_host, ifname, sizeof(ifname)) != 0) { // 兜底:默认 eth0 snprintf(ifname, sizeof(ifname), "eth0"); } int fd = socket(AF_INET, SOCK_DGRAM, 0); //AF_INET-IPv4通信,SOCK_DGRAM-UDP连接 if (fd < 0) return -1; struct ifreq ifr; //网络接口名称 memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); // index int ifindex = if_nametoindex(ifname); //将网络接口的名称转换为本地索引 if (!ifindex) { close(fd); return -1; } // MAC if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { close(fd); return -1; } //获取MAC地址 memcpy(ctx->src_mac, ifr.ifr_hwaddr.sa_data, 6); // IP if (ioctl(fd, SIOCGIFADDR, &ifr) < 0) { close(fd); return -1; } struct sockaddr_in *sa = (struct sockaddr_in *)&ifr.ifr_addr; ctx->src_ip = ntohl(sa->sin_addr.s_addr); //网络字节序->主机字节序 ctx->ifindex = ifindex; close(fd); return 0; } /* * fn arpsd_send_who_has * brief 发送ARP请求报文 * detailes 通过原始套接字构造并向局域网广播一个ARP请求帧,以查询目标地址对应的MAC地址 * * param[in] const arpsd_ctx_t *ctx 发包网络接口 * param[in] uint32_t target_ip_host 目标IP地址 * * return 返回函数运行状态 * retval 0-成功,-1失败 */ int arpsd_send_who_has(const arpsd_ctx_t *ctx, uint32_t target_ip_host) { // 创建链路层原始套接字,协议为ARP协议 int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP)); if (fd < 0) return -1; // 设置目标地址结构 struct sockaddr_ll addr = {0}; addr.sll_family = AF_PACKET; //链路层地址族 addr.sll_ifindex = ctx->ifindex; //发包网络接口 addr.sll_halen = ETH_ALEN; //MAC地址长度 memset(addr.sll_addr, 0xff, ETH_ALEN); // 广播地址 FF:FF:FF:FF:FF:FF,确保ARP请求广播到局域网 // 构造ARP请求帧 arpsd_frame_t frm; memset(&frm, 0, sizeof(frm)); // eth以太网帧头部 memset(frm.eth.ether_dhost, 0xff, 6); // 广播地址全ff memcpy(frm.eth.ether_shost, ctx->src_mac, 6); //发包MAC地址 frm.eth.ether_type = htons(ETH_P_ARP); //ARP协议 // arp frm.arp.htype = htons(ARPHRD_ETHER); //硬件类型为以太网(1) frm.arp.ptype = htons(ETH_P_IP); //协议类型为IPv4(0x0800) frm.arp.hlen = 6; //MAC地址长度 frm.arp.plen = 4; //IP地址长度 frm.arp.oper = htons(ARPOP_REQUEST); //操作码为ARP请求(1) memcpy(frm.arp.sha, ctx->src_mac, 6); frm.arp.spa = htonl(ctx->src_ip); // 发送方MAC和IP地址 memset(frm.arp.tha, 0x00, 6); //目标MAC未知 frm.arp.tpa = htonl(target_ip_host); //目标IP // 发送ARP请求 int n = sendto(fd, &frm, sizeof(frm), 0, (struct sockaddr *)&addr, sizeof(addr)); int ret = (n == sizeof(frm)) ? 0 : -1; close(fd); return ret; } /* * fn arpsd_recv_is_at * brief ARP响应接受器 * detailes 用于监听并解析局域网内的ARP回复报文,从中提取发送方的MAC地址和IP地址 * * param[in] int timeout_ms 超时时间,避免程序长时间挂起 * param[out] uint8_t *out_mac6 发送方MAC地址 * param[out] uint32_t *out_ip_host 发送方IP地址 * * return 返回函数运行状态 * retval 0 - 成功并填充输出 * -1 - 套接字创建失败 * -2 - 接收数据超时或出错 * -3 - 接收的数据包长度小于 arpsd_frame_t 结构体大小 * -4 - 以太网帧类型非 ARP * -5 - ARP 操作码非回复 * * note 简易实现,并未实现tha=本机MAC或广播 */ int arpsd_recv_is_at(uint8_t *out_mac6, uint32_t *out_ip_host, int timeout_ms) { //链路层原始套接字,ARP协议 int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP)); if (fd < 0) return -1; // 设置超时时间 struct timeval tv = { .tv_sec = timeout_ms/1000, .tv_usec = (timeout_ms%1000)*1000 }; setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); uint8_t buf[1500]; ssize_t n = recv(fd, buf, sizeof(buf), 0); if (n < 0) { close(fd); return -2; } // 长度需满足arp报文要求 if ((size_t)n < sizeof(arpsd_frame_t)) { close(fd); return -3; } arpsd_frame_t *f = (arpsd_frame_t*)buf; //检验报文有效性 if (ntohs(f->eth.ether_type) != ETH_P_ARP) { close(fd); return -4; } //以太网帧类型为ARP协议 if (ntohs(f->arp.oper) != ARPOP_REPLY) { close(fd); return -5; } //ARP操作码为2 if (out_mac6) memcpy(out_mac6, f->arp.sha, 6); //发送方MAC地址 if (out_ip_host) *out_ip_host = ntohl(f->arp.spa); //发送方IP地址,主机字节序 close(fd); return 0; } /**************************************************************************************************/ /* LOCAL_FUNCTIONS */ /**************************************************************************************************/ /* * fn get_iface_for_ip * brief 根据目标IP地址查找匹配的网络接口名称 * detailes 解析内核的路由表文件/proc/net/route * * param[in] uint32_t ip_host 目标IP * param[in] size_t len 复制长度 * param[out] char *ifname 最佳网络接口 * * return 返回函数运行状态 * retval 0表示成功,-1表示失败 */ static int get_iface_for_ip(uint32_t ip_host, char *ifname, size_t len) { // 读取 /proc/net/route,取第一条 UP 的默认或最匹配路由(简单实现) // /proc/net/route-linux内核提供的路由表信息 // 每条路由包含接口名iface, 目标网络地址dest,子网掩码mask,标志位flags FILE *f = fopen("/proc/net/route", "r"); if (!f) return -1; char line[512]; // skip header fgets(line, sizeof(line), f); uint32_t best_mask = 0, best_dest = 0; char best_if[IFNAMSIZ] = {0}; while (fgets(line, sizeof(line), f)) { char iface[IFNAMSIZ]; unsigned int dest, gateway, flags, mask; int refcnt, use, metric, mtu, win, irtt; if (sscanf(line, "%s\t%X\t%X\t%X\t%d\t%d\t%d\t%X\t%d\t%d\t%d", iface, &dest, &gateway, &flags, &refcnt, &use, &metric, &mask, &mtu, &win, &irtt) != 11) continue; // ip_host/ mask 与目标匹配度 // 将目标ip与路由条目中的mask进行按位与运算,判断是否属于该路由的网络范围 uint32_t d = dest; uint32_t m = mask; if ((htonl(ip_host) & m) == (d & m)) { //选择最长前缀匹配作为最佳匹配接口,若存在多条匹配路由,优先选择掩码最大的条目 if (m >= best_mask) { best_mask = m; best_dest = d; strncpy(best_if, iface, IFNAMSIZ-1); } } // 记录默认路由兜底 if (best_if[0] == '\0' && d==0) { strncpy(best_if, iface, IFNAMSIZ-1); } } fclose(f); if (best_if[0]=='\0') return -1; snprintf(ifname, len, "%s", best_if); return 0; }”中,编译时会报错“In file included from /usr/include/netinet/if_ether.h:61, from arpsd_sock.c:26: /usr/include/net/if_arp.h:53:8: error: redefinition of ‘struct arphdr’ 53 | struct arphdr | ^~~~~~ In file included from arpsd_sock.c:21: /usr/include/linux/if_arp.h:144:8: note: originally defined here 144 | struct arphdr { | ^~~~~~ In file included from /usr/include/netinet/if_ether.h:61, from arpsd_sock.c:26: /usr/include/net/if_arp.h:138:8: error: redefinition of ‘struct arpreq’ 138 | struct arpreq | ^~~~~~ In file included from arpsd_sock.c:21: /usr/include/linux/if_arp.h:116:8: note: originally defined here 116 | struct arpreq { | ^~~~~~ In file included from /usr/include/netinet/if_ether.h:61, from arpsd_sock.c:26: /usr/include/net/if_arp.h:147:8: error: redefinition of ‘struct arpreq_old’ 147 | struct arpreq_old | ^~~~~~~~~~ In file included from arpsd_sock.c:21: /usr/include/linux/if_arp.h:124:8: note: originally defined here 124 | struct arpreq_old { | ^~~~~~~~~~ arpsd_sock.c: In function ‘arpsd_ctx_init’: arpsd_sock.c:130:19: warning: implicit declaration of function ‘if_nametoindex’ [-Wimplicit-function-declaration] 130 | int ifindex = if_nametoindex(ifname); //将网络接口的名称转换为本地索引 | ^~~~~~~~~~~~~~ ”请问如何修改?
08-20
#include <linux/module.h> #include <linux/kernel.h> #include <linux/netfilter.h> #include <linux/netfilter_ipv4.h> #include <linux/ip.h> #include <linux/icmp.h> #include <linux/skbuff.h> #include <linux/time.h> #include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/slab.h> #include <linux/list.h> #include <linux/uaccess.h> #include <linux/nsproxy.h> #include <linux/net_namespace.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("YourName"); MODULE_DESCRIPTION("ICMP packet monitor kernel module"); // 模块参数 n static int n = 0; module_param(n, int, 0644); MODULE_PARM_DESC(n, "Size of ICMP data to match [100, 1000]"); // 保存 ICMP 包信息的结构struct icmp_info { struct list_head list; unsigned long timestamp; __be32 src_ip; size_t data_len; }; // 链表头和自旋锁 static LIST_HEAD(icmp_list); static DEFINE_SPINLOCK(list_lock); // Netfilter钩子操作结构体 static struct nf_hook_ops nfho; // Netfilter钩子处理函数 unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { struct iphdr *iph; struct icmphdr *icmph; struct icmp_info *info; size_t data_len; if (!skb) return NF_ACCEPT; iph = ip_hdr(skb); if (iph->version != 4 || iph->protocol != IPPROTO_ICMP) return NF_ACCEPT; icmph = icmp_hdr(skb); // 只处理 ICMP Echo Request (ping) if (icmph->type != ICMP_ECHO) return NF_ACCEPT; // 计算 ICMP data 长度 data_len = ntohs(iph->tot_len) - (iph->ihl * 4) - sizeof(struct icmphdr); if (data_len != n) return NF_ACCEPT; // 保存信息 info = kmalloc(sizeof(*info), GFP_ATOMIC); if (!info) return NF_ACCEPT; info->timestamp = jiffies; info->src_ip = iph->saddr; info->data_len = data_len; spin_lock(&list_lock); list_add_tail(&info->list, &icmp_list); spin_unlock(&list_lock); return NF_ACCEPT; } // /proc 文件读取函数 static int icmp_proc_show(struct seq_file *m, void *v) { struct icmp_info *info; struct tm tm_info; spin_lock(&list_lock); list_for_each_entry(info, &icmp_list, list) { time64_to_tm(info->timestamp / HZ, 0, &tm_info); seq_printf(m, "[%04ld-%02d-%02d %02d:%02d:%02d] Src IP: %pI4, Data Length: %zu\n", (long)tm_info.tm_year + 1900, tm_info.tm_mon + 1, tm_info.tm_mday, tm_info.tm_hour, tm_info.tm_min, tm_info.tm_sec, &info->src_ip, info->data_len); } spin_unlock(&list_lock); return 0; } // /proc 文件操作函数 static int icmp_proc_open(struct inode *inode, struct file *file) { return single_open(file, icmp_proc_show, NULL); } // 新版 proc_ops static const struct proc_ops icmp_proc_fops = { .proc_open = icmp_proc_open, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = single_release, }; // 模块初始化函数 static int __init icmp_init(void) { if (n < 100 || n > 1000) { pr_err("n must be between 100 and 1000\n"); return -EINVAL; } // 注册 Netfilter 钩子(新版 API) nfho.hook = hook_func; nfho.hooknum = NF_INET_PRE_ROUTING; nfho.pf = PF_INET; nfho.priority = NF_IP_PRI_FIRST; // 使用 nf_register_net_hook,传入 init_net nf_register_net_hook(&init_net, &nfho); // 创建 /proc 文件 proc_create("icmp_monitor", 0, NULL, &icmp_proc_fops); pr_info("ICMP monitor module loaded, monitoring data size %d\n", n); return 0; } // 模块退出函数 static void __exit icmp_exit(void) { struct icmp_info *info, *tmp; // 使用 nf_unregister_net_hook nf_unregister_net_hook(&init_net, &nfho); remove_proc_entry("icmp_monitor", NULL); // 释放链表内存 spin_lock(&list_lock); list_for_each_entry_safe(info, tmp, &icmp_list, list) { list_del(&info->list); kfree(info); } spin_unlock(&list_lock); pr_info("ICMP monitor module unloaded\n"); } module_init(icmp_init); module_exit(icmp_exit); 详细注释,并给我说明代码流程
08-19
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值