aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwensong <wensong@36f5d8da-7431-0410-8ca5-ec586ed2521a>1999-07-01 09:13:04 +0000
committerSimon Horman <horms@verge.net.au>2013-05-22 15:03:32 +0900
commit5a868fd4d95d20fcc5b901681cdbd5a7382f6aba (patch)
tree15de41b5a3186ecbb8bd2d20784bc21be3757165
parent13da3fbd884cad8afff6b7b09eef536e26e69442 (diff)
downloadipvsadm-5a868fd4d95d20fcc5b901681cdbd5a7382f6aba.tar.gz
ipvsadm v1.12 releasev1.12
git-svn-id: http://svn.linuxvirtualserver.org/repos/ipvsadm/trunk@39 36f5d8da-7431-0410-8ca5-ec586ed2521a
-rw-r--r--Makefile112
-rw-r--r--README51
-rw-r--r--config_stream.c107
-rw-r--r--config_stream.h19
-rw-r--r--dynamic_array.c256
-rw-r--r--dynamic_array.h177
-rw-r--r--ipvsadm-restore27
-rw-r--r--ipvsadm-save27
-rw-r--r--ipvsadm.8412
-rw-r--r--ipvsadm.c1464
-rw-r--r--ipvsadm.sh83
-rw-r--r--ipvsadm.spec.in79
12 files changed, 2814 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ca1b264
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,112 @@
+#
+# ipvsadm - IP Virtual Server ADMinistration program
+#
+# Version: $Id$
+#
+# Authors: Wensong Zhang <wensong@iinchina.net>
+# Peter Kese <peter.kese@ijs.si>
+#
+# This file:
+#
+# ChangeLog
+#
+# Wensong : Modified the Makefile and the spec files so
+# : that rpms can be created with ipvsadm alone
+# P.Copeland : Modified the Makefile and the spec files so
+# : that it is possible to create rpms on the fly
+# : using 'make rpms'
+# : Also added NAME, VERSION and RELEASE numbers to
+# : the Makefile
+# Horms : Updated to add config_stream.c dynamic_array.c
+# : Added autodetection of libpot
+# : Added BUILD_ROOT support
+# Wensong : Changed the OBJS according to detection
+# Horms : Moved ipvsadm back into /sbin where it belongs
+# as it is more or less analogous to both route
+# and ipchains both of which reside in /sbin.
+# Added rpm target whose only dependancy is
+# the rpms target
+#
+
+NAME = ipvsadm
+VERSION = 1.12
+RELEASE = 1
+
+CC = gcc
+CFLAGS = -Wall -Wunused -g -O2
+SBIN = $(BUILD_ROOT)/sbin
+MAN = $(BUILD_ROOT)/usr/man/man8
+MKDIR = mkdir
+INSTALL = install
+INCLUDE = -I/usr/src/linux/include
+LIB_SEARCH = /lib /usr/lib /usr/local/lib
+
+# Where to install INIT scripts
+# Will only install files here if these directories already exist
+# as if the directories don't exist then the system is unlikely to
+# use the files
+INIT = $(BUILD_ROOT)/etc/rc.d/init.d
+
+#####################################
+# No servicable parts below this line
+
+POPT_LIB = $(shell for i in $(LIB_SEARCH); do \
+ if [ -f $$i/libpopt.a ]; then \
+ if nm $$i/libpopt.a | fgrep -q poptGetContext; then \
+ echo "-L$$i -lpopt"; \
+ fi; \
+ fi; \
+done)
+
+ifneq (,$(POPT_LIB))
+POPT_DEFINE = -DHAVE_POPT
+OBJS = config_stream.o dynamic_array.o ipvsadm.o
+else
+OBJS = ipvsadm.o
+endif
+
+LIBS = $(POPT_LIB)
+DEFINES = $(POPT_DEFINE)
+
+.PHONY = all clean install
+
+all: ipvsadm
+
+ipvsadm: $(OBJS)
+ $(CC) $(CFLAGS) -o ipvsadm $(OBJS) $(LIBS)
+
+install: ipvsadm
+ strip ipvsadm
+ if [ ! -d $(SBIN) ]; then $(MKDIR) -p $(SBIN); fi
+ $(INSTALL) -m 0755 ipvsadm $(SBIN)
+ $(INSTALL) -m 0755 ipvsadm-save $(SBIN)
+ $(INSTALL) -m 0755 ipvsadm-restore $(SBIN)
+ if [ ! -d $(MAN) ]; then $(MKDIR) -p $(MAN); fi
+ $(INSTALL) -m 0644 ipvsadm.8 $(MAN)
+ if [ -d $(INIT) ]; then \
+ $(INSTALL) -m 0755 ipvsadm.sh $(INIT)/ipvsadm ;\
+ fi
+
+clean:
+ rm -f ipvsadm *.o core *~ $(NAME).spec \
+ $(NAME)-$(VERSION).tar.gz
+
+dist: clean
+ sed -e "s/@@VERSION@@/$(VERSION)/g" \
+ -e "s/@@RELEASE@@/$(RELEASE)/g" \
+ < ipvsadm.spec.in > ipvsadm.spec
+ ( cd .. ; tar czvf $(NAME)-$(VERSION).tar.gz \
+ --exclude CVS \
+ --exclude $(NAME)-$(VERSION).tar.gz \
+ ipvsadm ; \
+ mv $(NAME)-$(VERSION).tar.gz ipvsadm )
+
+rpm: rpms
+
+rpms: dist
+ cp $(NAME)-$(VERSION).tar.gz /usr/src/redhat/SOURCES/
+ cp $(NAME).spec /usr/src/redhat/SPECS/
+ (cd /usr/src/redhat/SPECS/ ; rpm -ba $(NAME).spec)
+
+%.o: %.c
+ $(CC) $(CFLAGS) $(INCLUDE) $(DEFINES) -c $<
diff --git a/README b/README
new file mode 100644
index 0000000..8231d62
--- /dev/null
+++ b/README
@@ -0,0 +1,51 @@
+--------------------------------------------------------------------------
+
+ipvsadm - Version 1.11 - 16th June 2000
+
+This is free software. See below for details.
+
+ippfvsadm is a utility to administer the IP virtual server services
+offered by the Linux kernel with virtual server patch.
+
+This version of ipvsadm requires Linux kernel version 2.2.14 plus IPVS
+patch 0.9.10 or later. Check out the Linux Virtual Server Project
+home page on the World Wide Web:
+ http://www.LinuxVirtualServer.org/
+ or
+ http://www.Linux-VS.org/
+for the most recent information and original sources about ipvsadm.
+
+To make, make sure your Linux kernel is already patched with IPVS and
+"make menuconfig" or "make xconfig" to setup the right compiling options,
+see the README of the virtual server patch for detail, then simply type
+ make
+in the source directory.
+
+Install to your liking. We suggest the following pathnames:
+ /sbin/ipvsadm
+ /usr/man/man8/ipvsadm.8
+This will be done automatically when calling
+ make install
+in the source directory.
+
+
+Wensong Zhang <wensong@iinchina.net>
+
+
+--------------------------------------------------------------------------
+
+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.
+
+--------------------------------------------------------------------
diff --git a/config_stream.c b/config_stream.c
new file mode 100644
index 0000000..7692ccc
--- /dev/null
+++ b/config_stream.c
@@ -0,0 +1,107 @@
+/*
+ * Code to convert a stream input into a dynamic array
+ * that can be parsed as argc and argv.
+ *
+ * Authors: Horms <horms@vergenet.net>
+ *
+ * Released under the terms of the GNU GPL
+ */
+
+#include "config_stream.h"
+
+
+/**********************************************************************
+ * config_stream_read
+ * Read in a config file and put elements in a dynamic array
+ * pre: stream: stream to read configuration from
+ * return: dynamic array whose elements are the space delimited
+ * tokens read from the stream. Result is returned
+ * once a newline is reached so multiple calls
+ * will be required to read an entire stream.
+ * Everthing including and after a hash (#) on a line is
+ * ignored
+ **********************************************************************/
+
+dynamic_array_t *config_stream_read (FILE *stream, const char *first_element){
+ char token[MAX_LINE_LENGTH];
+ char tail[2];
+ char format[MAX_LINE_LENGTH];
+ char format_whitespace[MAX_LINE_LENGTH];
+ int status;
+ int ntoken;
+ int comment=0;
+ char *s;
+ int c;
+ int flag;
+ dynamic_array_t *a;
+
+ extern int errno;
+
+ if((a=dynamic_array_create((size_t)0))==NULL){
+ perror("config_file_read: dynamic_array_create");
+ return(NULL);
+ }
+
+ /*insert a argv[0] into the dynamic array*/
+ if((a=dynamic_array_add_element(
+ a,
+ (first_element!=NULL?first_element:""),
+ DESTROY_STR,
+ DUP_STR
+ ))==NULL){
+ perror("config_file_read: dynamic_array_add_element");
+ return(NULL);
+ }
+
+ sprintf(format, "%%%d[^ \t\n\r]%%1[ \t\n\r]", MAX_LINE_LENGTH);
+ sprintf(format_whitespace, "%%%d[ \t\r]%%1[\n]", MAX_LINE_LENGTH);
+
+ ntoken=0;
+ while((status=fscanf(stream, format, token, tail))!=EOF){
+ if(status==0) {
+ flag=1;
+ while(flag){
+ c=fgetc(stream);
+ switch(c){
+ case EOF:
+ case '\n':
+ return(a);
+ case '\t':
+ case '\r':
+ case ' ':
+ break;
+ default:
+ ungetc(c, stream);
+ flag=0;
+ }
+ }
+ continue;
+ }
+ if(!comment && strcmp(token, "ipvsadm")){
+ ntoken++;
+ if((a=dynamic_array_add_element(
+ a,
+ token,
+ DESTROY_STR,
+ DUP_STR
+ ))==NULL){
+ perror("config_file_read: dynamic_array_add_element");
+ dynamic_array_destroy(a, DESTROY_STR);
+ return(NULL);
+ }
+ }
+ if((s=strrchr(tail, '\n'))!=NULL){
+ return(a);
+ }
+ if(!comment){
+ comment=(strchr((s!=NULL?s:tail), '#')==NULL)?0:1;
+ }
+ }
+
+ if(ntoken==0){
+ dynamic_array_destroy(a, DESTROY_STR);
+ return(NULL);
+ }
+
+ return(a);
+}
diff --git a/config_stream.h b/config_stream.h
new file mode 100644
index 0000000..528c01f
--- /dev/null
+++ b/config_stream.h
@@ -0,0 +1,19 @@
+/*
+ * Code to convert a stream input into a dynamic array
+ * that can be parsed as argc and argv.
+ *
+ * Authors: Horms <horms@vergenet.net>
+ *
+ * Released under the terms of the GNU GPL
+ */
+
+#ifndef CONFIG_STREAM_FLIM
+#define CONFIG_STREAM_FLIM
+
+#include "dynamic_array.h"
+
+#define MAX_LINE_LENGTH 4096
+
+dynamic_array_t *config_stream_read (FILE *stream, const char *first_element);
+
+#endif
diff --git a/dynamic_array.c b/dynamic_array.c
new file mode 100644
index 0000000..c8980db
--- /dev/null
+++ b/dynamic_array.c
@@ -0,0 +1,256 @@
+/*
+ * Dynamic array, to store all your flims in Includes macros required
+ * to create an array of strings but as the primitive type for the
+ * array is void * providing your own duplicate_primitive and
+ * destroy_primitive functions will allow you to use the dynamic_array
+ * API to have a dynamic array containing any primitive
+ *
+ * Authors: Horms <horms@vergenet.net>
+ *
+ * Released under the terms of the GNU GPL
+ *
+ */
+
+#include "dynamic_array.h"
+
+
+/**********************************************************************
+ * dynamic_array_create
+ * Create a dynamic array
+ * pre: block_size: blocking size to use.
+ * DEFAULT_DYNAMIC_ARRAY_BLOCK_SIZE is used if block_size is 0
+ * Block size refers to how many elements are prealocated
+ * each time the array is grown.
+ * return: An empty dynamic array
+ * NULL on error
+ **********************************************************************/
+
+dynamic_array_t *dynamic_array_create(size_t block_size){
+ dynamic_array_t *a;
+
+ extern int errno;
+
+ if((a=(dynamic_array_t *)malloc(sizeof(dynamic_array_t)))==NULL){
+ return(NULL);
+ }
+
+ a->vector=NULL;
+ a->count=0;
+ a->allocated_size=0;
+ a->block_size=block_size?block_size:DEFAULT_DYNAMIC_ARRAY_BLOCK_SIZE;
+
+ return(a);
+}
+
+
+/**********************************************************************
+ * dynamic_array_destroy
+ * Free an array an all the elements held within
+ * pre: a: array to destroy
+ * destroy_element: pointer to funtion to destroy array elements
+ * Function should take an argument of a pointer
+ * and free the memory allocated to the structure
+ * pointed to.
+ * post: array is freed and destroy_element is called for all elements
+ * of the array.
+ * Nothing if a is NULL
+ **********************************************************************/
+
+void dynamic_array_destroy(dynamic_array_t *a, void (*destroy_element)(void *)){
+ if(a==NULL) return;
+ while(a->count-->0){
+ destroy_element(*(a->vector+a->count));
+ }
+ if(a->allocated_size>0){
+ free(a->vector);
+ }
+}
+
+
+/**********************************************************************
+ * dynamic_array_add_element
+ * Add an element to a dynamic array
+ * pre: a: dynamic array to add element to
+ * e: element to add
+ * destroy_element: pointer to a function to destroy an element
+ * passed to dynamic_array_destroy on error
+ * duplicate_element: pointer to a function to duplicate an
+ * element should take a pointer to an element
+ * to duplicate as the only element and return
+ * a copy of the element Any memory allocation
+ * required should be done by this function.
+ * post: element in inserted in the first unused position in the array
+ * array size is incresaed by a->block_size if there is
+ * insufficient room in the array to add the element.
+ * Nothing is done if e is NULL
+ * return: a on success
+ * NULL if a is NULL or an error occurs
+ **********************************************************************/
+
+dynamic_array_t *dynamic_array_add_element(
+ dynamic_array_t *a,
+ const void *e,
+ void (*destroy_element)(void *s),
+ void *(*duplicate_element)(const void *s)
+){
+ extern int errno;
+
+ if(a==NULL) return(NULL);
+ if(e==NULL) return(a);
+ if(a->count==a->allocated_size){
+ a->allocated_size+=a->block_size;
+ if(
+ (a->vector=(void**)realloc(a->vector,a->allocated_size*sizeof(void*)))
+ ==NULL
+ ){
+ dynamic_array_destroy(a, destroy_element);
+ return(NULL);
+ }
+ }
+ if((*(a->vector+a->count)=(void *)duplicate_element(e))==NULL){
+ return(NULL);
+ }
+ a->count++;
+
+ return(a);
+}
+
+
+/**********************************************************************
+ * dynamic_array_display
+ * Print the contents of a dynamic array to a string
+ * pre: a: dynamic array to display
+ * delimiter: character to place between elements of the array
+ * display_element: pointer to a function to display an element
+ * element_length: pointer to a function to return the
+ * length of an element
+ * post: If a is NULL or there are no elements in a then nothing is done
+ * Else a character buffer is alocated and the contents
+ * of each array element, separated by delimiter is placed
+ * in the '\0' termintated buffer returned. It is up to the
+ * user to free this buffer.
+ * return: Allocated buffer as above
+ * NULL on error, NULL a or empty a
+ **********************************************************************/
+
+char *dynamic_array_display(
+ dynamic_array_t *a,
+ char delimiter,
+ void (*display_element)(char *, void *),
+ size_t (*element_length)(void *)
+){
+ void **a_current;
+ void **a_top;
+ char *buffer;
+ char *buffer_current;
+ size_t nochar;
+ size_t len=0;
+
+ if(a==NULL || a->count==0){ return(NULL); }
+ a_top=a->vector+a->count;
+ nochar=a->count;
+ for(a_current=a->vector;a_current<a_top;a_current++){
+ nochar+=(len=element_length(*a_current));
+ if(!len){
+ nochar--;
+ }
+ }
+ if((buffer=(char*)malloc(nochar))==NULL){
+ return(NULL);
+ }
+ buffer_current=buffer;
+ for(a_current=a->vector;a_current<a_top;a_current++){
+ if((len=element_length(*a_current))){
+ display_element(buffer_current, *a_current);
+ buffer_current+=element_length(*a_current);
+ *buffer_current++=delimiter;
+ }
+ }
+ if(len){
+ buffer_current--;
+ }
+ *buffer_current='\0';
+ return(buffer);
+}
+
+
+/**********************************************************************
+ * dynamic_array_get_element
+ * Get an element from an array
+ * pre: a: array to retrieve element from
+ * elementno: index element in array to retrieve
+ * post: no change is made to a
+ * return: element requested
+ * NULL if element is beyond the number of elements in the arary
+ **********************************************************************/
+
+void *dynamic_array_get_element(dynamic_array_t *a, size_t elementno){
+ if(elementno>a->count) return(NULL);
+ return(*((a->vector)+elementno));
+}
+
+
+/**********************************************************************
+ * dynamic_array_get_count
+ * Get the number of elements in the array
+ * pre: array to find the number of elements in
+ * return: number of elements in the array
+ * -1 if a is NULL
+ **********************************************************************/
+
+size_t dynamic_array_get_count(dynamic_array_t *a){
+ if(a==NULL) return(-1);
+ return(a->count);
+}
+
+
+/**********************************************************************
+ * dynamic_array_get_vector
+ * Get the array contained in the dynamic array
+ * pre: array to find the vector of
+ * return: vector
+ * NULL if a is NULL
+ **********************************************************************/
+
+void *dynamic_array_get_vector(dynamic_array_t *a){
+ if(a==NULL) return(NULL);
+ return(a->vector);
+}
+
+
+/**********************************************************************
+ * dynamic_array_split_str
+ * Split a string into substrings on a delimiter
+ * pre: str: string to split
+ * delimiter: character to split string on
+ * post: string is split.
+ * Note: The string is modified.
+ * return: dynamic array containing sub_strings
+ * NULL on error
+ * string being NULL is an error state
+ **********************************************************************/
+
+dynamic_array_t *dynamic_array_split_str(char *string, const char delimiter){
+ dynamic_array_t *a;
+ char *sub_string;
+
+ if(string==NULL){ return(NULL); }
+ if((a=dynamic_array_create(0))==NULL){
+ return(NULL);
+ }
+ while((sub_string=strchr(string, delimiter))!=NULL){
+ *sub_string='\0';
+ if(dynamic_array_add_element(a, string, DESTROY_STR, DUP_STR)==NULL){
+ return(NULL);
+ }
+ string=sub_string+1;
+ }
+ if(
+ *string!='\0' &&
+ dynamic_array_add_element(a, string, DESTROY_STR, DUP_STR)==NULL
+ ){
+ return(NULL);
+ }
+ return(a);
+}
+
diff --git a/dynamic_array.h b/dynamic_array.h
new file mode 100644
index 0000000..f1344d7
--- /dev/null
+++ b/dynamic_array.h
@@ -0,0 +1,177 @@
+/*
+ * Dynamic array, to store all your flims in Includes macros required
+ * to create an array of strings but as the primitive type for the
+ * array is void * providing your own duplicate_primitive and
+ * destroy_primitive functions will allow you to use the dynamic_array
+ * API to have a dynamic array containing any primitive
+ *
+ * Authors: Horms <horms@vergenet.net>
+ *
+ * Released under the terms of the GNU GPL
+ *
+ */
+
+#ifndef DYNAMIC_ARRAY_FLIM
+#define DYNAMIC_ARRAY_FLIM
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+
+/*
+ * Default blocking size for dynamic array
+ * can be overriden when array is created
+ */
+#define DEFAULT_DYNAMIC_ARRAY_BLOCK_SIZE (size_t)7
+
+
+/* #defines to destroy and dupilcate strings */
+#define DESTROY_STR (void (*)(void *s))free
+#define DUP_STR (void *(*)(const void *s))strdup
+#define DISPLAY_STR (void (*)(char *d, void *s))strcpy
+#define LEN_STR (size_t (*)(void *s))strlen
+
+
+typedef struct {
+ void **vector;
+ size_t count;
+ size_t allocated_size;
+ size_t block_size;
+} dynamic_array_t;
+
+
+/**********************************************************************
+ * dynamic_array_create
+ * Create a dynamic array
+ * pre: block_size: blocking size to use.
+ * DEFAULT_DYNAMIC_ARRAY_BLOCK_SIZE is used if
+ * block_size is 0
+ * Block size refers to how many elements are prealocated
+ * each time the array is grown.
+ * return: An empty dynamic array
+ * NULL on error
+ **********************************************************************/
+
+dynamic_array_t *dynamic_array_create(size_t block_size);
+
+
+/**********************************************************************
+ * dynamic_array_destroy
+ * Free an array an all the elements held within
+ * pre: a: array to destroy
+ * destroy_element: pointer to funtion to destroy array elements
+ * Function should take an argument of a pointer
+ * and free the memory allocated to the structure
+ * pointed to.
+ * post: array is freed and destroy_element is called for all elements
+ * of the array.
+ * Nothing if a is NULL
+ **********************************************************************/
+
+void dynamic_array_destroy(dynamic_array_t *a, void (*destroy_element)(void *));
+
+
+/**********************************************************************
+ * dynamic_array_add_element
+ * Add an element to a dynamic array
+ * pre: a: dynamic array to add element to
+ * e: element to add
+ * destroy_element: pointer to a function to destroy an element
+ * passed to dynamic_array_destroy on error
+ * duplicate_element: pointer to a function to duplicate an
+ * element should take a pointer to an element
+ * to duplicate as the only element and return
+ * a copy of the element Any memory allocation
+ * required should be done by this function.
+ * post: element in inserted in the first unused position in the array
+ * array size is incresaed by a->block_size if there is
+ * insufficient room in the array to add the element.
+ * Nothing is done if e is NULL
+ * return: a on success
+ * NULL if a is NULL or an error occurs
+ **********************************************************************/
+
+dynamic_array_t *dynamic_array_add_element(
+ dynamic_array_t *a,
+ const void *e,
+ void (*destroy_element)(void *s),
+ void *(*duplicate_element)(const void *s)
+);
+
+
+/**********************************************************************
+ * dynamic_array_display
+ * Print the contents of a dynamic array to a string
+ * pre: a: dynamic array to display
+ * delimiter: character to place between elements of the array
+ * display_element: pointer to a function to display an element
+ * element_length: pointer to a function to return the
+ * length of an element
+ * post: If a is NULL or there are no elements in a then nothing is done
+ * Else a character buffer is alocated and the contents
+ * of each array element, separated by delimiter is placed
+ * in the '\0' termintated buffer returned. It is up to the
+ * user to free this buffer.
+ * return: Allocated buffer as above
+ * NULL on error, NULL a or empty a
+ **********************************************************************/
+
+char *dynamic_array_display(
+ dynamic_array_t *a,
+ char delimiter,
+ void (*display_element)(char *, void *),
+ size_t (*element_length)(void *)
+);
+
+
+/**********************************************************************
+ * dynamic_array_get_element
+ * Get an element from an array
+ * pre: a: array to retrieve element from
+ * elementno: index element in array to retrieve
+ * post: no change is made to a
+ * return: element requested
+ * NULL if element is beyond the number of elements in the arary
+ **********************************************************************/
+
+void *dynamic_array_get_element(dynamic_array_t *a, size_t elementno);
+
+
+/**********************************************************************
+ * dynamic_array_get_count
+ * Get the number of elements in the array
+ * pre: array to find the number of elements in
+ * return: number of elements in the array
+ * -1 if a is NULL
+ **********************************************************************/
+
+size_t dynamic_array_get_count(dynamic_array_t *a);
+
+/**********************************************************************
+ * dynamic_array_get_vector
+ * Get the array contained in the dynamic array
+ * pre: array to find the vector of
+ * return: vector
+ * NULL if a is NULL
+ **********************************************************************/
+
+void *dynamic_array_get_vector(dynamic_array_t *a);
+
+
+/**********************************************************************
+ * dynamic_array_split_str
+ * Split a string into substrings on a delimiter
+ * pre: str: string to split
+ * delimiter: character to split string on
+ * post: string is split.
+ * Note: The string is modified.
+ * return: dynamic array containing sub_strings
+ * NULL on error
+ * string being NULL is an error state
+ **********************************************************************/
+
+dynamic_array_t *dynamic_array_split_str(char *string, const char delimiter);
+
+#endif
diff --git a/ipvsadm-restore b/ipvsadm-restore
new file mode 100644
index 0000000..9abf406
--- /dev/null
+++ b/ipvsadm-restore
@@ -0,0 +1,27 @@
+#!/bin/bash
+# ipvsadm-save - Save IPVS rules
+#
+# A very simple wrapper to save IPVS rules
+# Inspired by ipchains-save.
+#
+# Version: $Id$
+#
+# Script Author: Horms <horms@vergenet.net>
+#
+# This file:
+#
+# ChangeLog
+#
+#
+
+# The path is fixed, this should probably be generated at
+# compile time but since the Makefile isn't autogenereated
+# having static values is ok. /sbin is the default location
+# for ipvsadm, /usr/sbin is where some distributions like
+# to put it
+
+PATH=/sbin:/usr/sbin
+
+# All the work is actually done in ipvsadm, horay
+
+ipvsadm -R
diff --git a/ipvsadm-save b/ipvsadm-save
new file mode 100644
index 0000000..6663852
--- /dev/null
+++ b/ipvsadm-save
@@ -0,0 +1,27 @@
+#!/bin/bash
+# ipvsadm-save - Save IPVS rules
+#
+# A very simple wrapper to save IPVS rules
+# Inspired by ipchains-save.
+#
+# Version: $Id$
+#
+# Script Author: Horms <horms@vergenet.net>
+#
+# This file:
+#
+# ChangeLog
+#
+#
+
+# The path is fixed, this should probably be generated at
+# compile time but since the Makefile isn't autogenereated
+# having static values is ok. /sbin is the default location
+# for ipvsadm, /usr/sbin is where some distributions like
+# to put it
+
+PATH=/sbin:/usr/sbin
+
+# All the work is actually done in ipvsadm, horay
+
+ipvsadm -S
diff --git a/ipvsadm.8 b/ipvsadm.8
new file mode 100644
index 0000000..42368ee
--- /dev/null
+++ b/ipvsadm.8
@@ -0,0 +1,412 @@
+.\"
+.\" ipvsadm(8) manual page
+.\"
+.\" $Id$
+.\"
+.\" Authors: Mike Wangsmo <wanger@redhat.com>
+.\" Wensong Zhang <wensong@iinchina.net>
+.\" Horms <horms@valinux.com>
+.\"
+.\" Changes:
+.\" Horms : Updated to reflect recent change of ipvsadm
+.\" : Style guidance taken from ipchains(8)
+.\" where appropriate.
+.\" Wensong Zhang : Added a short note about the defense strategies
+.\" Horms : Tidy up some of the description and the
+.\" grammar in the -f and sysctl sections
+.\" Horms : Fixed minor grammatical and technical errors.
+.\" Added description of usefulness of fwmark services
+.\" Added note on using persistence and
+.\" ip_masq_ftp in conjunction with FTP.
+.\" Added example for fwmark services
+.\" Wensong Zhang : Added description about the lblc scheduler
+.\"
+.\" 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.
+.\"
+.\"
+.TH IPVSADM 8 "20th October 2000" "LVS Administration" " Linux Administrator's Guide"
+.UC 4
+.SH NAME
+ipvsadm \- Linux Virtual Server administration
+.SH SYNOPSIS
+.B ipvsadm -[A|E] -[t|u|f] \fIservice-address\fP [-s \fIscheduler\fP]
+.ti 15
+.B [-p [\fItimeout\fP]] [-M \fInetmask\fP]
+.br
+.B ipvsadm -D -[t|u|f] \fIservice-address\fP
+.br
+.B ipvsadm -C
+.br
+.B ipvsadm -R
+.br
+.B ipvsadm -S [-n]
+.br
+.B ipvsadm -[a|e] -[t|u|f] \fIservice-address\fP
+.ti 15
+.B -[r|R] \fIserver-address\fP [-g|-i|-m] [-w \fIweight\fP]
+.br
+.B ipvsadm -d -[t|u|f] \fIservice-address\fP -[r|R] \fIserver-address\fP
+.br
+.B ipvsadm -[L|l] [-n]
+.br
+.B ipvsadm -h
+.SH DESCRIPTION
+\fBIpvsadm\fR(8) is used to set up, maintain or inspect the virtual
+server table in the Linux kernel. The Linux Virtual Server can be used
+to build scalable network services based on a cluster of two or more
+nodes. The active node of the cluster redirects service requests to a
+collection of server hosts that will actually perform the
+services. Supported features include two protocols (TCP and UDP),
+three packet-forwarding methods (NAT, tunnelling, and direct routing),
+and four load balancing algorithms (round robin, weighted round robin,
+least-connection and weighted least-connection).
+.PP
+The command has two basic formats for execution:
+.TP
+.B ipvsadm \fICOMMAND\fP [\fIprotocol\fP] \fIservice-address\fP
+.ti 15
+.B [\fIscheduling-method\fP] [\fIpersistence options\fP]
+.TP
+.B ipvsadm \fIcommand\fP [\fIprotocol\fP] \fIservice-address\fP
+.ti 15
+.B \fIserver-address\fP [\fIpacket-forwarding-method\fP]
+.ti 15
+.B [\fIweight options\fP]
+.PP
+The first format manipulates a virtual service and the algorithm for
+assigning service requests to real servers. Optionally, a
+persistent timeout and netmask mask for the granularity of a persistent
+service may be specified. The second format manipulates a real server
+that is associated with an existing virtual service. When specifying
+a real server, the packet-forwarding method and the weight of the real
+server, relative to other real servers for the virtual service, may be
+specified, otherwise defaults will be used.
+.SS COMMANDS
+\fBipvsadm\fR(8) recognises the commands described below. Upper-case
+commands maintain virtual services. Lower-case commands maintain real
+servers that are associated with a virtual service.
+.TP
+\fB-A, --add-service\fR
+Add a virtual service. A service address is uniquely defined by a
+triplet: IP address, port number, and protocol. Alternatively, a
+virtual service may be defined by a firewall-mark.
+.TP
+\fB-E, --edit-service\fR
+Edit a virtual service.
+.TP
+\fB-D, --delete-service\fR
+Delete a virtual service, along with any associated real servers.
+.TP
+\fB-C, --clear\fR
+Clear the virtual server table.
+.TP
+\fB-R, --restore\fR
+Restore Linux Virtual Server rules from stdin. Each line read from stdin
+will be treated as the command line options to a separate invocation of
+\fIipvsadm\fP. Lines read from stdin can optionally begin with "ipvsadm".
+This option is useful to avoid executing a large number or \fIipvsadm\fP
+commands when constructing an extensive routing table.
+.sp
+This option only works if \fIipvsadm\fP is compiled against \fBpopt\fR(3).
+.TP
+\fB-S, --save\fR
+Dump the Linux Virtual Server rules to stdout in a format that can be read
+by -R|--restore.
+.sp
+This option only works if \fIipvsadm\fP is compiled against \fBpopt\fR(3).
+.TP
+\fB-a, --add-server\fR
+Add a real server to a virtual service.
+.TP
+\fB-d, --delete-server\fR
+Remove a real server from a virtual service.
+.TP
+\fB-L, -l, --list\fR
+Display the virtual server table.
+.TP
+\fB-h, --help\fR
+Display a description of the command syntax.
+.SS PARAMETERS
+The commands above accept or require zero or more of the following
+parameters.
+.TP
+.B -t, --tcp-service \fIservice-address\fP
+Use TCP service. The \fIservice-address\fP is of the form
+\fIhost[:port]\fP. \fIHost\fP may be either an IP address or a
+hostname. \fIPort\fP may be either a port number or the service name
+of port. The \fIPort\fP may be omitted, in which case zero will be
+used. A \fIPort\fP of zero is only valid if the service is persistent
+as per the -p|--persistent option, in which case it is a wild-card
+port, that is connections will be accepted to any port.
+.TP
+.B -u, --udp-service \fIservice-address\fP
+Use UDP service. See the -t|--tcp-service for the description of
+the \fIservice-address\fP.
+.TP
+.B -f, --fwmark-service \fIinteger\fP
+Use a firewall-mark, an integer value greater than zero, to denote a
+virtual service instead of an address, port and protocol (UDP or
+TCP). The marking of packets with a firewall-mark is configured using
+the -m|--mark option to \fBipchains\fR(8). It can be used to build a
+virtual service associated with the same real servers, covering
+multiple IP addresses, port and protocol triplets.
+.sp
+Using firewall-mark virtual services provides a convenient method of
+grouping together different IP addresses, ports and protocols into a
+single virtual service. This is useful for both simplifying
+configuration if a large number of virtual services are required and
+grouping persistence across what would otherwise be multiple virtual
+services.
+.TP
+.B -s, --scheduler \fIscheduling-method\fP
+\fIscheduling-method\fP Algorithm for allocating TCP connections and
+UDP datagrams to real servers. Scheduling algorithms are implemented
+as kernel modules. Five are shipped with the Linux Virtual Server:
+.sp
+\fBrr\fR - Robin Robin: distribute jobs equally amongst the
+available real servers.
+.sp
+\fBwrr\fR - Weighted Round Robin: assign jobs to real servers
+proportionally to there real servers' weight. Servers with higher
+weights receive new jobs first and get more jobs than servers with
+lower weights. Servers with equal weights get an equal distribution
+of new jobs.
+.sp
+\fBlc\fR - Least-Connection: assign more jobs to real servers with
+fewer active jobs.
+.sp
+\fBwlc\fR - Weighted Least-Connection: assign more jobs to servers
+with fewer jobs and relative to the real servers' weight. This is the
+default.
+.sp
+\fBlblc\fR - Locality-Based Least-Connection: assign jobs destined for
+the same IP address to the same server if the server is not overloaded
+and available; otherwise assign jobs to servers with fewer jobs, and
+keep it for future assignment.
+.TP
+.B -p, --persistent [\fItimeout\fP]
+Specify that a virtual service is persistent. If this option is
+specified, multiple requests from a client are redirected to the same
+real server selected for the first request. Optionally, the
+\fItimeout\fP of persistent sessions may be specified given in
+seconds, otherwise the default of 300 seconds will be used. This
+option may be used in conjunction with protocols such as FTP
+where it is important that clients consistently connect with the same
+real server.
+.sp
+\fBNote:\fR If a virtual service is to handle FTP connections then
+persistence must be set for the virtual service if Direct Routing or
+NAT is used as the forwarding mechanism. If masquerading is used in
+conjunction with an FTP service than persistence is not necessary, but
+the ip_masq_ftp kernel module must be used. This module may be
+manually inserted into the kernel using insmod(8).
+.TP
+.B -M, --netmask \fInetmask\fP
+Specify the granularity with which clients are grouped for persistent
+virtual services. The source address of the request is masked with
+this netmask to direct all clients from a network to the same real
+server. The default is \fI255.255.255.255\fP, that is, the persistence
+granularity is per client host. Less specific netmasks may be used to
+resolve problems with non-persistent cache clusters on the client side.
+.TP
+.B -r, -R, --real-server \fIserver-address\fP
+Real server that a request for service may be assigned. The
+\fIserver-address\fP is of the form \fIhost[:port]\fP. \fIHost\fP is
+the address of a real server and may be ither an IP address or a
+hostname. \fIPort\fP can be either a port number or the service name
+of port. In the case of the masquerading method, the host address is
+usually an RFC 1918 private IP address, and the port can be different
+from that of the associated service. With the tunnelling and direct
+routing methods, \fIport\fP must be equal to that of the service
+address. For normal services, the port specified in the service
+address will be used if \fIport\fP is not specified. For fwmark
+services, \fIport\fP may be , in which case the destination port on
+the real server will be the destination port of the request sent to
+the virtual service.
+.TP
+.B [packet-forwarding-method]
+.sp
+\fB-g, --gatewaying\fR Use gatewaying (direct routing). This is the default.
+.sp
+\fB-i, --ipip\fR Use ipip encapsulation (tunnelling).
+.sp
+\fB-m, --masquerading\fR Use masquerading (network access translation, or NAT).
+.sp
+\fBNote:\fR Regardless of the packet-forwarding mechanism specified,
+real servers for addresses for which there are interfaces on the local
+node will be use the local forwarding method. This cannot be specified
+by \fIipvsadm\fP, rather it set by the kernel as real servers are
+added or modified.
+.TP
+.B -w, --weight \fIweight\fP
+\fIWeight\fP is an integer specifying the capacity of a server
+relative to the others in the pool. The valid values of \fIweight\fP
+are 0 through to 65535. The default is 1. Quiescent servers are
+specified with a weight of zero. A quiescent server will receive no
+new jobs but still serve the existing jobs, for all scheduling
+algorithms distributed with the Linux Virtual Server. Setting a
+quiescent server may be useful if the server is overloaded or needs
+to be taken out of service for maintenance.
+.TP
+.B -n, --numeric
+Numeric output. IP addresses and port numbers will be printed in
+numeric format rather than as as host names and services respectively,
+which is the default.
+.SH EXAMPLE 1 - Simple Virtual Service
+The following commands configure a Linux Director to distribute incoming
+requests addressed to port 80 on 207.175.44.110 equally to port 80 on
+five real servers. The forwarding method used in this example
+is NAT, with each of the real servers being masqueraded by the Linux
+Director.
+.PP
+.nf
+ipvsadm -A -t 207.175.44.110:80 -s rr
+ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.1:80 -m
+ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.2:80 -m
+ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.3:80 -m
+ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.4:80 -m
+ipvsadm -a -t 207.175.44.110:80 -r 192.168.10.5:80 -m
+.fi
+.PP
+Alternatively, this could be achieved in a single ipvsadm command.
+.PP
+.nf
+echo "
+-A -t 207.175.44.110:80 -s rr
+-a -t 207.175.44.110:80 -r 192.168.10.1:80 -m
+-a -t 207.175.44.110:80 -r 192.168.10.2:80 -m
+-a -t 207.175.44.110:80 -r 192.168.10.3:80 -m
+-a -t 207.175.44.110:80 -r 192.168.10.4:80 -m
+-a -t 207.175.44.110:80 -r 192.168.10.5:80 -m
+" | ipvsadm -R
+.fi
+r
+.PP
+As masquerading is used as the forwarding mechanism in this example, the
+default route of the real servers must be set to the linux director, which
+will need to be configured to forward and masquerade packets. This can be
+achieved using the following commands:
+.PP
+.nf
+echo "1" > /proc/sys/net/ipv4/ip_forward
+ipchains -A forward -j MASQ -s 192.168.10.0/24 -d 0.0.0.0/0
+.fi
+.SH EXAMPLE 2 - Firewall-Mark Virtual Service
+The following commands configure a Linux Director to distribute incoming
+requests addressed to any port on 207.175.44.110 or 207.175.44.111 equally
+to the corresponding port on five real servers. As per the previous
+example, the forwarding method used in this example is NAT, with each of
+the real servers being masqueraded by the Linux Director.
+.PP
+.nf
+ipvsadm -A -f 1 -s rr
+ipvsadm -a -t 1 -r 192.168.10.1:0 -m
+ipvsadm -a -t 1 -r 192.168.10.2:0 -m
+ipvsadm -a -t 1 -r 192.168.10.3:0 -m
+ipvsadm -a -t 1 -r 192.168.10.4:0 -m
+ipvsadm -a -t 1 -r 192.168.10.5:0 -m
+.fi
+.PP
+As masquerading is used as the forwarding mechanism in this example,
+the default route of the real servers must be set to the linux
+director, which will need to be configured to forward and masquerade
+packets. The real server should also be configured to mark incoming
+packets addressed to any port on 207.175.44.110 and 207.175.44.111
+with firewall-mark 1. If FTP traffic is to be handled by this virtual
+service, then the ip_masq_ftp kernel module needs to be inserted into
+the kernel. These operations can be achieved using the following
+commands:
+.PP
+.nf
+echo "1" > /proc/sys/net/ipv4/ip_forward
+ipchains -A forward -j MASQ -s 192.168.10.0/24 -d 0.0.0.0/0
+ipchains -A input -j ACCEPT -s 0.0.0.0/0 -d 207.175.44.110/31 -m 1
+modprobe ip_masq_ftp
+.fi
+.SH NOTES
+The Linux Virtual Server implements three defense strategies against
+some types of denial of service (DoS) attacks. The Linux Director
+creates an entry for each connection in order to keep its state, and
+each entry occupies 128 bytes effective memory. LVS's vulnerability to
+a DoS attack lies in the potential to increase the number entries as
+much as possible until the linux director runs out of memory. The
+three defense strategies against the attack are: Randomly drop some
+entries in the table. Drop 1/rate packets before forwarding them. And
+use secure tcp state transition table and short timeouts. The
+strategies are controlled by sysctl variables and corresponding
+entries in the /proc filesystem:
+.sp
+/proc/sys/net/ipv4/vs/drop_entry
+/proc/sys/net/ipv4/vs/drop_packet
+/proc/sys/net/ipv4/vs/secure_tcp
+.PP
+Valid values for each variable are 0 through to 3. The default value
+is 0, which disables the respective defense strategy. 1 and 2 are
+automatic modes - when there is no enough available memory, the
+respective strategy will be enabled and the variable is automatically
+set to 2, otherwise the strategy is disabled and the variable is set
+to 1. A value of 3 denotes that the respective strategy is always
+enabled. The available memory threshold and secure TCP timeouts can
+be tuned using the sysctl variables and corresponding entries in the
+/proc filesystem:
+.sp
+/proc/sys/net/ipv4/vs/amemthresh
+/proc/sys/net/ipv4/vs/timeout_*
+.SH FILES
+.I /proc/net/ip_masq/vs
+.br
+.I /proc/sys/net/ipv4/vs/am_droprate
+.br
+.I /proc/sys/net/ipv4/vs/amemthresh
+.br
+.I /proc/sys/net/ipv4/vs/drop_entry
+.br
+.I /proc/sys/net/ipv4/vs/drop_packet
+.br
+.I /proc/sys/net/ipv4/vs/secure_tcp
+.br
+.I /proc/sys/net/ipv4/vs/timeout_close
+.br
+.I /proc/sys/net/ipv4/vs/timeout_closewait
+.br
+.I /proc/sys/net/ipv4/vs/timeout_established
+.br
+.I /proc/sys/net/ipv4/vs/timeout_finwait
+.br
+.I /proc/sys/net/ipv4/vs/timeout_icmp
+.br
+.I /proc/sys/net/ipv4/vs/timeout_lastack
+.br
+.I /proc/sys/net/ipv4/vs/timeout_listen
+.br
+.I /proc/sys/net/ipv4/vs/timeout_synack
+.br
+.I /proc/sys/net/ipv4/vs/timeout_synrecv
+.br
+.I /proc/sys/net/ipv4/vs/timeout_synsent
+.br
+.I /proc/sys/net/ipv4/vs/timeout_timewait
+.br
+.I /proc/sys/net/ipv4/vs/timeout_udp
+.SH SEE ALSO
+\fBpopt\fP(3), \fBipchains\fP(8), \fBinsmod\fP(8)
+.SH AUTHORS
+.nf
+ipvsadm - Wensong Zhang <wensong@gnuchina.org>
+ Peter Kese <peter.kese@ijs.si>
+man page - Mike Wangsmo <wanger@redhat.com>
+ Wensong Zhang <wensong@gnuchina.org>
+ Horms <horms@valinux.com>
+.fi
diff --git a/ipvsadm.c b/ipvsadm.c
new file mode 100644
index 0000000..5ec4794
--- /dev/null
+++ b/ipvsadm.c
@@ -0,0 +1,1464 @@
+/*
+ * ipvsadm - IP Virtual Server ADMinistration program
+ *
+ * Version: $Id$
+ *
+ * Authors: Wensong Zhang <wensong@iinchina.net>
+ * 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
+ *
+ *
+ * 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 <getopt.h>
+#include <netdb.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <arpa/inet.h>
+
+#include <asm/types.h> /* For __uXX types */
+#include <net/if.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/udp.h>
+#include <netinet/tcp.h>
+#include <linux/ip_fw.h> /* For IP_FW_MASQ_CTL */
+#include <linux/ip_masq.h> /* For specific masq defs */
+#include <net/ip_masq.h>
+#include <net/ip_vs.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_POPT
+#include "popt.h"
+#include "config_stream.h"
+
+#define IPVS_OPTION_PROCESSING "popt"
+#else
+#define IPVS_OPTION_PROCESSING "getopt_long"
+#endif
+
+#define IPVSADM_VERSION_NO "v1.12"
+#define IPVSADM_VERSION_DATE "2000/11/02"
+#define IPVSADM_VERSION IPVSADM_VERSION_NO " " IPVSADM_VERSION_DATE
+
+#define MINIMUM_IPVS_VERSION_MAJOR 0
+#define MINIMUM_IPVS_VERSION_MINOR 9
+#define MINIMUM_IPVS_VERSION_PATCH 10
+
+#ifndef IPVS_VERSION
+#define IPVS_VERSION(x,y,z) (((x)<<16)+((y)<<8)+(z))
+#endif
+
+/* default scheduler */
+#define DEF_SCHED "wlc"
+
+/* printing format flags */
+#define FMT_NONE 0x0000
+#define FMT_NUMERIC 0x0001
+#define FMT_RULE 0x0002
+
+#define SERVICE_NONE 0x0
+#define SERVICE_ADDR 0x1
+#define SERVICE_PORT 0x2
+
+#define VS_PROC_FILE "/proc/net/ip_masq/vs"
+
+int string_to_number(const char *s, int min, int max);
+int host_to_addr(const char *name, struct in_addr *addr);
+char * addr_to_host(struct in_addr *addr);
+char * addr_to_anyname(struct in_addr *addr);
+int service_to_port(const char *name, unsigned short proto);
+char * port_to_service(int port, unsigned short proto);
+char * port_to_anyname(int port, unsigned short proto);
+char * addrport_to_anyname(struct in_addr *addr, int port,
+ unsigned short proto, unsigned int format);
+
+int parse_service(char *buf, u_int16_t proto,
+ u_int32_t *addr, u_int16_t *port);
+int parse_fwmark(char *buf, u_int32_t *fwmark);
+int parse_netmask(char *buf, u_int32_t *addr);
+int parse_timeout(char *buf, unsigned *timeout);
+
+void usage_exit(const char *program, const int exit_status);
+void fail(int err, char *text);
+void check_ipvs_version(void);
+void list_vs(unsigned int options);
+void print_vsinfo(char *buf, unsigned int format);
+int process_options(int argc, char **argv, int reading_stdin);
+int str_is_digit(const char *str);
+
+
+int main(int argc, char **argv)
+{
+ /*
+ * If no other arguement, list VS_PROC_FILE
+ */
+ if (argc == 1){
+ list_vs(FMT_NONE);
+ exit(0);
+ }
+
+ /*
+ * Warn the user if the IPVS version is out of date
+ */
+ check_ipvs_version();
+
+ /*
+ * Process command line arguments
+ */
+ return process_options(argc, argv, 0);
+}
+
+
+#ifdef HAVE_POPT
+
+int parse_options(int argc, char **argv, int reading_stdin,
+ struct ip_masq_ctl *mc, unsigned int *format)
+{
+ int c, cmd, parse;
+ int result=0;
+ int forward_set=0;
+ int destination_set=0;
+ int read_stdin=0;
+ int write_stdout=0;
+ dynamic_array_t *a;
+ poptContext context;
+ char *optarg=NULL;
+
+ struct poptOption add_service_option =
+ {"add-service", 'A', POPT_ARG_NONE, NULL, 'A'};
+ struct poptOption edit_service_option =
+ {"edit-service", 'E', POPT_ARG_NONE, NULL, 'E'};
+ struct poptOption delete_service_option =
+ {"delete-service", 'D', POPT_ARG_NONE, NULL, 'D'};
+ struct poptOption clear_option =
+ {"clear", 'C', POPT_ARG_NONE, NULL, 'C'};
+ struct poptOption list_option =
+ {"list", 'L', POPT_ARG_NONE, NULL, 'L'};
+ struct poptOption list2_option =
+ {"list", 'l', POPT_ARG_NONE, NULL, 'l'};
+ struct poptOption add_server_option =
+ {"add-server", 'a', POPT_ARG_NONE, NULL, 'a'};
+ struct poptOption edit_server_option =
+ {"edit-server", 'e', POPT_ARG_NONE, NULL, 'e'};
+ struct poptOption delete_server_option =
+ {"delete-server", 'd', POPT_ARG_NONE, NULL, 'd'};
+ struct poptOption help_option =
+ {"help", 'h', POPT_ARG_NONE, NULL, 'h'};
+ struct poptOption read_stdin_option =
+ {"restore", 'R', POPT_ARG_NONE, NULL, 'R'};
+ struct poptOption write_stdout_option =
+ {"save", 'S', POPT_ARG_NONE, NULL, 'S'};
+ struct poptOption tcp_service_option =
+ {"tcp-service", 't', POPT_ARG_STRING, &optarg, 't'};
+ struct poptOption udp_service_option =
+ {"udp-service", 'u', POPT_ARG_STRING, &optarg, 'u'};
+ struct poptOption fwmark_service_option =
+ {"fwmark-service", 'f', POPT_ARG_STRING, &optarg, 'f'};
+ struct poptOption scheduler_option =
+ {"scheduler", 's', POPT_ARG_STRING, &optarg, 's'};
+ struct poptOption persistent_option =
+ {"persistent", 'p', POPT_ARG_NONE, NULL, 'p'};
+ struct poptOption netmask_option =
+ {"netmask", 'M', POPT_ARG_STRING, &optarg, 'M'};
+ struct poptOption real_server_option =
+ {"real-server", 'r', POPT_ARG_STRING, &optarg, 'r'};
+ struct poptOption real_server2_option =
+ {"real-server", 'R', POPT_ARG_STRING, &optarg, 'R'};
+ struct poptOption masquerading_option =
+ {"masquerading", 'm', POPT_ARG_NONE, NULL, 'm'};
+ struct poptOption ipip_option =
+ {"ipip", 'i', POPT_ARG_NONE, NULL, 'i'};
+ struct poptOption gatewaying_option =
+ {"gatewaying",'g', POPT_ARG_NONE, NULL, 'g'};
+ struct poptOption weight_option =
+ {"weight", 'w', POPT_ARG_STRING, &optarg, 'w'};
+ struct poptOption numeric_option =
+ {"numeric", 'n', POPT_ARG_NONE, NULL, 'n'};
+ struct poptOption NULL_option =
+ {NULL, 0, 0, NULL, 0};
+
+ struct poptOption *options_sub=NULL;
+
+ struct poptOption options_main[] =
+ {
+ add_service_option,
+ edit_service_option,
+ delete_service_option,
+ clear_option,
+ list_option,
+ list2_option,
+ add_server_option,
+ edit_server_option,
+ delete_server_option,
+ help_option,
+ read_stdin_option,
+ write_stdout_option,
+ NULL_option
+ };
+
+ struct poptOption options_delete_service[] =
+ {
+ tcp_service_option,
+ udp_service_option,
+ fwmark_service_option,
+ NULL_option
+ };
+
+ struct poptOption options_service[] =
+ {
+ tcp_service_option,
+ udp_service_option,
+ fwmark_service_option,
+ scheduler_option,
+ persistent_option,
+ netmask_option,
+ NULL_option
+ };
+
+ struct poptOption options_delete_server[] =
+ {
+ tcp_service_option,
+ udp_service_option,
+ fwmark_service_option,
+ weight_option,
+ real_server_option,
+ real_server2_option,
+ NULL_option
+ };
+
+ struct poptOption options_server[] =
+ {
+ tcp_service_option,
+ udp_service_option,
+ fwmark_service_option,
+ weight_option,
+ real_server_option,
+ real_server2_option,
+ gatewaying_option,
+ masquerading_option,
+ ipip_option,
+ NULL_option
+ };
+
+ struct poptOption options_list[] =
+ {
+ numeric_option,
+ NULL_option
+ };
+
+ struct poptOption options_NULL[] =
+ {
+ NULL_option
+ };
+
+ context= poptGetContext("ipvsadm", argc, argv, options_main, 0);
+
+ if ((cmd = poptGetNextOpt(context)) < 0)
+ usage_exit(argv[0], -1);
+
+ switch (cmd) {
+ case 'A':
+ mc->m_cmd = IP_MASQ_CMD_ADD;
+ options_sub = options_service;
+ break;
+ case 'E':
+ mc->m_cmd = IP_MASQ_CMD_SET;
+ options_sub = options_service;
+ break;
+ case 'D':
+ mc->m_cmd = IP_MASQ_CMD_DEL;
+ options_sub = options_delete_service;
+ break;
+ case 'a':
+ mc->m_cmd = IP_MASQ_CMD_ADD_DEST;
+ options_sub = options_server;
+ break;
+ case 'e':
+ mc->m_cmd = IP_MASQ_CMD_SET_DEST;
+ options_sub = options_server;
+ break;
+ case 'd':
+ mc->m_cmd = IP_MASQ_CMD_DEL_DEST;
+ mc->m_target = IP_MASQ_TARGET_VS;
+ options_sub = options_delete_server;
+ break;
+ case 'C':
+ mc->m_cmd = IP_MASQ_CMD_FLUSH;
+ options_sub = options_NULL;
+ break;
+ case 'L':
+ case 'l':
+ mc->m_cmd = IP_MASQ_CMD_LIST;
+ options_sub = options_list;
+ break;
+ case 'R':
+ read_stdin = 1;
+ options_sub = options_NULL;
+ break;
+ case 'S':
+ write_stdout = 1;
+ options_sub = options_list;
+ break;
+ case 'h':
+ usage_exit(argv[0], 0);
+ break;
+ default:
+ usage_exit(argv[0], -1);
+ }
+
+ poptFreeContext(context);
+ context = poptGetContext("ipvsadm", argc, argv, options_sub, 0);
+
+ /*
+ * Mangle the first argument
+ * The first option from this argument has been read,
+ * but there may be others
+ */
+ c = strlen(argv[1]);
+ if (c > 2) {
+ if(argv[1][1] != '-'){
+ /* Suffle first option out of argument */
+ memmove(argv[1]+1, argv[1]+2, c-2);
+ argv[1][c-1]='\0';
+ }
+ } else {
+ /* Skip argument */
+ poptGetNextOpt(context);
+ }
+
+ while ((c=poptGetNextOpt(context)) >= 0){
+ switch (c) {
+ case 't':
+ case 'u':
+ if (mc->u.vs_user.vfwmark != 0)
+ fail(2, "fwmark already specified");
+ if (mc->u.vs_user.protocol != 0)
+ fail(2, "protocol already specified");
+ mc->u.vs_user.protocol =
+ (c=='t' ? IPPROTO_TCP : IPPROTO_UDP);
+ parse = parse_service(optarg,
+ mc->u.vs_user.protocol,
+ &mc->u.vs_user.vaddr,
+ &mc->u.vs_user.vport);
+ if (!(parse & SERVICE_ADDR ))
+ fail(2, "illegal virtual server "
+ "address[:port] specified");
+ break;
+ case 'f':
+ if (mc->u.vs_user.vfwmark != 0)
+ fail(2, "fwmark already specified");
+ if (mc->u.vs_user.protocol != 0)
+ fail(2, "protocol already specified");
+ /*
+ * Set prtocol to a sane values, even
+ * though it is not used
+ */
+ mc->u.vs_user.protocol = IPPROTO_TCP;
+ /*
+ * Get the fwmark
+ */
+ parse = parse_fwmark(optarg, &mc->u.vs_user.vfwmark);
+ if (parse == 0 || mc->u.vs_user.vfwmark == 0 )
+ fail(2, "illegal virtual server "
+ "fwmark specified");
+ break;
+ case 's':
+ if (strlen(mc->m_tname) != 0)
+ fail(2, "multiple scheduling modules specified");
+ strncpy(mc->m_tname, optarg, IP_MASQ_TNAME_MAX);
+ break;
+ case 'p':
+ mc->u.vs_user.vs_flags = IP_VS_SVC_F_PERSISTENT;
+ break;
+ case 'M':
+ parse = parse_netmask(optarg,
+ &mc->u.vs_user.netmask);
+ if (parse != 1)
+ fail(2, "illegal virtual server "
+ "persistent mask specified");
+ break;
+ case 'r':
+ case 'R':
+ if (destination_set)
+ fail(2, "Destination already set");
+ destination_set=1;
+ parse = parse_service(optarg,
+ mc->u.vs_user.protocol,
+ &mc->u.vs_user.daddr,
+ &mc->u.vs_user.dport);
+ if (!(parse&SERVICE_ADDR))
+ fail(2, "illegal real server "
+ "address[:port] specified");
+ /* copy vport to dport if none specified */
+ if (parse == 1)
+ mc->u.vs_user.dport = mc->u.vs_user.vport;
+ break;
+ case 'i':
+ if(forward_set)
+ fail(2, "multiple forward mechanims set");
+ forward_set=1;
+ mc->u.vs_user.masq_flags = IP_MASQ_F_VS_TUNNEL;
+ break;
+ case 'g':
+ if(forward_set)
+ fail(2, "multiple forward mechanims set");
+ forward_set=1;
+ mc->u.vs_user.masq_flags = IP_MASQ_F_VS_DROUTE;
+ break;
+ case 'm':
+ if(forward_set)
+ fail(2, "multiple forward mechanims set");
+ forward_set=1;
+ mc->u.vs_user.masq_flags = 0;
+ break;
+ case 'w':
+ if (mc->u.vs_user.weight != -1)
+ fail(2, "multiple server weights specified");
+ if ((mc->u.vs_user.weight=
+ string_to_number(optarg,0,65535)) == -1)
+ fail(2, "illegal weight specified");
+ break;
+ case 'n':
+ *format |= FMT_NUMERIC;
+ break;
+ default:
+ fail(2, "invalid option");
+ }
+ }
+
+ 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 (read_stdin) {
+ /* avoid infinite loop */
+ if (reading_stdin != 0)
+ usage_exit(argv[0], -1);
+
+ while ((a = config_stream_read(stdin, argv[0])) != NULL) {
+ int i;
+ if ((i = (int)dynamic_array_get_count(a)) > 1)
+ result = process_options
+ (i,
+ (char **)dynamic_array_get_vector(a),
+ 1);
+ dynamic_array_destroy(a, DESTROY_STR);
+ }
+ poptFreeContext(context);
+ exit(result);
+ }
+
+ if (write_stdout) {
+ *format |= FMT_RULE;
+ list_vs(*format);
+ poptFreeContext(context);
+ exit(0);
+ }
+
+ /*
+ * If popt is used then optional arguments (persistent timeout)
+ * has to be handled last. This has the interesting
+ * side effect that the first non-option argument will
+ * be used as the timeout, regardless of its position
+ * in the argument list
+ */
+ if (mc->u.vs_user.vs_flags == IP_VS_SVC_F_PERSISTENT){
+ optarg=poptGetArg(context);
+ parse = parse_timeout(optarg, &mc->u.vs_user.timeout);
+ if (parse == 0)
+ fail(2, "illegal timeout for persistent service");
+ }
+ poptFreeContext(context);
+ return 0;
+}
+
+#else
+
+int parse_options(int argc, char **argv, int reading_stdin,
+ struct ip_masq_ctl *mc, unsigned int *format)
+{
+ int c, cmd, parse;
+ int forward_set=0;
+ int destination_set=0;
+ char *optstr = "";
+
+ struct option long_options[] =
+ {
+ {"add-service", 0, 0, 'A'},
+ {"edit-service", 0, 0, 'E'},
+ {"delete-service", 0, 0, 'D'},
+ {"clear", 0, 0, 'C'},
+ {"list", 0, 0, 'L'},
+ {"add-server", 0, 0, 'a'},
+ {"edit-server", 0, 0, 'e'},
+ {"delete-server", 0, 0, 'd'},
+ {"help", 0, 0, 'h'},
+ {"save", 0, 0, 'S'},
+ {"restore", 0, 0, 'R'},
+ {"tcp-service", 1, 0, 't'},
+ {"udp-service", 1, 0, 'u'},
+ {"fwmark-service", 1, 0, 'f'},
+ {"scheduler", 1, 0, 's'},
+ {"persistent", 2, 0, 'p'},
+ {"real-server", 1, 0, 'r'},
+ {"masquerading", 0, 0, 'm'},
+ {"netmask", 1, 0, 'M'},
+ {"ipip", 0, 0, 'i'},
+ {"gatewaying", 0, 0, 'g'},
+ {"weight", 1, 0, 'w'},
+ {"numeric", 0, 0, 'n'},
+ {"help", 0, 0, 'h'},
+ {0, 0, 0, 0}
+ };
+
+ extern char *optarg;
+ extern int optind;
+ extern int opterr;
+ extern int optopt;
+
+ /* Re-process the arguments each time options is called*/
+ optind = 1;
+
+ if ((cmd = getopt_long(argc, argv, "AEDCSRaedlLh",
+ long_options, NULL)) == EOF)
+ usage_exit(argv[0], -1);
+
+ switch (cmd) {
+ case 'A':
+ mc->m_cmd = IP_MASQ_CMD_ADD;
+ optstr = "t:u:f:s:M:p::";
+ break;
+ case 'E':
+ mc->m_cmd = IP_MASQ_CMD_SET;
+ optstr = "t:u:f:s:M:p::";
+ break;
+ case 'D':
+ mc->m_cmd = IP_MASQ_CMD_DEL;
+ optstr = "t:u:f:";
+ break;
+ case 'a':
+ mc->m_cmd = IP_MASQ_CMD_ADD_DEST;
+ optstr = "t:u:f:w:r:R:gmi";
+ break;
+ case 'e':
+ mc->m_cmd = IP_MASQ_CMD_SET_DEST;
+ optstr = "t:u:f:w:r:R:gmi";
+ break;
+ case 'd':
+ mc->m_cmd = IP_MASQ_CMD_DEL_DEST;
+ optstr = "t:u:f:w:r:R:";
+ break;
+ case 'C':
+ mc->m_cmd = IP_MASQ_CMD_FLUSH;
+ optstr = "";
+ break;
+ case 'L':
+ case 'l':
+ mc->m_cmd = IP_MASQ_CMD_LIST;
+ optstr = "n";
+ break;
+ case 'h':
+ usage_exit(argv[0], 0);
+ break;
+ case 'S':
+ fprintf(stderr,
+ "ipvsadm: Invalid option: -S or --save\n"
+ " Saving of ipvsadm rules is only supported when\n"
+ " ipvsadm is compiled against libpopt.\n");
+ exit(-1);
+ break;
+ case 'R':
+ fprintf(stderr,
+ "ipvsadm: Invalid option: -R or --restore\n"
+ "Restoring ipvsadm rules is only supported when \n"
+ "ipvsadm is compiled against libpopt.\n");
+ exit(-1);
+ break;
+ default:
+ usage_exit(argv[0], -1);
+ }
+
+ while ((c=getopt_long(argc, argv, optstr,
+ long_options, NULL)) != EOF) {
+ switch (c) {
+ case 't':
+ case 'u':
+ if (mc->u.vs_user.vfwmark != 0)
+ fail(2, "fwmark already specified");
+ if (mc->u.vs_user.protocol != 0)
+ fail(2, "protocol already specified");
+ mc->u.vs_user.protocol =
+ (c=='t' ? IPPROTO_TCP : IPPROTO_UDP);
+ parse = parse_service(optarg,
+ mc->u.vs_user.protocol,
+ &mc->u.vs_user.vaddr,
+ &mc->u.vs_user.vport);
+ if (!(parse & SERVICE_ADDR ))
+ fail(2, "illegal virtual server "
+ "address[:port] specified");
+ break;
+ case 'f':
+ if (mc->u.vs_user.vfwmark != 0)
+ fail(2, "fwmark already specified");
+ if (mc->u.vs_user.protocol != 0)
+ fail(2, "protocol already specified");
+ /*
+ * Set prtocol to a sane values, even
+ * though it is not used
+ */
+ mc->u.vs_user.protocol = IPPROTO_TCP;
+ /*
+ * Get the fwmark
+ */
+ parse = parse_fwmark(optarg, &mc->u.vs_user.vfwmark);
+ if (parse == 0 || mc->u.vs_user.vfwmark == 0 )
+ fail(2, "illegal virtual server "
+ "fwmark specified");
+ break;
+ case 's':
+ if (strlen(mc->m_tname) != 0)
+ fail(2, "multiple scheduling modules specified");
+ strncpy(mc->m_tname, optarg, IP_MASQ_TNAME_MAX);
+ break;
+ case 'p':
+ mc->u.vs_user.vs_flags = IP_VS_SVC_F_PERSISTENT;
+ if (!optarg && optind < argc && argv[optind][0] != '-'
+ && argv[optind][0] != '!')
+ optarg = argv[optind++];
+ parse = parse_timeout(optarg,
+ &mc->u.vs_user.timeout);
+ if (parse == 0)
+ fail(2, "illegal timeout "
+ "for persistent service");
+ break;
+ case 'M':
+ parse = parse_netmask(optarg,
+ &mc->u.vs_user.netmask);
+ if (parse != 1)
+ fail(2, "illegal virtual server "
+ "persistent mask specified");
+ break;
+ case 'r':
+ case 'R':
+ if (destination_set)
+ fail(2, "Destination already set");
+ destination_set=1;
+ parse = parse_service(optarg,
+ mc->u.vs_user.protocol,
+ &mc->u.vs_user.daddr,
+ &mc->u.vs_user.dport);
+ if (!(parse&SERVICE_ADDR))
+ fail(2, "illegal real server "
+ "address[:port] specified");
+ /* copy vport to dport if none specified */
+ if (parse == 1)
+ mc->u.vs_user.dport = mc->u.vs_user.vport;
+ break;
+ case 'i':
+ if(forward_set)
+ fail(2, "multiple forward mechanims set");
+ forward_set=1;
+ mc->u.vs_user.masq_flags = IP_MASQ_F_VS_TUNNEL;
+ break;
+ case 'g':
+ if(forward_set)
+ fail(2, "multiple forward mechanims set");
+ forward_set=1;
+ mc->u.vs_user.masq_flags = IP_MASQ_F_VS_DROUTE;
+ break;
+ case 'm':
+ if(forward_set)
+ fail(2, "multiple forward mechanims set");
+ forward_set=1;
+ mc->u.vs_user.masq_flags = 0;
+ break;
+ case 'w':
+ if (mc->u.vs_user.weight != -1)
+ fail(2, "multiple server weights specified");
+ if ((mc->u.vs_user.weight=
+ string_to_number(optarg,0,65535)) == -1)
+ fail(2, "illegal weight specified");
+ break;
+ case 'n':
+ *format |= FMT_NUMERIC;
+ break;
+ default:
+ fail(2, "invalid option");
+ }
+ }
+
+ if (optind < argc)
+ fail(2, "unknown arguments found in command line");
+ return 0;
+}
+
+#endif /* HAVE_POPT */
+
+
+int process_options(int argc, char **argv, int reading_stdin)
+{
+ struct ip_masq_ctl ctl;
+ int result=0;
+ int sockfd;
+ unsigned int format=FMT_NONE;
+
+ memset(&ctl, 0, sizeof(struct ip_masq_ctl));
+ ctl.m_target = IP_MASQ_TARGET_VS;
+ /* weight=0 is allowed, which means that server is quiesced */
+ ctl.u.vs_user.weight = -1;
+ /* Set direct routing as default forwarding method */
+ ctl.u.vs_user.masq_flags = IP_MASQ_F_VS_DROUTE;
+ /* Set the default persistent granularity to /32 masking */
+ ctl.u.vs_user.netmask = ((u_int32_t) 0xffffffff);
+
+ if (parse_options(argc, argv, reading_stdin, &ctl, &format))
+ return -1;
+
+ if (ctl.m_cmd == IP_MASQ_CMD_LIST) {
+ list_vs(format);
+ return 0;
+ }
+
+ if (ctl.m_cmd == IP_MASQ_CMD_ADD || ctl.m_cmd == IP_MASQ_CMD_SET) {
+ /*
+ * Make sure that port zero service is persistent
+ */
+ if (!ctl.u.vs_user.vfwmark &&
+ !ctl.u.vs_user.vport &&
+ (ctl.u.vs_user.vs_flags != IP_VS_SVC_F_PERSISTENT))
+ fail(2, "Zero port specified "
+ "for non-persistent service");
+
+ /*
+ * Set the default scheduling algorithm if not specified
+ */
+ if (strlen(ctl.m_tname) == 0)
+ strcpy(ctl.m_tname,DEF_SCHED);
+ }
+
+ /*
+ * Make sure that a destination is specified as required
+ * i.e. make sure that a -r accompanies a -[t|u|f]
+ */
+ if ((ctl.m_cmd == IP_MASQ_CMD_ADD_DEST
+ || ctl.m_cmd == IP_MASQ_CMD_SET_DEST
+ || ctl.m_cmd == IP_MASQ_CMD_DEL_DEST)
+ && !ctl.u.vs_user.daddr) {
+ fail(2, "No destination specified");
+ }
+
+ if (ctl.m_cmd == IP_MASQ_CMD_ADD_DEST
+ || ctl.m_cmd == IP_MASQ_CMD_SET_DEST) {
+ /*
+ * Set the default weight 1 if not specified
+ */
+ if (ctl.u.vs_user.weight == -1)
+ ctl.u.vs_user.weight = 1;
+
+ /*
+ * The destination port must be equal to the service port
+ * if the IP_MASQ_F_VS_TUNNEL or IP_MASQ_F_VS_DROUTE is set.
+ * Don't worry about this if fwmark is used.
+ */
+ if (!ctl.u.vs_user.vfwmark &&
+ (ctl.u.vs_user.masq_flags == IP_MASQ_F_VS_TUNNEL
+ || ctl.u.vs_user.masq_flags == IP_MASQ_F_VS_DROUTE))
+ ctl.u.vs_user.dport = ctl.u.vs_user.vport;
+ }
+
+ sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+ if (sockfd==-1) {
+ perror("socket creation failed");
+ exit(1);
+ }
+
+ result = setsockopt(sockfd, IPPROTO_IP,
+ IP_FW_MASQ_CTL, (char *)&ctl, sizeof(ctl));
+ if (result) {
+ perror("setsockopt failed");
+
+ /*
+ * Print most common error messages
+ */
+ switch (ctl.m_cmd) {
+ case IP_MASQ_CMD_ADD:
+ if (errno == EEXIST)
+ printf("Service already exists\n");
+ else if (errno == ENOENT)
+ printf("Scheduler not found: ip_vs_%s.o\n",
+ ctl.m_tname);
+ break;
+ case IP_MASQ_CMD_SET:
+ if (errno==ESRCH)
+ printf("No such service\n");
+ else if (errno == ENOENT)
+ printf("Scheduler not found: ip_vs_%s.o\n",
+ ctl.m_tname);
+ break;
+ case IP_MASQ_CMD_DEL:
+ if (errno==ESRCH)
+ printf("No such service\n");
+ break;
+ case IP_MASQ_CMD_ADD_DEST:
+ if (errno == ESRCH)
+ printf("Service not defined\n");
+ else if (errno == EEXIST)
+ printf("Destination already exists\n");
+ break;
+ case IP_MASQ_CMD_SET_DEST:
+ if (errno==ESRCH)
+ printf("Service not defined\n");
+ else if (errno == ENOENT)
+ printf("No such destination\n");
+ break;
+ case IP_MASQ_CMD_DEL_DEST:
+ if (errno==ESRCH)
+ printf("Service not defined\n");
+ else if (errno == ENOENT)
+ printf("No such destination\n");
+ break;
+ }
+ }
+
+ close(sockfd);
+ return result;
+}
+
+
+int string_to_number(const char *s, int min, int max)
+{
+ int number;
+ char *end;
+
+ number = (int)strtol(s, &end, 10);
+ if (*end == '\0' && end != s) {
+ /*
+ * We parsed a number, let's see if we want this.
+ * If max <= min then ignore ranges
+ */
+ if (max <= min || ( min <= number && number <= max))
+ return number;
+ else
+ return -1;
+ } else
+ return -1;
+}
+
+
+/*
+ * Get netmask.
+ * Return 0 if failed,
+ * 1 if addr read
+ */
+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 fwmark from the argument.
+ * Result is
+ * 1 on success
+ * 0 on error
+ */
+int parse_fwmark(char *buf, u_int32_t *fwmark)
+{
+ long tmpl;
+
+ if ((tmpl=string_to_number(buf, 0, 0)) == -1)
+ return 0;
+ *fwmark=tmpl;
+
+ 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
+ */
+int parse_service(char *buf, u_int16_t proto, u_int32_t *addr, u_int16_t *port)
+{
+ char *portp;
+ long portn;
+ int result=SERVICE_NONE;
+ struct in_addr inaddr;
+
+ if(buf==NULL || str_is_digit(buf))
+ return SERVICE_NONE;
+
+ portp = strchr(buf, ':');
+ if (portp != NULL)
+ *portp = '\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 SERVICE_NONE;
+
+ result|=SERVICE_ADDR;
+
+ if (portp != NULL){
+ result|=SERVICE_PORT;
+
+ if ((portn=string_to_number(portp+1, 0, 65535)) != -1)
+ *port = htons(portn);
+ else if ((portn=service_to_port(portp+1, proto)) != -1)
+ *port = htons(portn);
+ else
+ return SERVICE_NONE;
+ }
+
+ return result;
+}
+
+
+/*
+ * Get the timeout of persistent service.
+ * Return 0 if failed(the timeout value is less or equal than zero).
+ * 1 if succeed.
+ */
+int parse_timeout(char *buf, unsigned *timeout)
+{
+ int i;
+
+ if (buf == NULL) {
+ *timeout = IP_VS_TEMPLATE_TIMEOUT;
+ return 1;
+ }
+
+ if ((i=string_to_number(buf, 1, 86400*31)) == -1)
+ return 0;
+
+ *timeout = i * HZ;
+ return 1;
+}
+
+
+void usage_exit(const char *program, const int exit_status) {
+ FILE *stream;
+
+ if (exit_status != 0)
+ stream = stderr;
+ else
+ stream = stdout;
+
+ fprintf(stream,
+ "ipvsadm " IPVSADM_VERSION " (compiled with "
+ IPVS_OPTION_PROCESSING " and IPVS v%d.%d.%d)\n"
+ "Usage: %s -[A|E] -[t|u|f] service-address [-s scheduler] [-p [timeout]] [-M netmask]\n"
+ " %s -D -[t|u|f] service-address\n"
+ " %s -C\n"
+#ifdef HAVE_POPT
+ " %s -R\n"
+ " %s -S [-n]\n"
+#endif
+ " %s -[a|e] -[t|u|f] service-address -[r|R] server-address [-g|-i|-m] [-w weight]\n"
+ " %s -d -[t|u|f] service-address -[r|R] server-address\n"
+ " %s -[L|l] [-n]\n"
+ " %s -h\n\n",
+ NVERSION(IP_VS_VERSION_CODE),
+#ifdef HAVE_POPT
+ program, program,
+#endif
+ 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"
+#ifdef HAVE_POPT
+ " --restore -R restore rules from stdin\n"
+ " --save -S save rules to stdout\n"
+#endif
+ " --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"
+ " --help -h display this help message\n\n"
+ );
+
+ fprintf(stream,
+ "Options:\n"
+ " --tcp-service -t service-address service-address is host[:port]\n"
+ " --udp-service -u service-address service-address is host[:port]\n"
+ " --fwmark-service -f fwmark fwmark is an integer greater than zero\n"
+ " --scheduler -s <scheduler> one of rr|wrr|lc|wlc,\n"
+ " the default scheduler is %s.\n"
+ " --persistent -p [timeout] persistent service\n"
+ " --netmask -M [netmask] persistent granularity mask\n"
+ " --real-server -r|-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"
+ " --numeric -n numeric output of addresses and ports\n",
+ DEF_SCHED);
+
+ exit(exit_status);
+}
+
+
+void fail(int err, char *text) {
+ printf("%s\n",text);
+ exit(err);
+}
+
+
+void check_ipvs_version(void)
+{
+ static char buffer[1024];
+ int major;
+ int minor;
+ int patch;
+ FILE *handle;
+
+ handle = fopen(VS_PROC_FILE, "r");
+ if (!handle) {
+ fprintf(stderr, "Could not open the %s file\n"
+ "Are you sure that IP Virtual Server is supported "
+ "by the kernel?\n", VS_PROC_FILE);
+ exit(1);
+ }
+
+ /*
+ * Read the first line and verify the IPVS version
+ */
+ if (!feof(handle) && fgets(buffer, sizeof(buffer), handle)) {
+ sscanf(buffer, "%*1024[a-z A-Z] %d.%d.%d",
+ &major, &minor, &patch);
+ if (IPVS_VERSION(major,minor,patch) <
+ IPVS_VERSION(MINIMUM_IPVS_VERSION_MAJOR,
+ MINIMUM_IPVS_VERSION_MINOR,
+ MINIMUM_IPVS_VERSION_PATCH)) {
+ fprintf(stderr,
+ "Warning: IPVS version missmatch: \n"
+ " Kernel compiled with IPVS version %d.%d.%d\n"
+ " ipvsadm " IPVSADM_VERSION_NO
+ " requires minimum IPVS version %d.%d.%d\n\n",
+ major, minor, patch,
+ MINIMUM_IPVS_VERSION_MAJOR,
+ MINIMUM_IPVS_VERSION_MINOR,
+ MINIMUM_IPVS_VERSION_PATCH);
+ }
+ buffer[strlen(buffer)-1]='\0';
+ }
+ fclose(handle);
+}
+
+
+void list_vs(unsigned int format)
+{
+ static char buffer[1024];
+ FILE *handle;
+ int i;
+
+ handle = fopen(VS_PROC_FILE, "r");
+ if (!handle) {
+ fprintf(stderr, "Could not open the %s file\n"
+ "Are you sure that IP Virtual Server is supported "
+ "by the kernel?\n", VS_PROC_FILE);
+ exit(1);
+ }
+
+ /*
+ * Read and print the first three head lines
+ */
+ for (i=0; i<2 && !feof(handle); i++) {
+ if (fgets(buffer, sizeof(buffer), handle)
+ && !(format & FMT_RULE))
+ printf("%s", buffer);
+ }
+ if (fgets(buffer, sizeof(buffer), handle) && !(format & FMT_RULE))
+ printf(" -> RemoteAddress:Port "
+ "Forward Weight ActiveConn InActConn\n");
+
+ /*
+ * Print the VS information according to the format
+ */
+ while (!feof(handle)) {
+ if (fgets(buffer, sizeof(buffer), handle))
+ print_vsinfo(buffer, format);
+ }
+
+ fclose(handle);
+}
+
+
+char *get_fwd_switch(char *fwd)
+{
+ char *swt;
+
+ switch (fwd[0]) {
+ case 'M':
+ swt = "-m"; break;
+ case 'T':
+ swt = "-i"; break;
+ default:
+ swt = "-g"; break;
+ }
+ return swt;
+}
+
+void print_vsinfo(char *buf, unsigned int format)
+{
+ /* virtual service variables */
+ char protocol[10];
+ static unsigned short proto = 0;
+ static struct in_addr vaddr;
+ static unsigned short vport;
+ static u_int32_t fwmark;
+ char scheduler[10];
+ char flags[40];
+ unsigned int timeout;
+ struct in_addr vmask;
+
+ /* destination variables */
+ static char arrow[10];
+ struct in_addr daddr;
+ unsigned short dport;
+ static char fwd[10];
+ int weight;
+ int activeconns;
+ int inactconns;
+
+ int n;
+ unsigned long temp;
+ unsigned long temp2;
+ char *vname;
+ char *dname;
+
+ if (buf[0] == ' ') {
+ /* destination entry */
+ if ((n = sscanf(buf, " %s %lX:%hX %s %d %d %d",
+ arrow, &temp, &dport, fwd, &weight,
+ &activeconns, &inactconns)) == -1)
+ exit(1);
+ if (n != 7)
+ fail(2, "unexpected input data");
+
+ daddr.s_addr = (__u32) htonl(temp);
+
+ if (!(dname=addrport_to_anyname(&daddr, dport, proto, format)))
+ exit(1);
+ if (format & FMT_RULE) {
+ if (fwmark == 0) {
+ if (!(vname=addrport_to_anyname
+ (&vaddr, vport, proto, format)))
+ exit(1);
+
+ printf("-a %s %s -r %s %s -w %d\n",
+ proto==IPPROTO_TCP?"-t":"-u",
+ vname, dname, get_fwd_switch(fwd), weight);
+ free(vname);
+ } else {
+ printf("-a -f %d -r %s %s -w %d\n", fwmark,
+ dname, get_fwd_switch(fwd), weight);
+ }
+ } else {
+ printf(" -> %-27s %-7s %-6d %-10d %-10d\n",
+ dname , fwd, weight, activeconns, inactconns);
+ }
+ free(dname);
+ } else if (buf[0] == 'F') {
+ /* fwmark virtual service entry */
+ if ((n = sscanf(buf, "%s %X %s %s %d %lX", protocol,
+ &fwmark, scheduler, flags,
+ &timeout, &temp2)) == -1)
+ exit(1);
+ if (n!=6 && n!=3)
+ fail(2, "unexpected input data");
+
+ vmask.s_addr = (__u32) htonl(temp2);
+
+ if (format & FMT_RULE)
+ printf("-A -f %d -s %s", fwmark, scheduler);
+ else
+ printf("%s %d %s", protocol, fwmark, scheduler);
+
+ if (n == 3)
+ printf("\n");
+ else {
+
+ printf(" %s %d",
+ format&FMT_RULE?"-p":flags, timeout/HZ);
+
+ if (vmask.s_addr == (unsigned long int) 0xffffffff)
+ printf("\n");
+ else
+ printf(" %s %s\n",
+ format&FMT_RULE?"-M":"mask",
+ inet_ntoa(vmask));
+
+ }
+ } else {
+ /* TCP/UDP virtual service entry */
+ fwmark=0; /* Reset firewall mark to unused */
+
+ if ((n = sscanf(buf, "%s %lX:%hX %s %s %d %lX",
+ protocol, &temp, &vport, scheduler,
+ flags, &timeout, &temp2)) == -1)
+ exit(1);
+ if (n!=7 && n!=4)
+ fail(2, "unexpected input data");
+
+ vaddr.s_addr = (__u32) htonl(temp);
+ vmask.s_addr = (__u32) htonl(temp2);
+ if (strcmp(protocol, "TCP") == 0) proto = IPPROTO_TCP;
+ else if (strcmp(protocol, "UDP") == 0) proto = IPPROTO_UDP;
+ else proto = 0;
+
+
+ if (!(vname=addrport_to_anyname(&vaddr, vport, proto, format)))
+ exit(1);
+
+ if (format & FMT_RULE)
+ printf("-A %s %s -s %s", proto==IPPROTO_TCP?"-t":"-u",
+ vname, scheduler);
+ else
+ printf("%s %s %s", protocol, vname, scheduler);
+
+ if (n == 4)
+ printf("\n");
+ else {
+ printf(" %s %d",
+ format&FMT_RULE?"-p":flags, timeout/HZ);
+
+ if (vmask.s_addr == (unsigned long int) 0xffffffff)
+ printf("\n");
+ else
+ printf(" %s %s\n",
+ format&FMT_RULE?"-M":"mask",
+ inet_ntoa(vmask));
+ }
+ free(vname);
+ }
+}
+
+
+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;
+}
+
+
+char * addr_to_host(struct in_addr *addr)
+{
+ struct hostent *host;
+
+ if ((host = gethostbyaddr((char *) addr,
+ sizeof(struct in_addr), AF_INET)) != NULL)
+ return (char *) host->h_name;
+ else
+ return (char *) NULL;
+}
+
+
+char * addr_to_anyname(struct in_addr *addr)
+{
+ char *name;
+
+ if ((name = addr_to_host(addr)) != NULL)
+ return name;
+ else
+ return inet_ntoa(*addr);
+}
+
+
+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
+ return -1;
+}
+
+
+char * port_to_service(int 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
+ return (char *) NULL;
+}
+
+
+char * port_to_anyname(int port, unsigned short proto)
+{
+ char *name;
+ static char buf[10];
+
+ if ((name = port_to_service(port, proto)) != NULL)
+ return name;
+ else {
+ sprintf(buf, "%d", port);
+ return buf;
+ }
+}
+
+
+char * addrport_to_anyname(struct in_addr *addr, int port,
+ unsigned short proto, unsigned int format)
+{
+ char *buf;
+
+ if (!(buf=malloc(60)))
+ return NULL;
+
+ if (format & FMT_NUMERIC) {
+ snprintf(buf, 60, "%s:%u",
+ inet_ntoa(*addr), port);
+ } else {
+ snprintf(buf, 60, "%s:%s", addr_to_anyname(addr),
+ port_to_anyname(port, proto));
+ }
+
+ return buf;
+}
+
+
+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);
+}
diff --git a/ipvsadm.sh b/ipvsadm.sh
new file mode 100644
index 0000000..db3147a
--- /dev/null
+++ b/ipvsadm.sh
@@ -0,0 +1,83 @@
+#!/bin/sh
+#
+# Startup script handle the initialisation of IPVS
+#
+# chkconfig: - 08 92
+#
+# description: Initialise the Linux Virtual Server
+# http://www.linuxvirtualserver.org/
+#
+# Script Author: Horms <horms@vergenet.net>
+#
+# Based on init script for ipchains by Joshua Jensen <joshua@redhat.com>
+#
+# config: /etc/sysconfig/ipvsadm
+
+
+IPVSADM_CONFIG=/etc/sysconfig/ipvsadm
+
+# Exit silently if there is no configuration file
+if [ ! -f $IPVSADM_CONFIG ]; then
+ exit
+fi
+
+# Use the funtions provided by Red Hat
+# This should be made distribution agnostic
+. /etc/rc.d/init.d/functions
+
+# Check for ipvsadm in both /sbin and /usr/sbin
+# The default install puts it in /sbin, as it is analogos to commands such
+# as route and ipchains that live in /sbin. Some vendors, most notibly
+# Red Hat insist on movint it to /usr/sbin
+if [ ! -x /sbin/ipvsadm -a ! -x /usr/sbin/ipvsadm ]; then
+ exit 0
+fi
+
+case "$1" in
+ start)
+ # If we don't clear these first, we might be adding to
+ # pre-existing rules.
+ action "Clearing the current IPVS table:" ipvsadm -C
+ echo -n "Applying IPVS configuration: "
+ grep -v "^#" $IPVSADM_CONFIG | ipvsadm-restore -p -f && \
+ success "Applying IPVS configuration" || \
+ failure "Applying IPVS configuration"
+ echo
+ touch /var/lock/subsys/ipvsadm
+ ;;
+
+ stop)
+ action "Clearing the current IPVS table:" ipvsadm -C
+ rm -f /var/lock/subsys/ipvsadm
+ ;;
+
+ restart)
+ #Start should flush everything
+ $0 start
+ ;;
+
+ panic)
+ # I'm not sure what panic does but in the case of IPVS
+ # it makes sense just to clear everything
+ action "Clearing the current IPVS table:" ipvsadm -C
+ ;;
+
+ status)
+ ipvsadm -L -n
+ ;;
+
+ save)
+ echo -n "Saving IPVS table to $IPVSADM_CONFIG: "
+ ipvsadm-save > $IPVSADM_CONFIG 2>/dev/null && \
+ success "Saving IPVS table to $IPVSADM_CONFIG" || \
+ failure "Saving IPVS table to $IPVSADM_CONFIG"
+ echo
+ ;;
+
+ *)
+ echo "Usage: $0 {start|stop|restart|status|panic|save}"
+ exit 1
+esac
+
+exit 0
+
diff --git a/ipvsadm.spec.in b/ipvsadm.spec.in
new file mode 100644
index 0000000..32dfac9
--- /dev/null
+++ b/ipvsadm.spec.in
@@ -0,0 +1,79 @@
+%define prefix /usr
+
+Summary: Utility to administer the Linux Virtual Server
+Name: ipvsadm
+Version: @@VERSION@@
+Release: @@RELEASE@@
+Copyright: GNU General Public Licence
+URL: http://www.LinuxVirtualServer.org/
+Packager: Horms <horms@vergenet.net> P.Copeland <copeland@redhat.com>
+Group: Applications/System
+Source0: http://www.LinuxVirutalServer.org/software/ipvsadm-%{version}.tar.gz
+BuildRoot: /var/tmp/%name-%{PACKAGE_VERSION}-root
+Docdir: %{prefix}/doc
+Provides: %{name}-%{version}
+Conflicts: piranha <= 0.4.14
+
+%description
+ipvsadm is a utility to administer the IP virtual server services
+offered by the Linux kernel augmented with the virtual server patch.
+
+
+%prep
+%setup -n ipvsadm
+
+
+%build
+CFLAGS="${RPM_OPT_FLAGS}" make
+
+
+%install
+rm -rf $RPM_BUILD_ROOT
+mkdir -p ${RPM_BUILD_ROOT}/{sbin,usr/man/man8,etc/rc.d/init.d}
+BUILD_ROOT=${RPM_BUILD_ROOT} make install
+
+#File finding code thanks to Samuel Flory of VA Linux Systems
+cd ${RPM_BUILD_ROOT}
+# Directories
+find . -type d | sed '1,2d;s,^\.,\%attr(-\,root\,root) \%dir ,' \
+ > ${RPM_BUILD_DIR}/%{name}-%{version}-%{release}.files
+# Files
+find . -type f | sed "s,^\.\(.*\),%attr (-\,root\,root) \1," \
+ >> ${RPM_BUILD_DIR}/%{name}-%{version}-%{release}.files
+# Symbolic links
+find . -type l | sed 's,^\.,\%attr(-\,root\,root) ,' \
+ >> ${RPM_BUILD_DIR}/%{name}-%{version}-%{release}.files
+
+
+%clean
+rm -rf $RPM_BUILD_DIR/%{name}
+rm -rf $RPM_BUILD_ROOT
+rm ${RPM_BUILD_DIR}/%{name}-%{version}-%{release}.files
+
+
+%files -f ../%{name}-%{version}-%{release}.files
+%doc README
+
+
+%changelog
+* Wed Aug 9 2000 Horms <horms@vergenet.net>
+- Removed Obseletes tag as ipvsadm is back in /sbin where it belongs
+ as it is more or less analogous to both route and ipchains both of
+ which reside in /sbin.
+- Create directory to install init script into. Init scripts won't install
+ into build directory unless this is done
+
+* Thu Jul 6 2000 Wensong Zhang <wensong@linuxvirtualserver.org>
+- Changed to build rpms on the ipvsadm tar ball directly
+
+* Wed Jun 21 2000 P.Copeland <copeland@redhat.com>
+- fixed silly install permission settings
+
+* Mon Jun 19 2000 P.Copeland <copeland@redhat.com>
+- Added 'dist' and 'rpms' to the Makefile
+- Added Obsoletes tag since there were early versions
+ of ipvsadm-*.rpm that installed in /sbin
+- Obsolete tag was a bit vicious re: piranha
+
+* Mon Apr 10 2000 Horms <horms@vergenet.net>
+- created for version 1.9