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