1 /* 2 * net/core/dst_cache.c - dst entry cache 3 * 4 * Copyright (c) 2016 Paolo Abeni <pabeni@redhat.com> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 */ 11 12 #include <linux/kernel.h> 13 #include <linux/percpu.h> 14 #include <net/dst_cache.h> 15 #include <net/route.h> 16 #if IS_ENABLED(CONFIG_IPV6) 17 #include <net/ip6_fib.h> 18 #endif 19 #include <uapi/linux/in.h> 20 21 struct dst_cache_pcpu { 22 unsigned long refresh_ts; 23 struct dst_entry *dst; 24 u32 cookie; 25 union { 26 struct in_addr in_saddr; 27 struct in6_addr in6_saddr; 28 }; 29 }; 30 31 static void dst_cache_per_cpu_dst_set(struct dst_cache_pcpu *dst_cache, 32 struct dst_entry *dst, u32 cookie) 33 { 34 dst_release(dst_cache->dst); 35 if (dst) 36 dst_hold(dst); 37 38 dst_cache->cookie = cookie; 39 dst_cache->dst = dst; 40 } 41 42 static struct dst_entry *dst_cache_per_cpu_get(struct dst_cache *dst_cache, 43 struct dst_cache_pcpu *idst) 44 { 45 struct dst_entry *dst; 46 47 dst = idst->dst; 48 if (!dst) 49 goto fail; 50 51 /* the cache already hold a dst reference; it can't go away */ 52 dst_hold(dst); 53 54 if (unlikely(!time_after(idst->refresh_ts, dst_cache->reset_ts) || 55 (dst->obsolete && !dst->ops->check(dst, idst->cookie)))) { 56 dst_cache_per_cpu_dst_set(idst, NULL, 0); 57 dst_release(dst); 58 goto fail; 59 } 60 return dst; 61 62 fail: 63 idst->refresh_ts = jiffies; 64 return NULL; 65 } 66 67 struct dst_entry *dst_cache_get(struct dst_cache *dst_cache) 68 { 69 if (!dst_cache->cache) 70 return NULL; 71 72 return dst_cache_per_cpu_get(dst_cache, this_cpu_ptr(dst_cache->cache)); 73 } 74 EXPORT_SYMBOL_GPL(dst_cache_get); 75 76 struct rtable *dst_cache_get_ip4(struct dst_cache *dst_cache, __be32 *saddr) 77 { 78 struct dst_cache_pcpu *idst; 79 struct dst_entry *dst; 80 81 if (!dst_cache->cache) 82 return NULL; 83 84 idst = this_cpu_ptr(dst_cache->cache); 85 dst = dst_cache_per_cpu_get(dst_cache, idst); 86 if (!dst) 87 return NULL; 88 89 *saddr = idst->in_saddr.s_addr; 90 return container_of(dst, struct rtable, dst); 91 } 92 EXPORT_SYMBOL_GPL(dst_cache_get_ip4); 93 94 void dst_cache_set_ip4(struct dst_cache *dst_cache, struct dst_entry *dst, 95 __be32 saddr) 96 { 97 struct dst_cache_pcpu *idst; 98 99 if (!dst_cache->cache) 100 return; 101 102 idst = this_cpu_ptr(dst_cache->cache); 103 dst_cache_per_cpu_dst_set(idst, dst, 0); 104 idst->in_saddr.s_addr = saddr; 105 } 106 EXPORT_SYMBOL_GPL(dst_cache_set_ip4); 107 108 #if IS_ENABLED(CONFIG_IPV6) 109 void dst_cache_set_ip6(struct dst_cache *dst_cache, struct dst_entry *dst, 110 const struct in6_addr *saddr) 111 { 112 struct dst_cache_pcpu *idst; 113 114 if (!dst_cache->cache) 115 return; 116 117 idst = this_cpu_ptr(dst_cache->cache); 118 dst_cache_per_cpu_dst_set(this_cpu_ptr(dst_cache->cache), dst, 119 rt6_get_cookie((struct rt6_info *)dst)); 120 idst->in6_saddr = *saddr; 121 } 122 EXPORT_SYMBOL_GPL(dst_cache_set_ip6); 123 124 struct dst_entry *dst_cache_get_ip6(struct dst_cache *dst_cache, 125 struct in6_addr *saddr) 126 { 127 struct dst_cache_pcpu *idst; 128 struct dst_entry *dst; 129 130 if (!dst_cache->cache) 131 return NULL; 132 133 idst = this_cpu_ptr(dst_cache->cache); 134 dst = dst_cache_per_cpu_get(dst_cache, idst); 135 if (!dst) 136 return NULL; 137 138 *saddr = idst->in6_saddr; 139 return dst; 140 } 141 EXPORT_SYMBOL_GPL(dst_cache_get_ip6); 142 #endif 143 144 int dst_cache_init(struct dst_cache *dst_cache, gfp_t gfp) 145 { 146 dst_cache->cache = alloc_percpu_gfp(struct dst_cache_pcpu, 147 gfp | __GFP_ZERO); 148 if (!dst_cache->cache) 149 return -ENOMEM; 150 151 dst_cache_reset(dst_cache); 152 return 0; 153 } 154 EXPORT_SYMBOL_GPL(dst_cache_init); 155 156 void dst_cache_destroy(struct dst_cache *dst_cache) 157 { 158 int i; 159 160 if (!dst_cache->cache) 161 return; 162 163 for_each_possible_cpu(i) 164 dst_release(per_cpu_ptr(dst_cache->cache, i)->dst); 165 166 free_percpu(dst_cache->cache); 167 } 168 EXPORT_SYMBOL_GPL(dst_cache_destroy); 169