1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * IPVS An implementation of the IP virtual server support for the
4 * LINUX operating system. IPVS is now implemented as a module
5 * over the Netfilter framework. IPVS can be used to build a
6 * high-performance and highly available server based on a
7 * cluster of servers.
8 *
9 * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
10 * Peter Kese <peter.kese@ijs.si>
11 *
12 * Changes:
13 */
14
15 #define pr_fmt(fmt) "IPVS: " fmt
16
17 #include <linux/module.h>
18 #include <linux/spinlock.h>
19 #include <linux/interrupt.h>
20 #include <asm/string.h>
21 #include <linux/kmod.h>
22 #include <linux/sysctl.h>
23
24 #include <net/ip_vs.h>
25
26 EXPORT_SYMBOL(ip_vs_scheduler_err);
27 /*
28 * IPVS scheduler list
29 */
30 static LIST_HEAD(ip_vs_schedulers);
31
32 /* semaphore for schedulers */
33 static DEFINE_MUTEX(ip_vs_sched_mutex);
34
35
36 /*
37 * Bind a service with a scheduler
38 */
ip_vs_bind_scheduler(struct ip_vs_service * svc,struct ip_vs_scheduler * scheduler)39 int ip_vs_bind_scheduler(struct ip_vs_service *svc,
40 struct ip_vs_scheduler *scheduler)
41 {
42 int ret;
43
44 if (scheduler->init_service) {
45 ret = scheduler->init_service(svc);
46 if (ret) {
47 pr_err("%s(): init error\n", __func__);
48 return ret;
49 }
50 }
51 rcu_assign_pointer(svc->scheduler, scheduler);
52 return 0;
53 }
54
55
56 /*
57 * Unbind a service with its scheduler
58 */
ip_vs_unbind_scheduler(struct ip_vs_service * svc,struct ip_vs_scheduler * sched)59 void ip_vs_unbind_scheduler(struct ip_vs_service *svc,
60 struct ip_vs_scheduler *sched)
61 {
62 struct ip_vs_scheduler *cur_sched;
63
64 cur_sched = rcu_dereference_protected(svc->scheduler, 1);
65 /* This check proves that old 'sched' was installed */
66 if (!cur_sched)
67 return;
68
69 if (sched->done_service)
70 sched->done_service(svc);
71 /* svc->scheduler can be set to NULL only by caller */
72 }
73
74
75 /*
76 * Get scheduler in the scheduler list by name
77 */
ip_vs_sched_getbyname(const char * sched_name)78 static struct ip_vs_scheduler *ip_vs_sched_getbyname(const char *sched_name)
79 {
80 struct ip_vs_scheduler *sched;
81
82 IP_VS_DBG(2, "%s(): sched_name \"%s\"\n", __func__, sched_name);
83
84 mutex_lock(&ip_vs_sched_mutex);
85
86 list_for_each_entry(sched, &ip_vs_schedulers, n_list) {
87 /*
88 * Test and get the modules atomically
89 */
90 if (sched->module && !try_module_get(sched->module)) {
91 /*
92 * This scheduler is just deleted
93 */
94 continue;
95 }
96 if (strcmp(sched_name, sched->name)==0) {
97 /* HIT */
98 mutex_unlock(&ip_vs_sched_mutex);
99 return sched;
100 }
101 module_put(sched->module);
102 }
103
104 mutex_unlock(&ip_vs_sched_mutex);
105 return NULL;
106 }
107
108
109 /*
110 * Lookup scheduler and try to load it if it doesn't exist
111 */
ip_vs_scheduler_get(const char * sched_name)112 struct ip_vs_scheduler *ip_vs_scheduler_get(const char *sched_name)
113 {
114 struct ip_vs_scheduler *sched;
115
116 /*
117 * Search for the scheduler by sched_name
118 */
119 sched = ip_vs_sched_getbyname(sched_name);
120
121 /*
122 * If scheduler not found, load the module and search again
123 */
124 if (sched == NULL) {
125 request_module("ip_vs_%s", sched_name);
126 sched = ip_vs_sched_getbyname(sched_name);
127 }
128
129 return sched;
130 }
131
ip_vs_scheduler_put(struct ip_vs_scheduler * scheduler)132 void ip_vs_scheduler_put(struct ip_vs_scheduler *scheduler)
133 {
134 if (scheduler)
135 module_put(scheduler->module);
136 }
137
138 /*
139 * Common error output helper for schedulers
140 */
141
ip_vs_scheduler_err(struct ip_vs_service * svc,const char * msg)142 void ip_vs_scheduler_err(struct ip_vs_service *svc, const char *msg)
143 {
144 struct ip_vs_scheduler *sched = rcu_dereference(svc->scheduler);
145 char *sched_name = sched ? sched->name : "none";
146
147 if (svc->fwmark) {
148 IP_VS_ERR_RL("%s: FWM %u 0x%08X - %s\n",
149 sched_name, svc->fwmark, svc->fwmark, msg);
150 #ifdef CONFIG_IP_VS_IPV6
151 } else if (svc->af == AF_INET6) {
152 IP_VS_ERR_RL("%s: %s [%pI6c]:%d - %s\n",
153 sched_name, ip_vs_proto_name(svc->protocol),
154 &svc->addr.in6, ntohs(svc->port), msg);
155 #endif
156 } else {
157 IP_VS_ERR_RL("%s: %s %pI4:%d - %s\n",
158 sched_name, ip_vs_proto_name(svc->protocol),
159 &svc->addr.ip, ntohs(svc->port), msg);
160 }
161 }
162
163 /*
164 * Register a scheduler in the scheduler list
165 */
register_ip_vs_scheduler(struct ip_vs_scheduler * scheduler)166 int register_ip_vs_scheduler(struct ip_vs_scheduler *scheduler)
167 {
168 struct ip_vs_scheduler *sched;
169
170 if (!scheduler) {
171 pr_err("%s(): NULL arg\n", __func__);
172 return -EINVAL;
173 }
174
175 if (!scheduler->name) {
176 pr_err("%s(): NULL scheduler_name\n", __func__);
177 return -EINVAL;
178 }
179
180 /* increase the module use count */
181 if (!ip_vs_use_count_inc())
182 return -ENOENT;
183
184 mutex_lock(&ip_vs_sched_mutex);
185
186 if (!list_empty(&scheduler->n_list)) {
187 mutex_unlock(&ip_vs_sched_mutex);
188 ip_vs_use_count_dec();
189 pr_err("%s(): [%s] scheduler already linked\n",
190 __func__, scheduler->name);
191 return -EINVAL;
192 }
193
194 /*
195 * Make sure that the scheduler with this name doesn't exist
196 * in the scheduler list.
197 */
198 list_for_each_entry(sched, &ip_vs_schedulers, n_list) {
199 if (strcmp(scheduler->name, sched->name) == 0) {
200 mutex_unlock(&ip_vs_sched_mutex);
201 ip_vs_use_count_dec();
202 pr_err("%s(): [%s] scheduler already existed "
203 "in the system\n", __func__, scheduler->name);
204 return -EINVAL;
205 }
206 }
207 /*
208 * Add it into the d-linked scheduler list
209 */
210 list_add(&scheduler->n_list, &ip_vs_schedulers);
211 mutex_unlock(&ip_vs_sched_mutex);
212
213 pr_info("[%s] scheduler registered.\n", scheduler->name);
214
215 return 0;
216 }
217
218
219 /*
220 * Unregister a scheduler from the scheduler list
221 */
unregister_ip_vs_scheduler(struct ip_vs_scheduler * scheduler)222 int unregister_ip_vs_scheduler(struct ip_vs_scheduler *scheduler)
223 {
224 if (!scheduler) {
225 pr_err("%s(): NULL arg\n", __func__);
226 return -EINVAL;
227 }
228
229 mutex_lock(&ip_vs_sched_mutex);
230 if (list_empty(&scheduler->n_list)) {
231 mutex_unlock(&ip_vs_sched_mutex);
232 pr_err("%s(): [%s] scheduler is not in the list. failed\n",
233 __func__, scheduler->name);
234 return -EINVAL;
235 }
236
237 /*
238 * Remove it from the d-linked scheduler list
239 */
240 list_del(&scheduler->n_list);
241 mutex_unlock(&ip_vs_sched_mutex);
242
243 /* decrease the module use count */
244 ip_vs_use_count_dec();
245
246 pr_info("[%s] scheduler unregistered.\n", scheduler->name);
247
248 return 0;
249 }
250