IPVS中的TCP连接状态转换

TCP状态初始化在函数__ip_vs_tcp_init中完成,此函数在ipvs网络命名空间初始化时执行,其将全局TCP状态数组tcp_states的地址赋值给TCP协议数据结构的成员tcp_state_table指针。

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;
}

全局TCP状态数组tcp_states定义如下:

static struct tcp_states_t tcp_states [] = {
/*  INPUT */
/*        sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
/*syn*/ {{sSR, sES, sES, sSR, sSR, sSR, sSR, sSR, sSR, sSR, sSR }},
/*fin*/ {{sCL, sCW, sSS, sTW, sTW, sTW, sCL, sCW, sLA, sLI, sTW }},
/*ack*/ {{sES, sES, sSS, sES, sFW, sTW, sCL, sCW, sCL, sLI, sES }},
/*rst*/ {{sCL, sCL, sCL, sSR, sCL, sCL, sCL, sCL, sLA, sLI, sSR }},

/*  OUTPUT */
/*        sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
/*syn*/ {{sSS, sES, sSS, sSR, sSS, sSS, sSS, sSS, sSS, sLI, sSR }},
/*fin*/ {{sTW, sFW, sSS, sTW, sFW, sTW, sCL, sTW, sLA, sLI, sTW }},
/*ack*/ {{sES, sES, sSS, sES, sFW, sTW, sCL, sCW, sLA, sES, sES }},
/*rst*/ {{sCL, sCL, sSS, sCL, sCL, sTW, sCL, sCL, sCL, sCL, sCL }},

/*  INPUT-ONLY */
/*        sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
/*syn*/ {{sSR, sES, sES, sSR, sSR, sSR, sSR, sSR, sSR, sSR, sSR }},
/*fin*/ {{sCL, sFW, sSS, sTW, sFW, sTW, sCL, sCW, sLA, sLI, sTW }},
/*ack*/ {{sES, sES, sSS, sES, sFW, sTW, sCL, sCW, sCL, sLI, sES }},
/*rst*/ {{sCL, sCL, sCL, sSR, sCL, sCL, sCL, sCL, sLA, sLI, sCL }},
};

其索引由两部分组成。第一部分是偏移部分,参见数组tcp_state_off。偏移值为:0、4和8,对应于三种方向定义:TCP_DIR_INPUT、TCP_DIR_OUTPUT和TCP_DIR_INPUT_ONLY。

#define TCP_DIR_INPUT       0
#define TCP_DIR_OUTPUT      4
#define TCP_DIR_INPUT_ONLY  8

static const int tcp_state_off[IP_VS_DIR_LAST] = {
    [IP_VS_DIR_INPUT]       =   TCP_DIR_INPUT,
    [IP_VS_DIR_OUTPUT]      =   TCP_DIR_OUTPUT,
    [IP_VS_DIR_INPUT_ONLY]  =   TCP_DIR_INPUT_ONLY,
};

第二部分为偏移后的内部索引,参见函数tcp_state_idx,TCP头部的四个标志位:RST、SYN、FIN和ACK,分别对应索引:3、0、1、和2。需要注意的是这里是有优先权区分的,顺序不能颠倒,比如RST标志优先级最高,此标志如果存在,内部索引为3。

static inline int tcp_state_idx(struct tcphdr *th)
{
    if (th->rst)
        return 3;
    if (th->syn)
        return 0;
    if (th->fin)
        return 1;
    if (th->ack)
        return 2;
    return -1;
}

TCP状态转换数组是预定义的tcp_states,以下我们以IP_VS_DIR_INPUT方向为例看一下其初始化的内容。首先其子成员数组next_state的索引为TCP的状态值(总共11个状态);其次,对于接收到的SYN报文,如果当前TCP状态为sES(IP_VS_TCP_S_ESTABLISHED)状态不做变化,如果当前状态为sSS(IP_VS_TCP_S_SYN_SENT),表明为两端同时建立连接,下一个状态为sES(IP_VS_TCP_S_ESTABLISHED);其与任何情况下接收到SYN报文,下一个状态都是sSR(IP_VS_TCP_S_SYN_RECV)状态。最后,关于其它报文(FIN、ACK和RST)触发的状态变化,参见tcp_states数组。

struct tcp_states_t {
    int next_state[IP_VS_TCP_S_LAST];
}; 
static struct tcp_states_t tcp_states [] = {
/*  INPUT */
/*        sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */     /* TCP状态为索引 */
/*syn*/ {{sSR, sES, sES, sSR, sSR, sSR, sSR, sSR, sSR, sSR, sSR }},
/*fin*/ {{sCL, sCW, sSS, sTW, sTW, sTW, sCL, sCW, sLA, sLI, sTW }},
/*ack*/ {{sES, sES, sSS, sES, sFW, sTW, sCL, sCW, sCL, sLI, sES }},
/*rst*/ {{sCL, sCL, sCL, sSR, sCL, sCL, sCL, sCL, sLA, sLI, sSR }},

}

#define sNO IP_VS_TCP_S_NONE
#define sES IP_VS_TCP_S_ESTABLISHED
#define sSS IP_VS_TCP_S_SYN_SENT
#define sSR IP_VS_TCP_S_SYN_RECV

函数set_tcp_state完成TCP状态的变换。由于实际上仅有两个方向的流量,TCP_DIR_INPUT_ONLY计算所得。如果当前的连接标志变量中有IP_VS_CONN_F_NOOUTPUT标志,并且当前的状态偏移(即数据流向)也不等于TCP_DIR_OUTPUT,ipvs系统将这种情况归为TCP_DIR_INPUT_ONLY。

使用TCP协议数据结构中的成员tcp_state_table找到下一个新状态值。最后在改变连接状态值的同时,更新连接的超时时间。

static inline void set_tcp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp, int direction, struct tcphdr *th)
{
    int state_idx;
    int new_state = IP_VS_TCP_S_CLOSE;
    int state_off = tcp_state_off[direction];

    if (cp->flags & IP_VS_CONN_F_NOOUTPUT) {
        if (state_off == TCP_DIR_OUTPUT)
            cp->flags &= ~IP_VS_CONN_F_NOOUTPUT;
        else
            state_off = TCP_DIR_INPUT_ONLY;
    }
    if ((state_idx = tcp_state_idx(th)) < 0)
        goto tcp_state_out;

    new_state = pd->tcp_state_table[state_off+state_idx].next_state[cp->state];

    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状态的active/inactive属性,由函数tcp_state_active进行判读,参数为TCP状态值。

static bool tcp_state_active(int state)
{
    if (state >= IP_VS_TCP_S_LAST)
        return false;
    return tcp_state_active_table[state];
}

ipvs系统中使用全局数组tcp_state_active_table预定义了TCP状态所对应的active/inactive属性。如下所示,active属性的状态有4个:x_ESTABLISHED、x_SYN_SENT、x_SYN_RECV、x_SYNACK;其与的TCP状态都为inactive属性。

static const bool tcp_state_active_table[IP_VS_TCP_S_LAST] = {
    [IP_VS_TCP_S_NONE]      =   false,
    [IP_VS_TCP_S_ESTABLISHED]   =   true,
    [IP_VS_TCP_S_SYN_SENT]      =   true,
    [IP_VS_TCP_S_SYN_RECV]      =   true,
    [IP_VS_TCP_S_FIN_WAIT]      =   false,
    [IP_VS_TCP_S_TIME_WAIT]     =   false,
    [IP_VS_TCP_S_CLOSE]     =   false,
    [IP_VS_TCP_S_CLOSE_WAIT]    =   false,
    [IP_VS_TCP_S_LAST_ACK]      =   false,
    [IP_VS_TCP_S_LISTEN]        =   false,
    [IP_VS_TCP_S_SYNACK]        =   true,
}; 

在以上接收的TCP状态变化函数set_tcp_state中,根据TCP状态的active/inactive属性设置当前连接的相应标志,已经记录连接数量信息。对于活动active的连接,如果其TCP状态转换为inactive属性的状态,需要递减activeconns的数量,递增inactconns数量,并为此连接设置IP_VS_CONN_F_INACTIVE标志。

对于不活动inactive的连接,如果其TCP状态转换为active属性的状态,执行与以上相反的操作。

static inline void set_tcp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp, int direction, struct tcphdr *th)
{
    if (new_state != cp->state) {
        struct ip_vs_dest *dest = cp->dest;

        if (dest) {
            if (!(cp->flags & IP_VS_CONN_F_INACTIVE) && !tcp_state_active(new_state)) {
                atomic_dec(&dest->activeconns);
                atomic_inc(&dest->inactconns);
                cp->flags |= IP_VS_CONN_F_INACTIVE;
            } else if ((cp->flags & IP_VS_CONN_F_INACTIVE) && tcp_state_active(new_state)) {
                atomic_inc(&dest->activeconns);
                atomic_dec(&dest->inactconns);
                cp->flags &= ~IP_VS_CONN_F_INACTIVE;
            }
        }
    }
}

内核版本 4.15

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