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