IPVS真实服务器

先看一下与真实服务器相关的两个部分的初始化,一是在全局初始化函数ip_vs_init中调用的ip_vs_control_init函数;另外一个是在网络命名空间中的初始化函数ip_vs_control_net_init。

static int __init ip_vs_init(void)
{
    ret = ip_vs_control_init();
    ret = register_pernet_subsys(&ipvs_core_ops);   /* Alloc ip_vs struct */
}

static struct pernet_operations ipvs_core_ops = {
    .init = __ip_vs_init,
    .exit = __ip_vs_cleanup,
    .id   = &ip_vs_net_id,
    .size = sizeof(struct netns_ipvs),
};
static int __net_init __ip_vs_init(struct net *net)
{   
    struct netns_ipvs *ipvs;
    ipvs = net_generic(net, ip_vs_net_id);
        
    if (ip_vs_control_net_init(ipvs) < 0)
        goto control_fail;
}

在全局初始过程中,ip_vs_control_init函数注册了网络设备的事件通知结构ip_vs_dst_notifier,其事件处理函数为ip_vs_dst_event。

int __init ip_vs_control_init(void)
{
    ret = register_netdevice_notifier(&ip_vs_dst_notifier);
}
static struct notifier_block ip_vs_dst_notifier = {
    .notifier_call = ip_vs_dst_event,
};

函数ip_vs_dst_event目前仅处理网络设备的NETDEV_DOWN事件。遍历ip_vs_svc_table和ip_vs_svc_fwm_table两个全局虚拟服务链表数组,在循环内部遍历每个虚拟服务所关联的真实服务器链表,移除缓存在真实服务器结构中与发生NETDEV_DOWN事件的网络设备对应的路由缓存。以上移除操作由函数ip_vs_forget_dev完成。另外,对于连接在ipvs网络命名空间的dest_trash链表中的已删除的真实服务器结构,同样执行缓存删除操作。

static int ip_vs_dst_event(struct notifier_block *this, unsigned long event, void *ptr)
{
    if (event != NETDEV_DOWN || !ipvs)
        return NOTIFY_DONE;

    for (idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) {
        hlist_for_each_entry(svc, &ip_vs_svc_table[idx], s_list) {
            if (svc->ipvs == ipvs) {
                list_for_each_entry(dest, &svc->destinations, n_list) {
                    ip_vs_forget_dev(dest, dev);
                }
            }
        }
        hlist_for_each_entry(svc, &ip_vs_svc_fwm_table[idx], f_list) {
            if (svc->ipvs == ipvs) {
                list_for_each_entry(dest, &svc->destinations, n_list) {
                    ip_vs_forget_dev(dest, dev);
                }
            }

        }
    }
    list_for_each_entry(dest, &ipvs->dest_trash, t_list)
        ip_vs_forget_dev(dest, dev);

在IPVS网络命名空间初始化函数ip_vs_control_net_init中,初始化了一个命名空间内部的真实服务器链表数组,数组的长度为IP_VS_RTAB_SIZE(16)。并且初始化了一个用于回收真实服务器结构的链表dest_trash,以及回收定时器,其超时时间为60秒,用于超时之后删除dest_trash中的真实服务器结构,由函数ip_vs_dest_trash_expire实现。删除的真实服务器结构首先是存放到了ipvs网络命名空间的成员dest_trash链表中。

int __net_init ip_vs_control_net_init(struct netns_ipvs *ipvs)
{
    /* Initialize rs_table */
    for (idx = 0; idx < IP_VS_RTAB_SIZE; idx++)
        INIT_HLIST_HEAD(&ipvs->rs_table[idx]);

    INIT_LIST_HEAD(&ipvs->dest_trash);
    spin_lock_init(&ipvs->dest_trash_lock);
    timer_setup(&ipvs->dest_trash_timer, ip_vs_dest_trash_expire, 0);

添加真实服务器

以下的命令添加真实服务器,在添加真实服务器此之前,必须先创建虚拟服务(第一条命令)。

 ipvsadm -A -t 207.175.44.110:80 -s rr
 ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.1:80 -m

函数ip_vs_add_dest完成真实服务器的添加。其使用函数ip_vs_lookup_dest判断要添加的真实服务器是否已存在,实现为在当前虚拟服务svc结构体的destinations链表中遍历,查看是由存在匹配以下三个参数的元素:协议族、目的地址和目的端口号。

随后,在ipvs网络命名空间的回收链表dest_trash中进行查找,此链表中保存了已经从虚拟服务中删除的真实服务器结构,但是这些结构可能仍然被ipvs连接所使用,如果匹配,将匹配项从dest_trash链表中移除,并返回,在继续使用之前,使用函数__ip_vs_update_dest更新其信息。

static int ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest)
{
    struct ip_vs_dest *dest;
    __be16 dport = udest->port;

    ip_vs_addr_copy(udest->af, &daddr, &udest->addr);

    rcu_read_lock();
    dest = ip_vs_lookup_dest(svc, udest->af, &daddr, dport);
    rcu_read_unlock();
    if (dest != NULL)
        return -EEXIST;

    dest = ip_vs_trash_get_dest(svc, udest->af, &daddr, dport);

    if (dest != NULL) {
        __ip_vs_update_dest(svc, dest, udest, 1);
        ret = 0;
    } else {
        /* Allocate and initialize the dest structure */
        ret = ip_vs_new_dest(svc, udest, &dest);
    }

如果以上添加都不成立,由函数ip_vs_new_dest创建一个全新的真实服务器结构。在真实服务器的地址合法性判断中,IPv6地址的类型需要满足,1)应为单播地址(IPV6_ADDR_UNICAST);2)地址类型不应是本地链路地址(IPV6_ADDR_LINKLOCAL);3)以上两个条件都不成立的话,地址类型不应为环回地址(IPV6_ADDR_LOOPBACK)。即IPv6地址应为非本地链路类型的单播地址;否则应为环回地址。

对于IPv4地址类型应满足,1)单播地址(RTN_UNICAST);2)或者本地地址(RTN_LOCAL)。

static int ip_vs_new_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest, struct ip_vs_dest **dest_p)
{
    struct ip_vs_dest *dest;
    unsigned int atype, i;

#ifdef CONFIG_IP_VS_IPV6
    if (udest->af == AF_INET6) {
        atype = ipv6_addr_type(&udest->addr.in6);
        if ((!(atype & IPV6_ADDR_UNICAST) || atype & IPV6_ADDR_LINKLOCAL) &&
            !__ip_vs_addr_is_local_v6(svc->ipvs->net, &udest->addr.in6))
            return -EINVAL;
    } else
#endif
    {
        atype = inet_addr_type(svc->ipvs->net, udest->addr.ip);
        if (atype != RTN_LOCAL && atype != RTN_UNICAST)
            return -EINVAL;
    }

地址合法性检查通过之后,就可以分配真实服务器结构,并且初始化其成员变量了。

dest = kzalloc(sizeof(struct ip_vs_dest), GFP_KERNEL);
if (dest == NULL)
    return -ENOMEM;

dest->stats.cpustats = alloc_percpu(struct ip_vs_cpu_stats);
if (!dest->stats.cpustats)
    goto err_alloc;

for_each_possible_cpu(i) {
    struct ip_vs_cpu_stats *ip_vs_dest_stats;
    ip_vs_dest_stats = per_cpu_ptr(dest->stats.cpustats, i);
    u64_stats_init(&ip_vs_dest_stats->syncp);
}

dest->af = udest->af;
dest->protocol = svc->protocol;
dest->vaddr = svc->addr;
dest->vport = svc->port;
dest->vfwmark = svc->fwmark;
ip_vs_addr_copy(udest->af, &dest->addr, &udest->addr);
dest->port = udest->port;

atomic_set(&dest->activeconns, 0);
atomic_set(&dest->inactconns, 0);
atomic_set(&dest->persistconns, 0);
refcount_set(&dest->refcnt, 1);

INIT_HLIST_NODE(&dest->d_list);
spin_lock_init(&dest->dst_lock);
spin_lock_init(&dest->stats.lock);

__ip_vs_update_dest(svc, dest, udest, 1);

函数的最后对__ip_vs_update_dest的调用,旨在将新创建的真实服务器结构在对应的虚拟服务中做添加或更新。在以上的ip_vs_add_dest函数介绍中,也是使用此函数将由dest_trash链表中找到的真实服务结构重新添加到虚拟服务中,在下节编辑真实服务器中,也将使用到此函数。参见下节介绍。

编辑真实服务器

如下的配置命令,将上节创建的真实服务器的转发方式由Masquerading修改为IPIP隧道封装:

ipvsadm -e -t 207.175.44.110:80 -r 192.168.10.1:80 -i

内核函数ip_vs_edit_dest处理真实服务器结构的修改。首先判断权重值weight不能小于0,这对于基于Weighted Round Robin(wrr)算法的调度器有用,其值为[0,65535]。l_threshold和u_threshold分别表示此真实服务器接收新连接的下限和上限,默认值都为0,表示不设限;如果真实服务器的连接数量超过u_threshold,将不再为其调度新的连接;当其连接数量降低到l_threshold以下时,重新为其调度新的连接。如果l_threshold没有配置,当连接数量减低到u_threshold的3/4后,重启调度。

如果在系统中没有找到已存在的真实服务器结构,参见函数ip_vs_lookup_dest,表明其还未创建,编辑操作失败。

static int ip_vs_edit_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest)
{
    struct ip_vs_dest *dest;
    union nf_inet_addr daddr;
    __be16 dport = udest->port;

    if (udest->weight < 0)
        return -ERANGE;
    if (udest->l_threshold > udest->u_threshold) 
        return -ERANGE;
    ip_vs_addr_copy(udest->af, &daddr, &udest->addr);

    rcu_read_lock();
    dest = ip_vs_lookup_dest(svc, udest->af, &daddr, dport);
    rcu_read_unlock();
    if (dest == NULL) return -ENOENT;

    __ip_vs_update_dest(svc, dest, udest, 0);

无论是添加还是编辑,最终都由函数__ip_vs_update_dest完成最后的工作,其最后一个参数用来区分是新建还是编辑操作。首先对于编辑操作,不允许改变真实服务器的地址族类型。对于新建操作,如果真实服务器的地址族与所属的虚拟服务的地址族不同,将导致后续的同步线程不能工作(如果使能的话),此处由ipvs命名空间变量mixed_address_family_dests进行标记。

static void __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest, struct ip_vs_dest_user_kern *udest, int add)
{
    struct netns_ipvs *ipvs = svc->ipvs;
    struct ip_vs_service *old_svc;
    struct ip_vs_scheduler *sched;

    BUG_ON(!add && udest->af != dest->af);

    if (add && udest->af != svc->af)
        ipvs->mixed_address_family_dests++;

对于转发类型为:Masquerading/NAT,需要将此真实服务器结构添加到ipvs网络命名空间的rs_table链表中,由函数ip_vs_rs_hash完成。用于在接收到真实服务器的数据后,ipvs中已没有关联的连接时,确认此数据是否为ipvs服务中的真实服务器所发送。对于非Masquerading/NAT转发类型,不进行链表添加操作。

/* set the weight and the flags */
atomic_set(&dest->weight, udest->weight);
conn_flags = udest->conn_flags & IP_VS_CONN_F_DEST_MASK;
conn_flags |= IP_VS_CONN_F_INACTIVE;

/* set the IP_VS_CONN_F_NOOUTPUT flag if not masquerading/NAT */
if ((conn_flags & IP_VS_CONN_F_FWD_MASK) != IP_VS_CONN_F_MASQ) {
    conn_flags |= IP_VS_CONN_F_NOOUTPUT;
} else {
    /* Put the real service in rs_table if not present. For now only for NAT! */
    ip_vs_rs_hash(ipvs, dest);
}
atomic_set(&dest->conn_flags, conn_flags);

对于添加真实服务器操作,使用函数__ip_vs_bind_svc与虚拟服务做绑定;而对于编辑操作,由于可能修改真实服务所属的虚拟服务,此时会情况之前的真实服务器相关统计信息,在将其绑定到新的虚拟服务上。

/* bind the service */
old_svc = rcu_dereference_protected(dest->svc, 1);
if (!old_svc) {
    __ip_vs_bind_svc(dest, svc);
} else {
    if (old_svc != svc) {
        ip_vs_zero_stats(&dest->stats);
        __ip_vs_bind_svc(dest, svc);
        __ip_vs_svc_put(old_svc, true);
    }
}

最后,更新l_threshold和u_threshold的值等的相关初始化操作。以及将真实服务器结构链接到虚拟服务的destinations链表中。之后,将真实服务器更新到虚拟服务的调度器中。

/* set the dest status flags */
dest->flags |= IP_VS_DEST_F_AVAILABLE;

if (udest->u_threshold == 0 || udest->u_threshold > dest->u_threshold)
    dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
dest->u_threshold = udest->u_threshold;
dest->l_threshold = udest->l_threshold;

dest->af = udest->af;

spin_lock_bh(&dest->dst_lock);
__ip_vs_dst_cache_reset(dest);
spin_unlock_bh(&dest->dst_lock);

if (add) {
    ip_vs_start_estimator(svc->ipvs, &dest->stats);
    list_add_rcu(&dest->n_list, &svc->destinations);
    svc->num_dests++;
    sched = rcu_dereference_protected(svc->scheduler, 1);
    if (sched && sched->add_dest)
        sched->add_dest(svc, dest);
} else {
    sched = rcu_dereference_protected(svc->scheduler, 1);
    if (sched && sched->upd_dest)
        sched->upd_dest(svc, dest);
}

目前只有Weighted Round Robin和Source Hashing调度器定义了真实服务器更新函数(upd_dest),分别为ip_vs_wrr_dest_changed和ip_vs_sh_dest_changed。

真实服务器删除

如下命令,将从虚拟服务207.175.44.110:80中删除真实服务器192.168.10.1:

ipvsadm -d -t 207.175.44.110:80 -r 192.168.10.1

内核中的处理函数为ip_vs_del_dest,其首先进行合法性检查,由函数ip_vs_lookup_dest确定要删除的真实服务器是否存在;其次,如果存在,将其从虚拟服务的destinations链表中移除,由函数__ip_vs_unlink_dest实现。

static int ip_vs_del_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest)
{
    struct ip_vs_dest *dest;
    __be16 dport = udest->port;

    /* We use function that requires RCU lock */
    rcu_read_lock();
    dest = ip_vs_lookup_dest(svc, udest->af, &udest->addr, dport);
    rcu_read_unlock();

    if (dest == NULL)
        return -ENOENT;

    /*  Unlink dest from the service */
    __ip_vs_unlink_dest(svc, dest, 1);

    /*  Delete the destination */
    __ip_vs_del_dest(svc->ipvs, dest, false);

最后,__ip_vs_del_dest函数将真实服务器由ipvs网络命名空间的rs_table链表中移除,注意此链表上仅有转发模式为Masquerading/NAT的真实服务器。此函数并不直接删除真实服务器结构,而是将其链接到ipvs命名空间的dest_trash回收链表上,启动一个定时器dest_trash_timer,在其超时在实际的移除此真实服务器。

static void __ip_vs_del_dest(struct netns_ipvs *ipvs, struct ip_vs_dest *dest, bool cleanup)
{
    ip_vs_stop_estimator(ipvs, &dest->stats);

    ip_vs_rs_unhash(dest);

    spin_lock_bh(&ipvs->dest_trash_lock);
    if (list_empty(&ipvs->dest_trash) && !cleanup)
        mod_timer(&ipvs->dest_trash_timer, jiffies + (IP_VS_DEST_TRASH_PERIOD >> 1));
    /* dest lives in trash with reference */
    list_add(&dest->t_list, &ipvs->dest_trash);
    dest->idle_start = 0;
    spin_unlock_bh(&ipvs->dest_trash_lock);

此定时器的超时时长为IP_VS_DEST_TRASH_PERIOD的1/4,但是每个真实服务器结构只有在不被引用之后,再经过IP_VS_DEST_TRASH_PERIOD时长才被删除,由变量idle_start记录真实服务器加入dest_trash链表的时间。以下为超时处理函数ip_vs_dest_trash_expire。

static void ip_vs_dest_trash_expire(struct timer_list *t)
{
    struct netns_ipvs *ipvs = from_timer(ipvs, t, dest_trash_timer);
    struct ip_vs_dest *dest, *next;
    unsigned long now = jiffies;

    spin_lock(&ipvs->dest_trash_lock);
    list_for_each_entry_safe(dest, next, &ipvs->dest_trash, t_list) {
        if (refcount_read(&dest->refcnt) > 1)
            continue;
        if (dest->idle_start) {
            if (time_before(now, dest->idle_start + IP_VS_DEST_TRASH_PERIOD))
                continue;
        } else {
            dest->idle_start = max(1UL, now);
            continue;
        }
        list_del(&dest->t_list);
        ip_vs_dest_free(dest);
    }
    if (!list_empty(&ipvs->dest_trash))
        mod_timer(&ipvs->dest_trash_timer, jiffies + (IP_VS_DEST_TRASH_PERIOD >> 1));
    spin_unlock(&ipvs->dest_trash_lock);

Linux内核版本 4.15

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页