本文主要是介绍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, ¶m);cp = ip_vs_conn_new(¶m, 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(¶m);if (!ct || !ip_vs_check_template(ct, NULL)) {/* Create a template */ct = ip_vs_conn_new(¶m, 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);elsecall_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
这篇关于IPVS的OPS调度的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!