您好,欢迎访问代理记账网站
移动应用 微信公众号 联系我们

咨询热线 -

电话 15988168888

联系客服
  • 价格透明
  • 信息保密
  • 进度掌控
  • 售后无忧

IPv6定时路由

内核版本要求高于4.4。如下设置路由时长为1200秒,当允许命令查看时,还剩余1151秒。

# ip -6 route add 3302::/64 dev ens35 expires 1200    
# 
# ip -6 route 
3302::/64 dev ens35 metric 1024 expires 1151sec pref medium

在内核部分,将下发的expires值转换为系统时钟值,并且设置RTF_EXPIRES标志位。

static int rtm_to_fib6_config(struct sk_buff *skb, struct nlmsghdr *nlh,
                  struct fib6_config *cfg, struct netlink_ext_ack *extack)
{
    struct rtmsg *rtm;
    struct nlattr *tb[RTA_MAX+1];

    ...
    if (tb[RTA_EXPIRES]) {
        unsigned long timeout = addrconf_timeout_fixup(nla_get_u32(tb[RTA_EXPIRES]), HZ);

        if (addrconf_finite_timeout(timeout)) {
            cfg->fc_expires = jiffies_to_clock_t(timeout * HZ);
            cfg->fc_flags |= RTF_EXPIRES;
        }
    }

如果配置中设置了RTF_EXPIRES标志位,设置路由结构fib6_info中的expires值,否者清除其值以及RTF_EXPIRES标志。

static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
                          gfp_t gfp_flags, struct netlink_ext_ack *extack)
{

    if (cfg->fc_flags & RTF_EXPIRES)
        fib6_set_expires(rt, jiffies +
                clock_t_to_jiffies(cfg->fc_expires));
    else
        fib6_clean_expires(rt);

最终路由的添加有函数fib6_add完成,如下核心子函数fib6_add_rt2node,如果找到重复的路由项,根据新的路由项rt的超时时长,更新已有路由项的expires超时时长。

/* Insert routing information in a node.
 */
static int fib6_add_rt2node(struct fib6_node *fn, struct fib6_info *rt,
                struct nl_info *info, struct netlink_ext_ack *extack)
{
    struct fib6_info *leaf = rcu_dereference_protected(fn->leaf,
                    lockdep_is_held(&rt->fib6_table->tb6_lock));
    struct fib6_info *iter = NULL;

    ins = &fn->leaf;

    for (iter = leaf; iter; iter = rcu_dereference_protected(iter->fib6_next,
                lockdep_is_held(&rt->fib6_table->tb6_lock))) {
        /*
         *  Search for duplicates
         */
        if (iter->fib6_metric == rt->fib6_metric) {
            ...
            if (rt6_duplicate_nexthop(iter, rt)) {
                if (rt->fib6_nsiblings)
                    rt->fib6_nsiblings = 0;
                if (!(iter->fib6_flags & RTF_EXPIRES))
                    return -EEXIST;
                if (!(rt->fib6_flags & RTF_EXPIRES))
                    fib6_clean_expires(iter);
                else
                    fib6_set_expires(iter, rt->expires);

如下两个函数对fib6_info中expires进行处理。

static inline void fib6_clean_expires(struct fib6_info *f6i)
{
    f6i->fib6_flags &= ~RTF_EXPIRES;
    f6i->expires = 0;
}

static inline void fib6_set_expires(struct fib6_info *f6i, unsigned long expires)
{
    f6i->expires = expires;
    f6i->fib6_flags |= RTF_EXPIRES;
}

IPv6地址配置生成路由

如下配置接口IP地址,并且带有preferred和valid时长。内核将生成带有时长的直连路由。但是,也可在ip命令中指定noprefixroute选项,不添加相应路由。

# ip -6 addr add 3303::1/64 dev ens35 valid_lft 200 preferred_lft 100
# 
# ip address show ens35
4: ens35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    inet6 3303::1/64 scope global dynamic 
       valid_lft 195sec preferred_lft 95sec
#
# ip -6 route
3303::/64 dev ens35 proto kernel metric 256 expires 191sec pref medium

内核处理函数inet6_addr_add使用IP地址的valid时长作为直连路由的超时时长。

static int inet6_addr_add(struct net *net, int ifindex,
              struct ifa6_config *cfg, struct netlink_ext_ack *extack)
{
    struct inet6_ifaddr *ifp;
    struct inet6_dev *idev;
    struct net_device *dev;
    unsigned long timeout;
    clock_t expires;

    ...
    timeout = addrconf_timeout_fixup(cfg->valid_lft, HZ);
    if (addrconf_finite_timeout(timeout)) {
        expires = jiffies_to_clock_t(timeout * HZ);
        cfg->valid_lft = timeout;
        flags = RTF_EXPIRES;
    } else {
        expires = 0;
        flags = 0;
        cfg->ifa_flags |= IFA_F_PERMANENT;
    }

    ifp = ipv6_add_addr(idev, cfg, true, extack);
    if (!IS_ERR(ifp)) {
        if (!(cfg->ifa_flags & IFA_F_NOPREFIXROUTE)) {
            addrconf_prefix_route(&ifp->addr, ifp->prefix_len,
                          ifp->rt_priority, dev, expires, flags, GFP_KERNEL);

在函数addrconf_prefix_route中调用ip6_route_add函数添加路由。

static void
addrconf_prefix_route(struct in6_addr *pfx, int plen, u32 metric,
              struct net_device *dev, unsigned long expires, u32 flags, gfp_t gfp_flags)
{
    struct fib6_config cfg = {
        .fc_table = l3mdev_fib_table(dev) ? : RT6_TABLE_PREFIX,
        .fc_metric = metric ? : IP6_RT_PRIO_ADDRCONF,
        .fc_ifindex = dev->ifindex,
        .fc_expires = expires,
        .fc_dst_len = plen,
        .fc_flags = RTF_UP | flags,
        ...
    };
    ... 
    ip6_route_add(&cfg, gfp_flags, NULL);

函数ip6_route_add使用ip6_route_info_create创建fib6_info结构,在插入到fib6树中。fib6_info结构成员expires中保存的为jiffies为单位的超时值。

static struct fib6_info *ip6_route_info_create(struct fib6_config *cfg,
                          gfp_t gfp_flags, struct netlink_ext_ack *extack)
{
    struct net *net = cfg->fc_nlinfo.nl_net;
    struct fib6_info *rt = NULL;

    if (cfg->fc_flags & RTF_EXPIRES)
        fib6_set_expires(rt, jiffies + clock_t_to_jiffies(cfg->fc_expires));
    else
        fib6_clean_expires(rt);

IPv6地址删除

对于非定时IPv6地址,如果其添加了相应的前缀路由,在删除时,由函数check_cleanup_prefix_route决定是否保留前缀路由,以及确定其路由时长。

static void ipv6_del_addr(struct inet6_ifaddr *ifp)
{
    enum cleanup_prefix_rt_t action = CLEANUP_PREFIX_RT_NOP;
    unsigned long expires;

    if (ifp->flags & IFA_F_PERMANENT && !(ifp->flags & IFA_F_NOPREFIXROUTE))
        action = check_cleanup_prefix_route(ifp, &expires);

    ...
    if (action != CLEANUP_PREFIX_RT_NOP) {
        cleanup_prefix_route(ifp, expires,
            action == CLEANUP_PREFIX_RT_DEL, false);
    }

函数check_cleanup_prefix_route返回三种动作:删除路由、设置路由超时、或者不操作。判断条件如下:

  • 如果设备地址链表为空,或者仅有将要删除的IPv6地址对应的结构ifp,删除路由;
  • 如果设备地址链表中的地址都与将要删除的地址不在同一网段,删除路由;
  • 如果地址链表中有与将要删除地址相同网段的地址,并且设置了IFA_F_PERMANENT或者IFA_F_NOPREFIXROUTE标志,不对路由进行处理;
  • 否则,如果以上都不成立,更新路由超时时间点为遍历地址的有效期到期时间。
static enum cleanup_prefix_rt_t
check_cleanup_prefix_route(struct inet6_ifaddr *ifp, unsigned long *expires)
{
    struct inet6_ifaddr *ifa;
    struct inet6_dev *idev = ifp->idev;
    enum cleanup_prefix_rt_t action = CLEANUP_PREFIX_RT_DEL;

    *expires = jiffies;

    list_for_each_entry(ifa, &idev->addr_list, if_list) {
        if (ifa == ifp) continue;
        if (ifa->prefix_len != ifp->prefix_len ||
            !ipv6_prefix_equal(&ifa->addr, &ifp->addr, ifp->prefix_len))
            continue;
        if (ifa->flags & (IFA_F_PERMANENT | IFA_F_NOPREFIXROUTE))
            return CLEANUP_PREFIX_RT_NOP;

        action = CLEANUP_PREFIX_RT_EXPIRE;

        spin_lock(&ifa->lock);

        lifetime = addrconf_timeout_fixup(ifa->valid_lft, HZ);
        /* Note: Because this address is not permanent, lifetime < LONG_MAX / HZ here.
         */
        if (time_before(*expires, ifa->tstamp + lifetime * HZ))
            *expires = ifa->tstamp + lifetime * HZ;
        spin_unlock(&ifa->lock);
    }
    return action;

如果动作是设置路由超时,由函数fib6_set_expires完成。这里RTF_EXPIRES标志的判断,确保修改的不是超时路由的时长值,超时路由有其自身的超时控制。

static void
cleanup_prefix_route(struct inet6_ifaddr *ifp, unsigned long expires, bool del_rt, bool del_peer)
{
    struct fib6_info *f6i;

    f6i = addrconf_get_prefix_route(del_peer ? &ifp->peer_addr : &ifp->addr,
                    ifp->prefix_len,
                    ifp->idev->dev, 0, RTF_DEFAULT, true);
    if (f6i) {
        if (del_rt)
            ip6_del_rt(dev_net(ifp->idev->dev), f6i, false);
        else {
            if (!(f6i->fib6_flags & RTF_EXPIRES))
                fib6_set_expires(f6i, expires);

IPv6自动地址生成路由

对于IPv6邻居发现协议,在接收到RA报文之后,如果报文中带有前缀信息,交由函数addrconf_prefix_rcv处理。如果找到已经存在的fib6_info,根据新的超时时长修改更新已有值。

另外,如果超时时长为无限,清除RTF_EXPIRES标志,将fib6_info成员expires设置为0,参见函数fib6_clean_expires。

void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
{
    struct prefix_info *pinfo;
    struct inet6_dev *in6_dev;
    struct net *net = dev_net(dev);

    valid_lft = ntohl(pinfo->valid);
    prefered_lft = ntohl(pinfo->prefered);

    if (pinfo->onlink) {
        struct fib6_info *rt;
        unsigned long rt_expires;

        if (HZ > USER_HZ)
            rt_expires = addrconf_timeout_fixup(valid_lft, HZ);
        else
            rt_expires = addrconf_timeout_fixup(valid_lft, USER_HZ);

        if (addrconf_finite_timeout(rt_expires))
            rt_expires *= HZ;

        rt = addrconf_get_prefix_route(&pinfo->prefix, pinfo->prefix_len,
                           dev, RTF_ADDRCONF | RTF_PREFIX_RT, RTF_DEFAULT, true);
        if (rt) {
            if (valid_lft == 0) {
                ip6_del_rt(net, rt, false);
                rt = NULL;
            } else if (addrconf_finite_timeout(rt_expires)) {
                fib6_set_expires(rt, jiffies + rt_expires);
            } else {
                fib6_clean_expires(rt);
            }

否则,不存在已有路由项的情况下,调用上节介绍的addrconf_prefix_route函数,创建fib6_info结构,设置超时时长。

        } else if (valid_lft) {
            clock_t expires = 0;
            int flags = RTF_ADDRCONF | RTF_PREFIX_RT;
            if (addrconf_finite_timeout(rt_expires)) {
                flags |= RTF_EXPIRES;
                expires = jiffies_to_clock_t(rt_expires);
            }
            addrconf_prefix_route(&pinfo->prefix, pinfo->prefix_len,
                          0, dev, expires, flags, GFP_ATOMIC);
        }

路由超时检测

基础函数fib6_check_expired返回路由fib6_info是否超时。

static inline bool fib6_check_expired(const struct fib6_info *f6i)
{
    if (f6i->fib6_flags & RTF_EXPIRES)
        return time_after(jiffies, f6i->expires);
    return false;
}

路由超时删除

由定时器ip6_fib_timer完成,其到期处理函数为fib6_gc_timer_cb。

static void fib6_start_gc(struct net *net, struct fib6_info *rt)
{
    if (!timer_pending(&net->ipv6.ip6_fib_timer) &&
        (rt->fib6_flags & RTF_EXPIRES))
        mod_timer(&net->ipv6.ip6_fib_timer,
              jiffies + net->ipv6.sysctl.ip6_rt_gc_interval);
}

函数fib6_age检测路由项是否到期,如果fib6_info成员的expires已经过期,返回-1,函数fib6_clean_node将根据此返回值删除路由。

static int fib6_age(struct fib6_info *rt, void *arg)
{
    struct fib6_gc_args *gc_args = arg;
    unsigned long now = jiffies;

    /*  check addrconf expiration here.
     *  Routes are expired even if they are in use.
     */
    if (rt->fib6_flags & RTF_EXPIRES && rt->expires) {
        if (time_after(now, rt->expires)) {
            RT6_TRACE("expiring %p\n", rt);
            return -1;
        }
        gc_args->more++;
    }
    /*  Also age clones in the exception table.
     *  Note, that clones are aged out
     *  only if they are not in use now.
     */
    rt6_age_exceptions(rt, gc_args, now);

在函数fib6_clean_node中,对于到期的路由,c->func返回值为-1,调用fib6_del将其删除。

static int fib6_clean_node(struct fib6_walker *w)
{
    int res;
    struct fib6_info *rt;
    struct fib6_cleaner *c = container_of(w, struct fib6_cleaner, w);
    struct nl_info info = {
        .nl_net = c->net,
        .skip_notify = c->skip_notify,
    };

    if (c->sernum != FIB6_NO_SERNUM_CHANGE &&
        w->node->fn_sernum != c->sernum)
        w->node->fn_sernum = c->sernum;

    if (!c->func) {
        WARN_ON_ONCE(c->sernum == FIB6_NO_SERNUM_CHANGE);
        w->leaf = NULL;
        return 0;
    }

    for_each_fib6_walker_rt(w) {
        res = c->func(rt, c->arg);
        if (res == -1) {
            w->leaf = rt;
            res = fib6_del(rt, &info);

内核版本 5.10


分享:

低价透明

统一报价,无隐形消费

金牌服务

一对一专属顾问7*24小时金牌服务

信息保密

个人信息安全有保障

售后无忧

服务出问题客服经理全程跟进