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