当前设备的接口ens32的ip地址为192.168.1.109,当前的默认路由为192.168.1.1。

# ip address
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000link/ether 00:0c:29:a0:c2:ff brd ff:ff:ff:ff:ff:ffinet 192.168.1.109/24 brd 192.168.1.255 scope global dynamic ens32
#
# ip route
default via 192.168.1.1 dev ens32 proto dhcp src 192.168.1.109 metric 100
192.168.1.0/24 dev ens32 proto kernel scope link src 192.168.1.109

如下,增加一个到主机192.168.1.102的路由,网关设置为192.168.1.1。但是明显的192.168.1.102就在本地链路上,可直达,不需要经过192.168.1.1。

# ip route add 192.168.1.102/32 via 192.168.1.1
#
# ping 192.168.1.102
PING 192.168.1.102 (192.168.1.102) 56(84) bytes of data.
From 192.168.1.1: icmp_seq=1 Redirect Host(New nexthop: 192.168.1.102)
64 bytes from 192.168.1.102: icmp_seq=1 ttl=64 time=0.472 ms
^C
--- 192.168.1.102 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1023ms
rtt min/avg/max/mdev = 0.229/0.350/0.472/0.122 ms

网关192.168.1.1回复了重定向ICMP报文,之后的PING报文直接发送到192.168.1.102。

# ip -s route get 192.168.1.102
192.168.1.102 via 192.168.1.102 dev ens160 src 192.168.1.132 uid 1000 cache <redirected> expires 279sec users 2 age 19sec
# ip route show table cache
#
# ip route show cached

重定向发送

在生成路由项时,如果检测到报文的入口和出口设备相同,并且设备允许发送重定向报文,对于共享类型的链路,如果源地址和网关在同一链路上,设置重定向标志(IPSKB_DOREDIRECT)。

static int __mkroute_input(struct sk_buff *skb,const struct fib_result *res,struct in_device *in_dev,__be32 daddr, __be32 saddr, u32 tos)
{struct fib_nh_common *nhc = FIB_RES_NHC(*res);struct net_device *dev = nhc->nhc_dev;struct fib_nh_exception *fnhe;if (out_dev == in_dev && err && IN_DEV_TX_REDIRECTS(out_dev) &&skb->protocol == htons(ETH_P_IP)) {__be32 gw;gw = nhc->nhc_gw_family == AF_INET ? nhc->nhc_gw.ipv4 : 0;if (IN_DEV_SHARED_MEDIA(out_dev) ||inet_addr_onlink(out_dev, saddr, gw))IPCB(skb)->flags |= IPSKB_DOREDIRECT;

在转发函数中,发送ICMP重定向报文。

int ip_forward(struct sk_buff *skb)
{/**  We now generate an ICMP HOST REDIRECT giving the route*  we calculated.*/if (IPCB(skb)->flags & IPSKB_DOREDIRECT && !opt->srr &&!skb_sec_path(skb))ip_rt_send_redirect(skb);

重定向接收

对于code为ICMP_REDIR_HOST的ICMP重定向报文,如果其指定的新的网关(192.168.1.102)可达,邻居表存在。在路由存在的情况下,由函数update_or_create_fnhe更新或者创建一个exception项,超时时长设置为ip_rt_gc_timeout,默认为300秒,可通过PROC文件gc_timeout修改。

static void __ip_do_redirect(struct rtable *rt, struct sk_buff *skb, struct flowi4 *fl4, bool kill_route)
{__be32 new_gw = icmp_hdr(skb)->un.gateway;struct fib_result res;struct neighbour *n;n = __ipv4_neigh_lookup(rt->dst.dev, new_gw);if (!n)n = neigh_create(&arp_tbl, &new_gw, rt->dst.dev);if (!IS_ERR(n)) {if (!(n->nud_state & NUD_VALID)) {neigh_event_send(n, NULL);} else {if (fib_lookup(net, fl4, &res, 0) == 0) {struct fib_nh_common *nhc;fib_select_path(net, &res, fl4, skb);nhc = FIB_RES_NHC(res);update_or_create_fnhe(nhc, fl4->daddr, new_gw,0, false, jiffies + ip_rt_gc_timeout);}

对于下一跳exception存在的情况,更新网关值以及fnhe_expires超时值。并且更新相应的路由缓存,由函数fill_route_from_fnhe实现。

static void update_or_create_fnhe(struct fib_nh_common *nhc, __be32 daddr,__be32 gw, u32 pmtu, bool lock, unsigned long expires)
{struct fnhe_hash_bucket *hash;struct fib_nh_exception *fnhe;if (fnhe) {if (fnhe->fnhe_genid != genid)fnhe->fnhe_genid = genid;if (gw)fnhe->fnhe_gw = gw;fnhe->fnhe_expires = max(1UL, expires);/* Update all cached dsts too */rt = rcu_dereference(fnhe->fnhe_rth_input);if (rt)fill_route_from_fnhe(rt, fnhe);rt = rcu_dereference(fnhe->fnhe_rth_output);if (rt)fill_route_from_fnhe(rt, fnhe);} else {

否则,下一跳exception不存在的情况下,分配新的fnhe,初始化相应值,并且,如果之前存在入口或者出口路由缓存,将其设置为过期状态(DST_OBSOLETE_KILL)。

        if (depth > FNHE_RECLAIM_DEPTH)fnhe = fnhe_oldest(hash);else {fnhe = kzalloc(sizeof(*fnhe), GFP_ATOMIC);if (!fnhe)goto out_unlock;fnhe->fnhe_next = hash->chain;rcu_assign_pointer(hash->chain, fnhe);}fnhe->fnhe_genid = genid;fnhe->fnhe_daddr = daddr;fnhe->fnhe_gw = gw;fnhe->fnhe_pmtu = pmtu;fnhe->fnhe_mtu_locked = lock;fnhe->fnhe_expires = max(1UL, expires);/* Exception created; mark the cached routes for the nexthop* stale, so anyone caching it rechecks if this exception applies to them.*/rt = rcu_dereference(nhc->nhc_rth_input);if (rt)rt->dst.obsolete = DST_OBSOLETE_KILL;for_each_possible_cpu(i) {struct rtable __rcu **prt;prt = per_cpu_ptr(nhc->nhc_pcpu_rth_output, i);rt = rcu_dereference(*prt);if (rt)rt->dst.obsolete = DST_OBSOLETE_KILL;}}fnhe->fnhe_stamp = jiffies;

重定向生成的路由项设置RTCF_REDIRECTED标志。

static void fill_route_from_fnhe(struct rtable *rt, struct fib_nh_exception *fnhe)
{rt->rt_pmtu = fnhe->fnhe_pmtu;rt->rt_mtu_locked = fnhe->fnhe_mtu_locked;rt->dst.expires = fnhe->fnhe_expires;if (fnhe->fnhe_gw) {rt->rt_flags |= RTCF_REDIRECTED;rt->rt_uses_gateway = 1;rt->rt_gw_family = AF_INET;rt->rt_gw4 = fnhe->fnhe_gw;

cache信息

于路由缓存项中取得过期时间戳,如果其不为零,并且还没有到过期时刻,记算还有多次时间过期,否则将其设置为零。

static int rt_fill_info(struct net *net, __be32 dst, __be32 src,struct rtable *rt, u32 table_id, struct flowi4 *fl4,struct sk_buff *skb, u32 portid, u32 seq, unsigned int flags)
{r->rtm_flags    = (rt->rt_flags & ~0xFFFF) | RTM_F_CLONED;if (rt->rt_flags & RTCF_NOTIFY)r->rtm_flags |= RTM_F_NOTIFY;if (IPCB(skb)->flags & IPSKB_DOREDIRECT)r->rtm_flags |= RTCF_DOREDIRECT;expires = rt->dst.expires;if (expires) {unsigned long now = jiffies;if (time_before(now, expires))expires -= now;elseexpires = 0;}error = rt->dst.error;if (rtnl_put_cacheinfo(skb, &rt->dst, 0, expires, error) < 0)goto nla_put_failure;

成员变量rta_error为rt->dst.error的值,而rta_id固定为零。lastuse和__use分别表示此路由缓存最后一次使用时的时间戳和使用的次数。变量__refcnt为引用计数。rta_expires记录路由超时时长。

int rtnl_put_cacheinfo(struct sk_buff *skb, struct dst_entry *dst, u32 id,long expires, u32 error)
{   struct rta_cacheinfo ci = {.rta_error = error,.rta_id =  id,};      if (dst) {ci.rta_lastuse = jiffies_delta_to_clock_t(jiffies - dst->lastuse);ci.rta_used = dst->__use;ci.rta_clntref = atomic_read(&dst->__refcnt);}if (expires) {unsigned long clock;clock = jiffies_to_clock_t(abs(expires));clock = min_t(unsigned long, clock, INT_MAX);ci.rta_expires = (expires > 0) ? clock : -clock;}return nla_put(skb, RTA_CACHEINFO, sizeof(ci), &ci);

iproute2显示cache信息

在iproute2中由函数print_route显示路由信息。对于IPv4和IPv6的RTA_CACHEINFO属性,都由函数print_rta_cacheinfo显示。

int print_route(struct nlmsghdr *n, void *arg)
{if (r->rtm_family == AF_INET) {if (r->rtm_flags & RTM_F_CLONED)print_cache_flags(fp, r->rtm_flags);if (tb[RTA_CACHEINFO])print_rta_cacheinfo(fp, RTA_DATA(tb[RTA_CACHEINFO]));} else if (r->rtm_family == AF_INET6) {if (tb[RTA_CACHEINFO])print_rta_cacheinfo(fp, RTA_DATA(tb[RTA_CACHEINFO]));}

如下,简单的显示。

static void print_rta_cacheinfo(FILE *fp, const struct rta_cacheinfo *ci)
{   static int hz;if (!hz) hz = get_user_hz();if (ci->rta_expires != 0)print_int(PRINT_ANY, "expires","expires %dsec ", ci->rta_expires/hz);if (ci->rta_error != 0)print_uint(PRINT_ANY, "error","error %u ", ci->rta_error);if (show_stats) {if (ci->rta_clntref)print_uint(PRINT_ANY, "users","users %u ", ci->rta_clntref);if (ci->rta_used != 0)print_uint(PRINT_ANY, "used","used %u ", ci->rta_used);if (ci->rta_lastuse != 0) print_uint(PRINT_ANY, "age","age %usec ", ci->rta_lastuse/hz);}if (ci->rta_id)print_0xhex(PRINT_ANY, "ipid","ipid 0x%04llx ", ci->rta_id);if (ci->rta_ts || ci->rta_tsage) {print_0xhex(PRINT_ANY, "ts","ts 0x%llx", ci->rta_ts);print_uint(PRINT_ANY, "tsage",

超时处理

下一跳exception默认使用超时时长为300秒(RT_GC_TIMEOUT)。

 118 #define RT_GC_TIMEOUT (300*HZ)130 static int ip_rt_gc_timeout __read_mostly   = RT_GC_TIMEOUT;

在函数find_exception中,如果检测到exception的超时时间(fnhe_expires)已过,由函数ip_del_fnhe删除exception。

static struct fib_nh_exception *find_exception(struct fib_nh_common *nhc, __be32 daddr)
{struct fnhe_hash_bucket *hash = rcu_dereference(nhc->nhc_exceptions);struct fib_nh_exception *fnhe;u32 hval;if (!hash)return NULL;hval = fnhe_hashfun(daddr);for (fnhe = rcu_dereference(hash[hval].chain); fnhe;fnhe = rcu_dereference(fnhe->fnhe_next)) {if (fnhe->fnhe_daddr == daddr) {if (fnhe->fnhe_expires && time_after(jiffies, fnhe->fnhe_expires)) {ip_del_fnhe(nhc, daddr);break;}return fnhe;

遍历exceptions的链表,找到目的地址相同的项,由函数fnhe_flush_routes释放其对应的路由缓存,随后释放exceptions自身。

static void ip_del_fnhe(struct fib_nh_common *nhc, __be32 daddr)
{struct fnhe_hash_bucket *hash;struct fib_nh_exception *fnhe, __rcu **fnhe_p;u32 hval = fnhe_hashfun(daddr);spin_lock_bh(&fnhe_lock);hash = rcu_dereference_protected(nhc->nhc_exceptions, lockdep_is_held(&fnhe_lock));hash += hval;fnhe_p = &hash->chain;fnhe = rcu_dereference_protected(*fnhe_p, lockdep_is_held(&fnhe_lock));while (fnhe) {if (fnhe->fnhe_daddr == daddr) {rcu_assign_pointer(*fnhe_p, rcu_dereference_protected(fnhe->fnhe_next, lockdep_is_held(&fnhe_lock)));fnhe->fnhe_daddr = 0;fnhe_flush_routes(fnhe);kfree_rcu(fnhe, rcu);break;}fnhe_p = &fnhe->fnhe_next;fnhe = rcu_dereference_protected(fnhe->fnhe_next, lockdep_is_held(&fnhe_lock));

内核版本 5.10

IPv4路由exception缓存相关推荐

  1. 40.微信小程序(API--基础、路由、缓存、媒体)

    微信小程序(API–基础.路由.缓存.媒体) 大纲 (1)基础 API (2)路由API (3)数据缓存API (4)媒体API (1)基础API-系统 获取系统信息 wx.getSystemInfo ...

  2. IPv4路由cache统计信息

    内核定义如下的每处理器结构rt_cache_stat记录路由缓存信息. struct rt_cache_stat {unsigned int in_slow_tot;unsigned int in_s ...

  3. 若依路由刷新缓存页面 + router.push

    现象:带参数打开同一个页面的时候,页面会一直显示上一个页面的结果 this.$router.push({path: "/xmxx/xmxx/gzrz",query: {gcmc: ...

  4. vue 路由页面缓存

    VUE框架真的很神奇,神奇到好多坑你都要填 这次说说VUE里缓存页面的问题 现在前端做APP的话,估计很少人会直接去用VUE cli来写,因为那样特别麻烦,你得配置很多东西.填很多坑!现在如果要求前端 ...

  5. vue 使用keep-alive 三级以上的路由无法缓存解决方案

    1.include 包含页面组件name的这些组件页面,会被缓存起来 2.exclude 除了这些name以外的页面组件,会被缓存起来 3.没有include或者exclude属性,表示所有的路由组件 ...

  6. IPv4路由下一跳合法性检测

    函数fib_check_nh检测下一跳的合法性. int fib_check_nh(struct net *net, struct fib_nh *nh, u32 table, u8 scope, s ...

  7. vue切换路由页面数据缓存_Vue-Router实现前端页面缓存

    一.使用情景 在使用Vue开发单页面应用时,我们通常会使用Vue-Router进行页面导航,Vue-Router在进行路由切换的时候,页面是会重新加载,对应的生命周期函数也会再次执行一遍,但是在有些业 ...

  8. vue3缓存页面keep-alive+路由统一处理

    一.前言 当使用路由跳转到其他页面的时候,要求缓存当前页面,比如列表页面跳转到详情页面,需要缓存列表内容,并且保存滚动条位置,也有时候需要缓存的页面里面有部分内容不缓存,总之各种情况,下面就列举其中一 ...

  9. Linux 3.10内核锁瓶颈描述以及解决-IPv6路由cache的性能缺陷

    大量线程争抢锁导致CPU自旋乃至内核hang住的例子层出不穷. 我曾经解过很多关于这方面的内核bug: nat模块复制tso结构不完全导致SSL握手弹证书慢. IP路由neighbour系统对poin ...

最新文章

  1. Laravel Dcat Admin 安装
  2. 往有序单循环链表的插入元素使原链表依旧有序
  3. easyUI 添加CheckBox选择到DataGrid
  4. [SDOI2008]Cave 洞穴勘测
  5. matlab中gama,matlab积分结果中的gamma()函数参数问题,急求解答!!!
  6. zabbix监控进程的CPU和内存占用量
  7. Vue关于axios跨域问题的解决
  8. Java学习笔记1:Java中有关print、println、printf的用法和区别
  9. 企业转向云服务的速度比企业的思想更快
  10. 百度AI市场MYNT EYE小觅双目摄像机开箱试用全记录
  11. Word中大括号内公式左对齐
  12. 《图像处理、分析与机器视觉》(第4版)阅读笔记——第五章 图像预处理
  13. 使用ansible批量修改主机名后/etc/hosts文件不能被正确修改的修复方法
  14. 208. Implement Trie (Prefix Tree)(Leetcode每日一题-2021.04.14)
  15. 全文搜索引擎ElasticSearch
  16. 2023年上学期学习计划
  17. Kubernetes调度
  18. vue传值给子页面html,vue.js如何父传子?
  19. Weisfeiler-Lehman(WL)算法和WL Test的学习笔记
  20. top X 好听的英文歌

热门文章

  1. hadoop历史背景hdfs分布式文件系统hadoop的集群模式单机模式伪分布
  2. 北大博士生计算机学院任教,清华、北大博士为何到深圳中学任教?让他们自己回答你...
  3. 数控车削加工 的 过程能力分析
  4. oracle文本导入器只有1列,PL/SQL文本导入器使用步骤
  5. linux 查看动态库基址,一种动态call地址查找及使用方法 -- QQ游戏斗地主角色版-喊话call定位技巧...
  6. 笔记本进水事件处理办法
  7. autoCAD2010 块属性
  8. 《设计模式》王争 学习笔记
  9. 侠客C#营销软件开发
  10. linux cpufreq framework(4)_cpufreq governor