| Directory: | ./ |
|---|---|
| File: | pdserv/src/TCP.cpp |
| Date: | 2025-11-02 04:09:49 |
| Exec | Total | Coverage | |
|---|---|---|---|
| Lines: | 112 | 149 | 75.2% |
| Branches: | 58 | 179 | 32.4% |
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /***************************************************************************** | ||
| 2 | * | ||
| 3 | * Copyright 2017 Richard Hacker (lerichi at gmx dot net) | ||
| 4 | * | ||
| 5 | * This file is part of the pdserv library. | ||
| 6 | * | ||
| 7 | * The pdserv library is free software: you can redistribute it and/or modify | ||
| 8 | * it under the terms of the GNU Lesser General Public License as published | ||
| 9 | * by the Free Software Foundation, either version 3 of the License, or (at | ||
| 10 | * your option) any later version. | ||
| 11 | * | ||
| 12 | * The pdserv library is distributed in the hope that it will be useful, but | ||
| 13 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | ||
| 14 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public | ||
| 15 | * License for more details. | ||
| 16 | * | ||
| 17 | * You should have received a copy of the GNU Lesser General Public License | ||
| 18 | * along with the pdserv library. If not, see <http://www.gnu.org/licenses/>. | ||
| 19 | * | ||
| 20 | ****************************************************************************/ | ||
| 21 | |||
| 22 | #include "TCP.h" | ||
| 23 | #include "Debug.h" | ||
| 24 | |||
| 25 | #include <cerrno> | ||
| 26 | #include <cstring> // memset() | ||
| 27 | #include <stdexcept> | ||
| 28 | #include <sstream> | ||
| 29 | #include <unistd.h> // close() | ||
| 30 | #include <sys/select.h> // select() | ||
| 31 | #include <sys/time.h> // struct timeval | ||
| 32 | |||
| 33 | // socket stuff: getaddrinfo(), gai_strerror(), connect(), bind(), | ||
| 34 | // setsockopt(), inet_ntop() | ||
| 35 | #include <sys/types.h> | ||
| 36 | #include <sys/socket.h> | ||
| 37 | #include <arpa/inet.h> | ||
| 38 | #include <netdb.h> | ||
| 39 | #include <pthread.h> | ||
| 40 | |||
| 41 | using namespace net; | ||
| 42 | |||
| 43 | ///////////////////////////////////////////////////////////////////////////// | ||
| 44 | ///////////////////////////////////////////////////////////////////////////// | ||
| 45 | 372 | TCPSocket::TCPSocket(): fd(-1) | |
| 46 | { | ||
| 47 | 372 | } | |
| 48 | |||
| 49 | ///////////////////////////////////////////////////////////////////////////// | ||
| 50 | 744 | TCPSocket::~TCPSocket() | |
| 51 | { | ||
| 52 | 372 | close(); | |
| 53 | 372 | } | |
| 54 | |||
| 55 | ///////////////////////////////////////////////////////////////////////////// | ||
| 56 | 224 | bool TCPSocket::listenTo( | |
| 57 | const std::string& interface, const std::string& port, int backlog) | ||
| 58 | { | ||
| 59 | 224 | struct addrinfo hints; | |
| 60 | 224 | ::memset(&hints, 0, sizeof hints); | |
| 61 |
1/2✓ Branch 2 taken 224 times.
✗ Branch 3 not taken.
|
224 | hints.ai_family = interface.empty() ? AF_INET : AF_UNSPEC; |
| 62 | 224 | hints.ai_socktype = SOCK_STREAM; /* TCP socket */ | |
| 63 | 224 | hints.ai_protocol = 0; /* Any protocol */ | |
| 64 | 224 | hints.ai_flags = AI_PASSIVE; | |
| 65 | |||
| 66 | 224 | struct addrinfo *result; | |
| 67 |
1/2✓ Branch 3 taken 224 times.
✗ Branch 4 not taken.
|
224 | int s = ::getaddrinfo(interface.empty() ? NULL : interface.c_str(), |
| 68 |
1/2✓ Branch 2 taken 224 times.
✗ Branch 3 not taken.
|
448 | port.c_str(), &hints, &result); |
| 69 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 224 times.
|
224 | if (s != 0) { |
| 70 | ✗ | std::ostringstream os; | |
| 71 | os << "getaddrinfo(" | ||
| 72 | << "address=" << interface << ", " | ||
| 73 | << "port=" << port << "): " | ||
| 74 | ✗ | << ::gai_strerror(s); | |
| 75 | ✗ | throw os.str(); | |
| 76 | } | ||
| 77 | |||
| 78 |
1/2✓ Branch 2 taken 224 times.
✗ Branch 3 not taken.
|
224 | close(); |
| 79 | |||
| 80 | /* getaddrinfo() returns a list of address structures. | ||
| 81 | Try each address until we successfully connect(2). | ||
| 82 | If socket(2) (or connect(2)) fails, we (close the socket | ||
| 83 | and) try the next address. */ | ||
| 84 | |||
| 85 | 224 | const char* errfunc = 0; | |
| 86 | 448 | for (struct addrinfo* rp = result; | |
| 87 |
4/6✓ Branch 0 taken 224 times.
✓ Branch 1 taken 224 times.
✓ Branch 2 taken 224 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 224 times.
✗ Branch 6 not taken.
|
672 | rp and !errfunc and fd < 0; rp = rp->ai_next) { |
| 88 | |||
| 89 | 224 | fd = ::socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); | |
| 90 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 224 times.
|
224 | if (fd < 0) |
| 91 | ✗ | continue; | |
| 92 | |||
| 93 | // Reuse port | ||
| 94 | 224 | int optval = 1; | |
| 95 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 224 times.
|
448 | if (::setsockopt(fd, |
| 96 | 224 | SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval)) { | |
| 97 | ✗ | errfunc = "setsockopt"; | |
| 98 | } | ||
| 99 |
2/2✓ Branch 4 taken 219 times.
✓ Branch 5 taken 5 times.
|
224 | else if (!::bind(fd, rp->ai_addr, rp->ai_addrlen)) { |
| 100 |
1/2✓ Branch 2 taken 219 times.
✗ Branch 3 not taken.
|
219 | if (!::listen(fd, backlog)) { |
| 101 | 219 | addr = *rp->ai_addr; | |
| 102 | } | ||
| 103 | else { | ||
| 104 | ✗ | errfunc = "listen"; | |
| 105 | } | ||
| 106 | } | ||
| 107 | else | ||
| 108 |
2/4✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 224 times.
✗ Branch 6 not taken.
|
5 | close(); |
| 109 | } | ||
| 110 | |||
| 111 | 224 | ::freeaddrinfo(result); | |
| 112 | |||
| 113 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 224 times.
|
224 | if (errfunc) |
| 114 | ✗ | throw std::string(errfunc).append("(): ").append(::strerror(errno)); | |
| 115 | |||
| 116 | 224 | return fd >= 0; | |
| 117 | } | ||
| 118 | |||
| 119 | ///////////////////////////////////////////////////////////////////////////// | ||
| 120 | 601 | void TCPSocket::close() | |
| 121 | { | ||
| 122 | 601 | int cancel_state; | |
| 123 |
1/2✓ Branch 1 taken 601 times.
✗ Branch 2 not taken.
|
601 | pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state); |
| 124 |
2/2✓ Branch 1 taken 372 times.
✓ Branch 2 taken 229 times.
|
601 | if (fd >= 0) |
| 125 |
1/2✓ Branch 2 taken 372 times.
✗ Branch 3 not taken.
|
372 | ::close(fd); |
| 126 | 601 | fd = -1; | |
| 127 |
1/2✓ Branch 1 taken 601 times.
✗ Branch 2 not taken.
|
601 | pthread_setcancelstate(cancel_state, nullptr); |
| 128 | 601 | } | |
| 129 | |||
| 130 | ///////////////////////////////////////////////////////////////////////////// | ||
| 131 | ✗ | TCPSocket::operator bool() const | |
| 132 | { | ||
| 133 | ✗ | return fd >= 0; | |
| 134 | } | ||
| 135 | |||
| 136 | ///////////////////////////////////////////////////////////////////////////// | ||
| 137 | 22925 | bool TCPSocket::readable(struct timeval *timeout) const | |
| 138 | { | ||
| 139 | 22925 | fd_set readfds; | |
| 140 | 22925 | FD_ZERO(&readfds); | |
| 141 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 22925 times.
|
22925 | FD_SET(fd, &readfds); |
| 142 |
1/2✓ Branch 2 taken 22925 times.
✗ Branch 3 not taken.
|
22925 | return ::select(fd + 1, &readfds, 0, 0, timeout) > 0; |
| 143 | } | ||
| 144 | |||
| 145 | 297 | TCPSocket* TCPSocket::select(std::vector<TCPSocket*> const& sockets, int msec) | |
| 146 | { | ||
| 147 | 297 | struct timeval timeout; | |
| 148 | 297 | timeout.tv_sec = msec / 1000; | |
| 149 | 297 | timeout.tv_usec = 1000*(msec - 1000*timeout.tv_sec); | |
| 150 | |||
| 151 | 297 | fd_set fds; | |
| 152 | 297 | FD_ZERO(&fds); | |
| 153 | 297 | int max_fd = -1; | |
| 154 |
2/2✓ Branch 5 taken 432 times.
✓ Branch 6 taken 297 times.
|
729 | for (TCPSocket *s : sockets) |
| 155 | { | ||
| 156 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 432 times.
|
432 | FD_SET(s->fd, &fds); |
| 157 | 432 | max_fd = std::max(max_fd, s->fd); | |
| 158 | } | ||
| 159 |
3/4✗ Branch 0 not taken.
✓ Branch 1 taken 297 times.
✓ Branch 3 taken 148 times.
✓ Branch 4 taken 149 times.
|
297 | ::select(max_fd + 1, &fds, nullptr, nullptr, |
| 160 | msec > -1 ? &timeout : nullptr); | ||
| 161 |
4/8✓ Branch 5 taken 214 times.
✗ Branch 6 not taken.
✗ Branch 11 not taken.
✓ Branch 12 taken 148 times.
✗ Branch 14 not taken.
✓ Branch 15 taken 148 times.
✗ Branch 18 not taken.
✓ Branch 19 taken 148 times.
|
214 | for (TCPSocket *s : sockets) |
| 162 | { | ||
| 163 |
3/4✗ Branch 1 not taken.
✓ Branch 2 taken 214 times.
✓ Branch 6 taken 148 times.
✓ Branch 7 taken 66 times.
|
214 | if (FD_ISSET(s->fd, &fds)) |
| 164 | 148 | return s; | |
| 165 | } | ||
| 166 | ✗ | return nullptr; | |
| 167 | } | ||
| 168 | |||
| 169 | ///////////////////////////////////////////////////////////////////////////// | ||
| 170 | 148 | std::string TCPSocket::accept(TCPSocket* server) | |
| 171 | { | ||
| 172 | 148 | struct sockaddr_storage sa; | |
| 173 | 148 | socklen_t len = sizeof sa; | |
| 174 | 148 | struct sockaddr* addr = (struct sockaddr*)&sa; | |
| 175 | |||
| 176 |
1/2✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
|
148 | fd = ::accept(server->fd, addr, &len); |
| 177 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 148 times.
|
148 | if (fd < 0) |
| 178 | ✗ | throw std::string("accept() on ") | |
| 179 | ✗ | .append(server->getAddr()) | |
| 180 | ✗ | .append(": ") | |
| 181 | ✗ | .append(strerror(errno)); | |
| 182 | |||
| 183 | 148 | this->addr = *addr; | |
| 184 | |||
| 185 | // Set server FQDN | ||
| 186 | 148 | char host[NI_MAXHOST]; | |
| 187 |
2/4✓ Branch 1 taken 148 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 148 times.
✗ Branch 4 not taken.
|
148 | if (!::getnameinfo(addr, len, |
| 188 | host, NI_MAXHOST, 0, 0, NI_NUMERICSERV)) | ||
| 189 |
1/2✓ Branch 3 taken 148 times.
✗ Branch 4 not taken.
|
148 | return host; |
| 190 | |||
| 191 | ✗ | return "Unknown"; | |
| 192 | } | ||
| 193 | |||
| 194 | ///////////////////////////////////////////////////////////////////////////// | ||
| 195 | 296 | void TCPSocket::setSockOpt(int level, int opt, int val) | |
| 196 | { | ||
| 197 |
1/2✗ Branch 2 not taken.
✓ Branch 3 taken 296 times.
|
296 | if (::setsockopt(fd, level, opt, &val, sizeof val)) { |
| 198 | ✗ | throw std::string("setsockopt() on ") | |
| 199 | ✗ | .append(getAddr()) | |
| 200 | ✗ | .append(": ") | |
| 201 | ✗ | .append(strerror(errno)); | |
| 202 | } | ||
| 203 | 296 | } | |
| 204 | |||
| 205 | ///////////////////////////////////////////////////////////////////////////// | ||
| 206 | ✗ | std::string TCPSocket::reject() | |
| 207 | { | ||
| 208 | ✗ | TCPSocket s; | |
| 209 | ✗ | std::string name = s.accept(this); | |
| 210 | |||
| 211 | ✗ | return s.getAddr().append(" (").append(name).append(1,')'); | |
| 212 | } | ||
| 213 | |||
| 214 | ///////////////////////////////////////////////////////////////////////////// | ||
| 215 | 432 | std::string TCPSocket::getAddr(char sep) const | |
| 216 | { | ||
| 217 | 864 | std::string host; | |
| 218 | 432 | uint16_t port = 0; | |
| 219 | |||
| 220 |
1/3✓ Branch 1 taken 432 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
432 | switch (addr.sa_family) { |
| 221 | 432 | case AF_INET: | |
| 222 | { | ||
| 223 | 432 | struct sockaddr_in *s = (struct sockaddr_in*)&addr; | |
| 224 | 432 | char str[INET_ADDRSTRLEN]; | |
| 225 |
1/2✓ Branch 1 taken 432 times.
✗ Branch 2 not taken.
|
432 | if (::inet_ntop(AF_INET, &s->sin_addr, str, sizeof str)) { |
| 226 |
1/2✓ Branch 1 taken 432 times.
✗ Branch 2 not taken.
|
432 | host = str; |
| 227 | 432 | port = ntohs(s->sin_port); | |
| 228 | 432 | } | |
| 229 | } | ||
| 230 | 432 | break; | |
| 231 | ✗ | case AF_INET6: | |
| 232 | { | ||
| 233 | ✗ | struct sockaddr_in6 *s = (struct sockaddr_in6*)&addr; | |
| 234 | ✗ | char str[INET6_ADDRSTRLEN]; | |
| 235 | ✗ | if (::inet_ntop(AF_INET6, &s->sin6_addr, str, sizeof str)) { | |
| 236 | ✗ | host = str; | |
| 237 | ✗ | port = ntohs(s->sin6_port); | |
| 238 | } | ||
| 239 | } | ||
| 240 | ✗ | break; | |
| 241 | } | ||
| 242 | |||
| 243 |
1/2✓ Branch 2 taken 432 times.
✗ Branch 3 not taken.
|
864 | std::ostringstream os; |
| 244 |
1/2✓ Branch 2 taken 432 times.
✗ Branch 3 not taken.
|
432 | os << port; |
| 245 |
3/6✓ Branch 2 taken 432 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 432 times.
✗ Branch 8 not taken.
✓ Branch 12 taken 432 times.
✗ Branch 13 not taken.
|
864 | return host + sep + os.str(); |
| 246 | } | ||
| 247 | |||
| 248 | ///////////////////////////////////////////////////////////////////////////// | ||
| 249 | 15698 | ssize_t TCPSocket::readData(void *buf, size_t len) | |
| 250 | { | ||
| 251 | 15698 | int rv = ::read(fd, buf, len); | |
| 252 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15698 times.
|
15698 | return rv >= 0 ? rv : -errno;; |
| 253 | } | ||
| 254 | |||
| 255 | ///////////////////////////////////////////////////////////////////////////// | ||
| 256 | 1537 | ssize_t TCPSocket::writeData(const void *buf, size_t len) | |
| 257 | { | ||
| 258 | 1537 | int rv = ::send(fd, buf, len, MSG_NOSIGNAL); | |
| 259 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1537 times.
|
1537 | return rv >= 0 ? rv : -errno;; |
| 260 | } | ||
| 261 | |||
| 262 | ///////////////////////////////////////////////////////////////////////////// | ||
| 263 | ///////////////////////////////////////////////////////////////////////////// | ||
| 264 | 224 | TCPServer::TCPServer(int backlog): backlog(backlog) | |
| 265 | { | ||
| 266 | 224 | } | |
| 267 | |||
| 268 | ///////////////////////////////////////////////////////////////////////////// | ||
| 269 | 224 | bool TCPServer::listenTo( | |
| 270 | const std::string& interface, const std::string& port) | ||
| 271 | { | ||
| 272 | 224 | return TCPSocket::listenTo(interface, port, backlog); | |
| 273 | } | ||
| 274 | |||
| 275 | ///////////////////////////////////////////////////////////////////////////// | ||
| 276 | ✗ | bool TCPServer::isPendingConnection(int msec) | |
| 277 | { | ||
| 278 | ✗ | struct timeval timeout; | |
| 279 | ✗ | timeout.tv_sec = msec / 1000; | |
| 280 | ✗ | timeout.tv_usec = 1000*(msec - 1000*timeout.tv_sec); | |
| 281 | |||
| 282 | ✗ | return readable(msec < 0 ? 0 : &timeout); | |
| 283 | } | ||
| 284 | |||
| 285 | ///////////////////////////////////////////////////////////////////////////// | ||
| 286 | ✗ | std::string TCPServer::reject() | |
| 287 | { | ||
| 288 | ✗ | return TCPSocket::reject(); | |
| 289 | } | ||
| 290 | |||
| 291 | ///////////////////////////////////////////////////////////////////////////// | ||
| 292 | ///////////////////////////////////////////////////////////////////////////// | ||
| 293 | 148 | TCPSession::TCPSession(TCPServer *server): | |
| 294 |
2/4✓ Branch 5 taken 148 times.
✗ Branch 6 not taken.
✓ Branch 9 taken 148 times.
✗ Branch 10 not taken.
|
148 | pthread::Thread("pdserv-tcp") |
| 295 | { | ||
| 296 |
1/2✓ Branch 2 taken 148 times.
✗ Branch 3 not taken.
|
148 | peerName = accept(server); |
| 297 | 148 | } | |
| 298 | |||
| 299 | ///////////////////////////////////////////////////////////////////////////// | ||
| 300 | 148 | std::string TCPSession::getPeerName() const | |
| 301 | { | ||
| 302 | 148 | return peerName; | |
| 303 | } | ||
| 304 | |||
| 305 | ///////////////////////////////////////////////////////////////////////////// | ||
| 306 | 22925 | bool TCPSession::isPendingRead(int msec) const | |
| 307 | { | ||
| 308 | 22925 | struct timeval timeval; | |
| 309 | 22925 | timeval.tv_sec = msec / 1000; | |
| 310 | 22925 | timeval.tv_usec = (msec - timeval.tv_sec*1000) * 1000; | |
| 311 | |||
| 312 |
2/4✓ Branch 1 taken 22925 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22925 times.
✗ Branch 5 not taken.
|
22925 | return readable(msec < -1 ? 0 : &timeval); |
| 313 | } | ||
| 314 | |||
| 315 | ///////////////////////////////////////////////////////////////////////////// | ||
| 316 | 15698 | ssize_t TCPSession::readData(void *buf, size_t len) | |
| 317 | { | ||
| 318 | 15698 | return TCPSocket::readData(buf, len); | |
| 319 | } | ||
| 320 | |||
| 321 | ///////////////////////////////////////////////////////////////////////////// | ||
| 322 | 1537 | ssize_t TCPSession::writeData(const void *buf, size_t len) | |
| 323 | { | ||
| 324 | 1537 | return TCPSocket::writeData(buf, len); | |
| 325 | } | ||
| 326 |