// SPDX-License-Identifier: GPL-2.0-or-later 
 | 
/* 
 | 
 * IPVS:        Round-Robin Scheduling module 
 | 
 * 
 | 
 * Authors:     Wensong Zhang <wensong@linuxvirtualserver.org> 
 | 
 *              Peter Kese <peter.kese@ijs.si> 
 | 
 * 
 | 
 * Fixes/Changes: 
 | 
 *     Wensong Zhang            :     changed the ip_vs_rr_schedule to return dest 
 | 
 *     Julian Anastasov         :     fixed the NULL pointer access bug in debugging 
 | 
 *     Wensong Zhang            :     changed some comestics things for debugging 
 | 
 *     Wensong Zhang            :     changed for the d-linked destination list 
 | 
 *     Wensong Zhang            :     added the ip_vs_rr_update_svc 
 | 
 *     Wensong Zhang            :     added any dest with weight=0 is quiesced 
 | 
 */ 
 | 
  
 | 
#define KMSG_COMPONENT "IPVS" 
 | 
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt 
 | 
  
 | 
#include <linux/module.h> 
 | 
#include <linux/kernel.h> 
 | 
  
 | 
#include <net/ip_vs.h> 
 | 
  
 | 
  
 | 
static int ip_vs_rr_init_svc(struct ip_vs_service *svc) 
 | 
{ 
 | 
    svc->sched_data = &svc->destinations; 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
  
 | 
static int ip_vs_rr_del_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest) 
 | 
{ 
 | 
    struct list_head *p; 
 | 
  
 | 
    spin_lock_bh(&svc->sched_lock); 
 | 
    p = (struct list_head *) svc->sched_data; 
 | 
    /* dest is already unlinked, so p->prev is not valid but 
 | 
     * p->next is valid, use it to reach previous entry. 
 | 
     */ 
 | 
    if (p == &dest->n_list) 
 | 
        svc->sched_data = p->next->prev; 
 | 
    spin_unlock_bh(&svc->sched_lock); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
  
 | 
/* 
 | 
 * Round-Robin Scheduling 
 | 
 */ 
 | 
static struct ip_vs_dest * 
 | 
ip_vs_rr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, 
 | 
          struct ip_vs_iphdr *iph) 
 | 
{ 
 | 
    struct list_head *p; 
 | 
    struct ip_vs_dest *dest, *last; 
 | 
    int pass = 0; 
 | 
  
 | 
    IP_VS_DBG(6, "%s(): Scheduling...\n", __func__); 
 | 
  
 | 
    spin_lock_bh(&svc->sched_lock); 
 | 
    p = (struct list_head *) svc->sched_data; 
 | 
    last = dest = list_entry(p, struct ip_vs_dest, n_list); 
 | 
  
 | 
    do { 
 | 
        list_for_each_entry_continue_rcu(dest, 
 | 
                         &svc->destinations, 
 | 
                         n_list) { 
 | 
            if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) && 
 | 
                atomic_read(&dest->weight) > 0) 
 | 
                /* HIT */ 
 | 
                goto out; 
 | 
            if (dest == last) 
 | 
                goto stop; 
 | 
        } 
 | 
        pass++; 
 | 
        /* Previous dest could be unlinked, do not loop forever. 
 | 
         * If we stay at head there is no need for 2nd pass. 
 | 
         */ 
 | 
    } while (pass < 2 && p != &svc->destinations); 
 | 
  
 | 
stop: 
 | 
    spin_unlock_bh(&svc->sched_lock); 
 | 
    ip_vs_scheduler_err(svc, "no destination available"); 
 | 
    return NULL; 
 | 
  
 | 
  out: 
 | 
    svc->sched_data = &dest->n_list; 
 | 
    spin_unlock_bh(&svc->sched_lock); 
 | 
    IP_VS_DBG_BUF(6, "RR: server %s:%u " 
 | 
              "activeconns %d refcnt %d weight %d\n", 
 | 
              IP_VS_DBG_ADDR(dest->af, &dest->addr), ntohs(dest->port), 
 | 
              atomic_read(&dest->activeconns), 
 | 
              refcount_read(&dest->refcnt), atomic_read(&dest->weight)); 
 | 
  
 | 
    return dest; 
 | 
} 
 | 
  
 | 
  
 | 
static struct ip_vs_scheduler ip_vs_rr_scheduler = { 
 | 
    .name =            "rr",            /* name */ 
 | 
    .refcnt =        ATOMIC_INIT(0), 
 | 
    .module =        THIS_MODULE, 
 | 
    .n_list =        LIST_HEAD_INIT(ip_vs_rr_scheduler.n_list), 
 | 
    .init_service =        ip_vs_rr_init_svc, 
 | 
    .add_dest =        NULL, 
 | 
    .del_dest =        ip_vs_rr_del_dest, 
 | 
    .schedule =        ip_vs_rr_schedule, 
 | 
}; 
 | 
  
 | 
static int __init ip_vs_rr_init(void) 
 | 
{ 
 | 
    return register_ip_vs_scheduler(&ip_vs_rr_scheduler); 
 | 
} 
 | 
  
 | 
static void __exit ip_vs_rr_cleanup(void) 
 | 
{ 
 | 
    unregister_ip_vs_scheduler(&ip_vs_rr_scheduler); 
 | 
    synchronize_rcu(); 
 | 
} 
 | 
  
 | 
module_init(ip_vs_rr_init); 
 | 
module_exit(ip_vs_rr_cleanup); 
 | 
MODULE_LICENSE("GPL"); 
 |