diff options
Diffstat (limited to 'src/libnm-systemd-shared/src/basic/socket-util.c')
-rw-r--r-- | src/libnm-systemd-shared/src/basic/socket-util.c | 255 |
1 files changed, 231 insertions, 24 deletions
diff --git a/src/libnm-systemd-shared/src/basic/socket-util.c b/src/libnm-systemd-shared/src/basic/socket-util.c index 9b411e07a2..df3e2c17c2 100644 --- a/src/libnm-systemd-shared/src/basic/socket-util.c +++ b/src/libnm-systemd-shared/src/basic/socket-util.c @@ -46,6 +46,11 @@ # define IDN_FLAGS 0 #endif +/* From the kernel's include/net/scm.h */ +#ifndef SCM_MAX_FD +# define SCM_MAX_FD 253 +#endif + static const char* const socket_address_type_table[] = { [SOCK_STREAM] = "Stream", [SOCK_DGRAM] = "Datagram", @@ -547,7 +552,7 @@ int sockaddr_pretty( } else { if (path[path_len - 1] == '\0') /* We expect a terminating NUL and don't print it */ - path_len --; + path_len--; p = cescape_length(path, path_len); } @@ -628,28 +633,33 @@ int getsockname_pretty(int fd, char **ret) { return sockaddr_pretty(&sa.sa, salen, false, true, ret); } -int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) { +int socknameinfo_pretty(const struct sockaddr *sa, socklen_t salen, char **ret) { + char host[NI_MAXHOST]; int r; - char host[NI_MAXHOST], *ret; - assert(_ret); + assert(sa); + assert(salen > sizeof(sa_family_t)); - r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, IDN_FLAGS); + r = getnameinfo(sa, salen, host, sizeof(host), /* service= */ NULL, /* service_len= */ 0, IDN_FLAGS); if (r != 0) { - int saved_errno = errno; + if (r == EAI_MEMORY) + return log_oom_debug(); + if (r == EAI_SYSTEM) + log_debug_errno(errno, "getnameinfo() failed, ignoring: %m"); + else + log_debug("getnameinfo() failed, ignoring: %s", gai_strerror(r)); - r = sockaddr_pretty(&sa->sa, salen, true, true, &ret); - if (r < 0) - return r; + return sockaddr_pretty(sa, salen, /* translate_ipv6= */ true, /* include_port= */ true, ret); + } - log_debug_errno(saved_errno, "getnameinfo(%s) failed: %m", ret); - } else { - ret = strdup(host); - if (!ret) + if (ret) { + char *copy = strdup(host); + if (!copy) return -ENOMEM; + + *ret = copy; } - *_ret = ret; return 0; } @@ -874,13 +884,11 @@ bool address_label_valid(const char *p) { int getpeercred(int fd, struct ucred *ucred) { socklen_t n = sizeof(struct ucred); struct ucred u; - int r; assert(fd >= 0); assert(ucred); - r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n); - if (r < 0) + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n) < 0) return -errno; if (n != sizeof(struct ucred)) @@ -909,8 +917,10 @@ int getpeersec(int fd, char **ret) { if (!s) return -ENOMEM; - if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n) >= 0) + if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n) >= 0) { + s[n] = 0; break; + } if (errno != ERANGE) return -errno; @@ -927,12 +937,16 @@ int getpeersec(int fd, char **ret) { } int getpeergroups(int fd, gid_t **ret) { - socklen_t n = sizeof(gid_t) * 64; + socklen_t n = sizeof(gid_t) * 64U; _cleanup_free_ gid_t *d = NULL; assert(fd >= 0); assert(ret); + long ngroups_max = sysconf(_SC_NGROUPS_MAX); + if (ngroups_max > 0) + n = MAX(n, sizeof(gid_t) * (socklen_t) ngroups_max); + for (;;) { d = malloc(n); if (!d) @@ -950,7 +964,7 @@ int getpeergroups(int fd, gid_t **ret) { assert_se(n % sizeof(gid_t) == 0); n /= sizeof(gid_t); - if ((socklen_t) (int) n != n) + if (n > INT_MAX) return -E2BIG; *ret = TAKE_PTR(d); @@ -958,6 +972,68 @@ int getpeergroups(int fd, gid_t **ret) { return (int) n; } +int getpeerpidfd(int fd) { + socklen_t n = sizeof(int); + int pidfd = -EBADF; + + assert(fd >= 0); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERPIDFD, &pidfd, &n) < 0) + return -errno; + + if (n != sizeof(int)) + return -EIO; + + return pidfd; +} + +ssize_t send_many_fds_iov_sa( + int transport_fd, + int *fds_array, size_t n_fds_array, + const struct iovec *iov, size_t iovlen, + const struct sockaddr *sa, socklen_t len, + int flags) { + + _cleanup_free_ struct cmsghdr *cmsg = NULL; + struct msghdr mh = { + .msg_name = (struct sockaddr*) sa, + .msg_namelen = len, + .msg_iov = (struct iovec *)iov, + .msg_iovlen = iovlen, + }; + ssize_t k; + + assert(transport_fd >= 0); + assert(fds_array || n_fds_array == 0); + + /* The kernel will reject sending more than SCM_MAX_FD FDs at once */ + if (n_fds_array > SCM_MAX_FD) + return -E2BIG; + + /* We need either an FD array or data to send. If there's nothing, return an error. */ + if (n_fds_array == 0 && !iov) + return -EINVAL; + + if (n_fds_array > 0) { + mh.msg_controllen = CMSG_SPACE(sizeof(int) * n_fds_array); + mh.msg_control = cmsg = malloc(mh.msg_controllen); + if (!cmsg) + return -ENOMEM; + + *cmsg = (struct cmsghdr) { + .cmsg_len = CMSG_LEN(sizeof(int) * n_fds_array), + .cmsg_level = SOL_SOCKET, + .cmsg_type = SCM_RIGHTS, + }; + memcpy(CMSG_DATA(cmsg), fds_array, sizeof(int) * n_fds_array); + } + k = sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags); + if (k < 0) + return (ssize_t) -errno; + + return k; +} + ssize_t send_one_fd_iov_sa( int transport_fd, int fd, @@ -1013,6 +1089,78 @@ int send_one_fd_sa( return (int) send_one_fd_iov_sa(transport_fd, fd, NULL, 0, sa, len, flags); } +ssize_t receive_many_fds_iov( + int transport_fd, + struct iovec *iov, size_t iovlen, + int **ret_fds_array, size_t *ret_n_fds_array, + int flags) { + + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int) * SCM_MAX_FD)) control; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_iov = iov, + .msg_iovlen = iovlen, + }; + _cleanup_free_ int *fds_array = NULL; + size_t n_fds_array = 0; + struct cmsghdr *cmsg; + ssize_t k; + + assert(transport_fd >= 0); + assert(ret_fds_array); + assert(ret_n_fds_array); + + /* + * Receive many FDs via @transport_fd. We don't care for the transport-type. We retrieve all the FDs + * at once. This is best used in combination with send_many_fds(). + */ + + k = recvmsg_safe(transport_fd, &mh, MSG_CMSG_CLOEXEC | flags); + if (k < 0) + return k; + + CMSG_FOREACH(cmsg, &mh) + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + size_t n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + fds_array = GREEDY_REALLOC(fds_array, n_fds_array + n); + if (!fds_array) { + cmsg_close_all(&mh); + return -ENOMEM; + } + + memcpy(fds_array + n_fds_array, CMSG_TYPED_DATA(cmsg, int), sizeof(int) * n); + n_fds_array += n; + } + + if (n_fds_array == 0) { + cmsg_close_all(&mh); + + /* If didn't receive an FD or any data, return an error. */ + if (k == 0) + return -EIO; + } + + *ret_fds_array = TAKE_PTR(fds_array); + *ret_n_fds_array = n_fds_array; + + return k; +} + +int receive_many_fds(int transport_fd, int **ret_fds_array, size_t *ret_n_fds_array, int flags) { + ssize_t k; + + k = receive_many_fds_iov(transport_fd, NULL, 0, ret_fds_array, ret_n_fds_array, flags); + if (k == 0) + return 0; + + /* k must be negative, since receive_many_fds_iov() only returns a positive value if data was received + * through the iov. */ + assert(k < 0); + return (int) k; +} + ssize_t receive_one_fd_iov( int transport_fd, struct iovec *iov, size_t iovlen, @@ -1189,7 +1337,7 @@ void* cmsg_find_and_copy_data(struct msghdr *mh, int level, int type, void *buf, assert(buf_len > 0); /* This is similar to cmsg_find_data(), but copy the found data to buf. This should be typically used - * when reading possibly unaligned data such as timestamp, as time_t is 64bit and size_t is 32bit on + * when reading possibly unaligned data such as timestamp, as time_t is 64-bit and size_t is 32-bit on * RISCV32. See issue #27241. */ cmsg = cmsg_find(mh, level, type, CMSG_LEN(buf_len)); @@ -1532,6 +1680,50 @@ int socket_address_parse_unix(SocketAddress *ret_address, const char *s) { } #if 0 /* NM_IGNORED */ +int vsock_parse_port(const char *s, unsigned *ret) { + int r; + + assert(ret); + + if (!s) + return -EINVAL; + + unsigned u; + r = safe_atou(s, &u); + if (r < 0) + return r; + + /* Port 0 is apparently valid and not special in AF_VSOCK (unlike on IP). But VMADDR_PORT_ANY + * (UINT32_MAX) is. Hence refuse that. */ + + if (u == VMADDR_PORT_ANY) + return -EINVAL; + + *ret = u; + return 0; +} + +int vsock_parse_cid(const char *s, unsigned *ret) { + assert(ret); + + if (!s) + return -EINVAL; + + /* Parsed an AF_VSOCK "CID". This is a 32bit entity, and the usual type is "unsigned". We recognize + * the three special CIDs as strings, and otherwise parse the numeric CIDs. */ + + if (streq(s, "hypervisor")) + *ret = VMADDR_CID_HYPERVISOR; + else if (streq(s, "local")) + *ret = VMADDR_CID_LOCAL; + else if (streq(s, "host")) + *ret = VMADDR_CID_HOST; + else + return safe_atou(s, ret); + + return 0; +} + int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { /* AF_VSOCK socket in vsock:cid:port notation */ _cleanup_free_ char *n = NULL; @@ -1557,7 +1749,7 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { if (!e) return -EINVAL; - r = safe_atou(e+1, &port); + r = vsock_parse_port(e+1, &port); if (r < 0) return r; @@ -1568,15 +1760,15 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { if (isempty(n)) cid = VMADDR_CID_ANY; else { - r = safe_atou(n, &cid); + r = vsock_parse_cid(n, &cid); if (r < 0) return r; } *ret_address = (SocketAddress) { .sockaddr.vm = { - .svm_cid = cid, .svm_family = AF_VSOCK, + .svm_cid = cid, .svm_port = port, }, .type = type, @@ -1585,4 +1777,19 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { return 0; } + +int vsock_get_local_cid(unsigned *ret) { + _cleanup_close_ int vsock_fd = -EBADF; + + assert(ret); + + vsock_fd = open("/dev/vsock", O_RDONLY|O_CLOEXEC); + if (vsock_fd < 0) + return log_debug_errno(errno, "Failed to open /dev/vsock: %m"); + + if (ioctl(vsock_fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, ret) < 0) + return log_debug_errno(errno, "Failed to query local AF_VSOCK CID: %m"); + + return 0; +} #endif /* NM_IGNORED */ |