IPVS代码阅读笔记(二):NAT模式下的TCP数据包处理 - 我的文章 - langqi...
来源:百度文库 编辑:神马文学网 时间:2024/05/03 11:25:04
NAT模式是IPVS最常用的一种模式。相比于TUN和DR模式,NAT模式更容易部署,仅仅是需要更改真实服务器的默认网关配置。
IPVS是基于Netfilter实现的。它注册了4个Netfilter钩子函数,其中与NAT模式相关的是ip_vs_in和ip_vs_out两个钩子函数。前者处理了客户端-〉服务器的数据包,后者则针对服务器-〉客户端的数据包。
1、ip_vs_in钩子函数
ip_vs_in函数的处理流程非常清晰:
预校验--〉ip_vs_conn对象的查找或生成--〉更新统计信息--〉更新ip_vs_conn对象的状态--〉修改sk_buff并转发数据包--〉IPVS状态同步--〉结束
1.1、预校验
预校验的过程很简单,主要包括:
- 确保数据包的类型为PACKET_HOST
- 确保数据包不是loopback设备发出来的
- 确保数据包的协议类型是IPVS所支持的,目前IPVS支持TCP、UDP、AH和ESP协议
1.2、ip_vs_conn对象的查找或生成
既然有数据包来了,必然有对应的ip_vs_conn对象。首先根据数据包的<源地址-源端口-目的地址-目的端口>,查找当前的ip_vs_conn对象列表。如果没有找到的话,说明这是一个新连接,于是运行调度过程,找到一个合适的真实服务器,然后再生成一个新的ip_vs_conn对象。这里先不对调度过程进行展开描述。
1.3、更新统计信息
这里先看一下ip_vs_stats结构,其各个成员的作用很容易看出来:
/*
* IPVS statistics object
*/
struct ip_vs_stats
{
__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 */
spinlock_t lock; /* spin lock */
};
这是一个专门用于统计的数据结构,每个ip_vs_service对象、每个ip_vs_dest对象都包含有这么一个结构,另外还有一个ip_vs_stats全局变量。
函数ip_vs_in_stats和ip_vs_out_stats分别统计两个方向的数据包流量,函数ip_vs_conn_stats用于统计新建连接数。
static inline void
ip_vs_in_stats(struct ip_vs_conn *cp, struct sk_buff *skb)
{
struct ip_vs_dest *dest = cp->dest;
if (dest && (dest->flags & IP_VS_DEST_F_AVAILABLE)) {
spin_lock(&dest->stats.lock);
dest->stats.inpkts++;
dest->stats.inbytes += skb->len;
spin_unlock(&dest->stats.lock);
spin_lock(&dest->svc->stats.lock);
dest->svc->stats.inpkts++;
dest->svc->stats.inbytes += skb->len;
spin_unlock(&dest->svc->stats.lock);
spin_lock(&ip_vs_stats.lock);
ip_vs_stats.inpkts++;
ip_vs_stats.inbytes += skb->len;
spin_unlock(&ip_vs_stats.lock);
}
}
conns、inpkts、outpkts、inbytes和outbytes统计比较容易,只需简单的加1。但cps等统计起来就要复杂一些了,它是通过内核定时器来实现的。每个ip_vs_stats对象都对应有一个ip_vs_estimator结构:
struct ip_vs_estimator
{
struct ip_vs_estimator *next;
struct ip_vs_stats *stats;
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;
};
所有的ip_vs_estimator结构形成一张链表,通过全局变量est_list可以遍历这个链表。定时器的时间间隔为2秒,对应的触发函数为:
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 */
rate = (n_conns - e->last_conns)<<9;
e->last_conns = n_conns;
e->cps += ((long)rate - (long)e->cps)>>2;
s->cps = (e->cps+0x1FF)>>10;
rate = (n_inpkts - e->last_inpkts)<<9;
e->last_inpkts = n_inpkts;
e->inpps += ((long)rate - (long)e->inpps)>>2;
s->inpps = (e->inpps+0x1FF)>>10;
rate = (n_outpkts - e->last_outpkts)<<9;
e->last_outpkts = n_outpkts;
e->outpps += ((long)rate - (long)e->outpps)>>2;
s->outpps = (e->outpps+0x1FF)>>10;
rate = (n_inbytes - e->last_inbytes)<<4;
e->last_inbytes = n_inbytes;
e->inbps += ((long)rate - (long)e->inbps)>>2;
s->inbps = (e->inbps+0xF)>>5;
rate = (n_outbytes - e->last_outbytes)<<4;
e->last_outbytes = n_outbytes;
e->outbps += ((long)rate - (long)e->outbps)>>2;
s->outbps = (e->outbps+0xF)>>5;
spin_unlock(&s->lock);
}
read_unlock(&est_lock);
mod_timer(&est_timer, jiffies + 2*HZ);
}
以cps的统计为例,其计算过程很简单,cps = (rate+cps)/2,其中单位为2^10。
1.4、更新ip_vs_conn对象的状态
在TCP数据包处理过程中,每个ip_vs_conn对象对应于一个TCP连接,因此也必须有一个状态转换过程,才能够引导此TCP连接正常建立和终止。这个状态转换颇为复杂,在后续内容将IN/OUT结合一起,来看TCP连接的状态转换。
1.5、修改sk_buff并转发数据包
NAT模式下的数据包转发由ip_vs_nat_xmit函数完成。对sk_buff数据结构的操作不熟悉,略过。
1.6、IPVS状态同步
先判断此ip_vs_conn对象是否需要进行主备机同步。首先当前IPVS必须是MASTER,并且此ip_vs_conn对象的状态为ESTABLISHED。另外,满足这些条件时,并非每个Packet转发的时候都进行同步,而是每50个Packet,才同步一次。
同步过程由函数ip_vs_sync_conn完成:
/*
* Add an ip_vs_conn information into the current sync_buff.
* Called by 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) {
struct ip_vs_sync_conn_options *opt =
(struct ip_vs_sync_conn_options *)&s[1];
memcpy(opt, &cp->in_seq, sizeof(*opt));
}
m->nr_conns++;
m->size += len;
curr_sb->head += len;
/* check if there is a space for next one */
if (curr_sb->head+FULL_CONN_SIZE > curr_sb->end) {
sb_queue_tail(curr_sb);
curr_sb = NULL;
}
spin_unlock(&curr_sb_lock);
/* synchronize its controller if it has */
if (cp->control)
ip_vs_sync_conn(cp->control);
}
1.7、ip_vs_out钩子函数
ip_vs_out函数处理 服务器-〉客户端 的数据包。相比于ip_vs_in函数,它要简单得多。这里不再描述。
2、TCP状态转换过程
IPVS支持TCP、UDP、AH和ESP四种协议。由于TCP协议的逻辑相对复杂一些,所以IPVS对TCP协议的特殊处理也更多。
IPVS针对TCP协议的处理主要是体现在TCP状态维护上,而TCP状态维护依赖于一个状态转换矩阵:
/*
* Timeout table[state]
*/
static int tcp_timeouts[IP_VS_TCP_S_LAST+1] = {
[IP_VS_TCP_S_NONE] = 2*HZ,
[IP_VS_TCP_S_ESTABLISHED] = 15*60*HZ,
[IP_VS_TCP_S_SYN_SENT] = 2*60*HZ,
[IP_VS_TCP_S_SYN_RECV] = 1*60*HZ,
[IP_VS_TCP_S_FIN_WAIT] = 2*60*HZ,
[IP_VS_TCP_S_TIME_WAIT] = 2*60*HZ,
[IP_VS_TCP_S_CLOSE] = 10*HZ,
[IP_VS_TCP_S_CLOSE_WAIT] = 60*HZ,
[IP_VS_TCP_S_LAST_ACK] = 30*HZ,
[IP_VS_TCP_S_LISTEN] = 2*60*HZ,
[IP_VS_TCP_S_SYNACK] = 120*HZ,
[IP_VS_TCP_S_LAST] = 2*HZ,
};
static char * tcp_state_name_table[IP_VS_TCP_S_LAST+1] = {
[IP_VS_TCP_S_NONE] = "NONE",
[IP_VS_TCP_S_ESTABLISHED] = "ESTABLISHED",
[IP_VS_TCP_S_SYN_SENT] = "SYN_SENT",
[IP_VS_TCP_S_SYN_RECV] = "SYN_RECV",
[IP_VS_TCP_S_FIN_WAIT] = "FIN_WAIT",
[IP_VS_TCP_S_TIME_WAIT] = "TIME_WAIT",
[IP_VS_TCP_S_CLOSE] = "CLOSE",
[IP_VS_TCP_S_CLOSE_WAIT] = "CLOSE_WAIT",
[IP_VS_TCP_S_LAST_ACK] = "LAST_ACK",
[IP_VS_TCP_S_LISTEN] = "LISTEN",
[IP_VS_TCP_S_SYNACK] = "SYNACK",
[IP_VS_TCP_S_LAST] = "BUG!",
};
#define sNO IP_VS_TCP_S_NONE
#define sES IP_VS_TCP_S_ESTABLISHED
#define sSS IP_VS_TCP_S_SYN_SENT
#define sSR IP_VS_TCP_S_SYN_RECV
#define sFW IP_VS_TCP_S_FIN_WAIT
#define sTW IP_VS_TCP_S_TIME_WAIT
#define sCL IP_VS_TCP_S_CLOSE
#define sCW IP_VS_TCP_S_CLOSE_WAIT
#define sLA IP_VS_TCP_S_LAST_ACK
#define sLI IP_VS_TCP_S_LISTEN
#define sSA IP_VS_TCP_S_SYNACK
struct tcp_states_t {
int next_state[IP_VS_TCP_S_LAST];
};
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 }},
};
与NAT模式相关的为INPUT和OUTPUT两张表,其意思也较容易理解:
- sNO、sES等为TCP状态,tcp_state_name_table为状态名称表,而tcp_timeouts表指明了每个状态的维系时间。此维系时间决定了ip_vs_conn对象的生命期,当维系时间内此连接无任何输入输出,则ip_vs_conn对象自动销毁,它是通过设置ip_vs_conn对象的timeout来实现的。
- 当连接处于某状态时,在tcp_states矩阵中查找对应状态列,然后根据当前的输入(INPUT指客户端输入,OUTPUT指真实服务器输出),查找到下一个状态值。
状态转换矩阵也可以用一张状态转换图来表示:
这是一个完整的图,其中有很多状态转换都不太可能出现的。