summaryrefslogtreecommitdiff
path: root/ot_socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'ot_socket.c')
-rw-r--r--ot_socket.c258
1 files changed, 258 insertions, 0 deletions
diff --git a/ot_socket.c b/ot_socket.c
new file mode 100644
index 0000000..84020ca
--- /dev/null
+++ b/ot_socket.c
@@ -0,0 +1,258 @@
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
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
43static int set_nonblocking(int s) {
44 int flags = fcntl(s, F_GETFL, 0);
45
46 if (flags == -1)
47 return -1;
48 return fcntl(s, F_SETFL, flags | O_NONBLOCK);
49}
50
51/* Close without letting the failure we are about to report be overwritten */
52static int close_and_fail(int s) {
53 int saved = errno;
54 close(s);
55 errno = saved;
56 return -1;
57}
58
59static int socket_v6(int type) {
60 int s = socket(PF_INET6, type, 0);
61 int zero = 0;
62
63 if (s == -1)
64 return -1;
65
66 /* A tracker that answered only one family would be half a tracker */
67 if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) == -1)
68 return close_and_fail(s);
69
70 if (set_nonblocking(s) == -1)
71 return close_and_fail(s);
72
73 return s;
74}
75
76/* The only place in the tree that needs to know what a kernel calls this */
77static void socket_set_sharing(int s, socket_sharing sharing) {
78 int one = 1;
79
80 setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
81#ifdef SO_REUSEPORT
82 if (sharing == SOCKET_SHARED)
83 setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
84#endif
85}
86
87static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
88 struct sockaddr_in6 sa;
89
90 socket_set_sharing(s, sharing);
91
92 sockaddr_from_ip(&sa, ip, port);
93 return bind(s, (struct sockaddr *)&sa, sizeof(sa));
94}
95
96int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
97 int s = socket_v6(SOCK_STREAM);
98
99 if (s == -1)
100 return -1;
101 if (socket_bind_v6(s, ip, port, sharing) == -1 || listen(s, SOMAXCONN) == -1)
102 return close_and_fail(s);
103
104 return s;
105}
106
107int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
108 int s = socket_v6(SOCK_DGRAM);
109
110 if (s == -1)
111 return -1;
112 if (socket_bind_v6(s, ip, port, sharing) == -1)
113 return close_and_fail(s);
114
115 return s;
116}
117
118int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport) {
119 struct sockaddr_in6 sa;
120 int s = socket_v6(SOCK_STREAM);
121
122 if (s == -1)
123 return -1;
124 if (socket_bind_v6(s, srcip, srcport, SOCKET_EXCLUSIVE) == -1)
125 return close_and_fail(s);
126
127 sockaddr_from_ip(&sa, dstip, dstport);
128 if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1 && errno != EINPROGRESS && errno != EWOULDBLOCK)
129 return close_and_fail(s);
130
131 return s;
132}
133
134int socket_is_connected(int s) {
135 int error = 0;
136 socklen_t len = sizeof(error);
137
138 if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
139 return 0;
140 if (error) {
141 errno = error;
142 return 0;
143 }
144 return 1;
145}
146
147int socket_accept(int s, ot_ip6 ip, uint16_t *port) {
148 struct sockaddr_storage ss;
149 socklen_t len = sizeof(ss);
150 int fd;
151
152 memset(&ss, 0, sizeof(ss));
153 if ((fd = accept(s, (struct sockaddr *)&ss, &len)) == -1)
154 return -1;
155
156 /* Linux hands over a blocking socket whatever the listening socket was, the
157 BSDs and macOS inherit the listener's flags, so say which one we want. */
158 if (set_nonblocking(fd) == -1)
159 return close_and_fail(fd);
160
161 ip_from_sockaddr(ip, port, &ss);
162 return fd;
163}
164
165ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port) {
166 struct sockaddr_storage ss;
167 socklen_t sslen = sizeof(ss);
168 ssize_t received;
169
170 memset(&ss, 0, sizeof(ss));
171 received = recvfrom(s, buf, len, 0, (struct sockaddr *)&ss, &sslen);
172 if (received == -1)
173 return -1;
174
175 ip_from_sockaddr(ip, port, &ss);
176 return received;
177}
178
179ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port) {
180 struct sockaddr_in6 sa;
181
182 sockaddr_from_ip(&sa, ip, port);
183 return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa));
184}
185
186#ifdef WANT_SYNC_LIVE
187static void sockaddr4_from_ip(struct sockaddr_in *sa, const ot_ip6 ip, uint16_t port) {
188 memset(sa, 0, sizeof(*sa));
189 sa->sin_family = AF_INET;
190 sa->sin_port = htons(port);
191 memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4);
192}
193
194/* A multicast group is meant to have several listeners, so these are always
195 shared */
196static int socket_v4_bind(const ot_ip6 ip, uint16_t port) {
197 struct sockaddr_in sa;
198 int s;
199
200 if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
201 return -1;
202
203 socket_set_sharing(s, SOCKET_SHARED);
204
205 sockaddr4_from_ip(&sa, ip, port);
206 if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1)
207 return close_and_fail(s);
208
209 return s;
210}
211
212int socket_mcast_join(const ot_ip6 group, const ot_ip6 interface, uint16_t port) {
213 struct ip_mreq mreq;
214 ot_ip6 any;
215 int s;
216
217 /* The group is what is listened to, so the socket takes the wildcard and
218 the join names the interface to receive it on */
219 memcpy(any, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
220 memset(any + sizeof(ip_v4mapped_prefix), 0, 4);
221
222 if ((s = socket_v4_bind(any, port)) == -1)
223 return -1;
224
225 memset(&mreq, 0, sizeof(mreq));
226 memcpy(&mreq.imr_multiaddr, group + sizeof(ip_v4mapped_prefix), 4);
227 memcpy(&mreq.imr_interface, interface + sizeof(ip_v4mapped_prefix), 4);
228 if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
229 return close_and_fail(s);
230
231 return s;
232}
233
234int socket_mcast_bind(const ot_ip6 interface, uint16_t port, int loopback) {
235 /* The BSDs and macOS want a byte for the IPv4 multicast options, Linux
236 accepts either that or an int. */
237 unsigned char ttl = 1, loop = (unsigned char)(loopback != 0);
238 int s = socket_v4_bind(interface, port);
239
240 if (s == -1)
241 return -1;
242 if (set_nonblocking(s) == -1)
243 return close_and_fail(s);
244
245 if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == -1 ||
246 setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) == -1)
247 return close_and_fail(s);
248
249 return s;
250}
251
252ssize_t socket_mcast_send(int s, const void *buf, size_t len, const ot_ip6 group, uint16_t port) {
253 struct sockaddr_in sa;
254
255 sockaddr4_from_ip(&sa, group, port);
256 return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa));
257}
258#endif