summaryrefslogtreecommitdiff
path: root/src/ot_socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ot_socket.c')
-rw-r--r--src/ot_socket.c310
1 files changed, 310 insertions, 0 deletions
diff --git a/src/ot_socket.c b/src/ot_socket.c
new file mode 100644
index 0000000..79bb01a
--- /dev/null
+++ b/src/ot_socket.c
@@ -0,0 +1,310 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever. */
3
4/* System */
5#define _GNU_SOURCE /* accept4 on glibc */
6#include <errno.h>
7#include <fcntl.h>
8#include <netinet/in.h>
9#include <netinet/tcp.h>
10#include <string.h>
11#include <sys/socket.h>
12#include <unistd.h>
13
14/* Opentracker */
15#include "ot_ip.h"
16#include "ot_socket.h"
17
18static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) {
19 memset(sa, 0, sizeof(*sa));
20 sa->sin6_family = AF_INET6;
21 sa->sin6_port = htons(port);
22 memcpy(&sa->sin6_addr, ip, sizeof(ot_ip6));
23}
24
25/* Linux reports an IPv4 peer on a dual stack socket as a v4 mapped
26 sockaddr_in6, the BSDs and macOS hand back a plain sockaddr_in instead, so
27 both forms have to be read. */
28static void ip_from_sockaddr(ot_ip6 ip, uint16_t *port, const struct sockaddr_storage *ss) {
29 if (ss->ss_family == AF_INET) {
30 const struct sockaddr_in *sa = (const struct sockaddr_in *)ss;
31
32 memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
33 memcpy(ip + sizeof(ip_v4mapped_prefix), &sa->sin_addr, 4);
34 *port = ntohs(sa->sin_port);
35 } else {
36 const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)ss;
37
38 memcpy(ip, &sa->sin6_addr, sizeof(ot_ip6));
39 *port = ntohs(sa->sin6_port);
40 }
41}
42
43int socket_set_blocking(int s, int blocking) {
44 int flags = fcntl(s, F_GETFL, 0);
45
46 if (flags == -1)
47 return -1;
48 return fcntl(s, F_SETFL, blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK));
49}
50
51static int set_nonblocking(int s) { return socket_set_blocking(s, 0); }
52
53/* Close without letting the failure we are about to report be overwritten */
54static int close_and_fail(int s) {
55 int saved = errno;
56 close(s);
57 errno = saved;
58 return -1;
59}
60
61static int socket_v6(int type) {
62 int s = socket(PF_INET6, type, 0);
63 int zero = 0;
64
65 if (s == -1)
66 return -1;
67
68 /* A tracker that answered only one family would be half a tracker */
69 if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) == -1)
70 return close_and_fail(s);
71
72 if (set_nonblocking(s) == -1)
73 return close_and_fail(s);
74
75 return s;
76}
77
78/* SO_REUSEPORT means three different things. Linux spreads datagrams over
79 the group. FreeBSD permits the bind and spreads nothing, which is what
80 SO_REUSEPORT_LB was added for. Darwin hands every datagram to whichever
81 socket bound last, so a group there starves all but one reader. */
82#if defined(SO_REUSEPORT_LB)
83#define OT_BALANCE_OPTION SO_REUSEPORT_LB
84#elif defined(SO_REUSEPORT) && !defined(__APPLE__)
85#define OT_BALANCE_OPTION SO_REUSEPORT
86#endif
87
88int socket_can_balance(void) {
89#ifdef OT_BALANCE_OPTION
90 return 1;
91#else
92 return 0;
93#endif
94}
95
96/* The only place in the tree that needs to know what a kernel calls this */
97static void socket_set_sharing(int s, socket_sharing sharing) {
98 int one = 1;
99
100 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
101#ifdef OT_BALANCE_OPTION
102 if (sharing == SOCKET_BALANCED) {
103 setsockopt(s, SOL_SOCKET, OT_BALANCE_OPTION, &one, sizeof(one));
104 return;
105 }
106#endif
107#ifdef SO_REUSEPORT
108 if (sharing != SOCKET_EXCLUSIVE)
109 setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
110#endif
111}
112
113static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
114 struct sockaddr_in6 sa;
115
116 socket_set_sharing(s, sharing);
117
118 sockaddr_from_ip(&sa, ip, port);
119 return bind(s, (struct sockaddr *)&sa, sizeof(sa));
120}
121
122/* Defer the listening socket reporting it to be ready to accept until enough
123 sensible data is available for an accept+read pair. Where available. */
124int socket_defer_accept(int s) {
125#if defined(TCP_DEFER_ACCEPT)
126 int patience = OT_CLIENT_TIMEOUT;
127
128 return setsockopt(s, IPPROTO_TCP, TCP_DEFER_ACCEPT, &patience, sizeof(patience));
129#elif defined(SO_ACCEPTFILTER)
130 struct accept_filter_arg filter;
131 const char name[sizeof(filter.af_name)] = "dataready";
132
133 memset(&filter, 0, sizeof(filter));
134 memcpy(filter.af_name, name, sizeof(name));
135 return setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &filter, sizeof(filter));
136#else
137 (void)s;
138 return -1;
139#endif
140}
141
142int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
143 int s = socket_v6(SOCK_STREAM);
144
145 if (s == -1)
146 return -1;
147 if (socket_bind_v6(s, ip, port, sharing) == -1 || listen(s, SOMAXCONN) == -1)
148 return close_and_fail(s);
149
150 return s;
151}
152
153int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
154 int s = socket_v6(SOCK_DGRAM);
155
156 if (s == -1)
157 return -1;
158 if (socket_bind_v6(s, ip, port, sharing) == -1)
159 return close_and_fail(s);
160
161 return s;
162}
163
164int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport) {
165 struct sockaddr_in6 sa;
166 int s = socket_v6(SOCK_STREAM);
167
168 if (s == -1)
169 return -1;
170 if (socket_bind_v6(s, srcip, srcport, SOCKET_EXCLUSIVE) == -1)
171 return close_and_fail(s);
172
173 sockaddr_from_ip(&sa, dstip, dstport);
174 if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1 && errno != EINPROGRESS && errno != EWOULDBLOCK)
175 return close_and_fail(s);
176
177 return s;
178}
179
180int socket_is_connected(int s) {
181 int error = 0;
182 socklen_t len = sizeof(error);
183
184 if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
185 return 0;
186 if (error) {
187 errno = error;
188 return 0;
189 }
190 return 1;
191}
192
193/* Linux hands over a blocking socket whatever the listening socket was, the
194 BSDs and macOS inherit the listener's flags, so the accepted socket has to
195 be told either way. accept4 carries the flag itself, which saves the two
196 fcntl calls it otherwise takes. Darwin has neither accept4 nor the flag. */
197int socket_accept(int s, ot_ip6 ip, uint16_t *port) {
198 struct sockaddr_storage ss;
199 socklen_t len = sizeof(ss);
200 int fd;
201
202 memset(&ss, 0, sizeof(ss));
203#ifdef SOCK_NONBLOCK
204 if ((fd = accept4(s, (struct sockaddr *)&ss, &len, SOCK_NONBLOCK)) == -1)
205 return -1;
206#else
207 if ((fd = accept(s, (struct sockaddr *)&ss, &len)) == -1)
208 return -1;
209 if (set_nonblocking(fd) == -1)
210 return close_and_fail(fd);
211#endif
212
213 ip_from_sockaddr(ip, port, &ss);
214 return fd;
215}
216
217ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port) {
218 struct sockaddr_storage ss;
219 socklen_t sslen = sizeof(ss);
220 ssize_t received;
221
222 memset(&ss, 0, sizeof(ss));
223 received = recvfrom(s, buf, len, 0, (struct sockaddr *)&ss, &sslen);
224 if (received == -1)
225 return -1;
226
227 ip_from_sockaddr(ip, port, &ss);
228 return received;
229}
230
231ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port) {
232 struct sockaddr_in6 sa;
233
234 sockaddr_from_ip(&sa, ip, port);
235 return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa));
236}
237
238#ifdef WANT_SYNC_LIVE
239static void sockaddr4_from_ip(struct sockaddr_in *sa, const ot_ip6 ip, uint16_t port) {
240 memset(sa, 0, sizeof(*sa));
241 sa->sin_family = AF_INET;
242 sa->sin_port = htons(port);
243 memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4);
244}
245
246/* A multicast group is meant to have several listeners, so these are always
247 shared */
248static int socket_v4_bind(const ot_ip6 ip, uint16_t port) {
249 struct sockaddr_in sa;
250 int s;
251
252 if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
253 return -1;
254
255 socket_set_sharing(s, SOCKET_SHARED);
256
257 sockaddr4_from_ip(&sa, ip, port);
258 if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1)
259 return close_and_fail(s);
260
261 return s;
262}
263
264int socket_mcast_join(const ot_ip6 group, const ot_ip6 interface, uint16_t port) {
265 struct ip_mreq mreq;
266 ot_ip6 any;
267 int s;
268
269 /* The group is what is listened to, so the socket takes the wildcard and
270 the join names the interface to receive it on */
271 memcpy(any, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
272 memset(any + sizeof(ip_v4mapped_prefix), 0, 4);
273
274 if ((s = socket_v4_bind(any, port)) == -1)
275 return -1;
276
277 memset(&mreq, 0, sizeof(mreq));
278 memcpy(&mreq.imr_multiaddr, group + sizeof(ip_v4mapped_prefix), 4);
279 memcpy(&mreq.imr_interface, interface + sizeof(ip_v4mapped_prefix), 4);
280 if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
281 return close_and_fail(s);
282
283 return s;
284}
285
286int socket_mcast_bind(const ot_ip6 interface, uint16_t port, int loopback) {
287 /* The BSDs and macOS want a byte for the IPv4 multicast options, Linux
288 accepts either that or an int. */
289 unsigned char ttl = 1, loop = (unsigned char)(loopback != 0);
290 int s = socket_v4_bind(interface, port);
291
292 if (s == -1)
293 return -1;
294 if (set_nonblocking(s) == -1)
295 return close_and_fail(s);
296
297 if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == -1 ||
298 setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) == -1)
299 return close_and_fail(s);
300
301 return s;
302}
303
304ssize_t socket_mcast_send(int s, const void *buf, size_t len, const ot_ip6 group, uint16_t port) {
305 struct sockaddr_in sa;
306
307 sockaddr4_from_ip(&sa, group, port);
308 return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa));
309}
310#endif