|
1 /** |
|
2 * Relational pipes |
|
3 * Copyright © 2022 František Kučera (Frantovo.cz, GlobalCode.info) |
|
4 * |
|
5 * This program is free software: you can redistribute it and/or modify |
|
6 * it under the terms of the GNU General Public License as published by |
|
7 * the Free Software Foundation, version 3 of the License. |
|
8 * |
|
9 * This program is distributed in the hope that it will be useful, |
|
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 * GNU General Public License for more details. |
|
13 * |
|
14 * You should have received a copy of the GNU General Public License |
|
15 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
16 */ |
|
17 #include <string> |
|
18 #include <cstring> |
|
19 #include <unistd.h> |
|
20 #include <stdexcept> |
|
21 #include <arpa/inet.h> |
|
22 #include <sys/types.h> |
|
23 #include <sys/socket.h> |
|
24 #include <sys/un.h> |
|
25 #include <netinet/in.h> |
|
26 #include <vector> |
|
27 #include <memory> |
|
28 #include <regex> |
|
29 #include <iostream> |
|
30 #include <netdb.h> |
|
31 |
|
32 #include "Socket.h" |
|
33 |
|
34 namespace relpipe { |
|
35 namespace in { |
|
36 namespace socket { |
|
37 |
|
38 using namespace relpipe::in::socket::options; |
|
39 |
|
40 static const std::string findOption(SocketOptions options, std::string name, bool required = false, const std::string defaultValue = "") { |
|
41 for (auto o : options) if (o.name == name) return o.value; |
|
42 if (required) throw std::invalid_argument("Option " + name + " is required but was not found"); |
|
43 else return defaultValue; |
|
44 } |
|
45 |
|
46 class FD { |
|
47 private: |
|
48 int fd; |
|
49 public: |
|
50 |
|
51 FD(int fd) : fd(fd) { |
|
52 }; |
|
53 |
|
54 virtual ~FD() { |
|
55 close(fd); |
|
56 } |
|
57 |
|
58 int getFD() { |
|
59 return fd; |
|
60 } |
|
61 }; |
|
62 |
|
63 static void check(int result, std::string message) { |
|
64 if (result == 0); // OK |
|
65 else throw std::logic_error("Got error result: " + message + ": " + strerror(errno) + " (result=" + std::to_string(result) + ", errno=" + std::to_string(errno) + ")"); |
|
66 } |
|
67 |
|
68 class AddressInfos { |
|
69 private: |
|
70 std::shared_ptr<addrinfo> addrInfo; |
|
71 |
|
72 AddressInfos(addrinfo* addrInfo) : addrInfo(std::shared_ptr<addrinfo>(addrInfo, freeaddrinfo)) { |
|
73 } |
|
74 |
|
75 public: |
|
76 |
|
77 virtual ~AddressInfos() { |
|
78 } |
|
79 |
|
80 class AddressInfo { |
|
81 private: |
|
82 std::shared_ptr<addrinfo> parent; // to avoid premature deletion of the whole structure |
|
83 public: |
|
84 const addrinfo * const ai; |
|
85 |
|
86 AddressInfo(const addrinfo * const ai, std::shared_ptr<addrinfo> parent) : ai(ai), parent(parent) { |
|
87 } |
|
88 |
|
89 const std::string toString() const { |
|
90 char buffer[INET6_ADDRSTRLEN] = {0}; |
|
91 if (ai->ai_family == AF_INET) return inet_ntop(ai->ai_family, &((sockaddr_in const *) ai->ai_addr)->sin_addr, buffer, sizeof (buffer)); // TODO: check 0 result |
|
92 else if (ai->ai_family == AF_INET6) return inet_ntop(ai->ai_family, &((sockaddr_in6 const *) ai->ai_addr)->sin6_addr, buffer, sizeof (buffer)); // TODO: check 0 result |
|
93 else return "unknown address family: " + std::to_string(ai->ai_family); |
|
94 } |
|
95 }; |
|
96 |
|
97 static AddressInfos getAddressInfos(const std::string& host, const std::string& port, int socketType = SOCK_STREAM, int protocol = IPPROTO_TCP) { |
|
98 struct addrinfo query; |
|
99 memset(&query, sizeof (query), 0); |
|
100 query.ai_family = AF_UNSPEC; |
|
101 query.ai_socktype = socketType; |
|
102 query.ai_protocol = protocol; |
|
103 query.ai_flags = AI_ALL; |
|
104 |
|
105 struct addrinfo* addrInfo; |
|
106 check(getaddrinfo(host.c_str(), port.c_str(), &query, &addrInfo), "getaddrinfo"); |
|
107 |
|
108 return AddressInfos(addrInfo); |
|
109 } |
|
110 |
|
111 const std::size_t size() const { |
|
112 std::size_t size = 0; |
|
113 for (addrinfo* ai = addrInfo.get(); ai; ai = ai->ai_next) size++; |
|
114 return size; |
|
115 } |
|
116 |
|
117 const AddressInfo operator[](std::size_t index) const { |
|
118 for (addrinfo* ai = addrInfo.get(); ai; index--) { |
|
119 if (index == 0) return AddressInfo(ai, addrInfo); |
|
120 else ai = ai->ai_next; |
|
121 } |
|
122 |
|
123 throw std::out_of_range("invalid index for AddressInfo: " + std::to_string(index)); |
|
124 } |
|
125 |
|
126 }; |
|
127 |
|
128 template<class SocketClass, class... MoreArgs> static std::shared_ptr<SocketClass> openClientSocket(const SocketOptions& options, int socketType, int protocol, MoreArgs... moreArgs) { |
|
129 AddressInfos remoteAddresses = AddressInfos::getAddressInfos( |
|
130 findOption(options, OPTION_HOST, true), |
|
131 findOption(options, OPTION_PORT, true), |
|
132 socketType, |
|
133 protocol); |
|
134 |
|
135 return std::shared_ptr<SocketClass>(new SocketClass(remoteAddresses[0], moreArgs...)); |
|
136 } |
|
137 |
|
138 /** |
|
139 * abstract class for sockets that use sendmsg() / recvmsg() |
|
140 */ |
|
141 class MSGSocket : public Socket { |
|
142 protected: |
|
143 FD socket; |
|
144 |
|
145 void sendmsg(const std::string& message) { |
|
146 iovec iov[1]; |
|
147 msghdr msg = {}; |
|
148 msg.msg_iov = iov; |
|
149 msg.msg_iov[0].iov_base = (void*) message.c_str(); |
|
150 msg.msg_iov[0].iov_len = message.size(); |
|
151 msg.msg_iovlen = sizeof (iov) / sizeof (iov[0]); |
|
152 ssize_t written = ::sendmsg(socket.getFD(), &msg, 0); |
|
153 if (written != message.size()) throw std::logic_error("writing to the socket failed"); |
|
154 } |
|
155 public: |
|
156 |
|
157 MSGSocket(int socket) : socket(socket) { |
|
158 } |
|
159 |
|
160 void send(const OutgoingMessage& message) { |
|
161 sendmsg(message.data); |
|
162 } |
|
163 |
|
164 }; |
|
165 |
|
166 class UDPClientSocket : public Socket { |
|
167 private: |
|
168 AddressInfos::AddressInfo remoteAddress; |
|
169 useconds_t delay = 0; |
|
170 FD socket; |
|
171 |
|
172 public: |
|
173 |
|
174 UDPClientSocket(AddressInfos::AddressInfo remoteAddress) : remoteAddress(remoteAddress), socket(::socket(remoteAddress.ai->ai_family, remoteAddress.ai->ai_socktype, remoteAddress.ai->ai_protocol)) { |
|
175 } |
|
176 |
|
177 static std::shared_ptr<Socket> open(const SocketOptions& options) { |
|
178 auto socket = openClientSocket<UDPClientSocket>(options, SOCK_DGRAM, IPPROTO_UDP); |
|
179 socket->delay = std::stol(findOption(options, OPTION_DELAY, false, "0")); // TODO: Move to SocketHandler? Or delete. |
|
180 return socket; |
|
181 } |
|
182 |
|
183 void send(const OutgoingMessage& message) override { |
|
184 auto ai = remoteAddress.ai; |
|
185 sendto(socket.getFD(), message.data.c_str(), message.data.size(), 0, ai->ai_addr, ai->ai_addrlen); |
|
186 if (delay) usleep(delay); |
|
187 } |
|
188 |
|
189 const IncomingMessage receive() override { |
|
190 // TODO: UDP receive() |
|
191 return IncomingMessage("TODO: receive() a message"); |
|
192 } |
|
193 }; |
|
194 |
|
195 class UDPServerSocket : public Socket { |
|
196 private: |
|
197 AddressInfos::AddressInfo localAddress; |
|
198 sockaddr_in6 remoteAddressForSending = {}; |
|
199 useconds_t delay = 0; |
|
200 FD socket; |
|
201 |
|
202 public: |
|
203 |
|
204 UDPServerSocket(AddressInfos::AddressInfo remoteAddress) : localAddress(remoteAddress), socket(::socket(remoteAddress.ai->ai_family, remoteAddress.ai->ai_socktype, remoteAddress.ai->ai_protocol)) { |
|
205 } |
|
206 |
|
207 static std::shared_ptr<Socket> open(const SocketOptions& options) { |
|
208 auto socket = openClientSocket<UDPServerSocket>(options, SOCK_DGRAM, IPPROTO_UDP); |
|
209 auto ai = socket->localAddress.ai; |
|
210 int reuseAddr = true; |
|
211 check(::setsockopt(socket->socket.getFD(), SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof (reuseAddr)), "setsockopt SO_REUSEADDR"); |
|
212 check(::bind(socket->socket.getFD(), ai->ai_addr, ai->ai_addrlen), "bind"); |
|
213 return socket; |
|
214 } |
|
215 |
|
216 void send(const OutgoingMessage& message) override { |
|
217 // receive a message first to get a remote address: |
|
218 char buffer[8192]; |
|
219 memset((char *) &remoteAddressForSending, 0, sizeof (remoteAddressForSending)); |
|
220 socklen_t remoteAddressSize = sizeof (remoteAddressForSending); |
|
221 ssize_t msgSize = recvfrom(socket.getFD(), buffer, sizeof (buffer), 0, (sockaddr*) & remoteAddressForSending, &remoteAddressSize); |
|
222 |
|
223 // respond with our messsage: |
|
224 sendto(socket.getFD(), message.data.c_str(), message.data.size(), 0, (sockaddr*) & remoteAddressForSending, remoteAddressSize); |
|
225 } |
|
226 |
|
227 const IncomingMessage receive() override { |
|
228 char buffer[8192]; |
|
229 sockaddr_in6 remoteAddress; |
|
230 memset((char *) &remoteAddress, 0, sizeof (remoteAddress)); |
|
231 socklen_t remoteAddressSize = sizeof (remoteAddress); |
|
232 ssize_t msgSize = recvfrom(socket.getFD(), buffer, sizeof (buffer), 0, (sockaddr*) & remoteAddress, &remoteAddressSize); |
|
233 check(msgSize < 0, "recvfrom"); |
|
234 |
|
235 IncomingMessage message(std::string(buffer, std::min(sizeof (buffer), (size_t) msgSize))); |
|
236 |
|
237 // TODO: move to a common method |
|
238 char hostBuffer[INET6_ADDRSTRLEN] = {0}; |
|
239 if (remoteAddress.sin6_family == AF_INET) { |
|
240 sockaddr_in* remoteAddress4 = (sockaddr_in*) & remoteAddress; |
|
241 message.remoteHost = inet_ntop(remoteAddress4->sin_family, &remoteAddress4->sin_addr, hostBuffer, sizeof (hostBuffer)); // TODO: check 0 result |
|
242 message.remotePort = remoteAddress4->sin_port; |
|
243 } else if (remoteAddress.sin6_family == AF_INET6) { |
|
244 message.remoteHost = inet_ntop(remoteAddress.sin6_family, &remoteAddress.sin6_addr, hostBuffer, sizeof (hostBuffer)); // TODO: check 0 result |
|
245 message.remotePort = remoteAddress.sin6_port; |
|
246 } |
|
247 |
|
248 return message; |
|
249 } |
|
250 }; |
|
251 |
|
252 class TCPClientSocket : public Socket { |
|
253 private: |
|
254 AddressInfos::AddressInfo remoteAddress; |
|
255 |
|
256 public: |
|
257 |
|
258 TCPClientSocket(AddressInfos::AddressInfo remoteAddress) : remoteAddress(remoteAddress) { |
|
259 } |
|
260 |
|
261 static std::shared_ptr<Socket> open(const SocketOptions& options) { |
|
262 return openClientSocket<TCPClientSocket>(options, SOCK_STREAM, IPPROTO_TCP); |
|
263 } |
|
264 |
|
265 void send(const OutgoingMessage& message) override { |
|
266 auto ai = remoteAddress.ai; |
|
267 FD s(::socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)); |
|
268 check(::connect(s.getFD(), ai->ai_addr, ai->ai_addrlen), "connect socket"); |
|
269 ssize_t written = ::write(s.getFD(), message.data.c_str(), message.data.size()); |
|
270 if (written != message.data.size()) throw std::logic_error("writing to the socket failed"); |
|
271 // TODO: partial writes, repeat |
|
272 } |
|
273 |
|
274 const IncomingMessage receive() override { |
|
275 // TODO: TCP receive() |
|
276 return IncomingMessage("TODO: receive() a message"); |
|
277 } |
|
278 }; |
|
279 |
|
280 class TCPServerSocket : public Socket { |
|
281 private: |
|
282 AddressInfos::AddressInfo localAddress; |
|
283 FD socket; |
|
284 public: |
|
285 |
|
286 TCPServerSocket(AddressInfos::AddressInfo localAddress) : localAddress(localAddress), socket(::socket(localAddress.ai->ai_family, localAddress.ai->ai_socktype, localAddress.ai->ai_protocol)) { |
|
287 } |
|
288 |
|
289 static std::shared_ptr<Socket> open(const SocketOptions& options) { |
|
290 auto socket = openClientSocket<TCPServerSocket>(options, SOCK_STREAM, IPPROTO_TCP); |
|
291 auto ai = socket->localAddress.ai; |
|
292 int reuseAddr = true; |
|
293 check(::setsockopt(socket->socket.getFD(), SOL_SOCKET, SO_REUSEADDR, &reuseAddr, sizeof (reuseAddr)), "setsockopt SO_REUSEADDR"); |
|
294 check(::bind(socket->socket.getFD(), ai->ai_addr, ai->ai_addrlen), "bind"); |
|
295 check(::listen(socket->socket.getFD(), 10), "listen"); // TODO: configurable backlog connection count? |
|
296 return socket; |
|
297 } |
|
298 |
|
299 void send(const OutgoingMessage& message) override { |
|
300 FD clientSocket(accept(socket.getFD(), nullptr, 0)); |
|
301 ssize_t written = ::write(clientSocket.getFD(), message.data.c_str(), message.data.size()); |
|
302 if (written != message.data.size()) throw std::logic_error("writing to the socket failed"); |
|
303 // TODO: partial writes, repeat |
|
304 } |
|
305 |
|
306 const IncomingMessage receive() override { |
|
307 // TODO: TCP receive() |
|
308 return IncomingMessage("TODO: receive() a message"); |
|
309 } |
|
310 }; |
|
311 |
|
312 class SCTPClientSocket : public MSGSocket { |
|
313 private: |
|
314 AddressInfos::AddressInfo remoteAddress; |
|
315 public: |
|
316 |
|
317 SCTPClientSocket(AddressInfos::AddressInfo remoteAddress) : remoteAddress(remoteAddress), MSGSocket(::socket(remoteAddress.ai->ai_family, remoteAddress.ai->ai_socktype, remoteAddress.ai->ai_protocol)) { |
|
318 } |
|
319 |
|
320 static std::shared_ptr<Socket> open(const SocketOptions& options) { |
|
321 auto socket = openClientSocket<SCTPClientSocket>(options, SOCK_STREAM, IPPROTO_SCTP); |
|
322 check(::connect(socket->socket.getFD(), socket->remoteAddress.ai->ai_addr, socket->remoteAddress.ai->ai_addrlen), "connect socket"); |
|
323 return socket; |
|
324 } |
|
325 |
|
326 const IncomingMessage receive() override { |
|
327 // TODO: SCTP receive() |
|
328 return IncomingMessage("TODO: receive() a message"); |
|
329 } |
|
330 }; |
|
331 |
|
332 class UDSClientSocket : public MSGSocket { |
|
333 public: |
|
334 |
|
335 UDSClientSocket(int fd) : MSGSocket(fd) { |
|
336 } |
|
337 |
|
338 static std::shared_ptr<Socket> open(const SocketOptions& options) { |
|
339 struct sockaddr_un address; |
|
340 std::string path = findOption(options, OPTION_PATH); |
|
341 |
|
342 memset(&address, 0x00, sizeof (address)); |
|
343 address.sun_family = AF_UNIX; |
|
344 strncpy(address.sun_path, path.c_str(), path.size()); |
|
345 |
|
346 int fd = ::socket(AF_UNIX, SOCK_STREAM, 0); |
|
347 |
|
348 auto socket = std::make_shared<UDSClientSocket>(fd); |
|
349 check(::connect(socket->socket.getFD(), (const sockaddr*) &address, sizeof (address)), "connect socket"); |
|
350 |
|
351 |
|
352 if (findOption(options, "debug") == "true") { // TODO: undocumented feature → standardize or remove |
|
353 struct ucred credentials; |
|
354 socklen_t credentialsLength = sizeof (credentials); |
|
355 memset(&credentials, 0x00, credentialsLength); |
|
356 getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &credentials, &credentialsLength); |
|
357 |
|
358 printf("uds.fd = %d\n", fd); |
|
359 printf("uds.path = %s\n", address.sun_path); |
|
360 printf("uds.server.pid = %d\n", credentials.pid); |
|
361 printf("uds.server.uid = %d\n", credentials.uid); |
|
362 printf("uds.server.gid = %d\n", credentials.gid); |
|
363 } |
|
364 |
|
365 |
|
366 return socket; |
|
367 } |
|
368 |
|
369 const IncomingMessage receive() override { |
|
370 // TODO: UDS receive() |
|
371 return IncomingMessage("TODO: receive() a message"); |
|
372 } |
|
373 }; |
|
374 |
|
375 template<const char* protocol, const char* role, const char* mode, typename SocketClass> |
|
376 class TemplateSocketFactory : public SocketFactory { |
|
377 public: |
|
378 |
|
379 bool canHandle(const SocketOptions& options) override { |
|
380 return findOption(options, OPTION_PROTOCOL) == protocol |
|
381 && findOption(options, OPTION_ROLE) == role |
|
382 && findOption(options, OPTION_MODE) == mode; |
|
383 } |
|
384 |
|
385 std::shared_ptr<Socket> open(const SocketOptions& options) override { |
|
386 return SocketClass::open(options); |
|
387 } |
|
388 }; |
|
389 |
|
390 static std::vector<std::shared_ptr<SocketFactory>> factories |
|
391 { |
|
392 std::make_shared<TemplateSocketFactory<PROTOCOL_TCP, ROLE_CLIENT, MODE_STREAM, TCPClientSocket >> (), |
|
393 std::make_shared<TemplateSocketFactory<PROTOCOL_TCP, ROLE_SERVER, MODE_STREAM, TCPServerSocket >> (), |
|
394 std::make_shared<TemplateSocketFactory<PROTOCOL_UDP, ROLE_CLIENT, MODE_DATAGRAM, UDPClientSocket >> (), |
|
395 std::make_shared<TemplateSocketFactory<PROTOCOL_UDP, ROLE_SERVER, MODE_DATAGRAM, UDPServerSocket >> (), |
|
396 std::make_shared<TemplateSocketFactory<PROTOCOL_SCTP, ROLE_CLIENT, MODE_STREAM, SCTPClientSocket >> (), |
|
397 std::make_shared<TemplateSocketFactory<PROTOCOL_SCTP, ROLE_SERVER, MODE_STREAM, UDPClientSocket >> (), // TODO: correct class |
|
398 std::make_shared<TemplateSocketFactory<PROTOCOL_UDS, ROLE_CLIENT, MODE_STREAM, UDSClientSocket >> (), |
|
399 std::make_shared<TemplateSocketFactory<PROTOCOL_UDS, ROLE_CLIENT, MODE_DATAGRAM, UDSClientSocket >> (), // TODO: correct class |
|
400 std::make_shared<TemplateSocketFactory<PROTOCOL_UDS, ROLE_SERVER, MODE_STREAM, UDSClientSocket >> (), // TODO: correct class |
|
401 std::make_shared<TemplateSocketFactory<PROTOCOL_UDS, ROLE_SERVER, MODE_DATAGRAM, UDSClientSocket >> (), // TODO: correct class |
|
402 }; |
|
403 |
|
404 std::shared_ptr<SocketFactory> SocketFactory::find(const SocketOptions& options) { |
|
405 for (auto f : factories) if (f->canHandle(options)) return f; |
|
406 throw std::logic_error("Unable to find a SocketFactory"); // TODO: add relevant options? |
|
407 } |
|
408 |
|
409 |
|
410 } |
|
411 } |
|
412 } |