Android有线IPV6总结(二):内核中RS与RA的一点学习

本文详细介绍了在Android系统中开启IPv6功能时,内核中的RS(Router Solicitation)和RA(Router Advertisement)处理流程。通过分析addrconf_sysctl_disable等函数,揭示了如何触发发送RS报文,以及RA报文接收后如何设置路由和生成全局IPv6地址。

RS:Router Solicitation路由器请求

RA:Router Advertisement路由器公告

 

在Android系统中我们想要打开一个网络接口(比如eth0)的ipv6功能,用命令的话我们有如下两种办法:

1,echo 0 > /proc/sys/net/ipv6/conf/eth0/disable_ipv6

直接读写的内核proc文件

2,ndc interface ipv6 eth0 enable

用ndc命令,最终也是写内核proc文件

 

如上的操作proc文件就是触发网络终端发送RS报文的方式,接下来就记录一下内核中对应的代码流程:

写disable_ipv6文件对应的文件系统函数为:

addrconf_sysctl_disable

static
int addrconf_sysctl_disable(struct ctl_table *ctl, int write,
                            void __user *buffer, size_t *lenp, loff_t *ppos)
{
        int *valp = ctl->data;
        int val = *valp;
        loff_t pos = *ppos;
        struct ctl_table lctl;
        int ret;

        /*
         * ctl->data points to idev->cnf.disable_ipv6, we should
         * not modify it until we get the rtnl lock.
         */
        lctl = *ctl;
        lctl.data = &val;

        ret = proc_dointvec(&lctl, write, buffer, lenp, ppos);

        if (write)
                ret = addrconf_disable_ipv6(ctl, valp, val);
        if (ret)
                *ppos = pos;
        return ret;
}

接下来进入addrconf_disable_ipv6

static int addrconf_disable_ipv6(struct ctl_table *table, int *p, int newf)
{
        struct net *net;
        int old;

        if (!rtnl_trylock())
                return restart_syscall();

        net = (struct net *)table->extra2;
        old = *p;
        *p = newf;
        //如果值不变则不作处理
        if (p == &net->ipv6.devconf_dflt->disable_ipv6) {
                rtnl_unlock();
                return 0;
        }

        if (p == &net->ipv6.devconf_all->disable_ipv6) {
                net->ipv6.devconf_dflt->disable_ipv6 = newf;
                addrconf_disable_change(net, newf);
        } else if ((!newf) ^ (!old)) //如果值发生变化则作处理
                dev_disable_change((struct inet6_dev *)table->extra1);

        rtnl_unlock();
        return 0;
}

接下来进入dev_disable_change

static void dev_disable_change(struct inet6_dev *idev)
{
        struct netdev_notifier_info info;

        if (!idev || !idev->dev)
                return;

        netdev_notifier_info_init(&info, idev->dev);
        if (idev->cnf.disable_ipv6)
                addrconf_notify(NULL, NETDEV_DOWN, &info);  //关闭ipv6
        else
                addrconf_notify(NULL, NETDEV_UP, &info);  //开启ipv6
}

这里只看开启ipv6,所以接下来看addrconf_notify对NETDEV_UP的处理

        case NETDEV_UP:
        case NETDEV_CHANGE:
                if (dev->flags & IFF_SLAVE)
                        break;

                if (idev && idev->cnf.disable_ipv6)
                        break;

                if (event == NETDEV_UP) {  //NETDEV_UP
                        if (!addrconf_qdisc_ok(dev)) {
                                /* device is not ready yet. */
                                pr_info("ADDRCONF(NETDEV_UP): %s: link is not ready\n",
                                        dev->name);
                                break;
                        }

                        if (!idev && dev->mtu >= IPV6_MIN_MTU)
                                idev = ipv6_add_dev(dev);  //将dev加入ipv6协议栈

                        if (!IS_ERR_OR_NULL(idev)) {
                                idev->if_flags |= IF_READY;
                                run_pending = 1;
                        }
                } else {
                        if (!addrconf_qdisc_ok(dev)) {
                                /* device is still not ready. */
                                break;
                        }

                        if (idev) {
                                if (idev->if_flags & IF_READY)
                                        /* device is already configured. */
                                        break;
                                idev->if_flags |= IF_READY;
                        }

                        pr_info("ADDRCONF(NETDEV_CHANGE): %s: link becomes ready\n",
                                dev->name);

                        run_pending = 1;
                }
                switch (dev->type) {
#if IS_ENABLED(CONFIG_IPV6_SIT)
                case ARPHRD_SIT:
                        addrconf_sit_config(dev);
                        break;
#endif
#if IS_ENABLED(CONFIG_NET_IPGRE)
                case ARPHRD_IPGRE:
                        addrconf_gre_config(dev);
                        break;
#endif
                case ARPHRD_LOOPBACK:
                        init_loopback(dev);
                        break;

                default:
                        addrconf_dev_config(dev);  //生成本地链路地址
                        break;
                }

                if (!IS_ERR_OR_NULL(idev)) {
                        if (run_pending)
                                addrconf_dad_run(idev); //准备跑DAD检测本地链路地址唯一性
                        /*
                         * If the MTU changed during the interface down,
                         * when the interface up, the changed MTU must be
                         * reflected in the idev as well as routers.
                         */
                        if (idev->cnf.mtu6 != dev->mtu &&
                            dev->mtu >= IPV6_MIN_MTU) {
                                rt6_mtu_change(dev, dev->mtu);
                                idev->cnf.mtu6 = dev->mtu;
                        }
                        idev->tstamp = jiffies;
                        inet6_ifinfo_notify(RTM_NEWLINK, idev);  //netlink通信告知上层ipv6 link建立成功

                        /*
                         * If the changed mtu during down is lower than
                         * IPV6_MIN_MTU stop IPv6 on this interface.
                         */
                        if (dev->mtu < IPV6_MIN_MTU)
                                addrconf_ifdown(dev, 1);
                }
                break;

对于NETDEV_UP的处理主要有三点:

1,ipv6_add_dev将网络dev加入ipv6协议栈中

2,addrconf_dev_config生成本地链路地址

3,addrconf_dad_run跑DAD检测链路地址唯一性和发送RS报文

这里只看后续发送RS报文的地方

static void addrconf_dad_work(struct work_struct *w)
{
        struct inet6_ifaddr *ifp = container_of(to_delayed_work(w),
                                                struct inet6_ifaddr,
                                                dad_work);
        struct inet6_dev *idev = ifp->idev;
        struct in6_addr mcaddr;

        enum {
                DAD_PROCESS,
                DAD_BEGIN,
                DAD_ABORT,
        } action = DAD_PROCESS;

        rtnl_lock();

        spin_lock_bh(&ifp->state_lock);
        if (ifp->state == INET6_IFADDR_STATE_PREDAD) {
                action = DAD_BEGIN;
                ifp->state = INET6_IFADDR_STATE_DAD;
        } else if (ifp->state == INET6_IFADDR_STATE_ERRDAD) {
                action = DAD_ABORT;
                ifp->state = INET6_IFADDR_STATE_POSTDAD;
        }
        spin_unlock_bh(&ifp->state_lock);

        if (action == DAD_BEGIN) {
                addrconf_dad_begin(ifp);
                goto out;
        } else if (action == DAD_ABORT) {
                addrconf_dad_stop(ifp, 1);
                goto out;
        }

        if (!ifp->dad_probes && addrconf_dad_end(ifp))
                goto out;

        write_lock_bh(&idev->lock);
        if (idev->dead || !(idev->if_flags & IF_READY)) {
                write_unlock_bh(&idev->lock);
                goto out;
        }

        spin_lock(&ifp->lock);
        if (ifp->state == INET6_IFADDR_STATE_DEAD) {
                spin_unlock(&ifp->lock);
                write_unlock_bh(&idev->lock);
                goto out;
        }

        if (ifp->dad_probes == 0) {
                /*
                 * DAD was successful
                 */

                ifp->flags &= ~(IFA_F_TENTATIVE|IFA_F_OPTIMISTIC|IFA_F_DADFAILED);
                spin_unlock(&ifp->lock);
                write_unlock_bh(&idev->lock);

                addrconf_dad_completed(ifp);    //DAD完成就会开始发送RS了

                goto out;
        }

        ifp->dad_probes--;
        addrconf_mod_dad_work(ifp,
                              NEIGH_VAR(ifp->idev->nd_parms, RETRANS_TIME));
        spin_unlock(&ifp->lock);
        write_unlock_bh(&idev->lock);

        /* send a neighbour solicitation for our addr */
        addrconf_addr_solict_mult(&ifp->addr, &mcaddr);
        ndisc_send_ns(ifp->idev->dev, NULL, &ifp->addr, &mcaddr, &in6addr_any);
out:
        in6_ifa_put(ifp);
        rtnl_unlock();
}

接下来进入addrconf_dad_completed

static void addrconf_dad_completed(struct inet6_ifaddr *ifp)
{
        struct net_device *dev = ifp->idev->dev;
        struct in6_addr lladdr;
        bool send_rs, send_mld;

        addrconf_del_dad_work(ifp);

        /*
         *      Configure the address for reception. Now it is valid.
         */

        ipv6_ifa_notify(RTM_NEWADDR, ifp);  //通过Netlink通信告知上层,生成本地链路地址成功

        /* If added prefix is link local and we are prepared to process
           router advertisements, start sending router solicitations.
         */

        read_lock_bh(&ifp->idev->lock);
        send_mld = ifp->scope == IFA_LINK && ipv6_lonely_lladdr(ifp);
        send_rs = send_mld &&
                  ipv6_accept_ra(ifp->idev) &&
                  ifp->idev->cnf.rtr_solicits > 0 &&
                  (dev->flags&IFF_LOOPBACK) == 0;
        read_unlock_bh(&ifp->idev->lock);

        /* While dad is in progress mld report's source address is in6_addrany.
         * Resend with proper ll now.
         */
        if (send_mld)
                ipv6_mc_dad_complete(ifp->idev);

        if (send_rs) {    
                /*
                 *      If a host as already performed a random delay
                 *      [...] as part of DAD [...] there is no need
                 *      to delay again before sending the first RS
                 */
                if (ipv6_get_lladdr(dev, &lladdr, IFA_F_TENTATIVE))
                        return;
                ndisc_send_rs(dev, &lladdr, &in6addr_linklocal_allrouters); //发送RS

                write_lock_bh(&ifp->idev->lock);
                spin_lock(&ifp->lock);
                ifp->idev->rs_probes = 1;
                ifp->idev->if_flags |= IF_RS_SENT;
                addrconf_mod_rs_timer(ifp->idev,
                                      ifp->idev->cnf.rtr_solicit_interval);
                spin_unlock(&ifp->lock);
                write_unlock_bh(&ifp->idev->lock);
        }
}

RS请求属于邻居发现协议(Neighbor Discovery Protocol,NDP)中的一种,NDP包括路由器请求(RS),路由器通告(RA),邻居请求(NS),邻居通告(NA),重定向(Redirect),都是使用ipv6的icmp协议来通信的

接下来进入ndisc_send_rs

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;
        int send_sllao = dev->addr_len;
        int optlen = 0;

#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
        /*
         * According to section 2.2 of RFC 4429, we must not
         * send router solicitations with a sllao from
         * optimistic addresses, but we may send the solicitation
         * if we don't include the sllao.  So here we check
         * if our address is optimistic, and if so, we
         * suppress the inclusion of the sllao.
         */
        if (send_sllao) {
                struct inet6_ifaddr *ifp = ipv6_get_ifaddr(dev_net(dev), saddr,
                                                           dev, 1);
                if (ifp) {
                        if (ifp->flags & IFA_F_OPTIMISTIC)  {
                                send_sllao = 0;
                        }
                        in6_ifa_put(ifp);
                } else {
                        send_sllao = 0;
                }
        }
#endif
        if (send_sllao)
                optlen += ndisc_opt_addr_space(dev);

        skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
        if (!skb)
                return;

        msg = (struct rs_msg *)skb_put(skb, sizeof(*msg));
        *msg = (struct rs_msg) {
                .icmph = {
                        .icmp6_type = NDISC_ROUTER_SOLICITATION,
                },
        };

        if (send_sllao)
                ndisc_fill_addr_option(skb, ND_OPT_SOURCE_LL_ADDR,
                                       dev->dev_addr);

        ndisc_send_skb(skb, daddr, saddr);
}

这里通过ndisc_send_skb函数就可以将RS请求封装成icmp协议报文发送出去了

 

关于RA报文接收流程,RA报文对于网络终端意义很大,收到RA报文之后会设置路由

既然RA也属于NDP,那么也是icmp协议来首先收包

static int icmpv6_rcv(struct sk_buff *skb)
{
        struct net_device *dev = skb->dev;
        struct inet6_dev *idev = __in6_dev_get(dev);
        const struct in6_addr *saddr, *daddr;
        struct icmp6hdr *hdr;
        u8 type;

        if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                struct sec_path *sp = skb_sec_path(skb);
                int nh;

                if (!(sp && sp->xvec[sp->len - 1]->props.flags &
                                 XFRM_STATE_ICMP))
                        goto drop_no_count;

                if (!pskb_may_pull(skb, sizeof(*hdr) + sizeof(struct ipv6hdr)))
                        goto drop_no_count;

                nh = skb_network_offset(skb);
                skb_set_network_header(skb, sizeof(*hdr));

                if (!xfrm6_policy_check_reverse(NULL, XFRM_POLICY_IN, skb))
                        goto drop_no_count;

                skb_set_network_header(skb, nh);
        }

        ICMP6_INC_STATS_BH(dev_net(dev), idev, ICMP6_MIB_INMSGS);

        saddr = &ipv6_hdr(skb)->saddr;
        daddr = &ipv6_hdr(skb)->daddr;

        if (skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo)) {
                LIMIT_NETDEBUG(KERN_DEBUG
                               "ICMPv6 checksum failed [%pI6c > %pI6c]\n",
                               saddr, daddr);
                goto csum_error;
        }

        if (!pskb_pull(skb, sizeof(*hdr)))
                goto discard_it;

        hdr = icmp6_hdr(skb);

        type = hdr->icmp6_type;

        ICMP6MSGIN_INC_STATS_BH(dev_net(dev), idev, type);

        switch (type) {
        case ICMPV6_ECHO_REQUEST:
                icmpv6_echo_reply(skb);
                break;

        case ICMPV6_ECHO_REPLY:
                ping_rcv(skb);
                break;

        case ICMPV6_PKT_TOOBIG:
                /* BUGGG_FUTURE: if packet contains rthdr, we cannot update
                   standard destination cache. Seems, only "advanced"
                   destination cache will allow to solve this problem
                   --ANK (980726)
                 */
                if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
                        goto discard_it;
                hdr = icmp6_hdr(skb);

                /*
                 *      Drop through to notify
                 */

        case ICMPV6_DEST_UNREACH:
        case ICMPV6_TIME_EXCEED:
        case ICMPV6_PARAMPROB:
                icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu);
                break;

        case NDISC_ROUTER_SOLICITATION:
        case NDISC_ROUTER_ADVERTISEMENT:
        case NDISC_NEIGHBOUR_SOLICITATION:
        case NDISC_NEIGHBOUR_ADVERTISEMENT:
        case NDISC_REDIRECT:
                ndisc_rcv(skb);  //分发给NDP协议处理
                break;

        case ICMPV6_MGM_QUERY:
                igmp6_event_query(skb);
                break;

        case ICMPV6_MGM_REPORT:
                igmp6_event_report(skb);
                break;

        case ICMPV6_MGM_REDUCTION:
        case ICMPV6_NI_QUERY:
        case ICMPV6_NI_REPLY:
        case ICMPV6_MLD2_REPORT:
        case ICMPV6_DHAAD_REQUEST:
        case ICMPV6_DHAAD_REPLY:
        case ICMPV6_MOBILE_PREFIX_SOL:
        case ICMPV6_MOBILE_PREFIX_ADV:
                break;

        default:
                /* informational */
                if (type & ICMPV6_INFOMSG_MASK)
                        break;

                LIMIT_NETDEBUG(KERN_DEBUG "icmpv6: msg of unknown type\n");

                /*
                 * error of unknown type.
                 * must pass to upper level
                 */

                icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu);
        }

        kfree_skb(skb);
        return 0;

csum_error:
        ICMP6_INC_STATS_BH(dev_net(dev), idev, ICMP6_MIB_CSUMERRORS);
discard_it:
        ICMP6_INC_STATS_BH(dev_net(dev), idev, ICMP6_MIB_INERRORS);
drop_no_count:
        kfree_skb(skb);
        return 0;
}

icmp协议收包后,就会根据类型分发给NDP协议处理ndisc_rcv

int ndisc_rcv(struct sk_buff *skb)
{
        struct nd_msg *msg;

        if (ndisc_suppress_frag_ndisc(skb))
                return 0;

        if (skb_linearize(skb))
                return 0;

        msg = (struct nd_msg *)skb_transport_header(skb);

        __skb_push(skb, skb->data - skb_transport_header(skb));

        if (ipv6_hdr(skb)->hop_limit != 255) {
                ND_PRINTK(2, warn, "NDISC: invalid hop-limit: %d\n",
                          ipv6_hdr(skb)->hop_limit);
                return 0;
        }

        if (msg->icmph.icmp6_code != 0) {
                ND_PRINTK(2, warn, "NDISC: invalid ICMPv6 code: %d\n",
                          msg->icmph.icmp6_code);
                return 0;
        }

        memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb));

        switch (msg->icmph.icmp6_type) {
        case NDISC_NEIGHBOUR_SOLICITATION:
                ndisc_recv_ns(skb);
                break;

        case NDISC_NEIGHBOUR_ADVERTISEMENT:
                ndisc_recv_na(skb);
                break;

        case NDISC_ROUTER_SOLICITATION:
                ndisc_recv_rs(skb);
                break;

        case NDISC_ROUTER_ADVERTISEMENT:
                ndisc_router_discovery(skb);
                break;

        case NDISC_REDIRECT:
                ndisc_redirect_rcv(skb);
                break;
        }

        return 0;
}

接下来ndisc_router_discovery继续处理,ndisc_router_discovery的处理过程很长,这里主要记录3点:

1,解析RA报文中的MOFlag,并上报用户层

        /*
         * Remember the managed/otherconf flags from most recently
         * received RA message (RFC 2462) -- yoshfuji
         */
        old_if_flags = in6_dev->if_flags;
        in6_dev->if_flags = (in6_dev->if_flags & ~(IF_RA_MANAGED |
                                IF_RA_OTHERCONF)) |
                                (ra_msg->icmph.icmp6_addrconf_managed ?
                                        IF_RA_MANAGED : 0) |
                                (ra_msg->icmph.icmp6_addrconf_other ?
                                        IF_RA_OTHERCONF : 0);

        /*
         * transfer to native layer to handle this judgment
         */
        inet6_ifinfo_notify(RTM_NEWLINK, in6_dev);

2,设置ipv6默认路由

        ND_PRINTK(3, info, "RA: rt: %p  lifetime: %d, for dev: %s\n",
                  rt, lifetime, skb->dev->name);
        if (rt == NULL && lifetime) {
                ND_PRINTK(3, info, "RA: adding default router\n");

                rt = rt6_add_dflt_router(&ipv6_hdr(skb)->saddr, skb->dev, pref);
                if (rt == NULL) {
                        ND_PRINTK(0, err,
                                  "RA: %s failed to add default route\n",
                                  __func__);
                        return;
                }

                neigh = dst_neigh_lookup(&rt->dst, &ipv6_hdr(skb)->saddr);
                if (neigh == NULL) {
                        ND_PRINTK(0, err,
                                  "RA: %s got default router without neighbour\n",
                                  __func__);
                        ip6_rt_put(rt);
                        return;
                }
                neigh->flags |= NTF_ROUTER;
        } else if (rt) {
                rt->rt6i_flags = (rt->rt6i_flags & ~RTF_PREF_MASK) | RTF_PREF(pref);
        }

        if (rt)
                rt6_set_expires(rt, jiffies + (HZ * lifetime));
        if (ra_msg->icmph.icmp6_hop_limit) {
                /* Only set hop_limit on the interface if it is higher than
                 * the current hop_limit.
                 */
                if (in6_dev->cnf.hop_limit < ra_msg->icmph.icmp6_hop_limit) {
                        in6_dev->cnf.hop_limit = ra_msg->icmph.icmp6_hop_limit;
                } else {
                        ND_PRINTK(2, warn, "RA: Got route advertisement with lower hop_limit than current\n");
                }
                if (rt)
                        dst_metric_set(&rt->dst, RTAX_HOPLIMIT,
                                       ra_msg->icmph.icmp6_hop_limit);
        }

3,如果RA报文中携带地址前缀,则根据前缀生成global ipv6地址

        if (in6_dev->cnf.accept_ra_pinfo && ndopts.nd_opts_pi) {
                struct nd_opt_hdr *p;
                for (p = ndopts.nd_opts_pi;
                     p;
                     p = ndisc_next_option(p, ndopts.nd_opts_pi_end)) {
                        addrconf_prefix_rcv(skb->dev, (u8 *)p,
                                            (p->nd_opt_len) << 3,
                                            ndopts.nd_opts_src_lladdr != NULL);
                }
        }

 

整理的收发RA,RS的代码大致流程,细节研究中

<think> 我们正在讨论PPPoE拨号流程中的RSRA阶段。在IPv6环境中,RS(Router Solicitation,路由器请求)和RA(Router Advertisement,路由器通告)是邻居发现协议(NDP)的一部分,用于获取网络配置信息。 分析阶段: 1. **PPPoE连接建立后**:在PPPoE完成LCP、认证(如PAP/CHAP)、IPv6CP(IPv6控制协议)协商后,PPP接口被分配了链路本地地址,此时网络层开始工作。 2. **RS发送阶段**:在接口准备就绪后,客户端会主动发送RS报文(ICMPv6类型133),请求本地链路上的路由器立即发送RA。 3. **RA响应阶段**:路由器收到RS后,会发送RA报文(ICMPv6类型134),其中包含网络前缀、MTU、标志位(如M比特和O比特)等信息。 在脚本中的体现: - 脚本中在PPPoE接口配置后,设置了`accept_ra=2`(接受RA)和`sendrs=1`(允许发送RS),然后等待RA的到来。 - 具体代码位置通常在获得链路本地地址之后,启动DHCPv6或SLAAC之前。 因此,RSRA发生在**PPPoE连接建立完成(IPv6CP成功后)且网络层接口已启用**的阶段,是IPv6地址自动配置的开始。 §§相关问题§§ 1. 在PPPoE接口上发送RS报文以太网接口有何不同? 2. RA报文中的M比特和O比特分别对PPPoE客户端的配置流程产生什么影响? 3. 如果PPPoE客户端发送RS后未收到RA,可能的原因有哪些? 4. 在PPPoE环境下,RA报文中的MTU信息是否会被使用?为什么? 5. 如何通过内核参数调整RS的重传行为? </think> ### RSRA在PPPoE拨号流程中的阶段分析 RS(Router Solicitation,路由器请求)和RA(Router Advertisement,路由器通告)是**IPv6邻居发现协议(NDP)**的关键组成部分,在PPPoE拨号流程中发生在特定的时序阶段: #### 发生阶段定位 ```mermaid sequenceDiagram participant C as PPPoE客户端 participant S as PPPoE服务器 C->>S: PPPoE发现阶段(PADI/PADO/PADR/PADS) C->>S: LCP协商 C->>S: 认证(PAP/CHAP) C->>S: IPv6CP协商(建立LLLOCAL/LLREMOTE) Note right of C: 网络层就绪点 C->>广播: 发送RS请求 S->>C: 回复RA通告 C->>S: DHCPv6请求(若RA指示) S->>C: DHCPv6响应 ``` #### 具体阶段说明 1. **PPPoE连接建立后**: - 发生在IPv6CP协商完成之后 - 必须等待获得`$LLLOCAL`和`$LLREMOTE`链路地址 - 此时PPPoE会话已建立但IPv6地址未分配 2. **网络层初始化阶段**: ```bash # 脚本关键位置 echo "2" > /proc/sys/net/ipv6/conf/$IFNAME/accept_ra echo "1" > /proc/sys/net/ipv6/conf/$IFNAME/sendrs ``` - 启用RA接收(`accept_ra=2`) - 触发RS发送(`sendrs=1`) 3. **RS发送时机**: - 在接口UP后立即自动发送 - 客户端每隔`MAX_RTR_SOLICITATION_INTERVAL`(默认4秒)重发 - 最多发送`MAX_RTR_SOLICITATIONS`(默认3次) 4. **RA响应窗口期**: - 路由器应在0.5秒内响应(RFC4861) - 包含关键标志位: - `M-bit`(管理地址配置):1=需DHCPv6 - `O-bit`(其他配置):1=需DHCPv6获取DNS等 - `A-bit`(自治地址配置):1=允许SLAAC #### 阶段特性 | 特性 | RS阶段 | RA阶段 | |------|--------|--------| | **发起方** | 客户端 | 路由器 | | **报文类型** | ICMPv6类型133 | ICMPv6类型134 | | **目标地址** | FF02::2(所有路由器) | FF02::1(所有节点) | | **关键依赖** | 要求IPv6链路层就绪 | 要求路由表更新完成 | | **PPPoE限制** | MTU分片可能导致延迟 | 需穿透PPPoE隧道 | ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值