1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* tests/threads/gss-perf.c */
3 /*
4 * Copyright (C) 2009 by the Massachusetts Institute of Technology.
5 * All rights reserved.
6 *
7 * Export of this software from the United States of America may
8 * require a specific license from the United States Government.
9 * It is the responsibility of any person or organization contemplating
10 * export to obtain such a license before exporting.
11 *
12 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13 * distribute this software and its documentation for any purpose and
14 * without fee is hereby granted, provided that the above copyright
15 * notice appear in all copies and that both that copyright notice and
16 * this permission notice appear in supporting documentation, and that
17 * the name of M.I.T. not be used in advertising or publicity pertaining
18 * to distribution of the software without specific, written prior
19 * permission. Furthermore if you modify this software you must label
20 * your software as modified software and not distribute it in such a
21 * fashion that it might be confused with the original M.I.T. software.
22 * M.I.T. makes no representations about the suitability of
23 * this software for any purpose. It is provided "as is" without express
24 * or implied warranty.
25 */
26
27 /*
28 * GSSAPI performance testing
29 * initially contributed by Ken Raeburn
30 */
31 /*
32 * Possible to-do items:
33 * - init-mutual testing (process msg back from accept)
34 * - wrap/unwrap testing (one init/accept per thread, loop on wrap/unwrap)
35 * - wrap/unwrap MT testing (one init/accept for process) ?
36 * - init+accept with replay cache
37 * - default to target "host@localhostname"
38 * - input ccache option?
39 *
40 * Also, perhaps try to simulate certain application patterns, like
41 * init/accept, exchange N messages with wrap/unwrap, destroy context,
42 * all in a loop in M parallel threads.
43 */
44
45 #include <stdio.h>
46 #include <string.h>
47 #include <stdlib.h>
48 #include <limits.h>
49 #include <assert.h>
50 #include <unistd.h>
51 #include <pthread.h>
52 #include <krb5.h>
53 #include <gssapi/gssapi.h>
54
55 #include <sys/time.h>
56 #include <sys/resource.h>
57
58 #define N_THREADS 2
59 #define ITER_COUNT 10000
60 static int init_krb5_first = 0;
61
62 struct resource_info {
63 struct timeval start_time, end_time;
64 };
65 struct thread_info {
66 pthread_t tid;
67 struct resource_info r;
68 };
69
70 static gss_name_t target;
71 static char *prog, *target_name;
72 static unsigned int n_threads = N_THREADS;
73 static int iter_count = ITER_COUNT;
74 static int do_pause, do_mutual;
75 static int test_init, test_accept;
76
77 static void usage (void) __attribute__((noreturn));
78 static void set_target (char *);
79
80 static void
usage()81 usage ()
82 {
83 fprintf (stderr, "usage: %s [ options ] service-name\n", prog);
84 fprintf (stderr, " service-name\tGSSAPI host-based service name (e.g., 'host@FQDN')\n");
85 fprintf (stderr, "options:\n");
86 fprintf (stderr, "\t-I\ttest gss_init_sec_context\n");
87 fprintf (stderr, "\t-A\ttest gss_accept_sec_context\n");
88 fprintf (stderr, "\t-k K\tspecify keytab (remember FILE: or other prefix!)\n");
89 fprintf (stderr, "\t-t N\tspecify number of threads (default %d)\n",
90 N_THREADS);
91 fprintf (stderr, "\t-i N\tset iteration count (default %d)\n",
92 ITER_COUNT);
93 fprintf (stderr, "\t-m\tenable mutual authentication flag (but don't do the additional calls)\n");
94 fprintf (stderr, "\t-K\tinitialize a krb5_context for the duration\n");
95 fprintf (stderr, "\t-P\tpause briefly after starting, to allow attaching dtrace/strace/etc\n");
96 exit (1);
97 }
98
99 static int
numarg(char * arg)100 numarg (char *arg)
101 {
102 char *end;
103 long val;
104
105 val = strtol (arg, &end, 10);
106 if (*arg == 0 || *end != 0) {
107 fprintf (stderr, "invalid numeric argument '%s'\n", arg);
108 usage ();
109 }
110 if (val >= 1 && val <= INT_MAX)
111 return val;
112 fprintf (stderr, "out of range numeric value %ld (1..%d)\n",
113 val, INT_MAX);
114 usage ();
115 }
116
117 static char optstring[] = "k:t:i:KPmIA";
118
119 static void
process_options(int argc,char * argv[])120 process_options (int argc, char *argv[])
121 {
122 int c;
123
124 prog = strrchr (argv[0], '/');
125 if (prog)
126 prog++;
127 else
128 prog = argv[0];
129 while ((c = getopt (argc, argv, optstring)) != -1) {
130 switch (c) {
131 case '?':
132 case ':':
133 usage ();
134 break;
135
136 case 'k':
137 setenv ("KRB5_KTNAME", optarg, 1);
138 break;
139
140 case 't':
141 n_threads = numarg (optarg);
142 if (n_threads >= SIZE_MAX / sizeof (struct thread_info)) {
143 n_threads = SIZE_MAX / sizeof (struct thread_info);
144 fprintf (stderr, "limiting n_threads to %u\n", n_threads);
145 }
146 break;
147
148 case 'i':
149 iter_count = numarg (optarg);
150 break;
151
152 case 'K':
153 init_krb5_first = 1;
154 break;
155
156 case 'P':
157 do_pause = 1;
158 break;
159
160 case 'I':
161 test_init = 1;
162 break;
163 case 'A':
164 test_accept = 1;
165 break;
166 }
167 }
168 if (argc == optind + 1)
169 set_target (argv[optind]);
170 else
171 usage ();
172
173 if (test_init && test_accept) {
174 fprintf (stderr, "-I and -A are mutually exclusive\n");
175 usage ();
176 }
177 if (test_init == 0 && test_accept == 0)
178 test_init = 1;
179 }
180
181 static void
display_a_status(const char * s_type,OM_uint32 type,OM_uint32 val)182 display_a_status (const char *s_type, OM_uint32 type, OM_uint32 val)
183 {
184 OM_uint32 mctx = 0;
185 OM_uint32 maj_stat, min_stat;
186 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
187
188 do {
189 maj_stat = gss_display_status (&min_stat,
190 val,
191 type,
192 GSS_C_NO_OID,
193 &mctx,
194 &msg);
195 if (maj_stat != GSS_S_COMPLETE) {
196 fprintf (stderr,
197 "error getting display form of %s status code %#lx\n",
198 s_type, (unsigned long) val);
199 exit (1);
200 }
201 fprintf (stderr, " %s: %.*s\n", s_type,
202 (int) msg.length, (char *) msg.value);
203 gss_release_buffer (&min_stat, &msg);
204 } while (mctx != 0);
205 }
206
207 static void
gss_error(const char * where,OM_uint32 maj_stat,OM_uint32 min_stat)208 gss_error(const char *where, OM_uint32 maj_stat, OM_uint32 min_stat)
209 {
210 fprintf (stderr, "%s: %s:\n", prog, where);
211 display_a_status ("major", GSS_C_GSS_CODE, maj_stat);
212 display_a_status ("minor", GSS_C_MECH_CODE, min_stat);
213 exit (1);
214 }
215
216 static void
do_accept(gss_buffer_desc * msg,int iter)217 do_accept (gss_buffer_desc *msg, int iter)
218 {
219 OM_uint32 maj_stat, min_stat;
220 gss_name_t client = GSS_C_NO_NAME;
221 gss_buffer_desc reply = GSS_C_EMPTY_BUFFER;
222 gss_OID oid = GSS_C_NO_OID;
223 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
224 OM_uint32 flags = do_mutual ? GSS_C_MUTUAL_FLAG : 0;
225
226 reply.value = NULL;
227 reply.length = 0;
228 maj_stat = gss_accept_sec_context (&min_stat,
229 &ctx,
230 GSS_C_NO_CREDENTIAL,
231 msg,
232 GSS_C_NO_CHANNEL_BINDINGS,
233 &client,
234 &oid,
235 &reply,
236 &flags,
237 NULL, /* time_rec */
238 NULL); /* del_cred_handle */
239 if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) {
240 fprintf (stderr, "pid %lu thread %#lx failing in iteration %d\n",
241 (unsigned long) getpid (), (unsigned long) pthread_self (),
242 iter);
243 gss_error ("accepting context", maj_stat, min_stat);
244 }
245 gss_release_buffer (&min_stat, &reply);
246 if (ctx != GSS_C_NO_CONTEXT)
247 gss_delete_sec_context (&min_stat, &ctx, GSS_C_NO_BUFFER);
248 gss_release_name (&min_stat, &client);
249 }
250
251 static gss_buffer_desc
do_init()252 do_init ()
253 {
254 OM_uint32 maj_stat, min_stat;
255 gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
256 OM_uint32 flags = 0, ret_flags = 0;
257 gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
258
259 if (do_mutual)
260 flags |= GSS_C_MUTUAL_FLAG;
261
262 msg.value = NULL;
263 msg.length = 0;
264 maj_stat = gss_init_sec_context (&min_stat,
265 GSS_C_NO_CREDENTIAL,
266 &ctx,
267 target,
268 GSS_C_NO_OID,
269 flags,
270 0,
271 NULL, /* no channel bindings */
272 NULL, /* no previous token */
273 NULL, /* ignore mech type */
274 &msg,
275 &ret_flags,
276 NULL); /* time_rec */
277 if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) {
278 gss_error ("initiating", maj_stat, min_stat);
279 }
280 if (ctx != GSS_C_NO_CONTEXT)
281 gss_delete_sec_context (&min_stat, &ctx, GSS_C_NO_BUFFER);
282 return msg;
283 }
284
285 static void
set_target(char * name)286 set_target (char *name)
287 {
288 OM_uint32 maj_stat, min_stat;
289 gss_buffer_desc namebuf;
290
291 target_name = name;
292 namebuf.value = name;
293 namebuf.length = strlen (name);
294 maj_stat = gss_import_name (&min_stat,
295 &namebuf,
296 GSS_C_NT_HOSTBASED_SERVICE,
297 &target);
298 if (maj_stat != GSS_S_COMPLETE)
299 gss_error ("importing target name", maj_stat, min_stat);
300 }
301
302 static long double
tvsub(struct timeval t1,struct timeval t2)303 tvsub (struct timeval t1, struct timeval t2)
304 {
305 /* POSIX says .tv_usec is signed. */
306 return (t1.tv_sec - t2.tv_sec
307 + (long double) 1.0e-6 * (t1.tv_usec - t2.tv_usec));
308 }
309
310 static struct timeval
now(void)311 now (void)
312 {
313 struct timeval tv;
314 if (gettimeofday (&tv, NULL) < 0) {
315 perror ("gettimeofday");
316 exit (1);
317 }
318 return tv;
319 }
320
321 static gss_buffer_desc init_msg;
322
run_iterations(struct resource_info * r)323 static void run_iterations (struct resource_info *r)
324 {
325 int i;
326 OM_uint32 min_stat;
327
328 r->start_time = now ();
329 for (i = 0; i < iter_count; i++) {
330 if (test_init) {
331 gss_buffer_desc msg = do_init ();
332 gss_release_buffer (&min_stat, &msg);
333 } else if (test_accept) {
334 do_accept (&init_msg, i);
335 } else
336 assert (test_init || test_accept);
337 }
338 r->end_time = now ();
339 }
340
341 static void *
thread_proc(void * p)342 thread_proc (void *p)
343 {
344 run_iterations (p);
345 return 0;
346 }
347
348 static struct thread_info *tinfo;
349
350 static krb5_context kctx;
351 static struct rusage start, finish;
352 static struct timeval start_time, finish_time;
353
354 int
main(int argc,char * argv[])355 main (int argc, char *argv[])
356 {
357 long double user, sys, wallclock, total;
358 unsigned int i;
359
360 /* Probably should have a command-line option controlling this,
361 but if a replay cache is used, we can't do just one
362 init_sec_context and easily time just the accept_sec_context
363 side. */
364 setenv ("KRB5RCACHETYPE", "none", 1);
365
366 process_options (argc, argv);
367
368 /*
369 * Some places in the krb5 library cache data globally.
370 * This option allows you to test the effect of that.
371 */
372 if (init_krb5_first && krb5_init_context (&kctx) != 0) {
373 fprintf (stderr, "krb5_init_context error\n");
374 exit (1);
375 }
376 tinfo = calloc (n_threads, sizeof (*tinfo));
377 if (tinfo == NULL) {
378 perror ("calloc");
379 exit (1);
380 }
381 printf ("Test: %s threads: %d iterations: %d target: %s\n",
382 test_init ? "init" : "accept", n_threads, iter_count,
383 target_name ? target_name : "(NONE)");
384 if (do_pause) {
385 printf ("pid %lu napping...\n", (unsigned long) getpid ());
386 sleep (10);
387 }
388 /*
389 * Some tests use one message and process it over and over. Even
390 * if not, this sort of "primes" things by fetching any needed
391 * tickets just once.
392 */
393 init_msg = do_init ();
394 printf ("starting...\n");
395 /* And *now* we start measuring the performance. */
396 if (getrusage (RUSAGE_SELF, &start) < 0) {
397 perror ("getrusage");
398 exit (1);
399 }
400 start_time = now ();
401 #define foreach_thread(IDXVAR) for (IDXVAR = 0; IDXVAR < n_threads; IDXVAR++)
402 foreach_thread (i) {
403 int err;
404
405 err = pthread_create (&tinfo[i].tid, NULL, thread_proc, &tinfo[i].r);
406 if (err) {
407 fprintf (stderr, "pthread_create: %s\n", strerror (err));
408 exit (1);
409 }
410 }
411 foreach_thread (i) {
412 int err;
413 void *val;
414
415 err = pthread_join (tinfo[i].tid, &val);
416 if (err) {
417 fprintf (stderr, "pthread_join: %s\n", strerror (err));
418 exit (1);
419 }
420 }
421 finish_time = now ();
422 if (getrusage (RUSAGE_SELF, &finish) < 0) {
423 perror ("getrusage");
424 exit (1);
425 }
426 if (init_krb5_first)
427 krb5_free_context (kctx);
428 foreach_thread (i) {
429 printf ("Thread %2d: elapsed time %Lfs\n", i,
430 tvsub (tinfo[i].r.end_time, tinfo[i].r.start_time));
431 }
432 wallclock = tvsub (finish_time, start_time);
433 /*
434 * Report on elapsed time and CPU usage. Depending what
435 * performance issue you're chasing down, different values may be
436 * of particular interest, so report all the info we've got.
437 */
438 printf ("Overall run time with %d threads = %Lfs, %Lfms per iteration.\n",
439 n_threads, wallclock, 1000 * wallclock / iter_count);
440 user = tvsub (finish.ru_utime, start.ru_utime);
441 sys = tvsub (finish.ru_stime, start.ru_stime);
442 total = user + sys;
443 printf ("CPU usage: user=%Lfs sys=%Lfs total=%Lfs.\n", user, sys, total);
444 printf ("Utilization: user=%5.1Lf%% sys=%5.1Lf%% total=%5.1Lf%%\n",
445 100 * user / wallclock,
446 100 * sys / wallclock,
447 100 * total / wallclock);
448 printf ("Util/thread: user=%5.1Lf%% sys=%5.1Lf%% total=%5.1Lf%%\n",
449 100 * user / wallclock / n_threads,
450 100 * sys / wallclock / n_threads,
451 100 * total / wallclock / n_threads);
452 printf ("Total CPU use per iteration per thread: %Lfms\n",
453 1000 * total / n_threads / iter_count);
454 return 0;
455 }
456