Standardize License information
[openafs.git] / src / rxkad / test / stress_c.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /* RX Authentication Stress test: client side code. */
11
12 #include <afs/param.h>
13 #include <afs/stds.h>
14 #include <sys/types.h>
15 #include <stdio.h>
16 #ifdef AFS_NT40_ENV
17 #include <winsock2.h>
18 #else
19 #include <netdb.h>
20 #include <netinet/in.h>
21 #endif
22 #include <afs/com_err.h>
23 #include <afs/afsutil.h>
24 #include <rx/rxkad.h>
25 #include "stress.h"
26 #include "stress_internal.h"
27 #ifdef AFS_PTHREAD_ENV
28 #include <pthread.h>
29 #define FT_ApproxTime() (int)time(0)
30 #endif
31
32 #ifdef rx_GetPacketCksum
33 #include "../../permit_xprt.h"
34 #endif
35
36 extern int maxSkew;
37
38
39 static char *whoami;
40
41 static long GetServer(aname)
42   IN char *aname;
43 {
44     register struct hostent *th;
45     long addr;
46
47     th = gethostbyname(aname);
48     if (!th) {
49         fprintf (stderr, "host %s not found\n", aname);
50         return errno;
51     }
52     bcopy(th->h_addr, &addr, sizeof(addr));
53     return addr;
54 }
55
56 static long GetTicket (versionP, session, ticketLenP, ticket)
57   OUT long *versionP;
58   OUT struct ktc_encryptionKey *session;
59   OUT int *ticketLenP;
60   OUT char *ticket;
61 {
62     long code;
63
64     /* create random session key, using key for seed to good random */
65     des_init_random_number_generator (&serviceKey);
66     code = des_random_key (session);
67     if (code) return code;
68     
69     /* now create the actual ticket */
70     *ticketLenP = 0;
71     code = tkt_MakeTicket(ticket, ticketLenP, &serviceKey,
72                           RXKST_CLIENT_NAME, RXKST_CLIENT_INST, "",
73                           /*start,end*/0, 0xffffffff, session, /*host*/0,
74                           RXKST_SERVER_NAME, RXKST_SERVER_NAME);
75     /* parms were buffer, ticketlen, key to seal ticket with, principal name,
76      * instance and cell, start time, end time, session key to seal in ticket,
77      * inet host, server name and server instance */
78     if (code) return code;
79     *versionP = serviceKeyVersion;
80     return 0;
81 }
82 struct client {
83     struct rx_connection *conn;
84     u_long sendLen;                     /* parameters for call to Copious */
85     u_long recvLen;
86     u_long *fastCalls;                  /* number of calls to perform */
87     u_long *slowCalls;
88     u_long *copiousCalls;
89 };
90
91 static long Copious (c, buf, buflen)
92   IN struct client *c;
93   IN u_char *buf;
94   IN u_long buflen;
95 {
96     long code;
97     struct rx_call *call;
98     long i;
99     long inlen = c->sendLen;
100     long outlen = c->recvLen;
101     long d = 23;
102     long mysum;
103     long outsum;
104
105     mysum = 0;
106     for (i=0; i<inlen; i++) mysum += (d++ & 0xff);
107
108     call = rx_NewCall (c->conn);
109     code = StartRXKST_Copious (call, inlen, mysum, outlen);
110     if (code == 0) {
111         long tlen;
112         long xfer = 0;
113         long n;
114         d = 23;
115         while (xfer < inlen) {
116             tlen = inlen - xfer;
117             if (tlen > buflen) tlen = buflen;
118             for (i=0; i<tlen; i++) buf[i] = (d++ & 0xff);
119             n = rx_Write (call, buf, tlen);
120             if (n != tlen) {
121                 if (n < 0) code = n;
122                 else code = RXKST_WRITESHORT;
123                 break;
124             }
125             xfer += tlen;
126         }
127         if (code == 0) {
128             xfer = 0;
129             mysum = 0;
130             while (xfer < outlen) {
131                 tlen = outlen - xfer;
132                 if (tlen > buflen) tlen = buflen;
133                 n = rx_Read (call, buf, tlen);
134                 if (n != tlen) {
135                     if (n < 0) code = n;
136                     else code = RXKST_READSHORT;
137                     break;
138                 }
139                 for (i=0; i<tlen; i++) mysum += buf[i];
140                 xfer += tlen;
141             }
142         }
143     }
144     if (code == 0)
145         code = EndRXKST_Copious (call, &outsum);
146     code = rx_EndCall(call, code);
147     if (code) return code;
148     if (outsum != mysum) {
149         return RXKST_BADOUTPUTSUM;
150     }
151     return 0;
152 }
153
154 static long DoClient (index, rock)
155   IN u_int index;
156   IN opaque rock;
157 {
158     struct client *c = (struct client *)rock;
159     long code;
160     int i;
161     u_long n, inc_n;
162
163     n = 95678;
164     for (i=0; i<c->fastCalls[index]; i++) {
165         code = RXKST_Fast (c->conn, n, &inc_n);
166         if (code) return (code);
167         if (n+1 != inc_n) return RXKST_INCFAILED;
168         n++;
169     }
170
171     for (i=0; i<c->slowCalls[index]; i++) {
172         u_long ntime;
173         u_long now;
174         code = RXKST_Slow (c->conn, 1, &ntime);
175         if (code) return (code);
176         now = FT_ApproxTime();
177         if ((ntime < now-maxSkew) || (ntime > now+maxSkew)) return RXKST_TIMESKEW;
178     }
179
180     if (c->copiousCalls[index] > 0) {
181         u_long buflen = 10000;
182         u_char *buf = (u_char *) osi_Alloc (buflen);
183         for (i=0; i<c->copiousCalls[index]; i++) {
184             code = Copious (c, buf, buflen);
185             if (code) break;
186         }
187         osi_Free (buf, buflen);
188         if (code) return code;
189     }
190     return 0;
191 }
192
193 struct worker {
194     struct worker *next;
195     long exitCode;                      /* is PROCESSRUNNING until exit */
196     int index;
197     opaque rock;
198     long (*proc)();
199 };
200
201 #ifdef AFS_PTHREAD_ENV
202 static pthread_once_t workerOnce = PTHREAD_ONCE_INIT;
203 static pthread_mutex_t workerLock;
204 static pthread_cond_t workerCV;
205 void WorkerInit(void)
206 {
207     pthread_mutex_init(&workerLock, NULL);
208     pthread_cond_init(&workerCV, NULL);
209 }
210 #endif
211 static struct worker *workers;
212
213 static long DoWorker (w)
214   IN struct worker *w;
215 {
216     long code;
217     code = (*w->proc) (w->index, w->rock);
218 #ifdef AFS_PTHREAD_ENV
219     pthread_mutex_lock(&workerLock);
220 #endif
221     w->exitCode = code;
222 #ifdef AFS_PTHREAD_ENV
223     pthread_mutex_unlock(&workerLock);
224 #endif
225 #ifdef AFS_PTHREAD_ENV
226     pthread_cond_signal(&workerCV);
227 #else
228     LWP_NoYieldSignal(&workers);
229 #endif
230     return code;
231 }
232
233 #define MAX_CTHREADS 25
234
235 static long CallSimultaneously (threads, rock, proc)
236   IN u_int threads;
237   IN opaque rock;
238   IN long (*proc)();
239 {
240     long code;
241     int i;
242 #ifdef AFS_PTHREAD_ENV
243     pthread_once(&workerOnce, WorkerInit);
244 #endif
245
246     workers = 0;
247     for (i=0; i<threads; i++) {
248         struct worker *w;
249 #ifdef AFS_PTHREAD_ENV
250         pthread_t pid;
251 #else
252         PROCESS pid;
253 #endif
254         assert (i < MAX_CTHREADS);
255         w = (struct worker *) osi_Alloc (sizeof(struct worker));
256         bzero (w, sizeof(*w));
257         w->next = workers;
258         workers = w;
259         w->index = i;
260         w->exitCode = RXKST_PROCESSRUNNING;
261         w->rock = rock;
262         w->proc = proc;
263 #ifdef AFS_PTHREAD_ENV
264         {
265             pthread_attr_t tattr;
266
267             code = pthread_attr_init(&tattr);
268             if (code) {
269                 com_err (whoami, code, "can't pthread_attr_init worker process");
270                 return code;
271             }
272
273             code = pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
274             if (code) {
275                 com_err (whoami, code, "can't pthread_attr_setdetachstate worker process");
276                 return code;
277             }
278
279             code = pthread_create(&pid, &tattr, DoWorker, (void*)w);
280         }
281 #else
282         code = LWP_CreateProcess(DoWorker, 16000, LWP_NORMAL_PRIORITY,
283                                  (opaque) w, "Worker Process", &pid);
284 #endif
285         if (code) {
286             com_err (whoami, code, "can't create worker process");
287             return code;
288         }
289     }
290     code = 0;                           /* last non-zero code encountered */
291 #ifdef AFS_PTHREAD_ENV
292     pthread_mutex_lock(&workerLock);
293 #endif
294     while (workers) {
295         struct worker *w, *prevW, *nextW;
296         prevW = 0;
297         for (w=workers; w; w=nextW) {
298             nextW = w->next;
299             if (w->exitCode != RXKST_PROCESSRUNNING) {
300                 if (w->exitCode) {
301                     if (code == 0) code = w->exitCode;
302                 }
303                 if (prevW) prevW->next = w->next;
304                 else workers = w->next;
305                 osi_Free (w, sizeof(*w));
306                 continue;               /* don't bump prevW */
307             }
308             prevW = w;
309         }
310 #ifdef AFS_PTHREAD_ENV
311         if (workers) 
312             pthread_cond_wait(&workerCV, &workerLock);
313 #else
314         if (workers) LWP_WaitProcess (&workers);
315 #endif
316     }
317 #ifdef AFS_PTHREAD_ENV
318     pthread_mutex_unlock(&workerLock);
319 #endif
320     return code;
321 }
322
323 static void DivideUpCalls (calls, threads, threadCalls)
324   IN u_long calls;
325   IN u_int threads;
326   IN u_long threadCalls[];
327 {
328     int i;
329     for (i=0; i<threads; i++) {
330         threadCalls[i] = calls / (threads-i);
331         calls -= threadCalls[i];
332     }
333 }
334
335 static double ftime ()
336 {
337     struct timeval tv;
338     gettimeofday(&tv, 0);
339     return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
340 }
341
342 static long RunLoadTest (parms, conn)
343   IN struct clientParms *parms;
344   IN struct rx_connection *conn;
345 {
346     long code;
347     struct client c;
348     u_long fastCalls[MAX_CTHREADS];
349     u_long slowCalls[MAX_CTHREADS];
350     u_long copiousCalls[MAX_CTHREADS];
351     double start, interval;
352
353     DivideUpCalls (parms->fastCalls, parms->threads, fastCalls);
354     DivideUpCalls (parms->slowCalls, parms->threads, slowCalls);
355     DivideUpCalls (parms->copiousCalls, parms->threads, copiousCalls);
356
357     bzero (&c, sizeof(c));
358     c.conn = conn;
359     c.sendLen = parms->sendLen;
360     c.recvLen = parms->recvLen;
361     c.fastCalls = fastCalls;
362     c.slowCalls = slowCalls;
363     c.copiousCalls = copiousCalls;
364
365     start = ftime();
366     code = CallSimultaneously (parms->threads, &c, DoClient);
367     if (code) {
368         com_err (whoami, code, "in DoClient");
369         return code;
370     }
371     interval = ftime() - start;
372
373     if (parms->printTiming) {
374         u_long totalCalls =
375             parms->fastCalls + parms->slowCalls + parms->copiousCalls;
376         int t = (interval / totalCalls) * 1000.0 + 0.5;
377         if (totalCalls > 0) {
378             printf ("For %d calls: %d msec/call\n", totalCalls, t);
379         }
380         if (parms->copiousCalls > 0) {
381             long n = parms->sendLen + parms->recvLen;
382             double kbps;
383             int b;
384             kbps = (double)(parms->copiousCalls * n) / (interval * 1000.0);
385             b = kbps + 0.5;
386 #if 0
387 I just cannot get printing of floats to work on the pmax!!!!
388             printf ("%g %d %d %d\n", (float)kbps, b);
389             printf ("%g %d %d %d\n", kbps, b);
390             fprintf (stdout, "%g %d %d\n", kbps, b);
391             {
392                 char buf[100];
393                 buf[sizeof(buf)-1] = 0;
394                 sprintf (buf, "%g %d %d\n", kbps, b);
395                 assert (buf[sizeof(buf)-1] == 0);
396                 printf ("%s", buf);
397             }
398 #endif
399             printf ("For %d copious calls, %d send + %d recv = %d bytes each: %d kbytes/sec\n",
400                     parms->copiousCalls, parms->sendLen, parms->recvLen,
401                     n, b);
402 #if 0
403             printf ("%g\n", kbps);
404 #endif
405         }
406     }
407     return 0;
408 }
409
410 static long RepeatLoadTest (parms, conn)
411   IN struct clientParms *parms;
412   IN struct rx_connection *conn;
413 {
414     long code;
415     long count;
416
417     if (parms->repeatInterval == 0) {
418         if (parms->repeatCount == 0) parms->repeatCount = 1;
419     } else {
420         if (parms->repeatCount == 0) parms->repeatCount = 0x7fffffff;
421     }
422
423     if (parms->printTiming) {
424         int types;
425         types = (parms->fastCalls ? 1 : 0) +
426             (parms->slowCalls ? 1 : 0) + (parms->copiousCalls ? 1 : 0);
427         if (types > 1)
428             fprintf (stderr, "Combined timings of several types of calls may not be meaningful.\n");
429         if (types == 0)
430             /* do timings of copious calls by default */
431             parms->copiousCalls = 10;
432     }
433
434     for (count=0; count<parms->repeatCount; count++) {
435         code = RunLoadTest (parms, conn);
436         if (code) return code;
437         if (parms->repeatInterval) {
438             u_long i = parms->repeatInterval;
439             u_long now = time(0);
440             u_long next = (now + i-1) / i * i; /* round up to next interval */
441             while (now < next) {
442 #ifdef AFS_PTHREAD_ENV
443                 sleep(next-now);
444 #else
445                 IOMGR_Sleep (next-now);
446 #endif
447                 now = time(0);
448             }
449         }
450     }
451     return code;
452 }
453
454 /* For backward compatibility, don't try to use the CallNumber stuff unless
455  * we're compiling against the new Rx. */
456
457 #ifdef rx_GetPacketCksum
458
459 struct multiChannel {
460     struct rx_connection *conn;
461     int done;
462     long *codes;
463     int changes[RX_MAXCALLS];
464     long callNumbers[RX_MAXCALLS];
465 };
466 #define BIG_PRIME 1257056893            /* 0x4AED2A7d */
467 static u_long sequence = 0;
468
469 static long FastCall (conn)
470   IN struct rx_connection *conn;
471 {
472     long code;
473     u_long n = (sequence = sequence * BIG_PRIME + BIG_PRIME);
474     u_long inc_n;
475
476     code = RXKST_Fast (conn, n, &inc_n);
477     if (code) return code;
478     if (inc_n != n+1) return RXKST_INCFAILED;
479     return 0;
480 }
481
482 static long UniChannelCall (index, rock)
483   IN u_int index;
484   IN opaque rock;
485 {
486     struct multiChannel *mc = (struct multiChannel *)rock;
487     long code;
488     long callNumbers[RX_MAXCALLS];
489     int unchanged;
490
491     code = 0;
492     unchanged = 1;
493     while (!mc->done && unchanged) {
494         int i;
495         code = FastCall (mc->conn);
496         if (code) break;
497         code = rxi_GetCallNumberVector (mc->conn, callNumbers);
498         if (code) break;
499         unchanged=0;
500         for (i=0; i<RX_MAXCALLS; i++) {
501             if (callNumbers[i] > mc->callNumbers[i]) {
502                 mc->callNumbers[i] = callNumbers[i];
503                 mc->changes[i]--;       /* may go negative */
504             }
505             if (mc->changes[i] > 0) unchanged++;
506         }
507     }
508     mc->codes[index] = code;
509     mc->done++;
510     return code;
511 }
512   
513 static long MakeMultiChannelCall (conn, each, expectedCode, codes)
514   IN struct rx_connection *conn;
515   IN int each;                          /* calls to make on each channel */
516   IN long expectedCode;
517   OUT long codes[];
518 {
519     long code;
520     int i;
521     struct multiChannel mc;
522
523     bzero (&mc, sizeof(mc));
524     mc.conn = conn;
525     for (i=0; i<RX_MAXCALLS; i++) {
526         codes[i] = RXKST_PROCESSRUNNING;
527         mc.changes[i] = each;
528     }
529     mc.codes = codes;
530     code = rxi_GetCallNumberVector (conn, mc.callNumbers);
531     if (code) return code;
532     mc.done = 0;
533     code = CallSimultaneously (RX_MAXCALLS, &mc, UniChannelCall);
534     if (((expectedCode == RXKST_INCFAILED) || (expectedCode == -1)) &&
535         ((code == expectedCode) || (code == -3))) ; /* strange cases */
536     else if (code != expectedCode) {
537         com_err (whoami, code,
538                  "problem making multichannel call, expected '%s'",
539                  ((expectedCode == 0)
540                   ? "no error" : (char *)error_message (expectedCode)));
541     }
542     return code;
543 }
544
545 static long CheckCallFailure (conn, codes, code, msg)
546   IN struct rx_connection *conn;
547   IN long codes[];
548   IN long code;
549   IN char *msg;
550 {
551     if (code == 0) {
552         fprintf (stderr, "Failed to detect %s\n", msg);
553         return RXKST_NODUPLICATECALL;
554     } else {
555         int i;
556         int okay = 1;
557         int someZero = 0;
558         for (i=0; i<RX_MAXCALLS; i++)
559             if (!((codes[i] == 0) || (codes[i] == code) ||
560                   (codes[i] == -3))) okay = 0;
561         if (conn->error) okay = 0;
562         if (!okay) {
563             fprintf (stderr, "%s produced these errors:\n", msg);
564             for (i=0; i<RX_MAXCALLS; i++) {
565                 assert (codes[i] != RXKST_PROCESSRUNNING);
566                 if (codes[i] == 0) {
567                     someZero++;
568                     fprintf (stderr, "  %d no error\n", i);
569                 } else fprintf (stderr,
570                                 "  %d %s\n", i, error_message(codes[i]));
571             }
572             if (someZero) {
573                 char buf[100];
574                 sprintf (buf, "connection dead following %s", msg);
575                 code = FastCall (conn);
576                 if (code) com_err (whoami, code, buf);
577             }
578         }
579     }
580     return 0;
581 }
582
583 #endif rx_GetPacketCksum
584
585 static long RunCallTest (parms, host, sc, si)
586   IN struct clientParms *parms;
587   IN long host;
588   IN struct rx_securityClass *sc;
589   IN long si;
590 {
591     long code;
592
593 #ifndef rx_GetPacketCksum
594
595     code = RXKST_BADARGS;
596     com_err (whoami, code, "Older versions of Rx don't support Get/Set callNumber Vector procedures: can't run this CallTest");
597     return code;
598
599 #else
600
601     int i, ch;
602     struct rx_connection *conn;
603     long firstCall;
604     long callNumbers[RX_MAXCALLS];
605     long codes[RX_MAXCALLS];
606     long retCode = 0;                   /* ret. if nothing fatal goes wrong */
607
608     conn = rx_NewConnection(host, htons(RXKST_SERVICEPORT), RXKST_SERVICEID,
609                             sc, si);
610     if (!conn) return RXKST_NEWCONNFAILED;
611
612     /* First check the basic behaviour of call number handling */
613
614     code = rxi_GetCallNumberVector (conn, callNumbers);
615     if (code) return code;
616     for (i=0; i<RX_MAXCALLS; i++) {
617         if (callNumbers[i] != 0) {
618             fprintf (stderr, "Connection's initial call numbers not zero. call[%d] = %d\n", i, callNumbers[i]);
619             return RXKST_BADCALLNUMBERS;
620         }
621     }
622     code = FastCall (conn);
623     if (code) return code;
624     code = rxi_GetCallNumberVector (conn, callNumbers);
625     if (code) return code;
626     firstCall = callNumbers[0];
627     code = FastCall (conn);
628     if (code) return code;
629     code = rxi_GetCallNumberVector (conn, callNumbers);
630     if (code) return code;
631     if ((callNumbers[0] != firstCall+1) &&
632         ((firstCall == 1) || (firstCall == 2))) {
633         /* The call number after the first call should be one or, more likely,
634          * two (if the call is still DALLYing).  Between first and second call,
635          * the call number should have incremented by one. */
636         fprintf (stderr, "Connection's first channel call number not one. call[%d] = %d\n", 0, callNumbers[0]);
637         return RXKST_BADCALLNUMBERS;
638     }
639     for (i=1; i<RX_MAXCALLS; i++) {
640         if (callNumbers[i] != 0) {
641             fprintf (stderr, "Connection's other channel call numbers not zero. call[%d] = %d\n", i, callNumbers[i]);
642             return RXKST_BADCALLNUMBERS;
643         }
644     }
645     code = MakeMultiChannelCall (conn, 1, 0, codes);
646     if (code) return code;
647
648     /* Now try to resend a call that's already been executed by finding a
649      * non-zero call number on a channel other than zero and decrementing it by
650      * one.  This should appear to the server as a retransmitted call.  Since
651      * this is behaving as a broken client different strange behaviors may be
652      * exhibited by different servers.  If the response packet to the original
653      * call is discarded by the time the "retransmitted" call arrives (perhaps
654      * due to high server or client load) there is no way for the server to
655      * respond at all.  Further, it seems, that under some cases the connection
656      * will be kept alive indefinitely even though the server has discarded the
657      * "retransmitted" call and is making no effort to reexecute the call.  To
658      * handle these, accept either a timeout (-1) or and INCFAILED error here,
659      * also set the connenction HardDeadTime to punt after a reasonable
660      * interval. */
661
662     /* short dead time since may we expect some trouble */
663     rx_SetConnHardDeadTime (conn, 30);
664     code = rxi_GetCallNumberVector (conn, callNumbers);
665     if (code) return code;
666     for (ch=1; ch<RX_MAXCALLS; ch++)
667         if (callNumbers[ch] > 1) {
668             callNumbers[ch]--;
669             code = rxi_SetCallNumberVector (conn, callNumbers);
670             if (code) return code;
671             break;
672         }
673     if (ch>= RX_MAXCALLS)               /* didn't find any? all DALLYing? */
674         return RXKST_BADCALLNUMBERS;
675     code = MakeMultiChannelCall (conn, 1, RXKST_INCFAILED, codes);
676     code = CheckCallFailure (conn, codes, code, "retransmitted call");
677     if (code && !retCode) retCode = code;
678
679     /* Get a fresh connection, becasue if the above failed as it should the
680      * connection is dead. */
681     rx_DestroyConnection (conn);
682     conn = rx_NewConnection(host, htons(RXKST_SERVICEPORT), RXKST_SERVICEID,
683                             sc, si);
684     if (!conn) return RXKST_NEWCONNFAILED;
685
686     /* Similarly, but decrement call number by two which should be completely
687      * unmistakeable as a broken or malicious client. */
688
689     /* short dead time since may we expect some trouble */
690     rx_SetConnHardDeadTime (conn, 30);
691     code = MakeMultiChannelCall (conn, 2, 0, codes);
692     if (code) return code;
693     code = rxi_GetCallNumberVector (conn, callNumbers);
694     if (code) return code;
695     for (ch=1; ch<RX_MAXCALLS; ch++)
696         if (callNumbers[ch] > 2) {
697             callNumbers[ch] -= 2;
698             code = rxi_SetCallNumberVector (conn, callNumbers);
699             break;
700         }
701     if (ch>= RX_MAXCALLS)               /* didn't find any? all DALLYing? */
702         return RXKST_BADCALLNUMBERS;
703     code = MakeMultiChannelCall (conn, 1, -1, codes);
704     code = CheckCallFailure (conn, codes, code, "duplicate call");
705     if (code && !retCode) retCode = code;
706
707     rx_DestroyConnection (conn);
708     conn = rx_NewConnection(host, htons(RXKST_SERVICEPORT), RXKST_SERVICEID,
709                             sc, si);
710     if (!conn) return RXKST_NEWCONNFAILED;
711
712     /* Next, without waiting for the server to discard its state, we will check
713      * to see if the Challenge/Response protocol correctly informs the server
714      * of the client's callNumber state.  We do this by artificially increasing
715      * the call numbers of a new connection for all channels beyond zero,
716      * making a call on channel zero, then resetting the call number for the
717      * unused channels back to zero, then making calls on all channels. */
718
719     code = rxi_GetCallNumberVector (conn, callNumbers);
720     if (code) return code;
721     for (i=0; i<RX_MAXCALLS; i++) {
722         if (callNumbers[i] != 0) return RXKST_BADCALLNUMBERS;
723         callNumbers[i] = 51;            /* an arbitrary value... */
724     }
725     code = rxi_SetCallNumberVector (conn, callNumbers);
726     if (code) return code;
727     code = FastCall (conn);             /* use channel 0 */
728     if (code) return code;
729     code = rxi_GetCallNumberVector (conn, callNumbers);
730     if (code) return code;
731     if (callNumbers[0] != 52) return RXKST_BADCALLNUMBERS;
732     for (i=1; i<RX_MAXCALLS; i++) {
733         if (callNumbers[i] != 51) return RXKST_BADCALLNUMBERS;
734         callNumbers[i] = 37;            /* back up a ways */
735     }
736     code = rxi_SetCallNumberVector (conn, callNumbers);
737     if (code) return code;
738     /* now try calls on all channels... */
739     code = MakeMultiChannelCall (conn, 1, -1, codes);
740     code = CheckCallFailure (conn, codes, code,
741                              "alternate channel call replay");
742     if (code && !retCode) retCode = code;
743
744     rx_DestroyConnection (conn);
745     return retCode;
746
747 #endif rx_GetPacketCksum
748
749 }
750
751 #ifdef rx_GetPacketCksum
752
753 static struct {
754     int op;
755     u_long epoch;                       /* connection to attack */
756     u_long cid;
757     int client;                         /* TRUE => client side */
758     u_long newEpoch;                    /* conn to direct challenges to */
759     u_long newCid;
760     u_long counts[RX_N_PACKET_TYPES];
761 } incomingOps;
762 #define IO_NOOP                 0
763 #define IO_COUNT                1
764 #define IO_REDIRECTCHALLENGE    2
765
766 static int HandleIncoming (p, addr)
767   INOUT struct rx_packet *p;
768   INOUT struct sockaddr_in *addr;
769 {
770     int client;                         /* packet sent by client */
771     u_char type;                        /* packet type */
772
773     if (incomingOps.op == IO_NOOP) return 0;
774
775     client = ((p->header.flags&RX_CLIENT_INITIATED) != RX_CLIENT_INITIATED);
776     if ((p->header.epoch != incomingOps.epoch) ||
777         ((p->header.cid ^ incomingOps.cid) & RX_CIDMASK) ||
778         (client != incomingOps.client)) return 0;
779     type = p->header.type;
780     if ((type <= 0) || (type >= RX_N_PACKET_TYPES)) type = 0;
781     incomingOps.counts[type]++;
782
783     switch (incomingOps.op) {
784       case IO_NOOP:
785       case IO_COUNT:
786         break;
787
788       case IO_REDIRECTCHALLENGE:
789         if (p->header.type != RX_PACKET_TYPE_CHALLENGE) break;
790         p->header.epoch = incomingOps.newEpoch;
791         p->header.cid = incomingOps.newCid;
792         /* Now set up to watch for the corresponding challenge. */
793         incomingOps.epoch = incomingOps.newEpoch;
794         incomingOps.cid = incomingOps.newCid;
795         incomingOps.op = IO_COUNT;
796         break;
797
798       default:
799         fprintf (stderr, "Unknown incoming op %d\n", incomingOps.op);
800         break;
801     }
802     return 0;
803 }
804
805 static struct {
806     int op;
807     u_long epoch;                       /* connection to attack */
808     u_long cid;
809     int client;                         /* TRUE => client side */
810     u_long counts[RX_N_PACKET_TYPES];
811 } outgoingOps;
812 #define OO_NOOP         0
813 #define OO_COUNT        1
814 #define OO_ZEROCKSUM    2
815 #define OO_MUNGCKSUM    3
816
817 static int HandleOutgoing (p, addr)
818   INOUT struct rx_packet *p;
819   INOUT struct sockaddr_in *addr;
820 {
821     int client;                         /* packet sent by client */
822     u_char type;                        /* packet type */
823
824     if (outgoingOps.op == OO_NOOP) return 0;
825
826     client = ((p->header.flags&RX_CLIENT_INITIATED) == RX_CLIENT_INITIATED);
827     if ((p->header.epoch != outgoingOps.epoch) ||
828         ((p->header.cid ^ outgoingOps.cid) & RX_CIDMASK) ||
829         (client != outgoingOps.client)) return 0;
830     type = p->header.type;
831     if ((type <= 0) || (type >= RX_N_PACKET_TYPES)) type = 0;
832     outgoingOps.counts[type]++;
833
834     switch (outgoingOps.op) {
835       case OO_NOOP:
836       case OO_COUNT:
837         /* counting always happens above if not noop */
838         break;
839
840       case OO_ZEROCKSUM:
841         if (p->header.type != RX_PACKET_TYPE_DATA) break;
842         if (rx_GetPacketCksum (p) == 0) {
843             /* probably, a retransmitted packet */
844             fprintf (stderr, "Packet cksum already zero\n");
845             break;
846         }
847         rx_SetPacketCksum (p, 0);
848         break;
849
850       case OO_MUNGCKSUM: {
851           u_short cksum;
852           if (p->header.type != RX_PACKET_TYPE_DATA) break;
853           cksum = rx_GetPacketCksum (p);
854           if (cksum == 0) {
855               fprintf (stderr, "Packet cksum already zero\n");
856               break;
857           }
858           rx_SetPacketCksum (p, cksum ^ 8);
859           break;
860       }
861       default:
862         fprintf (stderr, "Unknown outgoing op %d\n", outgoingOps.op);
863         break;
864     }
865     return 0;
866 }
867
868 #ifdef AFS_PTHREAD_ENV
869 static pthread_once_t slowCallOnce = PTHREAD_ONCE_INIT;
870 static pthread_mutex_t slowCallLock;
871 static pthread_cond_t slowCallCV;
872 void SlowCallInit(void)
873 {
874     pthread_mutex_init(&slowCallLock, NULL);
875     pthread_cond_init(&slowCallCV, NULL);
876 }
877 #endif
878 static long slowCallCode;
879 static long SlowCall (conn)
880   IN opaque conn;
881 {
882     u_long ntime;
883     u_long now;
884     long temp_rc;
885
886 #ifdef AFS_PTHREAD_ENV
887     pthread_mutex_lock(&slowCallLock);
888 #endif
889     slowCallCode = RXKST_PROCESSRUNNING;
890 #ifdef AFS_PTHREAD_ENV
891     pthread_cond_signal(&slowCallCV);
892 #else
893     LWP_NoYieldSignal (&slowCallCode);
894 #endif
895     slowCallCode = RXKST_Slow (conn, 1, &ntime);
896     if (!slowCallCode) {
897         now = FT_ApproxTime();
898         if ((ntime < now-maxSkew) || (ntime > now+maxSkew))
899             slowCallCode = RXKST_TIMESKEW;
900     }
901     temp_rc = slowCallCode;
902 #ifdef AFS_PTHREAD_ENV
903     pthread_cond_signal(&slowCallCV);
904     pthread_mutex_unlock(&slowCallLock);
905 #else
906     LWP_NoYieldSignal (&slowCallCode);
907 #endif
908     return temp_rc;
909 }
910
911 #endif rx_GetPacketCksum
912
913 static long RunHijackTest (parms, host, sc, si)
914   IN struct clientParms *parms;
915   IN long host;
916   IN struct rx_securityClass *sc;
917   IN long si;
918 {
919
920 #ifndef rx_GetPacketCksum
921
922     code = RXKST_BADARGS;
923     com_err (whoami, code, "Older versions of Rx don't export packet tracing routines: can't run this HijackTest");
924     return code;
925
926 #else
927
928     extern int (*rx_justReceived)();
929     extern int (*rx_almostSent)();
930
931     long code;
932     struct rx_connection *conn = 0;
933     struct rx_connection *otherConn = 0;
934 #ifdef AFS_PTHREAD_ENV
935     pthread_t pid;
936 #else
937     PROCESS pid;
938 #endif
939     int nResp;                          /* otherConn responses seen */
940     long tmp_rc;
941
942 #ifdef AFS_PTHREAD_ENV
943     pthread_once(&slowCallOnce, SlowCallInit);
944 #endif
945     rx_justReceived = HandleIncoming;
946     rx_almostSent = HandleOutgoing;
947
948     incomingOps.op = IO_NOOP;
949     outgoingOps.op = OO_NOOP;
950
951 #define HIJACK_CONN(conn) \
952     {   if (conn) rx_DestroyConnection (conn); \
953         (conn) = rx_NewConnection(host, htons(RXKST_SERVICEPORT), \
954                                 RXKST_SERVICEID, sc, si); \
955         if (!(conn)) return RXKST_NEWCONNFAILED; \
956         outgoingOps.client = 1; \
957         outgoingOps.epoch = (conn)->epoch; \
958         outgoingOps.cid = (conn)->cid; }
959
960     HIJACK_CONN(conn);
961
962     /* First try switching from no packet cksum to sending packet cksum between
963      * calls, and see if server complains. */
964
965     outgoingOps.op = OO_ZEROCKSUM;
966     code = FastCall (conn);
967     if (code) {
968         com_err (whoami, code, "doing FastCall with ZEROCKSUM");
969         return code;
970     }    
971     /* The server thinks we're an old style client.  Now start sending cksums.
972      * Server shouldn't care. */
973     outgoingOps.op = OO_NOOP;
974     code = FastCall (conn);
975     if (code) {
976         com_err (whoami, code, "doing FastCall with non-ZEROCKSUM");
977         return code;
978     }    
979     /* The server now thinks we're a new style client, we can't go back now. */
980     outgoingOps.op = OO_ZEROCKSUM;
981     code = FastCall (conn);
982     if (code == 0) code = RXKST_NOBADCKSUM;
983     if (code != RXKADSEALEDINCON) {
984         com_err (whoami, code, "doing FastCall with ZEROCKSUM");
985         return code;
986     } else if (!conn->error) {
987         code = RXKST_NOCONNERROR;
988         com_err (whoami, code, "doing FastCall with ZEROCKSUM");
989         return code;    
990     } else code = 0;
991
992     HIJACK_CONN(conn);
993
994     /* Now try modifying packet cksum to see if server complains. */
995
996     outgoingOps.op = OO_MUNGCKSUM;
997     code = FastCall (conn);
998     if (code == 0) code = RXKST_NOBADCKSUM;
999     if (code != RXKADSEALEDINCON) {
1000         com_err (whoami, code, "doing FastCall with ZEROCKSUM");
1001         return code;
1002     } else if (!conn->error) {
1003         code = RXKST_NOCONNERROR;
1004         com_err (whoami, code, "doing FastCall with ZEROCKSUM");
1005         return code;    
1006     } else code = 0;
1007
1008     /* Now make two connection and direct the first challenge on one connection
1009      * to the other connection to see if it generates a response.  The
1010      * retransmitted challenge should allow the call on the first connection to
1011      * complete correctly.  Part one is to attack a new connection, then attack
1012      * it after it has made a call.  Part three, just for comparison, attacks a
1013      * otherConn while it is making a slow call (and thus has an active call).
1014      * Against this attack we have no defense so we expect a challenge in this
1015      * case, which the server will discard. */
1016
1017 #define RedirectChallenge(conn,otherConn)       \
1018     (incomingOps.epoch = (conn)->epoch,         \
1019      incomingOps.cid = (conn)->cid,             \
1020      incomingOps.client = 1,                    \
1021      incomingOps.newEpoch = (otherConn)->epoch, \
1022      incomingOps.newCid = (otherConn)->cid,     \
1023      incomingOps.op = IO_REDIRECTCHALLENGE,     \
1024      outgoingOps.epoch = (otherConn)->epoch,    \
1025      outgoingOps.cid = (otherConn)->cid,        \
1026      outgoingOps.client = 1,                    \
1027      outgoingOps.op = OO_COUNT,                 \
1028      outgoingOps.counts[RX_PACKET_TYPE_RESPONSE] = 0)
1029
1030     HIJACK_CONN(conn);
1031     HIJACK_CONN(otherConn)
1032     RedirectChallenge (conn, otherConn);
1033
1034     code = FastCall (conn);
1035     if (code) return code;
1036     assert (incomingOps.op == IO_COUNT); /* redirect code was triggered */
1037     if (outgoingOps.counts[RX_PACKET_TYPE_RESPONSE] > 0) {
1038   oracle:
1039         code = RXKST_CHALLENGEORACLE;
1040         com_err (whoami, code, "misdirecting challenge");
1041         return code;
1042     }
1043     code = FastCall (otherConn);        /* generate some activity here */
1044     if (code) return code;
1045     nResp = outgoingOps.counts[RX_PACKET_TYPE_RESPONSE];
1046     assert (nResp >= 1);
1047     code = FastCall (conn);
1048     if (code) return code;
1049     if (outgoingOps.counts[RX_PACKET_TYPE_RESPONSE] > nResp) goto oracle;
1050
1051     HIJACK_CONN(conn);
1052     RedirectChallenge (conn, otherConn);
1053     /* otherConn was authenticated during part one */
1054     code = FastCall (conn);
1055     if (code) return code;
1056     assert (incomingOps.op == IO_COUNT); /* redirect code was triggered */
1057     if (outgoingOps.counts[RX_PACKET_TYPE_RESPONSE] != 0) goto oracle;
1058
1059     HIJACK_CONN(conn);
1060     RedirectChallenge (conn, otherConn);
1061     /* otherConn is still authenticated */
1062     slowCallCode = RXKST_PROCESSCREATED;
1063 #ifdef AFS_PTHREAD_ENV
1064     {
1065         pthread_attr_t tattr;
1066
1067         code = pthread_attr_init(&tattr);
1068         if (code) {
1069             com_err (whoami, code, "can't pthread_attr_init slow call process");
1070             return code;
1071         }
1072
1073         code = pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
1074         if (code) {
1075             com_err (whoami, code, "can't pthread_attr_setdetachstate slow call process");
1076             return code;
1077         }
1078
1079         code = pthread_create(&pid, &tattr, SlowCall, (void*) otherConn);
1080     }
1081 #else
1082     code = LWP_CreateProcess(SlowCall, 16000, LWP_NORMAL_PRIORITY,
1083                              (opaque) otherConn, "Slow Call Process", &pid);
1084 #endif
1085     if (code) {
1086         com_err (whoami, code, "can't create slow call process");
1087         return code;
1088     }
1089 #ifdef AFS_PTHREAD_ENV
1090     pthread_mutex_lock(&slowCallLock);
1091     while (slowCallCode == RXKST_PROCESSCREATED)
1092         pthread_cond_wait(&slowCallCV, &slowCallLock);
1093 #else
1094     while (slowCallCode == RXKST_PROCESSCREATED)
1095         LWP_WaitProcess (&slowCallCode);        /* wait for process start */
1096 #endif
1097     if (slowCallCode != RXKST_PROCESSRUNNING) {
1098         tmp_rc = slowCallCode;
1099 #ifdef AFS_PTHREAD_ENV
1100         pthread_mutex_unlock(&slowCallLock);
1101 #endif  
1102         return tmp_rc;          /* make sure didn't fail immediately */
1103     }
1104     assert (incomingOps.op == IO_REDIRECTCHALLENGE);
1105     code = FastCall (conn);
1106     if (code) return code;
1107     assert (incomingOps.op == IO_COUNT); /* redirect code was triggered */
1108 #ifdef AFS_PTHREAD_ENV
1109     while (slowCallCode == RXKST_PROCESSRUNNING)
1110         pthread_cond_wait(&slowCallCV, &slowCallLock);
1111     pthread_mutex_unlock(&slowCallLock);
1112 #else
1113     while (slowCallCode == RXKST_PROCESSRUNNING)
1114         LWP_WaitProcess (&slowCallCode);        /* wait for process finish */
1115 #endif
1116     if (outgoingOps.counts[RX_PACKET_TYPE_RESPONSE] != 1) goto oracle;
1117
1118     rx_justReceived = 0;
1119     rx_almostSent = 0;
1120     rx_DestroyConnection (otherConn);
1121     rx_DestroyConnection (conn);
1122     return code;
1123
1124 #endif rx_GetPacketCksum
1125
1126 }
1127
1128 long rxkst_StartClient (parms)
1129   IN struct clientParms *parms;
1130 {
1131     long code;
1132     long host;
1133     long scIndex;
1134     struct rx_securityClass *sc;
1135
1136     whoami = parms->whoami;             /* set this global variable */
1137
1138     host = GetServer (parms->server);
1139
1140     if (parms->authentication >= 0) {
1141         long kvno;
1142         char ticket[MAXKTCTICKETLEN];
1143         int ticketLen;
1144         struct ktc_encryptionKey Ksession;
1145
1146         code = GetTicket (&kvno, &Ksession, &ticketLen, ticket);
1147         if (code) return code;
1148
1149         /* next, we have ticket, kvno and session key, authenticate the conn */
1150         sc = (struct rx_securityClass *)
1151             rxkad_NewClientSecurityObject (parms->authentication,
1152                                            &Ksession, kvno, ticketLen, ticket);
1153         assert (sc);
1154         scIndex = 2;                    /* kerberos security index */
1155     } else {
1156         /* unauthenticated connection */
1157         sc = (struct rx_securityClass *) rxnull_NewClientSecurityObject ();
1158         assert (sc);
1159         scIndex = 0;                    /* null security index */
1160     }
1161
1162     code = 0;
1163     if (!code && parms->callTest) {
1164         code = RunCallTest (parms, host, sc, scIndex);
1165     }
1166     if (!code && parms->hijackTest) {
1167         code = RunHijackTest (parms, host, sc, scIndex);
1168     }
1169     if (!code &&
1170         (parms->printTiming ||
1171          parms->fastCalls || parms->slowCalls || parms->copiousCalls)) {
1172         struct rx_connection *conn;
1173         conn = rx_NewConnection(host, htons(RXKST_SERVICEPORT),
1174                                 RXKST_SERVICEID, sc, scIndex);
1175         if (conn) {
1176             code = RepeatLoadTest (parms, conn);
1177             rx_DestroyConnection (conn);
1178         } else code = RXKST_NEWCONNFAILED;
1179     }
1180     if (!code && parms->stopServer) {
1181         struct rx_connection *conn;
1182         conn = rx_NewConnection(host, htons(RXKST_SERVICEPORT),
1183                                 RXKST_SERVICEID, sc, scIndex);
1184         if (conn) {
1185             code = RXKST_Kill (conn);
1186             if (code) {
1187                 com_err (whoami, code, "trying to stop server");
1188             }
1189             rx_DestroyConnection (conn);
1190         } else code = RXKST_NEWCONNFAILED;
1191     }
1192
1193     if (parms->printStats) {
1194         rx_PrintStats (stdout);
1195 #if 0
1196         /* use rxdebug style iteration here */
1197         rx_PrintPeerStats (stdout, rx_PeerOf(conn));
1198 #endif
1199     }
1200
1201     rxs_Release (sc);
1202     rx_Finalize();
1203     if (code) {
1204         com_err (parms->whoami, code, "test fails");
1205         exit (13);
1206     } else {
1207         printf ("Test Okay\n");
1208         if (!parms->noExit) exit (0);
1209     }
1210     return 0;
1211 }