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 ip_vs_use_count_inc(); 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