Cogs.Core
Connection.cpp
1#if !defined( EMSCRIPTEN )
2
3#include "Connection.h"
4#include "Network.h"
5
6#include "../Logging/Logger.h"
7
8#define __STD_FORMAT_MACROS
9
10#ifdef _WIN32
11 #include <system_error>
12 #include <Ws2tcpip.h>
13#else
14 #include <errno.h>
15 #include <fcntl.h>
16 #include <netdb.h>
17 #include <unistd.h>
18
19 #include <netinet/ip.h>
20 #include <netinet/tcp.h>
21 #include <sys/socket.h>
22 #include <sys/types.h>
23#endif
24
25#include <inttypes.h>
26
27#ifdef _WIN32
28#define FUNC_STR __FUNCTION__
29#elif __GNUC__
30#define FUNC_STR __PRETTY_FUNCTION__
31#else
32#define FUNC_STR __func__
33#endif
34
35#define check_net_error(_x) check_net_error_impl(__FILE__, __LINE__, FUNC_STR, (_x))
36
37namespace
38{
39 Cogs::Logging::Log logger = Cogs::Logging::getLogger("Connection");
40
41#if defined( _WIN32 )
42 constexpr int sendFlags = 0;
43#else
44 constexpr int sendFlags = MSG_NOSIGNAL;
45#endif
46
47 void check_net_error_impl(const char *file, unsigned int line, const char *func, int err)
48 {
49#ifdef _WIN32
50 LOG_ERROR(logger, "%s(%d): %s: WSAGetLastError %d (%s)", file, line, func, err, std::system_category().message(err).c_str());
51#else
52 LOG_ERROR(logger, "%s(%d): %s: errno %d: %s", file, line, func, err, strerror(err));
53#endif
54 }
55}
56
61{
62#if defined( _WIN32 )
63 u_long nonblocking = val;
64 ioctlsocket(socket, FIONBIO, &nonblocking);
65#else
66 int nonblocking = val ? 1 : 0;
67 fcntl(socket, F_SETFL, O_NONBLOCK, &nonblocking);
68#endif
69}
70
75bool Cogs::Network::ConnectionBase::bind(AddrIn ip, uint16_t port)
76{
77 create();
78 if (socket != InvalidSocket) {
79 sockAddr = SockaddrIn(ip, port);
80
81 int result = ::bind(socket, sockAddr.getPtr(), sockAddr.size());
82 if (!result) {
83 if (!(flags & cDoNotProcess)) {
84 addConnection(this);
85 }
86 return true;
87 }
88 LOG_ERROR(logger, "Failed to bind to %s:%d.", ip.string().c_str(), port);
89#if defined( _WIN32 )
90 check_net_error(WSAGetLastError());
91#else
92 check_net_error(errno);
93#endif
94 disconnect();
95 }
96 return false;
97}
98
105bool Cogs::Network::ConnectionBase::send(const void* data, uint64_t byteCount, uint64_t& bytesSent)
106{
107 const char* ptr = reinterpret_cast<const char*>(data);
108 bytesSent = 0;
109
110 while (bytesSent < byteCount) {
111 int64_t justSent = ::send(socket, ptr, static_cast<int>(byteCount - bytesSent), sendFlags);
112
113 if (justSent == -1) {
114#if defined( _WIN32 )
115 int err = WSAGetLastError();
116 if (err == WSAEWOULDBLOCK) {
117 return true;
118 }
119#else
120 int err = errno;
121 if ((err == EAGAIN) || (err == EWOULDBLOCK)) {
122 return true;
123 }
124#endif
125 check_net_error(err);
126 disconnect();
127 return false;
128 }
129 bytesSent += justSent;
130 ptr += justSent;
131 }
132 return true;
133}
134
141bool Cogs::Network::ConnectionBase::receive(void* buffer, uint64_t byteCount, uint64_t& bytesReceived)
142{
143 char* ptr = reinterpret_cast<char*>(buffer);
144 bytesReceived = 0;
145
146 for (int64_t justReceived; bytesReceived < byteCount; bytesReceived += justReceived, ptr += justReceived) {
147 justReceived = ::recv(socket, ptr, static_cast<int>(byteCount - bytesReceived), 0);
148
149 switch (justReceived) {
150 case 0: {
151 disconnect();
152 return false;
153 }
154 case -1: {
155#if defined( _WIN32 )
156 int err = WSAGetLastError();
157 if (err == WSAEWOULDBLOCK) {
158 return true;
159 }
160#else
161 int err = errno;
162 if ((err == EAGAIN) || (err == EWOULDBLOCK)) {
163 return true;
164 }
165#endif
166 check_net_error(err);
167 disconnect();
168 return false;
169 }
170 }
171 }
172 return true;
173}
174
182bool Cogs::Network::ConnectionBase::sendTo(const void* data, uint64_t byteCount, uint64_t& bytesSent, const SockaddrIn& addr)
183{
184 bytesSent = 0;
185
186 assert (type() == SOCK_DGRAM);
187
188 int64_t justSent = ::sendto(socket, static_cast<const char*>(data), static_cast<int>(byteCount), sendFlags, addr.getPtr(), addr.size());
189
190 if (justSent == -1) {
191#if defined( _WIN32 )
192 int err = WSAGetLastError();
193 if (err == WSAEWOULDBLOCK) {
194 return true;
195 }
196#else
197 int err = errno;
198 if ((err == EAGAIN) || (err == EWOULDBLOCK)) {
199 return true;
200 }
201#endif
202 check_net_error(err);
203 disconnect();
204 return false;
205 }
206 bytesSent = justSent;
207 return true;
208}
209
220bool Cogs::Network::ConnectionBase::recvFrom(void* buffer, uint64_t byteCount, uint64_t& bytesReceived, SockaddrIn &addr)
221{
222 bytesReceived = 0;
223
224 assert (type() == SOCK_DGRAM);
225
226 socklen_t fromlen = addr.size();
227 int64_t justReceived = ::recvfrom(socket, static_cast<char*>(buffer), static_cast<int>(byteCount), 0, addr.getPtr(), &fromlen);
228
229 if (justReceived == -1) {
230#if defined( _WIN32 )
231 int err = WSAGetLastError();
232 if (err == WSAEWOULDBLOCK) {
233 return true;
234 }
235#else
236 int err = errno;
237 if ((err == EAGAIN) || (err == EWOULDBLOCK)) {
238 return true;
239 }
240#endif
241 check_net_error(err);
242 disconnect();
243 return false;
244 }
245 bytesReceived = justReceived;
246 return true;
247}
248
253{
254 if (socket != InvalidSocket) {
255 disconnect();
256 }
257 socket = ::socket(family(), type(), proto());
258
259 if (socket == InvalidSocket) {
260 LOG_ERROR(logger, "Failed to create socket.");
261 }
262}
263
268{
269 if (socket != InvalidSocket) {
270 LOG_INFO(logger, "Closing socket %" PRIu64 ".", static_cast<uint64_t>(socket));
271
272#if defined( _WIN32 )
273 ::closesocket(socket);
274#else
275 ::close(socket);
276#endif
277 socket = InvalidSocket;
278 }
279}
280
281/*
282 * UDP
283 */
284Cogs::Network::ConnectionUDP::~ConnectionUDP() {
285 removeConnection(this);
286 disconnect();
287}
288
290 create();
291 if (socket != InvalidSocket) {
292 int enableAddrReuse = 1;
293
294 setNonblocking(true);
295 setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&enableAddrReuse), sizeof(enableAddrReuse));
296
297 if (multicastAdapter.string() != "0.0.0.0") {
298 ip_mreq data = {};
299
300 data.imr_multiaddr = ip.addr;
301 data.imr_interface = multicastAdapter.addr;
302
303 if (setsockopt(socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, reinterpret_cast<char*>(&data), sizeof(data))) {
304 LOG_ERROR(logger, "Socket %" PRIdPTR " failed to join multicast group %s using adapter %s.", static_cast< uintptr_t >( socket ), ip.string().c_str(), multicastAdapter.string().c_str());
305 }
306 else {
307 LOG_INFO(logger, "Socket %" PRIdPTR " joined multicast group %s using adapter %s.", static_cast< uintptr_t >( socket ), ip.string().c_str(), multicastAdapter.string().c_str());
308 }
309 sockAddr = SockaddrIn(INADDR_ANY, port);
310 }
311 else {
312 sockAddr = SockaddrIn(ip, port);
313 }
314
315 if (!::bind(socket, sockAddr.getPtr(), sockAddr.size())) {
316 if (!(flags & cDoNotProcess)) {
317 addConnection(this);
318 }
319 return true;
320 }
321 LOG_ERROR(logger, "Failed to bind to %s:%d.", ip.string().c_str(), port);
322#if defined( _WIN32 )
323 check_net_error(WSAGetLastError());
324#else
325 check_net_error(errno);
326#endif
327 disconnect();
328 }
329 return false;
330}
331
332bool Cogs::Network::ConnectionUDP::disconnect()
333{
334 close();
335 return true;
336}
337/*
338 * TCP
339 */
340
345{
346#if defined( _WIN32 )
347 ::shutdown(socket, SD_BOTH);
348#else
349 ::shutdown(socket, SHUT_RDWR);
350#endif
351}
352
357{
358 flags &= ~cNoDelay;
359
360 if (socket != InvalidSocket) {
361 int nodelay = val;
362 setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char*>(&nodelay), sizeof(nodelay));
363 }
364}
365
371Cogs::Network::ConnectionTCP::ConnectionTCP(Socket s, const SockaddrIn& addr)
372{
373 flags = cNoDelay | cConnected;
374 socket = s;
375 sockAddr = addr;
376 setNonblocking();
377 addConnection(this);
378}
379
384{
385 removeConnection(this);
386 disconnect();
387}
388
395{
396 sockAddr = addr;
397 return reconnect();
398}
399
404{
405 create();
406 if (socket != InvalidSocket) {
407 LOG_INFO(logger, "Reconnecting socket %" PRIu64 " to %s:%d.", static_cast<uint64_t>(socket), sockAddr.ip().string().c_str(), sockAddr.port());
408
409 // If we have a connection timeout value defined, we'll set the socket to
410 // non-blocking, and call select with the timeout value.
411 if (connectTimeout) {
412 setNonblocking();
413 }
414
415 int result = ::connect(socket, sockAddr.getPtr(), sockAddr.size());
416
417#if defined( _WIN32 )
418 if ((result < 0) && (WSAGetLastError() == WSAEWOULDBLOCK )) {
419#else
420 if ((result < 0) && (errno == EINPROGRESS)) {
421#endif
422 fd_set waitSet;
423 struct timeval timeout;
424
425 FD_ZERO(&waitSet);
426 FD_SET(socket, &waitSet);
427
428 timeout.tv_sec = static_cast<long>(connectTimeout / 1000);
429 timeout.tv_usec = static_cast<long>((connectTimeout - (timeout.tv_sec * 1000)) * 1000);
430
431 result = select(static_cast<int>(socket + 1), nullptr, &waitSet, nullptr, &timeout);
432 if (result > 0) {
433 int socketError;
434 socklen_t len = sizeof(socketError);
435
436 getsockopt(socket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&socketError), &len);
437
438 if (socketError == 0) {
439 // Connection succeeded.
440 result = 0;
441 }
442 }
443 else if (result == 0) {
444 // Connect timed out before completion.
445 result = -1;
446 }
447 }
448
449 if (result == 0) {
450 setNoDelay(flags & cNoDelay);
451 setNonblocking();
452 flags |= cConnected;
453 flags &= ~cRemove;
454 if (!(flags & cDoNotProcess)) {
455 addConnection(this);
456 }
457 return true;
458 }
459 else if (isAutoConnectEnabled()) {
460 if (!(flags & cDoNotProcess)) {
461 addConnection(this);
462 }
463 close();
464 }
465 else {
466#if defined( _WIN32 )
467 check_net_error(WSAGetLastError());
468#else
469 check_net_error(errno);
470#endif
471 close();
472 }
473 }
474 return false;
475}
476
484{
485 if (socket != InvalidSocket) {
486 LOG_INFO(logger, "Disconnecting socket %" PRIu64 ".", static_cast<uint64_t>(socket));
487
488 shutdown();
489 close();
490 }
491 flags &= ~(cListening | cConnected);
492
493 if (flags & cAutoReconnect) {
494 return !reconnect();
495 }
496 return true;
497}
498
503bool Cogs::Network::ConnectionTCP::listen(AddrIn ip, uint16_t port)
504{
505 create();
506 if (socket != InvalidSocket) {
507 sockAddr = SockaddrIn(ip, port);
508
509 if (!::bind(socket, sockAddr.getPtr(), sockAddr.size())) {
510 if (!::listen(socket, SOMAXCONN)) {
511 LOG_INFO(logger, "Socket %" PRIu64 " listening for connections on %s:%d.", static_cast<uint64_t>(socket), sockAddr.ip().string().c_str(), port);
512
513 flags |= cListening;
514 if (!(flags & cDoNotProcess)) {
515 addConnection(this);
516 }
517 return true;
518 }
519 }
520#if defined( _WIN32 )
521 check_net_error(WSAGetLastError());
522#else
523 check_net_error(errno);
524#endif
525 disconnect();
526 }
527 return false;
528}
529
534{
535 if (flags & cListening) {
536 Socket newsocket;
537 sockaddr_in addr;
538 socklen_t size = sizeof(addr);
539
540 newsocket = ::accept(socket, reinterpret_cast<sockaddr*>(&addr), &size);
541 if (newsocket != InvalidSocket) {
542 SockaddrIn sockaddr(addr);
543 ConnectionTCP* newConnection = accept(newsocket, sockaddr);
544
545 if (newConnection) {
546 newConnection->setNoDelay(flags & cNoDelay);
547 return true;
548 }
549#if defined( _WIN32 )
550 ::shutdown(newsocket, SD_BOTH);
551 ::closesocket(newsocket);
552#else
553 ::shutdown(newsocket, SHUT_RDWR);
554 ::close(newsocket);
555#endif
556 }
557 return false;
558 }
559 return true;
560}
561
562#endif
Log implementation class.
Definition: LogManager.h:140
void create()
Internal wrapper for the socket() call.
Definition: Connection.cpp:252
void close()
Internal wrapper for closing the socket.
Definition: Connection.cpp:267
bool receive(void *buffer, uint64_t byteCount, uint64_t &bytesReceived)
Attempts to read the specified number of bytes from the network socket this connection represents.
Definition: Connection.cpp:141
bool send(const void *data, uint64_t byteCount, uint64_t &bytesSent)
Attempts to send as much of the specified data as possible.
Definition: Connection.cpp:105
bool recvFrom(void *buffer, uint64_t byteCount, uint64_t &bytesReceived, SockaddrIn &addr)
Attempts to read the specified number of bytes from the network socket this connection represents.
Definition: Connection.cpp:220
void setNonblocking(bool val=true)
Sets socket non-blocking mode to val.
Definition: Connection.cpp:60
bool sendTo(const void *data, uint64_t byteCount, uint64_t &bytesSent, const SockaddrIn &addr)
Attempts to send the specified data as a single datagram.
Definition: Connection.cpp:182
virtual bool processIncoming() override
Manages accepting incoming connections when this connection is a listener.
Definition: Connection.cpp:533
virtual bool connect(const SockaddrIn &addr)
Sets up this connection ready to connect to the specified remote address and port.
Definition: Connection.cpp:394
void shutdown()
Shutdown this connection.
Definition: Connection.cpp:344
virtual bool reconnect() override
Reconnects this connection to the remote host previously specified through a call to connect.
Definition: Connection.cpp:403
void setNoDelay(bool val=true)
Set the TCP_NODELAY flag (Enable / Disables Nagles algorithm).
Definition: Connection.cpp:356
virtual bool disconnect() override
Disconnects this connection.
Definition: Connection.cpp:483
virtual ~ConnectionTCP()
Destroys this connection.
Definition: Connection.cpp:383
virtual bool bind(AddrIn ip, uint16_t port) override
Sets this connection up as a listener that will listen for and respond to incoming connection request...
Definition: Connection.cpp:289
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181