默认情况下router_solicitations值为-1,不限制RS的发送次数。如果其为正值,表示当发送到此数量的RS报文之后,还未收到响应,则认为当前链路不存在路由器。

$ cat /proc/sys/net/ipv6/conf/all/router_solicitations
-1
$ cat /proc/sys/net/ipv6/conf/default/router_solicitations
-1

router_solicitation_interval规定RS报文的发送间隔,默认为4秒钟。router_solicitation_max_interval定义最长间隔,默认为1小时。

$ cat /proc/sys/net/ipv6/conf/all/router_solicitation_interval 
4 
$ cat /proc/sys/net/ipv6/conf/default/router_solicitation_interval    
4
$ 
$ cat /proc/sys/net/ipv6/conf/all/router_solicitation_max_interval         
3600
$ cat /proc/sys/net/ipv6/conf/default/router_solicitation_max_interval 
3600

最后一次发送RS报文前等待的时长,即在发送第router_solicitations次RS报文前需等待的时长,一般情况下比router_solicitation_interval时长要短。

$ cat /proc/sys/net/ipv6/conf/all/router_solicitation_delay        
1
$ cat /proc/sys/net/ipv6/conf/default/router_solicitation_delay 
1

如何内核代码为以上RS传输参数的初始值。

static struct ipv6_devconf ipv6_devconf __read_mostly = {
    ...
    .rtr_solicits       = MAX_RTR_SOLICITATIONS,
    .rtr_solicit_interval   = RTR_SOLICITATION_INTERVAL,
    .rtr_solicit_max_interval = RTR_SOLICITATION_MAX_INTERVAL,
    .rtr_solicit_delay  = MAX_RTR_SOLICITATION_DELAY,

static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
    ...
    .rtr_solicits       = MAX_RTR_SOLICITATIONS,
    .rtr_solicit_interval   = RTR_SOLICITATION_INTERVAL,
    .rtr_solicit_max_interval = RTR_SOLICITATION_MAX_INTERVAL,
    .rtr_solicit_delay  = MAX_RTR_SOLICITATION_DELAY,

RS发送间隔计算

初始情况下,依据IRT(Initial Retransmission Time)生成RS发送间隔,IRT的值等于接口配置的cnf.router_solicitation_interval值。RFC3315给出的计算公式如下:

RT = IRT + RAND*IRT = (1 + RAND)*IRT

其中RT为(Retransmission Timeout)实际使用的重传间隔。RAND随机值范围在-0.1到0.1之间,即RT值在0.9IRT到1.1IRT之间,但是内核的真实范围为0.9到1.100001倍的IRT,跨度是0.2左右。最终计算得到的值保存在设备inet6_dev结构的成员rs_interval中,之后还会用到。

static inline s32 rfc3315_s14_backoff_init(s32 irt)
{
    /* multiply 'initial retransmission time' by 0.9 .. 1.1 */
    u64 tmp = (900000 + prandom_u32() % 200001) * (u64)irt;
    do_div(tmp, 1000000);
    return (s32)tmp;
}

非初始情况下,RFC3315给出的计算公式如下:

RT = 2*RTprev + RAND*RTprev = (2 + RAND)*RTprev

由于RAND取值-0.1到0.1,带入以上公式,为1.9到2.1倍的RTprev,其中RTprev为之前保存在设备结构inet6_dev成员rs_interval中的值。以下函数参数mrt(Maximum Retransmission Time)为接口配置的router_solicitation_max_interval值,如果计算得到的RT值大于mrt,使用如下公式,重新计算RT:

RT = MRT + RAND*MRT = (1 + RAND)*MRT

参见以下函数rfc3315_s14_backoff_update中的实现代码。

static inline s32 rfc3315_s14_backoff_update(s32 rt, s32 mrt)
{
    /* multiply 'retransmission timeout' by 1.9 .. 2.1 */
    u64 tmp = (1900000 + prandom_u32() % 200001) * (u64)rt;
    do_div(tmp, 1000000);
    if ((s32)tmp > mrt) {
        /* multiply 'maximum retransmission time' by 0.9 .. 1.1 */
        tmp = (900000 + prandom_u32() % 200001) * (u64)mrt;
        do_div(tmp, 1000000);
    }
    return (s32)tmp;
}

RS定时器初始化

当注册新的网络设备,或者在添加设备地址时,如果没有相应的inet6_dev设备,由函数ipv6_add_dev进行创建,其中将初始化RS定时器rs_timer,指定的超时处理函数为addrconf_rs_timer。

另外,设备的配置参数默认使用网络命名空间中的所有默认参数(对应于PROC文件下的default目录),RS相关配置参数即为默认参数。

static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
{
    struct inet6_dev *ndev;
    int err = -ENOMEM;

    ASSERT_RTNL();

    if (dev->mtu < IPV6_MIN_MTU)
        return ERR_PTR(-EINVAL);

    ndev = kzalloc(sizeof(struct inet6_dev), GFP_KERNEL);
    if (!ndev)
        return ERR_PTR(err);

    rwlock_init(&ndev->lock);
    ndev->dev = dev;
    INIT_LIST_HEAD(&ndev->addr_list);
    timer_setup(&ndev->rs_timer, addrconf_rs_timer, 0);
    memcpy(&ndev->cnf, dev_net(dev)->ipv6.devconf_dflt, sizeof(ndev->cnf));

启动RS定时器

函数addrconf_mod_rs_timer将rs_timer定时器的超时时间设定在when之后。

static void addrconf_mod_rs_timer(struct inet6_dev *idev, unsigned long when)
{
    if (!timer_pending(&idev->rs_timer))
        in6_dev_hold(idev);
    mod_timer(&idev->rs_timer, jiffies + when);
}

当地址的DAD检测完成之后,如果此地址是唯一的链路本地地址,表明在DAD期间,MLD的report消息没有可用地址,使用的是in6_addrany,此时,可使用有效的链路本地地址重发MLD消息。

如果send_mld为真,当前接口也可接收路由器的RA消息,并且rtr_solicits次数配置的不为0,接口不为环回接口,可发送RS报文,send_rs为真。

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

    addrconf_del_dad_work(ifp);

    /* If added prefix is link local and we are prepared to process
       router advertisements, start sending router solicitations.
     */
    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;

发送RS报文,计算初始的超时时间rs_interval,开启rs_timer定时器。

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);

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

在修改接口token时,如果设备的SLAAC状态为IF_READY,发送RS请求,在接收到RA报文之后,将根据新的token生成地址。初始化RS超时时间,启动RS定时器。

static int inet6_set_iftoken(struct inet6_dev *idev, struct in6_addr *token)
{
    ...
    if (!idev->dead && (idev->if_flags & IF_READY) &&
        !ipv6_get_lladdr(dev, &ll_addr, IFA_F_TENTATIVE | IFA_F_OPTIMISTIC)) {
        /* If we're not ready, then normal ifup will take care
         * of this. Otherwise, we need to request our rs here.
         */
        ndisc_send_rs(dev, &ll_addr, &in6addr_linklocal_allrouters);
        update_rs = true;
    }

update_lft:
    write_lock_bh(&idev->lock);

    if (update_rs) {
        idev->if_flags |= IF_RS_SENT;
        idev->rs_interval = rfc3315_s14_backoff_init(idev->cnf.rtr_solicit_interval);
        idev->rs_probes = 1;
        addrconf_mod_rs_timer(idev, idev->rs_interval);
    }

RS超时处理

如果接口没有开启RA接收配置,或者已经接收到RA报文(IF_RA_RCVD),退出处理。

static void addrconf_rs_timer(struct timer_list *t)
{
    struct inet6_dev *idev = from_timer(idev, t, rs_timer);
    struct net_device *dev = idev->dev;
    struct in6_addr lladdr;

    write_lock(&idev->lock);
    if (idev->dead || !(idev->if_flags & IF_READY))
        goto out;

    if (!ipv6_accept_ra(idev))
        goto out;

    /* Announcement received after solicitation was sent */
    if (idev->if_flags & IF_RA_RCVD)
        goto out;

如果RS发送次数次数(rs_probes)小于接口的配置的限定值(rtr_solicits),或者没有限定值(rtr_solicits小于零),重新发送RS报文。重新计算超时时间,但是对于最后一次重传,超时时间使用设定的rtr_solicit_delay值,而不是计算得来的rs_interval值。

if (idev->rs_probes++ < idev->cnf.rtr_solicits || idev->cnf.rtr_solicits < 0) {
        if (!ipv6_get_lladdr(dev, &lladdr, IFA_F_TENTATIVE))
            ndisc_send_rs(dev, &lladdr, &in6addr_linklocal_allrouters);
        else
            goto put;

        idev->rs_interval = rfc3315_s14_backoff_update(
            idev->rs_interval, idev->cnf.rtr_solicit_max_interval);

        /* The wait after the last probe can be shorter */
        addrconf_mod_rs_timer(idev, (idev->rs_probes ==
                         idev->cnf.rtr_solicits) ?
                      idev->cnf.rtr_solicit_delay : idev->rs_interval);
    } else {
        /* Note: we do not support deprecated "all on-link" assumption any longer.
         */
        pr_debug("%s: no IPv6 routers present\n", idev->dev->name);
    }

删除RS定时器

如下函数addrconf_del_rs_timer用于删除接口的RS定时器。

static void addrconf_del_rs_timer(struct inet6_dev *idev)
{
    if (del_timer(&idev->rs_timer))
        __in6_dev_put(idev);
}

在接口进入down状态,或者注销等情况下,需要删除RS定时器。

static int addrconf_ifdown(struct net_device *dev, bool unregister)
{
    unsigned long event = unregister ? NETDEV_UNREGISTER : NETDEV_DOWN;
    struct net *net = dev_net(dev);
    struct inet6_dev *idev;

    rt6_disable_ip(dev, event);

    idev = __in6_dev_get(dev);
    if (!idev) return -ENODEV;


    addrconf_del_rs_timer(idev);

内核版本 5.10