文档章节

两种负载均衡技术的实现原理与简单示例

fdhay
 fdhay
发布于 2017/07/28 18:43
字数 5657
阅读 31
收藏 5

一.    概述  

   
   
   

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

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

  1. DNS 域名解析负载;

  2. HTTP 重定向负载;

  3. 反向代理负载;

  4. IP 负载 (NAT 负载和 IP tunnel 负载);

  5. 数据链路负载 (DR);

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

1.1    名词解释  

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 地址 ;

二.    负载均衡的实现原理  

2.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 地址,进行数据包解析;

    2.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 缓存表,查询如下:

路由表:

[root@TendyRonSys-01 ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.0.0     0.0.0.0         255.255.255.0   U     1      0        0 eth0
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 缓存表:

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

    2.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.2    IP 负载和数据链路负载原理  

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

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

    2.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.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.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.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:

echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce
echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore
echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce

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

3.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. 钩子执行时的处理函数;

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

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.3     NAT 负载示例  

/*
 *      load-nat
 *      基于 vip 的负载实现主方法
 *      author:zjg
 *      since 2016-6-8
 */
#include<linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/netfilter.h>
#include<linux/skbuff.h>
#include<linux/ip.h>
#include<linux/netdevice.h>
#include<linux/if_ether.h>
#include<linux/if_packet.h>
#include<linux/inet.h>
#include<net/tcp.h>
#include<linux/netfilter_ipv4.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/slab.h>
#include<linux/time.h>
#include<asm/current.h>
#include<linux/sched.h>
#include<linux/ctype.h>
#include<net/route.h>


MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("zjg");

#define ETHALEN 14
#define SPLITCHAR ":"
#define MAX_BUFFER 1000
#define printk_ip(info, be32_addr)  printk("%s:%i :%s %d.%d.%d.%d\n",current->comm,current->pid,info,((unsigned char *)&(be32_addr))[0],((unsigned char *)&(be32_addr))[1],((unsigned char *)&(be32_addr))[2],((unsigned char *)&(be32_addr))[3])

#define    ETH    "eth0"

#define IP_VS_XMIT(pf, skb, rt)                \
do {                            \
     (skb)->ipvs_property = 1;            \
     skb_forward_csum(skb);                \
     NF_HOOK(pf, NF_INET_LOCAL_OUT, (skb), NULL,    \
        (rt)->u.dst.dev, dst_output);        \
} while (0)



int testPirntTcpHead(struct tcphdr *tcph);
struct rtable * setRoute(__u32 d_addr,__u32 s_addr,__u16 protocol,u32 rtos,struct net * nd_net);


static struct nf_hook_ops modify_ops;

// 负载的常量设置
char *load_dip = "192.168.0.199";
char *load_rip = "192.168.0.27";
char *load_cip = "172.16.32.87";

/**
 * 钩子函数
 * 根据负载目标,进行负载计算
 */
static unsigned int modify(unsigned int hooknum, struct sk_buff * skb,
                                  const struct net_device * in, const struct net_device * out,
                                  int (*okfn)(struct sk_buff *)){

        //ip 包头结构
        struct iphdr *ip_header;
        //tcp 包头结构
    struct tcphdr *tcp_header;
    //ip 包长度和偏移量
        unsigned int ip_hdr_off;
        unsigned int ip_tot_len;
        int oldlen;
        //tcp 目的端口、源端口
        unsigned int destport;
        unsigned int srcport;


        //route
        struct rtable *rt;

        char * srcip = NULL;
    char * dstip = NULL;

        //1-request -1-response
        int requestOrresponse = 1;
        // 是否需要负载
        bool isLoad = 0;

        /* 获取 ip 包头 */
        ip_header = ip_hdr(skb);
        // 获取 IP 包总长度和偏移量
        ip_tot_len = ntohs(ip_header->tot_len);
        ip_hdr_off = ip_hdrlen(skb);

        oldlen = skb->len - ip_hdr_off;

        // 获取 TCP 报头
        tcp_header = (void *)skb_network_header(skb) + ip_hdr_off;

        // 获取目的端口
        destport = ntohs(tcp_header->dest);
        // 获取源端口
        srcport = ntohs(tcp_header->source);
        //response
        if(srcport == 18080){
                printk("come in response\n");
                srcip = load_dip;//char *load_dip = "192.168.0.199";
                dstip = load_cip; //char *load_cip = "172.16.32.87";
                isLoad = 1;
                requestOrresponse = -1;
        }else    if(destport == 18080){//request
                printk("come in request\n");
                srcip = load_dip;//char *load_dip = "192.168.0.199";
                dstip = load_rip;//char *load_rip = "192.168.0.27";
                isLoad = 1;
            }
        if(isLoad){
            printk("%s\n", "come in load!");
            // 设置负载地址和源地址
            ip_header->daddr = in_aton(dstip);
            ip_header->saddr = in_aton(srcip);
            // 计算 ip 检验和
            ip_send_check(ip_header);

            // 计算 tcp 校验和
            tcp_header->check = 0;
            skb->csum = skb_checksum(skb,ip_hdr_off,skb->len-ip_hdr_off, 0);
            tcp_header->check = csum_tcpudp_magic(ip_header->saddr,ip_header->daddr,
                                 skb->len-ip_hdr_off,ip_header->protocol,skb->csum);

            skb->ip_summed = CHECKSUM_NONE;
            skb->pkt_type = PACKET_OTHERHOST;// 路由包,进行路由

            // 查找路由
            if(requestOrresponse<0){
                 rt = setRoute(in_aton(dstip),in_aton(load_dip),ip_header->protocol,
                          RT_TOS(ip_header->tos),skb->dev->nd_net);
            }else{
                 rt = setRoute(in_aton(dstip),in_aton(srcip),ip_header->protocol,
                          RT_TOS(ip_header->tos),skb->dev->nd_net);
            }
             // 丢弃旧路由,设置新路由
             dst_release(skb_dst(skb));
             skb_dst_set(skb,&rt->u.dst);
             // 发送数据包
             IP_VS_XMIT(PF_INET, skb, rt);

            return NF_STOLEN;
    }

    return NF_ACCEPT;
}

/**
 * 路由查找方法
 */
struct rtable * setRoute(__u32 d_addr,__u32 s_addr,__u16 protocol,u32 rtos,struct net * nd_net){
      int ret=-2;
      struct rtable *rt;
        struct flowi fl = {
            .oif = 0,
            .nl_u = {
                .ip4_u = {
                    .daddr = d_addr,
                    .saddr = s_addr,
                    .tos = rtos, } },
            .proto = protocol,
        };
        ret=ip_route_output_key(nd_net,&rt, &fl);
        printk("route search ret:%d\n",ret);
        return rt;
    }


/**
 * 系统回调初始化方法
 */
static int __init init(void){
    int ret;
    //set hook 的主要参数
    // 钩子主要函数
    modify_ops.hook = modify;
    // 钩子的加载点
    modify_ops.hooknum = NF_INET_PRE_ROUTING;
    // 协议族名
    modify_ops.pf = AF_INET;
    // 钩子的优先级
    modify_ops.priority = NF_IP_PRI_FIRST;
    //register hook
    ret = nf_register_hook(&modify_ops);
    if (ret < 0) {
        printk("%s\n", "can't modify skb hook!");
        return ret;
    }
    printk("%s\n", "hook register success!");
    return 0;
}

/**
 * 系统回调函数清除
 */
static void __exit fini(void){
    nf_unregister_hook(&modify_ops);
    printk("%s\n", "remove modify load_vip module.");

}
// 定义初始化函数和清理函数
module_init(init);
module_exit(fini);

3.4     DR 负载实现

/*
 *      load_dr
 *      基于 DR 的负载实现主方法 
 *      author:zjg
 *      since 2017-6-8
 */
#include<linux/module.h> 
#include<linux/kernel.h> 
#include<linux/init.h>
#include<linux/netfilter.h>
#include<linux/skbuff.h>
#include<linux/ip.h>
#include<linux/netdevice.h>
#include<linux/if_ether.h>
#include<linux/if_packet.h>
#include<linux/inet.h>
#include<net/tcp.h>
#include<linux/netfilter_ipv4.h>
#include<linux/fs.h>
#include<linux/uaccess.h>
#include<linux/slab.h>
#include<linux/time.h>
#include<asm/current.h>
#include<linux/sched.h> 
#include<linux/ctype.h>


MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("zjg");

#define printk_ip(info, be32_addr)  printk("%s:%i :%s %d.%d.%d.%d\n",current->comm,current->pid,info,((unsigned char *)&(be32_addr))[0],((unsigned char *)&(be32_addr))[1],((unsigned char *)&(be32_addr))[2],((unsigned char *)&(be32_addr))[3])

#define IP_VS_XMIT(pf, skb, rt)                \
do {                            \
     (skb)->ipvs_property = 1;            \
     skb_forward_csum(skb);                \
     NF_HOOK(pf, NF_INET_LOCAL_OUT, (skb), NULL,    \
        (rt)->u.dst.dev, dst_output);        \
} while (0)

struct rtable * setRoute(__u32 d_addr,__u32 s_addr,__u16 protocol,u32 rtos,struct net * nd_net);

int testPirntTcpHead(struct tcphdr *tcph);

static struct nf_hook_ops modify_ops;

// 负载设置
char *load_dip = "192.168.0.199";
char *load_rip = "192.168.0.27";
char *load_cip = "172.16.32.87";
char *load_vip = "192.168.0.198";

/**
 * 钩子函数
 * 根据负载目标,进行负载计算
 */
static unsigned int modify(unsigned int hooknum, struct sk_buff * skb,
                                  const struct net_device * in, const struct net_device * out,
                                  int (*okfn)(struct sk_buff *)){

    /*ip 包头结构 */
    struct iphdr *ip_header;
    /* 路由 */
    struct rtable *rt;            

    ip_header = ip_hdr(skb);

    printk("hook_func is called.==============\n");
    // 判断访问的目的地址,如果是 VIP 则进行负载 
    if(ip_header->daddr==in_aton(load_vip)){

            // 根据真实服务器地址进行路由查找
            rt= setRoute(in_aton(load_rip),in_aton(load_dip),
                ip_header->protocol,RT_TOS(ip_header->tos),dev_net(skb->dev));

            // 丢弃旧的路由信息
            skb_dst_drop(skb);
            // 设置新的路由信息
            skb_dst_set(skb, &rt->u.dst);
            // 将数据包进行发送
            IP_VS_XMIT(PF_INET, skb, rt);
            return NF_STOLEN;
        }
    return NF_ACCEPT;
}

struct rtable * setRoute(__u32 d_addr,__u32 s_addr,__u16 protocol,u32 rtos,struct net * nd_net){
      int ret=-2;
      struct rtable *rt;
        struct flowi fl = {
            .oif = 0,
            .nl_u = {
                .ip4_u = {
                    .daddr = d_addr,
                    .saddr = s_addr,
                    .tos = rtos, } },
            .proto = protocol,
        };
        ret=ip_route_output_key(nd_net,&rt, &fl);
        printk("route search ret:%d\n",ret);
        return rt;
    }


/**
 * 系统回调初始化方法
 */
static int __init init(void){
    int  ret = 0;
    // 设置钩子信息
    modify_ops.hook = modify;
    modify_ops.hooknum = NF_INET_PRE_ROUTING;
    modify_ops.pf = AF_INET;
    modify_ops.priority = NF_IP_PRI_FIRST;
    //register hook
    ret = nf_register_hook(&modify_ops);
    if (ret < 0) {
        printk("%s\n", "can't modify skb hook!");
        return ret;
    }
    printk("%s\n", "hook register success!");
    return 0;
}

/**
 * 系统回调函数清除
 */
static void __exit fini(void){
    nf_unregister_hook(&modify_ops);
    printk("%s\n", "remove modify load_vip module.");
}

module_init(init);
module_exit(fini);

针对 IP tunnel 的实现,请大家参考章文嵩博士的 LVS 或者参考 Linux2.6 的 IPIP 协议,在此就不列出。如果读者想要对负载均衡的完整实现进行了解,建议可以阅读章文嵩博士的 LVS 的源码。

四. 结束语  

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

作者介绍

蓝胖子,工作十年,一直从事移动互联网金融行业的开发,从 WAP1.2,到 WAP2.0;从 J2ME,BREW 到 Android,iOS。完成过中,农,工,建等各大银行的手机银行,手机支付等项目。目前供职于为金融行业提供安全技术解决方案的公司,职位为部门经理兼架构师,负责金融软件产品的相关研发工作和公司业务系统的日常运维。

本文转载自:http://mp.weixin.qq.com/s/Hua7_XdwAyuSwL6ivMPtzw

fdhay
粉丝 11
博文 69
码字总数 2796
作品 0
成都
高级程序员
私信 提问
负载均衡器技术Nginx和F5的优缺点对比

对于数据流量过大的网络中,往往单一设备无法承担,需要多台设备进行数据分流,而负载均衡器就是用来将数据分流到多台设备的一个转发器。   目前有许多不同的负载均衡技术用以满足不同的应...

Zero零_度
2015/04/17
5.6K
2
负载均衡器部署方式和工作原理

概述 负载均衡(Load Balance) 由于目前现有网络的各个核心部分随着业务量的提高,访问量和数据流量的快速增长,其处理能力和计算强度也相应地增大,使得单一的服务器设备根本无法承担。在此...

ugali
2017/08/25
0
0
快速理解高性能HTTP服务端的负载均衡技术原理

1、前言 在一个典型的高并发、大用户量的Web互联网系统的架构设计中,对HTTP集群的负载均衡设计是作为高性能系统优化环节中必不可少的方案。HTTP负载均衡的本质上是将Web用户流量进行均衡减压...

JackJiang2011
2018/09/11
0
0
不懂高性能的负载均衡设计?没关系,架构师带你飞

在软件系统的架构设计中,对集群的负载均衡设计是作为高性能系统优化环节中必不可少的方案。负载均衡本质上是用于将用户流量进行均衡减压的,因此在互联网的大流量项目中,其重要性不言而喻。...

微笑很纯洁
2018/09/11
0
0
大型网站的负载均衡器、db proxy和db

本文主要分析网站后台架构中的负载均衡器,企业常用的硬件负载均衡器软件负载均衡器、数据库代理服务器和数据库。 1.1 负载均衡 在大型网站部署中,负载均衡至少有三层部署。第一层为web ser...

凯文加内特
2014/07/24
482
2

没有更多内容

加载失败,请刷新页面

加载更多

zk中leader和follower启动时信息交互

QuorumPeer中读取节点状态信息,不同状态下设置不同角色 1 Leader启动Follower接收器LearnerCnxAcceptor LearnerCnxAcceptor负责接收非leader连接请求,线程中创建LearnerHandler处理器 2 Le...

writeademo
33分钟前
4
0
完美的Linux之【navi】使用笔记

今天要说的是才上线才两天,就已经获得超过1000星。开发者是一位来自巴西的小哥Denis Isidoro。 开发的工具navi Linux用户的日常困惑 > 新命令 用完就忘 ? > 一时想不起来命令的单词怎么拼?...

我们都很努力着
35分钟前
4
0
iptables删除命令中的相关问题

最近在做一个V**N中间件的配置工作,在配置iptables的时候,当用户想删除EIP(即释放当前连接),发现使用iptables的相关命令会提示错误。iptables: Bad rule (does a matching rule exist ...

Linux就该这么学
47分钟前
3
0
ExtJS 4.2 评分组件

本文转载于:专业的前端网站➸ExtJS 4.2 评分组件 上一文章是扩展ExtJS自带的Date组件。在这里将创建一个评分组件。 目录 1. 介绍 2. 示例 3. 资源下载 1. 介绍 代码参考的是 Sencha Touch 2...

前端老手
55分钟前
4
0
springboot2.0.6启动解析(四)观察器、启动的错报告、Headless模式相关分析

解析SpringApplication的run方法观察器、启动的错报告、Headless模式、监听器相关分析 public ConfigurableApplicationContext run(String... args) { // 构造一个任务执行观察器(Jav...

小亮89
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部