如下ip命令所示,可设置接口的token值。
# ip token set ::0102:0304 dev ens33
#
# ip token list
token ::1.2.3.4 dev ens33
token :: dev ens34
token :: dev ens35
内核处理TOKEN添加
如下inet6_set_iftoken函数,先检查合法性:
- 对于环回loopback接口,或者不需要邻居地址的接口(设置了IFF_NOARP标志位),不设置接口token;
2) 接口不接收邻居发现报文RA时,没有必要设置接口token;
3) solicit重传次数为0,不设置接口token。
static int inet6_set_iftoken(struct inet6_dev *idev, struct in6_addr *token)
{
struct inet6_ifaddr *ifp;
struct net_device *dev = idev->dev;
bool clear_token, update_rs = false;
struct in6_addr ll_addr;
ASSERT_RTNL();
if (!token)
return -EINVAL;
if (dev->flags & (IFF_LOOPBACK | IFF_NOARP))
return -EINVAL;
if (!ipv6_accept_ra(idev))
return -EINVAL;
if (idev->cnf.rtr_solicits == 0)
return -EINVAL;
保存token的后8字节到接口结构中。如果设置的token最后8字节为零,删除据此token生成的地址。
write_lock_bh(&idev->lock);
BUILD_BUG_ON(sizeof(token->s6_addr) != 16);
memcpy(idev->token.s6_addr + 8, token->s6_addr + 8, 8);
write_unlock_bh(&idev->lock);
clear_token = ipv6_addr_any(token);
if (clear_token)
goto update_lft;
否则,如果设备的SLAAC状态为IF_READY,发送RS请求,在接收到RA报文之后,将根据新的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;
}
接下来,初始化RS超时时间,启动RS重传定时器。
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);
}
在设置接口新的token之后,需要删除接口上依据旧的token生成的地址。以下遍历接口的地址链表,对于由接口token生成的地址,将其valid和prefered时长设置为0,之后在addrconf_verify_rtnl函数中,将删除这些地址。
/* Well, that's kinda nasty ... */
list_for_each_entry(ifp, &idev->addr_list, if_list) {
spin_lock(&ifp->lock);
if (ifp->tokenized) {
ifp->valid_lft = 0;
ifp->prefered_lft = 0;
}
spin_unlock(&ifp->lock);
}
write_unlock_bh(&idev->lock);
inet6_ifinfo_notify(RTM_NEWLINK, idev);
addrconf_verify_rtnl();
token地址生成
在处理接收到的地址前缀信息时,如果前缀设置了自动配置标志,并且前缀长度为64bit,在接口token不为空的情况下,优先使用token的后8个字节作为新地址的接口ID,与前缀的8个字节一同生成新地址。
void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
{
...
/* Try to figure out our local address for this prefix */
if (pinfo->autoconf && in6_dev->cnf.autoconf) {
struct in6_addr addr;
bool tokenized = false, dev_addr_generated = false;
if (pinfo->prefix_len == 64) {
memcpy(&addr, &pinfo->prefix, 8);
if (!ipv6_addr_any(&in6_dev->token)) {
read_lock_bh(&in6_dev->lock);
memcpy(addr.s6_addr + 8, in6_dev->token.s6_addr + 8, 8);
read_unlock_bh(&in6_dev->lock);
tokenized = true;
以下函数负责新生成地址的添加。目前仅有6lowpan会使用到函数ndisc_ops_prefix_rcv_add_addr。
err = addrconf_prefix_rcv_add_addr(net, dev, pinfo, in6_dev,
&addr, addr_type, addr_flags, sllao,
tokenized, valid_lft, prefered_lft);
if (err) goto put;
/* Ignore error case here because previous prefix add addr was
* successful which will be notified.
*/
ndisc_ops_prefix_rcv_add_addr(net, dev, pinfo, in6_dev, &addr,
addr_type, addr_flags, sllao,
tokenized, valid_lft, prefered_lft, dev_addr_generated);
如果地址不存在,使用ipv6_add_addr添加新的地址,并且设置tokenized标志,启动DAD检测。
int addrconf_prefix_rcv_add_addr(struct net *net, struct net_device *dev,...,
const struct in6_addr *addr, int addr_type,
u32 addr_flags, bool sllao, bool tokenized, ...)
{
struct inet6_ifaddr *ifp = ipv6_get_ifaddr(net, addr, dev, 1);
int create = 0;
if (!ifp && valid_lft) {
/* Do not allow to create too much of autoconfigured
* addresses; this would be too easy way to crash kernel.
*/
if (!max_addresses ||
ipv6_count_addresses(in6_dev) < max_addresses)
ifp = ipv6_add_addr(in6_dev, &cfg, false, NULL);
ifp->flags |= IFA_F_MANAGETEMPADDR;
ifp->cstamp = jiffies;
ifp->tokenized = tokenized;
addrconf_dad_start(ifp);
内核版本 5.10