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 */ 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 */ 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 */ 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 */ 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 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 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 */ 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 */ 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