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 
dst_cache_per_cpu_dst_set(struct dst_cache_pcpu * dst_cache,struct dst_entry * dst,u32 cookie)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 
dst_cache_per_cpu_get(struct dst_cache * dst_cache,struct dst_cache_pcpu * idst)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, dst_cache->reset_ts) ||
51 		     (dst->obsolete && !dst->ops->check(dst, idst->cookie)))) {
52 		dst_cache_per_cpu_dst_set(idst, NULL, 0);
53 		dst_release(dst);
54 		goto fail;
55 	}
56 	return dst;
57 
58 fail:
59 	idst->refresh_ts = jiffies;
60 	return NULL;
61 }
62 
dst_cache_get(struct dst_cache * dst_cache)63 struct dst_entry *dst_cache_get(struct dst_cache *dst_cache)
64 {
65 	if (!dst_cache->cache)
66 		return NULL;
67 
68 	return dst_cache_per_cpu_get(dst_cache, this_cpu_ptr(dst_cache->cache));
69 }
70 EXPORT_SYMBOL_GPL(dst_cache_get);
71 
dst_cache_get_ip4(struct dst_cache * dst_cache,__be32 * saddr)72 struct rtable *dst_cache_get_ip4(struct dst_cache *dst_cache, __be32 *saddr)
73 {
74 	struct dst_cache_pcpu *idst;
75 	struct dst_entry *dst;
76 
77 	if (!dst_cache->cache)
78 		return NULL;
79 
80 	idst = this_cpu_ptr(dst_cache->cache);
81 	dst = dst_cache_per_cpu_get(dst_cache, idst);
82 	if (!dst)
83 		return NULL;
84 
85 	*saddr = idst->in_saddr.s_addr;
86 	return container_of(dst, struct rtable, dst);
87 }
88 EXPORT_SYMBOL_GPL(dst_cache_get_ip4);
89 
dst_cache_set_ip4(struct dst_cache * dst_cache,struct dst_entry * dst,__be32 saddr)90 void dst_cache_set_ip4(struct dst_cache *dst_cache, struct dst_entry *dst,
91 		       __be32 saddr)
92 {
93 	struct dst_cache_pcpu *idst;
94 
95 	if (!dst_cache->cache)
96 		return;
97 
98 	idst = this_cpu_ptr(dst_cache->cache);
99 	dst_cache_per_cpu_dst_set(idst, dst, 0);
100 	idst->in_saddr.s_addr = saddr;
101 }
102 EXPORT_SYMBOL_GPL(dst_cache_set_ip4);
103 
104 #if IS_ENABLED(CONFIG_IPV6)
dst_cache_set_ip6(struct dst_cache * dst_cache,struct dst_entry * dst,const struct in6_addr * saddr)105 void dst_cache_set_ip6(struct dst_cache *dst_cache, struct dst_entry *dst,
106 		       const struct in6_addr *saddr)
107 {
108 	struct dst_cache_pcpu *idst;
109 
110 	if (!dst_cache->cache)
111 		return;
112 
113 	idst = this_cpu_ptr(dst_cache->cache);
114 	dst_cache_per_cpu_dst_set(this_cpu_ptr(dst_cache->cache), dst,
115 				  rt6_get_cookie((struct rt6_info *)dst));
116 	idst->in6_saddr = *saddr;
117 }
118 EXPORT_SYMBOL_GPL(dst_cache_set_ip6);
119 
dst_cache_get_ip6(struct dst_cache * dst_cache,struct in6_addr * saddr)120 struct dst_entry *dst_cache_get_ip6(struct dst_cache *dst_cache,
121 				    struct in6_addr *saddr)
122 {
123 	struct dst_cache_pcpu *idst;
124 	struct dst_entry *dst;
125 
126 	if (!dst_cache->cache)
127 		return NULL;
128 
129 	idst = this_cpu_ptr(dst_cache->cache);
130 	dst = dst_cache_per_cpu_get(dst_cache, idst);
131 	if (!dst)
132 		return NULL;
133 
134 	*saddr = idst->in6_saddr;
135 	return dst;
136 }
137 EXPORT_SYMBOL_GPL(dst_cache_get_ip6);
138 #endif
139 
dst_cache_init(struct dst_cache * dst_cache,gfp_t gfp)140 int dst_cache_init(struct dst_cache *dst_cache, gfp_t gfp)
141 {
142 	dst_cache->cache = alloc_percpu_gfp(struct dst_cache_pcpu,
143 					    gfp | __GFP_ZERO);
144 	if (!dst_cache->cache)
145 		return -ENOMEM;
146 
147 	dst_cache_reset(dst_cache);
148 	return 0;
149 }
150 EXPORT_SYMBOL_GPL(dst_cache_init);
151 
dst_cache_destroy(struct dst_cache * dst_cache)152 void dst_cache_destroy(struct dst_cache *dst_cache)
153 {
154 	int i;
155 
156 	if (!dst_cache->cache)
157 		return;
158 
159 	for_each_possible_cpu(i)
160 		dst_release(per_cpu_ptr(dst_cache->cache, i)->dst);
161 
162 	free_percpu(dst_cache->cache);
163 }
164 EXPORT_SYMBOL_GPL(dst_cache_destroy);
165 
dst_cache_reset_now(struct dst_cache * dst_cache)166 void dst_cache_reset_now(struct dst_cache *dst_cache)
167 {
168 	int i;
169 
170 	if (!dst_cache->cache)
171 		return;
172 
173 	dst_cache->reset_ts = jiffies;
174 	for_each_possible_cpu(i) {
175 		struct dst_cache_pcpu *idst = per_cpu_ptr(dst_cache->cache, i);
176 		struct dst_entry *dst = idst->dst;
177 
178 		idst->cookie = 0;
179 		idst->dst = NULL;
180 		dst_release(dst);
181 	}
182 }
183 EXPORT_SYMBOL_GPL(dst_cache_reset_now);
184