summaryrefslogtreecommitdiff
path: root/ot_socket.c
diff options
context:
space:
mode:
authorDirk Engling <erdgeist@erdgeist.org>2026-10-25 02:41:48 +0200
committerDirk Engling <erdgeist@erdgeist.org>2026-10-25 02:41:48 +0200
commit7af7598086e5a3f9a75507cd6f9907a98faf3d9d (patch)
tree8593bbd4f710a8a168ae3c9e5ce80596d424bb02 /ot_socket.c
parent50c7599ee584131e8ecb5d0742b46c7a7aeee0a9 (diff)
Add sub system to abstract low level multi platform socket handling ourselves
Diffstat (limited to 'ot_socket.c')
-rw-r--r--ot_socket.c256
1 files changed, 256 insertions, 0 deletions
diff --git a/ot_socket.c b/ot_socket.c
new file mode 100644
index 0000000..e26ad52
--- /dev/null
+++ b/ot_socket.c
@@ -0,0 +1,256 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <errno.h>
8#include <fcntl.h>
9#include <netinet/in.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
18int g_socket_reuseport;
19
20static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) {
21 memset(sa, 0, sizeof(*sa));
22 sa->sin6_family = AF_INET6;
23 sa->sin6_port = htons(port);
24 memcpy(&sa->sin6_addr, ip, sizeof(ot_ip6));
25}
26
27/* Linux reports an IPv4 peer on a dual stack socket as a v4 mapped
28 sockaddr_in6, the BSDs and macOS hand back a plain sockaddr_in instead, so
29 both forms have to be read. */
30static void ip_from_sockaddr(ot_ip6 ip, uint16_t *port, const struct sockaddr_storage *ss) {
31 if (ss->ss_family == AF_INET) {
32 const struct sockaddr_in *sa = (const struct sockaddr_in *)ss;
33
34 memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
35 memcpy(ip + sizeof(ip_v4mapped_prefix), &sa->sin_addr, 4);
36 *port = ntohs(sa->sin_port);
37 } else {
38 const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)ss;
39
40 memcpy(ip, &sa->sin6_addr, sizeof(ot_ip6));
41 *port = ntohs(sa->sin6_port);
42 }
43}
44
45static int set_nonblocking(int s) {
46 int flags = fcntl(s, F_GETFL, 0);
47
48 if (flags == -1)
49 return -1;
50 return fcntl(s, F_SETFL, flags | O_NONBLOCK);
51}
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
78static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port) {
79 struct sockaddr_in6 sa;
80 int one = 1;
81
82 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
83#ifdef SO_REUSEPORT
84 if (g_socket_reuseport)
85 setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
86#endif
87
88 sockaddr_from_ip(&sa, ip, port);
89 return bind(s, (struct sockaddr *)&sa, sizeof(sa));
90}
91
92int socket_tcp_listen(const ot_ip6 ip, uint16_t port) {
93 int s = socket_v6(SOCK_STREAM);
94
95 if (s == -1)
96 return -1;
97 if (socket_bind_v6(s, ip, port) == -1 || listen(s, SOMAXCONN) == -1)
98 return close_and_fail(s);
99
100 return s;
101}
102
103int socket_udp_bind(const ot_ip6 ip, uint16_t port) {
104 int s = socket_v6(SOCK_DGRAM);
105
106 if (s == -1)
107 return -1;
108 if (socket_bind_v6(s, ip, port) == -1)
109 return close_and_fail(s);
110
111 return s;
112}
113
114int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport) {
115 struct sockaddr_in6 sa;
116 int s = socket_v6(SOCK_STREAM);
117
118 if (s == -1)
119 return -1;
120 if (socket_bind_v6(s, srcip, srcport) == -1)
121 return close_and_fail(s);
122
123 sockaddr_from_ip(&sa, dstip, dstport);
124 if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1 && errno != EINPROGRESS && errno != EWOULDBLOCK)
125 return close_and_fail(s);
126
127 return s;
128}
129
130int socket_is_connected(int s) {
131 int error = 0;
132 socklen_t len = sizeof(error);
133
134 if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
135 return 0;
136 if (error) {
137 errno = error;
138 return 0;
139 }
140 return 1;
141}
142
143int socket_accept(int s, ot_ip6 ip, uint16_t *port) {
144 struct sockaddr_storage ss;
145 socklen_t len = sizeof(ss);
146 int fd;
147
148 memset(&ss, 0, sizeof(ss));
149 if ((fd = accept(s, (struct sockaddr *)&ss, &len)) == -1)
150 return -1;
151
152 /* Linux hands over a blocking socket whatever the listening socket was, the
153 BSDs and macOS inherit the listener's flags, so say which one we want. */
154 if (set_nonblocking(fd) == -1)
155 return close_and_fail(fd);
156
157 ip_from_sockaddr(ip, port, &ss);
158 return fd;
159}
160
161ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port) {
162 struct sockaddr_storage ss;
163 socklen_t sslen = sizeof(ss);
164 ssize_t received;
165
166 memset(&ss, 0, sizeof(ss));
167 received = recvfrom(s, buf, len, 0, (struct sockaddr *)&ss, &sslen);
168 if (received == -1)
169 return -1;
170
171 ip_from_sockaddr(ip, port, &ss);
172 return received;
173}
174
175ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port) {
176 struct sockaddr_in6 sa;
177
178 sockaddr_from_ip(&sa, ip, port);
179 return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa));
180}
181
182#ifdef WANT_SYNC_LIVE
183static void sockaddr4_from_ip(struct sockaddr_in *sa, const ot_ip6 ip, uint16_t port) {
184 memset(sa, 0, sizeof(*sa));
185 sa->sin_family = AF_INET;
186 sa->sin_port = htons(port);
187 memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4);
188}
189
190static int socket_v4_bind(const ot_ip6 ip, uint16_t port) {
191 struct sockaddr_in sa;
192 int s, one = 1;
193
194 if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
195 return -1;
196
197 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
198#ifdef SO_REUSEPORT
199 if (g_socket_reuseport)
200 setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
201#endif
202
203 sockaddr4_from_ip(&sa, ip, port);
204 if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1)
205 return close_and_fail(s);
206
207 return s;
208}
209
210int socket_mcast_join(const ot_ip6 group, const ot_ip6 interface, uint16_t port) {
211 struct ip_mreq mreq;
212 ot_ip6 any;
213 int s;
214
215 /* The group is what is listened to, so the socket takes the wildcard and
216 the join names the interface to receive it on */
217 memcpy(any, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
218 memset(any + sizeof(ip_v4mapped_prefix), 0, 4);
219
220 if ((s = socket_v4_bind(any, port)) == -1)
221 return -1;
222
223 memset(&mreq, 0, sizeof(mreq));
224 memcpy(&mreq.imr_multiaddr, group + sizeof(ip_v4mapped_prefix), 4);
225 memcpy(&mreq.imr_interface, interface + sizeof(ip_v4mapped_prefix), 4);
226 if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
227 return close_and_fail(s);
228
229 return s;
230}
231
232int socket_mcast_bind(const ot_ip6 interface, uint16_t port, int loopback) {
233 /* The BSDs and macOS want a byte for the IPv4 multicast options, Linux
234 accepts either that or an int. */
235 unsigned char ttl = 1, loop = (unsigned char)(loopback != 0);
236 int s = socket_v4_bind(interface, port);
237
238 if (s == -1)
239 return -1;
240 if (set_nonblocking(s) == -1)
241 return close_and_fail(s);
242
243 if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == -1 ||
244 setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) == -1)
245 return close_and_fail(s);
246
247 return s;
248}
249
250ssize_t socket_mcast_send(int s, const void *buf, size_t len, const ot_ip6 group, uint16_t port) {
251 struct sockaddr_in sa;
252
253 sockaddr4_from_ip(&sa, group, port);
254 return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa));
255}
256#endif