ipvsadm配置命令解析

ipvsadm配置命令解析以下为 ipvsadm 命令 第一行配置 Linux 虚拟服务 地址为 207 175 44 110 端口号为 80 A 选项 t 表明此服务为 TCP s 选项指定调度算法为 Round Robin 随后的几行为添加实际的服务器 如 192 168 10 1 80 r 选项 a 选项表明为添加实际服务器 t 选项表明是 TCP 协议 m 选项表明转发方式采用 NATmasquerad ipvsadm A

以下为ipvsadm命令,第一行配置Linux虚拟服务:地址为207.175.44.110,端口号为80(-A选项),-t表明此服务为TCP,-s选项指定调度算法为Round-Robin。随后的几行为添加实际的服务器,如192.168.10.1:80(-r选项),-a选项表明为添加实际服务器,-t选项表明是TCP协议,-m选项表明转发方式采用NAT masquerading。

ipvsadm -A -t 207.175.44.110:80 -s rr ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.1:80 -m ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.2:80 -m ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.3:80 -m 

ipvsadm向内核下发配置的方式由两种:netlink和raw套接口。由ipvsadm-1.29代码中的文件libipvs/Makefile可知,除非显示的将HAVE_NL定义为0,否则将使用netlink接口方式下发配置。即使不定义HAVE_NL,其为空,也是使用netlink。

ifneq (0,$(HAVE_NL)) CFLAGS += -DLIBIPVS_USE_NL CFLAGS += $(shell \ if which pkg-config > /dev/null 2>&1; then \ if pkg-config --cflags libnl-3.0 2> /dev/null; then :; \ elif pkg-config --cflags libnl-2.0 2> /dev/null; then :; \ elif pkg-config --cflags libnl-1 2> /dev/null; then :; \ fi; \ fi) endif 

参见初始化函数ipvs_init,如果通用netlink接口初始化失败,还是会初始化raw套接口,实现ipvs配置下发。

int ipvs_init(void) { #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; } 

添加虚拟服务

ipvsadm的参数解析由函数parse_options完成。本文开头的第一行配置命令的相关解析代码如下。解析过程中将初始化ipvs_command_entry类型的结构变量,主要为配置命令,如CMD_ADD对应-A选项;-t选项对应服务的协议变量ce->svc.protocol;-s选项指定的调度算法名称保存在变量ce->svc.sched_name中。

static int parse_options(int argc, char argv, struct ipvs_command_entry *ce, unsigned int *options, unsigned int *format) { switch (c) { case 'A': set_command(&ce->cmd, CMD_ADD); break; } while ((c=poptGetNextOpt(context)) >= 0){ switch (c) { case 't': case 'u': case TAG_SCTP_SERVICE: set_option(options, OPT_SERVICE); ce->svc.protocol = option_to_protocol(c); parse = parse_service(optarg, &ce->svc); break; case 's': set_option(options, OPT_SCHEDULER); strncpy(ce->svc.sched_name, optarg, IP_VS_SCHEDNAME_MAXLEN); break; } 

函数process_options根据配置命令调用相应的处理函数,如CMD_ADD命令,由函数ipvs_add_service处理。

static int process_options(int argc, char argv, int reading_stdin) { struct ipvs_command_entry ce; switch (ce.cmd) { case CMD_ADD: result = ipvs_add_service(&ce.svc); break; } 

如果general netlink成功初始化,即try_nl为真,使用netlink下发配置;否则,使用raw套接口的setsockopt系统调用下发。对于netlink使用命令字IPVS_CMD_NEW_SERVICE;对于raw套接口,使用IP_VS_SO_SET_ADD套接口选项。

int ipvs_add_service(ipvs_service_t *svc) { #ifdef LIBIPVS_USE_NL if (try_nl) { struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_NEW_SERVICE, 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)); } 

函数ipvs_nl_fill_service_attr负责填充netlink消息体的属性。以上命令使用到的属性分别为IPVS_SVC_ATTR_PROTOCOL(TCP)、IPVS_SVC_ATTR_ADDR(207.175.44.110),和IPVS_SVC_ATTR_PORT(80)。属性IPVS_SVC_ATTR_SCHED_NAME指定调度算法名称,此处为rr。

static int ipvs_nl_fill_service_attr(struct nl_msg *msg, ipvs_service_t *svc) { struct nlattr *nl_service; struct ip_vs_flags flags = { .flags = svc->flags, .mask = ~0 }; nl_service = nla_nest_start(msg, IPVS_CMD_ATTR_SERVICE); 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); } 

ipvsadm还支持使用iptables的mark值添加服务,如下。此种情况下的配置下发,使用netlink属性IPVS_SVC_ATTR_FWMARK。

iptables -A PREROUTING -t mangle -d 207.175.44.110/31 -j MARK --set-mark 1 ipvsadm -A -f 1 -s rr 

添加真实服务器

如本文开头部分的命令:ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.1:80 -m。添加真实服务器,我们使用到了参数-a,-t,-r和-m。其中-t选项与上节介绍的相同,用于指定使用TCP协议。参见如下函数parse_options,小写a选项对应的命令为CMD_ADDDEST;-r指定真实服务器的地址和端口等信息;-m选项设置转发方式标志IP_VS_CONN_F_MASQ。

while ((c=poptGetNextOpt(context)) >= 0){ switch (c) { case 't': case 'u': case TAG_SCTP_SERVICE: set_option(options, OPT_SERVICE); ce->svc.protocol = option_to_protocol(c); parse = parse_service(optarg, &ce->svc); break; case 'r': set_option(options, OPT_SERVER); ipvs_service_t t_dest = ce->svc; parse = parse_service(optarg, &t_dest); ce->dest.af = t_dest.af; ce->dest.addr = t_dest.addr; ce->dest.port = t_dest.port; if (parse == 1) ce->dest.port = ce->svc.port; break; case 'm': set_option(options, OPT_FORWARD); ce->dest.conn_flags = IP_VS_CONN_F_MASQ; break; 

}

在添加或者编辑真实服务器时,如果使用的是隧道转发方式(IP_VS_CONN_F_TUNNEL)或者直接路由方式(IP_VS_CONN_F_DROUTE),要求设置的真实服务器的端口号必须等于虚拟服务的端口号。另外,由于使用防火墙mark标志定义服务时,并不指定虚拟端口号,一定满足此要求。最后,只有在使用隧道转发方式时,才允许配置的真实服务器的地址族与虚拟服务的地址族不相同。

以上检查都通过的情况下,调用函数ipvs_add_dest执行添加操作。

static int process_options(int argc, char argv, int reading_stdin) { struct ipvs_command_entry ce; if (ce.cmd == CMD_ADDDEST || ce.cmd == CMD_EDITDEST) { if (!ce.svc.fwmark && (ce.dest.conn_flags == IP_VS_CONN_F_TUNNEL || ce.dest.conn_flags == IP_VS_CONN_F_DROUTE)) ce.dest.port = ce.svc.port; /* Tunneling allows different address family */ if (ce.dest.af != ce.svc.af && ce.dest.conn_flags != IP_VS_CONN_F_TUNNEL) fail(2, "Different address family is allowed only for tunneling servers"); } switch (ce.cmd) { case CMD_ADDDEST: result = ipvs_add_dest(&ce.svc, &ce.dest); break; } 

类似与以上的添加服务函数ipvs_add_service,ipvs_add_dest也是优先使用netlink下发接口。对于netlink使用命令字IPVS_CMD_NEW_DEST标识此命令;对于raw套接口使用套接口选项IP_VS_SO_SET_ADDDEST。

int ipvs_add_dest(ipvs_service_t *svc, ipvs_dest_t *dest) { #ifdef LIBIPVS_USE_NL ipvs_func = ipvs_add_dest; if (try_nl) { struct nl_msg *msg = ipvs_nl_message(IPVS_CMD_NEW_DEST, 0); if (!msg) return -1; if (ipvs_nl_fill_service_attr(msg, svc)) goto nla_put_failure; if (ipvs_nl_fill_dest_attr(msg, dest)) goto nla_put_failure; return ipvs_nl_send_message(msg, ipvs_nl_noop_cb, NULL); nla_put_failure: nlmsg_free(msg); return -1; } #endif CHECK_COMPAT_SVC(svc, -1); CHECK_COMPAT_DEST(dest, -1); memcpy(&svcdest.svc, svc, sizeof(svcdest.svc)); memcpy(&svcdest.dest, dest, sizeof(svcdest.dest)); return setsockopt(sockfd, IPPROTO_IP, IP_VS_SO_SET_ADDDEST, (char *)&svcdest, sizeof(svcdest)); } 

ipvs_add_dest函数的特定下发内容由函数ipvs_nl_fill_dest_attr完成,如下,真实服务器的信息保存在以下属性中:IPVS_DEST_ATTR_ADDR(192.168.10.1),IPVS_DEST_ATTR_PORT(80)和IPVS_DEST_ATTR_FWD_METHOD(-m)中。本例配置并没有用到属性IPVS_DEST_ATTR_WEIGHT,IPVS_DEST_ATTR_U_THRESH和IPVS_DEST_ATTR_L_THRESH,这三个是对应于其它调度算法的属性,如算法weighted round robin, least-connection, 和weighted least-connection等。

static int ipvs_nl_fill_dest_attr(struct nl_msg *msg, ipvs_dest_t *dst) { struct nlattr *nl_dest; nl_dest = nla_nest_start(msg, IPVS_CMD_ATTR_DEST); NLA_PUT_U16(msg, IPVS_DEST_ATTR_ADDR_FAMILY, dst->af); NLA_PUT(msg, IPVS_DEST_ATTR_ADDR, sizeof(dst->addr), &(dst->addr)); NLA_PUT_U16(msg, IPVS_DEST_ATTR_PORT, dst->port); NLA_PUT_U32(msg, IPVS_DEST_ATTR_FWD_METHOD, dst->conn_flags & IP_VS_CONN_F_FWD_MASK); NLA_PUT_U32(msg, IPVS_DEST_ATTR_WEIGHT, dst->weight); NLA_PUT_U32(msg, IPVS_DEST_ATTR_U_THRESH, dst->u_threshold); NLA_PUT_U32(msg, IPVS_DEST_ATTR_L_THRESH, dst->l_threshold); nla_nest_end(msg, nl_dest); } 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/222779.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月17日 下午3:02
下一篇 2026年3月17日 下午3:03


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号