Mercurial > irccd
comparison common/sockets.cpp @ 0:1158cffe5a5e
Initial import
author | David Demelier <markand@malikania.fr> |
---|---|
date | Mon, 08 Feb 2016 16:43:14 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:1158cffe5a5e |
---|---|
1 /* | |
2 * sockets.cpp -- portable C++ socket wrappers | |
3 * | |
4 * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> | |
5 * | |
6 * Permission to use, copy, modify, and/or distribute this software for any | |
7 * purpose with or without fee is hereby granted, provided that the above | |
8 * copyright notice and this permission notice appear in all copies. | |
9 * | |
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
17 */ | |
18 | |
19 #define TIMEOUT_MSG "operation timeout" | |
20 | |
21 #include <algorithm> | |
22 #include <atomic> | |
23 #include <cstring> | |
24 #include <mutex> | |
25 | |
26 #include "sockets.h" | |
27 | |
28 namespace irccd { | |
29 | |
30 namespace net { | |
31 | |
32 /* | |
33 * Portable constants | |
34 * ------------------------------------------------------------------ | |
35 */ | |
36 | |
37 /* {{{ Constants */ | |
38 | |
39 #if defined(_WIN32) | |
40 | |
41 const Handle Invalid{INVALID_SOCKET}; | |
42 const int Failure{SOCKET_ERROR}; | |
43 | |
44 #else | |
45 | |
46 const Handle Invalid{-1}; | |
47 const int Failure{-1}; | |
48 | |
49 #endif | |
50 | |
51 /* }}} */ | |
52 | |
53 /* | |
54 * Portable functions | |
55 * ------------------------------------------------------------------ | |
56 */ | |
57 | |
58 /* {{{ Functions */ | |
59 | |
60 #if defined(_WIN32) | |
61 | |
62 namespace { | |
63 | |
64 static std::mutex s_mutex; | |
65 static std::atomic<bool> s_initialized{false}; | |
66 | |
67 } // !namespace | |
68 | |
69 #endif // !_WIN32 | |
70 | |
71 void init() noexcept | |
72 { | |
73 #if defined(_WIN32) | |
74 std::lock_guard<std::mutex> lock(s_mutex); | |
75 | |
76 if (!s_initialized) { | |
77 s_initialized = true; | |
78 | |
79 WSADATA wsa; | |
80 WSAStartup(MAKEWORD(2, 2), &wsa); | |
81 | |
82 /* | |
83 * If SOCKET_WSA_NO_INIT is not set then the user | |
84 * must also call finish himself. | |
85 */ | |
86 #if !defined(SOCKET_NO_AUTO_INIT) | |
87 atexit(finish); | |
88 #endif | |
89 } | |
90 #endif | |
91 } | |
92 | |
93 void finish() noexcept | |
94 { | |
95 #if defined(_WIN32) | |
96 WSACleanup(); | |
97 #endif | |
98 } | |
99 | |
100 std::string error(int errn) | |
101 { | |
102 #if defined(_WIN32) | |
103 LPSTR str = nullptr; | |
104 std::string errmsg = "Unknown error"; | |
105 | |
106 FormatMessageA( | |
107 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, | |
108 NULL, | |
109 errn, | |
110 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), | |
111 (LPSTR)&str, 0, NULL); | |
112 | |
113 | |
114 if (str) { | |
115 errmsg = std::string(str); | |
116 LocalFree(str); | |
117 } | |
118 | |
119 return errmsg; | |
120 #else | |
121 return strerror(errn); | |
122 #endif | |
123 } | |
124 | |
125 std::string error() | |
126 { | |
127 #if defined(_WIN32) | |
128 return error(WSAGetLastError()); | |
129 #else | |
130 return error(errno); | |
131 #endif | |
132 } | |
133 | |
134 /* }}} */ | |
135 | |
136 /* | |
137 * SSL stuff | |
138 * ------------------------------------------------------------------ | |
139 */ | |
140 | |
141 /* {{{ SSL initialization */ | |
142 | |
143 #if !defined(SOCKET_NO_SSL) | |
144 | |
145 namespace ssl { | |
146 | |
147 namespace { | |
148 | |
149 std::mutex mutex; | |
150 std::atomic<bool> initialized{false}; | |
151 | |
152 } // !namespace | |
153 | |
154 void finish() noexcept | |
155 { | |
156 ERR_free_strings(); | |
157 } | |
158 | |
159 void init() noexcept | |
160 { | |
161 std::lock_guard<std::mutex> lock{mutex}; | |
162 | |
163 if (!initialized) { | |
164 initialized = true; | |
165 | |
166 SSL_library_init(); | |
167 SSL_load_error_strings(); | |
168 OpenSSL_add_all_algorithms(); | |
169 | |
170 #if !defined(SOCKET_NO_AUTO_SSL_INIT) | |
171 atexit(finish); | |
172 #endif // SOCKET_NO_AUTO_SSL_INIT | |
173 } | |
174 } | |
175 | |
176 } // !ssl | |
177 | |
178 #endif // SOCKET_NO_SSL | |
179 | |
180 /* }}} */ | |
181 | |
182 /* | |
183 * Error class | |
184 * ------------------------------------------------------------------ | |
185 */ | |
186 | |
187 /* {{{ Error */ | |
188 | |
189 Error::Error(Code code, std::string function) | |
190 : m_code{code} | |
191 , m_function{std::move(function)} | |
192 , m_error{error()} | |
193 { | |
194 } | |
195 | |
196 Error::Error(Code code, std::string function, int n) | |
197 : m_code{code} | |
198 , m_function{std::move(function)} | |
199 , m_error{error(n)} | |
200 { | |
201 } | |
202 | |
203 Error::Error(Code code, std::string function, std::string error) | |
204 : m_code{code} | |
205 , m_function{std::move(function)} | |
206 , m_error{std::move(error)} | |
207 { | |
208 } | |
209 | |
210 /* }}} */ | |
211 | |
212 /* | |
213 * Predefine addressed to be used | |
214 * ------------------------------------------------------------------ | |
215 */ | |
216 | |
217 /* {{{ Addresses */ | |
218 | |
219 namespace address { | |
220 | |
221 /* Default domain */ | |
222 int Ip::m_default{AF_INET}; | |
223 | |
224 Ip::Ip(Type domain) noexcept | |
225 : m_domain(static_cast<int>(domain)) | |
226 { | |
227 assert(m_domain == AF_INET6 || m_domain == AF_INET); | |
228 | |
229 if (m_domain == AF_INET6) { | |
230 std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); | |
231 } else { | |
232 std::memset(&m_sin, 0, sizeof (sockaddr_in)); | |
233 } | |
234 } | |
235 | |
236 Ip::Ip(const std::string &host, int port, Type domain) | |
237 : m_domain(static_cast<int>(domain)) | |
238 { | |
239 assert(m_domain == AF_INET6 || m_domain == AF_INET); | |
240 | |
241 if (host == "*") { | |
242 if (m_domain == AF_INET6) { | |
243 std::memset(&m_sin6, 0, sizeof (sockaddr_in6)); | |
244 | |
245 m_length = sizeof (sockaddr_in6); | |
246 m_sin6.sin6_addr = in6addr_any; | |
247 m_sin6.sin6_family = AF_INET6; | |
248 m_sin6.sin6_port = htons(port); | |
249 } else { | |
250 std::memset(&m_sin, 0, sizeof (sockaddr_in)); | |
251 | |
252 m_length = sizeof (sockaddr_in); | |
253 m_sin.sin_addr.s_addr = INADDR_ANY; | |
254 m_sin.sin_family = AF_INET; | |
255 m_sin.sin_port = htons(port); | |
256 } | |
257 } else { | |
258 addrinfo hints, *res; | |
259 | |
260 std::memset(&hints, 0, sizeof (addrinfo)); | |
261 hints.ai_family = domain; | |
262 | |
263 auto error = getaddrinfo(host.c_str(), std::to_string(port).c_str(), &hints, &res); | |
264 if (error != 0) { | |
265 throw Error{Error::System, "getaddrinfo", gai_strerror(error)}; | |
266 } | |
267 | |
268 if (m_domain == AF_INET6) { | |
269 std::memcpy(&m_sin6, res->ai_addr, res->ai_addrlen); | |
270 } else { | |
271 std::memcpy(&m_sin, res->ai_addr, res->ai_addrlen); | |
272 } | |
273 | |
274 m_length = res->ai_addrlen; | |
275 freeaddrinfo(res); | |
276 } | |
277 } | |
278 | |
279 Ip::Ip(const sockaddr_storage *ss, socklen_t length) noexcept | |
280 : m_length{length} | |
281 , m_domain{ss->ss_family} | |
282 { | |
283 assert(ss->ss_family == AF_INET6 || ss->ss_family == AF_INET); | |
284 | |
285 if (ss->ss_family == AF_INET6) { | |
286 std::memcpy(&m_sin6, ss, length); | |
287 } else if (ss->ss_family == AF_INET) { | |
288 std::memcpy(&m_sin, ss, length); | |
289 } | |
290 } | |
291 | |
292 #if !defined(_WIN32) | |
293 | |
294 Local::Local() noexcept | |
295 { | |
296 std::memset(&m_sun, 0, sizeof (sockaddr_un)); | |
297 } | |
298 | |
299 Local::Local(std::string path, bool rm) noexcept | |
300 : m_path{std::move(path)} | |
301 { | |
302 /* Silently remove the file even if it fails */ | |
303 if (rm) { | |
304 ::remove(m_path.c_str()); | |
305 } | |
306 | |
307 /* Copy the path */ | |
308 std::memset(m_sun.sun_path, 0, sizeof (m_sun.sun_path)); | |
309 std::strncpy(m_sun.sun_path, m_path.c_str(), sizeof (m_sun.sun_path) - 1); | |
310 | |
311 /* Set the parameters */ | |
312 m_sun.sun_family = AF_LOCAL; | |
313 } | |
314 | |
315 Local::Local(const sockaddr_storage *ss, socklen_t length) noexcept | |
316 { | |
317 assert(ss->ss_family == AF_LOCAL); | |
318 | |
319 if (ss->ss_family == AF_LOCAL) { | |
320 std::memcpy(&m_sun, ss, length); | |
321 m_path = reinterpret_cast<const sockaddr_un &>(m_sun).sun_path; | |
322 } | |
323 } | |
324 | |
325 #endif // !_WIN32 | |
326 | |
327 } // !address | |
328 | |
329 /* }}} */ | |
330 | |
331 /* | |
332 * Select | |
333 * ------------------------------------------------------------------ | |
334 */ | |
335 | |
336 /* {{{ Select */ | |
337 | |
338 std::vector<ListenerStatus> Select::wait(const ListenerTable &table, int ms) | |
339 { | |
340 timeval maxwait, *towait; | |
341 fd_set readset; | |
342 fd_set writeset; | |
343 | |
344 FD_ZERO(&readset); | |
345 FD_ZERO(&writeset); | |
346 | |
347 Handle max = 0; | |
348 | |
349 for (const auto &pair : table) { | |
350 if ((pair.second & Condition::Readable) == Condition::Readable) { | |
351 FD_SET(pair.first, &readset); | |
352 } | |
353 if ((pair.second & Condition::Writable) == Condition::Writable) { | |
354 FD_SET(pair.first, &writeset); | |
355 } | |
356 | |
357 if (pair.first > max) { | |
358 max = pair.first; | |
359 } | |
360 } | |
361 | |
362 maxwait.tv_sec = 0; | |
363 maxwait.tv_usec = ms * 1000; | |
364 | |
365 // Set to nullptr for infinite timeout. | |
366 towait = (ms < 0) ? nullptr : &maxwait; | |
367 | |
368 auto error = ::select(max + 1, &readset, &writeset, nullptr, towait); | |
369 if (error == Failure) { | |
370 throw Error{Error::System, "select"}; | |
371 } | |
372 if (error == 0) { | |
373 throw Error{Error::Timeout, "select", TIMEOUT_MSG}; | |
374 } | |
375 | |
376 std::vector<ListenerStatus> sockets; | |
377 | |
378 for (const auto &pair : table) { | |
379 if (FD_ISSET(pair.first, &readset)) { | |
380 sockets.push_back(ListenerStatus{pair.first, Condition::Readable}); | |
381 } | |
382 if (FD_ISSET(pair.first, &writeset)) { | |
383 sockets.push_back(ListenerStatus{pair.first, Condition::Writable}); | |
384 } | |
385 } | |
386 | |
387 return sockets; | |
388 } | |
389 | |
390 /* }}} */ | |
391 | |
392 /* | |
393 * Poll | |
394 * ------------------------------------------------------------------ | |
395 */ | |
396 | |
397 /* {{{ Poll */ | |
398 | |
399 /* | |
400 * Poll implementation | |
401 * ------------------------------------------------------------------ | |
402 */ | |
403 | |
404 #if defined(SOCKET_HAVE_POLL) | |
405 | |
406 #if defined(_WIN32) | |
407 # define poll WSAPoll | |
408 #endif | |
409 | |
410 short Poll::toPoll(Condition condition) const noexcept | |
411 { | |
412 short result(0); | |
413 | |
414 if ((condition & Condition::Readable) == Condition::Readable) { | |
415 result |= POLLIN; | |
416 } | |
417 if ((condition & Condition::Writable) == Condition::Writable) { | |
418 result |= POLLOUT; | |
419 } | |
420 | |
421 return result; | |
422 } | |
423 | |
424 Condition Poll::toCondition(short &event) const noexcept | |
425 { | |
426 Condition condition{Condition::None}; | |
427 | |
428 /* | |
429 * Poll implementations mark the socket differently regarding | |
430 * the disconnection of a socket. | |
431 * | |
432 * At least, even if POLLHUP or POLLIN is set, recv() always | |
433 * return 0 so we mark the socket as readable. | |
434 */ | |
435 if ((event & POLLIN) || (event & POLLHUP)) { | |
436 condition |= Condition::Readable; | |
437 } | |
438 if (event & POLLOUT) { | |
439 condition |= Condition::Writable; | |
440 } | |
441 | |
442 /* Reset event for safety */ | |
443 event = 0; | |
444 | |
445 return condition; | |
446 } | |
447 | |
448 void Poll::set(const ListenerTable &, Handle h, Condition condition, bool add) | |
449 { | |
450 if (add) { | |
451 m_fds.push_back(pollfd{h, toPoll(condition), 0}); | |
452 } else { | |
453 auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { | |
454 return pfd.fd == h; | |
455 }); | |
456 | |
457 it->events |= toPoll(condition); | |
458 } | |
459 } | |
460 | |
461 void Poll::unset(const ListenerTable &, Handle h, Condition condition, bool remove) | |
462 { | |
463 auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { | |
464 return pfd.fd == h; | |
465 }); | |
466 | |
467 if (remove) { | |
468 m_fds.erase(it); | |
469 } else { | |
470 it->events &= ~(toPoll(condition)); | |
471 } | |
472 } | |
473 | |
474 std::vector<ListenerStatus> Poll::wait(const ListenerTable &, int ms) | |
475 { | |
476 auto result = poll(m_fds.data(), m_fds.size(), ms); | |
477 if (result == 0) { | |
478 throw Error{Error::Timeout, "select", TIMEOUT_MSG}; | |
479 } | |
480 if (result < 0) { | |
481 throw Error{Error::System, "poll"}; | |
482 } | |
483 | |
484 std::vector<ListenerStatus> sockets; | |
485 for (auto &fd : m_fds) { | |
486 if (fd.revents != 0) { | |
487 sockets.push_back(ListenerStatus{fd.fd, toCondition(fd.revents)}); | |
488 } | |
489 } | |
490 | |
491 return sockets; | |
492 } | |
493 | |
494 #endif // !SOCKET_HAVE_POLL | |
495 | |
496 /* }}} */ | |
497 | |
498 /* | |
499 * Epoll implementation | |
500 * ------------------------------------------------------------------ | |
501 */ | |
502 | |
503 /* {{{ Epoll */ | |
504 | |
505 #if defined(SOCKET_HAVE_EPOLL) | |
506 | |
507 uint32_t Epoll::toEpoll(Condition condition) const noexcept | |
508 { | |
509 uint32_t events = 0; | |
510 | |
511 if ((condition & Condition::Readable) == Condition::Readable) { | |
512 events |= EPOLLIN; | |
513 } | |
514 if ((condition & Condition::Writable) == Condition::Writable) { | |
515 events |= EPOLLOUT; | |
516 } | |
517 | |
518 return events; | |
519 } | |
520 | |
521 Condition Epoll::toCondition(uint32_t events) const noexcept | |
522 { | |
523 Condition condition{Condition::None}; | |
524 | |
525 if ((events & EPOLLIN) || (events & EPOLLHUP)) { | |
526 condition |= Condition::Readable; | |
527 } | |
528 if (events & EPOLLOUT) { | |
529 condition |= Condition::Writable; | |
530 } | |
531 | |
532 return condition; | |
533 } | |
534 | |
535 void Epoll::update(Handle h, int op, int eflags) | |
536 { | |
537 epoll_event ev; | |
538 | |
539 std::memset(&ev, 0, sizeof (epoll_event)); | |
540 | |
541 ev.events = eflags; | |
542 ev.data.fd = h; | |
543 | |
544 if (epoll_ctl(m_handle, op, h, &ev) < 0) { | |
545 throw Error{Error::System, "epoll_ctl"}; | |
546 } | |
547 } | |
548 | |
549 Epoll::Epoll() | |
550 : m_handle{epoll_create1(0)} | |
551 { | |
552 if (m_handle < 0) { | |
553 throw Error{Error::System, "epoll_create"}; | |
554 } | |
555 } | |
556 | |
557 Epoll::~Epoll() | |
558 { | |
559 close(m_handle); | |
560 } | |
561 | |
562 /* | |
563 * For set and unset, we need to apply the whole flags required, so if the socket | |
564 * was set to Connection::Readable and user add Connection::Writable, we must | |
565 * place both. | |
566 */ | |
567 void Epoll::set(const ListenerTable &table, Handle sc, Condition condition, bool add) | |
568 { | |
569 if (add) { | |
570 update(sc, EPOLL_CTL_ADD, toEpoll(condition)); | |
571 m_events.resize(m_events.size() + 1); | |
572 } else { | |
573 update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) | condition)); | |
574 } | |
575 } | |
576 | |
577 /* | |
578 * Unset is a bit complicated case because Listener tells us which | |
579 * flag to remove but to update epoll descriptor we need to pass | |
580 * the effective flags that we want to be applied. | |
581 * | |
582 * So we put the same flags that are currently effective and remove the | |
583 * requested one. | |
584 */ | |
585 void Epoll::unset(const ListenerTable &table, Handle sc, Condition condition, bool remove) | |
586 { | |
587 if (remove) { | |
588 update(sc, EPOLL_CTL_DEL, 0); | |
589 m_events.resize(m_events.size() - 1); | |
590 } else { | |
591 update(sc, EPOLL_CTL_MOD, toEpoll(table.at(sc) & ~(condition))); | |
592 } | |
593 } | |
594 | |
595 std::vector<ListenerStatus> Epoll::wait(const ListenerTable &, int ms) | |
596 { | |
597 int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms); | |
598 std::vector<ListenerStatus> result; | |
599 | |
600 if (ret == 0) { | |
601 throw Error{Error::Timeout, "epoll_wait", TIMEOUT_MSG}; | |
602 } | |
603 if (ret < 0) { | |
604 throw Error{Error::System, "epoll_wait"}; | |
605 } | |
606 | |
607 for (int i = 0; i < ret; ++i) { | |
608 result.push_back(ListenerStatus{m_events[i].data.fd, toCondition(m_events[i].events)}); | |
609 } | |
610 | |
611 return result; | |
612 } | |
613 | |
614 #endif // !SOCKET_HAVE_EPOLL | |
615 | |
616 /* }}} */ | |
617 | |
618 /* | |
619 * Kqueue implementation | |
620 * ------------------------------------------------------------------ | |
621 */ | |
622 | |
623 /* {{{ Kqueue */ | |
624 | |
625 #if defined(SOCKET_HAVE_KQUEUE) | |
626 | |
627 Kqueue::Kqueue() | |
628 : m_handle(kqueue()) | |
629 { | |
630 if (m_handle < 0) { | |
631 throw Error{Error::System, "kqueue"}; | |
632 } | |
633 } | |
634 | |
635 Kqueue::~Kqueue() | |
636 { | |
637 close(m_handle); | |
638 } | |
639 | |
640 void Kqueue::update(Handle h, int filter, int kflags) | |
641 { | |
642 struct kevent ev; | |
643 | |
644 EV_SET(&ev, h, filter, kflags, 0, 0, nullptr); | |
645 | |
646 if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) { | |
647 throw Error{Error::System, "kevent"}; | |
648 } | |
649 } | |
650 | |
651 void Kqueue::set(const ListenerTable &, Handle h, Condition condition, bool add) | |
652 { | |
653 if ((condition & Condition::Readable) == Condition::Readable) { | |
654 update(h, EVFILT_READ, EV_ADD | EV_ENABLE); | |
655 } | |
656 if ((condition & Condition::Writable) == Condition::Writable) { | |
657 update(h, EVFILT_WRITE, EV_ADD | EV_ENABLE); | |
658 } | |
659 | |
660 if (add) { | |
661 m_result.resize(m_result.size() + 1); | |
662 } | |
663 } | |
664 | |
665 void Kqueue::unset(const ListenerTable &, Handle h, Condition condition, bool remove) | |
666 { | |
667 if ((condition & Condition::Readable) == Condition::Readable) { | |
668 update(h, EVFILT_READ, EV_DELETE); | |
669 } | |
670 if ((condition & Condition::Writable) == Condition::Writable) { | |
671 update(h, EVFILT_WRITE, EV_DELETE); | |
672 } | |
673 | |
674 if (remove) { | |
675 m_result.resize(m_result.size() - 1); | |
676 } | |
677 } | |
678 | |
679 std::vector<ListenerStatus> Kqueue::wait(const ListenerTable &, int ms) | |
680 { | |
681 std::vector<ListenerStatus> sockets; | |
682 timespec ts = { 0, 0 }; | |
683 timespec *pts = (ms <= 0) ? nullptr : &ts; | |
684 | |
685 ts.tv_sec = ms / 1000; | |
686 ts.tv_nsec = (ms % 1000) * 1000000; | |
687 | |
688 int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); | |
689 | |
690 if (nevents == 0) { | |
691 throw Error{Error::Timeout, "kevent", TIMEOUT_MSG}; | |
692 } | |
693 if (nevents < 0) { | |
694 throw Error{Error::System, "kevent"}; | |
695 } | |
696 | |
697 for (int i = 0; i < nevents; ++i) { | |
698 sockets.push_back(ListenerStatus{ | |
699 static_cast<Handle>(m_result[i].ident), | |
700 m_result[i].filter == EVFILT_READ ? Condition::Readable : Condition::Writable | |
701 }); | |
702 } | |
703 | |
704 return sockets; | |
705 } | |
706 | |
707 #endif // !SOCKET_HAVE_KQUEUE | |
708 | |
709 /* }}} */ | |
710 | |
711 } // !net | |
712 | |
713 } // !irccd |