Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 96 additions & 29 deletions daemon/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4289,6 +4289,41 @@ pub(crate) struct PathBinaryFlags {
pub only_binary: bool,
}

/// NEXT_HOP / MP_REACH are stored separately from attributes in the RIB.
/// Rebuild them for ListPath, including the NLRI inside MP_REACH.
fn path_nexthop_attribute(
net: &Nlri,
family: Family,
nexthop: Option<packet::bgp::Nexthop>,
) -> Option<Attribute> {
if family == Family::IPV4 && matches!(nexthop, Some(packet::bgp::Nexthop::V4(_))) {
return Attribute::new_with_bin(Attribute::NEXTHOP, nexthop.unwrap().to_bytes());
}
if family == Family::IPV4 && nexthop.is_none() {
return None;
}
let mut nh = nexthop.map(|n| n.to_bytes()).unwrap_or_default();
if matches!(
family,
Family::IPV4_FLOWSPEC
| Family::IPV6_FLOWSPEC
| Family::IPV4_FLOWSPEC_VPN
| Family::IPV6_FLOWSPEC_VPN
) {
nh.clear();
} else if matches!(family, Family::IPV4_VPN | Family::IPV6_VPN) {
let mut vpn_nh = vec![0; 8];
vpn_nh.extend(nh);
nh = vpn_nh;
}
let mut body = family.afi().to_be_bytes().to_vec();
body.extend([family.safi(), nh.len() as u8]);
body.extend(nh);
body.push(0);
body.extend(net.encode_to_bytes());
Attribute::new_with_bin(Attribute::MP_REACH, body)
}

pub(crate) fn destination_to_api(
d: rustybgp_table::DestinationEntry,
family: Family,
Expand All @@ -4299,37 +4334,69 @@ pub(crate) fn destination_to_api(
paths: d
.paths
.into_iter()
.map(|p| api::Path {
nlri: if binary.only_binary {
None
} else {
Some(nlri_to_api(&d.net))
},
family: Some(family_to_api(family)),
identifier: p.remote_path_id,
age: Some(prost_types::Timestamp {
seconds: p.timestamp as i64,
nanos: 0,
}),
pattrs: if binary.only_binary {
vec![]
} else {
p.attr.iter().map(attr_to_api).collect()
},
validation: p.validation.map(rpki_validation_to_api),
stale: p.stale,
filtered: p.filtered,
nlri_binary: if binary.nlri_binary {
d.net.encode_to_bytes()
} else {
.map(|p| {
let nh_attr = path_nexthop_attribute(&d.net, family, p.nexthop);
let attrs = p
.attr
.iter()
.filter(|a| !matches!(a.code(), Attribute::NEXTHOP | Attribute::MP_REACH))
.chain(nh_attr.iter());
let pattrs = if binary.only_binary {
vec![]
},
pattrs_binary: if binary.attr_binary {
p.attr.iter().map(|a| a.encode_to_bytes()).collect()
} else {
vec![]
},
..Default::default()
attrs
.clone()
.map(|a| {
if a.code() != Attribute::MP_REACH {
return attr_to_api(a);
}
let next_hops = match p.nexthop {
Some(packet::bgp::Nexthop::V6LinkLocal(global, local)) => {
vec![global.to_string(), local.to_string()]
}
Some(nh) => vec![nh.addr().to_string()],
None => vec![],
};
api::Attribute {
attr: Some(api::attribute::Attr::MpReach(
api::MpReachNlriAttribute {
family: Some(family_to_api(family)),
next_hops,
nlris: vec![nlri_to_api(&d.net)],
},
)),
}
})
.collect()
};
api::Path {
nlri: if binary.only_binary {
None
} else {
Some(nlri_to_api(&d.net))
},
family: Some(family_to_api(family)),
identifier: p.remote_path_id,
age: Some(prost_types::Timestamp {
seconds: p.timestamp as i64,
nanos: 0,
}),
pattrs,
validation: p.validation.map(rpki_validation_to_api),
stale: p.stale,
filtered: p.filtered,
nlri_binary: if binary.nlri_binary {
d.net.encode_to_bytes()
} else {
vec![]
},
pattrs_binary: if binary.attr_binary {
attrs.map(|a| a.encode_to_bytes()).collect()
} else {
vec![]
},
..Default::default()
}
})
.collect(),
}
Expand Down
3 changes: 2 additions & 1 deletion daemon/src/event/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,12 +505,13 @@ impl NlriSink for AdjOutSink {
_dest_id: u32,
nlri: packet::Nlri,
_path_id: u32,
_nexthop: Option<bgp::Nexthop>,
nexthop: Option<bgp::Nexthop>,
attr: Arc<Vec<packet::Attribute>>,
source: &Arc<table::Source>,
) {
let entry = table::PathEntry {
source: Arc::clone(source),
nexthop,
remote_path_id: 0,
timestamp: 0u32,
attr,
Expand Down
102 changes: 102 additions & 0 deletions daemon/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9278,6 +9278,108 @@ mod tests {
}
}

#[tokio::test]
async fn list_path_adj_in_restores_structured_and_binary_nexthops() {
let svc = make_grpc_service();
let peer: IpAddr = "10.0.0.2".parse().unwrap();
let source = Arc::new(table::Source::new(
peer,
"10.0.0.1".parse().unwrap(),
65002,
65001,
Ipv4Addr::new(10, 0, 0, 2),
PeerRole::Ebgp,
));
let cases = [
(Family::IPV4, "198.51.100.0/24", "192.0.2.1"),
(Family::IPV6, "2001:db8:1::/48", "2001:db8::1"),
];
for (family, prefix, nexthop) in cases {
let net: packet::Nlri = prefix.parse().unwrap();
let nh = if family == Family::IPV4 {
bgp::Nexthop::V4(nexthop.parse().unwrap())
} else {
bgp::Nexthop::V6(nexthop.parse().unwrap())
};
assert!(!svc.tables.insert_route(
source.clone(),
family,
packet::PathNlri {
nlri: net.clone(),
path_id: 0,
},
Some(nh),
Arc::new(vec![
packet::Attribute::new_with_value(packet::Attribute::ORIGIN, 0).unwrap(),
]),
None,
0,
));

let request = api::ListPathRequest {
table_type: api::TableType::AdjIn as i32,
name: peer.to_string(),
family: Some(convert::family_to_api(family)),
enable_nlri_binary: true,
enable_attribute_binary: true,
..Default::default()
};
let mut stream = svc
.list_path(tonic::Request::new(request.clone()))
.await
.unwrap()
.into_inner();
let destination = stream.next().await.unwrap().unwrap().destination.unwrap();
assert!(stream.next().await.is_none());
let path = &destination.paths[0];
assert_eq!(path.nlri_binary, net.encode_to_bytes());
assert!(path.nlri.is_some());
if family == Family::IPV4 {
assert!(path.pattrs.iter().any(|a| matches!(
&a.attr,
Some(api::attribute::Attr::NextHop(nh)) if nh.next_hop == nexthop
)));
let mut expected = vec![0x40, packet::Attribute::NEXTHOP, 4];
expected.extend(nexthop.parse::<Ipv4Addr>().unwrap().octets());
assert!(path.pattrs_binary.contains(&expected));
} else {
assert!(path.pattrs.iter().any(|a| matches!(
&a.attr,
Some(api::attribute::Attr::MpReach(mp))
if mp.next_hops == [nexthop] && mp.nlris.len() == 1
)));
assert!(path.pattrs_binary.iter().any(|bytes| {
bytes.starts_with(&[0x80, packet::Attribute::MP_REACH])
&& bytes.get(3..7) == Some(&[0, 2, 1, 16][..])
&& bytes.get(7..23)
== Some(&nexthop.parse::<Ipv6Addr>().unwrap().octets()[..])
}));
}

let mut only_binary = request;
only_binary.enable_nlri_binary = false;
only_binary.enable_attribute_binary = false;
only_binary.enable_only_binary = true;
let mut stream = svc
.list_path(tonic::Request::new(only_binary))
.await
.unwrap()
.into_inner();
let path = &stream
.next()
.await
.unwrap()
.unwrap()
.destination
.unwrap()
.paths[0];
assert!(path.nlri.is_none());
assert!(path.pattrs.is_empty());
assert_eq!(path.nlri_binary, net.encode_to_bytes());
assert!(!path.pattrs_binary.is_empty());
}
}

#[tokio::test]
async fn add_path_inserts_route() {
let svc = make_grpc_service();
Expand Down
11 changes: 9 additions & 2 deletions daemon/src/table_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,10 @@ impl TableManager {
net.path_id,
nh,
post_policy_attr,
Some(original_attr),
Some(table::OriginalPath {
attr: original_attr,
nexthop,
}),
filtered,
nexthop_invalid_flag,
pl,
Expand Down Expand Up @@ -1279,6 +1282,7 @@ impl TableShard {
) {
let paths = self.rtable.collect_adj_in_paths(peer, None, false);
for (family, net, remote_path_id, mut nh, source, original_attr, timestamp) in paths {
let original_nexthop = nh;
let old_nh =
self.rtable
.lookup_nexthop(source.remote_addr, family, &net, remote_path_id);
Expand Down Expand Up @@ -1333,7 +1337,10 @@ impl TableShard {
remote_path_id,
nh,
post_policy_attr,
Some(original_attr),
Some(table::OriginalPath {
attr: original_attr,
nexthop: original_nexthop,
}),
filtered,
nexthop_invalid_flag,
None,
Expand Down
Loading
Loading