Line data Source code
1 : // Copyright (c) 2012-2022 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 UDP server class.
22 : *
23 : * This class is used to listen and optionally send UDP messages.
24 : *
25 : * By default, the class only creates a server. With UDP, since it is state
26 : * less, the only way to communicate is via two servers and two clients.
27 : * A client is used to send messages and a server is used to listen and
28 : * receive messages.
29 : *
30 : * Since the port for the server and the client need to be different.
31 : * You may assign the server port 0 in which case it is automatically
32 : * generated and that port can be sent to the other side so that other
33 : * side can then reply to our messages.
34 : */
35 :
36 : // self
37 : //
38 : #include "eventdispatcher/udp_server_message_connection.h"
39 :
40 : #include "eventdispatcher/exception.h"
41 : #include "eventdispatcher/udp_client.h"
42 :
43 :
44 : // snaplogger
45 : //
46 : #include <snaplogger/message.h>
47 :
48 :
49 : // libaddr
50 : //
51 : #include <libaddr/iface.h>
52 :
53 :
54 : // boost
55 : //
56 : #include <boost/preprocessor/stringize.hpp>
57 :
58 :
59 : // last include
60 : //
61 : #include <snapdev/poison.h>
62 :
63 :
64 :
65 : namespace ed
66 : {
67 :
68 :
69 :
70 : /** \brief Initialize a UDP server to send and receive messages.
71 : *
72 : * This function initializes a UDP server as a Snap UDP server
73 : * connection attached to the specified address and port.
74 : *
75 : * It is expected to be used to send and receive UDP messages.
76 : *
77 : * Note that to send messages, you need the address and port
78 : * of the destination. In effect, we do not use this server
79 : * when sending. Instead we create a client that we immediately
80 : * destruct once the message was sent.
81 : *
82 : * The \p client_address, if not set to ANY (0.0.0.0 or ::) is
83 : * used to create a udp_client object. That object is used
84 : * by the send_message() function. It also allows you to use
85 : * port 0 for the server which means you do not have to have
86 : * a reserved port for the server. That port can then be sent
87 : * to the client which can use it to send you replies.
88 : *
89 : * \param[in] server_address The address and port to listen on.
90 : * \param[in] client_address The address for the client side.
91 : */
92 0 : udp_server_message_connection::udp_server_message_connection(
93 : addr::addr const & server_address
94 0 : , addr::addr const & client_address)
95 0 : : udp_server_connection(server_address)
96 : {
97 : // allow for looping over all the messages in one go
98 : //
99 0 : non_blocking();
100 :
101 0 : if(client_address.get_network_type() != addr::addr::network_type_t::NETWORK_TYPE_ANY)
102 : {
103 0 : f_udp_client = std::make_shared<ed::udp_client>(client_address);
104 : }
105 0 : }
106 :
107 :
108 : /** \brief Send a message over to the client.
109 : *
110 : * This function sends a message to the client at the address specified in
111 : * the constructor.
112 : *
113 : * The advantage of using this function is that the server port is
114 : * automatically attached to the message through the reply_port
115 : * parameter. This is important if you are running an application
116 : * which is not itself the main server (since the UDP mechanism is
117 : * opposite to the TCP mechanism, clients have to create servers
118 : * which have to listen and on one computer, multiple clients
119 : * would require you to assign additional ports to clients, which
120 : * is unusual).
121 : *
122 : * \exception initialization_missing
123 : * If no address was specified on the constructor (i.e. the ANY address
124 : * was used) then this exception is raised.
125 : *
126 : * \param[in] msg The message to send to the client.
127 : * \param[in] secret_code The secret code to attach to the message.
128 : *
129 : * \return true when the message was sent, false otherwise.
130 : */
131 0 : bool udp_server_message_connection::send_message(
132 : message const & msg
133 : , std::string const & secret_code)
134 : {
135 0 : if(f_udp_client == nullptr)
136 : {
137 0 : throw initialization_missing("this UDP server was not initialized with a client (see constructor).");
138 : }
139 :
140 0 : message with_address(msg);
141 0 : with_address.add_parameter("reply_to", get_address());
142 :
143 0 : return send_message(*f_udp_client, msg, secret_code);
144 : }
145 :
146 :
147 : /** \brief Send a UDP message.
148 : *
149 : * This function offers you to send a UDP message to the specified
150 : * address and port. The message should be small enough to fit in
151 : * one UDP packet or the call will fail.
152 : *
153 : * \note
154 : * The function returns true when the message was successfully sent.
155 : * This does not mean it was received.
156 : *
157 : * \param[in] client_address The destination address and port for the message.
158 : * \param[in] msg The message to send to the destination.
159 : * \param[in] secret_code The secret code to send along the message.
160 : *
161 : * \return true when the message was sent, false otherwise.
162 : */
163 0 : bool udp_server_message_connection::send_message(
164 : addr::addr const & client_address
165 : , message const & msg
166 : , std::string const & secret_code)
167 : {
168 : // Note: contrary to the TCP version, a UDP message does not
169 : // need to include the '\n' character since it is sent
170 : // in one UDP packet. However, it has a maximum size
171 : // limit which we enforce here.
172 : //
173 0 : udp_client client(client_address);
174 :
175 : // you should use the multi-cast
176 : //
177 : // TODO: also the is_broadcast_address() re-reads the list of interfaces
178 : // from the kernel, which is _slow_ (i.e. it doesn't get cached)
179 : // See: libaddr/iface.cpp in the libaddr project
180 : //
181 0 : if(client_address.get_network_type() == addr::addr::network_type_t::NETWORK_TYPE_MULTICAST
182 0 : || addr::is_broadcast_address(client_address))
183 : {
184 0 : client.set_broadcast(true);
185 : }
186 :
187 0 : return send_message(client, msg, secret_code);
188 : }
189 :
190 :
191 : /** \brief Send a UDP message to the specified \p client.
192 : *
193 : * This function sends a UDP message to the specified client. In most
194 : * cases, you want to send a message using the other two send_message()
195 : * functions. If you have your own instance of a udp_client object,
196 : * then you are free to use this function instead.
197 : *
198 : * \todo
199 : * I think it would be possible to have this function as part of the
200 : * udp_client class instead. Since it is static, there is no real
201 : * need for any specific field from the UDP servero.
202 : *
203 : * \param[in] client The client where the message gets sent.
204 : * \param[in] msg The message to send to the destination.
205 : * \param[in] secret_code The secret code to send along the message.
206 : *
207 : * \return true when the message was sent, false otherwise.
208 : */
209 0 : bool udp_server_message_connection::send_message(
210 : udp_client & client
211 : , message const & msg
212 : , std::string const & secret_code)
213 : {
214 0 : std::string buf;
215 0 : if(!secret_code.empty())
216 : {
217 0 : message m(msg);
218 0 : m.add_parameter("secret_code", secret_code);
219 0 : buf = m.to_message();
220 : }
221 : else
222 : {
223 0 : buf = msg.to_message();
224 : }
225 :
226 : // TODO: this maximum size needs to be checked dynamically;
227 : // also it's not forbidden to send a multiple packet
228 : // UDP buffer, it's just more likely to fail
229 : //
230 0 : if(buf.length() > DATAGRAM_MAX_SIZE)
231 : {
232 : // packet too large for our buffers
233 : //
234 : throw invalid_message(
235 : "message too large ("
236 0 : + std::to_string(buf.length())
237 0 : + " bytes) for a UDP server (max: "
238 : BOOST_PP_STRINGIZE(DATAGRAM_MAX_SIZE)
239 0 : ")");
240 : }
241 :
242 0 : if(client.send(buf.data(), buf.length()) != static_cast<ssize_t>(buf.length())) // we do not send the '\0'
243 : {
244 0 : int const e(errno);
245 0 : SNAP_LOG_ERROR
246 0 : << SNAP_LOG_FIELD("errno", std::to_string(e))
247 : << "udp_server_message_connection::send_message(): could not send UDP message."
248 : << SNAP_LOG_SEND;
249 0 : return false;
250 : }
251 :
252 0 : return true;
253 : }
254 :
255 :
256 : /** \brief Implementation of the process_read() callback.
257 : *
258 : * This function reads the datagram we just received using the
259 : * recv() function. The size of the datagram cannot be more than
260 : * DATAGRAM_MAX_SIZE (1Kb at time of writing.)
261 : *
262 : * The message is then parsed and further processing is expected
263 : * to be accomplished in your implementation of process_message().
264 : *
265 : * The function actually reads as many pending datagrams as it can.
266 : */
267 0 : void udp_server_message_connection::process_read()
268 : {
269 0 : char buf[DATAGRAM_MAX_SIZE];
270 : for(;;)
271 : {
272 0 : ssize_t const r(recv(buf, sizeof(buf) / sizeof(buf[0])));
273 0 : if(r <= 0)
274 : {
275 0 : break;
276 : }
277 0 : std::string const udp_message(buf, r);
278 0 : message msg;
279 0 : if(msg.from_message(udp_message))
280 : {
281 0 : std::string const expected(get_secret_code());
282 0 : if(msg.has_parameter("secret_code"))
283 : {
284 0 : std::string const secret(msg.get_parameter("secret_code"));
285 0 : if(secret != expected)
286 : {
287 0 : if(!expected.empty())
288 : {
289 : // our secret code and the message secret code do not match
290 : //
291 0 : SNAP_LOG_ERROR
292 : << "the incoming message has an unexpected secret_code code, message ignored."
293 : << SNAP_LOG_SEND;
294 0 : return;
295 : }
296 :
297 : // the sender included a UDP secret code but we don't
298 : // require it so we emit a warning but still accept
299 : // the message
300 : //
301 0 : SNAP_LOG_WARNING
302 : << "no secret_code=... parameter was expected (missing set_secret_code() call for this application?)"
303 : << SNAP_LOG_SEND;
304 : }
305 : }
306 0 : else if(!expected.empty())
307 : {
308 : // secret code is missing from incoming message
309 : //
310 0 : SNAP_LOG_ERROR
311 : << "the incoming message was expected to have a secret_code parameter, message ignored."
312 : << SNAP_LOG_SEND;
313 0 : return;
314 : }
315 :
316 : // we received a valid message, process it
317 : //
318 0 : dispatch_message(msg);
319 : }
320 : else
321 : {
322 0 : SNAP_LOG_ERROR
323 : << "udp_server_message_connection::process_read() was asked"
324 : " to process an invalid message ("
325 : << udp_message
326 : << ")"
327 : << SNAP_LOG_SEND;
328 : }
329 0 : }
330 : }
331 :
332 :
333 :
334 6 : } // namespace ed
335 : // vim: ts=4 sw=4 et
|