Line data Source code
1 : // Copyright (c) 2012-2024 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/eventdispatcher
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program; if not, write to the Free Software
18 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 :
20 : /** \file
21 : * \brief Implementation of the AF_UNIX socket class handling message packets.
22 : *
23 : */
24 :
25 : // self
26 : //
27 : #include "eventdispatcher/local_dgram_server_message_connection.h"
28 :
29 : #include "eventdispatcher/exception.h"
30 : #include "eventdispatcher/local_dgram_client.h"
31 :
32 :
33 : // snaplogger
34 : //
35 : #include <snaplogger/message.h>
36 :
37 :
38 : // snapdev
39 : //
40 : #include <snapdev/stringize.h>
41 :
42 :
43 : // last include
44 : //
45 : #include <snapdev/poison.h>
46 :
47 :
48 :
49 : namespace ed
50 : {
51 :
52 :
53 :
54 : /** \brief Initialize a UDP server to send and receive messages.
55 : *
56 : * This function initializes a UDP server as a Snap UDP server
57 : * connection attached to the specified address and port.
58 : *
59 : * It is expected to be used to send and receive UDP messages.
60 : *
61 : * Note that to send messages, you need the address and port
62 : * of the destination. In effect, we do not use this server
63 : * when sending. Instead we create a client that we immediately
64 : * destruct once the message was sent.
65 : *
66 : * \param[in] address The Unix address to listen on.
67 : * \param[in] sequential Whether the packets are kept in order.
68 : * \param[in] close_on_exec Whether the socket is closed on execve().
69 : * \param[in] force_reuse_addr Whether caller is okay with an unlink() of
70 : * a file socket if it exists.
71 : * \param[in] client_address A Unix address used to send replies. It cannot
72 : * be abstract if you do want a client (i.e. if abstract, no client is
73 : * created).
74 : * \param[in] service_name The name of the service if it applies to this
75 : * connection (i.e. a connection to connect to the communicator daemon).
76 : */
77 2 : local_dgram_server_message_connection::local_dgram_server_message_connection(
78 : addr::addr_unix const & address
79 : , bool sequential
80 : , bool close_on_exec
81 : , bool force_reuse_addr
82 : , addr::addr_unix const & client_address
83 2 : , std::string const & service_name)
84 : : local_dgram_server_connection(
85 : address
86 : , sequential
87 : , close_on_exec
88 : , force_reuse_addr)
89 2 : , connection_with_send_message(service_name)
90 : {
91 : // allow for looping over all the messages in one go
92 : //
93 2 : non_blocking();
94 :
95 2 : if(!client_address.is_unnamed())
96 : {
97 0 : f_dgram_client = std::make_shared<local_dgram_client>(client_address);
98 : }
99 2 : }
100 :
101 :
102 : /** \brief Send a message.
103 : *
104 : * This function sends \p message to the other side.
105 : *
106 : * The \p cache parameter is here because it is present in the send_message()
107 : * of the connection_with_send_message class. It is not used by the UDP
108 : * implementation, however.
109 : *
110 : * \param[in,out] msg The message to forward to the other side.
111 : * \param[in] cache This flag is ignored.
112 : *
113 : * \return true if the message was sent successfully.
114 : */
115 0 : bool local_dgram_server_message_connection::send_message(
116 : message & msg
117 : , bool cache)
118 : {
119 0 : snapdev::NOT_USED(cache);
120 :
121 0 : return send_message(msg, get_secret_code());
122 : }
123 :
124 :
125 : /** \brief Send a message over to the client.
126 : *
127 : * This function sends a message to the client at the address specified in
128 : * the constructor.
129 : *
130 : * The advantage of using this function is that the server port is
131 : * automatically attached to the message through the reply_port
132 : * parameter. This is important if you are running an application
133 : * which is not itself the main server (since the UDP mechanism is
134 : * opposite to the TCP mechanism, clients have to create servers
135 : * which have to listen and on one computer, multiple clients
136 : * would require you to assign additional ports to clients, which
137 : * is unusual).
138 : *
139 : * \exception initialization_missing
140 : * If no address was specified on the constructor (i.e. the ANY address
141 : * was used) then this exception is raised.
142 : *
143 : * \param[in] msg The message to send to the client.
144 : * \param[in] secret_code The secret code to attach to the message.
145 : *
146 : * \return true when the message was sent, false otherwise.
147 : */
148 0 : bool local_dgram_server_message_connection::send_message(
149 : message const & msg
150 : , std::string const & secret_code)
151 : {
152 0 : if(f_dgram_client == nullptr)
153 : {
154 0 : throw initialization_missing("this UDP server was not initialized with a client (see constructor).");
155 : }
156 :
157 0 : message with_address(msg);
158 0 : with_address.add_parameter("reply_to", get_address());
159 :
160 0 : return send_message(*f_dgram_client, msg, secret_code);
161 0 : }
162 :
163 :
164 : /** \brief Send a UDP message.
165 : *
166 : * This function offers you to send a UDP message to the specified
167 : * address and port. The message should be small enough to fit in
168 : * one UDP packet or the call will fail.
169 : *
170 : * \note
171 : * The function returns true when the message was successfully sent.
172 : * This does not mean it was received.
173 : *
174 : * \param[in] address The destination Unix address for the message.
175 : * \param[in] msg The message to send to the destination.
176 : * \param[in] secret_code The secret code to send along the message.
177 : *
178 : * \return true when the message was sent, false otherwise.
179 : */
180 3 : bool local_dgram_server_message_connection::send_message(
181 : addr::addr_unix const & address
182 : , message & msg
183 : , std::string const & secret_code)
184 : {
185 : // Note: contrary to the Stream version, a Datagram message does not
186 : // need to include the '\n' character since it is sent
187 : // in one packet. However, it has a maximum size limit
188 : // which we enforce here.
189 : //
190 3 : std::string buf;
191 3 : if(!secret_code.empty())
192 : {
193 0 : message m(msg);
194 0 : m.add_parameter("secret_code", secret_code);
195 0 : buf = m.to_message();
196 0 : }
197 : else
198 : {
199 3 : buf = msg.to_message();
200 : }
201 :
202 : // TODO: this maximum size needs to be checked dynamically
203 : //
204 3 : if(buf.length() > DATAGRAM_MAX_SIZE)
205 : {
206 : // packet too large for the socket buffers
207 : //
208 0 : throw invalid_message(
209 : "message too large ("
210 0 : + std::to_string(buf.length())
211 0 : + " bytes) for a Unix socket (max: "
212 : SNAPDEV_STRINGIZE(DATAGRAM_MAX_SIZE)
213 0 : ")");
214 : }
215 :
216 3 : local_dgram_client client(address);
217 3 : int const r(client.send(buf.data(), buf.length()));
218 3 : if(r != static_cast<ssize_t>(buf.length())) // we do not send the '\0'
219 : {
220 : // TODO: add errno to message
221 0 : int const e(errno);
222 0 : SNAP_LOG_ERROR
223 : << "local_dgram_server_message_connection::send_message(): could not send Datagram message to \""
224 0 : << address.to_uri()
225 0 : << "\", errno: "
226 : << e
227 : << ", "
228 0 : << strerror(e)
229 0 : << " (r = "
230 : << r
231 : << ")."
232 : << SNAP_LOG_SEND;
233 0 : errno = e;
234 0 : return false;
235 : }
236 :
237 3 : return true;
238 3 : }
239 :
240 :
241 0 : bool local_dgram_server_message_connection::send_message(
242 : local_dgram_client & client
243 : , message const & msg
244 : , std::string const & secret_code)
245 : {
246 0 : std::string buf;
247 0 : if(!secret_code.empty())
248 : {
249 0 : message m(msg);
250 0 : m.add_parameter("secret_code", secret_code);
251 0 : buf = m.to_message();
252 0 : }
253 : else
254 : {
255 0 : buf = msg.to_message();
256 : }
257 :
258 : // TODO: this maximum size needs to be checked dynamically;
259 : // also it's not forbidden to send a multiple packet
260 : // UDP buffer, it's just more likely to fail
261 : //
262 0 : if(buf.length() > DATAGRAM_MAX_SIZE)
263 : {
264 : // packet too large for our buffers
265 : //
266 0 : throw invalid_message(
267 : "message too large ("
268 0 : + std::to_string(buf.length())
269 0 : + " bytes) for a UDP server (max: "
270 : SNAPDEV_STRINGIZE(DATAGRAM_MAX_SIZE)
271 0 : ")");
272 : }
273 :
274 0 : if(client.send(buf.data(), buf.length()) != static_cast<ssize_t>(buf.length())) // we do not send the '\0'
275 : {
276 0 : int const e(errno);
277 0 : SNAP_LOG_ERROR
278 0 : << SNAP_LOG_FIELD("errno", std::to_string(e))
279 : << "udp_server_message_connection::send_message(): could not send UDP message."
280 : << SNAP_LOG_SEND;
281 0 : return false;
282 : }
283 :
284 0 : return true;
285 0 : }
286 :
287 :
288 : /** \brief Implementation of the process_read() callback.
289 : *
290 : * This function reads the datagram we just received using the
291 : * recv() function. The size of the datagram cannot be more than
292 : * DATAGRAM_MAX_SIZE (1Kb at time of writing.)
293 : *
294 : * The message is then parsed and further processing is expected
295 : * to be accomplished in your implementation of process_message().
296 : *
297 : * The function actually reads as many pending datagrams as it can.
298 : */
299 3 : void local_dgram_server_message_connection::process_read()
300 : {
301 3 : char buf[DATAGRAM_MAX_SIZE];
302 : for(;;)
303 : {
304 6 : ssize_t const r(recv(buf, sizeof(buf) / sizeof(buf[0])));
305 6 : if(r <= 0)
306 : {
307 3 : break;
308 : }
309 6 : std::string const local_dgram_message(buf, r);
310 3 : message msg;
311 3 : if(msg.from_message(local_dgram_message))
312 : {
313 3 : std::string const expected(get_secret_code());
314 3 : if(msg.has_parameter("secret_code"))
315 : {
316 0 : std::string const secret(msg.get_parameter("secret_code"));
317 0 : if(secret != expected)
318 : {
319 0 : if(!expected.empty())
320 : {
321 : // our secret code and the message secret code do not match
322 : //
323 0 : SNAP_LOG_ERROR
324 : << "the incoming message has an unexpected secret_code code, message ignored."
325 : << SNAP_LOG_SEND;
326 0 : return;
327 : }
328 :
329 : // the sender included a UDP secret code but we don't
330 : // require it so we emit a warning but still accept
331 : // the message
332 : //
333 0 : SNAP_LOG_WARNING
334 : << "no secret_code=... parameter was expected (missing set_secret_code() call for this application?)."
335 : << SNAP_LOG_SEND;
336 : }
337 0 : }
338 3 : else if(!expected.empty())
339 : {
340 : // secret code is missing from incoming message
341 : //
342 0 : SNAP_LOG_ERROR
343 : << "the incoming message was expected to have a secret_code parameter, message dropped."
344 : << SNAP_LOG_SEND;
345 0 : return;
346 : }
347 :
348 : // we received a valid message, process it
349 : //
350 3 : dispatch_message(msg);
351 3 : }
352 : else
353 : {
354 0 : SNAP_LOG_ERROR
355 : << "local_dgram_server_message_connection::process_read() was"
356 : " asked to process an invalid message ("
357 : << local_dgram_message
358 : << ")"
359 : << SNAP_LOG_SEND;
360 : }
361 6 : }
362 : }
363 :
364 :
365 :
366 : } // namespace ed
367 : // vim: ts=4 sw=4 et
|