| 1 | /* |
| 2 | * IXSocketConnect.cpp |
| 3 | * Author: Benjamin Sergeant |
| 4 | * Copyright (c) 2018 Machine Zone, Inc. All rights reserved. |
| 5 | */ |
| 6 | |
| 7 | #include "IXSocketConnect.h" |
| 8 | |
| 9 | #include "IXDNSLookup.h" |
| 10 | #include "IXNetSystem.h" |
| 11 | #include "IXSelectInterrupt.h" |
| 12 | #include "IXSocket.h" |
| 13 | #include "IXUniquePtr.h" |
| 14 | #include <fcntl.h> |
| 15 | #include <string.h> |
| 16 | #include <sys/types.h> |
| 17 | |
| 18 | // Android needs extra headers for TCP_NODELAY and IPPROTO_TCP |
| 19 | #ifdef ANDROID |
| 20 | #include <linux/in.h> |
| 21 | #include <linux/tcp.h> |
| 22 | #endif |
| 23 | #include <ixwebsocket/IXSelectInterruptFactory.h> |
| 24 | |
| 25 | namespace ix |
| 26 | { |
| 27 | // |
| 28 | // This function can be cancelled every 50 ms |
| 29 | // This is important so that we don't block the main UI thread when shutting down a |
| 30 | // connection which is already trying to reconnect, and can be blocked waiting for |
| 31 | // ::connect to respond. |
| 32 | // |
| 33 | int SocketConnect::connectToAddress(const struct addrinfo* address, |
| 34 | std::string& errMsg, |
| 35 | const CancellationRequest& isCancellationRequested) |
| 36 | { |
| 37 | errMsg = "no error" ; |
| 38 | |
| 39 | socket_t fd = socket(address->ai_family, address->ai_socktype, address->ai_protocol); |
| 40 | if (fd < 0) |
| 41 | { |
| 42 | errMsg = "Cannot create a socket" ; |
| 43 | return -1; |
| 44 | } |
| 45 | |
| 46 | // Set the socket to non blocking mode, so that slow responses cannot |
| 47 | // block us for too long |
| 48 | SocketConnect::configure(fd); |
| 49 | |
| 50 | int res = ::connect(fd, address->ai_addr, address->ai_addrlen); |
| 51 | |
| 52 | if (res == -1 && !Socket::isWaitNeeded()) |
| 53 | { |
| 54 | errMsg = strerror(Socket::getErrno()); |
| 55 | Socket::closeSocket(fd); |
| 56 | return -1; |
| 57 | } |
| 58 | |
| 59 | for (;;) |
| 60 | { |
| 61 | if (isCancellationRequested && isCancellationRequested()) // Must handle timeout as well |
| 62 | { |
| 63 | Socket::closeSocket(fd); |
| 64 | errMsg = "Cancelled" ; |
| 65 | return -1; |
| 66 | } |
| 67 | |
| 68 | int timeoutMs = 10; |
| 69 | bool readyToRead = false; |
| 70 | SelectInterruptPtr selectInterrupt = ix::createSelectInterrupt(); |
| 71 | PollResultType pollResult = Socket::poll(readyToRead, timeoutMs, fd, selectInterrupt); |
| 72 | |
| 73 | if (pollResult == PollResultType::Timeout) |
| 74 | { |
| 75 | continue; |
| 76 | } |
| 77 | else if (pollResult == PollResultType::Error) |
| 78 | { |
| 79 | Socket::closeSocket(fd); |
| 80 | errMsg = std::string("Connect error: " ) + strerror(Socket::getErrno()); |
| 81 | return -1; |
| 82 | } |
| 83 | else if (pollResult == PollResultType::ReadyForWrite) |
| 84 | { |
| 85 | return fd; |
| 86 | } |
| 87 | else |
| 88 | { |
| 89 | Socket::closeSocket(fd); |
| 90 | errMsg = std::string("Connect error: " ) + strerror(Socket::getErrno()); |
| 91 | return -1; |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | int SocketConnect::connect(const std::string& hostname, |
| 97 | int port, |
| 98 | std::string& errMsg, |
| 99 | const CancellationRequest& isCancellationRequested) |
| 100 | { |
| 101 | // |
| 102 | // First do DNS resolution |
| 103 | // |
| 104 | auto dnsLookup = std::make_shared<DNSLookup>(hostname, port); |
| 105 | struct addrinfo* res = dnsLookup->resolve(errMsg, isCancellationRequested); |
| 106 | if (res == nullptr) |
| 107 | { |
| 108 | return -1; |
| 109 | } |
| 110 | |
| 111 | int sockfd = -1; |
| 112 | |
| 113 | // iterate through the records to find a working peer |
| 114 | struct addrinfo* address; |
| 115 | for (address = res; address != nullptr; address = address->ai_next) |
| 116 | { |
| 117 | // |
| 118 | // Second try to connect to the remote host |
| 119 | // |
| 120 | sockfd = connectToAddress(address, errMsg, isCancellationRequested); |
| 121 | if (sockfd != -1) |
| 122 | { |
| 123 | break; |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | freeaddrinfo(res); |
| 128 | return sockfd; |
| 129 | } |
| 130 | |
| 131 | // FIXME: configure is a terrible name |
| 132 | void SocketConnect::configure(int sockfd) |
| 133 | { |
| 134 | // 1. disable Nagle's algorithm |
| 135 | int flag = 1; |
| 136 | setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char*) &flag, sizeof(flag)); |
| 137 | |
| 138 | // 2. make socket non blocking |
| 139 | #ifdef _WIN32 |
| 140 | unsigned long nonblocking = 1; |
| 141 | ioctlsocket(sockfd, FIONBIO, &nonblocking); |
| 142 | #else |
| 143 | fcntl(sockfd, F_SETFL, O_NONBLOCK); // make socket non blocking |
| 144 | #endif |
| 145 | |
| 146 | // 3. (apple) prevent SIGPIPE from being emitted when the remote end disconnect |
| 147 | #ifdef SO_NOSIGPIPE |
| 148 | int value = 1; |
| 149 | setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void*) &value, sizeof(value)); |
| 150 | #endif |
| 151 | } |
| 152 | } // namespace ix |
| 153 | |