Files
systemd/src/shared/socket-netlink.c
Zbigniew Jędrzejewski-Szmek 04499a70fb Drop the text argument from assert_not_reached()
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.
2021-08-03 10:05:10 +02:00

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;
}