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