主要参考了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;
}