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 along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 :
20 : /** \file
21 : * \brief UDP server class implementation.
22 : *
23 : * This file is the implementation of the UDP server.
24 : *
25 : * A UDP server accepts UDP packets from any number of clients (contrary to
26 : * a TCP connection which is one on one). Without the proper implementation,
27 : * a UDP _connection_ is generally considered insecure. Also unless you
28 : * handle a form of acknowledgement, there is no way to know whether a
29 : * packet made it to the other end.
30 : */
31 :
32 : // to get the POLLRDHUP definition
33 : #ifndef _GNU_SOURCE
34 : #define _GNU_SOURCE
35 : #endif
36 :
37 :
38 : // self
39 : //
40 : #include "eventdispatcher/udp_server.h"
41 :
42 : #include "eventdispatcher/exception.h"
43 :
44 :
45 : // snaplogger
46 : //
47 : #include <snaplogger/message.h>
48 :
49 :
50 : // C
51 : //
52 : #include <arpa/inet.h>
53 : #include <poll.h>
54 : #include <string.h>
55 :
56 :
57 : // last include
58 : //
59 : #include <snapdev/poison.h>
60 :
61 :
62 :
63 :
64 : namespace ed
65 : {
66 :
67 :
68 :
69 : /** \brief Initialize a UDP server object.
70 : *
71 : * This function initializes one UDP server object making it ready to
72 : * receive messages.
73 : *
74 : * The server address and port are specified in the constructor
75 : * as a libaddr `addr` object. It can represent an IPv4 or IPv6
76 : * address.
77 : *
78 : * Note that this function calls bind() to listen to the socket
79 : * at the specified address. To accept data on different UDP addresses
80 : * and ports, multiple UDP servers must be created.
81 : *
82 : * \note
83 : * The socket is open in this process. If you fork() or exec() then the
84 : * socket will be closed by the operating system.
85 : *
86 : * \warning
87 : * Remember that the multicast feature under Linux is shared by all
88 : * processes running on that server. Any one process can listen for
89 : * any and all multicast messages from any other process. Our
90 : * implementation limits the multicast from a specific IP. However.
91 : * other processes can also receive your packets and there is nothing
92 : * you can do to prevent that. Multicast is only supported with IPv4
93 : * addresses. We currently do not allow the default address as the
94 : * multicast address.
95 : *
96 : * \exception runtime_error
97 : * The runtime_error exception is raised when the socket
98 : * cannot be opened.
99 : *
100 : * \param[in] address The address we receive on.
101 : * \param[in] multicast_address A multicast address.
102 : */
103 0 : udp_server::udp_server(
104 : addr::addr const & address
105 0 : , addr::addr const & multicast_address)
106 0 : : udp_base(address)
107 : {
108 0 : int r(0);
109 :
110 0 : if(!multicast_address.is_default())
111 : {
112 : // in multicast we have to bind to the multicast IP (or IN_ANYADDR
113 : // which right now we do not support)
114 : //
115 0 : if(!multicast_address.is_ipv4()
116 0 : || !f_address.is_ipv4())
117 : {
118 0 : std::stringstream ss;
119 : ss << "the UDP multicast implementation only supports IPv4 at the moment; multicast: \""
120 0 : << multicast_address.to_ipv4or6_string(addr::addr::string_ip_t::STRING_IP_PORT)
121 : << "\", address: \""
122 0 : << f_address.to_ipv4or6_string(addr::addr::string_ip_t::STRING_IP_PORT)
123 0 : << "\".";
124 0 : SNAP_LOG_FATAL
125 : << ss
126 : << SNAP_LOG_SEND;
127 0 : throw runtime_error(ss.str());
128 : }
129 :
130 0 : r = multicast_address.bind(get_socket());
131 0 : if(r != 0)
132 : {
133 0 : int const e(errno);
134 0 : std::stringstream ss;
135 0 : ss << "the multicast address bind() function failed with errno: "
136 : << e
137 : << " ("
138 0 : << strerror(e)
139 : << "); address "
140 0 : << multicast_address.to_ipv4or6_string(addr::addr::string_ip_t::STRING_IP_PORT);
141 0 : SNAP_LOG_FATAL
142 : << ss
143 : << SNAP_LOG_SEND;
144 0 : throw runtime_error(ss.str());
145 : }
146 : }
147 : else
148 : {
149 : // note: if f_address has port set to 0 on entry, after this call
150 : // it will be changed to the port automatically assigned by
151 : // the network stack
152 : //
153 0 : r = f_address.bind(get_socket());
154 0 : if(r != 0)
155 : {
156 0 : int const e(errno);
157 :
158 0 : std::stringstream ss;
159 0 : ss << "the bind() function failed with errno: "
160 : << e
161 : << " ("
162 0 : << strerror(e)
163 : << "); address "
164 0 : << f_address.to_ipv4or6_string(addr::addr::string_ip_t::STRING_IP_PORT);
165 0 : SNAP_LOG_FATAL
166 : << ss
167 : << SNAP_LOG_SEND;
168 0 : throw runtime_error(ss.str());
169 : }
170 : }
171 :
172 : // are we creating a server to listen to multicast packets?
173 : //
174 0 : if(!multicast_address.is_default())
175 : {
176 0 : sockaddr_in m = {};
177 0 : sockaddr_in a = {};
178 :
179 0 : multicast_address.get_ipv4(m);
180 0 : f_address.get_ipv4(a);
181 :
182 : #pragma GCC diagnostic push
183 : #pragma GCC diagnostic ignored "-Wpedantic"
184 0 : ip_mreqn mreq = {
185 : .imr_multiaddr = m.sin_addr,
186 : .imr_address = a.sin_addr,
187 : .imr_ifindex = 0, // no specific interface
188 0 : };
189 : #pragma GCC diagnostic pop
190 :
191 0 : r = setsockopt(f_socket.get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
192 0 : if(r < 0)
193 : {
194 0 : int const e(errno);
195 : throw runtime_error(
196 : "IP_ADD_MEMBERSHIP failed for: \""
197 0 : + f_address.to_ipv4or6_string(addr::addr::string_ip_t::STRING_IP_PORT)
198 0 : + "\" or \""
199 0 : + multicast_address.to_ipv4or6_string(addr::addr::string_ip_t::STRING_IP_PORT)
200 0 : + "\", errno: "
201 0 : + std::to_string(e) + ", " + strerror(e));
202 : }
203 :
204 : // setup the multicast to 0 so we don't receive other's
205 : // messages; apparently the default would be 1
206 : //
207 0 : int multicast_all(0);
208 0 : r = setsockopt(f_socket.get(), IPPROTO_IP, IP_MULTICAST_ALL, &multicast_all, sizeof(multicast_all));
209 0 : if(r < 0)
210 : {
211 : // things should still work if the IP_MULTICAST_ALL is not
212 : // set as we want it
213 : //
214 0 : int const e(errno);
215 0 : SNAP_LOG_WARNING
216 0 : << "could not set IP_MULTICAST_ALL to zero, e = "
217 : << e
218 : << " -- "
219 0 : << strerror(e)
220 : << SNAP_LOG_SEND;
221 : }
222 : }
223 0 : }
224 :
225 :
226 : /** \brief Clean up the UDP server.
227 : *
228 : * This function frees the address info structures and close the socket.
229 : */
230 0 : udp_server::~udp_server()
231 : {
232 0 : }
233 :
234 :
235 : /** \brief Wait on a message.
236 : *
237 : * This function waits until a message is received on this UDP server.
238 : * There are no means to return from this function except by receiving
239 : * a message. Remember that UDP does not have a connect state so whether
240 : * another process quits does not change the status of this UDP server
241 : * and thus it continues to wait forever.
242 : *
243 : * Note that you may change the type of socket by making it non-blocking
244 : * (use the get_socket() to retrieve the socket identifier) in which
245 : * case this function will not block if no message is available. Instead
246 : * it returns immediately.
247 : *
248 : * \param[in] msg The buffer where the message is saved.
249 : * \param[in] max_size The maximum size the message (i.e. size of the \p msg buffer.)
250 : *
251 : * \return The number of bytes read or -1 if an error occurs.
252 : */
253 0 : int udp_server::recv(char * msg, size_t max_size)
254 : {
255 0 : return static_cast<int>(::recv(f_socket.get(), msg, max_size, 0));
256 : }
257 :
258 :
259 : /** \brief Wait for data to come in.
260 : *
261 : * This function waits for a given amount of time for data to come in. If
262 : * no data comes in after max_wait_ms, the function returns with -1 and
263 : * errno set to EAGAIN.
264 : *
265 : * The socket is expected to be a blocking socket (the default,) although
266 : * it is possible to setup the socket as non-blocking if necessary for
267 : * some other reason.
268 : *
269 : * This function blocks for a maximum amount of time as defined by
270 : * max_wait_ms. It may return sooner with an error or a message.
271 : *
272 : * \param[in] msg The buffer where the message will be saved.
273 : * \param[in] max_size The size of the \p msg buffer in bytes.
274 : * \param[in] max_wait_ms The maximum number of milliseconds to wait for a message.
275 : *
276 : * \return -1 if an error occurs or the function timed out, the number of bytes received otherwise.
277 : */
278 0 : int udp_server::timed_recv(char * msg, size_t const max_size, int const max_wait_ms)
279 : {
280 0 : pollfd fd;
281 0 : fd.events = POLLIN | POLLPRI | POLLRDHUP;
282 0 : fd.fd = f_socket.get();
283 0 : int const retval(poll(&fd, 1, max_wait_ms));
284 :
285 : // fd_set s;
286 : // FD_ZERO(&s);
287 : //#pragma GCC diagnostic push
288 : //#pragma GCC diagnostic ignored "-Wold-style-cast"
289 : // FD_SET(f_socket.get(), &s);
290 : //#pragma GCC diagnostic pop
291 : // struct timeval timeout;
292 : // timeout.tv_sec = max_wait_ms / 1000;
293 : // timeout.tv_usec = (max_wait_ms % 1000) * 1000;
294 : // int const retval(select(f_socket.get() + 1, &s, nullptr, &s, &timeout));
295 0 : if(retval == -1)
296 : {
297 : // poll() sets errno accordingly
298 0 : return -1;
299 : }
300 0 : if(retval > 0)
301 : {
302 : // our socket has data
303 0 : return static_cast<int>(::recv(f_socket.get(), msg, max_size, 0));
304 : }
305 :
306 : // our socket has no data
307 0 : errno = EAGAIN;
308 0 : return -1;
309 : }
310 :
311 :
312 : /** \brief Wait for data to come in, but return a std::string.
313 : *
314 : * This function waits for a given amount of time for data to come in. If
315 : * no data comes in after max_wait_ms, the function returns with -1 and
316 : * errno set to EAGAIN.
317 : *
318 : * The socket is expected to be a blocking socket (the default,) although
319 : * it is possible to setup the socket as non-blocking if necessary for
320 : * some other reason.
321 : *
322 : * This function blocks for a maximum amount of time as defined by
323 : * max_wait_ms. It may return sooner with an error or a message.
324 : *
325 : * \param[in] bufsize The maximum size of the returned string in bytes.
326 : * \param[in] max_wait_ms The maximum number of milliseconds to wait for a message.
327 : *
328 : * \return The received string or an empty string if not data received or error.
329 : *
330 : * \sa timed_recv()
331 : */
332 0 : std::string udp_server::timed_recv(int const bufsize, int const max_wait_ms)
333 : {
334 0 : std::vector<char> buf;
335 0 : buf.resize(bufsize + 1, '\0'); // +1 for ending \0
336 0 : int const r(timed_recv(&buf[0], bufsize, max_wait_ms));
337 0 : if(r <= -1)
338 : {
339 : // Timed out, so return empty string.
340 : // TBD: could std::string() smash errno?
341 : //
342 0 : return std::string();
343 : }
344 :
345 : // Resize the buffer, then convert to std string
346 : //
347 0 : buf.resize(r + 1, '\0');
348 :
349 0 : std::string word;
350 0 : word.resize(r);
351 0 : std::copy(buf.begin(), buf.end(), word.begin());
352 :
353 0 : return word;
354 : }
355 :
356 :
357 :
358 :
359 6 : } // namespace ed
360 : // vim: ts=4 sw=4 et
|