neutron-lbaas-lvs-ipvsadm+ip_vs

LVS是内核代码(ip_vs)和用户空间控制器(ipvsadm

neutron-lbaas-lvs底层实现机制:ipvsadm

下载ipvsadm客户端源码,ipvsad-1-26.8中有使用相关配置描述:

echo "1" > /proc/sys/net/ipv4/ip_forward

cat /proc/sys/net/ipv4/vs/amemthresh

/proc/sys/net/ipv4/vs/timeout_*

cat /proc/sys/net/ipv4/vs/snat_reroute

 

重要资料:

http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/LVS-HOWTO.LVS-NAT.html

http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/

http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/LVS-HOWTO.what_is_an_LVS.html

http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/LVS-HOWTO.ipvsadm.html

 http://ja.ssi.bg/

http://www.tldp.org/LDP/nag2/

http://www.linuxvirtualserver.org/software/ipvs.html

http://kb.linuxvirtualserver.org/wiki/Ipvsadm#Compiling_ipvsadm

 

# ll /proc/net/ip_vs*

ip_vs
ip_vs_app
ip_vs_conn
ip_vs_conn_sync
ip_vs_stats
ip_vs_stats_percpu

 

# cat /proc/net/ip_vs
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
UDP 0A212E47:22B8 rr
-> 0A212E04:1E61 Masq 10000 0 0

 

 

# ipvsadm -S
-A -u host-10-33-46-71.openstacklocal:ddi-udp-1 -s rr
-a -u host-10-33-46-71.openstacklocal:ddi-udp-1 -r host-10-33-46-4.openstacklocal:cbt -m -w 10000

# ipvsadm -L
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
UDP host-10-33-46-71.openstacklo rr
-> host-10-33-46-4.openstackloc Masq 10000 0 0

 

ipvsadm通用模型:CIP<-->VIP--DIP<-->RIP

Direct Routing:直接路由

Director:负载均衡层(Loader Balancer)

RS real server 真实提供服务的计算机

VIPVirtual IP(VIP)address:Director用来向客户端提供服务的IP地址

RIPReal IP (RIP) address:集群节点(后台真正提供服务的服务器)所使用的IP地址

DIPDirector's IP (DIP) address:Director用来和D/RIP 进行联系的地址

CIPClient computer's IP (CIP) address:公网IP,客户端使用的IP

ipvsadm -A|E -t|u|f service-address [-s scheduler] [-p [timeout]] [-M netmask] [--pe persistence_engine] [-b sched-flags]

LVS类型:

 --gatewaying   -g                   gatewaying (direct routing) (default)

  --ipip         -i                   ipip encapsulation (tunneling)

  --masquerading -m                   masquerading (NAT)

 --scheduler    -s scheduler         one of rr|wrr|lc|wlc|lblc|lblcr|dh|sh|sed|nq, the default scheduler is wlc.

1.Fixed Scheduling Method  静态调服方法

(1).RR     轮询

(2).WRR    加权轮询

(3).DH     目标地址hash

(4).SH     源地址hash

2.Dynamic Scheduling Method 动态调服方法

(1).LC     最少连接

(2).WLC    加权最少连接

(3).SED    最少期望延迟

(4).NQ     从不排队调度方法

(5).LBLC   基于本地的最少连接

(6).LBLCR  带复制的基于本地的最少连接

应用举例,典型配置

网卡配置:

Director,内网网卡,外网网卡

   外网eth0 : 10.19.172.188

   外网eth0:0 (vip):10.19.172.184

   内网eth1 : 192.168.1.1

RS(Realserver), 内网网卡

   eth0: 192.168.1.2

          192.168.1.3

          192.168.1.4

          gateway: 192.168.1.1

配置IPVS Table脚本 :
VIP=192.168.34.41
RIP1=192.168.34.27
RIP2=192.168.34.26
GW=192.168.34.1
#清除IPVS Table
ipvsadm -C
#设置IPVS Table
ipvsadm -A -t $VIP:443 -s wlcipvsadm -a -t $VIP:443 -r $RIP1:443 -g -w 1
ipvsadm -a -t $VIP:443 -r $RIP2:443 -g -w 1
#将IPVS Table保存到/etc/sysconfig/ipvsadm/etc/rc.d/init.d/
ipvsadm save
#启动IPVSservice
ipvsadm start
#显示IPVS状态  
ipvsadm -l

 

Director配置集群,添加RS

# ipvsadm -A -t 172.16.251.184:80 -s sh

# ipvsadm -a -t 172.16.251.184:80 -r 192.168.1.2 -m

# ipvsadm -a -t 172.16.251.184:80 -r 192.168.1.3–m

# ipvsadm -a -t 172.16.251.184:80 -r 192.168.1.4 -m  

LVS优于HAProxy的一个优点是LVS支持客户端的透明性(即不要SNAT)为什么

查看IPVS详情 查看 /proc/net目录下的 ip_vs ip_vs_app ip_vs_conn ip_vs_conn_sync ip_vs_ext_stats ip_vs_stats

ipvs相关的proc文件:

/proc/net/ip_vs :IPVS的规则表

/proc/net/ip_vs_app :IPVS应用协议

/proc/net/ip_vs_conn :IPVS当前连接

/proc/net/ip_vs_stats :IPVS状态统计信息

# depmod -n|grep ipvs
kernel/net/netfilter/xt_ipvs.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs.ko.xz: kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_rr.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_wrr.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_lc.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_wlc.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_lblc.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_lblcr.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_dh.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_sh.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_sed.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_nq.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_ftp.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_nat.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
kernel/net/netfilter/ipvs/ip_vs_pe_sip.ko.xz: kernel/net/netfilter/ipvs/ip_vs.ko.xz kernel/net/netfilter/nf_conntrack_sip.ko.xz kernel/net/netfilter/nf_conntrack.ko.xz kernel/lib/libcrc32c.ko.xz
alias ip6t_ipvs xt_ipvs
alias ipt_ipvs xt_ipvs

# modinfo ip_vs
filename: /lib/modules/3.10.0-862.11.6.el7.x86_64/kernel/net/netfilter/ipvs/ip_vs.ko.xz
\license: GPL
retpoline: Y
rhelversion: 7.5
srcversion: 69C7A8962537C8009A78FBC
depends: nf_conntrack,libcrc32c
intree: Y
vermagic: 3.10.0-862.11.6.el7.x86_64 SMP mod_unload modversions
signer: CentOS Linux kernel signing key
sig_key: C0:02:A7:AD:CC:7C:84:36:68:A1:BC:B4:97:4C:1A:30:2D:FF:EA:35
sig_hashalgo: sha256
parm: conn_tab_bits:Set connections' hash size (int)

# modinfo iptable_nat
filename: /lib/modules/3.10.0-862.11.6.el7.x86_64/kernel/net/ipv4/netfilter/iptable_nat.ko.xz
license: GPL
retpoline: Y
rhelversion: 7.5
srcversion: 291B36E315928812DAB1A47
depends: ip_tables,nf_nat_ipv4
intree: Y
vermagic: 3.10.0-862.11.6.el7.x86_64 SMP mod_unload modversions
signer: CentOS Linux kernel signing key
sig_key: C0:02:A7:AD:CC:7C:84:36:68:A1:BC:B4:97:4C:1A:30:2D:FF:EA:35
sig_hashalgo: sha256

 源码:http://www.linuxvirtualserver.org/software/ipvs.html

ipvsadm.sh:

# config: /etc/sysconfig/ipvsadm

# config: /etc/ipvsadm.rules

ipvsadm.c

int main(int argc, char **argv)
{
    int result;

    if (ipvs_init()) {
        /* try to insmod the ip_vs module if ipvs_init failed */
        if (modprobe_ipvs() || ipvs_init())
            fail(2, "Can't initialize ipvs: %s\n"
                "Are you sure that IP Virtual Server is "
                "built in the kernel or as module?",
                 ipvs_strerror(errno));
    }

    /* warn the user if the IPVS version is out of date */
    check_ipvs_version();

    /* list the table if there is no other arguement */
    if (argc == 1){
        list_all(FMT_NONE);
        ipvs_close();
        return 0;
    }

    /* process command line arguments */
    result = process_options(argc, argv, 0);

    ipvs_close();
    return result;
}
static int
parse_options(int argc, char **argv, struct ipvs_command_entry *ce,
          unsigned int *options, unsigned int *format)
{
    int c, parse;
    poptContext context;
    char *optarg=NULL;
    struct poptOption options_table[] = {
        { "add-service", 'A', POPT_ARG_NONE, NULL, 'A', NULL, NULL },
        { "edit-service", 'E', POPT_ARG_NONE, NULL, 'E', NULL, NULL },
        { "delete-service", 'D', POPT_ARG_NONE, NULL, 'D', NULL, NULL },
        { "clear", 'C', POPT_ARG_NONE, NULL, 'C', NULL, NULL },
        { "list", 'L', POPT_ARG_NONE, NULL, 'L', NULL, NULL },
        { "list", 'l', POPT_ARG_NONE, NULL, 'l', NULL, NULL },
        { "zero", 'Z', POPT_ARG_NONE, NULL, 'Z', NULL, NULL },
        { "add-server", 'a', POPT_ARG_NONE, NULL, 'a', NULL, NULL },
        { "edit-server", 'e', POPT_ARG_NONE, NULL, 'e', NULL, NULL },
        { "delete-server", 'd', POPT_ARG_NONE, NULL, 'd', NULL, NULL },
        { "set", '\0', POPT_ARG_NONE, NULL, TAG_SET, NULL, NULL },
        { "help", 'h', POPT_ARG_NONE, NULL, 'h', NULL, NULL },
        { "version", 'v', POPT_ARG_NONE, NULL, 'v', NULL, NULL },
        { "restore", 'R', POPT_ARG_NONE, NULL, 'R', NULL, NULL },
        { "save", 'S', POPT_ARG_NONE, NULL, 'S', NULL, NULL },
        { "start-daemon", '\0', POPT_ARG_STRING, &optarg,
          TAG_START_DAEMON, NULL, NULL },
        { "stop-daemon", '\0', POPT_ARG_STRING, &optarg,
          TAG_STOP_DAEMON, NULL, NULL },
        { "tcp-service", 't', POPT_ARG_STRING, &optarg, 't',
          NULL, NULL },
        { "udp-service", 'u', POPT_ARG_STRING, &optarg, 'u',
          NULL, NULL },
        { "fwmark-service", 'f', POPT_ARG_STRING, &optarg, 'f',
          NULL, NULL },
        { "scheduler", 's', POPT_ARG_STRING, &optarg, 's', NULL, NULL },
        { "persistent", 'p', POPT_ARG_STRING|POPT_ARGFLAG_OPTIONAL,
         &optarg, 'p', NULL, NULL },
        { "netmask", 'M', POPT_ARG_STRING, &optarg, 'M', NULL, NULL },
        { "real-server", 'r', POPT_ARG_STRING, &optarg, 'r',
          NULL, NULL },
        { "masquerading", 'm', POPT_ARG_NONE, NULL, 'm', NULL, NULL },
        { "ipip", 'i', POPT_ARG_NONE, NULL, 'i', NULL, NULL },
        { "gatewaying", 'g', POPT_ARG_NONE, NULL, 'g', NULL, NULL },
        { "weight", 'w', POPT_ARG_STRING, &optarg, 'w', NULL, NULL },
        { "u-threshold", 'x', POPT_ARG_STRING, &optarg, 'x',
          NULL, NULL },
        { "l-threshold", 'y', POPT_ARG_STRING, &optarg, 'y',
          NULL, NULL },
        { "numeric", 'n', POPT_ARG_NONE, NULL, 'n', NULL, NULL },
        { "connection", 'c', POPT_ARG_NONE, NULL, 'c', NULL, NULL },
        { "mcast-interface", '\0', POPT_ARG_STRING, &optarg,
          TAG_MCAST_INTERFACE, NULL, NULL },
        { "syncid", '\0', POPT_ARG_STRING, &optarg, 'I', NULL, NULL },
        { "timeout", '\0', POPT_ARG_NONE, NULL, TAG_TIMEOUT,
          NULL, NULL },
        { "daemon", '\0', POPT_ARG_NONE, NULL, TAG_DAEMON, NULL, NULL },
        { "stats", '\0', POPT_ARG_NONE, NULL, TAG_STATS, NULL, NULL },
        { "rate", '\0', POPT_ARG_NONE, NULL, TAG_RATE, NULL, NULL },
        { "thresholds", '\0', POPT_ARG_NONE, NULL,
           TAG_THRESHOLDS, NULL, NULL },
        { "persistent-conn", '\0', POPT_ARG_NONE, NULL,
          TAG_PERSISTENTCONN, NULL, NULL },
        { "nosort", '\0', POPT_ARG_NONE, NULL,
           TAG_NO_SORT, NULL, NULL },
        { "sort", '\0', POPT_ARG_NONE, NULL, TAG_SORT, NULL, NULL },
        { "exact", 'X', POPT_ARG_NONE, NULL, 'X', NULL, NULL },
        { "ipv6", '6', POPT_ARG_NONE, NULL, '6', NULL, NULL },
        { "ops", 'o', POPT_ARG_NONE, NULL, 'o', NULL, NULL },
        { "pe", '\0', POPT_ARG_STRING, &optarg, TAG_PERSISTENCE_ENGINE,
          NULL, NULL },
        { NULL, 0, 0, NULL, 0, NULL, NULL }
    };

    context = poptGetContext("ipvsadm", argc, (const char **)argv,
                 options_table, 0);

    if ((c = poptGetNextOpt(context)) < 0)
        tryhelp_exit(argv[0], -1);

    switch (c) {
    case 'A':
        set_command(&ce->cmd, CMD_ADD);
        break;
    case 'E':
        set_command(&ce->cmd, CMD_EDIT);
        break;
    case 'D':
        set_command(&ce->cmd, CMD_DEL);
        break;
    case 'a':
        set_command(&ce->cmd, CMD_ADDDEST);
        break;
    case 'e':
        set_command(&ce->cmd, CMD_EDITDEST);
        break;
    case 'd':
        set_command(&ce->cmd, CMD_DELDEST);
        break;
    case 'C':
        set_command(&ce->cmd, CMD_FLUSH);
        break;
    case 'L':
    case 'l':
        set_command(&ce->cmd, CMD_LIST);
        break;
    case 'Z':
        set_command(&ce->cmd, CMD_ZERO);
        break;
    case TAG_SET:
        set_command(&ce->cmd, CMD_TIMEOUT);
        break;
    case 'R':
        set_command(&ce->cmd, CMD_RESTORE);
        break;
    case 'S':
        set_command(&ce->cmd, CMD_SAVE);
        break;
    case TAG_START_DAEMON:
        set_command(&ce->cmd, CMD_STARTDAEMON);
        if (!strcmp(optarg, "master"))
            ce->daemon.state = IP_VS_STATE_MASTER;
        else if (!strcmp(optarg, "backup"))
            ce->daemon.state = IP_VS_STATE_BACKUP;
        else fail(2, "illegal start-daemon parameter specified");
        break;
    case TAG_STOP_DAEMON:
        set_command(&ce->cmd, CMD_STOPDAEMON);
        if (!strcmp(optarg, "master"))
            ce->daemon.state = IP_VS_STATE_MASTER;
        else if (!strcmp(optarg, "backup"))
            ce->daemon.state = IP_VS_STATE_BACKUP;
        else fail(2, "illegal start_daemon specified");
        break;
    case 'h':
        usage_exit(argv[0], 0);
        break;
    case 'v':
        version_exit(0);
        break;
    default:
        tryhelp_exit(argv[0], -1);
    }

    while ((c=poptGetNextOpt(context)) >= 0){
        switch (c) {
        case 't':
        case 'u':
            set_option(options, OPT_SERVICE);
            ce->svc.protocol =
                (c=='t' ? IPPROTO_TCP : IPPROTO_UDP);
            parse = parse_service(optarg, &ce->svc);
            if (!(parse & SERVICE_ADDR))
                fail(2, "illegal virtual server "
                     "address[:port] specified");
            break;
        case 'f':
            set_option(options, OPT_SERVICE);
            /*
             * Set protocol to a sane values, even
             * though it is not used
             */
            ce->svc.af = AF_INET;
            ce->svc.protocol = IPPROTO_TCP;
            ce->svc.fwmark = parse_fwmark(optarg);
            break;
        case 's':
            set_option(options, OPT_SCHEDULER);
            strncpy(ce->svc.sched_name,
                optarg, IP_VS_SCHEDNAME_MAXLEN);
            break;
        case 'p':
            set_option(options, OPT_PERSISTENT);
            ce->svc.flags |= IP_VS_SVC_F_PERSISTENT;
            ce->svc.timeout =
                parse_timeout(optarg, 1, MAX_TIMEOUT);
            break;
        case 'M':
            set_option(options, OPT_NETMASK);
            if (ce->svc.af != AF_INET6) {
                parse = parse_netmask(optarg, &ce->svc.netmask);
                if (parse != 1)
                    fail(2, "illegal virtual server "
                         "persistent mask specified");
            } else {
                ce->svc.netmask = atoi(optarg);
                if ((ce->svc.netmask < 1) || (ce->svc.netmask > 128))
                    fail(2, "illegal ipv6 netmask specified");
            }
            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 & SERVICE_ADDR))
                fail(2, "illegal real server "
                     "address[:port] specified");
            /* copy vport to dport if not specified */
            if (parse == 1)
                ce->dest.port = ce->svc.port;
            break;
        case 'i':
            set_option(options, OPT_FORWARD);
            ce->dest.conn_flags = IP_VS_CONN_F_TUNNEL;
            break;
        case 'g':
            set_option(options, OPT_FORWARD);
            ce->dest.conn_flags = IP_VS_CONN_F_DROUTE;
            break;
        case 'm':
            set_option(options, OPT_FORWARD);
            ce->dest.conn_flags = IP_VS_CONN_F_MASQ;
            break;
        case 'w':
            set_option(options, OPT_WEIGHT);
            if ((ce->dest.weight =
                 string_to_number(optarg, 0, 65535)) == -1)
                fail(2, "illegal weight specified");
            break;
        case 'x':
            set_option(options, OPT_UTHRESHOLD);
            if ((ce->dest.u_threshold =
                 string_to_number(optarg, 0, INT_MAX)) == -1)
                fail(2, "illegal u_threshold specified");
            break;
        case 'y':
            set_option(options, OPT_LTHRESHOLD);
            if ((ce->dest.l_threshold =
                 string_to_number(optarg, 0, INT_MAX)) == -1)
                fail(2, "illegal l_threshold specified");
            break;
        case 'c':
            set_option(options, OPT_CONNECTION);
            break;
        case 'n':
            set_option(options, OPT_NUMERIC);
            *format |= FMT_NUMERIC;
            break;
        case TAG_MCAST_INTERFACE:
            set_option(options, OPT_MCAST);
            strncpy(ce->daemon.mcast_ifn,
                optarg, IP_VS_IFNAME_MAXLEN);
            break;
        case 'I':
            set_option(options, OPT_SYNCID);
            if ((ce->daemon.syncid =
                 string_to_number(optarg, 0, 255)) == -1)
                fail(2, "illegal syncid specified");
            break;
        case TAG_TIMEOUT:
            set_option(options, OPT_TIMEOUT);
            break;
        case TAG_DAEMON:
            set_option(options, OPT_DAEMON);
            break;
        case TAG_STATS:
            set_option(options, OPT_STATS);
            *format |= FMT_STATS;
            break;
        case TAG_RATE:
            set_option(options, OPT_RATE);
            *format |= FMT_RATE;
            break;
        case TAG_THRESHOLDS:
            set_option(options, OPT_THRESHOLDS);
            *format |= FMT_THRESHOLDS;
            break;
        case TAG_PERSISTENTCONN:
            set_option(options, OPT_PERSISTENTCONN);
            *format |= FMT_PERSISTENTCONN;
            break;
        case TAG_NO_SORT:
            set_option(options, OPT_NOSORT    );
            *format |= FMT_NOSORT;
            break;
        case TAG_SORT:
            /* Sort is the default, this is a no-op for compatibility */
            break;
        case 'X':
            set_option(options, OPT_EXACT);
            *format |= FMT_EXACT;
            break;
        case '6':
            if (ce->svc.fwmark) {
                ce->svc.af = AF_INET6;
                ce->svc.netmask = 128;
            } else {
                fail(2, "-6 used before -f\n");
            }
            break;
        case 'o':
            set_option(options, OPT_ONEPACKET);
            ce->svc.flags |= IP_VS_SVC_F_ONEPACKET;
            break;
        case TAG_PERSISTENCE_ENGINE:
            set_option(options, OPT_PERSISTENCE_ENGINE);
            strncpy(ce->svc.pe_name, optarg, IP_VS_PENAME_MAXLEN);
            break;
        default:
            fail(2, "invalid option `%s'",
                 poptBadOption(context, POPT_BADOPTION_NOALIAS));
        }
    }

    if (c < -1) {
        /* an error occurred during option processing */
        fprintf(stderr, "%s: %s\n",
            poptBadOption(context, POPT_BADOPTION_NOALIAS),
            poptStrerror(c));
        poptFreeContext(context);
        return -1;
    }

    if (ce->cmd == CMD_TIMEOUT) {
        char *optarg1, *optarg2;

        if ((optarg=(char *)poptGetArg(context))
            && (optarg1=(char *)poptGetArg(context))
            && (optarg2=(char *)poptGetArg(context))) {
            ce->timeout.tcp_timeout =
                parse_timeout(optarg, 0, MAX_TIMEOUT);
            ce->timeout.tcp_fin_timeout =
                parse_timeout(optarg1, 0, MAX_TIMEOUT);
            ce->timeout.udp_timeout =
                parse_timeout(optarg2, 0, MAX_TIMEOUT);
        } else
            fail(2, "--set option requires 3 timeout values");
    }

    if ((optarg=(char *)poptGetArg(context)))
        fail(2, "unexpected argument %s", optarg);

    poptFreeContext(context);

    return 0;
}
IP_VS_CONN_F_MASQ在kernel代码中搜索相关处理

http://kb.linuxvirtualserver.org/wiki/Ipvsadm#Compiling_ipvsadm

IPVS源代码分析---总述和初始化

 
主要参考了http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/ 这个系列讲的非常详细。以及yfydz的博客,很多对代码的注释都是直接转载的他的内容,先说明一下,我自己写的主要是对代码整体脉络和思路的分析。
 
   
IPVS这部分的代码看了挺长时间了,对于普通应用的处理,相对简单。 
对于FTP这种多连接的处理,IPVS虽然目前只支持FTP,但是用了很多代码来处理这种affinity connection,其中用到了persistent connection和template。其中,persistent connection是指对于多连接的服务,需要把connection都定向到同一个server上面,persistent这个标记是在ipvsadm 添加service时就配置的。template是指对于多连接的服务,创建了一个template connection作为其他连接的template,(这里的其他连接是指从同一个src发出的,被iptables打过mark的连接,iptables可以对相关的连接根据端口号打上mark,方便IPVS的处理。)这样其他连接就根据template中指向的dest,也定向到了dest。也就说相关的连接都发到了同一个dest。
 
   
 
   
根据LVS官方网站的介绍,LVS支持三种负载均衡模式:NAT,tunnel和direct routing(DR)。
NAT是通用模式,所有交互数据必须通过均衡器;后两种则是一种半连接处理方式,请求数据通过均衡器,而服务器的回应则是直接路由返回的,
而这两种方法的区别是tunnel模式下由于进行了IP封装所以可路由,而DR方式是修改MAC地址来实现,所以必须同一网段.
[主要数据结构]
这个结构用来描述IPVS支持的IP协议。IPVS的IP层协议支持TCP, UDP, AH和ESP这4种IP层协议
struct ip_vs_protocol {
        //链表中的下一项
        struct ip_vs_protocol   *next;
        //协议名称, "TCP", "UDP".
        char                    *name;
        //协议值
        __u16                   protocol;
        //不进行分片
        int                     dont_defrag;
        //协议应用计数器,根据是该协议的中多连接协议的数量
        atomic_t                appcnt;
        //协议各状态的超时数组
        int                     *timeout_table;

        void (*init)(struct ip_vs_protocol *pp);  //协议初始化
        void (*exit)(struct ip_vs_protocol *pp); //协议释放
        int (*conn_schedule)(struct sk_buff *skb, struct ip_vs_protocol *pp, int *verdict, struct ip_vs_conn **cpp); //协议调度
        //查找in方向的IPVS连接
        struct ip_vs_conn * (*conn_in_get)(const struct sk_buff *skb, struct ip_vs_protocol *pp,
                       const struct iphdr *iph, unsigned int proto_off, int inverse);
        //查找out方向的IPVS连接
        struct ip_vs_conn * (*conn_out_get)(const struct sk_buff *skb, struct ip_vs_protocol *pp,
                        const struct iphdr *iph, unsigned int proto_off, int inverse);
        //源NAT操作
        int (*snat_handler)(struct sk_buff **pskb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp);
        //目的NAT操作
        int (*dnat_handler)(struct sk_buff **pskb, struct ip_vs_protocol *pp, struct ip_vs_conn *cp);
        //协议校验和计算
        int (*csum_check)(struct sk_buff *skb, struct ip_vs_protocol *pp);
        //当前协议状态名称: 如"LISTEN", "ESTABLISH"
        const char *(*state_name)(int state);
        //协议状态迁移
        int (*state_transition)(struct ip_vs_conn *cp, int direction, const struct sk_buff *skb, struct ip_vs_protocol *pp);
        //登记应用
        int (*register_app)(struct ip_vs_app *inc);
        //去除应用登记
        void (*unregister_app)(struct ip_vs_app *inc);

        int (*app_conn_bind)(struct ip_vs_conn *cp);
        //数据包打印
        void (*debug_packet)(struct ip_vs_protocol *pp, const struct sk_buff *skb, int offset, const char *msg);
        //调整超时
        void (*timeout_change)(struct ip_vs_protocol *pp, int flags);
        //设置各种状态下的协议超时
        int (*set_state_timeout)(struct ip_vs_protocol *pp, char *sname, int to);
};
这个结构用来描述IPVS的连接。IPVS的连接和netfilter定义的连接类似
struct ip_vs_conn {
        struct list_head         c_list;            //HASH链表
        __u32                   caddr;          //客户机地址
        __u32                   vaddr;          //服务器对外的虚拟地址
        __u32                   daddr;          //服务器实际地址
        __u16                   cport;          //客户端的端口
        __u16                   vport;         //服务器对外虚拟端口
        __u16                   dport;         //服务器实际端口
        __u16                   protocol;      //协议类型

        atomic_t                refcnt;        //连接引用计数
        struct timer_list       timer;           //定时器
        volatile unsigned long  timeout;       //超时时间

        spinlock_t              lock;           //状态转换锁
        volatile __u16          flags;          /* status flags */
        volatile __u16          state;          /* state info */

        struct ip_vs_conn       *control;       //主连接, 如FTP
        atomic_t                n_control;      //子连接数
        struct ip_vs_dest       *dest;          //真正服务器
        atomic_t                in_pkts;        //进入的数据统计

        int (*packet_xmit)(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp); //数据包发送

        struct ip_vs_app        *app;           //IPVS应用
        void                    *app_data;      //应用的私有数据
        struct ip_vs_seq        in_seq;         //进入数据的序列号
        struct ip_vs_seq        out_seq;       //发出数据的序列号
};
这个结构用来描述IPVS对外的虚拟服务器信息
struct ip_vs_service {
        struct list_head        s_list;        //按普通协议,地址,端口进行HASH的链表
        struct list_head        f_list;        //按nfmark进行HASH的链表
        atomic_t                refcnt;      //引用计数
        atomic_t                usecnt;      //使用计数

        __u16                   protocol;   //协议
        __u32                   addr;       //虚拟服务器地址
        __u16                   port;       //虚拟服务器端口
        __u32                   fwmark;    //就是skb中的nfmark
        unsigned                flags;       //状态标志
        unsigned                timeout;     //超时
        __u32                   netmask;    //网络掩码

        struct list_head        destinations;  //真实服务器的地址链表
        __u32                  num_dests;  //真实服务器的数量
        struct ip_vs_stats      stats;        //服务统计信息
        struct ip_vs_app        *inc;         //应用

        struct ip_vs_scheduler  *scheduler;    //调度指针
        rwlock_t                sched_lock;    //调度锁
        void                    *sched_data;   //调度私有数据
};
这个结构用来描述具体的真实服务器的信息
struct ip_vs_dest {
        struct list_head        n_list;   /* for the dests in the service */
        struct list_head        d_list;   /* for table with all the dests */

        __u32                   addr;           //服务器地址
        __u16                   port;           //服务器端口
        volatile unsigned        flags;          //目标标志,易变参数
        atomic_t                conn_flags;     //连接标志
        atomic_t                weight;         //服务器权重

        atomic_t                refcnt;         //引用计数
        struct ip_vs_stats      stats;          //统计数

        atomic_t                activeconns;    //活动的连接
        atomic_t                inactconns;     //不活动的连接
        atomic_t                persistconns;   //保持的连接,常驻
        __u32                   u_threshold;   //连接上限
        __u32                   l_threshold;    //连接下限

        /* for destination cache */
        spinlock_t              dst_lock;       /* lock of dst_cache */
        struct dst_entry        *dst_cache;     /* destination cache entry */
        u32                     dst_rtos;
        struct ip_vs_service    *svc;           /* service it belongs to */
        __u16                   protocol;       /* which protocol (TCP/UDP) */
        __u32                   vaddr;          /* virtual IP address */
        __u16                   vport;          /* virtual port number */
        __u32                   vfwmark;        /* firewall mark of service */
};
这个结构用来描述IPVS调度算法,目前调度方法包括rr,wrr,lc, wlc, lblc, lblcr, dh, sh等
struct ip_vs_scheduler {
        struct list_head        n_list;         /* d-linked list head */
        char                    *name;          /* scheduler name */
        atomic_t                refcnt;         /* reference counter */
        struct module           *module;        /* THIS_MODULE/NULL */

        /* scheduler initializing service */
        int (*init_service)(struct ip_vs_service *svc);
        /* scheduling service finish */
        int (*done_service)(struct ip_vs_service *svc);
        /* scheduler updating service */
        int (*update_service)(struct ip_vs_service *svc);

        /* selecting a server from the given service */
        struct ip_vs_dest* (*schedule)(struct ip_vs_service *svc, const struct sk_buff *skb);
};

IPVS应用是针对多连接协议的, 目前也就只支持FTP。
由于ip_vs_app.c是从2.2过来的,没有管内核是否本身有NAT的情况,所以相当于自身实现了应用协议的NAT处理,包 括内容信息的改变,
TCP序列号确认号的调整等,而现在这些都由netfilter实现了,IPVS可以不用管这些,只处理连接调度就行了。
IPVS的应用模块化还不是很好,在处理连接端口时,还要判断是否是FTPPORT,也就是说不支持其他多连接协议的,
应该象netfilter一样为每个多连接协议设置一个helper,自动调用,不用在程序里判断端口。

struct ip_vs_app
{
        struct list_head        a_list;           //用来挂接到应用链表
        int                     type;           /* IP_VS_APP_TYPE_xxx */
        char                    *name;          /* application module name */
        __u16                   protocol;      //协议, TCP, UD
        struct module           *module;        /* THIS_MODULE/NULL */
        struct list_head        incs_list;        //应用的具体实例链表

        /* members for application incarnations */
        struct list_head        p_list;         //将应用结构挂接到对应协议(TCP, UDP...)的应用表
        struct ip_vs_app        *app;           /* its real application */
        __u16                   port;           /* port number in net order */
        atomic_t                usecnt;         /* usage counter */

        /* output hook: return false if can't linearize. diff set for TCP.  */
        int (*pkt_out)(struct ip_vs_app *, struct ip_vs_conn *, struct sk_buff **, int *diff);
        /* input hook: return false if can't linearize. diff set for TCP. */
        int (*pkt_in)(struct ip_vs_app *, struct ip_vs_conn *, struct sk_buff **, int *diff);
        /* ip_vs_app initializer */
        int (*init_conn)(struct ip_vs_app *, struct ip_vs_conn *);
        /* ip_vs_app finish */
        int (*done_conn)(struct ip_vs_app *, struct ip_vs_conn *);
        /* not used now */
        int (*bind_conn)(struct ip_vs_app *, struct ip_vs_conn *, struct ip_vs_protocol *);
        void (*unbind_conn)(struct ip_vs_app *, struct ip_vs_conn *);

        int *                   timeout_table;
        int *                   timeouts;
        int                     timeouts_size;

        int (*conn_schedule)(struct sk_buff *skb, struct ip_vs_app *app, int *verdict, struct ip_vs_conn **cpp);
        struct ip_vs_conn *
        (*conn_in_get)(const struct sk_buff *skb, struct ip_vs_app *app, const struct iphdr *iph, unsigned int proto_off, int inverse);
        struct ip_vs_conn *
        (*conn_out_get)(const struct sk_buff *skb, struct ip_vs_app *app, const struct iphdr *iph, unsigned int proto_off, int inverse);
        int (*state_transition)(struct ip_vs_conn *cp, int direction, const struct sk_buff *skb, struct ip_vs_app *app);
        void (*timeout_change)(struct ip_vs_app *app, int flags);
};
用户空间信息是ipvsadm程序接收用户输入后传递给内核ipvs的信息,信息都是很直接的,没有各种控制信息。
ipvsadm和ipvs的关系相当于iptables和netfilter的关系.

用户空间的虚拟服务信息
struct ip_vs_service_user {
        /* virtual service addresses */
        u_int16_t               protocol;
        u_int32_t               addr;           /* virtual ip address */
        u_int16_t               port;
        u_int32_t               fwmark;         /* firwall mark of service */

        /* virtual service options */
        char                    sched_name[IP_VS_SCHEDNAME_MAXLEN];
        unsigned                flags;          /* virtual service flags */
        unsigned                timeout;        /* persistent timeout in sec */
        u_int32_t               netmask;        /* persistent netmask */
};
用户空间的真实服务器信息
struct ip_vs_dest_user {
        /* destination server address */
        u_int32_t               addr;
        u_int16_t               port;
        /* real server options */
        unsigned                conn_flags;     /* connection flags */
        int                     weight;         /* destination weight */
        /* thresholds for active connections */
        u_int32_t               u_threshold;    /* upper threshold */
        u_int32_t               l_threshold;    /* lower threshold */
};
用户空间的统计信息
struct ip_vs_stats_user
{
        __u32                   conns;          /* connections scheduled */
        __u32                   inpkts;         /* incoming packets */
        __u32                   outpkts;        /* outgoing packets */
        __u64                   inbytes;        /* incoming bytes */
        __u64                   outbytes;       /* outgoing bytes */
        __u32                   cps;            /* current connection rate */
        __u32                   inpps;          /* current in packet rate */
        __u32                   outpps;         /* current out packet rate */
        __u32                   inbps;          /* current in byte rate */
        __u32                   outbps;         /* current out byte rate */
};
用户空间的获取信息结构
struct ip_vs_getinfo {
        /* version number */
        unsigned int            version;
        /* size of connection hash table */
        unsigned int            size;
        /* number of virtual services */
        unsigned int            num_services;
};
用户空间的服务规则项信息
struct ip_vs_service_entry {
        /* which service: user fills in these */
        u_int16_t               protocol;
        u_int32_t               addr;           /* virtual address */
        u_int16_t               port;
        u_int32_t               fwmark;         /* firwall mark of service */

        /* service options */
        char                    sched_name[IP_VS_SCHEDNAME_MAXLEN];
        unsigned                flags;          /* virtual service flags */
        unsigned                timeout;        /* persistent timeout */
        u_int32_t               netmask;        /* persistent netmask */

        /* number of real servers */
        unsigned int            num_dests;
        /* statistics */
        struct ip_vs_stats_user stats;
};
用户空间的服务器项信息
struct ip_vs_dest_entry {
        u_int32_t               addr;           /* destination address */
        u_int16_t               port;
        unsigned                conn_flags;     /* connection flags */
        int                     weight;         /* destination weight */

        u_int32_t               u_threshold;    /* upper threshold */
        u_int32_t               l_threshold;    /* lower threshold */

        u_int32_t               activeconns;    /* active connections */
        u_int32_t               inactconns;     /* inactive connections */
        u_int32_t               persistconns;   /* persistent connections */

        /* statistics */
        struct ip_vs_stats_user stats;
};
用户空间的获取服务器项信息
struct ip_vs_get_dests {
        /* which service: user fills in these */
        u_int16_t               protocol;
        u_int32_t               addr;           /* virtual address */
        u_int16_t               port;
        u_int32_t               fwmark;         /* firwall mark of service */

        /* number of real servers */
        unsigned int            num_dests;

        /* the real servers */
        struct ip_vs_dest_entry entrytable[0];
};
用户空间的获取虚拟服务项信息
struct ip_vs_get_services {
        /* number of virtual services */
        unsigned int            num_services;
        /* service table */
        struct ip_vs_service_entry entrytable[0];
};
用户空间的获取超时信息结构
struct ip_vs_timeout_user {
        int                     tcp_timeout;
        int                     tcp_fin_timeout;
        int                     udp_timeout;
};
用户空间的获取IPVS内核守护进程信息结构
struct ip_vs_daemon_user {
        /* sync daemon state (master/backup) */
        int                     state;
        /* multicast interface name */
        char                    mcast_ifn[IP_VS_IFNAME_MAXLEN];
        /* SyncID we belong to */
        int                     syncid;
};
[/主要数据结构]
static int __init ip_vs_init(void)
{
      int ret;

      //初始化ipvs的控制接口,set/get sockopt操作
      ret = ip_vs_control_init();
      if (ret < 0) {
            IP_VS_ERR("can't setup control.\n");
            goto cleanup_nothing;
      }
      //协议初始化
      ip_vs_protocol_init();
      //应用层辅助接口初始化
      ret = ip_vs_app_init();
      if (ret < 0) {
            IP_VS_ERR("can't setup application helper.\n");
            goto cleanup_protocol;
      }
      //主要数据结构初始化
      ret = ip_vs_conn_init();
      if (ret < 0) {
            IP_VS_ERR("can't setup connection table.\n");
            goto cleanup_app;
      }
      //下面分别挂接各个处理点到netfilter架构中,看下面hook点实现
      //关于hook点知识,参考ip_conntrack实现
      ret = nf_register_hook(&ip_vs_in_ops);
      if (ret < 0) {
            IP_VS_ERR("can't register in hook.\n");
            goto cleanup_conn;
      }
      ret = nf_register_hook(&ip_vs_out_ops);
      if (ret < 0) {
            IP_VS_ERR("can't register out hook.\n");
            goto cleanup_inops;
      }
      ret = nf_register_hook(&ip_vs_post_routing_ops);
      if (ret < 0) {
            IP_VS_ERR("can't register post_routing hook.\n");
            goto cleanup_outops;
      }
      ret = nf_register_hook(&ip_vs_forward_icmp_ops);
      if (ret < 0) {
            IP_VS_ERR("can't register forward_icmp hook.\n");
            goto cleanup_postroutingops;
      }

      IP_VS_INFO("ipvs loaded.\n");
      return ret;
      ......
}
控制接口初始化
int ip_vs_control_init(void)
{
      int ret;
      int idx;
      //登记ipvs的sockopt控制,这样用户空间可通过setsockopt函数来和ipvs进行通信,看下面控制接口实现
      ret = nf_register_sockopt(&ip_vs_sockopts);
      if (ret) {
           IP_VS_ERR("cannot register sockopt.\n");
           return ret;
      }
      //建立/proc/net/ip_vs和/proc/net/ip_vs_stats只读项
      //看下面控制接口实现
      proc_net_fops_create("ip_vs", 0, &ip_vs_info_fops);
      proc_net_fops_create("ip_vs_stats",0, &ip_vs_stats_fops);

      //建立/proc/sys/net/ipv4/vs目录下的各可读写控制参数
      sysctl_header = register_sysctl_table(vs_root_table, 0);

      //初始化各种双向链表
      //svc_table是根据协议地址端口等信息进行服务结构struct ip_vs_service查找的HASH表
      //svc_fwm_table是根据数据包的nfmark信息进行服务结构struct ip_vs_service查找的HASH表
      for(idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++)  {
            INIT_LIST_HEAD(&ip_vs_svc_table[idx]);
            INIT_LIST_HEAD(&ip_vs_svc_fwm_table[idx]);
      }
      //rtable是目的结构struct ip_vs_dest的HASH链表
      for(idx = 0; idx < IP_VS_RTAB_SIZE; idx++)  {
            INIT_LIST_HEAD(&ip_vs_rtable[idx]);
      }
      //ipvs统计信息
      memset(&ip_vs_stats, 0, sizeof(ip_vs_stats));
      spin_lock_init(&ip_vs_stats.lock);              //统计锁
      //对当前统计信息建立一个预估器,可用于计算服务器的性能参数
      ip_vs_new_estimator(&ip_vs_stats);
      //挂一个定时操作,根据系统当前负载情况定时调整系统参数
      schedule_delayed_work(&defense_work, DEFENSE_TIMER_PERIOD);
      return 0;
}
//协议初始化,具体看下面协议实现
int ip_vs_protocol_init(void)
{
      //挂接ipvs能进行均衡处理的各种协议,目前支持TCP/UDP/AH/ESP
      char protocols[64];
#define REGISTER_PROTOCOL(p)                    \
      do {                                    \
            register_ip_vs_protocol(p);     \
            strcat(protocols, ", ");        \
            strcat(protocols, (p)->name);   \
      } while (0)

      //0,1字符是给", "预留的
      protocols[0] = '\0';
      protocols[2] = '\0';
#ifdef CONFIG_IP_VS_PROTO_TCP
      REGISTER_PROTOCOL(&ip_vs_protocol_tcp);
#endif
#ifdef CONFIG_IP_VS_PROTO_UDP
      REGISTER_PROTOCOL(&ip_vs_protocol_udp);
#endif
#ifdef CONFIG_IP_VS_PROTO_AH
      REGISTER_PROTOCOL(&ip_vs_protocol_ah);
#endif
#ifdef CONFIG_IP_VS_PROTO_ESP
      REGISTER_PROTOCOL(&ip_vs_protocol_esp);
#endif
      IP_VS_INFO("Registered protocols (%s)\n", &protocols[2]);
      return 0;
}
#define IP_VS_PROTO_TAB_SIZE  32
static int register_ip_vs_protocol(struct ip_vs_protocol *pp)
{
        //#define IP_VS_PROTO_HASH(proto)         ((proto) & (IP_VS_PROTO_TAB_SIZE-1))
        unsigned hash = IP_VS_PROTO_HASH(pp->protocol); //计算一个hash值

        pp->next = ip_vs_proto_table[hash];
        ip_vs_proto_table[hash] = pp;

        if (pp->init != NULL)
                pp->init(pp);
        return 0;
}
应用层辅助接口初始化
int ip_vs_app_init(void)
{
        //建立一个/proc/net/ip_vs_app项
        proc_net_fops_create("ip_vs_app", 0, &ip_vs_app_fops);
        return 0;
}
主要数据结构初始化
int ip_vs_conn_init(void)
{
      int idx;
      //ipvs连接HASH表 static struct list_head *ip_vs_conn_tab;
      ip_vs_conn_tab = vmalloc(IP_VS_CONN_TAB_SIZE*sizeof(struct list_head));
      if (!ip_vs_conn_tab)
return -ENOMEM;

      //ipvs连接cache
      ip_vs_conn_cachep = kmem_cache_create("ip_vs_conn", sizeof(struct ip_vs_conn), 0, SLAB_HWCACHE_ALIGN, NULL, NULL);
      if (!ip_vs_conn_cachep) {
            vfree(ip_vs_conn_tab);
            return -ENOMEM;
      }
      //初始化HASH链表头
      for (idx = 0; idx < IP_VS_CONN_TAB_SIZE; idx++) {
            INIT_LIST_HEAD(&ip_vs_conn_tab[idx]);
      }
      //初始化各读写锁
      for (idx = 0; idx < CT_LOCKARRAY_SIZE; idx++)  {
            rwlock_init(&__ip_vs_conntbl_lock_array[idx].l);
      }
      //建立/proc/net/ip_vs_conn项
      proc_net_fops_create("ip_vs_conn", 0, &ip_vs_conn_fops);
      //初始化随机数
      get_random_bytes(&ip_vs_conn_rnd, sizeof(ip_vs_conn_rnd));
      return 0;
}
 

分类: LINUX

2013-05-07 20:44:15

NAT模式是IPVS最常用的一种模式。相比于TUN和DR模式,NAT模式更容易部署,仅仅是需要更改真实服务器的默认网关配置。

IPVS是基于Netfilter实现的。它注册了4个Netfilter钩子函数,其中与NAT模式相关的是ip_vs_in和ip_vs_out两个钩子函数。前者处理了客户端-〉服务器的数据包,后者则针对服务器-〉客户端的数据包。

1、ip_vs_in钩子函数

ip_vs_in函数的处理流程非常清晰:

预校验--〉ip_vs_conn对象的查找或生成--〉更新统计信息--〉更新ip_vs_conn对象的状态--〉修改sk_buff并转发数据包--〉IPVS状态同步--〉结束

1.1、预校验

预校验的过程很简单,主要包括:

  • 确保数据包的类型为PACKET_HOST
  • 确保数据包不是loopback设备发出来的
  • 确保数据包的协议类型是IPVS所支持的,目前IPVS支持TCP、UDP、AH和ESP协议
1.2、ip_vs_conn对象的查找或生成

既然有数据包来了,必然有对应的ip_vs_conn对象。首先根据数据包的<源地址-源端口-目的地址-目的端口>,查找当前的ip_vs_conn对象列表。如果没有找到的话,说明这是一个新连接,于是运行调度过程,找到一个合适的真实服务器,然后再生成一个新的ip_vs_conn对象。这里先不对调度过程进行展开描述。

1.3、更新统计信息

这里先看一下ip_vs_stats结构,其各个成员的作用很容易看出来:

 
 
 

/*
 *    IPVS statistics object
 */
struct ip_vs_stats
{
    __u32 conns; /* connections scheduled */
    __u32 inpkts; /* incoming packets */
    __u32 outpkts; /* outgoing packets */
    __u64 inbytes; /* incoming bytes */
    __u64 outbytes; /* outgoing bytes */

    __u32            cps;        /* current connection rate */
    __u32            inpps;        /* current in packet rate */
    __u32            outpps;        /* current out packet rate */
    __u32            inbps;        /* current in byte rate */
    __u32            outbps;        /* current out byte rate */

    spinlock_t lock; /* spin lock */
};

这是一个专门用于统计的数据结构,每个ip_vs_service对象、每个ip_vs_dest对象都包含有这么一个结构,另外还有一个ip_vs_stats全局变量。

函数ip_vs_in_stats和ip_vs_out_stats分别统计两个方向的数据包流量,函数ip_vs_conn_stats用于统计新建连接数。

static inline void
ip_vs_in_stats(struct ip_vs_conn *cp, struct sk_buff *skb)
{
    struct ip_vs_dest *dest = cp->dest;
    if (dest && (dest->flags & IP_VS_DEST_F_AVAILABLE)) {
        spin_lock(&dest->stats.lock);
        dest->stats.inpkts++;
        dest->stats.inbytes += skb->len;
        spin_unlock(&dest->stats.lock);

        spin_lock(&dest->svc->stats.lock);
        dest->svc->stats.inpkts++;
        dest->svc->stats.inbytes += skb->len;
        spin_unlock(&dest->svc->stats.lock);

        spin_lock(&ip_vs_stats.lock);
        ip_vs_stats.inpkts++;
        ip_vs_stats.inbytes += skb->len;
        spin_unlock(&ip_vs_stats.lock);
    }
}

conns、inpkts、outpkts、inbytes和outbytes统计比较容易,只需简单的加1。但cps等统计起来就要复杂一些了,它是通过内核定时器来实现的。每个ip_vs_stats对象都对应有一个ip_vs_estimator结构:

struct ip_vs_estimator
{
    struct ip_vs_estimator    *next;
    struct ip_vs_stats    *stats;

    u32            last_conns;
    u32            last_inpkts;
    u32            last_outpkts;
    u64            last_inbytes;
    u64            last_outbytes;

    u32            cps;
    u32            inpps;
    u32            outpps;
    u32            inbps;
    u32            outbps;
};

所有的ip_vs_estimator结构形成一张链表,通过全局变量est_list可以遍历这个链表。定时器的时间间隔为2秒,对应的触发函数为:

static void estimation_timer(unsigned long arg)
{
    struct ip_vs_estimator *e;
    struct ip_vs_stats *s;
    u32 n_conns;
    u32 n_inpkts, n_outpkts;
    u64 n_inbytes, n_outbytes;
    u32 rate;

    read_lock(&est_lock);
    for (e = est_list; e; e = e->next) {
        s = e->stats;

        spin_lock(&s->lock);
        n_conns = s->conns;
        n_inpkts = s->inpkts;
        n_outpkts = s->outpkts;
        n_inbytes = s->inbytes;
        n_outbytes = s->outbytes;

        /* scaled by 2^10, but divided 2 seconds */
        rate = (n_conns - e->last_conns)<<9;
        e->last_conns = n_conns;
        e->cps += ((long)rate - (long)e->cps)>>2;
        s->cps = (e->cps+0x1FF)>>10;

        rate = (n_inpkts - e->last_inpkts)<<9;
        e->last_inpkts = n_inpkts;
        e->inpps += ((long)rate - (long)e->inpps)>>2;
        s->inpps = (e->inpps+0x1FF)>>10;

        rate = (n_outpkts - e->last_outpkts)<<9;
        e->last_outpkts = n_outpkts;
        e->outpps += ((long)rate - (long)e->outpps)>>2;
        s->outpps = (e->outpps+0x1FF)>>10;

        rate = (n_inbytes - e->last_inbytes)<<4;
        e->last_inbytes = n_inbytes;
        e->inbps += ((long)rate - (long)e->inbps)>>2;
        s->inbps = (e->inbps+0xF)>>5;

        rate = (n_outbytes - e->last_outbytes)<<4;
        e->last_outbytes = n_outbytes;
        e->outbps += ((long)rate - (long)e->outbps)>>2;
        s->outbps = (e->outbps+0xF)>>5;
        spin_unlock(&s->lock);
    }
    read_unlock(&est_lock);
    mod_timer(&est_timer, jiffies + 2*HZ);
}

以cps的统计为例,其计算过程很简单,cps = (rate+cps)/2,其中单位为2^10。

1.4、更新ip_vs_conn对象的状态

在TCP数据包处理过程中,每个ip_vs_conn对象对应于一个TCP连接,因此也必须有一个状态转换过程,才能够引导此TCP连接正常建立和终止。这个状态转换颇为复杂,在后续内容将IN/OUT结合一起,来看TCP连接的状态转换。

1.5、修改sk_buff并转发数据包

NAT模式下的数据包转发由ip_vs_nat_xmit函数完成。对sk_buff数据结构的操作不熟悉,略过。

1.6、IPVS状态同步

先判断此ip_vs_conn对象是否需要进行主备机同步。首先当前IPVS必须是MASTER,并且此ip_vs_conn对象的状态为ESTABLISHED。另外,满足这些条件时,并非每个Packet转发的时候都进行同步,而是每50个Packet,才同步一次。

同步过程由函数ip_vs_sync_conn完成:

 
 

/*
 * Add an ip_vs_conn information into the current sync_buff.
 * Called by ip_vs_in.
 */
void ip_vs_sync_conn(struct ip_vs_conn *cp)
{
    struct ip_vs_sync_mesg *m;
    struct ip_vs_sync_conn *s;
    int len;

    spin_lock(&curr_sb_lock);
    if (!curr_sb) {
        if (!(curr_sb=ip_vs_sync_buff_create())) {
            spin_unlock(&curr_sb_lock);
            IP_VS_ERR("ip_vs_sync_buff_create failed.\n");
            return;
        }
    }

    len = (cp->flags & IP_VS_CONN_F_SEQ_MASK) ? FULL_CONN_SIZE :
        SIMPLE_CONN_SIZE;
    m = curr_sb->mesg;
    s = (struct ip_vs_sync_conn *)curr_sb->head;

    /* copy members */
    s->protocol = cp->protocol;
    s->cport = cp->cport;
    s->vport = cp->vport;
    s->dport = cp->dport;
    s->caddr = cp->caddr;
    s->vaddr = cp->vaddr;
    s->daddr = cp->daddr;
    s->flags = htons(cp->flags & ~IP_VS_CONN_F_HASHED);
    s->state = htons(cp->state);
    if (cp->flags & IP_VS_CONN_F_SEQ_MASK) {
        struct ip_vs_sync_conn_options *opt =
            (struct ip_vs_sync_conn_options *)&s[1];
        memcpy(opt, &cp->in_seq, sizeof(*opt));
    }

    m->nr_conns++;
    m->size += len;
    curr_sb->head += len;

    /* check if there is a space for next one */
    if (curr_sb->head+FULL_CONN_SIZE > curr_sb->end) {
        sb_queue_tail(curr_sb);
        curr_sb = NULL;
    }
    spin_unlock(&curr_sb_lock);

    /* synchronize its controller if it has */
    if (cp->control)
        ip_vs_sync_conn(cp->control);
}

 
每个ip_vs_conn对象对应的同步数据拷贝到curr_sb中,然后将它放到ip_vs_sync_queue链表中。同步线程主函数sync_master_loop则从ip_vs_sync_queue链表取同步数据对象,然后发送到备机中去。备机中的同步线程主函数sync_backup_loop则从网络中读取同步数据对象,然后由函数ip_vs_process_message将其恢复成ip_vs_conn对象,并保存起来。
 
1.7、ip_vs_out钩子函数

ip_vs_out函数处理 服务器-〉客户端 的数据包。相比于ip_vs_in函数,它要简单得多。这里不再描述。

2、TCP状态转换过程

IPVS支持TCP、UDP、AH和ESP四种协议。由于TCP协议的逻辑相对复杂一些,所以IPVS对TCP协议的特殊处理也更多。

IPVS针对TCP协议的处理主要是体现在TCP状态维护上,而TCP状态维护依赖于一个状态转换矩阵:

/*
 *    Timeout table[state]
 */
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,
};

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!",
};

#define sNO IP_VS_TCP_S_NONE
#define sES IP_VS_TCP_S_ESTABLISHED
#define sSS IP_VS_TCP_S_SYN_SENT
#define sSR IP_VS_TCP_S_SYN_RECV
#define sFW IP_VS_TCP_S_FIN_WAIT
#define sTW IP_VS_TCP_S_TIME_WAIT
#define sCL IP_VS_TCP_S_CLOSE
#define sCW IP_VS_TCP_S_CLOSE_WAIT
#define sLA IP_VS_TCP_S_LAST_ACK
#define sLI IP_VS_TCP_S_LISTEN
#define sSA IP_VS_TCP_S_SYNACK

struct tcp_states_t {
    int next_state[IP_VS_TCP_S_LAST];
};

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 }},
};

与NAT模式相关的为INPUT和OUTPUT两张表,其意思也较容易理解:

  1. sNO、sES等为TCP状态,tcp_state_name_table为状态名称表,而tcp_timeouts表指明了每个状态的维系时间。此维系时间决定了ip_vs_conn对象的生命期,当维系时间内此连接无任何输入输出,则ip_vs_conn对象自动销毁,它是通过设置ip_vs_conn对象的timeout来实现的。
  2. 当连接处于某状态时,在tcp_states矩阵中查找对应状态列,然后根据当前的输入(INPUT指客户端输入,OUTPUT指真实服务器输出),查找到下一个状态值。

状态转换矩阵也可以用一张状态转换图来表示:



 

转载于:https://www.cnblogs.com/liuhongru/p/11424870.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值