本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
7. IPVS的协议管理
7.0 基本处理
IPVS协议的一些共用处理函数在net/ipv4/ipvs/ip_vs_proto.c中定义:
登记IPVS服务,就是把服务结构挂接到IPVS服务链表中
/*
* register an ipvs protocol
*/
static int register_ip_vs_protocol(struct ip_vs_protocol *pp)
{
// 计算协议号的HASH,一共32个HASH单向链表
unsigned hash = IP_VS_PROTO_HASH(pp->protocol);
// 添加到链表头
pp->next = ip_vs_proto_table[hash];
ip_vs_proto_table[hash] = pp;
// 进行协议初始化
if (pp->init != NULL)
pp->init(pp);
return 0;
}
}
拆除IPVS服务,就是把服务结构从IPVS服务链表中拆除
/*
* unregister an ipvs protocol
*/
static int unregister_ip_vs_protocol(struct ip_vs_protocol *pp)
{
struct ip_vs_protocol **pp_p;
unsigned hash = IP_VS_PROTO_HASH(pp->protocol);
* unregister an ipvs protocol
*/
static int unregister_ip_vs_protocol(struct ip_vs_protocol *pp)
{
struct ip_vs_protocol **pp_p;
unsigned hash = IP_VS_PROTO_HASH(pp->protocol);
pp_p = &ip_vs_proto_table[hash];
for (; *pp_p; pp_p = &(*pp_p)->next) {
// 是直接用服务结构的地址进行服务查找
if (*pp_p == pp) {
*pp_p = pp->next;
// 调用服务的退出函数
if (pp->exit != NULL)
pp->exit(pp);
return 0;
}
}
for (; *pp_p; pp_p = &(*pp_p)->next) {
// 是直接用服务结构的地址进行服务查找
if (*pp_p == pp) {
*pp_p = pp->next;
// 调用服务的退出函数
if (pp->exit != NULL)
pp->exit(pp);
return 0;
}
}
return -ESRCH;
}
}
查找服务,返回服务结构指针
/*
* get ip_vs_protocol object by its proto.
*/
struct ip_vs_protocol * ip_vs_proto_get(unsigned short proto)
{
struct ip_vs_protocol *pp;
unsigned hash = IP_VS_PROTO_HASH(proto);
// 使用IP协议号进行查找
for (pp = ip_vs_proto_table[hash]; pp; pp = pp->next) {
if (pp->protocol == proto)
return pp;
}
for (pp = ip_vs_proto_table[hash]; pp; pp = pp->next) {
if (pp->protocol == proto)
return pp;
}
return NULL;
}
}
修改协议状态超时
/*
* Propagate event for state change to all protocols
*/
void ip_vs_protocol_timeout_change(int flags)
{
struct ip_vs_protocol *pp;
int i;
for (i = 0; i < IP_VS_PROTO_TAB_SIZE; i++) {
for (pp = ip_vs_proto_table[i]; pp; pp = pp->next) {
// 遍历所有HASH链表调用各协议的timeout_change成员函数
if (pp->timeout_change)
pp->timeout_change(pp, flags);
}
}
}
for (pp = ip_vs_proto_table[i]; pp; pp = pp->next) {
// 遍历所有HASH链表调用各协议的timeout_change成员函数
if (pp->timeout_change)
pp->timeout_change(pp, flags);
}
}
}
创建状态超时表
int *
ip_vs_create_timeout_table(int *table, int size)
{
int *t;
int *
ip_vs_create_timeout_table(int *table, int size)
{
int *t;
t = kmalloc(size, GFP_ATOMIC);
if (t == NULL)
return NULL;
memcpy(t, table, size);
return t;
}
if (t == NULL)
return NULL;
memcpy(t, table, size);
return t;
}
修改状态超时值
/*
* Set timeout value for state specified by name
*/
int
ip_vs_set_state_timeout(int *table, int num, char **names, char *name, int to)
{
int i;
if (!table || !name || !to)
return -EINVAL;
return -EINVAL;
for (i = 0; i < num; i++) {
// 根据状态名称查找超时位置然后进行修改,超时参数to单位为秒
if (strcmp(names[i], name))
continue;
table[i] = to * HZ;
return 0;
}
return -ENOENT;
}
// 根据状态名称查找超时位置然后进行修改,超时参数to单位为秒
if (strcmp(names[i], name))
continue;
table[i] = to * HZ;
return 0;
}
return -ENOENT;
}
返回当前协议状态名称字符串
const char * ip_vs_state_name(__u16 proto, int state)
{
struct ip_vs_protocol *pp = ip_vs_proto_get(proto);
const char * ip_vs_state_name(__u16 proto, int state)
{
struct ip_vs_protocol *pp = ip_vs_proto_get(proto);
if (pp == NULL || pp->state_name == NULL)
return "ERR!";
return pp->state_name(state);
}
return "ERR!";
return pp->state_name(state);
}
IPVS协议初始化时初始化了TCP、UDP、AH和ESP四个协议,分别用一个struct ip_vs_protocol结构描述,这个结构定义了协议的各种操作,协议结构见3.1节,协议初始化函数见4.2节。
下面以TCP协议的实现来详细说明,相关代码文件为net/ipv4/ipvs/ip_vs_proto_tcp.c。
struct ip_vs_protocol ip_vs_protocol_tcp = {
.name = "TCP",
.protocol = IPPROTO_TCP,
.dont_defrag = 0,
.appcnt = ATOMIC_INIT(0),
.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,
};
7.1 TCP初始化函数
static void ip_vs_tcp_init(struct ip_vs_protocol *pp)
{
// 初始化应用协议(多连接协议)HASH表,其实只有FTP一种
IP_VS_INIT_HASH_TABLE(tcp_apps);
// TCP各连接状态的超时值
pp->timeout_table = tcp_timeouts;
}
{
// 初始化应用协议(多连接协议)HASH表,其实只有FTP一种
IP_VS_INIT_HASH_TABLE(tcp_apps);
// TCP各连接状态的超时值
pp->timeout_table = tcp_timeouts;
}
IPVS定义的超时,和netfilter类似,不过比netfilter的超时少得多,而且这些值不是通过/proc调整,而是通过ipvsadm命令来调整.
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,
};
[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,
};
7.2 TCP退出函数
是个啥也不作的空函数,只是让函数指针不为空.
static void ip_vs_tcp_exit(struct ip_vs_protocol *pp)
{
}
{
}
7.3 登记TCP应用(多连接)协议
static int tcp_register_app(struct ip_vs_app *inc)
{
struct ip_vs_app *i;
__u16 hash, port = inc->port;
int ret = 0;
// 根据端口计算一个HASH值
hash = tcp_app_hashkey(port);
hash = tcp_app_hashkey(port);
spin_lock_bh(&tcp_app_lock);
// 在HASH表中找是否已经存在对该端口处理的协议
list_for_each_entry(i, &tcp_apps[hash], p_list) {
if (i->port == port) {
ret = -EEXIST;
goto out;
}
}
// 将新应用协议添加到HASH表中
list_add(&inc->p_list, &tcp_apps[hash]);
// 增加应用协议计数器
atomic_inc(&ip_vs_protocol_tcp.appcnt);
// 在HASH表中找是否已经存在对该端口处理的协议
list_for_each_entry(i, &tcp_apps[hash], p_list) {
if (i->port == port) {
ret = -EEXIST;
goto out;
}
}
// 将新应用协议添加到HASH表中
list_add(&inc->p_list, &tcp_apps[hash]);
// 增加应用协议计数器
atomic_inc(&ip_vs_protocol_tcp.appcnt);
out:
spin_unlock_bh(&tcp_app_lock);
return ret;
}
spin_unlock_bh(&tcp_app_lock);
return ret;
}
7.4 去掉TCP应用(多连接)协议登记
static void
tcp_unregister_app(struct ip_vs_app *inc)
{
spin_lock_bh(&tcp_app_lock);
// 减少应用协议计数器
atomic_dec(&ip_vs_protocol_tcp.appcnt);
// 应用协议从HASH表中断开
list_del(&inc->p_list);
spin_unlock_bh(&tcp_app_lock);
}
7.5 连接调度
连接调度的目的是找到一个合适的目的服务器,生成新连接。该函数在ip_vs_in()函数中调用。
static int
tcp_conn_schedule(struct sk_buff *skb,
struct ip_vs_protocol *pp,
// cpp是将要新建立的连接的指针的地址
int *verdict, struct ip_vs_conn **cpp)
{
struct ip_vs_service *svc;
struct tcphdr _tcph, *th;
tcp_conn_schedule(struct sk_buff *skb,
struct ip_vs_protocol *pp,
// cpp是将要新建立的连接的指针的地址
int *verdict, struct ip_vs_conn **cpp)
{
struct ip_vs_service *svc;
struct tcphdr _tcph, *th;
th = skb_header_pointer(skb, skb->nh.iph->ihl*4,
sizeof(_tcph), &_tcph);
if (th == NULL) {
*verdict = NF_DROP;
return 0;
}
// 只对SYN包处理,标准的话应该再加上其他标志都为0的条件
// 非SYN包的话直接返回,cpp参数并不进行任何修改
if (th->syn &&
// 根据数据包的mark和协议目的地址目的端口等信息查找IPVS服务结构
(svc = ip_vs_service_get(skb->nfmark, skb->nh.iph->protocol,
skb->nh.iph->daddr, th->dest))) {
// 判断一下当前系统状态是否超载了,超载的话建立IPVS连接失败
if (ip_vs_todrop()) {
/*
* It seems that we are very loaded.
* We have to drop this packet :(
*/
ip_vs_service_put(svc);
// 丢弃数据包
*verdict = NF_DROP;
return 0;
}
sizeof(_tcph), &_tcph);
if (th == NULL) {
*verdict = NF_DROP;
return 0;
}
// 只对SYN包处理,标准的话应该再加上其他标志都为0的条件
// 非SYN包的话直接返回,cpp参数并不进行任何修改
if (th->syn &&
// 根据数据包的mark和协议目的地址目的端口等信息查找IPVS服务结构
(svc = ip_vs_service_get(skb->nfmark, skb->nh.iph->protocol,
skb->nh.iph->daddr, th->dest))) {
// 判断一下当前系统状态是否超载了,超载的话建立IPVS连接失败
if (ip_vs_todrop()) {
/*
* It seems that we are very loaded.
* We have to drop this packet :(
*/
ip_vs_service_put(svc);
// 丢弃数据包
*verdict = NF_DROP;
return 0;
}
/*
* Let the virtual server select a real server for the
* incoming connection, and create a connection entry.
*/
// 真正调度函数,返回新连接
*cpp = ip_vs_schedule(svc, skb);
if (!*cpp) {
*verdict = ip_vs_leave(svc, skb, pp);
return 0;
}
// 减少IPVS服务引用
ip_vs_service_put(svc);
}
return 1;
}
* Let the virtual server select a real server for the
* incoming connection, and create a connection entry.
*/
// 真正调度函数,返回新连接
*cpp = ip_vs_schedule(svc, skb);
if (!*cpp) {
*verdict = ip_vs_leave(svc, skb, pp);
return 0;
}
// 减少IPVS服务引用
ip_vs_service_put(svc);
}
return 1;
}
7.6 进入方向的连接查找连接
该函数在ip_vs_in()中正向调用,,在ip_vs_in_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[2], *pptr;
// 协议相关数据的指针,对TCP来说就是端口地址
pptr = skb_header_pointer(skb, proto_off, sizeof(_ports), _ports);
if (pptr == NULL)
return NULL;
pptr = skb_header_pointer(skb, proto_off, sizeof(_ports), _ports);
if (pptr == NULL)
return NULL;
if (likely(!inverse)) {
// 在绝大多数情况下是按正向查找连接
return ip_vs_conn_in_get(iph->protocol,
iph->saddr, pptr[0],
iph->daddr, pptr[1]);
} else {
// 少数情况是反向查找
return ip_vs_conn_in_get(iph->protocol,
iph->daddr, pptr[1],
iph->saddr, pptr[0]);
}
}
// 在绝大多数情况下是按正向查找连接
return ip_vs_conn_in_get(iph->protocol,
iph->saddr, pptr[0],
iph->daddr, pptr[1]);
} else {
// 少数情况是反向查找
return ip_vs_conn_in_get(iph->protocol,
iph->daddr, pptr[1],
iph->saddr, pptr[0]);
}
}
7.7 发出方向的连接查找
在ip_vs_out()函数中正向调用,在ip_vs_out_icmp()函数中反向调用
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[2], *pptr;
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[2], *pptr;
// 协议相关数据的指针,对TCP来说就是端口地址
pptr = skb_header_pointer(skb, proto_off, sizeof(_ports), _ports);
if (pptr == NULL)
return NULL;
pptr = skb_header_pointer(skb, proto_off, sizeof(_ports), _ports);
if (pptr == NULL)
return NULL;
if (likely(!inverse)) {
// 在绝大多数情况下是按正向查找连接
return ip_vs_conn_out_get(iph->protocol,
iph->saddr, pptr[0],
iph->daddr, pptr[1]);
} else {
// 少数情况是反向查找
return ip_vs_conn_out_get(iph->protocol,
iph->daddr, pptr[1],
iph->saddr, pptr[0]);
}
}
// 在绝大多数情况下是按正向查找连接
return ip_vs_conn_out_get(iph->protocol,
iph->saddr, pptr[0],
iph->daddr, pptr[1]);
} else {
// 少数情况是反向查找
return ip_vs_conn_out_get(iph->protocol,
iph->daddr, pptr[1],
iph->saddr, pptr[0]);
}
}
7.8 TCP源NAT操作
该函数完成对协议部分数据进行源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 int tcphoff = (*pskb)->nh.iph->ihl * 4;
tcp_snat_handler(struct sk_buff **pskb,
struct ip_vs_protocol *pp, struct ip_vs_conn *cp)
{
struct tcphdr *tcph;
unsigned int tcphoff = (*pskb)->nh.iph->ihl * 4;
// NAT操作skb必须是可写的
/* csum_check requires unshared skb */
if (!ip_vs_make_skb_writable(pskb, tcphoff+sizeof(*tcph)))
return 0;
/* csum_check requires unshared skb */
if (!ip_vs_make_skb_writable(pskb, tcphoff+sizeof(*tcph)))
return 0;
if (unlikely(cp->app != NULL)) {
// 如果是多连接协议,进行应用协议内容部分数据的修改
// 目前只支持FTP协议,对FTP作NAT时,需要修改PORT命令或227回应内容中的
// 地址端口信息
/* Some checks before mangling */
if (pp->csum_check && !pp->csum_check(*pskb, pp))
return 0;
// 如果是多连接协议,进行应用协议内容部分数据的修改
// 目前只支持FTP协议,对FTP作NAT时,需要修改PORT命令或227回应内容中的
// 地址端口信息
/* Some checks before mangling */
if (pp->csum_check && !pp->csum_check(*pskb, pp))
return 0;
/* Call application helper if needed */
if (!ip_vs_app_pkt_out(cp, pskb))
return 0;
}
if (!ip_vs_app_pkt_out(cp, pskb))
return 0;
}
tcph = (void *)(*pskb)->nh.iph + tcphoff;
// 修改当前TCP源端口
tcph->source = cp->vport;
// 修改当前TCP源端口
tcph->source = cp->vport;
/* Adjust TCP checksums */
if (!cp->app) {
// 如果只修改了源端口一个参数,就值需要用差值法快速计算新的TCP校验和
/* Only port and addr are changed, do fast csum update */
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校验和
/* full checksum calculation */
tcph->check = 0;
(*pskb)->csum = skb_checksum(*pskb, tcphoff,
(*pskb)->len - tcphoff, 0);
tcph->check = csum_tcpudp_magic(cp->vaddr, cp->caddr,
(*pskb)->len - tcphoff,
cp->protocol,
(*pskb)->csum);
IP_VS_DBG(11, "O-pkt: %s O-csum=%d (+%zd)\n",
pp->name, tcph->check,
(char*)&(tcph->check) - (char*)tcph);
}
return 1;
}
if (!cp->app) {
// 如果只修改了源端口一个参数,就值需要用差值法快速计算新的TCP校验和
/* Only port and addr are changed, do fast csum update */
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校验和
/* full checksum calculation */
tcph->check = 0;
(*pskb)->csum = skb_checksum(*pskb, tcphoff,
(*pskb)->len - tcphoff, 0);
tcph->check = csum_tcpudp_magic(cp->vaddr, cp->caddr,
(*pskb)->len - tcphoff,
cp->protocol,
(*pskb)->csum);
IP_VS_DBG(11, "O-pkt: %s O-csum=%d (+%zd)\n",
pp->name, tcph->check,
(char*)&(tcph->check) - (char*)tcph);
}
return 1;
}
TCP校验和快速计算法,因为只修改了端口一个参数,可根据RFC1141方法快速计算
static inline void
tcp_fast_csum_update(struct tcphdr *tcph, u32 oldip, u32 newip,
u16 oldport, u16 newport)
{
tcph->check =
ip_vs_check_diff(~oldip, newip,
ip_vs_check_diff(oldport ^ 0xFFFF,
newport, tcph->check));
}
tcp_fast_csum_update(struct tcphdr *tcph, u32 oldip, u32 newip,
u16 oldport, u16 newport)
{
tcph->check =
ip_vs_check_diff(~oldip, newip,
ip_vs_check_diff(oldport ^ 0xFFFF,
newport, tcph->check));
}
static inline u16 ip_vs_check_diff(u32 old, u32 new, u16 oldsum)
{
u32 diff[2] = { old, new };
{
u32 diff[2] = { old, new };
return csum_fold(csum_partial((char *) diff, sizeof(diff),
oldsum ^ 0xFFFF));
}
oldsum ^ 0xFFFF));
}
7.9 TCP目的NAT操作
该函数完成对协议部分数据进行目的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 int tcphoff = (*pskb)->nh.iph->ihl * 4;
tcp_dnat_handler(struct sk_buff **pskb,
struct ip_vs_protocol *pp, struct ip_vs_conn *cp)
{
struct tcphdr *tcph;
unsigned int tcphoff = (*pskb)->nh.iph->ihl * 4;
// NAT操作skb必须是可写的
/* csum_check requires unshared skb */
if (!ip_vs_make_skb_writable(pskb, tcphoff+sizeof(*tcph)))
return 0;
/* csum_check requires unshared skb */
if (!ip_vs_make_skb_writable(pskb, tcphoff+sizeof(*tcph)))
return 0;
if (unlikely(cp->app != NULL)) {
// 如果是多连接协议,进行应用协议内容部分数据的修改
// 目前只支持FTP协议,对FTP作NAT时,需要修改PORT命令或227回应内容中的
// 地址端口信息
/* Some checks before mangling */
if (pp->csum_check && !pp->csum_check(*pskb, pp))
return 0;
// 如果是多连接协议,进行应用协议内容部分数据的修改
// 目前只支持FTP协议,对FTP作NAT时,需要修改PORT命令或227回应内容中的
// 地址端口信息
/* Some checks before mangling */
if (pp->csum_check && !pp->csum_check(*pskb, pp))
return 0;
/*
* Attempt ip_vs_app call.
* It will fix ip_vs_conn and iph ack_seq stuff
*/
if (!ip_vs_app_pkt_in(cp, pskb))
return 0;
}
* Attempt ip_vs_app call.
* It will fix ip_vs_conn and iph ack_seq stuff
*/
if (!ip_vs_app_pkt_in(cp, pskb))
return 0;
}
tcph = (void *)(*pskb)->nh.iph + tcphoff;
// 修改当前TCP目的端口
tcph->dest = cp->dport;
// 修改当前TCP目的端口
tcph->dest = cp->dport;
/*
* Adjust TCP checksums
*/
if (!cp->app) {
// 如果只修改了源端口一个参数,就值需要用差值法快速计算新的TCP校验和
/* 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 {
// 如果修改了协议内容部分数据,需要根据全部数据重新计算TCP校验和
/* full checksum calculation */
tcph->check = 0;
(*pskb)->csum = skb_checksum(*pskb, tcphoff,
(*pskb)->len - tcphoff, 0);
tcph->check = csum_tcpudp_magic(cp->caddr, cp->daddr,
(*pskb)->len - tcphoff,
cp->protocol,
(*pskb)->csum);
(*pskb)->ip_summed = CHECKSUM_UNNECESSARY;
}
return 1;
}
* Adjust TCP checksums
*/
if (!cp->app) {
// 如果只修改了源端口一个参数,就值需要用差值法快速计算新的TCP校验和
/* 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 {
// 如果修改了协议内容部分数据,需要根据全部数据重新计算TCP校验和
/* full checksum calculation */
tcph->check = 0;
(*pskb)->csum = skb_checksum(*pskb, tcphoff,
(*pskb)->len - tcphoff, 0);
tcph->check = csum_tcpudp_magic(cp->caddr, cp->daddr,
(*pskb)->len - tcphoff,
cp->protocol,
(*pskb)->csum);
(*pskb)->ip_summed = CHECKSUM_UNNECESSARY;
}
return 1;
}
7.10 TCP校验和计算
计算IP协议中的校验和,对于TCP,UDP头中都有校验和参数,TCP中的校验和是必须的,而UDP的校验和可以不用计算。
该函数用的都是linux内核提供标准的校验和计算函数
该函数用的都是linux内核提供标准的校验和计算函数
static int
tcp_csum_check(struct sk_buff *skb, struct ip_vs_protocol *pp)
{
unsigned int tcphoff = skb->nh.iph->ihl*4;
tcp_csum_check(struct sk_buff *skb, struct ip_vs_protocol *pp)
{
unsigned int tcphoff = skb->nh.iph->ihl*4;
switch (skb->ip_summed) {
case CHECKSUM_NONE:
skb->csum = skb_checksum(skb, tcphoff, skb->len - tcphoff, 0);
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(0, pp, skb, 0,
"Failed checksum for");
return 0;
}
break;
default:
/* CHECKSUM_UNNECESSARY */
break;
}
case CHECKSUM_NONE:
skb->csum = skb_checksum(skb, tcphoff, skb->len - tcphoff, 0);
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(0, pp, skb, 0,
"Failed checksum for");
return 0;
}
break;
default:
/* CHECKSUM_UNNECESSARY */
break;
}
return 1;
}
}
7.11 TCP状态名称
该函数返回协议状态名称字符串
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] : "?";
}
{
if (state >= IP_VS_TCP_S_LAST)
return "ERR!";
return tcp_state_name_table[state] ? tcp_state_name_table[state] : "?";
}
TCP协议状态名称定义:
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!",
};
[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!",
};
7.12 TCP状态转换
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;
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*4,
sizeof(_tcph), &_tcph);
if (th == NULL)
return 0;
sizeof(_tcph), &_tcph);
if (th == NULL)
return 0;
spin_lock(&cp->lock);
// 重新设置连接状态
set_tcp_state(pp, cp, direction, th);
spin_unlock(&cp->lock);
// 重新设置连接状态
set_tcp_state(pp, cp, direction, th);
spin_unlock(&cp->lock);
return 1;
}
}
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];
/*
* Update state offset to INPUT_ONLY if necessary
* or delete NO_OUTPUT flag if output packet detected
*/
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;
}
* Update state offset to INPUT_ONLY if necessary
* or delete NO_OUTPUT flag if output packet detected
*/
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标志返回状态索引号
if ((state_idx = tcp_state_idx(th)) < 0) {
IP_VS_DBG(8, "tcp_state_idx=%d!!!\n", state_idx);
goto tcp_state_out;
}
// 从状态转换表中查新状态
new_state = tcp_state_table[state_off+state_idx].next_state[cp->state];
if ((state_idx = tcp_state_idx(th)) < 0) {
IP_VS_DBG(8, "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 (new_state != cp->state) {
// 状态迁移了
struct ip_vs_dest *dest = cp->dest;
IP_VS_DBG(8, "%s %s [%c%c%c%c] %u.%u.%u.%u:%d->"
"%u.%u.%u.%u:%d state: %s->%s conn->refcnt:%d\n",
pp->name,
(state_off==TCP_DIR_OUTPUT)?"output ":"input ",
th->syn? 'S' : '.',
th->fin? 'F' : '.',
th->ack? 'A' : '.',
th->rst? 'R' : '.',
NIPQUAD(cp->daddr), ntohs(cp->dport),
NIPQUAD(cp->caddr), ntohs(cp->cport),
tcp_state_name(cp->state),
tcp_state_name(new_state),
atomic_read(&cp->refcnt));
if (dest) {
// 连接的目的服务器存在
if (!(cp->flags & IP_VS_CONN_F_INACTIVE) &&
(new_state != IP_VS_TCP_S_ESTABLISHED)) {
// 如果连接是以前是活动的,新状态不是TCP连接建立好时,
// 将连接标志改为非活动连接,修改计数器
atomic_dec(&dest->activeconns);
atomic_inc(&dest->inactconns);
cp->flags |= IP_VS_CONN_F_INACTIVE;
} else if ((cp->flags & IP_VS_CONN_F_INACTIVE) &&
(new_state == IP_VS_TCP_S_ESTABLISHED)) {
// 如果连接以前是不活动的,新状态是TCP连接建立好时,
// 将连接标志改为活动连接,修改计数器
atomic_inc(&dest->activeconns);
atomic_dec(&dest->inactconns);
cp->flags &= ~IP_VS_CONN_F_INACTIVE;
}
}
}
// 更新连接超时
cp->timeout = pp->timeout_table[cp->state = new_state];
}
"%u.%u.%u.%u:%d state: %s->%s conn->refcnt:%d\n",
pp->name,
(state_off==TCP_DIR_OUTPUT)?"output ":"input ",
th->syn? 'S' : '.',
th->fin? 'F' : '.',
th->ack? 'A' : '.',
th->rst? 'R' : '.',
NIPQUAD(cp->daddr), ntohs(cp->dport),
NIPQUAD(cp->caddr), ntohs(cp->cport),
tcp_state_name(cp->state),
tcp_state_name(new_state),
atomic_read(&cp->refcnt));
if (dest) {
// 连接的目的服务器存在
if (!(cp->flags & IP_VS_CONN_F_INACTIVE) &&
(new_state != IP_VS_TCP_S_ESTABLISHED)) {
// 如果连接是以前是活动的,新状态不是TCP连接建立好时,
// 将连接标志改为非活动连接,修改计数器
atomic_dec(&dest->activeconns);
atomic_inc(&dest->inactconns);
cp->flags |= IP_VS_CONN_F_INACTIVE;
} else if ((cp->flags & IP_VS_CONN_F_INACTIVE) &&
(new_state == IP_VS_TCP_S_ESTABLISHED)) {
// 如果连接以前是不活动的,新状态是TCP连接建立好时,
// 将连接标志改为活动连接,修改计数器
atomic_inc(&dest->activeconns);
atomic_dec(&dest->inactconns);
cp->flags &= ~IP_VS_CONN_F_INACTIVE;
}
}
}
// 更新连接超时
cp->timeout = pp->timeout_table[cp->state = new_state];
}
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 }},
/* 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 }},
/* 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 }},
};
/* 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。
第3个数组是IPVS独有的,专用于处理半连接,因为对于TUNNEL和DR模式,服务器的响应包不经过IPVS, IPVS看到的数据都是单方向的.
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 }},
/* 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 }},
/* 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 }},
};
/* 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 }},
};
7.14 超时变化
timeout_change函数用来变化协议连接的超时,具体就是TCP有两个超时表,用哪个表由本函数决定:
flags参数是由ipvsadm配置时传递来的。
static void tcp_timeout_change(struct ip_vs_protocol *pp, int flags)
{
int on = (flags & 1); /* secure_tcp */
{
int on = (flags & 1); /* 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);
}
** 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);
}
7.15 TCP应用连接绑定
本函数实现将多连接应用协议处理模块和IPVS连接进行绑定。
static int
tcp_app_conn_bind(struct ip_vs_conn *cp)
{
int hash;
struct ip_vs_app *inc;
int result = 0;
tcp_app_conn_bind(struct ip_vs_conn *cp)
{
int hash;
struct ip_vs_app *inc;
int result = 0;
/* Default binding: bind app only for NAT */
// 只在NAT模式下处理
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
return 0;
// 只在NAT模式下处理
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
return 0;
/* Lookup application incarnations and bind the right one */
// 计算一下目的端口的HASH
hash = tcp_app_hashkey(cp->vport);
// 计算一下目的端口的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);
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);
IP_VS_DBG(9, "%s: Binding conn %u.%u.%u.%u:%u->"
"%u.%u.%u.%u:%u to app %s on port %u\n",
__FUNCTION__,
NIPQUAD(cp->caddr), ntohs(cp->cport),
NIPQUAD(cp->vaddr), ntohs(cp->vport),
inc->name, ntohs(inc->port));
// 将连接的应用模块指针指向改应用模块
cp->app = inc;
// 初始化一下应用模块
if (inc->init_conn)
result = inc->init_conn(inc, cp);
goto out;
}
}
spin_unlock(&tcp_app_lock);
"%u.%u.%u.%u:%u to app %s on port %u\n",
__FUNCTION__,
NIPQUAD(cp->caddr), ntohs(cp->cport),
NIPQUAD(cp->vaddr), ntohs(cp->vport),
inc->name, ntohs(inc->port));
// 将连接的应用模块指针指向改应用模块
cp->app = inc;
// 初始化一下应用模块
if (inc->init_conn)
result = inc->init_conn(inc, cp);
goto out;
}
}
spin_unlock(&tcp_app_lock);
out:
return result;
}
return result;
}
7.16 debug_packet
这个函数是TCP/UDP共享的调试函数,输出连接信息
void
ip_vs_tcpudp_debug_packet(struct ip_vs_protocol *pp,
const struct sk_buff *skb,
int offset,
const char *msg)
{
char buf[128];
struct iphdr _iph, *ih;
ip_vs_tcpudp_debug_packet(struct ip_vs_protocol *pp,
const struct sk_buff *skb,
int offset,
const char *msg)
{
char buf[128];
struct iphdr _iph, *ih;
ih = skb_header_pointer(skb, offset, sizeof(_iph), &_iph);
if (ih == NULL)
sprintf(buf, "%s TRUNCATED", pp->name);
else if (ih->frag_off & __constant_htons(IP_OFFSET))
// 分片时只输出IP头信息
sprintf(buf, "%s %u.%u.%u.%u->%u.%u.%u.%u frag",
pp->name, NIPQUAD(ih->saddr),
NIPQUAD(ih->daddr));
else {
__u16 _ports[2], *pptr
;
pptr = skb_header_pointer(skb, offset + ih->ihl*4,
sizeof(_ports), _ports);
if (pptr == NULL)
sprintf(buf, "%s TRUNCATED %u.%u.%u.%u->%u.%u.%u.%u",
pp->name,
NIPQUAD(ih->saddr),
NIPQUAD(ih->daddr));
else
// 输出协议名称,源地址,源端口,目的地址,目的端口信息
sprintf(buf, "%s %u.%u.%u.%u:%u->%u.%u.%u.%u:%u",
pp->name,
NIPQUAD(ih->saddr),
ntohs(pptr[0]),
NIPQUAD(ih->daddr),
ntohs(pptr[1]));
}
// 打印出来
printk(KERN_DEBUG "IPVS: %s: %s\n", msg, buf);
}
if (ih == NULL)
sprintf(buf, "%s TRUNCATED", pp->name);
else if (ih->frag_off & __constant_htons(IP_OFFSET))
// 分片时只输出IP头信息
sprintf(buf, "%s %u.%u.%u.%u->%u.%u.%u.%u frag",
pp->name, NIPQUAD(ih->saddr),
NIPQUAD(ih->daddr));
else {
__u16 _ports[2], *pptr
;
pptr = skb_header_pointer(skb, offset + ih->ihl*4,
sizeof(_ports), _ports);
if (pptr == NULL)
sprintf(buf, "%s TRUNCATED %u.%u.%u.%u->%u.%u.%u.%u",
pp->name,
NIPQUAD(ih->saddr),
NIPQUAD(ih->daddr));
else
// 输出协议名称,源地址,源端口,目的地址,目的端口信息
sprintf(buf, "%s %u.%u.%u.%u:%u->%u.%u.%u.%u:%u",
pp->name,
NIPQUAD(ih->saddr),
ntohs(pptr[0]),
NIPQUAD(ih->daddr),
ntohs(pptr[1]));
}
// 打印出来
printk(KERN_DEBUG "IPVS: %s: %s\n", msg, buf);
}
7.17 设置状态超时
该函数在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);
}
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);
}
/* net/ipv4/ipvs/ip_vs_proto.c */
/*
* Set timeout value for state specified by name
*/
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单位为秒
for (i = 0; i < num; i++) {
if (strcmp(names[i], name))
continue;
table[i] = to * HZ;
return 0;
}
return -ENOENT;
}
return -EINVAL;
// 根据状态名称查找在状态在超时表中的位置然后修改超时时间
// 超时参数to单位为秒
for (i = 0; i < num; i++) {
if (strcmp(names[i], name))
continue;
table[i] = to * HZ;
return 0;
}
return -ENOENT;
}