* Copyright (c) 2023 Huawei Device Co., Ltd.
* 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.
*/
#include "vpn_interface.h"
#include <cerrno>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include "net_manager_constants.h"
#include "netmgr_ext_log_wrapper.h"
#include "securec.h"
namespace OHOS {
namespace NetManagerStandard {
namespace {
constexpr int32_t CONNECT_TIMEOUT = 1;
constexpr int32_t INVALID_FD = -1;
int32_t FdReadyHandle(int32_t &sockfd, fd_set &rset, fd_set &wset, uint32_t flags)
{
int32_t result = NETMANAGER_EXT_ERR_INTERNAL;
socklen_t len = sizeof(result);
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &result, &len) < 0) {
NETMGR_EXT_LOG_E("getsockopt error: %{public}d", errno);
return NETMANAGER_EXT_ERR_INTERNAL;
}
} else {
NETMGR_EXT_LOG_E("select error: sockfd not set");
return NETMANAGER_EXT_ERR_INTERNAL;
}
if (result != NETMANAGER_EXT_SUCCESS) {
NETMGR_EXT_LOG_E("connect failed. error: %{public}d", result);
return NETMANAGER_EXT_ERR_INTERNAL;
} else {
fcntl(sockfd, F_SETFL, flags);
NETMGR_EXT_LOG_I("connect success.");
return NETMANAGER_EXT_SUCCESS;
}
}
}
int32_t VpnInterface::ConnectControl(int32_t sockfd, int32_t nsec)
{
uint32_t flags = static_cast<uint32_t>(fcntl(sockfd, F_GETFL, 0));
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
sockaddr_un serverPath = {AF_UNIX, "/dev/unix/socket/tunfd"};
#ifdef SUPPORT_SYSVPN
if (isSupportMultiVpn_) {
serverPath = {AF_UNIX, "/dev/unix/socket/multivpnfd"};
}
#endif
int32_t ret = connect(sockfd, reinterpret_cast<const sockaddr *>(&serverPath), sizeof(serverPath));
if ((ret < 0) && (errno != EINPROGRESS)) {
NETMGR_EXT_LOG_E("connect error: %{public}d", errno);
return NETMANAGER_EXT_ERR_INTERNAL;
} else if (ret == 0) {
fcntl(sockfd, F_SETFL, flags);
NETMGR_EXT_LOG_I("connect success.");
return NETMANAGER_EXT_SUCCESS;
}
fd_set rset;
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
fd_set wset = rset;
timeval tval;
tval.tv_sec = nsec;
tval.tv_usec = 0;
ret = select(sockfd + 1, &rset, &wset, NULL, nsec ? &tval : NULL);
if (ret < 0) {
NETMGR_EXT_LOG_E("select error: %{public}d", errno);
return NETMANAGER_EXT_ERR_INTERNAL;
} else if (ret == 0) {
NETMGR_EXT_LOG_E("connect timeout.");
return NETMANAGER_EXT_ERR_INTERNAL;
} else {
return FdReadyHandle(sockfd, rset, wset, flags);
}
}
int32_t VpnInterface::RecvMsgFromUnixServer(int32_t sockfd)
{
char buf[1] = {0};
iovec iov = {
.iov_base = buf,
.iov_len = sizeof(buf),
};
union {
cmsghdr align;
char cmsg[CMSG_SPACE(sizeof(int32_t))];
} cmsgu;
if (memset_s(cmsgu.cmsg, sizeof(cmsgu.cmsg), 0, sizeof(cmsgu.cmsg)) != EOK) {
NETMGR_EXT_LOG_E("memset_s cmsgu.cmsg failed!");
return NETMANAGER_EXT_ERR_INTERNAL;
}
msghdr message;
if (memset_s(&message, sizeof(message), 0, sizeof(message)) != EOK) {
NETMGR_EXT_LOG_E("memset_s message failed!");
return NETMANAGER_EXT_ERR_INTERNAL;
}
message.msg_iov = &iov;
message.msg_iovlen = 1;
message.msg_control = cmsgu.cmsg;
message.msg_controllen = sizeof(cmsgu.cmsg);
if (recvmsg(sockfd, &message, 0) < 0) {
NETMGR_EXT_LOG_E("recvmsg msg error: %{public}d", errno);
return NETMANAGER_EXT_ERR_INTERNAL;
}
cmsghdr *cmsgh = CMSG_FIRSTHDR(&message);
if (cmsgh == nullptr) {
NETMGR_EXT_LOG_E("cmsgh is nullptr");
return NETMANAGER_EXT_ERR_INTERNAL;
}
if (cmsgh->cmsg_level != SOL_SOCKET || cmsgh->cmsg_type != SCM_RIGHTS ||
cmsgh->cmsg_len != CMSG_LEN(sizeof(int32_t))) {
NETMGR_EXT_LOG_E("cmsg_level: [%{public}d], cmsg_type: [%{public}d], cmsg_len: [%{public}d]", cmsgh->cmsg_level,
cmsgh->cmsg_type, cmsgh->cmsg_len);
return NETMANAGER_EXT_ERR_INTERNAL;
}
if (memcpy_s(&tunFd_, sizeof(tunFd_), CMSG_DATA(cmsgh), sizeof(tunFd_)) != EOK) {
NETMGR_EXT_LOG_E("memcpy_s cmsgu failed!");
return NETMANAGER_EXT_ERR_INTERNAL;
}
return NETMANAGER_EXT_SUCCESS;
}
int32_t VpnInterface::GetVpnInterfaceFd()
{
CloseVpnInterfaceFd();
int32_t sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd < 0) {
NETMGR_EXT_LOG_E("create unix SOCK_STREAM socket error: %{public}d", errno);
return INVALID_FD;
}
if (ConnectControl(sockfd, CONNECT_TIMEOUT) != NETMANAGER_EXT_SUCCESS) {
close(sockfd);
NETMGR_EXT_LOG_E("connect error: %{public}d", errno);
return INVALID_FD;
}
if (RecvMsgFromUnixServer(sockfd) != NETMANAGER_EXT_SUCCESS) {
close(sockfd);
return INVALID_FD;
}
close(sockfd);
NETMGR_EXT_LOG_I("receive tun device fd: [%{public}d]", tunFd_.load());
return tunFd_;
}
void VpnInterface::CloseVpnInterfaceFd()
{
if (tunFd_ > 0) {
NETMGR_EXT_LOG_I("close tunfd[%{public}d] of vpn interface", tunFd_.load());
close(tunFd_);
tunFd_ = 0;
}
}
#ifdef SUPPORT_SYSVPN
void VpnInterface::SetSupportMultiVpn(bool isSupportMultiVpn)
{
isSupportMultiVpn_ = isSupportMultiVpn;
}
#endif
}
}