From: Greg Hudson Date: Fri, 17 Sep 2010 17:42:31 +0000 (+0000) Subject: KDC worker processes feature X-Git-Tag: krb5-1.9-beta1~82 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=62c814a80d26879594c78f750cd7e138fe1d7f96;p=krb5.git KDC worker processes feature Add support for a krb5kdc -w option which causes the KDC to spawn worker processes which can process requests in parallel. See also: http://k5wiki.kerberos.org/wiki/Projects/Parallel_KDC ticket: 6783 git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24328 dc483132-0cff-0310-8789-dd5450dbe970 --- diff --git a/src/include/net-server.h b/src/include/net-server.h index e4fa1dbac..105b006d8 100644 --- a/src/include/net-server.h +++ b/src/include/net-server.h @@ -43,7 +43,7 @@ krb5_error_code add_udp_port(int port); krb5_error_code add_tcp_port(int port); krb5_error_code add_rpc_service(int port, u_long prognum, u_long versnum, void (*dispatch)()); -krb5_error_code setup_network(void *handle, const char *prog); +krb5_error_code setup_network(void *handle, const char *prog, int no_reconfig); krb5_error_code listen_and_process(void *handle, const char *prog, void (*reset)(void)); void closedown_network(void); diff --git a/src/kadmin/server/ovsec_kadmd.c b/src/kadmin/server/ovsec_kadmd.c index 6b55273de..8e87616eb 100644 --- a/src/kadmin/server/ovsec_kadmd.c +++ b/src/kadmin/server/ovsec_kadmd.c @@ -393,7 +393,7 @@ int main(int argc, char *argv[]) : 0) #endif #undef server_handle - || (ret = setup_network(global_server_handle, whoami))) { + || (ret = setup_network(global_server_handle, whoami, 0))) { const char *e_txt = krb5_get_error_message (context, ret); krb5_klog_syslog(LOG_ERR, "%s: %s while initializing network, aborting", whoami, e_txt); diff --git a/src/kdc/Makefile.in b/src/kdc/Makefile.in index 49e4a35a4..44f0d21f0 100644 --- a/src/kdc/Makefile.in +++ b/src/kdc/Makefile.in @@ -70,6 +70,9 @@ check-unix:: rtest cmp test.out $(srcdir)/rtest.good $(RM) test.out +check-pytests:: + $(RUNPYTEST) $(srcdir)/t_workers.py $(PYTESTFLAGS) + install:: $(INSTALL_PROGRAM) krb5kdc ${DESTDIR}$(SERVER_BINDIR)/krb5kdc $(INSTALL_DATA) $(srcdir)/krb5kdc.M ${DESTDIR}$(SERVER_MANDIR)/krb5kdc.8 diff --git a/src/kdc/krb5kdc.M b/src/kdc/krb5kdc.M index 455b02e72..cd31dce1a 100644 --- a/src/kdc/krb5kdc.M +++ b/src/kdc/krb5kdc.M @@ -49,6 +49,9 @@ krb5kdc \- Kerberos V5 KDC ] [ .B \-n ] [ +.B \-w +.I numworkers +] [ .B \-P .I pid_file ] @@ -138,6 +141,23 @@ operation, you should always allow the KDC to place itself in the background. .PP The +.B \-w +.I numworkers +option tells the KDC to fork +.I numworkers +processes to listen to the KDC ports and process requests in parallel. +The top level KDC process (whose pid is recorded in the pid file if +the +.B \-P +option is also given) acts as a supervisor. The supervisor will relay +SIGHUP signals to the worker subprocesses, and will terminate the +worker subprocess if the it is itself terminated or if any other +worker process exits. NOTE: on operating systems which do not have +pktinfo support, using worker processes will prevent the KDC from +listening for UDP packets on network interfaces created after the KDC +starts. +.PP +The .B \-P .I pid_file option tells the KDC to write its PID (followed by a newline) into diff --git a/src/kdc/main.c b/src/kdc/main.c index 21c67f8b2..6aac1d8fc 100644 --- a/src/kdc/main.c +++ b/src/kdc/main.c @@ -61,6 +61,7 @@ #include #include #include +#include #include "k5-int.h" #include "com_err.h" @@ -93,6 +94,7 @@ static void initialize_realms (krb5_context, int, char **); static void finish_realms (void); static int nofork = 0; +static int workers = 0; static const char *pid_file = NULL; static int rkey_init_done = 0; @@ -523,6 +525,99 @@ setup_signal_handlers(void) return; } +/* + * Kill the worker subprocesses given by pids[0..bound-1], skipping any which + * are set to -1, and wait for them to exit (so that we know the ports are no + * longer in use). num_active must be the number of active (i.e. not -1) pids + * in the array. + */ +static void +terminate_workers(pid_t *pids, int bound, int num_active) +{ + int i, status; + pid_t pid; + + /* Kill the active worker pids. */ + for (i = 0; i < bound; i++) { + if (pids[i] != -1) + kill(pids[i], SIGTERM); + } + + /* Wait for them to exit. */ + while (num_active > 0) { + pid = wait(&status); + if (pid >= 0) + num_active--; + } +} + +/* + * Create num worker processes and return successfully in each child. The + * parent process will act as a supervisor and will only return from this + * function in error cases. + */ +static krb5_error_code +create_workers(int num) +{ + int i, status, numleft; + pid_t pid, *pids; + + /* Create child worker processes; return in each child. */ + krb5_klog_syslog(LOG_INFO, "creating %d worker processes", num); + pids = malloc(num * sizeof(pid_t)); + if (pids == NULL) + return ENOMEM; + for (i = 0; i < num; i++) { + pid = fork(); + if (pid == 0) + return 0; + if (pid == -1) { + /* Couldn't fork enough times. */ + status = errno; + terminate_workers(pids, i, i); + free(pids); + return status; + } + pids[i] = pid; + } + + /* Supervise the child processes. */ + numleft = num; + while (!signal_requests_exit) { + /* Wait until a child process exits or we get a signal. */ + pid = wait(&status); + if (pid >= 0) { + krb5_klog_syslog(LOG_ERR, "worker %ld exited with status %d", + (long) pid, status); + + /* Remove the pid from the table. */ + for (i = 0; i < num; i++) { + if (pids[i] == pid) + pids[i] = -1; + } + + /* When one process exits, terminate them all, so that KDC crashes + * behave similarly with or without worker processes. */ + break; + } + + /* Propagate HUP signal to worker processes if we received one. */ + if (signal_requests_reset) { + for (i = 0; i < num; i++) { + if (pids[i] != -1) + kill(pids[i], SIGHUP); + } + signal_requests_reset = 0; + } + } + if (signal_requests_exit) + krb5_klog_syslog(LOG_INFO, "shutdown signal received in supervisor"); + + terminate_workers(pids, num, numleft); + free(pids); + exit(0); +} + static krb5_error_code setup_sam(void) { @@ -532,11 +627,17 @@ setup_sam(void) static void usage(char *name) { - fprintf(stderr, "usage: %s [-x db_args]* [-d dbpathname] [-r dbrealmname]\n\t\t[-R replaycachename] [-m] [-k masterenctype] [-M masterkeyname]\n\t\t[-p port] [-P pid_file] [/]\n" - "\nwhere,\n\t[-x db_args]* - Any number of database specific arguments. Look at\n" - "\t\t\teach database module documentation for supported\n\t\t\targuments\n", + fprintf(stderr, + "usage: %s [-x db_args]* [-d dbpathname] [-r dbrealmname]\n" + "\t\t[-R replaycachename] [-m] [-k masterenctype]\n" + "\t\t[-M masterkeyname] [-p port] [-P pid_file]\n" + "\t\t[-n] [-w numworkers] [/]\n\n" + "where,\n" + "\t[-x db_args]* - Any number of database specific arguments.\n" + "\t\t\tLook at each database module documentation for supported\n" + "\t\t\targuments\n", name); - return; + exit(1); } @@ -609,7 +710,7 @@ initialize_realms(krb5_context kcontext, int argc, char **argv) * Loop through the option list. Each time we encounter a realm name, * use the previously scanned options to fill in for defaults. */ - while ((c = getopt(argc, argv, "x:r:d:mM:k:R:e:P:p:s:n4:X3")) != -1) { + while ((c = getopt(argc, argv, "x:r:d:mM:k:R:e:P:p:s:nw:4:X3")) != -1) { switch(c) { case 'x': db_args_size++; @@ -691,6 +792,11 @@ initialize_realms(krb5_context kcontext, int argc, char **argv) case 'n': nofork++; /* don't detach from terminal */ break; + case 'w': /* create multiple worker processes */ + workers = atoi(optarg); + if (workers <= 0) + usage(argv[0]); + break; case 'k': /* enctype for master key */ if (krb5_string_to_enctype(optarg, &menctype)) com_err(argv[0], 0, "invalid enctype %s", optarg); @@ -722,7 +828,6 @@ initialize_realms(krb5_context kcontext, int argc, char **argv) case '?': default: usage(argv[0]); - exit(1); } } @@ -805,6 +910,7 @@ finish_realms() finish_realm(kdc_realmlist[i]); kdc_realmlist[i] = 0; } + kdc_numrealms = 0; } /* @@ -921,7 +1027,13 @@ int main(int argc, char **argv) } } - if ((retval = setup_network(NULL, kdc_progname))) { + /* + * Setup network listeners. Disallow network reconfig in response to + * routing socket messages if we're using worker processes, since the + * children won't be able to re-open the listener sockets. Hopefully our + * platform has pktinfo support and doesn't need reconfigs. + */ + if ((retval = setup_network(NULL, kdc_progname, (workers > 0)))) { net_init_error: kdc_err(kcontext, retval, "while initializing network"); finish_realms(); @@ -940,6 +1052,16 @@ int main(int argc, char **argv) return 1; } } + if (workers > 0) { + finish_realms(); + retval = create_workers(workers); + if (retval) { + kdc_err(kcontext, errno, "creating worker processes"); + return 1; + } + /* We get here only in a worker child process; re-initialize realms. */ + initialize_realms(kcontext, argc, argv); + } krb5_klog_syslog(LOG_INFO, "commencing operation"); if (nofork) fprintf(stderr, "%s: starting...\n", kdc_progname); diff --git a/src/kdc/t_workers.py b/src/kdc/t_workers.py new file mode 100644 index 000000000..f36b5a793 --- /dev/null +++ b/src/kdc/t_workers.py @@ -0,0 +1,8 @@ +#!/usr/bin/python +from k5test import * + +realm = K5Realm(start_kdc=False, start_kadmind=False, create_host=False) +realm.start_kdc(['-w', '3']) +realm.kinit(realm.user_princ, password('user')) +realm.klist(realm.user_princ) +success('KDC worker processes.') diff --git a/src/lib/apputils/net-server.c b/src/lib/apputils/net-server.c index 906619d29..9d3daea40 100644 --- a/src/lib/apputils/net-server.c +++ b/src/lib/apputils/net-server.c @@ -808,6 +808,7 @@ setup_udp_port_1(struct socksetup *data, struct sockaddr *addr, sock = create_server_socket(data, addr, SOCK_DGRAM); if (sock == -1) return 1; + setnbio(sock); #if !(defined(CMSG_SPACE) && defined(HAVE_STRUCT_CMSGHDR) && \ (defined(IP_PKTINFO) || defined(IPV6_PKTINFO))) @@ -1092,7 +1093,7 @@ extern int krb5int_debug_sendto_kdc; extern void (*krb5int_sendtokdc_debug_handler)(const void*, size_t); krb5_error_code -setup_network(void *handle, const char *prog) +setup_network(void *handle, const char *prog, int no_reconfig) { struct socksetup setup_data; @@ -1108,7 +1109,8 @@ setup_network(void *handle, const char *prog) setup_data.retval = 0; krb5_klog_syslog (LOG_INFO, "setting up network..."); #ifdef HAVE_STRUCT_RT_MSGHDR - setup_routing_socket(&setup_data); + if (!no_reconfig) + setup_routing_socket(&setup_data); #endif /* * To do: Use RFC 2292 interface (or follow-on) and IPV6_PKTINFO, @@ -1381,7 +1383,7 @@ process_packet(void *handle, struct connection *conn, const char *prog, (struct sockaddr *)&daddr, &daddr_len, &auxaddr); if (cc == -1) { - if (errno != EINTR + if (errno != EINTR && errno != EAGAIN /* * This is how Linux indicates that a previous transmission was * refused, e.g., if the client timed out before getting the @@ -1837,7 +1839,7 @@ listen_and_process(void *handle, const char *prog, void (*reset)(void)) if (sret == 0 && netchanged) { network_reconfiguration_needed = 0; closedown_network_sockets(); - err = setup_network(handle, prog); + err = setup_network(handle, prog, 0); if (err) { com_err(prog, err, "while reinitializing network"); return err;