route rule: Add support of iif
and action
.
* Added `RouteRule.iif` for matching on incoming interface name. * Added `RouteRule.action` for these actions: * `RouteRuleAction::Blackhole` * `RouteRuleAction::Unreachable` * `RouteRuleAction::Prohibit` Bumped required of nispor to 1.2.9 for the fix of route rule actions. Integration test cases included. Signed-off-by: Gris Ge <fge@redhat.com>
This commit is contained in:
parent
c7d22d9f6d
commit
81c661c2f7
@ -16,7 +16,7 @@ edition = "2018"
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies.nispor]
|
||||
version = "1.2.8"
|
||||
version = "1.2.9"
|
||||
optional = true
|
||||
|
||||
[dependencies.ipnet]
|
||||
|
@ -158,4 +158,6 @@ pub use crate::policy::{
|
||||
NetworkCaptureRules, NetworkPolicy, NetworkStateTemplate,
|
||||
};
|
||||
pub use crate::route::{RouteEntry, RouteState, Routes};
|
||||
pub use crate::route_rule::{RouteRuleEntry, RouteRuleState, RouteRules};
|
||||
pub use crate::route_rule::{
|
||||
RouteRuleAction, RouteRuleEntry, RouteRuleState, RouteRules,
|
||||
};
|
||||
|
@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use log::warn;
|
||||
|
||||
use crate::{AddressFamily, RouteRuleEntry, RouteRules};
|
||||
use crate::{AddressFamily, RouteRuleAction, RouteRuleEntry, RouteRules};
|
||||
|
||||
// Due to a bug in NetworkManager all route rules added using NetworkManager are
|
||||
// using RTM_PROTOCOL UnSpec. Therefore, we need to support it until it is
|
||||
@ -38,8 +40,21 @@ pub(crate) fn get_route_rules(
|
||||
for np_rule in np_rules {
|
||||
let mut rule = RouteRuleEntry::new();
|
||||
// We only support route rules with 'table' action
|
||||
if np_rule.action != nispor::RuleAction::Table {
|
||||
continue;
|
||||
match np_rule.action {
|
||||
nispor::RuleAction::Table => (),
|
||||
nispor::RuleAction::Blackhole => {
|
||||
rule.action = Some(RouteRuleAction::Blackhole)
|
||||
}
|
||||
nispor::RuleAction::Unreachable => {
|
||||
rule.action = Some(RouteRuleAction::Unreachable)
|
||||
}
|
||||
nispor::RuleAction::Prohibit => {
|
||||
rule.action = Some(RouteRuleAction::Prohibit)
|
||||
}
|
||||
_ => {
|
||||
log::debug!("Got unsupported route rule {:?}", np_rule);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Filter out the routes with protocols that we do not support
|
||||
if let Some(rule_protocol) = np_rule.protocol.as_ref() {
|
||||
@ -47,6 +62,7 @@ pub(crate) fn get_route_rules(
|
||||
continue;
|
||||
}
|
||||
}
|
||||
rule.iif = np_rule.iif.clone();
|
||||
rule.ip_to = np_rule.dst.clone();
|
||||
rule.ip_from = np_rule.src.clone();
|
||||
rule.table_id = np_rule.table;
|
||||
|
@ -55,7 +55,7 @@ pub use self::ovs::{
|
||||
NmSettingOvsIface, NmSettingOvsPatch, NmSettingOvsPort,
|
||||
};
|
||||
pub use self::route::NmIpRoute;
|
||||
pub use self::route_rule::NmIpRouteRule;
|
||||
pub use self::route_rule::{NmIpRouteRule, NmIpRouteRuleAction};
|
||||
pub use self::sriov::{NmSettingSriov, NmSettingSriovVf, NmSettingSriovVfVlan};
|
||||
pub use self::user::NmSettingUser;
|
||||
pub use self::veth::NmSettingVeth;
|
||||
|
@ -32,6 +32,8 @@ pub struct NmIpRouteRule {
|
||||
pub table: Option<u32>,
|
||||
pub fw_mark: Option<u32>,
|
||||
pub fw_mask: Option<u32>,
|
||||
pub iifname: Option<String>,
|
||||
pub action: Option<NmIpRouteRuleAction>,
|
||||
_other: DbusDictionary,
|
||||
}
|
||||
|
||||
@ -48,6 +50,9 @@ impl TryFrom<DbusDictionary> for NmIpRouteRule {
|
||||
table: _from_map!(v, "table", u32::try_from)?,
|
||||
fw_mark: _from_map!(v, "fw_mark", u32::try_from)?,
|
||||
fw_mask: _from_map!(v, "fw_mask", u32::try_from)?,
|
||||
iifname: _from_map!(v, "iifname", String::try_from)?,
|
||||
action: _from_map!(v, "action", u8::try_from)?
|
||||
.map(NmIpRouteRuleAction::from),
|
||||
_other: v,
|
||||
})
|
||||
}
|
||||
@ -113,6 +118,18 @@ impl NmIpRouteRule {
|
||||
zvariant::Value::new(zvariant::Value::new(v)),
|
||||
)?;
|
||||
}
|
||||
if let Some(v) = &self.iifname {
|
||||
ret.append(
|
||||
zvariant::Value::new("iifname"),
|
||||
zvariant::Value::new(zvariant::Value::new(v)),
|
||||
)?;
|
||||
}
|
||||
if let Some(v) = &self.action {
|
||||
ret.append(
|
||||
zvariant::Value::new("action"),
|
||||
zvariant::Value::new(zvariant::Value::new(u8::from(*v))),
|
||||
)?;
|
||||
}
|
||||
|
||||
for (key, value) in self._other.iter() {
|
||||
ret.append(
|
||||
@ -144,3 +161,41 @@ pub(crate) fn nm_ip_rules_to_value(
|
||||
}
|
||||
Ok(zvariant::Value::Array(rule_values))
|
||||
}
|
||||
|
||||
const RTN_BLACKHOLE: u8 = 6;
|
||||
const RTN_UNREACHABLE: u8 = 7;
|
||||
const RTN_PROHIBIT: u8 = 8;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum NmIpRouteRuleAction {
|
||||
Blackhole,
|
||||
Unreachable,
|
||||
Prohibit,
|
||||
Other(u8),
|
||||
}
|
||||
|
||||
impl From<u8> for NmIpRouteRuleAction {
|
||||
fn from(v: u8) -> Self {
|
||||
match v {
|
||||
RTN_BLACKHOLE => Self::Blackhole,
|
||||
RTN_UNREACHABLE => Self::Unreachable,
|
||||
RTN_PROHIBIT => Self::Prohibit,
|
||||
_ => {
|
||||
log::warn!("Unsupported IP route rule action {}", v);
|
||||
Self::Other(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NmIpRouteRuleAction> for u8 {
|
||||
fn from(v: NmIpRouteRuleAction) -> Self {
|
||||
match v {
|
||||
NmIpRouteRuleAction::Blackhole => RTN_BLACKHOLE,
|
||||
NmIpRouteRuleAction::Unreachable => RTN_UNREACHABLE,
|
||||
NmIpRouteRuleAction::Prohibit => RTN_PROHIBIT,
|
||||
NmIpRouteRuleAction::Other(d) => d,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,14 +26,15 @@ pub use self::active_connection::{
|
||||
NmActiveConnection, NM_ACTIVATION_STATE_FLAG_EXTERNAL,
|
||||
};
|
||||
pub use self::connection::{
|
||||
NmConnection, NmIpRoute, NmIpRouteRule, NmSetting8021X, NmSettingBond,
|
||||
NmSettingBridge, NmSettingBridgePort, NmSettingBridgeVlanRange,
|
||||
NmSettingConnection, NmSettingEthtool, NmSettingInfiniBand, NmSettingIp,
|
||||
NmSettingIpMethod, NmSettingLoopback, NmSettingMacVlan, NmSettingOvsBridge,
|
||||
NmSettingOvsDpdk, NmSettingOvsExtIds, NmSettingOvsIface, NmSettingOvsPatch,
|
||||
NmSettingOvsPort, NmSettingSriov, NmSettingSriovVf, NmSettingSriovVfVlan,
|
||||
NmSettingUser, NmSettingVeth, NmSettingVlan, NmSettingVrf, NmSettingVxlan,
|
||||
NmSettingWired, NmSettingsConnectionFlag, NmVlanProtocol,
|
||||
NmConnection, NmIpRoute, NmIpRouteRule, NmIpRouteRuleAction,
|
||||
NmSetting8021X, NmSettingBond, NmSettingBridge, NmSettingBridgePort,
|
||||
NmSettingBridgeVlanRange, NmSettingConnection, NmSettingEthtool,
|
||||
NmSettingInfiniBand, NmSettingIp, NmSettingIpMethod, NmSettingLoopback,
|
||||
NmSettingMacVlan, NmSettingOvsBridge, NmSettingOvsDpdk, NmSettingOvsExtIds,
|
||||
NmSettingOvsIface, NmSettingOvsPatch, NmSettingOvsPort, NmSettingSriov,
|
||||
NmSettingSriovVf, NmSettingSriovVfVlan, NmSettingUser, NmSettingVeth,
|
||||
NmSettingVlan, NmSettingVrf, NmSettingVxlan, NmSettingWired,
|
||||
NmSettingsConnectionFlag, NmVlanProtocol,
|
||||
};
|
||||
#[cfg(feature = "query_apply")]
|
||||
pub use self::device::{NmDevice, NmDeviceState, NmDeviceStateReason};
|
||||
|
@ -64,6 +64,12 @@ pub(crate) fn gen_nm_ip_rules<'a>(
|
||||
|
||||
nm_rule.fw_mark = rule.fwmark;
|
||||
nm_rule.fw_mask = rule.fwmask;
|
||||
if let Some(iif) = rule.iif.as_ref() {
|
||||
nm_rule.iifname = Some(iif.to_string());
|
||||
}
|
||||
if let Some(action) = rule.action.as_ref() {
|
||||
nm_rule.action = Some(u8::from(*action).into());
|
||||
}
|
||||
|
||||
ret.push(nm_rule);
|
||||
}
|
||||
|
@ -271,6 +271,12 @@ pub struct RouteRuleEntry {
|
||||
)]
|
||||
/// Select the fwmask value to match
|
||||
pub fwmask: Option<u32>,
|
||||
/// Actions for matching packages.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub action: Option<RouteRuleAction>,
|
||||
/// Incoming interface.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub iif: Option<String>,
|
||||
}
|
||||
|
||||
impl RouteRuleEntry {
|
||||
@ -329,7 +335,9 @@ impl RouteRuleEntry {
|
||||
if self.fwmark.is_none() && self.fwmask.is_some() {
|
||||
let e = NmstateError::new(
|
||||
ErrorKind::InvalidArgument,
|
||||
format!("fwmask is present but fwmark is not defined or is zero {self:?}"
|
||||
format!(
|
||||
"fwmask is present but fwmark is \
|
||||
not defined or is zero {self:?}"
|
||||
),
|
||||
);
|
||||
log::error!("{}", e);
|
||||
@ -393,12 +401,15 @@ impl RouteRuleEntry {
|
||||
if self.fwmask.is_some() && self.fwmask != other.fwmask {
|
||||
return false;
|
||||
}
|
||||
if self.action.is_some() && self.action != other.action {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
// Return tuple of (no_absent, is_ipv4, table_id, ip_from,
|
||||
// ip_to, priority, fwmark, fwmask)
|
||||
fn sort_key(&self) -> (bool, bool, u32, &str, &str, i64, u32, u32) {
|
||||
// ip_to, priority, fwmark, fwmask, action)
|
||||
fn sort_key(&self) -> (bool, bool, u32, &str, &str, i64, u32, u32, u8) {
|
||||
(
|
||||
!matches!(self.state, Some(RouteRuleState::Absent)),
|
||||
{
|
||||
@ -424,6 +435,7 @@ impl RouteRuleEntry {
|
||||
.unwrap_or(RouteRuleEntry::USE_DEFAULT_PRIORITY),
|
||||
self.fwmark.unwrap_or(0),
|
||||
self.fwmask.unwrap_or(0),
|
||||
self.action.map(u8::from).unwrap_or(0),
|
||||
)
|
||||
}
|
||||
|
||||
@ -530,3 +542,41 @@ fn flat_absent_rule(
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
#[non_exhaustive]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub enum RouteRuleAction {
|
||||
Blackhole,
|
||||
Unreachable,
|
||||
Prohibit,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for RouteRuleAction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::Blackhole => "blackhole",
|
||||
Self::Unreachable => "unreachable",
|
||||
Self::Prohibit => "prohibit",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const FR_ACT_BLACKHOLE: u8 = 6;
|
||||
const FR_ACT_UNREACHABLE: u8 = 7;
|
||||
const FR_ACT_PROHIBIT: u8 = 8;
|
||||
|
||||
impl From<RouteRuleAction> for u8 {
|
||||
fn from(v: RouteRuleAction) -> u8 {
|
||||
match v {
|
||||
RouteRuleAction::Blackhole => FR_ACT_BLACKHOLE,
|
||||
RouteRuleAction::Unreachable => FR_ACT_UNREACHABLE,
|
||||
RouteRuleAction::Prohibit => FR_ACT_PROHIBIT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -167,6 +167,7 @@ fn gen_rule_entry(
|
||||
priority: Some(priority),
|
||||
fwmark: Some(fwmark),
|
||||
fwmask: Some(fwmask),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,11 @@ class RouteRule:
|
||||
FAMILY = "family"
|
||||
FAMILY_IPV4 = "ipv4"
|
||||
FAMILY_IPV6 = "ipv6"
|
||||
IIF = "iif"
|
||||
ACTION = "action"
|
||||
ACTION_BLACKHOLE = "blackhole"
|
||||
ACTION_UNREACHABLE = "unreachable"
|
||||
ACTION_PROHIBIT = "prohibit"
|
||||
|
||||
|
||||
class DNS:
|
||||
|
@ -1073,6 +1073,8 @@ def _check_ip_rules(rules):
|
||||
rule.get(RouteRule.FWMARK),
|
||||
rule.get(RouteRule.FWMASK),
|
||||
rule.get(RouteRule.FAMILY),
|
||||
rule.get(RouteRule.IIF),
|
||||
rule.get(RouteRule.ACTION),
|
||||
)
|
||||
|
||||
|
||||
@ -1343,3 +1345,67 @@ def test_do_not_show_bgp_route(static_route_with_additional_bgp_route):
|
||||
for route in routes:
|
||||
assert route[Route.DESTINATION] != BGP_ROUTE_DST_V4
|
||||
assert route[Route.DESTINATION] != BGP_ROUTE_DST_V6
|
||||
|
||||
|
||||
@pytest.mark.tier1
|
||||
def test_route_rule_iif(route_rule_test_env):
|
||||
desired_rules = [
|
||||
{
|
||||
RouteRule.IIF: "eth1",
|
||||
RouteRule.ROUTE_TABLE: IPV4_ROUTE_TABLE_ID1,
|
||||
RouteRule.IP_FROM: IPV4_TEST_NET1,
|
||||
},
|
||||
{
|
||||
RouteRule.IIF: "eth1",
|
||||
RouteRule.ROUTE_TABLE: IPV6_ROUTE_TABLE_ID1,
|
||||
RouteRule.IP_FROM: IPV6_TEST_NET1,
|
||||
},
|
||||
]
|
||||
|
||||
libnmstate.apply({RouteRule.KEY: {RouteRule.CONFIG: desired_rules}})
|
||||
_check_ip_rules(desired_rules)
|
||||
|
||||
|
||||
@pytest.mark.tier1
|
||||
def test_route_rule_action(route_rule_test_env):
|
||||
desired_rules = [
|
||||
{
|
||||
RouteRule.PRIORITY: 10000,
|
||||
RouteRule.IIF: "eth1",
|
||||
RouteRule.IP_FROM: "192.0.2.1/32",
|
||||
RouteRule.ACTION: RouteRule.ACTION_BLACKHOLE,
|
||||
},
|
||||
{
|
||||
RouteRule.PRIORITY: 10001,
|
||||
RouteRule.IIF: "eth1",
|
||||
RouteRule.IP_FROM: "192.0.2.2/32",
|
||||
RouteRule.ACTION: RouteRule.ACTION_UNREACHABLE,
|
||||
},
|
||||
{
|
||||
RouteRule.PRIORITY: 10002,
|
||||
RouteRule.IIF: "eth1",
|
||||
RouteRule.IP_FROM: "192.0.2.3/32",
|
||||
RouteRule.ACTION: RouteRule.ACTION_PROHIBIT,
|
||||
},
|
||||
{
|
||||
RouteRule.PRIORITY: 20000,
|
||||
RouteRule.IIF: "eth1",
|
||||
RouteRule.IP_FROM: "2001:db8:1::1/128",
|
||||
RouteRule.ACTION: RouteRule.ACTION_BLACKHOLE,
|
||||
},
|
||||
{
|
||||
RouteRule.PRIORITY: 20001,
|
||||
RouteRule.IIF: "eth1",
|
||||
RouteRule.IP_FROM: "2001:db8:1::2/128",
|
||||
RouteRule.ACTION: RouteRule.ACTION_UNREACHABLE,
|
||||
},
|
||||
{
|
||||
RouteRule.PRIORITY: 20002,
|
||||
RouteRule.IIF: "eth1",
|
||||
RouteRule.IP_FROM: "2001:db8:1::3/128",
|
||||
RouteRule.ACTION: RouteRule.ACTION_PROHIBIT,
|
||||
},
|
||||
]
|
||||
|
||||
libnmstate.apply({RouteRule.KEY: {RouteRule.CONFIG: desired_rules}})
|
||||
_check_ip_rules(desired_rules)
|
||||
|
@ -1,21 +1,4 @@
|
||||
#
|
||||
# Copyright (c) 2019-2020 Red Hat, Inc.
|
||||
#
|
||||
# This file is part of nmstate
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
import json
|
||||
import logging
|
||||
@ -25,7 +8,15 @@ from . import cmdlib
|
||||
|
||||
|
||||
def ip_rule_exist_in_os(
|
||||
ip_from, ip_to, priority, table, fwmark, fwmask, family
|
||||
ip_from,
|
||||
ip_to,
|
||||
priority,
|
||||
table,
|
||||
fwmark,
|
||||
fwmask,
|
||||
family,
|
||||
iif=None,
|
||||
action=None,
|
||||
):
|
||||
expected_rule = locals()
|
||||
logging.debug("Checking ip rule for {}".format(expected_rule))
|
||||
@ -76,6 +67,12 @@ def ip_rule_exist_in_os(
|
||||
if fwmask is not None and rule["fwmask"] != hex(fwmask):
|
||||
found = False
|
||||
continue
|
||||
if iif is not None and rule["iif"] != iif:
|
||||
found = False
|
||||
continue
|
||||
if action is not None and rule["action"] != action:
|
||||
found = False
|
||||
continue
|
||||
if found:
|
||||
break
|
||||
if not found:
|
||||
|
Loading…
x
Reference in New Issue
Block a user