blob: c9b7f27af2783bd2f5e2a0b76008d5c961fa80fe [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/bin/guest/manager/host_vsock_endpoint.h"
#include <unordered_map>
#include <lib/async-loop/cpp/loop.h>
#include <lib/fsl/handles/object_info.h>
#include <lib/gtest/test_loop_fixture.h>
#include "garnet/bin/guest/manager/guest_vsock_endpoint.h"
static constexpr uint32_t kGuestCid = 3;
static constexpr uint32_t kOtherGuestCid = 4;
template <typename T>
class TestVsockAcceptorBase : public T {
public:
struct ConnectionRequest {
uint32_t src_cid;
uint32_t src_port;
uint32_t port;
zx::handle handle;
typename T::AcceptCallback callback;
};
fidl::InterfaceHandle<T> NewBinding() { return binding_.NewBinding(); }
std::vector<ConnectionRequest> TakeRequests() {
return std::move(connection_requests_);
}
protected:
fidl::Binding<T> binding_{this};
std::vector<ConnectionRequest> connection_requests_;
};
class TestVsockAcceptor
: public TestVsockAcceptorBase<fuchsia::guest::GuestVsockAcceptor> {
private:
// |fuchsia::guest::GuestVsockAcceptor|
void Accept(uint32_t src_cid, uint32_t src_port, uint32_t port,
zx::handle handle, AcceptCallback callback) override {
connection_requests_.emplace_back(ConnectionRequest{
src_cid, src_port, port, std::move(handle), std::move(callback)});
}
};
class TestHostVsockAcceptor
: public TestVsockAcceptorBase<fuchsia::guest::HostVsockAcceptor> {
private:
// |fuchsia::guest::HostVsockAcceptor|
void Accept(uint32_t src_cid, uint32_t src_port, uint32_t port,
AcceptCallback callback) override {
connection_requests_.emplace_back(ConnectionRequest{
src_cid, src_port, port, zx::handle(), std::move(callback)});
}
};
struct TestConnectorConnection {
zx_status_t status = ZX_ERR_BAD_STATE;
zx::handle handle;
fuchsia::guest::HostVsockConnector::ConnectCallback callback() {
return [this](zx_status_t status, zx::handle handle) {
this->status = status;
this->handle = std::move(handle);
};
}
};
struct TestEndpointConnection {
zx_status_t status = ZX_ERR_BAD_STATE;
fuchsia::guest::HostVsockEndpoint::ConnectCallback callback() {
return [this](zx_status_t status) { this->status = status; };
}
};
static void NoOpCallback(zx_status_t status) {}
class HostVsockEndpointTest : public ::gtest::TestLoopFixture {
protected:
HostVsockEndpoint host_endpoint_{
fit::bind_member(this, &HostVsockEndpointTest::GetAcceptor)};
TestVsockAcceptor guest_acceptor_;
private:
fuchsia::guest::GuestVsockAcceptor* GetAcceptor(uint32_t cid) {
return cid == kGuestCid ? &guest_acceptor_ : nullptr;
}
};
TEST_F(HostVsockEndpointTest, ConnectGuestToGuest) {
TestConnectorConnection connection;
host_endpoint_.Connect(kOtherGuestCid, 1022, kGuestCid, 22,
connection.callback());
auto requests = guest_acceptor_.TakeRequests();
ASSERT_EQ(1u, requests.size());
EXPECT_EQ(kOtherGuestCid, requests[0].src_cid);
EXPECT_EQ(1022u, requests[0].src_port);
EXPECT_EQ(22u, requests[0].port);
EXPECT_TRUE(requests[0].handle.is_valid());
requests[0].callback(ZX_OK);
EXPECT_EQ(ZX_OK, connection.status);
EXPECT_TRUE(connection.handle.is_valid());
}
TEST_F(HostVsockEndpointTest, ConnectGuestToHost) {
TestHostVsockAcceptor host_acceptor;
host_endpoint_.Listen(22, host_acceptor.NewBinding(), NoOpCallback);
TestConnectorConnection connection;
host_endpoint_.Connect(kGuestCid, 1022, fuchsia::guest::HOST_CID, 22,
connection.callback());
RunLoopUntilIdle();
auto requests = host_acceptor.TakeRequests();
ASSERT_EQ(1u, requests.size());
EXPECT_EQ(kGuestCid, requests[0].src_cid);
EXPECT_EQ(1022u, requests[0].src_port);
EXPECT_EQ(22u, requests[0].port);
zx::socket h1, h2;
ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_STREAM, &h1, &h2));
requests[0].callback(ZX_OK, std::move(h1));
RunLoopUntilIdle();
EXPECT_EQ(ZX_OK, connection.status);
EXPECT_TRUE(connection.handle.is_valid());
}
TEST_F(HostVsockEndpointTest, ConnectHostToGuest) {
zx::socket h1, h2;
ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_STREAM, &h1, &h2));
TestEndpointConnection connection;
host_endpoint_.Connect(kGuestCid, 22, std::move(h1), connection.callback());
auto requests = guest_acceptor_.TakeRequests();
ASSERT_EQ(1u, requests.size());
EXPECT_EQ(fuchsia::guest::HOST_CID, requests[0].src_cid);
EXPECT_EQ(kFirstEphemeralPort, requests[0].src_port);
EXPECT_EQ(22u, requests[0].port);
EXPECT_TRUE(requests[0].handle.is_valid());
requests[0].callback(ZX_OK);
EXPECT_EQ(ZX_OK, connection.status);
}
TEST_F(HostVsockEndpointTest, ConnectHostToHost) {
zx::socket h1, h2;
ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_STREAM, &h1, &h2));
TestEndpointConnection connection;
host_endpoint_.Connect(fuchsia::guest::HOST_CID, 22, std::move(h1),
connection.callback());
ASSERT_EQ(ZX_ERR_CONNECTION_REFUSED, connection.status);
}
TEST_F(HostVsockEndpointTest, ConnectGuestToGuestNoAcceptor) {
TestConnectorConnection connection;
host_endpoint_.Connect(kOtherGuestCid, 1022, kGuestCid + 1000, 22,
connection.callback());
auto requests = guest_acceptor_.TakeRequests();
ASSERT_EQ(0u, requests.size());
EXPECT_EQ(ZX_ERR_CONNECTION_REFUSED, connection.status);
EXPECT_FALSE(connection.handle.is_valid());
}
TEST_F(HostVsockEndpointTest, ConnectGuestToHostNoAcceptor) {
TestConnectorConnection connection;
host_endpoint_.Connect(kGuestCid, 1022, fuchsia::guest::HOST_CID, 22,
connection.callback());
EXPECT_EQ(ZX_ERR_CONNECTION_REFUSED, connection.status);
EXPECT_FALSE(connection.handle.is_valid());
}
TEST_F(HostVsockEndpointTest, ConnectHostToGuestNoAcceptor) {
zx::socket h1, h2;
ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_STREAM, &h1, &h2));
TestEndpointConnection connection;
host_endpoint_.Connect(kGuestCid + 1000, 22, std::move(h1),
connection.callback());
EXPECT_EQ(ZX_ERR_CONNECTION_REFUSED, connection.status);
}
TEST_F(HostVsockEndpointTest, ListenMultipleTimesSamePort) {
TestEndpointConnection connection;
// Listen on port 22.
TestHostVsockAcceptor host_acceptor1;
host_endpoint_.Listen(22, host_acceptor1.NewBinding(), connection.callback());
EXPECT_EQ(ZX_OK, connection.status);
// Listen again on port 22 and verify that it fails.
TestHostVsockAcceptor host_acceptor2;
host_endpoint_.Listen(22, host_acceptor2.NewBinding(), connection.callback());
EXPECT_EQ(ZX_ERR_ALREADY_BOUND, connection.status);
}
TEST_F(HostVsockEndpointTest, ConnectHostToGuestMultipleTimes) {
constexpr size_t kNumTimes = 4;
zx::socket handles[2 * kNumTimes];
for (size_t i = 0; i < kNumTimes; i++) {
zx::socket* h = &handles[i * 2];
ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_STREAM, &h[0], &h[1]));
host_endpoint_.Connect(kGuestCid, 22, std::move(h[1]), NoOpCallback);
}
auto requests = guest_acceptor_.TakeRequests();
ASSERT_EQ(kNumTimes, requests.size());
uint32_t port = kFirstEphemeralPort;
for (const auto& request : requests) {
EXPECT_EQ(fuchsia::guest::HOST_CID, request.src_cid);
EXPECT_EQ(port++, request.src_port);
EXPECT_EQ(22u, request.port);
EXPECT_TRUE(request.handle.is_valid());
}
}
TEST_F(HostVsockEndpointTest, ConnectHostToGuestFreeEphemeralPort) {
// Connect twice.
constexpr size_t kNumTimes = 2;
zx::socket handles[2 * kNumTimes];
for (size_t i = 0; i < kNumTimes; i++) {
zx::socket* h = &handles[i * 2];
ASSERT_EQ(ZX_OK, zx::socket::create(ZX_SOCKET_STREAM, &h[0], &h[1]));
host_endpoint_.Connect(kGuestCid, 22, std::move(h[1]), NoOpCallback);
}
auto requests = guest_acceptor_.TakeRequests();
ASSERT_EQ(kNumTimes, requests.size());
uint32_t port = kFirstEphemeralPort;
for (const auto& request : requests) {
EXPECT_EQ(fuchsia::guest::HOST_CID, request.src_cid);
EXPECT_EQ(port++, request.src_port);
EXPECT_EQ(22u, request.port);
EXPECT_TRUE(request.handle.is_valid());
request.callback(ZX_OK);
}
// Disconnect the first connection and generate the shutdown event.
handles[0].reset();
host_endpoint_.OnShutdown(requests[0].src_port);
RunLoopUntilIdle();
// Connect again and verify that the port is reused.
ASSERT_EQ(ZX_OK,
zx::socket::create(ZX_SOCKET_STREAM, &handles[0], &handles[1]));
host_endpoint_.Connect(kGuestCid, 22, std::move(handles[1]), NoOpCallback);
requests = guest_acceptor_.TakeRequests();
ASSERT_EQ(1u, requests.size());
EXPECT_EQ(fuchsia::guest::HOST_CID, requests[0].src_cid);
EXPECT_EQ(kFirstEphemeralPort, requests[0].src_port);
EXPECT_EQ(22u, requests[0].port);
EXPECT_TRUE(requests[0].handle.is_valid());
}