blob: e975b78d7312f9db9c659a1eee7a8c1eb829658c [file] [log] [blame]
// 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.
//! Handles packet filtering requests for Router Manager.
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;
/// Storage for this PacketFilter's attributes.
pub struct PacketFilter {
filter_svc: FilterProxy,
}
/// Parses a [`netfilter::Rule`] into a [`router_config::FilterRule`].
fn to_filter_rule(rule: netfilter::Rule) -> Result<router_config::FilterRule, Error> {
// This is a good candidate to refactor to use TryInto/TryFrom.
Ok(router_config::FilterRule {
element: router_config::Id { uuid: [0; 16], version: 0 },
action: to_filter_action(rule.action),
selector: router_config::FlowSelector {
src_address: to_cidr_address(rule.src_subnet),
dst_address: to_cidr_address(rule.dst_subnet),
// TODO(cgibson): netfilter2 does not currently support port ranges (NET-2182)
// Put the `uint16` from the `netfilter::Rule` directly into the
// `router_config::PortRange::from` field for now.
src_ports: to_port_range(rule.src_port, rule.src_port),
dst_ports: to_port_range(rule.dst_port, rule.dst_port),
protocol: to_protocol(rule.proto),
},
})
}
/// Parses a [`netfilter::Action`] and turns it into a [`router_config::FilterAction`].
fn to_filter_action(action: netfilter::Action) -> router_config::FilterAction {
match action {
netfilter::Action::Pass => router_config::FilterAction::Allow,
// TODO(cgibson): What is our default drop policy? Should we gloss over the difference
// to users of the Router Manager or should it become a parse error?
netfilter::Action::Drop => router_config::FilterAction::Drop,
netfilter::Action::DropReset => router_config::FilterAction::Drop,
}
}
/// Parses a [`fidl_fuchsia_net::Subnet`] and turns it into a [`router_config::CidrAddress`].
fn to_cidr_address(
subnet: Option<Box<fidl_fuchsia_net::Subnet>>,
) -> Option<router_config::CidrAddress> {
match subnet {
Some(s) => Some(router_config::CidrAddress {
address: Some(s.addr),
prefix_length: Some(s.prefix_len),
}),
None => None,
}
}
// TODO(cgibson): netfilter2 currently does not support port ranges (NET-2182)
fn to_port_range(from: u16, to: u16) -> Option<Vec<router_config::PortRange>> {
Some(vec![router_config::PortRange { from, to }])
}
/// Parses a [`netfilter::SocketProtocol`] to a [`router_config::Protocol`].
///
/// [`netfilter::SocketProtocol`] cannot represent multiple protocols at once (i.e: It does not have
/// a representation for "Both", or "All", etc.).
fn to_protocol(proto: netfilter::SocketProtocol) -> Option<router_config::Protocol> {
match proto {
netfilter::SocketProtocol::Tcp => Some(router_config::Protocol::Tcp),
netfilter::SocketProtocol::Udp => Some(router_config::Protocol::Udp),
_ => None,
}
}
/// Parses a [`router_config::FilterRule`] into a [`netfilter::Rule`].
fn from_filter_rule(rule: router_config::FilterRule) -> Result<Vec<netfilter::Rule>, Error> {
let mut netfilter_rules = Vec::new();
let netfilter_rule = gen_netfilter_rule(&rule)?;
match from_protocol(rule.selector.protocol) {
Some(proto) => {
netfilter_rules.push(netfilter::Rule { proto, ..netfilter_rule });
}
None => {
netfilter_rules
.push(netfilter::Rule { proto: netfilter::SocketProtocol::Tcp, ..netfilter_rule });
// FIDL doesn't have the `Clone` trait on `netfilter::Rule`'s, so we have to resort to
// parsing the rule again to work around netfilter's lack of a `Both` definition in it's
// `SocketProtocol` API. Luckily it's a fairly trivial operation.
let udp_netfilter_rule = gen_netfilter_rule(&rule)?;
netfilter_rules.push(netfilter::Rule {
proto: netfilter::SocketProtocol::Udp,
..udp_netfilter_rule
});
}
}
Ok(netfilter_rules)
}
/// Takes a [`router_config::FilterRule`] and converts it into a [`netfilter::Rule`].
fn gen_netfilter_rule(rule: &router_config::FilterRule) -> Result<netfilter::Rule, Error> {
// This is a good candidate to refactor to use TryInto/TryFrom.
let src_port: u16 = match from_port_range(&rule.selector.src_ports) {
Ok(port) => match port {
Some(p) => p,
// TODO(cgibson): I think `src_port` not being optional is a bug in the netfilter
// FIDL API?
None => 0,
},
Err(e) => return Err(format_err!("Invalid source port: {:?}", e)),
};
let dst_port: u16 = match from_port_range(&rule.selector.dst_ports) {
Ok(port) => match port {
Some(p) => p,
// TODO(cgibson): I think `dst_port` not being optional is a bug in the netfilter
// FIDL API?
None => 0,
},
Err(e) => return Err(format_err!("Invalid destination port: {:?}", e)),
};
Ok(netfilter::Rule {
action: from_filter_action(&rule.action),
// TODO(cgibson): We need a way to specify the direction of traffic.
direction: Direction::Incoming,
dst_subnet: from_cidr_address(&rule.selector.dst_address)?,
dst_subnet_invert_match: false,
keep_state: true,
log: false,
quick: false,
src_subnet: from_cidr_address(&rule.selector.src_address)?,
src_subnet_invert_match: false,
src_port,
dst_port,
// TODO(cgibson): NIC 0 applies to *all* interfaces, however that doesn't seem like what we
// want to do at all. The `router_config::FilterRule` FIDL API doesn't specify interface
// names, and in fact should probably be a *property* of the WAN or LAN router_config FIDL
// APIs since we can have packet filters installed on every interface.
nic: 0,
// The proto field requires further processing, just set any value for now.
proto: netfilter::SocketProtocol::Ip,
})
}
/// Parses a [`router_config::Protocol`] and returns the equivalent [`netfilter::SocketProtocol`].
///
/// [`netfilter::SocketProtocol`] cannot represent multiple protocols at once (i.e: It does not have
/// a representation for "Both", or "All", etc.). "Both" is also the default when no protocol is
/// provided. Return `None` as the representation of "Both".
fn from_protocol(proto: Option<router_config::Protocol>) -> Option<netfilter::SocketProtocol> {
match proto {
Some(proto) => match proto {
router_config::Protocol::Tcp => Some(netfilter::SocketProtocol::Tcp),
router_config::Protocol::Udp => Some(netfilter::SocketProtocol::Udp),
router_config::Protocol::Both => None,
},
None => None,
}
}
/// Parses a [`router_config::FilterAction`] and turns it into a [`netfilter::Action`]
fn from_filter_action(action: &router_config::FilterAction) -> netfilter::Action {
match action {
router_config::FilterAction::Allow => netfilter::Action::Pass,
// TODO(cgibson): What is our default drop policy? Should we gloss over the difference
// to users of the Router Manager or should it become a parse error?
router_config::FilterAction::Drop => netfilter::Action::Drop,
}
}
/// Parses a [`router_config::PortRange`] and turns it into a `u16` result.
fn from_port_range(range: &Option<Vec<router_config::PortRange>>) -> Result<Option<u16>, Error> {
// TODO(cgibson): netfilter2 does not currently support port ranges (NET-2182)
// For now, we'll put the first `router_config::PortRange`'s `from` value into
// `netfilter::Rule`'s src or dst port field.
match range {
Some(v) => Ok(Some(v[0].from)),
None => Ok(None),
}
}
/// Parses a [`router_config::CidrAddress`] and turns it into a [`fidl_fuchsia_net::Subnet`].
fn from_cidr_address(
cidr_address: &Option<router_config::CidrAddress>,
) -> Result<Option<Box<fidl_fuchsia_net::Subnet>>, Error> {
let (addr, prefix_len) = match cidr_address {
Some(cidr_addr) => {
let ip = match cidr_addr.address {
Some(a) => a,
None => return Err(format_err!("CidrAddress is missing an IPv4 address")),
};
let prefix = match cidr_addr.prefix_length {
Some(p) => p,
None => return Err(format_err!("CidrAddress is missing the prefix length")),
};
(ip, prefix)
}
None => return Err(format_err!("CidrAddress does not have an IpAddress field")),
};
Ok(Some(Box::new(fidl_fuchsia_net::Subnet { addr, prefix_len })))
}
/// Manages a Packet Filter connection to netstack filter service (netfilter).
///
/// Mainly serves as a wrapper around the netfilter service. Converts Router Manager FIDL APIs into
/// something that the netfilter service can understand. Finally converts the netfilter response
/// back into a Router Manager FIDL for consumption by the caller.
impl PacketFilter {
/// Starts a new instance of a PacketFilter.
pub fn start() -> Result<Self, Error> {
let filter_svc = connect_to_service::<FilterMarker>()?;
info!("Connected to filter service");
Ok(PacketFilter { filter_svc })
}
/// Returns the current set of netfilter packet filters.
///
/// Using the existing handle to the netfilter service, request the set of packet filter rules
/// and converts them to a vector of [`router_config::FilterRule`]'s.
///
/// # Error
///
/// If the response from netfilter is anything other than [`netfilter::Status::Ok`] then
/// produce an error result. Failure to convert from the [`netfilter::Rule`] to a
/// [`router_config::FilterRule`] produces an error result to the caller.
pub async fn get_filters(&self) -> Result<Vec<router_config::FilterRule>, Error> {
info!("Received request to get all active packet filters");
let netfilter_rules: Vec<netfilter::Rule> = match self.filter_svc.get_rules().await {
Ok((rules, _, Status::Ok)) => rules,
Ok((_, _, status)) => {
return Err(format_err!("Failed to get filters: Status was: {:?}", status))
}
Err(e) => return Err(format_err!("fidl error: {:?}", e)),
};
netfilter_rules
.into_iter()
.map(|rule| match to_filter_rule(rule) {
Ok(f) => Ok(f),
Err(e) => Err(format_err!("Failed to parse filter rule: {:?}", e)),
})
.collect::<Result<Vec<router_config::FilterRule>, Error>>()
}
/// Installs a new packet filter rule.
///
/// We convert the [`router_config::FilterRule`] and parse it into a [`netfilter::Rule`] that we
/// can send on to the netfilter service. We also need to get a `generation` number from to
/// 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
/// produce an error result to the caller.
pub async fn set_filter(&self, rule: router_config::FilterRule) -> Result<(), Error> {
info!("Received request to add new packet filter rule");
let generation: u32 = match self.filter_svc.get_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)),
};
let mut netfilter_rules = from_filter_rule(rule)?;
match self.filter_svc.update_rules(&mut netfilter_rules.iter_mut(), generation).await {
Ok(Status::Ok) => Ok(()),
Ok(status) => Err(format_err!("Failed to add new packet filter: {:?}", status)),
Err(e) => Err(format_err!("fidl error: {:?}", e)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_net::IpAddress::Ipv4;
use fidl_fuchsia_net::Ipv4Address;
use fidl_fuchsia_router_config::{
CidrAddress, FilterAction, FilterRule, FlowSelector, Id, PortRange, Protocol,
};
use std::net::IpAddr;
#[test]
fn test_convert_subnet_to_cidr_address() {
let addr = [169, 254, 1, 1];
let prefix = 32;
let test_ip = fidl_fuchsia_net::IpAddress::Ipv4(fidl_fuchsia_net::Ipv4Address { addr });
let test_subnet = fidl_fuchsia_net::Subnet { addr: test_ip, prefix_len: prefix };
// Convert our `test_subnet` to a `CidrAddress`.
let cidr_addr = to_cidr_address(Some(Box::new(test_subnet))).unwrap();
// Check that it suceeded: We can't access the IpAddress octets directly, so we need to use
// a match statement.
match cidr_addr.address.unwrap() {
fidl_fuchsia_net::IpAddress::Ipv4(v4addr) => {
assert_eq!(v4addr.addr, addr);
}
_ => panic!("Failed to match CidrAddress!"),
}
match cidr_addr.prefix_length {
Some(p) => assert_eq!(p, prefix),
None => panic!("CidrAddress prefix length is None, expecting: {}", prefix),
}
}
#[test]
fn test_to_port_range() {
let port_ranges = to_port_range(1000, 2000);
assert_eq!(port_ranges.is_some(), true);
let p = port_ranges.unwrap();
assert_eq!(p[0].from, 1000);
assert_eq!(p[0].to, 2000);
}
#[test]
fn test_to_protocol() {
let tcp = to_protocol(netfilter::SocketProtocol::Tcp);
assert_eq!(tcp.unwrap(), router_config::Protocol::Tcp);
let udp = to_protocol(netfilter::SocketProtocol::Udp);
assert_eq!(udp.unwrap(), router_config::Protocol::Udp);
let icmpv6 = to_protocol(netfilter::SocketProtocol::Icmpv6);
assert_eq!(icmpv6.is_none(), true);
}
#[test]
fn test_to_filter_rule() {
let ip: IpAddr = "169.254.0.1".parse().unwrap();
let src_subnet = Some(Box::new(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: 32,
}));
let dst_subnet = src_subnet.clone();
let test_netfilter_rule = netfilter::Rule {
action: netfilter::Action::Pass,
direction: netfilter::Direction::Incoming,
quick: false,
src_subnet,
src_subnet_invert_match: false,
src_port: 1024,
dst_subnet,
dst_subnet_invert_match: false,
dst_port: 80,
proto: netfilter::SocketProtocol::Tcp,
nic: 0,
log: false,
keep_state: true,
};
let expected = FilterRule {
element: Id { uuid: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], version: 0 },
action: FilterAction::Allow,
selector: FlowSelector {
src_address: Some(CidrAddress {
address: Some(Ipv4(Ipv4Address { addr: [169, 254, 0, 1] })),
prefix_length: Some(32),
}),
src_ports: Some([PortRange { from: 1024, to: 1024 }].to_vec()),
dst_address: Some(CidrAddress {
address: Some(Ipv4(Ipv4Address { addr: [169, 254, 0, 1] })),
prefix_length: Some(32),
}),
dst_ports: Some([PortRange { from: 80, to: 80 }].to_vec()),
protocol: Some(Protocol::Tcp),
},
};
let actual = to_filter_rule(test_netfilter_rule);
assert_eq!(expected, actual.unwrap());
}
#[test]
fn test_from_filter_rule() {
let ip: IpAddr = "169.254.0.1".parse().unwrap();
let src_subnet = Some(Box::new(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: 32,
}));
let dst_subnet = src_subnet.clone();
let expected = netfilter::Rule {
action: netfilter::Action::Pass,
// TODO(cgibson): Temporary workaround for the lack of a "direction" field in
// `FilterRule`. The conversion logic assumes that if a direction is not specified,
// then we will default it to `Incoming`.
direction: netfilter::Direction::Incoming,
quick: false,
src_subnet,
src_subnet_invert_match: false,
src_port: 1024,
dst_subnet,
dst_subnet_invert_match: false,
dst_port: 80,
proto: netfilter::SocketProtocol::Tcp,
nic: 0,
log: false,
keep_state: true,
};
let filter_rule = FilterRule {
element: Id { uuid: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], version: 0 },
action: FilterAction::Allow,
selector: FlowSelector {
src_address: Some(CidrAddress {
address: Some(Ipv4(Ipv4Address { addr: [169, 254, 0, 1] })),
prefix_length: Some(32),
}),
src_ports: Some([PortRange { from: 1024, to: 1024 }].to_vec()),
dst_address: Some(CidrAddress {
address: Some(Ipv4(Ipv4Address { addr: [169, 254, 0, 1] })),
prefix_length: Some(32),
}),
dst_ports: Some([PortRange { from: 80, to: 80 }].to_vec()),
protocol: Some(Protocol::Tcp),
},
};
let actual = from_filter_rule(filter_rule).unwrap();
assert_eq!(1, actual.len());
assert_eq!(expected, actual[0]);
}
#[test]
fn test_from_protocol() {
let tcp = from_protocol(Some(router_config::Protocol::Tcp)).unwrap();
assert_eq!(netfilter::SocketProtocol::Tcp, tcp);
let udp = from_protocol(Some(router_config::Protocol::Udp)).unwrap();
assert_eq!(netfilter::SocketProtocol::Udp, udp);
let both = from_protocol(Some(router_config::Protocol::Both));
assert_eq!(both.is_none(), true);
}
}