Client-side TCP support update for Windows.
authorKen Raeburn <raeburn@mit.edu>
Tue, 18 Jun 2002 23:46:32 +0000 (23:46 +0000)
committerKen Raeburn <raeburn@mit.edu>
Tue, 18 Jun 2002 23:46:32 +0000 (23:46 +0000)
Some support for scatter/gather socket i/o added via macros in port-sockets.h.

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

src/include/ChangeLog
src/include/port-sockets.h
src/lib/krb5/os/ChangeLog
src/lib/krb5/os/sendto_kdc.c

index c69a621f3b2a16feb04bf653a39eacd7dbed44f1..fbb8704b5f7171bef13c18e1275934e19735ce3d 100644 (file)
@@ -1,3 +1,16 @@
+2002-06-18  Ken Raeburn  <raeburn@mit.edu>
+
+       * port-sockets.h (sg_buf): New typedef name for OS-specific
+       scatter-gather buffer handle type.
+       (SG_ADVANCE, SG_LEN, SG_SET): New macros to manipulate sg_buf.
+       (SOCKET_WRITEV, SOCKET_WRITEV_TEMP): New macros for sending on
+       socket with gathered input.
+       (SHUTDOWN_READ, SHUTDOWN_WRITE, SHUTDOWN_BOTH): New macros, to be
+       passed to shutdown() to indicate direction.
+       (EINPROGRESS, EWOULDBLOCK, ECONNRESET, ECONNABORTED,
+       ECONNREFUSED, EHOSTUNREACH, ETIMEDOUT) [_WIN32]: Define as WSA
+       equivalents if not already defined.
+
 2002-06-15  Ken Raeburn  <raeburn@mit.edu>
 
        * krb5.hin: Delete inclusion of profile.h again.
index 052b40270ccac90001bb723ad726c9cab063d73d..0681ddb0b5110e4b140dde0dd37b98156e169818 100644 (file)
@@ -8,6 +8,16 @@
 /* Some of our own infrastructure where the WinSock stuff was too hairy
    to dump into a clean Unix program...  */
 
+typedef WSABUF sg_buf;
+
+#define SG_ADVANCE(SG, N) \
+       ((SG)->len < (N)                                \
+        ? (abort(), 0)                                 \
+        : ((SG)->buf += (N), (SG)->len -= (N), 0))
+
+#define SG_LEN(SG)             ((SG)->len + 0)
+#define SG_SET(SG, B, N)       ((SG)->buf = (char *)(B),(SG)->len = (N))
+
 #define SOCKET_INITIALIZE()     0
 #define SOCKET_CLEANUP()
 #define SOCKET_ERRNO            (WSAGetLastError())
 #define SOCKET_WRITE(fd, b, l)  (send(fd, b, l, 0))
 #define SOCKET_EINTR            WSAEINTR
 
+/* Return -1 for error or number of bytes written.
+   TMP is a temporary variable; must be declared by the caller, and
+   must be used by this macro (to avoid compiler warnings).  */
+/* WSASend returns 0 or SOCKET_ERROR.  */
+#define SOCKET_WRITEV_TEMP DWORD
+#define SOCKET_WRITEV(FD, SG, LEN, TMP)        \
+       (WSASend((FD), (SG), (LEN), &(TMP), 0, 0, 0) ? -1 : (TMP))
+
+#define SHUTDOWN_READ  SD_RECEIVE
+#define SHUTDOWN_WRITE SD_SEND
+#define SHUTDOWN_BOTH  SD_BOTH
+
+#ifndef EINPROGRESS
+#define EINPROGRESS WSAEINPROGRESS
+#endif
+#ifndef EWOULDBLOCK
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#endif
+#ifndef ECONNRESET
+#define ECONNRESET  WSAECONNRESET
+#endif
+#ifndef ECONNABORTED
+#define ECONNABORTED WSAECONNABORTED
+#endif
+#ifndef ECONNREFUSED
+#define ECONNREFUSED WSAECONNREFUSED
+#endif
+#ifndef EHOSTUNREACH
+#define EHOSTUNREACH WSAEHOSTUNREACH
+#endif
+#ifndef ETIMEDOUT
+#define ETIMEDOUT WSAETIMEDOUT
+#endif
+
 int win_socket_initialize();
 
 #else /* not _WIN32 */
@@ -46,6 +90,17 @@ int win_socket_initialize();
 #define        ioctlsocket     ioctl
 #define        SOCKET_ERROR    (-1)
 
+typedef struct iovec sg_buf;
+
+#define SG_ADVANCE(SG, N) \
+       ((SG)->iov_len < (N)                                    \
+        ? (abort(), 0)                                         \
+        : ((SG)->iov_base = (char *) (SG)->iov_base + (N),     \
+           (SG)->iov_len -= (N), 0))
+
+#define SG_LEN(SG)             ((SG)->iov_len + 0)
+#define SG_SET(SG, B, L)       ((SG)->iov_base = (char*)(B), (SG)->iov_len = (L))
+
 /* Some of our own infrastructure where the WinSock stuff was too hairy
    to dump into a clean Unix program...  */
 
@@ -57,6 +112,15 @@ int win_socket_initialize();
 #define SOCKET_READ            read
 #define SOCKET_WRITE           write
 #define SOCKET_EINTR           EINTR
+#define SOCKET_WRITEV_TEMP int
+/* Use TMP to avoid compiler warnings and keep things consistent with
+   Windoze version.  */
+#define SOCKET_WRITEV(FD, SG, LEN, TMP) \
+       ((TMP) = writev((FD), (SG), (LEN)))
+
+#define SHUTDOWN_READ  0
+#define SHUTDOWN_WRITE 1
+#define SHUTDOWN_BOTH  2
 
 #endif /* HAVE_MACSOCK_H */
 
index e075175e1079b2b133dac473512ff09c6ed2748a..24a3f0b9880d66867ded6f1c186f8c304aefd00d 100644 (file)
@@ -1,3 +1,31 @@
+2002-06-18  Ken Raeburn  <raeburn@mit.edu>
+
+       * sendto_kdc.c: Include sys/timeb.h on Windows.
+       (ENABLE_TCP): Macro deleted, always enable code.
+       (krb5_sendto_kdc): Don't try UDP if outgoing message is 1500
+       bytes or larger.  Print info on arguments if DEBUG defined.
+       (bogus_strerror) [_WIN32 && DEBUG]: New function.
+       (dperror, dfprintf): New macros.
+       (krb5int_sendto_udp): Use SOCKET_ERRNO where appropriate.  Log
+       more info if DEBUG defined; use dperror and dfprintf.
+       (struct conn_state): Use new scatter-gather definitions from
+       port-sockets.h.  Don't try to reuse data structures for both
+       input and output.
+       (struct select_state): Add exception fd set.
+       (print_fdsets) [DEBUG]: Print exception fd set; callers changed.
+       (getcurtime): New function.
+       (call_select): Call it.  Pass exception fd set to select.
+       (make_nonblocking, iov_advance): Deleted.
+       (start_tcp_connection): Expand non-blocking setting inline; set
+       linger period to zero.  Use new scatter-gather framework.  Log
+       more info if DEBUG defined.
+       (service_tcp_fd): New argument indicates exception; handle
+       exception fd set.  Print more info if DEBUG.  Use SOCKET_*
+       macros, scatter-gather support, SHUTDOWN_* macros.  Adjust for
+       new conn_state definitions.
+       (krb5int_sendto_tcp): Set up exception fd set.  Adjust for new
+       conn_state definitions.  Use new scatter-gather support.
+
 2002-06-11  Ken Raeburn  <raeburn@mit.edu>
 
        * sendto_kdc.c: Include sys/ioctl.h and sys/filio.h only if
index 10fe435cab98c63b692e76c8cc3b3eaf190c65b1..c0628cc32a09df0a8d75c3a82e431ac5ecf518e0 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * lib/krb5/os/sendto_kdc.c
  *
- * Copyright 1990,1991 by the Massachusetts Institute of Technology.
+ * Copyright 1990,1991,2001,2002 by the Massachusetts Institute of Technology.
  * All Rights Reserved.
  *
  * Export of this software from the United States of America may
 #include <time.h>
 #endif
 #include "os-proto.h"
+#ifdef _WIN32
+#include <sys/timeb.h>
+#endif
 
 #ifdef _AIX
 #include <sys/select.h>
 #endif
 
-#ifdef _WIN32
-#define ENABLE_TCP 0 /* for now */
-#else
-#define ENABLE_TCP 1
-#endif
-
-#if defined(ENABLE_TCP) && !defined(_WIN32)
+#ifndef _WIN32
 /* For FIONBIO.  */
 #include <sys/ioctl.h>
 #ifdef HAVE_SYS_FILIO_H
@@ -97,9 +94,18 @@ krb5_sendto_kdc (context, message, realm, reply, use_master, tcp_only)
      * should probably be returned as well.
      */
 
+#ifdef DEBUG
+    fprintf(stderr, "krb5_sendto_kdc(%d@%p, \"",
+           message->length, message->data);
+    /* realm need not be nul-terminated */
+    fwrite(realm->data, 1, realm->length, stderr);
+    fprintf(stderr, "\", use_master=%d, tcp_only=%d)\n", use_master, tcp_only);
+#endif
+
     retval = (use_master ? KRB5_KDC_UNREACH : KRB5_REALM_UNKNOWN);
 
     if (!tcp_only
+       && message->length < 1500
        && ! krb5_locate_kdc(context, realm, &addrs, use_master, SOCK_DGRAM)) {
        if (addrs.naddrs > 0) {
            retval = krb5int_sendto_udp (context, message, &addrs, reply);
@@ -108,8 +114,6 @@ krb5_sendto_kdc (context, message, realm, reply, use_master, tcp_only)
                return 0;
        }
     }
-
-#if ENABLE_TCP
     if (! krb5_locate_kdc(context, realm, &addrs, use_master, SOCK_STREAM)) {
        if (addrs.naddrs > 0) {
            retval = krb5int_sendto_tcp (context, message, &addrs, reply);
@@ -118,7 +122,6 @@ krb5_sendto_kdc (context, message, realm, reply, use_master, tcp_only)
                return 0;
        }
     }
-#endif
 
     return retval;
 }
@@ -136,19 +139,44 @@ static void debug_log_connect (int fd, struct addrinfo *ai)
 #endif
 }
 
+#if defined(_WIN32) && defined(DEBUG)
+static char *bogus_strerror (int xerr)
+{
+    static char buf[30];
+    sprintf(buf, "[err%d]", xerr);
+    return buf;
+}
+#define strerror(S) bogus_strerror(S)
+#endif
+
 #ifdef DEBUG
+
+#ifdef _WIN32
+#define dperror(MSG) \
+       fprintf(stderr,                                                 \
+               "%s: an error occurred ... wouldn't you like to know more?\n" \
+               "\tline=%d errno=%d socketerrno=%d\n",                  \
+               (MSG), __LINE__, errno, SOCKET_ERRNO)
+#else
 #define dperror(MSG) perror(MSG)
+#endif
 #define dfprintf(ARGLIST) fprintf ARGLIST
-#else
+
+static void print_fdsets (FILE *, fd_set *, fd_set *, fd_set *, int);
+
+#else /* ! DEBUG */
+
 #define dperror(MSG) ((void)(MSG))
 #define dfprintf(ARGLIST) ((void)0)
+
 #endif
 
 krb5_error_code
 krb5int_sendto_udp (krb5_context context, const krb5_data *message,
                    const struct addrlist *addrs, krb5_data *reply)
 {
-    int timeout, host, i;
+    int host, i;
+    unsigned int timeout;
     int sent, nready;
     krb5_error_code retval;
     SOCKET *socklist;
@@ -213,7 +241,7 @@ krb5int_sendto_udp (krb5_context context, const krb5_data *message,
                socklist[host] = socket(ai->ai_family, SOCK_DGRAM, 0);
                if (socklist[host] == INVALID_SOCKET) {
                    dfprintf((stderr, "socket: %s\naf was %d\n",
-                             strerror(errno), ai->ai_family));
+                             strerror(SOCKET_ERRNO), ai->ai_family));
                    continue;           /* try other hosts */
                }
                debug_log_connect(socklist[host], ai);
@@ -228,10 +256,11 @@ krb5int_sendto_udp (krb5_context context, const krb5_data *message,
                    dperror ("connect");
                    continue;
                }
+           } else {
+               dfprintf((stderr, "fd %d...", socklist[host]));
            }
            dfprintf((stderr, "sending..."));
-           if (send(socklist[host],
-                      message->data, message->length, 0) 
+           if (send(socklist[host], message->data, message->length, 0) 
                != message->length) {
                dperror ("sendto");
                continue;
@@ -242,6 +271,12 @@ krb5int_sendto_udp (krb5_context context, const krb5_data *message,
            waitlen.tv_sec = timeout;
            FD_ZERO(&readable);
            FD_SET(socklist[host], &readable);
+#ifdef DEBUG
+           fprintf(stderr, "selecting on one socket [");
+           print_fdsets(stderr, &readable, 0, 0, SOCKET_NFDS(socklist[host]));
+           fprintf(stderr, " ] timeout %ld.%06ld\n",
+                   waitlen.tv_sec, waitlen.tv_usec);
+#endif
            if ((nready = select(SOCKET_NFDS(socklist[host]),
                                 &readable,
                                 0,
@@ -255,8 +290,7 @@ krb5int_sendto_udp (krb5_context context, const krb5_data *message,
                }
                if ((cc = recv(socklist[host],
                               reply->data, reply->length, 
-                              0)) == SOCKET_ERROR)
-                 {
+                              0)) == SOCKET_ERROR) {
                    /* man page says error could be:
                       EBADF: won't happen
                       ENOTSOCK: it's a socket.
@@ -270,10 +304,14 @@ krb5int_sendto_udp (krb5_context context, const krb5_data *message,
                       server (i.e. don't set sent = 1).
                       */
 
-                   if (SOCKET_ERRNO == SOCKET_EINTR)
+                   int e = SOCKET_ERRNO;
+#ifdef DEBUG
+                   dperror("recv");
+#endif
+                   if (e == SOCKET_EINTR)
                      sent = 1;
                    continue;
-                 }
+               }
 
                /* We might consider here verifying that the reply
                   came from one of the KDC's listed for that address type,
@@ -317,8 +355,18 @@ krb5int_sendto_udp (krb5_context context, const krb5_data *message,
     return retval;
 }
 
-#if ENABLE_TCP
 /*
+ * Notes:
+ *
+ * Getting "connection refused" on a connected UDP socket causes
+ * select to indicate write capability on UNIX, but only shows up
+ * as an exception on Windows.  (I don't think any UNIX system flags
+ * the error as an exception.)  So we check for both, or make it
+ * system-specific.
+ *
+ * Always watch for responses from *any* of the servers.  Eventually
+ * fix the UDP code to do the same.
+ *
  * To do:
  * - TCP NOPUSH/CORK socket options?
  * - error codes that don't suck
@@ -334,40 +382,72 @@ struct conn_state {
     SOCKET fd;
     krb5_error_code err;
     enum { CONNECTING, WRITING, READING, FAILED } state;
-    char *buf;
-    size_t bufsize;
     unsigned char bufsizebytes[4];
-    size_t bufsizebytes_read;
-    struct iovec iov[2];
-    struct iovec *iovp;
-    int iov_count;
+    union {
+       /* When state is CONNECTING or WRITING, 'out' is valid.  When
+          state is READING, 'in' is valid.  When state is FAILED,
+          both should be ignored.  */
+       struct {
+           sg_buf sgbuf[2];
+           sg_buf *sgp;
+           int sg_count;
+       } out;
+       struct {
+           size_t bufsizebytes_read;
+           size_t bufsize;
+           char *buf;
+           char *pos;
+           size_t n_left;
+       } in;
+    } x;
 };
 
 struct select_state {
     int max, nfds;
-    fd_set rfds, wfds;
+    fd_set rfds, wfds, xfds;
     struct timeval end_time;
 };
 
 #ifdef DEBUG
 static void
-print_fdsets (FILE *f, fd_set *rfds, fd_set *wfds, int maxfd)
+print_fdsets (FILE *f, fd_set *rfds, fd_set *wfds, fd_set *xfds, int maxfd)
 {
     int i;
     for (i = 0; i < maxfd; i++) {
        int r = FD_ISSET(i, rfds);
-       int w = FD_ISSET(i, wfds);
-       if (r || w) {
+       int w = wfds && FD_ISSET(i, wfds);
+       int x = xfds && FD_ISSET(i, xfds);
+       if (r || w || x) {
            fprintf(f, " %d", i);
            if (r)
                fprintf(f, "r");
            if (w)
                fprintf(f, "w");
+           if (x)
+               fprintf(f, "x");
        }
     }
 }
 #endif
 
+static int getcurtime (struct timeval *tvp)
+{
+#ifdef _WIN32
+    struct _timeb tb;
+    _ftime(&tb);
+    tvp->tv_sec = tb.time;
+    tvp->tv_usec = tb.millitm * 1000;
+    /* Can _ftime fail?  */
+    return 0;
+#else
+    if (gettimeofday(tvp, 0)) {
+       dperror("gettimeofday");
+       return errno;
+    }
+    return 0;
+#endif
+}
+
 /*
  * Call select and return results.
  * Input: interesting file descriptors and absolute timeout
@@ -381,11 +461,9 @@ call_select (struct select_state *in, struct select_state *out, int *sret)
     krb5_error_code e;
 
     *out = *in;
-    if (gettimeofday(&now, 0)) {
-       e = errno;
-       dperror("gettimeofday");
+    e = getcurtime(&now);
+    if (e)
        return e;
-    }
     out->end_time.tv_sec -= now.tv_sec;
     out->end_time.tv_usec -= now.tv_usec;
     if (out->end_time.tv_usec < 0) {
@@ -398,38 +476,27 @@ call_select (struct select_state *in, struct select_state *out, int *sret)
     }
 #ifdef DEBUG
     fprintf(stderr, "selecting on %d sockets [", out->nfds);
-    print_fdsets(stderr, &out->rfds, &out->wfds, out->max);
+    print_fdsets(stderr, &out->rfds, &out->wfds, &out->xfds, out->max);
     fprintf(stderr, " ] timeout %ld.%06ld\n", (long) out->end_time.tv_sec,
            (long) out->end_time.tv_usec);
 #endif
-    *sret = select(out->max, &out->rfds, &out->wfds, 0, &out->end_time);
-    e = errno;
+    *sret = select(out->max, &out->rfds, &out->wfds, &out->xfds,
+                  &out->end_time);
+    e = SOCKET_ERRNO;
 #ifdef DEBUG
     fprintf(stderr, "select returns %d", *sret);
     if (*sret < 0)
-       fprintf(stderr, ", errno = %d/%s\n", e, strerror(e));
+       fprintf(stderr, ", error = %d/%s\n", e, strerror(e));
     else if (*sret == 0)
        fprintf(stderr, " (timeout)\n");
     else {
        fprintf(stderr, ":");
-       print_fdsets(stderr, &out->rfds, &out->wfds, out->max);
+       print_fdsets(stderr, &out->rfds, &out->wfds, &out->xfds, out->max);
        fprintf(stderr, "\n");
     }
 #endif
     if (*sret < 0)
-       return errno;
-    return 0;
-}
-
-static int
-make_nonblocking (int fd)
-{
-    static const int one = 1;
-
-    if (ioctlsocket (fd, FIONBIO, &one)) {
-       dperror("sendto_kdc_tcp: ioctl(FIONBIO)");
-       return errno;
-    }
+       return e;
     return 0;
 }
 
@@ -439,28 +506,32 @@ start_tcp_connection (struct conn_state *state, struct addrinfo *ai)
     int fd, e;
 
     state->err = 0;
-    state->buf = 0;
-    state->bufsizebytes_read = 0;
-    state->iovp = state->iov;
-    state->iov[0].iov_base = (char *) state->bufsizebytes;
-    state->iov[0].iov_len = 4;
-    state->iov[1].iov_base = 0;
-    state->iov[1].iov_len = 0;
-    state->iov_count = 2;
+    state->x.out.sgp = state->x.out.sgbuf;
+    SG_SET(&state->x.out.sgbuf[0], state->bufsizebytes, 4);
+    SG_SET(&state->x.out.sgbuf[1], 0, 0);
+    state->x.out.sg_count = 2;
 
     dfprintf((stderr, "getting stream socket in family %d...", ai->ai_family));
     fd = socket(ai->ai_family, SOCK_STREAM, 0);
     if (fd == INVALID_SOCKET) {
-       state->err = errno;
+       state->err = SOCKET_ERRNO;
        dfprintf((stderr, "socket: %s connecting with af %d\n",
                  strerror (state->err), ai->ai_family));
        return -1;              /* try other hosts */
     }
-    debug_log_connect(fd, ai);
     /* Make it non-blocking.  */
-    make_nonblocking(fd);
+    {
+       static const int one = 1;
+       static const struct linger lopt = { 0, 0 };
+
+       if (ioctlsocket(fd, FIONBIO, (const void *) &one))
+           dperror("sendto_kdc_tcp: ioctl(FIONBIO)");
+       if (setsockopt(fd, SOL_SOCKET, SO_LINGER, &lopt, sizeof(lopt)))
+           dperror("sendto_kdc_tcp: setsockopt(SO_LINGER)");
+    }
 
     /* Start connecting to KDC.  */
+    debug_log_connect(fd, ai);
     e = connect(fd, ai->ai_addr, ai->ai_addrlen);
     if (e != 0) {
        /*
@@ -471,7 +542,6 @@ start_tcp_connection (struct conn_state *state, struct addrinfo *ai)
            state->state = CONNECTING;
        } else {
            state->err = SOCKET_ERRNO;
-           dfprintf((stderr, "socket error %d in connect()\n", state->err));
            state->state = FAILED;
            return -2;
        }
@@ -489,40 +559,37 @@ start_tcp_connection (struct conn_state *state, struct addrinfo *ai)
     return 0;
 }
 
-static void
-iov_advance (struct iovec *iov, int how_far)
-{
-    if (iov->iov_len < how_far)
-       abort();
-    /* Can't do math on void pointers.  */
-    iov->iov_base = (char *) iov->iov_base + how_far;
-    iov->iov_len -= how_far;
-}
-
 /* Return nonzero only if we're finished and the caller should exit
    its loop.  This happens in two cases: We have a complete message,
    or the socket has closed and no others are open.  */
 static int
 service_tcp_fd (struct conn_state *conn, struct select_state *selstate,
-               int can_read, int can_write)
+               int can_read, int can_write, int exception)
 {
     krb5_error_code e = 0;
     int nwritten, nread;
 
 #ifdef DEBUG
-    /* index with can_read + 2 * can_write */
-    static const char *const select_states[] = {
-       "cannot_read_or_write??",
-       "can_read", "can_write", "can_read/can_write",
-    };
-    fprintf(stderr, "handling %s on fd %d in state %s\n",
-           select_states[can_read + 2 * can_write],
-           conn->fd, state_strings[(int) conn->state]);
+    {
+       int sep = ' ';
+       fprintf(stderr, "handling");
+       if (can_read)
+           fprintf(stderr, "%cread", sep), sep = '/';
+       if (can_write)
+           fprintf(stderr, "%cwrite", sep), sep = '/';
+       if (exception)
+           fprintf(stderr, "%cexception", sep), sep = '/';
+       if (sep == ' ')
+           fprintf(stderr, " no_flags?!");
+       fprintf(stderr, " on fd %d in state %s\n",
+               conn->fd, state_strings[(int) conn->state]);
+    }
 #endif
 
-    if (!can_read && !can_write)
+    if (!can_read && !can_write && !exception)
        abort();
     switch (conn->state) {
+       SOCKET_WRITEV_TEMP tmp;
 
     case CONNECTING:
        if (can_read) {
@@ -530,9 +597,10 @@ service_tcp_fd (struct conn_state *conn, struct select_state *selstate,
            e = EINVAL /* ?? */;
        kill_conn:
            conn->state = FAILED;
-           shutdown(conn->fd, 2);
+           shutdown(conn->fd, SHUTDOWN_BOTH);
            FD_CLR(conn->fd, &selstate->rfds);
            FD_CLR(conn->fd, &selstate->wfds);
+           FD_CLR(conn->fd, &selstate->xfds);
            conn->err = e;
            dfprintf((stderr, "abandoning connection %d: %s\n",
                     conn->fd, strerror(e)));
@@ -540,7 +608,8 @@ service_tcp_fd (struct conn_state *conn, struct select_state *selstate,
            if (selstate->max == 1 + conn->fd) {
                while (selstate->max > 0
                       && ! FD_ISSET(selstate->max-1, &selstate->rfds)
-                      && ! FD_ISSET(selstate->max-1, &selstate->wfds))
+                      && ! FD_ISSET(selstate->max-1, &selstate->wfds)
+                      && ! FD_ISSET(selstate->max-1, &selstate->xfds))
                    selstate->max--;
                dfprintf((stderr, "new max_fd + 1 is %d\n", selstate->max));
            }
@@ -551,9 +620,35 @@ service_tcp_fd (struct conn_state *conn, struct select_state *selstate,
            }
            return e == 0;
        }
+       if (exception) {
+#ifdef DEBUG
+           int sockerr;
+           size_t sockerrlen = sizeof(sockerr);
+#endif
+       handle_exception:
+#ifdef DEBUG
+           e = getsockopt(conn->fd, SOL_SOCKET, SO_ERROR,
+                          &sockerr, &sockerrlen);
+           if (e != 0) {
+               /* What to do now?  */
+               e = SOCKET_ERRNO;
+               dfprintf((stderr,
+                         "getsockopt(SO_ERROR) on exception fd failed: %d\n",
+                         e));
+               goto kill_conn;
+           }
+           /* Okay, got the error back.  Either way, kill the
+              connection.  */
+           e = sockerr;
+#else
+           e = 1;              /* need only be non-zero */
+#endif
+           goto kill_conn;
+       }
 
        /*
         * Connect finished -- but did it succeed or fail?
+        * UNIX sets can_write if failed.
         * Try writing, I guess, and find out.
         */
        conn->state = WRITING;
@@ -565,15 +660,17 @@ service_tcp_fd (struct conn_state *conn, struct select_state *selstate,
            /* Bad -- the KDC shouldn't be sending anything yet.  */
            goto kill_conn;
        }
+       if (exception)
+           goto handle_exception;
 
-       try_writing:
+    try_writing:
        dfprintf((stderr, "trying to writev %d (%d bytes) to fd %d\n",
-                 conn->iov_count,
-                 ((conn->iov_count == 2 ? conn->iovp[1].iov_len : 0)
-                  + conn->iovp[0].iov_len),
+                 conn->x.out.sg_count,
+                 ((conn->x.out.sg_count == 2 ? SG_LEN(&conn->x.out.sgp[1]) : 0)
+                  + SG_LEN(&conn->x.out.sgp[0])),
                  conn->fd));
-       nwritten = writev(conn->fd, conn->iovp,
-                         conn->iov_count);
+       nwritten = SOCKET_WRITEV(conn->fd, conn->x.out.sgp,
+                                conn->x.out.sg_count, tmp);
        if (nwritten < 0) {
            e = SOCKET_ERRNO;
            dfprintf((stderr, "failed: %s\n", strerror(e)));
@@ -581,61 +678,73 @@ service_tcp_fd (struct conn_state *conn, struct select_state *selstate,
        }
        dfprintf((stderr, "wrote %d bytes\n", nwritten));
        while (nwritten) {
-           struct iovec *iovp = conn->iovp;
-           if (nwritten < iovp->iov_len) {
-               iov_advance(iovp, nwritten);
+           sg_buf *sgp = conn->x.out.sgp;
+           if (nwritten < SG_LEN(sgp)) {
+               SG_ADVANCE(sgp, nwritten);
                nwritten = 0;
            } else {
-               nwritten -= conn->iovp->iov_len;
-               conn->iovp++;
-               conn->iov_count--;
-               if (conn->iov_count == 0 && nwritten != 0)
+               nwritten -= SG_LEN(conn->x.out.sgp);
+               conn->x.out.sgp++;
+               conn->x.out.sg_count--;
+               if (conn->x.out.sg_count == 0 && nwritten != 0)
                    /* Wrote more than we wanted to?  */
                    abort();
            }
        }
-       if (conn->iov_count == 0) {
+       if (conn->x.out.sg_count == 0) {
            /* Done writing, switch to reading.  */
-           shutdown(conn->fd, 1);
+           shutdown(conn->fd, SHUTDOWN_WRITE);
            FD_CLR(conn->fd, &selstate->wfds);
            /* Q: How do we detect failures to send the remaining data
               to the remote side, since we're in non-blocking mode?
               Will we always get errors on the reading side?  */
            dfprintf((stderr, "switching fd %d to READING\n", conn->fd));
            conn->state = READING;
-           conn->bufsizebytes_read = 0;
+           conn->x.in.bufsizebytes_read = 0;
+           conn->x.in.bufsize = 0;
+           conn->x.in.buf = 0;
+           conn->x.in.pos = 0;
+           conn->x.in.n_left = 0;
        }
        return 0;
 
     case READING:
-       if (conn->bufsizebytes_read == 4) {
+       if (exception) {
+           if (conn->x.in.buf) {
+               free(conn->x.in.buf);
+               conn->x.in.buf = 0;
+           }
+           goto handle_exception;
+       }
+
+       if (conn->x.in.bufsizebytes_read == 4) {
            /* Reading data.  */
-           dfprintf((stderr, "reading %ld bytes of data from fd %d\n",
-                     (long) conn->iov[0].iov_len, conn->fd));
-           nread = read(conn->fd, conn->iov[0].iov_base,
-                        conn->iov[0].iov_len);
+           dfprintf((stderr, "reading %lu bytes of data from fd %d\n",
+                     (unsigned long) conn->x.in.n_left, conn->fd));
+           nread = SOCKET_READ(conn->fd, conn->x.in.pos, conn->x.in.n_left);
            if (nread <= 0) {
                e = nread ? SOCKET_ERRNO : ECONNRESET;
-               free(conn->buf);
-               conn->buf = 0;
+               free(conn->x.in.buf);
+               conn->x.in.buf = 0;
                goto kill_conn;
            }
-           iov_advance(conn->iov, nread);
-           if (conn->iov[0].iov_len == 0) {
+           conn->x.in.n_left -= nread;
+           conn->x.in.pos += nread;
+           if (conn->x.in.n_left <= 0) {
                /* We win!  */
                return 1;
            }
        } else {
            /* Reading length.  */
-           nread = read(conn->fd,
-                        conn->bufsizebytes + conn->bufsizebytes_read,
-                        4 - conn->bufsizebytes_read);
+           nread = SOCKET_READ(conn->fd,
+                               conn->bufsizebytes + conn->x.in.bufsizebytes_read,
+                               4 - conn->x.in.bufsizebytes_read);
            if (nread < 0) {
                e = SOCKET_ERRNO;
                goto kill_conn;
            }
-           conn->bufsizebytes_read += nread;
-           if (conn->bufsizebytes_read == 4) {
+           conn->x.in.bufsizebytes_read += nread;
+           if (conn->x.in.bufsizebytes_read == 4) {
                unsigned long len;
                len = conn->bufsizebytes[0];
                len = (len << 8) + conn->bufsizebytes[1];
@@ -648,11 +757,12 @@ service_tcp_fd (struct conn_state *conn, struct select_state *selstate,
                    e = E2BIG;
                    goto kill_conn;
                }
-               conn->bufsize = conn->iov[0].iov_len = len;
-               conn->buf = conn->iov[0].iov_base = malloc(len);
+               conn->x.in.bufsize = conn->x.in.n_left = len;
+               conn->x.in.buf = conn->x.in.pos = malloc(len);
                dfprintf((stderr, "allocated %lu byte buffer at %p\n",
-                         len, conn->buf));
-               if (conn->iov[0].iov_base == 0) {
+                         len, conn->x.in.buf));
+               if (conn->x.in.buf == 0) {
+                   /* allocation failure */
                    e = errno;
                    goto kill_conn;
                }
@@ -687,16 +797,17 @@ service_fds (struct select_state *selstate,
 
        /* Got something on a socket, process it.  */
        for (i = 0; i <= selstate->max && selret > 0; i++) {
-           int can_read, can_write;
+           int can_read, can_write, exception;
            if (conns[i].fd == INVALID_SOCKET)
                continue;
            can_read = FD_ISSET(conns[i].fd, &sel_results.rfds);
            can_write = FD_ISSET(conns[i].fd, &sel_results.wfds);
-           if (!can_read && !can_write)
+           exception = FD_ISSET(conns[i].fd, &sel_results.xfds);
+           if (!can_read && !can_write && !exception)
                continue;
 
            selret--;
-           if (service_tcp_fd(&conns[i], selstate, can_read, can_write)) {
+           if (service_tcp_fd(&conns[i], selstate, can_read, can_write, exception)) {
                dfprintf((stderr, "service_tcp_fd says we're done\n"));
                *winning_conn = i;
                return 1;
@@ -729,8 +840,8 @@ krb5int_sendto_tcp (krb5_context context, const krb5_data *message,
     if (conns == NULL) {
        return ENOMEM;
     }
+    memset(conns, 0, n_conns * sizeof(conns[i]));
     for (i = 0; i < n_conns; i++) {
-       memset(&conns[i], 0, sizeof(conns[i]));
        conns[i].fd = INVALID_SOCKET;
     }
 
@@ -738,6 +849,7 @@ krb5int_sendto_tcp (krb5_context context, const krb5_data *message,
     select_state.nfds = 0;
     FD_ZERO(&select_state.rfds);
     FD_ZERO(&select_state.wfds);
+    FD_ZERO(&select_state.xfds);
 
     message_len_buf[0] = (message->length >> 24) & 0xff;
     message_len_buf[1] = (message->length >> 16) & 0xff;
@@ -750,24 +862,22 @@ krb5int_sendto_tcp (krb5_context context, const krb5_data *message,
            continue;
        /* Send to the host, wait timeout seconds for a response,
           then move on. */
-       if (start_tcp_connection (&conns[host], addrs->addrs[host]))
+       if (start_tcp_connection (&conns[host], addrs->addrs[host])) {
            continue;
-       conns[host].iov[0].iov_base = (char *) message_len_buf;
-       conns[host].iov[0].iov_len = 4;
-       conns[host].iov[1].iov_base = message->data;
-       conns[host].iov[1].iov_len = message->length;
-           
+       }
+       SG_SET(&conns[host].x.out.sgbuf[0], message_len_buf, 4);
+       SG_SET(&conns[host].x.out.sgbuf[1], message->data, message->length);
+
        FD_SET(conns[host].fd, &select_state.rfds);
        FD_SET(conns[host].fd, &select_state.wfds);
+       FD_SET(conns[host].fd, &select_state.xfds);
        if (select_state.max <= conns[host].fd)
            select_state.max = conns[host].fd + 1;
        select_state.nfds++;
 
-       if (gettimeofday(&now, 0)) {
-           retval = errno;
-           dperror("gettimeofday");
+       retval = getcurtime(&now);
+       if (retval)
            goto egress;
-       }
        select_state.end_time = now;
        select_state.end_time.tv_sec++;
        e = service_fds(&select_state, conns, host+1, &winning_conn);
@@ -781,11 +891,9 @@ krb5int_sendto_tcp (krb5_context context, const krb5_data *message,
        return KRB5_KDC_UNREACH;
     }
     if (e == 0) {
-       if (gettimeofday(&now, 0)) {
-           retval = errno;
-           dperror("gettimeofday");
+       retval = getcurtime(&now);
+       if (retval)
            goto egress;
-       }
        select_state.end_time = now;
        select_state.end_time.tv_sec += 30;
        e = service_fds(&select_state, conns, n_conns, &winning_conn);
@@ -795,20 +903,19 @@ krb5int_sendto_tcp (krb5_context context, const krb5_data *message,
        goto egress;
     }
     /* Success!  */
-    reply->data = conns[winning_conn].buf;
-    reply->length = conns[winning_conn].bufsize;
+    reply->data = conns[winning_conn].x.in.buf;
+    reply->length = conns[winning_conn].x.in.bufsize;
     dfprintf((stderr, "returning %lu bytes in buffer %p\n",
              (unsigned long) reply->length, reply->data));
     retval = 0;
-    conns[winning_conn].buf = 0;
+    conns[winning_conn].x.in.buf = 0;
 egress:
     for (i = 0; i < n_conns; i++) {
        if (conns[i].fd != INVALID_SOCKET)
            close(conns[i].fd);
-       if (conns[i].buf != 0)
-           free(conns[i].buf);
+       if (conns[i].state == READING && conns[i].x.in.buf != 0)
+           free(conns[i].x.in.buf);
     }
     free(conns);
     return retval;
 }
-#endif