欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

IPVS子系统的app应用结构

程序员文章站 2024-03-13 15:56:21
...

在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