zenith-stack 0.1.0

Zenith 全协议栈框架:AF_XDP + eBPF + TLS 1.3 + HTTP/1-2-3 + Web + Proxy + WAF,按需导入
Documentation
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __ZENITH_XDP_COMMON_H__
#define __ZENITH_XDP_COMMON_H__

#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>

/* ============ Map 定义 ============ */

/* XSKMAP: 队列 ID → XSK FD 映射(AF_XDP 重定向目标) */
struct {
    __uint(type, BPF_MAP_TYPE_XSKMAP);
    __uint(max_entries, 64);          /* 支持最多 64 个队列 */
    __uint(key_size, sizeof(u32));
    __uint(value_size, sizeof(u32));
} xsk_map SEC(".maps");

/* STATS_MAP: per-CPU 统计计数 */
struct {
    __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
    __uint(max_entries, 256);
    __uint(key_size, sizeof(u32));
    __uint(value_size, sizeof(u64));
} stats_map SEC(".maps");

/* CONFIG_MAP: 运行时配置(用户态写入,内核态只读) */
struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 128);
    __uint(key_size, sizeof(u32));
    __uint(value_size, sizeof(u64));
} config_map SEC(".maps");

/* EXPECTATION_MAP: 协议预期配置(用户态写入 80 字节 ExpectationConfig,内核态读取)
 *
 * 独立 Map 的原因:config_map 的 value_size=8(u64),无法容纳 80 字节的
 * ExpectationConfig。此处使用专用 ARRAY Map,max_entries=1,key 固定为 0。
 *
 * ExpectationConfig 字节布局(与 zenith-ebpf/src/maps.rs ExpectationConfig::to_bytes() 严格一致):
 *   [0..8]   allowed_protocols  (u64, native endian, bit N = 协议号 N)
 *   [8]      fragment_policy    (u8, 0=不限制 1=丢弃重叠 2=丢弃所有分片)
 *   [9]      min_ttl            (u8, 0=不检查)
 *   [10..12] _pad
 *   [12..14] tcp_port_count     (u16, native endian, 0=不检查 TCP 端口)
 *   [14..46] tcp_ports          (16 × u16, native endian, TCP 端口白名单)
 *   [46..48] udp_port_count     (u16, native endian, 0=不检查 UDP 端口)
 *   [48..80] udp_ports          (16 × u16, native endian, UDP 端口白名单)
 */
struct {
    __uint(type, BPF_MAP_TYPE_ARRAY);
    __uint(max_entries, 1);
    __uint(key_size, sizeof(u32));
    __uint(value_size, 80);  /* ExpectationConfig SIZE = 80 bytes */
} expectation_map SEC(".maps");

/* ============ 统计项 ID ============ */
#define STATS_RX_PACKETS        0   /* 收到数据包总数 */
#define STATS_RX_VALID          1   /* 有效数据包数 */
#define STATS_RX_DROP_SHORT     2   /* 因包过短丢弃 */
#define STATS_RX_DROP_BAD_ETH  3   /* 以太网头异常 */
#define STATS_RX_DROP_BAD_IP   4   /* IP头异常 */
#define STATS_RX_DROP_NO_XSK   5   /* 无对应 XSK 映射 */
#define STATS_REDIRECTED        6   /* 成功重定向到 XSK 的包数 */
#define STATS_RX_DROP_PROTO     7   /* 协议不在白名单 */

/* ============ 配置键 ID ============ */
#define CONFIG_KEY_MTU            0   /* MTU 阈值 */
#define CONFIG_KEY_PROTO_WHITELIST 1   /* 协议白名单位图 */
#define CONFIG_KEY_FAIL_CLOSED    2   /* Fail-Closed 模式(0=关, 1=开) */
#define CONFIG_KEY_WHITELIST_ENABLED 3   /* 白名单显式启用(0=默认回退, 1=用户态完全控制) */
#define CONFIG_KEY_EXPECTATION     10  /* 协议预期配置(端口白名单/分片策略/TTL) */

/* vmlinux.h 未定义 IPPROTO_ICMPV6,在此补齐(linux/in6.h: 58) */
#ifndef IPPROTO_ICMPV6
#define IPPROTO_ICMPV6 58
#endif

/*
 * 默认协议白名单位图:bit N = 协议号
 * 放行 TCP(6)、UDP(17)、ICMP(1)、ICMPv6(58)
 * 注:协议号 58 超出 32 位范围,位图必须使用 64 位(CONFIG_MAP value 为 u64)
 */
#define DEFAULT_PROTO_WHITELIST \
    ((1ULL << IPPROTO_TCP) | (1ULL << IPPROTO_UDP) | \
     (1ULL << IPPROTO_ICMP) | (1ULL << IPPROTO_ICMPV6))

/* ============ 工具函数 ============ */

/* 更新统计计数(per-CPU,无锁) */
static __always_inline void
update_stats(u32 stat_id, u64 inc)
{
    u64 *val;
    val = bpf_map_lookup_elem(&stats_map, &stat_id);
    if (val)
        __sync_fetch_and_add(val, inc);
}

/* 检查以太网头合法性 */
static __always_inline bool
check_eth_header(struct xdp_md *ctx, void *data, void *data_end)
{
    struct ethhdr *eth;

    /* 最小以太网头长度检查 */
    if ((void *)(data + sizeof(struct ethhdr)) > data_end)
        return false;

    eth = data;

    /* 检查 EtherType(支持 IPv4、IPv6、ARP) */
    switch (eth->h_proto) {
    case bpf_htons(ETH_P_IP):
    case bpf_htons(ETH_P_IPV6):
    case bpf_htons(ETH_P_ARP):
    case bpf_htons(ETH_P_VLAN):
        return true;
    default:
        return false;
    }
}

/* 检查 IPv4 头合法性(l3_hdr 指向 IP 头起始,可含 VLAN 偏移) */
static __always_inline bool
check_ipv4_header(void *l3_hdr, void *data_end)
{
    struct iphdr *iph = l3_hdr;

    /* IPv4 头最小长度检查 */
    if ((void *)(iph + 1) > data_end)
        return false;

    /* 版本检查(必须是 IPv4) */
    if (iph->version != 4)
        return false;

    /* 检查 ihl(Internet Header Length,以 4 字节为单位) */
    if (iph->ihl < 5)
        return false;

    /* 检查头长度是否超出数据包 */
    u32 ip_len = iph->ihl * 4;
    if ((void *)((u8 *)iph + ip_len) > data_end)
        return false;

    /* 禁止 IP 广播作为目的地址(DDoS 防护基础) */
    u32 daddr = iph->daddr;
    if (daddr == 0xFFFFFFFF)
        return false;

    return true;
}

/* 检查 IPv6 头合法性(l3_hdr 指向 IPv6 头起始,可含 VLAN 偏移) */
static __always_inline bool
check_ipv6_header(void *l3_hdr, void *data_end)
{
    struct ipv6hdr *ip6h = l3_hdr;

    /* IPv6 头最小长度检查 */
    if ((void *)(ip6h + 1) > data_end)
        return false;

    /* 版本检查(必须是 IPv6) */
    if (ip6h->version != 6)
        return false;

    return true;
}

/*
 * 判断 IPv6 next header 是否为扩展头(NET-011)。
 *
 * 内核态最小导流原则:不解析扩展头链(含可变长位字段,BPF verifier 不友好)。
 * 当 next header 命中下列扩展头编号时,真实 L4 协议号在链的更深处,
 * 据此做白名单/端口校验会错位,故调用方应直接 PASS 交用户态做完整解析。
 * 编号依据 RFC 8200 §4(IPv6 扩展头取值,IANA IPv6 Next Header 表)。
 */
static __always_inline bool
is_ipv6_extension_header(u8 nexthdr)
{
    switch (nexthdr) {
    case 0:    /* Hop-by-Hop Options */
    case 43:   /* Routing */
    case 44:   /* Fragment */
    case 50:   /* ESP */
    case 51:   /* AH */
    case 60:   /* Destination Options */
        return true;
    default:
        return false;
    }
}

/* 从配置 Map 读取 u64 值(未找到返回 0) */
static __always_inline u64
get_config_u64(u32 key)
{
    u64 *val;
    val = bpf_map_lookup_elem(&config_map, &key);
    if (val)
        return *val;
    return 0;
}

/* 从配置 Map 读取 u32 值(底层存储为 u64,未找到返回 0) */
static __always_inline u32
get_config_u32(u32 key)
{
    return (u32)get_config_u64(key);
}

/*
 * 协议白名单检查(CONFIG_MAP 位图,bit N = 协议号)
 * - whitelist_enabled=0(默认/未配置):使用默认白名单语义,
 *   bitmap=0 时回退到 DEFAULT_PROTO_WHITELIST(兼容旧行为)。
 * - whitelist_enabled=1(用户态显式启用):用户态完全控制位图,
 *   bitmap=0 即拒绝所有(fail-closed,不再回退默认白名单)。
 * - 协议号 >= 64 超出位图表示范围,一律视为不在白名单。
 */
static __always_inline bool
proto_whitelist_allowed(u8 proto)
{
    u64 bitmap = get_config_u64(CONFIG_KEY_PROTO_WHITELIST);
    u32 whitelist_enabled = get_config_u32(CONFIG_KEY_WHITELIST_ENABLED);

    if (whitelist_enabled == 0 && bitmap == 0)
        bitmap = DEFAULT_PROTO_WHITELIST;

    if (proto >= 64)
        return false;
    return (bitmap & (1ULL << proto)) != 0;
}

#endif /* __ZENITH_XDP_COMMON_H__ */