/src/pacemaker/lib/common/ipc_common.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2004-2025 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 <stdbool.h> |
13 | | #include <stdio.h> |
14 | | #include <stdint.h> // uint64_t |
15 | | #include <sys/types.h> |
16 | | |
17 | | #include <crm/common/xml.h> |
18 | | #include "crmcommon_private.h" |
19 | | |
20 | | /* The IPC buffer is always 128k. If we are asked to send a message larger |
21 | | * than that size, it will be split into multiple messages that must be |
22 | | * reassembled on the other end. |
23 | | */ |
24 | 0 | #define BUFFER_SIZE (128*1024) // 128k |
25 | | |
26 | | /*! |
27 | | * \brief Return pacemaker's IPC buffer size |
28 | | * |
29 | | * \return IPC buffer size in bytes |
30 | | */ |
31 | | unsigned int |
32 | | crm_ipc_default_buffer_size(void) |
33 | 0 | { |
34 | 0 | return BUFFER_SIZE; |
35 | 0 | } |
36 | | |
37 | | /*! |
38 | | * \internal |
39 | | * \brief Check whether an IPC header is valid |
40 | | * |
41 | | * \param[in] header IPC header to check |
42 | | * |
43 | | * \return true if IPC header has a supported version, false otherwise |
44 | | */ |
45 | | bool |
46 | | pcmk__valid_ipc_header(const pcmk__ipc_header_t *header) |
47 | 0 | { |
48 | 0 | if (header == NULL) { |
49 | 0 | pcmk__err("IPC message without header"); |
50 | 0 | return false; |
51 | |
|
52 | 0 | } else if (header->version > PCMK__IPC_VERSION) { |
53 | 0 | pcmk__err("Filtering incompatible v%d IPC message (only versions <= %d " |
54 | 0 | "supported)", |
55 | 0 | header->version, PCMK__IPC_VERSION); |
56 | 0 | return false; |
57 | 0 | } |
58 | 0 | return true; |
59 | 0 | } |
60 | | |
61 | | const char * |
62 | | pcmk__client_type_str(uint64_t client_type) |
63 | 0 | { |
64 | 0 | switch (client_type) { |
65 | 0 | case pcmk__client_ipc: |
66 | 0 | return "IPC"; |
67 | 0 | case pcmk__client_tcp: |
68 | 0 | return "TCP"; |
69 | 0 | case pcmk__client_tls: |
70 | 0 | return "TLS"; |
71 | 0 | default: |
72 | 0 | return "unknown"; |
73 | 0 | } |
74 | 0 | } |
75 | | |
76 | | /*! |
77 | | * \internal |
78 | | * \brief Add more data to a received partial IPC message |
79 | | * |
80 | | * This function can be called repeatedly to build up a complete IPC message |
81 | | * from smaller parts. It does this by inspecting flags on the message. |
82 | | * Most of the time, IPC messages will be small enough where this function |
83 | | * won't get called more than once, but more complex clusters can end up with |
84 | | * very large IPC messages that don't fit in a single buffer. |
85 | | * |
86 | | * Important return values: |
87 | | * |
88 | | * - EBADMSG - Something was wrong with the data. |
89 | | * - pcmk_rc_ipc_more - \p data was a chunk of a partial message and there is |
90 | | * more to come. The caller should not process the message |
91 | | * yet and should continue reading from the IPC connection. |
92 | | * - pcmk_rc_ok - We have the complete message. The caller should process |
93 | | * it and free the buffer to prepare for the next message. |
94 | | * |
95 | | * \param[in,out] buffer The buffer to add this data to |
96 | | * \param[in] data The received IPC message or message portion. |
97 | | * |
98 | | * \return Standard Pacemaker return code |
99 | | */ |
100 | | int |
101 | | pcmk__ipc_msg_append(GByteArray **buffer, guint8 *data) |
102 | 0 | { |
103 | 0 | pcmk__ipc_header_t *full_header = NULL; |
104 | 0 | pcmk__ipc_header_t *header = (pcmk__ipc_header_t *) (void *) data; |
105 | 0 | const guint8 *payload = (guint8 *) data + sizeof(pcmk__ipc_header_t); |
106 | 0 | int rc = pcmk_rc_ok; |
107 | |
|
108 | 0 | if (!pcmk__valid_ipc_header(header)) { |
109 | 0 | return EBADMSG; |
110 | 0 | } |
111 | | |
112 | 0 | if (pcmk__is_set(header->flags, crm_ipc_multipart_end)) { |
113 | 0 | full_header = (pcmk__ipc_header_t *) (void *) (*buffer)->data; |
114 | | |
115 | | /* This is the end of a multipart IPC message. Add the payload of the |
116 | | * received data (so, don't include the header) to the partial buffer. |
117 | | * Remember that this needs to include the null terminating character. |
118 | | */ |
119 | 0 | CRM_CHECK(buffer != NULL && *buffer != NULL && header->part_id != 0, |
120 | 0 | return EINVAL); |
121 | 0 | CRM_CHECK(full_header->qb.id == header->qb.id, return EBADMSG); |
122 | 0 | g_byte_array_append(*buffer, payload, header->size); |
123 | |
|
124 | 0 | pcmk__trace("Received IPC message %" PRId32 " (final part %" PRIu16 ") " |
125 | 0 | "of %" PRId32 " bytes", |
126 | 0 | header->qb.id, header->part_id, header->qb.size); |
127 | |
|
128 | 0 | } else if (pcmk__is_set(header->flags, crm_ipc_multipart)) { |
129 | 0 | if (header->part_id == 0) { |
130 | | /* This is the first part of a multipart IPC message. Initialize |
131 | | * the buffer with the entire message, including its header. Do |
132 | | * not include the null terminating character. |
133 | | */ |
134 | 0 | CRM_CHECK(buffer != NULL && *buffer == NULL, return EINVAL); |
135 | 0 | *buffer = g_byte_array_new(); |
136 | | |
137 | | /* Clear any multipart flags from the header of the incoming part |
138 | | * so they'll be clear in the fully reassembled message. This |
139 | | * message is passed to pcmk__client_data2xml, which will extract |
140 | | * the header flags and return them. Those flags can then be used |
141 | | * when constructing a reply, including ACKs. We don't want these |
142 | | * specific incoming flags to influence the reply. |
143 | | */ |
144 | 0 | pcmk__clear_ipc_flags(header->flags, "server", crm_ipc_multipart); |
145 | |
|
146 | 0 | g_byte_array_append(*buffer, data, |
147 | 0 | sizeof(pcmk__ipc_header_t) + header->size - 1); |
148 | |
|
149 | 0 | } else { |
150 | 0 | full_header = (pcmk__ipc_header_t *) (void *) (*buffer)->data; |
151 | | |
152 | | /* This is some intermediate part of a multipart message. Add |
153 | | * the payload of the received data (so, don't include the header) |
154 | | * to the partial buffer and return. Do not include the null |
155 | | * terminating character. |
156 | | */ |
157 | 0 | CRM_CHECK(buffer != NULL && *buffer != NULL, return EINVAL); |
158 | 0 | CRM_CHECK(full_header->qb.id == header->qb.id, return EBADMSG); |
159 | 0 | g_byte_array_append(*buffer, payload, header->size - 1); |
160 | 0 | } |
161 | | |
162 | 0 | pcmk__trace("Received IPC message %" PRId32 " (part %" PRIu16 ") " |
163 | 0 | "of %" PRId32 " bytes", |
164 | 0 | header->qb.id, header->part_id, header->qb.size); |
165 | | |
166 | 0 | rc = pcmk_rc_ipc_more; |
167 | |
|
168 | 0 | } else { |
169 | | /* This is a standalone IPC message. For simplicity in the caller, |
170 | | * copy the entire message over into a byte array so it can be handled |
171 | | * the same as a multipart message. |
172 | | */ |
173 | 0 | CRM_CHECK(buffer != NULL && *buffer == NULL, return EINVAL); |
174 | 0 | *buffer = g_byte_array_new(); |
175 | 0 | g_byte_array_append(*buffer, data, |
176 | 0 | sizeof(pcmk__ipc_header_t) + header->size); |
177 | |
|
178 | 0 | pcmk__trace("Received IPC message %" PRId32 " of %" PRId32 " bytes", |
179 | 0 | header->qb.id, header->qb.size); |
180 | 0 | } |
181 | | |
182 | 0 | pcmk__trace("Text = %s", payload); |
183 | 0 | pcmk__trace("Buffer = %s", (*buffer)->data + sizeof(pcmk__ipc_header_t)); |
184 | | |
185 | | /* The buffer's header should have a size that matches the full size of |
186 | | * the received message, not just the last chunk of it. |
187 | | */ |
188 | 0 | full_header = (pcmk__ipc_header_t *) (void *) (*buffer)->data; |
189 | 0 | full_header->size = (*buffer)->len - sizeof(pcmk__ipc_header_t); |
190 | |
|
191 | 0 | return rc; |
192 | 0 | } |