Directory: | ./ |
---|---|
File: | pdserv/src/TCP.cpp |
Date: | 2025-08-17 04:10:43 |
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 | 22844 | bool TCPSocket::readable(struct timeval *timeout) const | |
138 | { | ||
139 | 22844 | fd_set readfds; | |
140 | 22844 | FD_ZERO(&readfds); | |
141 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 22844 times.
|
22844 | FD_SET(fd, &readfds); |
142 |
1/2✓ Branch 2 taken 22844 times.
✗ Branch 3 not taken.
|
22844 | 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 | 15604 | ssize_t TCPSocket::readData(void *buf, size_t len) | |
250 | { | ||
251 | 15604 | int rv = ::read(fd, buf, len); | |
252 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 15604 times.
|
15604 | 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 | 22844 | bool TCPSession::isPendingRead(int msec) const | |
307 | { | ||
308 | 22844 | struct timeval timeval; | |
309 | 22844 | timeval.tv_sec = msec / 1000; | |
310 | 22844 | timeval.tv_usec = (msec - timeval.tv_sec*1000) * 1000; | |
311 | |||
312 |
2/4✓ Branch 1 taken 22844 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 22844 times.
✗ Branch 5 not taken.
|
22844 | return readable(msec < -1 ? 0 : &timeval); |
313 | } | ||
314 | |||
315 | ///////////////////////////////////////////////////////////////////////////// | ||
316 | 15604 | ssize_t TCPSession::readData(void *buf, size_t len) | |
317 | { | ||
318 | 15604 | 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 |