blob: 0a3f7bf47213a34d9ac31d232543cd9800585e4b [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.
use pest::{iterators::Pair, Parser};
use fidl_fuchsia_hardware_network as fhnet;
use fidl_fuchsia_net as net;
use fidl_fuchsia_net_filter_deprecated as filter;
use crate::{
grammar::{Error, FilterRuleParser, InvalidReason, Rule},
util,
};
fn parse_action(pair: Pair<'_, Rule>) -> filter::Action {
assert_eq!(pair.as_rule(), Rule::action);
match pair.into_inner().next().unwrap().as_rule() {
Rule::pass => filter::Action::Pass,
Rule::drop => filter::Action::Drop,
Rule::dropreset => filter::Action::DropReset,
_ => unreachable!("action must be one of (pass|drop|dropreset)"),
}
}
fn parse_direction(pair: Pair<'_, Rule>) -> filter::Direction {
assert_eq!(pair.as_rule(), Rule::direction);
match pair.into_inner().next().unwrap().as_rule() {
Rule::incoming => filter::Direction::Incoming,
Rule::outgoing => filter::Direction::Outgoing,
_ => unreachable!("direction must be one of (in|out)"),
}
}
fn parse_proto(pair: Pair<'_, Rule>) -> filter::SocketProtocol {
assert_eq!(pair.as_rule(), Rule::proto);
match pair.into_inner().next() {
Some(pair) => match pair.as_rule() {
Rule::tcp => filter::SocketProtocol::Tcp,
Rule::udp => filter::SocketProtocol::Udp,
Rule::icmp => filter::SocketProtocol::Icmp,
_ => unreachable!("protocol must be one of (tcp|udp|icmp)"),
},
None => filter::SocketProtocol::Any,
}
}
fn parse_devclass(pair: Pair<'_, Rule>) -> filter::DeviceClass {
assert_eq!(pair.as_rule(), Rule::devclass);
match pair.into_inner().next() {
Some(pair) => match pair.as_rule() {
Rule::virt => filter::DeviceClass::Match_(fhnet::DeviceClass::Virtual),
Rule::ethernet => filter::DeviceClass::Match_(fhnet::DeviceClass::Ethernet),
Rule::wlan => filter::DeviceClass::Match_(fhnet::DeviceClass::Wlan),
Rule::ppp => filter::DeviceClass::Match_(fhnet::DeviceClass::Ppp),
Rule::bridge => filter::DeviceClass::Match_(fhnet::DeviceClass::Bridge),
Rule::ap => filter::DeviceClass::Match_(fhnet::DeviceClass::WlanAp),
_ => unreachable!("devclass must be one of (virt|ethernet|wlan|ppp|bridge|ap)"),
},
None => filter::DeviceClass::Any(filter::Empty {}),
}
}
fn parse_src(
pair: Pair<'_, Rule>,
) -> Result<(Option<Box<net::Subnet>>, bool, filter::PortRange), Error> {
assert_eq!(pair.as_rule(), Rule::src);
parse_src_or_dst(pair)
}
fn parse_dst(
pair: Pair<'_, Rule>,
) -> Result<(Option<Box<net::Subnet>>, bool, filter::PortRange), Error> {
assert_eq!(pair.as_rule(), Rule::dst);
parse_src_or_dst(pair)
}
fn parse_src_or_dst(
pair: Pair<'_, Rule>,
) -> Result<(Option<Box<net::Subnet>>, bool, filter::PortRange), Error> {
let mut inner = pair.into_inner();
match inner.next() {
Some(pair) => match pair.as_rule() {
Rule::invertible_subnet => {
let (subnet, invert_match) = util::parse_invertible_subnet(pair)?;
let port = match inner.next() {
Some(pair) => parse_port_range(pair)?,
None => filter::PortRange { start: 0, end: 0 },
};
Ok((Some(Box::new(subnet)), invert_match, port))
}
Rule::port_range => Ok((None, false, parse_port_range(pair)?)),
_ => unreachable!("src or dst must be either an invertible subnet or port range"),
},
None => Ok((None, false, filter::PortRange { start: 0, end: 0 })),
}
}
fn parse_port_range(pair: Pair<'_, Rule>) -> Result<filter::PortRange, Error> {
assert_eq!(pair.as_rule(), Rule::port_range);
let mut inner = pair.into_inner();
let pair = inner.next().unwrap();
match pair.as_rule() {
Rule::port => {
let port_num = util::parse_port_num(inner.next().unwrap())?;
Ok(filter::PortRange { start: port_num, end: port_num })
}
Rule::range => {
let port_start = util::parse_port_num(inner.next().unwrap())?;
let port_end = util::parse_port_num(inner.next().unwrap())?;
Ok(filter::PortRange { start: port_start, end: port_end })
}
_ => unreachable!("port range must be either a single port, or a port range"),
}
}
fn parse_log(pair: Pair<'_, Rule>) -> bool {
assert_eq!(pair.as_rule(), Rule::log);
pair.as_str() == "log"
}
fn parse_state(pair: Pair<'_, Rule>) -> bool {
assert_eq!(pair.as_rule(), Rule::state);
let mut inner = pair.into_inner();
match inner.next() {
Some(pair) => {
assert_eq!(pair.as_rule(), Rule::state_adj);
match pair.as_str() {
"no" => false,
"keep" => true,
_ => unreachable!("state must be either (no|keep)"),
}
}
None => false, // no state by default
}
}
fn parse_rule(pair: Pair<'_, Rule>) -> Result<filter::Rule, Error> {
assert_eq!(pair.as_rule(), Rule::rule);
let mut pairs = pair.into_inner();
let action = parse_action(pairs.next().unwrap());
let direction = parse_direction(pairs.next().unwrap());
let proto = parse_proto(pairs.next().unwrap());
let device_class = parse_devclass(pairs.next().unwrap());
let (src_subnet, src_subnet_invert_match, src_port_range) = parse_src(pairs.next().unwrap())?;
let (dst_subnet, dst_subnet_invert_match, dst_port_range) = parse_dst(pairs.next().unwrap())?;
let log = parse_log(pairs.next().unwrap());
let keep_state = parse_state(pairs.next().unwrap());
Ok(filter::Rule {
action,
direction,
proto,
src_subnet,
src_subnet_invert_match,
src_port_range,
dst_subnet,
dst_subnet_invert_match,
dst_port_range,
nic: 0, // TODO: Support NICID (currently always 0 (= any))
log,
keep_state,
device_class,
})
}
fn parse_nat(pair: Pair<'_, Rule>) -> Result<filter::Nat, Error> {
assert_eq!(pair.as_rule(), Rule::nat);
let mut pairs = pair.into_inner();
let proto = parse_proto(pairs.next().unwrap());
let src_subnet = util::parse_subnet(pairs.next().unwrap())?;
Ok(filter::Nat {
proto,
src_subnet,
outgoing_nic: 0, // TODO: Support NICID.
})
}
fn parse_rdr(pair: Pair<'_, Rule>) -> Result<filter::Rdr, Error> {
assert_eq!(pair.as_rule(), Rule::rdr);
let mut pairs = pair.into_inner();
let proto = parse_proto(pairs.next().unwrap());
let dst_addr = util::parse_ipaddr(pairs.next().unwrap())?;
let dst_port_range = parse_port_range(pairs.next().unwrap())?;
let new_dst_addr = util::parse_ipaddr(pairs.next().unwrap())?;
let new_dst_port_range = parse_port_range(pairs.next().unwrap())?;
Ok(filter::Rdr {
proto,
dst_addr,
dst_port_range,
new_dst_addr,
new_dst_port_range,
nic: 0, // TODO: Support NICID.
})
}
fn validate_port_range(range: &filter::PortRange) -> Result<(), Error> {
if (range.start == 0 && range.end != 0) || range.start > range.end {
return Err(Error::Invalid(InvalidReason::InvalidPortRange));
}
Ok(())
}
fn port_range_length(range: &filter::PortRange) -> Result<u16, Error> {
let () = validate_port_range(&range)?;
Ok(range.end - range.start)
}
fn validate_rule(rule: &filter::Rule) -> Result<(), Error> {
if let (Some(src_subnet), Some(dst_subnet)) = (&rule.src_subnet, &rule.dst_subnet) {
if !util::ip_version_eq(&src_subnet.addr, &dst_subnet.addr) {
return Err(Error::Invalid(InvalidReason::MixedIPVersions));
}
}
let () = validate_port_range(&rule.src_port_range)?;
let () = validate_port_range(&rule.dst_port_range)?;
Ok(())
}
fn validate_rdr(rdr: &filter::Rdr) -> Result<(), Error> {
if !util::ip_version_eq(&rdr.dst_addr, &rdr.new_dst_addr) {
return Err(Error::Invalid(InvalidReason::MixedIPVersions));
}
if port_range_length(&rdr.dst_port_range)? != port_range_length(&rdr.new_dst_port_range)? {
return Err(Error::Invalid(InvalidReason::PortRangeLengthMismatch));
}
Ok(())
}
pub fn parse_str_to_rules(line: &str) -> Result<Vec<filter::Rule>, Error> {
let mut pairs = FilterRuleParser::parse(Rule::rules, &line).map_err(Error::Pest)?;
let mut rules = Vec::new();
for filter_rule in pairs.next().unwrap().into_inner() {
match filter_rule.as_rule() {
Rule::rule => {
let rule = parse_rule(filter_rule)?;
let () = validate_rule(&rule)?;
rules.push(rule);
}
Rule::EOI => (),
_ => unreachable!("rule must only have a rule case"),
}
}
Ok(rules)
}
pub fn parse_str_to_nat_rules(line: &str) -> Result<Vec<filter::Nat>, Error> {
let mut pairs = FilterRuleParser::parse(Rule::nat_rules, &line).map_err(Error::Pest)?;
let mut nat_rules = Vec::new();
for filter_rule in pairs.next().unwrap().into_inner() {
match filter_rule.as_rule() {
Rule::nat => {
let nat = parse_nat(filter_rule)?;
nat_rules.push(nat);
}
Rule::EOI => (),
_ => unreachable!("nat must only have a nat case"),
}
}
Ok(nat_rules)
}
pub fn parse_str_to_rdr_rules(line: &str) -> Result<Vec<filter::Rdr>, Error> {
let mut pairs = FilterRuleParser::parse(Rule::rdr_rules, &line).map_err(Error::Pest)?;
let mut rdr_rules = Vec::new();
for filter_rule in pairs.next().unwrap().into_inner() {
match filter_rule.as_rule() {
Rule::rdr => {
let rdr = parse_rdr(filter_rule)?;
let () = validate_rdr(&rdr)?;
rdr_rules.push(rdr);
}
Rule::EOI => (),
_ => unreachable!("rdr must only have a rdr case"),
}
}
Ok(rdr_rules)
}
#[cfg(test)]
mod test {
use super::*;
use net_declare::{fidl_ip, fidl_subnet};
#[test]
fn test_rule_with_proto_any() {
assert_eq!(
parse_str_to_rules("pass in;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Any,
src_subnet: None,
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 0, end: 0 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
}])
);
}
#[test]
fn test_rule_with_proto_tcp() {
assert_eq!(
parse_str_to_rules("pass in proto tcp;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: None,
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 0, end: 0 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
}])
);
}
#[test]
fn test_multiple_rules() {
assert_eq!(
parse_str_to_rules("pass in proto tcp; drop out proto udp;"),
Ok(vec![
filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: None,
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 0, end: 0 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
},
filter::Rule {
action: filter::Action::Drop,
direction: filter::Direction::Outgoing,
proto: filter::SocketProtocol::Udp,
src_subnet: None,
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 0, end: 0 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
},
])
);
}
#[test]
fn test_rule_with_from_v4_address() {
assert_eq!(
parse_str_to_rules("pass in proto tcp from 1.2.3.4/24;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: Some(Box::new(fidl_subnet!("1.2.3.4/24"))),
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 0, end: 0 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
}])
);
}
#[test]
fn test_rule_with_from_port() {
assert_eq!(
parse_str_to_rules("pass in proto tcp from port 10000;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: None,
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 10000, end: 10000 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
}])
);
}
#[test]
fn test_rule_with_from_range() {
assert_eq!(
parse_str_to_rules("pass in proto tcp from range 10000:10010;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: None,
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 10000, end: 10010 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
}])
);
}
#[test]
fn test_rule_with_from_invalid_range_1() {
assert_eq!(
parse_str_to_rules("pass in proto tcp from range 0:5;"),
Err(Error::Invalid(InvalidReason::InvalidPortRange))
);
}
#[test]
fn test_rule_with_from_invalid_range_2() {
assert_eq!(
parse_str_to_rules("pass in proto tcp from range 10005:10000;"),
Err(Error::Invalid(InvalidReason::InvalidPortRange))
);
}
#[test]
fn test_rule_with_from_v4_address_port() {
assert_eq!(
parse_str_to_rules("pass in proto tcp from 1.2.3.4/24 port 10000;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: Some(Box::new(fidl_subnet!("1.2.3.4/24"))),
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 10000, end: 10000 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
}])
);
}
#[test]
fn test_rule_with_from_not_v4_address_port() {
assert_eq!(
parse_str_to_rules("pass in proto tcp from !1.2.3.4/24 port 10000;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: Some(Box::new(fidl_subnet!("1.2.3.4/24"))),
src_subnet_invert_match: true,
src_port_range: filter::PortRange { start: 10000, end: 10000 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
}])
);
}
#[test]
fn test_rule_with_from_v6_address_port() {
assert_eq!(
parse_str_to_rules("pass in proto tcp from 1234:5678::/32 port 10000;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: Some(Box::new(fidl_subnet!("1234:5678::/32"))),
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 10000, end: 10000 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
}])
);
}
#[test]
fn test_rule_with_to_v6_address_port() {
assert_eq!(
parse_str_to_rules("pass in proto tcp to 1234:5678::/32 port 10000;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: None,
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 0, end: 0 },
dst_subnet: Some(Box::new(fidl_subnet!("1234:5678::/32"))),
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 10000, end: 10000 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
}])
);
}
#[test]
fn test_rule_with_from_v6_address_port_to_v4_address_port() {
assert_eq!(
parse_str_to_rules(
"pass in proto tcp from 1234:5678::/32 port 10000 to 1.2.3.4/8 port 1000;"
),
Err(Error::Invalid(InvalidReason::MixedIPVersions))
);
}
#[test]
fn test_rule_with_from_v6_address_port_to_v6_address_port() {
assert_eq!(
parse_str_to_rules(
"pass in proto tcp from 1234:5678::/32 port 10000 to 2345:6789::/32 port 1000;"
),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: Some(Box::new(fidl_subnet!("1234:5678::/32"))),
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 10000, end: 10000 },
dst_subnet: Some(Box::new(fidl_subnet!("2345:6789::/32"))),
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 1000, end: 1000 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
}])
);
}
#[test]
fn test_rule_with_log_no_state() {
assert_eq!(
parse_str_to_rules("pass in proto tcp log no state;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: None,
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 0, end: 0 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: true,
keep_state: false,
device_class: filter::DeviceClass::Any(filter::Empty {}),
}])
);
}
#[test]
fn test_rule_with_keep_state() {
assert_eq!(
parse_str_to_rules("pass in proto tcp keep state;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: None,
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 0, end: 0 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: false,
keep_state: true,
device_class: filter::DeviceClass::Any(filter::Empty {}),
}])
);
}
#[test]
fn test_rule_with_device_class() {
assert_eq!(
parse_str_to_rules("pass in proto tcp devclass ap;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: None,
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 0, end: 0 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 0, end: 0 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Match_(fhnet::DeviceClass::WlanAp),
}])
);
}
#[test]
fn test_rule_with_device_class_and_dst_range() {
assert_eq!(
parse_str_to_rules("pass in proto tcp devclass ap to range 1:2;"),
Ok(vec![filter::Rule {
action: filter::Action::Pass,
direction: filter::Direction::Incoming,
proto: filter::SocketProtocol::Tcp,
src_subnet: None,
src_subnet_invert_match: false,
src_port_range: filter::PortRange { start: 0, end: 0 },
dst_subnet: None,
dst_subnet_invert_match: false,
dst_port_range: filter::PortRange { start: 1, end: 2 },
nic: 0,
log: false,
keep_state: false,
device_class: filter::DeviceClass::Match_(fhnet::DeviceClass::WlanAp),
}])
);
}
#[test]
fn test_nat_rule_from_v4_subnet() {
assert_eq!(
parse_str_to_nat_rules("nat from 1.2.3.0/24 -> from 192.168.1.1;"),
Ok(vec![filter::Nat {
proto: filter::SocketProtocol::Any,
src_subnet: fidl_subnet!("1.2.3.0/24"),
outgoing_nic: 0,
}])
);
}
#[test]
fn test_nat_rule_with_proto_tcp_from_v4_subnet() {
assert_eq!(
parse_str_to_nat_rules("nat proto tcp from 1.2.3.0/24 -> from 192.168.1.1;"),
Ok(vec![filter::Nat {
proto: filter::SocketProtocol::Tcp,
src_subnet: fidl_subnet!("1.2.3.0/24"),
outgoing_nic: 0,
}])
);
}
#[test]
fn test_rdr_rule_with_to_v4_address_port_to_v4_address_port() {
assert_eq!(
parse_str_to_rdr_rules("rdr to 1.2.3.4 port 10000 -> to 192.168.1.1 port 20000;"),
Ok(vec![filter::Rdr {
proto: filter::SocketProtocol::Any,
dst_addr: fidl_ip!("1.2.3.4"),
dst_port_range: filter::PortRange { start: 10000, end: 10000 },
new_dst_addr: fidl_ip!("192.168.1.1"),
new_dst_port_range: filter::PortRange { start: 20000, end: 20000 },
nic: 0,
}])
);
}
#[test]
fn test_rdr_rule_with_proto_tcp_to_v4_address_port_to_v4_address_port() {
assert_eq!(
parse_str_to_rdr_rules(
"rdr proto tcp to 1.2.3.4 port 10000 -> to 192.168.1.1 port 20000;"
),
Ok(vec![filter::Rdr {
proto: filter::SocketProtocol::Tcp,
dst_addr: fidl_ip!("1.2.3.4"),
dst_port_range: filter::PortRange { start: 10000, end: 10000 },
new_dst_addr: fidl_ip!("192.168.1.1"),
new_dst_port_range: filter::PortRange { start: 20000, end: 20000 },
nic: 0,
}])
);
}
#[test]
fn test_rdr_rule_with_to_v4_address_range_to_v4_address_range() {
assert_eq!(
parse_str_to_rdr_rules(
"rdr proto tcp to 1.2.3.4 range 10000:10005 -> to 192.168.1.1 range 20000:20005;"
),
Ok(vec![filter::Rdr {
proto: filter::SocketProtocol::Tcp,
dst_addr: fidl_ip!("1.2.3.4"),
dst_port_range: filter::PortRange { start: 10000, end: 10005 },
new_dst_addr: fidl_ip!("192.168.1.1"),
new_dst_port_range: filter::PortRange { start: 20000, end: 20005 },
nic: 0,
}])
);
}
#[test]
fn test_rdr_rule_with_to_v4_address_range_to_v4_address_invalid_range() {
assert_eq!(
parse_str_to_rdr_rules(
"rdr proto tcp to 1.2.3.4 range 10000:10005 -> to 192.168.1.1 range 0:5;"
),
Err(Error::Invalid(InvalidReason::InvalidPortRange))
);
}
#[test]
fn test_rdr_rule_with_to_v4_address_range_to_v4_address_port_range_length_mismatch() {
assert_eq!(
parse_str_to_rdr_rules(
"rdr proto tcp to 1.2.3.4 range 10000:10005 -> to 192.168.1.1 range 20000:20003;"
),
Err(Error::Invalid(InvalidReason::PortRangeLengthMismatch))
);
}
#[test]
fn test_rdr_rule_with_to_v4_address_range_to_v6_address_range() {
assert_eq!(
parse_str_to_rdr_rules(
"rdr proto tcp to 1.2.3.4 range 10000:10005 -> to 1234:5678:: range 20000:20005;"
),
Err(Error::Invalid(InvalidReason::MixedIPVersions))
);
}
#[test]
fn test_rdr_rule_with_to_v6_address_range_to_v6_address_range() {
assert_eq!(
parse_str_to_rdr_rules(
"rdr proto tcp to 1234:5678:: range 10000:10005 -> to 2345:6789:: range 20000:20005;"
),
Ok(vec![filter::Rdr {
proto: filter::SocketProtocol::Tcp,
dst_addr: fidl_ip!("1234:5678::"),
dst_port_range: filter::PortRange {
start: 10000,
end: 10005,
},
new_dst_addr: fidl_ip!("2345:6789::"),
new_dst_port_range: filter::PortRange {
start: 20000,
end: 20005,
},
nic: 0,
}])
);
}
}