IPVS子系统的app应用结构
在ipvs网络命名空间初始化时,调用函数ip_vs_app_net_init初始化app模块。其中初始化了ipvs网络命名空间结构中的成员app_list链表。另外其还在proc文件系统中创建了名称为"ip_vs_app"的文件,用于显示当前的app应用信息。
int __net_init ip_vs_app_net_init(struct netns_ipvs *ipvs)
{
INIT_LIST_HEAD(&ipvs->app_list);
proc_create("ip_vs_app", 0, ipvs->net->proc_net, &ip_vs_app_fops);
return 0;
}
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;
net->ipvs = ipvs;
if (ip_vs_app_net_init(ipvs) < 0)
goto app_fail;
}
IPVS应用注册
ipvs应用的注册由函数register_ip_vs_app完成,如下所示。其首先在ipvs网络命名空间结构的成员app_list链表中进行查找,如果已经存在名称与要注册的app相同的应用,返回错误EEXIST。否则,将重新分配一个全新的ip_vs_app结构,并将要注册的app内容拷贝到新分配的ip_vs_app结构中。
初始化新分配ip_vs_app结构的incs_list链表(稍后介绍此链表),并将其链接到ipvs网络命名空间结构的app_list链表中。此函数的返回值为新创建的ip_vs_app结构。
struct ip_vs_app *register_ip_vs_app(struct netns_ipvs *ipvs, struct ip_vs_app *app)
{
struct ip_vs_app *a;
list_for_each_entry(a, &ipvs->app_list, a_list) {
if (!strcmp(app->name, a->name)) {
err = -EEXIST;
goto out_unlock;
}
}
a = kmemdup(app, sizeof(*app), GFP_KERNEL);
if (!a) {
err = -ENOMEM;
goto out_unlock;
}
INIT_LIST_HEAD(&a->incs_list);
list_add(&a->a_list, &ipvs->app_list);
}
一些网络应用使用多个端口,ipvs称每个端口为该应用的一个化身(incarnation)。ipvs目前支持一个应用最多8个(IP_VS_APP_MAX_PORTS)不同的端口。函数ip_vs_app_inc_new用于添加新的应用incarnation,首先分配一个ip_vs_app结构,并将参数app拷贝到新分配的结构中;
其次与以上的register_ip_vs_app函数不同,这里要初始化应用的端口号,如果有参数app的成员timeouts有值,则进行拷贝,之前的结构体拷贝不能拷贝timeouts的内存。
最后是调用协议相关的应用注册函数(register_app回调指针),对于TCP协议来说,其为tcp_register_app;对于UDP协议来说,其为udp_register_app;SCTP协议的相应函数为sctp_register_app。将此应用incarnation连接到应用的incs_list链表中。
static int ip_vs_app_inc_new(struct netns_ipvs *ipvs, struct ip_vs_app *app, __u16 proto, __u16 port)
{
struct ip_vs_protocol *pp;
struct ip_vs_app *inc;
if (!(pp = ip_vs_proto_get(proto)))
return -EPROTONOSUPPORT;
if (!pp->unregister_app)
return -EOPNOTSUPP;
inc = kmemdup(app, sizeof(*inc), GFP_KERNEL);
if (!inc) return -ENOMEM;
INIT_LIST_HEAD(&inc->p_list);
INIT_LIST_HEAD(&inc->incs_list);
inc->app = app;
inc->port = htons(port);
atomic_set(&inc->usecnt, 0);
if (app->timeouts) {
inc->timeout_table = ip_vs_create_timeout_table(app->timeouts, app->timeouts_size);
if (!inc->timeout_table) {
ret = -ENOMEM;
goto out;
}
}
ret = pp->register_app(ipvs, inc);
if (ret) goto out;
list_add(&inc->a_list, &app->incs_list);
}
此处以TCP协议为例,tcp_register_app函数执行的功能很简单,即将应用的incarnation结构体连接到ipvs网络命名空间的链表数组tcp_apps相应的链表上,数组的索引由端口号取hash值获得。
static int tcp_register_app(struct netns_ipvs *ipvs, struct ip_vs_app *inc)
{
struct ip_vs_app *i;
__u16 hash;
__be16 port = inc->port;
int ret = 0;
struct ip_vs_proto_data *pd = ip_vs_proto_data_get(ipvs, IPPROTO_TCP);
hash = tcp_app_hashkey(port);
list_for_each_entry(i, &ipvs->tcp_apps[hash], p_list) {
if (i->port == port) {
ret = -EEXIST;
goto out;
}
}
list_add_rcu(&inc->p_list, &ipvs->tcp_apps[hash]);
atomic_inc(&pd->appcnt);
}
其与两个协议应用注册函数udp_register_ap和sctp_register_ap功能与以上TCP协议应用注册函数类型,只是链接的ipvs网络命名空间结构的成员的链表不同,如下,UDP协议应用链接在udp_apps链表上;SCTP协议应用链接在sctp_apps链表上。
struct netns_ipvs {
/* ip_vs_proto_tcp */
#ifdef CONFIG_IP_VS_PROTO_TCP
#define TCP_APP_TAB_BITS 4
#define TCP_APP_TAB_SIZE (1 << TCP_APP_TAB_BITS)
#define TCP_APP_TAB_MASK (TCP_APP_TAB_SIZE - 1)
struct list_head tcp_apps[TCP_APP_TAB_SIZE];
#endif
/* ip_vs_proto_udp */
#ifdef CONFIG_IP_VS_PROTO_UDP
#define UDP_APP_TAB_BITS 4
#define UDP_APP_TAB_SIZE (1 << UDP_APP_TAB_BITS)
#define UDP_APP_TAB_MASK (UDP_APP_TAB_SIZE - 1)
struct list_head udp_apps[UDP_APP_TAB_SIZE];
#endif
/* ip_vs_proto_sctp */
#ifdef CONFIG_IP_VS_PROTO_SCTP
#define SCTP_APP_TAB_BITS 4
#define SCTP_APP_TAB_SIZE (1 << SCTP_APP_TAB_BITS)
#define SCTP_APP_TAB_MASK (SCTP_APP_TAB_SIZE - 1)
/* Hash table for SCTP application incarnations */
struct list_head sctp_apps[SCTP_APP_TAB_SIZE];
#endif
}
IPVS应用绑定
诸如TCP协议的应用注册函数tcp_register_app,在注册完成之后,将ipvs网络命名空间中的协议数据结构的appcnt计数增加一。这样在做应用绑定之前,可先检查appcnt的值,如果没有注册任何应用,就不需进行绑定。
static int tcp_register_app(struct netns_ipvs *ipvs, struct ip_vs_app *inc)
{
struct ip_vs_proto_data *pd = ip_vs_proto_data_get(ipvs, IPPROTO_TCP);
atomic_inc(&pd->appcnt);
}
在新建连接(函数ip_vs_conn_new)过程中,或者连接同步操作中,使用函数ip_vs_bind_app将连接与应用进行绑定。此函数封装了特定协议的应用绑定函数。
int ip_vs_bind_app(struct ip_vs_conn *cp, struct ip_vs_protocol *pp)
{
return pp->app_conn_bind(cp);
}
对于TCP协议而言,此函数为tcp_app_conn_bind。对于UDP和SCTP协议,分别为函数:udp_app_conn_bind和sctp_app_conn_bind。以下以TCP协议的应用绑定函数为例,由以下代码可知,仅需对转发模式为NAT的连接进行绑定。
对于TCP将遍历ipvs网络命名空间结构中的tcp_apps应用incarnation链表,对于连接的端口号和应用incarnation的端口,如果相等进行绑定,即将应用incarnation结构赋予连接的app指针。
最后,如果应用incarnation初始化了init_conn函数指针,执行此指针函数。目前ipvs仅有一个ftp应用模块,其注册的init_conn函数指针为ip_vs_ftp_init_conn函数。
static int tcp_app_conn_bind(struct ip_vs_conn *cp)
{
struct netns_ipvs *ipvs = cp->ipvs;
struct ip_vs_app *inc;
/* Default binding: bind app only for NAT */
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
return 0;
/* Lookup application incarnations and bind the right one */
hash = tcp_app_hashkey(cp->vport);
list_for_each_entry_rcu(inc, &ipvs->tcp_apps[hash], p_list) {
if (inc->port == cp->vport) {
if (unlikely(!ip_vs_app_inc_get(inc)))
break;
IP_VS_DBG_BUF(9, "%s(): Binding conn %s:%u->%s:%u to app %s on port %u\n", __func__,
IP_VS_DBG_ADDR(cp->af, &cp->caddr), ntohs(cp->cport), IP_VS_DBG_ADDR(cp->af, &cp->vaddr), ntohs(cp->vport),
inc->name, ntohs(inc->port));
cp->app = inc;
if (inc->init_conn)
result = inc->init_conn(inc, cp);
break;
}
}
}
IPVS应用输入处理
在连接的协议处理过程中,对于NAT转发模式,检查是否有ipvs应用可处理此连接。对于TCP协议而言,即在函数tcp_dnat_handler中调用ipvs应用输入处理函数:ip_vs_app_pkt_in。在输入端执行DNAT操作。
static int tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, struct ip_vs_iphdr *iph)
{
if (unlikely(cp->app != NULL)) {
if (!(ret = ip_vs_app_pkt_in(cp, skb)))
return 0;
}
对于TCP协议,需要调用app_tcp_pkt_in进行处理,其它协议如UDP或SCTP,直接调用ipvs应用的pkt_in函数指针进行处理。
int ip_vs_app_pkt_in(struct ip_vs_conn *cp, struct sk_buff *skb)
{
struct ip_vs_app *app;
/* check if application module is bound to this ip_vs_conn.
*/
if ((app = cp->app) == NULL)
return 1;
/* TCP is complicated */
if (cp->protocol == IPPROTO_TCP)
return app_tcp_pkt_in(cp, skb, app);
/* Call private input hook function
*/
if (app->pkt_in == NULL)
return 1;
return app->pkt_in(app, cp, skb, NULL);
}
对于TCP协议,由于ipvs应用的NAT操作可能修改数据包的大小,所以在执行其操作的前后,需要按需要调整TCP头部的序号信息。
static inline int app_tcp_pkt_in(struct ip_vs_conn *cp, struct sk_buff *skb, struct ip_vs_app *app)
{
const unsigned int tcp_offset = ip_hdrlen(skb);
struct tcphdr *th;
/* Remember seq number in case this pkt gets resized
*/
seq = ntohl(th->seq);
/* Fix seq stuff if flagged as so.
*/
if (cp->flags & IP_VS_CONN_F_IN_SEQ)
vs_fix_seq(&cp->in_seq, th);
if (cp->flags & IP_VS_CONN_F_OUT_SEQ)
vs_fix_ack_seq(&cp->out_seq, th);
/* Call private input hook function
*/
if (app->pkt_in == NULL)
return 1;
if (!app->pkt_in(app, cp, skb, &diff))
return 0;
/* Update ip_vs seq stuff if len has changed.
*/
if (diff != 0)
vs_seq_update(cp, &cp->in_seq, IP_VS_CONN_F_IN_SEQ, seq, diff);
return 1;
}
IPVS应用输出处理
在连接的协议处理过程中,对于NAT转发模式,检查是否有ipvs应用可处理此连接。对于TCP协议而言,即在函数tcp_snat_handler中调用ipvs应用输入处理函数:ip_vs_app_pkt_out。在输出端执行SNAT操作。
static int tcp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp, struct ip_vs_iphdr *iph)
{
if (unlikely(cp->app != NULL)) {
/* Call application helper if needed */
if (!(ret = ip_vs_app_pkt_out(cp, skb)))
return 0;
}
对于TCP协议,需要调用app_tcp_pkt_out进行处理,其它协议如UDP或SCTP,直接调用ipvs应用的pkt_out函数指针进行处理。
int ip_vs_app_pkt_out(struct ip_vs_conn *cp, struct sk_buff *skb)
{
struct ip_vs_app *app;
/* check if application module is bound to this ip_vs_conn.
*/
if ((app = cp->app) == NULL)
return 1;
/* TCP is complicated */
if (cp->protocol == IPPROTO_TCP)
return app_tcp_pkt_out(cp, skb, app);
/* Call private output hook function */
if (app->pkt_out == NULL)
return 1;
return app->pkt_out(app, cp, skb, NULL);
}
内核版本 4.15
上一篇: IPVS支持的协议
下一篇: IPVS收发数据相关的速率计算
推荐阅读
-
IPVS子系统的app应用结构
-
ASP.NET(C#)应用程序配置文件app.config/web.config的增、删、改操作
-
ASP.NET(C#)应用程序配置文件app.config/web.config的增、删、改操作
-
Android应用开发的一般文件组织结构讲解
-
如何实现双(多)语种网站内容的国际化? 博客分类: Work Related 数据结构CMS企业应用配置管理项目管理
-
解析WPF绑定层次结构数据的应用详解
-
读取linux input 的event 事件的 应用程序 博客分类: linux_app
-
Android应用APP自动更新功能的代码实现
-
Android开发5:应用程序窗口小部件App Widgets的实现(附demo)
-
解析WPF绑定层次结构数据的应用详解