1.1 LVS实现分析
1.1.1 LVS流程概述
Netfiler的报文流程概述
数据报从左边进入系统,进行IP校验以后,数据报经过第一个钩子函数NF_IP_PRE_ROUTING进行处理;然后进行路由选择,决定该数据报是需要转发还是发给本机;
若该数据报是发被本机的,则该数据经过钩子函数 NF_IP_LOCAL_IN处理后传递给上层协议;
若该数据报应该被转发,则它被NF_IP_FORWARD处理;经过转发的数据报经过最后一个钩子函数NF_IP_POST_ROUTING处理以后,再传输到网络上。
本地产生的数据经过钩子函数 NF_IP_LOCAL_OUT处理后,进行路由选择处理,然后经过NF_IP_POST_ROUTING处理后发送到网络上。
1.1.1.1IPVS初始化注册
当启动IPVS加载ip_vs模块时,模块的初始化函数ip_vs_init( )注册了NF_IP_LOCAL_IN、NF_IP_FORWARD、NF_IP_POST_ROUTING钩子函数用于处理进出的数据报。
NF_IP_LOCAL_IN处理过程
1、NF_IP_LOCAL_IN处理过程
用户向虚拟服务器发起请求,数据报经过NF_IP_LOCAL_IN[HOOK2],进入ip_vs_in( )进行处理。
(1)如果传入的是icmp数据报,则调用ip_vs_in_icmp( )。
(2)如果不是tcp/udp数据报,则函数返回NF_ACCEPT(让内核继续处理该数据报)。
(3)如果是tcp/udp数据报,则进行tcp/udp数据报处理:
首先,调用ip_vs_header_check( )检查报头,如果异常,则函数返回NF_DROP(丢弃该数据报)。
调用ip_vs_conn_in_get( )去ip_vs_conn_tab表中查找是否存在这样的连接:它的客户机和虚拟服务器的ip地址和端口号以及协议类型均与数据报中的相应信息一致。
如果不存在相应连接,则意味着连接尚未建立,此时如果数据报为tcp的sync报文或udp数据报则查找相应的虚拟服务器;如果相应虚拟服务器存在但是已经满负荷,则返回NF_DROP;如果相应虚拟服务器存在并且未满负荷,那么调用ip_vs_schedule( )调度一个RS并创建一个新的连接,如果调度失败则调用ip_vs_leave( )继续传递或者丢弃数据报。
如果存在相应连接,首先判断连接上的RS是否可用,如果不可用则处理相关信息后返回NF_DROP。找到已存在的连接或建立新的连接后,修改系统记录的相关信息如传入的数据报的个数等。如果这个连接在创建时绑定了特定的数据报传输函数,调用这个函数传输数据报,否则返回 NF_ACCEPT。
2、ICMP处理细节
ip_vs_in()调用的ip_vs_in_icmp( )处理icmp报文。函数开始时检查数据报的长度,如果异常则返回NF_DROP。函数只处理由tcp/udp报文传送错误引起的目的不可达、源端被关闭或超时的icmp报文,其他情况则让内核处理。针对上述三类报文,首先检查检验和。如果检验和错误,直接返回NF_DROP;否则,分析返回的icmp差错信息,查找相应的连接是否存在。如果连接不存在,返回NF_ACCEPT;如果连接存在,根据连接信息,依次修改差错信息包头的ip地址与端口号及 ICMP数据报包头的ip地址,并重新计算和修改各个包头中的检验和,之后查找路由调用ip_send( )发送修改过的数据报,并返回NF_STOLEN(退出数据报的处理过程)。
3、调度算法及连接建立细节
ip_vs_in()调用的函数ip_vs_schedule( )为虚拟服务器调度可用的RS并建立相应连接。它将根据虚拟服务器绑定的调度算法分配一个RS,如果成功,则调用ip_vs_conn_new( )建立连接。ip_vs_conn_new( )将进行一系列初始化操作:设置连接的协议、ip地址、端口号、协议超时信息,绑定application helper、RS和数据报传输函数,最后调用ip_vs_conn_hash( )将这个连接插入哈希表ip_vs_conn_tab中。一个连接绑定的数据报传输函数,依据IPVS工作方式可分为ip_vs_nat_xmit( )、ip_vs_tunnel_xmit( )、ip_vs_dr_xmit( )。例如ip_vs_nat_xmit( )的主要操作是:修改报文的目的地址和目的端口为RS信息,重新计算并设置检验和,调用ip_send( )发送修改后的数据报。
1.1.1.2NF_IP_FORWARD处理过程【NAT方式】
1、NF_IP_FORWARD处理过程
数据报进入NF_IP_FORWARD后,将进入ip_vs_out( )进行处理。这个函数只在NAT方式下被调用。
(1)它首先判断数据报类型,如果为icmp数据报则直接调用ip_vs_out_icmp( )。
(2)其次判断是否为tcp/udp数据报,如果不是这二者则返回NF_ACCEPT。
(3)余下就是tcp/udp数据报的处理:
首先,调用 ip_vs_header_check( )检查报头,如果异常则返回NF_DROP。
其次,调用ip_vs_conn_out_get( )判断是否存在相应的连接。
若不存在相应连接:调用ip_vs_lookup_real_service( )去哈希表中查找发送数据报的RS是否仍然存在,如果RS存在且报文是tcp非复位报文或udp 报文,则调用icmp_send( )给RS发送目的不可达icmp报文并返回NF_STOLEN;其余情况下均返回NF_ACCEPT。
若存在相应连接:检查数据报的检验和,如果错误则返回NF_DROP,如果正确,修改数据报,将源地址修改为虚拟服务器ip地址,源端口修改为虚拟服务器端口号,重新计算并设置检验和,并返回 NF_ACCEPT。
2、ICMP处理细节
ip_vs_out_icmp( )的流程与ip_vs_in_icmp( )类似,只是修改数据报时有所区别:ip报头的源地址和差错信息中udp或tcp报头的目的地址均修改为虚拟服务器地址,差错信息中udp或tcp报头的目的端口号修改为虚拟服务器的端口号。
1.1.1.3NF_IP_POST_ROUTING处理过程【NAT方式】
NF_IP_POST_ROUTING钩子函数只在NAT方式下使用。数据报进入NF_IP_POST_ROUTING后,由 ip_vs_post_routing( )进行处理。它首先判断数据报是否经过IPVS,如果未经过则返回NF_ACCEPT;否则立刻传输数据报,函数返回NF_STOLEN,防止数据报被 iptable的规则修改
1.1.1.4LVS系统配置与管理
IPVS模块初始化时注册了setsockopt/getsockopt( ),ipvsadm命令调用这两个函数向IPVS内核模块传递ip_vs_rule_user结构的系统配置数据,完成系统的配置,实现虚拟服务器和RS 地址的添加、修改、删除操作。系统通过这些操作完成对虚拟服务器和RS链表的管理。
虚拟服务器的添加操作由ip_vs_add_service( )完成,该函数根据哈希算法向虚拟服务器哈希表添加一个新的节点,查找用户设定的调度算法并将此算法绑定到该节点;虚拟服务器的修改由 ip_vs_edit_service( )完成,此函数修改指定服务器的调度算法;虚拟服务器的删除由ip_vs_del_service( )完成,在删除一个虚拟服务器之前,必须先删除此虚拟服务器所带的所有RS,并解除虚拟服务器所绑定的调度算法。
与之类似,RS的添加、修改、删除操作分别由ip_vs_add_dest( )、ip_vs_edit_dest( )和ip_vs_edit_server( )完成。
1.1.1.5负载均衡调度算法
前面已经提到,用户在添加一个虚拟服务时要绑定调度算法,这由ip_vs_bind_scheduler( )完成,调度算法的查找则由ip_vs_scheduler_get( )完成。ip_vs_scheduler_get( )根据调度算法的名字,调用ip_vs_sched_getbyname( )从调度算法队列中查找此调度算法,如果没找到则加载相应调度算法模块再查找,最后返回查找结果。
目前系统有八种负载均衡调度算法,具体如下:
rr:轮循调度(Round-Robin) 它将请求依次分配不同的RS,也就是在RS中均摊请求。这种算法简单,但是只适合于RS处理性能相差不大的情况。
wrr:加权轮循调度(Weighted Round-Robin) 它将依据不同RS的权值分配任务。权值较高的RS将优先获得任务,并且分配到的连接数将比权值较低的RS更多。相同权值的RS得到相同数目的连接数。
dh:目的地址哈希调度 (Destination Hashing) 以目的地址为关键字查找一个静态hash表来获得需要的RS。
sh:源地址哈希调度(Source Hashing) 以源地址为关键字查找一个静态hash表来获得需要的RS。
Lc:最小连接数调度(Least-Connection) IPVS表存储了所有的活动的连接。把新的连接请求发送到当前连接数最小的RS。
Wlc:加权最小连接数调度(Weighted Least-Connection) 假设各台RS的权值依次为Wi(I = 1..n),当前的TCP连接数依次为Ti(I=1..n),依次选取Ti/Wi为最小的RS作为下一个分配的RS。Lblc:基于地址的最小连接数调度(Locality-Based Least-Connection) 将来自同一目的地址的请求分配给同一台RS如果这台服务器尚未满负荷,否则分配给连接数最小的RS,并以它为下一次分配的首先考虑。
Lblcr:基于地址的带重复最小连接数调度(Locality-Based Least-Connection with Replication) 对于某一目的地址,对应有一个RS子集。对此地址的请求,为它分配子集中连接数最小的RS;如果子集中所有的服务器均已满负荷,则从集群中选择一个连接数较小的服务器,将它加入到此子集并分配连接;若一定时间内,这个子集未被做任何修改,则将子集中负载最大的节点从子集删除。
1.1.2 LVS具体实现
1.1.2.1 内核态几个重要的数据结构
1. 【数据结构】协议
这个结构用来描述IPVS支持的IP协议。IPVS的IP层协议支持TCP, UDP, AH和ESP这4种IP层协议
struct ip_vs_protocol {
struct ip_vs_protocol *next;//链表中的下一项
char *name;//协议名称, "TCP", "UDP"...
__u16 protocol;//协议值: 6, 17,...
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);//协议调度
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);//查找in方向的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);//查找out方向的IPVS连接
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);//目的NAT操作
int (*csum_check)(struct sk_buff *skb, struct ip_vs_protocol *pp);//协议校验和
const char *(*state_name)(int state);//当前协议状态: 如"LISTEN","ESTABLISH"...
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);//设置各种状态下的协议超时
};
2. 【数据结构】IPVS连接
这个结构用来描述IPVS的连接。IPVS的连接和netfilter定义的连接类似
struct ip_vs_conn {
struct list_head c_list; /* hashed list heads */
/* Protocol, addresses and port numbers */
__u32 caddr; /* client address客户机地址*/
__u32 vaddr; /* virtual address服务器对外的虚拟地址*/
__u32 daddr; /* destination address服务器实际地址*/
__u16 cport;//客户端的端口
__u16 vport;//服务器对外虚拟端口
__u16 dport;//服务器实际端口
__u16 protocol; /*协议类型:Which protocol (TCP/UDP) */
/* counter and timer */
atomic_t refcnt; /* reference count连接引用计数*/
struct timer_list timer; /* Expiration timer定时器*/
volatile unsigned long timeout;/* timeout 超时时间*/
/* Flags and state transition */
spinlock_t lock; /*状态转换锁lock for state transition */
volatile __u16 flags; /* status flags */
volatile __u16 state; /* state info */
/* Control members */
struct ip_vs_conn *control; /*主连接, 如FTP Master control connection */
atomic_t n_control; /* Number of controlled ones子连接数*/
struct ip_vs_dest *dest; /* real server真正服务器*/
atomic_t in_pkts; /* incoming packet counter进入的数据统计*/
//数据包发送
int (*packet_xmit)(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp);
/* Note: we can group the following members into a structure,
in order to save more space, and the following members are
only used in VS/NAT anyway */
struct ip_vs_app *app; /* bound ip_vs_app object IPVS应用 */
void *app_data;/* Application private data应用的私有数据*/
struct ip_vs_seq in_seq; /* incoming seq.struct进入数据的序列号*/
struct ip_vs_seq out_seq; /* outgoing seq.struct发出数据的序列号*/
};
3. 【数据结构】IPVS虚拟服务器
这个结构用来描述IPVS对外的服务器信息。
struct ip_vs_service {
struct list_head s_list; /*按普通协议,地址,端口进行HASH的链表*/
struct list_head f_list; /*按nfmark进行HASH的链表(感觉没必要) */
atomic_t refcnt; /* reference counter引用计数*/
atomic_t usecnt; /* use counter使用计数*/
__u16 protocol;/*协议:which protocol (TCP/UDP) */
__u32 addr; /*虚拟服务器地址IP address for virtual service */
__u16 port; /*虚拟端口port number for the service */
__u32 fwmark;/skb中的nfmark:firewall mark of the service */
unsigned flags; /* service status flags标志*/
unsigned timeout; /* persistent timeout in ticks超时*/
__u32 netmask; /* grouping granularity网络掩码*/
struct list_head destinations;/*真实服务器的地址链表*/
__u32 num_dests; /* number of servers真实服务器的数量*/
struct ip_vs_stats stats;/* statistics for the service服务统计信息*/
struct ip_vs_app *inc; /* bind conns to this app inc应用*/
struct ip_vs_scheduler *scheduler; /*调度指针 bound scheduler object */
rwlock_t sched_lock; /* lock sched_data */
void *sched_data; /* scheduler application data */
};
4. 【数据结构】IPVS真实服务器
这个结构用来描述具体的真实服务器的信息
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; /* IP address of the server服务器地址*/
__u16 port; /* port number of the server服务器端口*/
volatile unsigned flags; /* dest status flags ,目标标志,易变参数*/
atomic_t conn_flags;/* flags to copy to conn连接标志*/
atomic_t weight; /* server weight服务器权重*/
atomic_t refcnt; /* reference counter引用次数*/
struct ip_vs_stats stats; /* statistics 统计数*/
/* connection counters and thresholds */
atomic_t activeconns;/* active connections活动的连接*/
atomic_t inactconns;/* inactive connections 不活动的连接*/
atomic_t persistconns;/* persistent connections保持的连接*/
__u32 u_threshold;/* upper threshold连接上限*/
__u32 l_threshold;/* lower threshold连接下限*/
/* for destination cache */
spinlock_t dst_lock;/* lock of dst_cache */
struct dst_entry *dst_cache;/* destination cache entry */
u32 dst_rtos;/* RT_TOS(tos) for dst */
/* for virtual service */
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 */
};
5. 【数据结构】IPVS调度器
IPVS 这个结构用来描述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 */
int (*init_service)(struct ip_vs_service *svc);/* initializing */
int (*done_service)(struct ip_vs_service *svc);/* scheduling service finish */
int (*update_service)(struct ip_vs_service *svc);/* scheduler updating service*/
struct ip_vs_dest* (*schedule)(struct ip_vs_service *svc,
const struct sk_buff *skb);/* selecting a server from the given service */
};
6. 【数据结构】IPVS多连接应用
IPVS应用是针对多连接协议的, 目前也就只支持FTP。由于ip_vs_app.c是从2.2过来的,
没有管内核是否本身有NAT的情况,所以相当于自身实现了应用协议的NAT处理,包括内容信息的改变,TCP序列号确认号的调整等,而现在这些都由netfilter实现了,IPVS可以不用管这些,只处理连接调度就行了。
IPVS的应用模块化还不是很好,在处理连接端口时,还要判断是否是FTPPORT,也就是说
不支持其他多连接协议的,应该象netfilter一样为每个多连接协议设置一个helper,自动调用,不用在程序里判断端口。
1.1.2.2用户态几个重要的数据结构
用户空间信息是ipvsadm程序接收用户输入后传递给内核ipvs的信息,信息都是很直接的,没有各种控制信息。ipvsadm和ipvs的关系相当于iptables和netfilter的关系。
1. 【数据结构】用户态虚拟服务器
struct ip_vs_service_user {
u_int16_t protocol;/* virtual service addresses */
u_int32_t addr; /* virtual ip address */
u_int16_t port;
u_int32_t fwmark; /* firwall mark of service */
char sched_name[IP_VS_SCHEDNAME_MAXLEN];/* virtual service options */
unsigned flags; /* virtual service flags */
unsigned timeout;/* persistent timeout in sec */
u_int32_t netmask;/* persistent netmask */
};
2. 【数据结构】用户态真实服务器
struct ip_vs_dest_user {
u_int32_t addr;/* destination server address */
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 */
};
3. 【数据结构】用户态统计信息
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 */
};
4. 【数据结构】用户态获取信息结构
/* The argument to IP_VS_SO_GET_INFO */
struct ip_vs_getinfo {
unsigned int version;/* version number */
unsigned int size;/* size of connection hash table */
unsigned int num_services;/* number of virtual services */
};
5. 【数据结构】用户态服务规则项信息
/* The argument to IP_VS_SO_GET_SERVICE */
struct ip_vs_service_entry {
u_int16_t protocol;/* which service: user fills in these */
u_int32_t addr; /* virtual address */
u_int16_t port;
u_int32_t fwmark; /* firwall mark of service */
char sched_name[IP_VS_SCHEDNAME_MAXLEN]; /* service options */
unsigned flags; /* virtual service flags */
unsigned timeout;/* persistent timeout */
u_int32_t netmask;/* persistent netmask */
unsigned int num_dests;/* number of real servers */
struct ip_vs_stats_user stats;/* statistics */
};
6. 【数据结构】用户态服务器项信息
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;
};
7. 【数据结构】用户态获取服务器项信息
struct ip_vs_get_dests {
u_int16_t protocol;/* which service: user fills in these */
u_int32_t addr; /* virtual address */
u_int16_t port;
u_int32_t fwmark; /* firwall mark of service */
unsigned int num_dests;/* number of real servers */
struct ip_vs_dest_entry entrytable[0];/* the real servers */
};
8. 【数据结构】用户态获取虚拟服务器项信息
struct ip_vs_get_services {
unsigned int num_services;/* number of virtual services */
struct ip_vs_service_entry entrytable[0];/* service table */
};
9. 【数据结构】用户态获取超时信息
struct ip_vs_timeout_user {
int tcp_timeout;
int tcp_fin_timeout;
int udp_timeout;
};
10. 【数据结构】用户态获取IPVS内核守护信息
struct ip_vs_daemon_user {
int state;/* sync daemon state (master/backup) */
char mcast_ifn[IP_VS_IFNAME_MAXLEN];/* multicast interface name */
int syncid;/* SyncID we belong to */
};
1.1.2.3【函数】模块初始化
初始化函数先初始化ipvs的各种处理机制,然后将ipvs的处理函数挂接到netfilter架构中。
static int __init ip_vs_init(void)/* net/ipv4/ipvs/ip_vs_core.c */
int ip_vs_control_init(void) /* net/ipv4/ipvs/ip_vs_ctl.c */
int ip_vs_protocol_init(void)/* net/ipv4/ipvs/ip_vs_proto.c */
int ip_vs_app_init(void)/* net/ipv4/ipvs/ip_vs_app.c */
int ip_vs_conn_init(void)/* net/ipv4/ipvs/ip_vs_conn.c */
1.1.2.4【函数】挂接点netfilter
nf_hook_ops分别在FORWARD点挂2个, INPUT点和POST_ROUTING点各挂一个
LOCAL_IN点:ip_vs_in()/* net/ipv4/ipvs/ip_vs_core.c */
NF_IP_FORWARD:ip_vs_out()/* net/ipv4/ipvs/ip_vs_core.c */
NF_IP_FORWARD :ip_vs_forward_icmp()/* net/ipv4/ipvs/ip_vs_core.c */
NF_IP_POST_ROUTING: ip_vs_post_routing()/* net/ipv4/ipvs/ip_vs_core.c */
1.1.2.5均衡调度函数
1. 【函数】均衡调度算法描述
在内核中的连接调度算法上,IPVS已实现了以下八种调度算法:
轮叫调度(Round-Robin Scheduling)
加权轮叫调度(Weighted Round-Robin Scheduling)
最小连接调度(Least-Connection Scheduling)
加权最小连接调度(Weighted Least-Connection Scheduling)
基于局部性的最少链接(Locality-Based Least Connections Scheduling)
带复制的基于局部性最少链接(Locality-Based Least Connections with Replication Scheduling)
目标地址散列调度(Destination Hashing Scheduling)
源地址散列调度(Source Hashing Scheduling)
2.1.轮叫调度
轮叫调度(Round Robin Scheduling)算法就是以轮叫的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。算法的优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度。
在系统实现时,我们引入了一个额外条件,当服务器的权值为零时,表示该服务器不可用而不被调度。这样做的目的是将服务器切出服务(如屏蔽服务器故障和系统维护),同时与其他加权算法保持一致。所以,算法要作相应的改动,它的算法流程如下:
假设有一组服务器S = {S0, S1, …, Sn-1},一个指示变量i表示上一次选择的服务器,W(Si)表示服务器Si的权值。变量i被初始化为n-1,其中n > 0。
j = i;
do {
j = (j + 1) mod n;
if (W(Sj) > 0) { /*当权值为0时,表示服务器或者链路不通*/
i = j;
return Si;
}
} while (j != i);
return NULL;
轮叫调度算法假设所有服务器处理性能均相同,不管服务器的当前连接数和响应速度。该算法相对简单,不适用于服务器组中处理性能不一的情况,而且当请求服务时间变化比较大时,轮叫调度算法容易导致服务器间的负载不平衡。
虽然Round-Robin DNS方法也是以轮叫调度的方式将一个域名解析到多个IP地址,但轮叫DNS方法的调度粒度是基于每个域名服务器的,域名服务器对域名解析的缓存会妨碍轮叫解析域名生效,这会导致服务器间负载的严重不平衡。这里,IPVS轮叫调度算法的粒度是基于每个连接的,同一用户的不同连接都会被调度到不同的服务器上,所以这种细粒度的轮叫调度要比DNS的轮叫调度优越很多。
2.2.加权轮叫调度
加权轮叫调度(Weighted Round-Robin Scheduling)算法可以解决服务器间性能不一的情况,它用相应的权值表示服务器的处理性能,服务器的缺省权值为1。假设服务器A的权值为1,B的权值为2,则表示服务器B的处理性能是A的两倍。加权轮叫调度算法是按权值的高低和轮叫方式分配请求到各服务器。权值高的服务器先收到的连接,权值高的服务器比权值低的服务器处理更多的连接,相同权值的服务器处理相同数目的连接数。加权轮叫调度算法流程如下:
假设有一组服务器S = {S0, S1, …, Sn-1},W(Si)表示服务器Si的权值,一个
指示变量i表示上一次选择的服务器,指示变量cw表示当前调度的权值,max(S) 表示集合S中所有服务器的最大权值,gcd(S)表示集合S中所有服务器权值的最大公约数。变量i初始化为-1,cw初始化为零。
while (true) {
i = (i + 1) mod n;
if (i == 0) {
cw = cw - gcd(S);
if (cw <= 0) {
cw = max(S);
if (cw == 0)
return NULL;
}
}
if (W(Si) >= cw)
return Si;
}
例如,有三个服务器A、B和C分别有权值4、3和2,则在一个调度周期内(mod sum(W(Si)))调度序列为AABABCABC。加权轮叫调度算法还是比较简单和高效。当请求的服务时间变化很大,单独的加权轮叫调度算法依然会导致服务器间的负载不平衡。
从上面的算法流程中,我们可以看出当服务器的权值为零时,该服务器不被被调度;当所有服务器的权值为零,即对于任意i有W(Si)=0,则没有任何服务器可用,算法返回NULL,所有的新连接都会被丢掉。加权轮叫调度也无需记录当前所有连接的状态,所以它也是一种无状态调度。
2.3.最小连接调度
最小连接调度(Least-Connection Scheduling)算法是把新的连接请求分配到当前连接数最小的服务器。最小连接调度是一种动态调度算法,它通过服务器当前所活跃的连接数来估计服务器的负载情况。调度器需要记录各个服务器已建立连接的数目,当一个请求被调度到某台服务器,其连接数加1;当连接中止或超时,其连接数减一。
在系统实现时,我们也引入当服务器的权值为零时,表示该服务器不可用而不被调度,它的
算法流程如下:
假设有一组服务器S = {S0, S1,..., Sn-1},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。
for (m = 0; m < n; m++) {
if (W(Sm) > 0) {
for (i = m+1; i < n; i++) {
if (W(Si) <= 0)
continue;
if (C(Si) < C(Sm))
m = i;
}
return Sm;
}
}
return NULL;
当各个服务器有相同的处理性能时,最小连接调度算法能把负载变化大的请求分布平滑到各个服务器上,所有处理时间比较长的请求不可能被发送到同一台服务器上。但是,当各个服务器的处理能力不同时,该算法并不理想,因为TCP连接处理请求后会进入TIME_WAIT状态,TCP的TIME_WAIT一般为2分钟,此时连接还占用服务器的资源,所以会出现这样情形,性能高的服务器已处理所收到的连接,连接处于TIME_WAIT状态,而性能低的服务器已经忙于处理所收到的连接,还不断地收到新的连接请求。
2.4.加权最小连接调度
加权最小连接调度(Weighted Least-Connection Scheduling)算法是最小连接调度的超集,各个服务器用相应的权值表示其处理性能。服务器的缺省权值为1,系统管理员可以动态地设置服务器的权值。加权最小连接调度在调度新连接时尽可能使服务器的已建立连接数和其权值成比例。加权最小连接调度的算法流程如下:
假设有一组服务器S = {S0, S1,..., Sn-1},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。所有服务器当前连接数的总和为 CSUM = ΣC(Si) (i=0, 1,.., n-1)。当前的新连接请求会被发送服务器Sm,当且仅当服务器Sm满足以下条件
(C(Sm)/CSUM)/W(Sm) = min { (C(Si)/CSUM)/W(Si)} (i=0, 1,., n-1)
其中W(Si)不为零 因为CSUM在这一轮查找中是个常数,所以判断条件可以简化为 C(Sm)/W(Sm) = min { C(Si)/W(Si)} (i=0, 1,., n-1) 。其中W(Si)不为零 因为除法所需的CPU周期比乘法多,且在Linux内核中不允许浮点除法,服务器的 权值都大于零,所以判断条件C(Sm)/W(Sm) > C(Si)/W(Si) 可以进一步优化 为C(Sm)*W(Si) > C(Si)* W(Sm)。同时保证服务器的权值为零时,服务器不被调 度。所以,算法只要执行以下流程。
for (m = 0; m < n; m++) {
if (W(Sm) > 0) {
for (i = m+1; i < n; i++) {
if (C(Sm)*W(Si) > C(Si)*W(Sm))
m = i;
}
return Sm;
}
}
return NULL;
2.5.基于局部性的最少链接调度
基于局部性的最少链接调度(Locality-Based Least Connections Scheduling,简称为LBLC)算法是针对请求报文的目标IP地址的负载均衡调度,目前主要用于Cache集群系统,因为在Cache集群中客户请求报文的目标IP地址是变化的。这里假设任何后端服务器都可以处理任一请求,算法的设计目标是在服务器的负载基本平衡情况下,将相同目标IP地址的请求调度到同一台服务器,来提高各台服务器的访问局部性和主存Cache命中率,从而整个集群系统的处理能力。
LBLC调度算法先根据请求的目标IP地址找出该目标IP地址最近使用的服务器,若该服务器是可用的且没有超载,将请求发送到该服务器;若服务器不存在,或者该服务器超载且有服务器处于其一半的工作负载,则用“最少链接”的原则选出一个可用的服务器,将请求发送到该服务器。该算法的详细流程如下:
假设有一组服务器S = {S0, S1,..., Sn-1},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。ServerNode[dest_ip]是一个关联变量,表示 目标IP地址所对应的服务器结点,一般来说它是通过Hash表实现的。WLC(S)表示在集合S中的加权最小连接服务器,即前面的加权最小连接调度。Now为当前系统 时间。
if (ServerNode[dest_ip] is NULL) then {
n = WLC(S);
if (n is NULL) then return NULL;
ServerNode[dest_ip].server = n;
} else {
n = ServerNode[dest_ip].server;
if ((n is dead) OR
(C(n) > W(n) AND
there is a node m wit
h C(m) < W(m)/2))) then {
n = WLC(S);
if (n is NULL) then return NULL;
ServerNode[dest_ip].server = n;
}
}
ServerNode[dest_ip].lastuse = Now;
return n;
此外,对关联变量ServerNode[dest_ip]要进行周期性的垃圾回收(Garbage Collection),将过期的目标IP地址到服务器关联项进行回收。过期的关联项是指哪些当前时间(实现时采用系统时钟节拍数jiffies)减去最近使用时间超过设定过期时间的关联项,系统缺省的设定过期时间为24小时。
2.6.带复制的基于局部性最少链接调度
带复制的基于局部性最少链接调度(Locality-Based Least Connections with Replication Scheduling,以下简称为LBLCR)算法也是针对目标IP地址的负载均衡,目前主要用于Cache集群系统。它与LBLC算法的不同之处是它要维护从一个目标IP地址到一组服务器的映射,而LBLC算法维护从一个目标IP地址到一台服务器的映射。对于一个“热门”站点的服务请求,一台Cache 服务器可能会忙不过来处理这些请求。这时,LBLC调度算法会从所有的Cache服务器中按“最小连接”原则选出一台Cache服务器,映射该“热门”站点到这台
Cache服务器,很快这台Cache服务器也会超载,就会重复上述过程选出新的Cache服务器。这样,可能会导致该“热门”站点的映像会出现在所有的Cache服务器上,降低了Cache服务器的使用效率。LBLCR调度算法将“热门”站点映射到一组Cache服务器(服务器集合),当该“热门”站点的请求负载增加时,会增加集合里的Cache服务器,来处理不断增长的负载;当该“热门”站点的请求负载降低时,会减少集合里的Cache服务器数目。这样,该“热门”站点的映像不太可能出现在所有的Cache服务器上,从而提供Cache集群系统的使用效率。
LBLCR算法先根据请求的目标IP地址找出该目标IP地址对应的服务器组;按“最小连接”原则从该服务器组中选出一台服务器,若服务器没有超载,将请求发送到该服务器;若服务器超载;则按“最小连接”原则从整个集群中选出一台服务器,将该服务器加入到服务器组中,将请求发送到该服务器。同时,当该服务器组有一段时间没有被修改,将最忙的服务器从服务器组中删除,以降低复制的程度。LBLCR调度算法的流程如下:
LBLCR调度算法流程
假设有一组服务器S = {S0, S1,..., Sn-1},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。ServerSet[dest_ip]是一个关联变量,表示 目标IP地址所对应的服务器集合,一般来说它是通过Hash表实现的。WLC(S)表示 在集合S中的加权最小连接服务器,即前面的加权最小连接调度;WGC(S)表示在 集合S中的加权最大连接服务器。Now为当前系统时间,lastmod表示集合的最近 修改时间,T为对集合进行调整的设定时间。
if (ServerSet[dest_ip] is NULL) then {
n = WLC(S);
if (n is NULL) then return NULL;
add n into ServerSet[dest_ip];
} else {
n = WLC(ServerSet[dest_ip]);
if ((n is NULL) OR
(n is dead) OR
(C(n) > W(n) AND
there is a node m with C(m) < W(m)/2))) then {
n = WLC(S);
if (n is NULL) then return NULL;
add n into ServerSet[dest_ip];
} else
if (|ServerSet[dest_ip]| > 1 AND
Now - ServerSet[dest_ip].lastmod > T) then {
m = WGC(ServerSet[dest_ip]);
remove m from ServerSet[dest_ip];
}
}
ServerSet[dest_ip].lastuse = Now;
if (ServerSet[dest_ip] changed) then
ServerSet[dest_ip].lastmod = Now;
return n;
此外,对关联变量ServerSet[dest_ip]也要进行周期性的垃圾回收(Garbage Collection),将过期的目标IP地址到服务器关联项进行回收。过期的关联项是指哪些当前时间(实现时采用系统时钟节拍数jiffies)减去最近使用时间(lastuse)超过设定过期时间的关联项,系统缺省的设定过期时间为24小时。
2.7.目标地址散列调度
目标地址散列调度(Destination Hashing Scheduling)算法也是针对目标IP地址的负载均衡,但它是一种静态映射算法,通过一个散列(Hash)函数将一个目标IP地址映射到一台服务器。 目标地址散列调度算法先根据请求的目标IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。该算法的流程如下:
假设有一组服务器S = {S0, S1,..., Sn-1},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。ServerNode[]是一个有256个桶(Bucket)的 Hash表,一般来说服务器的数目会运小于256,当然表的大小也是可以调整的。 算法的初始化是将所有服务器顺序、循环地放置到ServerNode表中。若服务器的 连接数目大于2倍的权值,则表示服务器已超载。
n = ServerNode[hashkey(dest_ip)];
if ((n is dead) OR
(W(n) == 0) OR
(C(n) > 2*W(n))) then
return NULL;
return n;
在实现时,我们采用素数乘法Hash函数,通过乘以素数使得散列键值尽可能地达到较均匀的分布。所采用的素数乘法Hash函数如下: 素数乘法Hash函数
static inline unsigned hashkey(unsigned int dest_ip)
{
return (dest_ip* 2654435761UL) & HASH_TAB_MASK;
}
其中,2654435761UL是2到2
^32 (4294967296)间接近于黄金分割的素数,
(sqrt(5) - 1)/2 = 0.618033989
2654435761/4294967296 = 0.618033987
2.8.源地址散列调度
源地址散列调度(Source Hashing Scheduling)算法正好与目标地址散列调度算法相反,它根据请求的源IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。它采用的散列函数与目标地址散列调度算法的相同。它的算法流程与目标地址散列调度算法的基本相似,除了将请求的目标IP地址换成请求的源IP地址,所以这里不一一叙述。
在实际应用中,源地址散列调度和目标地址散列调度可以结合使用在防火墙集群中,它们可以保证整个系统的唯一出入口。
2. 【函数】均衡调度算法实现
每个调度算法的实现就是填写一个ip_vs_scheduler结构,在IPVS服务ip_vs_service结构中指向它即可,这样在连接到达该服务时,通过调度算法选择具体的目的主机。每个算法作为一个单独的内核模块,可由内核配置是否包括该模块。
以下以最简单的rr算法来说明,该算法在net/ipv4/ipvs/ip_vs_rr.c中定义。
static struct ip_vs_scheduler ip_vs_rr_scheduler = {.name = "rr",/* name */
.refcnt = ATOMIC_INIT(0),
.module = THIS_MODULE,
.init_service = ip_vs_rr_init_svc,
.done_service = ip_vs_rr_done_svc,
.update_service = ip_vs_rr_update_svc,
.schedule = ip_vs_rr_schedule,
};
init_service()函数进行算法初始化,在虚拟服务ip_vs_service和调度器绑定时调用
(ip_vs_bind_scheduler()函数);done_service()函数进行算法的清除,在虚拟服务ip_vs_service和调度器解除绑定时调用(ip_vs_unbind_scheduler()函数);update_service()函数在目的服务器变化时调用(如ip_vs_add_dest(), ip_vs_edit_dest()等函数);
算法核心函数schedule()则是在ip_vs_schedule()函数中在新建IPVS连接前调用,找到真
正的服务器提供服务,建立IPVS连接。
系统基本调度函数为ip_vs_schedule(),在TCP、UDP的conn_shedule中调用,而AH、ESP协议不管: ip_vs_schedule() /* net/ipv4/ipv4/ip_vs_core.c */
固定调度函数,用在多连接协议处理中将子连接与主连接挂钩:
ip_vs_sched_persist() /* net/ipv4/ipv4/ip_vs_core.c */
1.1.2.6IPVS的连接管理
和netfilter的连接类似,IPVS的连接管理是IPVS的一个重要组成部分,但相对来说IPVS的连接比netfilter的连接要简单一些。
1. IPVS五元组
和netfilter一样是五元组,为IP协议、源地址、源端口、目的地址和目的端口,不过没定义方向的概念,所以在IPVS中请求方向和回应方向要用不同的查找函数处理,由于IPVS是在INPUT点处理请求,在FORWARD点处理回应包,不会在同一个点同时处理请求包和回应包,因此可以没有方向的概念。
进入方向:
static inline struct ip_vs_conn *__ip_vs_conn_in_get()
struct ip_vs_conn *ip_vs_conn_in_get ()
另外还有个获取连接模板的函数,没有s_port为0的特殊处理,在查找固定连接和模板连接时使用: struct ip_vs_conn *ip_vs_ct_in_get()
发出方向查找:
struct ip_vs_conn *ip_vs_conn_out_get()
2. 连接HASH表
和netfilter一样,IPVS的连接表是通过HASH表来实现的,不过和netfilter不同的是该HASH表大小是固定的,可在内核内核参数时设置,而不是象netfitler那样是根据系统内存动态计算出来的:
3. 连接的建立
struct ip_vs_conn * ip_vs_conn_new(int proto, __u32 caddr, __u16 cport, __u32 vaddr, __u16 vport, __u32 daddr, __u16 dport, unsigned flags, struct ip_vs_dest *dest);
绑定连接目的服务器:
ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
绑定协议应用:
int ip_vs_bind_app(struct ip_vs_conn *cp, struct ip_vs_protocol *pp)
绑定发送方法:
static inline void ip_vs_bind_xmit(struct ip_vs_conn *cp);
将连接结构添加到连接HASH表
static inline int ip_vs_conn_hash(struct ip_vs_conn *cp) ;
4. 连接的释放
接超时函数: static void ip_vs_conn_expire(unsigned long data);
从连接HASH表中断开: static inline int ip_vs_conn_unhash(struct ip_vs_conn *cp);
从主连接中断开: static inline void ip_vs_control_del(struct ip_vs_conn *cp)
解除与应用的绑定: void ip_vs_unbind_app(struct ip_vs_conn *cp)
连接和目的服务器解除绑定: static inline void ip_vs_unbind_dest()
5. 其他的释放连接的函数
释放所有的连接:static void ip_vs_conn_flush(void)
在删除IPVS模块时调用,方法是让所有连接定时器到期而自动调用定时到期函数。
连接定时器立即到期: void ip_vs_conn_expire_now(struct ip_vs_conn *cp)
6.5.2 定时器随机删除连接
定时函数defense_work_handler()定期调用。
随机删除连接:vid ip_vs_random_dropentry(void) 。
6. 其他的连接相关函数
//该函数对未设置客户端端口的连接提供一个端口值,在ip_vs_nat_xmit()函数中调用
void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __u16 cport)
检查连接:ip_vs_check_template()
模板的目的服务器是否可用,ip_vs_sched_persist()函数中调用
1.1.2.7IPVS的协议(TCP)管理
1. 基本管理
IPVS协议的一些共用处理函数在net/ipv4/ipvs/ip_vs_proto.c中定义:
IPVS服务登记:static int register_ip_vs_protocol(struct ip_vs_protocol *pp)
登记IPVS服务,就是把服务结构挂接到IPVS服务链表中
IPVS服务拆除:static int unregister_ip_vs_protocol(struct ip_vs_protocol *pp)
拆除IPVS服务,就是把服务结构从IPVS服务链表中拆除
查找服务,返回服务结构指针: struct ip_vs_protocol * ip_vs_proto_get()
创建状态超时表:int * ip_vs_create_timeout_table(int *table, int size)
修改状态超时值: int ip_vs_set_state_timeout()
返回当前协议状态名称字符串:const char * ip_vs_state_name(__u16 proto, int state)
时初始化了TCP、UDP、AH和ESP四个协议,分别用一个struct ip_vs_protocol结构描述,这个结构定义了协议的各种操作。
下面以TCP协议的实现来详细说明,相关代码文件为net/ipv4/ipvs/ip_vs_proto_tcp.c。
struct ip_vs_protocol ip_vs_protocol_tcp = {
.name = "TCP",
.protocol = IPPROTO_TCP,
.dont_defrag = 0,
.appcnt = ATOMIC_INIT(0),
.init = ip_vs_tcp_init,
.exit = ip_vs_tcp_exit,
.register_app = tcp_register_app,
.unregister_app = tcp_unregister_app,
.conn_schedule = tcp_conn_schedule,
.conn_in_get = tcp_conn_in_get, .conn_out_get = tcp_conn_out_get,
.snat_handler = tcp_snat_handler,
.dnat_handler = tcp_dnat_handler,
.csum_check = tcp_csum_check,
.state_name = tcp_state_name,
.state_transition = tcp_state_transition,
.app_conn_bind = tcp_app_conn_bind,
.debug_packet = ip_vs_tcpudp_debug_packet,
.timeout_change = tcp_timeout_change,
.set_state_timeout = tcp_set_state_timeout,
};
2. TCP初始化函数
static void ip_vs_tcp_init(struct ip_vs_protocol *pp);
IPVS定义的超时,和netfilter类似,不过比netfilter的超时少得多,而且这些值不是通过/proc调整,而是通过ipvsadm命令来调整.
static int tcp_timeouts[IP_VS_TCP_S_LAST+1] = {
[IP_VS_TCP_S_NONE] = 2*HZ,
[IP_VS_TCP_S_ESTABLISHED] = 15*60*HZ,
[IP_VS_TCP_S_SYN_SENT] = 2*60*HZ,
[IP_VS_TCP_S_SYN_RECV] = 1*60*HZ,
[IP_VS_TCP_S_FIN_WAIT] = 2*60*HZ,
[IP_VS_TCP_S_TIME_WAIT] = 2*60*HZ,
[IP_VS_TCP_S_CLOSE] = 10*HZ,
[IP_VS_TCP_S_CLOSE_WAIT] = 60*HZ,
[IP_VS_TCP_S_LAST_ACK] = 30*HZ,
[IP_VS_TCP_S_LISTEN] = 2*60*HZ,
[IP_VS_TCP_S_SYNACK] = 120*HZ,
[IP_VS_TCP_S_LAST] = 2*HZ,
};
3. TCP退出函数
是个啥也不作的空函数,只是让函数指针不为空.
static void ip_vs_tcp_exit(struct ip_vs_protocol *pp){}。
4. 登记TCP应用(多连接)协议
static int tcp_register_app(struct ip_vs_app *inc)
5. 去掉TCP应用(多连接)协议登记
static void tcp_unregister_app(struct ip_vs_app *inc)
6. 连接调度
连接调度的目的是找到一个合适的目的服务器,生成新连接。该函数在ip_vs_in()函数中调用。
static int tcp_conn_schedule(struct sk_buff *skb, struct ip_vs_protocol *pp,
int *verdict, struct ip_vs_conn **cpp)
7. 进入方向的连接查找连接
该函数在ip_vs_in()中正向调用,,在ip_vs_in_icmp()函数中反向调用
static struct ip_vs_conn *
tcp_conn_in_get(const struct sk_buff *skb, struct ip_vs_protocol *pp,
const struct iphdr *iph, unsigned int proto_off, int inverse) ;
8. 发出方向的连接查找
在ip_vs_out()函数中正向调用,在ip_vs_out_icmp()函数中反向调用
static struct ip_vs_conn *
tcp_conn_out_get(const struct sk_buff *skb, struct ip_vs_protocol *pp,
const struct iphdr *iph, unsigned int proto_off, int inverse);
9. TCP源NAT操作
该函数完成对协议部分数据进行源NAT操作,对TCP来说,NAT部分的数据就是源端口
static int
tcp_snat_handler(struct sk_buff **pskb,
struct ip_vs_protocol *pp, struct ip_vs_conn *cp);
10. TCP目的NAT操作
该函数完成对协议部分数据进行目的NAT操作,对TCP来说,NAT部分的数据就是目的端口
static int tcp_dnat_handler(struct sk_buff **pskb,
struct ip_vs_protocol *pp, struct ip_vs_conn *cp);
11. TCP校验和计算
11.
计算IP协议中的校验和,对于TCP,UDP头中都有校验和参数,TCP中的校验和是必须的,而UDP的校验和可以不用计算。
该函数用的都是linux内核提供标准的校验和计算函数
static int tcp_csum_check(struct sk_buff *skb, struct ip_vs_protocol *pp);
12. TCP状态名称
该函数返回协议状态名称字符串
static const char * tcp_state_name(int state);
TCP协议状态名称定义:
static char * tcp_state_name_table[IP_VS_TCP_S_LAST+1] = {
[IP_VS_TCP_S_NONE] = "NONE",
[IP_VS_TCP_S_ESTABLISHED] = "ESTABLISHED",
[IP_VS_TCP_S_SYN_SENT] = "SYN_SENT",
[IP_VS_TCP_S_SYN_RECV] = "SYN_RECV",
[IP_VS_TCP_S_FIN_WAIT] = "FIN_WAIT",
[IP_VS_TCP_S_TIME_WAIT] = "TIME_WAIT",
[IP_VS_TCP_S_CLOSE] = "CLOSE",
[IP_VS_TCP_S_CLOSE_WAIT] = "CLOSE_WAIT",
[IP_VS_TCP_S_LAST_ACK] = "LAST_ACK",
[IP_VS_TCP_S_LISTEN] = "LISTEN",
[IP_VS_TCP_S_SYNACK] = "SYNACK",
[IP_VS_TCP_S_LAST] = "BUG!",
};
13. TCP状态转换
IPVS的TCP状态转换和netfilter是类似的,在NAT模式下几乎就是相同的,在TUNNEL和DR模式下是半连接的状态转换。
在每个数据包进出IPVS时都会调用
static int tcp_state_transition(struct ip_vs_conn *cp, int direction,
const struct sk_buff *skb, struct ip_vs_protocol *pp);
IPVS的TCP状态转换表
static struct tcp_states_t tcp_states [] = {
/* INPUT */
/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
/*syn*/{{sSR, sES, sES, sSR, sSR, sSR, sSR, sSR, sSR, sSR, sSR }},/*fin*/{{sCL, sCW, sSS, sTW, sTW, sTW, sCL, sCW, sLA, sLI, sTW }},
/*ack*/{{sCL, sES, sSS, sES, sFW, sTW, sCL, sCW, sCL, sLI, sES }},
/*rst*/{{sCL, sCL, sCL, sSR, sCL, sCL, sCL, sCL, sLA, sLI, sSR }},
/* OUTPUT */
/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
/*syn*/{{sSS, sES, sSS, sSR, sSS, sSS, sSS, sSS, sSS, sLI, sSR }},
/*fin*/{{sTW, sFW, sSS, sTW, sFW, sTW, sCL, sTW, sLA, sLI, sTW }},
/*ack*/{{sES, sES, sSS, sES, sFW, sTW, sCL, sCW, sLA, sES, sES }},
/*rst*/{{sCL, sCL, sSS, sCL, sCL, sTW, sCL, sCL, sCL, sCL, sCL }},
/* INPUT-ONLY */
/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
/*syn*/{{sSR, sES, sES, sSR, sSR, sSR, sSR, sSR, sSR,
sSR, sSR }},
/*fin*/{{sCL, sFW, sSS, sTW, sFW, sTW, sCL, sCW, sLA, sLI, sTW }},
/*ack*/{{sCL, sES, sSS, sES, sFW, sTW, sCL, sCW, sCL, sLI, sES }},
/*rst*/{{sCL, sCL, sCL, sSR, sCL, sCL, sCL, sCL, sLA, sLI, sCL }},
};
这个状态转换表的前两个数组和2.4内核中的TCP转换表类似,少了“none”类型标志,不
过从表中数据看是INPUT对应REPLY方向,OUTPUT对应ORIGINAL方向,这个有点怪,好象是IPVS站在就是服务器本身的角度看状态,而不是象netfilter是站在中间人的角度, 数组的查看方法和netfilter相同: 对于三次握手, 刚开始连接状态是sNO,来了个SYN包后, IPVS就觉得自己是服务器,状态就变为sSR而不是sSS, 如果是NAT模式SYNACK返回通过IPVS时,状态仍然是sSR, 等第3个ACK来时转为sES。
第3个数组是IPVS独有的,专用于处理半连接,因为对于TUNNEL和DR模式,服务器的响应包不经过IPVS, IPVS看到的数据都是单方向的.
IPVS还有另一个状态转换表,相对更严格一些,也安全一些:
static struct tcp_states_t tcp_states_dos [] = {
/* INPUT */
/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
/*syn*/{{sSR, sES, sES, sSR, sSR, sSR, sSR, sSR, sSR, sSR, sSA }},
/*fin*/{{sCL, sCW, sSS, sTW, sTW, sTW, sCL, sCW, sLA, sLI, sSA }},
/*ack*/{{sCL, sES, sSS, sSR, sFW, sTW, sCL, sCW, sCL, sLI, sSA }},
/*rst*/{{sCL, sCL, sCL, sSR, sCL, sCL, sCL, sCL, sLA, sLI, sCL }},
/* OUTPUT */
/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
/*syn*/{{sSS, sES, sSS, sSA, sSS, sSS, sSS, sSS, sSS, sLI, sSA }},
/*fin*/{{sTW, sFW, sSS, sTW, sFW, sTW, sCL, sTW, sLA, sLI, sTW }},
/*ack*/{{sES, sES, sSS, sES, sFW, sTW, sCL, sCW, sLA, sES, sES }},
/*rst*/{{sCL, sCL, sSS, sCL, sCL, sTW, sCL, sCL, sCL, sCL, sCL }},
/* INPUT-ONLY */
/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
/*syn*/{{sSA, sES, sES, sSR, sSA, sSA, sSA, sSA, sSA, sSA, sSA }},
/*fin*/{{sCL, sFW, sSS, sTW, sFW, sTW, sCL, sCW, sLA, sLI, sTW }},
/*ack*/{{sCL, sES, sSS, sES, sFW, sTW, sCL, sCW, sCL, sLI, sES }},/*rst*/{{sCL, sCL, sCL, sSR, sCL, sCL, sCL, sCL, sLA, sLI, sCL }},
};
14. 超时变化
timeout_change函数用来变化协议连接的超时,具体就是TCP有两个超时表,用哪个表由本函数决定。 flags参数是由ipvsadm配置时传递来的。
static void tcp_timeout_change(struct ip_vs_protocol *pp, int flags);
15. TCP应用连接绑定
本函数实现将多连接应用协议处理模块和IPVS连接进行绑定。
static int tcp_app_conn_bind(struct ip_vs_conn *cp)
16. 调试函数输出debug_packet
这个函数是TCP/UDP共享的调试函数,输出连接信息。
17. 设置状态超时
该函数在ipvsadm设置相关命令时调用.
static int tcp_set_state_timeout(struct ip_vs_protocol *pp, char *sname, int to);
1.1.2.8IPVS数据包发送
IPVS连接中的数据包的发送方法是由ip_vs_bind_xmit()函数定义的,具体的发送数据包处理
函数定义在net/ipv4/ipvs/ip_vs_xmit.c中。
1. NAT发送
NAT发送只发送请求方向的数据,因此是进行目的NAT
int ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp);
2. TUNNEL发送
TUNNEL发送是把原来的IP部分再加在一个IPIP协议(4)头后发出去,新头的目的IP是真实
目的服务器,源IP是真实客户端IP,该包是可以路由的,服务器的回应包将直接路由回去而不经过IPVS.
int ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp)
3. DR发送
DR发送是将原来的skb包中的目的MAC地址修改为目的服务器的MAC地址后直接发出,因此是不能路由的,IPVS均衡设备和目的服务器物理上必须在同一个二层子网。在DR模式下,IPVS和服务器都配置了相同的对外服务的VIP,服务器也配了自己的真实IP,不过服务器上配VIP的网卡属性中的NOARP信息是打开的,就是在该网卡上不响应ARP信息,但可以接收到达该VIP的数据包,这样外面请求包先是到IPVS均衡器,因为IPVS的VIP是响应ARP的,然后根据调度找一台服务器,用服务器的真实IP来确定路由,然后直接把包发出来,这时包中所有数据都没修改,因为目的服务器上VIP地址符合包中的目的地址,因此是可以接收该包的。
int ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp) ;
4. NULL发送
啥也没干
int ip_vs_null_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp)
{
/* we do not touch skb and do not need pskb ptr */
return NF_ACCEPT;
}
5. 旁路发送
旁路模式,实际数据包不是给IPVS均衡器自己的,由IPVS进行转发
int ip_vs_bypass_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp) ;
6. ICMP发送
发送各种ICMP错误信息包
int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp, int offset).
1.1.2.9IPVS的应用(多连接协议)管理
IPVS的应用是针对象FTP等的多连接协议处理的,由于多连接协议的特殊性,任何以连接为基础进行处理的模块如IPVS,netfilter等都必须对这些协议特别处理,不过IPVS相对没有netfilter那么完善,目前也仅仅支持FTP协议,而netfilter已经可以支持FTP、TFTP、IRC、AMANDA、MMS、SIP、H.323等多种多连接协议。
IPVS应用也是模块化的,不过其实现有点特别,对于每一个应用协议,会定义一个静态的struct ip_vs_app结构作为模板,以后登记该协议时,对应的应用指针并不是直接指向这个静态结构,而是新分配一个struct ip_vs_app结构,结构中的struct ip_vs_app指针指向这个静态结构,然后把新分配的这个结构分别挂接到静态struct ip_vs_app结构的具体实现链表和IP协议的应用HASH链表中进行使用,这种实现方法和netfilter完全不同。
IPVS应用一些共享的处理函数在net/ipv4/ipvs/ip_vs_app.c中定义,其他各协议相关处理分别由各自文件处理,如net/ipv4/ipvs/ip_vs_ftp.c.
1. 新建应用结构
新建一个应用实例,注意输入参数除了协议端口外,还需要提供一个应用模板的指针
而且函数并不直接返回应用结构本身,而是在函数中新建的应用实例直接挂接到链表中
只返回建立成功(0)或失败(<0)
static int ip_vs_app_inc_new(struct ip_vs_app *app, __u16 proto, __u16 port)
2. 释放应用结构
static void ip_vs_app_inc_release(struct ip_vs_app *inc);
3. 登记应用
登记应用实例,新建并登记
int register_ip_vs_app_inc(struct ip_vs_app *app, __u16 proto, __u16 port)
登记应用, 只登记
int register_ip_vs_app(struct ip_vs_app *app);
4. 拆除应用
void unregister_ip_vs_app(struct ip_vs_app *app);
5. 应用于连接的绑定和拆除
int ip_vs_bind_app(struct ip_vs_conn *cp, struct ip_vs_protocol *pp);
void ip_vs_unbind_app(struct ip_vs_conn *cp) ;
6. 处理输出方向的应用数据
应用协议修改输出方向的应用层数据,在协议的snat_handler()函数中调用
int ip_vs_app_pkt_out(struct ip_vs_conn *cp, struct sk_buff **pskb)
处理TCP应用发出方向的数据包
static inline int app_tcp_pkt_out(struct ip_vs_conn *cp, struct sk_buff **pskb,
struct ip_vs_app *app);
7. 处理进入方向的应用数据
应用协议修改进入方向的应用层数据,在协议的dnat_handler()函数中调用
int ip_vs_app_pkt_in(struct ip_vs_conn *cp, struct sk_buff **pskb);
处理TCP应用进入方向的数据包
static inline int app_tcp_pkt_in(struct ip_vs_conn *cp, struct sk_buff **pskb,
struct ip_vs_app *app);
8. 修改数据包中的内容
将skb包中某段数据更改为新的数据,是一个通用函数,可供应用协议修改协议数据的函数调
用,类似于netfilter的mangle_contents()函数.
int ip_vs_skb_replace(struct sk_buff *skb, gfp_t pri,
char *o_buf, int o_len, char *n_buf, int n_len);
1.1.2.10 IPVS应用实例FTP
在IPVS中只实现了对FTP的处理,具体代码在net/ipv4/ipvs/ip_vs_ftp.c中实现.
1. FTP协议应用结构模板
static struct ip_vs_app ip_vs_ftp = {
.name = "ftp",
.type = IP_VS_APP_TYPE_FTP,
.protocol = IPPROTO_TCP,
.module = THIS_MODULE,
.incs_list = LIST_HEAD_INIT(ip_vs_ftp.incs_list),
.init_conn = ip_vs_ftp_init_conn,
.done_conn = ip_vs_ftp_done_conn,
.bind_conn = NULL,
.unbind_conn = NULL,
.pkt_out = ip_vs_ftp_out,
.pkt_in = ip_vs_ftp_in,
};
2. 应用初始化和删除
static int __init ip_vs_ftp_init(void);
static void __exit ip_vs_ftp_exit(void);
3. 初始化和结束FTP连接
都是空函数
static int ip_vs_ftp_init_conn(struct ip_vs_app *app, struct ip_vs_conn *cp)
{
return 0;
}
static int ip_vs_ftp_done_conn(struct ip_vs_app *app, struct ip_vs_conn *cp)
{
return 0;
}
4. 处理FTP进入方向数据
进入方向的数据是FTP客户端发出的, 和子连接相关的命令为PORT命令,建立一个主动模式的子连接
static int ip_vs_ftp_in(struct ip_vs_app *app, struct ip_vs_conn *cp,
struct sk_buff **pskb, int *diff);
TCP监听:void ip_vs_tcp_conn_listen(struct ip_vs_conn *cp)
从FTP数据中提取IP地址和端口值
static int ip_vs_ftp_get_addrport(char *data, char *data_limit,
const char *pattern, size_t plen, char term,
__u32 *addr, __u16 *port,
char **start, char **end);
5. 处理FTP发出方向的数据
FTP 发出方向的数据是FTP服务器发出的, 和子连接相关的回应为227类型回应,建立一个被动模式的子连接.
static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp,
struct sk_buff **pskb, int *diff);
1.1.2.11 IPVS的同步
IPVS支持对连接的同步,两台IPVS设备可分别以MASTER或BACKUP运行,MASTER进程可将连接信息备份到BACKUP设备上,这样主设备死机时从设备可以无缝切换。 可以在IPVS设备上同时启动MASTER和BACKUP进程,使设备之间互为备份,实现IPVS设备的均衡。
IPVS同步实现在net/ipv4/ipvs/ip_vs_sync.c中
1. 数据结构
同步信息块的格式如下,开始是4字节的信息头,后面是多个IPVS连接同步信息,每个块大小不固定,连接同步信息个数从0到多个:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Count Conns | SyncID | Size |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| IPVS Sync Connection (1) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| . |
| . |
| . |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| IPVS Sync Connection (n) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
信息头结构:
#define SYNC_MESG_HEADER_LEN 4
struct ip_vs_sync_mesg {
__u8 nr_conns;//连接数
__u8 syncid;//同步ID
__u16 size;//数据总长
/* ip_vs_sync_conn entries start here */
};
IPVS连接同步信息结构:
struct ip_vs_sync_conn {
__u8 reserved;
/* Protocol, addresses and port numbers连接基本信息*/
__u8 protocol; /* Which protocol (TCP/UDP) */
__u16 cport;
__u16 vport;
__u16 dport;
__u32 caddr; /* client address */
__u32 vaddr; /* virtual address */
__u32 daddr; /* destination address */
/* Flags and state transition连接的状态和标志*/
__u16 flags; /* status flags */
__u16 state; /* state info */
//后续可能
有连接选项参数,就是TCP的序列号和确认号信息
/* The sequence options start here */
};
IPVS连接同步选项结构,,就是进入和发出发现TCP的序列号信息:
struct ip_vs_sync_conn_options {
struct ip_vs_seq in_seq; /* incoming seq.struct */
struct ip_vs_seq out_seq; /* outgoing seq.struct */
};
连接数据控制块结构
struct ip_vs_sync_buff {
struct list_head list;//形成队列
unsigned long firstuse;
/* pointers for the message data */
struct ip_vs_sync_mesg *mesg;//实际的同步信息
unsigned char *head; 数据空闲区头指针
unsigned char *end;//数据尾指针
};
2. 进程启动
IPVS同步进程是一个内核进程,是由IPVSADM通过命令启动的.
int start_sync_thread(int state, char *mcast_ifn, __u8 syncid);
static int fork_sync_thread(void *startup)
fork_sync_thread()函数也继续fork出一个进程,形成守护进程
static int sync_thread(void *startup)
3. 进程停止
同步进程也是由ipvsadm命令发出的.
int stop_sync_thread(int state)
4. MASTER循环
MASTER循环
static void sync_master_loop(void)
同步数据块出队列
static inline struct ip_vs_sync_buff * sb_dequeue(void)
发现同步信息块
static void ip_vs_send_sync_msg(struct socket *sock, struct ip_vs_sync_mesg *msg)
发送缓冲数据,就是调用kernel_sendmsg()函数发送
static int
ip_vs_send_async(struct socket *sock, const char *buffer, const size_t length)
获取当前同步数据块
static inline struct ip_vs_sync_buff * get_curr_sync_buff(unsigned long time)
5. BACKUP循环
BACKUP循环
static void sync_backup_loop(void)
接收数据函数,比较简单,直接调用内核的kernel_recvmsg函数
static int ip_vs_receive(struct socket *sock, char *buffer, const size_t buflen)
处理接收数据函数
static void ip_vs_process_message(const char *buffer, const size_t buflen)
6. 连接同步
连接同步函数ip_vs_sync_conn()是由ip_vs_in()函数调用的:
void ip_vs_sync_conn(struct ip_vs_conn *cp)
分配新连接同步数据块
static inline struct ip_vs_sync_buff * ip_vs_sync_buff_create(void)
1.1.2.12 IPVS预估器
1.1.2.12
IPVS预估器用的估算在一个短暂时间间隔内的连接率,可在用户空间开一个daemon定时读取预估器的值以实现较长时间的预估。
预估算法为:
取最后8秒钟内,每两秒取一个采样点进行平滑处理:
avgrate = avgrate*(1-W) + rate*W
其中 W = 2^(-2) = 0.25,速率单位是KBytes/s
预估代码在net/ipv4/ipvs/ip_vs_est.c中实现。
1. 数据结构
预估器结构定义如下:
struct ip_vs_estimator
{
struct ip_vs_estimator *next;//链表的下一项
struct ip_vs_stats *stats;//IPVS统计
u32 last_conns;//上次的连接数
u32 last_inpkts;//上次的进入包数
u32 last_outpkts;//上次发出包数
u64 last_inbytes;//上次进入字节数
u64 last_outbytes;//上次发出字节数
u32 cps;//连接率
u32 inpps;//进入的包数率
u32 outpps;//发出的包速率
u32 inbps;//进入的速率
u32 outbps;//发出的速率
};
2. 新建预估器
新建预估器:int ip_vs_new_estimator(struct ip_vs_stats *stats)
定时函数预估计算:static void estimation_timer(unsigned long arg)
3. 删除预估器
删除预估器void ip_vs_kill_estimator(struct ip_vs_stats *stats)
4. 预估器统计值清零
void ip_vs_zero_estimator(struct ip_vs_stats *stats)
IPVS的协议管理
1.1.2.13 IPVS的proc文件
/proc IPVS在/proc目录下建立了以下文件:
/proc/net:
/proc/net/ip_vs:IPVS的规则表
/proc/net/ip_vs_app:IPVS应用协议
/proc/net/ip_vs_conn:IPVS当前连接
/proc/net/ip_vs_stats:IPVS状态统计信息
/proc/sys/net/ipv4/vs:
/proc/sys/net/ipv4/vs/am_droprate:丢包率(缺省10)
/proc/sys/net/ipv4/vs/amemthresh:可用内存阈值(缺省1024)
/proc/sys/net/ipv4/vs/cache_bypass:是否建立旁路cache项 /proc/sys/net/ipv4/vs/debug_level:调试级别
/proc/sys/net/ipv4/vs/drop_entry:确定删除连接处理级别
/proc/sys/net/ipv4/vs/drop_packet:丢包级别
/proc/sys/net/ipv4/vs/expire_nodest_conn:是否删除没有目的服务器的连接
/proc/sys/net/ipv4/vs/lblc_expiration:lblc算法的到期时间(缺省1天)
/proc/sys/net/ipv4/vs/lblcr_expiration:lblcr算法的到期时间(缺省1天)
/proc/sys/net/ipv4/vs/nat_icmp_send:NAT模式下连接异常时发送ICMP包
/proc/sys/net/ipv4/vs/secure_tcp:更安全的TCP状态转换
/proc/sys/net/ipv4/vs/sync_threshold:连接同步时的包数阈值数值
/proc/sys/net/ipv4/vs/timeout_close:TCP sCL状态超时
/proc/sys/net/ipv4/vs/timeout_closewait:TCP sCW状态超时
/proc/sys/net/ipv4/vs/timeout_established:TCP sES状态超时
/proc/sys/net/ipv4/vs/timeout_finwait:TCP sFW状态超时
/proc/sys/net/ipv4/vs/timeout_icmp:ICMP超时
/proc/sys/net/ipv4/vs/timeout_lastack:TCP sLA状态超时
/proc/sys/net/ipv4/vs/timeout_listen:TCP sLI状态超时
/proc/sys/net/ipv4/vs/timeout_synack:TCP sSA状态超时
/proc/sys/net/ipv4/vs/timeout_synrecv:TCP sSR状态超时
/proc/sys/net/ipv4/vs/timeout_synsent:TCP sSS状态超时
/proc/sys/net/ipv4/vs/timeout_timewait:TCP sTW状态超时
/proc/sys/net/ipv4/vs/timeout_udp:UDP超时 13.IPVS
1.1.2.14 IPVS控制
IPVS控制包括定义IPVS提供的虚拟服务参数和实际的目的服务器等各种参数。
IPVS的控制信息是通过setsockopt系统调用传递到内核的,IPVS在用户层的管理工具是ipvsadm。
关于IPVS控制代码在net/ipv4/ipvs/ip_vs_ctl.c中。
1. 登记sockopt操作
借用netfilter的struct nf_sockopt_ops结构来添加:
static struct nf_sockopt_ops ip_vs_sockopts = {
.pf = PF_INET,
.set_optmin = IP_VS_BASE_CTL,
.set_optmax = IP_VS_SO_SET_MAX+1,
.set = do_ip_vs_set_ctl,
.get_optmin = IP_VS_BASE_CTL,
.get_optmax = IP_VS_SO_GET_MAX+1, .get = do_ip_vs_get_ctl,
};
ret = nf_register_sockopt(&ip_vs_sockopts);
2. 写控制
static int do_ip_vs_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len);
3. 读控制
static int do_ip_vs_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
4. 服务管理
IPVS服务是IPVS均衡器对外提供的地址和端口信息,由结构struct ip_vs_serivce描述,一般情况下此结构的具体对象不多,由具体对外提供的服务数决定。 服务参数是通过ipvsadm命令添加的,命令为:
ipvsadm -A -t/u v_srv_ip:vport -s scheduler
13.4.1 添加服务
static int ip_vs_add_service(struct ip_vs_service_user *u, struct ip_vs_service **svc_p);
13.4.2 修改服务参数
static int ip_vs_edit_service(struct ip_vs_service *svc, struct ip_vs_service_user *u)
13.4.3 删除服务
static int ip_vs_del_service(struct ip_vs_service *svc);
static void __ip_vs_del_service(struct ip_vs_service *svc);
13.4.4 删除所有的服务
static int ip_vs_flush(void);
13.4.5 清空服务计数器
清空一个服务结构的统计值 static int ip_vs_zero_service(struct ip_vs_service *svc);
清空所有服务的统计值 static int ip_vs_zero_all(void);
5. 目的服务器管理
目的服务器是实际提供对外服务的服务器,由结构struct ip_vs_dest描述,该结构的具体对象可以很多,每个结构描述一个具体的服务器, 目的服务器参数是通过ipvsadm命令添加的,命令为:
ipvsadm -a -t/u v_srv_ip:vport -r dest_ip:dest_port -w weight
13.5.1 添加目的服务器
ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user *udest)
新分配目的服务器结构
static int ip_vs_new_dest(struct ip_vs_service *svc, struct ip_vs_dest_user *udest, struct ip_vs_dest **dest_p);
13.5.2 修改服务器参数
static int ip_vs_edit_dest(struct ip_vs_service *svc, struct ip_vs_dest_user *udest);
13.5.3 删除服务器
static int ip_vs_del_dest(struct ip_vs_service *svc,struct ip_vs_dest_user *udest);
static void __ip_vs_del_dest(struct ip_vs_dest *dest);
从目的服务器从服务的链表中拆除 static void __ip_vs_unlink_dest(struct
ip_vs_service *svc, struct ip_vs_dest *dest, int svcupd);
13.6 防御级别调整
IPVS有个定时函数周期性根据当前系统配置和性能状况调整IPVS的防御级别:
#define DEFENSE_TIMER_PERIOD 1*HZ//调整周期1秒
static void defense_work_handler(void *data);
static DECLARE_WORK(defense_work, defense_work_handler, NULL);
定时处理函数:static void defense_work_handler(void *data)
更新IPVS的防御级别,需要用到/proc下定义的一些控制参数 static void update_defense_level(void);
14.小结
总的说,IPVS用不太长的代码量(1万多行)就完成了服务器均衡的这个任务,而且实现多种均衡算法和连接方式,可说是一个效率比较高的模块。以后要完善的地方我觉得一是如何将IPVS连接和netfilter的连接合并处理,毕竟没必要维护两个连接表;而二是多连接协议的helper支持,IPVS这里封装的不是很好。在NAT模式下IPVS和netfilter比较容易合并,IPVS就只体现在NAT转换时的目的地址的选取处理上即可,和其他模块无关,而目前netfilter是HASH均衡方式来动态选择的,这样连接跟踪和NAT处理完全是由netfilter来完成的,也很好支持了多连接协议;但其他两种模式支持起来相对麻烦点,要区别不同的状态转换表处理,连接跟踪处理需要修改。