默认情况下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