[netemul] Basic layout for netemul runner/sandbox

- Netemul runner offers fuchsia.sys.Runner and calls sandbox to start
tests in tailored environment for network emulation
- Netemul sandbox is the "root process" that will create the emulated
environment, launch the tests and observe exit codes

TESTS: no significant tests here apart from a simple test to make sure
runner and sandbox are creating processes as intended

Change-Id: Id2b8f99528a7ede3c585e15e816f756a621f672e
diff --git a/bin/netemul_runner/BUILD.gn b/bin/netemul_runner/BUILD.gn
new file mode 100644
index 0000000..313485b
--- /dev/null
+++ b/bin/netemul_runner/BUILD.gn
@@ -0,0 +1,59 @@
+# 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.
+
+import("//build/package.gni")
+
+executable("bin") {
+  testonly = true
+  output_name = "netemul_sandbox"
+  sources = [
+    "main.cc",
+    "managed_environment.cc",
+    "managed_environment.h",
+    "managed_launcher.cc",
+    "managed_launcher.h",
+    "sandbox.cc",
+    "sandbox.h",
+    "sandbox_env.h",
+  ]
+
+  deps = [
+    "//garnet/lib/cmx:cmx",
+    "//garnet/lib/process",
+    "//garnet/public/fidl/fuchsia.sys",
+    "//garnet/public/lib/component/cpp",
+    "//garnet/public/lib/component/cpp/testing",
+    "//garnet/public/lib/fsl",
+    "//garnet/public/lib/fxl",
+    "//garnet/public/lib/netemul/fidl:network",
+    "//garnet/public/lib/netemul/network:network_service",
+    "//third_party/rapidjson",
+    "//zircon/public/lib/async-cpp",
+    "//zircon/public/lib/async-default",
+    "//zircon/public/lib/async-loop-cpp",
+    "//zircon/public/lib/fdio",
+    "//zircon/public/lib/zx",
+  ]
+}
+
+package("netemul_sandbox") {
+  testonly = true
+
+  deps = [
+    ":bin",
+  ]
+
+  binaries = [
+    {
+      name = "netemul_sandbox"
+    },
+  ]
+
+  meta = [
+    {
+      path = rebase_path("meta/netemul_sandbox.cmx")
+      dest = "netemul_sandbox.cmx"
+    },
+  ]
+}
diff --git a/bin/netemul_runner/MAINTAINERS b/bin/netemul_runner/MAINTAINERS
new file mode 100644
index 0000000..c891e99
--- /dev/null
+++ b/bin/netemul_runner/MAINTAINERS
@@ -0,0 +1 @@
+brunodalbo@google.com
diff --git a/bin/netemul_runner/main.cc b/bin/netemul_runner/main.cc
new file mode 100644
index 0000000..03da980
--- /dev/null
+++ b/bin/netemul_runner/main.cc
@@ -0,0 +1,61 @@
+// 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 <lib/async-loop/cpp/loop.h>
+#include <lib/component/cpp/termination_reason.h>
+#include <lib/fxl/command_line.h>
+#include <lib/fxl/log_settings_command_line.h>
+#include <lib/fxl/logging.h>
+#include <iostream>
+#include "sandbox.h"
+
+using namespace netemul;
+
+void PrintUsage() {
+  fprintf(stderr, R"(
+Usage: netemul_sandbox <package_url> [arguments...]
+
+       *package_url* takes the form of component manifest URL which uniquely
+       identifies a component. Example:
+          fuchsia-pkg://fuchsia.com/component_hello_world#meta/hello.cmx
+)");
+}
+
+int main(int argc, const char** argv) {
+  auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
+  if (!fxl::SetLogSettingsFromCommandLine(command_line))
+    return 1;
+
+  async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
+  async_set_default_dispatcher(loop.dispatcher());
+
+  SandboxArgs args;
+
+  auto posargs = command_line.positional_args().begin();
+  if (posargs == command_line.positional_args().end()) {
+    PrintUsage();
+    return 1;
+  }
+  args.package = *posargs++;
+  args.args.insert(args.args.end(), posargs,
+                   command_line.positional_args().end());
+
+  FXL_LOG(INFO) << "Starting netemul sandbox for " << args.package;
+
+  Sandbox sandbox(std::move(args));
+  sandbox.SetTerminationCallback([](int64_t exit_code,
+                                    Sandbox::TerminationReason reason) {
+    FXL_LOG(INFO) << "Sandbox terminated with (" << exit_code << ") reason: "
+                  << component::HumanReadableTerminationReason(reason);
+    if (reason != Sandbox::TerminationReason::EXITED) {
+      exit_code = 1;
+    }
+    zx_process_exit(exit_code);
+  });
+
+  sandbox.Start();
+  loop.Run();
+
+  return 0;
+}
\ No newline at end of file
diff --git a/bin/netemul_runner/managed_environment.cc b/bin/netemul_runner/managed_environment.cc
new file mode 100644
index 0000000..7b8f5f6
--- /dev/null
+++ b/bin/netemul_runner/managed_environment.cc
@@ -0,0 +1,42 @@
+// 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 "managed_environment.h"
+
+namespace netemul {
+
+using component::testing::EnclosingEnvironment;
+using component::testing::EnvironmentServices;
+
+ManagedEnvironment::Ptr ManagedEnvironment::CreateRoot(
+    const fuchsia::sys::EnvironmentPtr& parent,
+    const SandboxEnv::Ptr& sandbox_env) {
+  auto services = EnvironmentServices::Create(parent);
+
+  fuchsia::sys::EnvironmentOptions options = {.kill_on_oom = true,
+                                              .allow_parent_runners = false,
+                                              .inherit_parent_services = true};
+
+  auto enclosing = EnclosingEnvironment::Create("root", parent,
+                                                std::move(services), options);
+
+  return ManagedEnvironment::Ptr(
+      new ManagedEnvironment(std::move(enclosing), sandbox_env));
+}
+
+ManagedEnvironment::ManagedEnvironment(
+    std::unique_ptr<component::testing::EnclosingEnvironment> env,
+    const SandboxEnv::Ptr& sandbox_env)
+    : sandbox_env_(sandbox_env), env_(std::move(env)), launcher_(this) {
+  env_->SetRunningChangedCallback([this](bool running) {
+    if (running && running_callback_) {
+      running_callback_();
+    }
+  });
+}
+
+component::testing::EnclosingEnvironment& ManagedEnvironment::environment() {
+  return *env_;
+}
+}  // namespace netemul
diff --git a/bin/netemul_runner/managed_environment.h b/bin/netemul_runner/managed_environment.h
new file mode 100644
index 0000000..b2a1014
--- /dev/null
+++ b/bin/netemul_runner/managed_environment.h
@@ -0,0 +1,57 @@
+// 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.
+
+#ifndef GARNET_BIN_NETEMUL_RUNNER_MANAGED_ENVIRONMENT_H_
+#define GARNET_BIN_NETEMUL_RUNNER_MANAGED_ENVIRONMENT_H_
+
+#include <lib/component/cpp/testing/enclosing_environment.h>
+#include <lib/svc/cpp/services.h>
+#include <memory>
+#include "managed_launcher.h"
+#include "sandbox_env.h"
+
+namespace netemul {
+
+class ManagedEnvironment {
+ public:
+  using EnvironmentRunningCallback = fit::closure;
+  using Ptr = std::unique_ptr<ManagedEnvironment>;
+  static Ptr CreateRoot(const fuchsia::sys::EnvironmentPtr& parent,
+                        const SandboxEnv::Ptr& sandbox_env);
+
+  const SandboxEnv::Ptr& sandbox_env() const { return sandbox_env_; }
+
+  const std::shared_ptr<component::Services>& services() {
+    if (!services_) {
+      services_ = std::make_shared<component::Services>();
+    }
+    return services_;
+  }
+
+  ManagedLauncher& launcher() { return launcher_; }
+
+  void SetRunningCallback(EnvironmentRunningCallback cb) {
+    running_callback_ = std::move(cb);
+  }
+
+ protected:
+  friend ManagedLauncher;
+
+  component::testing::EnclosingEnvironment& environment();
+
+ private:
+  ManagedEnvironment(
+      std::unique_ptr<component::testing::EnclosingEnvironment> env,
+      const SandboxEnv::Ptr& sandbox_env);
+
+  SandboxEnv::Ptr sandbox_env_;
+  std::unique_ptr<component::testing::EnclosingEnvironment> env_;
+  ManagedLauncher launcher_;
+  std::shared_ptr<component::Services> services_;
+  EnvironmentRunningCallback running_callback_;
+};
+
+}  // namespace netemul
+
+#endif  // GARNET_BIN_NETEMUL_RUNNER_MANAGED_ENVIRONMENT_H_
diff --git a/bin/netemul_runner/managed_launcher.cc b/bin/netemul_runner/managed_launcher.cc
new file mode 100644
index 0000000..c67f6ed
--- /dev/null
+++ b/bin/netemul_runner/managed_launcher.cc
@@ -0,0 +1,38 @@
+// 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 "managed_launcher.h"
+#include <lib/fdio/io.h>
+#include <lib/fxl/logging.h>
+#include <lib/fxl/strings/concatenate.h>
+#include <zircon/status.h>
+#include "garnet/lib/process/process_builder.h"
+#include "managed_environment.h"
+
+namespace netemul {
+
+using fuchsia::sys::TerminationReason;
+using process::ProcessBuilder;
+
+struct LaunchArgs {
+  std::string binary;
+  fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller;
+};
+
+void ManagedLauncher::CreateComponent(
+    fuchsia::sys::LaunchInfo launch_info,
+    fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller) {
+  // Just pass through to real launcher.
+  real_launcher_->CreateComponent(std::move(launch_info),
+                                  std::move(controller));
+}
+
+ManagedLauncher::ManagedLauncher(ManagedEnvironment* environment)
+    : env_(environment) {
+  env_->environment().ConnectToService(real_launcher_.NewRequest());
+}
+
+ManagedLauncher::~ManagedLauncher() = default;
+
+}  // namespace netemul
diff --git a/bin/netemul_runner/managed_launcher.h b/bin/netemul_runner/managed_launcher.h
new file mode 100644
index 0000000..eecf6d8
--- /dev/null
+++ b/bin/netemul_runner/managed_launcher.h
@@ -0,0 +1,31 @@
+// 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.
+
+#ifndef GARNET_BIN_NETEMUL_RUNNER_MANAGED_LAUNCHER_H_
+#define GARNET_BIN_NETEMUL_RUNNER_MANAGED_LAUNCHER_H_
+
+#include <fuchsia/sys/cpp/fidl.h>
+#include <string>
+
+namespace netemul {
+class ManagedEnvironment;
+class ManagedLauncher {
+ public:
+  explicit ManagedLauncher(ManagedEnvironment* environment);
+
+  ~ManagedLauncher();
+
+  void CreateComponent(
+      fuchsia::sys::LaunchInfo launch_info,
+      fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller);
+
+ private:
+  // Pointer to parent environment. Not owned.
+  ManagedEnvironment* env_;
+  fuchsia::sys::LauncherPtr real_launcher_;
+};
+
+}  // namespace netemul
+
+#endif  // GARNET_BIN_NETEMUL_RUNNER_MANAGED_LAUNCHER_H_
diff --git a/bin/netemul_runner/meta/netemul_sandbox.cmx b/bin/netemul_runner/meta/netemul_sandbox.cmx
new file mode 100644
index 0000000..21fd9b1
--- /dev/null
+++ b/bin/netemul_runner/meta/netemul_sandbox.cmx
@@ -0,0 +1,12 @@
+{
+    "program": {
+        "binary": "bin/netemul_sandbox"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.sys.Environment",
+            "fuchsia.sys.Loader",
+            "fuchsia.sys.Launcher"
+        ]
+    }
+}
diff --git a/bin/netemul_runner/runner/BUILD.gn b/bin/netemul_runner/runner/BUILD.gn
new file mode 100644
index 0000000..e7fb80d
--- /dev/null
+++ b/bin/netemul_runner/runner/BUILD.gn
@@ -0,0 +1,46 @@
+# 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.
+
+import("//build/package.gni")
+
+executable("bin") {
+  testonly = true
+  output_name = "netemul_runner"
+  sources = [
+    "main.cc",
+    "runner.cc",
+    "runner.h",
+  ]
+
+  deps = [
+    "//garnet/lib/cmx:cmx",
+    "//garnet/public/fidl/fuchsia.sys",
+    "//garnet/public/lib/component/cpp",
+    "//garnet/public/lib/fsl",
+    "//garnet/public/lib/fxl",
+    "//garnet/public/lib/pkg_url",
+    "//third_party/rapidjson",
+    "//zircon/public/lib/async-cpp",
+    "//zircon/public/lib/async-default",
+    "//zircon/public/lib/async-loop-cpp",
+  ]
+}
+
+package("netemul_runner") {
+  testonly = true
+
+  deps = [
+    ":bin",
+    "//garnet/bin/netemul_runner:netemul_sandbox",
+  ]
+
+  binary = "netemul_runner"
+
+  meta = [
+    {
+      path = rebase_path("meta/netemul_runner.cmx")
+      dest = "netemul_runner.cmx"
+    },
+  ]
+}
diff --git a/bin/netemul_runner/runner/main.cc b/bin/netemul_runner/runner/main.cc
new file mode 100644
index 0000000..848bc27
--- /dev/null
+++ b/bin/netemul_runner/runner/main.cc
@@ -0,0 +1,28 @@
+// 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 <lib/async-loop/cpp/loop.h>
+#include <lib/fxl/command_line.h>
+#include <lib/fxl/log_settings_command_line.h>
+#include <lib/fxl/logging.h>
+#include <iostream>
+#include "runner.h"
+
+using namespace netemul;
+
+int main(int argc, const char** argv) {
+  auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
+  if (!fxl::SetLogSettingsFromCommandLine(command_line))
+    return 1;
+
+  async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
+  async_set_default_dispatcher(loop.dispatcher());
+
+  FXL_LOG(INFO) << "Starting netemul runner";
+
+  Runner r(loop.dispatcher());
+  loop.Run();
+
+  return 0;
+}
\ No newline at end of file
diff --git a/bin/netemul_runner/runner/meta/netemul_runner.cmx b/bin/netemul_runner/runner/meta/netemul_runner.cmx
new file mode 100644
index 0000000..ef8c26b
--- /dev/null
+++ b/bin/netemul_runner/runner/meta/netemul_runner.cmx
@@ -0,0 +1,11 @@
+{
+    "program": {
+        "binary": "bin/app"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.sys.Launcher",
+            "fuchsia.sys.Loader"
+        ]
+    }
+}
diff --git a/bin/netemul_runner/runner/runner.cc b/bin/netemul_runner/runner/runner.cc
new file mode 100644
index 0000000..210ba68
--- /dev/null
+++ b/bin/netemul_runner/runner/runner.cc
@@ -0,0 +1,107 @@
+// 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 "runner.h"
+
+#include <lib/async/default.h>
+#include <lib/fsl/io/fd.h>
+#include <lib/fxl/files/unique_fd.h>
+#include <lib/fxl/logging.h>
+#include <lib/pkg_url/fuchsia_pkg_url.h>
+#include "garnet/lib/cmx/cmx.h"
+
+namespace netemul {
+using component::StartupContext;
+static const char* kSandbox =
+    "fuchsia-pkg://fuchsia.com/netemul_sandbox#meta/netemul_sandbox.cmx";
+
+Runner::Runner(async_dispatcher_t* dispatcher) {
+  if (dispatcher == nullptr) {
+    dispatcher = async_get_default_dispatcher();
+  }
+  dispatcher_ = dispatcher;
+
+  startup_context_ = StartupContext::CreateFromStartupInfo();
+  startup_context_->ConnectToEnvironmentService(
+      launcher_.NewRequest(dispatcher_));
+  startup_context_->ConnectToEnvironmentService(
+      loader_.NewRequest(dispatcher_));
+  startup_context_->outgoing().AddPublicService(
+      bindings_.GetHandler(this, dispatcher_));
+}
+
+struct RunnerArgs {
+  fuchsia::sys::StartupInfo startup_info;
+  fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller;
+};
+
+void Runner::StartComponent(
+    fuchsia::sys::Package package, fuchsia::sys::StartupInfo startup_info,
+    fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller) {
+  FXL_LOG(INFO) << "resolved URL: " << package.resolved_url;
+
+  // args needs to be shared ptr 'cause Loader fidl
+  // uses std::function and not fit::function (i.e. legacy callbacks)
+  std::shared_ptr<RunnerArgs> args = std::make_shared<RunnerArgs>();
+  args->startup_info = std::move(startup_info);
+  args->controller = std::move(controller);
+
+  // go through loader to get information, 'cause info is not complete
+  // from caller (missing directory and other stuff)
+  loader_->LoadUrl(
+      package.resolved_url, [this, args](fuchsia::sys::PackagePtr package) {
+        RunComponent(std::move(package), std::move(args->startup_info),
+                     std::move(args->controller));
+      });
+}
+
+void Runner::RunComponent(
+    fuchsia::sys::PackagePtr package, fuchsia::sys::StartupInfo startup_info,
+    fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller) {
+  if (!package) {
+    // TODO(brunodalbo) expose errors correctly through interface request
+    FXL_LOG(ERROR) << "Can't load package";
+    return;
+  }
+  // extract and parse cmx:
+  component::FuchsiaPkgUrl fp;
+  if (!fp.Parse(package->resolved_url.get())) {
+    FXL_LOG(ERROR) << "can't parse fuchsia URL " << package->resolved_url;
+    // TODO(brunodalbo) expose errors correctly through interface request
+    return;
+  }
+
+  if (!package->directory.is_valid()) {
+    FXL_LOG(ERROR) << "Package directory not provided";
+    return;
+  }
+
+  component::CmxMetadata cmx;
+  fxl::UniqueFD fd =
+      fsl::OpenChannelAsFileDescriptor(std::move(package->directory));
+
+  json::JSONParser json_parser;
+  if (!cmx.ParseFromFileAt(fd.get(), fp.resource_path(), &json_parser)) {
+    FXL_LOG(ERROR) << "cmx file failed to parse: " << json_parser.error_str();
+    // TODO(brunodalbo) expose errors correctly through interface request
+    return;
+  }
+
+  std::stringstream launchpkg;
+  launchpkg << "fuchsia-pkg://fuchsia.com/" << fp.package_name() << "#"
+            << cmx.program_meta().data();
+
+  auto sandbox_arg = launchpkg.str();
+
+  auto linfo = std::move(startup_info.launch_info);
+  linfo.url = kSandbox;
+  linfo.arguments->push_back(sandbox_arg);
+  linfo.arguments->insert(linfo.arguments->end(),
+                          startup_info.launch_info.arguments->begin(),
+                          startup_info.launch_info.arguments->end());
+
+  launcher_->CreateComponent(std::move(linfo), std::move(controller));
+}
+
+}  // namespace netemul
diff --git a/bin/netemul_runner/runner/runner.h b/bin/netemul_runner/runner/runner.h
new file mode 100644
index 0000000..1c837cf
--- /dev/null
+++ b/bin/netemul_runner/runner/runner.h
@@ -0,0 +1,37 @@
+// 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.
+
+#ifndef GARNET_BIN_NETEMUL_RUNNER_RUNNER_RUNNER_H_
+#define GARNET_BIN_NETEMUL_RUNNER_RUNNER_RUNNER_H_
+
+#include <lib/component/cpp/startup_context.h>
+#include <lib/fidl/cpp/binding_set.h>
+#include "fuchsia/sys/cpp/fidl.h"
+
+namespace netemul {
+class Runner : public fuchsia::sys::Runner {
+ public:
+  using FRunner = fuchsia::sys::Runner;
+  explicit Runner(async_dispatcher_t* dispatcher = nullptr);
+
+  void StartComponent(fuchsia::sys::Package package,
+                      fuchsia::sys::StartupInfo startup_info,
+                      fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+                          controller) override;
+
+ private:
+  void RunComponent(
+      fuchsia::sys::PackagePtr package, fuchsia::sys::StartupInfo startup_info,
+      fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller);
+
+  async_dispatcher_t* dispatcher_;
+  std::unique_ptr<component::StartupContext> startup_context_;
+  fidl::BindingSet<FRunner> bindings_;
+  fuchsia::sys::LauncherPtr launcher_;
+  fuchsia::sys::LoaderPtr loader_;
+};
+
+}  // namespace netemul
+
+#endif  // GARNET_BIN_NETEMUL_RUNNER_RUNNER_RUNNER_H_
diff --git a/bin/netemul_runner/sandbox.cc b/bin/netemul_runner/sandbox.cc
new file mode 100644
index 0000000..1b8e89a
--- /dev/null
+++ b/bin/netemul_runner/sandbox.cc
@@ -0,0 +1,95 @@
+// 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 "sandbox.h"
+#include <fcntl.h>
+#include <lib/async/default.h>
+#include <lib/component/cpp/startup_context.h>
+#include <lib/component/cpp/testing/test_util.h>
+#include <lib/fdio/watcher.h>
+#include <lib/fsl/io/fd.h>
+#include <lib/fxl/logging.h>
+#include <lib/fxl/strings/concatenate.h>
+#include <lib/pkg_url/fuchsia_pkg_url.h>
+#include <zircon/status.h>
+#include "garnet/lib/cmx/cmx.h"
+
+namespace netemul {
+
+Sandbox::Sandbox(SandboxArgs args) : args_(std::move(args)) {
+  auto startup_context = component::StartupContext::CreateFromStartupInfo();
+  startup_context->ConnectToEnvironmentService(parent_env_.NewRequest());
+  startup_context->ConnectToEnvironmentService(loader_.NewRequest());
+}
+
+void Sandbox::Start() {
+  if (!parent_env_ || !loader_) {
+    Terminate(TerminationReason::INTERNAL_ERROR);
+  }
+
+  loader_->LoadUrl(args_.package, [this](fuchsia::sys::PackagePtr package) {
+    if (!package) {
+      Terminate(TerminationReason::PACKAGE_NOT_FOUND);
+      return;
+    } else if (!package->directory) {
+      Terminate(TerminationReason::INTERNAL_ERROR);
+    }
+    LoadPackage(std::move(package));
+  });
+}
+
+void Sandbox::Terminate(int64_t exit_code, Sandbox::TerminationReason reason) {
+  if (termination_callback_) {
+    termination_callback_(exit_code, reason);
+  }
+}
+
+void Sandbox::Terminate(Sandbox::TerminationReason reason) {
+  Terminate(-1, reason);
+}
+void Sandbox::LoadPackage(fuchsia::sys::PackagePtr package) {
+  // package is loaded, proceed to parsing cmx and starting child env
+  component::FuchsiaPkgUrl pkgUrl;
+  if (!pkgUrl.Parse(package->resolved_url)) {
+    FXL_LOG(ERROR) << "Can't parse fuchsia url: " << package->resolved_url;
+    Terminate(TerminationReason::INTERNAL_ERROR);
+    return;
+  }
+
+  fxl::UniqueFD dirfd =
+      fsl::OpenChannelAsFileDescriptor(std::move(package->directory));
+  sandbox_env_ = std::make_shared<SandboxEnv>(args_.package, std::move(dirfd));
+
+  component::CmxMetadata cmx;
+
+  json::JSONParser json_parser;
+  if (!cmx.ParseFromFileAt(sandbox_env_->dir().get(), pkgUrl.resource_path(),
+                           &json_parser)) {
+    FXL_LOG(ERROR) << "cmx file failed to parse: " << json_parser.error_str();
+    Terminate(TerminationReason::INTERNAL_ERROR);
+    return;
+  }
+
+  root_ = ManagedEnvironment::CreateRoot(parent_env_, sandbox_env_);
+
+  // TODO(brunodalbo) parameterize environment based on
+  // facets in cmx file
+
+  root_->SetRunningCallback([this] {
+    root_proc_.events().OnTerminated = [this](int64_t code,
+                                              TerminationReason reason) {
+      Terminate(code, reason);  // mimic termination of root process
+    };
+
+    // start root test process:
+    fuchsia::sys::LaunchInfo linfo;
+    linfo.url = sandbox_env_->name();
+    linfo.out = component::testing::CloneFileDescriptor(STDOUT_FILENO);
+    linfo.err = component::testing::CloneFileDescriptor(STDERR_FILENO);
+    root_->launcher().CreateComponent(std::move(linfo),
+                                      root_proc_.NewRequest());
+  });
+}
+
+}  // namespace netemul
\ No newline at end of file
diff --git a/bin/netemul_runner/sandbox.h b/bin/netemul_runner/sandbox.h
new file mode 100644
index 0000000..71bd3a2
--- /dev/null
+++ b/bin/netemul_runner/sandbox.h
@@ -0,0 +1,50 @@
+// 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.
+
+#ifndef GARNET_BIN_NETEMUL_RUNNER_SANDBOX_H_
+#define GARNET_BIN_NETEMUL_RUNNER_SANDBOX_H_
+
+#include <fuchsia/sys/cpp/fidl.h>
+#include "managed_environment.h"
+#include "sandbox_env.h"
+
+namespace netemul {
+
+class SandboxArgs {
+ public:
+  std::string package;
+  std::vector<std::string> args;
+};
+
+class Sandbox {
+ public:
+  using TerminationReason = fuchsia::sys::TerminationReason;
+  using TerminationCallback =
+      fit::function<void(int64_t code, TerminationReason reason)>;
+  explicit Sandbox(SandboxArgs args);
+
+  void SetTerminationCallback(TerminationCallback callback) {
+    termination_callback_ = std::move(callback);
+  }
+
+  void Start();
+
+ private:
+  void LoadPackage(fuchsia::sys::PackagePtr package);
+  void Terminate(TerminationReason reason);
+  void Terminate(int64_t exit_code, TerminationReason reason);
+  void StartRootEnvironment();
+
+  SandboxArgs args_;
+  SandboxEnv::Ptr sandbox_env_;
+  TerminationCallback termination_callback_;
+  fuchsia::sys::EnvironmentPtr parent_env_;
+  fuchsia::sys::LoaderPtr loader_;
+  ManagedEnvironment::Ptr root_;
+  fuchsia::sys::ComponentControllerPtr root_proc_;
+};
+
+}  // namespace netemul
+
+#endif  // GARNET_BIN_NETEMUL_RUNNER_SANDBOX_H_
diff --git a/bin/netemul_runner/sandbox_env.h b/bin/netemul_runner/sandbox_env.h
new file mode 100644
index 0000000..671fcb8
--- /dev/null
+++ b/bin/netemul_runner/sandbox_env.h
@@ -0,0 +1,32 @@
+#include <utility>
+
+// 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.
+
+#ifndef GARNET_BIN_NETEMUL_RUNNER_SANDBOX_ENV_H_
+#define GARNET_BIN_NETEMUL_RUNNER_SANDBOX_ENV_H_
+
+#include <lib/fxl/files/unique_fd.h>
+#include <memory>
+#include <string>
+
+namespace netemul {
+class SandboxEnv {
+ public:
+  using Ptr = std::shared_ptr<SandboxEnv>;
+
+  SandboxEnv(std::string package_name, fxl::UniqueFD dir)
+      : package_name_(std::move(package_name)), package_dir_(std::move(dir)) {}
+
+  const std::string& name() const { return package_name_; }
+
+  const fxl::UniqueFD& dir() const { return package_dir_; }
+
+ private:
+  std::string package_name_;
+  fxl::UniqueFD package_dir_;
+};
+}  // namespace netemul
+
+#endif  // GARNET_BIN_NETEMUL_RUNNER_SANDBOX_ENV_H_
diff --git a/bin/netemul_runner/test/BUILD.gn b/bin/netemul_runner/test/BUILD.gn
new file mode 100644
index 0000000..e4d1e5f
--- /dev/null
+++ b/bin/netemul_runner/test/BUILD.gn
@@ -0,0 +1,24 @@
+# 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.
+
+import("//build/test/test_package.gni")
+
+test_package("netemul_sandbox_test") {
+  deps = [
+    "//garnet/bin/netemul_runner/test/svc_list",
+  ]
+
+  meta = [
+    {
+      path = rebase_path("meta/svc_list_run.cmx")
+      dest = "svc_list_run.cmx"
+    },
+  ]
+
+  tests = [
+    {
+      name = "svc_list"
+    },
+  ]
+}
diff --git a/bin/netemul_runner/test/meta/svc_list.cmx b/bin/netemul_runner/test/meta/svc_list.cmx
new file mode 100644
index 0000000..bd906ba
--- /dev/null
+++ b/bin/netemul_runner/test/meta/svc_list.cmx
@@ -0,0 +1,6 @@
+{
+    "program": {
+        "data": "meta/svc_list_run.cmx"
+    },
+    "runner": "fuchsia-pkg://fuchsia.com/netemul_runner#meta/netemul_runner.cmx"
+}
diff --git a/bin/netemul_runner/test/meta/svc_list_run.cmx b/bin/netemul_runner/test/meta/svc_list_run.cmx
new file mode 100644
index 0000000..1f58670
--- /dev/null
+++ b/bin/netemul_runner/test/meta/svc_list_run.cmx
@@ -0,0 +1,5 @@
+{
+    "program": {
+        "binary": "test/svc_list"
+    }
+}
diff --git a/bin/netemul_runner/test/svc_list/BUILD.gn b/bin/netemul_runner/test/svc_list/BUILD.gn
new file mode 100644
index 0000000..206acbb
--- /dev/null
+++ b/bin/netemul_runner/test/svc_list/BUILD.gn
@@ -0,0 +1,18 @@
+# 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.
+
+import("//build/package.gni")
+import("//build/rust/rustc_binary.gni")
+
+rustc_binary("svc_list") {
+  name = "svc_list"
+  edition = "2018"
+  deps = [
+    "//garnet/lib/rust/ethernet",
+    "//garnet/public/rust/fuchsia-async",
+    "//garnet/public/rust/fuchsia-zircon",
+    "//third_party/rust-crates/rustc_deps:failure",
+    "//third_party/rust-crates/rustc_deps:futures-preview",
+  ]
+}
diff --git a/bin/netemul_runner/test/svc_list/src/main.rs b/bin/netemul_runner/test/svc_list/src/main.rs
new file mode 100644
index 0000000..b5e921b
--- /dev/null
+++ b/bin/netemul_runner/test/svc_list/src/main.rs
@@ -0,0 +1,29 @@
+// 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.
+
+use std::fs;
+use std::io;
+use std::path::Path;
+
+fn visit_dirs(dir: &Path) -> io::Result<()> {
+    if dir.is_dir() {
+        for entry in fs::read_dir(dir)? {
+            let entry = entry?;
+            let path = entry.path();
+            let str = path.to_str().expect("paths need strings?");
+            println!("{}", str);
+            visit_dirs(dir.join(path).as_path())?;
+        }
+    }
+    Ok(())
+}
+
+fn main() -> io::Result<()> {
+    println!("Hello World");
+    visit_dirs(Path::new("/"))?;
+
+    // TODO(brunodalbo) the idea of this test is to find the sandbox services and ensure they're there
+
+    Ok(())
+}
diff --git a/packages/testing/all b/packages/testing/all
index 1a2bb15..5e5803d 100644
--- a/packages/testing/all
+++ b/packages/testing/all
@@ -1,5 +1,6 @@
 {
     "imports": [
-        "garnet/packages/testing/run_test_component"
+        "garnet/packages/testing/run_test_component",
+        "garnet/packages/testing/netemul_runner"
     ]
 }
diff --git a/packages/testing/netemul_runner b/packages/testing/netemul_runner
new file mode 100644
index 0000000..7a11092
--- /dev/null
+++ b/packages/testing/netemul_runner
@@ -0,0 +1,6 @@
+{
+    "packages": [
+        "//garnet/bin/netemul_runner/runner:netemul_runner",
+        "//garnet/bin/netemul_runner:netemul_sandbox"
+    ]
+}
diff --git a/packages/tests/netemul b/packages/tests/netemul
index 8a97dcc..d5cb344 100644
--- a/packages/tests/netemul
+++ b/packages/tests/netemul
@@ -1,8 +1,10 @@
 {
     "imports": [
-        "garnet/packages/prod/netstack"
+        "garnet/packages/prod/netstack",
+        "garnet/packages/testing/netemul_runner"
     ],
     "packages": [
-        "//garnet/public/lib/netemul/network:netemul_network_test"
+        "//garnet/public/lib/netemul/network:netemul_network_test",
+        "//garnet/bin/netemul_runner/test:netemul_sandbox_test"
     ]
 }
diff --git a/public/lib/netemul/MAINTAINERS b/public/lib/netemul/MAINTAINERS
new file mode 100644
index 0000000..c891e99
--- /dev/null
+++ b/public/lib/netemul/MAINTAINERS
@@ -0,0 +1 @@
+brunodalbo@google.com