blob: fa636e7d7aa1442030d9314e7daf24b07afbf2f3 [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 <ddk/protocol/ethernet.h>
#include <fbl/auto_call.h>
#include <fuchsia/net/filter/cpp/fidl.h>
#include <fuchsia/netstack/cpp/fidl.h>
#include <garnet/lib/inet/ip_address.h>
#include <lib/fdio/util.h>
#include <lib/fdio/watcher.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/zx/socket.h>
#include <zircon/device/ethertap.h>
#include <zircon/ethernet/cpp/fidl.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <string>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include "gtest/gtest.h"
#include "lib/component/cpp/termination_reason.h"
#include "lib/component/cpp/testing/test_util.h"
#include "lib/component/cpp/testing/test_with_environment.h"
namespace {
class NetstackFilterTest : public component::testing::TestWithEnvironment {};
const char kEthernetDir[] = "/dev/class/ethernet";
const char kTapctl[] = "/dev/misc/tapctl";
const uint8_t kTapMac[] = {0x12, 0x20, 0x30, 0x40, 0x50, 0x60};
const zx::duration kTimeout = zx::sec(5);
// TODO(cgibson): These first couple of functions are all boilerplate code
// copied from https://fuchsia-review.googlesource.com/c/garnet/+/221747 --
// Work is in progress to turn this code into a separate library, at which point
// this can be refactored.
zx_status_t CreateEthertap(zx::socket* sock) {
int ctlfd = open(kTapctl, O_RDONLY);
if (ctlfd < 0) {
fprintf(stderr, "could not open %s: %s\n", kTapctl, strerror(errno));
return ZX_ERR_IO;
}
auto closer = fbl::MakeAutoCall([ctlfd]() { close(ctlfd); });
ethertap_ioctl_config_t config = {};
strlcpy(config.name, "netstack_filter_test", ETHERTAP_MAX_NAME_LEN);
config.mtu = 1500;
memcpy(config.mac, kTapMac, 6);
ssize_t rc =
ioctl_ethertap_config(ctlfd, &config, sock->reset_and_get_address());
if (rc < 0) {
zx_status_t status = static_cast<zx_status_t>(rc);
fprintf(stderr, "could not configure ethertap device: %s\n",
zx_status_get_string(status));
return status;
}
return ZX_OK;
}
zx_status_t WatchCb(int dirfd, int event, const char* fn, void* cookie) {
if (event != WATCH_EVENT_ADD_FILE) {
return ZX_OK;
}
if (!strcmp(fn, ".") || !strcmp(fn, "..")) {
return ZX_OK;
}
zx::channel svc;
{
int devfd = openat(dirfd, fn, O_RDONLY);
if (devfd < 0) {
return ZX_OK;
}
zx_status_t status =
fdio_get_service_handle(devfd, svc.reset_and_get_address());
if (status != ZX_OK) {
return status;
}
}
::zircon::ethernet::Device_SyncProxy dev(std::move(svc));
// See if this device is our ethertap device
zircon::ethernet::Info info;
zx_status_t status = dev.GetInfo(&info);
if (status != ZX_OK) {
fprintf(stderr, "could not get ethernet info for %s/%s: %s\n", kEthernetDir,
fn, zx_status_get_string(status));
// Return ZX_OK to keep watching for devices.
return ZX_OK;
}
if (!(info.features & zircon::ethernet::INFO_FEATURE_SYNTH)) {
// Not a match, keep looking.
return ZX_OK;
}
// Found it!
// TODO(tkilbourn): this might not be the test device we created; need a
// robust way of getting the name of the tap device to check. Note that
// ioctl_device_get_device_name just returns "ethernet" since that's the child
// of the tap device that we've opened here.
auto svcp = reinterpret_cast<zx_handle_t*>(cookie);
*svcp = dev.proxy().TakeChannel().release();
return ZX_ERR_STOP;
}
zx_status_t OpenEthertapDev(zx::channel* svc) {
if (svc == nullptr) {
return ZX_ERR_INVALID_ARGS;
}
int ethdir = open(kEthernetDir, O_RDONLY);
if (ethdir < 0) {
fprintf(stderr, "could not open %s: %s\n", kEthernetDir, strerror(errno));
return ZX_ERR_IO;
}
zx_status_t status;
status = fdio_watch_directory(
ethdir, WatchCb, zx_deadline_after(ZX_SEC(2)),
reinterpret_cast<void*>(svc->reset_and_get_address()));
if (status == ZX_ERR_STOP) {
return ZX_OK;
} else {
return status;
}
}
fuchsia::sys::LaunchInfo CreateLaunchInfo(
const std::string& url, const std::vector<std::string>& args = {}) {
fuchsia::sys::LaunchInfo launch_info;
launch_info.url = url;
for (const auto& a : args) {
launch_info.arguments.push_back(a);
}
launch_info.out = component::testing::CloneFileDescriptor(1);
launch_info.err = component::testing::CloneFileDescriptor(2);
return launch_info;
}
fuchsia::sys::ComponentControllerPtr RunComponent(
component::testing::EnclosingEnvironment* enclosing_environment,
const std::string& url, const std::vector<std::string>& args = {}) {
return enclosing_environment->CreateComponent(
CreateLaunchInfo(url, std::move(args)));
}
TEST_F(NetstackFilterTest, TestRuleset) {
auto services = CreateServices();
const std::string netstack_url =
"fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx";
fuchsia::sys::LaunchInfo netstack_launch_info =
CreateLaunchInfo(netstack_url);
zx_status_t status = services->AddServiceWithLaunchInfo(
std::move(netstack_launch_info), fuchsia::netstack::Netstack::Name_);
fprintf(stderr, "added netstack service!\n");
fuchsia::sys::LaunchInfo filter_launch_info = CreateLaunchInfo(netstack_url);
status = services->AddServiceWithLaunchInfo(
std::move(filter_launch_info), fuchsia::net::filter::Filter::Name_);
ASSERT_TRUE(status == ZX_OK);
fprintf(stderr, "added filter service!\n");
auto env = CreateNewEnclosingEnvironment("NetstackFilterTest_TestRules",
std::move(services));
ASSERT_TRUE(WaitForEnclosingEnvToStart(env.get()));
zx::socket sock;
status = CreateEthertap(&sock);
EXPECT_EQ(ZX_OK, status);
fprintf(stderr, "created tap device\n");
zx::channel svc;
status = OpenEthertapDev(&svc);
EXPECT_EQ(ZX_OK, status);
fprintf(stderr, "found tap device\n");
status = sock.signal_peer(0u, ETHERTAP_SIGNAL_ONLINE);
EXPECT_EQ(ZX_OK, status);
fprintf(stderr, "set ethertap link status online\n");
fuchsia::netstack::NetstackPtr netstack;
env->ConnectToService(netstack.NewRequest());
fidl::StringPtr topo_path = "/fake/device";
fidl::StringPtr interface_name = "test_filter_interface";
fuchsia::netstack::InterfaceConfig config =
fuchsia::netstack::InterfaceConfig{};
config.name = interface_name;
inet::IpAddress test_static_ip =
inet::IpAddress::FromString("192.168.250.1", AF_INET);
ASSERT_NE(test_static_ip, inet::IpAddress::kInvalid)
<< "Failed to create static IP address: "
<< test_static_ip.ToString().c_str();
fprintf(stderr, "created static ip address: %s\n",
test_static_ip.ToString().c_str());
fuchsia::net::Subnet subnet;
fuchsia::net::IPv4Address ipv4;
memcpy(ipv4.addr.data(), test_static_ip.as_bytes(), 4);
subnet.addr.set_ipv4(ipv4);
subnet.prefix_len = 24;
config.ip_address_config.set_static_ip(std::move(subnet));
netstack->AddEthernetDevice(
std::move(topo_path), std::move(config),
fidl::InterfaceHandle<::zircon::ethernet::Device>(std::move(svc)));
fprintf(stderr, "added new ethernet device\n");
// Validate that the interface was actually added and that the static IP
// address now shows up in the list of interfaces.
static inet::IpAddress ip_address;
bool found_static_ip_on_interface = false;
netstack->GetInterfaces(
[&found_static_ip_on_interface, &test_static_ip](
const fidl::VectorPtr<fuchsia::netstack::NetInterface>& interfaces) {
for (const auto& interface : *interfaces) {
ip_address = inet::IpAddress(&interface.addr);
fprintf(stderr, "Found interface %d with IPv4 address: %s\n",
interface.id, ip_address.ToString().c_str());
if (test_static_ip == ip_address) {
found_static_ip_on_interface = true;
break;
}
}
});
RealLoopFixture::RunLoopWithTimeout();
ASSERT_TRUE(found_static_ip_on_interface)
<< "Static IP address was not found in the interface list!";
// Launch the test program.
std::vector<std::string> args = {test_static_ip.ToString()};
auto controller = RunComponent(env.get(), "test_filter_client", args);
bool wait = false;
int64_t exit_code;
fuchsia::sys::TerminationReason term_reason;
controller.events().OnTerminated =
[&wait, &exit_code, &term_reason](
int64_t retcode, fuchsia::sys::TerminationReason reason) {
wait = true;
exit_code = retcode;
term_reason = reason;
};
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&wait] { return wait; }, kTimeout));
ASSERT_TRUE(exit_code == 0) << "Exit code was non-zero, got: " << exit_code;
ASSERT_TRUE(term_reason == fuchsia::sys::TerminationReason::EXITED)
<< "TerminationReason was not 'EXITED' as expected, got: "
<< component::TerminationReasonToString(term_reason);
}
} // namespace