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 27 EXPORT_SYMBOL(ip_vs_scheduler_err); 28 /* 29 * IPVS scheduler list 30 */ 31 static LIST_HEAD(ip_vs_schedulers); 32 33 /* semaphore for schedulers */ 34 static DEFINE_MUTEX(ip_vs_sched_mutex); 35 36 37 /* 38 * Bind a service with a scheduler 39 */ 40 int 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 */ 60 void 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 */ 79 static 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 */ 113 struct 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 133 void 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 143 void 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 */ 167 int 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 */ 223 int 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