/*
 * $Id: updgen.c,v 1.4 2008/02/23 00:25:30 reid Exp $
 *

/*
 * In ISC's NSF-sponsored performance testing of BIND and the DNS
 * protocols, one of the important tests is the performance of an
 * authoritative server while it is being updated. In order to have
 * repeatable tests, we need to have update test streams. Updates
 * include both adds and drops; a record needs to be in the zone
 * before it can be dropped, and it ought not be in the zone if it
 * is to be added as part of the test. For this reason, the test
 * stream must be generated by processing of the zone that will be
 * updated.
 * 
 * This program is part of a package that produces test streams
 * from zone files. The script "make-preloads.sh" separates the
 * zone file by record type (A and NS), and the program in which
 * you are reading these words (updgen) imports those separated
 * parts and emits (on its standard output) an update stream
 * suitable for piping to nsupdate.
 * 
 * Because zone files can be very large, and because there are
 * good reasons for performing tests with portions of a zone,
 * this program (updgen) includes three compile-time parameters
 * that cause it to produce an output stream that is a subset of
 * the input files. Those parameters are:
 * 
 * #define DISCARD_LESS_THAN "M"
 * #define DISCARD_GEQ NULL
 * #define DISCARD_RANDOM_FRACTION 0.7
 * 
 * DISCARD_LESS_THAN causes any name in the zone that begins with
 * a character lexically less than that value to be discarded and
 * not included in the program's output. If its value is NULL,
 * then no less-than comparison will cause discards.
 * 
 * DISCARD_GEQ causes any name in the zon that begins with a
 * character lexically greater than or equal to that value to be
 * discarded and not included in the program's output. If its
 * value is NULL, then no greater-or-equal comparison will cause
 * discards.
 * 
 * DISCARD_RANDOM_FRACTION specifies a floating point value
 * between 0 and 1 specifying the fraction of the input stream to
 * discard at random. A value of 0 specifies that nothing is to be
 * discarded, and a value of 1.0 specifies that everything is to
 * be discarded. 
 * 
 * 
 * Usage:   updgen how-many > output.txt
 * 
 * Brian Reid, October 2007
 */

    #include <fcntl.h>
    #include <stdio.h>
    #include <ndbm.h>
    #include <stdlib.h>
    #include "math.h"

#define	TLD	".COM."

/* These three defines determine what (if anything) we will discard out of
   the input zone to make its size more tractable. NB that DISCARD_LESS_THAN
   is not LESS_OR_EQUAL; names beginning with this string will not be
   discarded. 
 */

#define DISCARD_LESS_THAN "M"
#define DISCARD_GEQ NULL
#define DISCARD_RANDOM_FRACTION 0.7

/* ------------------------------------------------------------------ */
/*
   Mongo globals. If this weren't a one-off I'd do something more
   sensible here.
 */
    char *NSnames[100000000];   /* 100 million NS names  */
    char *Anames[10000000];     /* 10 million A names */

#define TRUE	1
#define FALSE	0    

/* ------------------------------------------------------------------ */
/*
 * Measurements of the COM zone show that a recent copy (September 2007)
 * has 174637418 NS records, 1125162 A records, and 19 AAAA records.
 */

    char *Types[]={"NS","A"};
    int  CTypes[]={1746,1746+11};
    int  CMax    = 1746+11l;

/* ------------------------------------------------------------------ */
/*
 * http://dailychanges.com/ showed 1,541,184 adds, 41,235 drops, and 374,128
 * transfers on 25 October 2007. But the total net gain each day is only 
 * a few tens of thousands, so we have to add more drops. Not sure how
 * they are avoiding being noticed.
 */

    char *oTypes[] = {"add", "delete", "change"};
    int  CoTypes[] = {1541, 1541+1200, 1541+1200+374};
    int  CoMax     = 1541+1200+374;

/* ------------------------------------------------------------------ */
     usage () {
        fprintf (stderr, "\n\
	updgen how-many \n\
	outputs on stdout a generated nsupdate stream\n\n\
");
            return (0);
       }
/* ------------------------------------------------------------------ */
    char *
    GenRandomName() {
	static char linebuf[1000];
	sprintf(linebuf,"%d-ISC-test-%d",random(),random());
	return(linebuf);
    }
/* ------------------------------------------------------------------ */
    char *
    ChooseRandomHost(char *Array[], int Size) {
	int randIndex;
	do 
            randIndex = 1+(((long) Size-1 ) * (long) random()) / RAND_MAX;
	while (Array[randIndex][0] == ' ');  /* space means deleted */
	return (Array[randIndex]);
    }
	
/* ------------------------------------------------------------------ */
    char *
    GenRandomIP() {
	static char buffer[500];
        sprintf(buffer,"%d.%d.%d.%d",
	    192+(20l *  random()) / RAND_MAX,
	    1+(250l *  random()) / RAND_MAX,
	    1+(250l *  random()) / RAND_MAX,
	    1+(250l *  random()) / RAND_MAX);
	return(buffer);

    }

 /* ------------------------------------------------------------------ */

    int should_we_discard(char *TheName) {

 /* Discard a configured fraction of the input records. We have to make sure
    that enough remain undiscarded that we can generate the test data. */

	if (((double) random()) / ((double) RAND_MAX) 
	    < DISCARD_RANDOM_FRACTION) return(TRUE);
    
 /* Discard strings that are lexicograpically less than our marker */
    
	if (DISCARD_LESS_THAN != NULL) {
	    if (strcasecmp(TheName,DISCARD_LESS_THAN,TheName)<0) {
		return(TRUE);
	    }
	} else if (DISCARD_GEQ != NULL) {
	    if (strcasecmp(TheName,DISCARD_GEQ) >= 0) {
		return(TRUE);
	    }
	}
	
	return(FALSE);
    }

/* ------------------------------------------------------------------ */

    int  main (int argc, char **argv)
    {
	int HowMany = 10;
	FILE *NSpreload, *Apreload;

	int NNS=0, NA=0;
	int i,j,k;
	char *status, *allocmem, *newlinechar;
	char *ThisOp, *ThisType, *RandomName, *RandomHost;
	char linebuf[6000];
	char RandomArgument[6000];
	long rand1,rand2,rand3;

	if (argc > 1) HowMany = (int) strtol(argv[1],NULL,10);
	fprintf(stderr,"Generating = %d tests. Discarding %g%% of input.\n",HowMany,100*DISCARD_RANDOM_FRACTION);
	if (DISCARD_GEQ != NULL) fprintf(stderr,"Discarding names geq %s\n",DISCARD_GEQ);
	if (DISCARD_LESS_THAN != NULL) fprintf(stderr,"Discarding names lt %s\n",DISCARD_LESS_THAN);

	NSpreload = fopen("NS.preload","r");
	if (NSpreload == NULL) {
	    perror("NS.preload");
	    exit(1);
	}

	while (1) {
	    status = fgets(linebuf,sizeof(linebuf),NSpreload);
	    if (status == NULL) break;
	    newlinechar = strchr(linebuf,'\n');
	    if (newlinechar != NULL) {
	        *newlinechar = '\0';
	    }

	    if (should_we_discard(linebuf)) continue;

	    allocmem = malloc(1+strlen(linebuf));
	    if (allocmem == NULL) {
	        fprintf(stderr,"Out of VM!\n");
		exit(1);
	    }
	    NSnames[++NNS]=strcpy(allocmem,linebuf);
	}	
	fclose(NSpreload);
	fprintf(stderr,"Loaded %d NS names, ",NNS);

	Apreload = fopen("A.preload","r");
	if (Apreload == NULL) {
	    perror("A.preload");
	    exit(1);
	}

	while (1) {
	    status = fgets(linebuf,sizeof(linebuf),Apreload);
	    if (status == NULL) break;
	    newlinechar = strchr(linebuf,'\n');
	    if (newlinechar != NULL) {
	        *newlinechar = '\0';
	    }

	    if (should_we_discard(linebuf)) continue;

	    allocmem = malloc(1+strlen(linebuf));
	    if (allocmem == NULL) {
	        fprintf(stderr,"Out of VM!\n");
		exit(1);
	    }
	    Anames[++NA]=strcpy(allocmem,linebuf);
	}	
	fclose(Apreload);
	fprintf(stderr,"and %d A names.\n",NA);
	if (NA+NNS < 2*HowMany) {
	    fprintf(stderr,"%s input %d records, but was asked to generate %d test cases.\n",argv[0],NA+NNS,HowMany);
	    fprintf(stderr,"This won't work.\n");
	    if (DISCARD_RANDOM_FRACTION > 0.0) {
	        fprintf(stderr,"Currently discarding %d%% of input data.\n\
Consider recompiling %s with that number changed.\n",(int)(100.0*DISCARD_RANDOM_FRACTION),argv[0]);
	    }
	    fprintf(stderr,"Revise the requested number of test cases.\n");
	    exit(1);
	}

	srandomdev();


/* The databases are loaded. Start generating test cases. */

	fprintf(stdout,"server localhost\n");
	fprintf(stdout,"zone %s\n",&(TLD[1]));
	for (k=1; k<=HowMany; k++) {

/* Determine the type of record to be changed */
	    rand1 = (((long) CMax ) * (long) random()) / RAND_MAX;
	    for (j=0; j<sizeof(CTypes); j++) {
	        if (rand1 < CTypes[j]) {
		    ThisType = Types[j];
		    break;
		}
	    }
/* Determine the tpye of change to be made to it */
	    rand2 = (((long) CoMax ) * (long) random()) / RAND_MAX;
	    for (j=0; j<sizeof(oTypes); j++) {
	        if (rand2 < CoTypes[j]) {
		    ThisOp = oTypes[j];
		    break;
		}
	    }

/* add something */
	    if (!strcmp(ThisOp,"add")) {
    /* add an A record */
		if (!strcmp(ThisType,"A")) {
		    fprintf(stdout,"update add %s%s 86400 A %s\n",
			        GenRandomName(),TLD,GenRandomIP());
		} else {
    /* add a set of NS records */
		    int NScount;
		    char *NewName = GenRandomName();
		    char *NShost = ChooseRandomHost(NSnames,NNS);
		    NScount= 1+ (long) sqrt((double) (16*random()/RAND_MAX));
		    for (j=1; j<=NScount; j++) {
		        fprintf(stdout,"update add %s%s 172800 NS ns%d.%s\n",
			  NewName,TLD,j,NShost);
		    }

		}
/* delete something */
	    } else if (!strcmp(ThisOp,"delete")) {
 		if (!strcmp(ThisType,"A")) {
    /* Delete an A record */
		    RandomName = ChooseRandomHost(Anames,NA);
		    fprintf(stdout,"update delete %s%s A\n",RandomName,TLD);
		} else if (!strcmp(ThisType,"NS")) {
    /* Delete NS records */
		    RandomName = ChooseRandomHost(NSnames,NNS);
		    fprintf(stdout,"update delete %s%s NS\n",RandomName,TLD);
		}
		RandomName[0] = ' ';		/* mark deleted */
	    } else {
/* update something */
		if (!strcmp(ThisType,"A")) {
    /* update an A record */
		    RandomName = ChooseRandomHost(Anames,NA);
		    fprintf(stdout,"update delete %s%s A\n",RandomName,TLD);
		    fprintf(stdout,"update add %s%s 86400 A %s\n",RandomName,TLD,GenRandomIP());
		} else {
    /* update a set of NS records */
		    int NScount;
		    char *NShost = ChooseRandomHost(NSnames,NNS);

		    RandomName = ChooseRandomHost(NSnames,NNS);
		    fprintf(stdout,"update delete %s%s NS\n",RandomName,TLD);

		    NScount= 1+ (long) sqrt((double) (16*random()/RAND_MAX));
		    for (j=1; j<=NScount; j++) {
		        fprintf(stdout,"update add %s%s 172800 NS ns%d.%s\n",
			  RandomName,TLD,j,NShost);
		    }
		} 
	    }
	    
	    fprintf(stdout,"\n");
	}
	exit(0);
    }



