On systems with struct rt_msghdr, open a routing socket and wait for
authorKen Raeburn <raeburn@mit.edu>
Fri, 18 Jul 2008 06:45:34 +0000 (06:45 +0000)
committerKen Raeburn <raeburn@mit.edu>
Fri, 18 Jul 2008 06:45:34 +0000 (06:45 +0000)
messages; when they come in, if the types suggest a possibility of
network interface reconfiguration, shut down the KDC's networking and
bring it back up again, rescanning the interfaces in the process.

Leaving the ticket open because it should be improved:
 * It should only close down sockets on addresses we no longer have, and
   bring up sockets only on new addresses.
 * If we have IPV6_PKTINFO support, it should only listen for IPv4
   routing changes.
 * If we also have IP_PKTINFO support, it shouldn't be used at all.
 * If we build a KDC on a system with neither struct rt_msghdr nor
   IP_PKTINFO (do we have any such?), we'll need another solution.

Thanks to Nico Williams for the routing socket suggestion, and Apple
for the initial (signal-driven) reconfiguration code.

ticket: 6019
status: open

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@20540 dc483132-0cff-0310-8789-dd5450dbe970

src/configure.in
src/kdc/network.c

index 362df045971afdcbb2a1df837c29039495891bd6..402e2516bd2bc1389c2bcf15115e5e3a1f4ceaac 100644 (file)
@@ -611,6 +611,11 @@ AC_CHECK_TYPES([struct cmsghdr, struct in_pktinfo, struct in6_pktinfo], , , [
 #include <sys/socket.h>
 #include <netinet/in.h>
 ])
+AC_CHECK_TYPES([struct rt_msghdr], , , [
+#include <sys/socket.h>
+#include <net/if.h>
+#include <net/route.h>
+])
 dnl
 dnl stuff for util/profile
 dnl
index 4b01ba4a9a66f65d8cec9d555d1557abe3194724..f471a3a70985d135f547050a45b264186bdb906c 100644 (file)
@@ -175,7 +175,7 @@ static const char *paddr (struct sockaddr *sa)
 
 /* KDC data.  */
 
-enum kdc_conn_type { CONN_UDP, CONN_UDP_PKTINFO, CONN_TCP_LISTENER, CONN_TCP };
+enum kdc_conn_type { CONN_UDP, CONN_UDP_PKTINFO, CONN_TCP_LISTENER, CONN_TCP, CONN_ROUTING };
 
 /* Per-connection info.  */
 struct connection {
@@ -770,6 +770,127 @@ scan_for_newlines:
 }
 #endif
 
+static int network_reconfiguration_needed = 0;
+
+#ifdef HAVE_STRUCT_RT_MSGHDR
+#include <net/route.h>
+
+static char *rtm_type_name(int type)
+{
+    switch (type) {
+    case RTM_ADD: return "RTM_ADD";
+    case RTM_DELETE: return "RTM_DELETE";
+    case RTM_NEWADDR: return "RTM_NEWADDR";
+    case RTM_DELADDR: return "RTM_DELADDR";
+    case RTM_IFINFO: return "RTM_IFINFO";
+    case RTM_OLDADD: return "RTM_OLDADD";
+    case RTM_OLDDEL: return "RTM_OLDDEL";
+    case RTM_RESOLVE: return "RTM_RESOLVE";
+#ifdef RTM_NEWMADDR
+    case RTM_NEWMADDR: return "RTM_NEWMADDR";
+    case RTM_DELMADDR: return "RTM_DELMADDR";
+#endif
+    case RTM_MISS: return "RTM_MISS";
+    case RTM_REDIRECT: return "RTM_REDIRECT";
+    case RTM_LOSING: return "RTM_LOSING";
+    case RTM_GET: return "RTM_GET";
+    default: return "?";
+    }
+}
+
+static void process_routing_update(struct connection *conn, const char *prog,
+                                  int selflags)
+{
+    static struct {
+       struct rt_msghdr rtm;
+       char scratchbuf[1024];
+    } s;
+    int n_read;
+
+    krb5_klog_syslog(LOG_INFO, "routing socket readable");
+    while ((n_read = read(conn->fd, &s, sizeof(s))) > 0) {
+       if (n_read < sizeof(s.rtm)) {
+           krb5_klog_syslog(LOG_ERR, "short read (%d/%d) from routing socket",
+                            n_read, (int) sizeof(s.rtm));
+           /* Quick hack to figure out if the interesting
+              fields are present in a short read.  */
+#define RS(FIELD) (offsetof(struct rt_msghdr, FIELD) + sizeof(s.rtm.FIELD))
+           if (n_read < RS(rtm_type) ||
+               n_read < RS(rtm_version) ||
+               n_read < RS(rtm_msglen))
+               return;
+       }
+       krb5_klog_syslog(LOG_INFO,
+                        "got routing msg type %d(%s) v%d",
+                        s.rtm.rtm_type, rtm_type_name(s.rtm.rtm_type),
+                        s.rtm.rtm_version);
+       if (s.rtm.rtm_msglen > sizeof(s)) {
+           krb5_klog_syslog(LOG_ERR,
+                            "routing message bigger than buffer (%d/%d)",
+                            s.rtm.rtm_msglen, (int) sizeof(s));
+           /* This will probably start spewing messages.  Slow it
+              down.  */
+           sleep(1);
+           return;
+       }
+       if (s.rtm.rtm_msglen != n_read) {
+           krb5_klog_syslog(LOG_ERR,
+                            "read %d from routing socket but msglen is %d",
+                            n_read, s.rtm.rtm_msglen);
+           if (s.rtm.rtm_msglen < sizeof(s.rtm)) {
+               krb5_klog_syslog(LOG_ERR,
+                                "routing msglen shorter than header??");
+               return;
+       }
+       }
+       switch (s.rtm.rtm_type) {
+       case RTM_ADD:
+       case RTM_DELETE:
+       case RTM_NEWADDR:
+       case RTM_DELADDR:
+       case RTM_IFINFO:
+       case RTM_OLDADD:
+       case RTM_OLDDEL:
+           krb5_klog_syslog(LOG_INFO, "reconfiguration needed");
+           network_reconfiguration_needed = 1;
+           break;
+       case RTM_RESOLVE:
+#ifdef RTM_NEWMADDR
+       case RTM_NEWMADDR:
+       case RTM_DELMADDR:
+#endif
+       case RTM_MISS:
+       case RTM_REDIRECT:
+       case RTM_LOSING:
+       case RTM_GET:
+           /* Not interesting.  */
+           krb5_klog_syslog(LOG_DEBUG, "routing msg not interesting");
+           break;
+       default:
+           krb5_klog_syslog(LOG_INFO, "unhandled routing message type, will reconfigure just for the fun of it");
+           network_reconfiguration_needed = 1;
+           break;
+       }
+    }
+}
+
+static void
+setup_routing_socket(struct socksetup *data)
+{
+    int sock = socket(PF_ROUTE, SOCK_RAW, 0);
+    if (sock < 0) {
+       int e = errno;
+       krb5_klog_syslog(LOG_INFO, "couldn't set up routing socket: %s",
+                        strerror(e));
+    } else {
+       krb5_klog_syslog(LOG_INFO, "routing socket is fd %d", sock);
+       add_fd(data, sock, CONN_ROUTING, process_routing_update);
+       setnbio(sock);
+       FD_SET(sock, &sstate.rfds);
+    }
+}
+#endif
+
 /* XXX */
 extern int krb5int_debug_sendto_kdc;
 extern void (*krb5int_sendtokdc_debug_handler)(const void*, size_t);
@@ -824,6 +945,9 @@ setup_network(const char *prog)
     setup_data.prog = prog;
     setup_data.retval = 0;
     krb5_klog_syslog (LOG_INFO, "setting up network...");
+#ifdef HAVE_STRUCT_RT_MSGHDR
+    setup_routing_socket(&setup_data);
+#endif
     /* To do: Use RFC 2292 interface (or follow-on) and IPV6_PKTINFO,
        so we might need only one UDP socket; fall back to binding
        sockets on each address only if IPV6_PKTINFO isn't
@@ -1395,6 +1519,20 @@ static void service_conn(struct connection *conn, const char *prog,
     conn->service(conn, prog, selflags);
 }
 
+/* from sendto_kdc.c */
+static int getcurtime(struct timeval *tvp)
+{
+#ifdef _WIN32
+    struct _timeb tb;
+    _ftime(&tb);
+    tvp->tv_sec = tb.time;
+    tvp->tv_usec = tb.millitm * 1000;
+    return 0;
+#else
+    return gettimeofday(tvp, 0) ? errno : 0;
+#endif
+}
+
 krb5_error_code
 listen_and_process(const char *prog)
 {
@@ -1403,7 +1541,7 @@ listen_and_process(const char *prog)
        can be rather large.  Making this static avoids putting all
        that junk on the stack.  */
     static struct select_state sout;
-    int                        i, sret;
+    int                        i, sret, netchanged = 0;
     krb5_error_code    err;
 
     if (conns == (struct connection **) NULL)
@@ -1414,12 +1552,37 @@ listen_and_process(const char *prog)
            krb5_klog_reopen(kdc_context);
            signal_requests_hup = 0;
        }
-       sstate.end_time.tv_sec = sstate.end_time.tv_usec = 0;
+
+       if (network_reconfiguration_needed) {
+           krb5_klog_syslog(LOG_INFO, "network reconfiguration needed");
+           /* It might be tidier to add a timer-callback interface to
+              the control loop here, but for this one use, it's not a
+              big deal.  */
+           err = getcurtime(&sstate.end_time);
+           if (err) {
+               com_err(prog, err, "while getting the time");
+               continue;
+           }
+           sstate.end_time.tv_sec += 3;
+           netchanged = 1;
+       } else
+           sstate.end_time.tv_sec = sstate.end_time.tv_usec = 0;
+
        err = krb5int_cm_call_select(&sstate, &sout, &sret);
        if (err) {
            com_err(prog, err, "while selecting for network input(1)");
            continue;
        }
+       if (sret == 0 && netchanged) {
+           network_reconfiguration_needed = 0;
+           closedown_network(prog);
+           err = setup_network(prog);
+           if (err) {
+               com_err(prog, err, "while reinitializing network");
+               return err;
+           }
+           netchanged = 0;
+       }
        if (sret == -1) {
            if (errno != EINTR)
                com_err(prog, errno, "while selecting for network input(2)");
@@ -1451,8 +1614,10 @@ closedown_network(const char *prog)
        return KDC5_NONET;
 
     FOREACH_ELT (connections, i, conn) {
-       if (conn->fd >= 0)
+       if (conn->fd >= 0) {
+           krb5_klog_syslog(LOG_INFO, "closing down fd %d", conn->fd);
            (void) close(conn->fd);
+       }
        DEL (connections, i);
        /* There may also be per-connection data in the tcp structure
           (tcp.buffer, tcp.response) that we're not freeing here.