IP负载与DR负载的实现原理与简单示例

概述

负载均衡作为目前服务器集群部署的一款常用设备,当一台机器性能无法满足业务的增长需求时,不是去找一款性能更好的机器,而是通过负载均衡,利用集群来满足客户增长的需求。

负载均衡技术的实现,主要分为以下几种:

  1. DNS域名解析负载;
  2. HTTP重定向负载;
  3. 反向代理负载;
  4. IP负载(NAT负载和IP tunnel负载);
  5. 数据链路负载(DR);

本篇主要讨论IP负载和数据链路负载(DR)的原理,并且给出NAT负载和DR负载的简单代码示例,包括基于netfilter钩子的定义,数据包的获取,数据包的修改,报头的设置,路由查找,数据包的发送。

名词解释

Load Balance:负载均衡机器;

VIP(Virtual IP): Load Balance面向前端客户机器的请求地址;

RS(Real Server): Load Balance进行负载的目标机器,面向客户端真实提供服务的机器;

RIP(Real Server IP):RS的真实IP;

CIP(Client IP):客户端IP地址,发送请求包中的源IP地址;

DIP(Director IP): Load Balance与RS在同一局域的网卡IP地址;

负载均衡的实现原理

1.TCP/IP的通信原理

图1

TCP/IP协议是当今网络世界中使用的最为广泛的协议。图1列出了TCP/IP与OSI分层之间的大致关系。由此可以看出,TCP/IP与OSI在分层模块上有一定的区别。TCP/IP主要分为应用层,传输层,互联网层,(网卡层)数据链路层,硬件层。对于IP负载和数据链路负载更多的需要关注IP层和数据链路层。

图2

图2通过简单例子描述TCP/IP协议中IP层及以下的通信流程。

  1. Host1查找本机路由表,根据目的IP(192.168.16.188)地址,确定下一步的路由地址R1(192.168.32.1);
  2. Host1根据路由IP地址(192.168.32.1),查找ARP缓存表,确定下一步MAC地址(12-13-14-15-16-18);
  3. 产生数据封包,如图所示,在IP层报头,Src:源IP地址,Dst:目的IP地址;ethernet表示数据链路层,以太网报头,SMAC:源MAC地址,DMAC:目的MAC地址;
  4. 数据包到达R1路由器,R1重复(1),(2)两步查找路由表和ARP缓存表,确定下一步数据包路由信息;
  5. 数据包到达R2路由器,R2重复(1),(2)两步查找路由表和ARP缓存表,确定下一步数据包路由信息;
  6. 数据包到达R3路由器,R3重复(1),(2)两步查找路由表和ARP缓存表,确定Host2主机MAC地址,发送数据包;
  7. Host2主机接收数据包,比对MAC地址,IP地址,进行数据包解析;

1.1 路由表和ARP缓存表

在整个数据包的传输中,重复的利用到两张表,路由表和ARP缓存表。路由分为静态路由和动态路由,静态路由通常由管理员手工完成,动态路由由管理员设置好动态路由协议,动态路由协议会自动生成路由表。路由协议大致分为两类:一类是外部网关协议EGP,一类是内部网关协议IGP。

EGP使用BGP路由协议;

IGP使用RIP,RIP2,OSPF等路由协议;

图3

ARP缓存表的生成主要依靠ARP协议,Host1(ip1)将要发送数据给Host3(ip3)。发送ARP广播“谁能告诉我,ip3的MAC地址是多少啊?”。Host2收到广播包,发现问的是ip3的地址,则不响应。Host3收到数据包,发现ip地址与自己相符,则发回响应包,“ip3的MAC地址是**。”Host1收到响应包,缓存到ARP缓存表中。

在Linux主机(CentOS 6.7),路由表和ARP缓存表,查询如下:

路由表:


  1. [root@TendyRonSys-01 ~]# route -n 
  2. Kernel IP routing table 
  3. Destination Gateway Genmask Flags Metric Ref Use Iface 
  4. 192.168.0.0 0.0.0.0 255.255.255.0 U 1 0 0 eth0 
  5. 0.0.0.0 192.168.0.1 0.0.0.0 UG 0 0 0 eth0  

Destination和Genmask分别是network和netmask,合起来表示一个网络。Flags:U 表示该路由是启动的,可用;G表示网关;

ARP缓存表:


  1. [root@TendyRonSys-01 ~]# arp -a 
  2.  
  3. localhost (192.168.0.27) at 6c:62:6d:bf:25:2f [ether] on eth0 
  4.  
  5. localhost (192.168.0.15) at 94:65:9c:2a:29:f2 [ether] on eth0 
  6.  
  7. localhost (192.168.0.91) at e0:94:67:06:d9:ba [ether] on eth0 
  8.  
  9. localhost (192.168.0.1) at ec:6c:9f:2c:d1:08 [ether] on eth0  

1.2 IP层与数据链路层的关系

由图1可知,在OSI和TCP/IP的对比模型中,IP层和数据链路层分别位于第二层和第三层。

数据链路层定义了通过通信媒介互连的设备之间的传输规范。而IP层是整个TCP/IP体系的核心,IP层的作用是使数据分组发往任何网络,并使分组独立地传向目标。IP层并不关心这些分组的传输路径,也不保证每个分组的到达顺序,甚至并不保证一定能够到达。

举个例子来说明IP层与数据链路层的关系。小A在X省Y市Z街道Z1号,通过网络订购了商家B(商家在U省V市)的一个产品,商家通过快递发送产品到小A的家中。

图4

图4简要显示了整个商品的物流快递流程:

  1. 商品通过汽车走公路,从商家到达U省快递转运中心;
  2. 商品通过飞机从空中,从U省快递转运中心达到X省快递转运中心;
  3. 商品通过汽车走高速公路,从X省快递转运中心到达X省Y市快递点;
  4. 商品通过电三轮走市内街道,从X省Y市快递点到达客户家(X省Y市Z街道Z1号)

在整个物流快递流程中,分为了4个运输区间,分别使用了公路,天空,高速公路和市内街道。

再来看下IP层的数据分组传输示意图:

图5

在整个数据分组的传输过程中,分别经过了以太网,IP_VPN,千兆以太网,ATM等数据链路。

两张图相比是不是很相像?数据链路层其实就是利用物理层的传输媒介定义出相应互连设备的传输规则,包括数据封装,MAC寻址,差错检测,网络拓扑,环路检测等等。其实数据链路层就是网络传输的最小单元,所以有人说过整个互联网其实就是“数据链路的集合”。

IP层在数据链路层之上,实现了终端节点之间的端到端的通信。在上面两个图可以看出,在整个传输过程中,无论是在哪个区间,数据分组(快递包裹)的源地址和目的地址始终没有变化。当跨越多个数据链路层时,IP层必不可少,通过IP标出了数据包的真正目的地址。从IP层来看,互联网是由路由器连接的网络组合而成。路由器通过动态路由协议生成的路由表,更像一种互联网中的地图,但是因为互联网的庞大,在每个路由上保存的是个局部地图。

数据分组通过IP标出源地址和目的地址,这就像快递包裹上也仅仅是标出了寄件地址和收件地址,并不会标出快递第一步从哪里到达哪里,第二步从哪里达到哪里…。从源地址到目的地址的整个传输路径是一步一步通过查询路由表来确定的,这就像快递的运输流程,参考图4,先到达U省快递中心,再达到X省快递转运中心…,在每一个运输区间内,通过特定的运输线路保证快递的运输,到达下一个站点后,由下一个运输区间来进行下一阶段的运输。在数据分组的传输过程中,也是通过目的地址,查询路由表确定每一步的区间目的地址(并不是一次性查出,而是在每个路由器上,查询下一步的目的地址),然后数据链路层通过ARP缓存表将IP地址转为MAC地址,再通过数据链路层运输。打个比喻来说在整个数据分组的传输过程中,IP层的路由表给出了地图,数据链路层提供了运输工具,物理层提供了运输的基础——道路(比如快递运输的公路,高速公路,天空)。

2. IP负载和数据链路负载原理

理清了IP单播路由的原理,接下来我们来说IP负载和数据链路负载(DR)的原理。

IP负载分为NAT负载和IP隧道负载(tunnel)。

2.1 NAT负载

图6

NAT负载如图所示,流程如下:

  1. 客户端发送数据包到达Load Balance,Load Balance通过负载算法计算,确定RS;
  2. Load Balance修改请求包DST地址 (VIP)为RIP。对于串接模式,可以不修改请求包SRC地址(CIP);对于旁接模式必须修改SRC地址(CIP)为DIP地址;
  3. 数据包负载到RS Host2A/RS Host2B,RS处理数据,根据SRC地址,返回响应包。
  4. Load Balance修改响应包SRC地址为VIP,DST地址为CIP;
  5. Load Balance返回响应包到客户端;

NAT负载主要是通过修改请求包IP层的目的地址来使请求包进行重新路由,达到负载的效果。在此过程中,是否修改请求包的SRC地址?需要根据网络拓扑和真实的需求来进行决定。

2.2 IP tunnel负载

图7

IP隧道负载(tunnel)流程如下:

  1. 客户端发送数据包到达Load Balance,Load Balance通过负载算法计算,确定RS;
  2. Load Balance进行IP封包,封成IPIP包,外层IP包为Load Balance的封包,DST地址为负载RS的RIP地址;
  3. RS解封IPIP包,因VIP地址一样,则RS应用处理此请求;
  4. RS根据内层IP包的SRC地址和VIP地址进行响应包封包;
  5. 响应包不用再次经过Load Balance,直接返回客户端;

Tunnel模式中,Load Balance并不修改请求包,而是通过与RS 建立tunnel,进行IPIP封包,将新的IPIP包重新路由,负载到RS上。

在RS上,通过加载IPIP包的解包程序,保证了对IPIP包的正常解包。同时

RS与Load Balance配置同一个VIP,保证了响应包可以不通过Load Balance,直接返回到客户端。

2.3 数据链路负载(DR)

 

图8

数据链路负载流程如下:

  1. 客户端发送数据包到达Load Balance,Load Balance通过负载算法计算,确定RS;
  2. Load Balance根据RS的MAC地址,修改数据包的目的MAC地址;
  3. RS收到数据包,因VIP地址一样,则RS应用处理此请求;
  4. RS根据数据包的SRC地址和DST地址进行响应包封包;
  5. 响应包不用再次经过Load Balance,直接返回客户端;

数据链路负载(DR)模式中,Load Balance通过修改请求包的目的MAC地址来达到请求包重新路由的目的。DR与tunnel模式有一定的相同之处,都是通过VIP来保证响应包可以不经过Load Balance。

2.4 三种负载方法的比较

在实现方式上,Load Balance的目的就是通过负载算法的计算,找到合适的RS机器,然后通过修改请求包,使请求包重新路由到RS上。NAT模式是相对直接的方式,直接修改请求包的目的地址。虽然实现起来容易,但是这也限制了响应包也必须经过Load Balance,这样在NAT模式下Load Balance成为了系统的瓶颈。Tunnel模式下,tunnel通过建立IP隧道,即实现了对请求包的重新路由,也实现了对VIP的封装。通过Load Balance与RS的VIP配置,保证了响应包的独立返回,不必经过Load Balance。DR模式下,通过针对MAC层的修改,更直接的对请求包进行了重新路由,但是这也导致来的DR模式的局限性,不能跨网段。

在性能方面,IP tunnel和DR模式,响应包都不需要经过Load Balance,性能自然会高很多,能够负载的机器也会增加很多。DR模式与IP tunnel模式相比并不需要封装和解析IPIP包,自然性能也会比有一定的提升。

在网络拓扑方面,DR模式因为是从数据链路层负载,数据链路层是网络传输的最小单元,所以DR模式必然不能跨网段。IP tunnel模式通过建立IP tunnel进行负载,IP层实现的是终端节点端到端的通信,自然可以跨网段。

DR模式,通过VIP来保证RS对请求包的响应。在ARP缓存表中,IP地址与MAC地址进行一一对应,但是在DR模式下,VIP会出现多个MAC地址,如何处理呢?解决方式就是在DR模式下需要对RS的VIP进行ARP抑制。这样当局域网内广播ARP包时,RS的VIP网卡就不会进行响应,而只有负载均衡机器进行ARP响应,这样就能保证针对VIP的请求包首先到达Load Balance,由Load Balance进行负载计算,修改目的MAC地址后,再路由到RS。

抑制ARP响应命令,假设lo绑定VIP:


  1. echo " 
  2. " >/proc/sys/net/ipv4/conf/ 
  3. lo 
  4. /arp_ignore 
  5. echo " 
  6. " >/proc/sys/net/ipv4/conf/ 
  7. lo 
  8. /arp_announce 
  9. echo " 
  10. " >/proc/sys/net/ipv4/conf/ 
  11. all 
  12. /arp_ignore 
  13. echo " 
  14. " >/proc/sys/net/ipv4/conf/ 
  15. all 
  16. /arp_announce  

三、NAT负载/ DR负载代码示例

1 Netfilter简介

Netfilter/Iptables是Linux2.4之后的新一代的Linux防火墙机制。Netfilter采用模块化设计,具有良好的可扩展性。通俗的来说,就是在整个数据包的传递过程中,在若干个位置设置了Hook,可以根据需要在响应的Hook处,登记相应的处理函数。

图9

  1. NF_IP_PRE_ROUTING:刚进入网络层的数据包通过此点,目的地址转换可以在此点进行;
  2. NF_IP_LOCAL_IN:经路由决策后,送往本机的数据包通过此点,INPUT包过滤可以在此点进行;
  3. NF_IP_FORWARD:通过本机要转发的包数据包通过此点,FORWARD包过滤可以在此点进行;
  4. NF_IP_LOCAL_OUT:本机进程发出的数据包通过此点,OUTPUT包过滤可以在此点进行。
  5. NF_IP_POST_ROUTING:通过网络设备即将出去的数据包通过此点,源地址转换可以(包括地址伪装)在此点进行;

对于数据包的处理结果以下几种:

  1. NF_DROP:丢弃该数据包,主要用于数据包的过滤;
  2. NF_ACCEPT :保留该数据包,该数据包继续在协议栈中进行流转;
  3. NF_STOLEN :忘掉该数据包,该数据包交给Hook函数处理,协议栈忘掉该数据包;
  4. NF_QUEUE :将该数据包插入到用户空间;
  5. NF_REPEAT :再次调用该Hook函数;

一个Hook处理函数(钩子)主要包含三部分:

  1. 加载时的初始化函数;
  2. 卸载时的清理函数;
  3. 钩子执行时的处理函数;

详情请参考下面的代码示例。

2. 验证环境介绍

验证环境CentOS release 6.7,内核版本2.6.32-573.el6.x86_64。

图10

验证环境介绍:

  1. 192.168.0.199作为Load Balance机器;
  2. 192.168.0.27作为应用部署机器;
  3. 172.16.32.187作为客户端机器,模拟客户端访问;
  4. 抑制192.168.0.27机器对VIP的ARP响应;

实验介绍:

  1. 在NAT负载示例中,客户端发起请求,请求地址172.16.32.202:18080,接收到正常响应;
  2. 在DR负载示例中,客户端发起请求,请求地址172.16.32.202:28080,接收到正常响应;

以下代码主要介绍了基于netfilter钩子的定义,数据包的获取,数据包的修改,报头的设置,路由查找,数据包的发送。在代码中,并没有涉及到负载的算法,连接状态保持等功能。

3. NAT负载示例


  1. /* 
  2. * load-nat 
  3. * 基于vip的负载实现主方法 
  4. * author:zjg 
  5. * since 2016-6-8 
  6. */ 
  7. #include<linux/module.h> 
  8. #include<linux/kernel.h> 
  9. #include<linux/init.h> 
  10. #include<linux/netfilter.h> 
  11. #include<linux/skbuff.h> 
  12. #include<linux/ip.h> 
  13. #include<linux/netdevice.h> 
  14. #include<linux/if_ether.h> 
  15. #include<linux/if_packet.h> 
  16. #include<linux/inet.h> 
  17. #include<net/tcp.h> 
  18. #include<linux/netfilter_ipv4.h> 
  19. #include<linux/fs.h> 
  20. #include<linux/uaccess.h> 
  21. #include<linux/slab.h> 
  22. #include<linux/time.h> 
  23. #include<asm/current.h> 
  24. #include<linux/sched.h> 
  25. #include<linux/ctype.h> 
  26. #include<net/route.h> 
  27. MODULE_LICENSE("Dual BSD/GPL"); 
  28. MODULE_AUTHOR("zjg"); 
  29. #define ETHALEN 14 
  30. #define SPLITCHAR ":" 
  31. #define MAX_BUFFER 1000 
  32. #define printk_ip(info, be32_addr) printk("%s:%i :%s %d.%d.%d.%d\n", 
  33. current->comm,current->pid,info,((unsigned char *)&(be32_addr))[0], 
  34. ((unsigned char *)&(be32_addr))[1],((unsigned char *)&(be32_addr))[2], 
  35. ((unsigned char *)&(be32_addr))[3]) 
  36. #define ETH "eth0" 
  37. #define IP_VS_XMIT(pf, skb, rt) \ 
  38. do { \ 
  39. (skb)->ipvs_property = 1; \ 
  40. skb_forward_csum(skb); \ 
  41. NF_HOOK(pf, NF_INET_LOCAL_OUT, (skb), NULL, \ 
  42. (rt)->u.dst.dev, dst_output); \ 
  43. } while (0) 
  44. int testPirntTcpHead(struct tcphdr *tcph); 
  45. struct rtable * setRoute(__u32 d_addr,__u32 s_addr, 
  46. __u16 protocol,u32 rtos,struct net * nd_net); 
  47. static struct nf_hook_ops modify_ops; 
  48. //负载的常量设置 
  49. char *load_dip = "192.168.0.199"; 
  50. char *load_rip = "192.168.0.27"; 
  51. char *load_cip = "172.16.32.87"; 
  52. /** 
  53. * 钩子函数 
  54. * 根据负载目标,进行负载计算 
  55. */ 
  56. static unsigned int modify(unsigned int hooknum, struct sk_buff * skb, 
  57. const struct net_device * in, const struct net_device * out, 
  58. int (*okfn)(struct sk_buff *)){ 
  59. //ip包头结构 
  60. struct iphdr *ip_header; 
  61. //tcp包头结构 
  62. struct tcphdr *tcp_header; 
  63. //ip包长度和偏移量 
  64. unsigned int ip_hdr_off; 
  65. unsigned int ip_tot_len; 
  66. int oldlen; 
  67. //tcp目的端口、源端口 
  68. unsigned int destport; 
  69. unsigned int srcport; 
  70. //route 
  71. struct rtable *rt; 
  72. char * srcip = NULL; 
  73. char * dstip = NULL; 
  74. //1-request -1-response 
  75. int requestOrresponse = 1; 
  76. //是否需要负载 
  77. bool isLoad = 0; 
  78. /*获取ip包头*/ 
  79. ip_header = ip_hdr(skb); 
  80. //获取IP包总长度和偏移量 
  81. ip_tot_len = ntohs(ip_header->tot_len); 
  82. ip_hdr_off = ip_hdrlen(skb); 
  83. oldlen = skb->len - ip_hdr_off; 
  84. //获取TCP报头 
  85. tcp_header = (void *)skb_network_header(skb) + ip_hdr_off; 
  86. //获取目的端口 
  87. destport = ntohs(tcp_header->dest); 
  88. //获取源端口 
  89. srcport = ntohs(tcp_header->source); 
  90. //response 
  91. if(srcport == 18080){ 
  92. printk("come in response\n"); 
  93. srcip = load_dip;//char *load_dip = "192.168.0.199"; 
  94. dstip = load_cip; //char *load_cip = "172.16.32.87"; 
  95. isLoad = 1; 
  96. requestOrresponse = -1; 
  97. }else if(destport == 18080){//request 
  98. printk("come in request\n"); 
  99. srcip = load_dip;//char *load_dip = "192.168.0.199"; 
  100. dstip = load_rip;//char *load_rip = "192.168.0.27"; 
  101. isLoad = 1; 
  102. if(isLoad){ 
  103. printk("%s\n", "come in load!"); 
  104. //设置负载地址和源地址 
  105. ip_header->daddr = in_aton(dstip); 
  106. ip_header->saddr = in_aton(srcip); 
  107. //计算ip检验和 
  108. ip_send_check(ip_header); 
  109. //计算tcp校验和 
  110. tcp_header->check = 0; 
  111. skb->csum = skb_checksum(skb,ip_hdr_off,skb->len-ip_hdr_off, 0); 
  112. tcp_header->check = csum_tcpudp_magic(ip_header->saddr,ip_header->daddr, 
  113. skb->len-ip_hdr_off,ip_header->protocol,skb->csum); 
  114. skb->ip_summed = CHECKSUM_NONE; 
  115. skb->pkt_type = PACKET_OTHERHOST;//路由包,进行路由 
  116. //查找路由 
  117. if(requestOrresponse<0){ 
  118. rt = setRoute(in_aton(dstip),in_aton(load_dip),ip_header->protocol, 
  119. RT_TOS(ip_header->tos),skb->dev->nd_net); 
  120. }else{ 
  121. rt = setRoute(in_aton(dstip),in_aton(srcip),ip_header->protocol, 
  122. RT_TOS(ip_header->tos),skb->dev->nd_net); 
  123. //丢弃旧路由,设置新路由 
  124. dst_release(skb_dst(skb)); 
  125. skb_dst_set(skb,&rt->u.dst); 
  126. //发送数据包 
  127. IP_VS_XMIT(PF_INET, skb, rt); 
  128. return NF_STOLEN; 
  129. return NF_ACCEPT; 
  130. /** 
  131. *路由查找方法 
  132. */ 
  133. struct rtable * setRoute(__u32 d_addr,__u32 s_addr,__u16 protocol,u32 rtos,struct net * nd_net){ 
  134. int ret=-2; 
  135. struct rtable *rt; 
  136. struct flowi fl = { 
  137. .oif = 0, 
  138. .nl_u = { 
  139. .ip4_u = { 
  140. .daddr = d_addr, 
  141. .saddr = s_addr, 
  142. .tos = rtos, } }, 
  143. .proto = protocol, 
  144. }; 
  145. ret=ip_route_output_key(nd_net,&rt, &fl); 
  146. printk("route search ret:%d\n",ret); 
  147. return rt; 
  148. /** 
  149. * 系统回调初始化方法 
  150. */ 
  151. static int __init init(void){ 
  152. int ret; 
  153. //set hook的主要参数 
  154. //钩子主要函数 
  155. modify_ops.hook = modify; 
  156. //钩子的加载点 
  157. modify_ops.hooknum = NF_INET_PRE_ROUTING; 
  158. //协议族名 
  159. modify_ops.pf = AF_INET; 
  160. //钩子的优先级 
  161. modify_ops.priority = NF_IP_PRI_FIRST; 
  162. //register hook 
  163. ret = nf_register_hook(&modify_ops); 
  164. if (ret < 0) { 
  165. printk("%s\n", "can't modify skb hook!"); 
  166. return ret; 
  167. printk("%s\n", "hook register success!"); 
  168. return 0; 
  169. /** 
  170. * 系统回调函数清除 
  171. */ 
  172. static void __exit fini(void){ 
  173. nf_unregister_hook(&modify_ops); 
  174. printk("%s\n", "remove modify load_vip module."); 
  175. //定义初始化函数和清理函数 
  176. module_init(init); 
  177. module_exit(fini);  

4. DR负载实现


  1. /* 
  2. * load_dr 
  3. * 基于DR的负载实现主方法 
  4. * author:zjg 
  5. * since 2017-6-8 
  6. */ 
  7. #include<linux/module.h> 
  8. #include<linux/kernel.h> 
  9. #include<linux/init.h> 
  10. #include<linux/netfilter.h> 
  11. #include<linux/skbuff.h> 
  12. #include<linux/ip.h> 
  13. #include<linux/netdevice.h> 
  14. #include<linux/if_ether.h> 
  15. #include<linux/if_packet.h> 
  16. #include<linux/inet.h> 
  17. #include<net/tcp.h> 
  18. #include<linux/netfilter_ipv4.h> 
  19. #include<linux/fs.h> 
  20. #include<linux/uaccess.h> 
  21. #include<linux/slab.h> 
  22. #include<linux/time.h> 
  23. #include<asm/current.h> 
  24. #include<linux/sched.h> 
  25. #include<linux/ctype.h> 
  26. MODULE_LICENSE("Dual BSD/GPL"); 
  27. MODULE_AUTHOR("zjg"); 
  28. #define printk_ip(info, be32_addr) printk("%s:%i :%s %d.%d.%d.%d\n", 
  29. current->comm,current->pid,info,((unsigned char *)&(be32_addr))[0], 
  30. ((unsigned char *)&(be32_addr))[1],((unsigned char *)&(be32_addr))[2], 
  31. ((unsigned char *)&(be32_addr))[3]) 
  32. #define IP_VS_XMIT(pf, skb, rt) \ 
  33. do { \ 
  34. (skb)->ipvs_property = 1; \ 
  35. skb_forward_csum(skb); \ 
  36. NF_HOOK(pf, NF_INET_LOCAL_OUT, (skb), NULL, \ 
  37. (rt)->u.dst.dev, dst_output); \ 
  38. } while (0) 
  39. struct rtable * setRoute(__u32 d_addr,__u32 s_addr,__u16 protocol,u32 rtos,struct net * nd_net); 
  40. int testPirntTcpHead(struct tcphdr *tcph); 
  41. static struct nf_hook_ops modify_ops; 
  42. //负载设置 
  43. char *load_dip = "192.168.0.199"; 
  44. char *load_rip = "192.168.0.27"; 
  45. char *load_cip = "172.16.32.87"; 
  46. char *load_vip = "192.168.0.198"; 
  47. /** 
  48. * 钩子函数 
  49. * 根据负载目标,进行负载计算 
  50. */ 
  51. static unsigned int modify(unsigned int hooknum, struct sk_buff * skb, 
  52. const struct net_device * in, const struct net_device * out, 
  53. int (*okfn)(struct sk_buff *)){ 
  54. /*ip包头结构*/ 
  55. struct iphdr *ip_header; 
  56. /* 路由 */ 
  57. struct rtable *rt; 
  58. ip_header = ip_hdr(skb); 
  59. printk("hook_func is called.==============\n"); 
  60. //判断访问的目的地址,如果是VIP则进行负载 
  61. if(ip_header->daddr==in_aton(load_vip)){ 
  62. //根据真实服务器地址进行路由查找 
  63. rt= setRoute(in_aton(load_rip),in_aton(load_dip), 
  64. ip_header->protocol,RT_TOS(ip_header->tos),dev_net(skb->dev)); 
  65. //丢弃旧的路由信息 
  66. skb_dst_drop(skb); 
  67. //设置新的路由信息 
  68. skb_dst_set(skb, &rt->u.dst); 
  69. //将数据包进行发送 
  70. IP_VS_XMIT(PF_INET, skb, rt); 
  71. return NF_STOLEN; 
  72. return NF_ACCEPT; 
  73. struct rtable * setRoute(__u32 d_addr,__u32 s_addr,__u16 protocol,u32 rtos,struct net * nd_net){ 
  74. int ret=-2; 
  75. struct rtable *rt; 
  76. struct flowi fl = { 
  77. .oif = 0, 
  78. .nl_u = { 
  79. .ip4_u = { 
  80. .daddr = d_addr, 
  81. .saddr = s_addr, 
  82. .tos = rtos, } }, 
  83. .proto = protocol, 
  84. }; 
  85. ret=ip_route_output_key(nd_net,&rt, &fl); 
  86. printk("route search ret:%d\n",ret); 
  87. return rt; 
  88. /** 
  89. * 系统回调初始化方法 
  90. */ 
  91. static int __init init(void){ 
  92. int ret = 0; 
  93. //设置钩子信息 
  94. modify_ops.hook = modify; 
  95. modify_ops.hooknum = NF_INET_PRE_ROUTING; 
  96. modify_ops.pf = AF_INET; 
  97. modify_ops.priority = NF_IP_PRI_FIRST; 
  98. //register hook 
  99. ret = nf_register_hook(&modify_ops); 
  100. if (ret < 0) { 
  101. printk("%s\n", "can't modify skb hook!"); 
  102. return ret; 
  103. printk("%s\n", "hook register success!"); 
  104. return 0; 
  105. /** 
  106. * 系统回调函数清除 
  107. */ 
  108. static void __exit fini(void){ 
  109. nf_unregister_hook(&modify_ops); 
  110. printk("%s\n", "remove modify load_vip module."); 
  111. module_init(init); 
  112. module_exit(fini);  

针对IP tunnel的实现,请大家参考章文嵩博士的LVS或者参考Linux2.6的IPIP协议,在此就不列出。

如果读者想要对负载均衡的完整实现进行了解,建议可以阅读章文嵩博士的LVS的源码。

结束语

本文主要介绍了IP负载、DR负载的原理和基于netfilter钩子的定义,数据包的获取,数据包的修改,报头的设置,路由查找,数据包的发送。作者在研究的过程中参考了章文嵩博士的LVS实现和Linux的源码,在此感谢章文嵩博士和无数的开源代码贡献者。 

本文作者:蓝胖子

来源:51CTO

时间: 2025-01-29 18:54:05

IP负载与DR负载的实现原理与简单示例的相关文章

ASP小偷程序原理和简单示例

程序|示例|小偷程序     (一)原理 小偷程序实际上是通过了XML中的XMLHTTP组件调用其它网站上的网页.比如新闻小偷程序,很多都是调用了sina的新闻网页,并且对其中的html进行了一些替换,同时对广告也进行了过滤.用小偷程序的优点有:无须维护网站,因为小偷程序中的数据来自其他网站,它将随着该网站的更新而更新:可以节省服务器资源,一般小偷程序就几个文件,所有网页内容都是来自其他网站.缺点有:不稳定,如果目标网站出错,程序也会出错,而且,如果目标网站进行升级维护,那么小偷程序也要进行相应

ASP中实现小偷程序的原理和简单示例

程序|示例|小偷程序 现在网上流行的小偷程序比较多,有新闻类小偷,音乐小偷,下载小偷,那么它们是如何做的呢,下面我来做个简单介绍,希望对各位站长有所帮助. (一)原理 小偷程序实际上是通过了XML中的XMLHTTP组件调用其它网站上的网页.比如新闻小偷程序,很多都是调用了sina的新闻网页,并且对其中的html进行了一些替换,同时对广告也进行了过滤.用小偷程序的优点有:无须维护网站,因为小偷程序中的数据来自其他网站,它将随着该网站的更新而更新:可以节省服务器资源,一般小偷程序就几个文件,所有网页

小偷程序原理和简单示例

(一)原理 小偷程序实际上是通过了XML中的XMLHTTP组件调用其它网站上的网页.比如新闻小偷程序,很多都是调用了sina的新闻网页,并且对其中的html进行了一些替换,同时对广告也进行了过滤.用小偷程序的优点有:无须维护网站,因为小偷程序中的数据来自其他网站,它将随着该网站的更新而更新:可以节省服务器资源,一般小偷程序就几个文件,所有网页内容都是来自其他网站.缺点有:不稳定,如果目标网站出错,程序也会出错,而且,如果目标网站进行升级维护,那么小偷程序也要进行相应修改:速度,因为是远程调用,速

PHP实现采集程序原理和简单示例代码_php实例

<entry SKIPIFREF="YES">  <title>I Believe In Love</title>  <author> 蓝牙音乐网 - 8391.com</author>  <copyright> 蓝牙音乐网 - 8391.com</copyright>  <ref href="http://218.78.213.183:880/daolianmtvfuc________

Win Socket编程原理及简单实例

[转]http://www.cnblogs.com/tornadomeet/archive/2012/04/11/2442140.html  使用Linux Socket做了小型的分布式,如Linux C Socket编程原理及简单实例. 为了更好地分布也得看看Win Socket. Win Socket TCP原理图: Win Socket UDP原理图:    简单TCP连接实例: 服务器端: 1 #include <WINSOCK2.H> 2 #include <stdio.h&g

php-现在的爬虫原理还是简单的用正则抓取么?

问题描述 现在的爬虫原理还是简单的用正则抓取么? 本人实习生小菜鸟一枚,公司让写个爬虫练练手,之前对这个完全没概念,刚才在网上看了一会,觉得大致思路是抓下来整个文件,用正则表达式处理文本似的根据文法抓取要抓的东西,然后再处理,想问问现在也是这个思路么,就拿最初级的表单里的数据来说,现在有没有更直接的抓取方法,另外希望给几个php爬虫的demo,公司服务器没有python环境,只能用php了,多谢. 解决方案 正则不是用来抓取的,抓取用curl 正则是抓取了html后,解析你需要的数据的. 具体例

Java 队列实现原理及简单实现代码_java

Java 队列实现原理 "队列"这个单词是英国人说的"排".在英国"排队"的意思就是站到一排当中去.计算机科学中,队列是一种数据结构,有点类似栈,只是在队列中第一个插入的数据项也会最先被移除,而在栈中,最后插入的数据项最先移除.队列的作用就像电影院前的人们站成的排一样:第一个进入附属的人将最先到达队头买票.最后排队的人最后才能买到票. 队列和栈一样也被用作程序员的工具.它也可以用于模拟真实世界的环境,例如模拟人们在银行里排队等待,飞机等待起飞,或

Linux C Socket编程原理及简单实例

部分转自:http://goodcandle.cnblogs.com/archive/2005/12/10/294652.aspx 1.   什么是TCP/IP.UDP? 2.   Socket在哪里呢? 3.   Socket是什么呢? 4.   有很多的框架,为什么还在从Socket开始? 5.   Linux C Socket简单示例   1.什么是TCP/IP.UDP? TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控

Centos LVS Dr 负载均衡 配置说明详解

环境: LVS/Dr服务器:200.168.10.1 真实rip: 200.168.10.2 真实rip: 200.168.10.3 VIP : 200.168.10.10 LVS服务器配置: 关闭 iptables 和 selinux ,防止因为防火墙等原因照成失败 安装ipvsadm yum -y install ipvsadm* 执行脚本如下 #!/bin/bash #ipvs.sh # 把200.168.10.10 绑定到 eth0:0接口上 子网掩码是4个255 使得 200.168.