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_pton、in4_ntop、in6_pton、in6_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 |
九、注意事项
-
字节序 :内核中网络地址通常使用网络字节序(__be32),转换时注意使用
htonl/ntohl -
线程安全 :与用户态
inet_ntoa不同,内核的in4_ntop/in6_ntop需要调用者提供缓冲区,因此是线程安全的 -
性能考虑:在数据平面(如软中断)中,避免频繁调用地址转换函数,尽量使用二进制形式
-
IPv6作用域 :处理IPv6链路本地地址时,需要正确设置
sin6_scope_id -
RCU保护:访问接口地址列表时,需要使用RCU读锁保护
以上函数是Linux内核网络协议栈的核心工具函数,可用于内核模块开发、网络驱动实现、防火墙规则处理等场景。