Move epoch and cid generation into the rx core
[openafs.git] / src / rx / rx.c
index a2ac7b5..07c6d73 100644 (file)
@@ -73,6 +73,7 @@ extern afs_int32 afs_termState;
 #endif /* KERNEL */
 
 #include <opr/queue.h>
+#include <hcrypto/rand.h>
 
 #include "rx.h"
 #include "rx_clock.h"
@@ -125,7 +126,8 @@ static void rxi_AckAll(struct rx_call *call);
 static struct rx_connection
        *rxi_FindConnection(osi_socket socket, afs_uint32 host, u_short port,
                            u_short serviceId, afs_uint32 cid,
-                           afs_uint32 epoch, int type, u_int securityIndex);
+                           afs_uint32 epoch, int type, u_int securityIndex,
+                            int *unknownService);
 static struct rx_packet
        *rxi_ReceiveDataPacket(struct rx_call *call, struct rx_packet *np,
                               int istack, osi_socket socket,
@@ -156,6 +158,7 @@ static void rxi_AckAllInTransmitQueue(struct rx_call *call);
 static void rxi_CancelKeepAliveEvent(struct rx_call *call);
 static void rxi_CancelDelayedAbortEvent(struct rx_call *call);
 static void rxi_CancelGrowMTUEvent(struct rx_call *call);
+static void update_nextCid(void);
 
 #ifdef RX_ENABLE_LOCKS
 struct rx_tq_debug {
@@ -393,6 +396,7 @@ struct rx_connection *rxLastConn = 0;
  * tiers:
  *
  * rx_connHashTable_lock - synchronizes conn creation, rx_connHashTable access
+ *                         also protects updates to rx_nextCid
  * conn_call_lock - used to synchonize rx_EndCall and rx_NewCall
  * call->lock - locks call data fields.
  * These are independent of each other:
@@ -436,17 +440,9 @@ static int rxdb_fileID = RXDB_FILE_RX;
 #define CLEAR_CALL_QUEUE_LOCK(C)
 #endif /* RX_ENABLE_LOCKS */
 struct rx_serverQueueEntry *rx_waitForPacket = 0;
-struct rx_serverQueueEntry *rx_waitingForPacket = 0;
 
 /* ------------Exported Interfaces------------- */
 
-/* This function allows rxkad to set the epoch to a suitably random number
- * which rx_NewConnection will use in the future.  The principle purpose is to
- * get rxnull connections to use the same epoch as the rxkad connections do, at
- * least once the first rxkad connection is established.  This is important now
- * that the host/port addresses aren't used in FindConnection: the uniqueness
- * of epoch/cid matters and the start time won't do. */
-
 #ifdef AFS_PTHREAD_ENV
 /*
  * This mutex protects the following global variables:
@@ -477,19 +473,7 @@ rx_SetEpoch(afs_uint32 epoch)
 #ifndef AFS_NT40_ENV
 static
 #endif
-int rxinit_status = 1;
-#ifdef AFS_PTHREAD_ENV
-/*
- * This mutex protects the following global variables:
- * rxinit_status
- */
-
-#define LOCK_RX_INIT MUTEX_ENTER(&rx_init_mutex)
-#define UNLOCK_RX_INIT MUTEX_EXIT(&rx_init_mutex)
-#else
-#define LOCK_RX_INIT
-#define UNLOCK_RX_INIT
-#endif
+rx_atomic_t rxinit_status = RX_ATOMIC_INIT(1);
 
 int
 rx_InitHost(u_int host, u_int port)
@@ -500,17 +484,13 @@ rx_InitHost(u_int host, u_int port)
     struct timeval tv;
 #endif /* KERNEL */
     char *htable, *ptable;
-    int tmp_status;
 
     SPLVAR;
 
     INIT_PTHREAD_LOCKS;
-    LOCK_RX_INIT;
-    if (rxinit_status == 0) {
-       tmp_status = rxinit_status;
-       UNLOCK_RX_INIT;
-       return tmp_status;      /* Already started; return previous error code. */
-    }
+    if (!rx_atomic_test_and_clear_bit(&rxinit_status, 0))
+       return 0; /* already started */
+
 #ifdef RXDEBUG
     rxi_DebugInit();
 #endif
@@ -532,7 +512,6 @@ rx_InitHost(u_int host, u_int port)
 
     rx_socket = rxi_GetHostUDPSocket(host, (u_short) port);
     if (rx_socket == OSI_NULLSOCKET) {
-       UNLOCK_RX_INIT;
        return RX_ADDRINUSE;
     }
 #if defined(RX_ENABLE_LOCKS) && defined(KERNEL)
@@ -625,12 +604,12 @@ rx_InitHost(u_int host, u_int port)
 #endif
     }
     rx_stats.minRtt.sec = 9999999;
-#ifdef KERNEL
-    rx_SetEpoch(tv.tv_sec | 0x80000000);
-#else
-    rx_SetEpoch(tv.tv_sec);    /* Start time of this package, rxkad
-                                * will provide a randomer value. */
-#endif
+    if (RAND_bytes(&rx_epoch, sizeof(rx_epoch)) != 1)
+       return -1;
+    rx_epoch  = (rx_epoch & ~0x40000000) | 0x80000000;
+    if (RAND_bytes(&rx_nextCid, sizeof(rx_nextCid)) != 1)
+       return -1;
+    rx_nextCid &= RX_CIDMASK;
     MUTEX_ENTER(&rx_quota_mutex);
     rxi_dataQuota += rx_extraQuota; /* + extra pkts caller asked to rsrv */
     MUTEX_EXIT(&rx_quota_mutex);
@@ -655,16 +634,13 @@ rx_InitHost(u_int host, u_int port)
     rx_GetIFInfo();
 #endif
 
-#if defined(RXK_LISTENER_ENV) || !defined(KERNEL)
     /* Start listener process (exact function is dependent on the
      * implementation environment--kernel or user space) */
     rxi_StartListener();
-#endif
 
     USERPRI;
-    tmp_status = rxinit_status = 0;
-    UNLOCK_RX_INIT;
-    return tmp_status;
+    rx_atomic_clear_bit(&rxinit_status, 0);
+    return 0;
 }
 
 int
@@ -730,8 +706,10 @@ rxi_rto_startTimer(struct rx_call *call, int lastPacket, int istack)
 static_inline void
 rxi_rto_cancel(struct rx_call *call)
 {
-    rxevent_Cancel(&call->resendEvent);
-    CALL_RELE(call, RX_CALL_REFCOUNT_RESEND);
+    if (call->resendEvent != NULL) {
+       rxevent_Cancel(&call->resendEvent);
+       CALL_RELE(call, RX_CALL_REFCOUNT_RESEND);
+    }
 }
 
 /*!
@@ -814,7 +792,7 @@ rx_rto_setPeerTimeoutSecs(struct rx_peer *peer, int secs) {
 void
 rx_SetBusyChannelError(afs_int32 onoff)
 {
-    osi_Assert(rxinit_status != 0);
+    osi_Assert(rx_atomic_test_bit(&rxinit_status, 0));
     rxi_busyChannelError = onoff ? 1 : 0;
 }
 
@@ -1060,7 +1038,6 @@ rx_NewConnection(afs_uint32 shost, u_short sport, u_short sservice,
                 int serviceSecurityIndex)
 {
     int hashindex, i;
-    afs_int32 cid;
     struct rx_connection *conn;
 
     SPLVAR;
@@ -1081,10 +1058,10 @@ rx_NewConnection(afs_uint32 shost, u_short sport, u_short sservice,
 #endif
     NETPRI;
     MUTEX_ENTER(&rx_connHashTable_lock);
-    cid = (rx_nextCid += RX_MAXCALLS);
     conn->type = RX_CLIENT_CONNECTION;
-    conn->cid = cid;
     conn->epoch = rx_epoch;
+    conn->cid = rx_nextCid;
+    update_nextCid();
     conn->peer = rxi_FindPeer(shost, sport, 1);
     conn->serviceId = sservice;
     conn->securityObject = securityObject;
@@ -2147,8 +2124,6 @@ rx_GetCall(int tno, struct rx_service *cur_service, osi_socket * socketp)
            opr_queue_Append(&rx_idleServerQueue, &sq->entry);
 #ifndef AFS_AIX41_ENV
            rx_waitForPacket = sq;
-#else
-           rx_waitingForPacket = sq;
 #endif /* AFS_AIX41_ENV */
            do {
                CV_WAIT(&sq->cv, &rx_serverPool_lock);
@@ -2532,6 +2507,10 @@ rx_EndCall(struct rx_call *call, afs_int32 rc)
      * Map errors to the local host's errno.h format.
      */
     error = ntoh_syserr_conv(error);
+
+    /* If the caller said the call failed with some error, we had better
+     * return an error code. */
+    osi_Assert(!rc || error);
     return error;
 }
 
@@ -2549,11 +2528,9 @@ rx_Finalize(void)
     struct rx_connection **conn_ptr, **conn_end;
 
     INIT_PTHREAD_LOCKS;
-    LOCK_RX_INIT;
-    if (rxinit_status == 1) {
-       UNLOCK_RX_INIT;
+    if (rx_atomic_test_and_set_bit(&rxinit_status, 0))
        return;                 /* Already shutdown. */
-    }
+
     rxi_DeleteCachedConnections();
     if (rx_connHashTable) {
        MUTEX_ENTER(&rx_connHashTable_lock);
@@ -2593,8 +2570,6 @@ rx_Finalize(void)
     afs_winsockCleanup();
 #endif
 
-    rxinit_status = 1;
-    UNLOCK_RX_INIT;
 }
 #endif
 
@@ -2943,7 +2918,7 @@ rxi_SetPeerMtu(struct rx_peer *peer, afs_uint32 host, afs_uint32 port, int mtu)
 
 #ifdef AFS_RXERRQ_ENV
 static void
-rxi_SetPeerDead(afs_uint32 host, afs_uint16 port)
+rxi_SetPeerDead(struct sock_extended_err *err, afs_uint32 host, afs_uint16 port)
 {
     int hashIndex = PEER_HASH(host, port);
     struct rx_peer *peer;
@@ -2952,15 +2927,25 @@ rxi_SetPeerDead(afs_uint32 host, afs_uint16 port)
 
     for (peer = rx_peerHashTable[hashIndex]; peer; peer = peer->next) {
        if (peer->host == host && peer->port == port) {
+           peer->refCount++;
            break;
        }
     }
 
+    MUTEX_EXIT(&rx_peerHashTable_lock);
+
     if (peer) {
        rx_atomic_inc(&peer->neterrs);
-    }
+       MUTEX_ENTER(&peer->peer_lock);
+       peer->last_err_origin = RX_NETWORK_ERROR_ORIGIN_ICMP;
+       peer->last_err_type = err->ee_type;
+       peer->last_err_code = err->ee_code;
+       MUTEX_EXIT(&peer->peer_lock);
 
-    MUTEX_EXIT(&rx_peerHashTable_lock);
+       MUTEX_ENTER(&rx_peerHashTable_lock);
+       peer->refCount--;
+       MUTEX_EXIT(&rx_peerHashTable_lock);
+    }
 }
 
 void
@@ -2979,13 +2964,87 @@ rxi_ProcessNetError(struct sock_extended_err *err, afs_uint32 addr, afs_uint16 p
        case ICMP_PORT_UNREACH:
        case ICMP_NET_ANO:
        case ICMP_HOST_ANO:
-           rxi_SetPeerDead(addr, port);
+           rxi_SetPeerDead(err, addr, port);
            break;
        }
     }
 }
+
+static const char *
+rxi_TranslateICMP(int type, int code)
+{
+    switch (type) {
+    case ICMP_DEST_UNREACH:
+       switch (code) {
+       case ICMP_NET_UNREACH:
+           return "Destination Net Unreachable";
+       case ICMP_HOST_UNREACH:
+           return "Destination Host Unreachable";
+       case ICMP_PROT_UNREACH:
+           return "Destination Protocol Unreachable";
+       case ICMP_PORT_UNREACH:
+           return "Destination Port Unreachable";
+       case ICMP_NET_ANO:
+           return "Destination Net Prohibited";
+       case ICMP_HOST_ANO:
+           return "Destination Host Prohibited";
+       }
+       break;
+    }
+    return NULL;
+}
 #endif /* AFS_RXERRQ_ENV */
 
+/**
+ * Get the last network error for a connection
+ *
+ * A "network error" here means an error retrieved from ICMP, or some other
+ * mechanism outside of Rx that informs us of errors in network reachability.
+ *
+ * If a peer associated with the given Rx connection has received a network
+ * error recently, this function allows the caller to know what error
+ * specifically occurred. This can be useful to know, since e.g. ICMP errors
+ * can cause calls to that peer to be quickly aborted. So, this function can
+ * help see why a call was aborted due to network errors.
+ *
+ * If we have received traffic from a peer since the last network error, we
+ * treat that peer as if we had not received an network error for it.
+ *
+ * @param[in] conn  The Rx connection to examine
+ * @param[out] err_origin  The origin of the last network error (e.g. ICMP);
+ *                         one of the RX_NETWORK_ERROR_ORIGIN_* constants
+ * @param[out] err_type  The type of the last error
+ * @param[out] err_code  The code of the last error
+ * @param[out] msg  Human-readable error message, if applicable; NULL otherwise
+ *
+ * @return If we have an error
+ *  @retval -1 No error to get; 'out' params are undefined
+ *  @retval 0 We have an error; 'out' params contain the last error
+ */
+int
+rx_GetNetworkError(struct rx_connection *conn, int *err_origin, int *err_type,
+                   int *err_code, const char **msg)
+{
+#ifdef AFS_RXERRQ_ENV
+    struct rx_peer *peer = conn->peer;
+    if (rx_atomic_read(&peer->neterrs)) {
+       MUTEX_ENTER(&peer->peer_lock);
+       *err_origin = peer->last_err_origin;
+       *err_type = peer->last_err_type;
+       *err_code = peer->last_err_code;
+       MUTEX_EXIT(&peer->peer_lock);
+
+       *msg = NULL;
+       if (*err_origin == RX_NETWORK_ERROR_ORIGIN_ICMP) {
+           *msg = rxi_TranslateICMP(*err_type, *err_code);
+       }
+
+       return 0;
+    }
+#endif
+    return -1;
+}
+
 /* Find the peer process represented by the supplied (host,port)
  * combination.  If there is no appropriate active peer structure, a
  * new one will be allocated and initialized
@@ -3041,10 +3100,12 @@ rxi_FindPeer(afs_uint32 host, u_short port, int create)
 static struct rx_connection *
 rxi_FindConnection(osi_socket socket, afs_uint32 host,
                   u_short port, u_short serviceId, afs_uint32 cid,
-                  afs_uint32 epoch, int type, u_int securityIndex)
+                  afs_uint32 epoch, int type, u_int securityIndex,
+                   int *unknownService)
 {
     int hashindex, flag, i;
     struct rx_connection *conn;
+    *unknownService = 0;
     hashindex = CONN_HASH(host, port, cid, epoch, type);
     MUTEX_ENTER(&rx_connHashTable_lock);
     rxLastConn ? (conn = rxLastConn, flag = 0) : (conn =
@@ -3089,6 +3150,7 @@ rxi_FindConnection(osi_socket socket, afs_uint32 host,
        if (!service || (securityIndex >= service->nSecurityObjects)
            || (service->securityObjects[securityIndex] == 0)) {
            MUTEX_EXIT(&rx_connHashTable_lock);
+            *unknownService = 1;
            return (struct rx_connection *)0;
        }
        conn = rxi_AllocConnection();   /* This bzero's the connection */
@@ -3315,14 +3377,13 @@ rxi_ReceiveServerCall(osi_socket socket, struct rx_packet *np,
      * flag is cleared.
      */
 #ifdef RX_ENABLE_LOCKS
-    if (call->state == RX_STATE_ACTIVE) {
-       int old_error = call->error;
+    if (call->state == RX_STATE_ACTIVE && !call->error) {
        rxi_WaitforTQBusy(call);
         /* If we entered error state while waiting,
          * must call rxi_CallError to permit rxi_ResetCall
          * to processed when the tqWaiter count hits zero.
          */
-        if (call->error && call->error != old_error) {
+        if (call->error) {
            rxi_CallError(call, call->error);
            MUTEX_EXIT(&call->lock);
             return NULL;
@@ -3386,6 +3447,7 @@ rxi_ReceivePacket(struct rx_packet *np, osi_socket socket,
     struct rx_call *call;
     struct rx_connection *conn;
     int type;
+    int unknownService = 0;
 #ifdef RXDEBUG
     char *packetType;
 #endif
@@ -3466,12 +3528,12 @@ rxi_ReceivePacket(struct rx_packet *np, osi_socket socket,
     conn =
        rxi_FindConnection(socket, host, port, np->header.serviceId,
                           np->header.cid, np->header.epoch, type,
-                          np->header.securityIndex);
+                          np->header.securityIndex, &unknownService);
 
     /* To avoid having 2 connections just abort at each other,
        don't abort an abort. */
     if (!conn) {
-        if (np->header.type != RX_PACKET_TYPE_ABORT)
+        if (unknownService && (np->header.type != RX_PACKET_TYPE_ABORT))
             rxi_SendRawAbort(socket, host, port, RX_INVALID_OPERATION,
                              np, 0);
         return np;
@@ -4717,6 +4779,30 @@ rxi_ReceiveAckPacket(struct rx_call *call, struct rx_packet *np,
     return np;
 }
 
+/**
+ * Schedule a connection abort to be sent after some delay.
+ *
+ * @param[in] conn The connection to send the abort on.
+ * @param[in] msec The number of milliseconds to wait before sending.
+ *
+ * @pre conn_data_lock must be held
+ */
+static void
+rxi_SendConnectionAbortLater(struct rx_connection *conn, int msec)
+{
+    struct clock when, now;
+    if (!conn->error) {
+       return;
+    }
+    if (!conn->delayedAbortEvent) {
+       clock_GetTime(&now);
+       when = now;
+       clock_Addmsec(&when, msec);
+       conn->delayedAbortEvent =
+           rxevent_Post(&when, &now, rxi_SendDelayedConnAbort, conn, NULL, 0);
+    }
+}
+
 /* Received a response to a challenge packet */
 static struct rx_packet *
 rxi_ReceiveResponsePacket(struct rx_connection *conn,
@@ -4736,13 +4822,12 @@ rxi_ReceiveResponsePacket(struct rx_connection *conn,
     error = RXS_CheckResponse(conn->securityObject, conn, np);
     if (error) {
        /* If the response is invalid, reset the connection, sending
-        * an abort to the peer */
-#ifndef KERNEL
-       rxi_Delay(1);
-#endif
+        * an abort to the peer. Send the abort with a 1 second delay,
+        * to avoid a peer hammering us by constantly recreating a
+        * connection with bad credentials. */
        rxi_ConnectionError(conn, error);
        MUTEX_ENTER(&conn->conn_data_lock);
-       np = rxi_SendConnectionAbort(conn, np, istack, 0);
+       rxi_SendConnectionAbortLater(conn, 1000);
        MUTEX_EXIT(&conn->conn_data_lock);
        return np;
     } else {
@@ -5136,7 +5221,6 @@ rxi_SendConnectionAbort(struct rx_connection *conn,
                        struct rx_packet *packet, int istack, int force)
 {
     afs_int32 error;
-    struct clock when, now;
 
     if (!conn->error)
        return packet;
@@ -5157,12 +5241,8 @@ rxi_SendConnectionAbort(struct rx_connection *conn,
                            RX_PACKET_TYPE_ABORT, (char *)&error,
                            sizeof(error), istack);
        MUTEX_ENTER(&conn->conn_data_lock);
-    } else if (!conn->delayedAbortEvent) {
-       clock_GetTime(&now);
-       when = now;
-       clock_Addmsec(&when, rxi_connAbortDelay);
-       conn->delayedAbortEvent =
-           rxevent_Post(&when, &now, rxi_SendDelayedConnAbort, conn, NULL, 0);
+    } else {
+       rxi_SendConnectionAbortLater(conn, rxi_connAbortDelay);
     }
     return packet;
 }
@@ -6641,6 +6721,19 @@ rxi_CancelGrowMTUEvent(struct rx_call *call)
     }
 }
 
+/*
+ * Increment the counter for the next connection ID, handling overflow.
+ */
+static void
+update_nextCid(void)
+{
+    /* Overflow is technically undefined behavior; avoid it. */
+    if (rx_nextCid > MAX_AFS_INT32 - (1 << RX_CIDSHIFT))
+       rx_nextCid = -1 * ((MAX_AFS_INT32 / RX_CIDSHIFT) * RX_CIDSHIFT);
+    else
+       rx_nextCid += 1 << RX_CIDSHIFT;
+}
+
 static void
 rxi_KeepAliveOn(struct rx_call *call)
 {
@@ -6744,6 +6837,13 @@ rxi_ChallengeEvent(struct rxevent *event,
     if (event)
        rxevent_Put(&conn->challengeEvent);
 
+    /* If there are no active calls it is not worth re-issuing the
+     * challenge.  If the client issues another call on this connection
+     * the challenge can be requested at that time.
+     */
+    if (!rxi_HasActiveCalls(conn))
+        return;
+
     if (RXS_CheckAuthentication(conn->securityObject, conn) != 0) {
        struct rx_packet *packet;
        struct clock when, now;
@@ -7845,11 +7945,9 @@ shutdown_rx(void)
     struct rx_serverQueueEntry *sq;
 #endif /* KERNEL */
 
-    LOCK_RX_INIT;
-    if (rxinit_status == 1) {
-       UNLOCK_RX_INIT;
+    if (rx_atomic_test_and_set_bit(&rxinit_status, 0))
        return;                 /* Already shutdown. */
-    }
+
 #ifndef KERNEL
     rx_port = 0;
 #ifndef AFS_PTHREAD_ENV
@@ -7973,8 +8071,6 @@ shutdown_rx(void)
     rxi_dataQuota = RX_MAX_QUOTA;
     rxi_availProcs = rxi_totalMin = rxi_minDeficit = 0;
     MUTEX_EXIT(&rx_quota_mutex);
-    rxinit_status = 1;
-    UNLOCK_RX_INIT;
 }
 
 #ifndef KERNEL
@@ -9061,7 +9157,7 @@ rx_clearProcessRPCStats(afs_uint32 clearFlag)
     for (opr_queue_Scan(&processStats, cursor)) {
        unsigned int num_funcs = 0, i;
        struct rx_interface_stat *rpc_stat
-            = opr_queue_Entry(rpc_stat, struct rx_interface_stat, entry);
+            = opr_queue_Entry(cursor, struct rx_interface_stat, entry);
 
        num_funcs = rpc_stat->stats[0].func_total;
        for (i = 0; i < num_funcs; i++) {