[nmh][network_manager] Adds NAT control

This patch adds support to control NAT from Network Manager. Actually
enabling and disabling NAT will be done in a future commit.

Bug: 35788
Tests: network_manager_{tests, integration, and cli} tests, all pass.

Change-Id: Ibde673ae87da46aec5f040843b9aebcb64daf78b
diff --git a/src/connectivity/management/network_manager/core/BUILD.gn b/src/connectivity/management/network_manager/core/BUILD.gn
index 3dea146..7c7be9f 100644
--- a/src/connectivity/management/network_manager/core/BUILD.gn
+++ b/src/connectivity/management/network_manager/core/BUILD.gn
@@ -11,9 +11,12 @@
   with_unit_tests = true
 
   deps = [
+    "//garnet/lib/rust/fidl_fuchsia_net_stack_ext",
+    "//garnet/public/lib/fidl/rust/fidl",
     "//garnet/public/rust/fuchsia-async",
     "//garnet/public/rust/fuchsia-component",
     "//garnet/public/rust/fuchsia-syslog",
+    "//sdk/fidl/fuchsia.net.dhcp:fuchsia.net.dhcp-rustc",
     "//sdk/fidl/fuchsia.net.filter:fuchsia.net.filter-rustc",
     "//sdk/fidl/fuchsia.netstack:fuchsia.netstack-rustc",
     "//sdk/fidl/fuchsia.router.config:fuchsia.router.config-rustc",
diff --git a/src/connectivity/management/network_manager/core/src/error.rs b/src/connectivity/management/network_manager/core/src/error.rs
index 2a09f50..5039d3e 100644
--- a/src/connectivity/management/network_manager/core/src/error.rs
+++ b/src/connectivity/management/network_manager/core/src/error.rs
@@ -4,7 +4,8 @@
 
 //! Custom error types for the network manager.
 
-use failure::Fail;
+use core::fmt::{self, Display};
+use failure::{Context, Fail};
 
 pub type Result<T> = std::result::Result<T, NetworkManager>;
 
@@ -23,6 +24,9 @@
     /// Errors related to HAL layer.
     #[fail(display = "{}", _0)]
     HAL(#[cause] Hal),
+    /// Errors with a detail string attached.
+    #[fail(display = "An error occurred.")]
+    INTERNAL(ErrorWithDetail),
     // Add error types here.
 }
 
@@ -41,6 +45,23 @@
         NetworkManager::HAL(e)
     }
 }
+impl From<Context<&'static str>> for NetworkManager {
+    fn from(inner: Context<&'static str>) -> Self {
+        NetworkManager::INTERNAL(ErrorWithDetail { inner: Context::new(inner.to_string()) })
+    }
+}
+
+#[derive(Fail, Debug)]
+pub struct ErrorWithDetail {
+    #[cause]
+    inner: Context<String>,
+}
+
+impl Display for ErrorWithDetail {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        Display::fmt(&self.inner, f)
+    }
+}
 
 /// Error type for packet LIFManager.
 #[derive(Fail, Debug, PartialEq)]
@@ -79,6 +100,12 @@
     NotEnabled,
     #[fail(display = "Could not disable service")]
     NotDisabled,
+    #[fail(display = "Failed to add new packet filter rules")]
+    ErrorAddingPacketFilterRules,
+    #[fail(display = "Failed to get packet filter rules")]
+    ErrorGettingPacketFilterRules,
+    #[fail(display = "Failed to enable NAT")]
+    ErrorFailedEnableNat,
     #[fail(display = "Service is not supported")]
     NotSupported,
 }
diff --git a/src/connectivity/management/network_manager/core/src/hal.rs b/src/connectivity/management/network_manager/core/src/hal.rs
index 4e840f11..04fc010 100644
--- a/src/connectivity/management/network_manager/core/src/hal.rs
+++ b/src/connectivity/management/network_manager/core/src/hal.rs
@@ -9,6 +9,7 @@
 use failure::{Error, ResultExt};
 use fidl_fuchsia_net;
 use fidl_fuchsia_net_stack::{InterfaceInfo, StackMarker, StackProxy};
+use fidl_fuchsia_net_stack_ext::FidlReturn;
 use fidl_fuchsia_netstack::{NetstackMarker, NetstackProxy};
 use fuchsia_component::client::connect_to_service;
 use std::collections::HashSet;
@@ -159,7 +160,7 @@
 
     pub async fn get_interface(&mut self, port: u64) -> Option<Interface> {
         match self.stack.get_interface_info(port).await {
-            Ok((Some(info), _)) => Some((&(*info)).into()),
+            Ok(Ok(info)) => Some((&info).into()),
             _ => None,
         }
     }
@@ -222,16 +223,9 @@
                 &mut addr.to_fidl_interface_address(),
             )
             .await;
-        match r {
-            Err(_) => Err(error::NetworkManager::HAL(error::Hal::OperationFailed)),
-            Ok(r) => match r {
-                None => Ok(()),
-                Some(e) => {
-                    println!("could not set interface address: ${:?}", e);
-                    Err(error::NetworkManager::HAL(error::Hal::OperationFailed))
-                }
-            },
-        }
+        r.squash_result()
+            .map_err(|_| error::NetworkManager::HAL(error::Hal::OperationFailed))
+            .map(|_| ())
     }
 
     /// `unset_ip_address` removes an IP address from the interface configuration.
@@ -273,14 +267,24 @@
     }
 
     pub async fn set_dhcp_client_state(&mut self, pid: PortId, enable: bool) -> error::Result<()> {
-        let r = self.netstack.set_dhcp_client_status(StackPortId::from(pid).to_u32(), enable).await;
-        match r {
-            Ok(fidl_fuchsia_netstack::NetErr {
-                status: fidl_fuchsia_netstack::Status::Ok,
-                message: _,
-            }) => Ok(()),
-            _ => Err(error::NetworkManager::HAL(error::Hal::OperationFailed)),
+        let (dhcp_client, server_end) =
+            fidl::endpoints::create_proxy::<fidl_fuchsia_net_dhcp::ClientMarker>()
+                .context("dhcp client: failed to create fidl endpoints")?;
+        if let Err(e) = self.netstack.get_dhcp_client(pid.to_u32(), server_end).await {
+            warn!("failed to create fidl endpoint for dhch client: {:?}", e);
+            return Err(error::NetworkManager::HAL(error::Hal::OperationFailed));
         }
+        let r = if enable {
+            dhcp_client.start().await.context("failed to start dhcp client")?
+        } else {
+            dhcp_client.stop().await.context("failed to stop dhcp client")?
+        };
+        if let Err(e) = r {
+            warn!("failed to start dhcp client: {:?}", e);
+            return Err(error::NetworkManager::HAL(error::Hal::OperationFailed));
+        }
+        info!("DHCP client on nicid: {}, enabled: {}", pid.to_u32(), enable);
+        Ok(())
     }
 
     // apply_manual_address updates the configured IP address.
diff --git a/src/connectivity/management/network_manager/core/src/lib.rs b/src/connectivity/management/network_manager/core/src/lib.rs
index 927a6e7..5ed7d4f 100644
--- a/src/connectivity/management/network_manager/core/src/lib.rs
+++ b/src/connectivity/management/network_manager/core/src/lib.rs
@@ -32,18 +32,20 @@
     port_manager: portmgr::PortManager,
     lif_manager: lifmgr::LIFManager,
     service_manager: servicemgr::Manager,
+    packet_filter: packet_filter::PacketFilter,
     hal: hal::NetCfg,
 }
 
 impl DeviceState {
     //! Create an empty DeviceState.
-    pub fn new(hal: hal::NetCfg) -> Self {
+    pub fn new(hal: hal::NetCfg, packet_filter: packet_filter::PacketFilter) -> Self {
         let v = 0;
         DeviceState {
             version: v,
             port_manager: portmgr::PortManager::new(),
             lif_manager: lifmgr::LIFManager::new(),
             service_manager: servicemgr::Manager::new(),
+            packet_filter: packet_filter,
             hal,
         }
     }
@@ -285,6 +287,7 @@
                         address: Some(address),
                         prefix_length: Some(prefix_length),
                     }) => {
+                        // The WAN IPv4 address is changing.
                         if lp.dhcp {
                             warn!(
                                 "Setting a static ip is not allowed when \
@@ -293,8 +296,9 @@
                             return Err(error::NetworkManager::LIF(error::Lif::NotSupported));
                         }
                         info!("setting ip to {:?} {:?}", address, prefix_length);
-                        let a = LifIpAddr::from(p.address_v4.as_ref().unwrap());
-                        lp.address = Some(a);
+                        let v4addr = LifIpAddr::from(p.address_v4.as_ref().unwrap());
+                        self.service_manager.set_global_ip_nat(v4addr.clone(), lif.pid());
+                        lp.address = Some(v4addr);
                     }
                     _ => {
                         warn!("invalid address {:?}", p.address_v4);
@@ -321,6 +325,7 @@
                         address: Some(address),
                         prefix_length: Some(prefix_length),
                     }) => {
+                        // WAN IPv6 address is changing.
                         if lp.dhcp {
                             warn!(
                                 "Setting a static ip is not allowed when \
@@ -376,9 +381,11 @@
                         address: Some(address),
                         prefix_length: Some(prefix_length),
                     }) => {
+                        // LAN IPv4 address is changing.
                         info!("setting ip to {:?} {:?}", address, prefix_length);
-                        let a = LifIpAddr::from(p.address_v4.as_ref().unwrap());
-                        lp.address = Some(a);
+                        let v4addr = LifIpAddr::from(p.address_v4.as_ref().unwrap());
+                        self.service_manager.set_local_subnet_nat(v4addr.clone());
+                        lp.address = Some(v4addr);
                     }
                     _ => {
                         warn!("invalid address {:?}", p.address_v4);
@@ -391,6 +398,7 @@
                         address: Some(address),
                         prefix_length: Some(prefix_length),
                     }) => {
+                        // LAN IPv6 address is changing.
                         info!("setting ip to {:?} {:?}", address, prefix_length);
                         let a = LifIpAddr::from(p.address_v6.as_ref().unwrap());
                         lp.address = Some(a);
@@ -407,8 +415,19 @@
                         lp.enabled = *enable
                     }
                 };
+
                 self.hal.apply_properties(lif.pid(), &old, &lp).await?;
                 lif.set_properties(self.version, lp)?;
+                if self.service_manager.is_nat_enabled() {
+                    if let Err(e) =
+                        self.packet_filter.update_nat(self.service_manager.get_nat_config()).await
+                    {
+                        warn!("Failed to enable nat: {:?}", e);
+                        return Err(error::NetworkManager::SERVICE(
+                            error::Service::ErrorFailedEnableNat,
+                        ));
+                    }
+                }
                 self.version += 1;
                 Ok(())
             }
@@ -434,6 +453,28 @@
     pub fn version(&self) -> Version {
         self.version
     }
+
+    /// `set_filter` installs a new packet filter rule.
+    pub async fn set_filter(
+        &self,
+        rule: fidl_fuchsia_router_config::FilterRule,
+    ) -> error::Result<()> {
+        match self.packet_filter.set_filter(rule).await {
+            Ok(_) => Ok(()),
+            Err(e) => {
+                warn!("Failed to set new filter rules: {:?}", e);
+                Err(error::NetworkManager::SERVICE(error::Service::ErrorAddingPacketFilterRules))
+            }
+        }
+    }
+
+    /// `get_filters` returns the currently installed packet filter rules.
+    pub async fn get_filters(&self) -> error::Result<Vec<fidl_fuchsia_router_config::FilterRule>> {
+        self.packet_filter.get_filters().await.map_err(|e| {
+            warn!("Failed to get filter rules: {:?}", e);
+            error::NetworkManager::SERVICE(error::Service::ErrorGettingPacketFilterRules)
+        })
+    }
 }
 
 type Version = u64;
@@ -473,7 +514,10 @@
 
     #[fasync::run_singlethreaded(test)]
     async fn test_new_device_state() {
-        let d = DeviceState::new(hal::NetCfg::new().unwrap());
+        let d = DeviceState::new(
+            hal::NetCfg::new().unwrap(),
+            packet_filter::PacketFilter::start().unwrap(),
+        );
 
         assert_eq!(d.version, 0);
         assert_eq!(d.port_manager.ports().count(), 0);
diff --git a/src/connectivity/management/network_manager/core/src/lifmgr.rs b/src/connectivity/management/network_manager/core/src/lifmgr.rs
index 1cb86df..f80d508 100644
--- a/src/connectivity/management/network_manager/core/src/lifmgr.rs
+++ b/src/connectivity/management/network_manager/core/src/lifmgr.rs
@@ -231,6 +231,26 @@
             },
         }
     }
+    pub fn to_fidl_subnet(&self) -> fidl_fuchsia_net::Subnet {
+        match self.address {
+            IpAddr::V4(a) => fidl_fuchsia_net::Subnet {
+                addr: fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address {
+                    addr: (u32::from_be_bytes(a.octets()) >> (32 - self.prefix)
+                        << (32 - self.prefix))
+                        .to_be_bytes(),
+                }),
+                prefix_len: self.prefix,
+            },
+            IpAddr::V6(a) => fidl_fuchsia_net::Subnet {
+                addr: fidl_fuchsia_net::IpAddress::Ipv6(fidl_fuchsia_net::Ipv6Address {
+                    addr: (u128::from_be_bytes(a.octets()) >> (128 - self.prefix)
+                        << (128 - self.prefix))
+                        .to_be_bytes(),
+                }),
+                prefix_len: self.prefix,
+            },
+        }
+    }
     pub fn to_fidl_interface_address(&self) -> fidl_fuchsia_net_stack::InterfaceAddress {
         match self.address {
             IpAddr::V4(a) => fidl_fuchsia_net_stack::InterfaceAddress {
@@ -367,6 +387,7 @@
     #![allow(unused)]
     use super::*;
     use crate::portmgr::{Port, PortManager};
+    use fidl_fuchsia_net::Ipv4Address;
 
     fn create_ports() -> PortManager {
         let mut pm = PortManager::new();
@@ -740,4 +761,45 @@
             }
         );
     }
+
+    fn build_lif_subnet(
+        lifip_addr: &str,
+        expected_addr: &str,
+        prefix_len: u8,
+    ) -> (LifIpAddr, fidl_fuchsia_net::Subnet) {
+        let lifip = LifIpAddr { address: lifip_addr.parse().unwrap(), prefix: prefix_len };
+
+        let ip: IpAddr = expected_addr.parse().unwrap();
+        let expected_subnet = fidl_fuchsia_net::Subnet {
+            addr: fidl_fuchsia_net::IpAddress::Ipv4(Ipv4Address {
+                addr: match ip {
+                    std::net::IpAddr::V4(v4addr) => v4addr.octets(),
+                    std::net::IpAddr::V6(_) => panic!("unexpected ipv6 address"),
+                },
+            }),
+            prefix_len,
+        };
+        (lifip, expected_subnet)
+    }
+
+    #[test]
+    fn test_fidl_subnet_math() {
+        let (lifip, expected_subnet) = build_lif_subnet("169.254.10.10", "169.254.10.10", 32);
+        assert_eq!(lifip.to_fidl_subnet(), expected_subnet);
+
+        let (lifip, expected_subnet) = build_lif_subnet("169.254.10.10", "169.254.10.0", 24);
+        assert_eq!(lifip.to_fidl_subnet(), expected_subnet);
+
+        let (lifip, expected_subnet) = build_lif_subnet("169.254.10.10", "169.254.0.0", 16);
+        assert_eq!(lifip.to_fidl_subnet(), expected_subnet);
+
+        let (lifip, expected_subnet) = build_lif_subnet("169.254.10.10", "169.0.0.0", 8);
+        assert_eq!(lifip.to_fidl_subnet(), expected_subnet);
+
+        let (lifip, expected_subnet) = build_lif_subnet("169.254.127.254", "169.254.124.0", 22);
+        assert_eq!(lifip.to_fidl_subnet(), expected_subnet);
+
+        let (lifip, expected_subnet) = build_lif_subnet("16.25.12.25", "16.16.0.0", 12);
+        assert_eq!(lifip.to_fidl_subnet(), expected_subnet);
+    }
 }
diff --git a/src/connectivity/management/network_manager/core/src/packet_filter.rs b/src/connectivity/management/network_manager/core/src/packet_filter.rs
index 78c3fe5..67cb6cc 100644
--- a/src/connectivity/management/network_manager/core/src/packet_filter.rs
+++ b/src/connectivity/management/network_manager/core/src/packet_filter.rs
@@ -4,10 +4,12 @@
 
 //! Handles packet filtering requests for Network Manager.
 
+use crate::servicemgr::NatConfig;
 use failure::{format_err, Error};
 use fidl_fuchsia_net_filter::{self as netfilter, Direction, FilterMarker, FilterProxy, Status};
 use fidl_fuchsia_router_config as router_config;
 use fuchsia_component::client::connect_to_service;
+use std::net::IpAddr;
 
 /// Storage for this PacketFilter's attributes.
 pub struct PacketFilter {
@@ -183,6 +185,30 @@
     Ok(Some(Box::new(fidl_fuchsia_net::Subnet { addr, prefix_len })))
 }
 
+/// Parses a [`servicemgr::NatConfig`] and extracts the required fields.
+fn from_nat_config(
+    nat_config: &NatConfig,
+) -> Result<(fidl_fuchsia_net::Subnet, fidl_fuchsia_net::IpAddress, u32), Error> {
+    let src_subnet = match &nat_config.local_subnet {
+        Some(subnet) => subnet.clone().to_fidl_subnet(),
+        None => return Err(format_err!("NatConfig must have a local_subnet set")),
+    };
+    let wan_ip = match nat_config.global_ip.clone() {
+        Some(lif) => match lif.address {
+            IpAddr::V4(a) => fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address {
+                addr: a.octets(),
+            }),
+            IpAddr::V6(_) => return Err(format_err!("IPv6 is not supported")),
+        },
+        None => return Err(format_err!("NatConfig must have a global_ip set")),
+    };
+    let nicid = match nat_config.pid {
+        Some(pid) => pid.to_u32(),
+        None => return Err(format_err!("NatConfig must have a pid set")),
+    };
+    Ok((src_subnet, wan_ip, nicid))
+}
+
 /// Manages a Packet Filter connection to netstack filter service (netfilter).
 ///
 /// Mainly serves as a wrapper around the netfilter service. Converts Network Manager FIDL APIs into
@@ -232,6 +258,7 @@
     /// include in the request.
     ///
     /// # Error
+    ///
     /// If we fail to get the generation number from the netfilter service, or the result of the
     /// request to netfilter is anything other than [`netfilter::Status::Ok`] then produce an error
     /// result. Failure to convert the [`router_config::FilterRule`] to a [`netfilter::Rule`] will
@@ -255,12 +282,79 @@
             Err(e) => Err(format_err!("fidl error: {:?}", e)),
         }
     }
+
+    /// Installs a Network Address Translation (NAT) rule.
+    ///
+    /// This method calls the netfilter API to install a rule that rewrites source addresses on the
+    /// LAN side with a publicly routable IP address (SNAT). Reverse translation (DNAT) is handled
+    /// by netstack's connection state tracker.
+    ///
+    /// # Error
+    ///
+    /// If we fail to get the generation number from the netfilter service, or fail to update the
+    /// the NAT ruleset, then we'll return an error result to the caller.
+    ///
+    /// A complete [`servicemgr::NatConfig`] is required at this point. If any fields are missing,
+    /// then an appropriate error will be returned to the caller.
+    // TODO(cgibson): Currently there is no way for this to be called. Add a method to the CLI
+    // to actually enable NAT, which would allow this method to run: fxb/35788.
+    pub async fn update_nat(&self, nat_config: &mut NatConfig) -> Result<(), Error> {
+        info!("Received request to enable NAT");
+        let (src_subnet, wan_ip, nicid) = from_nat_config(nat_config)
+            .map_err(|e| format_err!("Failed to parse NAT config: {:?}", e))?;
+
+        // TODO(cgibson): NAT should work on IP packets, we shouldn't need to provide a proto field
+        // here. This is a bug: fxb/35950.
+        //
+        // Ultimately this should all collapse into a single rule.
+        let mut nat_rules = vec![
+            netfilter::Nat {
+                proto: netfilter::SocketProtocol::Tcp,
+                src_subnet: src_subnet.clone(),
+                new_src_addr: wan_ip.clone(),
+                nic: nicid,
+            },
+            netfilter::Nat {
+                proto: netfilter::SocketProtocol::Udp,
+                src_subnet: src_subnet.clone(),
+                new_src_addr: wan_ip.clone(),
+                nic: nicid,
+            },
+            netfilter::Nat {
+                proto: netfilter::SocketProtocol::Icmp,
+                src_subnet: src_subnet.clone(),
+                new_src_addr: wan_ip.clone(),
+                nic: nicid,
+            },
+        ];
+
+        // Since we discard any existing NAT rules here, this is a pure "update-only" situation.
+        let generation: u32 = match self.filter_svc.get_nat_rules().await {
+            Ok((_, generation, Status::Ok)) => generation,
+            Ok((_, _, status)) => {
+                return Err(format_err!(
+                    "Failed to get generation number! Status was: {:?}",
+                    status
+                ))
+            }
+            Err(e) => return Err(format_err!("fidl error: {:?}", e)),
+        };
+        // TODO(cgibson): When we add the CLI command to enable/disable NAT, we will be able to use
+        // our CLI integration framework to test this.
+        match self.filter_svc.update_nat_rules(&mut nat_rules.iter_mut(), generation).await {
+            Ok(Status::Ok) => Ok(()),
+            Ok(status) => Err(format_err!("failed to set NAT state: {:?}", status)),
+            Err(e) => Err(format_err!("fidl error: {:?}", e)),
+        }
+    }
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
 
+    use crate::hal;
+    use crate::lifmgr::LifIpAddr;
     use fidl_fuchsia_net::IpAddress::Ipv4;
     use fidl_fuchsia_net::Ipv4Address;
     use fidl_fuchsia_router_config::{
@@ -430,4 +524,85 @@
         let both = from_protocol(Some(router_config::Protocol::Both));
         assert_eq!(both.is_none(), true);
     }
+
+    #[test]
+    fn test_from_nat_config() {
+        let expected_subnet_lifip =
+            LifIpAddr { address: "192.168.0.0".parse().unwrap(), prefix: 24 };
+        let expected_lifip = LifIpAddr { address: "17.0.0.1".parse().unwrap(), prefix: 32 };
+        let ip: IpAddr = "192.168.0.0".parse().unwrap();
+        let expected_subnet = fidl_fuchsia_net::Subnet {
+            addr: fidl_fuchsia_net::IpAddress::Ipv4(Ipv4Address {
+                addr: match ip {
+                    std::net::IpAddr::V4(v4addr) => v4addr.octets(),
+                    std::net::IpAddr::V6(_) => panic!("unexpected ipv6 address"),
+                },
+            }),
+            prefix_len: 24,
+        };
+        let expected_pid = hal::PortId::from(1);
+
+        // should fail if all fields are set to None.
+        let mut nat_config = NatConfig {
+            enable: true, // not being validated, so we don't care what this is set to.
+            local_subnet: None,
+            global_ip: None,
+            pid: None,
+        };
+        let result = from_nat_config(&mut nat_config);
+        assert_eq!(result.is_err(), true);
+
+        // should fail if local_subnet is missing.
+        let mut nat_config = NatConfig {
+            enable: true, // not being validated, so we don't care what this is set to.
+            local_subnet: None,
+            global_ip: Some(expected_lifip.clone()),
+            pid: Some(expected_pid),
+        };
+        let result = from_nat_config(&mut nat_config);
+        assert_eq!(result.is_err(), true);
+
+        // should fail if global_ip is missing.
+        let mut nat_config = NatConfig {
+            enable: true, // not being validated, so we don't care what this is set to.
+            local_subnet: Some(expected_subnet_lifip.clone()),
+            global_ip: None,
+            pid: Some(expected_pid),
+        };
+        let result = from_nat_config(&mut nat_config);
+        assert_eq!(result.is_err(), true);
+
+        // should fail if pid is missing.
+        let mut nat_config = NatConfig {
+            enable: true, // not being validated, so we don't care what this is set to.
+            local_subnet: Some(expected_subnet_lifip.clone()),
+            global_ip: Some(expected_lifip.clone()),
+            pid: None,
+        };
+        let result = from_nat_config(&mut nat_config);
+        assert_eq!(result.is_err(), true);
+
+        // should pass when all fields are present.
+        let expected_wan_ip = fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address {
+            addr: match expected_lifip.clone().address {
+                IpAddr::V4(v4addr) => v4addr.octets(),
+                IpAddr::V6(_) => panic!("unexpected ipv6 address"),
+            },
+        });
+        let mut nat_config = NatConfig {
+            enable: true, // not being validated, so we don't care what this is set to.
+            local_subnet: Some(expected_subnet_lifip.clone()),
+            global_ip: Some(expected_lifip.clone()),
+            pid: Some(expected_pid),
+        };
+        let result = from_nat_config(&mut nat_config);
+        assert_eq!(result.is_ok(), true);
+        let (actual_subnet, actual_wan_ip, actual_nicid) = match result {
+            Ok((s, w, n)) => (s, w, n),
+            Err(e) => panic!("NatConfig test failed: {:?}", e),
+        };
+        assert_eq!(expected_subnet, actual_subnet);
+        assert_eq!(expected_wan_ip, actual_wan_ip);
+        assert_eq!(expected_pid.to_u32(), actual_nicid);
+    }
 }
diff --git a/src/connectivity/management/network_manager/core/src/servicemgr.rs b/src/connectivity/management/network_manager/core/src/servicemgr.rs
index 70133f1..c06701f 100644
--- a/src/connectivity/management/network_manager/core/src/servicemgr.rs
+++ b/src/connectivity/management/network_manager/core/src/servicemgr.rs
@@ -13,10 +13,22 @@
 // TODO(dpradilla): remove allow
 #![allow(dead_code)]
 
-use crate::lifmgr::LIF;
+use crate::hal::PortId;
+use crate::lifmgr::{LifIpAddr, LIF};
 use crate::{error, UUID};
 use std::collections::HashSet;
 
+pub struct NatConfig {
+    pub enable: bool,
+    pub local_subnet: Option<LifIpAddr>,
+    pub global_ip: Option<LifIpAddr>,
+    pub pid: Option<PortId>,
+}
+
+struct SecurityFeatures {
+    nat: NatConfig,
+}
+
 /// `Manager` keeps track of interfaces where a service is enabled
 /// and verifies conflicting services are not enabled.
 pub struct Manager {
@@ -24,12 +36,20 @@
     dhcp_server: std::collections::HashSet<UUID>,
     // dhcp_client has collection of interfaces DHCP dhcp_client is enabled.
     dhcp_client: std::collections::HashSet<UUID>,
+    // security features.
+    security: SecurityFeatures,
 }
 
 impl Manager {
     //! Creates a new Manager.
     pub fn new() -> Self {
-        Manager { dhcp_server: HashSet::new(), dhcp_client: HashSet::new() }
+        Manager {
+            dhcp_server: HashSet::new(),
+            dhcp_client: HashSet::new(),
+            security: SecurityFeatures {
+                nat: NatConfig { enable: false, local_subnet: None, global_ip: None, pid: None },
+            },
+        }
     }
     /// `enable_server` sets dhcp dhcp_server as enabled on indicated interface.
     pub fn enable_server(&mut self, lif: &LIF) -> error::Result<bool> {
@@ -61,12 +81,37 @@
     pub fn is_client_enabled(&mut self, lif: &LIF) -> bool {
         self.dhcp_client.contains(&lif.id().uuid)
     }
+    /// `is_nat_enabled` returns true if NAT is enabled.
+    pub fn is_nat_enabled(&mut self) -> bool {
+        self.security.nat.enable
+    }
+    /// `enable_nat` method indicates that NAT is now enabled.
+    pub fn enable_nat(&mut self) {
+        self.security.nat.enable = true;
+    }
+    /// `disable_nat` method indicates that NAT is now disabled.
+    pub fn disable_nat(&mut self) {
+        self.security.nat.enable = false;
+    }
+    /// `get_nat_config` returns the current NAT configuration.
+    pub fn get_nat_config(&mut self) -> &mut NatConfig {
+        &mut self.security.nat
+    }
+    /// `set_local_subnet_nat` sets the local subnet to be NATed.
+    pub fn set_local_subnet_nat(&mut self, s: LifIpAddr) {
+        self.security.nat.local_subnet = Some(s);
+    }
+    /// `set_global_ip_nat` sets the global IP to be NATed.
+    pub fn set_global_ip_nat(&mut self, g: LifIpAddr, p: PortId) {
+        self.security.nat.global_ip = Some(g);
+        self.security.nat.pid = Some(p);
+    }
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::lifmgr::{LIFType, LIF};
+    use crate::lifmgr::{LIFType, LifIpAddr, LIF};
     use crate::portmgr::PortId;
     use crate::portmgr::{Port, PortManager};
 
@@ -191,4 +236,26 @@
         assert!(!m.enable_client(&l).unwrap_or(false));
         assert_eq!(m.dhcp_client.len(), 0);
     }
+
+    #[test]
+    fn test_nat_config() {
+        let mut m = Manager::new();
+
+        assert_eq!(m.get_nat_config().enable, false);
+        assert_eq!(m.is_nat_enabled(), false);
+
+        m.enable_nat();
+        assert_eq!(m.is_nat_enabled(), true);
+
+        m.disable_nat();
+        assert_eq!(m.is_nat_enabled(), false);
+
+        let lifip = LifIpAddr { address: "169.254.0.1".parse().unwrap(), prefix: 32 };
+        m.set_local_subnet_nat(lifip.clone());
+        assert_eq!(m.get_nat_config().local_subnet.as_ref().unwrap(), &lifip);
+
+        let pid = PortId::from(1);
+        m.set_global_ip_nat(lifip.clone(), pid);
+        assert_eq!(m.get_nat_config().pid.unwrap(), pid);
+    }
 }
diff --git a/src/connectivity/management/network_manager/src/eventloop.rs b/src/connectivity/management/network_manager/src/eventloop.rs
index ae773d4..58af16a 100644
--- a/src/connectivity/management/network_manager/src/eventloop.rs
+++ b/src/connectivity/management/network_manager/src/eventloop.rs
@@ -82,7 +82,6 @@
 pub struct EventLoop {
     event_recv: Option<mpsc::UnboundedReceiver<Event>>,
     device: DeviceState,
-    packet_filter: PacketFilter,
 }
 
 impl EventLoop {
@@ -96,10 +95,10 @@
         let packet_filter = PacketFilter::start()
             .context("network_manager failed to start packet filter!")
             .unwrap();
+
         Ok(EventLoop {
             event_recv: Some(event_recv),
-            device: DeviceState::new(netcfg),
-            packet_filter,
+            device: DeviceState::new(netcfg, packet_filter),
         })
     }
 
@@ -109,6 +108,7 @@
             self.event_recv.take().unwrap(),
             self.device.take_event_stream().map(|e| Event::StackObservable(e)),
         );
+
         loop {
             match select_stream.next().await {
                 Some(Event::FidlRouterAdminEvent(req)) => {
@@ -277,7 +277,7 @@
             }
             RouterAdminRequest::SetFilter { rule, responder } => {
                 let r = self
-                    .packet_filter
+                    .device
                     .set_filter(rule)
                     .await
                     .context("Error installing new packet filter rule");
@@ -553,8 +553,7 @@
                 responder.send(None, not_supported!())
             }
             RouterStateRequest::GetFilters { responder } => {
-                let result =
-                    self.packet_filter.get_filters().await.context("Error getting filters");
+                let result = self.device.get_filters().await.context("Error getting filters");
                 let mut filter_rules = Vec::new();
                 match result {
                     Ok(f) => {