1    	/*
2    	 * Copyright 2015-2026 the Pacemaker project contributors
3    	 *
4    	 * The version control history for this file may have further details.
5    	 *
6    	 * This source code is licensed under the GNU Lesser General Public License
7    	 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8    	 */
9    	
10   	#include <crm_internal.h>
11   	
12   	#include <errno.h>                      // EACCES, ENXIO
13   	#include <stdbool.h>                    // bool, true
14   	#include <stddef.h>                     // NULL
15   	#include <stdint.h>                     // uint32_t
16   	#include <stdlib.h>                     // free
17   	#include <sys/types.h>                  // ssize_t
18   	#include <time.h>                       // time
19   	
20   	#include <glib.h>                       // g_*, etc.
21   	#include <libxml/tree.h>                // xmlNode
22   	
23   	#include <crm/cib.h>                    // cib_*
24   	#include <crm/common/internal.h>        // pcmk__xe_*, pcmk__xml_*, etc.
25   	#include <crm/common/ipc.h>             // crm_ipc_*
26   	#include <crm/common/iso8601.h>         // crm_time_*
27   	#include <crm/common/logging.h>         // CRM_CHECK, crm_log_xml_explicit
28   	#include <crm/common/mainloop.h>        // ipc_client_callbacks, mainloop_*
29   	#include <crm/common/nvpair.h>          // pcmk_unpack_nvpair_blocks
30   	#include <crm/common/options.h>         // PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS
31   	#include <crm/common/results.h>         // pcmk_ok, pcmk_rc_*, pcmk_strerror
32   	#include <crm/common/rules.h>           // pcmk_rule_input_t
33   	#include <crm/common/xml.h>             // PCMK_XA_*, PCMK_XE_*, etc.
34   	#include <crm/crm.h>                    // crm_system_name
35   	#include <crm/lrmd.h>                   // lrmd_t
36   	#include <crm/lrmd_internal.h>          // lrmd__*
37   	
38   	#include "pacemaker-controld.h"         // remote_proxy_*
39   	
40   	typedef struct {
41   	    char *node_name;
42   	    char *session_id;
43   	
44   	    bool is_local;
45   	
46   	    crm_ipc_t *ipc;
47   	    mainloop_io_t *source;
48   	    uint32_t last_request_id;
49   	    lrmd_t *lrm;
50   	} remote_proxy_t;
51   	
52   	static GHashTable *proxy_table = NULL;
53   	
54   	static void
55   	remote_proxy_free(gpointer data)
56   	{
57   	    remote_proxy_t *proxy = data;
58   	
59   	    pcmk__trace("Freed proxy session ID %s", proxy->session_id);
60   	    free(proxy->node_name);
61   	    free(proxy->session_id);
62   	    free(proxy);
63   	}
64   	
65   	void
66   	controld_remote_proxy_table_init(void)
67   	{
68   	    if (proxy_table != NULL) {
69   	        return;
70   	    }
71   	
72   	    proxy_table = pcmk__strikey_table(NULL, remote_proxy_free);
73   	}
74   	
75   	void
76   	controld_remote_proxy_table_free(void)
77   	{
CID (unavailable; MK=dc1acd037e1be73d814eac022da19cd7) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(1) Event assign_union_field: The union field "in" of "_pp" is written.
(2) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
78   	    g_clear_pointer(&proxy_table, g_hash_table_destroy);
79   	}
80   	
81   	static void
82   	remote_proxy_relay_response(remote_proxy_t *proxy, xmlNode *msg, int msg_id)
83   	{
84   	    /* sending to the remote node a response msg. */
85   	    xmlNode *response = pcmk__xe_create(NULL, PCMK__XE_LRMD_IPC_PROXY);
86   	    xmlNode *wrapper = NULL;
87   	
88   	    pcmk__xe_set(response, PCMK__XA_LRMD_IPC_OP, LRMD_IPC_OP_RESPONSE);
89   	    pcmk__xe_set(response, PCMK__XA_LRMD_IPC_SESSION, proxy->session_id);
90   	    pcmk__xe_set_int(response, PCMK__XA_LRMD_IPC_MSG_ID, msg_id);
91   	
92   	    wrapper = pcmk__xe_create(response, PCMK__XE_LRMD_IPC_MSG);
93   	    pcmk__xml_copy(wrapper, msg);
94   	
95   	    lrmd__proxy_send(proxy->lrm, response);
96   	    pcmk__xml_free(response);
97   	}
98   	
99   	static void
100  	remote_proxy_relay_event(remote_proxy_t *proxy, xmlNode *msg)
101  	{
102  	    /* sending to the remote node an event msg. */
103  	    xmlNode *event = pcmk__xe_create(NULL, PCMK__XE_LRMD_IPC_PROXY);
104  	    xmlNode *wrapper = NULL;
105  	
106  	    pcmk__xe_set(event, PCMK__XA_LRMD_IPC_OP, LRMD_IPC_OP_EVENT);
107  	    pcmk__xe_set(event, PCMK__XA_LRMD_IPC_SESSION, proxy->session_id);
108  	
109  	    wrapper = pcmk__xe_create(event, PCMK__XE_LRMD_IPC_MSG);
110  	    pcmk__xml_copy(wrapper, msg);
111  	
112  	    crm_log_xml_explicit(event, "EventForProxy");
113  	    lrmd__proxy_send(proxy->lrm, event);
114  	    pcmk__xml_free(event);
115  	}
116  	
117  	static int
118  	remote_proxy_dispatch(const char *buffer, ssize_t length, gpointer userdata)
119  	{
120  	    // Async responses from servers to clients via the remote executor
121  	    xmlNode *xml = NULL;
122  	    uint32_t flags = 0;
123  	    remote_proxy_t *proxy = userdata;
124  	
125  	    xml = pcmk__xml_parse(buffer);
126  	    if (xml == NULL) {
127  	        pcmk__warn("Received a NULL msg from IPC service.");
128  	        return 1;
129  	    }
130  	
131  	    flags = crm_ipc_buffer_flags(proxy->ipc);
132  	    if (flags & crm_ipc_proxied_relay_response) {
133  	        pcmk__trace("Passing response back to %.8s on %s: %.200s - request id: "
134  	                    "%d", proxy->session_id, proxy->node_name, buffer,
135  	                    proxy->last_request_id);
136  	        remote_proxy_relay_response(proxy, xml, proxy->last_request_id);
137  	        proxy->last_request_id = 0;
138  	
139  	    } else {
140  	        pcmk__trace("Passing event back to %.8s on %s: %.200s",
141  	                    proxy->session_id, proxy->node_name, buffer);
142  	        remote_proxy_relay_event(proxy, xml);
143  	    }
144  	    pcmk__xml_free(xml);
145  	    return 1;
146  	}
147  	
148  	static void
149  	remote_proxy_notify_destroy(lrmd_t *lrmd, const char *session_id)
150  	{
151  	    /* sending to the remote node that an ipc connection has been destroyed */
152  	    xmlNode *msg = pcmk__xe_create(NULL, PCMK__XE_LRMD_IPC_PROXY);
153  	    pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_OP, LRMD_IPC_OP_DESTROY);
154  	    pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_SESSION, session_id);
155  	    lrmd__proxy_send(lrmd, msg);
156  	    pcmk__xml_free(msg);
157  	}
158  	
159  	static void
160  	remote_proxy_disconnected(gpointer userdata)
161  	{
162  	    remote_proxy_t *proxy = userdata;
163  	
164  	    proxy->source = NULL;
165  	    proxy->ipc = NULL;
166  	
167  	    if(proxy->lrm) {
168  	        remote_proxy_notify_destroy(proxy->lrm, proxy->session_id);
169  	        proxy->lrm = NULL;
170  	    }
171  	
172  	    g_hash_table_remove(proxy_table, proxy->session_id);
173  	}
174  	
175  	static remote_proxy_t *
176  	remote_proxy_new(lrmd_t *lrmd, const char *node_name, const char *session_id,
177  	                 const char *channel)
178  	{
179  	    static struct ipc_client_callbacks callbacks = {
180  	        .dispatch = remote_proxy_dispatch,
181  	        .destroy = remote_proxy_disconnected
182  	    };
183  	
184  	    remote_proxy_t *proxy = NULL;
185  	
186  	    if(channel == NULL) {
187  	        pcmk__err("No channel specified to proxy");
188  	        remote_proxy_notify_destroy(lrmd, session_id);
189  	        return NULL;
190  	    }
191  	
192  	    proxy = pcmk__assert_alloc(1, sizeof(remote_proxy_t));
193  	
194  	    proxy->node_name = pcmk__str_copy(node_name);
195  	    proxy->session_id = pcmk__str_copy(session_id);
196  	    proxy->lrm = lrmd;
197  	
198  	    if ((pcmk__parse_server(crm_system_name) == pcmk_ipc_controld)
199  	        && (pcmk__parse_server(channel) == pcmk_ipc_controld)) {
200  	        // The controller doesn't need to connect to itself
201  	        proxy->is_local = true;
202  	
203  	    } else {
204  	        proxy->source = mainloop_add_ipc_client(channel, G_PRIORITY_LOW, 0,
205  	                                                proxy, &callbacks);
206  	        proxy->ipc = mainloop_get_ipc_client(proxy->source);
207  	        if (proxy->source == NULL) {
208  	            remote_proxy_free(proxy);
209  	            remote_proxy_notify_destroy(lrmd, session_id);
210  	            return NULL;
211  	        }
212  	    }
213  	
214  	    pcmk__trace("New remote proxy client established to %s on %s, session id "
215  	                "%s", channel, node_name, session_id);
216  	    g_hash_table_insert(proxy_table, proxy->session_id, proxy);
217  	
218  	    return proxy;
219  	}
220  	
221  	int
222  	controld_remote_proxy_send(const char *session, xmlNode *msg)
223  	{
224  	    remote_proxy_t *proxy = g_hash_table_lookup(proxy_table, session);
225  	
226  	    if (proxy == NULL) {
227  	        return ENXIO;
228  	    }
229  	
230  	    if (controld_get_executor_state(proxy->node_name, false) == NULL) {
231  	        return pcmk_rc_ok;
232  	    }
233  	
234  	    pcmk__trace("Sending event to %.8s on %s", proxy->session_id,
235  	                proxy->node_name);
236  	    remote_proxy_relay_event(proxy, msg);
237  	    return pcmk_rc_ok;
238  	}
239  	
240  	static void
241  	remote_config_check(xmlNode *msg, int call_id, int rc, xmlNode *output,
242  	                    void *user_data)
243  	{
244  	    lrmd_t *lrmd = user_data;
245  	    GHashTable *config_hash = NULL;
246  	    crm_time_t *now = NULL;
247  	    pcmk_rule_input_t rule_input = { NULL, };
248  	
249  	    if (rc != pcmk_ok) {
250  	        pcmk__err("Query resulted in an error: %s", pcmk_strerror(rc));
251  	
252  	        if ((rc == -EACCES) || (rc == -pcmk_err_schema_validation)) {
253  	            pcmk__err("The cluster is misconfigured - shutting down and "
254  	                      "staying down");
255  	        }
256  	
257  	        return;
258  	    }
259  	
260  	    config_hash = pcmk__strkey_table(free, free);
261  	    now = crm_time_new(NULL);
262  	    rule_input.now = now;
263  	
264  	    pcmk__debug("Call %d : Parsing CIB options", call_id);
265  	    if (output != NULL) {
266  	        pcmk__unpack_nvpair_blocks(output, PCMK_XE_CLUSTER_PROPERTY_SET,
267  	                                   PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS,
268  	                                   &rule_input, config_hash, NULL, output->doc);
269  	    }
270  	
271  	    // Now send it to the remote peer
272  	    lrmd__validate_remote_settings(lrmd, config_hash);
273  	
274  	    g_hash_table_destroy(config_hash);
275  	    crm_time_free(now);
276  	}
277  	
278  	/*!
279  	 * \internal
280  	 * \brief Acknowledge or reject a remote proxy shutdown request
281  	 *
282  	 * \param[in,out] lrmd  Connection to proxy
283  	 * \param[in]     ack   If \c true, send ack; if \c false, send nack
284  	 */
285  	static void
286  	remote_proxy_ack_shutdown(lrmd_t *lrmd, bool ack)
287  	{
288  	    xmlNode *msg = pcmk__xe_create(NULL, PCMK__XE_LRMD_IPC_PROXY);
289  	
290  	    pcmk__xe_set(msg, PCMK__XA_LRMD_IPC_OP,
291  	                 (ack? LRMD_IPC_OP_SHUTDOWN_ACK : LRMD_IPC_OP_SHUTDOWN_NACK));
292  	    lrmd__proxy_send(lrmd, msg);
293  	    pcmk__xml_free(msg);
294  	}
295  	
296  	static void
297  	crmd_proxy_dispatch(const char *session, xmlNode *msg)
298  	{
299  	    pcmk__trace("Processing proxied IPC message from session %s", session);
300  	    pcmk__log_xml_trace(msg, "controller[inbound]");
301  	    pcmk__xe_set(msg, PCMK__XA_CRM_SYS_FROM, session);
302  	    if (controld_authorize_ipc_message(msg, NULL, session)) {
303  	        route_message(C_IPC_MESSAGE, msg);
304  	    }
305  	    controld_trigger_fsa();
306  	}
307  	
308  	static void
309  	remote_proxy_end_session(remote_proxy_t *proxy)
310  	{
311  	    if (proxy == NULL) {
312  	        return;
313  	    }
314  	    pcmk__trace("Ending session ID %s", proxy->session_id);
315  	
316  	    if (proxy->source) {
317  	        mainloop_del_ipc_client(proxy->source);
318  	    }
319  	}
320  	
321  	void
322  	controld_remote_proxy_cb(lrmd_t *lrmd, void *user_data, xmlNode *msg)
323  	{
324  	    lrm_state_t *lrm_state = user_data;
325  	    const char *op = pcmk__xe_get(msg, PCMK__XA_LRMD_IPC_OP);
326  	    const char *session = pcmk__xe_get(msg, PCMK__XA_LRMD_IPC_SESSION);
327  	    remote_proxy_t *proxy = g_hash_table_lookup(proxy_table, session);
328  	    int msg_id = 0;
329  	
330  	    /* sessions are raw ipc connections to IPC,
331  	     * all we do is proxy requests/responses exactly
332  	     * like they are given to us at the ipc level. */
333  	
334  	    CRM_CHECK((op != NULL) && (session != NULL), return);
335  	
336  	    pcmk__xe_get_int(msg, PCMK__XA_LRMD_IPC_MSG_ID, &msg_id);
337  	    /* This is msg from remote ipc client going to real ipc server */
338  	
339  	    if (pcmk__str_eq(op, LRMD_IPC_OP_NEW, pcmk__str_casei)) {
340  	        const char *channel = pcmk__xe_get(msg, PCMK__XA_LRMD_IPC_SERVER);
341  	
342  	        proxy = remote_proxy_new(lrmd, lrm_state->node_name, session, channel);
343  	
344  	        if (!remote_ra_controlling_guest(lrm_state)) {
345  	            if (proxy != NULL) {
346  	                cib_t *cib_conn = controld_globals.cib_conn;
347  	
348  	                /* Look up PCMK_OPT_FENCING_WATCHDOG_TIMEOUT and send to the
349  	                 * remote peer for validation
350  	                 */
351  	                int rc = cib_conn->cmds->query(cib_conn, PCMK_XE_CRM_CONFIG,
352  	                                               NULL, cib_none);
353  	                cib_conn->cmds->register_callback_full(cib_conn, rc, 10, FALSE,
354  	                                                       lrmd,
355  	                                                       "remote_config_check",
356  	                                                       remote_config_check,
357  	                                                       NULL);
358  	            }
359  	        } else {
360  	            pcmk__debug("Skipping remote_config_check for guest-nodes");
361  	        }
362  	
363  	    } else if (pcmk__str_eq(op, LRMD_IPC_OP_SHUTDOWN_REQ, pcmk__str_casei)) {
364  	        char *now_s = NULL;
365  	
366  	        pcmk__notice("%s requested shutdown of its remote connection",
367  	                     lrm_state->node_name);
368  	
369  	        if (!remote_ra_is_in_maintenance(lrm_state)) {
370  	            now_s = pcmk__ttoa(time(NULL));
371  	            update_attrd(lrm_state->node_name, PCMK__NODE_ATTR_SHUTDOWN, now_s,
372  	                         true);
373  	            free(now_s);
374  	
375  	            remote_proxy_ack_shutdown(lrmd, true);
376  	
377  	            pcmk__warn("Reconnection attempts to %s may result in failures "
378  	                       "that must be cleared",
379  	                       lrm_state->node_name);
380  	        } else {
381  	            remote_proxy_ack_shutdown(lrmd, false);
382  	
383  	            pcmk__notice("Remote resource for %s is not managed so no ordered "
384  	                         "shutdown happening",
385  	                         lrm_state->node_name);
386  	        }
387  	        return;
388  	
389  	    } else if (pcmk__str_eq(op, LRMD_IPC_OP_REQUEST, pcmk__str_casei)
390  	               && (proxy != NULL) && proxy->is_local) {
391  	        /* This is for the controller, which we are, so don't try
392  	         * to send to ourselves over IPC -- do it directly.
393  	         */
394  	        uint32_t flags = 0U;
395  	        int rc = pcmk_rc_ok;
396  	        xmlNode *wrapper = pcmk__xe_first_child(msg, PCMK__XE_LRMD_IPC_MSG,
397  	                                                NULL, NULL);
398  	        xmlNode *request = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
399  	
400  	        CRM_CHECK(request != NULL, return);
401  	        CRM_CHECK(lrm_state->node_name, return);
402  	        pcmk__xe_set(request, PCMK_XE_ACL_ROLE, "pacemaker-remote");
403  	        pcmk__update_acl_user(request, PCMK__XA_LRMD_IPC_USER,
404  	                              lrm_state->node_name);
405  	
406  	        /* Pacemaker Remote nodes don't know their own names (as known to the
407  	         * cluster). When getting a node info request with no name or ID, add
408  	         * the name, so we don't return info for ourselves instead of the
409  	         * Pacemaker Remote node.
410  	         */
411  	        if (pcmk__str_eq(pcmk__xe_get(request, PCMK__XA_CRM_TASK),
412  	                         CRM_OP_NODE_INFO, pcmk__str_none)) {
413  	            int node_id = 0;
414  	
415  	            pcmk__xe_get_int(request, PCMK_XA_ID, &node_id);
416  	            if ((node_id <= 0)
417  	                && (pcmk__xe_get(request, PCMK_XA_UNAME) == NULL)) {
418  	                pcmk__xe_set(request, PCMK_XA_UNAME, lrm_state->node_name);
419  	            }
420  	        }
421  	
422  	        crmd_proxy_dispatch(session, request);
423  	
424  	        rc = pcmk__xe_get_flags(msg, PCMK__XA_LRMD_IPC_MSG_FLAGS, &flags, 0U);
425  	        if (rc != pcmk_rc_ok) {
426  	            pcmk__warn("Couldn't parse controller flags from remote request: "
427  	                       "%s",
428  	                       pcmk_rc_str(rc));
429  	        }
430  	        if (pcmk__is_set(flags, crm_ipc_client_response)) {
431  	            int msg_id = 0;
432  	            xmlNode *op_reply = pcmk__xe_create(NULL, PCMK__XE_ACK);
433  	
434  	            pcmk__xe_set(op_reply, PCMK_XA_FUNCTION, __func__);
435  	            pcmk__xe_set_int(op_reply, PCMK__XA_LINE, __LINE__);
436  	
437  	            pcmk__xe_get_int(msg, PCMK__XA_LRMD_IPC_MSG_ID, &msg_id);
438  	            remote_proxy_relay_response(proxy, op_reply, msg_id);
439  	
440  	            pcmk__xml_free(op_reply);
441  	        }
442  	
443  	    } else if (pcmk__str_eq(op, LRMD_IPC_OP_DESTROY, pcmk__str_casei)) {
444  	        remote_proxy_end_session(proxy);
445  	
446  	    } else if (pcmk__str_eq(op, LRMD_IPC_OP_REQUEST, pcmk__str_casei)) {
447  	        uint32_t flags = 0U;
448  	        int rc = pcmk_rc_ok;
449  	        const char *name = pcmk__xe_get(msg, PCMK__XA_LRMD_IPC_CLIENT);
450  	
451  	        xmlNode *wrapper = pcmk__xe_first_child(msg, PCMK__XE_LRMD_IPC_MSG,
452  	                                                NULL, NULL);
453  	        xmlNode *request = pcmk__xe_first_child(wrapper, NULL, NULL, NULL);
454  	
455  	        CRM_CHECK(request != NULL, return);
456  	
457  	        if (proxy == NULL) {
458  	            /* proxy connection no longer exists */
459  	            remote_proxy_notify_destroy(lrmd, session);
460  	            return;
461  	        }
462  	
463  	        // Controller requests MUST be handled by the controller, not us
464  	        CRM_CHECK(!proxy->is_local,
465  	                  remote_proxy_end_session(proxy); return);
466  	
467  	        if (!crm_ipc_connected(proxy->ipc)) {
468  	            remote_proxy_end_session(proxy);
469  	            return;
470  	        }
471  	        proxy->last_request_id = 0;
472  	        pcmk__xe_set(request, PCMK_XE_ACL_ROLE, "pacemaker-remote");
473  	
474  	        rc = pcmk__xe_get_flags(msg, PCMK__XA_LRMD_IPC_MSG_FLAGS, &flags, 0U);
475  	        if (rc != pcmk_rc_ok) {
476  	            pcmk__warn("Couldn't parse controller flags from remote request: "
477  	                       "%s",
478  	                       pcmk_rc_str(rc));
479  	        }
480  	
481  	        pcmk__assert(lrm_state->node_name != NULL);
482  	        pcmk__update_acl_user(request, PCMK__XA_LRMD_IPC_USER,
483  	                              lrm_state->node_name);
484  	
485  	        if (pcmk__is_set(flags, crm_ipc_proxied)) {
486  	            const char *type = pcmk__xe_get(request, PCMK__XA_T);
487  	            int rc = 0;
488  	
489  	            if (pcmk__str_eq(type, PCMK__VALUE_ATTRD, pcmk__str_none)
490  	                && (pcmk__xe_get(request, PCMK__XA_ATTR_HOST) == NULL)
491  	                && pcmk__str_any_of(pcmk__xe_get(request, PCMK_XA_TASK),
492  	                                    PCMK__ATTRD_CMD_UPDATE,
493  	                                    PCMK__ATTRD_CMD_UPDATE_BOTH,
494  	                                    PCMK__ATTRD_CMD_UPDATE_DELAY, NULL)) {
495  	
496  	                pcmk__xe_set(request, PCMK__XA_ATTR_HOST, proxy->node_name);
497  	            }
498  	
499  	            rc = crm_ipc_send(proxy->ipc, request, flags, 5000, NULL);
500  	
501  	            if (rc < 0) {
502  	                xmlNode *op_reply = pcmk__xe_create(NULL, PCMK__XE_NACK);
503  	
504  	                pcmk__err("Could not relay request %d from %s to %s for %s: "
505  	                          "%s (%d)",
506  	                          msg_id, proxy->node_name, crm_ipc_name(proxy->ipc),
507  	                          name, pcmk_strerror(rc), rc);
508  	
509  	                /* Send a NACK to the caller (for instance, a program like
510  	                 * cibadmin or crm_mon running on the remote node) so it doesn't
511  	                 * block waiting for a reply.  Nothing actually checks that it
512  	                 * receives a PCMK__XE_NACK, but it's got to receive something
513  	                 * and since this message isn't being used anywhere else, it's
514  	                 * a good one to use.
515  	                 */
516  	                pcmk__xe_set(op_reply, PCMK_XA_FUNCTION, __func__);
517  	                pcmk__xe_set_int(op_reply, PCMK__XA_LINE, __LINE__);
518  	                pcmk__xe_set_int(op_reply, PCMK_XA_RC, rc);
519  	                remote_proxy_relay_response(proxy, op_reply, msg_id);
520  	                pcmk__xml_free(op_reply);
521  	                return;
522  	            }
523  	
524  	            pcmk__trace("Relayed request %d from %s to %s for %s", msg_id,
525  	                        proxy->node_name, crm_ipc_name(proxy->ipc), name);
526  	            proxy->last_request_id = msg_id;
527  	
528  	        } else {
529  	            int rc = pcmk_ok;
530  	            xmlNode *op_reply = NULL;
531  	            // @COMPAT pacemaker_remoted <= 1.1.10
532  	
533  	            pcmk__trace("Relaying request %d from %s to %s for %s", msg_id,
534  	                        proxy->node_name, crm_ipc_name(proxy->ipc), name);
535  	
536  	            rc = crm_ipc_send(proxy->ipc, request, flags, 10000, &op_reply);
537  	            if(rc < 0) {
538  	                pcmk__err("Could not relay request %d from %s to %s for %s: "
539  	                          "%s (%d)",
540  	                          msg_id, proxy->node_name, crm_ipc_name(proxy->ipc),
541  	                          name, pcmk_strerror(rc), rc);
542  	            } else {
543  	                pcmk__trace("Relayed request %d from %s to %s for %s", msg_id,
544  	                            proxy->node_name, crm_ipc_name(proxy->ipc), name);
545  	            }
546  	
547  	            if(op_reply) {
548  	                remote_proxy_relay_response(proxy, op_reply, msg_id);
549  	                pcmk__xml_free(op_reply);
550  	            }
551  	        }
552  	    } else {
553  	        pcmk__err("Unknown proxy operation: %s", op);
554  	    }
555  	}
556  	
557  	static remote_proxy_t *
558  	find_proxy_by_node(const char *node_name)
559  	{
560  	    GHashTableIter gIter;
561  	    remote_proxy_t *proxy = NULL;
562  	
563  	    g_hash_table_iter_init(&gIter, proxy_table);
564  	
565  	    while (g_hash_table_iter_next(&gIter, NULL, (gpointer *) &proxy)) {
566  	        if (proxy->source
567  	            && pcmk__str_eq(node_name, proxy->node_name, pcmk__str_casei)) {
568  	            return proxy;
569  	        }
570  	    }
571  	
572  	    return NULL;
573  	}
574  	
575  	static gboolean
576  	remote_proxy_node_matches(void *key, void *value, void *user_data)
577  	{
578  	    remote_proxy_t *proxy = value;
579  	    const char *node_name = user_data;
580  	
581  	    return pcmk__str_eq(proxy->node_name, node_name, pcmk__str_casei);
582  	}
583  	
584  	void
585  	controld_remote_proxy_disconnect_node(const char *node_name)
586  	{
587  	    remote_proxy_t *proxy = NULL;
588  	
589  	    CRM_CHECK(proxy_table != NULL, return);
590  	
591  	    proxy = find_proxy_by_node(node_name);
592  	
593  	    while (proxy != NULL) {
594  	        /* mainloop_del_ipc_client() eventually calls remote_proxy_disconnected()
595  	         * , which removes the entry from proxy_table.
596  	         * Do not do this in a g_hash_table_iter_next() loop. */
597  	        if (proxy->source) {
598  	            mainloop_del_ipc_client(proxy->source);
599  	        }
600  	
601  	        proxy = find_proxy_by_node(node_name);
602  	    }
603  	
604  	    /* In case there are any leftovers in proxy_table
605  	     *
606  	     * @TODO Is that even possible?
607  	     */
608  	    g_hash_table_foreach_remove(proxy_table, remote_proxy_node_matches,
609  	                                (void *) node_name);
610  	}
611