diff --git a/dpd-client/tests/integration_tests/service.rs b/dpd-client/tests/integration_tests/service.rs index 78e70bee..47f98302 100644 --- a/dpd-client/tests/integration_tests/service.rs +++ b/dpd-client/tests/integration_tests/service.rs @@ -277,6 +277,62 @@ async fn test_ndp_needed() -> TestResult { Ok(()) } +// An IPv4 packet routed via an IPv6 nexthop (e.g. BGP unnumbered) with no +// neighbor entry should come up to the service port asking for NDP resolution +// of the IPv6 nexthop. +#[tokio::test] +#[ignore] +async fn test_ndp_needed_ipv4_via_ipv6() -> TestResult { + let switch = &*get_switch().await; + + let ingress_port = PhysPort(10); + let egress_port = PhysPort(15); + let router_ip = "fe80::50"; + let src_mac = "e0:d5:5e:67:89:ab"; + let router_mac = switch.get_port_mac(ingress_port).unwrap(); + + common::set_route_ipv4_over_ipv6( + switch, + "10.10.50.0/24", + egress_port, + router_ip, + ) + .await?; + + let (send_pkt, mut recv_pkt) = common::gen_udp_routed_pair( + switch, + egress_port, + router_mac, + Endpoint::parse(src_mac, "10.10.10.10", 3333).unwrap(), + Endpoint::parse("e0:d5:5e:67:89:ac", "10.10.50.11", 4444).unwrap(), + ); + + let send = TestPacket { packet: Arc::new(send_pkt), port: ingress_port }; + + let payload = router_ip.parse::().unwrap().octets(); + common::add_sidecar_hdr( + switch, + &mut recv_pkt, + sidecar::SC_NEIGHBOR_NEEDED, + ingress_port, + egress_port, + Some(&payload[..]), + ); + + // Because the packet wasn't successfully routed, the src/dst mac addresses + // should be unchanged. + let src_eth = send.packet.hdrs.eth_hdr.as_ref().unwrap(); + eth::EthHdr::rewrite_smac(&mut recv_pkt, src_eth.eth_smac); + eth::EthHdr::rewrite_dmac(&mut recv_pkt, src_eth.eth_dmac); + + let expected = + TestPacket { packet: Arc::new(recv_pkt), port: SERVICE_PORT }; + + assert!(one_counted_test(switch, send, Some(expected), "ndp_miss").await?); + + Ok(()) +} + #[tokio::test] #[ignore] async fn test_ttl_exceeded() -> TestResult { diff --git a/packet/src/sidecar.rs b/packet/src/sidecar.rs index fbd4cc62..eab75fdb 100644 --- a/packet/src/sidecar.rs +++ b/packet/src/sidecar.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::convert::TryInto; use std::fmt; diff --git a/tfportd/src/sidecar.rs b/tfportd/src/sidecar.rs index d87c0298..b92864f0 100644 --- a/tfportd/src/sidecar.rs +++ b/tfportd/src/sidecar.rs @@ -2,7 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/ // -// Copyright 2025 Oxide Computer Company +// Copyright 2026 Oxide Computer Company use std::net::IpAddr; use std::sync::Arc; @@ -100,39 +100,12 @@ pub fn resolution_needed( } } -pub fn handle_neighbor_needed(g: &Global, p: packet::Packet) { - if p.hdrs.ipv6_hdr.is_none() { - return error!(g.log, "not an ipv6 packet"); - } - - let sc = p.hdrs.sidecar_hdr.unwrap(); - let asic_id = p.hdrs.sidecar_hdr.as_ref().unwrap().sc_egress; - let ipv6 = { - let x = u128::from_be_bytes(sc.sc_payload[0..16].try_into().unwrap()); - x.into() - }; - resolution_needed(g, asic_id, IpAddr::V6(ipv6), p); -} - -pub fn handle_arp_needed(g: &Global, p: packet::Packet) { - if p.hdrs.ipv4_hdr.is_none() { - return error!(g.log, "not an ipv4 packet: {:?}", p); - } - - let sc = p.hdrs.sidecar_hdr.unwrap(); - let asic_id = p.hdrs.sidecar_hdr.as_ref().unwrap().sc_egress; - let ipv4 = { - let x = u32::from_be_bytes(sc.sc_payload[12..16].try_into().unwrap()); - x.into() - }; - resolution_needed(g, asic_id, IpAddr::V4(ipv4), p); -} - fn handle_packet(g: &Global, p: packet::Packet) { - let sc_code = match &p.hdrs.sidecar_hdr { - Some(sc) => sc.sc_code, + let sc = match &p.hdrs.sidecar_hdr { + Some(sc) => sc, None => return debug!(g.log, "received a non-sidecar packet"), }; + let sc_code = sc.sc_code; match sc_code { sidecar::SC_FWD_FROM_USERSPACE => { @@ -142,8 +115,23 @@ fn handle_packet(g: &Global, p: packet::Packet) { // These packets were addressed to a port that doesn't have a corresponding // tfport link. There's nothing we want to do with them. } - sidecar::SC_ARP_NEEDED => handle_arp_needed(g, p), - sidecar::SC_NEIGHBOR_NEEDED => handle_neighbor_needed(g, p), + sidecar::SC_ARP_NEEDED => { + if p.hdrs.ipv4_hdr.is_none() && p.hdrs.ipv6_hdr.is_none() { + error!(g.log, "not an ip packet (sc_code: {sc_code}): {p:?}"); + return None; + } + let [.., a, b, c, d] = sc.sc_payload; + let ip = IpAddr::V4(Ipv4Addr::new(a, b, c, d)); + resolution_needed(g, sc.sc_egress, ip, p) + } + sidecar::SC_NEIGHBOR_NEEDED => { + if p.hdrs.ipv6_hdr.is_none() { + error!(g.log, "not an ipv6 packet (sc_code: {sc_code}): {p:?}"); + return None; + } + let ip = IpAddr::V6(Ipv6Addr::from(sc.sc_payload)); + resolution_needed(g, sc.sc_egress, ip, p) + } // TODO-completeness: implement this as per dendrite#156 sidecar::SC_ICMP_NEEDED => {} x => debug!(g.log, "unrecognized sidecar code: {:x}", x),