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

网络地址快速打印:printk扩展格式说明符

内核中最便捷的网络地址打印方式------printk扩展格式说明符 。这些是内核printk函数专门为网络地址提供的格式化输出功能。


一、IPv4地址打印

1.1 基本格式

c 复制代码
#include <linux/printk.h>

__be32 ipaddr = htonl(0xC0A80101);  // 192.168.1.1

// %pI4 - 打印点分十进制,不带前导零(标准格式)
printk(KERN_INFO "IP: %pI4\n", &ipaddr);
// 输出: IP: 192.168.1.1

// %pi4 - 打印点分十进制,带前导零(固定宽度)
printk(KERN_INFO "IP: %pi4\n", &ipaddr);
// 输出: IP: 192.168.001.001

1.2 字节序说明

c 复制代码
struct iphdr *iph = ip_hdr(skb);

// 注意:必须传递指针!不能直接传值
printk(KERN_DEBUG "src IP: %pI4, dst IP: %pI4\n", 
       &iph->saddr, &iph->daddr);
// iph->saddr 已经是网络字节序(__be32),无需转换

二、IPv6地址打印

2.1 基本格式

c 复制代码
#include <net/ipv6.h>

struct in6_addr ipv6_addr;
// 假设已填充地址数据

// %pI6 - 完整格式,冒号分隔,16位字使用网络字节序
printk(KERN_INFO "IPv6: %pI6\n", &ipv6_addr);
// 输出: 2001:0db8:0000:0000:0000:ff00:0042:8329

// %pi6 - 紧凑格式,不带冒号
printk(KERN_INFO "IPv6: %pi6\n", &ipv6_addr);
// 输出: 20010db8000000000000ff0000428329

// %pI6c - 压缩格式(RFC 5952标准)★ 最常用
printk(KERN_INFO "IPv6: %pI6c\n", &ipv6_addr);
// 输出: 2001:db8::ff00:42:8329

2.2 IPv6格式对比

格式符 输出示例 说明
%pI6 2001:0db8:0000:0000:0000:ff00:0042:8329 完整格式,前导零保留
%pi6 20010db8000000000000ff0000428329 无冒号,完整32位十六进制
%pI6c 2001:db8::ff00:42:8329 压缩格式,符合RFC 5952

三、MAC地址打印

c 复制代码
#include <linux/netdevice.h>

// MAC地址通常以 u8[6] 或 unsigned char[6] 存储
u8 mac[6] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};

// %pM - 标准格式,冒号分隔
printk(KERN_INFO "MAC: %pM\n", mac);
// 输出: MAC: 00:11:22:33:44:55

// %pMR - 反转顺序(某些协议需要)
printk(KERN_INFO "MAC (reversed): %pMR\n", mac);
// 输出: MAC (reversed): 55:44:33:22:11:00

// %pMF - 不带冒号的格式
printk(KERN_INFO "MAC: %pMF\n", mac);
// 输出: MAC: 00-11-22-33-44-55

四、sockaddr结构体打印(最强大)

4.1 基本用法

c 复制代码
#include <linux/socket.h>
#include <net/sock.h>

struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
struct sockaddr *sa;

// %pIS - 打印sockaddr中的IP地址和端口
printk(KERN_INFO "Address: %pIS\n", sa);
// IPv4输出: 192.168.1.1:8080
// IPv6输出: [2001:db8::1]:8080

// %pISc - 压缩IPv6格式的sockaddr
printk(KERN_INFO "Address: %pISc\n", sa);
// IPv6输出: [2001:db8::1]:8080(已压缩)

4.2 完整格式选项

c 复制代码
// %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格式,仅IP
printk(KERN_DEBUG "%pISpc\n", sa);
// IPv6: 2001:db8::1

五、实际使用示例

5.1 网络数据包调试

c 复制代码
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <net/ipv6.h>

void dump_packet_info(struct sk_buff *skb)
{
    struct iphdr *ip4;
    struct ipv6hdr *ip6;
    
    switch (skb->protocol) {
    case htons(ETH_P_IP):
        ip4 = ip_hdr(skb);
        // 简洁的地址打印
        printk(KERN_INFO "IPv4: %pI4 -> %pI4, len=%u\n",
               &ip4->saddr, &ip4->daddr, ntohs(ip4->tot_len));
        break;
        
    case htons(ETH_P_IPV6):
        ip6 = ipv6_hdr(skb);
        // 使用压缩格式
        printk(KERN_INFO "IPv6: %pI6c -> %pI6c, len=%u\n",
               &ip6->saddr, &ip6->daddr, ntohs(ip6->payload_len));
        break;
    }
}

5.2 网络设备地址打印

c 复制代码
#include <linux/netdevice.h>

void show_netdev_info(struct net_device *dev)
{
    const struct net_device_ops *ops = dev->netdev_ops;
    
    // MAC地址
    printk(KERN_INFO "Device: %s\n", dev->name);
    printk(KERN_INFO "  MAC: %pM\n", dev->dev_addr);
    
    // IPv4地址(需要遍历地址列表)
    if (dev->ip_ptr) {
        struct in_device *in_dev = __in_dev_get_rtnl(dev);
        if (in_dev && in_dev->ifa_list) {
            struct in_ifaddr *ifa = in_dev->ifa_list;
            printk(KERN_INFO "  IPv4: %pI4\n", &ifa->ifa_local);
        }
    }
}

5.3 套接字地址打印

c 复制代码
#include <net/sock.h>

void print_socket_address(struct sock *sk)
{
    struct sockaddr_in sin;
    struct sockaddr_in6 sin6;
    int len;
    
    // 获取本地地址
    len = sizeof(sin);
    if (kernel_getsockname(sk, (struct sockaddr *)&sin, &len) == 0) {
        printk(KERN_INFO "Local: %pIS\n", &sin);
    }
    
    // 获取对端地址
    len = sizeof(sin6);
    if (kernel_getpeername(sk, (struct sockaddr *)&sin6, &len) == 0) {
        printk(KERN_INFO "Remote: %pISc\n", &sin6);
    }
}

六、格式说明符完整参考

6.1 汇总表

格式符 数据类型 输出示例 说明
%pI4 __be32* 192.168.1.1 IPv4,无前导零
%pi4 __be32* 192.168.001.001 IPv4,带前导零
%pI6 struct in6_addr* 2001:0db8:0000:0000:0000:ff00:0042:8329 IPv6完整格式
%pi6 struct in6_addr* 20010db8000000000000ff0000428329 IPv6无冒号
%pI6c struct in6_addr* 2001:db8::ff00:42:8329 IPv6压缩格式
%pM u8[6] 00:11:22:33:44:55 MAC地址,冒号分隔
%pMR u8[6] 55:44:33:22:11:00 MAC地址,反转
%pMF u8[6] 00-11-22-33-44-55 MAC地址,横线分隔
%pIS struct sockaddr* 192.168.1.1:80[2001:db8::1]:80 带端口
%pISp struct sockaddr* 192.168.1.12001:db8::1 仅地址
%pISpc struct sockaddr* 2001:db8::1 IPv6压缩+仅地址

6.2 IPv6压缩格式的特殊说明

c 复制代码
// 压缩格式遵循 RFC 5952 标准
// 规则1: 省略前导零
// 规则2: 用 :: 表示最长的连续零段
// 规则3: 字母使用小写

struct in6_addr addr;
// 地址: 2001:0db8:0000:0000:0000:0000:0000:0001
// %pI6c 输出: 2001:db8::1

七、与传统方式的对比

7.1 旧式方法(已过时)

c 复制代码
// 旧式宏(仍在部分代码中存在,但不推荐)
#include <linux/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 现代方法(推荐)

c 复制代码
// 现代方法:简洁、安全、类型检查
printk("IP: %pI4\n", &iph->saddr);
printk("IPv6: %pI6c\n", &ipv6_hdr(skb)->saddr);

八、注意事项

8.1 必须传递指针

c 复制代码
__be32 ip = htonl(0xC0A80101);

// 错误:直接传值
printk("%pI4\n", ip);        // 错误!

// 正确:传递地址
printk("%pI4\n", &ip);       // 正确

8.2 字节序自动处理

c 复制代码
// IPv4地址在网络协议栈中已经是网络字节序(__be32)
// 格式说明符会自动按网络字节序解释
struct iphdr *iph = ip_hdr(skb);
printk("%pI4\n", &iph->saddr);  // 直接使用,无需 ntohl

8.3 RCU/锁保护

c 复制代码
// 访问网络设备地址时可能需要锁保护
rcu_read_lock();
struct net_device *dev = dev_get_by_name(&init_net, "eth0");
if (dev) {
    printk(KERN_INFO "MAC: %pM\n", dev->dev_addr);
    dev_put(dev);
}
rcu_read_unlock();

8.4 格式符与内核版本

c 复制代码
// %pI6c(IPv6压缩格式)需要较新的内核(3.x+)
// 如果需要兼容老内核,可以使用:
#ifdef CONFIG_PRINTK
    printk("%pI6c\n", &addr);
#else
    // 备选方案
#endif

九、完整调试示例

c 复制代码
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <net/ipv6.h>

static void dump_skb_info(struct sk_buff *skb)
{
    struct iphdr *ip4;
    struct ipv6hdr *ip6;
    struct ethhdr *eth = eth_hdr(skb);
    
    printk(KERN_INFO "=== Packet Info ===\n");
    printk(KERN_INFO "MAC: %pM -> %pM\n", eth->h_source, eth->h_dest);
    
    switch (skb->protocol) {
    case htons(ETH_P_IP):
        ip4 = ip_hdr(skb);
        printk(KERN_INFO "IPv4: %pI4:%d -> %pI4:%d\n",
               &ip4->saddr, ntohs(tcp_hdr(skb)->source),
               &ip4->daddr, ntohs(tcp_hdr(skb)->dest));
        break;
        
    case htons(ETH_P_IPV6):
        ip6 = ipv6_hdr(skb);
        printk(KERN_INFO "IPv6: %pI6c -> %pI6c\n",
               &ip6->saddr, &ip6->daddr);
        break;
    }
}

// 注册到netfilter或驱动中使用

十、总结

需求 推荐格式符 示例
IPv4地址快速打印 %pI4 printk("%pI4", &addr)
IPv6地址打印(首选) %pI6c printk("%pI6c", &addr6)
MAC地址打印 %pM printk("%pM", mac)
带端口的地址打印 %pIS printk("%pIS", &sockaddr)

核心要点

  1. 所有格式符都必须传递指针,不能直接传值
  2. 地址数据应保持内核原有的字节序(IPv4为网络字节序)
  3. IPv6优先使用 %pI6c(压缩格式)
  4. %pIS 系列可直接打印 sockaddr 结构,最为方便

这些扩展格式说明符是Linux内核网络调试中最常用的工具,比手动调用 in4_ntop/in6_ntop 要简洁得多。

相关推荐
十年编程老舅3 小时前
窥探内核心脏:深入解析 proc 虚拟文件系统
linux·服务器·数据库·c++·linux内核·文件系统·读写锁
江公望3 小时前
Linux kernel completion(完成量)10分钟讲清楚
linux
Sakuyu434683 小时前
sed和awk
linux
码农多耕地呗4 小时前
VMware创建虚拟机
linux·运维·服务器
wggmrlee4 小时前
性能压测-单机
linux
youyudexiaowangzi4 小时前
ubuntu 1604安装组件报错
linux·运维·ubuntu
muls14 小时前
java面试宝典
java·linux·服务器·网络·算法·操作系统
Eric.Lee20214 小时前
python实现pdf转图片png
linux·python·pdf
剑锋所指,所向披靡!4 小时前
linux的目录结构
linux·运维·服务器