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