IPVS支持的协议

IPVS协议初始化函数ip_vs_protocol_init,通过函数register_ip_vs_protocol完成。目前注册的协议结构体有:ip_vs_protocol_tcp/ip_vs_protocol_udp/ip_vs_protocol_sctp/ip_vs_protocol_ah/ip_vs_protocol_esp等五个,根据内核的具体配置进行注册。

int __init ip_vs_protocol_init(void)
{
    char protocols[64];
#define REGISTER_PROTOCOL(p)            \
    do {                    \
        register_ip_vs_protocol(p); \
        strcat(protocols, ", ");    \
        strcat(protocols, (p)->name);   \
    } while (0)

    protocols[0] = '\0';
    protocols[2] = '\0';
#ifdef CONFIG_IP_VS_PROTO_TCP
    REGISTER_PROTOCOL(&ip_vs_protocol_tcp);
#endif
#ifdef CONFIG_IP_VS_PROTO_UDP
    REGISTER_PROTOCOL(&ip_vs_protocol_udp);
#endif
#ifdef CONFIG_IP_VS_PROTO_SCTP
    REGISTER_PROTOCOL(&ip_vs_protocol_sctp);
#endif
#ifdef CONFIG_IP_VS_PROTO_AH
    REGISTER_PROTOCOL(&ip_vs_protocol_ah);
#endif
#ifdef CONFIG_IP_VS_PROTO_ESP
    REGISTER_PROTOCOL(&ip_vs_protocol_esp);
#endif
    pr_info("Registered protocols (%s)\n", &protocols[2]);
}

协议注册

目前IPVS支持的协议有:TCP/UDP/SCTP/AH/ESP五个,内核注册的协议保存在全局hash数组ip_vs_proto_table中,索引为由协议号计算的哈希值,数组元素为一个单链表。

#define IP_VS_PROTO_TAB_SIZE        32  /* must be power of 2 */
#define IP_VS_PROTO_HASH(proto)     ((proto) & (IP_VS_PROTO_TAB_SIZE-1))

static struct ip_vs_protocol *ip_vs_proto_table[IP_VS_PROTO_TAB_SIZE];

函数register_ip_vs_protocol完成协议的注册,如下代码所示,将要注册的ip_vs_protocol结构体加入到相应单链表的头部。并且,如果此协议结构体实现了init回调函数,执行此回调。目前内核中支持的这五种协议都没有实现init回调函数。

static int __used __init register_ip_vs_protocol(struct ip_vs_protocol *pp)
{
    unsigned int hash = IP_VS_PROTO_HASH(pp->protocol);

    pp->next = ip_vs_proto_table[hash];
    ip_vs_proto_table[hash] = pp;

    if (pp->init != NULL)
        pp->init(pp);

    return 0;
}

以上的全局注册函数执行完成之后,内核会为每个网络命名空间执行__ip_vs_init函数,其中调用ip_vs_protocol_net_init函数完成协议protocol相关的命名空间数据初始化工作。

static int __net_init __ip_vs_init(struct net *net)
{
    struct netns_ipvs *ipvs;

    ipvs = net_generic(net, ip_vs_net_id);
    if (ipvs == NULL)
        return -ENOMEM;

    if (ip_vs_protocol_net_init(ipvs) < 0)
        goto protocol_fail;
}

函数ip_vs_protocol_net_init的工作主要是由函数register_ip_vs_proto_netns来完成,需要为每个支持的协议调用一次此函数,参数为协议相应的ip_vs_protocl类型结构体。

int __net_init ip_vs_protocol_net_init(struct netns_ipvs *ipvs)
{    
    static struct ip_vs_protocol *protos[] = {
#ifdef CONFIG_IP_VS_PROTO_TCP
    &ip_vs_protocol_tcp,
#endif
#ifdef CONFIG_IP_VS_PROTO_UDP
    &ip_vs_protocol_udp,
#endif
#ifdef CONFIG_IP_VS_PROTO_SCTP
    &ip_vs_protocol_sctp,
#endif
#ifdef CONFIG_IP_VS_PROTO_AH
    &ip_vs_protocol_ah,
#endif   
#ifdef CONFIG_IP_VS_PROTO_ESP
    &ip_vs_protocol_esp,
#endif
    };
    for (i = 0; i < ARRAY_SIZE(protos); i++) {
        ret = register_ip_vs_proto_netns(ipvs, protos[i]);
        if (ret < 0)  goto cleanup;
    }
}

在函数register_ip_vs_proto_netns中,内核为每个协议分配一个协议数据结构体,此结构最终保存在ipvs网络命名空间中的proto_data_table链表中。所有哈希值相同的协议的ip_vs_proto_data数据结构链接在一个链表上。

如果注册的协议初始化了init_netns回调指针,在此执行此回调函数,以下是对各个协议介绍。TCP/UDP/SCTP协议初始化了此函数,而AH/ESP无此函数。

static int register_ip_vs_proto_netns(struct netns_ipvs *ipvs, struct ip_vs_protocol *pp)
{
    unsigned int hash = IP_VS_PROTO_HASH(pp->protocol);
    struct ip_vs_proto_data *pd = kzalloc(sizeof(struct ip_vs_proto_data), GFP_KERNEL);

    if (!pd) return -ENOMEM;
    pd->pp = pp;    /* For speed issues */
    pd->next = ipvs->proto_data_table[hash];
    ipvs->proto_data_table[hash] = pd;
    atomic_set(&pd->appcnt, 0); /* Init app counter */

    if (pp->init_netns != NULL) {
        int ret = pp->init_netns(ipvs, pd);
        if (ret) {
            /* unlink an free proto data */
            ipvs->proto_data_table[hash] = pd->next;
            kfree(pd);
            return ret;
        }
    }
}

TCP协议

TCP协议的ip_vs_protocl结构体为以下的ip_vs_protocol_tcp,在其注册的函数回调中,最先使用的是init_netns,即函数__ip_vs_tcp_init,如上所述,在ipvs网络命名空间初始化时,将调用此函数。

struct ip_vs_protocol ip_vs_protocol_tcp = {
    .name =         "TCP",
    .protocol =     IPPROTO_TCP,
    .num_states =       IP_VS_TCP_S_LAST,
    .dont_defrag =      0,
    .init =         NULL,
    .exit =         NULL,
    .init_netns =       __ip_vs_tcp_init,
    .exit_netns =       __ip_vs_tcp_exit,
    .register_app =     tcp_register_app,
    .unregister_app =   tcp_unregister_app,
    .conn_schedule =    tcp_conn_schedule,
    .conn_in_get =      ip_vs_conn_in_get_proto,
    .conn_out_get =     ip_vs_conn_out_get_proto,
    .snat_handler =     tcp_snat_handler,
    .dnat_handler =     tcp_dnat_handler,
    .csum_check =       tcp_csum_check,
    .state_name =       tcp_state_name,
    .state_transition = tcp_state_transition,
    .app_conn_bind =    tcp_app_conn_bind,
    .debug_packet =     ip_vs_tcpudp_debug_packet,
    .timeout_change =   tcp_timeout_change,
};

其初始化ipvs网络命名空间中的链表数组tcp_apps,并且初始化协议状态相关的超时时间timeout_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;
} 
void ip_vs_init_hash_table(struct list_head *table, int rows)
{  
    while (--rows >= 0)
        INIT_LIST_HEAD(&table[rows]);
}

由以上的ip_vs_protocol_tcp结构可知,ipvs协议结构体ip_vs_protocol注意包含以下几类操作函数:

  • 初始化/退出
    init/exit, init_netns/exit_netns

  • 协议应用相关
    register_app/unregister_app/app_conn_bind

  • 连接相关
    conn_schedule/conn_in_get/conn_out_get/app_conn_bind

  • NAT相关
    tcp_snat_handler/tcp_dnat_handler

  • 协议状态相关
    state_name/state_transition/timeout_change

  • 其它
    csum_check/debug_packet

UDP协议

UDP协议的ip_vs_protocl结构体为以下的ip_vs_protocol_udp,其未实现init函数回调。

struct ip_vs_protocol ip_vs_protocol_udp = {
    .name =         "UDP",
    .protocol =     IPPROTO_UDP,
    .num_states =       IP_VS_UDP_S_LAST,
    .dont_defrag =      0,
    .init =         NULL,
    .exit =         NULL,
    .init_netns =       __udp_init,
    .exit_netns =       __udp_exit,
    .conn_schedule =    udp_conn_schedule,
    .conn_in_get =      ip_vs_conn_in_get_proto,
    .conn_out_get =     ip_vs_conn_out_get_proto,
    .snat_handler =     udp_snat_handler,
    .dnat_handler =     udp_dnat_handler,
    .csum_check =       udp_csum_check,
    .state_transition = udp_state_transition,
    .state_name =       udp_state_name,
    .register_app =     udp_register_app,
    .unregister_app =   udp_unregister_app,
    .app_conn_bind =    udp_app_conn_bind,
    .debug_packet =     ip_vs_tcpudp_debug_packet,
    .timeout_change =   NULL,
};

其中init_netns回调函数指针,即__udp_init函数在ipvs网络命名空间初始化时被调用,用于初始化ipvs命名空间结构成员udp_apps链表数组,以及初始化超时时间表timeout_table。

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

SCTP协议

SCTP协议的ip_vs_protocl结构体为以下的ip_vs_protocol_sctp,其未实现init函数回调。

struct ip_vs_protocol ip_vs_protocol_sctp = {
    .name       = "SCTP",
    .protocol   = IPPROTO_SCTP,
    .num_states = IP_VS_SCTP_S_LAST,
    .dont_defrag    = 0,
    .init       = NULL,
    .exit       = NULL,
    .init_netns = __ip_vs_sctp_init,
    .exit_netns = __ip_vs_sctp_exit,
    .register_app   = sctp_register_app,
    .unregister_app = sctp_unregister_app,
    .conn_schedule  = sctp_conn_schedule,
    .conn_in_get    = ip_vs_conn_in_get_proto,
    .conn_out_get   = ip_vs_conn_out_get_proto,
    .snat_handler   = sctp_snat_handler,
    .dnat_handler   = sctp_dnat_handler,
    .csum_check = sctp_csum_check,
    .state_name = sctp_state_name,
    .state_transition = sctp_state_transition,
    .app_conn_bind  = sctp_app_conn_bind,
    .debug_packet   = ip_vs_tcpudp_debug_packet,
    .timeout_change = NULL,
};

其中init_netns回调函数指针,即__ip_vs_sctp_init函数在ipvs网络命名空间初始化时被调用,用于初始化ipvs命名空间结构成员sctp_apps链表数组,以及初始化超时时间表timeout_table。

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

AH协议

AH协议的ip_vs_protocl结构体为以下的ip_vs_protocol_ah,其未实现init函数指针以及init_netns函数指针。所以在初始化过程中不会调用到其中的函数。AH协议与之后的ESP协议仅实现了ip_vs_protocol结构中连接相关的函数调用。

struct ip_vs_protocol ip_vs_protocol_ah = {
    .name =         "AH",
    .protocol =     IPPROTO_AH,
    .num_states =       1,
    .dont_defrag =      1,
    .init =         NULL,
    .exit =         NULL,
    .conn_schedule =    ah_esp_conn_schedule,
    .conn_in_get =      ah_esp_conn_in_get,
    .conn_out_get =     ah_esp_conn_out_get,
    .snat_handler =     NULL,
    .dnat_handler =     NULL,
    .csum_check =       NULL,
    .state_transition = NULL,
    .register_app =     NULL,
    .unregister_app =   NULL,
    .app_conn_bind =    NULL,
    .debug_packet =     ip_vs_tcpudp_debug_packet,
    .timeout_change =   NULL,       /* ISAKMP */
};

ESP协议

ESP协议的ip_vs_protocl结构体为以下的ip_vs_protocol_esp,其未实现init函数指针以及init_netns函数指针。所以在初始化过程中不会调用到其中的函数。

struct ip_vs_protocol ip_vs_protocol_esp = {
    .name =         "ESP",
    .protocol =     IPPROTO_ESP,
    .num_states =       1,
    .dont_defrag =      1,
    .init =         NULL,
    .exit =         NULL,
    .conn_schedule =    ah_esp_conn_schedule,
    .conn_in_get =      ah_esp_conn_in_get,
    .conn_out_get =     ah_esp_conn_out_get,
    .snat_handler =     NULL,
    .dnat_handler =     NULL,
    .csum_check =       NULL,
    .state_transition = NULL,
    .register_app =     NULL,
    .unregister_app =   NULL,
    .app_conn_bind =    NULL,
    .debug_packet =     ip_vs_tcpudp_debug_packet,
    .timeout_change =   NULL,       /* ISAKMP */
};

协议查询

由以上介绍的协议注册函数register_ip_vs_protocol可知,内核支持的协议都保存在了ip_vs_proto_table全局链表数组中,其索引为由协议号计算的hash值。以下函数ip_vs_proto_get遍历协议所对应的链表,通过比较协议号,来查找注册的ip_vs_protocol协议结构。

struct ip_vs_protocol * ip_vs_proto_get(unsigned short proto)
{
    struct ip_vs_protocol *pp;
    unsigned int hash = IP_VS_PROTO_HASH(proto);

    for (pp = ip_vs_proto_table[hash]; pp; pp = pp->next) {
        if (pp->protocol == proto)
            return pp;
    }

    return NULL;
}

与协议相关的协议数据结构,内核使用函数ip_vs_proto_data_get来查找。由于ipvs系统将协议的数据都保存在了ipvs网络命名空间结构的链表数组proto_data_table中,其索引为由协议号计算而来的hash值,此函数遍历协议对应的链表,通过比较协议号是否相等,查找协议数据结构ip_vs_proto_data。

struct ip_vs_proto_data *ip_vs_proto_data_get(struct netns_ipvs *ipvs, unsigned short proto)
{
    struct ip_vs_proto_data *pd;
    unsigned int hash = IP_VS_PROTO_HASH(proto);

    for (pd = ipvs->proto_data_table[hash]; pd; pd = pd->next) {
        if (pd->pp->protocol == proto)
            return pd;
    }

    return NULL;
}

内核版本 4.15

你可能感兴趣的:(负载均衡,ipvs)