IPVS实现分析
IPVS实现分析
- IPVS实现分析
- 根据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_get_services {
- /* number of virtual services */
- unsigned int num_services;
- /* service table */
- ];
- };
- 用户空间的获取超时信息结构
- 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();
- ) {
- IP_VS_ERR("can't setup control.\n");
- goto cleanup_nothing;
- }
- //协议初始化
- ip_vs_protocol_init();
- //应用层辅助接口初始化
- ret = ip_vs_app_init();
- ) {
- IP_VS_ERR("can't setup application helper.\n");
- goto cleanup_protocol;
- }
- //主要数据结构初始化
- ret = ip_vs_conn_init();
- ) {
- 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);
- ) {
- IP_VS_ERR("can't register in hook.\n");
- goto cleanup_conn;
- }
- ret = nf_register_hook(&ip_vs_out_ops);
- ) {
- IP_VS_ERR("can't register out hook.\n");
- goto cleanup_inops;
- }
- ret = nf_register_hook(&ip_vs_post_routing_ops);
- ) {
- IP_VS_ERR("can't register post_routing hook.\n");
- goto cleanup_outops;
- }
- ret = nf_register_hook(&ip_vs_forward_icmp_ops);
- ) {
- 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_info_fops);
- proc_net_fops_create(, &ip_vs_stats_fops);
- //建立/proc/sys/net/ipv4/vs目录下的各可读写控制参数
- sysctl_header = register_sysctl_table(vs_root_table, );
- //初始化各种双向链表
- //svc_table是根据协议地址端口等信息进行服务结构struct ip_vs_service查找的HASH表
- //svc_fwm_table是根据数据包的nfmark信息进行服务结构struct ip_vs_service查找的HASH表
- ; 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链表
- ; idx < IP_VS_RTAB_SIZE; idx++) {
- INIT_LIST_HEAD(&ip_vs_rtable[idx]);
- }
- //ipvs统计信息
- memset(&ip_vs_stats, , 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);
- ;
- }
- //协议初始化,具体看下面协议实现
- int ip_vs_protocol_init(void)
- {
- //挂接ipvs能进行均衡处理的各种协议,目前支持TCP/UDP/AH/ESP
- ];
- #define REGISTER_PROTOCOL(p) \
- do { \
- register_ip_vs_protocol(p); \
- strcat(protocols, ", "); \
- strcat(protocols, (p)->name); \
- } )
- //0,1字符是给", "预留的
- protocols[] = '\0';
- protocols[] = '\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(]);
- ;
- }
- #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);
- ;
- }
- 应用层辅助接口初始化
- int ip_vs_app_init(void)
- {
- //建立一个/proc/net/ip_vs_app项
- proc_net_fops_create(, &ip_vs_app_fops);
- ;
- }
- 主要数据结构初始化
- 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(, SLAB_HWCACHE_ALIGN, NULL, NULL);
- if (!ip_vs_conn_cachep) {
- vfree(ip_vs_conn_tab);
- return -ENOMEM;
- }
- //初始化HASH链表头
- ; idx < IP_VS_CONN_TAB_SIZE; idx++) {
- INIT_LIST_HEAD(&ip_vs_conn_tab[idx]);
- }
- //初始化各读写锁
- ; 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_fops);
- //初始化随机数
- get_random_bytes(&ip_vs_conn_rnd, sizeof(ip_vs_conn_rnd));
- ;
- }
- [hook点实现]
- 我们一个一个看
- static struct nf_hook_ops ip_vs_in_ops = {
- .hook = ip_vs_in,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_IP_LOCAL_IN,
- .priority = ,
- };
- static unsigned int ip_vs_in(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in, const struct net_device * out, int (*okfn)(struct sk_buff *))
- {
- struct sk_buff *skb = *pskb;
- struct iphdr *iph;
- struct ip_vs_protocol *pp;
- struct ip_vs_conn *cp;
- int ret, restart;
- int ihl;
- //不处理目的非本机的包
- if (unlikely(skb->pkt_type != PACKET_HOST || skb->dev == &loopback_dev || skb->sk)) {
- IP_VS_DBG(, "packet type=%d proto=%d daddr=%d.%d.%d.%d ignored\n",
- skb->pkt_type, skb->nh.iph->protocol, NIPQUAD(skb->nh.iph->daddr));
- return NF_ACCEPT;
- }
- iph = skb->nh.iph;
- if (unlikely(iph->protocol == IPPROTO_ICMP)) {
- //如果是ICMP,可能是指示连接错误的ICMP信息,调用ip_vs_in_icmp进行检查是否是相关的ICMP信息
- int related, verdict = ip_vs_in_icmp(pskb, &related, hooknum);
- if (related)
- return verdict;
- //非相关ICMP,恢复处理流程
- // 但其实ipvs是不均衡ICMP信息的,后面就返回了
- skb = *pskb;
- iph = skb->nh.iph;
- }
- //获取协议支持模块,由于只支持TCP、UDP、AH和ESP,如果是ICMP,返回为NULL
- pp = ip_vs_proto_get(iph->protocol);
- if (unlikely(!pp))
- return NF_ACCEPT;
- ihl = iph->ihl << ; //ip头长度
- //找到和该skb相关的ipvs连接,类似netfilter的根据tuple查找连接,
- //不过sk_buff结构中没有增加nfct那样能直接指向连接的成员
- //对TCP协议来说是tcp_conn_in_get(),看下面协议实现
- cp = pp->conn_in_get(skb, pp, iph, ihl, );
- if (unlikely(!cp)) {
- int v;
- //如果没有连接, 表明是新连接, 调用IPVS连接的conn_schedule为连接分配和处理连接调度,
- //要根据调度算法选择一个真实目的服务器,然后建立新的IPVS连接
- //对TCP协议来说是tcp_conn_schedule()
- if (!pp->conn_schedule(skb, pp, &v, &cp))
- return v;
- }
- if (unlikely(!cp)) { //这种情况主要是没内存空间了,IPVS没提供主动删除连接的机制
- IP_VS_DBG_PKT(, pp, skb, , "packet continues traversal as normal");
- return NF_ACCEPT;
- }
- //对于目的服务器失效的包丢弃
- if (cp->dest && !(cp->dest->flags & IP_VS_DEST_F_AVAILABLE)) {
- if (sysctl_ip_vs_expire_nodest_conn) {
- /* try to expire the connection immediately */
- ip_vs_conn_expire_now(cp);
- }
- __ip_vs_conn_put(cp);
- return NF_DROP;
- }
- //连接信息统计
- ip_vs_in_stats(cp, skb);
- //进行连接状态的迁移, restart这个参数其实没用
- //对TCP协议来说是调用tcp_state_transition
- restart = ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pp);
- if (cp->packet_xmit)
- //将包发送出去, 具体xmit的实现在ip_vs_xmit.c中实现,
- //NAT模式下为 ip_vs_nat_xmit;
- //通道模式下为 ip_vs_tunnel_xmit;
- //直接路由模式下为: ip_vs_dr_xmit;
- //本机数据为: ip_vs_null_xmit;
- //旁路模式下为: ip_vs_bypass_xmit;
- //函数成功时基本都返回NF_STOLEN使netfilter不再处理该包
- //所以对于NAT模式,应该是不需要配置DNAT规则的,请求方向数据也不经过FORWARD链
- ret = cp->packet_xmit(skb, cp, pp);
- else {
- IP_VS_DBG_RL("warning: packet_xmit is null");
- ret = NF_ACCEPT;
- }
- atomic_inc(&cp->in_pkts); //增加进入包计数器
- //在进行均衡器热备时将连接信息要从MASTER传递到SLAVE,使系统切换时
- //连接不丢弃,但还是要有一定条件才进行同步
- if ((ip_vs_sync_state & IP_VS_STATE_MASTER) //同步状态类型为主机
- && (cp->protocol != IPPROTO_TCP || cp->state == IP_VS_TCP_S_ESTABLISHED) && //非TCP连接或是已经建立的连接
- //当前连接的包数适当时
- (atomic_read(&cp->in_pkts) % sysctl_ip_vs_sync_threshold[] == sysctl_ip_vs_sync_threshold[]))
- ip_vs_sync_conn(cp); //进行连接的同步,看下面IPVS的同步
- //调整连接超时,释放连接计数
- ip_vs_conn_put(cp);
- return ret;
- }
- static struct nf_hook_ops ip_vs_out_ops = {
- .hook = ip_vs_out,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_IP_FORWARD,
- .priority = ,
- };
- 这个函数对转发包进行处理, 只用在NAT模式的均衡处理,TUNNEL和DR方式下都是直接发送了,
- 实际处理的只是服务器返回的回应包,而客户端请求的包是不经过这里的,
- 但如果设置了DNAT规则,数据包在PREROUTING点进行了目的地址修改,这样就不会再进入INPUT点而是直接转到FORWARD点处理,
- 这时时针对该包的 IPVS连接是没有建立的.
- static unsigned int ip_vs_out(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
- {
- struct sk_buff *skb = *pskb;
- struct iphdr *iph;
- struct ip_vs_protocol *pp;
- struct ip_vs_conn *cp;
- int ihl;
- //这个标志只占一位,标志置位就是已经经过IPVS处理了,直接返回
- if (skb->ipvs_property)
- return NF_ACCEPT;
- iph = skb->nh.iph;
- if (unlikely(iph->protocol == IPPROTO_ICMP)) {
- //处理可能的连接相关ICMP错误信息,如地址端口不可达等
- int related, verdict = ip_vs_out_icmp(pskb, &related);
- if (related)
- return verdict;
- skb = *pskb;
- iph = skb->nh.iph;
- }
- //取得IPVS协议, tcp/udp/ah/esp之一
- pp = ip_vs_proto_get(iph->protocol);
- if (unlikely(!pp))
- return NF_ACCEPT;
- //如果是碎片包进行重组,基本不可能,因为数据包进入netfilter时就要进行碎片重组
- if (unlikely(iph->frag_off & __constant_htons(IP_MF|IP_OFFSET) && !pp->dont_defrag)) {
- skb = ip_vs_gather_frags(skb, IP_DEFRAG_VS_OUT);
- if (!skb)
- return NF_STOLEN;
- iph = skb->nh.iph;
- *pskb = skb;
- }
- ihl = iph->ihl << ; //ip头长度
- //查找IPVS连接
- cp = pp->conn_out_get(skb, pp, iph, ihl, );
- if (unlikely(!cp)) { //没有找到,可能是请求方向的包经过DNAT过来的
- if (sysctl_ip_vs_nat_icmp_send && (pp->protocol == IPPROTO_TCP || pp->protocol == IPPROTO_UDP)) {
- __u16 _ports[], *pptr;
- pptr = skb_header_pointer(skb, ihl, sizeof(_ports), _ports);
- if (pptr == NULL)
- return NF_ACCEPT;
- //用源地址,源端口来查真实服务器结构,如果是请求方向是找不到的
- //这种情况下数据包就不再被IPVS处理
- ])) {
- if (iph->protocol != IPPROTO_TCP || !is_tcp_reset(skb)) {
- icmp_send(skb,ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, ); //发送icmp不可达信息
- return NF_DROP;
- }
- }
- }
- return NF_ACCEPT;
- }
- //找到连接,该包是服务器的回应包
- //skb数据包要求是可写的
- if (!ip_vs_make_skb_writable(pskb, ihl))
- goto drop;
- //修改协议部分信息,如TCP、UDP的端口
- if (pp->snat_handler && !pp->snat_handler(pskb, pp, cp))
- goto drop;
- skb = *pskb;
- //修改源地址, 由于是服务器的返回包,只修改源地址
- skb->nh.iph->saddr = cp->vaddr;
- ip_send_check(skb->nh.iph); //重新计算校验和
- //重新计算路由信息,对于本地产生的数据包
- )
- goto drop;
- skb = *pskb;
- //PVS输出统计
- ip_vs_out_stats(cp, skb);
- ip_vs_set_state(cp, IP_VS_DIR_OUTPUT, skb, pp); //状态迁移
- ip_vs_conn_put(cp); //调整连接超时,释放连接计数
- skb->ipvs_property = ; //对该包设置标志表示IPVS处理过了
- return NF_ACCEPT;
- drop:
- ip_vs_conn_put(cp);
- kfree_skb(*pskb);
- return NF_STOLEN;
- }
- static struct nf_hook_ops ip_vs_forward_icmp_ops = {
- .hook = ip_vs_forward_icmp,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_IP_FORWARD,
- .priority = , //在ip_vs_out_ops之前进行
- };
- //这个函数对转发的ICMP包进行处理, 处理由于服务器失效而引起的网络或端口不可达的ICMP信息,其他和服务器无关的ICMP信息不处理.
- static unsigned int ip_vs_forward_icmp(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in,
- const struct net_device *out, int (*okfn)(struct sk_buff *))
- {
- int r;
- if ((*pskb)->nh.iph->protocol != IPPROTO_ICMP)
- return NF_ACCEPT;
- return ip_vs_in_icmp(pskb, &r, hooknum);
- }
- static int ip_vs_in_icmp(struct sk_buff **pskb, int *related, unsigned int hooknum)
- {
- struct sk_buff *skb = *pskb;
- struct iphdr *iph;
- struct icmphdr _icmph, *ic;
- struct iphdr _ciph, *cih; /* The ip header contained within the ICMP */
- struct ip_vs_conn *cp;
- struct ip_vs_protocol *pp;
- unsigned int offset, ihl, verdict;
- *related = ; //这个参数指示该ICMP包是否和IPVS的连接相关,好像没用
- if (skb->nh.iph->frag_off & __constant_htons(IP_MF|IP_OFFSET)) {
- //进行碎片重组
- skb = ip_vs_gather_frags(skb, hooknum == NF_IP_LOCAL_IN ? IP_DEFRAG_VS_IN : IP_DEFRAG_VS_FWD);
- if (!skb)
- return NF_STOLEN;
- *pskb = skb;
- }
- iph = skb->nh.iph; //ip头
- offset = ihl = iph->ihl * ; //数据开始
- //获取icmp头
- ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph);
- if (ic == NULL)
- return NF_DROP;
- //如果不是这三种ICMP信息,则该skb与IPVS无关
- if ((ic->type != ICMP_DEST_UNREACH) && (ic->type != ICMP_SOURCE_QUENCH) && (ic->type != ICMP_TIME_EXCEEDED)) {
- *related = ;
- return NF_ACCEPT;
- }
- offset += sizeof(_icmph); //获取ip头
- cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph);
- if (cih == NULL)
- return NF_ACCEPT;
- //找的是ICMP信息中包含的原始包中的协议,而不是ICMP
- pp = ip_vs_proto_get(cih->protocol);
- if (!pp)
- return NF_ACCEPT;
- //如果是碎片包且定义了不处理标志则直接返回
- if (unlikely(cih->frag_off & __constant_htons(IP_OFFSET) && pp->dont_defrag))
- return NF_ACCEPT;
- offset += cih->ihl * ; // + ip头长度
- //查找IPVS连接
- cp = pp->conn_in_get(skb, pp, cih, offset, );
- if (!cp)
- return NF_ACCEPT;
- //缺省的裁定结果是丢弃包
- verdict = NF_DROP;
- //检查ip校验和
- if (skb->ip_summed != CHECKSUM_UNNECESSARY && ip_vs_checksum_complete(skb, ihl)) {
- IP_VS_DBG(, "Incoming ICMP: failed checksum from %d.%d.%d.%d!\n", NIPQUAD(iph->saddr));
- goto out;
- }
- ip_vs_in_stats(cp, skb);//进行输入统计
- //如果内部协议是TCP/UDP,发送偏移量要包括前4个字节: 源端口和目的端口
- if (IPPROTO_TCP == cih->protocol || IPPROTO_UDP == cih->protocol)
- offset += * sizeof(__u16);
- //发送ICMP
- verdict = ip_vs_icmp_xmit(skb, cp, pp, offset);
- out:
- __ip_vs_conn_put(cp);
- return verdict;
- }
- 发送各种ICMP错误信息包
- int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, int offset)
- {
- struct rtable *rt; /* Route to the other host */
- int mtu;
- int rc;
- //如果不是NAT情况的IPVS连接, 即是TUNNEL或DR,直接调用连接的发送函数发送
- if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) {
- if (cp->packet_xmit)
- rc = cp->packet_xmit(skb, cp, pp);
- else
- rc = NF_ACCEPT;
- /* do not touch skb anymore */
- atomic_inc(&cp->in_pkts);
- goto out;
- }
- //查找路由
- if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(skb->nh.iph->tos))))
- goto tx_error_icmp;
- mtu = dst_mtu(&rt->u.dst);
- //数据包过长超过MTU,但又是不允许分片的,发送ICMP出错包
- if ((skb->len > mtu) && (skb->nh.iph->frag_off & __constant_htons(IP_DF))) {
- ip_rt_put(rt);
- icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
- IP_VS_DBG_RL("ip_vs_in_icmp(): frag needed\n");
- goto tx_error;
- }
- //让skb可写
- if (!ip_vs_make_skb_writable(&skb, offset))
- goto tx_error_put;
- //skb留出足够的硬件头空间
- if (skb_cow(skb, rt->u.dst.dev->hard_header_len))
- goto tx_error_put;
- dst_release(skb->dst);
- skb->dst = &rt->u.dst;
- //修改ICMP包
- ip_vs_nat_icmp(skb, pp, cp, );
- /* Another hack: avoid icmp_send in ip_fragment */
- skb->local_df = ;
- //将该包用OUTPUT点的hook_ops进行处理
- /* #define IP_VS_XMIT(skb, rt) \
- * do { \
- * (skb)->ipvs_property = 1; \
- * (skb)->ip_summed = CHECKSUM_NONE; \
- * NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, (skb), NULL, (rt)->u.dst.dev, dst_output); \
- * } while (0)
- */
- IP_VS_XMIT(skb, rt);
- rc = NF_STOLEN; //表示该skb不用返回到正常的IP栈了
- goto out;
- tx_error_icmp:
- dst_link_failure(skb);
- tx_error:
- dev_kfree_skb(skb);
- rc = NF_STOLEN;
- out:
- return rc;
- tx_error_put:
- ip_rt_put(rt);
- goto tx_error;
- }
- static struct nf_hook_ops ip_vs_post_routing_ops = {
- .hook = ip_vs_post_routing,
- .owner = THIS_MODULE,
- .pf = PF_INET,
- .hooknum = NF_IP_POST_ROUTING,
- .priority = NF_IP_PRI_NAT_SRC-, //在源NAT之前进行
- };
- 这个函数对最后要发出的包进行检查,这个包是经过FORWARD链的,源地址已经被IPVS修改过了,不用再被netfilter进行修改,那么返回NF_STOP.
- 如果是IPVS处理过的包,直接跳出POSTROUTING点, 不再继续可能的该点的更低优先级的hook点操作,即不用进行netfilter标准的SNAT操作.
- static unsigned int ip_vs_post_routing(unsigned int hooknum, struct sk_buff **pskb, const struct net_device *in,
- const struct net_device *out, int (*okfn)(struct sk_buff *))
- {
- if (!((*pskb)->ipvs_property)) //如果没被IPVS处理过,继续后续hook点操作
- return NF_ACCEPT;
- //STOP就不继续后面的低优先级的hook_ops的操作了
- return NF_STOP;
- }
- [/hook点实现]
- [IPVS调度算法]
- 均衡调度算法是IPVS实现均衡功能的理论精髓,其他各种东西都只算是程序技巧,现在介绍.
- IPVS支持8种静态均衡算法,以下文字直接拷贝自IPVS网站:
- IPVS在内核中的负载均衡是以连接为粒度的.在HTTP协议(非持久)中,每个对象从WEB服务器上获取都需要建立一个TCP连接,
- 同一用户的不同请求会被调度到不同的服务器上,所以这种细粒度的调度在一定程度上可以避免单个用户访问的突发性引起服务器间的负载不平衡.
- 在内核中的连接调度算法上,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)
- 下面,我们先介绍这八种连接调度算法的工作原理和算法流程,然后会在下面描述怎么用它们.
- 轮叫调度 :
- (Round Robin Scheduling)算法就是以轮叫的方式依次将请求调度不同的服务器,即每次调度执行i = (i + ) mod n,并选出第i台服务器。
- 算法的优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度。
- 在系统实现时,我们引入了一个额外条件,当服务器的权值为零时,表示该服务器不可用而不被调度。
- 这样做的目的是将服务器切出服务(如屏蔽服务器故障和系统维护),同时与其他加权算法保持一致。所以,算法要作相应的改动,它的算法流程如下:
- 轮叫调度算法流程
- 假设有一组服务器S = {S0, S1, …, Sn-},一个指示变量i表示上一次选择的服务器,W(Si)表示服务器Si的权值。变量 i 被初始化为n-,其中n > 。
- j = i;
- do {
- j = (j + ) mod n;
- ) {
- i = j;
- return Si;
- }
- } while (j != i);
- return NULL;
- 轮叫调度算法假设所有服务器处理性能均相同,不管服务器的当前连接数和响应速度。
- 该算法相对简单,不适用于服务器组中处理性能不一的情况,而且当请求服务时间变化比较大时,轮叫调度算法容易导致服务器间的负载不平衡。
- 虽然Round-Robin DNS方法也是以轮叫调度的方式将一个域名解析到多个IP地址,但轮叫DNS方法的调度粒度是基于每个域名服务器的,
- 域名服务器对域名解析的缓存会妨碍轮叫解析域名生效,这会导致服务器间负载的严重不平衡。
- 这里,IPVS轮叫调度算法的粒度是基于每个连接的,同一用户的不同连接都会被调度到不同的服务器上,所以这种细粒度的轮叫调度要比DNS的轮叫调度优越很多.
- 加权轮叫调度 :
- (Weighted Round-Robin Scheduling)算法可以解决服务器间性能不一的情况,它用相应的权值表示服务器的处理性能,服务器的缺省权值为1。
- 假设服务器A的权值为1,B的权值为2,则表示服务器B的处理性能是A的两倍。加权轮叫调度算法是按权值的高低和轮叫方式分配请求到各服务器。
- 权值高的服务器先收到的连接,权值高的服务器比权值低的服务器处理更多的连接,相同权值的服务器处理相同数目的连接数。加权轮叫调度算法流程如下:
- 加权轮叫调度算法流程
- 假设有一组服务器S = {S0, S1, …, Sn-},W(Si)表示服务器Si的权值,一个指示变量i表示上一次选择的服务器,
- 指示变量cw表示当前调度的权值,max(S)表示集合S中所有服务器的最大权值,gcd(S)表示集合S中所有服务器权值的最大公约数。
- 变量i初始化为-,cw初始化为零。
- while (true) {
- i = (i + ) mod n;
- ) {
- cw = cw - gcd(S);
- ) {
- cw = max(S);
- )
- return NULL;
- }
- }
- if (W(Si) >= cw)
- return Si;
- }
- 例如,有三个服务器A、B和C分别有权值4、3和2,则在一个调度周期内(mod sum(W(Si)))调度序列为AABABCABC。
- 加权轮叫调度算法还是比较简单和高效。当请求的服务时间变化很大,单独的加权轮叫调度算法依然会导致服务器间的负载不平衡。
- 从上面的算法流程中,我们可以看出当服务器的权值为零时,该服务器不被被调度;
- 当所有服务器的权值为零,即对于任意i有 W(Si)=,则没有任何服务器可用,算法返回NULL,所有的新连接都会被丢掉。
- 加权轮叫调度也无需记录当前所有连接的状态,所以它也是一种无状态调度。
- 最小连接调度 :
- (Least-Connection Scheduling)算法是把新的连接请求分配到当前连接数最小的服务器。
- 最小连接调度是一种动态调度算法,它通过服务器当前所活跃的连接数来估计服务器的负载情况。
- 调度器需要记录各个服务器已建立连接的数目,当一个请求被调度到某台服务器,其连接数加1;当连接中止或超时,其连接数减一。
- 在系统实现时,我们也引入当服务器的权值为零时,表示该服务器不可用而不被调度,它的算法流程如下:
- 最小连接调度算法流程
- 假设有一组服务器S = {S0, S1, ..., Sn-},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。
- ; m < n; m++) {
- ) {
- ; i < n; i++) {
- )
- continue;
- if (C(Si) < C(Sm))
- m = i;
- }
- return Sm;
- }
- }
- return NULL;
- 当各个服务器有相同的处理性能时,最小连接调度算法能把负载变化大的请求分布平滑到各个服务器上,所有处理时间比较长的请求不可能被发送到同一台服务器上。
- 但是,当各个服务器的处理能力不同时,该算法并不理想,因为TCP连接处理请求后会进入TIME_WAIT状态,TCP的TIME_WAIT一般为2分钟,
- 此时连接还占用服务器的资源,所以会出现这样情形,性能高的服务器已处理完所收到的连接,连接处于TIME_WAIT状态,
- 而性能低的服务器已经忙于处理所收到的连接,还不断地收到新的连接请求。
- 加权最小连接调度 :
- (Weighted Least-Connection Scheduling)算法是最小连接调度的超集,各个服务器用相应的权值表示其处理性能。
- 服务器的缺省权值为1,系统管理员可以动态地设置服务器的权值。加权最小连接调度在调度新连接时尽可能使服务器的已建立连接数和其权值成比例。
- 加权最小连接调度的算法流程如下:
- 加权最小连接调度的算法流程
- 假设有一组服务器S = {S0, S1, ..., Sn-},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。
- 所有服务器当前连接数的总和为CSUM = ΣC(Si) (i=, , .. , n-)。当前的新连接请求会被发送服务器Sm,
- 当且仅当服务器Sm满足以下条件
- (C(Sm) / CSUM)/ W(Sm) = min { (C(Si) / CSUM) / W(Si)} (i=, , . , n-)
- 其中W(Si)不为零
- 因为CSUM在这一轮查找中是个常数,所以判断条件可以简化为
- C(Sm) / W(Sm) = min { C(Si) / W(Si)} (i=, , . , n-)
- 其中W(Si)不为零
- 因为除法所需的CPU周期比乘法多,且在Linux内核中不允许浮点除法,服务器的权值都大于零,
- 所以判断条件C(Sm) / W(Sm) > C(Si) / W(Si) 可以进一步优化为C(Sm)*W(Si) > C(Si)* W(Sm)。
- 同时保证服务器的权值为零时,服务器不被调度。所以,算法只要执行以下流程。
- ; m < n; m++) {
- ) {
- ; i < n; i++) {
- if (C(Sm)*W(Si) > C(Si)*W(Sm))
- m = i;
- }
- return Sm;
- }
- }
- return NULL;
- 基于局部性的最少链接调度 :
- (Locality-Based Least Connections Scheduling,以下简称为LBLC)算法是针对请求报文的目标IP地址的负载均衡调度,目前主要用于Cache集群系统,
- 因为在Cache集群中客户请求报文的目标IP地址是变化的。这里假设任何后端服务器都可以处理任一请求,算法的设计目标是在服务器的负载基本平衡情况下,
- 将相同目标IP地址的请求调度到同一台服务器,来提高各台服务器的访问局部性和主存Cache命中率,从而整个集群系统的处理能力。
- LBLC调度算法先根据请求的目标IP地址找出该目标IP地址最近使用的服务器,若该服务器是可用的且没有超载,将请求发送到该服务器;
- 若服务器不存在,或者该服务器超载且有服务器处于其一半的工作负载,则用“最少链接”的原则选出一个可用的服务器,将请求发送到该服务器。
- 该算法的详细流程如下:
- LBLC调度算法流程
- 假设有一组服务器S = {S0, S1, ..., Sn-},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。
- ServerNode[dest_ip]是一个关联变量,表示目标IP地址所对应的服务器结点,一般来说它是通过Hash表实现的。
- WLC(S)表示在集合S中的加权最小连接服务器,即前面的加权最小连接调度。Now为当前系统时间。
- if (ServerNode[dest_ip] is NULL) {
- n = WLC(S);
- if (n is NULL)
- return NULL;
- ServerNode[dest_ip].server = n;
- } else {
- n = ServerNode[dest_ip].server;
- ))) {
- n = WLC(S);
- if (n is NULL)
- return NULL;
- ServerNode[dest_ip].server = n;
- }
- }
- ServerNode[dest_ip].lastuse = Now;
- return n;
- 此外,对关联变量ServerNode[dest_ip]要进行周期性的垃圾回收(Garbage Collection),将过期的目标IP地址到服务器关联项进行回收。
- 过期的关联项是指哪些当前时间(实现时采用系统时钟节拍数jiffies)减去最近使用时间超过设定过期时间的关联项,系统缺省的设定过期时间为24小时。
- 带复制的基于局部性最少链接调度 :
- (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-},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)
- return NULL;
- add n into ServerSet[dest_ip];
- } else {
- n = WLC(ServerSet[dest_ip]);
- ))) {
- n = WLC(S);
- if (n is NULL)
- return NULL;
- add n into ServerSet[dest_ip];
- } && Now - ServerSet[dest_ip].lastmod > T) {
- m = WGC(ServerSet[dest_ip]);
- remove m from ServerSet[dest_ip];
- }
- }
- ServerSet[dest_ip].lastuse = Now;
- if (ServerSet[dest_ip] changed)
- ServerSet[dest_ip].lastmod = Now;
- return n;
- 此外,对关联变量ServerSet[dest_ip]也要进行周期性的垃圾回收(Garbage Collection),将过期的目标IP地址到服务器关联项进行回收。
- 过期的关联项是指哪些当前时间(实现时采用系统时钟节拍数jiffies)减去最近使用时间(lastuse)超过设定过期时间的关联项,系统缺省的设定过期时间为24小时。
- 目标地址散列调度 :
- (Destination Hashing Scheduling)算法也是针对目标IP地址的负载均衡,但它是一种静态映射算法,通过一个散列(Hash)函数将一个目标IP地址映射到一台服务器。
- 目标地址散列调度算法先根据请求的目标IP地址,作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,
- 将请求发送到该服务器,否则返回空。该算法的流程如下:
- 目标地址散列调度算法流程
- 假设有一组服务器S = {S0, S1, ..., Sn-},W(Si)表示服务器Si的权值,C(Si)表示服务器Si的当前连接数。
- ServerNode[]是一个有256个桶(Bucket)的Hash表,一般来说服务器的数目会远小于256,当然表的大小也是可以调整的。
- 算法的初始化是将所有服务器顺序、循环地放置到ServerNode表中。若服务器的连接数目大于2倍的权值,则表示服务器已超载。
- n = ServerNode[hashkey(dest_ip)];
- ) || (C(n) > *W(n)))
- 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^ ()间接近于黄金分割的素数,
- (sqrt() - ) / = 0.618033989
- / = 0.618033987
- 源地址散列调度 :
- (Source Hashing Scheduling)算法正好与目标地址散列调度算法相反,它根据请求的源IP地址,
- 作为散列键(Hash Key)从静态分配的散列表找出对应的服务器,若该服务器是可用的且未超载,将请求发送到该服务器,否则返回空。
- 它采用的散列函数与目标地址散列调度算法的相同。它的算法流程与目标地址散列调度算法的基本相似,
- 除了将请求的目标IP地址换成请求的源IP地址,所以这里不一一叙述。
- 在实际应用中,源地址散列调度和目标地址散列调度可以结合使用在防火墙集群中,它们可以保证整个系统的唯一出入口。
- 下面我们看算法具体实现.
- 每个调度算法的实现就是填写一个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(),
- .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()等函数);
- static int ip_vs_rr_init_svc(struct ip_vs_service *svc)
- {
- //其实RR算法也没有什么专用调度数据,sched_data被初始化为目的服务器链表头
- svc->sched_data = &svc->destinations;
- ;
- }
- static int ip_vs_rr_done_svc(struct ip_vs_service *svc)
- {
- ; //没什么可释放的
- }
- static int ip_vs_rr_update_svc(struct ip_vs_service *svc)
- {
- svc->sched_data = &svc->destinations; //sched_data重新指向服务器链表头
- ;
- }
- static struct ip_vs_dest * ip_vs_rr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb)
- {
- struct list_head *p, *q;
- struct ip_vs_dest *dest;
- write_lock(&svc->sched_lock);
- //p实际就是实际目的服务器的链表中的某一个节点
- //sched_data保存的是上一次调度时用到的节点
- p = (struct list_head *)svc->sched_data;
- p = p->next;
- q = p;
- do {
- /* skip list head */
- if (q == &svc->destinations) {
- q = q->next;
- continue;
- }
- dest = list_entry(q, struct ip_vs_dest, n_list);
- //只要当前链表目的服务器不是超载而且该服务器权重不为0,就返回该节点
- )
- /* HIT */
- goto out;
- q = q->next;
- } while (q != p);
- write_unlock(&svc->sched_lock);
- return NULL;
- out:
- //保存要使用的节点到sched_data,下次调度时就会取下一个节点,实现轮询
- svc->sched_data = q;
- write_unlock(&svc->sched_lock);
- return dest;
- }
- rr模块初始花函数是
- static int __init ip_vs_rr_init(void)
- {
- INIT_LIST_HEAD(&ip_vs_rr_scheduler.n_list); //初始化连表
- //注册调度其中就是把调度结构连接到一个全局连表中
- //list_add(&scheduler->n_list, &ip_vs_schedulers);
- //查找时通过name字段,所以name字段不能为空
- return register_ip_vs_scheduler(&ip_vs_rr_scheduler);
- }
- [/IPVS调度算法]
- [协议实现]
- 我们假定是tcp协议
- struct ip_vs_protocol ip_vs_protocol_tcp = {
- .name = "TCP",
- .protocol = IPPROTO_TCP,
- .dont_defrag = ,
- .appcnt = ATOMIC_INIT(),
- .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,
- };
- 我们一个一个看实现.
- static void ip_vs_tcp_init(struct ip_vs_protocol *pp)
- {
- IP_VS_INIT_HASH_TABLE(tcp_apps);
- pp->timeout_table = tcp_timeouts;
- }
- IPVS定义的超时,和netfilter类似,不过比netfilter的超时少得多,而且这些值不是通过/proc调整,而是通过ipvsadm命令来调整
- ] = {
- [IP_VS_TCP_S_NONE] = *HZ,
- [IP_VS_TCP_S_ESTABLISHED] = **HZ,
- [IP_VS_TCP_S_SYN_SENT] = **HZ,
- [IP_VS_TCP_S_SYN_RECV] = **HZ,
- [IP_VS_TCP_S_FIN_WAIT] = **HZ,
- [IP_VS_TCP_S_TIME_WAIT] = **HZ,
- [IP_VS_TCP_S_CLOSE] = *HZ,
- [IP_VS_TCP_S_CLOSE_WAIT] = *HZ,
- [IP_VS_TCP_S_LAST_ACK] = *HZ,
- [IP_VS_TCP_S_LISTEN] = **HZ,
- [IP_VS_TCP_S_SYNACK] = *HZ,
- [IP_VS_TCP_S_LAST] = *HZ,
- };
- 是个啥也不作的空函数,只是让函数指针不为空
- static void ip_vs_tcp_exit(struct ip_vs_protocol *pp)
- {}
- 登记TCP应用(多连接)协议
- static int tcp_register_app(struct ip_vs_app *inc)
- {
- struct ip_vs_app *i;
- __u16 hash, port = inc->port;
- ;
- //根据端口计算一个HASH值
- hash = tcp_app_hashkey(port);
- spin_lock_bh(&tcp_app_lock);
- list_for_each_entry(i, &tcp_apps[hash], p_list) {
- if (i->port == port) {
- ret = -EEXIST;
- goto out;
- }
- }
- list_add(&inc->p_list, &tcp_apps[hash]);// 将新应用协议添加到HASH表中
- atomic_inc(&ip_vs_protocol_tcp.appcnt);// 增加应用协议计数器
- out:
- spin_unlock_bh(&tcp_app_lock);
- return ret;
- }
- static void tcp_unregister_app(struct ip_vs_app *inc)
- {
- spin_lock_bh(&tcp_app_lock);
- atomic_dec(&ip_vs_protocol_tcp.appcnt);
- list_del(&inc->p_list);
- spin_unlock_bh(&tcp_app_lock);
- }
- 连接调度的目的是找到一个合适的目的服务器,生成新连接。该函数在ip_vs_in()函数中调用
- static int tcp_conn_schedule(struct sk_buff *skb, struct ip_vs_protocol *pp, int *verdict, struct ip_vs_conn **cpp)
- {
- struct ip_vs_service *svc;
- struct tcphdr _tcph, *th;
- //取出tcp头
- th = skb_header_pointer(skb, skb->nh.iph->ihl*, sizeof(_tcph), &_tcph);
- if (th == NULL) {
- *verdict = NF_DROP;
- ;
- }
- //如果是syn包且有虚拟服务器
- if (th->syn && (svc = ip_vs_service_get(skb->nfmark, skb->nh.iph->protocol, skb->nh.iph->daddr, th->dest))) {
- if (ip_vs_todrop()) { //是否这虚拟服务器本身已经负载严重
- ip_vs_service_put(svc);
- *verdict = NF_DROP;
- ;
- }
- *cpp = ip_vs_schedule(svc, skb); //建立ipvs连接
- if (!*cpp) { //没有成功
- *verdict = ip_vs_leave(svc, skb, pp); //后续处理,更新统计包,发送icmp不可达数据包等.
- ;
- }
- ip_vs_service_put(svc);
- }
- ;
- }
- struct ip_vs_conn * ip_vs_schedule(struct ip_vs_service *svc, const struct sk_buff *skb)
- {
- struct ip_vs_conn *cp = NULL;
- struct iphdr *iph = skb->nh.iph;
- struct ip_vs_dest *dest;
- __u16 _ports[], *pptr;
- //TCP/UDP头指针,[0]为源端口,[1]为目的端口
- pptr = skb_header_pointer(skb, iph->ihl*, sizeof(_ports), _ports);
- if (pptr == NULL)
- return NULL;
- if (svc->flags & IP_VS_SVC_F_PERSISTENT) //处理持久服务器
- return ip_vs_sched_persist(svc, skb, pptr);
- ] != svc->port) {
- if (!svc->port)
- IP_VS_ERR("Schedule: port zero only supported in persistent services, check your ipvs configuration\n");
- return NULL;
- }
- //调用调度器的调度函数获取一个目的服务器指针,调度器的调度函数看上面IPVS调度算法.
- dest = svc->scheduler->schedule(svc, skb);
- if (dest == NULL) {
- IP_VS_DBG(, "Schedule: no dest found.\n");
- return NULL;
- }
- //新建一个IPVS连接
- cp = ip_vs_conn_new(iph->protocol, iph->saddr, pptr[], iph->daddr, pptr[], dest->addr, dest->port ? dest->port : pptr[], , dest);
- if (cp == NULL)
- return NULL;
- //更新服务和连接相关计数器统计
- ip_vs_conn_stats(cp, svc);
- return cp;
- }
- 持久调度函数,用在多连接协议处理中将子连接与主连接挂钩
- ])
- {
- struct ip_vs_conn *cp = NULL;
- struct iphdr *iph = skb->nh.iph;
- struct ip_vs_dest *dest;
- struct ip_vs_conn *ct;
- __u16 dport; /* destination port to forward */
- __u32 snet;
- //取出网络部分地址
- snet = iph->saddr & svc->netmask;
- //数据包目的端口是服务端口,正向请求子连接
- ] == svc->port) {
- //查找连接模板, 专门区分了是否是FTP端口,所以程序在此没有扩展性
- //源地址用的是网络部分地址,源端口用0
- //所谓模板,应该就是指主连接,相当于netfilter跟踪子连接时端口部分不进行限制
- //不过这里IPVS跟踪子连接作的没有netfilter好
- if (svc->port != FTPPORT)
- ct = ip_vs_ct_in_get(iph->protocol, snet, , iph->daddr, ports[]);
- else
- ct = ip_vs_ct_in_get(iph->protocol, snet, , iph->daddr, );
- if (!ct || !ip_vs_check_template(ct)) { //找不到连接模板或者连接模板的目的服务器不可用
- //使用调度器调度找一个服务器
- dest = svc->scheduler->schedule(svc, skb);
- if (dest == NULL) {
- IP_VS_DBG(, "p-schedule: no dest found.\n");
- return NULL;
- }
- //建立一个新连接模板,如果是FTP服务,目的端口不确定
- if (svc->port != FTPPORT)
- ct = ip_vs_conn_new(iph->protocol, snet, , iph->daddr, ports[], dest->addr, dest->port,
- IP_VS_CONN_F_TEMPLATE, dest);
- else
- ct = ip_vs_conn_new(iph->protocol, snet, , iph->daddr, , dest->addr, ,
- IP_VS_CONN_F_TEMPLATE, dest);
- } else { //找到连接模板,目的服务器用连接的目的服务器
- dest = ct->dest;
- }
- dport = dest->port; //目的端口为目的服务器端口
- } else { //数据包目的端口不是服务端口,可能是反向请求的子连接
- if (svc->fwmark)//找相关连接模板,此时用的端口都是0
- ct = ip_vs_ct_in_get(IPPROTO_IP, snet, , htonl(svc->fwmark), );
- else
- ct = ip_vs_ct_in_get(iph->protocol, snet, , iph->daddr, );
- if (!ct || !ip_vs_check_template(ct)) {
- if (svc->port) return NULL; dest = svc->scheduler->schedule(svc, skb);
- if (dest == NULL) {
- IP_VS_DBG(, "p-schedule: no dest found.\n");
- return NULL;
- }
- if (svc->fwmark)
- ct = ip_vs_conn_new(IPPROTO_IP, snet, , htonl(svc->fwmark), , dest->addr, ,
- IP_VS_CONN_F_TEMPLATE, dest);
- else
- ct = ip_vs_conn_new(iph->protocol, snet, , iph->daddr, , dest->addr, ,
- IP_VS_CONN_F_TEMPLATE, dest);
- if (ct == NULL)
- return NULL;
- ct->timeout = svc->timeout;
- } else { //找到连接模板,目的服务器用连接的目的服务器
- dest = ct->dest;
- }
- dport = ports[];
- }
- //建立一个新连接
- cp = ip_vs_conn_new(iph->protocol, iph->saddr, ports[], iph->daddr, ports[], dest->addr, dport, , dest);
- if (cp == NULL) {
- ip_vs_conn_put(ct);
- return NULL;
- }
- //将连接模板作为新连接的主连接
- ip_vs_control_add(cp, ct);
- //get的时候增加了连接模板ct的引用计数,现在减少
- ip_vs_conn_put(ct);
- //更新连接服务计数器统计
- ip_vs_conn_stats(cp, svc);
- return cp;
- }
- 获取连接模板的函数,没有s_port为0的特殊处理,在查找固定连接和模板连接时使用
- struct ip_vs_conn *ip_vs_ct_in_get (int protocol, __u32 s_addr, __u16 s_port, __u32 d_addr, __u16 d_port)
- {
- unsigned hash;
- struct ip_vs_conn *cp;
- //计算HASH值是用源的三元组来计算:IP协议、源地址、源端口
- hash = ip_vs_conn_hashkey(protocol, s_addr, s_port);
- ct_read_lock(hash);
- list_for_each_entry(cp, &ip_vs_conn_tab[hash], c_list) {
- if (s_addr==cp->caddr && s_port==cp->cport && d_port==cp->vport && d_addr==cp->vaddr &&
- cp->flags & IP_VS_CONN_F_TEMPLATE && protocol==cp->protocol) {
- /* HIT */
- atomic_inc(&cp->refcnt);
- goto out; }
- }
- cp = NULL;
- out:
- ct_read_unlock(hash);
- return cp;
- }
- 建立一个新连接
- 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)
- {
- struct ip_vs_conn *cp;
- struct ip_vs_protocol *pp = ip_vs_proto_get(proto);
- //从cache中分配连接
- cp = kmem_cache_alloc(ip_vs_conn_cachep, GFP_ATOMIC);
- if (cp == NULL) {
- IP_VS_ERR_RL("ip_vs_conn_new: no memory available.\n");
- return NULL;
- }
- memset(cp, , sizeof(*cp));
- INIT_LIST_HEAD(&cp->c_list);
- init_timer(&cp->timer);
- cp->timer.data = (unsigned long)cp;
- cp->timer.function = ip_vs_conn_expire; //连接超时函数
- cp->protocol = proto;
- cp->caddr = caddr;
- cp->cport = cport;
- cp->vaddr = vaddr;
- cp->vport = vport;
- cp->daddr = daddr;
- cp->dport = dport;
- cp->flags = flags;
- spin_lock_init(&cp->lock);
- atomic_set(&cp->refcnt, );// 引用初始值为1
- atomic_set(&cp->n_control, );// 子连接数置0
- atomic_set(&cp->in_pkts, );
- atomic_inc(&ip_vs_conn_count);
- if (flags & IP_VS_CONN_F_NO_CPORT)
- atomic_inc(&ip_vs_conn_no_cport_cnt);
- ip_vs_bind_dest(cp, dest); // 将连接和目的服务器进行绑定
- cp->state = ; //连接初始状态为0
- cp->timeout = *HZ; // 缺省超时为3秒
- ip_vs_bind_xmit(cp);// 绑定连接的数据包的发送方法
- //绑定协议应用,其实目前只有TCP的FTP一种,所以用了unlikely
- if (unlikely(pp && atomic_read(&pp->appcnt)))
- //调用协议的app_conn_bind成员函数,对TCP协议来说就是tcp_app_conn_bind()函数
- //只在NAT模式下有效
- //检查该端口是否属于某多连接应用协议,是的话连接上绑定该协议处理, 相当于netfilter的连接的helper
- ip_vs_bind_app(cp, pp);//实现 return pp->app_conn_bind(cp);
- //将该连接节点加入到IPVS连接表中
- ip_vs_conn_hash(cp);
- return cp;
- }
- 绑定连接目的服务器
- static inline void ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
- {
- if (!dest)
- return;
- atomic_inc(&dest->refcnt);
- //根据服务器情况设置连接标志,主要是用来确定连接数据包的发送方法
- cp->flags |= atomic_read(&dest->conn_flags);
- cp->dest = dest;
- if (!(cp->flags & IP_VS_CONN_F_TEMPLATE)) {
- /* It is a normal connection, so increase the inactive connection counter because it is in TCP SYNRECV
- state (inactive) or other protocol inacive state */
- atomic_inc(&dest->inactconns); // 增加目的服务器的不活动连接计数,目前还属于不活动连接
- } else {
- /* It is a persistent connection/template, so increase the peristent connection counter */
- atomic_inc(&dest->persistconns); // 如果是永久连接或模板,增加目的服务器的永久连接计数 }
- //检查目的服务器的连接数是否超载了
- && ip_vs_dest_totalconns(dest) >= dest->u_threshold)
- dest->flags |= IP_VS_DEST_F_OVERLOAD;
- }
- 绑定发送方法,参看下面发送方法实现
- static inline void ip_vs_bind_xmit(struct ip_vs_conn *cp)
- {
- //#define IP_VS_FWD_METHOD(cp) (cp->flags & IP_VS_CONN_F_FWD_MASK)
- switch (IP_VS_FWD_METHOD(cp)) {
- case IP_VS_CONN_F_MASQ:
- cp->packet_xmit = ip_vs_nat_xmit; // NAT发送
- break;
- case IP_VS_CONN_F_TUNNEL:
- cp->packet_xmit = ip_vs_tunnel_xmit;// TUNNEL发送
- break;
- case IP_VS_CONN_F_DROUTE:
- cp->packet_xmit = ip_vs_dr_xmit;// DR发送
- break;
- case IP_VS_CONN_F_LOCALNODE:
- cp->packet_xmit = ip_vs_null_xmit;// 本地包
- break;
- case IP_VS_CONN_F_BYPASS:
- cp->packet_xmit = ip_vs_bypass_xmit;// 旁路发送
- break;
- }
- }
- 要实现面向连接的处理的基本功能就是根据数据包内容查找连接,IPVS区分每个连接的关键数据和netfilter一样是五元组,
- 为IP协议、源地址、源端口、目的地址和目的端口,不过没定义方向的概念,所以在IPVS中请求方向和回应方向要用不同的查找函数处理,
- 由于IPVS是在INPUT点处理请求, 在FORWARD点处理回应包,不会在同一个点同时处理请求包和回应包,因此可以没有方向的概念。
- 在ip_vs_out()函数中正向调用,在ip_vs_out_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)
- {
- __u16 _ports[], *pptr;
- //TCP/UDP头指针,[0]为源端口,[1]为目的端口
- pptr = skb_header_pointer(skb, proto_off, sizeof(_ports), _ports);
- if (pptr == NULL)
- return NULL;
- if (likely(!inverse)) { //正向还是反向,在绝大多数情况下是按正向查找连接
- ], iph->daddr, pptr[]);
- } else {
- ], iph->saddr, pptr[]);
- }
- }
- struct ip_vs_conn *ip_vs_conn_in_get(int protocol, __u32 s_addr, __u16 s_port, __u32 d_addr, __u16 d_port)
- {
- struct ip_vs_conn *cp;
- cp = __ip_vs_conn_in_get(protocol, s_addr, s_port, d_addr, d_port);
- //没有找到,源端口设为0,再次查找
- if (!cp && atomic_read(&ip_vs_conn_no_cport_cnt))
- cp = __ip_vs_conn_in_get(protocol, s_addr, , d_addr, d_port);
- return cp;
- }
- static inline struct ip_vs_conn *__ip_vs_conn_in_get(int protocol, __u32 s_addr, __u16 s_port, __u32 d_addr, __u16 d_port)
- {
- unsigned hash;
- struct ip_vs_conn *cp;
- //入(请求)方向计算HASH值是用源的三元组来计算:IP协议、源地址、源端口
- hash = ip_vs_conn_hashkey(protocol, s_addr, s_port);
- ct_read_lock(hash);
- list_for_each_entry(cp, &ip_vs_conn_tab[hash], c_list) {
- //caddr,cport是连接记录的客户端的地址和端口
- if (s_addr == cp->caddr && s_port == cp->cport && d_port == cp->vport && d_addr == cp->vaddr &&
- //连接中的客户端端口为0的情况
- ((!s_port) ^ (!(cp->flags & IP_VS_CONN_F_NO_CPORT))) && protocol == cp->protocol) {
- /* HIT */
- atomic_inc(&cp->refcnt); //增加连接引用
- ct_read_unlock(hash);
- return cp;
- }
- }
- ct_read_unlock(hash);
- return NULL;
- }
- 与in几乎一样
- 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)
- {
- __u16 _ports[], *pptr;
- pptr = skb_header_pointer(skb, proto_off, sizeof(_ports), _ports);
- if (pptr == NULL)
- return NULL;
- if (likely(!inverse)) {
- ], iph->daddr, pptr[]);
- } else {
- ], iph->saddr, pptr[]);
- }
- }
- struct ip_vs_conn *ip_vs_conn_out_get(int protocol, __u32 s_addr, __u16 s_port, __u32 d_addr, __u16 d_port)
- {
- unsigned hash;
- struct ip_vs_conn *cp, *ret=NULL;
- //出方向计算HASH值是用目的三元组来计算:IP协议、目的地址和目的端口,
- //这样计算结果和入方向的计算值是相同的
- hash = ip_vs_conn_hashkey(protocol, d_addr, d_port);
- ct_read_lock(hash);
- list_for_each_entry(cp, &ip_vs_conn_tab[hash], c_list) {
- if (d_addr == cp->caddr && d_port == cp->cport && s_port == cp->dport && s_addr == cp->daddr && protocol == cp->protocol) {
- /* HIT */
- atomic_inc(&cp->refcnt);
- ret = cp;
- break;
- }
- }
- ct_read_unlock(hash);
- return ret;
- }
- 该函数完成对协议部分数据进行源NAT操作,对TCP来说,NAT部分的数据就是源端口
- static int tcp_snat_handler(struct sk_buff **pskb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp)
- {
- struct tcphdr *tcph;
- unsigned ;
- //NAT操作skb必须是可写的
- if (!ip_vs_make_skb_writable(pskb, tcphoff+sizeof(*tcph)))
- ;
- if (unlikely(cp->app != NULL)) {// 如果是多连接协议,进行应用协议内容部分数据的修改 //目前只支持FTP协议,对FTP作NAT时,需要修改PORT命令或227回应内容中的地址端口信息
- if (pp->csum_check && !pp->csum_check(*pskb, pp))
- ;
- /* Call application helper if needed */
- if (!ip_vs_app_pkt_out(cp, pskb))
- ;
- }
- tcph = (void *)(*pskb)->nh.iph + tcphoff;
- tcph->source = cp->vport;// 修改当前TCP源端口
- if (!cp->app) {
- //如果只修改了源端口一个参数,就值需要用差值法快速计算新的TCP校验和
- tcp_fast_csum_update(tcph, cp->daddr, cp->vaddr, cp->dport, cp->vport);
- if ((*pskb)->ip_summed == CHECKSUM_HW)
- (*pskb)->ip_summed = CHECKSUM_NONE;
- } else {
- // 如果修改了协议内容部分数据,需要根据全部数据重新计算TCP校验和
- tcph->check = ;
- (*pskb)->csum = skb_checksum(*pskb, tcphoff, (*pskb)->len - tcphoff, );
- tcph->check = csum_tcpudp_magic(cp->vaddr, cp->caddr, (*pskb)->len - tcphoff, cp->protocol, (*pskb)->csum);
- }
- ;}
- 应用协议修改输出方向的应用层数据
- int ip_vs_app_pkt_out(struct ip_vs_conn *cp, struct sk_buff **pskb)
- {
- struct ip_vs_app *app;
- //检查连接是否和应用绑定
- if ((app = cp->app) == NULL)
- ;
- //TCP协议另外单独处理
- if (cp->protocol == IPPROTO_TCP)
- return app_tcp_pkt_out(cp, pskb, app);
- //非TCP协议调用应用协议的pkt_out()函数,我们只看tcp协议
- if (app->pkt_out == NULL)
- ;
- return app->pkt_out(app, cp, pskb, NULL);
- }
- 处理TCP应用发出方向的数据包
- static inline int app_tcp_pkt_out(struct ip_vs_conn *cp, struct sk_buff **pskb, struct ip_vs_app *app)
- {
- int diff;
- //计算偏移值
- unsigned ;
- struct tcphdr *th;
- __u32 seq;
- //首先要让数据包可写
- if (!ip_vs_make_skb_writable(pskb, tcp_offset + sizeof(*th)))
- ;
- th = (struct tcphdr *)((*pskb)->nh.raw + tcp_offset);
- seq = ntohl(th->seq);//当前的序列号
- if (cp->flags & IP_VS_CONN_F_OUT_SEQ)
- vs_fix_seq(&cp->out_seq, th); //修改发出方向序列号
- if (cp->flags & IP_VS_CONN_F_IN_SEQ) vs_fix_ack_seq(&cp->in_seq, th); //修改进入方向序列号
- ;
- //调用应用协议的pkt_out()函数,看下面处理多连接协议 if (!app->pkt_out(app, cp, pskb, &diff))
- ;
- )//数据长度发生变化,再次修改发出方向的序列号 vs_seq_update(cp, &cp->out_seq, IP_VS_CONN_F_OUT_SEQ, seq, diff);
- ;
- }
- 该函数完成对协议部分数据进行目的NAT操作,对TCP来说,NAT部分的数据就是目的端口
- static int tcp_dnat_handler(struct sk_buff **pskb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp)
- {
- struct tcphdr *tcph;
- unsigned ;
- /* csum_check requires unshared skb */
- if (!ip_vs_make_skb_writable(pskb, tcphoff+sizeof(*tcph)))
- ;
- if (unlikely(cp->app != NULL)) {// 如果是多连接协议,进行应用协议内容部分数据的修改
- // 目前只支持FTP协议,对FTP作NAT时,需要修改PORT命令或227回应内容中的地址端口信息
- if (pp->csum_check && !pp->csum_check(*pskb, pp))
- ;
- if (!ip_vs_app_pkt_in(cp, pskb))
- ;
- }
- tcph = (void *)(*pskb)->nh.iph + tcphoff;
- tcph->dest = cp->dport;// 修改当前TCP目的端口
- if (!cp->app) {
- /* Only port and addr are changed, do fast csum update */
- tcp_fast_csum_update(tcph, cp->vaddr, cp->daddr, cp->vport, cp->dport);
- if ((*pskb)->ip_summed == CHECKSUM_HW)
- (*pskb)->ip_summed = CHECKSUM_NONE;
- } else {
- /* full checksum calculation */
- tcph->check = ;
- (*pskb)->csum = skb_checksum(*pskb, tcphoff, (*pskb)->len - tcphoff, );
- tcph->check = csum_tcpudp_magic(cp->caddr, cp->daddr, (*pskb)->len - tcphoff, cp->protocol, (*pskb)->csum);
- (*pskb)->ip_summed = CHECKSUM_UNNECESSARY;
- }
- ;
- }
- int ip_vs_app_pkt_in(struct ip_vs_conn *cp, struct sk_buff **pskb)
- {
- struct ip_vs_app *app;
- //检查连接是否和应用绑定
- if ((app = cp->app) == NULL)
- ;
- if (cp->protocol == IPPROTO_TCP)//TCP协议另外单独处理
- return app_tcp_pkt_in(cp, pskb, app);
- if (app->pkt_in == NULL)
- ;
- return app->pkt_in(app, cp, pskb, NULL);
- }
- static inline int app_tcp_pkt_in(struct ip_vs_conn *cp, struct sk_buff **pskb, struct ip_vs_app *app)
- {
- int diff;
- unsigned ;
- struct tcphdr *th;
- __u32 seq;
- //首先要让数据包可写
- if (!ip_vs_make_skb_writable(pskb, tcp_offset + sizeof(*th)))
- ;
- th = (struct tcphdr *)((*pskb)->nh.raw + tcp_offset);
- seq = ntohl(th->seq);
- 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);
- ;
- if (!app->pkt_in(app, cp, pskb, &diff))//调用应用协议的pkt_in()函数,参看下面处理多连接协议
- ;
- )//数据长度发生变化,再次修改输入方向的序列号 vs_seq_update(cp, &cp->in_seq, IP_VS_CONN_F_IN_SEQ, seq, diff);
- ;
- }
- 计算IP协议中的校验和,对于TCP,UDP头中都有校验和参数,TCP中的校验和是必须的,而UDP的校验和可以不用计算。
- 该函数用的都是linux内核提供标准的校验和计算函数
- static int tcp_csum_check(struct sk_buff *skb, struct ip_vs_protocol *pp)
- {
- unsigned ;
- switch (skb->ip_summed) {
- case CHECKSUM_NONE:
- skb->csum = skb_checksum(skb, tcphoff, skb->len - tcphoff, );
- case CHECKSUM_HW:
- if (csum_tcpudp_magic(skb->nh.iph->saddr, skb->nh.iph->daddr, skb->len - tcphoff, skb->nh.iph->protocol, skb->csum)) {
- IP_VS_DBG_RL_PKT(, pp, skb, , "Failed checksum for");
- ;
- }
- break;
- default:
- /* CHECKSUM_UNNECESSARY */
- break;
- }
- ; //checksum correct
- }
- 该函数返回协议状态名称字符串
- static const char * tcp_state_name(int state)
- {
- if (state >= IP_VS_TCP_S_LAST)
- return "ERR!";
- return tcp_state_name_table[state] ? tcp_state_name_table[state] : "?";
- }
- ] = {
- [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!",
- };
- 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)
- {
- struct tcphdr _tcph, *th;
- th = skb_header_pointer(skb, skb->nh.iph->ihl*, sizeof(_tcph), &_tcph);
- if (th == NULL)
- ;
- spin_lock(&cp->lock);
- set_tcp_state(pp, cp, direction, th);// 重新设置连接状态
- spin_unlock(&cp->lock);
- ;
- }
- static inline void set_tcp_state(struct ip_vs_protocol *pp, struct ip_vs_conn *cp, int direction, struct tcphdr *th)
- {
- int state_idx;
- int new_state = IP_VS_TCP_S_CLOSE;// 缺省新状态,连接关闭
- int state_off = tcp_state_off[direction];// 各方向的状态偏移值,确定是用转换表中的哪个数组
- if (cp->flags & IP_VS_CONN_F_NOOUTPUT) {// 修正一下半连接时的控制参数
- if (state_off == TCP_DIR_OUTPUT)
- cp->flags &= ~IP_VS_CONN_F_NOOUTPUT;
- else
- state_off = TCP_DIR_INPUT_ONLY;
- }
- ) {// 根据TCP标志返回状态索引号
- , "tcp_state_idx=%d!!!\n", state_idx);
- goto tcp_state_out;
- }
- // 从状态转换表中查新状态
- new_state = tcp_state_table[state_off + state_idx].next_state[cp->state];
- tcp_state_out:
- if (new_state != cp->state) {//状态迁移了
- struct ip_vs_dest *dest = cp->dest;
- if (dest) {// 连接的目的服务器存在
- //如果连接是以前是活动的,新状态不是TCP连接建立好时, 将连接标志改为非活动连接,修改计数器
- if (!(cp->flags & IP_VS_CONN_F_INACTIVE) && (new_state != IP_VS_TCP_S_ESTABLISHED)) {
- atomic_dec(&dest->activeconns);
- atomic_inc(&dest->inactconns);
- cp->flags |= IP_VS_CONN_F_INACTIVE;
- //如果连接以前是不活动的,新状态是TCP连接建立好时, 将连接标志改为活动连接,修改计数器
- } else if ((cp->flags & IP_VS_CONN_F_INACTIVE) && (new_state == IP_VS_TCP_S_ESTABLISHED)) {
- atomic_inc(&dest->activeconns);
- atomic_dec(&dest->inactconns);
- cp->flags &= ~IP_VS_CONN_F_INACTIVE;
- }
- }
- }
- cp->timeout = pp->timeout_table[cp->state = new_state];// 更新连接超时
- }
- static inline int tcp_state_idx(struct tcphdr *th)
- {
- if (th->rst)
- ;
- if (th->syn)
- ;
- if (th->fin)
- ;
- if (th->ack)
- ;
- ;
- }
- #define TCP_DIR_INPUT 0
- #define TCP_DIR_OUTPUT 4
- #define TCP_DIR_INPUT_ONLY 8
- static const int tcp_state_off[IP_VS_DIR_LAST] = {
- [IP_VS_DIR_INPUT] = TCP_DIR_INPUT,
- [IP_VS_DIR_OUTPUT] = TCP_DIR_OUTPUT,
- [IP_VS_DIR_INPUT_ONLY] = TCP_DIR_INPUT_ONLY,
- };
- 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。
- 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 }},
- };
- 本函数实现将多连接应用协议处理模块和IPVS连接进行绑定
- static int tcp_app_conn_bind(struct ip_vs_conn *cp)
- {
- int hash;
- struct ip_vs_app *inc;
- ;
- // 只在NAT模式下处理
- if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
- ;
- // 计算一下目的端口的HASH hash = tcp_app_hashkey(cp->vport);
- spin_lock(&tcp_app_lock);
- list_for_each_entry(inc, &tcp_apps[hash], p_list) {
- if (inc->port == cp->vport) {// 根据端口找到相应的应用模块
- if (unlikely(!ip_vs_app_inc_get(inc)))// 增加模块引用计数
- break;
- spin_unlock(&tcp_app_lock);
- cp->app = inc;// 将连接的应用模块指针指向改应用模块
- if (inc->init_conn)
- result = inc->init_conn(inc, cp);//初始化应用模块
- goto out;
- }
- }
- spin_unlock(&tcp_app_lock);
- out:
- return result;
- }
- timeout_change函数用来变化协议连接的超时,具体就是TCP有两个超时表,用哪个表由本函数决定:
- flags参数是由ipvsadm配置时传递来的。
- static void tcp_timeout_change(struct ip_vs_protocol *pp, int flags)
- {
- ); /* secure_tcp */
- /*
- ** FIXME: change secure_tcp to independent sysctl var
- ** or make it per-service or per-app because it is valid
- ** for most if not for all of the applications. Something
- ** like "capabilities" (flags) for each object.
- */
- tcp_state_table = (on ? tcp_states_dos : tcp_states);
- }
- 该函数在ipvsadm设置相关命令时调用
- static int tcp_set_state_timeout(struct ip_vs_protocol *pp, char *sname, int to)
- {
- return ip_vs_set_state_timeout(pp->timeout_table, IP_VS_TCP_S_LAST, tcp_state_name_table, sname, to);
- }
- int ip_vs_set_state_timeout(int *table, int num, char **names, char *name, int to)
- {
- int i;
- if (!table || !name || !to)
- return -EINVAL;
- //根据状态名称查找在状态在超时表中的位置然后修改超时时间,超时参数to单位为秒
- ; i < num; i++) {
- if (strcmp(names[i], name))
- continue;
- table[i] = to * HZ;
- ;
- }
- return -ENOENT;
- }
- [/协议实现]
- [发送方法实现]
- NAT发送只发送请求方向的数据,因此是进行目的NAT
- int ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp)
- {
- struct rtable *rt; /* Route to the other host */
- int mtu;
- struct iphdr *iph = skb->nh.iph;
- //如果连接标志了客户端端口为0,将当前skb中的端口填给连接
- if (unlikely(cp->flags & IP_VS_CONN_F_NO_CPORT)) {
- __u16 _pt, *p;
- p = skb_header_pointer(skb, iph->ihl*, sizeof(_pt), &_pt);
- if (p == NULL)
- goto tx_error;
- ip_vs_conn_fill_cport(cp, *p);// *p是源端口
- IP_VS_DBG(, "filled cport=%d\n", ntohs(*p));
- }
- //查找路由,找不到的话发ICMP出错包
- if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos))))
- goto tx_error_icmp;
- //检查路由发出网卡的MTU,如果包长超过MTU又有DF标志,发送ICMP错误信息,而不进行分片操作
- mtu = dst_mtu(&rt->u.dst);
- if ((skb->len > mtu) && (iph->frag_off&__constant_htons(IP_DF))) {
- ip_rt_put(rt);
- icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
- IP_VS_DBG_RL_PKT(, pp, skb, , "ip_vs_nat_xmit(): frag needed for");
- goto tx_error;
- }
- //让skb包的IP头部分是可写的
- if (!ip_vs_make_skb_writable(&skb, sizeof(struct iphdr)))
- goto tx_error_put;
- //扩充skb头部空间以容纳硬件MAC头数据 if (skb_cow(skb, rt->u.dst.dev->hard_header_len))
- goto tx_error_put;
- //释放skb当前的路由cache dst_release(skb->dst);
- skb->dst = &rt->u.dst;
- //对上层协议(TCP/UDP...)进行目的NAT,因为要发送给实际的目的服务器,看上面协议实现
- if (pp->dnat_handler && !pp->dnat_handler(&skb, pp, cp))
- goto tx_error;
- //修改目的地址为真实目的服务器地址 skb->nh.iph->daddr = cp->daddr;
- ip_send_check(skb->nh.iph);//计算IP头校验和
- /* Another hack: avoid icmp_send in ip_fragment */
- skb->local_df = ;
- //发送数据包,实际还是HOOK住netfilter的OUTPUT点,受OUTPUT规则限制
- IP_VS_XMIT(skb, rt);
- return NF_STOLEN;
- tx_error_icmp:
- dst_link_failure(skb);
- tx_error:
- kfree_skb(skb);
- return NF_STOLEN;
- tx_error_put:
- ip_rt_put(rt);
- goto tx_error;
- }
- TUNNEL发送是把原来的IP部分再加在一个IPIP协议头后发出去,新头的目的IP是真实目的服务器,源IP是真实客户端IP,
- 该包是可以路由的,服务器的回应包将直接路由回去而不经过IPVS.
- int ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp)
- {
- struct rtable *rt; /* Route to the other host */
- struct net_device *tdev; /* Device to other host */
- struct iphdr *old_iph = skb->nh.iph;
- u8 tos = old_iph->tos;
- __be16 df = old_iph->frag_off;
- struct iphdr *iph; /* Our new IP header */
- int max_headroom; /* The extra header space needed */
- int mtu;
- //只包装IP包,其他协议如ARP,IPX等不管
- if (skb->protocol != __constant_htons(ETH_P_IP)) {
- IP_VS_DBG_RL("ip_vs_tunnel_xmit(): protocol error, ETH_P_IP: %d, skb protocol: %d\n", __constant_htons(ETH_P_IP), skb->protocol);
- goto tx_error;
- }
- //根据连接信息找外出的路由cache
- if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(tos))))
- goto tx_error_icmp;
- //数据包发出网卡
- tdev = rt->u.dst.dev;
- //检查路径的MTU
- mtu = dst_mtu(&rt->u.dst) - sizeof(struct iphdr);
- ) {
- ip_rt_put(rt);
- IP_VS_DBG_RL("ip_vs_tunnel_xmit(): mtu less than 68\n");
- goto tx_error;
- }
- //更新路由的MTU
- if (skb->dst)
- skb->dst->ops->update_pmtu(skb->dst, mtu);
- df |= (old_iph->frag_off & __constant_htons(IP_DF));
- //检查don't fragement标志
- if ((old_iph->frag_off & __constant_htons(IP_DF)) && mtu < ntohs(old_iph->tot_len)) {
- icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
- ip_rt_put(rt);
- IP_VS_DBG_RL("ip_vs_tunnel_xmit(): frag needed\n");
- goto tx_error;
- }
- //#define LL_RESERVED_SPACE(dev) (((dev)->hard_header_len&~(HH_DATA_MOD - 1)) + HH_DATA_MOD)
- //计算需要添加的IP头的最大长度
- max_headroom = LL_RESERVED_SPACE(tdev) + sizeof(struct iphdr);
- if (skb_headroom(skb) < max_headroom || skb_cloned(skb) || skb_shared(skb)) {
- //重新分配一个skb包,该skb头部足够大可容纳外部IP头空间
- //分配失败则不发送该包了
- struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
- if (!new_skb) {
- ip_rt_put(rt);
- kfree_skb(skb);
- IP_VS_ERR_RL("ip_vs_tunnel_xmit(): no memory\n");
- return NF_STOLEN;
- }
- kfree_skb(skb);//将原来的skb释放掉
- skb = new_skb;//将skb指向新包
- old_iph = skb->nh.iph;//更新ip头指针
- }
- //skb->h是传输层头,现在要新加个IP头,原来的IP头就升级为传输层头
- skb->h.raw = (void *) old_iph;
- //计算老IP头的校验和
- ip_send_check(old_iph);
- //skb的data指针前移出IP头长度作为新IP头的起点
- skb->nh.raw = skb_push(skb, sizeof(struct iphdr));
- memset(&(IPCB(skb)->opt), , sizeof(IPCB(skb)->opt));
- //更新路由cache
- dst_release(skb->dst);
- skb->dst = &rt->u.dst;
- //填写新IP头部信息
- iph = skb->nh.iph;
- iph->version = ;
- iph->ihl = ;
- iph->frag_off = df;
- iph->protocol = IPPROTO_IPIP;//协议设置为IPIP, 值为4
- iph->tos = tos;
- iph->daddr = rt->rt_dst;
- iph->saddr = rt->rt_src;
- iph->ttl = old_iph->ttl;
- iph->tot_len = htons(skb->len);
- ip_select_ident(iph, &rt->u.dst, NULL); //设置IP头中的ID值
- ip_send_check(iph);//计算IP头校验和
- /* Another hack: avoid icmp_send in ip_fragment */
- skb->local_df = ;
- //发送新的skb包
- IP_VS_XMIT(skb, rt);
- return NF_STOLEN;
- tx_error_icmp:
- dst_link_failure(skb);
- tx_error:
- kfree_skb(skb);
- return NF_STOLEN;
- }
- 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)
- {
- struct rtable *rt; /* Route to the other host */
- struct iphdr *iph = skb->nh.iph;
- int mtu;
- //根据连接指定的目的服务器找路由
- if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos))))
- goto tx_error_icmp;
- //检查MTU
- mtu = dst_mtu(&rt->u.dst);
- if ((iph->frag_off&__constant_htons(IP_DF)) && skb->len > mtu) {
- icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
- ip_rt_put(rt);
- IP_VS_DBG_RL("ip_vs_dr_xmit(): frag needed\n");
- goto tx_error;
- }
- //防止skb包是共用的,还被其他地方使用
- if (unlikely((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)) {
- ip_rt_put(rt);
- return NF_STOLEN;
- }
- ip_send_check(skb->nh.iph);//重新计算IP头校验和
- dst_release(skb->dst);//释放原来的路由
- skb->dst = &rt->u.dst;
- skb->local_df = ;
- IP_VS_XMIT(skb, rt);//直接发出了
- return NF_STOLEN;
- tx_error_icmp:
- dst_link_failure(skb);
- tx_error:
- kfree_skb(skb);
- return NF_STOLEN;
- }
- 什么都没作
- 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;
- }
- 旁路模式,实际数据包不是给IPVS均衡器自己的,由IPVS进行转发
- int ip_vs_bypass_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp)
- {
- struct rtable *rt; /* Route to the other host */
- struct iphdr *iph = skb->nh.iph;
- u8 tos = iph->tos;
- int mtu;
- struct flowi fl = {//用当前IP包的目的地址作为查路由的key
- .oif = ,
- .nl_u = {
- .ip4_u = {
- .daddr = iph->daddr,
- .saddr = ,
- .tos = RT_TOS(tos), } },
- };
- //查找当前数据包的目的IP地址对应的路由,而不是IPVS连接的信息找路由
- if (ip_route_output_key(&rt, &fl)) {
- IP_VS_DBG_RL("ip_vs_bypass_xmit(): ip_route_output error, dest: %u.%u.%u.%u\n", NIPQUAD(iph->daddr));
- goto tx_error_icmp;
- }
- //MTU检查
- mtu = dst_mtu(&rt->u.dst);
- if ((skb->len > mtu) && (iph->frag_off & __constant_htons(IP_DF))) {
- ip_rt_put(rt);
- icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
- IP_VS_DBG_RL("ip_vs_bypass_xmit(): frag needed\n");
- goto tx_error;
- }
- //防止skb包是共用的,还被其他地方使用
- if (unlikely((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)) {
- ip_rt_put(rt);
- return NF_STOLEN;
- }
- //计算IP头校验和
- ip_send_check(skb->nh.iph);
- //释放老路由,更新路由
- dst_release(skb->dst);
- skb->dst = &rt->u.dst;
- /* Another hack: avoid icmp_send in ip_fragment */
- skb->local_df = ;
- IP_VS_XMIT(skb, rt);//发送
- return NF_STOLEN;
- tx_error_icmp:
- dst_link_failure(skb);
- tx_error:
- kfree_skb(skb);
- return NF_STOLEN;
- }
- [/发送方法实现]
- [处理多连接协议]
- 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.
- 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,
- };
- //初始化和释放什么也没作
- static int ip_vs_ftp_init_conn(struct ip_vs_app *app, struct ip_vs_conn *cp)
- {
- ;
- }
- static int ip_vs_ftp_done_conn(struct ip_vs_app *app, struct ip_vs_conn *cp)
- {
- ;
- }
- 发出方向的数据是FTP服务器发出的, 和子连接相关的回应为227类型,建立一个被动模式的子连接
- static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp, struct sk_buff **pskb, int *diff)
- {
- struct iphdr *iph;
- struct tcphdr *th;
- char *data, *data_limit;
- char *start, *end;
- __u32 from;
- __u16 port;
- struct ip_vs_conn *n_cp;
- ]; /* xxx.xxx.xxx.xxx,ppp,ppp\000 */
- unsigned buf_len;
- int ret;
- *diff = ;
- //发子连接信息数据时主连接必然是TCP连接建立好状态,否则就出错
- if (cp->state != IP_VS_TCP_S_ESTABLISHED)
- ;
- //让数据包可写
- if (!ip_vs_make_skb_writable(pskb, (*pskb)->len))
- ;
- //子连接必须是被动模式的
- if (cp->app_data == &ip_vs_ftp_pasv) {
- //数据定位
- iph = (*pskb)->nh.iph;
- th = (]);
- data = ();
- data_limit = (*pskb)->tail;
- //#define SERVER_STRING "227 Entering Passive Mode ("
- //查找"227 "回应中的地址端口信息,from和port返回的是网路字节序
- , )
- ;
- //查找发出方向的连接
- );
- if (!n_cp) {//正常情况下是找不到的
- //新建子连接, 注意各地址端口参数的位置
- , cp->vaddr, port, from, port, IP_VS_CONN_F_NO_CPORT, cp->dest);
- if (!n_cp)
- ;
- //将子连接和主连接联系起来
- ip_vs_control_add(n_cp, cp);
- }
- //新地址端口用连接的虚拟地址和端口
- //需要修改数据包中的数据
- from = n_cp->vaddr;
- port = n_cp->vport;
- //修改后的地址端口信息
- sprintf(buf,, (port>>)&);
- buf_len = strlen(buf);
- //检查数据长度差异
- *diff = buf_len - (end-start);
- ) {//长度相同的话直接覆盖就行了
- memcpy(start, buf, buf_len);
- ret = ;
- } else {//修改数据
- ret = !ip_vs_skb_replace(*pskb, GFP_ATOMIC, start, end-start, buf, buf_len);
- }
- cp->app_data = NULL;
- //连接状态设为监听
- ip_vs_tcp_conn_listen(n_cp);
- ip_vs_conn_put(n_cp);
- return ret;
- }
- ;
- }
- 将skb包中某段数据更改为新的数据,是一个通用函数,可供应用协议修改协议数据的函数调用
- int ip_vs_skb_replace(struct sk_buff *skb, gfp_t pri, char *o_buf, int o_len, char *n_buf, int n_len)
- {
- struct iphdr *iph;
- int diff;
- int o_offset;
- int o_left;
- //新数据和老数据的长度差,这影响序列号和确认号
- diff = n_len - o_len;
- //老数据在数据包中的偏移
- o_offset = o_buf - (char *)skb->data;
- /* The length of left data after o_buf+o_len in the skb data */
- o_left = skb->len - (o_offset + o_len);//老数据左边的第一个数据
- ) {//新长度不大于老长度,把原来老数据右边的数据移过来
- memmove(o_buf + n_len, o_buf + o_len, o_left);
- memcpy(o_buf, n_buf, n_len);
- skb_trim(skb, skb->len + diff);//减少数据包的长度
- } else if (diff <= skb_tailroom(skb)) {
- //新长度大于老长度,但skb包后面的空闲区可以容纳下新数据
- //扩展数据包长
- skb_put(skb, diff);
- memmove(o_buf + n_len, o_buf + o_len, o_left);
- memcpy(o_buf, n_buf, n_len);
- } else {
- //新长度大于老长度,但skb包后面的空闲区也容纳不下新数据
- //需要重新扩展skb大小
- if (pskb_expand_head(skb, skb_headroom(skb), diff, pri))
- return -ENOMEM;
- skb_put(skb, diff);
- memmove(skb->data + o_offset + n_len, skb->data + o_offset + o_len, o_left);
- memcpy(skb->data + o_offset, n_buf, n_len);
- }
- iph = skb->nh.iph;
- iph->tot_len = htons(skb->len);
- ;
- }
- void ip_vs_tcp_conn_listen(struct ip_vs_conn *cp)
- {
- spin_lock(&cp->lock);
- cp->state = IP_VS_TCP_S_LISTEN;//连接状态为监听
- cp->timeout = ip_vs_protocol_tcp.timeout_table[IP_VS_TCP_S_LISTEN];//连接超时为监听状态的超时
- spin_unlock(&cp->lock);
- }
- 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)
- {
- unsigned ];
- ;
- if (data_limit - data < plen) {
- /* check if there is partial match */
- )
- ;
- else
- ;
- }
- //模式匹配,"PORT "或"227 "
- ) {
- ;
- }
- *start = data + plen;
- for (data = *start; *data != term; data++) {
- if (data == data_limit)
- ;
- }
- *end = data;
- memset(p, , sizeof(p));
- //解析出6个数值
- for (data = *start; data != *end; data++) {
- ') {
- p[i] = p[i]* + *data - ';
- } ) {
- i++;
- } else {
- /* unexpected character */
- ;
- }
- }
- )
- ;
- //前4个是地址
- *addr = (p[]<<) | (p[]<<) | (p[]<<) | p[];
- //后两个是端口
- *port = (p[]<<) | p[];
- ;
- }
- 进入方向的数据是FTP客户端发出的, 和子连接相关的命令为PORT命令,建立一个主动模式的子连接
- static int ip_vs_ftp_in(struct ip_vs_app *app, struct ip_vs_conn *cp, struct sk_buff **pskb, int *diff)
- {
- struct iphdr *iph;
- struct tcphdr *th;
- char *data, *data_start, *data_limit;
- char *start, *end;
- __u32 to;
- __u16 port;
- struct ip_vs_conn *n_cp;
- *diff = ;
- //发子连接信息数据时主连接必然是TCP连接建立好状态,否则就出错
- if (cp->state != IP_VS_TCP_S_ESTABLISHED)
- ;
- //让数据包可写
- if (!ip_vs_make_skb_writable(pskb, (*pskb)->len))
- ;
- //协议头指针定位
- iph = (*pskb)->nh.iph;
- th = (]);
- /* Since there may be OPTIONS in the TCP packet and the HLEN is
- the length of the header in 32-bit multiples, it is accurate
- to calculate data address by th+HLEN*4 */
- //数据定位
- data = data_start = ();
- data_limit = (*pskb)->tail;
- //防止数据越界
- ) {
- //PASV命令,表示要进入被动模式
- ) == ) {
- /* Passive mode on */
- cp->app_data = &ip_vs_ftp_pasv;
- ;
- }
- data++;
- }
- //#define CLIENT_STRING "PORT "
- // 查找FTP数据是否是PORT命令,提取出地址端口信息及其位置
- , )
- ;
- cp->app_data = NULL;
- //用找到的地址端口和服务器虚地址虚端口找连接
- n_cp = ip_vs_conn_in_get(iph->protocol, to, port, cp->vaddr, htons(ntohs(cp->vport)-));
- if (!n_cp) {//找不到连接,这是大部分的情况
- //新建连接作为子连接
- n_cp = ip_vs_conn_new(IPPROTO_TCP, to, port, cp->vaddr, htons(ntohs(cp->vport)-),
- cp->daddr, htons(ntohs(cp->dport)-), , cp->dest);
- if (!n_cp)
- ;
- //子连接和主连接相连
- //不需要修改数据内容
- ip_vs_control_add(n_cp, cp);
- }
- //将子连接状态设置为监听状态
- ip_vs_tcp_conn_listen(n_cp);
- ip_vs_conn_put(n_cp);
- ;
- }
- 初始化
- #define IP_VS_APP_MAX_PORTS 8
- , };
- static int __init ip_vs_ftp_init(void)
- {
- int i, ret;
- struct ip_vs_app *app = &ip_vs_ftp;
- //注册FTP应用模板
- ret = register_ip_vs_app(app); //实现list_add(&app->a_list, &ip_vs_app_list); ip_vs_app_list全局连表
- if (ret)
- return ret;
- //可从模块插入时,输入端口参数,指定在哪些端口上进行FTP应用绑定
- ; i<IP_VS_APP_MAX_PORTS; i++) {
- if (!ports[i])
- continue;
- || ports[i] > 0xffff) {
- IP_VS_WARNING("ip_vs_ftp: Ignoring invalid configuration port[%d] = %d\n", i, ports[i]);
- continue;
- }
- //新建应用实例
- ret = register_ip_vs_app_inc(app, app->protocol, ports[i]); //实现result = ip_vs_app_inc_new(app, proto, port);
- if (ret)
- break;
- IP_VS_INFO("%s: loaded support on port[%d] = %d\n", app->name, i, ports[i]);
- }
- if (ret)
- unregister_ip_vs_app(app);
- return ret;
- }
- //新建一个应用实例,注意输入参数除了协议端口外,还需要提供一个应用模板的指针
- //而且函数并不直接返回应用结构本身,而是在函数中新建的应用实例直接挂接到链表中
- //只返回建立成功(0)或失败(<0)
- static int ip_vs_app_inc_new(struct ip_vs_app *app, __u16 proto, __u16 port)
- {
- struct ip_vs_protocol *pp;
- struct ip_vs_app *inc;
- int ret;
- //查找IPVS协议结构
- if (!(pp = ip_vs_proto_get(proto)))
- return -EPROTONOSUPPORT;
- //协议中不能只有应用登记函数而没有拆除函数
- if (!pp->unregister_app)
- return -EOPNOTSUPP;
- //分配应用结构内存
- inc = kmalloc(sizeof(struct ip_vs_app), GFP_KERNEL);
- if (!inc)
- return -ENOMEM;
- //将应用模板中的内容全部拷贝到新应用结构中
- memcpy(inc, app, sizeof(*inc));
- INIT_LIST_HEAD(&inc->p_list);
- INIT_LIST_HEAD(&inc->incs_list);
- inc->app = app;//应用实例中指向模板本身的指针
- inc->port = htons(port);//应用协议的端口
- atomic_set(&inc->usecnt, );//实例的使用计数
- if (app->timeouts) { //建立应用协议状态超时数组
- inc->timeout_table = ip_vs_create_timeout_table(app->timeouts, app->timeouts_size);
- if (!inc->timeout_table) {
- ret = -ENOMEM;
- goto out;
- }
- }
- //将应用实例向IP协议结构登记
- ret = pp->register_app(inc);
- if (ret)
- goto out;
- //将应用实例添加到应用模板的实例链表
- list_add(&inc->a_list, &app->incs_list);
- IP_VS_DBG(, "%s application %s:%u registered\n", pp->name, inc->name, inc->port);
- ;
- out:
- kfree(inc->timeout_table);
- kfree(inc);
- return ret;
- }
- [/处理多连接协议]
- [控制接口实现]
- IPVS控制包括定义IPVS提供的虚拟服务参数和实际的目的服务器等各种参数。
- IPVS的控制信息是通过setsockopt系统调用传递到内核的,IPVS在用户层的管理工具是ipvsadm。
- 用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+,
- .set = do_ip_vs_set_ctl,
- .get_optmin = IP_VS_BASE_CTL,
- .get_optmax = IP_VS_SO_GET_MAX+,
- .get = do_ip_vs_get_ctl,
- };
- 写控制
- static int do_ip_vs_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len)
- {
- int ret;
- unsigned char arg[MAX_ARG_LEN];
- struct ip_vs_service_user *usvc;
- struct ip_vs_service *svc;
- struct ip_vs_dest_user *udest;
- //当前进程有网络管理权限
- if (!capable(CAP_NET_ADMIN))
- return -EPERM;
- //检查从用户空间传过来的数据长度是否正确
- if (len != set_arglen[SET_CMDID(cmd)]) {
- IP_VS_ERR("set_ctl: len %u != %u\n", len, set_arglen[SET_CMDID(cmd)]);
- return -EINVAL;
- }
- //将数据从用户空间拷贝到内核空间
- )
- return -EFAULT;
- ip_vs_use_count_inc();
- //可中断互斥锁
- if (mutex_lock_interruptible(&__ip_vs_mutex)) {
- ret = -ERESTARTSYS;
- goto out_dec;
- }
- if (cmd == IP_VS_SO_SET_FLUSH) {
- /* Flush the virtual service */
- ret = ip_vs_flush();//删除所有虚拟服务
- goto out_unlock;
- } else if (cmd == IP_VS_SO_SET_TIMEOUT) {
- /* Set timeout values for (tcp tcpfin udp) */
- ret = ip_vs_set_timeout((struct ip_vs_timeout_user *)arg);//设置超时时间
- goto out_unlock;
- } else if (cmd == IP_VS_SO_SET_STARTDAEMON) {
- struct ip_vs_daemon_user *dm = (struct ip_vs_daemon_user *)arg;
- ret = start_sync_thread(dm->state, dm->mcast_ifn, dm->syncid);//启动IPVS同步进程
- goto out_unlock;
- } else if (cmd == IP_VS_SO_SET_STOPDAEMON) {
- struct ip_vs_daemon_user *dm = (struct ip_vs_daemon_user *)arg;
- ret = stop_sync_thread(dm->state); //停止IPVS同步进程
- goto out_unlock;
- }
- usvc = (struct ip_vs_service_user *)arg;
- udest = ();
- if (cmd == IP_VS_SO_SET_ZERO) {
- /* if no service address is set, zero counters in all */
- if (!usvc->fwmark && !usvc->addr && !usvc->port) {
- ret = ip_vs_zero_all();//所有计数器清零
- goto out_unlock;
- }
- }
- //检查协议是否正确,必须是TCP或UDP
- if (usvc->protocol != IPPROTO_TCP && usvc->protocol != IPPROTO_UDP) {
- IP_VS_ERR("set_ctl: invalid protocol: %d %d.%d.%d.%d:%d %s\n", usvc->protocol, NIPQUAD(usvc->addr),
- ntohs(usvc->port), usvc->sched_name);
- ret = -EFAULT;
- goto out_unlock;
- }
- /* Lookup the exact service by <protocol, addr, port> or fwmark */
- )
- svc = __ip_vs_service_get(usvc->protocol, usvc->addr, usvc->port);
- else
- svc = __ip_vs_svc_fwm_get(usvc->fwmark);
- //不是添加命令的话,服务不能为空,或者协议不匹配
- if (cmd != IP_VS_SO_SET_ADD && (svc == NULL || svc->protocol != usvc->protocol)) {
- ret = -ESRCH;
- goto out_unlock;
- }
- switch (cmd) {
- case IP_VS_SO_SET_ADD:
- if (svc != NULL)
- ret = -EEXIST;
- else
- ret = ip_vs_add_service(usvc, &svc);//添加服务
- break;
- case IP_VS_SO_SET_EDIT:
- ret = ip_vs_edit_service(svc, usvc);//修改服务参数
- break;
- case IP_VS_SO_SET_DEL:
- ret = ip_vs_del_service(svc);//删除服务
- if (!ret)
- goto out_unlock;
- break;
- case IP_VS_SO_SET_ZERO:
- ret = ip_vs_zero_service(svc);//服务数据清零
- break;
- case IP_VS_SO_SET_ADDDEST:
- ret = ip_vs_add_dest(svc, udest);//添加目的服务器
- break;
- case IP_VS_SO_SET_EDITDEST:
- ret = ip_vs_edit_dest(svc, udest);//修改目的服务器参数
- break;
- case IP_VS_SO_SET_DELDEST:
- ret = ip_vs_del_dest(svc, udest);//删除目的服务器
- break;
- default:
- ret = -EINVAL;
- }
- if (svc)//减少服务的引用计数
- ip_vs_service_put(svc);
- out_unlock:
- mutex_unlock(&__ip_vs_mutex);
- out_dec:
- /* decrease the module use count */
- ip_vs_use_count_dec();
- return ret;
- }
- 读控制
- static int do_ip_vs_get_ctl(struct sock *sk, int cmd, void __user *user, int *len)
- {
- unsigned ];
- ;
- if (!capable(CAP_NET_ADMIN))
- return -EPERM;
- if (*len < get_arglen[GET_CMDID(cmd)]) {
- IP_VS_ERR("get_ctl: len %u < %u\n", *len, get_arglen[GET_CMDID(cmd)]);
- return -EINVAL;
- }
- )
- return -EFAULT;
- if (mutex_lock_interruptible(&__ip_vs_mutex))
- return -ERESTARTSYS;
- switch (cmd) {case IP_VS_SO_GET_VERSION: //读取IPVS版本信息
- {
- ];
- sprintf(buf, "IP Virtual Server version %d.%d.%d (size=%d)", NVERSION(IP_VS_VERSION_CODE), IP_VS_CONN_TAB_SIZE);
- ) != ) {
- ret = -EFAULT;
- goto out;
- }
- *len = strlen(buf)+;
- }
- break;
- case IP_VS_SO_GET_INFO://IPVS基本信息:版本号,连接HASH数,服务数
- {
- struct ip_vs_getinfo info;
- info.version = IP_VS_VERSION_CODE;
- info.size = IP_VS_CONN_TAB_SIZE;
- info.num_services = ip_vs_num_services;
- )
- ret = -EFAULT;
- }
- break;
- case IP_VS_SO_GET_SERVICES://获取IPVS服务表
- {
- struct ip_vs_get_services *get;
- int size;
- get = (struct ip_vs_get_services *)arg;
- size = sizeof(*get) + sizeof(struct ip_vs_service_entry) * get->num_services;
- if (*len != size) {
- IP_VS_ERR("length: %u != %u\n", *len, size);
- ret = -EINVAL;
- goto out;
- }
- ret = __ip_vs_get_service_entries(get, user);
- }
- break;
- case IP_VS_SO_GET_SERVICE://获取单个IPVS服务
- {
- struct ip_vs_service_entry *entry;
- struct ip_vs_service *svc;
- entry = (struct ip_vs_service_entry *)arg;
- if (entry->fwmark)
- svc = __ip_vs_svc_fwm_get(entry->fwmark);
- else
- svc = __ip_vs_service_get(entry->protocol, entry->addr, entry->port);
- if (svc) {
- ip_vs_copy_service(entry, svc);
- )
- ret = -EFAULT;
- ip_vs_service_put(svc);
- } else
- ret = -ESRCH;
- }
- break;
- case IP_VS_SO_GET_DESTS://获取目的服务器表
- {
- struct ip_vs_get_dests *get;
- int size;
- get = (struct ip_vs_get_dests *)arg;
- size = sizeof(*get) + sizeof(struct ip_vs_dest_entry) * get->num_dests;
- if (*len != size) {
- IP_VS_ERR("length: %u != %u\n", *len, size);
- ret = -EINVAL;
- goto out;
- }
- ret = __ip_vs_get_dest_entries(get, user);
- }
- break;
- case IP_VS_SO_GET_TIMEOUT://获取tcp状态超时时间
- {
- struct ip_vs_timeout_user t;
- __ip_vs_get_timeouts(&t);
- )
- ret = -EFAULT;
- }
- break;
- case IP_VS_SO_GET_DAEMON://获取同步进程信息:状态,同步通信网卡,同步ID
- {
- ];
- memset(&d, , sizeof(d));
- if (ip_vs_sync_state & IP_VS_STATE_MASTER) {
- d[].state = IP_VS_STATE_MASTER;
- strlcpy(d[].mcast_ifn, ip_vs_master_mcast_ifn, ].mcast_ifn));
- d[].syncid = ip_vs_master_syncid;
- }
- if (ip_vs_sync_state & IP_VS_STATE_BACKUP) {
- d[].state = IP_VS_STATE_BACKUP;
- strlcpy(d[].mcast_ifn, ip_vs_backup_mcast_ifn, ].mcast_ifn));
- d[].syncid = ip_vs_backup_syncid;
- }
- )
- ret = -EFAULT;
- }
- break;
- default:
- ret = -EINVAL;
- }
- out:
- mutex_unlock(&__ip_vs_mutex);
- return ret;
- }
- 添加服务
- static int ip_vs_add_service(struct ip_vs_service_user *u, struct ip_vs_service **svc_p)
- {
- ;
- struct ip_vs_scheduler *sched = NULL;
- struct ip_vs_service *svc = NULL;
- /* increase the module use count */
- ip_vs_use_count_inc();
- //根据名称查找调度算法结构
- sched = ip_vs_scheduler_get(u->sched_name);
- if (sched == NULL) {
- IP_VS_INFO("Scheduler module ip_vs_%s not found\n", u->sched_name);
- ret = -ENOENT;
- goto out_mod_dec;
- }
- svc = kzalloc(sizeof(struct ip_vs_service), GFP_ATOMIC);
- if (svc == NULL) {
- IP_VS_DBG(, "ip_vs_add_service: kmalloc failed.\n");
- ret = -ENOMEM;
- goto out_err;
- }
- //初始值: 使用数1, 引用数0
- atomic_set(&svc->usecnt, );
- atomic_set(&svc->refcnt, );
- //根据用户空间传递过来的参数初始化服务参数
- svc->protocol = u->protocol;
- svc->addr = u->addr;
- svc->port = u->port;
- svc->fwmark = u->fwmark;
- svc->flags = u->flags;
- svc->timeout = u->timeout * HZ;
- svc->netmask = u->netmask;
- //初始化服务结构中的内核相关参数
- //目的服务器链表初始化
- INIT_LIST_HEAD(&svc->destinations);
- rwlock_init(&svc->sched_lock);
- spin_lock_init(&svc->stats.lock);
- //将服务和调度算法绑定
- ret = ip_vs_bind_scheduler(svc, sched);
- if (ret)
- goto out_err;
- sched = NULL;
- //端口是FTP或0时增加相关计数器,这里应该象netfilter那样建立一个服务helper,
- //根据端口自动查找对应的helper,统一处理,就不用在程序中明确指定特殊服务端口号了
- if (svc->port == FTPPORT)
- atomic_inc(&ip_vs_ftpsvc_counter);
- )
- atomic_inc(&ip_vs_nullsvc_counter);
- //建立服务状态预估器,用于根据服务的连接数,包数等调整均衡策略,看下面IPVS预估器
- ip_vs_new_estimator(&svc->stats);
- ip_vs_num_services++;//IPVS服务数增加
- //将服务挂接到服务HASH表中
- write_lock_bh(&__ip_vs_svc_lock);
- ip_vs_svc_hash(svc);
- write_unlock_bh(&__ip_vs_svc_lock);
- *svc_p = svc;//返回的新服务指针
- ;
- ......
- }
- 目的服务器是实际提供对外服务的服务器,由结构struct ip_vs_dest描述,该结构的具体对象可以很多,每个结构描述一个具体的服务器,
- 目的服务器参数是通过ipvsadm命令添加的,命令为:
- ipvsadm -a -t/u v_srv_ip:vport -r dest_ip:dest_port -w weight
- 添加服务器
- static int ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user *udest)
- {
- struct ip_vs_dest *dest;
- __u32 daddr = udest->addr;
- __u16 dport = udest->port;
- int ret;
- //每个服务器权重值不能小于0
- ) {
- IP_VS_ERR("ip_vs_add_dest(): server weight less than zero\n");
- return -ERANGE;
- }
- //阈值上限要不能小于下限
- if (udest->l_threshold > udest->u_threshold) {
- IP_VS_ERR("ip_vs_add_dest(): lower threshold is higher than upper threshold\n");
- return -ERANGE;
- }
- //根据目的地址和目的端口查找服务器是否已经存在
- dest = ip_vs_lookup_dest(svc, daddr, dport);
- if (dest != NULL) {
- IP_VS_DBG(, "ip_vs_add_dest(): dest already exists\n");
- return -EEXIST;
- }
- //在准备释放的垃圾内存中检查是否有目的服务器存在
- dest = ip_vs_trash_get_dest(svc, daddr, dport);
- if (dest != NULL) {
- //找到目的服务器,恢复目的服务器的使用
- //更新目的服务器
- __ip_vs_update_dest(svc, dest, udest);
- //从垃圾链表中拆除
- list_del(&dest->n_list);
- //新建目的服务器的预估器
- ip_vs_new_estimator(&dest->stats);
- write_lock_bh(&__ip_vs_svc_lock);
- IP_VS_WAIT_WHILE(atomic_read(&svc->usecnt) > );
- //添加到服务的目的服务器链表中
- list_add(&dest->n_list, &svc->destinations);
- svc->num_dests++;
- /* call the update_service function of its scheduler */
- svc->scheduler->update_service(svc);
- write_unlock_bh(&__ip_vs_svc_lock);
- ;
- }
- //目的服务器不存在,重新分配内存建立一个
- ret = ip_vs_new_dest(svc, udest, &dest);
- if (ret) {
- return ret;
- }
- atomic_inc(&dest->refcnt);//增加目的服务器引用计数
- write_lock_bh(&__ip_vs_svc_lock);
- IP_VS_WAIT_WHILE(atomic_read(&svc->usecnt) > );
- //将目的服务器添加到服务的服务器链表
- list_add(&dest->n_list, &svc->destinations);
- svc->num_dests++;
- //更新调度算法的调度处理
- svc->scheduler->update_service(svc);
- write_unlock_bh(&__ip_vs_svc_lock);
- ;
- }
- [/控制接口实现]
- [IPVS的同步]
- IPVS支持对连接的同步,两台IPVS设备可分别以MASTER或BACKUP运行,MASTER进程可将连接信息备份到BACKUP设备上,这样主设备死机时从设备可以无缝切换。
- 可以在IPVS设备上同时启动MASTER和BACKUP进程,使设备之间互为备份,实现IPVS设备的均衡。
- IPVS同步实现在net/ipv4/ipvs/ip_vs_sync.c中.
- 同步信息块的格式如下,开始是4字节的信息头,后面是多个IPVS连接同步信息,每个块大小不固定,连接同步信息个数从0到多个:
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | Count Conns | SyncID | Size |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | |
- | IPVS Sync Connection () |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | . |
- | . |
- | . |
- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- | |
- | 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;
- };
- IPVS同步进程是一个内核进程,是由IPVSADM通过命令启动的.
- int start_sync_thread(int state, char *mcast_ifn, __u8 syncid)
- {
- DECLARE_COMPLETION_ONSTACK(startup);
- pid_t pid;
- //检查进程是否已经存在
- if ((state == IP_VS_STATE_MASTER && sync_master_pid) || (state == IP_VS_STATE_BACKUP && sync_backup_pid))
- return -EEXIST;
- ip_vs_sync_state |= state;
- if (state == IP_VS_STATE_MASTER) {//MASTER进程
- //通信的网卡
- strlcpy(ip_vs_master_mcast_ifn, mcast_ifn, sizeof(ip_vs_master_mcast_ifn));
- ip_vs_master_syncid = syncid; //同步ID
- } else {//SLAVE进程
- strlcpy(ip_vs_backup_mcast_ifn, mcast_ifn, sizeof(ip_vs_backup_mcast_ifn));
- ip_vs_backup_syncid = syncid;
- }
- repeat:
- )) < ) { //启动内核进程
- IP_VS_ERR("could not create fork_sync_thread due to %d... retrying.\n", pid);
- ssleep();
- goto repeat;
- }
- wait_for_completion(&startup);
- ;
- }
- fork_sync_thread()函数也继续fork出一个进程,形成守护进程
- static int fork_sync_thread(void *startup)
- {
- pid_t pid;
- /* fork the sync thread here, then the parent process of the
- sync thread is the init process after this thread exits. */
- repeat:
- )) < ) {
- IP_VS_ERR("could not create sync_thread due to %d... "
- "retrying.\n", pid);
- ssleep();
- goto repeat;
- }
- ;
- }
- static int sync_thread(void *startup)
- {
- DECLARE_WAITQUEUE(wait, current);
- mm_segment_t oldmm;
- int state;
- const char *name;
- /* increase the module use count */
- ip_vs_use_count_inc();
- //设置进程状态的名称
- if (ip_vs_sync_state & IP_VS_STATE_MASTER && !sync_master_pid) {
- state = IP_VS_STATE_MASTER;
- name = "ipvs_syncmaster";
- } else if (ip_vs_sync_state & IP_VS_STATE_BACKUP && !sync_backup_pid) {
- state = IP_VS_STATE_BACKUP;
- name = "ipvs_syncbackup";
- } else {
- IP_VS_BUG();
- ip_vs_use_count_dec();
- return -EINVAL;
- }
- daemonize(name);//daemon化进程
- oldmm = get_fs();
- set_fs(KERNEL_DS);
- /* Block all signals */
- spin_lock_irq(¤t->sighand->siglock);
- siginitsetinv(¤t->blocked, ); //本进程不接收所有信号
- recalc_sigpending();
- spin_unlock_irq(¤t->sighand->siglock);
- /* set the maximum length of sync message */
- set_sync_mesg_maxlen(state); //设置最大同步信息长度
- //#define IP_VS_SYNC_GROUP 0xe0000051 /* multicast addr - 224.0.0.81 */
- //#define IP_VS_SYNC_PORT 8848 /* multicast port */
- //设置UDP多播sock的参数
- mcast_addr.sin_family = AF_INET;
- mcast_addr.sin_port = htons(IP_VS_SYNC_PORT);
- mcast_addr.sin_addr.s_addr = htonl(IP_VS_SYNC_GROUP);
- //增加等待队列,由于接收发送数据必须在top half中,在bottom half中
- //只是发个WQ信号告诉top half可以进行接收或发送数据了
- add_wait_queue(&sync_wait, &wait);
- set_sync_pid(state, current->pid); //保存当前进程的pid
- complete((struct completion *)startup);
- //分别进行MASTER或SLAVE循环
- if (state == IP_VS_STATE_MASTER)
- sync_master_loop();
- else if (state == IP_VS_STATE_BACKUP)
- sync_backup_loop();
- else
- IP_VS_BUG();
- //循环退出,删除等待队列
- remove_wait_queue(&sync_wait, &wait);
- /* thread exits */
- set_sync_pid(state, ); //清零
- IP_VS_INFO("sync thread stopped!\n");
- set_fs(oldmm);
- /* decrease the module use count */
- ip_vs_use_count_dec();
- set_stop_sync(state, );
- wake_up(&stop_sync_wait);
- ;
- }
- MASTER循环
- static void sync_master_loop(void)
- {
- struct socket *sock;
- struct ip_vs_sync_buff *sb;
- /* create the sending multicast socket */
- sock = make_send_sock();//建立多播SOCK,同步信息由此SOCK发出
- if (!sock)
- return;
- for (;;) {//进入死循环
- //从队列中取发送数据块
- while ((sb=sb_dequeue())) {
- ip_vs_send_sync_msg(sock, sb->mesg);//发出同步数据块
- ip_vs_sync_buff_release(sb);//释放数据块缓冲
- }
- /* check if entries stay in curr_sb for 2 seconds */
- //如果2秒内数据块没准备好,直接将未完成的数据块发出去
- //最差情况下数据块里没有IPVS连接信息,只有一个数据头,
- //相当于同步信号,表明MASTER还没死
- *HZ))) {
- ip_vs_send_sync_msg(sock, sb->mesg);
- ip_vs_sync_buff_release(sb);
- }
- //发现停止MASTER进程标志,中断循环
- if (stop_master_sync)
- break;
- //休眠1秒
- ssleep();
- }
- //循环退出,将当前发送队列中的数据块都释放
- while ((sb=sb_dequeue())) {
- ip_vs_sync_buff_release(sb);
- }
- //清除当前块,当前块是构造中还没放到发送队列中的数据块
- ))) {
- ip_vs_sync_buff_release(sb);
- }
- /* release the sending multicast socket */
- sock_release(sock);
- }
- BACKUP循环
- static void sync_backup_loop(void)
- {
- struct socket *sock;
- char *buf;
- int len;
- //分配数据接收空间
- if (!(buf = kmalloc(sync_recv_mesg_maxlen, GFP_ATOMIC))) {
- IP_VS_ERR("sync_backup_loop: kmalloc error\n");
- return;
- }
- //创建UDP接收SOCK,并把这个SOCK地址加入到多播组中
- sock = make_receive_sock();
- if (!sock)
- goto out;
- for (;;) {
- //接收队列非空
- while (!skb_queue_empty(&(sock->sk->sk_receive_queue))) {
- //接收数据函数,比较简单,直接调用内核的kernel_recvmsg函数
- ) {
- IP_VS_ERR("receiving message error\n");
- break;
- }
- /* disable bottom half, because it accessed the data shared by softirq while getting/creating conns */
- local_bh_disable();//处理数据时不能再进入bottom half
- ip_vs_process_message(buf, len);//处理接收数据
- local_bh_enable();
- }
- //检查是否设置进程停止标志
- if (stop_backup_sync)
- break;
- ssleep();//睡眠1秒
- }
- sock_release(sock);
- out:
- kfree(buf);
- }
- 处理接收数据函数
- static void ip_vs_process_message(const char *buffer, const size_t buflen)
- {
- struct ip_vs_sync_mesg *m = (struct ip_vs_sync_mesg *)buffer;
- struct ip_vs_sync_conn *s;
- struct ip_vs_sync_conn_options *opt;
- struct ip_vs_conn *cp;
- char *p;
- int i;
- m->size = ntohs(m->size);
- //检查接收的数据长度是否正确
- if (buflen != m->size) {
- IP_VS_ERR("bogus message\n");
- return;
- }
- //检查同步ID是否匹配
- && m->syncid != ip_vs_backup_syncid) {
- IP_VS_DBG(, "Ignoring incoming msg with syncid = %d\n", m->syncid);
- return;
- }
- //同步信息块头后面是真正的IPVS连接同步信息
- //p现在是第一个同步连接结构指针
- p = (char *)buffer + sizeof(struct ip_vs_sync_mesg);
- ; i < m->nr_conns; i++) {//循环读取缓冲区中的同步连接信息
- unsigned flags;
- s = (struct ip_vs_sync_conn *)p;
- flags = ntohs(s->flags);
- //根据同步连接信息查找连接
- if (!(flags & IP_VS_CONN_F_TEMPLATE))
- cp = ip_vs_conn_in_get(s->protocol, s->caddr, s->cport, s->vaddr, s->vport);
- else
- cp = ip_vs_ct_in_get(s->protocol, s->caddr, s->cport, s->vaddr, s->vport);
- if (!cp) {
- //找不到连接,说明是MASTER新建的连接同步过来了
- //新建连接,连接的dest参数为NULL,表明是同步产生的连接,而不是BACKUP自己生成的连接
- cp = ip_vs_conn_new(s->protocol, s->caddr, s->cport, s->vaddr, s->vport, s->daddr, s->dport, flags, NULL);
- if (!cp) {
- IP_VS_ERR("ip_vs_conn_new failed\n");
- return;
- }
- cp->state = ntohs(s->state);//设置连接状态
- } else if (!cp->dest) {
- //找到了连接但没有dest指针,说明该连接是同步产生的连接,而不是BACKUP主动产生的连接
- cp->state = ntohs(s->state);
- cp->flags = flags | IP_VS_CONN_F_HASHED;
- } /* Note that we don't touch its state and flags if it is a normal entry. */
- if (flags & IP_VS_CONN_F_SEQ_MASK) {
- //拷贝连接选项
- ];
- memcpy(&cp->in_seq, opt, sizeof(*opt));
- p += FULL_CONN_SIZE;
- } else
- p += SIMPLE_CONN_SIZE;
- //设置连接计数,是个固定值
- ]);
- cp->timeout = IP_VS_SYNC_CONN_TIMEOUT;
- ip_vs_conn_put(cp);
- if (p > buffer + buflen) {//检查当前缓冲区指针是否越界了
- IP_VS_ERR("bogus message\n");
- return;
- }
- }
- }
- 还记得hook函数ip_vs_in()吗?连接同步函数ip_vs_sync_conn()是由ip_vs_in()函数调用的.
- void ip_vs_sync_conn(struct ip_vs_conn *cp)
- {
- struct ip_vs_sync_mesg *m;
- struct ip_vs_sync_conn *s;
- int len;
- spin_lock(&curr_sb_lock);
- if (!curr_sb) {
- //当前连接数据块为空,分配新块
- if (!(curr_sb=ip_vs_sync_buff_create())) {
- spin_unlock(&curr_sb_lock);
- IP_VS_ERR("ip_vs_sync_buff_create failed.\n");
- return;
- }
- }
- //检查是否包括选项长度
- len = (cp->flags & IP_VS_CONN_F_SEQ_MASK) ? FULL_CONN_SIZE : SIMPLE_CONN_SIZE;
- m = curr_sb->mesg;
- //空闲缓冲区头,作为一个连接同步单元头
- s = (struct ip_vs_sync_conn *)curr_sb->head;
- /* copy members */
- s->protocol = cp->protocol;
- s->cport = cp->cport;
- s->vport = cp->vport;
- s->dport = cp->dport;
- s->caddr = cp->caddr;
- s->vaddr = cp->vaddr;
- s->daddr = cp->daddr;
- s->flags = htons(cp->flags & ~IP_VS_CONN_F_HASHED);
- s->state = htons(cp->state);
- if (cp->flags & IP_VS_CONN_F_SEQ_MASK) {//增加选项信息,即TCP序列号
- ];
- memcpy(opt, &cp->in_seq, sizeof(*opt));
- }
- m->nr_conns++;
- m->size += len;//有效数据长度增加
- curr_sb->head += len;//空闲指针后移
- //检查剩下的空间是否还能容纳一个同步连接结构
- if (curr_sb->head + FULL_CONN_SIZE > curr_sb->end) {
- sb_queue_tail(curr_sb);//空间不够的话将当前同步数据块添加到发送链表中
- curr_sb = NULL;
- }
- spin_unlock(&curr_sb_lock);
- //如果有主连接,递归调用本函数同步主连接信息
- if (cp->control)
- ip_vs_sync_conn(cp->control);
- }
- [/IPVS的同步]
- [IPVS预估器]
- IPVS预估器估算在一个短暂时间间隔内的连接率,可在用户空间开一个daemon定时读取预估器的值以实现较长时间的预估。
- 预估算法为:
- 取最后8秒钟内,每两秒取一个采样点进行平滑处理:
- avgrate = avgrate*(-W) + rate*W
- 其中 W = ^(-) = 0.25,速率单位是KBytes/s
- 预估代码在net/ipv4/ipvs/ip_vs_est.c中实现.
- 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; // 发出的速率
- };
- int ip_vs_new_estimator(struct ip_vs_stats *stats)
- {
- struct ip_vs_estimator *est;
- est = kzalloc(sizeof(*est), GFP_KERNEL); //分配空间
- if (est == NULL)
- return -ENOMEM;
- est->stats = stats; //统计结构,包括IPVS服务,目的服务器等都有这个统计结构,预估器就是根据此统计值计算
- est->last_conns = stats->conns; //将当前统计结构中的值作为预估器中的参数初始值
- est->cps = stats->cps<<; //连接率值扩大2^10
- est->last_inpkts = stats->inpkts;
- est->inpps = stats->inpps<<; //进入包速率值扩大2^10
- est->last_outpkts = stats->outpkts;
- est->outpps = stats->outpps<<; //发出包速率值扩大2^10
- est->last_inbytes = stats->inbytes;
- est->inbps = stats->inbps<<; //进入速率值扩大2^5
- est->last_outbytes = stats->outbytes;
- est->outbps = stats->outbps<<; //发出速率值扩大2^5
- write_lock_bh(&est_lock);
- est->next = est_list; //将结构加入链表
- if (est->next == NULL) { //初始化定时器,整个链表只有一个定时器
- init_timer(&est_timer);
- est_timer.expires = jiffies + *HZ; //超时两秒
- est_timer.function = estimation_timer;
- add_timer(&est_timer);
- }
- est_list = est;
- write_unlock_bh(&est_lock);
- ;
- }
- 定时函数,预估计算
- static void estimation_timer(unsigned long arg)
- {
- struct ip_vs_estimator *e;
- struct ip_vs_stats *s;
- u32 n_conns;
- u32 n_inpkts, n_outpkts;
- u64 n_inbytes, n_outbytes;
- u32 rate;
- read_lock(&est_lock);
- //循环预估链表对所有预估器数据进行更新
- for (e = est_list; e; e = e->next) {
- s = e->stats;
- spin_lock(&s->lock);
- //当前统计结构中的新数值
- n_conns = s->conns;
- n_inpkts = s->inpkts;
- n_outpkts = s->outpkts;
- n_inbytes = s->inbytes;
- n_outbytes = s->outbytes;
- /* scaled by 2^10, but divided 2 seconds */
- //连接率计算
- //1秒内的连接数之差,扩大2^10
- rate = (n_conns - e->last_conns)<<;
- e->last_conns = n_conns;//保存连接数
- e->cps += ((;//预估器连接率变化
- s->cps = (e->cps+;//统计结构的连接率变化
- //进入包率计算,方法和上面相同
- rate = (n_inpkts - e->last_inpkts)<<;
- e->last_inpkts = n_inpkts;
- e->inpps += ((;
- s->inpps = (e->inpps+;
- //发出包率计算,方法和上面相同
- rate = (n_outpkts - e->last_outpkts)<<;
- e->last_outpkts = n_outpkts;
- e->outpps += ((;
- s->outpps = (e->outpps+;
- //进入字节流量率计算,方法和上面相同
- rate = (n_inbytes - e->last_inbytes)<<;
- e->last_inbytes = n_inbytes;
- e->inbps += ((;
- s->inbps = (e->inbps+;
- //发出字节流量率计算,方法和上面相同
- rate = (n_outbytes - e->last_outbytes)<<;
- e->last_outbytes = n_outbytes;
- e->outbps += ((;
- s->outbps = (e->outbps+;
- spin_unlock(&s->lock);
- }
- read_unlock(&est_lock);
- mod_timer(&est_timer, jiffies + *HZ);
- }
- [/IPVS预估器]
- IPVS有个定时函数周期性根据当前系统配置和性能状况调整IPVS的防御级别.
- #define DEFENSE_TIMER_PERIOD 1*HZ
- static void defense_work_handler(void *data);
- static DECLARE_WORK(defense_work, defense_work_handler, NULL);
- static void defense_work_handler(void *data)
- {
- update_defense_level();//更新防御级别
- if (atomic_read(&ip_vs_dropentry))
- ip_vs_random_dropentry();
- schedule_delayed_work(&defense_work, DEFENSE_TIMER_PERIOD);
- }
- 更新IPVS的防御级别,需要用到/proc下定义的一些控制参数
- static void update_defense_level(void)
- {
- struct sysinfo i;
- ;
- int availmem;
- int nomem;
- ;
- si_meminfo(&i);//计算可用的内存量
- availmem = i.freeram + i.bufferram;
- nomem = (availmem < sysctl_ip_vs_amemthresh);
- local_bh_disable();
- spin_lock(&__ip_vs_dropentry_lock);
- switch (sysctl_ip_vs_drop_entry) {//删除连接策略
- :
- atomic_set(&ip_vs_dropentry, );//不丢包
- break;
- :
- if (nomem) {
- atomic_set(&ip_vs_dropentry, );//内存不足时允许丢
- ;
- } else {
- atomic_set(&ip_vs_dropentry, );//内存足时不用丢
- }
- break;
- :
- if (nomem) {
- atomic_set(&ip_vs_dropentry, );//内存不足时允许丢
- } else {
- atomic_set(&ip_vs_dropentry, );//内存足时不用丢
- ;
- }
- break;
- :
- atomic_set(&ip_vs_dropentry, );//允许丢
- break;
- }
- spin_unlock(&__ip_vs_dropentry_lock);
- spin_lock(&__ip_vs_droppacket_lock);
- switch (sysctl_ip_vs_drop_packet) {//丢包率策略
- :
- ip_vs_drop_rate = ;//不用丢
- break;
- :
- if (nomem) {//没内存时根据当前内存情况计算丢包率
- ip_vs_drop_rate = ip_vs_drop_counter = sysctl_ip_vs_amemthresh / (sysctl_ip_vs_amemthresh-availmem);
- sysctl_ip_vs_drop_packet = ;//更改丢包策略
- } else {
- ip_vs_drop_rate = ;//不用丢
- }
- break;
- :
- if (nomem) {//没内存时根据当前内存情况计算丢包率
- ip_vs_drop_rate = ip_vs_drop_counter = sysctl_ip_vs_amemthresh / (sysctl_ip_vs_amemthresh-availmem);
- } else {
- ip_vs_drop_rate = ;//不用丢
- sysctl_ip_vs_drop_packet = ;
- }
- break;
- :
- ip_vs_drop_rate = sysctl_ip_vs_am_droprate;//丢包率设为用户设置的丢包率
- break;
- }
- spin_unlock(&__ip_vs_droppacket_lock);
- write_lock(&__ip_vs_securetcp_lock);
- switch (sysctl_ip_vs_secure_tcp) {//安全TCP参数
- :
- )//原模式是安全模式,不改
- ;
- break;
- :
- if (nomem) {
- )//原模式非安全模式,改变
- ;
- sysctl_ip_vs_secure_tcp = ;
- } else {
- )
- to_change = ;
- }
- break;
- :
- if (nomem) {
- )//原模式非安全模式,改变
- ;
- } else {
- )
- to_change = ;
- sysctl_ip_vs_secure_tcp = ;
- }
- break;
- :
- )//原模式非安全模式,改变
- ;
- break;
- }
- old_secure_tcp = sysctl_ip_vs_secure_tcp;//保存上次的安全TCP参数
- )//改变tcp状态超时值
- );
- write_unlock(&__ip_vs_securetcp_lock);
- local_bh_enable();
- }
- /proc/net/ip_vs :IPVS的规则表
- /proc/net/ip_vs_app :IPVS应用协议
- /proc/net/ip_vs_conn :IPVS当前连接
- /proc/net/ip_vs_stats :IPVS状态统计信息
- ipvsadm 的用法和格式如下:
- ipvsadm COMMAND [protocol] service-address [scheduling-method] [persistence options]
- ipvsadm command [protocol] service-address server-address [packet-forwarding-method] [weight options]
- command:
- ipvsadm -A|E -t|u|f virutal-service-address:port [-s scheduler] [-p [timeout]] [-M netmask]
- ipvsadm -D -t|u|f virtual-service-address
- ipvsadm -C
- ipvsadm -R
- ipvsadm -S [-n]
- ipvsadm -a|e -t|u|f service-address:port -r real-server-address:port [-g|i|m] [-w weight]
- ipvsadm -d -t|u|f service-address -r server-address
- ipvsadm -L|l [options]
- ipvsadm -Z [-t|u|f service-address]
- ipvsadm --set tcp tcpfin udp
- ipvsadm --start-daemon state [--mcast-interface interface]
- ipvsadm --stop-daemon
- ipvsadm –h
- 术语:
- virtual-service-address:是指虚拟服务器的ip 地址;
- real-service-address:是指真实服务器的ip 地址;
- scheduler:调度方法。
- 命令选项解释:
- 有两种命令选项格式,长的和短的,具有相同的意思。在实际使用时,两种都可以。
- -A --add-service 在内核的虚拟服务器表中添加一条新的虚拟服务器记录。也就是增加一台新的虚拟服务器。
- -E --edit-service 编辑内核虚拟服务器表中的一条虚拟服务器记录。
- -D --delete-service 删除内核虚拟服务器表中的一条虚拟服务器记录。
- -C --clear 清除内核虚拟服务器表中的所有记录。
- -R --restore 恢复虚拟服务器规则
- -S --save 保存虚拟服务器规则,输出为-R 选项可读的格式
- -a --add-server 在内核虚拟服务器表的一条记录里添加一条新的真实服务器
- 记录。也就是在一个虚拟服务器中增加一台新的真实服务器
- -e --edit-server 编辑一条虚拟服务器记录中的某条真实服务器记录
- -d --delete-server 删除一条虚拟服务器记录中的某条真实服务器记录
- -L|-l --list 显示内核虚拟服务器表
- -Z --zero 虚拟服务表计数器清零(清空当前的连接数量等)
- --set tcp tcpfin udp 设置连接超时值timeout value: tcp, tcp FIN packet, udp
- --start-daemon state启动同步守护进程。state可以是master 或backup,用来说明LVS Router 是master 或是backup。在这个功能上也可以采用keepalived 的VRRP 功能。
- --stop-daemon 停止同步守护进程
- -h --help 显示帮助信息
- 其他的选项:
- -t --tcp-service service-address 说明虚拟服务器提供的是tcp 的服务
- [vip:port] or [real-server-ip:port] port为0时且有-p timeout 时接受任何端口。
- -u --udp-service service-address 说明虚拟服务器提供的是udp 的服务
- [vip:port] or [real-server-ip:port]
- -f --fwmark-service fwmark 说明是经过iptables 标记过的服务类型firewall-mark。
- -s --scheduler scheduling-method 使用的调度算法,有这样几个选项
- rr|wrr|lc|wlc|lblc|lblcr|dh|sh|sed|nq, 默认的调度算法是:wlc.
- Round Robin; Weighted Round-Robin; Least-Connection; Weighted Least-Connection; Locality-Based Least Connections; Locality-Based Least Connections with Replication; Destination Hashing; Source Hashing; Shortest Expected Delay; Never Queue。
- -p --persistent [timeout] 持久稳固的服务。这个选项的意思是来自同一个客
- 户的多次请求,将被同一台真实的服务器处理。timeout 的默认值为300 秒。
- -M --netmask netmask persistent granularity mask
- -r --real-server server-address 真实的服务器[Real-Server:port]
- packet-forwarding-method:
- -g --gatewaying 指定LVS 的工作模式为直接路由模式(也是LVS 默认的模式)
- -i --ipip 指定LVS 的工作模式为隧道模式
- -m --masquerading 指定LVS 的工作模式为NAT 模式
- -w --weight weight 真实服务器的权值0~65535默认为1
- --mcast-interface interface 指定组播的同步接口
- -c --connection 显示LVS 目前的连接 如:ipvsadm -L -c
- --timeout 显示tcp tcpfin udp 的timeout 值 如:ipvsadm -L --timeout
- --daemon 显示同步守护进程状态
- --stats 显示统计信息statistics information
- --rate 显示速率信息
- --sort 对虚拟服务器和真实服务器排序输出
- --numeric -n 输出IP 地址和端口的数字形式
- 例子:
- ipvsadm –A –t –s rr
- ipvsadm –a –t –r –m
- ipvsadm –a –t –r –m
- ipvsadm –a –t –r –m
- ipvsadm –a –t –r –m
- ipvsadm –a –t –r –m
- 配置IPVS Table脚本 :
- VIP=192.168.34.41 #Vritual IP地址
- RIP1= IP
- RIP2= IP
- GW=192.168.34.1 #Real Server 网关IP
- #清除IPVS Table
- ipvsadm -C
- #设置IPVS Table
- ipvsadm -A -t $VIP: -s wlc
- ipvsadm -a -t $VIP: -r $RIP1: -g -w
- ipvsadm -a -t $VIP: -r $RIP2: -g -w
- #将IPVS Table保存到/etc/sysconfig/ipvsadm
- /etc/rc.d/init.d/ipvsadm save
- #启动IPVS
- service ipvsadm start #或者/etc/rc.d/init.d/ipvsadm start也可以
- #显示IPVS状态
- ipvsadm -l
IPVS实现分析的更多相关文章
- 14.深入k8s:kube-proxy ipvs及其源码分析
转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 源码版本是1.19 这一篇是讲service,但是基础使用以及基本概念由于官方实在是写的 ...
- Kubernetes网络的iptables模式和ipvs模式支持ping分析
1.iptables模式无法ping通原因分析 iptables模式下,无法ping通任何svc,包括clusterip.所有ns下,下面来分析原因: 查看kubernetes的网络模式 curl 1 ...
- lvs源代码分析
以linux-2.6.21为例. 数据结构介绍: ip_vs_conn 对于某个连接记录其N元组, (client, vserver, rserver) & (address, port) Q ...
- Mini2440 DM9000 驱动分析(一)
Mini2440 DM9000 驱动分析(一) 硬件特性 Mini2440开发板上DM9000的电气连接和Mach-mini2440.c文件的关系: PW_RST 连接到复位按键,复位按键按下,低电平 ...
- 应用负载均衡之LVS(三):使用ipvsadm以及详细分析VS/DR模式
*/ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hl ...
- 抓包分析LVS-NAT中出现的SYN_RECV
CIP:192.168.10.193 VIP:192.168.10.152:8000 DIP:100.10.8.152:8000 RIP:100.10.8.101:8000 和 100.10.8.10 ...
- 为什么比IPVS的WRR要好?
动机 五一临近,四月也接近尾声,五一节乃小长假的最后一天.今天是最后一天工作日,竟然感冒了,半夜里翻来覆去无法安睡,加上窗外大飞机屋里小飞机(也就是蚊子)的骚扰,实在是必须起来做点有意义的事了! ...
- IPVS和Nginx两种WRR负载均衡算法详解
动机 五一临近,四月也接近尾声,五一节乃小长假的最后一天.今天是最后一天工作日,竟然感冒了,半夜里翻来覆去无法安睡,加上窗外大飞机屋里小飞机(也就是蚊子)的骚扰,实在是必须起来做点有意义的事了! ...
- 【linux驱动分析】之dm9000驱动分析(三):sk_buff结构分析
[linux驱动分析]之dm9000驱动分析(一):dm9000原理及硬件分析 [linux驱动分析]之dm9000驱动分析(二):定义在板文件里的资源和设备以及几个宏 [linux驱动分析]之dm9 ...
随机推荐
- SQL Server 2008 TOP 新用途
/***************创建测试表******************************/ IF NOT OBJECT_ID('[Demo_Top]') IS NULL DROP TAB ...
- 20151214 jquery插件代码备份
;(function ($) { //局部性的 /*$.fn.extend({ 'nav' : function (color) { $(this).find('.nav').css({ 'list- ...
- Tomcat中JSP引擎工作原理
http://blog.csdn.net/linjiaxingqqqq/article/details/7164449 JSP运行环境: 执行JSP代码需要在服务器上安装JSP引擎,比较常见的引擎有W ...
- Java之反射的应用
package com.zheges; import java.util.Date; public class Customer {//JavaBean 对象 private String nam ...
- 命令行下上传文件到iOS软件 专业文件管理/gplayer
U盘丢了, 就拿手机当U盘用用先. 一般情况下软件打开上传功能, 在浏览器里上传即可. 可是偏偏我的电影放在了 树莓派里面(搭建了一个SMB), 直接浏览器的话,会多占用些带宽, 我的破路由器.... ...
- 关于Navicat导入MySQL数据库遇到的一些问题
今天本想将之前的一个数据库easy.sql用图形化工具Navicat导入的,开始是用“运行SQL文件”导入,结果是“queries successfully”啥的,去查是否导表成功,一看并没有. 结果 ...
- [开发环境] Ubuntu12.04 Telnet服务设置
Ubuntu12.04默认未启用Telnet服务,配置Telnet服务步骤如下: 1. 安装和配置 安装openbsd-inetd: #sudo apt-get install openbsd-ine ...
- MySQL数据库原理
我们知道,数据是信息的载体——一种我们约定了如何解释的符号.在计算机系统中,最常见的应该是文本数据.我们用它记录配置信息,写日志,等等.而在应用程序中,按一定的数据结构来组织数据的方式叫做数据库管理系 ...
- express开发实例
express获取参数有三种方法:官网介绍如下 Checks route params (req.params), ex: /user/:id Checks query string params ( ...
- Couldn't get lock for %t/vertx.log
今天在启动vertx框架的项目时,报“Couldn't get lock for %t/vertx.log”的错误. 解决方案: 1,找出vertx.log的目录.一般在(C:\Users\Admin ...