IPVS的OPS调度

如下ipvsadm配置命令:

# ipvsadm -A -t 207.175.44.110:80 -s rr --ops

OPS是One-Packet Scheduling的缩写,即单一报文调度。仅应用于UDP协议的虚拟服务,或者是标记UDP报文的防火墙fwmark配置,每个连接仅执行一次报文调度。

以下为ipvsadm代码中对–ops参数的解析部分,可见为虚拟服务置位了标志IP_VS_SVC_F_ONEPACKET。

static int parse_options(int argc, char **argv, struct ipvs_command_entry *ce, unsigned int *options, unsigned int *format)
{		  
    while ((c=poptGetNextOpt(context)) >= 0){
        switch (c) {			  
        case 'o':
            set_option(options, OPT_ONEPACKET);
            ce->svc.flags |= IP_VS_SVC_F_ONEPACKET;
            break;

连接OPS

IPVS在创建连接之时,会根据虚拟服务的标志是否设置了IP_VS_SVC_F_ONEPACKET,以及当前报文是否为UDP协议报文,为连接增加标志IP_VS_CONN_F_ONE_PACKET。

struct ip_vs_conn *ip_vs_schedule(struct ip_vs_service *svc, struct sk_buff *skb,
           struct ip_vs_proto_data *pd, int *ignored, struct ip_vs_iphdr *iph)
{

    flags = (svc->flags & IP_VS_SVC_F_ONEPACKET && iph->protocol == IPPROTO_UDP) ?
        IP_VS_CONN_F_ONE_PACKET : 0;

    /* Create a connection entry. */
    {
        struct ip_vs_conn_param p;

        ip_vs_conn_fill_param(svc->ipvs, svc->af, iph->protocol, caddr, cport, vaddr, vport, &p);
        cp = ip_vs_conn_new(&p, dest->af, &dest->addr, dest->port ? dest->port : vport, flags, dest, skb->mark);

另外,创建bypass类型的连接结构时,连接结构的flags成员也根据以上相同的判读,决定是否设置IP_VS_CONN_F_ONE_PACKET标志。

int ip_vs_leave(struct ip_vs_service *svc, struct sk_buff *skb, struct ip_vs_proto_data *pd, struct ip_vs_iphdr *iph)
{
    if (sysctl_cache_bypass(ipvs) && svc->fwmark && ...)) {
        unsigned int flags = (svc->flags & IP_VS_SVC_F_ONEPACKET && iph->protocol == IPPROTO_UDP) ? 
                                IP_VS_CONN_F_ONE_PACKET : 0;
        {
            ip_vs_conn_fill_param(svc->ipvs, svc->af, iph->protocol, &iph->saddr, pptr[0], &iph->daddr, pptr[1], &p);
            cp = ip_vs_conn_new(&p, svc->af, &daddr, 0, IP_VS_CONN_F_BYPASS | flags, NULL, skb->mark);

再者,在创建Persistent连接时,也会做如上的判断,决定是否为连接flags增加OPS标志IP_VS_CONN_F_ONE_PACKET。

static struct ip_vs_conn * ip_vs_sched_persist(struct ip_vs_service *svc,
            struct sk_buff *skb, __be16 src_port, __be16 dst_port, int *ignored, struct ip_vs_iphdr *iph)
{

    flags = (svc->flags & IP_VS_SVC_F_ONEPACKET && iph->protocol == IPPROTO_UDP) ?
        IP_VS_CONN_F_ONE_PACKET : 0;

    /* Create a new connection according to the template */
    ip_vs_conn_fill_param(svc->ipvs, svc->af, iph->protocol, src_addr, src_port, dst_addr, dst_port, &param);

    cp = ip_vs_conn_new(&param, dest->af, &dest->addr, dport, flags, dest, skb->mark);

但是在ip_vs_sched_persist函数中创建Persistent的模板连接时,其连接结构的flags固定设置IP_VS_CONN_F_TEMPLATE标志,可见模板连接与OPS无关。

    /* Check if a template already exists */
    ct = ip_vs_ct_in_get(&param);
    if (!ct || !ip_vs_check_template(ct, NULL)) {

        /* Create a template */
        ct = ip_vs_conn_new(&param, dest->af, &dest->addr, dport, IP_VS_CONN_F_TEMPLATE, dest, skb->mark);

这样,就保证了OPS可以同Persistent选项一同使用,即使OPS的每个连接仅调度一次,但是由于模板连接的存在,还是可以保证同一个客户端的请求调度到同一个真实服务器上。

# sudo ipvsadm -A -u 207.175.44.110:80 -s rr --ops --persistent 

OPS连接同步

对于仅执行一次调度的OPS连接,不需要同步到对端Slave,如下函数ip_vs_sync_conn中的判断。但是如果此OPS连接关联有控制模板连接,因为模板连接是可能长时间存在的,执行模板连接的同步。

void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
{
    /* Do not sync ONE PACKET */
    if (cp->flags & IP_VS_CONN_F_ONE_PACKET)
        goto control;

sloop:
    if (!ip_vs_sync_conn_needed(ipvs, cp, pkts))
    ...
	
control:
    /* synchronize its controller if it has */
    cp = cp->control;
    if (!cp) return;

    goto sloop;

OPS连接操作

在IPVS的连接创建函数ip_vs_conn_new中,最后将创建好的连接插入到全局链表ip_vs_conn_tab中。

struct ip_vs_conn *ip_vs_conn_new(const struct ip_vs_conn_param *p, int dest_af,
           const union nf_inet_addr *daddr, __be16 dport, unsigned int flags, struct ip_vs_dest *dest, __u32 fwmark)
{
    /* Hash it in the ip_vs_conn_tab finally */
    ip_vs_conn_hash(cp);

    return cp;

以下为链表插入函数,可见,对于设置了IP_VS_CONN_F_ONE_PACKET标志的连接结构,不执行插入操作,直接返回了。加入全局链表会增加连接的计数到2,直接返回导致的后果是,此连接的引用计数为1,在IPVS调度完成之后,此连接将被释放。

static inline int ip_vs_conn_hash(struct ip_vs_conn *cp)
{
    if (cp->flags & IP_VS_CONN_F_ONE_PACKET)
        return 0;

    if (!(cp->flags & IP_VS_CONN_F_HASHED)) {
        cp->flags |= IP_VS_CONN_F_HASHED;
        refcount_inc(&cp->refcnt);
        hlist_add_head_rcu(&cp->c_list, &ip_vs_conn_tab[hash]);

OPS连接的释放在函数ip_vs_in中,如下在连接执行完成报文发送函数之后,使用函数ip_vs_conn_put释放连接的引用计数。

static unsigned int ip_vs_in(struct netns_ipvs *ipvs, unsigned int hooknum, struct sk_buff *skb, int af)
{
    if (unlikely(!cp)) {
        if (!ip_vs_try_to_schedule(ipvs, af, skb, pd, &v, &cp, &iph))
            return v;
    }
    if (cp->packet_xmit)
        ret = cp->packet_xmit(skb, cp, pp, &iph);

    ip_vs_conn_put(cp);
    return ret;

对于OPS连接,由于其设置有IP_VS_CONN_F_ONE_PACKET标志,并且其引用计数等于1,在连接创建时仅初始化了连接定时器,并未启动,定时器并不处在pending状态,使用__ip_vs_conn_put_notimer函数处理。

void ip_vs_conn_put(struct ip_vs_conn *cp)
{
    if ((cp->flags & IP_VS_CONN_F_ONE_PACKET) && (refcount_read(&cp->refcnt) == 1) &&
        !timer_pending(&cp->timer))
        /* expire connection immediately */
        __ip_vs_conn_put_notimer(cp);

如下所示,调用__ip_vs_conn_put函数递减连接的引用计数,对于OPS连接此处将引用计数递减到零。之后,直接调用连接定时器的超时处理函数ip_vs_conn_expire,处理连接的释放。

static void __ip_vs_conn_put_notimer(struct ip_vs_conn *cp)
{
    __ip_vs_conn_put(cp);
    ip_vs_conn_expire(&cp->timer);
}
static inline void __ip_vs_conn_put(struct ip_vs_conn *cp)
{
    smp_mb__before_atomic();
    refcount_dec(&cp->refcnt);
} 

如下为超时处理函数逻辑,由于OPS连接并没有连接到全局链表中,所以ip_vs_conn_unlink解除链接函数总是成功。接着将执行以下的清理工作,释放连接。

static void ip_vs_conn_expire(struct timer_list *t)
{
    if (likely(ip_vs_conn_unlink(cp))) {
        del_timer(&cp->timer);

        if (cp->control)
            ip_vs_control_del(cp);

        if ((cp->flags & IP_VS_CONN_F_NFCT) && !(cp->flags & IP_VS_CONN_F_ONE_PACKET)) {
            smp_rmb();
            if (ipvs->enable) ip_vs_conn_drop_conntrack(cp);
        }

        if (unlikely(cp->app != NULL)) ip_vs_unbind_app(cp);
        ip_vs_unbind_dest(cp);
        if (cp->flags & IP_VS_CONN_F_NO_CPORT)
            atomic_dec(&ip_vs_conn_no_cport_cnt);
        if (cp->flags & IP_VS_CONN_F_ONE_PACKET)
            ip_vs_conn_rcu_free(&cp->rcu_head);
        else
            call_rcu(&cp->rcu_head, ip_vs_conn_rcu_free);
        atomic_dec(&ipvs->conn_count);
        return;

OPS连接随机删除

函数ip_vs_conn_ops_mode用于判断连接所关联的虚拟服务是否设置了IP_VS_SVC_F_ONEPACKET标志,以及此连接是否关联了目的服务器,以上条件都成立的话,返回真。

static inline bool ip_vs_conn_ops_mode(struct ip_vs_conn *cp)
{
    struct ip_vs_service *svc;

    if (!cp->dest)
        return false;
    svc = rcu_dereference(cp->dest->svc);
    return svc && (svc->flags & IP_VS_SVC_F_ONEPACKET);

目前仅在函数ip_vs_random_dropentry中使用到以上的连接是否为OPS的判断函数,如下,如果在判断的连接为一个模板连接,即设置了IP_VS_CONN_F_TEMPLATE标志,并且其所控制的派生连接数不为空,不能删除此连接。或者,其派生的连接为空,但是此连接不是OPS连接,也不能删除它。

即如果OPS的模板连接,没有派生连接的话,在ip_vs_random_dropentry函数中是可以将其删除的。

void ip_vs_random_dropentry(struct netns_ipvs *ipvs)
{
    for (idx = 0; idx < (ip_vs_conn_tab_size>>5); idx++) {
        unsigned int hash = prandom_u32() & ip_vs_conn_tab_mask;

        hlist_for_each_entry_rcu(cp, &ip_vs_conn_tab[hash], c_list) {
            if (cp->ipvs != ipvs)
                continue;
            if (cp->flags & IP_VS_CONN_F_TEMPLATE) {
                if (atomic_read(&cp->n_control) || !ip_vs_conn_ops_mode(cp))
                    continue;

内核版本 4.15

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