blob: 0cb2b68b5809c53c912d94733ebc326cafab3a9e [file] [log] [blame]
/*
* ipvsadm - IP Virtual Server ADMinistration program
* for IPVS NetFilter Module in kernel 2.4
*
* Version: $Id$
*
* Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
* Peter Kese <peter.kese@ijs.si>
*
* This program is based on ippfvsadm.
*
* Changes:
* Wensong Zhang : added the editting service & destination support
* Wensong Zhang : added the feature to specify persistent port
* Jacob Rief : found the bug that masquerading dest of
* different vport and dport cannot be deleted.
* Wensong Zhang : fixed it and changed some cosmetic things
* Wensong Zhang : added the timeout setting for persistent service
* Wensong Zhang : added specifying the dest weight zero
* Wensong Zhang : fixed the -E and -e options
* Wensong Zhang : added the long options
* Wensong Zhang : added the hostname and portname input
* Wensong Zhang : added the hostname and portname output
* Lars Marowsky-Brée : added persistence granularity support
* Julian Anastasov : fixed the (null) print for unknown services
* Wensong Zhang : added the port_to_anyname function
* Horms : added option to read commands from stdin
* Horms : modified usage function so it prints to
* : stdout if an exit value of 0 is used and
* : stdout otherwise. Program is then terminated
* : with the supplied exit value.
* Horms : updated manpage and usage funtion so
* : the reflect the options available
* Wensong Zhang : added option to write rules to stdout
* Horms : added ability to specify a fwmark
* : instead of a server and port for
* : a virtual service
* Horms : tightened up checking of services
* : in parse_service
* Horms : ensure that a -r is passed when needed
* Wensong Zhang : fixed the output of fwmark rules
* Horms : added kernel version verification
* Horms : Specifying command and option options
* (e.g. -Ln or -At) in one short option
* with popt problem fixed.
* Wensong Zhang : split the process_options and make
* two versions of parse_options.
* Horms : attempting to save or restore when
* compiled against getopt_long now results
* in an informative error message rather
* than the usage information
* Horms : added -v option
* Wensong Zhang : rewrite most code of parsing options and
* processing options.
* Alexandre Cassen : added ipvs_syncd SyncdID support to filter
* incoming sync messages.
* Guy Waugh & Ratz : added --exact option and spelling cleanup
* vbusam@google.com : added IPv6 support
*
*
* ippfvsadm - Port Fowarding & Virtual Server ADMinistration program
*
* Copyright (c) 1998 Wensong Zhang
* All rights reserved.
*
* Author: Wensong Zhang <wensong@iinchina.net>
*
* This ippfvsadm is derived from Steven Clarke's ipportfw program.
*
* portfw - Port Forwarding Table Editing v1.1
*
* Copyright (c) 1997 Steven Clarke
* All rights reserved.
*
* Author: Steven Clarke <steven@monmouth.demon.co.uk>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#undef __KERNEL__ /* Makefile lazyness ;) */
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <stdarg.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/wait.h> /* For waitpid */
#include <arpa/inet.h>
#include <net/if.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include "popt.h"
#define IPVS_OPTION_PROCESSING "popt"
#include "config_stream.h"
#include "libipvs/libipvs.h"
#define IPVSADM_VERSION_NO "v" VERSION
#define IPVSADM_VERSION_DATE "2016/12/23"
#define IPVSADM_VERSION IPVSADM_VERSION_NO " " IPVSADM_VERSION_DATE
#define MAX_TIMEOUT (86400*31) /* 31 days */
#define CMD_NONE 0
#define CMD_ADD (CMD_NONE+1)
#define CMD_EDIT (CMD_NONE+2)
#define CMD_DEL (CMD_NONE+3)
#define CMD_FLUSH (CMD_NONE+4)
#define CMD_LIST (CMD_NONE+5)
#define CMD_ADDDEST (CMD_NONE+6)
#define CMD_DELDEST (CMD_NONE+7)
#define CMD_EDITDEST (CMD_NONE+8)
#define CMD_TIMEOUT (CMD_NONE+9)
#define CMD_STARTDAEMON (CMD_NONE+10)
#define CMD_STOPDAEMON (CMD_NONE+11)
#define CMD_RESTORE (CMD_NONE+12)
#define CMD_SAVE (CMD_NONE+13)
#define CMD_ZERO (CMD_NONE+14)
#define CMD_MAX CMD_ZERO
#define NUMBER_OF_CMD (CMD_MAX - CMD_NONE)
static const char* cmdnames[] = {
"add-service",
"edit-service",
"delete-service",
"flush",
"list",
"add-server",
"delete-server",
"edit-server",
"set",
"start-daemon",
"stop-daemon",
"restore",
"save",
"zero",
};
#define OPT_NONE 0x000000
#define OPT_NUMERIC 0x000001
#define OPT_CONNECTION 0x000002
#define OPT_SERVICE 0x000004
#define OPT_SCHEDULER 0x000008
#define OPT_PERSISTENT 0x000010
#define OPT_NETMASK 0x000020
#define OPT_SERVER 0x000040
#define OPT_FORWARD 0x000080
#define OPT_WEIGHT 0x000100
#define OPT_UTHRESHOLD 0x000200
#define OPT_LTHRESHOLD 0x000400
#define OPT_MCAST 0x000800
#define OPT_TIMEOUT 0x001000
#define OPT_DAEMON 0x002000
#define OPT_STATS 0x004000
#define OPT_RATE 0x008000
#define OPT_THRESHOLDS 0x010000
#define OPT_PERSISTENTCONN 0x020000
#define OPT_NOSORT 0x040000
#define OPT_SYNCID 0x080000
#define OPT_EXACT 0x100000
#define OPT_ONEPACKET 0x200000
#define OPT_PERSISTENCE_ENGINE 0x400000
#define OPT_SCHED_FLAGS 0x800000
#define OPT_MCAST_GROUP 0x01000000
#define OPT_MCAST_PORT 0x02000000
#define OPT_MCAST_TTL 0x04000000
#define OPT_SYNC_MAXLEN 0x08000000
#define NUMBER_OF_OPT 28
static const char* optnames[] = {
"numeric",
"connection",
"service-address",
"scheduler",
"persistent",
"netmask",
"real-server",
"forwarding-method",
"weight",
"u-threshold",
"l-threshold",
"mcast-interface",
"timeout",
"daemon",
"stats",
"rate",
"thresholds",
"persistent-conn",
"nosort",
"syncid",
"exact",
"ops",
"pe",
"sched-flags",
"mcast-group",
"mcast-port",
"mcast-ttl",
"sync-maxlen",
};
/*
* Table of legal combinations of commands and options.
* Key:
* '+' compulsory
* 'x' illegal
* '1' exclusive (only one '1' option can be supplied)
* ' ' optional
*/
static const char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] =
{
/* -n -c svc -s -p -M -r fwd -w -x -y -mc tot dmn -st -rt thr -pc srt sid -ex ops -pe -b grp port ttl size */
/*ADD*/ {'x', 'x', '+', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', ' ', ' ', 'x', 'x', 'x', 'x'},
/*EDIT*/ {'x', 'x', '+', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', ' ', ' ', 'x', 'x', 'x', 'x'},
/*DEL*/ {'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'},
/*FLUSH*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'},
/*LIST*/ {' ', '1', '1', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', '1', '1', ' ', ' ', ' ', ' ', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x'},
/*ADDSRV*/ {'x', 'x', '+', 'x', 'x', 'x', '+', ' ', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'},
/*DELSRV*/ {'x', 'x', '+', 'x', 'x', 'x', '+', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'},
/*EDITSRV*/ {'x', 'x', '+', 'x', 'x', 'x', '+', ' ', ' ', ' ', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'},
/*TIMEOUT*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'},
/*STARTD*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', 'x', 'x', ' ', ' ', ' ', ' '},
/*STOPD*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'},
/*RESTORE*/ {'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'},
/*SAVE*/ {' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'},
/*ZERO*/ {'x', 'x', ' ', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x'},
};
/* printing format flags */
#define FMT_NONE 0x0000
#define FMT_NUMERIC 0x0001
#define FMT_RULE 0x0002
#define FMT_STATS 0x0004
#define FMT_RATE 0x0008
#define FMT_THRESHOLDS 0x0010
#define FMT_PERSISTENTCONN 0x0020
#define FMT_NOSORT 0x0040
#define FMT_EXACT 0x0080
#define SERVICE_NONE 0x0000
#define SERVICE_ADDR 0x0001
#define SERVICE_PORT 0x0002
/* default scheduler */
#define DEF_SCHED "wlc"
/* default multicast interface name */
#define DEF_MCAST_IFN "eth0"
#define CONN_PROC_FILE "/proc/net/ip_vs_conn"
struct ipvs_command_entry {
int cmd;
ipvs_service_t svc;
ipvs_dest_t dest;
ipvs_timeout_t timeout;
ipvs_daemon_t daemon;
};
/* Use values outside ASCII range so that if an option has
* a short name it can be used as the tag
*/
enum {
TAG_SET = 128,
TAG_START_DAEMON,
TAG_STOP_DAEMON ,
TAG_MCAST_INTERFACE,
TAG_TIMEOUT,
TAG_DAEMON,
TAG_STATS,
TAG_RATE,
TAG_THRESHOLDS,
TAG_PERSISTENTCONN,
TAG_SORT,
TAG_NO_SORT,
TAG_PERSISTENCE_ENGINE,
TAG_SCTP_SERVICE,
TAG_MCAST_GROUP,
TAG_MCAST_PORT,
TAG_MCAST_TTL,
TAG_SYNC_MAXLEN,
};
/* various parsing helpers & parsing functions */
static int str_is_digit(const char *str);
static int string_to_number(const char *s, int min, int max);
static int host_to_addr(const char *name, struct in_addr *addr);
static char * addr_to_host(int af, const void *addr);
static char * addr_to_anyname(int af, const void *addr);
static int service_to_port(const char *name, unsigned short proto);
static char * port_to_service(unsigned short port, unsigned short proto);
static char * port_to_anyname(unsigned short port, unsigned short proto);
static char * addrport_to_anyname(int af, const void *addr, unsigned short port,
unsigned short proto, unsigned int format);
static int parse_service(char *buf, ipvs_service_t *svc);
static int parse_netmask(char *buf, u_int32_t *addr);
static int parse_timeout(char *buf, int min, int max);
static unsigned int parse_fwmark(char *buf);
static unsigned int parse_sched_flags(const char *sched, char *optarg);
/* check the options based on the commands_v_options table */
static void generic_opt_check(int command, int options);
static void set_command(int *cmd, const int newcmd);
static void set_option(unsigned int *options, unsigned int option);
static void tryhelp_exit(const char *program, const int exit_status);
static void usage_exit(const char *program, const int exit_status);
static void version_exit(int exit_status);
static void version(FILE *stream);
static void fail(int err, char *msg, ...);
/* various listing functions */
static void list_conn(unsigned int format);
static void list_service(ipvs_service_t *svc, unsigned int format);
static void list_all(unsigned int format);
static void list_timeout(void);
static void list_daemon(void);
static int modprobe_ipvs(void);
static void check_ipvs_version(void);
static int process_options(int argc, char **argv, int reading_stdin);
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 option_to_protocol(int opt)
{
switch (opt) {
case 't':
return IPPROTO_TCP;
case 'u':
return IPPROTO_UDP;
case TAG_SCTP_SERVICE:
return IPPROTO_SCTP;
default:
return IPPROTO_IP;
}
}
static char *option_from_protocol(int proto)
{
switch (proto) {
case IPPROTO_TCP:
return "-t";
case IPPROTO_UDP:
return "-u";
case IPPROTO_SCTP:
return "--sctp-service";
default:
return NULL;
}
}
static char *protocol_name(int proto)
{
switch (proto) {
case IPPROTO_TCP:
return "TCP";
case IPPROTO_UDP:
return "UDP";
case IPPROTO_SCTP:
return "SCTP";
default:
return "?";
}
}
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, sched_flags_arg[128];
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 },
{ "sctp-service", '\0', POPT_ARG_STRING, &optarg,
TAG_SCTP_SERVICE, 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 },
{ "sched-flags", 'b', POPT_ARG_STRING, &optarg, 'b',
NULL, NULL },
{ "mcast-group", '\0', POPT_ARG_STRING, &optarg,
TAG_MCAST_GROUP, NULL, NULL },
{ "mcast-port", '\0', POPT_ARG_STRING, &optarg,
TAG_MCAST_PORT, NULL, NULL },
{ "mcast-ttl", '\0', POPT_ARG_STRING, &optarg,
TAG_MCAST_TTL, NULL, NULL },
{ "sync-maxlen", '\0', POPT_ARG_STRING, &optarg,
TAG_SYNC_MAXLEN, NULL, NULL },
{ NULL, 0, 0, NULL, 0, NULL, NULL }
};
sched_flags_arg[0] = '\0';
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':
case TAG_SCTP_SERVICE:
set_option(options, OPT_SERVICE);
ce->svc.protocol = option_to_protocol(c);
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 - 1);
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 - 1);
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;
case 'b':
set_option(options, OPT_SCHED_FLAGS);
snprintf(sched_flags_arg, sizeof(sched_flags_arg),
"%s", optarg);
break;
case TAG_MCAST_GROUP:
set_option(options, OPT_MCAST_GROUP);
if (strchr(optarg, ':')) {
if (inet_pton(AF_INET6, optarg,
&ce->daemon.mcast_group) <= 0 ||
!IN6_IS_ADDR_MULTICAST(
&ce->daemon.mcast_group.in6))
fail(2, "invalid IPv6 mcast-group `%s'",
optarg);
ce->daemon.mcast_af = AF_INET6;
} else {
if (inet_pton(AF_INET, optarg,
&ce->daemon.mcast_group) <= 0 ||
!IN_MULTICAST(ntohl(
ce->daemon.mcast_group.ip)))
fail(2, "invalid IPv4 mcast-group `%s'",
optarg);
ce->daemon.mcast_af = AF_INET;
}
break;
case TAG_MCAST_PORT:
set_option(options, OPT_MCAST_PORT);
parse = string_to_number(optarg, 1, 65535);
if (parse == -1)
fail(2, "illegal mcast-port specified");
ce->daemon.mcast_port = parse;
break;
case TAG_MCAST_TTL:
set_option(options, OPT_MCAST_TTL);
parse = string_to_number(optarg, 1, 255);
if (parse == -1)
fail(2, "illegal mcast-ttl specified");
ce->daemon.mcast_ttl = parse;
break;
case TAG_SYNC_MAXLEN:
set_option(options, OPT_SYNC_MAXLEN);
parse = string_to_number(optarg, 1, 65535 - 20 - 8);
if (parse == -1)
fail(2, "illegal sync-maxlen specified");
ce->daemon.sync_maxlen = parse;
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);
if (sched_flags_arg[0]) {
ce->svc.flags &= ~(IP_VS_SVC_F_SCHED1 |
IP_VS_SVC_F_SCHED2 |
IP_VS_SVC_F_SCHED3);
ce->svc.flags |= parse_sched_flags(ce->svc.sched_name,
sched_flags_arg);
}
poptFreeContext(context);
return 0;
}
static int restore_table(int argc, char **argv, int reading_stdin)
{
int result = 0;
dynamic_array_t *a;
/* avoid infinite loop */
if (reading_stdin != 0)
tryhelp_exit(argv[0], -1);
while ((a = config_stream_read(stdin, argv[0])) != NULL) {
int i;
if ((i = (int)dynamic_array_get_count(a)) > 1) {
char **strv = dynamic_array_get_vector(a);
result = process_options(i, strv, 1);
}
dynamic_array_destroy(a, DESTROY_STR);
}
return result;
}
static int process_options(int argc, char **argv, int reading_stdin)
{
struct ipvs_command_entry ce;
unsigned int options = OPT_NONE;
unsigned int format = FMT_NONE;
int result = 0;
memset(&ce, 0, sizeof(struct ipvs_command_entry));
ce.cmd = CMD_NONE;
/* Set the default weight 1 */
ce.dest.weight = 1;
/* Set direct routing as default forwarding method */
ce.dest.conn_flags = IP_VS_CONN_F_DROUTE;
/* Set the default persistent granularity to /32 mask */
ce.svc.netmask = ((u_int32_t) 0xffffffff);
if (parse_options(argc, argv, &ce, &options, &format))
return -1;
generic_opt_check(ce.cmd, options);
if (ce.cmd == CMD_ADD || ce.cmd == CMD_EDIT) {
/* Make sure that port zero service is persistent */
if (!ce.svc.fwmark && !ce.svc.port &&
!(ce.svc.flags & IP_VS_SVC_F_PERSISTENT))
fail(2, "Zero port specified "
"for non-persistent service");
if (ce.svc.flags & IP_VS_SVC_F_ONEPACKET &&
!ce.svc.fwmark && ce.svc.protocol != IPPROTO_UDP)
fail(2, "One-Packet Scheduling is only "
"for UDP virtual services");
/* Set the default scheduling algorithm if not specified */
if (strlen(ce.svc.sched_name) == 0)
strcpy(ce.svc.sched_name, DEF_SCHED);
}
if (ce.cmd == CMD_STARTDAEMON && strlen(ce.daemon.mcast_ifn) == 0)
strcpy(ce.daemon.mcast_ifn, DEF_MCAST_IFN);
if (ce.cmd == CMD_ADDDEST || ce.cmd == CMD_EDITDEST) {
/*
* The destination port must be equal to the service port
* if the IP_VS_CONN_F_TUNNEL or IP_VS_CONN_F_DROUTE is set.
* Don't worry about this if fwmark is used.
*/
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_LIST:
if ((options & (OPT_CONNECTION|OPT_TIMEOUT|OPT_DAEMON) &&
options & (OPT_STATS|OPT_RATE|OPT_THRESHOLDS)) ||
(options & (OPT_TIMEOUT|OPT_DAEMON) &&
options & OPT_PERSISTENTCONN))
fail(2, "options conflicts in the list command");
if (options & OPT_CONNECTION)
list_conn(format);
else if (options & OPT_SERVICE)
list_service(&ce.svc, format);
else if (options & OPT_TIMEOUT)
list_timeout();
else if (options & OPT_DAEMON)
list_daemon();
else
list_all(format);
return 0;
case CMD_RESTORE:
return restore_table(argc, argv, reading_stdin);
case CMD_SAVE:
format |= FMT_RULE;
list_all(format);
return 0;
case CMD_FLUSH:
result = ipvs_flush();
break;
case CMD_ADD:
result = ipvs_add_service(&ce.svc);
break;
case CMD_EDIT:
result = ipvs_update_service(&ce.svc);
break;
case CMD_DEL:
result = ipvs_del_service(&ce.svc);
break;
case CMD_ZERO:
result = ipvs_zero_service(&ce.svc);
break;
case CMD_ADDDEST:
result = ipvs_add_dest(&ce.svc, &ce.dest);
break;
case CMD_EDITDEST:
result = ipvs_update_dest(&ce.svc, &ce.dest);
break;
case CMD_DELDEST:
result = ipvs_del_dest(&ce.svc, &ce.dest);
break;
case CMD_TIMEOUT:
result = ipvs_set_timeout(&ce.timeout);
break;
case CMD_STARTDAEMON:
result = ipvs_start_daemon(&ce.daemon);
break;
case CMD_STOPDAEMON:
result = ipvs_stop_daemon(&ce.daemon);
}
if (result)
fprintf(stderr, "%s\n", ipvs_strerror(errno));
return result;
}
static int string_to_number(const char *s, int min, int max)
{
long number;
char *end;
errno = 0;
number = strtol(s, &end, 10);
if (*end == '\0' && end != s) {
/* We parsed a number, let's see if we want this. */
if (errno != ERANGE && min <= number && number <= max)
return number;
}
return -1;
}
/*
* Parse the timeout value.
*/
static int parse_timeout(char *buf, int min, int max)
{
int i;
/* it is just for parsing timeout of persistent service */
if (buf == NULL)
return IPVS_SVC_PERSISTENT_TIMEOUT;
if ((i=string_to_number(buf, min, max)) == -1)
fail(2, "invalid timeout value `%s' specified", buf);
return i;
}
/*
* Parse IP fwmark from the argument.
*/
static unsigned int parse_fwmark(char *buf)
{
unsigned long l;
char *end;
errno = 0;
l = strtol(buf, &end, 10);
if (*end != '\0' || end == buf ||
errno == ERANGE || l <= 0 || l > UINT_MAX)
fail(2, "invalid fwmark value `%s' specified", buf);
return l;
}
/*
* Get netmask.
* Return 0 if failed,
* 1 if addr read
*/
static int parse_netmask(char *buf, u_int32_t *addr)
{
struct in_addr inaddr;
if(buf == NULL)
return 0;
if (inet_aton(buf, &inaddr) != 0)
*addr = inaddr.s_addr;
else if (host_to_addr(buf, &inaddr) != -1)
*addr = inaddr.s_addr;
else
return 0;
return 1;
}
/*
* Get IP address and port from the argument.
* Result is a logical or of
* SERVICE_NONE: no service elements set/error
* SERVICE_ADDR: addr set
* SERVICE_PORT: port set
*/
static int
parse_service(char *buf, ipvs_service_t *svc)
{
char *portp = NULL;
long portn;
int result=SERVICE_NONE;
struct in_addr inaddr;
struct in6_addr inaddr6;
if (buf == NULL || str_is_digit(buf))
return SERVICE_NONE;
if (buf[0] == '[') {
buf++;
portp = strchr(buf, ']');
if (portp == NULL)
return SERVICE_NONE;
*portp = '\0';
portp++;
if (*portp == ':')
*portp = '\0';
else
return SERVICE_NONE;
}
if (inet_pton(AF_INET6, buf, &inaddr6) > 0) {
svc->addr.in6 = inaddr6;
svc->af = AF_INET6;
svc->netmask = 128;
} else {
portp = strrchr(buf, ':');
if (portp != NULL)
*portp = '\0';
if (inet_aton(buf, &inaddr) != 0) {
svc->addr.ip = inaddr.s_addr;
svc->af = AF_INET;
} else if (host_to_addr(buf, &inaddr) != -1) {
svc->addr.ip = inaddr.s_addr;
svc->af = AF_INET;
} else
return SERVICE_NONE;
}
result |= SERVICE_ADDR;
if (portp != NULL) {
result |= SERVICE_PORT;
if ((portn = string_to_number(portp+1, 0, 65535)) != -1)
svc->port = htons(portn);
else if ((portn = service_to_port(portp+1, svc->protocol)) != -1)
svc->port = htons(portn);
else
return SERVICE_NONE;
}
return result;
}
static unsigned int parse_sched_flags(const char *sched, char *optarg)
{
unsigned int flags = 0;
char *flag;
sched = (sched && *sched) ? sched : DEF_SCHED;
flag = strtok(optarg, ",");
do {
if (!strcmp(flag, "flag-1")) {
flags |= IP_VS_SVC_F_SCHED1;
} else if (!strcmp(flag, "flag-2")) {
flags |= IP_VS_SVC_F_SCHED2;
} else if (!strcmp(flag, "flag-3")) {
flags |= IP_VS_SVC_F_SCHED3;
} else if (!strcmp(flag, "sh-fallback")) {
flags |= IP_VS_SVC_F_SCHED_SH_FALLBACK;
if (strcmp(sched, "sh"))
fail(2, "incompatible scheduler flag `%s'",
flag);
} else if (!strcmp(flag, "sh-port")) {
flags |= IP_VS_SVC_F_SCHED_SH_PORT;
if (strcmp(sched, "sh"))
fail(2, "incompatible scheduler flag `%s'",
flag);
} else if (!strcmp(flag, "mh-fallback")) {
flags |= IP_VS_SVC_F_SCHED_MH_FALLBACK;
if (strcmp(sched, "mh"))
fail(2, "incompatible scheduler flag `%s'",
flag);
} else if (!strcmp(flag, "mh-port")) {
flags |= IP_VS_SVC_F_SCHED_MH_PORT;
if (strcmp(sched, "mh"))
fail(2, "incompatible scheduler flag `%s'",
flag);
} else {
fail(2, "invalid scheduler flag `%s'", flag);
}
} while ((flag = strtok(NULL, ",")) != NULL);
return flags;
}
static void
generic_opt_check(int command, int options)
{
int i, j;
int last = 0, count = 0;
/* Check that commands are valid with options. */
i = command - CMD_NONE -1;
for (j = 0; j < NUMBER_OF_OPT; j++) {
if (!(options & (1<<j))) {
if (commands_v_options[i][j] == '+')
fail(2, "You need to supply the '%s' "
"option for the '%s' command",
optnames[j], cmdnames[i]);
} else {
if (commands_v_options[i][j] == 'x')
fail(2, "Illegal '%s' option with "
"the '%s' command",
optnames[j], cmdnames[i]);
if (commands_v_options[i][j] == '1') {
count++;
if (count == 1) {
last = j;
continue;
}
fail(2, "The option '%s' conflicts with the "
"'%s' option in the '%s' command",
optnames[j], optnames[last], cmdnames[i]);
}
}
}
}
static inline const char *
opt2name(int option)
{
const char **ptr;
for (ptr = optnames; option > 1; option >>= 1, ptr++);
return *ptr;
}
static void
set_command(int *cmd, const int newcmd)
{
if (*cmd != CMD_NONE)
fail(2, "multiple commands specified");
*cmd = newcmd;
}
static void
set_option(unsigned int *options, unsigned int option)
{
if (*options & option)
fail(2, "multiple '%s' options specified", opt2name(option));
*options |= option;
}
static void tryhelp_exit(const char *program, const int exit_status)
{
fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n",
program, program);
exit(exit_status);
}
static void usage_exit(const char *program, const int exit_status)
{
FILE *stream;
if (exit_status != 0)
stream = stderr;
else
stream = stdout;
version(stream);
fprintf(stream,
"Usage:\n"
" %s -A|E virtual-service [-s scheduler] [-p [timeout]] [-M netmask] [--pe persistence_engine] [-b sched-flags]\n"
" %s -D virtual-service\n"
" %s -C\n"
" %s -R\n"
" %s -S [-n]\n"
" %s -a|e virtual-service -r server-address [options]\n"
" %s -d virtual-service -r server-address\n"
" %s -L|l [virtual-service] [options]\n"
" %s -Z [virtual-service]\n"
" %s --set tcp tcpfin udp\n"
" %s --start-daemon {master|backup} [daemon-options]\n"
" %s --stop-daemon {master|backup}\n"
" %s -h\n\n",
program, program, program,
program, program, program, program, program,
program, program, program, program, program);
fprintf(stream,
"Commands:\n"
"Either long or short options are allowed.\n"
" --add-service -A add virtual service with options\n"
" --edit-service -E edit virtual service with options\n"
" --delete-service -D delete virtual service\n"
" --clear -C clear the whole table\n"
" --restore -R restore rules from stdin\n"
" --save -S save rules to stdout\n"
" --add-server -a add real server with options\n"
" --edit-server -e edit real server with options\n"
" --delete-server -d delete real server\n"
" --list -L|-l list the table\n"
" --zero -Z zero counters in a service or all services\n"
" --set tcp tcpfin udp set connection timeout values\n"
" --start-daemon start connection sync daemon\n"
" --stop-daemon stop connection sync daemon\n"
" --help -h display this help message\n\n"
);
fprintf(stream,
"virtual-service:\n"
" --tcp-service|-t service-address service-address is host[:port]\n"
" --udp-service|-u service-address service-address is host[:port]\n"
" --sctp-service service-address service-address is host[:port]\n"
" --fwmark-service|-f fwmark fwmark is an integer greater than zero\n"
"\n");
fprintf(stream,
"Options:\n"
" --ipv6 -6 fwmark entry uses IPv6\n"
" --scheduler -s scheduler one of " SCHEDULERS ",\n"
" the default scheduler is %s.\n"
" --pe engine alternate persistence engine may be " PE_LIST ",\n"
" not set by default.\n"
" --persistent -p [timeout] persistent service\n"
" --netmask -M netmask persistent granularity mask\n"
" --real-server -r server-address server-address is host (and port)\n"
" --gatewaying -g gatewaying (direct routing) (default)\n"
" --ipip -i ipip encapsulation (tunneling)\n"
" --masquerading -m masquerading (NAT)\n"
" --weight -w weight capacity of real server\n"
" --u-threshold -x uthreshold upper threshold of connections\n"
" --l-threshold -y lthreshold lower threshold of connections\n"
" --connection -c output of current IPVS connections\n"
" --timeout output of timeout (tcp tcpfin udp)\n"
" --daemon output of daemon information\n"
" --stats output of statistics information\n"
" --rate output of rate information\n"
" --exact expand numbers (display exact values)\n"
" --thresholds output of thresholds information\n"
" --persistent-conn output of persistent connection info\n"
" --nosort disable sorting output of service/server entries\n"
" --sort does nothing, for backwards compatibility\n"
" --ops -o one-packet scheduling\n"
" --numeric -n numeric output of addresses and ports\n"
" --sched-flags -b flags scheduler flags (comma-separated)\n",
DEF_SCHED);
fprintf(stream,
"Daemon Options:\n"
" --syncid sid syncid for connection sync (default=255)\n"
" --sync-maxlen length Max sync message length (default=1472)\n"
" --mcast-interface interface multicast interface for connection sync\n"
" --mcast-group address IPv4/IPv6 group (default=224.0.0.81)\n"
" --mcast-port port UDP port (default=8848)\n"
" --mcast-ttl ttl Multicast TTL (default=1)\n"
);
exit(exit_status);
}
static void version_exit(const int exit_status)
{
FILE *stream;
if (exit_status != 0)
stream = stderr;
else
stream = stdout;
version(stream);
exit(exit_status);
}
static void version(FILE *stream)
{
fprintf(stream,
"ipvsadm " IPVSADM_VERSION " (compiled with "
IPVS_OPTION_PROCESSING " and IPVS v%d.%d.%d)\n",
NVERSION(IP_VS_VERSION_CODE));
}
static void fail(int err, char *msg, ...)
{
va_list args;
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
fprintf(stderr, "\n");
exit(err);
}
static int modprobe_ipvs(void)
{
char *argv[] = { "/sbin/modprobe", "--", "ip_vs", NULL };
int child;
int status;
int rc;
if (!(child = fork())) {
execv(argv[0], argv);
exit(1);
}
rc = waitpid(child, &status, 0);
if (rc == -1 || !WIFEXITED(status) || WEXITSTATUS(status)) {
return 1;
}
return 0;
}
static void check_ipvs_version(void)
{
/* verify the IPVS version */
if (ipvs_info.version <
IPVS_VERSION(MINIMUM_IPVS_VERSION_MAJOR,
MINIMUM_IPVS_VERSION_MINOR,
MINIMUM_IPVS_VERSION_PATCH)) {
fprintf(stderr,
"Warning: IPVS version mismatch: \n"
" Kernel compiled with IPVS version %d.%d.%d\n"
" ipvsadm " IPVSADM_VERSION_NO
" requires minimum IPVS version %d.%d.%d\n\n",
NVERSION(ipvs_info.version),
MINIMUM_IPVS_VERSION_MAJOR,
MINIMUM_IPVS_VERSION_MINOR,
MINIMUM_IPVS_VERSION_PATCH);
}
}
static void print_conn(char *buf, unsigned int format)
{
char protocol[8];
unsigned short proto;
union nf_inet_addr caddr;
unsigned short cport;
union nf_inet_addr vaddr;
unsigned short vport;
union nf_inet_addr daddr;
unsigned short dport;
char state[16];
unsigned int expires;
unsigned short af = AF_INET;
unsigned short daf = AF_INET;
char pe_name[IP_VS_PENAME_MAXLEN + 1];
char pe_data[IP_VS_PEDATA_MAXLEN + 1];
int n;
char temp1[INET6_ADDRSTRLEN], temp2[INET6_ADDRSTRLEN], temp3[INET6_ADDRSTRLEN];
char *cname, *vname, *dname;
unsigned int minutes, seconds;
char expire_str[12];
if ((n = sscanf(buf, "%s %s %hX %s %hX %s %hX %s %d %s %s",
protocol, temp1, &cport, temp2, &vport,
temp3, &dport, state, &expires,
pe_name, pe_data)) == -1)
exit(1);
if (strcmp(protocol, "TCP") == 0)
proto = IPPROTO_TCP;
else if (strcmp(protocol, "UDP") == 0)
proto = IPPROTO_UDP;
else if (strcmp(protocol, "SCTP") == 0)
proto = IPPROTO_SCTP;
else
proto = 0;
if (inet_pton(AF_INET6, temp1, &caddr.in6) > 0) {
inet_pton(AF_INET6, temp2, &vaddr.in6);
af = AF_INET6;
} else if (inet_pton(AF_INET, temp1, &caddr.ip) > 0) {
inet_pton(AF_INET, temp2, &vaddr.ip);
} else {
caddr.ip = (__u32) htonl(strtoul(temp1, NULL, 16));
vaddr.ip = (__u32) htonl(strtoul(temp2, NULL, 16));
}
if (inet_pton(AF_INET6, temp3, &daddr.in6) > 0)
daf = AF_INET6;
else if (inet_pton(AF_INET, temp3, &daddr.ip) <= 0)
daddr.ip = (__u32) htonl(strtoul(temp3, NULL, 16));
if (!(cname = addrport_to_anyname(af, &caddr, cport, proto, format)))
exit(1);
if (!(vname = addrport_to_anyname(af, &vaddr, vport, proto, format)))
exit(1);
if (!(dname = addrport_to_anyname(daf, &daddr, dport, proto, format)))
exit(1);
seconds = expires % 60;
minutes = expires / 60;
sprintf(expire_str, "%02d:%02d", minutes, seconds);
if (format & FMT_PERSISTENTCONN && n == 11)
printf("%-3s %-6s %-11s %-18s %-18s %-16s %-18s %s\n",
protocol, expire_str, state, cname, vname, dname,
pe_name, pe_data);
else
printf("%-3s %-6s %-11s %-18s %-18s %s\n",
protocol, expire_str, state, cname, vname, dname);
free(cname);
free(vname);
free(dname);
}
void list_conn(unsigned int format)
{
static char buffer[256];
FILE *handle;
handle = fopen(CONN_PROC_FILE, "r");
if (!handle) {
fprintf(stderr, "cannot open file %s\n", CONN_PROC_FILE);
exit(1);
}
/* read the first line */
if (fgets(buffer, sizeof(buffer), handle) == NULL) {
fprintf(stderr, "unexpected input from %s\n",
CONN_PROC_FILE);
exit(1);
}
printf("IPVS connection entries\n");
if (format & FMT_PERSISTENTCONN)
printf("pro expire %-11s %-18s %-18s %-18s %-16s %s\n",
"state", "source", "virtual", "destination",
"pe name", "pe_data");
else
printf("pro expire %-11s %-18s %-18s %s\n",
"state", "source", "virtual", "destination");
/*
* Print the VS information according to the format
*/
while (!feof(handle)) {
if (fgets(buffer, sizeof(buffer), handle))
print_conn(buffer, format);
}
fclose(handle);
}
static inline char *fwd_name(unsigned flags)
{
char *fwd = NULL;
switch (flags & IP_VS_CONN_F_FWD_MASK) {
case IP_VS_CONN_F_MASQ:
fwd = "Masq";
break;
case IP_VS_CONN_F_LOCALNODE:
fwd = "Local";
break;
case IP_VS_CONN_F_TUNNEL:
fwd = "Tunnel";
break;
case IP_VS_CONN_F_DROUTE:
fwd = "Route";
break;
}
return fwd;
}
static inline char *fwd_switch(unsigned flags)
{
char *swt = NULL;
switch (flags & IP_VS_CONN_F_FWD_MASK) {
case IP_VS_CONN_F_MASQ:
swt = "-m"; break;
case IP_VS_CONN_F_TUNNEL:
swt = "-i"; break;
case IP_VS_CONN_F_LOCALNODE:
case IP_VS_CONN_F_DROUTE:
swt = "-g"; break;
}
return swt;
}
static void print_largenum(unsigned long long i, unsigned int format)
{
if (format & FMT_EXACT) {
char mytmp[32];
int len;
len = snprintf(mytmp, 32, "%llu", i);
printf("%*llu", len <= 8 ? 9 : len + 1, i);
return;
}
if (i < 100000000) /* less than 100 million */
printf("%9llu", i);
else if (i < 1000000000) /* less than 1 billion */
printf("%8lluK", i / 1000);
else if (i < 100000000000ULL) /* less than 100 billion */
printf("%8lluM", i / 1000000);
else if (i < 100000000000000ULL) /* less than 100 trillion */
printf("%8lluG", i / 1000000000ULL);
else
printf("%8lluT", i / 1000000000000ULL);
}
static void print_sched_flags(ipvs_service_entry_t *se)
{
char flags[64];
flags[0] = '\0';
if (!strcmp(se->sched_name, "sh")) {
if (se->flags & IP_VS_SVC_F_SCHED_SH_FALLBACK)
strcat(flags, "sh-fallback,");
if (se->flags & IP_VS_SVC_F_SCHED_SH_PORT)
strcat(flags, "sh-port,");
} else if (!strcmp(se->sched_name, "mh")) {
if (se->flags & IP_VS_SVC_F_SCHED_MH_FALLBACK)
strcat(flags, "mh-fallback,");
if (se->flags & IP_VS_SVC_F_SCHED_MH_PORT)
strcat(flags, "mh-port,");
} else {
if (se->flags & IP_VS_SVC_F_SCHED1)
strcat(flags, "flag-1,");
if (se->flags & IP_VS_SVC_F_SCHED2)
strcat(flags, "flag-2,");
if (se->flags & IP_VS_SVC_F_SCHED3)
strcat(flags, "flag-3,");
}
if (flags[0]) {
flags[strlen(flags)-1] = '\0';
printf("%s", flags);
}
}
static void print_title(unsigned int format)
{
if (format & FMT_STATS)
printf("%-33s %8s %8s %8s %8s %8s\n"
" -> RemoteAddress:Port\n",
"Prot LocalAddress:Port",
"Conns", "InPkts", "OutPkts", "InBytes", "OutBytes");
else if (format & FMT_RATE)
printf("%-33s %8s %8s %8s %8s %8s\n"
" -> RemoteAddress:Port\n",
"Prot LocalAddress:Port",
"CPS", "InPPS", "OutPPS", "InBPS", "OutBPS");
else if (format & FMT_THRESHOLDS)
printf("%-33s %-10s %-10s %-10s %-10s\n"
" -> RemoteAddress:Port\n",
"Prot LocalAddress:Port",
"Uthreshold", "Lthreshold", "ActiveConn", "InActConn");
else if (format & FMT_PERSISTENTCONN)
printf("%-33s %-9s %-11s %-10s %-10s\n"
" -> RemoteAddress:Port\n",
"Prot LocalAddress:Port",
"Weight", "PersistConn", "ActiveConn", "InActConn");
else if (!(format & FMT_RULE))
printf("Prot LocalAddress:Port Scheduler Flags\n"
" -> RemoteAddress:Port Forward Weight ActiveConn InActConn\n");
}
static void
print_service_entry(ipvs_service_entry_t *se, unsigned int format)
{
struct ip_vs_get_dests *d;
char svc_name[1024];
int i;
if (!(d = ipvs_get_dests(se))) {
fprintf(stderr, "%s\n", ipvs_strerror(errno));
exit(1);
}
if (se->fwmark) {
if (format & FMT_RULE)
if (se->af == AF_INET6)
sprintf(svc_name, "-f %d -6", se->fwmark);
else
sprintf(svc_name, "-f %d", se->fwmark);
else
if (se->af == AF_INET6)
sprintf(svc_name, "FWM %d IPv6", se->fwmark);
else
sprintf(svc_name, "FWM %d", se->fwmark);
} else {
char *vname;
if (!(vname = addrport_to_anyname(se->af, &se->addr, ntohs(se->port),
se->protocol, format)))
fail(2, "addrport_to_anyname: %s", strerror(errno));
if (format & FMT_RULE) {
char *stype = option_from_protocol(se->protocol) ? :
"--xxx-service";
snprintf(svc_name, sizeof(svc_name), "%s %s",
stype, vname);
} else {
char *stype = protocol_name(se->protocol);
snprintf(svc_name, sizeof(svc_name), "%-4s %s",
stype, vname);
if (se->af != AF_INET6)
svc_name[33] = '\0';
}
free(vname);
}
/* print virtual service info */
if (format & FMT_RULE) {
printf("-A %s -s %s", svc_name, se->sched_name);
if (se->flags & IP_VS_SVC_F_PERSISTENT) {
printf(" -p %u", se->timeout);
if (se->af == AF_INET)
if (se->netmask != (unsigned long int) 0xffffffff) {
struct in_addr mask;
mask.s_addr = se->netmask;
printf(" -M %s", inet_ntoa(mask));
}
if (se->af == AF_INET6)
if (se->netmask != 128) {
printf(" -M %i", se->netmask);
}
}
if (se->pe_name[0])
printf(" --pe %s", se->pe_name);
if (se->flags & IP_VS_SVC_F_ONEPACKET)
printf(" -o");
if (se->flags & (IP_VS_SVC_F_SCHED1 |
IP_VS_SVC_F_SCHED2 |
IP_VS_SVC_F_SCHED3)) {
printf(" -b ");
print_sched_flags(se);
}
} else if (format & FMT_STATS) {
printf("%-33s", svc_name);
print_largenum(se->stats64.conns, format);
print_largenum(se->stats64.inpkts, format);
print_largenum(se->stats64.outpkts, format);
print_largenum(se->stats64.inbytes, format);
print_largenum(se->stats64.outbytes, format);
} else if (format & FMT_RATE) {
printf("%-33s", svc_name);
print_largenum(se->stats64.cps, format);
print_largenum(se->stats64.inpps, format);
print_largenum(se->stats64.outpps, format);
print_largenum(se->stats64.inbps, format);
print_largenum(se->stats64.outbps, format);
} else {
printf("%s %s", svc_name, se->sched_name);
if (se->flags & (IP_VS_SVC_F_SCHED1 |
IP_VS_SVC_F_SCHED2 |
IP_VS_SVC_F_SCHED3)) {
printf(" (");
print_sched_flags(se);
printf(")");
}
if (se->flags & IP_VS_SVC_F_PERSISTENT) {
printf(" persistent %u", se->timeout);
if (se->af == AF_INET)
if (se->netmask != (unsigned long int) 0xffffffff) {
struct in_addr mask;
mask.s_addr = se->netmask;
printf(" mask %s", inet_ntoa(mask));
}
if (se->af == AF_INET6)
if (se->netmask != 128)
printf(" mask %i", se->netmask);
if (se->pe_name[0])
printf(" pe %s", se->pe_name);
}
if (se->flags & IP_VS_SVC_F_ONEPACKET)
printf(" ops");
}
printf("\n");
/* print all the destination entries */
if (!(format & FMT_NOSORT))
ipvs_sort_dests(d, ipvs_cmp_dests);
for (i = 0; i < d->num_dests; i++) {
char *dname;
ipvs_dest_entry_t *e = &d->entrytable[i];
if (!(dname = addrport_to_anyname(e->af, &(e->addr), ntohs(e->port),
se->protocol, format))) {
fprintf(stderr, "addrport_to_anyname fails\n");
exit(1);
}
if (!(format & FMT_RULE) && (e->af != AF_INET6))
dname[28] = '\0';
if (format & FMT_RULE) {
printf("-a %s -r %s %s -w %d\n", svc_name, dname,
fwd_switch(e->conn_flags), e->weight);
} else if (format & FMT_STATS) {
printf(" -> %-28s", dname);
print_largenum(e->stats64.conns, format);
print_largenum(e->stats64.inpkts, format);
print_largenum(e->stats64.outpkts, format);
print_largenum(e->stats64.inbytes, format);
print_largenum(e->stats64.outbytes, format);
printf("\n");
} else if (format & FMT_RATE) {
printf(" -> %-28s %8llu %8llu %8llu", dname,
e->stats64.cps,
e->stats64.inpps,
e->stats64.outpps);
print_largenum(e->stats64.inbps, format);
print_largenum(e->stats64.outbps, format);
printf("\n");
} else if (format & FMT_THRESHOLDS) {
printf(" -> %-28s %-10u %-10u %-10u %-10u\n", dname,
e->u_threshold, e->l_threshold,
e->activeconns, e->inactconns);
} else if (format & FMT_PERSISTENTCONN) {
printf(" -> %-28s %-9u %-11u %-10u %-10u\n", dname,
e->weight, e->persistconns,
e->activeconns, e->inactconns);
} else
printf(" -> %-28s %-7s %-6d %-10u %-10u\n",
dname, fwd_name(e->conn_flags),
e->weight, e->activeconns, e->inactconns);
free(dname);
}
free(d);
}
static void list_service(ipvs_service_t *svc, unsigned int format)
{
ipvs_service_entry_t *entry;
if (!(entry = ipvs_get_service(svc->fwmark, svc->af, svc->protocol,
svc->addr, svc->port))) {
fprintf(stderr, "%s\n", ipvs_strerror(errno));
exit(1);
}
print_title(format);
print_service_entry(entry, format);
free(entry);
}
static void list_all(unsigned int format)
{
struct ip_vs_get_services *get;
int i;
if (!(format & FMT_RULE))
printf("IP Virtual Server version %d.%d.%d (size=%d)\n",
NVERSION(ipvs_info.version), ipvs_info.size);
if (!(get = ipvs_get_services())) {
fprintf(stderr, "%s\n", ipvs_strerror(errno));
exit(1);
}
if (!(format & FMT_NOSORT))
ipvs_sort_services(get, ipvs_cmp_services);
print_title(format);
for (i = 0; i < get->num_services; i++)
print_service_entry(&get->entrytable[i], format);
free(get);
}
void list_timeout(void)
{
ipvs_timeout_t *u;
if (!(u = ipvs_get_timeout()))
exit(1);
printf("Timeout (tcp tcpfin udp): %d %d %d\n",
u->tcp_timeout, u->tcp_fin_timeout, u->udp_timeout);
free(u);
}
static void list_daemon(void)
{
ipvs_daemon_t *u;
int i;
if (!(u = ipvs_get_daemon()))
exit(1);
for (i = 0; i < 2; i++) {
char *type;
if (u[i].state & IP_VS_STATE_MASTER)
type = "master";
else if (u[i].state & IP_VS_STATE_BACKUP)
type = "backup";
else
continue;
printf("%s sync daemon (mcast=%s, syncid=%d",
type, u[i].mcast_ifn, u[i].syncid);
if (u[i].sync_maxlen)
printf(", maxlen=%u", u[i].sync_maxlen);
if (u[i].mcast_af != AF_UNSPEC) {
char addr[INET6_ADDRSTRLEN];
if (inet_ntop(u[i].mcast_af, &u[i].mcast_group,
addr, sizeof(addr)))
printf(", group=%s", addr);
}
if (u[i].mcast_port)
printf(", port=%u", u[i].mcast_port);
if (u[i].mcast_ttl)
printf(", ttl=%u", u[i].mcast_ttl);
printf(")\n");
}
free(u);
}
int host_to_addr(const char *name, struct in_addr *addr)
{
struct hostent *host;
if ((host = gethostbyname(name)) != NULL) {
if (host->h_addrtype != AF_INET ||
host->h_length != sizeof(struct in_addr))
return -1;
/* warning: we just handle h_addr_list[0] here */
memcpy(addr, host->h_addr_list[0], sizeof(struct in_addr));
return 0;
}
return -1;
}
static char * addr_to_host(int af, const void *addr)
{
struct hostent *host;
if (af == AF_INET)
host = gethostbyaddr((char *) addr,
sizeof(struct in_addr), af);
else
host = gethostbyaddr((char *) addr,
sizeof(struct in6_addr), af);
if (host != NULL)
return (char *) host->h_name;
else
return (char *) NULL;
}
static char * addr_to_anyname(int af, const void *addr)
{
char *name;
static char buf[INET6_ADDRSTRLEN];
if ((name = addr_to_host(af, addr)) != NULL)
return name;
inet_ntop(af, addr, buf, sizeof(buf));
return buf;
}
int service_to_port(const char *name, unsigned short proto)
{
struct servent *service;
if (proto == IPPROTO_TCP
&& (service = getservbyname(name, "tcp")) != NULL)
return ntohs((unsigned short) service->s_port);
else if (proto == IPPROTO_UDP
&& (service = getservbyname(name, "udp")) != NULL)
return ntohs((unsigned short) service->s_port);
else if (proto == IPPROTO_SCTP
&& (service = getservbyname(name, "sctp")) != NULL)
return ntohs((unsigned short) service->s_port);
else
return -1;
}
static char * port_to_service(unsigned short port, unsigned short proto)
{
struct servent *service;
if (proto == IPPROTO_TCP &&
(service = getservbyport(htons(port), "tcp")) != NULL)
return service->s_name;
else if (proto == IPPROTO_UDP &&
(service = getservbyport(htons(port), "udp")) != NULL)
return service->s_name;
else if (proto == IPPROTO_SCTP &&
(service = getservbyport(htons(port), "sctp")) != NULL)
return service->s_name;
else
return (char *) NULL;
}
static char * port_to_anyname(unsigned short port, unsigned short proto)
{
char *name;
static char buf[10];
if ((name = port_to_service(port, proto)) != NULL)
return name;
else {
sprintf(buf, "%u", port);
return buf;
}
}
static char *
addrport_to_anyname(int af, const void *addr, unsigned short port,
unsigned short proto, unsigned int format)
{
char *buf, pbuf[INET6_ADDRSTRLEN];
if (!(buf=malloc(60)))
return NULL;
if (format & FMT_NUMERIC) {
snprintf(buf, 60, "%s%s%s:%u",
af == AF_INET ? "" : "[",
inet_ntop(af, addr, pbuf, sizeof(pbuf)),
af == AF_INET ? "" : "]",
port);
} else {
snprintf(buf, 60, "%s%s%s:%s",
af == AF_INET ? "" : "[",
addr_to_anyname(af, addr),
af == AF_INET ? "" : "]",
port_to_anyname(port, proto));
}
return buf;
}
static int str_is_digit(const char *str)
{
size_t offset;
size_t top;
top = strlen(str);
for (offset=0; offset<top; offset++) {
if (!isdigit((int)*(str+offset))) {
break;
}
}
return (offset<top)?0:1;
}