LVS包转发功能由内核模块IPVS实现。Keepalived的Check进程周期性地对后端RealServer进行健康检测,根据检测结果摘除或恢复。摘除和恢复RealServer等操作本质上为Keepalived这个用户态进程与IPVS内核模块的通信操作。
libipvs封装了用户态程序对内核模块IPVS可以进行的操作,如:
创建LVS服务 
删除LVS服务 
添加RealServer 
删除RealServer 
获取相关信息 
 
我们以2.6版本内核的libipvs为例来简单分析,源码文件位于keepalived/libipvs-2.6下。
IPVS内核模块实现了两种方式供用户态程序来进行上述操作:
首先看ipvs_init函数,在使用libipvs前应该先调用这个函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int  ipvs_init (void ) {     socklen_t  len;     ipvs_func = ipvs_init; #ifdef  LIBIPVS_USE_NL     try_nl = 1 ;     if  (ipvs_nl_send_message(NULL , NULL , NULL ) == 0 ) {         try_nl = 1 ;         return  ipvs_getinfo();     }     try_nl = 0 ; #endif      len = sizeof (ipvs_info);     if  ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1 )         return  -1 ;     if  (getsockopt(sockfd, IPPROTO_IP, IP_VS_SO_GET_INFO,                (char  *)&ipvs_info, &len))         return  -1 ;     return  0 ; } 
当编译keepalived时使用了libnl库时,宏LIBIPVS_USE_NL会被定义, 则首先尝试使用NETLINK方式进行操作。若没有使用libnl或者尝试NETLINK失败,则使用sockopt方式。该方式需要一个socket, ipvs_init函数将创建的socket存储在全局变量sockfd中。
sockopt方式就是根据相应操作确定sockopt的值,指定好相应的参数信息需要存储或已经存储的位置,简单的调用getsockopt或setsockopt来完成操作。我们重点分析NETLINK方式。
来看ipvs_init调用的ipvs_nl_send_message, 简单逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 int  ipvs_nl_send_message (struct  nl_msg *msg, nl_recvmsg_msg_cb_t  func, void  *arg) {     sock = nl_socket_alloc();     ...     if  (genl_connect(sock) < 0 )         goto  fail_genl;     family = genl_ctrl_resolve(sock, IPVS_GENL_NAME);     ...          if  (msg == NULL ) {         nl_socket_free(sock);         sock = NULL ;         return  0 ;     }         if  (nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, func, arg) != 0 )         goto  fail_genl;     if  (nl_send_auto_complete(sock, msg) < 0 )         goto  fail_genl;     if  ((err = -nl_recvmsgs_default(sock)) > 0 )         goto  fail_genl;     nlmsg_free(msg);     nl_socket_free(sock);     return  0 ;     ... } 
若传入的msg参数为NULL,ipvs_nl_send_message函数只是测试下NETLINK是否可用。否则,设置NETLINK响应消息的处理回调函数,发送该消息,NETLINK响应消息到达后,回调函数被调用来处理该消息。
libipvs的基本所有的NETLINK操作流程为:
构造NETLINK消息 
调用ipvs_nl_send_message处理 
 
以添加LVS服务为例说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int  ipvs_add_service (ipvs_service_t  *svc) {     ipvs_func = ipvs_add_service; #ifdef  LIBIPVS_USE_NL     if  (try_nl) {         struct  nl_msg  *msg  =0 );         if  (!msg) return  -1 ;         if  (ipvs_nl_fill_service_attr(msg, svc)) {             nlmsg_free(msg);             return  -1 ;         }         return  ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL );     } #endif      CHECK_COMPAT_SVC(svc, -1 );     return  setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_ADD, (char  *)svc,               sizeof (struct  ip_vs_service_kern)); out_err:     return  -1 ; } 
若使用NETLINK方式操作,首先调用ipvs_nl_message构建一条NETLINK消息:
1 2 3 4 5 6 7 8 9 10 11 12 13 struct  nl_msg *ipvs_nl_message (int  cmd, int  flags) {     struct  nl_msg  *msg ;     msg = nlmsg_alloc();     if  (!msg)         return  NULL ;     genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, family, 0 , flags,             cmd, IPVS_GENL_VERSION);     return  msg; } 
然后调用ipvs_nl_fill_service_attr将添加LVS服务所需的参数以NETLINK Attributes方式填充到NETLINK消息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static  int  ipvs_nl_fill_service_attr (struct  nl_msg *msg, ipvs_service_t  *svc) {     struct  nlattr  *nl_service ;     struct  ip_vs_flags  flags  =                      .mask = ~0  };     nl_service = nla_nest_start(msg, IPVS_CMD_ATTR_SERVICE);     if  (!nl_service)         return  -1 ;     NLA_PUT_U16(msg, IPVS_SVC_ATTR_AF, svc->af);     if  (svc->fwmark) {         NLA_PUT_U32(msg, IPVS_SVC_ATTR_FWMARK, svc->fwmark);     } else  {         NLA_PUT_U16(msg, IPVS_SVC_ATTR_PROTOCOL, svc->protocol);         NLA_PUT(msg, IPVS_SVC_ATTR_ADDR, sizeof (svc->addr), &(svc->addr));         NLA_PUT_U16(msg, IPVS_SVC_ATTR_PORT, svc->port);     }     NLA_PUT_STRING(msg, IPVS_SVC_ATTR_SCHED_NAME, svc->sched_name);     if  (svc->pe_name[0 ])         NLA_PUT_STRING(msg, IPVS_SVC_ATTR_PE_NAME, svc->pe_name);     NLA_PUT(msg, IPVS_SVC_ATTR_FLAGS, sizeof (flags), &flags);     NLA_PUT_U32(msg, IPVS_SVC_ATTR_TIMEOUT, svc->timeout);     NLA_PUT_U32(msg, IPVS_SVC_ATTR_NETMASK, svc->netmask);     nla_nest_end(msg, nl_service);     return  0 ; nla_put_failure:     return  -1 ; } 
最后调用ipvs_nl_send_message发送消息,因为添加LVS服务没有响应需要处理,回调函数设为ipvs_nl_noop_cb:
1 2 3 4 static  int  ipvs_nl_noop_cb (struct  nl_msg *msg, void  *arg) {     return  NL_OK; } 
以获取所有LVS服务说明读取LVS相关信息的过程。ipvs_get_services用于获取所有的LVS服务,简单的逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 struct  ip_vs_get_services *ipvs_get_services (void ) {     struct  ip_vs_get_services  *get ;     struct  ip_vs_get_services_kern  *getk ;     socklen_t  len;     int  i; #ifdef  LIBIPVS_USE_NL     if  (try_nl) {         struct  nl_msg  *msg ;         len = sizeof (*get) + sizeof (ipvs_service_entry_t );         if  (!(get = malloc (len)))             return  NULL ;         get->num_services = 0 ;         msg = ipvs_nl_message(IPVS_CMD_GET_SERVICE, NLM_F_DUMP);         if  (msg && (ipvs_nl_send_message(msg, ipvs_services_parse_cb, &get) == 0 ))             return  get;         free (get);         return  NULL ;     } #endif      ...     return  get; } 
首先,分配好存储一个LVS服务所需要的内存空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 static  int  ipvs_services_parse_cb (struct  nl_msg *msg, void  *arg) {     struct  nlmsghdr  *nlh  =     struct  nlattr  *attrs [IPVS_CMD_ATTR_MAX  + 1];     struct  nlattr  *svc_attrs [IPVS_SVC_ATTR_MAX  + 1];     struct  ip_vs_get_services  **getp  =struct  ip_vs_get_services **)arg;     struct  ip_vs_get_services  *get  =struct  ip_vs_get_services *)*getp;     struct  ip_vs_flags  flags ;     int  i = get->num_services;     if  (genlmsg_parse(nlh, 0 , attrs, IPVS_CMD_ATTR_MAX, ipvs_cmd_policy) != 0 )         return  -1 ;     if  (!attrs[IPVS_CMD_ATTR_SERVICE])         return  -1 ;     if  (nla_parse_nested(svc_attrs, IPVS_SVC_ATTR_MAX, attrs[IPVS_CMD_ATTR_SERVICE], ipvs_service_policy))         return  -1 ;     memset (&(get->entrytable[i]), 0 , sizeof (get->entrytable[i]));     ...             get->entrytable[i].af = nla_get_u16(svc_attrs[IPVS_SVC_ATTR_AF]);     if  (svc_attrs[IPVS_SVC_ATTR_FWMARK])         get->entrytable[i].fwmark = nla_get_u32(svc_attrs[IPVS_SVC_ATTR_FWMARK]);     else  {         get->entrytable[i].protocol = nla_get_u16(svc_attrs[IPVS_SVC_ATTR_PROTOCOL]);         memcpy (&(get->entrytable[i].addr), nla_data(svc_attrs[IPVS_SVC_ATTR_ADDR]),                sizeof (get->entrytable[i].addr));         get->entrytable[i].port = nla_get_u16(svc_attrs[IPVS_SVC_ATTR_PORT]);     }     strncpy (get->entrytable[i].sched_name,         nla_get_string(svc_attrs[IPVS_SVC_ATTR_SCHED_NAME]),         IP_VS_SCHEDNAME_MAXLEN);     if  (svc_attrs[IPVS_SVC_ATTR_PE_NAME])         strncpy (get->entrytable[i].pe_name,             nla_get_string(svc_attrs[IPVS_SVC_ATTR_PE_NAME]),             IP_VS_PENAME_MAXLEN);     get->entrytable[i].netmask = nla_get_u32(svc_attrs[IPVS_SVC_ATTR_NETMASK]);     get->entrytable[i].timeout = nla_get_u32(svc_attrs[IPVS_SVC_ATTR_TIMEOUT]);     nla_memcpy(&flags, svc_attrs[IPVS_SVC_ATTR_FLAGS], sizeof (flags));     get->entrytable[i].flags = flags.flags & flags.mask;     if  (ipvs_parse_stats(&(get->entrytable[i].stats),                  svc_attrs[IPVS_SVC_ATTR_STATS]) != 0 )         return  -1 ;     get->entrytable[i].num_dests = 0 ;     i++;         get->num_services = i;     get = realloc (get, sizeof (*get)           + sizeof (ipvs_service_entry_t ) * (get->num_services + 1 ));     *getp = get;     return  0 ; } 
ipvs_services_parse_cb首先调用genlmsg_parse和nla_parse_nested函数从响应消息中解析出LVS服务的相应信息并保存到分配的内存中。对于每一个LVS服务,ipvs_services_parse_cb会被调用一次,因而函数中递增服务数量并在内存中添加一个LVS服务结构,为下次调用ipvs_services_parse_cb提供存储空间。
NETLINK及libnl的API参考:http://www.infradead.org/~tgr/libnl/