summaryrefslogtreecommitdiff
path: root/ot_socket.c
blob: 84020ca4006dfdfc0cc3dde6d38180ca2c8a1c38 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
   It is considered beerware. Prost. Skol. Cheers or whatever.

   $id$ */

/* System */
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

/* Opentracker */
#include "ot_ip.h"
#include "ot_socket.h"

static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) {
  memset(sa, 0, sizeof(*sa));
  sa->sin6_family = AF_INET6;
  sa->sin6_port   = htons(port);
  memcpy(&sa->sin6_addr, ip, sizeof(ot_ip6));
}

/* Linux reports an IPv4 peer on a dual stack socket as a v4 mapped
   sockaddr_in6, the BSDs and macOS hand back a plain sockaddr_in instead, so
   both forms have to be read. */
static void ip_from_sockaddr(ot_ip6 ip, uint16_t *port, const struct sockaddr_storage *ss) {
  if (ss->ss_family == AF_INET) {
    const struct sockaddr_in *sa = (const struct sockaddr_in *)ss;

    memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
    memcpy(ip + sizeof(ip_v4mapped_prefix), &sa->sin_addr, 4);
    *port = ntohs(sa->sin_port);
  } else {
    const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)ss;

    memcpy(ip, &sa->sin6_addr, sizeof(ot_ip6));
    *port = ntohs(sa->sin6_port);
  }
}

static int set_nonblocking(int s) {
  int flags = fcntl(s, F_GETFL, 0);

  if (flags == -1)
    return -1;
  return fcntl(s, F_SETFL, flags | O_NONBLOCK);
}

/* Close without letting the failure we are about to report be overwritten */
static int close_and_fail(int s) {
  int saved = errno;
  close(s);
  errno = saved;
  return -1;
}

static int socket_v6(int type) {
  int s = socket(PF_INET6, type, 0);
  int zero = 0;

  if (s == -1)
    return -1;

  /* A tracker that answered only one family would be half a tracker */
  if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) == -1)
    return close_and_fail(s);

  if (set_nonblocking(s) == -1)
    return close_and_fail(s);

  return s;
}

/* The only place in the tree that needs to know what a kernel calls this */
static void socket_set_sharing(int s, socket_sharing sharing) {
  int one = 1;

  setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
#ifdef SO_REUSEPORT
  if (sharing == SOCKET_SHARED)
    setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
#endif
}

static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
  struct sockaddr_in6 sa;

  socket_set_sharing(s, sharing);

  sockaddr_from_ip(&sa, ip, port);
  return bind(s, (struct sockaddr *)&sa, sizeof(sa));
}

int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
  int s = socket_v6(SOCK_STREAM);

  if (s == -1)
    return -1;
  if (socket_bind_v6(s, ip, port, sharing) == -1 || listen(s, SOMAXCONN) == -1)
    return close_and_fail(s);

  return s;
}

int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing) {
  int s = socket_v6(SOCK_DGRAM);

  if (s == -1)
    return -1;
  if (socket_bind_v6(s, ip, port, sharing) == -1)
    return close_and_fail(s);

  return s;
}

int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport) {
  struct sockaddr_in6 sa;
  int                 s = socket_v6(SOCK_STREAM);

  if (s == -1)
    return -1;
  if (socket_bind_v6(s, srcip, srcport, SOCKET_EXCLUSIVE) == -1)
    return close_and_fail(s);

  sockaddr_from_ip(&sa, dstip, dstport);
  if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1 && errno != EINPROGRESS && errno != EWOULDBLOCK)
    return close_and_fail(s);

  return s;
}

int socket_is_connected(int s) {
  int       error = 0;
  socklen_t len   = sizeof(error);

  if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1)
    return 0;
  if (error) {
    errno = error;
    return 0;
  }
  return 1;
}

int socket_accept(int s, ot_ip6 ip, uint16_t *port) {
  struct sockaddr_storage ss;
  socklen_t               len = sizeof(ss);
  int                     fd;

  memset(&ss, 0, sizeof(ss));
  if ((fd = accept(s, (struct sockaddr *)&ss, &len)) == -1)
    return -1;

  /* Linux hands over a blocking socket whatever the listening socket was, the
     BSDs and macOS inherit the listener's flags, so say which one we want. */
  if (set_nonblocking(fd) == -1)
    return close_and_fail(fd);

  ip_from_sockaddr(ip, port, &ss);
  return fd;
}

ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port) {
  struct sockaddr_storage ss;
  socklen_t               sslen = sizeof(ss);
  ssize_t                 received;

  memset(&ss, 0, sizeof(ss));
  received = recvfrom(s, buf, len, 0, (struct sockaddr *)&ss, &sslen);
  if (received == -1)
    return -1;

  ip_from_sockaddr(ip, port, &ss);
  return received;
}

ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port) {
  struct sockaddr_in6 sa;

  sockaddr_from_ip(&sa, ip, port);
  return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa));
}

#ifdef WANT_SYNC_LIVE
static void sockaddr4_from_ip(struct sockaddr_in *sa, const ot_ip6 ip, uint16_t port) {
  memset(sa, 0, sizeof(*sa));
  sa->sin_family = AF_INET;
  sa->sin_port   = htons(port);
  memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4);
}

/* A multicast group is meant to have several listeners, so these are always
   shared */
static int socket_v4_bind(const ot_ip6 ip, uint16_t port) {
  struct sockaddr_in sa;
  int                s;

  if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
    return -1;

  socket_set_sharing(s, SOCKET_SHARED);

  sockaddr4_from_ip(&sa, ip, port);
  if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1)
    return close_and_fail(s);

  return s;
}

int socket_mcast_join(const ot_ip6 group, const ot_ip6 interface, uint16_t port) {
  struct ip_mreq mreq;
  ot_ip6         any;
  int            s;

  /* The group is what is listened to, so the socket takes the wildcard and
     the join names the interface to receive it on */
  memcpy(any, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix));
  memset(any + sizeof(ip_v4mapped_prefix), 0, 4);

  if ((s = socket_v4_bind(any, port)) == -1)
    return -1;

  memset(&mreq, 0, sizeof(mreq));
  memcpy(&mreq.imr_multiaddr, group + sizeof(ip_v4mapped_prefix), 4);
  memcpy(&mreq.imr_interface, interface + sizeof(ip_v4mapped_prefix), 4);
  if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
    return close_and_fail(s);

  return s;
}

int socket_mcast_bind(const ot_ip6 interface, uint16_t port, int loopback) {
  /* The BSDs and macOS want a byte for the IPv4 multicast options, Linux
     accepts either that or an int. */
  unsigned char ttl = 1, loop = (unsigned char)(loopback != 0);
  int           s   = socket_v4_bind(interface, port);

  if (s == -1)
    return -1;
  if (set_nonblocking(s) == -1)
    return close_and_fail(s);

  if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == -1 ||
      setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) == -1)
    return close_and_fail(s);

  return s;
}

ssize_t socket_mcast_send(int s, const void *buf, size_t len, const ot_ip6 group, uint16_t port) {
  struct sockaddr_in sa;

  sockaddr4_from_ip(&sa, group, port);
  return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa));
}
#endif