[net_ctl] First cut net_ctl commandline tool

- Create net_ctl bianry
- Implement the following commands
  $net_ctl if list
  $net_ctl if get <iface>
  Or
  $run fuchsia-pkg://fuchsia.com/net_ctl#meta/net_ctl.cmx if list
  $run fuchsia-pkg://fuchsia.com/net_ctl#meta/net_ctl.cmx if get <iface>

Test: CQ
Change-Id: I7eeed7364029a1d2c910af67ffbfcf7050bfdd03
diff --git a/bin/net_ctl/BUILD.gn b/bin/net_ctl/BUILD.gn
new file mode 100644
index 0000000..ac6ce8c
--- /dev/null
+++ b/bin/net_ctl/BUILD.gn
@@ -0,0 +1,42 @@
+# Copyright 2019 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("bin") {
+  name = "net_ctl"
+  edition = "2018"
+
+  deps = [
+    "//garnet/lib/rust/fidl_fuchsia_net_policy_ext",
+    "//garnet/public/fidl/fuchsia.net.policy:fuchsia.net.policy-rustc",
+    "//garnet/public/rust/fuchsia-app",
+    "//garnet/public/rust/fuchsia-async",
+    "//garnet/public/rust/fuchsia-zircon",
+    "//third_party/rust-crates/rustc_deps:failure",
+    "//third_party/rust-crates/rustc_deps:structopt",
+  ]
+}
+
+package("net_ctl") {
+  deps = [
+    ":bin",
+  ]
+
+  binaries = [
+    {
+      name = "net_ctl"
+      path = "rust_crates/net_ctl"
+      shell = true
+    },
+  ]
+
+  meta = [
+    {
+      path = rebase_path("meta/net_ctl.cmx")
+      dest = "net_ctl.cmx"
+    },
+  ]
+}
diff --git a/bin/net_ctl/meta/net_ctl.cmx b/bin/net_ctl/meta/net_ctl.cmx
new file mode 100644
index 0000000..f55b6c2
--- /dev/null
+++ b/bin/net_ctl/meta/net_ctl.cmx
@@ -0,0 +1,10 @@
+{
+    "program": {
+        "binary": "bin/net_ctl"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.net.policy.Observer"
+        ]
+    }
+}
diff --git a/bin/net_ctl/src/main.rs b/bin/net_ctl/src/main.rs
new file mode 100644
index 0000000..c2a18d8
--- /dev/null
+++ b/bin/net_ctl/src/main.rs
@@ -0,0 +1,57 @@
+// Copyright 2019 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.
+
+#![feature(async_await, await_macro, futures_api)]
+
+mod opts;
+
+use {
+    crate::opts::*,
+    failure::{Error, ResultExt},
+    fidl_fuchsia_net_policy::{ObserverMarker, ObserverProxy},
+    fidl_fuchsia_net_policy_ext::InterfaceInfo,
+    fuchsia_async as fasync, fuchsia_zircon as zx,
+    structopt::StructOpt,
+};
+
+fn main() -> Result<(), Error> {
+    let opt = Opt::from_args();
+    let mut exec = fasync::Executor::new().context("error creating executor")?;
+    let observer_proxy = fuchsia_app::client::connect_to_service::<ObserverMarker>()
+        .context("Failed to connect to Observer service")?;
+
+    let fut = async {
+        match opt {
+            Opt::If(cmd) => await!(process_observer(cmd, observer_proxy)),
+        }
+    };
+    exec.run_singlethreaded(fut)?;
+    Ok(())
+}
+
+async fn process_observer(cmd: ObserverCmd, observer_proxy: ObserverProxy) -> Result<(), Error> {
+    match cmd {
+        ObserverCmd::List => {
+            let (infos, status) =
+                await!(observer_proxy.list_interfaces()).context("error getting response")?;
+            match zx::Status::ok(status) {
+                Ok(()) => {
+                    for info in infos.unwrap() {
+                        println!("{}", InterfaceInfo::from(info));
+                    }
+                }
+                Err(e) => println!("error listing interfaces:{}", e),
+            }
+        }
+        ObserverCmd::Get { name } => {
+            let (info, status) = await!(observer_proxy.get_interface_info(&name))
+                .with_context(|_| format!("error getting response"))?;
+            match zx::Status::ok(status) {
+                Ok(()) => println!("{}", InterfaceInfo::from(*info.unwrap())),
+                Err(e) => println!("error querying interface {}:{}", name, e),
+            }
+        }
+    }
+    Ok(())
+}
diff --git a/bin/net_ctl/src/opts.rs b/bin/net_ctl/src/opts.rs
new file mode 100644
index 0000000..9b24574
--- /dev/null
+++ b/bin/net_ctl/src/opts.rs
@@ -0,0 +1,30 @@
+// Copyright 2019 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 structopt::StructOpt;
+
+#[derive(Debug, StructOpt)]
+#[structopt(
+    name = "net_ctl commands",
+    about = "Commands to configure networking interface"
+)]
+pub enum Opt {
+    /// Network interface configuration
+    #[structopt(name = "if")]
+    If(ObserverCmd),
+}
+
+#[derive(Debug, StructOpt)]
+pub enum ObserverCmd {
+    /// List network interfaces
+    #[structopt(name = "list")]
+    List,
+
+    #[structopt(name = "get")]
+    /// Query a network interface
+    Get {
+        /// Name of the network interface to query
+        name: String,
+    },
+}
diff --git a/packages/prod/all b/packages/prod/all
index a242175c..4cc153c 100644
--- a/packages/prod/all
+++ b/packages/prod/all
@@ -49,6 +49,7 @@
         "garnet/packages/prod/mediasession",
         "garnet/packages/prod/memory_monitor",
         "garnet/packages/prod/net-cli",
+        "garnet/packages/prod/net_ctl",
         "garnet/packages/prod/netcfg",
         "garnet/packages/prod/netconnector",
         "garnet/packages/prod/netstack",
diff --git a/packages/prod/net_ctl b/packages/prod/net_ctl
new file mode 100644
index 0000000..59850802
--- /dev/null
+++ b/packages/prod/net_ctl
@@ -0,0 +1,5 @@
+{
+    "packages": [
+        "//garnet/bin/net_ctl"
+    ]
+}