blob: 80a9d0ec9679f52341e3fc90b4f7fa7d38294c9e [file] [log] [blame]
Harald Welte468b6432014-09-11 13:05:51 +08001/*
Harald Welte48f55832017-01-26 00:03:10 +01002 * (C) 2011-2017 by Harald Welte <laforge@gnumonks.org>
Harald Welte468b6432014-09-11 13:05:51 +08003 *
4 * All Rights Reserved
5 *
Harald Weltee08da972017-11-13 01:00:26 +09006 * SPDX-License-Identifier: GPL-2.0+
7 *
Harald Welte468b6432014-09-11 13:05:51 +08008 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
Harald Welte468b6432014-09-11 13:05:51 +080018 */
19
Pau Espin Pedrol88955fb2023-01-18 18:54:00 +010020#include "config.h"
Harald Welte33cb71a2011-05-21 18:54:32 +020021
Harald Welteba6988b2011-08-17 12:46:48 +020022/*! \addtogroup socket
23 * @{
Neels Hofmeyr17518fe2017-06-20 04:35:06 +020024 * Osmocom socket convenience functions.
25 *
26 * \file socket.c */
Harald Welte96e2a002017-06-12 21:44:18 +020027
Harald Weltee4764422011-05-22 12:25:57 +020028#ifdef HAVE_SYS_SOCKET_H
Pau Espin Pedrolc9ad3452024-03-12 14:04:55 +010029#define _GNU_SOURCE /* for struct ucred on glibc >= 2.8 */
Harald Weltee4764422011-05-22 12:25:57 +020030
Harald Welte33cb71a2011-05-21 18:54:32 +020031#include <osmocom/core/logging.h>
32#include <osmocom/core/select.h>
33#include <osmocom/core/socket.h>
Alexander Couzens43957e62020-08-01 21:56:45 +020034#include <osmocom/core/sockaddr_str.h>
Harald Welte48f55832017-01-26 00:03:10 +010035#include <osmocom/core/talloc.h>
Neels Hofmeyr59f4caf2018-07-19 22:13:19 +020036#include <osmocom/core/utils.h>
Harald Welte33cb71a2011-05-21 18:54:32 +020037
Pablo Neira Ayuso0849c9a2011-06-09 15:04:30 +020038#include <sys/ioctl.h>
Harald Welte33cb71a2011-05-21 18:54:32 +020039#include <sys/socket.h>
40#include <sys/types.h>
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +010041#include <sys/un.h>
Harald Welte44b99262020-03-07 14:59:05 +010042#include <net/if.h>
Harald Welte33cb71a2011-05-21 18:54:32 +020043
Holger Hans Peter Freyther47723482011-11-09 11:26:15 +010044#include <netinet/in.h>
Harald Weltee30d7e62017-07-13 16:02:50 +020045#include <arpa/inet.h>
Holger Hans Peter Freyther47723482011-11-09 11:26:15 +010046
Harald Welte33cb71a2011-05-21 18:54:32 +020047#include <stdio.h>
48#include <unistd.h>
49#include <stdint.h>
50#include <string.h>
51#include <errno.h>
52#include <netdb.h>
53#include <ifaddrs.h>
54
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +020055#ifdef HAVE_LIBSCTP
56#include <netinet/sctp.h>
57#endif
58
Harald Weltedda70fc2017-04-08 20:52:33 +020059static struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t proto,
60 const char *host, uint16_t port, bool passive)
61{
Pau Espin Pedrolff428522019-10-10 17:38:16 +020062 struct addrinfo hints, *result, *rp;
Oliver Smith860651e2018-10-30 14:31:57 +010063 char portbuf[6];
Harald Weltedda70fc2017-04-08 20:52:33 +020064 int rc;
65
66 snprintf(portbuf, sizeof(portbuf), "%u", port);
67 memset(&hints, 0, sizeof(struct addrinfo));
68 hints.ai_family = family;
69 if (type == SOCK_RAW) {
70 /* Workaround for glibc, that returns EAI_SERVICE (-8) if
71 * SOCK_RAW and IPPROTO_GRE is used.
Pau Espin Pedrolff428522019-10-10 17:38:16 +020072 * http://sourceware.org/bugzilla/show_bug.cgi?id=15015
Harald Weltedda70fc2017-04-08 20:52:33 +020073 */
74 hints.ai_socktype = SOCK_DGRAM;
75 hints.ai_protocol = IPPROTO_UDP;
76 } else {
77 hints.ai_socktype = type;
78 hints.ai_protocol = proto;
79 }
Pau Espin Pedrol3119d472020-08-25 12:30:55 +020080
Harald Weltedda70fc2017-04-08 20:52:33 +020081 if (passive)
82 hints.ai_flags |= AI_PASSIVE;
83
84 rc = getaddrinfo(host, portbuf, &hints, &result);
85 if (rc != 0) {
Pau Espin Pedrolba828c32020-08-20 18:28:10 +020086 LOGP(DLGLOBAL, LOGL_ERROR, "getaddrinfo(%s, %u) failed: %s\n",
87 host, port, gai_strerror(rc));
Harald Weltedda70fc2017-04-08 20:52:33 +020088 return NULL;
89 }
90
Pau Espin Pedrolff428522019-10-10 17:38:16 +020091 for (rp = result; rp != NULL; rp = rp->ai_next) {
92 /* Workaround for glibc again */
93 if (type == SOCK_RAW) {
94 rp->ai_socktype = SOCK_RAW;
95 rp->ai_protocol = proto;
96 }
97 }
98
Harald Weltedda70fc2017-04-08 20:52:33 +020099 return result;
100}
101
Pau Espin Pedrol8fac5112019-10-24 15:39:25 +0200102#ifdef HAVE_LIBSCTP
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200103/*! Retrieve an array of addrinfo with specified hints, one for each host in the hosts array.
104 * \param[out] addrinfo array of addrinfo pointers, will be filled by the function on success.
105 * Its size must be at least the one of hosts.
106 * \param[in] family Socket family like AF_INET, AF_INET6.
107 * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM.
108 * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP.
109 * \param[in] hosts array of char pointers (strings) containing the addresses to query.
110 * \param[in] host_cnt length of the hosts array (in items).
111 * \param[in] port port number in host byte order.
112 * \param[in] passive whether to include the AI_PASSIVE flag in getaddrinfo() hints.
113 * \returns 0 is returned on success together with a filled addrinfo array; negative on error
114 */
115static int addrinfo_helper_multi(struct addrinfo **addrinfo, uint16_t family, uint16_t type, uint8_t proto,
116 const char **hosts, size_t host_cnt, uint16_t port, bool passive)
117{
Harald Welte0b08d512022-01-09 12:03:12 +0100118 unsigned int i, j;
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200119
120 for (i = 0; i < host_cnt; i++) {
121 addrinfo[i] = addrinfo_helper(family, type, proto, hosts[i], port, passive);
122 if (!addrinfo[i]) {
123 for (j = 0; j < i; j++)
124 freeaddrinfo(addrinfo[j]);
125 return -EINVAL;
126 }
127 }
128 return 0;
129}
Pau Espin Pedrol8fac5112019-10-24 15:39:25 +0200130#endif /* HAVE_LIBSCTP*/
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200131
Harald Weltefaf6b702021-04-28 13:04:59 +0200132static int socket_helper_tail(int sfd, unsigned int flags)
133{
Harald Weltec545ff62021-04-28 13:09:49 +0200134 int rc, on = 1;
135 uint8_t dscp = GET_OSMO_SOCK_F_DSCP(flags);
136 uint8_t prio = GET_OSMO_SOCK_F_PRIO(flags);
Harald Weltefaf6b702021-04-28 13:04:59 +0200137
138 if (flags & OSMO_SOCK_F_NONBLOCK) {
139 if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
140 LOGP(DLGLOBAL, LOGL_ERROR,
141 "cannot set this socket unblocking: %s\n",
142 strerror(errno));
143 close(sfd);
144 return -EINVAL;
145 }
146 }
147
Harald Weltec545ff62021-04-28 13:09:49 +0200148 if (dscp) {
149 rc = osmo_sock_set_dscp(sfd, dscp);
150 if (rc) {
151 LOGP(DLGLOBAL, LOGL_ERROR, "cannot set IP DSCP of socket to %u: %s\n",
152 dscp, strerror(errno));
153 /* we consider this a non-fatal error */
154 }
155 }
156
157 if (prio) {
158 rc = osmo_sock_set_priority(sfd, prio);
159 if (rc) {
160 LOGP(DLGLOBAL, LOGL_ERROR, "cannot set priority of socket to %u: %s\n",
161 prio, strerror(errno));
162 /* we consider this a non-fatal error */
163 }
164 }
165
Harald Weltefaf6b702021-04-28 13:04:59 +0200166 return 0;
167}
168
Harald Weltedda70fc2017-04-08 20:52:33 +0200169static int socket_helper(const struct addrinfo *rp, unsigned int flags)
170{
Harald Weltefaf6b702021-04-28 13:04:59 +0200171 int sfd, rc;
Harald Weltedda70fc2017-04-08 20:52:33 +0200172
173 sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
Pau Espin Pedrol5d50fa52018-04-05 17:00:22 +0200174 if (sfd == -1) {
175 LOGP(DLGLOBAL, LOGL_ERROR,
176 "unable to create socket: %s\n", strerror(errno));
Harald Weltedda70fc2017-04-08 20:52:33 +0200177 return sfd;
Pau Espin Pedrol5d50fa52018-04-05 17:00:22 +0200178 }
Harald Weltefaf6b702021-04-28 13:04:59 +0200179
180 rc = socket_helper_tail(sfd, flags);
181 if (rc < 0)
182 return rc;
183
Harald Weltedda70fc2017-04-08 20:52:33 +0200184 return sfd;
185}
186
Alexander Couzens43957e62020-08-01 21:56:45 +0200187static int socket_helper_osa(const struct osmo_sockaddr *addr, uint16_t type, uint8_t proto, unsigned int flags)
188{
Harald Weltefaf6b702021-04-28 13:04:59 +0200189 int sfd, rc;
Alexander Couzens43957e62020-08-01 21:56:45 +0200190
191 sfd = socket(addr->u.sa.sa_family, type, proto);
192 if (sfd == -1) {
193 LOGP(DLGLOBAL, LOGL_ERROR,
194 "unable to create socket: %s\n", strerror(errno));
195 return sfd;
196 }
Harald Weltefaf6b702021-04-28 13:04:59 +0200197
198 rc = socket_helper_tail(sfd, flags);
199 if (rc < 0)
200 return rc;
201
Alexander Couzens43957e62020-08-01 21:56:45 +0200202 return sfd;
203}
204
Pau Espin Pedrol8fac5112019-10-24 15:39:25 +0200205#ifdef HAVE_LIBSCTP
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200206/* Fill buf with a string representation of the address set, in the form:
207 * buf_len == 0: "()"
208 * buf_len == 1: "hostA"
209 * buf_len >= 2: (hostA|hostB|...|...)
210 */
211static int multiaddr_snprintf(char* buf, size_t buf_len, const char **hosts, size_t host_cnt)
212{
213 int len = 0, offset = 0, rem = buf_len;
Harald Welte0b08d512022-01-09 12:03:12 +0100214 size_t i;
215 int ret;
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200216 char *after;
217
218 if (buf_len < 3)
219 return -EINVAL;
220
221 if (host_cnt != 1) {
222 ret = snprintf(buf, rem, "(");
223 if (ret < 0)
224 return ret;
225 OSMO_SNPRINTF_RET(ret, rem, offset, len);
226 }
227 for (i = 0; i < host_cnt; i++) {
228 if (host_cnt == 1)
229 after = "";
230 else
231 after = (i == (host_cnt - 1)) ? ")" : "|";
232 ret = snprintf(buf + offset, rem, "%s%s", hosts[i] ? : "0.0.0.0", after);
233 OSMO_SNPRINTF_RET(ret, rem, offset, len);
234 }
235
236 return len;
237}
Pau Espin Pedrol8fac5112019-10-24 15:39:25 +0200238#endif /* HAVE_LIBSCTP */
Harald Weltedda70fc2017-04-08 20:52:33 +0200239
Harald Weltec47bbda2017-07-13 16:13:26 +0200240static int osmo_sock_init_tail(int fd, uint16_t type, unsigned int flags)
241{
Harald Weltebc43a622017-07-13 16:20:21 +0200242 int rc;
Harald Weltec47bbda2017-07-13 16:13:26 +0200243
244 /* Make sure to call 'listen' on a bound, connection-oriented sock */
245 if ((flags & (OSMO_SOCK_F_BIND|OSMO_SOCK_F_CONNECT)) == OSMO_SOCK_F_BIND) {
246 switch (type) {
247 case SOCK_STREAM:
248 case SOCK_SEQPACKET:
249 rc = listen(fd, 10);
Harald Weltebc43a622017-07-13 16:20:21 +0200250 if (rc < 0) {
251 LOGP(DLGLOBAL, LOGL_ERROR, "unable to listen on socket: %s\n",
252 strerror(errno));
Maxdb7cb692023-02-11 21:35:21 +0300253 return -errno;
Harald Weltebc43a622017-07-13 16:20:21 +0200254 }
255 break;
Harald Weltec47bbda2017-07-13 16:13:26 +0200256 }
257 }
258
Harald Weltebc43a622017-07-13 16:20:21 +0200259 if (flags & OSMO_SOCK_F_NO_MCAST_LOOP) {
260 rc = osmo_sock_mcast_loop_set(fd, false);
261 if (rc < 0) {
262 LOGP(DLGLOBAL, LOGL_ERROR, "unable to disable multicast loop: %s\n",
263 strerror(errno));
264 return rc;
265 }
266 }
Harald Weltec47bbda2017-07-13 16:13:26 +0200267
Harald Welte37d204a2017-07-13 16:33:16 +0200268 if (flags & OSMO_SOCK_F_NO_MCAST_ALL) {
269 rc = osmo_sock_mcast_all_set(fd, false);
270 if (rc < 0) {
271 LOGP(DLGLOBAL, LOGL_ERROR, "unable to disable receive of all multicast: %s\n",
272 strerror(errno));
273 /* do not abort here, as this is just an
274 * optional additional optimization that only
275 * exists on Linux only */
276 }
277 }
Harald Weltebc43a622017-07-13 16:20:21 +0200278 return 0;
Harald Weltec47bbda2017-07-13 16:13:26 +0200279}
280
Neels Hofmeyr87e45502017-06-20 00:17:59 +0200281/*! Initialize a socket (including bind and/or connect)
Harald Weltedda70fc2017-04-08 20:52:33 +0200282 * \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
283 * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
284 * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
285 * \param[in] local_host local host name or IP address in string form
286 * \param[in] local_port local port number in host byte order
287 * \param[in] remote_host remote host name or IP address in string form
288 * \param[in] remote_port remote port number in host byte order
289 * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
290 * \returns socket file descriptor on success; negative on error
291 *
292 * This function creates a new socket of the designated \a family, \a
293 * type and \a proto and optionally binds it to the \a local_host and \a
294 * local_port as well as optionally connects it to the \a remote_host
295 * and \q remote_port, depending on the value * of \a flags parameter.
296 *
297 * As opposed to \ref osmo_sock_init(), this function allows to combine
298 * the \ref OSMO_SOCK_F_BIND and \ref OSMO_SOCK_F_CONNECT flags. This
299 * is useful if you want to connect to a remote host/port, but still
300 * want to bind that socket to either a specific local alias IP and/or a
301 * specific local source port.
302 *
303 * You must specify either \ref OSMO_SOCK_F_BIND, or \ref
304 * OSMO_SOCK_F_CONNECT, or both.
305 *
306 * If \ref OSMO_SOCK_F_NONBLOCK is specified, the socket will be set to
307 * non-blocking mode.
308 */
309int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,
310 const char *local_host, uint16_t local_port,
311 const char *remote_host, uint16_t remote_port, unsigned int flags)
312{
Alexander Couzens2c962f52020-06-03 00:28:02 +0200313 struct addrinfo *local = NULL, *remote = NULL, *rp;
Harald Weltedda70fc2017-04-08 20:52:33 +0200314 int sfd = -1, rc, on = 1;
315
Alexander Couzens2c962f52020-06-03 00:28:02 +0200316 bool local_ipv4 = false, local_ipv6 = false;
317 bool remote_ipv4 = false, remote_ipv6 = false;
318
Harald Weltedda70fc2017-04-08 20:52:33 +0200319 if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) {
320 LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either "
321 "BIND or CONNECT flags\n");
322 return -EINVAL;
323 }
324
Alexander Couzens2c962f52020-06-03 00:28:02 +0200325 /* figure out local address infos */
326 if (flags & OSMO_SOCK_F_BIND) {
327 local = addrinfo_helper(family, type, proto, local_host, local_port, true);
328 if (!local)
329 return -EINVAL;
330 }
331
332 /* figure out remote address infos */
333 if (flags & OSMO_SOCK_F_CONNECT) {
334 remote = addrinfo_helper(family, type, proto, remote_host, remote_port, false);
335 if (!remote) {
336 if (local)
337 freeaddrinfo(local);
338
339 return -EINVAL;
340 }
341 }
342
343 /* It must do a full run to ensure AF_UNSPEC does not fail.
344 * In case first local valid entry is IPv4 and only remote valid entry
345 * is IPv6 or vice versa */
346 if (family == AF_UNSPEC) {
347 for (rp = local; rp != NULL; rp = rp->ai_next) {
348 switch (rp->ai_family) {
349 case AF_INET:
350 local_ipv4 = true;
351 break;
352 case AF_INET6:
353 local_ipv6 = true;
354 break;
355 }
356 }
357
358 for (rp = remote; rp != NULL; rp = rp->ai_next) {
359 switch (rp->ai_family) {
360 case AF_INET:
361 remote_ipv4 = true;
362 break;
363 case AF_INET6:
364 remote_ipv6 = true;
365 break;
366 }
367 }
368
Pau Espin Pedrold8cf52b2020-08-31 19:00:59 +0200369 if ((flags & OSMO_SOCK_F_BIND) && (flags & OSMO_SOCK_F_CONNECT)) {
370 /* prioritize ipv6 as per RFC */
371 if (local_ipv6 && remote_ipv6)
372 family = AF_INET6;
373 else if (local_ipv4 && remote_ipv4)
374 family = AF_INET;
375 else {
376 if (local)
377 freeaddrinfo(local);
378 if (remote)
379 freeaddrinfo(remote);
380 LOGP(DLGLOBAL, LOGL_ERROR,
381 "Unable to find a common protocol (IPv4 or IPv6) "
382 "for local host: %s and remote host: %s.\n",
383 local_host, remote_host);
384 return -ENODEV;
385 }
386 } else if ((flags & OSMO_SOCK_F_BIND)) {
387 family = local_ipv6 ? AF_INET6 : AF_INET;
388 } else if ((flags & OSMO_SOCK_F_CONNECT)) {
389 family = remote_ipv6 ? AF_INET6 : AF_INET;
Alexander Couzens2c962f52020-06-03 00:28:02 +0200390 }
391 }
392
Harald Weltedda70fc2017-04-08 20:52:33 +0200393 /* figure out local side of socket */
394 if (flags & OSMO_SOCK_F_BIND) {
Alexander Couzens2c962f52020-06-03 00:28:02 +0200395 for (rp = local; rp != NULL; rp = rp->ai_next) {
396 /* When called with AF_UNSPEC, family will set to IPv4 or IPv6 */
397 if (rp->ai_family != family)
398 continue;
Harald Weltedda70fc2017-04-08 20:52:33 +0200399
Harald Weltedda70fc2017-04-08 20:52:33 +0200400 sfd = socket_helper(rp, flags);
401 if (sfd < 0)
402 continue;
403
Philipp Maier73196e72018-08-23 20:11:50 +0200404 if (proto != IPPROTO_UDP || flags & OSMO_SOCK_F_UDP_REUSEADDR) {
Philipp Maier99f706d2018-08-01 12:40:36 +0200405 rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
406 &on, sizeof(on));
407 if (rc < 0) {
408 LOGP(DLGLOBAL, LOGL_ERROR,
409 "cannot setsockopt socket:"
410 " %s:%u: %s\n",
411 local_host, local_port,
412 strerror(errno));
413 close(sfd);
414 continue;
415 }
Harald Weltedda70fc2017-04-08 20:52:33 +0200416 }
Philipp Maier99f706d2018-08-01 12:40:36 +0200417
Pau Espin Pedrol5d50fa52018-04-05 17:00:22 +0200418 if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == -1) {
419 LOGP(DLGLOBAL, LOGL_ERROR, "unable to bind socket: %s:%u: %s\n",
420 local_host, local_port, strerror(errno));
421 close(sfd);
422 continue;
423 }
424 break;
Harald Weltedda70fc2017-04-08 20:52:33 +0200425 }
Alexander Couzens2c962f52020-06-03 00:28:02 +0200426
427 freeaddrinfo(local);
Harald Weltedda70fc2017-04-08 20:52:33 +0200428 if (rp == NULL) {
Alexander Couzens2c962f52020-06-03 00:28:02 +0200429 if (remote)
430 freeaddrinfo(remote);
Pau Espin Pedrol5d50fa52018-04-05 17:00:22 +0200431 LOGP(DLGLOBAL, LOGL_ERROR, "no suitable local addr found for: %s:%u\n",
432 local_host, local_port);
Harald Weltedda70fc2017-04-08 20:52:33 +0200433 return -ENODEV;
434 }
435 }
436
Pau Espin Pedrol5d50fa52018-04-05 17:00:22 +0200437 /* Reached this point, if OSMO_SOCK_F_BIND then sfd is valid (>=0) or it
438 was already closed and func returned. If OSMO_SOCK_F_BIND is not
439 set, then sfd = -1 */
440
Harald Weltedda70fc2017-04-08 20:52:33 +0200441 /* figure out remote side of socket */
442 if (flags & OSMO_SOCK_F_CONNECT) {
Alexander Couzens2c962f52020-06-03 00:28:02 +0200443 for (rp = remote; rp != NULL; rp = rp->ai_next) {
444 /* When called with AF_UNSPEC, family will set to IPv4 or IPv6 */
445 if (rp->ai_family != family)
446 continue;
Harald Weltedda70fc2017-04-08 20:52:33 +0200447
Harald Welte5cfa6dc2017-07-21 16:52:29 +0200448 if (sfd < 0) {
Harald Weltedda70fc2017-04-08 20:52:33 +0200449 sfd = socket_helper(rp, flags);
450 if (sfd < 0)
451 continue;
452 }
453
454 rc = connect(sfd, rp->ai_addr, rp->ai_addrlen);
Pau Espin Pedrol27cf8df2018-04-05 17:49:08 +0200455 if (rc != 0 && errno != EINPROGRESS) {
456 LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n",
457 remote_host, remote_port, strerror(errno));
458 /* We want to maintain the bind socket if bind was enabled */
459 if (!(flags & OSMO_SOCK_F_BIND)) {
460 close(sfd);
461 sfd = -1;
462 }
463 continue;
464 }
465 break;
Harald Weltedda70fc2017-04-08 20:52:33 +0200466 }
Alexander Couzens2c962f52020-06-03 00:28:02 +0200467
468 freeaddrinfo(remote);
Harald Weltedda70fc2017-04-08 20:52:33 +0200469 if (rp == NULL) {
Pau Espin Pedrol27cf8df2018-04-05 17:49:08 +0200470 LOGP(DLGLOBAL, LOGL_ERROR, "no suitable remote addr found for: %s:%u\n",
471 remote_host, remote_port);
472 if (sfd >= 0)
473 close(sfd);
Harald Weltedda70fc2017-04-08 20:52:33 +0200474 return -ENODEV;
475 }
476 }
477
Harald Weltec47bbda2017-07-13 16:13:26 +0200478 rc = osmo_sock_init_tail(sfd, type, flags);
479 if (rc < 0) {
480 close(sfd);
481 sfd = -1;
Harald Weltedda70fc2017-04-08 20:52:33 +0200482 }
Harald Weltec47bbda2017-07-13 16:13:26 +0200483
Harald Weltedda70fc2017-04-08 20:52:33 +0200484 return sfd;
485}
486
Alexander Couzens43957e62020-08-01 21:56:45 +0200487#define _SOCKADDR_TO_STR(dest, sockaddr) do { \
Neels Hofmeyrf3270f22021-12-17 14:21:29 +0100488 if (osmo_sockaddr_str_from_sockaddr(dest, &sockaddr->u.sas)) \
489 osmo_strlcpy((dest)->ip, "Invalid IP", 11); \
Alexander Couzens43957e62020-08-01 21:56:45 +0200490 } while (0)
491
492/*! Initialize a socket (including bind and/or connect)
493 * \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
494 * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
495 * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
496 * \param[in] local local address
497 * \param[in] remote remote address
498 * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
499 * \returns socket file descriptor on success; negative on error
500 *
501 * This function creates a new socket of the
502 * \a type and \a proto and optionally binds it to the \a local
503 * as well as optionally connects it to the \a remote
504 * depending on the value * of \a flags parameter.
505 *
506 * As opposed to \ref osmo_sock_init(), this function allows to combine
507 * the \ref OSMO_SOCK_F_BIND and \ref OSMO_SOCK_F_CONNECT flags. This
508 * is useful if you want to connect to a remote host/port, but still
509 * want to bind that socket to either a specific local alias IP and/or a
510 * specific local source port.
511 *
512 * You must specify either \ref OSMO_SOCK_F_BIND, or \ref
513 * OSMO_SOCK_F_CONNECT, or both.
514 *
515 * If \ref OSMO_SOCK_F_NONBLOCK is specified, the socket will be set to
516 * non-blocking mode.
517 */
518int osmo_sock_init_osa(uint16_t type, uint8_t proto,
519 const struct osmo_sockaddr *local,
520 const struct osmo_sockaddr *remote,
521 unsigned int flags)
522{
523 int sfd = -1, rc, on = 1;
Neels Hofmeyrf3270f22021-12-17 14:21:29 +0100524 struct osmo_sockaddr_str _sastr = {};
525 struct osmo_sockaddr_str *sastr = &_sastr;
Alexander Couzens43957e62020-08-01 21:56:45 +0200526
527 if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) {
528 LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either "
529 "BIND or CONNECT flags\n");
530 return -EINVAL;
531 }
532
533 if ((flags & OSMO_SOCK_F_BIND) && !local) {
534 LOGP(DLGLOBAL, LOGL_ERROR, "invalid argument. Cannot BIND when local is NULL\n");
535 return -EINVAL;
536 }
537
538 if ((flags & OSMO_SOCK_F_CONNECT) && !remote) {
539 LOGP(DLGLOBAL, LOGL_ERROR, "invalid argument. Cannot CONNECT when remote is NULL\n");
540 return -EINVAL;
541 }
542
543 if ((flags & OSMO_SOCK_F_BIND) &&
544 (flags & OSMO_SOCK_F_CONNECT) &&
545 local->u.sa.sa_family != remote->u.sa.sa_family) {
546 LOGP(DLGLOBAL, LOGL_ERROR, "invalid: the family for "
547 "local and remote endpoint must be same.\n");
548 return -EINVAL;
549 }
550
551 /* figure out local side of socket */
552 if (flags & OSMO_SOCK_F_BIND) {
553 sfd = socket_helper_osa(local, type, proto, flags);
554 if (sfd < 0) {
555 _SOCKADDR_TO_STR(sastr, local);
Neels Hofmeyrf3270f22021-12-17 14:21:29 +0100556 LOGP(DLGLOBAL, LOGL_ERROR, "no suitable local addr found for: " OSMO_SOCKADDR_STR_FMT "\n",
557 OSMO_SOCKADDR_STR_FMT_ARGS(sastr));
Alexander Couzens43957e62020-08-01 21:56:45 +0200558 return -ENODEV;
559 }
560
561 if (proto != IPPROTO_UDP || (flags & OSMO_SOCK_F_UDP_REUSEADDR)) {
562 rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
563 &on, sizeof(on));
564 if (rc < 0) {
Pau Espin Pedrolc3b772b2023-07-06 12:25:26 +0200565 int err = errno;
Alexander Couzens43957e62020-08-01 21:56:45 +0200566 _SOCKADDR_TO_STR(sastr, local);
567 LOGP(DLGLOBAL, LOGL_ERROR,
Neels Hofmeyrf3270f22021-12-17 14:21:29 +0100568 "cannot setsockopt socket: " OSMO_SOCKADDR_STR_FMT ": %s\n",
Pau Espin Pedrolc3b772b2023-07-06 12:25:26 +0200569 OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(err));
Alexander Couzens43957e62020-08-01 21:56:45 +0200570 close(sfd);
571 return rc;
572 }
573 }
574
575 if (bind(sfd, &local->u.sa, sizeof(struct osmo_sockaddr)) == -1) {
Pau Espin Pedrolc3b772b2023-07-06 12:25:26 +0200576 int err = errno;
Alexander Couzens43957e62020-08-01 21:56:45 +0200577 _SOCKADDR_TO_STR(sastr, local);
Neels Hofmeyrf3270f22021-12-17 14:21:29 +0100578 LOGP(DLGLOBAL, LOGL_ERROR, "unable to bind socket: " OSMO_SOCKADDR_STR_FMT ": %s\n",
Pau Espin Pedrolc3b772b2023-07-06 12:25:26 +0200579 OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(err));
Alexander Couzens43957e62020-08-01 21:56:45 +0200580 close(sfd);
581 return -1;
582 }
583 }
584
585 /* Reached this point, if OSMO_SOCK_F_BIND then sfd is valid (>=0) or it
586 was already closed and func returned. If OSMO_SOCK_F_BIND is not
587 set, then sfd = -1 */
588
589 /* figure out remote side of socket */
590 if (flags & OSMO_SOCK_F_CONNECT) {
591 if (sfd < 0) {
592 sfd = socket_helper_osa(remote, type, proto, flags);
593 if (sfd < 0) {
594 return sfd;
595 }
596 }
597
598 rc = connect(sfd, &remote->u.sa, sizeof(struct osmo_sockaddr));
599 if (rc != 0 && errno != EINPROGRESS) {
Pau Espin Pedrolc3b772b2023-07-06 12:25:26 +0200600 int err = errno;
Alexander Couzens43957e62020-08-01 21:56:45 +0200601 _SOCKADDR_TO_STR(sastr, remote);
Neels Hofmeyrf3270f22021-12-17 14:21:29 +0100602 LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: " OSMO_SOCKADDR_STR_FMT ": %s\n",
Pau Espin Pedrolc3b772b2023-07-06 12:25:26 +0200603 OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(err));
Alexander Couzens43957e62020-08-01 21:56:45 +0200604 close(sfd);
605 return rc;
606 }
607 }
608
609 rc = osmo_sock_init_tail(sfd, type, flags);
610 if (rc < 0) {
611 close(sfd);
612 sfd = -1;
613 }
614
615 return sfd;
616}
617
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200618#ifdef HAVE_LIBSCTP
619
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100620/* Check whether there's an addrinfo item in the addrinfo set with an IPv4 or IPv6 option */
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200621static void addrinfo_has_v4v6addr(const struct addrinfo **result, size_t result_count, bool *has_v4, bool *has_v6)
622{
623 size_t host_idx;
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100624 const struct addrinfo *rp;
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200625 *has_v4 = false;
626 *has_v6 = false;
627
628 for (host_idx = 0; host_idx < result_count; host_idx++) {
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100629 for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
630 if (result[host_idx]->ai_family == AF_INET)
631 *has_v4 = true;
632 else if (result[host_idx]->ai_family == AF_INET6)
633 *has_v6 = true;
634 }
635 }
636}
637
638/* Check whether there's an addrinfo item in the addrinfo set with only an IPv4 or IPv6 option */
639static void addrinfo_has_v4v6only_addr(const struct addrinfo **result, size_t result_count, bool *has_v4only, bool *has_v6only)
640{
641 size_t host_idx;
642 const struct addrinfo *rp;
643 *has_v4only = false;
644 *has_v6only = false;
645
646 for (host_idx = 0; host_idx < result_count; host_idx++) {
647 bool has_v4 = false;
648 bool has_v6 = false;
649 for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
650 if (rp->ai_family == AF_INET6)
651 has_v6 = true;
652 else
653 has_v4 = true;
654 }
655 if (has_v4 && !has_v6)
656 *has_v4only = true;
657 else if (has_v6 && !has_v4)
658 *has_v6only = true;
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200659 }
660}
661
Pau Espin Pedrol4f463c52020-08-28 14:32:02 +0200662/* Check whether there's an IPv6 with IN6ADDR_ANY_INIT ("::") */
663static bool addrinfo_has_in6addr_any(const struct addrinfo **result, size_t result_count)
664{
665 size_t host_idx;
666 struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100667 const struct addrinfo *rp;
Pau Espin Pedrol4f463c52020-08-28 14:32:02 +0200668
669 for (host_idx = 0; host_idx < result_count; host_idx++) {
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100670 for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
671 if (rp->ai_family != AF_INET6)
672 continue;
673 if (memcmp(&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr,
674 &in6addr_any, sizeof(in6addr_any)) == 0)
675 return true;
676 }
Pau Espin Pedrol4f463c52020-08-28 14:32:02 +0200677 }
678 return false;
679}
680
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200681static int socket_helper_multiaddr(uint16_t family, uint16_t type, uint8_t proto, unsigned int flags)
682{
Harald Weltefaf6b702021-04-28 13:04:59 +0200683 int sfd, rc;
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200684
685 sfd = socket(family, type, proto);
686 if (sfd == -1) {
687 LOGP(DLGLOBAL, LOGL_ERROR,
688 "Unable to create socket: %s\n", strerror(errno));
689 return sfd;
690 }
Harald Weltefaf6b702021-04-28 13:04:59 +0200691
692 rc = socket_helper_tail(sfd, flags);
693 if (rc < 0)
694 return rc;
695
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200696 return sfd;
697}
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200698
699/* Build array of addresses taking first addrinfo result of the requested family
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200700 * for each host in addrs_buf. */
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200701static int addrinfo_to_sockaddr(uint16_t family, const struct addrinfo **result,
Harald Welte0b08d512022-01-09 12:03:12 +0100702 const char **hosts, unsigned int host_cont,
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200703 uint8_t *addrs_buf, size_t addrs_buf_len) {
704 size_t host_idx, offset = 0;
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200705 const struct addrinfo *rp;
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200706
707 for (host_idx = 0; host_idx < host_cont; host_idx++) {
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200708 /* Addresses are ordered based on RFC 3484, see man getaddrinfo */
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200709 for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100710 if (family == AF_UNSPEC || rp->ai_family == family)
711 break;
712 }
713 if (!rp && family == AF_INET6) {
714 /* See if we can find an AF_INET addr for the AF_INET6 socket instead: */
715 for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
716 if (rp->ai_family == AF_INET)
717 break;
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200718 }
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200719 }
720 if (!rp) { /* No addr could be bound for this host! */
721 LOGP(DLGLOBAL, LOGL_ERROR, "No suitable remote address found for host: %s\n",
722 hosts[host_idx]);
723 return -ENODEV;
724 }
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100725 if (offset + rp->ai_addrlen > addrs_buf_len) {
726 LOGP(DLGLOBAL, LOGL_ERROR, "Output buffer to small: %zu\n",
727 addrs_buf_len);
728 return -ENOSPC;
729 }
730 memcpy(addrs_buf + offset, rp->ai_addr, rp->ai_addrlen);
731 offset += rp->ai_addrlen;
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200732 }
733 return 0;
734}
735
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200736static int setsockopt_sctp_auth_supported(int fd, uint32_t val)
Pau Espin Pedrole83227f2023-08-08 19:32:56 +0200737{
738#ifdef SCTP_AUTH_SUPPORTED
739 struct sctp_assoc_value assoc_val = {
740 .assoc_id = SCTP_FUTURE_ASSOC,
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200741 .assoc_value = val,
Pau Espin Pedrole83227f2023-08-08 19:32:56 +0200742 };
743 return setsockopt(fd, IPPROTO_SCTP, SCTP_AUTH_SUPPORTED, &assoc_val, sizeof(assoc_val));
744#else
745#pragma message "setsockopt(SCTP_AUTH_SUPPORTED) not supported! some SCTP features may not be available!"
746 LOGP(DLGLOBAL, LOGL_NOTICE, "Built without support for setsockopt(SCTP_AUTH_SUPPORTED), skipping\n");
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200747 return -ENOTSUP;
Pau Espin Pedrole83227f2023-08-08 19:32:56 +0200748#endif
749}
750
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200751static int setsockopt_sctp_asconf_supported(int fd, uint32_t val)
Pau Espin Pedrole83227f2023-08-08 19:32:56 +0200752{
753#ifdef SCTP_ASCONF_SUPPORTED
754 struct sctp_assoc_value assoc_val = {
755 .assoc_id = SCTP_FUTURE_ASSOC,
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200756 .assoc_value = val,
Pau Espin Pedrole83227f2023-08-08 19:32:56 +0200757 };
758 return setsockopt(fd, IPPROTO_SCTP, SCTP_ASCONF_SUPPORTED, &assoc_val, sizeof(assoc_val));
759#else
760#pragma message "setsockopt(SCTP_ASCONF_SUPPORTED) not supported! some SCTP features may not be available!"
761 LOGP(DLGLOBAL, LOGL_NOTICE, "Built without support for setsockopt(SCTP_ASCONF_SUPPORTED), skipping\n");
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200762 return -ENOTSUP;
Pau Espin Pedrole83227f2023-08-08 19:32:56 +0200763#endif
764}
765
Pau Espin Pedrola45b0be2023-09-08 13:42:50 +0200766static int setsockopt_sctp_initmsg(int fd, const struct osmo_sock_init2_multiaddr_pars *pars)
767{
768 if (!pars->sctp.sockopt_initmsg.num_ostreams_present &&
769 !pars->sctp.sockopt_initmsg.max_instreams_present &&
770 !pars->sctp.sockopt_initmsg.max_attempts_present &&
771 !pars->sctp.sockopt_initmsg.max_init_timeo_present)
772 return 0; /* nothing to set/do */
773
774#ifdef SCTP_INITMSG
775 struct sctp_initmsg si = {0};
776 socklen_t si_len = sizeof(si);
777 int rc;
778
779 /* If at least one field not present, obtain current value from kernel: */
780 if (!pars->sctp.sockopt_initmsg.num_ostreams_present ||
781 !pars->sctp.sockopt_initmsg.max_instreams_present ||
782 !pars->sctp.sockopt_initmsg.max_attempts_present ||
783 !pars->sctp.sockopt_initmsg.max_init_timeo_present) {
784 rc = getsockopt(fd, IPPROTO_SCTP, SCTP_INITMSG, &si, &si_len);
785 if (rc < 0)
786 return rc;
787 }
788
789 if (pars->sctp.sockopt_initmsg.num_ostreams_present)
790 si.sinit_num_ostreams = pars->sctp.sockopt_initmsg.num_ostreams_value;
791 if (pars->sctp.sockopt_initmsg.max_instreams_present)
792 si.sinit_max_instreams = pars->sctp.sockopt_initmsg.max_instreams_value;
793 if (pars->sctp.sockopt_initmsg.max_attempts_present)
794 si.sinit_max_attempts = pars->sctp.sockopt_initmsg.max_attempts_value;
795 if (pars->sctp.sockopt_initmsg.max_init_timeo_present)
796 si.sinit_max_init_timeo = pars->sctp.sockopt_initmsg.max_init_timeo_value;
797
798 return setsockopt(fd, IPPROTO_SCTP, SCTP_INITMSG, &si, sizeof(si));
799#else
800#pragma message "setsockopt(SCTP_INITMSG) not supported! some SCTP features may not be available!"
801 LOGP(DLGLOBAL, LOGL_NOTICE, "Built without support for setsockopt(SCTP_INITMSG), skipping\n");
802 return -ENOTSUP
803#endif
804}
805
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200806/*! Initialize a socket (including bind and/or connect) with multiple local or remote addresses.
807 * \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
808 * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
809 * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
810 * \param[in] local_hosts array of char pointers (strings), each containing local host name or IP address in string form
811 * \param[in] local_hosts_cnt length of local_hosts (in items)
812 * \param[in] local_port local port number in host byte order
813 * \param[in] remote_host array of char pointers (strings), each containing remote host name or IP address in string form
814 * \param[in] remote_hosts_cnt length of remote_hosts (in items)
815 * \param[in] remote_port remote port number in host byte order
816 * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
817 * \returns socket file descriptor on success; negative on error
818 *
819 * This function is similar to \ref osmo_sock_init2(), but can be passed an
820 * array of local or remote addresses for protocols supporting multiple
821 * addresses per socket, like SCTP (currently only one supported). This function
822 * should not be used by protocols not supporting this kind of features, but
823 * rather \ref osmo_sock_init2() should be used instead.
824 * See \ref osmo_sock_init2() for more information on flags and general behavior.
825 */
826int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
827 const char **local_hosts, size_t local_hosts_cnt, uint16_t local_port,
828 const char **remote_hosts, size_t remote_hosts_cnt, uint16_t remote_port,
829 unsigned int flags)
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200830{
831 return osmo_sock_init2_multiaddr2(family, type, proto, local_hosts, local_hosts_cnt, local_port,
832 remote_hosts, remote_hosts_cnt, remote_port, flags, NULL);
833}
834
835/*! Initialize a socket (including bind and/or connect) with multiple local or remote addresses.
836 * \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
837 * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
838 * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
839 * \param[in] local_hosts array of char pointers (strings), each containing local host name or IP address in string form
840 * \param[in] local_hosts_cnt length of local_hosts (in items)
841 * \param[in] local_port local port number in host byte order
842 * \param[in] remote_host array of char pointers (strings), each containing remote host name or IP address in string form
843 * \param[in] remote_hosts_cnt length of remote_hosts (in items)
844 * \param[in] remote_port remote port number in host byte order
845 * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
846 * \param[in] pars Extra parameters for multi-address specific protocols, such as SCTP. Can be NULL.
847 * \returns socket file descriptor on success; negative on error
848 *
849 * This function is similar to \ref osmo_sock_init2(), but can be passed an
850 * array of local or remote addresses for protocols supporting multiple
851 * addresses per socket, like SCTP (currently only one supported). This function
852 * should not be used by protocols not supporting this kind of features, but
853 * rather \ref osmo_sock_init2() should be used instead.
854 * See \ref osmo_sock_init2() for more information on flags and general behavior.
855 *
856 * pars: If "pars" parameter is passed to the function, sctp.version shall be set to 0.
857 */
858int osmo_sock_init2_multiaddr2(uint16_t family, uint16_t type, uint8_t proto,
859 const char **local_hosts, size_t local_hosts_cnt, uint16_t local_port,
860 const char **remote_hosts, size_t remote_hosts_cnt, uint16_t remote_port,
861 unsigned int flags, struct osmo_sock_init2_multiaddr_pars *pars)
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200862
863{
Pau Espin Pedrol796c6512020-08-19 12:03:25 +0200864 struct addrinfo *res_loc[OSMO_SOCK_MAX_ADDRS], *res_rem[OSMO_SOCK_MAX_ADDRS];
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200865 int sfd = -1, rc, on = 1;
Harald Welte0b08d512022-01-09 12:03:12 +0100866 unsigned int i;
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100867 bool loc_has_v4addr = false, loc_has_v6addr = false;
868 bool rem_has_v4addr = false, rem_has_v6addr = false;
869 bool loc_has_v4only_addr, rem_has_v4only_addr;
870 bool loc_has_v6only_addr, rem_has_v6only_addr;
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200871 struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS];
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200872 char strbuf[512];
873
874 /* TODO: So far this function is only aimed for SCTP, but could be
875 reused in the future for other protocols with multi-addr support */
876 if (proto != IPPROTO_SCTP)
877 return -ENOTSUP;
878
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200879 if (pars && pars->sctp.version != 0)
880 return -EINVAL;
881
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200882 if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) {
883 LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either "
884 "BIND or CONNECT flags\n");
885 return -EINVAL;
886 }
887
888 if (((flags & OSMO_SOCK_F_BIND) && !local_hosts_cnt) ||
889 ((flags & OSMO_SOCK_F_CONNECT) && !remote_hosts_cnt) ||
890 local_hosts_cnt > OSMO_SOCK_MAX_ADDRS ||
891 remote_hosts_cnt > OSMO_SOCK_MAX_ADDRS)
892 return -EINVAL;
893
894 /* figure out local side of socket */
895 if (flags & OSMO_SOCK_F_BIND) {
Pau Espin Pedrol796c6512020-08-19 12:03:25 +0200896 rc = addrinfo_helper_multi(res_loc, family, type, proto, local_hosts,
897 local_hosts_cnt, local_port, true);
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +0200898 if (rc < 0)
899 return -EINVAL;
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100900 /* Figure out if there's any IPv4 or IPv6 entry in the result set */
901 addrinfo_has_v4v6addr((const struct addrinfo **)res_loc, local_hosts_cnt,
902 &loc_has_v4addr, &loc_has_v6addr);
903 /* Figure out if there's any IPv4-only or IPv6-only addr in the result set */
904 addrinfo_has_v4v6only_addr((const struct addrinfo **)res_loc, local_hosts_cnt,
905 &loc_has_v4only_addr, &loc_has_v6only_addr);
906 if (family == AF_INET && loc_has_v6only_addr) {
907 LOGP(DLGLOBAL, LOGL_ERROR, "Cannot bind an IPv6 address to an AF_INET socket\n");
908 rc = -EINVAL;
Pau Espin Pedrold2e8f672023-12-08 20:07:38 +0100909 goto ret_freeaddrinfo_loc;
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100910 }
Pau Espin Pedrol796c6512020-08-19 12:03:25 +0200911 }
912 /* figure out remote side of socket */
913 if (flags & OSMO_SOCK_F_CONNECT) {
914 rc = addrinfo_helper_multi(res_rem, family, type, proto, remote_hosts,
915 remote_hosts_cnt, remote_port, false);
Pau Espin Pedrol4541cf22020-08-25 11:22:09 +0200916 if (rc < 0) {
917 rc = -EINVAL;
918 goto ret_freeaddrinfo_loc;
919 }
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100920 /* Figure out if there's any IPv4 or IPv6 entry in the result set */
921 addrinfo_has_v4v6addr((const struct addrinfo **)res_rem, remote_hosts_cnt,
922 &rem_has_v4addr, &rem_has_v6addr);
923 /* Figure out if there's any IPv4-only or IPv6-only addr in the result set */
924 addrinfo_has_v4v6only_addr((const struct addrinfo **)res_rem, remote_hosts_cnt,
925 &rem_has_v4only_addr, &rem_has_v6only_addr);
926 if (family == AF_INET && rem_has_v6only_addr) {
927 LOGP(DLGLOBAL, LOGL_ERROR, "Cannot connect to an IPv6 address in an AF_INET socket\n");
928 rc = -EINVAL;
929 goto ret_freeaddrinfo;
930 }
Pau Espin Pedrol796c6512020-08-19 12:03:25 +0200931 }
932
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100933 /* Find out the socket family now if not established by caller:
934 * Both are checked here through "or" here to account for "bind flag set,
935 * connect flag not set" and viceversa. */
936 if (family == AF_UNSPEC) {
937 if (!loc_has_v6addr && !rem_has_v6addr)
938 family = AF_INET;
939 else
940 family = AF_INET6;
941 }
942
943 /* if both sets are used, make sure there's at least 1 address of the
944 * same type on each set so that SCTP INIT/INIT-ACK can work. */
945 if (family == AF_INET6 && ((flags & OSMO_SOCK_F_BIND) && (flags & OSMO_SOCK_F_CONNECT)) &&
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200946 (loc_has_v4addr != rem_has_v4addr || loc_has_v6addr != rem_has_v6addr)) {
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100947 if (!addrinfo_has_in6addr_any((const struct addrinfo **)res_loc, local_hosts_cnt)) {
948 LOGP(DLGLOBAL, LOGL_ERROR, "Invalid v4 vs v6 in local vs remote addresses: "
949 "local:%s%s remote:%s%s\n",
950 loc_has_v4addr ? " v4" : "", loc_has_v6addr ? " v6" : "",
951 rem_has_v4addr ? " v4" : "", rem_has_v6addr ? " v6" : "");
952 rc = -EINVAL;
953 goto ret_freeaddrinfo;
954 }
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200955 }
956
Pau Espin Pedrol1c797fc2023-12-05 18:28:28 +0100957 sfd = socket_helper_multiaddr(family, type, proto, flags);
Pau Espin Pedrolcd133312020-08-19 17:25:04 +0200958 if (sfd < 0) {
959 rc = sfd;
960 goto ret_freeaddrinfo;
961 }
962
Pau Espin Pedrolcb5cec22023-12-05 19:57:03 +0100963 if (pars) {
964 if (pars->sctp.sockopt_auth_supported.set) {
Pau Espin Pedrole83227f2023-08-08 19:32:56 +0200965 /* RFC 5061 4.2.7: ASCONF also requires AUTH feature. */
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200966 rc = setsockopt_sctp_auth_supported(sfd, pars->sctp.sockopt_auth_supported.value);
Pau Espin Pedrole83227f2023-08-08 19:32:56 +0200967 if (rc < 0) {
968 int err = errno;
969 multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
970 LOGP(DLGLOBAL, LOGL_ERROR,
971 "cannot setsockopt(SCTP_AUTH_SUPPORTED) socket: %s:%u: %s\n",
972 strbuf, local_port, strerror(err));
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200973 if (pars->sctp.sockopt_auth_supported.abort_on_failure)
974 goto ret_close;
Pau Espin Pedrolb437f802023-08-28 13:56:25 +0200975 /* do not fail, some features such as Peer Primary Address won't be available
976 * unless configured system-wide through sysctl */
Pau Espin Pedrole83227f2023-08-08 19:32:56 +0200977 }
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200978 }
Pau Espin Pedrole83227f2023-08-08 19:32:56 +0200979
Pau Espin Pedrolcb5cec22023-12-05 19:57:03 +0100980 if (pars->sctp.sockopt_asconf_supported.set) {
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200981 rc = setsockopt_sctp_asconf_supported(sfd, pars->sctp.sockopt_asconf_supported.value);
Pau Espin Pedrole83227f2023-08-08 19:32:56 +0200982 if (rc < 0) {
983 int err = errno;
984 multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
985 LOGP(DLGLOBAL, LOGL_ERROR,
986 "cannot setsockopt(SCTP_ASCONF_SUPPORTED) socket: %s:%u: %s\n",
987 strbuf, local_port, strerror(err));
Pau Espin Pedrol658c5092023-09-08 13:02:14 +0200988 if (pars->sctp.sockopt_asconf_supported.abort_on_failure)
989 goto ret_close;
Pau Espin Pedrolb437f802023-08-28 13:56:25 +0200990 /* do not fail, some features such as Peer Primary Address won't be available
991 * unless configured system-wide through sysctl */
Pau Espin Pedrole83227f2023-08-08 19:32:56 +0200992 }
993 }
994
Pau Espin Pedrolcb5cec22023-12-05 19:57:03 +0100995 if (pars->sctp.sockopt_initmsg.set) {
Pau Espin Pedrola45b0be2023-09-08 13:42:50 +0200996 rc = setsockopt_sctp_initmsg(sfd, pars);
997 if (rc < 0) {
998 int err = errno;
999 multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
1000 LOGP(DLGLOBAL, LOGL_ERROR,
1001 "cannot setsockopt(SCTP_INITMSG) socket: %s:%u: %s\n",
1002 strbuf, local_port, strerror(err));
1003 if (pars->sctp.sockopt_initmsg.abort_on_failure)
1004 goto ret_close;
1005 /* do not fail, some parameters will be left as the global default */
1006 }
1007 }
Pau Espin Pedrolcb5cec22023-12-05 19:57:03 +01001008 }
1009
1010 if (flags & OSMO_SOCK_F_BIND) {
1011 /* Since so far we only allow IPPROTO_SCTP in this function,
1012 no need to check below for "proto != IPPROTO_UDP || flags & OSMO_SOCK_F_UDP_REUSEADDR" */
1013 rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
1014 &on, sizeof(on));
1015 if (rc < 0) {
1016 int err = errno;
1017 multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
1018 LOGP(DLGLOBAL, LOGL_ERROR,
1019 "cannot setsockopt socket:"
1020 " %s:%u: %s\n",
1021 strbuf, local_port,
1022 strerror(err));
1023 goto ret_close;
1024 }
Pau Espin Pedrola45b0be2023-09-08 13:42:50 +02001025
Pau Espin Pedrolcd133312020-08-19 17:25:04 +02001026 /* Build array of addresses taking first entry for each host.
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001027 TODO: Ideally we should use backtracking storing last used
1028 indexes and trying next combination if connect() fails .*/
Pau Espin Pedrolcd133312020-08-19 17:25:04 +02001029 /* We could alternatively use v4v6 mapped addresses and call sctp_bindx once with an array od sockaddr_in6 */
Pau Espin Pedrol796c6512020-08-19 12:03:25 +02001030 rc = addrinfo_to_sockaddr(family, (const struct addrinfo **)res_loc,
Pau Espin Pedrolcd133312020-08-19 17:25:04 +02001031 local_hosts, local_hosts_cnt,
1032 (uint8_t*)addrs_buf, sizeof(addrs_buf));
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001033 if (rc < 0) {
Pau Espin Pedrol796c6512020-08-19 12:03:25 +02001034 rc = -ENODEV;
1035 goto ret_close;
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001036 }
1037
Pau Espin Pedrolcd133312020-08-19 17:25:04 +02001038 rc = sctp_bindx(sfd, (struct sockaddr *)addrs_buf, local_hosts_cnt, SCTP_BINDX_ADD_ADDR);
1039 if (rc == -1) {
Pau Espin Pedrolc3b772b2023-07-06 12:25:26 +02001040 int err = errno;
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001041 multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
1042 LOGP(DLGLOBAL, LOGL_NOTICE, "unable to bind socket: %s:%u: %s\n",
Pau Espin Pedrolc3b772b2023-07-06 12:25:26 +02001043 strbuf, local_port, strerror(err));
Pau Espin Pedrol796c6512020-08-19 12:03:25 +02001044 rc = -ENODEV;
1045 goto ret_close;
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001046 }
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001047 }
1048
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001049 if (flags & OSMO_SOCK_F_CONNECT) {
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001050 /* Build array of addresses taking first of same family for each host.
1051 TODO: Ideally we should use backtracking storing last used
1052 indexes and trying next combination if connect() fails .*/
Pau Espin Pedrol796c6512020-08-19 12:03:25 +02001053 rc = addrinfo_to_sockaddr(family, (const struct addrinfo **)res_rem,
Pau Espin Pedrolcd133312020-08-19 17:25:04 +02001054 remote_hosts, remote_hosts_cnt,
1055 (uint8_t*)addrs_buf, sizeof(addrs_buf));
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001056 if (rc < 0) {
Pau Espin Pedrol796c6512020-08-19 12:03:25 +02001057 rc = -ENODEV;
1058 goto ret_close;
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001059 }
1060
Pau Espin Pedrolcd133312020-08-19 17:25:04 +02001061 rc = sctp_connectx(sfd, (struct sockaddr *)addrs_buf, remote_hosts_cnt, NULL);
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001062 if (rc != 0 && errno != EINPROGRESS) {
Pau Espin Pedrolc3b772b2023-07-06 12:25:26 +02001063 int err = errno;
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001064 multiaddr_snprintf(strbuf, sizeof(strbuf), remote_hosts, remote_hosts_cnt);
1065 LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n",
Pau Espin Pedrolc3b772b2023-07-06 12:25:26 +02001066 strbuf, remote_port, strerror(err));
Pau Espin Pedrol796c6512020-08-19 12:03:25 +02001067 rc = -ENODEV;
1068 goto ret_close;
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001069 }
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001070 }
1071
1072 rc = osmo_sock_init_tail(sfd, type, flags);
1073 if (rc < 0) {
1074 close(sfd);
1075 sfd = -1;
1076 }
1077
Pau Espin Pedrol796c6512020-08-19 12:03:25 +02001078 rc = sfd;
1079 goto ret_freeaddrinfo;
1080
1081ret_close:
1082 if (sfd >= 0)
1083 close(sfd);
1084ret_freeaddrinfo:
Pau Espin Pedrol4541cf22020-08-25 11:22:09 +02001085 if (flags & OSMO_SOCK_F_CONNECT) {
1086 for (i = 0; i < remote_hosts_cnt; i++)
1087 freeaddrinfo(res_rem[i]);
1088 }
1089ret_freeaddrinfo_loc:
1090 if (flags & OSMO_SOCK_F_BIND) {
1091 for (i = 0; i < local_hosts_cnt; i++)
1092 freeaddrinfo(res_loc[i]);
1093 }
Pau Espin Pedrol796c6512020-08-19 12:03:25 +02001094 return rc;
Pau Espin Pedrol3f464fc2019-10-10 17:38:35 +02001095}
1096#endif /* HAVE_LIBSCTP */
Harald Weltedda70fc2017-04-08 20:52:33 +02001097
Neels Hofmeyr87e45502017-06-20 00:17:59 +02001098/*! Initialize a socket (including bind/connect)
Harald Welteba6988b2011-08-17 12:46:48 +02001099 * \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
1100 * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
1101 * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
1102 * \param[in] host remote host name or IP address in string form
1103 * \param[in] port remote port number in host byte order
1104 * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
Harald Welte2d2e2cc2016-04-25 12:11:20 +02001105 * \returns socket file descriptor on success; negative on error
Harald Welteba6988b2011-08-17 12:46:48 +02001106 *
1107 * This function creates a new socket of the designated \a family, \a
1108 * type and \a proto and optionally binds or connects it, depending on
1109 * the value of \a flags parameter.
1110 */
Harald Welte33cb71a2011-05-21 18:54:32 +02001111int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
Pablo Neira Ayuso0849c9a2011-06-09 15:04:30 +02001112 const char *host, uint16_t port, unsigned int flags)
Harald Welte33cb71a2011-05-21 18:54:32 +02001113{
Harald Weltedda70fc2017-04-08 20:52:33 +02001114 struct addrinfo *result, *rp;
Pau Espin Pedrol6fe865d2021-07-15 13:08:08 +02001115 int sfd = -1; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */
1116 int on = 1;
1117 int rc;
Harald Welte33cb71a2011-05-21 18:54:32 +02001118
Pablo Neira Ayuso0849c9a2011-06-09 15:04:30 +02001119 if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) ==
Neels Hofmeyrf0f07d92016-08-22 13:34:23 +02001120 (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) {
Philipp Maier6f0f5602017-02-09 14:09:06 +01001121 LOGP(DLGLOBAL, LOGL_ERROR, "invalid: both bind and connect flags set:"
Neels Hofmeyrb7f191f2016-08-29 11:22:03 +02001122 " %s:%u\n", host, port);
Pablo Neira Ayuso0849c9a2011-06-09 15:04:30 +02001123 return -EINVAL;
Neels Hofmeyrf0f07d92016-08-22 13:34:23 +02001124 }
Pablo Neira Ayuso0849c9a2011-06-09 15:04:30 +02001125
Harald Weltedda70fc2017-04-08 20:52:33 +02001126 result = addrinfo_helper(family, type, proto, host, port, flags & OSMO_SOCK_F_BIND);
Pau Espin Pedrolba828c32020-08-20 18:28:10 +02001127 if (!result)
Harald Welte33cb71a2011-05-21 18:54:32 +02001128 return -EINVAL;
Harald Welte33cb71a2011-05-21 18:54:32 +02001129
1130 for (rp = result; rp != NULL; rp = rp->ai_next) {
Harald Weltedda70fc2017-04-08 20:52:33 +02001131 sfd = socket_helper(rp, flags);
Harald Welte33cb71a2011-05-21 18:54:32 +02001132 if (sfd == -1)
1133 continue;
Harald Weltedda70fc2017-04-08 20:52:33 +02001134
Pablo Neira Ayuso0849c9a2011-06-09 15:04:30 +02001135 if (flags & OSMO_SOCK_F_CONNECT) {
1136 rc = connect(sfd, rp->ai_addr, rp->ai_addrlen);
Pau Espin Pedrol3a321472018-04-05 17:49:40 +02001137 if (rc != 0 && errno != EINPROGRESS) {
1138 close(sfd);
1139 continue;
1140 }
Harald Welte33cb71a2011-05-21 18:54:32 +02001141 } else {
Philipp Maier73196e72018-08-23 20:11:50 +02001142 if (proto != IPPROTO_UDP || flags & OSMO_SOCK_F_UDP_REUSEADDR) {
Philipp Maier99f706d2018-08-01 12:40:36 +02001143 rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
1144 &on, sizeof(on));
1145 if (rc < 0) {
1146 LOGP(DLGLOBAL, LOGL_ERROR,
1147 "cannot setsockopt socket:"
1148 " %s:%u: %s\n",
1149 host, port, strerror(errno));
1150 close(sfd);
1151 continue;
1152 }
Pablo Neira Ayuso0849c9a2011-06-09 15:04:30 +02001153 }
Pau Espin Pedrol3a321472018-04-05 17:49:40 +02001154 if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == -1) {
1155 LOGP(DLGLOBAL, LOGL_ERROR, "unable to bind socket:"
1156 "%s:%u: %s\n",
1157 host, port, strerror(errno));
1158 close(sfd);
1159 continue;
1160 }
Harald Welte33cb71a2011-05-21 18:54:32 +02001161 }
Pau Espin Pedrol3a321472018-04-05 17:49:40 +02001162 break;
Harald Welte33cb71a2011-05-21 18:54:32 +02001163 }
1164 freeaddrinfo(result);
1165
1166 if (rp == NULL) {
Pau Espin Pedrol3a321472018-04-05 17:49:40 +02001167 LOGP(DLGLOBAL, LOGL_ERROR, "no suitable addr found for: %s:%u\n",
1168 host, port);
Harald Welte33cb71a2011-05-21 18:54:32 +02001169 return -ENODEV;
1170 }
Harald Welte68b15742011-05-22 21:47:29 +02001171
Philipp Maier73196e72018-08-23 20:11:50 +02001172 if (proto != IPPROTO_UDP || flags & OSMO_SOCK_F_UDP_REUSEADDR) {
Philipp Maier99f706d2018-08-01 12:40:36 +02001173 rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
1174 if (rc < 0) {
1175 LOGP(DLGLOBAL, LOGL_ERROR,
1176 "cannot setsockopt socket: %s:%u: %s\n", host,
1177 port, strerror(errno));
1178 close(sfd);
1179 sfd = -1;
1180 }
Philipp Maier0659c5d2018-08-01 12:43:08 +02001181 }
Harald Welte68b15742011-05-22 21:47:29 +02001182
Harald Weltec47bbda2017-07-13 16:13:26 +02001183 rc = osmo_sock_init_tail(sfd, type, flags);
1184 if (rc < 0) {
1185 close(sfd);
1186 sfd = -1;
Harald Welte68b15742011-05-22 21:47:29 +02001187 }
Harald Weltec47bbda2017-07-13 16:13:26 +02001188
Harald Welte68b15742011-05-22 21:47:29 +02001189 return sfd;
1190}
1191
Neels Hofmeyr87e45502017-06-20 00:17:59 +02001192/*! fill \ref osmo_fd for a give sfd
Max862ba652014-10-13 14:54:25 +02001193 * \param[out] ofd file descriptor (will be filled in)
1194 * \param[in] sfd socket file descriptor
Harald Welte24980ba2021-04-23 14:07:18 +02001195 * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
Harald Welte2d2e2cc2016-04-25 12:11:20 +02001196 * \returns socket fd on success; negative on error
Max862ba652014-10-13 14:54:25 +02001197 *
1198 * This function fills the \a ofd structure.
1199 */
Harald Welte24980ba2021-04-23 14:07:18 +02001200static inline int osmo_fd_init_ofd(struct osmo_fd *ofd, int sfd, unsigned int flags)
Max862ba652014-10-13 14:54:25 +02001201{
1202 int rc;
1203
1204 if (sfd < 0)
1205 return sfd;
1206
1207 ofd->fd = sfd;
Harald Welte16886992019-03-20 10:26:39 +01001208 ofd->when = OSMO_FD_READ;
Max862ba652014-10-13 14:54:25 +02001209
Harald Welte24980ba2021-04-23 14:07:18 +02001210 /* if we're doing a non-blocking connect, the completion will be signaled
1211 * by marking the fd as WRITE-able. So in this exceptional case, we're
1212 * also interested in when the socket becomes write-able */
1213 if ((flags & (OSMO_SOCK_F_CONNECT|OSMO_SOCK_F_NONBLOCK)) ==
1214 (OSMO_SOCK_F_CONNECT|OSMO_SOCK_F_NONBLOCK)) {
1215 ofd->when |= OSMO_FD_WRITE;
1216 }
1217
Max862ba652014-10-13 14:54:25 +02001218 rc = osmo_fd_register(ofd);
1219 if (rc < 0) {
1220 close(sfd);
1221 return rc;
1222 }
1223
1224 return sfd;
1225}
1226
Neels Hofmeyr87e45502017-06-20 00:17:59 +02001227/*! Initialize a socket and fill \ref osmo_fd
Katerina Barone-Adesic28c6a02013-02-15 13:27:59 +01001228 * \param[out] ofd file descriptor (will be filled in)
Harald Welteba6988b2011-08-17 12:46:48 +02001229 * \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
1230 * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
1231 * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
1232 * \param[in] host remote host name or IP address in string form
1233 * \param[in] port remote port number in host byte order
1234 * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
Harald Welte2d2e2cc2016-04-25 12:11:20 +02001235 * \returns socket fd on success; negative on error
Harald Welteba6988b2011-08-17 12:46:48 +02001236 *
1237 * This function creates (and optionall binds/connects) a socket using
1238 * \ref osmo_sock_init, but also fills the \a ofd structure.
1239 */
Harald Welte68b15742011-05-22 21:47:29 +02001240int osmo_sock_init_ofd(struct osmo_fd *ofd, int family, int type, int proto,
Pablo Neira Ayuso0849c9a2011-06-09 15:04:30 +02001241 const char *host, uint16_t port, unsigned int flags)
Harald Welte68b15742011-05-22 21:47:29 +02001242{
Harald Welte24980ba2021-04-23 14:07:18 +02001243 return osmo_fd_init_ofd(ofd, osmo_sock_init(family, type, proto, host, port, flags), flags);
Harald Welte33cb71a2011-05-21 18:54:32 +02001244}
1245
Neels Hofmeyr87e45502017-06-20 00:17:59 +02001246/*! Initialize a socket and fill \ref osmo_fd
Pau Espin Pedrol75989e62017-05-26 12:39:53 +02001247 * \param[out] ofd file descriptor (will be filled in)
1248 * \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
1249 * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
1250 * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
1251 * \param[in] local_host local host name or IP address in string form
1252 * \param[in] local_port local port number in host byte order
1253 * \param[in] remote_host remote host name or IP address in string form
1254 * \param[in] remote_port remote port number in host byte order
1255 * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
1256 * \returns socket fd on success; negative on error
1257 *
1258 * This function creates (and optionall binds/connects) a socket using
1259 * \ref osmo_sock_init2, but also fills the \a ofd structure.
1260 */
1261int osmo_sock_init2_ofd(struct osmo_fd *ofd, int family, int type, int proto,
1262 const char *local_host, uint16_t local_port,
1263 const char *remote_host, uint16_t remote_port, unsigned int flags)
1264{
1265 return osmo_fd_init_ofd(ofd, osmo_sock_init2(family, type, proto, local_host,
Harald Welte24980ba2021-04-23 14:07:18 +02001266 local_port, remote_host, remote_port, flags), flags);
Pau Espin Pedrol75989e62017-05-26 12:39:53 +02001267}
1268
Alexander Couzens43957e62020-08-01 21:56:45 +02001269int osmo_sock_init_osa_ofd(struct osmo_fd *ofd, int type, int proto,
1270 const struct osmo_sockaddr *local,
1271 const struct osmo_sockaddr *remote, unsigned int flags)
1272{
Harald Welte24980ba2021-04-23 14:07:18 +02001273 return osmo_fd_init_ofd(ofd, osmo_sock_init_osa(type, proto, local, remote, flags), flags);
Alexander Couzens43957e62020-08-01 21:56:45 +02001274}
1275
Neels Hofmeyr87e45502017-06-20 00:17:59 +02001276/*! Initialize a socket and fill \ref sockaddr
Harald Welteba6988b2011-08-17 12:46:48 +02001277 * \param[out] ss socket address (will be filled in)
1278 * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
1279 * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
1280 * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
Harald Welte2d2e2cc2016-04-25 12:11:20 +02001281 * \returns socket fd on success; negative on error
Harald Welteba6988b2011-08-17 12:46:48 +02001282 *
1283 * This function creates (and optionall binds/connects) a socket using
1284 * \ref osmo_sock_init, but also fills the \a ss structure.
1285 */
Harald Welte33cb71a2011-05-21 18:54:32 +02001286int osmo_sock_init_sa(struct sockaddr *ss, uint16_t type,
Pablo Neira Ayuso0849c9a2011-06-09 15:04:30 +02001287 uint8_t proto, unsigned int flags)
Harald Welte33cb71a2011-05-21 18:54:32 +02001288{
1289 char host[NI_MAXHOST];
1290 uint16_t port;
1291 struct sockaddr_in *sin;
1292 struct sockaddr_in6 *sin6;
1293 int s, sa_len;
1294
1295 /* determine port and host from ss */
1296 switch (ss->sa_family) {
1297 case AF_INET:
1298 sin = (struct sockaddr_in *) ss;
1299 sa_len = sizeof(struct sockaddr_in);
1300 port = ntohs(sin->sin_port);
1301 break;
1302 case AF_INET6:
1303 sin6 = (struct sockaddr_in6 *) ss;
1304 sa_len = sizeof(struct sockaddr_in6);
1305 port = ntohs(sin6->sin6_port);
1306 break;
1307 default:
1308 return -EINVAL;
1309 }
Harald Welte33cb71a2011-05-21 18:54:32 +02001310
1311 s = getnameinfo(ss, sa_len, host, NI_MAXHOST,
1312 NULL, 0, NI_NUMERICHOST);
1313 if (s != 0) {
Philipp Maier6f0f5602017-02-09 14:09:06 +01001314 LOGP(DLGLOBAL, LOGL_ERROR, "getnameinfo failed:"
1315 " %s\n", strerror(errno));
Harald Welte33cb71a2011-05-21 18:54:32 +02001316 return s;
1317 }
1318
Pablo Neira Ayuso0849c9a2011-06-09 15:04:30 +02001319 return osmo_sock_init(ss->sa_family, type, proto, host, port, flags);
Harald Welte33cb71a2011-05-21 18:54:32 +02001320}
1321
Pau Espin Pedrole4f34d82023-10-23 11:23:45 +02001322#ifdef HAVE_LIBSCTP
Pau Espin Pedrol64ba9ed2023-09-29 19:21:10 +02001323/*! Add addresses to the multi-address (SCTP) socket active binding set
1324 * \param[in] sfd The multi-address (SCTP) socket
1325 * \param[in] addrs array of char pointers (strings), each containing local host name or IP address in string form
1326 * \param[in] addrs_cnt length of addrs_hosts (in items)
1327 * \returns 0 on success; negative on error
1328 *
1329 * This function only supports SCTP sockets so far, and hence it should be
1330 * called only on socket file descriptions referencing that kind of sockets.
1331 */
1332int osmo_sock_multiaddr_add_local_addr(int sfd, const char **addrs, size_t addrs_cnt)
1333{
1334 struct osmo_sockaddr osa;
1335 socklen_t slen = sizeof(osa);
1336 uint16_t sfd_family;
1337 uint16_t type = SOCK_STREAM ;/* Fixme: we assume fd is SOCK_STREAM */
1338 uint8_t proto = IPPROTO_SCTP; /* Fixme: we assume fd is IPPROTO_SCTP */
1339 struct addrinfo *res[OSMO_SOCK_MAX_ADDRS];
1340 uint16_t port;
1341 struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS];
1342 unsigned int i;
1343 int rc;
1344 bool res_has_v4addr = false, res_has_v6addr = false;
1345
1346 rc = getsockname(sfd, &osa.u.sa, &slen);
1347 if (rc < 0)
1348 return rc; /* TODO: log error? */
1349 sfd_family = osa.u.sa.sa_family;
1350 port = osmo_sockaddr_port(&osa.u.sa);
1351
1352 if (sfd_family != AF_INET && sfd_family != AF_INET6)
1353 return -EINVAL;
1354
1355 rc = addrinfo_helper_multi(res, AF_UNSPEC, type, proto, addrs,
1356 addrs_cnt, port, true);
1357 if (rc < 0)
1358 return -EINVAL;
1359
1360 addrinfo_has_v4v6addr((const struct addrinfo **)res, addrs_cnt,
1361 &res_has_v4addr, &res_has_v6addr);
1362 if (sfd_family == AF_INET && !res_has_v4addr) {
1363 rc = -EINVAL;
1364 goto ret_free;
1365 }
1366
1367 uint16_t new_addr_family;
1368 if (sfd_family == AF_INET)
1369 new_addr_family = AF_INET;
1370 else if (sfd_family == AF_INET6 && !res_has_v4addr)
1371 new_addr_family = AF_INET6;
1372 else
1373 new_addr_family = AF_UNSPEC;
1374 rc = addrinfo_to_sockaddr(new_addr_family, (const struct addrinfo **)res,
1375 addrs, addrs_cnt,
1376 (uint8_t *)addrs_buf, sizeof(addrs_buf));
1377 if (rc < 0) {
1378 rc = -ENODEV;
1379 goto ret_free;
1380 }
1381
1382 rc = sctp_bindx(sfd, (struct sockaddr *)addrs_buf, addrs_cnt, SCTP_BINDX_ADD_ADDR);
1383 if (rc == -1) {
1384 int err = errno;
1385 char strbuf[512];
1386 multiaddr_snprintf(strbuf, sizeof(strbuf), addrs, addrs_cnt);
1387 LOGP(DLGLOBAL, LOGL_NOTICE, "Unable to bind socket to new addresses: %s:%u: %s\n",
1388 strbuf, port, strerror(err));
1389 rc = -ENODEV;
1390 goto ret_free;
1391 }
1392
1393ret_free:
1394 for (i = 0; i < addrs_cnt; i++)
1395 freeaddrinfo(res[i]);
1396 return rc;
1397}
1398
1399/*! Remove addresses from the multi-address (SCTP) socket active binding set
1400 * \param[in] sfd The multi-address (SCTP) socket
1401 * \param[in] addrs array of char pointers (strings), each containing local host name or IP address in string form
1402 * \param[in] addrs_cnt length of addrs_hosts (in items)
1403 * \returns 0 on success; negative on error
1404 *
1405 * This function only supports SCTP sockets so far, and hence it should be
1406 * called only on socket file descriptions referencing that kind of sockets.
1407 */
1408int osmo_sock_multiaddr_del_local_addr(int sfd, const char **addrs, size_t addrs_cnt)
1409{
1410 struct osmo_sockaddr osa;
1411 socklen_t slen = sizeof(osa);
1412 uint16_t sfd_family;
1413 uint16_t type = SOCK_STREAM ;/* Fixme: we assume fd is SOCK_STREAM */
1414 uint8_t proto = IPPROTO_SCTP; /* Fixme: we assume fd is IPPROTO_SCTP */
1415 struct addrinfo *res[OSMO_SOCK_MAX_ADDRS];
1416 uint16_t port;
1417 struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS];
1418 unsigned int i;
1419 int rc;
1420 bool res_has_v4addr = false, res_has_v6addr = false;
1421
1422 rc = getsockname(sfd, &osa.u.sa, &slen);
1423 if (rc < 0)
1424 return rc; /* TODO: log error? */
1425 sfd_family = osa.u.sa.sa_family;
1426 port = osmo_sockaddr_port(&osa.u.sa);
1427
1428 if (sfd_family != AF_INET && sfd_family != AF_INET6)
1429 return -EINVAL;
1430
1431 rc = addrinfo_helper_multi(res, AF_UNSPEC, type, proto, addrs,
1432 addrs_cnt, port, true);
1433 if (rc < 0)
1434 return -EINVAL;
1435
1436 addrinfo_has_v4v6addr((const struct addrinfo **)res, addrs_cnt,
1437 &res_has_v4addr, &res_has_v6addr);
1438 if (sfd_family == AF_INET && !res_has_v4addr) {
1439 rc = -EINVAL;
1440 goto ret_free;
1441 }
1442
1443 uint16_t del_addr_family;
1444 if (sfd_family == AF_INET)
1445 del_addr_family = AF_INET;
1446 else if (sfd_family == AF_INET6 && !res_has_v4addr)
1447 del_addr_family = AF_INET6;
1448 else
1449 del_addr_family = AF_UNSPEC;
1450 rc = addrinfo_to_sockaddr(del_addr_family, (const struct addrinfo **)res,
1451 addrs, addrs_cnt,
1452 (uint8_t *)addrs_buf, sizeof(addrs_buf));
1453 if (rc < 0) {
1454 rc = -ENODEV;
1455 goto ret_free;
1456 }
1457
1458 rc = sctp_bindx(sfd, (struct sockaddr *)addrs_buf, addrs_cnt, SCTP_BINDX_REM_ADDR);
1459 if (rc == -1) {
1460 int err = errno;
1461 char strbuf[512];
1462 multiaddr_snprintf(strbuf, sizeof(strbuf), addrs, addrs_cnt);
1463 LOGP(DLGLOBAL, LOGL_NOTICE, "Unable to unbind socket from addresses: %s:%u: %s\n",
1464 strbuf, port, strerror(err));
1465 rc = -ENODEV;
1466 goto ret_free;
1467 }
1468
1469ret_free:
1470 for (i = 0; i < addrs_cnt; i++)
1471 freeaddrinfo(res[i]);
1472 return rc;
1473}
Pau Espin Pedrole4f34d82023-10-23 11:23:45 +02001474#endif /* HAVE_LIBSCTP */
Pau Espin Pedrol64ba9ed2023-09-29 19:21:10 +02001475
Harald Welte33cb71a2011-05-21 18:54:32 +02001476static int sockaddr_equal(const struct sockaddr *a,
Harald Weltee4764422011-05-22 12:25:57 +02001477 const struct sockaddr *b, unsigned int len)
Harald Welte33cb71a2011-05-21 18:54:32 +02001478{
1479 struct sockaddr_in *sin_a, *sin_b;
1480 struct sockaddr_in6 *sin6_a, *sin6_b;
1481
1482 if (a->sa_family != b->sa_family)
1483 return 0;
1484
1485 switch (a->sa_family) {
1486 case AF_INET:
1487 sin_a = (struct sockaddr_in *)a;
1488 sin_b = (struct sockaddr_in *)b;
1489 if (!memcmp(&sin_a->sin_addr, &sin_b->sin_addr,
1490 sizeof(struct in_addr)))
1491 return 1;
1492 break;
1493 case AF_INET6:
1494 sin6_a = (struct sockaddr_in6 *)a;
1495 sin6_b = (struct sockaddr_in6 *)b;
1496 if (!memcmp(&sin6_a->sin6_addr, &sin6_b->sin6_addr,
1497 sizeof(struct in6_addr)))
1498 return 1;
1499 break;
1500 }
1501 return 0;
1502}
1503
Eric8ae40cb2021-09-27 22:10:01 +02001504/* linux has a default route:
1505local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
1506*/
1507static int sockaddr_is_local_routed(const struct sockaddr *a)
1508{
1509#if __linux__
1510 if (a->sa_family != AF_INET)
1511 return 0;
1512
1513 uint32_t address = ((struct sockaddr_in *)a)->sin_addr.s_addr; /* already BE */
1514 uint32_t eightmask = htonl(0xff000000); /* /8 mask */
1515 uint32_t local_prefix_127 = htonl(0x7f000000); /* 127.0.0.0 */
1516
1517 if ((address & eightmask) == local_prefix_127)
1518 return 1;
1519#endif
1520 return 0;
1521}
1522
Neels Hofmeyr87e45502017-06-20 00:17:59 +02001523/*! Determine if the given address is a local address
Harald Welteba6988b2011-08-17 12:46:48 +02001524 * \param[in] addr Socket Address
1525 * \param[in] addrlen Length of socket address in bytes
1526 * \returns 1 if address is local, 0 otherwise.
1527 */
Harald Weltebc32d052012-04-08 11:31:32 +02001528int osmo_sockaddr_is_local(struct sockaddr *addr, unsigned int addrlen)
Harald Welte33cb71a2011-05-21 18:54:32 +02001529{
1530 struct ifaddrs *ifaddr, *ifa;
1531
Eric8ae40cb2021-09-27 22:10:01 +02001532 if (sockaddr_is_local_routed(addr))
1533 return 1;
1534
Harald Welte33cb71a2011-05-21 18:54:32 +02001535 if (getifaddrs(&ifaddr) == -1) {
Philipp Maier6f0f5602017-02-09 14:09:06 +01001536 LOGP(DLGLOBAL, LOGL_ERROR, "getifaddrs:"
1537 " %s\n", strerror(errno));
Harald Welte33cb71a2011-05-21 18:54:32 +02001538 return -EIO;
1539 }
1540
1541 for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
Harald Welte4d3a7b12011-05-24 21:31:53 +02001542 if (!ifa->ifa_addr)
1543 continue;
Pau Espin Pedrol15753e92018-04-18 19:57:41 +02001544 if (sockaddr_equal(ifa->ifa_addr, addr, addrlen)) {
1545 freeifaddrs(ifaddr);
Harald Welte33cb71a2011-05-21 18:54:32 +02001546 return 1;
Pau Espin Pedrol15753e92018-04-18 19:57:41 +02001547 }
Harald Welte33cb71a2011-05-21 18:54:32 +02001548 }
1549
Pau Espin Pedrol15753e92018-04-18 19:57:41 +02001550 freeifaddrs(ifaddr);
Harald Welte33cb71a2011-05-21 18:54:32 +02001551 return 0;
1552}
Harald Weltee4764422011-05-22 12:25:57 +02001553
Pau Espin Pedrol3b3955b2022-10-04 12:38:49 +02001554/*! Determine if the given address is an ANY address ("0.0.0.0", "::"). Port is not checked.
1555 * \param[in] addr Socket Address
1556 * \param[in] addrlen Length of socket address in bytes
1557 * \returns 1 if address is ANY, 0 otherwise. -1 is address family not supported/detected.
1558 */
1559int osmo_sockaddr_is_any(const struct osmo_sockaddr *addr)
1560{
1561 switch (addr->u.sa.sa_family) {
1562 case AF_INET6: {
1563 struct in6_addr ip6_any = IN6ADDR_ANY_INIT;
1564 return memcmp(&addr->u.sin6.sin6_addr,
1565 &ip6_any, sizeof(ip6_any)) == 0;
1566 }
1567 case AF_INET:
1568 return addr->u.sin.sin_addr.s_addr == INADDR_ANY;
1569 default:
1570 return -1;
1571 }
1572}
1573
Max9d7a2472018-11-20 15:18:31 +01001574/*! Convert sockaddr_in to IP address as char string and port as uint16_t.
1575 * \param[out] addr String buffer to write IP address to, or NULL.
1576 * \param[out] addr_len Size of \a addr.
1577 * \param[out] port Pointer to uint16_t to write the port number to, or NULL.
1578 * \param[in] sin Sockaddr to convert.
1579 * \returns the required string buffer size, like osmo_strlcpy(), or 0 if \a addr is NULL.
1580 */
1581size_t osmo_sockaddr_in_to_str_and_uint(char *addr, unsigned int addr_len, uint16_t *port,
1582 const struct sockaddr_in *sin)
1583{
1584 if (port)
1585 *port = ntohs(sin->sin_port);
1586
1587 if (addr)
1588 return osmo_strlcpy(addr, inet_ntoa(sin->sin_addr), addr_len);
1589
1590 return 0;
1591}
1592
Neels Hofmeyr59f4caf2018-07-19 22:13:19 +02001593/*! Convert sockaddr to IP address as char string and port as uint16_t.
1594 * \param[out] addr String buffer to write IP address to, or NULL.
1595 * \param[out] addr_len Size of \a addr.
1596 * \param[out] port Pointer to uint16_t to write the port number to, or NULL.
1597 * \param[in] sa Sockaddr to convert.
1598 * \returns the required string buffer size, like osmo_strlcpy(), or 0 if \a addr is NULL.
1599 */
1600unsigned int osmo_sockaddr_to_str_and_uint(char *addr, unsigned int addr_len, uint16_t *port,
1601 const struct sockaddr *sa)
1602{
Neels Hofmeyr59f4caf2018-07-19 22:13:19 +02001603
Pau Espin Pedrol1a3d24e2020-08-28 18:31:32 +02001604 const struct sockaddr_in6 *sin6;
1605
1606 switch (sa->sa_family) {
1607 case AF_INET:
1608 return osmo_sockaddr_in_to_str_and_uint(addr, addr_len, port,
1609 (const struct sockaddr_in *)sa);
1610 case AF_INET6:
1611 sin6 = (const struct sockaddr_in6 *)sa;
1612 if (port)
1613 *port = ntohs(sin6->sin6_port);
1614 if (addr && inet_ntop(sa->sa_family, &sin6->sin6_addr, addr, addr_len))
1615 return strlen(addr);
1616 break;
1617 }
1618 return 0;
Neels Hofmeyr59f4caf2018-07-19 22:13:19 +02001619}
1620
Pau Espin Pedrol5cc4fe42020-08-31 12:52:06 +02001621/*! inet_ntop() wrapper for a struct sockaddr.
1622 * \param[in] sa source sockaddr to get the address from.
1623 * \param[out] dst string buffer of at least INET6_ADDRSTRLEN size.
1624 * \returns returns a non-null pointer to dst. NULL is returned if there was an
1625 * error, with errno set to indicate the error.
1626 */
1627const char *osmo_sockaddr_ntop(const struct sockaddr *sa, char *dst)
1628{
1629 const struct osmo_sockaddr *osa = (const struct osmo_sockaddr *)sa;
1630 return inet_ntop(osa->u.sa.sa_family,
1631 osa->u.sa.sa_family == AF_INET6 ?
1632 (const void *)&osa->u.sin6.sin6_addr :
1633 (const void *)&osa->u.sin.sin_addr,
1634 dst, INET6_ADDRSTRLEN);
1635}
1636
1637/*! Get sockaddr port content (in host byte order)
1638 * \param[in] sa source sockaddr to get the port from.
1639 * \returns returns the sockaddr port in host byte order
1640 */
1641uint16_t osmo_sockaddr_port(const struct sockaddr *sa)
1642{
1643 const struct osmo_sockaddr *osa = (const struct osmo_sockaddr *)sa;
1644 switch (osa->u.sa.sa_family) {
1645 case AF_INET6:
1646 return ntohs(osa->u.sin6.sin6_port);
1647 case AF_INET:
1648 return ntohs(osa->u.sin.sin_port);
1649 }
1650 return 0;
1651}
1652
Neels Hofmeyr9c7f7f82022-01-11 19:25:40 +01001653/*! Set sockaddr port content (to network byte order).
1654 * \param[out] sa sockaddr to set the port of.
1655 * \param[in] port port nr to set.
1656 */
1657void osmo_sockaddr_set_port(struct sockaddr *sa, uint16_t port)
1658{
1659 struct osmo_sockaddr *osa = (struct osmo_sockaddr *)sa;
1660 switch (osa->u.sa.sa_family) {
1661 case AF_INET6:
1662 osa->u.sin6.sin6_port = htons(port);
1663 return;
1664 case AF_INET:
1665 osa->u.sin.sin_port = htons(port);
1666 return;
1667 }
1668}
1669
Pau Espin Pedrolcc296c92023-01-16 14:54:55 +01001670static unsigned int in6_addr_netmask_to_prefixlen(const struct in6_addr *netmask)
1671{
1672 #if defined(__linux__)
1673 #define ADDRFIELD(i) s6_addr32[i]
1674 #else
1675 #define ADDRFIELD(i) __u6_addr.__u6_addr32[i]
1676 #endif
1677
1678 unsigned int i, j, prefix = 0;
1679
1680 for (j = 0; j < 4; j++) {
1681 uint32_t bits = netmask->ADDRFIELD(j);
1682 uint8_t *b = (uint8_t *)&bits;
1683 for (i = 0; i < 4; i++) {
1684 while (b[i] & 0x80) {
1685 prefix++;
1686 b[i] = b[i] << 1;
1687 }
1688 }
1689 }
1690
1691 #undef ADDRFIELD
1692
1693 return prefix;
1694}
1695
1696static unsigned int in_addr_netmask_to_prefixlen(const struct in_addr *netmask)
1697{
1698 uint32_t bits = netmask->s_addr;
1699 uint8_t *b = (uint8_t *)&bits;
1700 unsigned int i, prefix = 0;
1701
1702 for (i = 0; i < 4; i++) {
1703 while (b[i] & 0x80) {
1704 prefix++;
1705 b[i] = b[i] << 1;
1706 }
1707 }
1708 return prefix;
1709}
1710
1711/*! Convert netmask to prefix length representation
1712 * \param[in] netmask sockaddr containing a netmask (consecutive list of 1-bit followed by consecutive list of 0-bit)
1713 * \returns prefix length representation of the netmask (count of 1-bit from the start of the netmask), negative on error.
1714 */
1715int osmo_sockaddr_netmask_to_prefixlen(const struct osmo_sockaddr *netmask)
1716{
1717 switch (netmask->u.sa.sa_family) {
1718 case AF_INET6:
1719 return in6_addr_netmask_to_prefixlen(&netmask->u.sin6.sin6_addr);
1720 case AF_INET:
1721 return in_addr_netmask_to_prefixlen(&netmask->u.sin.sin_addr);
1722 default:
1723 return -ENOTSUP;
1724 }
1725}
1726
Harald Welte641cc3c2023-11-21 19:45:57 +01001727/*! Convert an IP address string (and port number) into a 'struct osmo_sockaddr'.
1728 * \param[out] osa_out caller-allocated osmo_sockaddr storage
1729 * \param[in] ipstr IP[v4,v6] address in string format
1730 * \param[in] port port number (host byte order)
1731 * \returns 0 on success; negative on error. */
1732int osmo_sockaddr_from_str_and_uint(struct osmo_sockaddr *osa_out, const char *ipstr, uint16_t port)
1733{
1734 struct addrinfo *ai = addrinfo_helper(AF_UNSPEC, 0, 0, ipstr, port, true);
1735
1736 if (!ai)
1737 return -EIO;
1738
1739 if (ai->ai_addrlen > sizeof(*osa_out))
1740 return -ENOSPC;
1741
1742 memcpy(&osa_out->u.sa, ai->ai_addr, ai->ai_addrlen);
1743 freeaddrinfo(ai);
1744
1745 return 0;
1746}
1747
Neels Hofmeyr87e45502017-06-20 00:17:59 +02001748/*! Initialize a unix domain socket (including bind/connect)
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001749 * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
1750 * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
1751 * \param[in] socket_path path to identify the socket
1752 * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
Harald Welte2d2e2cc2016-04-25 12:11:20 +02001753 * \returns socket fd on success; negative on error
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001754 *
1755 * This function creates a new unix domain socket, \a
1756 * type and \a proto and optionally binds or connects it, depending on
1757 * the value of \a flags parameter.
1758 */
Eric Wildeb5769b2019-06-27 15:31:17 +02001759#if defined(__clang__) && defined(SUN_LEN)
1760__attribute__((no_sanitize("undefined")))
1761#endif
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001762int osmo_sock_unix_init(uint16_t type, uint8_t proto,
1763 const char *socket_path, unsigned int flags)
1764{
1765 struct sockaddr_un local;
Harald Weltefaf6b702021-04-28 13:04:59 +02001766 int sfd, rc;
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001767 unsigned int namelen;
1768
1769 if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) ==
1770 (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT))
1771 return -EINVAL;
1772
1773 local.sun_family = AF_UNIX;
Stefan Sperling6afb3f52018-09-20 17:21:05 +02001774 /* When an AF_UNIX socket is bound, sun_path should be NUL-terminated. See unix(7) man page. */
1775 if (osmo_strlcpy(local.sun_path, socket_path, sizeof(local.sun_path)) >= sizeof(local.sun_path)) {
Stefan Sperling896ff6d2018-08-28 14:34:17 +02001776 LOGP(DLGLOBAL, LOGL_ERROR, "Socket path exceeds maximum length of %zd bytes: %s\n",
1777 sizeof(local.sun_path), socket_path);
1778 return -ENOSPC;
1779 }
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001780
1781#if defined(BSD44SOCKETS) || defined(__UNIXWARE__)
Stefan Sperling6afb3f52018-09-20 17:21:05 +02001782 local.sun_len = strlen(local.sun_path);
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001783#endif
1784#if defined(BSD44SOCKETS) || defined(SUN_LEN)
1785 namelen = SUN_LEN(&local);
1786#else
Stefan Sperling6afb3f52018-09-20 17:21:05 +02001787 namelen = strlen(local.sun_path) +
1788 offsetof(struct sockaddr_un, sun_path);
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001789#endif
1790
1791 sfd = socket(AF_UNIX, type, proto);
1792 if (sfd < 0)
Maxdb7cb692023-02-11 21:35:21 +03001793 return -errno;
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001794
1795 if (flags & OSMO_SOCK_F_CONNECT) {
1796 rc = connect(sfd, (struct sockaddr *)&local, namelen);
1797 if (rc < 0)
1798 goto err;
1799 } else {
1800 unlink(local.sun_path);
1801 rc = bind(sfd, (struct sockaddr *)&local, namelen);
1802 if (rc < 0)
1803 goto err;
1804 }
1805
Harald Weltefaf6b702021-04-28 13:04:59 +02001806 rc = socket_helper_tail(sfd, flags);
1807 if (rc < 0)
1808 return rc;
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001809
Harald Weltec47bbda2017-07-13 16:13:26 +02001810 rc = osmo_sock_init_tail(sfd, type, flags);
1811 if (rc < 0) {
1812 close(sfd);
Maxdb7cb692023-02-11 21:35:21 +03001813 sfd = rc;
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001814 }
1815
1816 return sfd;
1817err:
1818 close(sfd);
Maxdb7cb692023-02-11 21:35:21 +03001819 return -errno;
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001820}
1821
Neels Hofmeyr87e45502017-06-20 00:17:59 +02001822/*! Initialize a unix domain socket and fill \ref osmo_fd
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001823 * \param[out] ofd file descriptor (will be filled in)
1824 * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
1825 * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
1826 * \param[in] socket_path path to identify the socket
1827 * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
Harald Welte2d2e2cc2016-04-25 12:11:20 +02001828 * \returns socket fd on success; negative on error
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001829 *
Vadim Yanitskiyb606d762019-06-01 19:02:47 +07001830 * This function creates (and optionally binds/connects) a socket
1831 * using osmo_sock_unix_init, but also fills the ofd structure.
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001832 */
1833int osmo_sock_unix_init_ofd(struct osmo_fd *ofd, uint16_t type, uint8_t proto,
1834 const char *socket_path, unsigned int flags)
1835{
Harald Welte24980ba2021-04-23 14:07:18 +02001836 return osmo_fd_init_ofd(ofd, osmo_sock_unix_init(type, proto, socket_path, flags), flags);
Álvaro Neira Ayuso5ade61a2014-03-24 13:02:00 +01001837}
1838
Neels Hofmeyr01457512018-12-12 01:48:54 +01001839/*! Get the IP and/or port number on socket in separate string buffers.
Oliver Smith7acd5d02018-10-25 11:16:36 +02001840 * \param[in] fd file descriptor of socket
1841 * \param[out] ip IP address (will be filled in when not NULL)
1842 * \param[in] ip_len length of the ip buffer
1843 * \param[out] port number (will be filled in when not NULL)
1844 * \param[in] port_len length of the port buffer
1845 * \param[in] local (true) or remote (false) name will get looked at
1846 * \returns 0 on success; negative otherwise
1847 */
Neels Hofmeyr01457512018-12-12 01:48:54 +01001848int osmo_sock_get_ip_and_port(int fd, char *ip, size_t ip_len, char *port, size_t port_len, bool local)
Oliver Smith7acd5d02018-10-25 11:16:36 +02001849{
Pau Espin Pedroled42a882020-08-21 15:39:17 +02001850 struct sockaddr_storage sa;
Oliver Smith7acd5d02018-10-25 11:16:36 +02001851 socklen_t len = sizeof(sa);
Oliver Smith860651e2018-10-30 14:31:57 +01001852 char ipbuf[INET6_ADDRSTRLEN], portbuf[6];
Oliver Smith7acd5d02018-10-25 11:16:36 +02001853 int rc;
1854
Pau Espin Pedroled42a882020-08-21 15:39:17 +02001855 rc = local ? getsockname(fd, (struct sockaddr*)&sa, &len) : getpeername(fd, (struct sockaddr*)&sa, &len);
Oliver Smith7acd5d02018-10-25 11:16:36 +02001856 if (rc < 0)
1857 return rc;
1858
Pau Espin Pedroled42a882020-08-21 15:39:17 +02001859 rc = getnameinfo((const struct sockaddr*)&sa, len, ipbuf, sizeof(ipbuf),
Oliver Smith7acd5d02018-10-25 11:16:36 +02001860 portbuf, sizeof(portbuf),
1861 NI_NUMERICHOST | NI_NUMERICSERV);
1862 if (rc < 0)
1863 return rc;
1864
1865 if (ip)
1866 strncpy(ip, ipbuf, ip_len);
1867 if (port)
1868 strncpy(port, portbuf, port_len);
1869 return 0;
1870}
1871
Andreas Eversberge8ab1b72024-02-22 15:38:47 +01001872#ifdef HAVE_LIBSCTP
Pau Espin Pedrol6a2975c2023-11-30 18:15:30 +01001873/*! Get multiple IP addresses and/or port number on socket in separate string buffers
1874 * \param[in] fd file descriptor of socket.
1875 * \param[out] ip_proto IPPROTO of the socket, eg: IPPROTO_SCTP.
1876 * \param[out] ip Pointer to memory holding consecutive buffers of size ip_len.
1877 * \param[out] ip_cnt length ip array pointer. on return it contains the number of addresses found.
1878 * \param[in] ip_len length of each of the string buffer in the the ip array.
1879 * \param[out] port number (will be filled in when not NULL).
1880 * \param[in] port_len length of the port buffer.
1881 * \param[in] local (true) or remote (false) name will get looked at.
1882 * \returns 0 on success; negative otherwise.
1883 *
1884 * Upon return, ip_cnt can be set to a higher value than the one set by the
1885 * caller. This can be used by the caller to find out the required array length
1886 * and then obtaining by calling the function twice. Only up to ip_cnt addresses
1887 * are filed in, as per the value provided by the caller.
1888 *
1889 * Usage example retrieving all (up to OSMO_SOCK_MAX_ADDRS, 32) bound IP addresses and bound port:
1890 * char hostbuf[OSMO_SOCK_MAX_ADDRS][INET6_ADDRSTRLEN];
1891 * size_t num_hostbuf = ARRAY_SIZE(hostbuf);
1892 * char portbuf[6];
1893 * rc = osmo_sock_multiaddr_get_ip_and_port(fd, IPPROTO_SCTP, &hostbuf[0][0], &num_hostbuf,
1894 * sizeof(hostbuf[0]), portbuf, sizeof(portbuf), true);
1895 * if (rc < 0)
1896 * goto error;
1897 * if (num_hostbuf > ARRAY_SIZE(hostbuf))
1898 * goto not_enough_buffers;
1899 */
1900int osmo_sock_multiaddr_get_ip_and_port(int fd, int ip_proto, char *ip, size_t *ip_cnt, size_t ip_len,
1901 char *port, size_t port_len, bool local)
1902{
1903 struct sockaddr *addrs = NULL;
1904 unsigned int n_addrs, i;
1905 void *addr_buf;
1906 int rc;
1907
1908 switch (ip_proto) {
1909 case IPPROTO_SCTP:
1910 break; /* continue below */
1911 default:
1912 if (*ip_cnt == 0) {
1913 *ip_cnt = 1;
1914 return 0;
1915 }
1916 *ip_cnt = 1;
1917 return osmo_sock_get_ip_and_port(fd, ip, ip_len, port, port_len, local);
1918 }
1919
1920 rc = local ? sctp_getladdrs(fd, 0, &addrs) : sctp_getpaddrs(fd, 0, &addrs);
1921 if (rc < 0)
1922 return rc;
1923 if (rc == 0)
1924 return -ENOTCONN;
1925
1926 n_addrs = rc;
1927 addr_buf = (void *)addrs;
1928 for (i = 0; i < n_addrs; i++) {
1929 struct sockaddr *sa_addr = (struct sockaddr *)addr_buf;
1930 size_t addrlen;
1931
1932 if (i >= *ip_cnt)
1933 break;
1934
1935 switch (sa_addr->sa_family) {
1936 case AF_INET:
1937 addrlen = sizeof(struct sockaddr_in);
1938 break;
1939 case AF_INET6:
1940 addrlen = sizeof(struct sockaddr_in6);
1941 break;
1942 default:
1943 rc = -EINVAL;
1944 goto free_addrs_ret;
1945 }
1946
1947 rc = getnameinfo(sa_addr, addrlen, &ip[i * ip_len], ip_len,
1948 port, port_len,
1949 NI_NUMERICHOST | NI_NUMERICSERV);
1950 if (rc < 0)
1951 goto free_addrs_ret;
1952 addr_buf += addrlen;
1953 }
1954
1955 *ip_cnt = n_addrs;
1956 rc = 0;
1957free_addrs_ret:
1958 local ? sctp_freeladdrs(addrs) : sctp_freepaddrs(addrs);
1959 return rc;
1960}
Andreas Eversberge8ab1b72024-02-22 15:38:47 +01001961#endif
Pau Espin Pedrol6a2975c2023-11-30 18:15:30 +01001962
Oliver Smith7acd5d02018-10-25 11:16:36 +02001963/*! Get local IP address on socket
1964 * \param[in] fd file descriptor of socket
1965 * \param[out] ip IP address (will be filled in)
1966 * \param[in] len length of the output buffer
1967 * \returns 0 on success; negative otherwise
1968 */
1969int osmo_sock_get_local_ip(int fd, char *ip, size_t len)
1970{
Neels Hofmeyr01457512018-12-12 01:48:54 +01001971 return osmo_sock_get_ip_and_port(fd, ip, len, NULL, 0, true);
Oliver Smith7acd5d02018-10-25 11:16:36 +02001972}
1973
1974/*! Get local port on socket
1975 * \param[in] fd file descriptor of socket
1976 * \param[out] port number (will be filled in)
1977 * \param[in] len length of the output buffer
1978 * \returns 0 on success; negative otherwise
1979 */
1980int osmo_sock_get_local_ip_port(int fd, char *port, size_t len)
1981{
Neels Hofmeyr01457512018-12-12 01:48:54 +01001982 return osmo_sock_get_ip_and_port(fd, NULL, 0, port, len, true);
Oliver Smith7acd5d02018-10-25 11:16:36 +02001983}
1984
1985/*! Get remote IP address on socket
1986 * \param[in] fd file descriptor of socket
1987 * \param[out] ip IP address (will be filled in)
1988 * \param[in] len length of the output buffer
1989 * \returns 0 on success; negative otherwise
1990 */
1991int osmo_sock_get_remote_ip(int fd, char *ip, size_t len)
1992{
Neels Hofmeyr01457512018-12-12 01:48:54 +01001993 return osmo_sock_get_ip_and_port(fd, ip, len, NULL, 0, false);
Oliver Smith7acd5d02018-10-25 11:16:36 +02001994}
1995
1996/*! Get remote port on socket
1997 * \param[in] fd file descriptor of socket
1998 * \param[out] port number (will be filled in)
1999 * \param[in] len length of the output buffer
2000 * \returns 0 on success; negative otherwise
2001 */
2002int osmo_sock_get_remote_ip_port(int fd, char *port, size_t len)
2003{
Neels Hofmeyr01457512018-12-12 01:48:54 +01002004 return osmo_sock_get_ip_and_port(fd, NULL, 0, port, len, false);
Oliver Smith7acd5d02018-10-25 11:16:36 +02002005}
2006
Neels Hofmeyr01457512018-12-12 01:48:54 +01002007/*! Get address/port information on socket in dyn-alloc string like "(r=1.2.3.4:5<->l=6.7.8.9:10)".
2008 * Usually, it is better to use osmo_sock_get_name2() for a static string buffer or osmo_sock_get_name_buf() for a
2009 * caller provided string buffer, to avoid the dynamic talloc allocation.
Harald Welte48f55832017-01-26 00:03:10 +01002010 * \param[in] ctx talloc context from which to allocate string buffer
2011 * \param[in] fd file descriptor of socket
Neels Hofmeyr01457512018-12-12 01:48:54 +01002012 * \returns string identifying the connection of this socket, talloc'd from ctx.
Harald Welte48f55832017-01-26 00:03:10 +01002013 */
Harald Weltec0dfc9d2019-03-18 18:29:43 +01002014char *osmo_sock_get_name(const void *ctx, int fd)
Harald Welte48f55832017-01-26 00:03:10 +01002015{
Philipp Maier64b51eb2019-01-14 11:59:11 +01002016 char str[OSMO_SOCK_NAME_MAXLEN];
Neels Hofmeyr01457512018-12-12 01:48:54 +01002017 int rc;
2018 rc = osmo_sock_get_name_buf(str, sizeof(str), fd);
2019 if (rc <= 0)
2020 return NULL;
2021 return talloc_asprintf(ctx, "(%s)", str);
2022}
2023
Andreas Eversberge8ab1b72024-02-22 15:38:47 +01002024#ifdef HAVE_LIBSCTP
Pau Espin Pedrol5ac8aa52023-11-30 19:12:16 +01002025/*! Format multiple IP addresses and/or port number into a combined string buffer
2026 * \param[out] str Destination string buffer.
Pau Espin Pedrola37921a2023-12-12 14:00:10 +01002027 * \param[in] str_len sizeof(str), usually OSMO_SOCK_MULTIADDR_PEER_STR_MAXLEN.
Pau Espin Pedrol5ac8aa52023-11-30 19:12:16 +01002028 * \param[out] ip Pointer to memory holding ip_cnt consecutive buffers of size ip_len.
2029 * \param[out] ip_cnt length ip array pointer. on return it contains the number of addresses found.
2030 * \param[in] ip_len length of each of the string buffer in the the ip array.
2031 * \param[out] port number (will be printed in when not NULL).
2032 * \return String length as returned by snprintf(), or negative on error.
2033 *
Pau Espin Pedrola37921a2023-12-12 14:00:10 +01002034 * This API expects an ip array as the one filled in by
Pau Espin Pedrol5ac8aa52023-11-30 19:12:16 +01002035 * osmo_sock_multiaddr_get_ip_and_port(), and hence it's a good companion for
2036 * that API.
2037 */
2038int osmo_multiaddr_ip_and_port_snprintf(char *str, size_t str_len,
2039 const char *ip, size_t ip_cnt, size_t ip_len,
2040 const char *portbuf)
2041{
2042 struct osmo_strbuf sb = { .buf = str, .len = str_len };
2043 bool is_v6 = false;
2044 unsigned int i;
2045
2046 if (ip_cnt == 0) {
2047 OSMO_STRBUF_PRINTF(sb, "NULL:%s", portbuf);
2048 return sb.chars_needed;
2049 }
2050 if (ip_cnt > 1)
2051 OSMO_STRBUF_PRINTF(sb, "(");
2052 else if ((is_v6 = !!strchr(&ip[0], ':'))) /* IPv6, add [] to separate from port. */
2053 OSMO_STRBUF_PRINTF(sb, "[");
2054
2055 for (i = 0; i < ip_cnt - 1; i++)
2056 OSMO_STRBUF_PRINTF(sb, "%s|", &ip[i * ip_len]);
2057 OSMO_STRBUF_PRINTF(sb, "%s", &ip[i * ip_len]);
2058
2059 if (ip_cnt > 1)
2060 OSMO_STRBUF_PRINTF(sb, ")");
2061 else if (is_v6)
2062 OSMO_STRBUF_PRINTF(sb, "]");
2063 if (portbuf)
2064 OSMO_STRBUF_PRINTF(sb, ":%s", portbuf);
2065
2066 return sb.chars_needed;
2067}
2068
2069/*! Get address/port information on socket in provided string buffer, like "r=1.2.3.4:5<->l=6.7.8.9:10".
2070 * This does not include braces like osmo_sock_get_name().
2071 * \param[out] str Destination string buffer.
Pau Espin Pedrola37921a2023-12-12 14:00:10 +01002072 * \param[in] str_len sizeof(str), usually OSMO_SOCK_MULTIADDR_NAME_MAXLEN.
Pau Espin Pedrol5ac8aa52023-11-30 19:12:16 +01002073 * \param[in] fd File descriptor of socket.
2074 * \param[in] fd IPPROTO of the socket, eg: IPPROTO_SCTP.
2075 * \return String length as returned by snprintf(), or negative on error.
2076 */
2077int osmo_sock_multiaddr_get_name_buf(char *str, size_t str_len, int fd, int sk_proto)
2078{
2079 char hostbuf[OSMO_SOCK_MAX_ADDRS][INET6_ADDRSTRLEN];
2080 size_t num_hostbuf = ARRAY_SIZE(hostbuf);
2081 char portbuf[6];
2082 struct osmo_strbuf sb = { .buf = str, .len = str_len };
2083
2084 if (fd < 0) {
2085 osmo_strlcpy(str, "<error-bad-fd>", str_len);
2086 return sb.chars_needed;
2087 }
2088
2089 switch (sk_proto) {
2090 case IPPROTO_SCTP:
2091 break; /* continue below */
2092 default:
2093 return osmo_sock_get_name_buf(str, str_len, fd);
2094 }
2095
2096 /* get remote */
2097 OSMO_STRBUF_PRINTF(sb, "r=");
2098 if (osmo_sock_multiaddr_get_ip_and_port(fd, sk_proto, &hostbuf[0][0], &num_hostbuf,
2099 sizeof(hostbuf[0]), portbuf, sizeof(portbuf), false) != 0) {
2100 OSMO_STRBUF_PRINTF(sb, "NULL");
2101 } else {
2102 const bool need_more_bufs = num_hostbuf > ARRAY_SIZE(hostbuf);
2103 if (need_more_bufs)
2104 num_hostbuf = ARRAY_SIZE(hostbuf);
2105 OSMO_STRBUF_APPEND(sb, osmo_multiaddr_ip_and_port_snprintf,
2106 &hostbuf[0][0], num_hostbuf, sizeof(hostbuf[0]), portbuf);
2107 if (need_more_bufs)
2108 OSMO_STRBUF_PRINTF(sb, "<need-more-bufs!>");
2109 }
2110
2111 OSMO_STRBUF_PRINTF(sb, "<->l=");
2112
2113 /* get local */
2114 num_hostbuf = ARRAY_SIZE(hostbuf);
2115 if (osmo_sock_multiaddr_get_ip_and_port(fd, sk_proto, &hostbuf[0][0], &num_hostbuf,
2116 sizeof(hostbuf[0]), portbuf, sizeof(portbuf), true) != 0) {
2117 OSMO_STRBUF_PRINTF(sb, "NULL");
2118 } else {
2119 const bool need_more_bufs = num_hostbuf > ARRAY_SIZE(hostbuf);
2120 if (need_more_bufs)
2121 num_hostbuf = ARRAY_SIZE(hostbuf);
2122 OSMO_STRBUF_APPEND(sb, osmo_multiaddr_ip_and_port_snprintf,
2123 &hostbuf[0][0], num_hostbuf, sizeof(hostbuf[0]), portbuf);
2124 if (need_more_bufs)
2125 OSMO_STRBUF_PRINTF(sb, "<need-more-bufs!>");
2126 }
2127
2128 return sb.chars_needed;
2129}
Andreas Eversberge8ab1b72024-02-22 15:38:47 +01002130#endif
Pau Espin Pedrol5ac8aa52023-11-30 19:12:16 +01002131
Neels Hofmeyr01457512018-12-12 01:48:54 +01002132/*! Get address/port information on socket in provided string buffer, like "r=1.2.3.4:5<->l=6.7.8.9:10".
2133 * This does not include braces like osmo_sock_get_name().
2134 * \param[out] str Destination string buffer.
2135 * \param[in] str_len sizeof(str).
2136 * \param[in] fd File descriptor of socket.
2137 * \return String length as returned by snprintf(), or negative on error.
2138 */
2139int osmo_sock_get_name_buf(char *str, size_t str_len, int fd)
2140{
Pau Espin Pedrol9a7b2c92024-03-12 13:05:31 +01002141 struct osmo_strbuf sb = { .buf = str, .len = str_len };
2142 struct osmo_sockaddr osa;
2143 struct sockaddr_un *sun;
2144 socklen_t len;
Neels Hofmeyr01457512018-12-12 01:48:54 +01002145 int rc;
Harald Welte48f55832017-01-26 00:03:10 +01002146
Daniel Willmann7052cc62023-06-16 09:53:15 +02002147 if (fd < 0) {
2148 osmo_strlcpy(str, "<error-bad-fd>", str_len);
2149 return -EBADF;
2150 }
2151
Pau Espin Pedrol9a7b2c92024-03-12 13:05:31 +01002152
2153 len = sizeof(osa.u.sas);
2154 rc = getsockname(fd, &osa.u.sa, &len);
2155 if (rc < 0) {
Harald Welteea9ea522019-05-10 09:28:24 +02002156 osmo_strlcpy(str, "<error-in-getsockname>", str_len);
Neels Hofmeyr01457512018-12-12 01:48:54 +01002157 return rc;
Harald Welteea9ea522019-05-10 09:28:24 +02002158 }
Harald Welte48f55832017-01-26 00:03:10 +01002159
Pau Espin Pedrol9a7b2c92024-03-12 13:05:31 +01002160 switch (osa.u.sa.sa_family) {
2161 case AF_INET:
2162 case AF_INET6:
Pau Espin Pedrolc9ad3452024-03-12 14:04:55 +01002163 {
2164 char hostbuf_l[INET6_ADDRSTRLEN], hostbuf_r[INET6_ADDRSTRLEN];
2165 char portbuf_l[6], portbuf_r[6];
2166
Pau Espin Pedrol9a7b2c92024-03-12 13:05:31 +01002167 len = sizeof(osa.u.sas);
2168 rc = getnameinfo(&osa.u.sa, len, hostbuf_l, sizeof(hostbuf_l),
2169 portbuf_l, sizeof(portbuf_l),
2170 NI_NUMERICHOST | NI_NUMERICSERV);
2171 if (rc < 0) {
2172 osmo_strlcpy(str, "<error-in-getnameinfo>", str_len);
2173 return rc;
2174 }
2175 /* Now attempt to get remote: */
2176 len = sizeof(osa.u.sas);
2177 rc = getpeername(fd, &osa.u.sa, &len);
2178 if (rc < 0) {
2179 OSMO_STRBUF_PRINTF(sb, "r=NULL<->l=%s:%s", hostbuf_l, portbuf_l);
2180 return sb.chars_needed;
2181 }
2182 len = sizeof(osa.u.sas);
2183 rc = getnameinfo(&osa.u.sa, len, hostbuf_r, sizeof(hostbuf_r),
2184 portbuf_r, sizeof(portbuf_r),
2185 NI_NUMERICHOST | NI_NUMERICSERV);
2186 if (rc < 0) {
2187 OSMO_STRBUF_PRINTF(sb, "r=NULL<->l=%s:%s", hostbuf_l, portbuf_l);
2188 return sb.chars_needed;
2189 }
2190 OSMO_STRBUF_PRINTF(sb, "r=%s:%s<->l=%s:%s", hostbuf_r, portbuf_r, hostbuf_l, portbuf_l);
2191 return sb.chars_needed;
Pau Espin Pedrolc9ad3452024-03-12 14:04:55 +01002192 }
Pau Espin Pedrol9a7b2c92024-03-12 13:05:31 +01002193 case AF_UNIX:
Pau Espin Pedrolc9ad3452024-03-12 14:04:55 +01002194 {
2195 unsigned long long remote_pid;
2196 bool have_remote_pid;
2197#if defined(SO_PEERCRED)
2198 struct ucred ucred;
2199 len = sizeof(struct ucred);
2200 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) {
2201 have_remote_pid = false;
2202 } else {
2203 have_remote_pid = true;
2204 remote_pid = (unsigned long long)ucred.pid;
2205 }
2206#else
2207 #pragma message "SO_PEERCRED not available"
2208 have_remote_pid = false;
2209#endif
Pau Espin Pedrol9a7b2c92024-03-12 13:05:31 +01002210 /* Make sure sun_path is NULL terminated: */
2211 sun = (struct sockaddr_un *)&osa.u.sa;
2212 sun->sun_path[sizeof(sun->sun_path) - 1] = '\0';
Pau Espin Pedrolc9ad3452024-03-12 14:04:55 +01002213 if (have_remote_pid)
2214 OSMO_STRBUF_PRINTF(sb, "r=%llu<->", remote_pid);
2215 else
2216 OSMO_STRBUF_PRINTF(sb, "r=NULL<->");
2217 OSMO_STRBUF_PRINTF(sb, "l=%s:%d", sun->sun_path, fd);
Pau Espin Pedrol9a7b2c92024-03-12 13:05:31 +01002218 return sb.chars_needed;
Pau Espin Pedrolc9ad3452024-03-12 14:04:55 +01002219 }
Pau Espin Pedrol9a7b2c92024-03-12 13:05:31 +01002220 default:
2221 osmo_strlcpy(str, "<socket-family-no-supported>", str_len);
2222 return -ENOTSUP;
2223 }
Neels Hofmeyr01457512018-12-12 01:48:54 +01002224}
2225
2226/*! Get address/port information on socket in static string, like "r=1.2.3.4:5<->l=6.7.8.9:10".
2227 * This does not include braces like osmo_sock_get_name().
2228 * \param[in] fd File descriptor of socket.
2229 * \return Static string buffer containing the result.
2230 */
2231const char *osmo_sock_get_name2(int fd)
2232{
Harald Welte171ef822019-03-28 10:49:05 +01002233 static __thread char str[OSMO_SOCK_NAME_MAXLEN];
Neels Hofmeyr01457512018-12-12 01:48:54 +01002234 osmo_sock_get_name_buf(str, sizeof(str), fd);
2235 return str;
Harald Welte48f55832017-01-26 00:03:10 +01002236}
2237
Harald Welte179f3572019-03-18 18:38:47 +01002238/*! Get address/port information on socket in static string, like "r=1.2.3.4:5<->l=6.7.8.9:10".
2239 * This does not include braces like osmo_sock_get_name().
2240 * \param[in] fd File descriptor of socket.
2241 * \return Static string buffer containing the result.
2242 */
2243char *osmo_sock_get_name2_c(const void *ctx, int fd)
2244{
2245 char *str = talloc_size(ctx, OSMO_SOCK_NAME_MAXLEN);
2246 if (!str)
2247 return NULL;
Vadim Yanitskiy4f619c22019-04-12 21:48:07 +07002248 osmo_sock_get_name_buf(str, OSMO_SOCK_NAME_MAXLEN, fd);
Harald Welte179f3572019-03-18 18:38:47 +01002249 return str;
2250}
2251
Harald Weltee30d7e62017-07-13 16:02:50 +02002252static int sock_get_domain(int fd)
2253{
2254 int domain;
2255#ifdef SO_DOMAIN
2256 socklen_t dom_len = sizeof(domain);
2257 int rc;
2258
2259 rc = getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &domain, &dom_len);
2260 if (rc < 0)
2261 return rc;
2262#else
2263 /* This of course sucks, but what shall we do on OSs like
2264 * FreeBSD that don't seem to expose a method by which one can
2265 * learn the address family of a socket? */
2266 domain = AF_INET;
2267#endif
2268 return domain;
2269}
2270
2271
2272/*! Activate or de-activate local loop-back of transmitted multicast packets
2273 * \param[in] fd file descriptor of related socket
2274 * \param[in] enable Enable (true) or disable (false) loop-back
2275 * \returns 0 on success; negative otherwise */
2276int osmo_sock_mcast_loop_set(int fd, bool enable)
2277{
2278 int domain, loop = 0;
2279
2280 if (enable)
2281 loop = 1;
2282
2283 domain = sock_get_domain(fd);
2284 if (domain < 0)
2285 return domain;
2286
2287 switch (domain) {
2288 case AF_INET:
2289 return setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
2290 case AF_INET6:
2291 return setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof(loop));
2292 default:
2293 return -EINVAL;
2294 }
2295}
2296
2297/*! Set the TTL of outbound multicast packets
2298 * \param[in] fd file descriptor of related socket
2299 * \param[in] ttl TTL of to-be-sent multicast packets
2300 * \returns 0 on success; negative otherwise */
2301int osmo_sock_mcast_ttl_set(int fd, uint8_t ttl)
2302{
2303 int domain, ttli = ttl;
2304
2305 domain = sock_get_domain(fd);
2306 if (domain < 0)
2307 return domain;
2308
2309 switch (domain) {
2310 case AF_INET:
2311 return setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttli, sizeof(ttli));
2312 case AF_INET6:
2313 return setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttli, sizeof(ttli));
2314 default:
2315 return -EINVAL;
2316 }
2317}
2318
Harald Welte44b99262020-03-07 14:59:05 +01002319/*! Set the network device to which we should bind the multicast socket
2320 * \param[in] fd file descriptor of related socket
2321 * \param[in] ifname name of network interface to user for multicast
2322 * \returns 0 on success; negative otherwise */
2323int osmo_sock_mcast_iface_set(int fd, const char *ifname)
2324{
2325 unsigned int ifindex;
2326 struct ip_mreqn mr;
2327
2328 /* first, resolve interface name to ifindex */
2329 ifindex = if_nametoindex(ifname);
2330 if (ifindex == 0)
2331 return -errno;
2332
2333 /* next, configure kernel to use that ifindex for this sockets multicast traffic */
2334 memset(&mr, 0, sizeof(mr));
2335 mr.imr_ifindex = ifindex;
2336 return setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &mr, sizeof(mr));
2337}
2338
2339
Harald Weltee30d7e62017-07-13 16:02:50 +02002340/*! Enable/disable receiving all multicast packets, even for non-subscribed groups
2341 * \param[in] fd file descriptor of related socket
2342 * \param[in] enable Enable or Disable receiving of all packets
2343 * \returns 0 on success; negative otherwise */
2344int osmo_sock_mcast_all_set(int fd, bool enable)
2345{
2346 int domain, all = 0;
2347
2348 if (enable)
2349 all = 1;
2350
2351 domain = sock_get_domain(fd);
2352 if (domain < 0)
2353 return domain;
2354
2355 switch (domain) {
2356 case AF_INET:
2357#ifdef IP_MULTICAST_ALL
2358 return setsockopt(fd, IPPROTO_IP, IP_MULTICAST_ALL, &all, sizeof(all));
2359#endif
2360 case AF_INET6:
2361 /* there seems no equivalent ?!? */
2362 default:
2363 return -EINVAL;
2364 }
2365}
2366
2367/* FreeBSD calls the socket option differently */
2368#if !defined(IPV6_ADD_MEMBERSHIP) && defined(IPV6_JOIN_GROUP)
2369#define IPV6_ADD_MEMBERSHIP IPV6_JOIN_GROUP
2370#endif
2371
2372/*! Subscribe to the given IP multicast group
2373 * \param[in] fd file descriptor of related scoket
2374 * \param[in] grp_addr ASCII representation of the multicast group address
2375 * \returns 0 on success; negative otherwise */
2376int osmo_sock_mcast_subscribe(int fd, const char *grp_addr)
2377{
2378 int rc, domain;
2379 struct ip_mreq mreq;
2380 struct ipv6_mreq mreq6;
2381 struct in6_addr i6a;
2382
2383 domain = sock_get_domain(fd);
2384 if (domain < 0)
2385 return domain;
2386
2387 switch (domain) {
2388 case AF_INET:
2389 memset(&mreq, 0, sizeof(mreq));
2390 mreq.imr_multiaddr.s_addr = inet_addr(grp_addr);
2391 mreq.imr_interface.s_addr = htonl(INADDR_ANY);
2392 return setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
2393#ifdef IPV6_ADD_MEMBERSHIP
2394 case AF_INET6:
2395 memset(&mreq6, 0, sizeof(mreq6));
2396 rc = inet_pton(AF_INET6, grp_addr, (void *)&i6a);
2397 if (rc < 0)
2398 return -EINVAL;
2399 mreq6.ipv6mr_multiaddr = i6a;
2400 return setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6));
2401#endif
2402 default:
2403 return -EINVAL;
2404 }
2405}
2406
Philipp Maier2d2490e2017-10-20 19:41:26 +02002407/*! Determine the matching local IP-address for a given remote IP-Address.
2408 * \param[out] local_ip caller provided memory for resulting local IP-address
2409 * \param[in] remote_ip remote IP-address
Philipp Maier2d2490e2017-10-20 19:41:26 +02002410 * \returns 0 on success; negative otherwise
2411 *
2412 * The function accepts IPv4 and IPv6 address strings. The caller must provide
2413 * at least INET6_ADDRSTRLEN bytes for local_ip if an IPv6 is expected as
2414 * as result. For IPv4 addresses the required amount is INET_ADDRSTRLEN. */
2415int osmo_sock_local_ip(char *local_ip, const char *remote_ip)
2416{
2417 int sfd;
2418 int rc;
2419 struct addrinfo addrinfo_hint;
2420 struct addrinfo *addrinfo = NULL;
Pau Espin Pedrol81e7a6c2020-08-28 20:44:26 +02002421 struct sockaddr_storage local_addr;
2422 struct sockaddr_in *sin;
2423 struct sockaddr_in6 *sin6;
Philipp Maier2d2490e2017-10-20 19:41:26 +02002424 socklen_t local_addr_len;
2425 uint16_t family;
2426
2427 /* Find out the address family (AF_INET or AF_INET6?) */
2428 memset(&addrinfo_hint, '\0', sizeof(addrinfo_hint));
Pau Espin Pedrol308ab792020-08-28 19:47:25 +02002429 addrinfo_hint.ai_family = AF_UNSPEC;
Philipp Maier2d2490e2017-10-20 19:41:26 +02002430 addrinfo_hint.ai_flags = AI_NUMERICHOST;
2431 rc = getaddrinfo(remote_ip, NULL, &addrinfo_hint, &addrinfo);
2432 if (rc)
2433 return -EINVAL;
2434 family = addrinfo->ai_family;
2435 freeaddrinfo(addrinfo);
2436
2437 /* Connect a dummy socket to trick the kernel into determining the
2438 * ip-address of the interface that would be used if we would send
2439 * out an actual packet */
2440 sfd = osmo_sock_init2(family, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, remote_ip, 0, OSMO_SOCK_F_CONNECT);
2441 if (sfd < 0)
2442 return -EINVAL;
2443
2444 /* Request the IP address of the interface that the kernel has
2445 * actually choosen. */
2446 memset(&local_addr, 0, sizeof(local_addr));
2447 local_addr_len = sizeof(local_addr);
2448 rc = getsockname(sfd, (struct sockaddr *)&local_addr, &local_addr_len);
Philipp Maier8b7975b2018-01-22 15:38:07 +01002449 close(sfd);
Philipp Maier2d2490e2017-10-20 19:41:26 +02002450 if (rc < 0)
2451 return -EINVAL;
Pau Espin Pedrol81e7a6c2020-08-28 20:44:26 +02002452
2453 switch (local_addr.ss_family) {
2454 case AF_INET:
2455 sin = (struct sockaddr_in*)&local_addr;
2456 if (!inet_ntop(AF_INET, &sin->sin_addr, local_ip, INET_ADDRSTRLEN))
2457 return -EINVAL;
2458 break;
2459 case AF_INET6:
2460 sin6 = (struct sockaddr_in6*)&local_addr;
Pau Espin Pedrol7bda8542020-08-31 11:21:30 +02002461 if (!inet_ntop(AF_INET6, &sin6->sin6_addr, local_ip, INET6_ADDRSTRLEN))
Pau Espin Pedrol81e7a6c2020-08-28 20:44:26 +02002462 return -EINVAL;
2463 break;
2464 default:
Philipp Maier2d2490e2017-10-20 19:41:26 +02002465 return -EINVAL;
Pau Espin Pedrol81e7a6c2020-08-28 20:44:26 +02002466 }
Philipp Maier2d2490e2017-10-20 19:41:26 +02002467
2468 return 0;
2469}
2470
Alexander Couzens0f364212020-07-27 22:33:01 +02002471/*! Determine the matching local address for a given remote address.
2472 * \param[out] local_ip caller provided memory for resulting local address
2473 * \param[in] remote_ip remote address
2474 * \returns 0 on success; negative otherwise
2475 */
2476int osmo_sockaddr_local_ip(struct osmo_sockaddr *local_ip, const struct osmo_sockaddr *remote_ip)
2477{
2478 int sfd;
2479 int rc;
2480 socklen_t local_ip_len;
2481
2482 sfd = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP, NULL, remote_ip, OSMO_SOCK_F_CONNECT);
2483 if (sfd < 0)
2484 return -EINVAL;
2485
2486 memset(local_ip, 0, sizeof(*local_ip));
2487 local_ip_len = sizeof(*local_ip);
2488 rc = getsockname(sfd, (struct sockaddr *)local_ip, &local_ip_len);
2489 close(sfd);
2490
2491 return rc;
2492}
2493
Neels Hofmeyrcf7f7922022-02-02 03:08:40 +01002494/*! Copy the addr part, the IP address octets in network byte order, to a buffer.
2495 * Useful for encoding network protocols.
2496 * \param[out] dst Write octets to this buffer.
2497 * \param[in] dst_maxlen Space available in buffer.
2498 * \param[in] os Sockaddr to copy IP of.
2499 * \return nr of octets written on success, negative on error.
2500 */
2501int osmo_sockaddr_to_octets(uint8_t *dst, size_t dst_maxlen, const struct osmo_sockaddr *os)
2502{
2503 const void *addr;
2504 size_t len;
Neels Hofmeyr1790f172022-02-02 03:08:40 +01002505 switch (os->u.sa.sa_family) {
Neels Hofmeyrcf7f7922022-02-02 03:08:40 +01002506 case AF_INET:
2507 addr = &os->u.sin.sin_addr;
2508 len = sizeof(os->u.sin.sin_addr);
2509 break;
2510 case AF_INET6:
2511 addr = &os->u.sin6.sin6_addr;
2512 len = sizeof(os->u.sin6.sin6_addr);
2513 break;
2514 default:
2515 return -ENOTSUP;
2516 }
2517 if (dst_maxlen < len)
2518 return -ENOSPC;
2519 memcpy(dst, addr, len);
2520 return len;
2521}
2522
2523/*! Copy the addr part, the IP address octets in network byte order, from a buffer.
2524 * Useful for decoding network protocols.
2525 * \param[out] os Write IP address to this sockaddr.
2526 * \param[in] src Source buffer to read IP address octets from.
2527 * \param[in] src_len Number of octets to copy.
2528 * \return number of octets read on success, negative on error.
2529 */
2530int osmo_sockaddr_from_octets(struct osmo_sockaddr *os, const void *src, size_t src_len)
2531{
2532 void *addr;
2533 size_t len;
2534 *os = (struct osmo_sockaddr){0};
2535 switch (src_len) {
2536 case sizeof(os->u.sin.sin_addr):
Neels Hofmeyr1790f172022-02-02 03:08:40 +01002537 os->u.sa.sa_family = AF_INET;
Neels Hofmeyrcf7f7922022-02-02 03:08:40 +01002538 addr = &os->u.sin.sin_addr;
2539 len = sizeof(os->u.sin.sin_addr);
2540 break;
2541 case sizeof(os->u.sin6.sin6_addr):
2542 os->u.sin6.sin6_family = AF_INET6;
2543 addr = &os->u.sin6.sin6_addr;
2544 len = sizeof(os->u.sin6.sin6_addr);
2545 break;
2546 default:
2547 return -ENOTSUP;
2548 }
2549 memcpy(addr, src, len);
2550 return len;
2551}
2552
Alexander Couzense4181ea2020-07-20 00:03:16 +02002553/*! Compare two osmo_sockaddr.
2554 * \param[in] a
2555 * \param[in] b
2556 * \return 0 if a and b are equal. Otherwise it follows memcmp()
2557 */
Vadim Yanitskiydef5a402020-10-09 21:40:47 +07002558int osmo_sockaddr_cmp(const struct osmo_sockaddr *a,
2559 const struct osmo_sockaddr *b)
Alexander Couzense4181ea2020-07-20 00:03:16 +02002560{
2561 if (a == b)
2562 return 0;
2563 if (!a)
2564 return 1;
2565 if (!b)
2566 return -1;
2567
2568 if (a->u.sa.sa_family != b->u.sa.sa_family) {
2569 return OSMO_CMP(a->u.sa.sa_family, b->u.sa.sa_family);
2570 }
2571
2572 switch (a->u.sa.sa_family) {
2573 case AF_INET:
2574 return memcmp(&a->u.sin, &b->u.sin, sizeof(struct sockaddr_in));
2575 case AF_INET6:
2576 return memcmp(&a->u.sin6, &b->u.sin6, sizeof(struct sockaddr_in6));
2577 default:
2578 /* fallback to memcmp for remaining AF over the full osmo_sockaddr length */
2579 return memcmp(a, b, sizeof(struct osmo_sockaddr));
2580 }
2581}
2582
Alexander Couzens80788fa2020-10-12 01:11:20 +02002583/*! string-format a given osmo_sockaddr address
2584 * \param[in] sockaddr the osmo_sockaddr to print
2585 * \return pointer to the string on success; NULL on error
2586 */
2587const char *osmo_sockaddr_to_str(const struct osmo_sockaddr *sockaddr)
2588{
2589 /* INET6_ADDRSTRLEN contains already a null termination,
2590 * adding '[' ']' ':' '16 bit port' */
2591 static __thread char buf[INET6_ADDRSTRLEN + 8];
2592 return osmo_sockaddr_to_str_buf(buf, sizeof(buf), sockaddr);
2593}
2594
Neels Hofmeyr09d65742021-12-01 10:25:02 +01002595/*! string-format a given osmo_sockaddr address into a user-supplied buffer.
2596 * Same as osmo_sockaddr_to_str_buf() but returns a would-be length in snprintf() style.
2597 * \param[in] buf user-supplied output buffer
2598 * \param[in] buf_len size of the user-supplied output buffer in bytes
2599 * \param[in] sockaddr the osmo_sockaddr to print
2600 * \return number of characters that would be written if the buffer is large enough, like snprintf().
2601 */
2602int osmo_sockaddr_to_str_buf2(char *buf, size_t buf_len, const struct osmo_sockaddr *sockaddr)
2603{
2604 struct osmo_strbuf sb = { .buf = buf, .len = buf_len };
2605 uint16_t port = 0;
2606
2607 if (!sockaddr) {
2608 OSMO_STRBUF_PRINTF(sb, "NULL");
2609 return sb.chars_needed;
2610 }
2611
2612 switch (sockaddr->u.sa.sa_family) {
2613 case AF_INET:
2614 OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_and_uint, &port, &sockaddr->u.sa);
2615 if (port)
2616 OSMO_STRBUF_PRINTF(sb, ":%u", port);
2617 break;
2618 case AF_INET6:
2619 OSMO_STRBUF_PRINTF(sb, "[");
2620 OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_and_uint, &port, &sockaddr->u.sa);
2621 OSMO_STRBUF_PRINTF(sb, "]");
2622 if (port)
2623 OSMO_STRBUF_PRINTF(sb, ":%u", port);
2624 break;
2625 default:
2626 OSMO_STRBUF_PRINTF(sb, "unsupported family %d", sockaddr->u.sa.sa_family);
2627 break;
2628 }
2629
2630 return sb.chars_needed;
2631}
2632
2633/*! string-format a given osmo_sockaddr address into a talloc allocated buffer.
2634 * Like osmo_sockaddr_to_str_buf2() but returns a talloc allocated string.
2635 * \param[in] ctx talloc context to allocate from, e.g. OTC_SELECT.
2636 * \param[in] sockaddr the osmo_sockaddr to print.
2637 * \return human readable string.
2638 */
2639char *osmo_sockaddr_to_str_c(void *ctx, const struct osmo_sockaddr *sockaddr)
2640{
2641 OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_sockaddr_to_str_buf2, sockaddr)
2642}
2643
2644/*! string-format a given osmo_sockaddr address into a user-supplied buffer.
2645 * Like osmo_sockaddr_to_str_buf2() but returns buf, or NULL if too short.
Alexander Couzens80788fa2020-10-12 01:11:20 +02002646 * \param[in] buf user-supplied output buffer
2647 * \param[in] buf_len size of the user-supplied output buffer in bytes
2648 * \param[in] sockaddr the osmo_sockaddr to print
2649 * \return pointer to the string on success; NULL on error
2650 */
2651char *osmo_sockaddr_to_str_buf(char *buf, size_t buf_len,
2652 const struct osmo_sockaddr *sockaddr)
2653{
Neels Hofmeyr09d65742021-12-01 10:25:02 +01002654 int chars_needed = osmo_sockaddr_to_str_buf2(buf, buf_len, sockaddr);
2655 if (chars_needed >= buf_len)
Alexander Couzens80788fa2020-10-12 01:11:20 +02002656 return NULL;
Alexander Couzens80788fa2020-10-12 01:11:20 +02002657 return buf;
2658}
2659
Harald Weltece53e032021-04-27 21:44:34 +02002660/*! Set the DSCP (differentiated services code point) of a socket.
2661 * \param[in] dscp DSCP value in range 0..63
2662 * \returns 0 on success; negative on error. */
2663int osmo_sock_set_dscp(int fd, uint8_t dscp)
2664{
Harald Welte903e6702021-04-28 13:27:12 +02002665 struct sockaddr_storage local_addr;
2666 socklen_t local_addr_len = sizeof(local_addr);
Harald Weltece53e032021-04-27 21:44:34 +02002667 uint8_t tos;
2668 socklen_t tos_len = sizeof(tos);
Harald Welte903e6702021-04-28 13:27:12 +02002669 int tclass;
2670 socklen_t tclass_len = sizeof(tclass);
Harald Weltece53e032021-04-27 21:44:34 +02002671 int rc;
2672
2673 /* DSCP is a 6-bit value stored in the upper 6 bits of the 8-bit TOS */
2674 if (dscp > 63)
2675 return -EINVAL;
2676
Harald Welte903e6702021-04-28 13:27:12 +02002677 rc = getsockname(fd, (struct sockaddr *)&local_addr, &local_addr_len);
Harald Weltece53e032021-04-27 21:44:34 +02002678 if (rc < 0)
2679 return rc;
2680
Harald Welte903e6702021-04-28 13:27:12 +02002681 switch (local_addr.ss_family) {
2682 case AF_INET:
2683 /* read the original value */
2684 rc = getsockopt(fd, IPPROTO_IP, IP_TOS, &tos, &tos_len);
2685 if (rc < 0)
2686 return rc;
2687 /* mask-in the DSCP into the upper 6 bits */
2688 tos &= 0x03;
2689 tos |= dscp << 2;
2690 /* and write it back to the kernel */
2691 rc = setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
2692 break;
2693 case AF_INET6:
2694 /* read the original value */
2695 rc = getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tclass, &tclass_len);
2696 if (rc < 0)
2697 return rc;
2698 /* mask-in the DSCP into the upper 6 bits */
2699 tclass &= 0x03;
2700 tclass |= dscp << 2;
2701 /* and write it back to the kernel */
2702 rc = setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tclass, sizeof(tclass));
2703 break;
2704 case AF_UNSPEC:
2705 default:
2706 LOGP(DLGLOBAL, LOGL_ERROR, "No DSCP support for socket family %u\n",
2707 local_addr.ss_family);
2708 rc = -1;
2709 break;
2710 }
Harald Weltece53e032021-04-27 21:44:34 +02002711
Harald Welte903e6702021-04-28 13:27:12 +02002712 return rc;
Harald Weltece53e032021-04-27 21:44:34 +02002713}
2714
Harald Welteecc0bd82021-04-27 22:24:08 +02002715/*! Set the priority value of a socket.
2716 * \param[in] prio priority value. Values outside 0..6 require CAP_NET_ADMIN.
2717 * \returns 0 on success; negative on error. */
2718int osmo_sock_set_priority(int fd, int prio)
2719{
2720 /* and write it back to the kernel */
2721 return setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio));
2722}
Alexander Couzens80788fa2020-10-12 01:11:20 +02002723
Pau Espin Pedrol19f27bb2023-12-08 14:58:51 +01002724#ifdef HAVE_LIBSCTP
2725/*! Fill in array of struct sctp_paddrinfo with each of the remote addresses of an SCTP socket
2726 * \param[in] fd file descriptor of SCTP socket
2727 * \param[out] pinfo Pointer to memory holding an array of struct sctp_paddrinfo (pinfo_cnt length).
2728 * \param[in,out] pinfo_cnt length of pinfo array (in elements). On return it contains the number of addresses found.
2729 * \returns 0 on success; negative otherwise
2730 *
2731 * Upon return, pinfo_cnt can be set to a higher value than the one set by the
2732 * caller. This can be used by the caller to find out the required array length
2733 * and then obtaining by calling the function twice. Only up to pinfo_cnt addresses
2734 * are filled in, as per the value provided by the caller.
2735 *
2736 * Usage example retrieving struct sctp_paddrinfo for all (up to OSMO_SOCK_MAX_ADDRS, 32) remote IP addresses:
2737 * struct sctp_paddrinfo pinfo[OSMO_SOCK_MAX_ADDRS];
2738 * size_t pinfo_cnt = ARRAY_SIZE(pinfo);
2739 * rc = osmo_sock_sctp_get_peer_addr_info(fd, &pinfo[0], &num_hostbuf, pinfo_cnt);
2740 * if (rc < 0)
2741 * goto error;
2742 * if (pinfo_cnt > ARRAY_SIZE(hostbuf))
2743 * goto not_enough_buffers;
2744 */
2745int osmo_sock_sctp_get_peer_addr_info(int fd, struct sctp_paddrinfo *pinfo, size_t *pinfo_cnt)
2746{
2747 struct sockaddr *addrs = NULL;
2748 unsigned int n_addrs, i;
2749 void *addr_buf;
2750 int rc;
2751 socklen_t optlen;
2752
2753 rc = sctp_getpaddrs(fd, 0, &addrs);
2754
2755 if (rc < 0)
2756 return rc;
2757 if (rc == 0)
2758 return -ENOTCONN;
2759
2760 n_addrs = rc;
2761 addr_buf = (void *)addrs;
2762 for (i = 0; i < n_addrs; i++) {
2763 struct sockaddr *sa_addr = (struct sockaddr *)addr_buf;
2764 size_t addrlen;
2765
2766 switch (sa_addr->sa_family) {
2767 case AF_INET:
2768 addrlen = sizeof(struct sockaddr_in);
2769 break;
2770 case AF_INET6:
2771 addrlen = sizeof(struct sockaddr_in6);
2772 break;
2773 default:
2774 rc = -EINVAL;
2775 goto free_addrs_ret;
2776 }
2777
2778 if (i >= *pinfo_cnt) {
2779 addr_buf += addrlen;
2780 continue;
2781 }
2782
2783 memset(&pinfo[i], 0, sizeof(pinfo[0]));
2784 memcpy(&pinfo[i].spinfo_address, sa_addr, addrlen);
2785 optlen = sizeof(pinfo[0]);
2786 rc = getsockopt(fd, SOL_SCTP, SCTP_GET_PEER_ADDR_INFO, &pinfo[i], &optlen);
2787 if (rc < 0)
2788 goto free_addrs_ret;
2789
2790 addr_buf += addrlen;
2791 }
2792
2793 *pinfo_cnt = n_addrs;
2794 rc = 0;
2795free_addrs_ret:
2796 sctp_freepaddrs(addrs);
2797 return rc;
2798}
2799#endif
2800
Harald Weltee4764422011-05-22 12:25:57 +02002801#endif /* HAVE_SYS_SOCKET_H */
Harald Welteba6988b2011-08-17 12:46:48 +02002802
Sylvain Munautdca7d2c2012-04-18 21:53:23 +02002803/*! @} */