1514 lines · 39.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/*****************************************************************************************************************************************************************  Copyright (C) Sistina Software, Inc.  1997-2003  All rights reserved.**  Copyright (C) 2004-2021 Red Hat, Inc.  All rights reserved.*****************************************************************************************************************************************************************/ /* * midcomms.c * * This is the appallingly named "mid-level" comms layer. It takes care about * deliver an on application layer "reliable" communication above the used * lowcomms transport layer. * * How it works: * * Each nodes keeps track of all send DLM messages in send_queue with a sequence * number. The receive will send an DLM_ACK message back for every DLM message * received at the other side. If a reconnect happens in lowcomms we will send * all unacknowledged dlm messages again. The receiving side might drop any already * received message by comparing sequence numbers. * * How version detection works: * * Due the fact that dlm has pre-configured node addresses on every side * it is in it's nature that every side connects at starts to transmit * dlm messages which ends in a race. However DLM_RCOM_NAMES, DLM_RCOM_STATUS * and their replies are the first messages which are exchanges. Due backwards * compatibility these messages are not covered by the midcomms re-transmission * layer. These messages have their own re-transmission handling in the dlm * application layer. The version field of every node will be set on these RCOM * messages as soon as they arrived and the node isn't yet part of the nodes * hash. There exists also logic to detect version mismatched if something weird * going on or the first messages isn't an expected one. * * Termination: * * The midcomms layer does a 4 way handshake for termination on DLM protocol * like TCP supports it with half-closed socket support. SCTP doesn't support * half-closed socket, so we do it on DLM layer. Also socket shutdown() can be * interrupted by .e.g. tcp reset itself. Additional there exists the othercon * paradigm in lowcomms which cannot be easily without breaking backwards * compatibility. A node cannot send anything to another node when a DLM_FIN * message was send. There exists additional logic to print a warning if * DLM wants to do it. There exists a state handling like RFC 793 but reduced * to termination only. The event "member removal event" describes the cluster * manager removed the node from internal lists, at this point DLM does not * send any message to the other node. There exists two cases: * * 1. The cluster member was removed and we received a FIN * OR * 2. We received a FIN but the member was not removed yet * * One of these cases will do the CLOSE_WAIT to LAST_ACK change. * * *                              +---------+ *                              | CLOSED  | *                              +---------+ *                                   | add member/receive RCOM version *                                   |            detection msg *                                   V *                              +---------+ *                              |  ESTAB  | *                              +---------+ *                       CLOSE    |     |    rcv FIN *                      -------   |     |    ------- * +---------+          snd FIN  /       \   snd ACK          +---------+ * |  FIN    |<-----------------           ------------------>|  CLOSE  | * | WAIT-1  |------------------                              |   WAIT  | * +---------+          rcv FIN  \                            +---------+ * | rcv ACK of FIN   -------   |                            CLOSE  | member * | --------------   snd ACK   |                           ------- | removal * V        x                   V                           snd FIN V event * +---------+                  +---------+                   +---------+ * |FINWAIT-2|                  | CLOSING |                   | LAST-ACK| * +---------+                  +---------+                   +---------+ * |                rcv ACK of FIN |                 rcv ACK of FIN | * |  rcv FIN       -------------- |                 -------------- | * |  -------              x       V                        x       V *  \ snd ACK                 +---------+                   +---------+ *   ------------------------>| CLOSED  |                   | CLOSED  | *                            +---------+                   +---------+ * * NOTE: any state can interrupted by midcomms_close() and state will be * switched to CLOSED in case of fencing. There exists also some timeout * handling when we receive the version detection RCOM messages which is * made by observation. * * Future improvements: * * There exists some known issues/improvements of the dlm handling. Some * of them should be done in a next major dlm version bump which makes * it incompatible with previous versions. * * Unaligned memory access: * * There exists cases when the dlm message buffer length is not aligned * to 8 byte. However seems nobody detected any problem with it. This * can be fixed in the next major version bump of dlm. * * Version detection: * * The version detection and how it's done is related to backwards * compatibility. There exists better ways to make a better handling. * However this should be changed in the next major version bump of dlm. * * Tail Size checking: * * There exists a message tail payload in e.g. DLM_MSG however we don't * check it against the message length yet regarding to the receive buffer * length. That need to be validated. * * Fencing bad nodes: * * At timeout places or weird sequence number behaviours we should send * a fencing request to the cluster manager. */ /* Debug switch to enable a 5 seconds sleep waiting of a termination. * This can be useful to test fencing while termination is running. * This requires a setup with only gfs2 as dlm user, so that the * last umount will terminate the connection. * * However it became useful to test, while the 5 seconds block in umount * just press the reset button. In a lot of dropping the termination * process can could take several seconds. */#define DLM_DEBUG_FENCE_TERMINATION	0 #include <trace/events/dlm.h>#include <net/tcp.h> #include "dlm_internal.h"#include "lowcomms.h"#include "config.h"#include "memory.h"#include "lock.h"#include "util.h"#include "midcomms.h" /* init value for sequence numbers for testing purpose only e.g. overflows */#define DLM_SEQ_INIT		0/* 5 seconds wait to sync ending of dlm */#define DLM_SHUTDOWN_TIMEOUT	msecs_to_jiffies(5000)#define DLM_VERSION_NOT_SET	0#define DLM_SEND_ACK_BACK_MSG_THRESHOLD 32#define DLM_RECV_ACK_BACK_MSG_THRESHOLD (DLM_SEND_ACK_BACK_MSG_THRESHOLD * 8) struct midcomms_node {	int nodeid;	uint32_t version;	atomic_t seq_send;	atomic_t seq_next;	/* These queues are unbound because we cannot drop any message in dlm.	 * We could send a fence signal for a specific node to the cluster	 * manager if queues hits some maximum value, however this handling	 * not supported yet.	 */	struct list_head send_queue;	spinlock_t send_queue_lock;	atomic_t send_queue_cnt;#define DLM_NODE_FLAG_CLOSE	1#define DLM_NODE_FLAG_STOP_TX	2#define DLM_NODE_FLAG_STOP_RX	3	atomic_t ulp_delivered;	unsigned long flags;	wait_queue_head_t shutdown_wait; 	/* dlm tcp termination state */#define DLM_CLOSED	1#define DLM_ESTABLISHED	2#define DLM_FIN_WAIT1	3#define DLM_FIN_WAIT2	4#define DLM_CLOSE_WAIT	5#define DLM_LAST_ACK	6#define DLM_CLOSING	7	int state;	spinlock_t state_lock; 	/* counts how many lockspaces are using this node	 * this refcount is necessary to determine if the	 * node wants to disconnect.	 */	int users; 	/* not protected by srcu, node_hash lifetime */	void *debugfs; 	struct hlist_node hlist;	struct rcu_head rcu;}; struct dlm_mhandle {	const union dlm_packet *inner_p;	struct midcomms_node *node;	struct dlm_opts *opts;	struct dlm_msg *msg;	bool committed;	uint32_t seq; 	void (*ack_rcv)(struct midcomms_node *node); 	/* get_mhandle/commit srcu idx exchange */	int idx; 	struct list_head list;	struct rcu_head rcu;}; static struct hlist_head node_hash[CONN_HASH_SIZE];static DEFINE_SPINLOCK(nodes_lock);DEFINE_STATIC_SRCU(nodes_srcu); /* This mutex prevents that midcomms_close() is running while * stop() or remove(). As I experienced invalid memory access * behaviours when DLM_DEBUG_FENCE_TERMINATION is enabled and * resetting machines. I will end in some double deletion in nodes * datastructure. */static DEFINE_MUTEX(close_lock); struct kmem_cache *dlm_midcomms_cache_create(void){	return KMEM_CACHE(dlm_mhandle, 0);} static inline const char *dlm_state_str(int state){	switch (state) {	case DLM_CLOSED:		return "CLOSED";	case DLM_ESTABLISHED:		return "ESTABLISHED";	case DLM_FIN_WAIT1:		return "FIN_WAIT1";	case DLM_FIN_WAIT2:		return "FIN_WAIT2";	case DLM_CLOSE_WAIT:		return "CLOSE_WAIT";	case DLM_LAST_ACK:		return "LAST_ACK";	case DLM_CLOSING:		return "CLOSING";	default:		return "UNKNOWN";	}} const char *dlm_midcomms_state(struct midcomms_node *node){	return dlm_state_str(node->state);} unsigned long dlm_midcomms_flags(struct midcomms_node *node){	return node->flags;} int dlm_midcomms_send_queue_cnt(struct midcomms_node *node){	return atomic_read(&node->send_queue_cnt);} uint32_t dlm_midcomms_version(struct midcomms_node *node){	return node->version;} static struct midcomms_node *__find_node(int nodeid, int r){	struct midcomms_node *node; 	hlist_for_each_entry_srcu(node, &node_hash[r], hlist,			srcu_read_lock_held(&nodes_srcu)) {		if (node->nodeid == nodeid)			return node;	} 	return NULL;} static void dlm_mhandle_release(struct rcu_head *rcu){	struct dlm_mhandle *mh = container_of(rcu, struct dlm_mhandle, rcu); 	dlm_lowcomms_put_msg(mh->msg);	dlm_free_mhandle(mh);} static void dlm_mhandle_delete(struct midcomms_node *node,			       struct dlm_mhandle *mh){	list_del_rcu(&mh->list);	atomic_dec(&node->send_queue_cnt);	call_rcu(&mh->rcu, dlm_mhandle_release);} static void dlm_send_queue_flush(struct midcomms_node *node){	struct dlm_mhandle *mh; 	pr_debug("flush midcomms send queue of node %d\n", node->nodeid); 	rcu_read_lock();	spin_lock_bh(&node->send_queue_lock);	list_for_each_entry_rcu(mh, &node->send_queue, list) {		dlm_mhandle_delete(node, mh);	}	spin_unlock_bh(&node->send_queue_lock);	rcu_read_unlock();} static void midcomms_node_reset(struct midcomms_node *node){	pr_debug("reset node %d\n", node->nodeid); 	atomic_set(&node->seq_next, DLM_SEQ_INIT);	atomic_set(&node->seq_send, DLM_SEQ_INIT);	atomic_set(&node->ulp_delivered, 0);	node->version = DLM_VERSION_NOT_SET;	node->flags = 0; 	dlm_send_queue_flush(node);	node->state = DLM_CLOSED;	wake_up(&node->shutdown_wait);} static struct midcomms_node *nodeid2node(int nodeid){	return __find_node(nodeid, nodeid_hash(nodeid));} int dlm_midcomms_addr(int nodeid, struct sockaddr_storage *addr){	int ret, idx, r = nodeid_hash(nodeid);	struct midcomms_node *node; 	ret = dlm_lowcomms_addr(nodeid, addr);	if (ret)		return ret; 	idx = srcu_read_lock(&nodes_srcu);	node = __find_node(nodeid, r);	if (node) {		srcu_read_unlock(&nodes_srcu, idx);		return 0;	}	srcu_read_unlock(&nodes_srcu, idx); 	node = kmalloc_obj(*node, GFP_NOFS);	if (!node)		return -ENOMEM; 	node->debugfs = dlm_create_debug_comms_file(nodeid, node);	node->nodeid = nodeid;	spin_lock_init(&node->state_lock);	spin_lock_init(&node->send_queue_lock);	atomic_set(&node->send_queue_cnt, 0);	INIT_LIST_HEAD(&node->send_queue);	init_waitqueue_head(&node->shutdown_wait);	node->users = 0;	midcomms_node_reset(node); 	spin_lock_bh(&nodes_lock);	hlist_add_head_rcu(&node->hlist, &node_hash[r]);	spin_unlock_bh(&nodes_lock); 	return 0;} static int dlm_send_ack(int nodeid, uint32_t seq){	int mb_len = sizeof(struct dlm_header);	struct dlm_header *m_header;	struct dlm_msg *msg;	char *ppc; 	msg = dlm_lowcomms_new_msg(nodeid, mb_len, &ppc, NULL, NULL);	if (!msg)		return -ENOMEM; 	m_header = (struct dlm_header *)ppc; 	m_header->h_version = cpu_to_le32(DLM_HEADER_MAJOR | DLM_HEADER_MINOR);	m_header->h_nodeid = cpu_to_le32(dlm_our_nodeid());	m_header->h_length = cpu_to_le16(mb_len);	m_header->h_cmd = DLM_ACK;	m_header->u.h_seq = cpu_to_le32(seq); 	dlm_lowcomms_commit_msg(msg);	dlm_lowcomms_put_msg(msg); 	return 0;} static void dlm_send_ack_threshold(struct midcomms_node *node,				   uint32_t threshold){	uint32_t oval, nval;	bool send_ack; 	/* let only send one user trigger threshold to send ack back */	do {		oval = atomic_read(&node->ulp_delivered);		send_ack = (oval > threshold);		/* abort if threshold is not reached */		if (!send_ack)			break; 		nval = 0;		/* try to reset ulp_delivered counter */	} while (atomic_cmpxchg(&node->ulp_delivered, oval, nval) != oval); 	if (send_ack)		dlm_send_ack(node->nodeid, atomic_read(&node->seq_next));} static int dlm_send_fin(struct midcomms_node *node,			void (*ack_rcv)(struct midcomms_node *node)){	int mb_len = sizeof(struct dlm_header);	struct dlm_header *m_header;	struct dlm_mhandle *mh;	char *ppc; 	mh = dlm_midcomms_get_mhandle(node->nodeid, mb_len, &ppc);	if (!mh)		return -ENOMEM; 	set_bit(DLM_NODE_FLAG_STOP_TX, &node->flags);	mh->ack_rcv = ack_rcv; 	m_header = (struct dlm_header *)ppc; 	m_header->h_version = cpu_to_le32(DLM_HEADER_MAJOR | DLM_HEADER_MINOR);	m_header->h_nodeid = cpu_to_le32(dlm_our_nodeid());	m_header->h_length = cpu_to_le16(mb_len);	m_header->h_cmd = DLM_FIN; 	pr_debug("sending fin msg to node %d\n", node->nodeid);	dlm_midcomms_commit_mhandle(mh, NULL, 0); 	return 0;} static void dlm_receive_ack(struct midcomms_node *node, uint32_t seq){	struct dlm_mhandle *mh; 	rcu_read_lock();	list_for_each_entry_rcu(mh, &node->send_queue, list) {		if (before(mh->seq, seq)) {			if (mh->ack_rcv)				mh->ack_rcv(node);		} else {			/* send queue should be ordered */			break;		}	} 	spin_lock_bh(&node->send_queue_lock);	list_for_each_entry_rcu(mh, &node->send_queue, list) {		if (before(mh->seq, seq)) {			dlm_mhandle_delete(node, mh);		} else {			/* send queue should be ordered */			break;		}	}	spin_unlock_bh(&node->send_queue_lock);	rcu_read_unlock();} static void dlm_pas_fin_ack_rcv(struct midcomms_node *node){	spin_lock_bh(&node->state_lock);	pr_debug("receive passive fin ack from node %d with state %s\n",		 node->nodeid, dlm_state_str(node->state)); 	switch (node->state) {	case DLM_LAST_ACK:		/* DLM_CLOSED */		midcomms_node_reset(node);		break;	case DLM_CLOSED:		/* not valid but somehow we got what we want */		wake_up(&node->shutdown_wait);		break;	default:		spin_unlock_bh(&node->state_lock);		log_print("%s: unexpected state: %d",			  __func__, node->state);		WARN_ON_ONCE(1);		return;	}

Lines 1–500 of 1514.