本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn

9. IPVS的应用管理

IPVS的应用是针对象FTP等的多连接协议处理的,由于多连接协议的特殊性,任何以连接为基础进行处理的模块如IPVS,netfilter等都必须对这些协议特别处理,不过IPVS相对没有netfilter那么完善,目前也仅仅支持FTP协议,而netfilter已经可以支持FTP、TFTP、IRC、AMANDA、MMS、SIP、H.323等多种多连接协议。

IPVS应用也是模块化的,不过其实现有点特别,对于每一个应用协议,会定义一个静态的struct ip_vs_app结构作为模板,以后登记该协议时,对应的应用指针并不是直接指向这个静态结构,而是新分配一个struct ip_vs_app结构,结构中的struct ip_vs_app指针指向这个静态结构,然后把新分配的这个结构分别挂接到静态struct ip_vs_app结构的具体实现链表和IP协议的应用HASH链表中进行使用,这种实现方法和netfilter完全不同。

IPVS应用一些共享的处理函数在net/ipv4/ipvs/ip_vs_app.c中定义,其他各协议相关处理分别由各自文件处理,如net/ipv4/ipvs/ip_vs_ftp.c.

9.1 新建应用结构
/*
 * Allocate/initialize app incarnation and register it in proto apps.
 */
// 新建一个应用实例,注意输入参数除了协议端口外,还需要提供一个应用模板的指针
// 而且函数并不直接返回应用结构本身,而是在函数中新建的应用实例直接挂接到链表中
// 只返回建立成功(0)或失败(<0)
static int
ip_vs_app_inc_new(struct ip_vs_app *app, __u16 proto, __u16 port)
{
 struct ip_vs_protocol *pp;
 struct ip_vs_app *inc;
 int ret;
// 查找IPVS协议结构
 if (!(pp = ip_vs_proto_get(proto)))
  return -EPROTONOSUPPORT;
// 协议中不能只有应用登记函数而没有拆除函数
 if (!pp->unregister_app)
  return -EOPNOTSUPP;
// 分配 应用结构内存
 inc = kmalloc(sizeof(struct ip_vs_app), GFP_KERNEL);
 if (!inc)
  return -ENOMEM;
// 将应用模板中的内容全部拷贝到新应用结构中
 memcpy(inc, app, sizeof(*inc));
// 所有应用链表
 INIT_LIST_HEAD(&inc->p_list);
// 应用实例链表
 INIT_LIST_HEAD(&inc->incs_list);
// 应用实例中指向模板本身的指针
 inc->app = app;
// 应用协议的端口
 inc->port = htons(port);
// 实例的使用计数
 atomic_set(&inc->usecnt, 0);
if (app->timeouts) {
// 建立应用协议状态超时数组
  inc->timeout_table =
   ip_vs_create_timeout_table(app->timeouts,
         app->timeouts_size);
  if (!inc->timeout_table) {
   ret = -ENOMEM;
   goto out;
  }
 }
// 将应用实例向IP协议结构登记
 ret = pp->register_app(inc);
 if (ret)
  goto out;
// 将应用实例添加到应用模板的实例链表
 list_add(&inc->a_list, &app->incs_list);
 IP_VS_DBG(9, "%s application %s:%u registered\n",
    pp->name, inc->name, inc->port);
return 0;
out:
 kfree(inc->timeout_table);
 kfree(inc);
 return ret;
}

9.2 释放应用结构

/*
 * Release app incarnation
 */
static void
ip_vs_app_inc_release(struct ip_vs_app *inc)
{
 struct ip_vs_protocol *pp;
// 找协议结构指针
 if (!(pp = ip_vs_proto_get(inc->protocol)))
  return;
// 调用协议的应用拆除函数,这里应该不用进行判断的
 if (pp->unregister_app)
  pp->unregister_app(inc);
IP_VS_DBG(9, "%s App %s:%u unregistered\n",
    pp->name, inc->name, inc->port);
// 从所有应用链表中删除
 list_del(&inc->a_list);
// 释放超时表,这里怎么不检查timeout_table是否存在呢?
// 分配应用实例的时候有可能不分配的
 kfree(inc->timeout_table);
// 释放结构本身
 kfree(inc);
}
9.3 登记应用

/*
 * Register an application incarnation in protocol applications
 */
// 登记应用实例,新建并登记
int
register_ip_vs_app_inc(struct ip_vs_app *app, __u16 proto, __u16 port)
{
 int result;
mutex_lock(&__ip_vs_app_mutex);
// 新生成一个应用实例并进行登记
 result = ip_vs_app_inc_new(app, proto, port);
mutex_unlock(&__ip_vs_app_mutex);
return result;
}

/*
 * ip_vs_app registration routine
 */
// 登记应用, 只登记
int register_ip_vs_app(struct ip_vs_app *app)
{
 /* increase the module use count */
 ip_vs_use_count_inc();
mutex_lock(&__ip_vs_app_mutex);
// 直接将应用挂接到IPVS的应用链表中
 list_add(&app->a_list, &ip_vs_app_list);
mutex_unlock(&__ip_vs_app_mutex);
return 0;
}

9.4 拆除应用
/*
 * ip_vs_app unregistration routine
 * We are sure there are no app incarnations attached to services
 */
void unregister_ip_vs_app(struct ip_vs_app *app)
{
 struct ip_vs_app *inc, *nxt;
mutex_lock(&__ip_vs_app_mutex);
list_for_each_entry_safe(inc, nxt, &app->incs_list, a_list) {
  ip_vs_app_inc_release(inc);
 }
list_del(&app->a_list);
mutex_unlock(&__ip_vs_app_mutex);
/* decrease the module use count */
 ip_vs_use_count_dec();
}

9.5 应用与连接的绑定和拆除
/*
 * Bind ip_vs_conn to its ip_vs_app (called by cp constructor)
 */
int ip_vs_bind_app(struct ip_vs_conn *cp, struct ip_vs_protocol *pp)
{
// 直接调用协议的app_conn_bind函数处理,通过连接端口查找应用
 return pp->app_conn_bind(cp);
}

/*
 * Unbind cp from application incarnation (called by cp destructor)
 */
void ip_vs_unbind_app(struct ip_vs_conn *cp)
{
 struct ip_vs_app *inc = cp->app;
if (!inc)
  return;
// 分别调用应用实例的unbind_conn和done_conn函数拆除连接中应用的绑定
 if (inc->unbind_conn)
  inc->unbind_conn(inc, cp);
 if (inc->done_conn)
  inc->done_conn(inc, cp);
 ip_vs_app_inc_put(inc);
 cp->app = NULL;
}

9.6 处理输出方向的应用数据

应用协议修改输出方向的应用层数据,在协议的snat_handler()函数中调用
/*
 * Output pkt hook. Will call bound ip_vs_app specific function
 * called by ipvs packet handler, assumes previously checked cp!=NULL
 * returns false if it can't handle packet (oom)
 */
int ip_vs_app_pkt_out(struct ip_vs_conn *cp, struct sk_buff **pskb)
{
 struct ip_vs_app *app;
/*
  * check if application module is bound to
  * this ip_vs_conn.
  */
// 检查连接是否和应用绑定
 if ((app = cp->app) == NULL)
  return 1;
/* TCP is complicated */
 if (cp->protocol == IPPROTO_TCP)
// TCP协议另外单独处理
  return app_tcp_pkt_out(cp, pskb, app);
/*
  * Call private output hook function
  */
 if (app->pkt_out == NULL)
  return 1;
// 非TCP协议调用应用协议的pkt_out()函数
 return app->pkt_out(app, cp, pskb, NULL);
}
// 处理TCP应用发出方向的数据包
static inline int app_tcp_pkt_out(struct ip_vs_conn *cp, struct sk_buff **pskb,
      struct ip_vs_app *app)
{
 int diff;
// 现在就计算偏移值有点危险,最好在数据包可写操作完再设
 unsigned int tcp_offset = (*pskb)->nh.iph->ihl*4;
 struct tcphdr *th;
 __u32 seq;
// 首先要让数据包可写
 if (!ip_vs_make_skb_writable(pskb, tcp_offset + sizeof(*th)))
  return 0;
th = (struct tcphdr *)((*pskb)->nh.raw + tcp_offset);
/*
  * Remember seq number in case this pkt gets resized
  */
// 当前的序列号
 seq = ntohl(th->seq);
/*
  * Fix seq stuff if flagged as so.
  */
 if (cp->flags & IP_VS_CONN_F_OUT_SEQ)
// 修改发出方向序列号
  vs_fix_seq(&cp->out_seq, th);
 if (cp->flags & IP_VS_CONN_F_IN_SEQ)
// 修改进入方向序列号
  vs_fix_ack_seq(&cp->in_seq, th);
/*
  * Call private output hook function
  */
 if (app->pkt_out == NULL)
  return 1;
// 调用应用协议的pkt_out()函数
 if (!app->pkt_out(app, cp, pskb, &diff))
  return 0;
/*
  * Update ip_vs seq stuff if len has changed.
  */
 if (diff != 0)
// 数据长度发生变化,再次修改发出方向的序列号
  vs_seq_update(cp, &cp->out_seq,
         IP_VS_CONN_F_OUT_SEQ, seq, diff);
return 1;
}

9.6 处理进入方向的应用数据

应用协议修改进入方向的应用层数据,在协议的dnat_handler()函数中调用

/*
 * Input pkt hook. Will call bound ip_vs_app specific function
 * called by ipvs packet handler, assumes previously checked cp!=NULL.
 * returns false if can't handle packet (oom).
 */
int ip_vs_app_pkt_in(struct ip_vs_conn *cp, struct sk_buff **pskb)
{
 struct ip_vs_app *app;
/*
  * check if application module is bound to
  * this ip_vs_conn.
  */
// 检查连接是否和应用绑定
 if ((app = cp->app) == NULL)
  return 1;
/* TCP is complicated */
 if (cp->protocol == IPPROTO_TCP)
// TCP协议另外单独处理
  return app_tcp_pkt_in(cp, pskb, app);
/*
  * Call private input hook function
  */
 if (app->pkt_in == NULL)
  return 1;
// 非TCP协议调用应用协议的pkt_out()函数
 return app->pkt_in(app, cp, pskb, NULL);
}
// 处理TCP应用进入方向的数据包
static inline int app_tcp_pkt_in(struct ip_vs_conn *cp, struct sk_buff **pskb,
     struct ip_vs_app *app)
{
 int diff;
// 现在就计算偏移值有点危险,最好在数据包可写操作完再设
 unsigned int tcp_offset = (*pskb)->nh.iph->ihl*4;
 struct tcphdr *th;
 __u32 seq;
// 首先要让数据包可写
 if (!ip_vs_make_skb_writable(pskb, tcp_offset + sizeof(*th)))
  return 0;
th = (struct tcphdr *)((*pskb)->nh.raw + tcp_offset);
/*
  * Remember seq number in case this pkt gets resized
  */
// 当前的序列号
 seq = ntohl(th->seq);
/*
  * Fix seq stuff if flagged as so.
  */
 if (cp->flags & IP_VS_CONN_F_IN_SEQ)
// 修改进入方向序列号
  vs_fix_seq(&cp->in_seq, th);
 if (cp->flags & IP_VS_CONN_F_OUT_SEQ)
// 修改发出方向序列号
  vs_fix_ack_seq(&cp->out_seq, th);
/*
  * Call private input hook function
  */
 if (app->pkt_in == NULL)
  return 1;
// 调用应用协议的pkt_in()函数
 if (!app->pkt_in(app, cp, pskb, &diff))
  return 0;
/*
  * Update ip_vs seq stuff if len has changed.
  */
 if (diff != 0)
// 数据长度发生变化,再次修改输入方向的序列号
  vs_seq_update(cp, &cp->in_seq,
         IP_VS_CONN_F_IN_SEQ, seq, diff);
return 1;
}

9.7 修改数据包中内容

将skb包中某段数据更改为新的数据,是一个通用函数,可供应用协议修改协议数据的函数调用,类似于netfilter的mangle_contents()函数.
/*
 * Replace a segment of data with a new segment
 */
int ip_vs_skb_replace(struct sk_buff *skb, gfp_t pri,
        char *o_buf, int o_len, char *n_buf, int n_len)
{
 struct iphdr *iph;
 int diff;
 int o_offset;
 int o_left;
EnterFunction(9);
// 新数据和老数据的长度差,这影响序列号和确认号
 diff = n_len - o_len;
// 老数据在数据包中的偏移
 o_offset = o_buf - (char *)skb->data;
 /* The length of left data after o_buf+o_len in the skb data */
// 老数据左边的第一个数据
 o_left = skb->len - (o_offset + o_len);
if (diff <= 0) {
// 新长度不大于老长度,把原来老数据右边的数据移过来
  memmove(o_buf + n_len, o_buf + o_len, o_left);
// 老数据部分用新数据替代
  memcpy(o_buf, n_buf, n_len);
// 减少数据包的长度
  skb_trim(skb, skb->len + diff);
 } else if (diff <= skb_tailroom(skb)) {
// 新长度大于老长度,但skb包后面的空闲区可以容纳下新数据
// 扩展数据包长
  skb_put(skb, diff);
// 移老数据右边的数据
  memmove(o_buf + n_len, o_buf + o_len, o_left);
// 拷贝新数据
  memcpy(o_buf, n_buf, n_len);
 } else {
// 新长度大于老长度,但skb包后面的空闲区也容纳不下新数据
// 需要重新扩展skb大小
  if (pskb_expand_head(skb, skb_headroom(skb), diff, pri))
   return -ENOMEM;
// 扩展数据包长
  skb_put(skb, diff);
// 移老数据右边的数据
  memmove(skb->data + o_offset + n_len,
   skb->data + o_offset + o_len, o_left);
// 拷贝新数据
  memcpy(skb->data + o_offset, n_buf, n_len);
 }
/* must update the iph total length here */
 iph = skb->nh.iph;
 iph->tot_len = htons(skb->len);
LeaveFunction(9);
 return 0;
}

9.8 应用实例: FTP

在IPVS中只实现了对FTP的处理,具体代码在net/ipv4/ipvs/ip_vs_ftp.c中实现.
9.8.0 FTP协议应用结构模板
static struct ip_vs_app ip_vs_ftp = {
 .name =  "ftp",
 .type =  IP_VS_APP_TYPE_FTP,
 .protocol = IPPROTO_TCP,
 .module = THIS_MODULE,
 .incs_list = LIST_HEAD_INIT(ip_vs_ftp.incs_list),
 .init_conn = ip_vs_ftp_init_conn,
 .done_conn = ip_vs_ftp_done_conn,
 .bind_conn = NULL,
 .unbind_conn = NULL,
 .pkt_out = ip_vs_ftp_out,
 .pkt_in = ip_vs_ftp_in,
};

9.8.1 应用初始化和删除

/*
 * ip_vs_ftp initialization
 */
static int __init ip_vs_ftp_init(void)
{
 int i, ret;
 struct ip_vs_app *app = &ip_vs_ftp;
// 登记FTP应用模板
 ret = register_ip_vs_app(app);
 if (ret)
  return ret;
// 可从模块插入时输入端口参数指定在哪些端口上进行FTP应用绑定
 for (i=0; i<IP_VS_APP_MAX_PORTS; i++) {
  if (!ports[i])
   continue;
// 新建应用实例
  ret = register_ip_vs_app_inc(app, app->protocol, ports[i]);
  if (ret)
   break;
  IP_VS_DBG(1-debug, "%s: loaded support on port[%d] = %d\n",
     app->name, i, ports[i]);
 }
if (ret)
  unregister_ip_vs_app(app);
return ret;
}

/*
 * ip_vs_ftp finish.
 */
static void __exit ip_vs_ftp_exit(void)
{
// 直接拆除FTP应用协议模板
 unregister_ip_vs_app(&ip_vs_ftp);
}
9.8.2 初始化FTP连接
空函数
static int
ip_vs_ftp_init_conn(struct ip_vs_app *app, struct ip_vs_conn *cp)
{
 return 0;
}

9.8.3 结束FTP连接
空函数
static int
ip_vs_ftp_done_conn(struct ip_vs_app *app, struct ip_vs_conn *cp)
{
 return 0;
}

9.8.4 处理FTP进入方向数据

进入方向的数据是FTP客户端发出的, 和子连接相关的命令为PORT命令,建立一个主动模式的子连接
/*
 * Look at incoming ftp packets to catch the PASV/PORT command
 * (outside-to-inside).
 *
 * The incoming packet having the PORT command should be something like
 *      "PORT xxx,xxx,xxx,xxx,ppp,ppp\n".
 * xxx,xxx,xxx,xxx is the client address, ppp,ppp is the client port number.
 * In this case, we create a connection entry using the client address and
 * port, so that the active ftp data connection from the server can reach
 * the client.
 */
static int ip_vs_ftp_in(struct ip_vs_app *app, struct ip_vs_conn *cp,
   struct sk_buff **pskb, int *diff)
{
 struct iphdr *iph;
 struct tcphdr *th;
 char *data, *data_start, *data_limit;
 char *start, *end;
 __u32 to;
 __u16 port;
 struct ip_vs_conn *n_cp;
/* no diff required for incoming packets */
 *diff = 0;
/* Only useful for established sessions */
// 发子连接信息数据时主连接必然是TCP连接建立好状态,否则就出错
 if (cp->state != IP_VS_TCP_S_ESTABLISHED)
  return 1;
/* Linear packets are much easier to deal with. */
// 让数据包可写
 if (!ip_vs_make_skb_writable(pskb, (*pskb)->len))
  return 0;
/*
  * Detecting whether it is passive
  */
// 协议头指针定位
 iph = (*pskb)->nh.iph;
 th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
/* Since there may be OPTIONS in the TCP packet and the HLEN is
    the length of the header in 32-bit multiples, it is accurate
    to calculate data address by th+HLEN*4 */
// 数据定位
 data = data_start = (char *)th + (th->doff << 2);
 data_limit = (*pskb)->tail;
// 防止数据越界
 while (data <= data_limit - 6) {
  if (strnicmp(data, "PASV\r\n", 6) == 0) {
// PASV命令,表示要进入被动模式
   /* Passive mode on */
   IP_VS_DBG(1-debug, "got PASV at %zd of %zd\n",
      data - data_start,
      data_limit - data_start);
   cp->app_data = &ip_vs_ftp_pasv;
   return 1;
  }
  data++;
 }
/*
  * To support virtual FTP server, the scenerio is as follows:
  *       FTP client ----> Load Balancer ----> FTP server
  * First detect the port number in the application data,
  * then create a new connection entry for the coming data
  * connection.
  */
// 查找FTP数据是否是PORT命令,提取出地址端口信息及其位置
 if (ip_vs_ftp_get_addrport(data_start, data_limit,
       CLIENT_STRING, sizeof(CLIENT_STRING)-1,
       '\r', &to, &port,
       &start, &end) != 1)
  return 1;
IP_VS_DBG(1-debug, "PORT %u.%u.%u.%u:%d detected\n",
    NIPQUAD(to), ntohs(port));
/* Passive mode off */
 cp->app_data = NULL;
/*
  * Now update or create a connection entry for it
  */
 IP_VS_DBG(1-debug, "protocol %s %u.%u.%u.%u:%d %u.%u.%u.%u:%d\n",
    ip_vs_proto_name(iph->protocol),
    NIPQUAD(to), ntohs(port), NIPQUAD(cp->vaddr), 0);
// 用找到的地址端口和服务器虚地址虚端口找连接
 n_cp = ip_vs_conn_in_get(iph->protocol,
     to, port,
     cp->vaddr, htons(ntohs(cp->vport)-1));
 if (!n_cp) {
// 找不到连接,这是大部分的情况
// 新建连接作为子连接
  n_cp = ip_vs_conn_new(IPPROTO_TCP,
          to, port,
          cp->vaddr, htons(ntohs(cp->vport)-1),
          cp->daddr, htons(ntohs(cp->dport)-1),
          0,
          cp->dest);
  if (!n_cp)
   return 0;
/* add its controller */
// 子连接和主连接相连
// 不需要修改数据内容
  ip_vs_control_add(n_cp, cp);
 }
/*
  * Move tunnel to listen state
  */
// 将子连接状态设置为监听状态
 ip_vs_tcp_conn_listen(n_cp);
 ip_vs_conn_put(n_cp);
return 1;
}

/* net/ipv4/ipvs/ip_vs_proto_tcp.c */
/*
 * Set LISTEN timeout. (ip_vs_conn_put will setup timer)
 */
void ip_vs_tcp_conn_listen(struct ip_vs_conn *cp)
{
 spin_lock(&cp->lock);
// 连接状态为监听
 cp->state = IP_VS_TCP_S_LISTEN;
// 连接超时为监听状态的超时
 cp->timeout = ip_vs_protocol_tcp.timeout_table[IP_VS_TCP_S_LISTEN];
 spin_unlock(&cp->lock);
}

从FTP数据中提取IP地址和端口值
/*
 * Get <addr,port> from the string "xxx.xxx.xxx.xxx,ppp,ppp", started
 * with the "pattern" and terminated with the "term" character.
 * <addr,port> is in network order.
 */
static int ip_vs_ftp_get_addrport(char *data, char *data_limit,
      const char *pattern, size_t plen, char term,
      __u32 *addr, __u16 *port,
      char **start, char **end)
{
 unsigned char p[6];
 int i = 0;
if (data_limit - data < plen) {
  /* check if there is partial match */
  if (strnicmp(data, pattern, data_limit - data) == 0)
   return -1;
  else
   return 0;
 }
// 模式匹配,"PORT "或"227 "
 if (strnicmp(data, pattern, plen) != 0) {
  return 0;
 }
 *start = data + plen;
for (data = *start; *data != term; data++) {
  if (data == data_limit)
   return -1;
 }
 *end = data;
memset(p, 0, sizeof(p));
// 解析出6个数值
 for (data = *start; data != *end; data++) {
  if (*data >= '0' && *data <= '9') {
   p[i] = p[i]*10 + *data - '0';
  } else if (*data == ',' && i < 5) {
   i++;
  } else {
   /* unexpected character */
   return -1;
  }
 }
if (i != 5)
  return -1;
// 前4个是地址
 *addr = (p[3]<<24) | (p[2]<<16) | (p[1]<<8) | p[0];
// 后两个是端口
 *port = (p[5]<<8) | p[4];
 return 1;
}

9.8.5 处理FTP发出数据
发出方向的数据是FTP服务器发出的, 和子连接相关的回应为227类型回应,建立一个被动模式的子连接
/*
 * Look at outgoing ftp packets to catch the response to a PASV command
 * from the server (inside-to-outside).
 * When we see one, we build a connection entry with the client address,
 * client port 0 (unknown at the moment), the server address and the
 * server port.  Mark the current connection entry as a control channel
 * of the new entry. All this work is just to make the data connection
 * can be scheduled to the right server later.
 *
 * The outgoing packet should be something like
 *   "227 Entering Passive Mode (xxx,xxx,xxx,xxx,ppp,ppp)".
 * xxx,xxx,xxx,xxx is the server address, ppp,ppp is the server port number.
 */
static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp,
    struct sk_buff **pskb, int *diff)
{
 struct iphdr *iph;
 struct tcphdr *th;
 char *data, *data_limit;
 char *start, *end;
 __u32 from;
 __u16 port;
 struct ip_vs_conn *n_cp;
 char buf[24];  /* xxx.xxx.xxx.xxx,ppp,ppp\000 */
 unsigned buf_len;
 int ret;
*diff = 0;
/* Only useful for established sessions */
// 发子连接信息数据时主连接必然是TCP连接建立好状态,否则就出错
 if (cp->state != IP_VS_TCP_S_ESTABLISHED)
  return 1;
/* Linear packets are much easier to deal with. */
// 让数据包可写
 if (!ip_vs_make_skb_writable(pskb, (*pskb)->len))
  return 0;
// 子连接必须是被动模式的
 if (cp->app_data == &ip_vs_ftp_pasv) {
// 数据定位
  iph = (*pskb)->nh.iph;
  th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
  data = (char *)th + (th->doff << 2);
  data_limit = (*pskb)->tail;
// 查找"227 "回应中的地址端口信息
  if (ip_vs_ftp_get_addrport(data, data_limit,
        SERVER_STRING,
        sizeof(SERVER_STRING)-1, ')',
        &from, &port,
        &start, &end) != 1)
   return 1;
IP_VS_DBG(1-debug, "PASV response (%u.%u.%u.%u:%d) -> "
     "%u.%u.%u.%u:%d detected\n",
     NIPQUAD(from), ntohs(port), NIPQUAD(cp->caddr), 0);
/*
   * Now update or create an connection entry for it
   */
// 查找发出方向的连接
  n_cp = ip_vs_conn_out_get(iph->protocol, from, port,
       cp->caddr, 0);
  if (!n_cp) {
// 正常情况下是找不到的
// 新建子连接, 注意各地址端口参数的位置
   n_cp = ip_vs_conn_new(IPPROTO_TCP,
           cp->caddr, 0,
           cp->vaddr, port,
           from, port,
           IP_VS_CONN_F_NO_CPORT,
           cp->dest);
   if (!n_cp)
    return 0;
/* add its controller */
// 将子连接和主连接联系起来
   ip_vs_control_add(n_cp, cp);
  }
/*
   * Replace the old passive address with the new one
   */
// 新地址端口用连接的虚拟地址和端口
// 需要修改数据包中的数据
  from = n_cp->vaddr;
  port = n_cp->vport;
// 修改后的地址端口信息
  sprintf(buf,"%d,%d,%d,%d,%d,%d", NIPQUAD(from),
   port&255, (port>>8)&255);
  buf_len = strlen(buf);
/*
   * Calculate required delta-offset to keep TCP happy
   */
// 检查数据长度差异
  *diff = buf_len - (end-start);
if (*diff == 0) {
   /* simply replace it with new passive address */
// 长度相同的话直接覆盖就行了
   memcpy(start, buf, buf_len);
   ret = 1;
  } else {
// 修改数据
   ret = !ip_vs_skb_replace(*pskb, GFP_ATOMIC, start,
       end-start, buf, buf_len);
  }
cp->app_data = NULL;
// 连接状态设为监听
  ip_vs_tcp_conn_listen(n_cp);
// 减少连接引用计数
  ip_vs_conn_put(n_cp);
  return ret;
 }
 return 1;
}

转载于:https://www.cnblogs.com/qq78292959/archive/2012/07/12/2587836.html

ip_vs实现分析(7)相关推荐

  1. ip_vs实现分析(2)

    本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途. msn: yfydz_no1@hotmail.com 来源:http:/ ...

  2. 使用Kubeadm来搭建k8s-v1.18.2(包含所有错误集锦分析)

    k8s的搭建 使用 Kubeadm来搭建master集群,⽬前所安装的版本是 v1.18.2 欢迎运维萌新大佬等进群,涵盖业务运维.应用运维.系统运维.网络运维.数据库运维.桌面运维.运维开发等,地区 ...

  3. K8S kube-proxy ipvs 原理分析

    1 在k8s 设置ipvs模式 1.1 Perquisites 1.2 修改kube-proxy 启动参数 2 ipvs kube-proxy原理分析 2.1 集群内部发送出去的packet通过clu ...

  4. 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析

    目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...

  5. 2022-2028年中国自动驾驶系统行业现状调研分析报告

    [报告类型]产业研究 [报告价格]4500起 [出版时间]即时更新(交付时间约3个工作日) [发布机构]智研瞻产业研究院 [报告格式]PDF版 本报告介绍了中国自动驾驶系统行业市场行业相关概述.中国自 ...

  6. 2022-2028年中国阻尼涂料市场研究及前瞻分析报告

    [报告类型]产业研究 [报告价格]4500起 [出版时间]即时更新(交付时间约3个工作日) [发布机构]智研瞻产业研究院 [报告格式]PDF版 本报告介绍了中国阻尼涂料行业市场行业相关概述.中国阻尼涂 ...

  7. 2021-2028年中国阻燃装饰行业市场需求与投资规划分析报告

    [报告类型]产业研究 [报告价格]4500起 [出版时间]即时更新(交付时间约3个工作日) [发布机构]智研瞻产业研究院 [报告格式]PDF版 本报告介绍了中国阻燃装饰行业市场行业相关概述.中国阻燃装 ...

  8. 2022-2028年全球与中国漂白吸水棉市场研究及前瞻分析报告

    [报告类型]产业研究 [报告价格]4500起 [出版时间]即时更新(交付时间约3个工作日) [发布机构]智研瞻产业研究院 [报告格式]PDF版 本报告介绍了全球与中国漂白吸水棉行业市场行业相关概述.全 ...

  9. 2022-2028年全球与中国青苔清洗剂市场研究及前瞻分析报告

    [报告类型]产业研究 [报告价格]4500起 [出版时间]即时更新(交付时间约3个工作日) [发布机构]智研瞻产业研究院 [报告格式]PDF版 本报告介绍了全球与中国青苔清洗剂行业市场行业相关概述.全 ...

最新文章

  1. 如何把一个用户加入sodu组
  2. JS 之 innerHTML
  3. 20170817 - 今日技能封装 - A
  4. docker使用镜像报错:standard_init_linux.go:211: exec user process caused “exec format error“
  5. java jni librtmp_librtmp 编译集成
  6. mysql sql select for update_mysql SELECT FOR UPDATE语句使用示例
  7. 构建高性能.NET应用之配置高可用IIS服务器-第五篇 IIS常见问题之:工作进程回收机制(中)...
  8. Linux内核深入理解中断和异常(8):串口驱动程序
  9. PRML笔记 第一章 Introduction
  10. yolov1-v5学习笔记及源码解读
  11. koa 的 Context
  12. hdoj 3533 Escape
  13. linux 压缩文件与解压文件
  14. 【智能制造】工业软件:智能制造的大脑
  15. wordpress 替换国外主题字体为微软雅黑
  16. 你必须知道alexa排名的重要性
  17. C++类模板怎么写在单独的头文件和源文件中
  18. 科技让教育更美好:跟谁学更名高途,重新定义教育场景
  19. ipad越狱常用软件
  20. springboot+vue+elementUI用户头像获取

热门文章

  1. 【VS+QT开发】找不到到qhostinfo.h文件
  2. mysql中主从_MySQL的主从
  3. 【HDU5306】【DTOJ2481】Gorgeous Sequence【线段树】
  4. codeforces271D
  5. Jmeter创建一个点对点的 JMS 测试计划
  6. docker容器相关
  7. 【ECSHOP】格式化商品价格
  8. Java——Java封装
  9. T-Sql 实现类似访问数组变量的操作
  10. Ubuntu 9.04安设教程(傻瓜版)