blob: 63262a0d0102a81d5a7f1901fe2fe442d78d794a [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "libtrusty"
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <unistd.h>
#include <linux/vm_sockets.h> /* must be after sys/socket.h */
#include <log/log.h>
#include <trusty/ipc.h>
static const char* strip_prefix(const char* str, const char* prefix) {
size_t prefix_len = strlen(prefix);
if (strncmp(str, prefix, prefix_len) == 0) {
return str + prefix_len;
} else {
return NULL;
}
}
static bool use_vsock_connection = false;
static int tipc_vsock_connect(const char* type_cid_port_str, const char* srv_name) {
int ret;
const char* cid_port_str;
char* port_str;
char* end_str;
int socket_type;
if ((cid_port_str = strip_prefix(type_cid_port_str, "STREAM:"))) {
socket_type = SOCK_STREAM;
} else if ((cid_port_str = strip_prefix(type_cid_port_str, "SEQPACKET:"))) {
socket_type = SOCK_SEQPACKET;
} else {
/*
* Default to SOCK_STREAM if neither type is specified.
*
* TODO: use SOCK_SEQPACKET by default instead of SOCK_STREAM when SOCK_SEQPACKET is fully
* supported since it matches tipc better. At the moment SOCK_SEQPACKET is not supported by
* crosvm. It is also significantly slower since the Linux kernel implementation (as of
* v6.7-rc1) sends credit update packets every time it receives a data packet while the
* SOCK_STREAM version skips these unless the remaining buffer space is "low".
*/
socket_type = SOCK_STREAM;
cid_port_str = type_cid_port_str;
}
long cid = strtol(cid_port_str, &port_str, 0);
if (port_str[0] != ':') {
ALOGE("%s: invalid VSOCK str, \"%s\", need cid:port missing : after cid\n", __func__,
cid_port_str);
return -EINVAL;
}
long port = strtol(port_str + 1, &end_str, 0);
if (end_str[0] != '\0') {
ALOGE("%s: invalid VSOCK str, \"%s\", need cid:port got %ld:%ld\n", __func__, cid_port_str,
cid, port);
return -EINVAL;
}
int fd = socket(AF_VSOCK, socket_type, 0);
if (fd < 0) {
ret = -errno;
ALOGE("%s: can't get vsock %ld:%ld socket for tipc service \"%s\" (err=%d)\n", __func__,
cid, port, srv_name, errno);
return ret < 0 ? ret : -1;
}
struct timeval connect_timeout = {.tv_sec = 60, .tv_usec = 0};
ret = setsockopt(fd, AF_VSOCK, SO_VM_SOCKETS_CONNECT_TIMEOUT, &connect_timeout,
sizeof(connect_timeout));
if (ret) {
ALOGE("%s: vsock %ld:%ld: Failed to set connect timeout (err=%d)\n", __func__, cid, port,
errno);
/* failed to set longer timeout, but try to connect anyway */
}
struct sockaddr_vm sa = {
.svm_family = AF_VSOCK,
.svm_port = port,
.svm_cid = cid,
};
int retry = 10;
do {
ret = TEMP_FAILURE_RETRY(connect(fd, (struct sockaddr*)&sa, sizeof(sa)));
if (ret && (errno == ENODEV || errno == ESOCKTNOSUPPORT) && --retry) {
/*
* The kernel returns ESOCKTNOSUPPORT instead of ENODEV if the socket type is
* SOCK_SEQPACKET and the guest CID we are trying to connect to is not ready yet.
*/
ALOGE("%s: Can't connect to vsock %ld:%ld for tipc service \"%s\" (err=%d) %d retries "
"remaining\n",
__func__, cid, port, srv_name, errno, retry);
sleep(1);
} else {
retry = 0;
}
} while (retry);
if (ret) {
ret = -errno;
ALOGE("%s: Can't connect to vsock %ld:%ld for tipc service \"%s\" (err=%d)\n", __func__,
cid, port, srv_name, errno);
close(fd);
return ret < 0 ? ret : -1;
}
/*
* TODO: Current vsock tipc bridge in trusty expects a port name in the
* first packet. We need to replace this with a protocol that also does DICE
* based authentication.
*/
ret = TEMP_FAILURE_RETRY(write(fd, srv_name, strlen(srv_name)));
if (ret != strlen(srv_name)) {
ret = -errno;
ALOGE("%s: vsock %ld:%ld: failed to send tipc service name \"%s\" (err=%d)\n", __func__,
cid, port, srv_name, errno);
close(fd);
return ret < 0 ? ret : -1;
}
/*
* Work around lack of seq packet support. Read a status byte to prevent
* the caller from sending more data until srv_name has been read.
*/
int8_t status;
ret = TEMP_FAILURE_RETRY(read(fd, &status, sizeof(status)));
if (ret != sizeof(status)) {
ALOGE("%s: vsock %ld:%ld: failed to read status byte for connect to tipc service name "
"\"%s\" (err=%d)\n",
__func__, cid, port, srv_name, errno);
close(fd);
return ret < 0 ? ret : -1;
}
use_vsock_connection = true;
return fd;
}
static size_t tipc_vsock_send(int fd, const struct iovec* iov, int iovcnt, struct trusty_shm* shms,
int shmcnt) {
int ret;
(void)shms;
if (shmcnt != 0) {
ALOGE("%s: vsock does not yet support passing fds\n", __func__);
return -ENOTSUP;
}
ret = TEMP_FAILURE_RETRY(writev(fd, iov, iovcnt));
if (ret < 0) {
ret = -errno;
ALOGE("%s: failed to send message (err=%d)\n", __func__, errno);
return ret < 0 ? ret : -1;
}
return ret;
}
int tipc_connect(const char* dev_name, const char* srv_name) {
int fd;
int rc;
const char* type_cid_port_str = strip_prefix(dev_name, "VSOCK:");
if (type_cid_port_str) {
return tipc_vsock_connect(type_cid_port_str, srv_name);
}
fd = TEMP_FAILURE_RETRY(open(dev_name, O_RDWR));
if (fd < 0) {
rc = -errno;
ALOGE("%s: cannot open tipc device \"%s\": %s\n", __func__, dev_name, strerror(errno));
return rc < 0 ? rc : -1;
}
rc = TEMP_FAILURE_RETRY(ioctl(fd, TIPC_IOC_CONNECT, srv_name));
if (rc < 0) {
rc = -errno;
ALOGE("%s: can't connect to tipc service \"%s\" (err=%d)\n", __func__, srv_name, errno);
close(fd);
return rc < 0 ? rc : -1;
}
ALOGV("%s: connected to \"%s\" fd %d\n", __func__, srv_name, fd);
return fd;
}
ssize_t tipc_send(int fd, const struct iovec* iov, int iovcnt, struct trusty_shm* shms,
int shmcnt) {
if (use_vsock_connection) {
return tipc_vsock_send(fd, iov, iovcnt, shms, shmcnt);
}
struct tipc_send_msg_req req;
req.iov = (__u64)iov;
req.iov_cnt = (__u64)iovcnt;
req.shm = (__u64)shms;
req.shm_cnt = (__u64)shmcnt;
int rc = TEMP_FAILURE_RETRY(ioctl(fd, TIPC_IOC_SEND_MSG, &req));
if (rc < 0) {
ALOGE("%s: failed to send message (err=%d)\n", __func__, rc);
}
return rc;
}
void tipc_close(int fd) {
close(fd);
}