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

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