【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

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

  • 2017-08-06
  • 本文字数:9776 字

    阅读完需:约 32 分钟

概述

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

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

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

路由表:

复制代码
[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

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:

复制代码
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 负载代码示例

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

复制代码
/*
* 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);

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。完成过中,农,工,建等各大银行的手机银行,手机支付等项目。目前供职于为金融行业提供安全技术解决方案的公司,职位为部门经理兼架构师,负责金融软件产品的相关研发工作和公司业务系统的日常运维;

针对 JAVA 开发,J2EE 开发非常的精通;对系统架构,设计原则,设计模式非常的精通;对于 Android,iOS 等移动开发也有一定的实践经验;对于 Linux c 开发有一定的实践经验;对于大数据领域有一定的使用经验;擅长领域:J2EE 开发,系统架构,软件安全 (PKI 体系);目前关注的领域: 机器学习,Linux 操作系统,Android 操作系统,网络通讯 (协议栈)


感谢木环对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017-08-06 17:564280

评论

发布
暂无评论
发现更多内容

抖音严打虚假宣传滋补膳食内容广告主:必须严格监管信息流广告

石头IT视角

Nginx 的日志

HoneyMoose

Hadoop Java api操作hdfs(一)

Emperor_LawD

hadoop 5月月更

函数计算 HTTP 触发器支持异步,解放双手搭建 Web 服务

阿里巴巴云原生

阿里云 云原生 函数计算

网站开发进阶(四十五)浅谈XML与HTML的区别

No Silver Bullet

html xml 5月月更

机器学习:真正的底层是什么?

海拥(haiyong.site)

5月月更

java内存模型之happenbefore原则

急需上岸的小谢

5月月更

在线TSV转HTMLTable工具

入门小站

工具

python使用 pywin32 模块操作 excel,Python 操作 excel 系列之五

梦想橡皮擦

5月月更

八、高可用之故障隔离

穿过生命散发芬芳

5月月更 高可用设计

模块五作业

HZ

架构实战营 #架构实战营

浅谈Java中的Math.random

工程师日月

java 5月月更

从这些云原生企业身上,我看到了数字化创新者该有的样子

阿里巴巴云原生

阿里云 云原生 实战案例

面向对象的系统分析

奔向架构师

信息系统 5月月更

架构训练营-毕业总结

默光

架构训练营5期

OpenYurt 开源之夏开始申请啦

阿里巴巴云原生

阿里云 云原生 开源之夏

kubernetes下的Nginx加Tomcat三部曲之一:极速体验

程序员欣宸

Java tomcat Kubernetes 5月月更

网站开发进阶(四十九)由JS报“未结束的字符串常量”引发的思考

No Silver Bullet

作用域 5月月更 解析引擎

架构实战营总结

晨亮

「架构实战营」

在线HTML文本提取URL链接工具

入门小站

工具

Go Web 编程入门:创建动态 HTML 和文本文件

宇宙之一粟

Web Go 语言 5月月更

集成 ShenYu 网关实现 Dubbo 泛化调用

码农大熊

盘古开发框架 Dubbo网关 泛化调用 ShenYu网关

目标检测的算法

恒山其若陋兮

5月月更

java内存模型之双重检查锁定与线程安全的延迟初始化

急需上岸的小谢

5月月更

SAP GUI 一些实用技巧分享

Jerry Wang

router 客户端 SAP GUI 5月月更

linux之man命令

入门小站

JAVA为什么需要泛型?

源字节1号

软件开发 后端开发 小程序开发

Bigdata 作业第九周

Pyel

docker可视化管理工具之shipyard

乌龟哥哥

5月月更

面试突击46:公平锁和非公平锁有什么区别?

王磊

Java 面试 java常见面试题

算力免费,还奖钱,OpenI日常激励活动“我为开源打榜狂”来袭

OpenI启智社区

开源 我为开源打榜狂

IP负载与DR负载的实现原理与简单示例_大数据_蓝胖子_InfoQ精选文章