#endif /* KERNEL */
#include <opr/queue.h>
+#include <hcrypto/rand.h>
#include "rx.h"
#include "rx_clock.h"
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,
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 {
* 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:
#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:
#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)
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
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)
#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);
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
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);
+ }
}
/*!
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;
}
int serviceSecurityIndex)
{
int hashindex, i;
- afs_int32 cid;
struct rx_connection *conn;
SPLVAR;
#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;
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);
* 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;
}
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);
afs_winsockCleanup();
#endif
- rxinit_status = 1;
- UNLOCK_RX_INIT;
}
#endif
#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;
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
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
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 =
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 */
* 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;
struct rx_call *call;
struct rx_connection *conn;
int type;
+ int unknownService = 0;
#ifdef RXDEBUG
char *packetType;
#endif
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;
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,
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 {
struct rx_packet *packet, int istack, int force)
{
afs_int32 error;
- struct clock when, now;
if (!conn->error)
return packet;
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;
}
}
}
+/*
+ * 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)
{
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;
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
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
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++) {