资讯动态

Linux 内核中网络地址快速打印符

发布时间:2026/9/9 14:03:33 来源:尧图企业网站定制
网络地址快速打印printk扩展格式说明符内核中最便捷的网络地址打印方式——printk扩展格式说明符。这些是内核printk函数专门为网络地址提供的格式化输出功能。一、IPv4地址打印1.1 基本格式#includelinux/printk.h__be32 ipaddrhtonl(0xC0A80101);// 192.168.1.1// %pI4 - 打印点分十进制不带前导零标准格式printk(KERN_INFOIP: %pI4\n,ipaddr);// 输出: IP: 192.168.1.1// %pi4 - 打印点分十进制带前导零固定宽度printk(KERN_INFOIP: %pi4\n,ipaddr);// 输出: IP: 192.168.001.0011.2 字节序说明structiphdr*iphip_hdr(skb);// 注意必须传递指针不能直接传值printk(KERN_DEBUGsrc IP: %pI4, dst IP: %pI4\n,iph-saddr,iph-daddr);// iph-saddr 已经是网络字节序__be32无需转换二、IPv6地址打印2.1 基本格式#includenet/ipv6.hstructin6_addripv6_addr;// 假设已填充地址数据// %pI6 - 完整格式冒号分隔16位字使用网络字节序printk(KERN_INFOIPv6: %pI6\n,ipv6_addr);// 输出: 2001:0db8:0000:0000:0000:ff00:0042:8329// %pi6 - 紧凑格式不带冒号printk(KERN_INFOIPv6: %pi6\n,ipv6_addr);// 输出: 20010db8000000000000ff0000428329// %pI6c - 压缩格式RFC 5952标准★ 最常用printk(KERN_INFOIPv6: %pI6c\n,ipv6_addr);// 输出: 2001:db8::ff00:42:83292.2 IPv6格式对比格式符输出示例说明%pI62001:0db8:0000:0000:0000:ff00:0042:8329完整格式前导零保留%pi620010db8000000000000ff0000428329无冒号完整32位十六进制%pI6c2001:db8::ff00:42:8329压缩格式符合RFC 5952三、MAC地址打印#includelinux/netdevice.h// MAC地址通常以 u8[6] 或 unsigned char[6] 存储u8 mac[6]{0x00,0x11,0x22,0x33,0x44,0x55};// %pM - 标准格式冒号分隔printk(KERN_INFOMAC: %pM\n,mac);// 输出: MAC: 00:11:22:33:44:55// %pMR - 反转顺序某些协议需要printk(KERN_INFOMAC (reversed): %pMR\n,mac);// 输出: MAC (reversed): 55:44:33:22:11:00// %pMF - 不带冒号的格式printk(KERN_INFOMAC: %pMF\n,mac);// 输出: MAC: 00-11-22-33-44-55四、sockaddr结构体打印最强大4.1 基本用法#includelinux/socket.h#includenet/sock.hstructsockaddr_in*sin;structsockaddr_in6*sin6;structsockaddr*sa;// %pIS - 打印sockaddr中的IP地址和端口printk(KERN_INFOAddress: %pIS\n,sa);// IPv4输出: 192.168.1.1:8080// IPv6输出: [2001:db8::1]:8080// %pISc - 压缩IPv6格式的sockaddrprintk(KERN_INFOAddress: %pISc\n,sa);// IPv6输出: [2001:db8::1]:8080已压缩4.2 完整格式选项// %pIS - 包含端口IPv6地址用方括号括起printk(KERN_DEBUG%pIS\n,sa);// IPv4: 192.168.1.1:80// IPv6: [2001:db8::1]:80// %pISp - 仅打印IP地址不含端口printk(KERN_DEBUG%pISp\n,sa);// IPv4: 192.168.1.1// IPv6: 2001:db8::1// %pISpc - 压缩IPv6格式仅IPprintk(KERN_DEBUG%pISpc\n,sa);// IPv6: 2001:db8::1五、实际使用示例5.1 网络数据包调试#includelinux/skbuff.h#includelinux/ip.h#includenet/ipv6.hvoiddump_packet_info(structsk_buff*skb){structiphdr*ip4;structipv6hdr*ip6;switch(skb-protocol){casehtons(ETH_P_IP):ip4ip_hdr(skb);// 简洁的地址打印printk(KERN_INFOIPv4: %pI4 - %pI4, len%u\n,ip4-saddr,ip4-daddr,ntohs(ip4-tot_len));break;casehtons(ETH_P_IPV6):ip6ipv6_hdr(skb);// 使用压缩格式printk(KERN_INFOIPv6: %pI6c - %pI6c, len%u\n,ip6-saddr,ip6-daddr,ntohs(ip6-payload_len));break;}}5.2 网络设备地址打印#includelinux/netdevice.hvoidshow_netdev_info(structnet_device*dev){conststructnet_device_ops*opsdev-netdev_ops;// MAC地址printk(KERN_INFODevice: %s\n,dev-name);printk(KERN_INFO MAC: %pM\n,dev-dev_addr);// IPv4地址需要遍历地址列表if(dev-ip_ptr){structin_device*in_dev__in_dev_get_rtnl(dev);if(in_devin_dev-ifa_list){structin_ifaddr*ifain_dev-ifa_list;printk(KERN_INFO IPv4: %pI4\n,ifa-ifa_local);}}}5.3 套接字地址打印#includenet/sock.hvoidprint_socket_address(structsock*sk){structsockaddr_insin;structsockaddr_in6sin6;intlen;// 获取本地地址lensizeof(sin);if(kernel_getsockname(sk,(structsockaddr*)sin,len)0){printk(KERN_INFOLocal: %pIS\n,sin);}// 获取对端地址lensizeof(sin6);if(kernel_getpeername(sk,(structsockaddr*)sin6,len)0){printk(KERN_INFORemote: %pISc\n,sin6);}}六、格式说明符完整参考6.1 汇总表格式符数据类型输出示例说明%pI4__be32*192.168.1.1IPv4无前导零%pi4__be32*192.168.001.001IPv4带前导零%pI6struct in6_addr*2001:0db8:0000:0000:0000:ff00:0042:8329IPv6完整格式%pi6struct in6_addr*20010db8000000000000ff0000428329IPv6无冒号%pI6cstruct in6_addr*2001:db8::ff00:42:8329IPv6压缩格式%pMu8[6]00:11:22:33:44:55MAC地址冒号分隔%pMRu8[6]55:44:33:22:11:00MAC地址反转%pMFu8[6]00-11-22-33-44-55MAC地址横线分隔%pISstruct sockaddr*192.168.1.1:80或[2001:db8::1]:80带端口%pISpstruct sockaddr*192.168.1.1或2001:db8::1仅地址%pISpcstruct sockaddr*2001:db8::1IPv6压缩仅地址6.2 IPv6压缩格式的特殊说明// 压缩格式遵循 RFC 5952 标准// 规则1: 省略前导零// 规则2: 用 :: 表示最长的连续零段// 规则3: 字母使用小写structin6_addraddr;// 地址: 2001:0db8:0000:0000:0000:0000:0000:0001// %pI6c 输出: 2001:db8::1七、与传统方式的对比7.1 旧式方法已过时// 旧式宏仍在部分代码中存在但不推荐#includelinux/ip.h// 旧式IPv4打印printk(IP: %d.%d.%d.%d\n,NIPQUAD(iph-saddr));// 繁琐需要多个参数// 旧式IPv6打印已基本废弃printk(IPv6: NIP6_FMT\n,NIP6(ipv6_hdr(skb)-saddr));// 复杂容易出错7.2 现代方法推荐// 现代方法简洁、安全、类型检查printk(IP: %pI4\n,iph-saddr);printk(IPv6: %pI6c\n,ipv6_hdr(skb)-saddr);八、注意事项8.1 必须传递指针__be32 iphtonl(0xC0A80101);// 错误直接传值printk(%pI4\n,ip);// 错误// 正确传递地址printk(%pI4\n,ip);// 正确8.2 字节序自动处理// IPv4地址在网络协议栈中已经是网络字节序__be32// 格式说明符会自动按网络字节序解释structiphdr*iphip_hdr(skb);printk(%pI4\n,iph-saddr);// 直接使用无需 ntohl8.3 RCU/锁保护// 访问网络设备地址时可能需要锁保护rcu_read_lock();structnet_device*devdev_get_by_name(init_net,eth0);if(dev){printk(KERN_INFOMAC: %pM\n,dev-dev_addr);dev_put(dev);}rcu_read_unlock();8.4 格式符与内核版本// %pI6cIPv6压缩格式需要较新的内核3.x// 如果需要兼容老内核可以使用#ifdefCONFIG_PRINTKprintk(%pI6c\n,addr);#else// 备选方案#endif九、完整调试示例#includelinux/module.h#includelinux/netdevice.h#includelinux/skbuff.h#includelinux/ip.h#includenet/ipv6.hstaticvoiddump_skb_info(structsk_buff*skb){structiphdr*ip4;structipv6hdr*ip6;structethhdr*etheth_hdr(skb);printk(KERN_INFO Packet Info \n);printk(KERN_INFOMAC: %pM - %pM\n,eth-h_source,eth-h_dest);switch(skb-protocol){casehtons(ETH_P_IP):ip4ip_hdr(skb);printk(KERN_INFOIPv4: %pI4:%d - %pI4:%d\n,ip4-saddr,ntohs(tcp_hdr(skb)-source),ip4-daddr,ntohs(tcp_hdr(skb)-dest));break;casehtons(ETH_P_IPV6):ip6ipv6_hdr(skb);printk(KERN_INFOIPv6: %pI6c - %pI6c\n,ip6-saddr,ip6-daddr);break;}}// 注册到netfilter或驱动中使用十、总结需求推荐格式符示例IPv4地址快速打印%pI4printk(%pI4, addr)IPv6地址打印首选%pI6cprintk(%pI6c, addr6)MAC地址打印%pMprintk(%pM, mac)带端口的地址打印%pISprintk(%pIS, sockaddr)核心要点所有格式符都必须传递指针不能直接传值地址数据应保持内核原有的字节序IPv4为网络字节序IPv6优先使用%pI6c压缩格式%pIS系列可直接打印sockaddr结构最为方便这些扩展格式说明符是Linux内核网络调试中最常用的工具比手动调用in4_ntop/in6_ntop要简洁得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价