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协议的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协议的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协议的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协议的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协议的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