use log::debug;
use crate::{
bgp::BgpSessionType,
event::EventQueue,
formatter::NetworkFormatter,
network::Network,
ospf::{LinkWeight, OspfArea, OspfImpl, DEFAULT_LINK_WEIGHT},
route_map::{RouteMap, RouteMapDirection},
router::StaticRoute,
types::{ConfigError, NetworkDeviceRef, NetworkError, Prefix, PrefixMap, RouterId},
};
use petgraph::algo::FloatMeasure;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::ops::Index;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound(deserialize = "P: for<'a> serde::Deserialize<'a>"))]
pub struct Config<P: Prefix> {
pub expr: HashMap<ConfigExprKey<P>, ConfigExpr<P>>,
}
impl<P: Prefix> Default for Config<P> {
fn default() -> Self {
Self::new()
}
}
impl<P: Prefix> Config<P> {
pub fn new() -> Self {
Self {
expr: HashMap::new(),
}
}
pub fn add(&mut self, expr: ConfigExpr<P>) -> Result<(), ConfigError> {
if let Some(old_expr) = self.expr.insert(expr.key(), expr) {
self.expr.insert(old_expr.key(), old_expr);
Err(ConfigError::ConfigExprOverload)
} else {
Ok(())
}
}
pub fn apply_modifier(&mut self, modifier: &ConfigModifier<P>) -> Result<(), ConfigError> {
match modifier {
ConfigModifier::Insert(expr) => {
if let Some(old_expr) = self.expr.insert(expr.key(), expr.clone()) {
self.expr.insert(old_expr.key(), old_expr);
return Err(ConfigError::ConfigModifier);
}
}
ConfigModifier::Remove(expr) => match self.expr.remove(&expr.key()) {
Some(old_expr) if &old_expr != expr => {
self.expr.insert(old_expr.key(), old_expr);
return Err(ConfigError::ConfigModifier);
}
None => return Err(ConfigError::ConfigModifier),
_ => {}
},
ConfigModifier::Update {
from: expr_a,
to: expr_b,
} => {
let key = expr_a.key();
if key != expr_b.key() {
return Err(ConfigError::ConfigModifier);
}
match self.expr.remove(&key) {
Some(old_expr) if &old_expr != expr_a => {
self.expr.insert(key, old_expr);
return Err(ConfigError::ConfigModifier);
}
None => return Err(ConfigError::ConfigModifier),
_ => {}
}
self.expr.insert(key, expr_b.clone());
}
ConfigModifier::BatchRouteMapEdit { router, updates } => {
for update in updates {
self.apply_modifier(&update.clone().into_modifier(*router))?;
}
}
};
Ok(())
}
pub fn apply_patch(&mut self, patch: &ConfigPatch<P>) -> Result<(), ConfigError> {
let mut config_before = self.expr.clone();
for modifier in patch.modifiers.iter() {
match self.apply_modifier(modifier) {
Ok(()) => {}
Err(e) => {
std::mem::swap(&mut self.expr, &mut config_before);
return Err(e);
}
};
}
Ok(())
}
pub fn get_diff(&self, other: &Self) -> ConfigPatch<P> {
let mut patch = ConfigPatch::new();
let self_keys: HashSet<&ConfigExprKey<P>> = self.expr.keys().collect();
let other_keys: HashSet<&ConfigExprKey<P>> = other.expr.keys().collect();
for k in self_keys.difference(&other_keys) {
patch.add(ConfigModifier::Remove(self.expr.get(k).unwrap().clone()));
}
for k in other_keys.difference(&self_keys) {
patch.add(ConfigModifier::Insert(other.expr.get(k).unwrap().clone()));
}
for k in self_keys.intersection(&other_keys) {
let self_e = self.expr.get(k).unwrap();
let other_e = other.expr.get(k).unwrap();
if self_e != other_e {
patch.add(ConfigModifier::Update {
from: self_e.clone(),
to: other_e.clone(),
})
}
}
patch
}
pub fn len(&self) -> usize {
self.expr.len()
}
pub fn is_empty(&self) -> bool {
self.expr.is_empty()
}
pub fn iter(&self) -> std::collections::hash_map::Values<ConfigExprKey<P>, ConfigExpr<P>> {
self.expr.values()
}
pub fn get(&self, mut index: ConfigExprKey<P>) -> Option<&ConfigExpr<P>> {
index.normalize();
self.expr.get(&index)
}
pub fn get_mut(&mut self, mut index: ConfigExprKey<P>) -> Option<&mut ConfigExpr<P>> {
index.normalize();
self.expr.get_mut(&index)
}
}
impl<P: Prefix> Index<ConfigExprKey<P>> for Config<P> {
type Output = ConfigExpr<P>;
fn index(&self, index: ConfigExprKey<P>) -> &Self::Output {
self.get(index).unwrap()
}
}
impl<P: Prefix> PartialEq for Config<P> {
fn eq(&self, other: &Self) -> bool {
if self.expr.keys().collect::<HashSet<_>>() != other.expr.keys().collect::<HashSet<_>>() {
return false;
}
for key in self.expr.keys() {
match (self.expr[key].clone(), other.expr[key].clone()) {
(
ConfigExpr::BgpSession {
source: s1,
target: t1,
session_type: ty1,
},
ConfigExpr::BgpSession {
source: s2,
target: t2,
session_type: ty2,
},
) if ty1 == ty2 && ty1 == BgpSessionType::IBgpPeer => {
if !((s1 == s2 && t1 == t2) || (s1 == t2 && t1 == s2)) {
return false;
}
}
(acq, exp) if acq != exp => return false,
_ => {}
}
}
true
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(bound(deserialize = "P: for<'a> serde::Deserialize<'a>"))]
pub enum ConfigExpr<P: Prefix> {
IgpLinkWeight {
source: RouterId,
target: RouterId,
weight: LinkWeight,
},
OspfArea {
source: RouterId,
target: RouterId,
area: OspfArea,
},
BgpSession {
source: RouterId,
target: RouterId,
session_type: BgpSessionType,
},
BgpRouteMap {
router: RouterId,
neighbor: RouterId,
direction: RouteMapDirection,
map: RouteMap<P>,
},
StaticRoute {
router: RouterId,
prefix: P,
target: StaticRoute,
},
LoadBalancing {
router: RouterId,
},
}
impl<P: Prefix + PartialEq> PartialEq for ConfigExpr<P> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(
ConfigExpr::IgpLinkWeight {
source: s1,
target: t1,
weight: w1,
},
ConfigExpr::IgpLinkWeight {
source: s2,
target: t2,
weight: w2,
},
) => (s1, t1, w1) == (s2, t2, w2),
(
ConfigExpr::OspfArea {
source: s1,
target: t1,
area: a1,
},
ConfigExpr::OspfArea {
source: s2,
target: t2,
area: a2,
},
) => (s1, t1, a1) == (s2, t2, a2),
(
ConfigExpr::BgpSession {
source: s1,
target: t1,
session_type: ty1,
},
ConfigExpr::BgpSession {
source: s2,
target: t2,
session_type: ty2,
},
) => match (ty1, ty2) {
(BgpSessionType::IBgpPeer, BgpSessionType::IBgpPeer)
| (BgpSessionType::EBgp, BgpSessionType::EBgp) => {
(s1, t1) == (s2, t2) || (s1, t1) == (t2, s2)
}
(BgpSessionType::IBgpClient, BgpSessionType::IBgpClient) => (s1, t1) == (s2, t2),
(BgpSessionType::IBgpClient, BgpSessionType::IBgpPeer) => (s1, t1) == (t2, s2),
(BgpSessionType::IBgpPeer, BgpSessionType::IBgpClient) => (s1, t1) == (t2, s2),
_ => false,
},
(
ConfigExpr::BgpRouteMap {
router: r1,
neighbor: n1,
direction: d1,
map: m1,
},
ConfigExpr::BgpRouteMap {
router: r2,
neighbor: n2,
direction: d2,
map: m2,
},
) => (r1, n1, d1, m1) == (r2, n2, d2, m2),
(
ConfigExpr::StaticRoute {
router: r1,
prefix: p1,
target: t1,
},
ConfigExpr::StaticRoute {
router: r2,
prefix: p2,
target: t2,
},
) => (r1, p1, t1) == (r2, p2, t2),
(
ConfigExpr::LoadBalancing { router: r1 },
ConfigExpr::LoadBalancing { router: r2 },
) => r1 == r2,
(ConfigExpr::IgpLinkWeight { .. }, _)
| (ConfigExpr::OspfArea { .. }, _)
| (ConfigExpr::BgpSession { .. }, _)
| (ConfigExpr::BgpRouteMap { .. }, _)
| (ConfigExpr::StaticRoute { .. }, _)
| (ConfigExpr::LoadBalancing { .. }, _) => false,
}
}
}
impl<P: Prefix> ConfigExpr<P> {
pub fn key(&self) -> ConfigExprKey<P> {
match self {
ConfigExpr::IgpLinkWeight {
source,
target,
weight: _,
} => ConfigExprKey::IgpLinkWeight {
source: *source,
target: *target,
},
ConfigExpr::OspfArea {
source,
target,
area: _,
} => {
if source < target {
ConfigExprKey::OspfArea {
router_a: *source,
router_b: *target,
}
} else {
ConfigExprKey::OspfArea {
router_a: *target,
router_b: *source,
}
}
}
ConfigExpr::BgpSession {
source,
target,
session_type: _,
} => {
if source < target {
ConfigExprKey::BgpSession {
speaker_a: *source,
speaker_b: *target,
}
} else {
ConfigExprKey::BgpSession {
speaker_a: *target,
speaker_b: *source,
}
}
}
ConfigExpr::BgpRouteMap {
router,
neighbor,
direction,
map,
} => ConfigExprKey::BgpRouteMap {
router: *router,
neighbor: *neighbor,
direction: *direction,
order: map.order,
},
ConfigExpr::StaticRoute {
router,
prefix,
target: _,
} => ConfigExprKey::StaticRoute {
router: *router,
prefix: *prefix,
},
ConfigExpr::LoadBalancing { router } => {
ConfigExprKey::LoadBalancing { router: *router }
}
}
}
pub fn routers(&self) -> Vec<RouterId> {
match self {
ConfigExpr::IgpLinkWeight { source, .. } => vec![*source],
ConfigExpr::OspfArea { source, target, .. } => vec![*source, *target],
ConfigExpr::BgpSession { source, target, .. } => vec![*source, *target],
ConfigExpr::BgpRouteMap { router, .. } => vec![*router],
ConfigExpr::StaticRoute { router, .. } => vec![*router],
ConfigExpr::LoadBalancing { router } => vec![*router],
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ConfigExprKey<P> {
IgpLinkWeight {
source: RouterId,
target: RouterId,
},
OspfArea {
router_a: RouterId,
router_b: RouterId,
},
BgpSession {
speaker_a: RouterId,
speaker_b: RouterId,
},
BgpRouteMap {
router: RouterId,
neighbor: RouterId,
direction: RouteMapDirection,
order: i16,
},
StaticRoute {
router: RouterId,
prefix: P,
},
LoadBalancing {
router: RouterId,
},
}
impl<P> ConfigExprKey<P> {
pub fn normalize(&mut self) {
if let ConfigExprKey::BgpSession {
speaker_a,
speaker_b,
} = self
{
if speaker_a > speaker_b {
std::mem::swap(speaker_a, speaker_b)
}
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(bound(deserialize = "P: for<'a> serde::Deserialize<'a>"))]
pub struct RouteMapEdit<P: Prefix> {
pub neighbor: RouterId,
pub direction: RouteMapDirection,
pub old: Option<RouteMap<P>>,
pub new: Option<RouteMap<P>>,
}
impl<P: Prefix> RouteMapEdit<P> {
pub fn reverse(self) -> Self {
Self {
neighbor: self.neighbor,
direction: self.direction,
old: self.new,
new: self.old,
}
}
pub fn into_modifier(self, router: RouterId) -> ConfigModifier<P> {
let neighbor = self.neighbor;
let direction = self.direction;
match (self.old, self.new) {
(None, None) => panic!("Constructed a RouteMapEdit that doesn't perform any edit!"),
(None, Some(new)) => ConfigModifier::Insert(ConfigExpr::BgpRouteMap {
router,
neighbor,
direction,
map: new,
}),
(Some(old), None) => ConfigModifier::Remove(ConfigExpr::BgpRouteMap {
router,
neighbor,
direction,
map: old,
}),
(Some(old), Some(new)) => ConfigModifier::Update {
from: ConfigExpr::BgpRouteMap {
router,
neighbor,
direction,
map: old,
},
to: ConfigExpr::BgpRouteMap {
router,
neighbor,
direction,
map: new,
},
},
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(bound(deserialize = "P: for<'a> serde::Deserialize<'a>"))]
pub enum ConfigModifier<P: Prefix> {
Insert(ConfigExpr<P>),
Remove(ConfigExpr<P>),
Update {
from: ConfigExpr<P>,
to: ConfigExpr<P>,
},
BatchRouteMapEdit {
router: RouterId,
updates: Vec<RouteMapEdit<P>>,
},
}
impl<P: Prefix> ConfigModifier<P> {
pub fn key(&self) -> Option<ConfigExprKey<P>> {
match self {
Self::Insert(e) => Some(e.key()),
Self::Remove(e) => Some(e.key()),
Self::Update { to, .. } => Some(to.key()),
Self::BatchRouteMapEdit { .. } => None,
}
}
pub fn routers(&self) -> Vec<RouterId> {
match self {
Self::Insert(e) => e.routers(),
Self::Remove(e) => e.routers(),
Self::Update { to, .. } => to.routers(),
Self::BatchRouteMapEdit { router, .. } => vec![*router],
}
}
pub fn reverse(self) -> Self {
match self {
Self::Insert(e) => Self::Remove(e),
Self::Remove(e) => Self::Insert(e),
Self::Update { from, to } => Self::Update { from: to, to: from },
Self::BatchRouteMapEdit { router, updates } => Self::BatchRouteMapEdit {
router,
updates: updates.into_iter().map(|x| x.reverse()).collect(),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(bound(deserialize = "P: for<'a> serde::Deserialize<'a>"))]
pub struct ConfigPatch<P: Prefix> {
pub modifiers: Vec<ConfigModifier<P>>,
}
impl<P: Prefix> Default for ConfigPatch<P> {
fn default() -> Self {
Self::new()
}
}
impl<P: Prefix> ConfigPatch<P> {
pub fn new() -> Self {
Self {
modifiers: Vec::new(),
}
}
pub fn add(&mut self, modifier: ConfigModifier<P>) {
self.modifiers.push(modifier);
}
}
pub trait NetworkConfig<P: Prefix> {
fn set_config(&mut self, config: &Config<P>) -> Result<(), NetworkError>;
fn apply_patch(&mut self, patch: &ConfigPatch<P>) -> Result<(), NetworkError>;
fn apply_modifier(&mut self, modifier: &ConfigModifier<P>) -> Result<(), NetworkError>;
fn apply_modifier_unchecked(
&mut self,
modifier: &ConfigModifier<P>,
) -> Result<(), NetworkError>;
fn can_apply_modifier(&self, expr: &ConfigModifier<P>) -> bool;
fn get_config(&self) -> Result<Config<P>, NetworkError>;
}
impl<P: Prefix, Q: EventQueue<P>, Ospf: OspfImpl> NetworkConfig<P> for Network<P, Q, Ospf> {
fn set_config(&mut self, config: &Config<P>) -> Result<(), NetworkError> {
let patch = self.get_config()?.get_diff(config);
self.apply_patch(&patch)
}
fn apply_patch(&mut self, patch: &ConfigPatch<P>) -> Result<(), NetworkError> {
self.skip_queue = true;
for modifier in patch.modifiers.iter() {
self.apply_modifier(modifier)?;
}
self.skip_queue = false;
self.do_queue_maybe_skip()
}
fn apply_modifier(&mut self, modifier: &ConfigModifier<P>) -> Result<(), NetworkError> {
if self.can_apply_modifier(modifier) {
self.apply_modifier_unchecked(modifier)
} else {
log::warn!("Cannot apply mod.: {}", modifier.fmt(self));
Err(ConfigError::ConfigModifier)?
}
}
fn apply_modifier_unchecked(
&mut self,
modifier: &ConfigModifier<P>,
) -> Result<(), NetworkError> {
debug!("Applying modifier: {}", modifier.fmt(self));
match modifier {
ConfigModifier::Insert(expr) | ConfigModifier::Update { to: expr, .. } => match expr {
ConfigExpr::IgpLinkWeight {
source,
target,
weight,
} => self.set_link_weight(*source, *target, *weight).map(|_| ()),
ConfigExpr::OspfArea {
source,
target,
area,
} => self.set_ospf_area(*source, *target, *area).map(|_| ()),
ConfigExpr::BgpSession {
source,
target,
session_type,
} => self.set_bgp_session(*source, *target, Some(*session_type)),
ConfigExpr::BgpRouteMap {
router,
neighbor,
direction,
map,
} => self
.set_bgp_route_map(*router, *neighbor, *direction, map.clone())
.map(|_| ()),
ConfigExpr::StaticRoute {
router,
prefix,
target,
} => {
self.set_static_route(*router, *prefix, Some(*target))?;
Ok(())
}
ConfigExpr::LoadBalancing { router } => {
self.set_load_balancing(*router, true)?;
Ok(())
}
},
ConfigModifier::Remove(expr) => match expr {
ConfigExpr::IgpLinkWeight {
source,
target,
weight: _,
} => self
.set_link_weight(*source, *target, LinkWeight::infinite())
.map(|_| ()),
ConfigExpr::OspfArea {
source,
target,
area: _,
} => self
.set_ospf_area(*source, *target, OspfArea::BACKBONE)
.map(|_| ()),
ConfigExpr::BgpSession {
source,
target,
session_type: _,
} => self.set_bgp_session(*source, *target, None),
ConfigExpr::BgpRouteMap {
router,
neighbor,
direction,
map,
} => self
.remove_bgp_route_map(*router, *neighbor, *direction, map.order)
.map(|_| ()),
ConfigExpr::StaticRoute { router, prefix, .. } => {
self.set_static_route(*router, *prefix, None)?;
Ok(())
}
ConfigExpr::LoadBalancing { router } => {
self.set_load_balancing(*router, false)?;
Ok(())
}
},
ConfigModifier::BatchRouteMapEdit { router, updates } => {
self.batch_update_route_maps(*router, updates)
}
}
}
fn can_apply_modifier(&self, expr: &ConfigModifier<P>) -> bool {
match expr {
ConfigModifier::Insert(x) => match x {
ConfigExpr::IgpLinkWeight { source, target, .. } => {
self.get_link_weight(*source, *target).is_ok()
}
ConfigExpr::OspfArea { source, target, .. } => self
.get_ospf_area(*source, *target)
.map(|x| x == OspfArea::BACKBONE)
.unwrap_or(false),
ConfigExpr::BgpSession { source, target, .. } => match self.get_device(*source) {
Ok(NetworkDeviceRef::InternalRouter(r)) => {
r.bgp.get_session_type(*target).is_none()
}
Ok(NetworkDeviceRef::ExternalRouter(r)) => !r.neighbors.contains(target),
Err(_) => false,
},
ConfigExpr::BgpRouteMap {
router,
neighbor,
direction,
map,
} => self
.get_device(*router)
.ok()
.and_then(|r| r.internal())
.map(|r| {
r.bgp
.get_route_map(*neighbor, *direction, map.order)
.is_none()
})
.unwrap_or(false),
ConfigExpr::StaticRoute { router, prefix, .. } => self
.get_device(*router)
.ok()
.and_then(|r| r.internal())
.map(|r| r.sr.get_table().get(prefix).is_none())
.unwrap_or(false),
ConfigExpr::LoadBalancing { router } => self
.get_device(*router)
.ok()
.and_then(|r| r.internal())
.map(|r| !r.get_load_balancing())
.unwrap_or(false),
},
ConfigModifier::Remove(x) | ConfigModifier::Update { from: x, .. } => match x {
ConfigExpr::IgpLinkWeight { source, target, .. } => {
self.get_link_weight(*source, *target).is_ok()
}
ConfigExpr::OspfArea { source, target, .. } => self
.get_ospf_area(*source, *target)
.map(|x| x != OspfArea::BACKBONE)
.unwrap_or(false),
ConfigExpr::BgpSession { source, target, .. } => match self.get_device(*source) {
Ok(NetworkDeviceRef::InternalRouter(r)) => {
r.bgp.get_session_type(*target).is_some()
}
Ok(NetworkDeviceRef::ExternalRouter(r)) => r.neighbors.contains(target),
Err(_) => false,
},
ConfigExpr::BgpRouteMap {
router,
neighbor,
direction,
map,
} => self
.get_device(*router)
.ok()
.and_then(|r| r.internal())
.map(|r| {
r.bgp
.get_route_map(*neighbor, *direction, map.order)
.is_some()
})
.unwrap_or(false),
ConfigExpr::StaticRoute { router, prefix, .. } => self
.get_device(*router)
.ok()
.and_then(|r| r.internal())
.map(|r| r.sr.get_table().get(prefix).is_some())
.unwrap_or(false),
ConfigExpr::LoadBalancing { router } => self
.get_device(*router)
.ok()
.and_then(|r| r.internal())
.map(|r| r.get_load_balancing())
.unwrap_or(false),
},
ConfigModifier::BatchRouteMapEdit { router, updates } => {
if let Some(r) = self.get_device(*router).ok().and_then(|r| r.internal()) {
for update in updates {
let neighbor = update.neighbor;
let direction = update.direction;
if !match (update.old.as_ref(), update.new.as_ref()) {
(None, None) => true,
(None, Some(rm)) => {
r.bgp.get_route_map(neighbor, direction, rm.order).is_none()
}
(Some(rm), _) => {
r.bgp.get_route_map(neighbor, direction, rm.order).is_some()
}
} {
return false;
}
}
true
} else {
false
}
}
}
}
fn get_config(&self) -> Result<Config<P>, NetworkError> {
let mut c = Config::new();
for (a, edges) in self.ospf.links.iter() {
for (b, (weight, area)) in edges.iter() {
if *weight != DEFAULT_LINK_WEIGHT {
c.add(ConfigExpr::IgpLinkWeight {
source: *a,
target: *b,
weight: *weight,
})?;
}
if !area.is_backbone() {
let _ = c.add(ConfigExpr::OspfArea {
source: *a,
target: *b,
area: *area,
});
}
}
}
for ((source, target), session_type) in &self.bgp_sessions {
let Some(session_type) = session_type else {
continue;
};
let (src, dst, session_type) = (*source, *target, *session_type);
match c.add(ConfigExpr::BgpSession {
source: src,
target: dst,
session_type,
}) {
Ok(_) => {}
Err(ConfigError::ConfigExprOverload) => {
let Some(ConfigExpr::BgpSession {
source,
target,
session_type: old_session,
}) = c.get_mut(ConfigExprKey::BgpSession {
speaker_a: src,
speaker_b: dst,
})
else {
unreachable!()
};
if *old_session == BgpSessionType::IBgpPeer
&& session_type == BgpSessionType::IBgpClient
{
std::mem::swap(source, target);
*old_session = BgpSessionType::IBgpClient;
} else if *old_session == BgpSessionType::IBgpClient
&& session_type == BgpSessionType::IBgpClient
{
return Err(NetworkError::InconsistentBgpSession(src, dst));
}
}
Err(ConfigError::ConfigModifier) => unreachable!(),
}
}
for r in self.internal_routers() {
let rid = r.router_id();
for neighbor in r.bgp.get_sessions().keys() {
for rm in r.bgp.get_route_maps(*neighbor, RouteMapDirection::Incoming) {
c.add(ConfigExpr::BgpRouteMap {
router: rid,
neighbor: *neighbor,
direction: RouteMapDirection::Incoming,
map: rm.clone(),
})?;
}
for rm in r.bgp.get_route_maps(*neighbor, RouteMapDirection::Outgoing) {
c.add(ConfigExpr::BgpRouteMap {
router: rid,
neighbor: *neighbor,
direction: RouteMapDirection::Outgoing,
map: rm.clone(),
})?;
}
}
for (prefix, target) in r.sr.get_table().iter() {
c.add(ConfigExpr::StaticRoute {
router: rid,
prefix: *prefix,
target: *target,
})?;
}
for r in self.internal_routers() {
if r.get_load_balancing() {
c.add(ConfigExpr::LoadBalancing {
router: r.router_id(),
})?;
}
}
}
Ok(c)
}
}