Instrument Neutral Distributed Interface INDI 2.2.5
utils.cpp
Go to the documentation of this file.
1/*******************************************************************************
2 Copyright(c) 2022 Ludovic Pollet. All rights reserved.
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License version 2 as published by the Free Software Foundation.
7
8 This library is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 Library General Public License for more details.
12
13 You should have received a copy of the GNU Library General Public License
14 along with this library; see the file COPYING.LIB. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
17*******************************************************************************/
18
19#ifndef _GNU_SOURCE
20#define _GNU_SOURCE // needed for siginfo_t and sigaction
21#endif
22
23#include <unistd.h>
24#include <sys/types.h>
25#include <sys/socket.h>
26#include <sys/un.h>
27#include <netdb.h>
28#include <netinet/in.h>
29#include <errno.h>
30#include <fcntl.h>
31#include <sys/socket.h>
32#include <signal.h>
33#include <system_error>
34
35#include "utils.h"
36
38{
39 signal(SIGPIPE, SIG_IGN);
40}
41
42static void initUnixSocketAddr(const std::string &unixAddr, struct sockaddr_un &serv_addr_un, socklen_t &addrlen, bool bind)
43{
44 memset(&serv_addr_un, 0, sizeof(serv_addr_un));
45 serv_addr_un.sun_family = AF_UNIX;
46
47#ifdef __linux__
48 (void) bind;
49
50 // Using abstract socket path to avoid filesystem boilerplate
51 strncpy(serv_addr_un.sun_path + 1, unixAddr.c_str(), sizeof(serv_addr_un.sun_path) - 1);
52
53 int len = offsetof(struct sockaddr_un, sun_path) + unixAddr.size() + 1;
54
55 addrlen = len;
56#else
57 // Using filesystem socket path
58 strncpy(serv_addr_un.sun_path, unixAddr.c_str(), sizeof(serv_addr_un.sun_path) - 1);
59
60 int len = offsetof(struct sockaddr_un, sun_path) + unixAddr.size();
61
62 if (bind)
63 {
64 unlink(unixAddr.c_str());
65 }
66#endif
67 addrlen = len;
68}
69
70
71int unixSocketListen(const std::string &unixAddr)
72{
73 struct sockaddr_un serv_addr_un;
74 socklen_t addrlen;
75
76 int sockfd;
77 if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
78 {
79 throw std::system_error(errno, std::generic_category(), "Socket");
80 }
81
82#ifndef __linux__
83 fcntl(sockfd, F_SETFD, FD_CLOEXEC);
84#endif
85
86 int reuse = 1;
87 if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
88 {
89 throw std::system_error(errno, std::generic_category(), "setsockopt");
90 }
91
92 initUnixSocketAddr(unixAddr, serv_addr_un, addrlen, true);
93
94 if (bind(sockfd, (struct sockaddr *)&serv_addr_un, addrlen) < 0)
95 {
96 throw std::system_error(errno, std::generic_category(), "Bind to " + unixAddr);
97 }
98
99 /* willing to accept connections with a backlog of 5 pending */
100 if (::listen(sockfd, 5) < 0)
101 {
102 throw std::system_error(errno, std::generic_category(), "Listen to " + unixAddr);
103 }
104
105 return sockfd;
106}
107
108int tcpSocketListen(int port)
109{
110 struct sockaddr_in serv_socket;
111 int sockfd;
112 int reuse = 1;
113
114 /* make socket endpoint */
115 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
116 {
117 throw std::system_error(errno, std::generic_category(), "Socket");
118 }
119
120 /* bind to given port for any IP address */
121 memset(&serv_socket, 0, sizeof(serv_socket));
122 serv_socket.sin_family = AF_INET;
123 serv_socket.sin_addr.s_addr = htonl(INADDR_ANY);
124 serv_socket.sin_port = htons((unsigned short)port);
125 if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
126 {
127 throw std::system_error(errno, std::generic_category(), "SO_REUSEADDR");
128 }
129 if (bind(sockfd, (struct sockaddr *)&serv_socket, sizeof(serv_socket)) < 0)
130 {
131 throw std::system_error(errno, std::generic_category(), "Bind to " + std::to_string(port));
132 }
133
134 /* willing to accept connections with a backlog of 5 pending */
135 if (::listen(sockfd, 5) < 0)
136 {
137 throw std::system_error(errno, std::generic_category(), "Listen to " + std::to_string(port));
138 }
139
140 return sockfd;
141}
142
143
145{
146 /* get a private connection to new client */
147 int cli_fd = ::accept(fd, 0, 0);
148 if (cli_fd < 0)
149 {
150 throw std::system_error(errno, std::generic_category(), "Accept failed");
151 }
152 return cli_fd;
153}
154
155int unixSocketConnect(const std::string &unixAddr, bool failAllowed)
156{
157 struct sockaddr_un serv_addr_un;
158 socklen_t addrlen;
159
160 int sockfd;
161 if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
162 {
163 throw std::system_error(errno, std::generic_category(), "Socket");
164 }
165
166 initUnixSocketAddr(unixAddr, serv_addr_un, addrlen, false);
167
168 int ret = ::connect(sockfd, (struct sockaddr *)&serv_addr_un, addrlen);
169 if (ret != -1)
170 {
171 return sockfd;
172 }
173
174 int e = errno;
175 close(sockfd);
176 if (!failAllowed)
177 {
178 throw std::system_error(e, std::generic_category(), "Connect to " + unixAddr);
179 }
180 return -1;
181}
182
183void unixSocketSendFds(int fd, int count, int * fds)
184{
185 struct msghdr msgh;
186 struct iovec iov[1];
187 char buff[1] = {0};
188 int cmsghdrlength;
189 struct cmsghdr * cmsgh;
190
191
192 cmsghdrlength = CMSG_SPACE((count * sizeof(int)));
193 // FIXME: abort on alloc error here
194 cmsgh = (struct cmsghdr*)malloc(cmsghdrlength);
195
196 /* Write the fd as ancillary data */
197 cmsgh->cmsg_len = CMSG_LEN(count * sizeof(int));
198 cmsgh->cmsg_level = SOL_SOCKET;
199 cmsgh->cmsg_type = SCM_RIGHTS;
200 msgh.msg_control = cmsgh;
201 msgh.msg_controllen = cmsghdrlength;
202 for(int i = 0; i < count; ++i)
203 {
204 ((int *) CMSG_DATA(CMSG_FIRSTHDR(&msgh)))[i] = fds[i];
205 }
206
207 iov[0].iov_base = buff;
208 iov[0].iov_len = 1;
209
210 msgh.msg_flags = 0;
211 msgh.msg_name = NULL;
212 msgh.msg_namelen = 0;
213 msgh.msg_iov = iov;
214 msgh.msg_iovlen = 1;
215
216 int ret = sendmsg(fd, &msgh, MSG_NOSIGNAL);
217
218 if (ret == -1)
219 {
220 throw std::system_error(errno, std::generic_category(), "Failed to send fds");
221 }
222 if (ret == 0)
223 {
224 throw std::runtime_error("Channel closed when sending fds");
225 }
226}
227
228void unixSocketRecvFds(int fd, int count, int * fdsDest)
229{
230 char buf[1];
231 struct msghdr msgh;
232 struct iovec iov;
233
234 union
235 {
236 struct cmsghdr cmsgh;
237 /* Space large enough to hold an 'int' */
238 char control[CMSG_SPACE(16 * sizeof(int))];
239 } control_un;
240
241 if (count > 16)
242 {
243 throw std::runtime_error("Cannot pass that amount of fds");
244 }
245
246
247
248 iov.iov_base = buf;
249 iov.iov_len = 1;
250
251 msgh.msg_name = NULL;
252 msgh.msg_namelen = 0;
253 msgh.msg_iov = &iov;
254 msgh.msg_iovlen = 1;
255 msgh.msg_flags = 0;
256 msgh.msg_control = control_un.control;
257 msgh.msg_controllen = sizeof(control_un.control);
258
259 int recvflag;
260#ifdef __linux__
261 recvflag = MSG_CMSG_CLOEXEC;
262#else
263 recvflag = 0;
264#endif
265
266 int size = recvmsg(fd, &msgh, recvflag);
267 if (size == -1)
268 {
269 throw std::system_error(errno, std::generic_category(), "Could not receive fds");
270 }
271
272 for (struct cmsghdr * cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; cmsg = CMSG_NXTHDR(&msgh, cmsg))
273 {
274 if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS)
275 {
276 int fdCount = 0;
277 while(cmsg->cmsg_len >= CMSG_LEN((fdCount + 1) * sizeof(int)))
278 {
279 fdCount++;
280 }
281 fprintf(stderr, "Received fd : %d\n", fdCount);
282 if (fdCount != count)
283 {
284 throw std::runtime_error("Wrong number of fds received");
285 }
286 int * fds = (int*)CMSG_DATA(cmsg);
287 for(int i = 0; i < fdCount; ++i)
288 {
289#ifndef __linux__
290 fcntl(fds[i], F_SETFD, FD_CLOEXEC);
291#endif
292 fdsDest[i] = fds[i];
293 }
294
295 return;
296 }
297 }
298 throw std::runtime_error("Did not receive fds");
299}
300
301int tcpSocketConnect(const std::string &host, int port, bool failAllowed)
302{
303 struct sockaddr_in serv_addr;
304 int sockfd;
305
306 /* lookup host address */
307 auto hp = gethostbyname(host.c_str());
308 if (!hp)
309 {
310 throw std::system_error(h_errno, std::generic_category(), "Could not resolve " + host);
311 }
312
313 /* create a socket to the INDI server */
314 (void)memset((char *)&serv_addr, 0, sizeof(serv_addr));
315 serv_addr.sin_family = AF_INET;
316 serv_addr.sin_addr.s_addr = ((struct in_addr *)(hp->h_addr_list[0]))->s_addr;
317 serv_addr.sin_port = htons(port);
318 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
319 {
320 throw std::system_error(errno, std::generic_category(), "socket");
321 }
322
323 /* connect */
324 if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
325 {
326 if (!failAllowed)
327 {
328 throw std::system_error(errno, std::generic_category(), "Connect to " + host);
329 }
330 auto e = errno;
331 close(sockfd);
332 errno = e;
333 return -1;
334 }
335
336 return sockfd;
337}
338
339
341{
342 struct sockaddr_in addr;
343 socklen_t len = sizeof(addr);
344 if (getsockname(fd, (struct sockaddr *)&addr, &len) < 0)
345 {
346 throw std::system_error(errno, std::generic_category(), "getsockname");
347 }
348 return ntohs(addr.sin_port);
349}
350
352{
353 // Let the kernel pick a free port, then release it for the process under test
354 int fd = tcpSocketListen(0);
355 int port = tcpSocketPort(fd);
356 close(fd);
357 return port;
358}
359
360std::string uniqueTestName(const std::string &prefix)
361{
362 return prefix + "-" + std::to_string(getpid());
363}
int errno
int fd
int socketAccept(int fd)
Definition utils.cpp:144
int unixSocketListen(const std::string &unixAddr)
Definition utils.cpp:71
std::string uniqueTestName(const std::string &prefix)
Definition utils.cpp:360
int findFreeTcpPort()
Definition utils.cpp:351
int tcpSocketPort(int fd)
Definition utils.cpp:340
int tcpSocketListen(int port)
Definition utils.cpp:108
void unixSocketRecvFds(int fd, int count, int *fdsDest)
Definition utils.cpp:228
int unixSocketConnect(const std::string &unixAddr, bool failAllowed)
Definition utils.cpp:155
void unixSocketSendFds(int fd, int count, int *fds)
Definition utils.cpp:183
void setupSigPipe()
Definition utils.cpp:37
int tcpSocketConnect(const std::string &host, int port, bool failAllowed)
Definition utils.cpp:301