Linux 内核中与网络地址相关的函数

Linux内核中网络地址相关的工具函数

Linux内核提供了丰富的网络地址处理函数,主要用于协议栈内部和内核模块开发。以下是内核中可用的网络地址工具函数分类介绍。


一、地址转换函数

1.1 字符串与二进制转换(核心函数)

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

// ========== IPv4地址转换 ==========
// 字符串转二进制(点分十进制 -> 网络字节序32位整数)
int in4_pton(const char *src, int srclen, u8 *dst, int delim, const char **end);
// 返回值:1成功,0失败
// srclen: 字符串长度,0表示自动检测
// delim: 分隔符(通常为'.'),0表示无分隔符

// 二进制转字符串(网络字节序32位整数 -> 点分十进制)
int in4_ntop(__be32 addr, char *buf, int buflen);
// 或使用辅助函数
char *ip4_string(unsigned char *addr, char *buf, int buflen);

// ========== IPv6地址转换 ==========
// 字符串转二进制
int in6_pton(const char *src, int srclen, u8 *dst, int delim, const char **end);

// 二进制转字符串
int in6_ntop(const struct in6_addr *addr, char *buf, int buflen);

实际使用示例:

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

// IPv4地址转换
void convert_ipv4(void)
{
    __be32 addr;
    char buf[INET_ADDRSTRLEN];
    
    // 字符串 -> 二进制
    if (in4_pton("192.168.1.100", -1, (u8 *)&addr, '.', NULL)) {
        printk("Converted to 0x%x\n", addr);
    }
    
    // 二进制 -> 字符串
    addr = htonl(0xC0A80164);  // 192.168.1.100
    in4_ntop(addr, buf, sizeof(buf));
    printk("IP: %s\n", buf);
}

// IPv6地址转换
void convert_ipv6(void)
{
    struct in6_addr addr;
    char buf[INET6_ADDRSTRLEN];
    
    // 字符串 -> 二进制
    if (in6_pton("2001:db8::1", -1, addr.s6_addr, ':', NULL)) {
        // 使用地址
    }
    
    // 二进制 -> 字符串
    in6_ntop(&addr, buf, sizeof(buf));
    printk("IPv6: %s\n", buf);
}

二、地址类型判断函数

2.1 IPv4地址类型判断

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

// 判断地址类型
bool ipv4_is_loopback(__be32 addr);      // 环回地址 (127.0.0.0/8)
bool ipv4_is_multicast(__be32 addr);     // 组播地址 (224.0.0.0/4)
bool ipv4_is_local_multicast(__be32 addr); // 本地组播 (224.0.0.0/24)
bool ipv4_is_lbcast(__be32 addr);        // 链路层广播
bool ipv4_is_zeronet(__be32 addr);       // 零网络 (0.0.0.0/8)
bool ipv4_is_private_10(__be32 addr);    // 私有地址 10.0.0.0/8
bool ipv4_is_private_172(__be32 addr);   // 私有地址 172.16.0.0/12
bool ipv4_is_private_192(__be32 addr);   // 私有地址 192.168.0.0/16

// 通用私有地址判断
bool ipv4_is_private(__be32 addr);

实际使用示例:

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

void check_address_type(__be32 addr)
{
    if (ipv4_is_multicast(addr)) {
        printk("Multicast address\n");
    } else if (ipv4_is_loopback(addr)) {
        printk("Loopback address\n");
    } else if (ipv4_is_private(addr)) {
        printk("Private address\n");
    } else if (ipv4_is_lbcast(addr)) {
        printk("Limited broadcast\n");
    }
}

2.2 IPv6地址类型判断(内核核心函数)

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

// IPv6地址类型判断核心函数
int __ipv6_addr_type(const struct in6_addr *addr);

// 返回的地址类型标志(定义在 include/net/ipv6.h)
// IPV6_ADDR_ANY            - 未指定地址 (::)
// IPV6_ADDR_LOOPBACK       - 环回地址 (::1)
// IPV6_ADDR_MULTICAST      - 组播地址
// IPV6_ADDR_LINKLOCAL      - 链路本地地址 (fe80::/10)
// IPV6_ADDR_SITELOCAL      - 站点本地地址 (fec0::/10,已弃用)
// IPV6_ADDR_UNICAST        - 单播地址
// IPV6_ADDR_MAPPED         - IPv4映射地址 (::ffff:0:0/96)
// IPV6_ADDR_COMPATv4       - 兼容IPv4地址 (::/96)

// 便捷判断宏
#define ipv6_addr_type(addr) __ipv6_addr_type(addr)
#define ipv6_addr_any(addr) \
    (ipv6_addr_type(addr) == IPV6_ADDR_ANY)
#define ipv6_addr_loopback(addr) \
    (ipv6_addr_type(addr) == IPV6_ADDR_LOOPBACK)
#define ipv6_addr_multicast(addr) \
    (!!(ipv6_addr_type(addr) & IPV6_ADDR_MULTICAST))
#define ipv6_addr_linklocal(addr) \
    (!!(ipv6_addr_type(addr) & IPV6_ADDR_LINKLOCAL))

实际使用示例:

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

void check_ipv6_address_type(struct in6_addr *addr)
{
    int type = ipv6_addr_type(addr);
    
    if (type & IPV6_ADDR_MULTICAST) {
        printk("IPv6 multicast address\n");
        if (type & IPV6_ADDR_LINKLOCAL) {
            printk("  Link-local scope\n");
        }
    } else if (type & IPV6_ADDR_LOOPBACK) {
        printk("IPv6 loopback address\n");
    } else if (type & IPV6_ADDR_LINKLOCAL) {
        printk("IPv6 link-local address\n");
    } else if (type & IPV6_ADDR_MAPPED) {
        printk("IPv4-mapped IPv6 address\n");
    }
}

三、地址操作函数

3.1 地址比较函数

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

// IPv4地址比较
static inline bool ipv4_addr_equal(__be32 a1, __be32 a2)
{
    return a1 == a2;
}

// IPv6地址比较(高效实现)
static inline bool ipv6_addr_equal(const struct in6_addr *a1,
                                    const struct in6_addr *a2)
{
    return (a1->s6_addr32[0] == a2->s6_addr32[0] &&
            a1->s6_addr32[1] == a2->s6_addr32[1] &&
            a1->s6_addr32[2] == a2->s6_addr32[2] &&
            a1->s6_addr32[3] == a2->s6_addr32[3]);
}

// 带掩码的地址比较
bool ipv6_addr_equal_with_mask(const struct in6_addr *a1,
                                const struct in6_addr *a2,
                                unsigned int prefix_len);

3.2 地址复制和初始化

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

// IPv6地址复制
static inline void ipv6_addr_copy(struct in6_addr *dst,
                                   const struct in6_addr *src)
{
    dst->s6_addr32[0] = src->s6_addr32[0];
    dst->s6_addr32[1] = src->s6_addr32[1];
    dst->s6_addr32[2] = src->s6_addr32[2];
    dst->s6_addr32[3] = src->s6_addr32[3];
}

// IPv6地址设置为任意地址(::)
static inline void ipv6_addr_set_any(struct in6_addr *addr)
{
    addr->s6_addr32[0] = 0;
    addr->s6_addr32[1] = 0;
    addr->s6_addr32[2] = 0;
    addr->s6_addr32[3] = 0;
}

// IPv6地址设置为环回地址(::1)
static inline void ipv6_addr_set_loopback(struct in6_addr *addr)
{
    addr->s6_addr32[0] = 0;
    addr->s6_addr32[1] = 0;
    addr->s6_addr32[2] = 0;
    addr->s6_addr32[3] = htonl(1);
}

3.3 地址作用域判断

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

// IPv6地址作用域
// IPV6_ADDR_SCOPE_NODELOCAL   - 节点本地
// IPV6_ADDR_SCOPE_LINKLOCAL   - 链路本地
// IPV6_ADDR_SCOPE_SITELOCAL   - 站点本地
// IPV6_ADDR_SCOPE_GLOBAL      - 全局

int ipv6_addr_scope(const struct in6_addr *addr);

四、网络字节序与主机字节序转换

4.1 基本字节序函数

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

// 主机字节序 -> 网络字节序
__be16 htons(uint16_t host);
__be32 htonl(uint32_t host);
__be64 htonll(uint64_t host);  // 64位版本

// 网络字节序 -> 主机字节序
uint16_t ntohs(__be16 net);
uint32_t ntohl(__be32 net);
uint64_t ntohll(__be64 net);

// 通用宏(根据CPU架构自动优化)
#define cpu_to_be16(x) htons(x)
#define cpu_to_be32(x) htonl(x)
#define be16_to_cpu(x) ntohs(x)
#define be32_to_cpu(x) ntohl(x)

4.2 地址常量定义

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

// IPv4特殊地址常量
#define INADDR_ANY        0x00000000  // 0.0.0.0
#define INADDR_LOOPBACK   0x7f000001  // 127.0.0.1
#define INADDR_BROADCAST  0xffffffff  // 255.255.255.255
#define INADDR_NONE       0xffffffff  // 无效地址

// IPv6特殊地址(定义在 net/ipv6.h)
extern const struct in6_addr in6addr_any;       // ::
extern const struct in6_addr in6addr_loopback;  // ::1

五、地址前缀和掩码操作

5.1 IPv4掩码函数

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

// 根据前缀长度生成掩码
static inline __be32 ipv4_prefix_mask(unsigned int prefix_len)
{
    if (prefix_len == 0)
        return 0;
    if (prefix_len >= 32)
        return htonl(0xFFFFFFFF);
    
    return htonl(0xFFFFFFFF << (32 - prefix_len));
}

// 获取网络地址(地址 & 掩码)
static inline __be32 ipv4_net_addr(__be32 addr, __be32 mask)
{
    return addr & mask;
}

5.2 IPv6前缀操作

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

// IPv6前缀匹配
static inline bool ipv6_prefix_equal(const struct in6_addr *a1,
                                      const struct in6_addr *a2,
                                      unsigned int prefix_len)
{
    // 按32位字比较
    if (prefix_len >= 32) {
        if (a1->s6_addr32[0] != a2->s6_addr32[0])
            return false;
        prefix_len -= 32;
    } else {
        __be32 mask = htonl(~((1 << (32 - prefix_len)) - 1));
        if ((a1->s6_addr32[0] & mask) != (a2->s6_addr32[0] & mask))
            return false;
        return true;
    }
    
    // 继续比较剩余部分...
}

六、实用地址处理函数

6.1 获取地址族字符串

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

const char *sockaddr_family_name(int family)
{
    switch (family) {
    case AF_INET:
        return "AF_INET";
    case AF_INET6:
        return "AF_INET6";
    case AF_UNIX:
        return "AF_UNIX";
    default:
        return "UNKNOWN";
    }
}

6.2 地址结构初始化

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

// 初始化IPv4地址结构
static inline void init_ipv4_addr(struct sockaddr_in *sin, 
                                   __be32 addr, 
                                   __be16 port)
{
    sin->sin_family = AF_INET;
    sin->sin_addr.s_addr = addr;
    sin->sin_port = port;
}

// 初始化IPv6地址结构
static inline void init_ipv6_addr(struct sockaddr_in6 *sin6,
                                   const struct in6_addr *addr,
                                   __be16 port)
{
    sin6->sin6_family = AF_INET6;
    sin6->sin6_addr = *addr;
    sin6->sin6_port = port;
    sin6->sin6_flowinfo = 0;
    sin6->sin6_scope_id = 0;
}

七、完整的实际使用示例

7.1 网络驱动中的地址处理

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

struct my_netdev_stats {
    u64 rx_bytes;
    u64 rx_packets;
    __be32 local_ip;
    struct in6_addr local_ipv6;
};

// 处理接收到的数据包,提取源地址
void rx_packet_handler(struct sk_buff *skb, struct my_netdev_stats *stats)
{
    struct iphdr *ipv4_hdr;
    struct ipv6hdr *ipv6_hdr;
    
    if (ip_hdr(skb)->version == 4) {
        ipv4_hdr = ip_hdr(skb);
        // 检查是否是发往本机的包
        if (ipv4_addr_equal(ipv4_hdr->daddr, stats->local_ip)) {
            stats->rx_packets++;
            stats->rx_bytes += skb->len;
        }
    } else if (ip_hdr(skb)->version == 6) {
        ipv6_hdr = ipv6_hdr(skb);
        // 检查IPv6地址类型
        if (ipv6_addr_type(&ipv6_hdr->daddr) & IPV6_ADDR_LINKLOCAL) {
            // 处理链路本地地址
            stats->rx_packets++;
            stats->rx_bytes += skb->len;
        }
    }
}

7.2 地址配置管理

c 复制代码
// 配置网络接口的IP地址
int configure_ip_address(struct net_device *dev, const char *ip_str)
{
    struct in_device *in_dev;
    struct in_ifaddr *ifa;
    __be32 addr;
    
    // 转换字符串地址
    if (!in4_pton(ip_str, -1, (u8 *)&addr, '.', NULL)) {
        printk(KERN_ERR "Invalid IP address: %s\n", ip_str);
        return -EINVAL;
    }
    
    // 获取接口的in_device结构
    in_dev = in_dev_get(dev);
    if (!in_dev)
        return -ENODEV;
    
    // 检查是否是私有地址
    if (ipv4_is_private(addr)) {
        printk(KERN_INFO "Configuring private address\n");
    } else if (ipv4_is_multicast(addr)) {
        printk(KERN_WARNING "Cannot configure multicast address\n");
        in_dev_put(in_dev);
        return -EINVAL;
    }
    
    // 添加地址到接口(简化示例)
    ifa = inet_alloc_ifa();
    if (!ifa) {
        in_dev_put(in_dev);
        return -ENOMEM;
    }
    
    ifa->ifa_address = addr;
    ifa->ifa_mask = ipv4_prefix_mask(24);  // 255.255.255.0
    ifa->ifa_local = addr;
    ifa->ifa_dev = in_dev;
    
    inet_insert_ifa(ifa);
    in_dev_put(in_dev);
    
    return 0;
}

7.3 地址打印调试

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

void print_packet_address(struct sk_buff *skb)
{
    struct iphdr *ip4;
    struct ipv6hdr *ip6;
    char src_str[INET6_ADDRSTRLEN];
    char dst_str[INET6_ADDRSTRLEN];
    
    switch (skb->protocol) {
    case htons(ETH_P_IP):
        ip4 = ip_hdr(skb);
        in4_ntop(ip4->saddr, src_str, sizeof(src_str));
        in4_ntop(ip4->daddr, dst_str, sizeof(dst_str));
        printk("IPv4: %s -> %s\n", src_str, dst_str);
        break;
        
    case htons(ETH_P_IPV6):
        ip6 = ipv6_hdr(skb);
        in6_ntop(&ip6->saddr, src_str, sizeof(src_str));
        in6_ntop(&ip6->daddr, dst_str, sizeof(dst_str));
        
        // 同时打印地址类型
        if (ipv6_addr_type(&ip6->saddr) & IPV6_ADDR_LINKLOCAL) {
            printk("IPv6 (link-local): %s -> %s\n", src_str, dst_str);
        } else {
            printk("IPv6: %s -> %s\n", src_str, dst_str);
        }
        break;
    }
}

八、相关头文件总结

头文件 主要内容
linux/inet.h in4_ptonin4_ntopin6_ptonin6_ntop
linux/ip.h IPv4地址判断函数、掩码操作
net/ipv6.h IPv6地址类型判断、比较、复制函数
linux/byteorder/generic.h 字节序转换函数
linux/in.h IPv4地址常量、struct in_addr
linux/socket.h 地址族定义、struct sockaddr

九、注意事项

  1. 字节序 :内核中网络地址通常使用网络字节序(__be32),转换时注意使用 htonl/ntohl

  2. 线程安全 :与用户态 inet_ntoa 不同,内核的 in4_ntop/in6_ntop 需要调用者提供缓冲区,因此是线程安全的

  3. 性能考虑:在数据平面(如软中断)中,避免频繁调用地址转换函数,尽量使用二进制形式

  4. IPv6作用域 :处理IPv6链路本地地址时,需要正确设置 sin6_scope_id

  5. RCU保护:访问接口地址列表时,需要使用RCU读锁保护

以上函数是Linux内核网络协议栈的核心工具函数,可用于内核模块开发、网络驱动实现、防火墙规则处理等场景。

相关推荐
steins_甲乙4 小时前
# 从 0 做一个小型内存泄漏检测器:开篇与架构设计
linux
蒸蒸yyyyzwd5 小时前
后端学习笔记 day4
linux·笔记·学习
upp5 小时前
[最新版本centos 10系统制作与安装]
linux·运维·centos
ShineWinsu6 小时前
对于Linux:进程优先级、进程切换以及进程调度的解析
linux·面试·笔试·进程·进程切换·进程调度·进程优先级
Kira Skyler7 小时前
kprobe函数入口时的汇编跳板执行流程与栈帧机制
linux·汇编
桌面运维家7 小时前
VHD/VHDX 数据守护:BAT位图校验与修复
linux·服务器·网络
pupudawang7 小时前
Linux下安装Nginx服务及systemctl方式管理nginx详情
linux·运维·nginx
零K沁雪7 小时前
Linux 内核遍历宏介绍
linux·内核
淼淼爱喝水8 小时前
openEuler 下 Ansible 基础命令详解与实操演示2
linux·运维·windows