IPVS支持协议的超时时间
以下ipvsadm命令用于显示和设置ipvs连接的超时时间,单位为秒(seconds)。
$ sudo ipvsadm -l --timeout
Timeout (tcp tcpfin udp): 900 120 300
$
$ sudo ipvsadm --set 3600 120 300
$
$ sudo ipvsadm -l --timeout
Timeout (tcp tcpfin udp): 3600 120 300
$
三个值依次为TCP会话的超时时间,在接收到FIN报文后的TCP会话超时时间,以及UDP报文的超时时间。当其中某个值为0时,表示不设置此值,其将保留上一次设置的数值。
需要注意的是,这三个timeout值是针对ipvs网络命名空间的全局数值,而不只是针对某个虚拟服务。
内核函数ip_vs_set_timeout处理timeout的参数设置。有以下代码可见,前两个timeout设置到了IPPROTO_TCP协议数据的timeout_table数组,最后一个timeout值设置到了IPPROTO_UDP协议数据结构的timeout_table数组。这三个值分别对应的状态为:IP_VS_TCP_S_ESTABLISHED、IP_VS_TCP_S_FIN_WAIT 和 IP_VS_UDP_S_NORMAL。
static int ip_vs_set_timeout(struct netns_ipvs *ipvs, struct ip_vs_timeout_user *u)
{
#if defined(CONFIG_IP_VS_PROTO_TCP) || defined(CONFIG_IP_VS_PROTO_UDP)
struct ip_vs_proto_data *pd;
#endif
#ifdef CONFIG_IP_VS_PROTO_TCP
if (u->tcp_timeout) {
pd = ip_vs_proto_data_get(ipvs, IPPROTO_TCP);
pd->timeout_table[IP_VS_TCP_S_ESTABLISHED] = u->tcp_timeout * HZ;
}
if (u->tcp_fin_timeout) {
pd = ip_vs_proto_data_get(ipvs, IPPROTO_TCP);
pd->timeout_table[IP_VS_TCP_S_FIN_WAIT] = u->tcp_fin_timeout * HZ;
}
#endif
#ifdef CONFIG_IP_VS_PROTO_UDP
if (u->udp_timeout) {
pd = ip_vs_proto_data_get(ipvs, IPPROTO_UDP);
pd->timeout_table[IP_VS_UDP_S_NORMAL] = u->udp_timeout * HZ;
}
#endif
}
TCP协议超时时间
对于TCP协议,其协议数据结构在函数__ip_vs_tcp_init中初始化,tcp_timeouts是预先定义好的TCP相关timeout值。
static int __ip_vs_tcp_init(struct netns_ipvs *ipvs, struct ip_vs_proto_data *pd)
{
ip_vs_init_hash_table(ipvs->tcp_apps, TCP_APP_TAB_SIZE);
pd->timeout_table = ip_vs_create_timeout_table((int *)tcp_timeouts, sizeof(tcp_timeouts));
if (!pd->timeout_table)
return -ENOMEM;
pd->tcp_state_table = tcp_states;
return 0;
}
int *ip_vs_create_timeout_table(int *table, int size)
{
return kmemdup(table, size, GFP_KERNEL);
}
在tcp_timeouts数组中,内核为TCP协议的每个状态都定义了相应的超时时间,回忆以上的介绍,ipvsadm工具仅是可修改IP_VS_TCP_S_ESTABLISHED状态和IP_VS_TCP_S_FIN_WAIT状态的超时值,默认情况下分别为1560(900s)和260(120s)。
static const int tcp_timeouts[IP_VS_TCP_S_LAST+1] = {
[IP_VS_TCP_S_NONE] = 2*HZ,
[IP_VS_TCP_S_ESTABLISHED] = 15*60*HZ,
[IP_VS_TCP_S_SYN_SENT] = 2*60*HZ,
[IP_VS_TCP_S_SYN_RECV] = 1*60*HZ,
[IP_VS_TCP_S_FIN_WAIT] = 2*60*HZ,
[IP_VS_TCP_S_TIME_WAIT] = 2*60*HZ,
[IP_VS_TCP_S_CLOSE] = 10*HZ,
[IP_VS_TCP_S_CLOSE_WAIT] = 60*HZ,
[IP_VS_TCP_S_LAST_ACK] = 30*HZ,
[IP_VS_TCP_S_LISTEN] = 2*60*HZ,
[IP_VS_TCP_S_SYNACK] = 120*HZ,
[IP_VS_TCP_S_LAST] = 2*HZ,
};
内核函数set_tcp_state在处理TCP状态转换的同时,更新当前连接的超时时间。
static inline void set_tcp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp, int direction, struct tcphdr *th)
{
if (likely(pd))
cp->timeout = pd->timeout_table[cp->state = new_state];
else /* What to do ? */
cp->timeout = tcp_timeouts[cp->state = new_state];
}
另外,对于TCP协议,在函数ip_vs_tcp_conn_listen中也进行超时时间的更新,此函数主要由IPVS的FTP模块使用。
void ip_vs_tcp_conn_listen(struct ip_vs_conn *cp)
{
struct ip_vs_proto_data *pd = ip_vs_proto_data_get(cp->ipvs, IPPROTO_TCP);
spin_lock_bh(&cp->lock);
cp->state = IP_VS_TCP_S_LISTEN;
cp->timeout = (pd ? pd->timeout_table[IP_VS_TCP_S_LISTEN] : tcp_timeouts[IP_VS_TCP_S_LISTEN]);
spin_unlock_bh(&cp->lock);
}
UDP协议超时时间
函数__udp_init负责初始化UDP协议数据结构中的timeout_table数组,udp_timeouts为预定义的超时时间。
static int __udp_init(struct netns_ipvs *ipvs, struct ip_vs_proto_data *pd)
{
ip_vs_init_hash_table(ipvs->udp_apps, UDP_APP_TAB_SIZE);
pd->timeout_table = ip_vs_create_timeout_table((int *)udp_timeouts,
sizeof(udp_timeouts));
if (!pd->timeout_table)
return -ENOMEM;
return 0;
}
由于UDP是无状态的协议,此处只定义了一个IP_VS_UDP_S_NORMAL状态,其超时时间为5*60(300s)。可使用ipvsadm命令修改此值。
static const int udp_timeouts[IP_VS_UDP_S_LAST+1] = {
[IP_VS_UDP_S_NORMAL] = 5*60*HZ,
[IP_VS_UDP_S_LAST] = 2*HZ,
};
内核函数的UDP协议转换函数udp_state_transition中,修改当前连接的超时时间。
static void udp_state_transition(struct ip_vs_conn *cp, int direction, const struct sk_buff *skb, struct ip_vs_proto_data *pd)
{
if (unlikely(!pd)) {
pr_err("UDP no ns data\n");
return;
}
cp->timeout = pd->timeout_table[IP_VS_UDP_S_NORMAL];
}
SCTP协议超时时间
函数__ip_vs_sctp_init初始化SCTP的协议数据结构的成员timeout_table数组,sctp_timeouts为预定义的SCTP相关超时时间。
static int __ip_vs_sctp_init(struct netns_ipvs *ipvs, struct ip_vs_proto_data *pd)
{
ip_vs_init_hash_table(ipvs->sctp_apps, SCTP_APP_TAB_SIZE);
pd->timeout_table = ip_vs_create_timeout_table((int *)sctp_timeouts, sizeof(sctp_timeouts));
if (!pd->timeout_table)
return -ENOMEM;
return 0;
}
目前,命令行工具ipvsadm不能修改SCTP相关的超时时间。
#define IP_VS_SCTP_MAX_RTO ((60 + 1) * HZ)
static const int sctp_timeouts[IP_VS_SCTP_S_LAST + 1] = {
[IP_VS_SCTP_S_NONE] = 2 * HZ,
[IP_VS_SCTP_S_INIT1] = (0 + 3 + 1) * HZ,
[IP_VS_SCTP_S_INIT] = IP_VS_SCTP_MAX_RTO,
[IP_VS_SCTP_S_COOKIE_SENT] = IP_VS_SCTP_MAX_RTO,
[IP_VS_SCTP_S_COOKIE_REPLIED] = IP_VS_SCTP_MAX_RTO,
[IP_VS_SCTP_S_COOKIE_WAIT] = IP_VS_SCTP_MAX_RTO,
[IP_VS_SCTP_S_COOKIE] = IP_VS_SCTP_MAX_RTO,
[IP_VS_SCTP_S_COOKIE_ECHOED] = IP_VS_SCTP_MAX_RTO,
[IP_VS_SCTP_S_ESTABLISHED] = 15 * 60 * HZ,
[IP_VS_SCTP_S_SHUTDOWN_SENT] = IP_VS_SCTP_MAX_RTO,
[IP_VS_SCTP_S_SHUTDOWN_RECEIVED] = IP_VS_SCTP_MAX_RTO,
[IP_VS_SCTP_S_SHUTDOWN_ACK_SENT] = IP_VS_SCTP_MAX_RTO,
[IP_VS_SCTP_S_REJECTED] = (0 + 3 + 1) * HZ,
[IP_VS_SCTP_S_CLOSED] = IP_VS_SCTP_MAX_RTO,
[IP_VS_SCTP_S_LAST] = 2 * HZ,
};
在函数SCTP状态变更函数set_sctp_state中,修改当前连接的超时时间值。
static inline void set_sctp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp, int direction, const struct sk_buff *skb)
{
if (likely(pd))
cp->timeout = pd->timeout_table[cp->state = next_state];
else /* What to do ? */
cp->timeout = sctp_timeouts[cp->state = next_state];
}
IPVS超时时间获取
内核函数__ip_vs_get_timeouts用于获取TCP协议相应的IP_VS_TCP_S_ESTABLISHED和IP_VS_TCP_S_FIN_WAIT状态的超时时间,以及UDP协议的IP_VS_UDP_S_NORMAL状态的超时时间。
static inline void __ip_vs_get_timeouts(struct netns_ipvs *ipvs, struct ip_vs_timeout_user *u)
{
#if defined(CONFIG_IP_VS_PROTO_TCP) || defined(CONFIG_IP_VS_PROTO_UDP)
struct ip_vs_proto_data *pd;
#endif
memset(u, 0, sizeof (*u));
#ifdef CONFIG_IP_VS_PROTO_TCP
pd = ip_vs_proto_data_get(ipvs, IPPROTO_TCP);
u->tcp_timeout = pd->timeout_table[IP_VS_TCP_S_ESTABLISHED] / HZ;
u->tcp_fin_timeout = pd->timeout_table[IP_VS_TCP_S_FIN_WAIT] / HZ;
#endif
#ifdef CONFIG_IP_VS_PROTO_UDP
pd = ip_vs_proto_data_get(ipvs, IPPROTO_UDP);
u->udp_timeout = pd->timeout_table[IP_VS_UDP_S_NORMAL] / HZ;
#endif
}
内核版本 4.15
上一篇: Java数据库连接池的几种配置方法(以MySQL数据库为例)
下一篇: IPVS支持的协议