socket-util.c revision 8333c77edf8fd1654cd96f3f6ee0f078dd64b58b
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2010 Lennart Poettering
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include "macro.h"
#include "util.h"
#include "mkdir.h"
#include "path-util.h"
#include "socket-util.h"
#include "missing.h"
#include "fileio.h"
int socket_address_parse(SocketAddress *a, const char *s) {
int r;
char *e, *n;
unsigned u;
assert(a);
assert(s);
zero(*a);
a->type = SOCK_STREAM;
if (*s == '[') {
/* IPv6 in [x:.....:z]:p notation */
if (!socket_ipv6_is_supported()) {
log_warning("Binding to IPv6 address not available since kernel does not support IPv6.");
return -EAFNOSUPPORT;
}
return -EINVAL;
return -ENOMEM;
errno = 0;
free(n);
}
free(n);
e++;
if (*e != ':')
return -EINVAL;
e++;
if ((r = safe_atou(e, &u)) < 0)
return r;
if (u <= 0 || u > 0xFFFF)
return -EINVAL;
a->size = sizeof(struct sockaddr_in6);
} else if (*s == '/') {
/* AF_UNIX socket */
size_t l;
l = strlen(s);
return -EINVAL;
} else if (*s == '@') {
/* Abstract AF_UNIX socket */
size_t l;
l = strlen(s+1);
return -EINVAL;
} else {
if ((e = strchr(s, ':'))) {
if ((r = safe_atou(e+1, &u)) < 0)
return r;
if (u <= 0 || u > 0xFFFF)
return -EINVAL;
if (!(n = strndup(s, e-s)))
return -ENOMEM;
/* IPv4 in w.x.y.z:p notation? */
free(n);
return -errno;
}
if (r > 0) {
/* Gotcha, it's a traditional IPv4 address */
free(n);
a->size = sizeof(struct sockaddr_in);
} else {
unsigned idx;
free(n);
return -EINVAL;
}
/* Uh, our last resort, an interface name */
idx = if_nametoindex(n);
free(n);
if (idx == 0)
return -EINVAL;
if (!socket_ipv6_is_supported()) {
log_warning("Binding to interface is not available since kernel does not support IPv6.");
return -EAFNOSUPPORT;
}
a->size = sizeof(struct sockaddr_in6);
}
} else {
/* Just a port */
r = safe_atou(s, &u);
if (r < 0)
return r;
if (u <= 0 || u > 0xFFFF)
return -EINVAL;
if (socket_ipv6_is_supported()) {
a->size = sizeof(struct sockaddr_in6);
} else {
a->size = sizeof(struct sockaddr_in);
}
}
}
return 0;
}
int socket_address_parse_netlink(SocketAddress *a, const char *s) {
int family;
unsigned group = 0;
assert(a);
assert(s);
zero(*a);
errno = 0;
if (family < 0)
return -EINVAL;
a->size = sizeof(struct sockaddr_nl);
return 0;
}
int socket_address_verify(const SocketAddress *a) {
assert(a);
switch (socket_address_family(a)) {
case AF_INET:
if (a->size != sizeof(struct sockaddr_in))
return -EINVAL;
return -EINVAL;
return -EINVAL;
return 0;
case AF_INET6:
if (a->size != sizeof(struct sockaddr_in6))
return -EINVAL;
return -EINVAL;
return -EINVAL;
return 0;
case AF_UNIX:
return -EINVAL;
char *e;
/* path */
return -EINVAL;
return -EINVAL;
}
}
return -EINVAL;
return 0;
case AF_NETLINK:
if (a->size != sizeof(struct sockaddr_nl))
return -EINVAL;
return -EINVAL;
return 0;
default:
return -EAFNOSUPPORT;
}
}
int socket_address_print(const SocketAddress *a, char **p) {
int r;
assert(a);
assert(p);
if ((r = socket_address_verify(a)) < 0)
return r;
switch (socket_address_family(a)) {
case AF_INET: {
char *ret;
return -ENOMEM;
return -errno;
}
*p = ret;
return 0;
}
case AF_INET6: {
char *ret;
return -ENOMEM;
ret[0] = '[';
return -errno;
}
*p = ret;
return 0;
}
case AF_UNIX: {
char *ret;
return -ENOMEM;
/* abstract */
/* FIXME: We assume we can print the
* socket path here and that it hasn't
* more than one NUL byte. That is
* actually an invalid assumption */
return -ENOMEM;
ret[0] = '@';
} else {
return -ENOMEM;
}
*p = ret;
return 0;
}
case AF_NETLINK: {
if (r < 0)
return r;
if (r < 0)
return -ENOMEM;
return 0;
}
default:
return -EINVAL;
}
}
bool socket_address_can_accept(const SocketAddress *a) {
assert(a);
return
a->type == SOCK_STREAM ||
a->type == SOCK_SEQPACKET;
}
assert(a);
assert(b);
/* Invalid addresses are unequal to all */
if (socket_address_verify(a) < 0 ||
socket_address_verify(b) < 0)
return false;
return false;
return false;
if (socket_address_family(a) != socket_address_family(b))
return false;
switch (socket_address_family(a)) {
case AF_INET:
return false;
return false;
break;
case AF_INET6:
if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0)
return false;
return false;
break;
case AF_UNIX:
return false;
return false;
} else {
return false;
}
break;
case AF_NETLINK:
return false;
return false;
break;
default:
/* Cannot compare, so we assume the addresses are different */
return false;
}
return true;
}
struct SocketAddress b;
assert(a);
assert(s);
if (socket_address_parse(&b, s) < 0)
return false;
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);
}
assert(a);
if (socket_address_family(a) != AF_UNIX)
return false;
return false;
}
bool socket_ipv6_is_supported(void) {
char *l = 0;
bool enabled;
return 0;
/* If we can't check "disable" parameter, assume enabled */
if (read_one_line_file("/sys/module/ipv6/parameters/disable", &l) < 0)
return 1;
/* If module was loaded with disable=1 no IPv6 available */
enabled = l[0] == '0';
free(l);
return enabled;
}
union sockaddr_union sa;
assert(a);
return false;
return false;
return false;
return false;
if (a->protocol != 0) {
return false;
return false;
}
case AF_INET:
case AF_INET6:
case AF_UNIX:
memcmp(sa.un.sun_path, a->sockaddr.un.sun_path, salen - offsetof(struct sockaddr_un, sun_path)) == 0;
}
return false;
}
int fd, r;
char _cleanup_free_ *p = NULL;
r = socket_address_parse(&a, address);
if (r < 0) {
return r;
}
if (fd < 0) {
log_error("socket(): %m");
return -errno;
}
r = socket_address_print(&a, &p);
if (r < 0) {
return r;
}
log_info("Listening on %s", p);
if (r < 0) {
return -errno;
}
if (r < 0) {
return -errno;
}
return fd;
}
static const char* const netlink_family_table[] = {
[NETLINK_ROUTE] = "route",
[NETLINK_FIREWALL] = "firewall",
[NETLINK_INET_DIAG] = "inet-diag",
[NETLINK_NFLOG] = "nflog",
[NETLINK_XFRM] = "xfrm",
[NETLINK_SELINUX] = "selinux",
[NETLINK_ISCSI] = "iscsi",
[NETLINK_AUDIT] = "audit",
[NETLINK_FIB_LOOKUP] = "fib-lookup",
[NETLINK_CONNECTOR] = "connector",
[NETLINK_NETFILTER] = "netfilter",
[NETLINK_IP6_FW] = "ip6-fw",
[NETLINK_DNRTMSG] = "dnrtmsg",
[NETLINK_KOBJECT_UEVENT] = "kobject-uevent",
[NETLINK_GENERIC] = "generic",
[NETLINK_SCSITRANSPORT] = "scsitransport",
[NETLINK_ECRYPTFS] = "ecryptfs"
};
static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = {
[SOCKET_ADDRESS_DEFAULT] = "default",
[SOCKET_ADDRESS_BOTH] = "both",
[SOCKET_ADDRESS_IPV6_ONLY] = "ipv6-only"
};