首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > VSTS >

ip_vs兑现分析(5)

2012-07-28 
ip_vs实现分析(5)本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,

ip_vs实现分析(5)
本文档的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); 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;  } } 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; } 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);  } }}创建状态超时表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;}修改状态超时值/* * Set timeout value for state specified by name */intip_vs_set_state_timeout(int *table, int num, char **names, char *name, int to){ int i; if (!table || !name || !to)  return -EINVAL; for (i = 0; i < num; i++) {// 根据状态名称查找超时位置然后进行修改,超时参数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); if (pp == NULL || pp->state_name == NULL)  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;}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,};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); 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);  out: spin_unlock_bh(&tcp_app_lock); return ret;} 7.4 去掉TCP应用(多连接)协议登记static voidtcp_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 inttcp_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;  }  /*   * 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; 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]); }}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来说就是端口地址 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]); }} 7.8 TCP源NAT操作该函数完成对协议部分数据进行源NAT操作,对TCP来说,NAT部分的数据就是源端口static inttcp_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; 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;  /* Call application helper if needed */  if (!ip_vs_app_pkt_out(cp, pskb))   return 0; } tcph = (void *)(*pskb)->nh.iph + tcphoff;// 修改当前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;}TCP校验和快速计算法,因为只修改了端口一个参数,可根据RFC1141方法快速计算static inline voidtcp_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 }; return csum_fold(csum_partial((char *) diff, sizeof(diff),          oldsum ^ 0xFFFF));}7.9 TCP目的NAT操作该函数完成对协议部分数据进行目的NAT操作,对TCP来说,NAT部分的数据就是目的端口static inttcp_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; 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;  /*   * 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; /*  * 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内核提供标准的校验和计算函数static inttcp_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; } 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] : "?";}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!",};7.12 TCP状态转换IPVS的TCP状态转换和netfilter是类似的,在NAT模式下几乎就是相同的,在TUNNEL和DR模式下是半连接的状态转换。在每个数据包进出IPVS时都会调用static inttcp_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; spin_lock(&cp->lock);// 重新设置连接状态 set_tcp_state(pp, cp, direction, th); spin_unlock(&cp->lock); return 1;}static inline voidset_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; }// 根据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];  tcp_state_out: 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];}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 }},/* 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 }},};这个状态转换表的前两个数组和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 }},/* 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 }},/* 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 }},};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 */ /* ** 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 inttcp_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; /* Lookup application incarnations and bind the right one */// 计算一下目的端口的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);   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);  out: return result;} 7.16 debug_packet这个函数是TCP/UDP共享的调试函数,输出连接信息voidip_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);} 7.17 设置状态超时该函数在ipvsadm设置相关命令时调用.static inttcp_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 */intip_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;} ......待续......

热点排行