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