Line data Source code
1 : // Event Dispatcher
2 : // Copyright (c) 2012-2019 Made to Order Software Corp. All Rights Reserved
3 : //
4 : // This program is free software; you can redistribute it and/or modify
5 : // it under the terms of the GNU General Public License as published by
6 : // the Free Software Foundation; either version 2 of the License, or
7 : // (at your option) any later version.
8 : //
9 : // This program is distributed in the hope that it will be useful,
10 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : // GNU General Public License for more details.
13 : //
14 : // You should have received a copy of the GNU General Public License
15 : // along with this program; if not, write to the Free Software
16 : // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 :
18 :
19 :
20 : // self
21 : //
22 : #include "eventdispatcher/udp_server.h"
23 :
24 : #include "eventdispatcher/exception.h"
25 :
26 :
27 : // snapwebsites lib
28 : //
29 : #include "snaplogger/message.h"
30 :
31 :
32 : // C lib
33 : //
34 : #include <arpa/inet.h>
35 : #include <poll.h>
36 : #include <string.h>
37 :
38 :
39 : // last include
40 : //
41 : #include "snapdev/poison.h"
42 :
43 :
44 :
45 :
46 : namespace ed
47 : {
48 :
49 :
50 :
51 : /** \brief Initialize a UDP server object.
52 : *
53 : * This function initializes a UDP server object making it ready to
54 : * receive messages.
55 : *
56 : * The server address and port are specified in the constructor so
57 : * if you need to receive messages from several different addresses
58 : * and/or port, you'll have to create a server for each.
59 : *
60 : * The address is a string and it can represent an IPv4 or IPv6
61 : * address.
62 : *
63 : * Note that this function calls bind() to listen to the socket
64 : * at the specified address. To accept data on different UDP addresses
65 : * and ports, multiple UDP servers must be created.
66 : *
67 : * \note
68 : * The socket is open in this process. If you fork() or exec() then the
69 : * socket will be closed by the operating system.
70 : *
71 : * \warning
72 : * We only make use of the first address found by getaddrinfo(). All
73 : * the other addresses are ignored.
74 : *
75 : * \warning
76 : * Remember that the multicast feature under Linux is shared by all
77 : * processes running on that server. Any one process can listen for
78 : * any and all multicast messages from any other process. Our
79 : * implementation limits the multicast from a specific IP. However.
80 : * other processes can also receive you packets and there is nothing
81 : * you can do to prevent that.
82 : *
83 : * \exception udp_client_server_runtime_error
84 : * The udp_client_server_runtime_error exception is raised when the address
85 : * and port combinaison cannot be resolved or if the socket cannot be
86 : * opened.
87 : *
88 : * \param[in] addr The address we receive on.
89 : * \param[in] port The port we receive from.
90 : * \param[in] family The family used to search for 'addr'.
91 : * \param[in] multicast_addr A multicast address.
92 : */
93 0 : udp_server::udp_server(std::string const & addr, int port, int family, std::string const * multicast_addr)
94 0 : : udp_base(addr, port, family)
95 : {
96 : // bind to the very first address
97 : //
98 0 : int r(bind(f_socket.get(), f_addrinfo->ai_addr, f_addrinfo->ai_addrlen));
99 0 : if(r != 0)
100 : {
101 0 : int const e(errno);
102 :
103 : // reverse the address from the f_addrinfo so we know exactly
104 : // which one was picked
105 : //
106 : char addr_buf[256];
107 0 : switch(f_addrinfo->ai_family)
108 : {
109 : case AF_INET:
110 0 : inet_ntop(AF_INET
111 0 : , &reinterpret_cast<struct sockaddr_in *>(f_addrinfo->ai_addr)->sin_addr
112 : , addr_buf
113 0 : , sizeof(addr_buf));
114 0 : break;
115 :
116 : case AF_INET6:
117 0 : inet_ntop(AF_INET6
118 0 : , &reinterpret_cast<struct sockaddr_in6 *>(f_addrinfo->ai_addr)->sin6_addr
119 : , addr_buf
120 0 : , sizeof(addr_buf));
121 0 : break;
122 :
123 : default:
124 0 : strncpy(addr_buf, "Unknown Adress Family", sizeof(addr_buf));
125 0 : break;
126 :
127 : }
128 :
129 : SNAP_LOG_ERROR
130 0 : << "the bind() function failed with errno: "
131 0 : << e
132 0 : << " ("
133 0 : << strerror(e)
134 0 : << "); address length "
135 0 : << f_addrinfo->ai_addrlen
136 0 : << " and address is \""
137 0 : << addr_buf
138 0 : << "\"";
139 0 : throw event_dispatcher_runtime_error("could not bind UDP socket to \"" + f_addr + ":" + std::to_string(port) + "\"");
140 : }
141 :
142 : // are we creating a server to listen to multicast packets?
143 : //
144 0 : if(multicast_addr != nullptr)
145 : {
146 : struct ip_mreqn mreq;
147 :
148 0 : std::stringstream decimal_port;
149 0 : decimal_port << f_port;
150 0 : std::string port_str(decimal_port.str());
151 :
152 0 : struct addrinfo hints = addrinfo();
153 0 : hints.ai_family = AF_UNSPEC;
154 0 : hints.ai_socktype = SOCK_DGRAM;
155 0 : hints.ai_protocol = IPPROTO_UDP;
156 :
157 : // we use the multicast address, but the same port as for
158 : // the other address
159 : //
160 0 : addrinfo * a(nullptr);
161 0 : r = getaddrinfo(multicast_addr->c_str(), port_str.c_str(), &hints, &a);
162 0 : if(r != 0 || a == nullptr)
163 : {
164 0 : throw event_dispatcher_runtime_error("invalid address or port for UDP socket: \"" + addr + ":" + port_str + "\"");
165 : }
166 :
167 : // both addresses must have the right size
168 : //
169 0 : if(a->ai_addrlen != sizeof(mreq.imr_multiaddr)
170 0 : || f_addrinfo->ai_addrlen != sizeof(mreq.imr_address))
171 : {
172 0 : throw event_dispatcher_runtime_error("invalid address type for UDP multicast: \"" + addr + ":" + port_str
173 0 : + "\" or \"" + *multicast_addr + ":" + port_str + "\"");
174 : }
175 :
176 0 : memcpy(&mreq.imr_multiaddr, a->ai_addr->sa_data, sizeof(mreq.imr_multiaddr));
177 0 : memcpy(&mreq.imr_address, f_addrinfo->ai_addr->sa_data, sizeof(mreq.imr_address));
178 0 : mreq.imr_ifindex = 0; // no specific interface
179 :
180 0 : r = setsockopt(f_socket.get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
181 0 : if(r < 0)
182 : {
183 0 : int const e(errno);
184 0 : throw event_dispatcher_runtime_error("IP_ADD_MEMBERSHIP failed for: \"" + addr + ":" + port_str
185 0 : + "\" or \"" + *multicast_addr + ":" + port_str + "\", "
186 0 : + std::to_string(e) + strerror(e));
187 : }
188 :
189 : // setup the multicast to 0 so we don't receive other's
190 : // messages; apparently the default would be 1
191 : //
192 0 : int multicast_all(0);
193 0 : r = setsockopt(f_socket.get(), IPPROTO_IP, IP_MULTICAST_ALL, &multicast_all, sizeof(multicast_all));
194 0 : if(r < 0)
195 : {
196 : // things should still work if the IP_MULTICAST_ALL is not
197 : // set as we want it
198 : //
199 0 : int const e(errno);
200 : SNAP_LOG_WARNING
201 0 : << "could not set IP_MULTICAST_ALL to zero, e = "
202 0 : << e
203 0 : << " -- "
204 0 : << strerror(e);
205 : }
206 : }
207 0 : }
208 :
209 :
210 : /** \brief Clean up the UDP server.
211 : *
212 : * This function frees the address info structures and close the socket.
213 : */
214 0 : udp_server::~udp_server()
215 : {
216 0 : }
217 :
218 :
219 : /** \brief Wait on a message.
220 : *
221 : * This function waits until a message is received on this UDP server.
222 : * There are no means to return from this function except by receiving
223 : * a message. Remember that UDP does not have a connect state so whether
224 : * another process quits does not change the status of this UDP server
225 : * and thus it continues to wait forever.
226 : *
227 : * Note that you may change the type of socket by making it non-blocking
228 : * (use the get_socket() to retrieve the socket identifier) in which
229 : * case this function will not block if no message is available. Instead
230 : * it returns immediately.
231 : *
232 : * \param[in] msg The buffer where the message is saved.
233 : * \param[in] max_size The maximum size the message (i.e. size of the \p msg buffer.)
234 : *
235 : * \return The number of bytes read or -1 if an error occurs.
236 : */
237 0 : int udp_server::recv(char * msg, size_t max_size)
238 : {
239 0 : return static_cast<int>(::recv(f_socket.get(), msg, max_size, 0));
240 : }
241 :
242 :
243 : /** \brief Wait for data to come in.
244 : *
245 : * This function waits for a given amount of time for data to come in. If
246 : * no data comes in after max_wait_ms, the function returns with -1 and
247 : * errno set to EAGAIN.
248 : *
249 : * The socket is expected to be a blocking socket (the default,) although
250 : * it is possible to setup the socket as non-blocking if necessary for
251 : * some other reason.
252 : *
253 : * This function blocks for a maximum amount of time as defined by
254 : * max_wait_ms. It may return sooner with an error or a message.
255 : *
256 : * \param[in] msg The buffer where the message will be saved.
257 : * \param[in] max_size The size of the \p msg buffer in bytes.
258 : * \param[in] max_wait_ms The maximum number of milliseconds to wait for a message.
259 : *
260 : * \return -1 if an error occurs or the function timed out, the number of bytes received otherwise.
261 : */
262 0 : int udp_server::timed_recv(char * msg, size_t const max_size, int const max_wait_ms)
263 : {
264 : pollfd fd;
265 0 : fd.events = POLLIN | POLLPRI | POLLRDHUP;
266 0 : fd.fd = f_socket.get();
267 0 : int const retval(poll(&fd, 1, max_wait_ms));
268 :
269 : // fd_set s;
270 : // FD_ZERO(&s);
271 : //#pragma GCC diagnostic push
272 : //#pragma GCC diagnostic ignored "-Wold-style-cast"
273 : // FD_SET(f_socket.get(), &s);
274 : //#pragma GCC diagnostic pop
275 : // struct timeval timeout;
276 : // timeout.tv_sec = max_wait_ms / 1000;
277 : // timeout.tv_usec = (max_wait_ms % 1000) * 1000;
278 : // int const retval(select(f_socket.get() + 1, &s, nullptr, &s, &timeout));
279 0 : if(retval == -1)
280 : {
281 : // poll() sets errno accordingly
282 0 : return -1;
283 : }
284 0 : if(retval > 0)
285 : {
286 : // our socket has data
287 0 : return static_cast<int>(::recv(f_socket.get(), msg, max_size, 0));
288 : }
289 :
290 : // our socket has no data
291 0 : errno = EAGAIN;
292 0 : return -1;
293 : }
294 :
295 :
296 : /** \brief Wait for data to come in, but return a std::string.
297 : *
298 : * This function waits for a given amount of time for data to come in. If
299 : * no data comes in after max_wait_ms, the function returns with -1 and
300 : * errno set to EAGAIN.
301 : *
302 : * The socket is expected to be a blocking socket (the default,) although
303 : * it is possible to setup the socket as non-blocking if necessary for
304 : * some other reason.
305 : *
306 : * This function blocks for a maximum amount of time as defined by
307 : * max_wait_ms. It may return sooner with an error or a message.
308 : *
309 : * \param[in] bufsize The maximum size of the returned string in bytes.
310 : * \param[in] max_wait_ms The maximum number of milliseconds to wait for a message.
311 : *
312 : * \return received string. nullptr if error.
313 : *
314 : * \sa timed_recv()
315 : */
316 0 : std::string udp_server::timed_recv( int const bufsize, int const max_wait_ms )
317 : {
318 0 : std::vector<char> buf;
319 0 : buf.resize( bufsize + 1, '\0' ); // +1 for ending \0
320 0 : int const r(timed_recv( &buf[0], bufsize, max_wait_ms ));
321 0 : if( r <= -1 )
322 : {
323 : // Timed out, so return empty string.
324 : // TBD: could std::string() smash errno?
325 : //
326 0 : return std::string();
327 : }
328 :
329 : // Resize the buffer, then convert to std string
330 : //
331 0 : buf.resize( r + 1, '\0' );
332 :
333 0 : std::string word;
334 0 : word.resize( r );
335 0 : std::copy( buf.begin(), buf.end(), word.begin() );
336 :
337 0 : return word;
338 : }
339 :
340 :
341 :
342 :
343 6 : } // namespace ed
344 : // vim: ts=4 sw=4 et
|