KDC worker processes feature
authorGreg Hudson <ghudson@mit.edu>
Fri, 17 Sep 2010 17:42:31 +0000 (17:42 +0000)
committerGreg Hudson <ghudson@mit.edu>
Fri, 17 Sep 2010 17:42:31 +0000 (17:42 +0000)
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

src/include/net-server.h
src/kadmin/server/ovsec_kadmd.c
src/kdc/Makefile.in
src/kdc/krb5kdc.M
src/kdc/main.c
src/kdc/t_workers.py [new file with mode: 0644]
src/lib/apputils/net-server.c

index e4fa1dbac0893ca5046c409a7382c0515c23399c..105b006d86eff3c296e7f652c875e1eb5f8a6c8e 100644 (file)
@@ -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);
index 6b55273de731551f97df27723530c5f558d86014..8e87616ebdfc5f06e7779e47a2f4da8ae13b1aaf 100644 (file)
@@ -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);
index 49e4a35a481c733fc8f8c860cc10baf8bfb7c638..44f0d21f0f62b6d2d3ec43d590bdbd74f57f2945 100644 (file)
@@ -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
index 455b02e7260011295382aa141d8cbf26db280a27..cd31dce1ae63271b34ec267ab2957919ddb8a70a 100644 (file)
@@ -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
index 21c67f8b24ce172308fa83c7c782ec6af5d63446..6aac1d8fc3a93fab5bcdeb91d743695debbd3e9d 100644 (file)
@@ -61,6 +61,7 @@
 #include <netdb.h>
 #include <unistd.h>
 #include <ctype.h>
+#include <sys/wait.h>
 
 #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 (file)
index 0000000..f36b5a7
--- /dev/null
@@ -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.')
index 906619d2930eeb56152aa4c1a980bf82742e46ee..9d3daea40e1181fa45cc8c57ff8ee9040060b2db 100644 (file)
@@ -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;