Skip to content

IPv6 NDisc 邻居发现

1. 模块架构

1.1 功能概述

NDisc (Neighbor Discovery Protocol) 是 IPv6 的核心协议,实现了地址解析、路由器发现、重复地址检测等功能。

1.2 关键源文件

文件作用
net/ipv6/ndisc.cNDisc 实现 (约 2000 行)
include/net/ndisc.hNDisc 定义
include/net/addrconf.h地址配置

2. NDisc 消息类型

2.1 ICMPv6 类型

类型名称用途
133Router Solicitation (RS)主机请求路由器配置
134Router Advertisement (RA)路由器通告前缀/路由
135Neighbor Solicitation (NS)地址解析 + DAD
136Neighbor Advertisement (NA)地址解析回复 + DAD
137Redirect更好的第一跳通知

2.2 NDisc 消息结构

c
// include/net/ndisc.h
struct nd_msg {
    struct icmp6hdr icmph;
    struct in6_addr target;    // NS/NA 的目标地址
    __u8        opt[];       // 选项
};

struct ra_msg {
    struct icmp6hdr icmph;
    __be32        reachable_time;  // 可达时间
    __be32        retrans_timer;   // 重传定时器
};

struct rs_msg {
    struct icmp6hdr icmph;
    __u8        opt[];
};

2.3 NDisc 选项

c
// include/net/ndisc.h
struct nd_opt_hdr {
    __u8    nd_opt_type;   // 选项类型
    __u8    nd_opt_len;    // 长度 (8字节单位)
};

#define ND_OPT_SOURCE_LL_ADDR      1   // 源链路层地址
#define ND_OPT_TARGET_LL_ADDR     2   // 目标链路层地址
#define ND_OPT_PREFIX_INFO         3   // 前缀信息
#define ND_OPT_MTU                5   // MTU
#define ND_OPT_NONCE              14  // DAD 随机数
#define ND_OPT_ROUTE_INFO         24  // 路由信息
#define ND_OPT_RDNSS              25  // 递归 DNS
#define ND_OPT_DNSSL              31  // DNS 搜索列表

3. 邻居请求 (NS)

3.1 ndisc_send_ns()

c
// net/ipv6/ndisc.c:653
void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,
                  const struct in6_addr *daddr, const struct in6_addr *saddr,
                  u64 nonce)
{
    struct sk_buff *skb;

    // 创建 NS 包
    skb = ndisc_ns_create(dev, solicit, saddr, nonce);
    if (skb)
        ndisc_send_skb(skb, daddr, saddr);
}

// ns 创建
static struct sk_buff *ndisc_ns_create(struct net_device *dev, ...)
{
    struct nd_msg *msg;

    skb = alloc_skb(LL_ALLOCATED_SPACE(dev) + sizeof(*msg), GFP_ATOMIC);
    if (!skb) return NULL;

    // 添加 Eth + IPv6 头
    skb_reserve(skb, LL_RESERVED_SPACE(dev));
    eth_hdr(skb)->h_proto = htons(ETH_P_IPV6);

    // 添加 ICMPv6 头
    msg = skb_put(skb, sizeof(*msg));
    msg->icmph.icmp6_type = NDISC_NEIGHBOUR_SOLICITATION;
    msg->target = *target;

    // 添加选项 (源链路层地址)
    if (saddr)
        ndisc_fill_addr_option(skb, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr);

    return skb;
}

3.2 ndisc_recv_ns()

c
// net/ipv6/ndisc.c:787
static void ndisc_recv_ns(struct sk_buff *skb)
{
    struct nd_msg *msg = (struct nd_msg *)skb->data;
    struct inet6_ifaddr *ifp;
    bool dad = ipv6_addr_any(saddr);  // DAD 检测

    // DAD: 目标是试探性地址
    if (dad) {
        // 检查 DAD
        if (ipv6_addr_is_solict_mult(daddr)) {
            // 必须是 solicitation-node 多播
            if (nonce && ifp->dad_nonce == nonce) {
                // 环路检测,忽略
                goto out;
            }
            // 地址冲突
            addrconf_dad_failure(skb, ifp);
        }
    } else {
        // 地址解析:查找邻居
        neigh = __neigh_lookup(&nd_tbl, &msg->target, dev);
        if (neigh) {
            // 更新邻居
            neigh_update(neigh, lladdr);
            // 发送 NA
            ndisc_send_na(dev, saddr, &msg->target, ...);
        }
    }
}

4. 邻居通告 (NA)

4.1 ndisc_send_na()

c
// net/ipv6/ndisc.c:524
void ndisc_send_na(struct net_device *dev, const struct in6_addr *daddr,
                  const struct in6_addr *solicited_addr,
                  bool router, bool solicited, bool override, bool inc_opt)
{
    struct sk_buff *skb;
    struct nd_msg *msg;

    skb = ndisc_na_create(dev, solicited_addr, inc_opt, router,
                         solicited, override);
    if (!skb) return;

    ndisc_send_skb(skb, daddr, solicited_addr ? solicited_addr : dev->dev_addr);
}

static struct sk_buff *ndisc_na_create(struct net_device *dev, ...)
{
    msg->icmph.icmp6_router = router;       // 是否为路由器
    msg->icmph.icmp6_solicited = solicited;  // 是否为 NS 响应
    msg->icmph.icmp6_override = override;    // 是否覆盖缓存
}

4.2 ndisc_recv_na()

c
// net/ipv6/ndisc.c:988
static void ndisc_recv_na(struct sk_buff *skb)
{
    struct nd_msg *msg = (struct nd_msg *)skb->data;
    struct neighbour *neigh;

    // 查找邻居条目
    neigh = __neigh_lookup(&nd_tbl, &msg->target, dev);
    if (!neigh) {
        // 创建新条目
        neigh = neigh_add(&nd_tbl, &msg->target, dev);
    }

    // 更新邻居
    if (solicited) {
        // NS 响应 -> REACHABLE
        neigh->nud_state = NUD_REACHABLE;
        neigh_update(neigh, lladdr);
    } else {
        // 非请求 NA -> STALE
        neigh->nud_state = NUD_STALE;
    }
}

5. 路由器发现

5.1 RS 发送

c
// net/ipv6/ndisc.c:550
void ndisc_send_rs(struct net_device *dev, const struct in6_addr *saddr,
                  const struct in6_addr *daddr)
{
    struct sk_buff *skb;
    struct rs_msg *msg;

    skb = ndisc_rs_create(dev, saddr, daddr);
    if (!skb) return;

    ndisc_send_skb(skb, daddr, saddr);
}

5.2 RA 处理

c
// net/ipv6/ndisc.c:1232
static void ndisc_router_discovery(struct sk_buff *skb)
{
    struct ra_msg *msg = (struct ra_msg *)skb->data;

    // 1. 验证源地址是链路本地
    if (!ipv6_addr_is_lladdr(&ipv6_hdr(skb)->saddr))
        return;

    // 2. 更新默认路由
    if (msg->icmph.icmp6_router)
        rt6_add_dflt_router(skb->dev, &ipv6_hdr(skb)->saddr);

    // 3. 更新设备标志
    if (msg->icmph.icmp6_flags & ND_RA_FLAG_MANAGED)
        idev->if_flags |= IF_RA_MANAGED;
    if (msg->icmph.icmp6_flags & ND_RA_FLAG_OTHER)
        idev->if_flags |= IF_RA_OTHERCONF;

    // 4. 更新定时器参数
    idev->nd_parms->reachable_time = ntohl(msg->reachable_time);
    idev->nd_parms->retrans_time = ntohl(msg->retrans_timer);

    // 5. 处理前缀选项
    for (each option) {
        if (option->nd_opt_type == ND_OPT_PREFIX_INFORMATION)
            addrconf_prefix_rcv(dev, option);
    }
}

6. Solicited-Node 多播地址

6.1 计算公式

ff02::1:ffXX:XXXX
  ││      │ └────┘└────┘
  ││      │    │      └─ 后 24 位 = 目标地址的后 24 位
  ││      └─── 固定: ffXX:XXXX
  │└──────────── 固定前缀
  └────────────── 链路本地多播组 (ff02::1)

6.2 生成函数

c
// include/net/addrconf.h:484
static inline void addrconf_addr_solict_mult(const struct in6_addr *addr,
                                            struct in6_addr *solicited)
{
    ipv6_addr_set(solicited,
              htonl(0xFF020000), 0,
              htonl(0x1),
              htonl(0xFF000000) | addr->s6_addr32[3]);
}

7. 安全性

7.1 Hop Limit 验证

所有 NDisc 消息必须在本地链路发送,Hop Limit 必须为 255:

c
// ndisc.c:1804
int ndisc_rcv(struct sk_buff *skb)
{
    // 1. 验证 hop limit = 255
    if (ipv6_hdr(skb)->hop_limit != 255)
        return;

    // 2. 验证 ICMPv6 code = 0
    if (msg->icmph.icmp6_code != 0)
        return;

    // 3. 分发到处理函数
    switch (msg->icmph.icmp6_type) {
    case NDISC_NEIGHBOUR_SOLICITATION:
        ndisc_recv_ns(skb);
        break;
    // ...
    }
}

7.2 DAD 随机数 (RFC 7527)

c
// 用于防止多接口场景下的 DAD 误判
// 随机数在 NS 中携带
// 收到匹配的 NA 时比较随机数

8. 与 IPv4 ARP 对比

特性IPv6 NDiscIPv4 ARP
协议ICMPv6 (IP 协议 58)独立 Ethernet 类型
消息RS/RA/NS/NA/RedirectARP Request/Reply
多播组Solicited-node广播 (ff:ff:ff:ff:ff:ff)
DAD内置 NS/NA 交换
路由器发现RS/RAProxy ARP 或 DHCP
扩展性TLV 选项固定格式
安全SEND (RFC 3971)无标准安全

基于 VitePress 构建