ipvsadm配置命令解析

负载均衡 专栏收录该内容
42 篇文章 4 订阅

以下为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。

static int parse_options(int argc, char **argv, struct ipvs_command_entry *ce, unsigned int *options, unsigned int *format)
{
switch © {
case ‘a’:
set_command(&ce->cmd, CMD_ADDDEST);
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 '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);
}
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值