mirror of
https://github.com/Dasharo/systemd.git
synced 2026-03-06 15:02:31 -08:00
In general we almost never hit those asserts in production code, so users see them very rarely, if ever. But either way, we just need something that users can pass to the developers. We have quite a few of those asserts, and some have fairly nice messages, but many are like "WTF?" or "???" or "unexpected something". The error that is printed includes the file location, and function name. In almost all functions there's at most one assert, so the function name alone is enough to identify the failure for a developer. So we don't get much extra from the message, and we might just as well drop them. Dropping them makes our code a tiny bit smaller, and most importantly, improves development experience by making it easy to insert such an assert in the code without thinking how to phrase the argument.
456 lines
14 KiB
C
456 lines
14 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <arpa/inet.h>
|
|
#include <errno.h>
|
|
#include <net/if.h>
|
|
#include <string.h>
|
|
|
|
#include "alloc-util.h"
|
|
#include "errno-util.h"
|
|
#include "extract-word.h"
|
|
#include "log.h"
|
|
#include "memory-util.h"
|
|
#include "netlink-util.h"
|
|
#include "parse-util.h"
|
|
#include "socket-netlink.h"
|
|
#include "socket-util.h"
|
|
#include "string-util.h"
|
|
|
|
int socket_address_parse(SocketAddress *a, const char *s) {
|
|
_cleanup_free_ char *n = NULL;
|
|
char *e;
|
|
int r;
|
|
|
|
assert(a);
|
|
assert(s);
|
|
|
|
if (IN_SET(*s, '/', '@')) {
|
|
/* AF_UNIX socket */
|
|
struct sockaddr_un un;
|
|
|
|
r = sockaddr_un_set_path(&un, s);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
*a = (SocketAddress) {
|
|
.sockaddr.un = un,
|
|
.size = r,
|
|
};
|
|
|
|
} else if (startswith(s, "vsock:")) {
|
|
/* AF_VSOCK socket in vsock:cid:port notation */
|
|
const char *cid_start = s + STRLEN("vsock:");
|
|
unsigned port, cid;
|
|
|
|
e = strchr(cid_start, ':');
|
|
if (!e)
|
|
return -EINVAL;
|
|
|
|
r = safe_atou(e+1, &port);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
n = strndup(cid_start, e - cid_start);
|
|
if (!n)
|
|
return -ENOMEM;
|
|
|
|
if (isempty(n))
|
|
cid = VMADDR_CID_ANY;
|
|
else {
|
|
r = safe_atou(n, &cid);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*a = (SocketAddress) {
|
|
.sockaddr.vm = {
|
|
.svm_cid = cid,
|
|
.svm_family = AF_VSOCK,
|
|
.svm_port = port,
|
|
},
|
|
.size = sizeof(struct sockaddr_vm),
|
|
};
|
|
|
|
} else {
|
|
uint16_t port;
|
|
|
|
r = parse_ip_port(s, &port);
|
|
if (r == -ERANGE)
|
|
return r; /* Valid port syntax, but the numerical value is wrong for a port. */
|
|
if (r >= 0) {
|
|
/* Just a port */
|
|
if (socket_ipv6_is_supported())
|
|
*a = (SocketAddress) {
|
|
.sockaddr.in6 = {
|
|
.sin6_family = AF_INET6,
|
|
.sin6_port = htobe16(port),
|
|
.sin6_addr = in6addr_any,
|
|
},
|
|
.size = sizeof(struct sockaddr_in6),
|
|
};
|
|
else
|
|
*a = (SocketAddress) {
|
|
.sockaddr.in = {
|
|
.sin_family = AF_INET,
|
|
.sin_port = htobe16(port),
|
|
.sin_addr.s_addr = INADDR_ANY,
|
|
},
|
|
.size = sizeof(struct sockaddr_in),
|
|
};
|
|
|
|
} else {
|
|
union in_addr_union address;
|
|
int family, ifindex;
|
|
|
|
r = in_addr_port_ifindex_name_from_string_auto(s, &family, &address, &port, &ifindex, NULL);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (port == 0) /* No port, no go. */
|
|
return -EINVAL;
|
|
|
|
if (family == AF_INET)
|
|
*a = (SocketAddress) {
|
|
.sockaddr.in = {
|
|
.sin_family = AF_INET,
|
|
.sin_addr = address.in,
|
|
.sin_port = htobe16(port),
|
|
},
|
|
.size = sizeof(struct sockaddr_in),
|
|
};
|
|
else if (family == AF_INET6)
|
|
*a = (SocketAddress) {
|
|
.sockaddr.in6 = {
|
|
.sin6_family = AF_INET6,
|
|
.sin6_addr = address.in6,
|
|
.sin6_port = htobe16(port),
|
|
.sin6_scope_id = ifindex,
|
|
},
|
|
.size = sizeof(struct sockaddr_in6),
|
|
};
|
|
else
|
|
assert_not_reached();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int socket_address_parse_and_warn(SocketAddress *a, const char *s) {
|
|
SocketAddress b;
|
|
int r;
|
|
|
|
/* Similar to socket_address_parse() but warns for IPv6 sockets when we don't support them. */
|
|
|
|
r = socket_address_parse(&b, s);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
if (!socket_ipv6_is_supported() && b.sockaddr.sa.sa_family == AF_INET6) {
|
|
log_warning("Binding to IPv6 address not available since kernel does not support IPv6.");
|
|
return -EAFNOSUPPORT;
|
|
}
|
|
|
|
*a = b;
|
|
return 0;
|
|
}
|
|
|
|
int socket_address_parse_netlink(SocketAddress *a, const char *s) {
|
|
_cleanup_free_ char *word = NULL;
|
|
unsigned group = 0;
|
|
int family, r;
|
|
|
|
assert(a);
|
|
assert(s);
|
|
|
|
r = extract_first_word(&s, &word, NULL, 0);
|
|
if (r < 0)
|
|
return r;
|
|
if (r == 0)
|
|
return -EINVAL;
|
|
|
|
family = netlink_family_from_string(word);
|
|
if (family < 0)
|
|
return -EINVAL;
|
|
|
|
if (!isempty(s)) {
|
|
r = safe_atou(s, &group);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
*a = (SocketAddress) {
|
|
.type = SOCK_RAW,
|
|
.sockaddr.nl.nl_family = AF_NETLINK,
|
|
.sockaddr.nl.nl_groups = group,
|
|
.protocol = family,
|
|
.size = sizeof(struct sockaddr_nl),
|
|
};
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool socket_address_is(const SocketAddress *a, const char *s, int type) {
|
|
struct SocketAddress b;
|
|
|
|
assert(a);
|
|
assert(s);
|
|
|
|
if (socket_address_parse(&b, s) < 0)
|
|
return false;
|
|
|
|
b.type = type;
|
|
|
|
return socket_address_equal(a, &b);
|
|
}
|
|
|
|
bool socket_address_is_netlink(const SocketAddress *a, const char *s) {
|
|
struct SocketAddress b;
|
|
|
|
assert(a);
|
|
assert(s);
|
|
|
|
if (socket_address_parse_netlink(&b, s) < 0)
|
|
return false;
|
|
|
|
return socket_address_equal(a, &b);
|
|
}
|
|
|
|
int make_socket_fd(int log_level, const char* address, int type, int flags) {
|
|
SocketAddress a;
|
|
int fd, r;
|
|
|
|
r = socket_address_parse(&a, address);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to parse socket address \"%s\": %m", address);
|
|
|
|
a.type = type;
|
|
|
|
fd = socket_address_listen(&a, type | flags, SOMAXCONN, SOCKET_ADDRESS_DEFAULT,
|
|
NULL, false, false, false, 0755, 0644, NULL);
|
|
if (fd < 0 || log_get_max_level() >= log_level) {
|
|
_cleanup_free_ char *p = NULL;
|
|
|
|
r = socket_address_print(&a, &p);
|
|
if (r < 0)
|
|
return log_error_errno(r, "socket_address_print(): %m");
|
|
|
|
if (fd < 0)
|
|
log_error_errno(fd, "Failed to listen on %s: %m", p);
|
|
else
|
|
log_full(log_level, "Listening on %s", p);
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
int in_addr_port_ifindex_name_from_string_auto(
|
|
const char *s,
|
|
int *ret_family,
|
|
union in_addr_union *ret_address,
|
|
uint16_t *ret_port,
|
|
int *ret_ifindex,
|
|
char **ret_server_name) {
|
|
|
|
_cleanup_free_ char *buf1 = NULL, *buf2 = NULL, *name = NULL;
|
|
int family, ifindex = 0, r;
|
|
union in_addr_union a;
|
|
uint16_t port = 0;
|
|
const char *m;
|
|
|
|
assert(s);
|
|
|
|
/* This accepts the following:
|
|
* 192.168.0.1:53#example.com
|
|
* [2001:4860:4860::8888]:53%eth0#example.com
|
|
*
|
|
* If ret_port is NULL, then the port cannot be specified.
|
|
* If ret_ifindex is NULL, then the interface index cannot be specified.
|
|
* If ret_server_name is NULL, then server_name cannot be specified.
|
|
*
|
|
* ret_family is always AF_INET or AF_INET6.
|
|
*/
|
|
|
|
m = strchr(s, '#');
|
|
if (m) {
|
|
if (!ret_server_name)
|
|
return -EINVAL;
|
|
|
|
if (isempty(m + 1))
|
|
return -EINVAL;
|
|
|
|
name = strdup(m + 1);
|
|
if (!name)
|
|
return -ENOMEM;
|
|
|
|
s = buf1 = strndup(s, m - s);
|
|
if (!buf1)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
m = strchr(s, '%');
|
|
if (m) {
|
|
if (!ret_ifindex)
|
|
return -EINVAL;
|
|
|
|
if (isempty(m + 1))
|
|
return -EINVAL;
|
|
|
|
if (!ifname_valid_full(m + 1, IFNAME_VALID_ALTERNATIVE | IFNAME_VALID_NUMERIC))
|
|
return -EINVAL; /* We want to return -EINVAL for syntactically invalid names,
|
|
* and -ENODEV for valid but nonexistent interfaces. */
|
|
|
|
ifindex = rtnl_resolve_interface(NULL, m + 1);
|
|
if (ifindex < 0)
|
|
return ifindex;
|
|
|
|
s = buf2 = strndup(s, m - s);
|
|
if (!buf2)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
m = strrchr(s, ':');
|
|
if (m) {
|
|
if (*s == '[') {
|
|
_cleanup_free_ char *ip_str = NULL;
|
|
|
|
if (!ret_port)
|
|
return -EINVAL;
|
|
|
|
if (*(m - 1) != ']')
|
|
return -EINVAL;
|
|
|
|
family = AF_INET6;
|
|
|
|
r = parse_ip_port(m + 1, &port);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
ip_str = strndup(s + 1, m - s - 2);
|
|
if (!ip_str)
|
|
return -ENOMEM;
|
|
|
|
r = in_addr_from_string(family, ip_str, &a);
|
|
if (r < 0)
|
|
return r;
|
|
} else {
|
|
/* First try to parse the string as IPv6 address without port number */
|
|
r = in_addr_from_string(AF_INET6, s, &a);
|
|
if (r < 0) {
|
|
/* Then the input should be IPv4 address with port number */
|
|
_cleanup_free_ char *ip_str = NULL;
|
|
|
|
if (!ret_port)
|
|
return -EINVAL;
|
|
|
|
family = AF_INET;
|
|
|
|
ip_str = strndup(s, m - s);
|
|
if (!ip_str)
|
|
return -ENOMEM;
|
|
|
|
r = in_addr_from_string(family, ip_str, &a);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = parse_ip_port(m + 1, &port);
|
|
if (r < 0)
|
|
return r;
|
|
} else
|
|
family = AF_INET6;
|
|
}
|
|
} else {
|
|
family = AF_INET;
|
|
r = in_addr_from_string(family, s, &a);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
if (ret_family)
|
|
*ret_family = family;
|
|
if (ret_address)
|
|
*ret_address = a;
|
|
if (ret_port)
|
|
*ret_port = port;
|
|
if (ret_ifindex)
|
|
*ret_ifindex = ifindex;
|
|
if (ret_server_name)
|
|
*ret_server_name = TAKE_PTR(name);
|
|
|
|
return r;
|
|
}
|
|
|
|
struct in_addr_full *in_addr_full_free(struct in_addr_full *a) {
|
|
if (!a)
|
|
return NULL;
|
|
|
|
free(a->server_name);
|
|
free(a->cached_server_string);
|
|
return mfree(a);
|
|
}
|
|
|
|
int in_addr_full_new(
|
|
int family,
|
|
const union in_addr_union *a,
|
|
uint16_t port,
|
|
int ifindex,
|
|
const char *server_name,
|
|
struct in_addr_full **ret) {
|
|
|
|
_cleanup_free_ char *name = NULL;
|
|
struct in_addr_full *x;
|
|
|
|
assert(ret);
|
|
|
|
if (!isempty(server_name)) {
|
|
name = strdup(server_name);
|
|
if (!name)
|
|
return -ENOMEM;
|
|
}
|
|
|
|
x = new(struct in_addr_full, 1);
|
|
if (!x)
|
|
return -ENOMEM;
|
|
|
|
*x = (struct in_addr_full) {
|
|
.family = family,
|
|
.address = *a,
|
|
.port = port,
|
|
.ifindex = ifindex,
|
|
.server_name = TAKE_PTR(name),
|
|
};
|
|
|
|
*ret = x;
|
|
return 0;
|
|
}
|
|
|
|
int in_addr_full_new_from_string(const char *s, struct in_addr_full **ret) {
|
|
_cleanup_free_ char *server_name = NULL;
|
|
int family, ifindex, r;
|
|
union in_addr_union a;
|
|
uint16_t port;
|
|
|
|
assert(s);
|
|
|
|
r = in_addr_port_ifindex_name_from_string_auto(s, &family, &a, &port, &ifindex, &server_name);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return in_addr_full_new(family, &a, port, ifindex, server_name, ret);
|
|
}
|
|
|
|
const char *in_addr_full_to_string(struct in_addr_full *a) {
|
|
assert(a);
|
|
|
|
if (!a->cached_server_string)
|
|
(void) in_addr_port_ifindex_name_to_string(
|
|
a->family,
|
|
&a->address,
|
|
a->port,
|
|
a->ifindex,
|
|
a->server_name,
|
|
&a->cached_server_string);
|
|
|
|
return a->cached_server_string;
|
|
}
|