IPVS系统的连接同步 - SLAVE同步线程

负载均衡 专栏收录该内容
42 篇文章 4 订阅

Slave同步线程函数sync_thread_backup如下,其处理多播同步报文的接收。如下可见,其检测监听套接口的sk_receive_queue队列,一旦队列中有值,使用ip_vs_receive函数将报文接收到线程的缓存中。之后调用函数ip_vs_process_message进行实际的处理。

static int sync_thread_backup(void *data)
{   
    struct ip_vs_sync_thread_data *tinfo = data;
    struct netns_ipvs *ipvs = tinfo->ipvs;
    
    pr_info("sync thread started: state = BACKUP, mcast_ifn = %s, "
        "syncid = %d, id = %d\n", ipvs->bcfg.mcast_ifn, ipvs->bcfg.syncid, tinfo->id);
    
    while (!kthread_should_stop()) {
        wait_event_interruptible(*sk_sleep(tinfo->sock->sk),
             !skb_queue_empty(&tinfo->sock->sk->sk_receive_queue) || kthread_should_stop());
        
        /* do we have data now? */
        while (!skb_queue_empty(&(tinfo->sock->sk->sk_receive_queue))) {
            len = ip_vs_receive(tinfo->sock, tinfo->buf, ipvs->bcfg.sync_maxlen);
            if (len <= 0) {
                if (len != -EAGAIN) pr_err("receiving message error\n");
                break;
            }
            
            ip_vs_process_message(ipvs, tinfo->buf, len);
        }
    }

同步线程接收

如下的函数ip_vs_receive,其十分简单,相当于在应用层调用recvmsg函数,将报文保存在传入的参数buffer空间中。

static int ip_vs_receive(struct socket *sock, char *buffer, const size_t buflen)
{
    struct msghdr       msg = {NULL,};
    struct kvec     iov;

    /* Receive a packet */
    iov.iov_base     = buffer;
    iov.iov_len      = (size_t)buflen;

    len = kernel_recvmsg(sock, &msg, &iov, 1, buflen, MSG_DONTWAIT);

同步数据处理

如下的函数ip_vs_process_message,首先进行一些合法性检查,确保接收的数据长度符合要求,长度应当等于头部结构ip_vs_sync_mesg中保存的size的值。并且,如果此backup接收线程的同步ID(syncid)与报文头部的syncid不匹配,也返回不做处理。

static void ip_vs_process_message(struct netns_ipvs *ipvs, __u8 *buffer, const size_t buflen)
{
    struct ip_vs_sync_mesg *m2 = (struct ip_vs_sync_mesg *)buffer;
    __u8 *p, *msg_end;

    if (buflen < sizeof(struct ip_vs_sync_mesg_v0)) {
        IP_VS_DBG(2, "BACKUP, message header too short\n");
        return;
    }

    if (buflen != ntohs(m2->size)) {
        IP_VS_DBG(2, "BACKUP, bogus message size\n");
        return;
    }
    /* SyncID sanity check */
    if (ipvs->bcfg.syncid != 0 && m2->syncid != ipvs->bcfg.syncid) {
        IP_VS_DBG(7, "BACKUP, Ignoring syncid = %d\n", m2->syncid);
        return;
    }

以下的判断条件保证接收到的同步数据版本是v1(SYNC_PROTO_VER)。同步数据头部结构ip_vs_sync_mesg的成员nr_conns表明此报文中包含的连接信息的个数,以下使用循环处理每一个连接数据。

    /* Handle version 1  message */
    if ((m2->version == SYNC_PROTO_VER) && (m2->reserved == 0) && (m2->spare == 0)) {

        msg_end = buffer + sizeof(struct ip_vs_sync_mesg);
        nr_conns = m2->nr_conns;

        for (i=0; i<nr_conns; i++) {
            union ip_vs_sync_conn *s;
            unsigned int size;
            int retc;

接下来是对连接信息的合法性判读,如果缓存中剩余的数据长度小于ip_vs_sync_v4结构的大小时,表明已经不是一个完整的连接信息了,结束处理。如果连接头部结构ip_vs_sync_conn成员size(12bit长度,见SVER_MASK宏定义)中保存的长度值,已经超出缓存中的数据长度,出错结束处理。另外,如果连接信息的版本值不为零,返回结束处理。

            p = msg_end;
            if (p + sizeof(s->v4) > buffer+buflen) {
                IP_VS_ERR_RL("BACKUP, Dropping buffer, to small\n");
                return;
            }
            s = (union ip_vs_sync_conn *)p;
            size = ntohs(s->v4.ver_size) & SVER_MASK;
            msg_end = p + size;
            /* Basic sanity checks */
            if (msg_end  > buffer+buflen) {
                IP_VS_ERR_RL("BACKUP, Dropping buffer, msg > buffer\n");
                return;
            }
            if (ntohs(s->v4.ver_size) >> SVER_SHIFT) {
                IP_VS_ERR_RL("BACKUP, Dropping buffer, Unknown version %d\n", ntohs(s->v4.ver_size) >> SVER_SHIFT);
                return;
            }

如果以上的合法性检查通过之后,调用函数ip_vs_proc_sync_conn处理同步连接信息。

            /* Process a single sync_conn */
            retc = ip_vs_proc_sync_conn(ipvs, p, msg_end);
            if (retc < 0) {
                IP_VS_ERR_RL("BACKUP, Dropping buffer, Err: %d in decoding\n", retc);
                return;
            }
            /* Make sure we have 32 bit alignment */
            msg_end = p + ((size + 3) & ~3);
        }
    }

连接信息处理

首先看一下函数开头的合法性检查,对于IPv6而言,如果数据长度小于基本的IPv6连接结构ip_vs_sync_v6的长度,认为是不合法报文。对于IPv4而言,如果数据长度小于基本的IPv4连接结构ip_vs_sync_v4的长度,则认为是不合法报文。

static inline int ip_vs_proc_sync_conn(struct netns_ipvs *ipvs, __u8 *p, __u8 *msg_end)
{
    struct ip_vs_sync_conn_options opt;
    union  ip_vs_sync_conn *s;
    struct ip_vs_protocol *pp;
    struct ip_vs_conn_param param;

    s = (union ip_vs_sync_conn *) p;

    if (s->v6.type & STYPE_F_INET6) {
#ifdef CONFIG_IP_VS_IPV6
        af = AF_INET6;
        p += sizeof(struct ip_vs_sync_v6);
#endif
    } else if (!s->v4.type) {
        af = AF_INET;
        p += sizeof(struct ip_vs_sync_v4);
    } else {
        return -10;
    }
    if (p > msg_end)
        return -20;

在掉过了连接基础结构的大小之后,以下处理可能的连接选项数据。选项数据时TLV结构,首先是选项类型,接着是长度,最后是数据内容。目前IPVS支持的选项有三种,分别为:IPVS_OPT_SEQ_DATA、IPVS_OPT_PE_DATA和IPVS_OPT_PE_NAME。第一种选项使用函数ip_vs_proc_seqopt进行处理;后两者都使用函数ip_vs_proc_str解析。

    /* Process optional params check Type & Len. */
    while (p < msg_end) {
        int ptype;
        int plen;

        if (p+2 > msg_end)
            return -30;
        ptype = *(p++);
        plen  = *(p++);

        if (!plen || ((p + plen) > msg_end))
            return -40;
        /* Handle seq option  p = param data */
        switch (ptype & ~IPVS_OPT_F_PARAM) {
        case IPVS_OPT_SEQ_DATA:
            if (ip_vs_proc_seqopt(p, plen, &opt_flags, &opt))
                return -50;
            break;

        case IPVS_OPT_PE_DATA:
            if (ip_vs_proc_str(p, plen, &pe_data_len, &pe_data, IP_VS_PEDATA_MAXLEN, &opt_flags, IPVS_OPT_F_PE_DATA))
                return -60;
            break;

        case IPVS_OPT_PE_NAME:
            if (ip_vs_proc_str(p, plen,&pe_name_len, &pe_name, IP_VS_PENAME_MAXLEN, &opt_flags, IPVS_OPT_F_PE_NAME))
                return -70;
            break;
        }
        p += plen;  /* Next option */
    }

对于同步线程创建的连接,增加标志IP_VS_CONN_F_SYNC,以示区分。

    /* Get flags and Mask off unsupported */
    flags  = ntohl(s->v4.flags) & IP_VS_CONN_F_BACKUP_MASK;
    flags |= IP_VS_CONN_F_SYNC;
    state = ntohs(s->v4.state);

对于非模板连接(未设置IP_VS_CONN_F_TEMPLATE标志),检查此连接的协议号,本机IPVS是否支持;以及数据中的协议状态是否合法。反之,对于模板连接,由于其不用于状态变迁及超时处理,故仅要求其大于零即可。

    if (!(flags & IP_VS_CONN_F_TEMPLATE)) {
        pp = ip_vs_proto_get(s->v4.protocol);
        if (!pp) {
            IP_VS_DBG(3,"BACKUP, Unsupported protocol %u\n", s->v4.protocol);
            retc = 30;
            goto out;
        }
        if (state >= pp->num_states) {
            IP_VS_DBG(3, "BACKUP, Invalid %s state %u\n", pp->name, state);
            retc = 40;
            goto out;
        }
    } else {
        /* protocol in templates is not used for state/timeout */
        if (state > 0) {
            IP_VS_DBG(3, "BACKUP, Invalid template state %u\n", state);
            state = 0;
        }
    }

函数ip_vs_conn_fill_param_sync初始化连接参数结构ip_vs_conn_param(参数param),其使用以上获得的PE数据,PE名称,以及连接基本信息结构ip_vs_sync_conn(参数s)。注意序列号相关参数在函数ip_vs_proc_conn中填充。

    if (ip_vs_conn_fill_param_sync(ipvs, af, s, &param, pe_data, pe_data_len, pe_name, pe_name_len)) {
        retc = 50;
        goto out;
    }
    /* If only IPv4, just silent skip IPv6 */
    if (af == AF_INET)
        ip_vs_proc_conn(ipvs, &param, flags, state, s->v4.protocol, af, (union nf_inet_addr *)&s->v4.daddr, s->v4.dport,
                ntohl(s->v4.timeout), ntohl(s->v4.fwmark), (opt_flags & IPVS_OPT_F_SEQ_DATA ? &opt : NULL) );
#ifdef CONFIG_IP_VS_IPV6
    else
        ip_vs_proc_conn(ipvs, &param, flags, state, s->v6.protocol, af, (union nf_inet_addr *)&s->v6.daddr, s->v6.dport,
                ntohl(s->v6.timeout), ntohl(s->v6.fwmark), (opt_flags & IPVS_OPT_F_SEQ_DATA ? &opt : NULL) );
#endif 

连接创建更新

函数ip_vs_proc_conn负责连接的创建与更新。对于非模板类型连接,如连接的目的端口或者目的地址发生改变,并且此最新同步过来的连接为活动连接,就需要将本机的相同连接删除,内核通过函数ip_vs_conn_expire_now将本地连接的超时时间设置为当前时间。

对于模板连接,调用函数ip_vs_ct_in_get获取此连接的模板连接。

static void ip_vs_proc_conn(struct netns_ipvs *ipvs, struct ip_vs_conn_param *param, unsigned int flags, unsigned int state,
                unsigned int protocol, unsigned int type, const union nf_inet_addr *daddr, __be16 dport,
                unsigned long timeout, __u32 fwmark, struct ip_vs_sync_conn_options *opt)
{
    struct ip_vs_dest *dest;
    struct ip_vs_conn *cp;

    if (!(flags & IP_VS_CONN_F_TEMPLATE)) {
        cp = ip_vs_conn_in_get(param);
        if (cp && ((cp->dport != dport) || !ip_vs_addr_equal(cp->daf, &cp->daddr, daddr))) {
            if (!(flags & IP_VS_CONN_F_INACTIVE)) {
                ip_vs_conn_expire_now(cp);
                __ip_vs_conn_put(cp);
                cp = NULL;
            } else {
                /* This is the expiration message for the connection that was already replaced, so we just ignore it. */
                __ip_vs_conn_put(cp);
                kfree(param->pe_data);
                return;
            }
        }
    } else {
        cp = ip_vs_ct_in_get(param);
    }

如果本地已有相同的连接,执行相关更新操作。

    if (cp) {
        /* Free pe_data */
        kfree(param->pe_data);

        dest = cp->dest;
        spin_lock_bh(&cp->lock);
        if ((cp->flags ^ flags) & IP_VS_CONN_F_INACTIVE && !(flags & IP_VS_CONN_F_TEMPLATE) && dest) {
            if (flags & IP_VS_CONN_F_INACTIVE) {
                atomic_dec(&dest->activeconns);
                atomic_inc(&dest->inactconns);
            } else {
                atomic_inc(&dest->activeconns);
                atomic_dec(&dest->inactconns);
            }
        }
        flags &= IP_VS_CONN_F_BACKUP_UPD_MASK;
        flags |= cp->flags & ~IP_VS_CONN_F_BACKUP_UPD_MASK;
        cp->flags = flags;
        spin_unlock_bh(&cp->lock);
        if (!dest)
            ip_vs_try_bind_dest(cp);
    } else {

否则,如果没有本地连接,需新建一个连接结构。即函数ip_vs_conn_new,在执行之前,尝试查找合适的真实目的服务器。

        dest = ip_vs_find_dest(ipvs, type, type, daddr, dport, param->vaddr, param->vport, protocol, fwmark, flags);

        cp = ip_vs_conn_new(param, type, daddr, dport, flags, dest, fwmark);
        rcu_read_unlock();
        if (!cp) {
            kfree(param->pe_data);
            IP_VS_DBG(2, "BACKUP, add new conn. failed\n");
            return;
        }
        if (!(flags & IP_VS_CONN_F_TEMPLATE))
            kfree(param->pe_data);
    }

填充序列号数据,以及状态state和连接超时值timeoute等。

    if (opt) {
        cp->in_seq = opt->in_seq;
        cp->out_seq = opt->out_seq;
    }
    atomic_set(&cp->in_pkts, sysctl_sync_threshold(ipvs));
    cp->state = state;
    cp->old_state = cp->state;

    if (timeout) {
        if (timeout > MAX_SCHEDULE_TIMEOUT / HZ)
            timeout = MAX_SCHEDULE_TIMEOUT / HZ;
        cp->timeout = timeout*HZ;
    } else {
        struct ip_vs_proto_data *pd;

        pd = ip_vs_proto_data_get(ipvs, protocol);
        if (!(flags & IP_VS_CONN_F_TEMPLATE) && pd && pd->timeout_table)
            cp->timeout = pd->timeout_table[state];
        else
            cp->timeout = (3*60*HZ);
    }

内核版本 4.15

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值