IPVS源代码分析---总述和初始化

主要参考了http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/ 这个系列讲的非常详细。以及yfydz的博客,很多对代码的注释都是直接转载的他的内容,先说明一下,我自己写的主要是对代码整体脉络和思路的分析。

IPVS这部分的代码看了挺长时间了,对于普通应用的处理,相对简单。 
对于FTP这种多连接的处理,IPVS虽然目前只支持FTP,但是用了很多代码来处理这种affinity connection,其中用到了persistent connection和template。其中,persistent connection是指对于多连接的服务,需要把connection都定向到同一个server上面,persistent这个标记是在ipvsadm 添加service时就配置的。template是指对于多连接的服务,创建了一个template connection作为其他连接的template,(这里的其他连接是指从同一个src发出的,被iptables打过mark的连接,iptables可以对相关的连接根据端口号打上mark,方便IPVS的处理。)这样其他连接就根据template中指向的dest,也定向到了dest。也就说相关的连接都发到了同一个dest。


根据LVS官方网站的介绍,LVS支持三种负载均衡模式:NAT,tunnel和direct routing(DR)。
NAT是通用模式,所有交互数据必须通过均衡器;后两种则是一种半连接处理方式,请求数据通过均衡器,而服务器的回应则是直接路由返回的,
而这两种方法的区别是tunnel模式下由于进行了IP封装所以可路由,而DR方式是修改MAC地址来实现,所以必须同一网段.
[主要数据结构]
这个结构用来描述IPVS支持的IP协议。IPVS的IP层协议支持TCP, UDP, AH和ESP这4种IP层协议
struct ip_vs_protocol {
        //链表中的下一项
        struct ip_vs_protocol   *next;
        //协议名称, "TCP", "UDP".
        char                    *name;
        //协议值
        __u16                   protocol;
        //不进行分片
        int                     dont_defrag;
        //协议应用计数器,根据是该协议的中多连接协议的数量
        atomic_t                appcnt;
        //协议各状态的超时数组
        int                     *timeout_table;

        void (*init)(struct ip_vs_protocol *pp);  //协议初始化
        void (*exit)(struct ip_vs_protocol *pp); //协议释放
        int (*conn_schedule)(struct sk_buff *skb, struct ip_vs_protocol *pp, int *verdict, struct ip_vs_conn **cpp); //协议调度
        //查找in方向的IPVS连接
        struct ip_vs_conn * (*conn_in_get)(const struct sk_buff *skb, struct ip_vs_protocol *pp,
                       const struct iphdr *iph, unsigned int proto_off, int inverse);
        //查找out方向的IPVS连接
        struct ip_vs_conn * (*conn_out_get)(const struct sk_buff *skb, struct ip_vs_protocol *pp,
                        const struct iphdr *iph, unsigned int proto_off, int inverse);
        //源NAT操作
        int (*snat_handler)(struct sk_buff **pskb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp);
        //目的NAT操作
        int (*dnat_handler)(struct sk_buff **pskb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp);
        //协议校验和计算
        int (*csum_check)(struct sk_buff *skb, struct ip_vs_protocol *pp);
        //当前协议状态名称: 如"LISTEN", "ESTABLISH"
        const char *(*state_name)(int state);
        //协议状态迁移
        int (*state_transition)(struct ip_vs_conn *cp, int direction, const struct sk_buff *skb, struct ip_vs_protocol *pp);
        //登记应用
        int (*register_app)(struct ip_vs_app *inc);
        //去除应用登记
        void (*unregister_app)(struct ip_vs_app *inc);

        int (*app_conn_bind)(struct ip_vs_conn *cp);
        //数据包打印
        void (*debug_packet)(struct ip_vs_protocol *pp, const struct sk_buff *skb, int offset, const char *msg);
        //调整超时
        void (*timeout_change)(struct ip_vs_protocol *pp, int flags);
        //设置各种状态下的协议超时
        int (*set_state_timeout)(struct ip_vs_protocol *pp, char *sname, int to);
};
这个结构用来描述IPVS的连接。IPVS的连接和netfilter定义的连接类似
struct ip_vs_conn {
        struct list_head         c_list;            //HASH链表
        __u32                   caddr;          //客户机地址
        __u32                   vaddr;          //服务器对外的虚拟地址
        __u32                   daddr;          //服务器实际地址
        __u16                   cport;          //客户端的端口
        __u16                   vport;         //服务器对外虚拟端口
        __u16                   dport;         //服务器实际端口
        __u16                   protocol;      //协议类型

        atomic_t                refcnt;        //连接引用计数
        struct timer_list       timer;           //定时器
        volatile unsigned long  timeout;       //超时时间

        spinlock_t              lock;           //状态转换锁
        volatile __u16          flags;          /* status flags */
        volatile __u16          state;          /* state info */

        struct ip_vs_conn       *control;       //主连接, 如FTP
        atomic_t                n_control;      //子连接数
        struct ip_vs_dest       *dest;          //真正服务器
        atomic_t                in_pkts;        //进入的数据统计

        int (*packet_xmit)(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp); //数据包发送

        struct ip_vs_app        *app;           //IPVS应用
        void                    *app_data;      //应用的私有数据
        struct ip_vs_seq        in_seq;         //进入数据的序列号
        struct ip_vs_seq        out_seq;       //发出数据的序列号
};
这个结构用来描述IPVS对外的虚拟服务器信息
struct ip_vs_service {
        struct list_head        s_list;        //按普通协议,地址,端口进行HASH的链表
        struct list_head        f_list;        //按nfmark进行HASH的链表
        atomic_t                refcnt;      //引用计数
        atomic_t                usecnt;      //使用计数

        __u16                   protocol;   //协议
        __u32                   addr;       //虚拟服务器地址
        __u16                   port;       //虚拟服务器端口
        __u32                   fwmark;    //就是skb中的nfmark
        unsigned                flags;       //状态标志
        unsigned                timeout;     //超时
        __u32                   netmask;    //网络掩码

        struct list_head        destinations;  //真实服务器的地址链表
        __u32                  num_dests;  //真实服务器的数量
        struct ip_vs_stats      stats;        //服务统计信息
        struct ip_vs_app        *inc;         //应用

        struct ip_vs_scheduler  *scheduler;    //调度指针
        rwlock_t                sched_lock;    //调度锁
        void                    *sched_data;   //调度私有数据
};
这个结构用来描述具体的真实服务器的信息
struct ip_vs_dest {
        struct list_head        n_list;   /* for the dests in the service */
        struct list_head        d_list;   /* for table with all the dests */

        __u32                   addr;           //服务器地址
        __u16                   port;           //服务器端口
        volatile unsigned        flags;          //目标标志,易变参数
        atomic_t                conn_flags;     //连接标志
        atomic_t                weight;         //服务器权重

        atomic_t                refcnt;         //引用计数
        struct ip_vs_stats      stats;          //统计数

        atomic_t                activeconns;    //活动的连接
        atomic_t                inactconns;     //不活动的连接
        atomic_t                persistconns;   //保持的连接,常驻
        __u32                   u_threshold;   //连接上限
        __u32                   l_threshold;    //连接下限

        /* for destination cache */
        spinlock_t              dst_lock;       /* lock of dst_cache */
        struct dst_entry        *dst_cache;     /* destination cache entry */
        u32                     dst_rtos;
        struct ip_vs_service    *svc;           /* service it belongs to */
        __u16                   protocol;       /* which protocol (TCP/UDP) */
        __u32                   vaddr;          /* virtual IP address */
        __u16                   vport;          /* virtual port number */
        __u32                   vfwmark;        /* firewall mark of service */
};
这个结构用来描述IPVS调度算法,目前调度方法包括rr,wrr,lc, wlc, lblc, lblcr, dh, sh等
struct ip_vs_scheduler {
        struct list_head        n_list;         /* d-linked list head */
        char                    *name;          /* scheduler name */
        atomic_t                refcnt;         /* reference counter */
        struct module           *module;        /* THIS_MODULE/NULL */

        /* scheduler initializing service */
        int (*init_service)(struct ip_vs_service *svc);
        /* scheduling service finish */
        int (*done_service)(struct ip_vs_service *svc);
        /* scheduler updating service */
        int (*update_service)(struct ip_vs_service *svc);

        /* selecting a server from the given service */
        struct ip_vs_dest* (*schedule)(struct ip_vs_service *svc, const struct sk_buff *skb);
};

IPVS应用是针对多连接协议的, 目前也就只支持FTP。
由于ip_vs_app.c是从2.2过来的,没有管内核是否本身有NAT的情况,所以相当于自身实现了应用协议的NAT处理,包 括内容信息的改变,
TCP序列号确认号的调整等,而现在这些都由netfilter实现了,IPVS可以不用管这些,只处理连接调度就行了。
IPVS的应用模块化还不是很好,在处理连接端口时,还要判断是否是FTPPORT,也就是说不支持其他多连接协议的,
应该象netfilter一样为每个多连接协议设置一个helper,自动调用,不用在程序里判断端口。

struct ip_vs_app
{
        struct list_head        a_list;           //用来挂接到应用链表
        int                     type;           /* IP_VS_APP_TYPE_xxx */
        char                    *name;          /* application module name */
        __u16                   protocol;      //协议, TCP, UD
        struct module           *module;        /* THIS_MODULE/NULL */
        struct list_head        incs_list;        //应用的具体实例链表

        /* members for application incarnations */
        struct list_head        p_list;         //将应用结构挂接到对应协议(TCP, UDP...)的应用表
        struct ip_vs_app        *app;           /* its real application */
        __u16                   port;           /* port number in net order */
        atomic_t                usecnt;         /* usage counter */

        /* output hook: return false if can't linearize. diff set for TCP.  */
        int (*pkt_out)(struct ip_vs_app *, struct ip_vs_conn *, struct sk_buff **, int *diff);
        /* input hook: return false if can't linearize. diff set for TCP. */
        int (*pkt_in)(struct ip_vs_app *, struct ip_vs_conn *, struct sk_buff **, int *diff);
        /* ip_vs_app initializer */
        int (*init_conn)(struct ip_vs_app *, struct ip_vs_conn *);
        /* ip_vs_app finish */
        int (*done_conn)(struct ip_vs_app *, struct ip_vs_conn *);
        /* not used now */
        int (*bind_conn)(struct ip_vs_app *, struct ip_vs_conn *, struct ip_vs_protocol *);
        void (*unbind_conn)(struct ip_vs_app *, struct ip_vs_conn *);

        int *                   timeout_table;
        int *                   timeouts;
        int                     timeouts_size;

        int (*conn_schedule)(struct sk_buff *skb, struct ip_vs_app *app, int *verdict, struct ip_vs_conn **cpp);
        struct ip_vs_conn *
        (*conn_in_get)(const struct sk_buff *skb, struct ip_vs_app *app, const struct iphdr *iph, unsigned int proto_off, int inverse);
        struct ip_vs_conn *
        (*conn_out_get)(const struct sk_buff *skb, struct ip_vs_app *app, const struct iphdr *iph, unsigned int proto_off, int inverse);
        int (*state_transition)(struct ip_vs_conn *cp, int direction, const struct sk_buff *skb, struct ip_vs_app *app);
        void (*timeout_change)(struct ip_vs_app *app, int flags);
};
用户空间信息是ipvsadm程序接收用户输入后传递给内核ipvs的信息,信息都是很直接的,没有各种控制信息。
ipvsadm和ipvs的关系相当于iptables和netfilter的关系.

用户空间的虚拟服务信息
struct ip_vs_service_user {
        /* virtual service addresses */
        u_int16_t               protocol;
        u_int32_t               addr;           /* virtual ip address */
        u_int16_t               port;
        u_int32_t               fwmark;         /* firwall mark of service */

        /* virtual service options */
        char                    sched_name[IP_VS_SCHEDNAME_MAXLEN];
        unsigned                flags;          /* virtual service flags */
        unsigned                timeout;        /* persistent timeout in sec */
        u_int32_t               netmask;        /* persistent netmask */
};
用户空间的真实服务器信息
struct ip_vs_dest_user {
        /* destination server address */
        u_int32_t               addr;
        u_int16_t               port;
        /* real server options */
        unsigned                conn_flags;     /* connection flags */
        int                     weight;         /* destination weight */
        /* thresholds for active connections */
        u_int32_t               u_threshold;    /* upper threshold */
        u_int32_t               l_threshold;    /* lower threshold */
};
用户空间的统计信息
struct ip_vs_stats_user
{
        __u32                   conns;          /* connections scheduled */
        __u32                   inpkts;         /* incoming packets */
        __u32                   outpkts;        /* outgoing packets */
        __u64                   inbytes;        /* incoming bytes */
        __u64                   outbytes;       /* outgoing bytes */
        __u32                   cps;            /* current connection rate */
        __u32                   inpps;          /* current in packet rate */
        __u32                   outpps;         /* current out packet rate */
        __u32                   inbps;          /* current in byte rate */
        __u32                   outbps;         /* current out byte rate */
};
用户空间的获取信息结构
struct ip_vs_getinfo {
        /* version number */
        unsigned int            version;
        /* size of connection hash table */
        unsigned int            size;
        /* number of virtual services */
        unsigned int            num_services;
};
用户空间的服务规则项信息
struct ip_vs_service_entry {
        /* which service: user fills in these */
        u_int16_t               protocol;
        u_int32_t               addr;           /* virtual address */
        u_int16_t               port;
        u_int32_t               fwmark;         /* firwall mark of service */

        /* service options */
        char                    sched_name[IP_VS_SCHEDNAME_MAXLEN];
        unsigned                flags;          /* virtual service flags */
        unsigned                timeout;        /* persistent timeout */
        u_int32_t               netmask;        /* persistent netmask */

        /* number of real servers */
        unsigned int            num_dests;
        /* statistics */
        struct ip_vs_stats_user stats;
};
用户空间的服务器项信息
struct ip_vs_dest_entry {
        u_int32_t               addr;           /* destination address */
        u_int16_t               port;
        unsigned                conn_flags;     /* connection flags */
        int                     weight;         /* destination weight */

        u_int32_t               u_threshold;    /* upper threshold */
        u_int32_t               l_threshold;    /* lower threshold */

        u_int32_t               activeconns;    /* active connections */
        u_int32_t               inactconns;     /* inactive connections */
        u_int32_t               persistconns;   /* persistent connections */

        /* statistics */
        struct ip_vs_stats_user stats;
};
用户空间的获取服务器项信息
struct ip_vs_get_dests {
        /* which service: user fills in these */
        u_int16_t               protocol;
        u_int32_t               addr;           /* virtual address */
        u_int16_t               port;
        u_int32_t               fwmark;         /* firwall mark of service */

        /* number of real servers */
        unsigned int            num_dests;

        /* the real servers */
        struct ip_vs_dest_entry entrytable[0];
};
用户空间的获取虚拟服务项信息
struct ip_vs_get_services {
        /* number of virtual services */
        unsigned int            num_services;
        /* service table */
        struct ip_vs_service_entry entrytable[0];
};
用户空间的获取超时信息结构
struct ip_vs_timeout_user {
        int                     tcp_timeout;
        int                     tcp_fin_timeout;
        int                     udp_timeout;
};
用户空间的获取IPVS内核守护进程信息结构
struct ip_vs_daemon_user {
        /* sync daemon state (master/backup) */
        int                     state;
        /* multicast interface name */
        char                    mcast_ifn[IP_VS_IFNAME_MAXLEN];
        /* SyncID we belong to */
        int                     syncid;
};
[/主要数据结构]
static int __init ip_vs_init(void)
{
      int ret;

      //初始化ipvs的控制接口,set/get sockopt操作
      ret = ip_vs_control_init();
      if (ret < 0) {
            IP_VS_ERR("can't setup control.\n");
            goto cleanup_nothing;
      }
      //协议初始化
      ip_vs_protocol_init();
      //应用层辅助接口初始化
      ret = ip_vs_app_init();
      if (ret < 0) {
            IP_VS_ERR("can't setup application helper.\n");
            goto cleanup_protocol;
      }
      //主要数据结构初始化
      ret = ip_vs_conn_init();
      if (ret < 0) {
            IP_VS_ERR("can't setup connection table.\n");
            goto cleanup_app;
      }
      //下面分别挂接各个处理点到netfilter架构中,看下面hook点实现
      //关于hook点知识,参考ip_conntrack实现
      ret = nf_register_hook(&ip_vs_in_ops);
      if (ret < 0) {
            IP_VS_ERR("can't register in hook.\n");
            goto cleanup_conn;
      }
      ret = nf_register_hook(&ip_vs_out_ops);
      if (ret < 0) {
            IP_VS_ERR("can't register out hook.\n");
            goto cleanup_inops;
      }
      ret = nf_register_hook(&ip_vs_post_routing_ops);
      if (ret < 0) {
            IP_VS_ERR("can't register post_routing hook.\n");
            goto cleanup_outops;
      }
      ret = nf_register_hook(&ip_vs_forward_icmp_ops);
      if (ret < 0) {
            IP_VS_ERR("can't register forward_icmp hook.\n");
            goto cleanup_postroutingops;
      }

      IP_VS_INFO("ipvs loaded.\n");
      return ret;
      ......
}
控制接口初始化
int ip_vs_control_init(void)
{
      int ret;
      int idx;
      //登记ipvs的sockopt控制,这样用户空间可通过setsockopt函数来和ipvs进行通信,看下面控制接口实现
      ret = nf_register_sockopt(&ip_vs_sockopts);
      if (ret) {
           IP_VS_ERR("cannot register sockopt.\n");
           return ret;
      }
      //建立/proc/net/ip_vs和/proc/net/ip_vs_stats只读项
      //看下面控制接口实现
      proc_net_fops_create("ip_vs", 0, &ip_vs_info_fops);
      proc_net_fops_create("ip_vs_stats",0, &ip_vs_stats_fops);

      //建立/proc/sys/net/ipv4/vs目录下的各可读写控制参数
      sysctl_header = register_sysctl_table(vs_root_table, 0);

      //初始化各种双向链表
      //svc_table是根据协议地址端口等信息进行服务结构struct ip_vs_service查找的HASH表
      //svc_fwm_table是根据数据包的nfmark信息进行服务结构struct ip_vs_service查找的HASH表
      for(idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++)  {
            INIT_LIST_HEAD(&ip_vs_svc_table[idx]);
            INIT_LIST_HEAD(&ip_vs_svc_fwm_table[idx]);
      }
      //rtable是目的结构struct ip_vs_dest的HASH链表
      for(idx = 0; idx < IP_VS_RTAB_SIZE; idx++)  {
            INIT_LIST_HEAD(&ip_vs_rtable[idx]);
      }
      //ipvs统计信息
      memset(&ip_vs_stats, 0, sizeof(ip_vs_stats));
      spin_lock_init(&ip_vs_stats.lock);              //统计锁
      //对当前统计信息建立一个预估器,可用于计算服务器的性能参数
      ip_vs_new_estimator(&ip_vs_stats);
      //挂一个定时操作,根据系统当前负载情况定时调整系统参数
      schedule_delayed_work(&defense_work, DEFENSE_TIMER_PERIOD);
      return 0;
}
//协议初始化,具体看下面协议实现
int ip_vs_protocol_init(void)
{
      //挂接ipvs能进行均衡处理的各种协议,目前支持TCP/UDP/AH/ESP
      char protocols[64];
#define REGISTER_PROTOCOL(p)                    \
      do {                                    \
            register_ip_vs_protocol(p);     \
            strcat(protocols, ", ");        \
            strcat(protocols, (p)->name);   \
      } while (0)

      //0,1字符是给", "预留的
      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_AH
      REGISTER_PROTOCOL(&ip_vs_protocol_ah);
#endif
#ifdef CONFIG_IP_VS_PROTO_ESP
      REGISTER_PROTOCOL(&ip_vs_protocol_esp);
#endif
      IP_VS_INFO("Registered protocols (%s)\n", &protocols[2]);
      return 0;
}
#define IP_VS_PROTO_TAB_SIZE  32
static int register_ip_vs_protocol(struct ip_vs_protocol *pp)
{
        //#define IP_VS_PROTO_HASH(proto)         ((proto) & (IP_VS_PROTO_TAB_SIZE-1))
        unsigned hash = IP_VS_PROTO_HASH(pp->protocol); //计算一个hash值

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

        if (pp->init != NULL)
                pp->init(pp);
        return 0;
}
应用层辅助接口初始化
int ip_vs_app_init(void)
{
        //建立一个/proc/net/ip_vs_app项
        proc_net_fops_create("ip_vs_app", 0, &ip_vs_app_fops);
        return 0;
}
主要数据结构初始化
int ip_vs_conn_init(void)
{
      int idx;
      //ipvs连接HASH表 static struct list_head *ip_vs_conn_tab;
      ip_vs_conn_tab = vmalloc(IP_VS_CONN_TAB_SIZE*sizeof(struct list_head));
      if (!ip_vs_conn_tab)
return -ENOMEM;

      //ipvs连接cache
      ip_vs_conn_cachep = kmem_cache_create("ip_vs_conn", sizeof(struct ip_vs_conn), 0, SLAB_HWCACHE_ALIGN, NULL, NULL);
      if (!ip_vs_conn_cachep) {
            vfree(ip_vs_conn_tab);
            return -ENOMEM;
      }
      //初始化HASH链表头
      for (idx = 0; idx < IP_VS_CONN_TAB_SIZE; idx++) {
            INIT_LIST_HEAD(&ip_vs_conn_tab[idx]);
      }
      //初始化各读写锁
      for (idx = 0; idx < CT_LOCKARRAY_SIZE; idx++)  {
            rwlock_init(&__ip_vs_conntbl_lock_array[idx].l);
      }
      //建立/proc/net/ip_vs_conn项
      proc_net_fops_create("ip_vs_conn", 0, &ip_vs_conn_fops);
      //初始化随机数
      get_random_bytes(&ip_vs_conn_rnd, sizeof(ip_vs_conn_rnd));
      return 0;
}
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页