1use crate::constants;
8use anyhow::Context as _;
9use assert_matches::assert_matches;
10use fuchsia_async::{DurationExt as _, TimeoutExt as _};
11use futures::{future, FutureExt as _, Stream, StreamExt as _, TryStreamExt as _};
12use net_types::ip::Ip as _;
13use net_types::Witness as _;
14use packet::serialize::{InnerPacketBuilder, Serializer};
15use packet::Buf;
16use packet_formats::ethernet::{
17 EtherType, EthernetFrameBuilder, EthernetFrameLengthCheck, ETHERNET_MIN_BODY_LEN_NO_TAG,
18};
19use packet_formats::icmp::ndp::options::{NdpOption, NdpOptionBuilder};
20use packet_formats::icmp::ndp::{
21 NeighborAdvertisement, NeighborSolicitation, OptionSequenceBuilder, RouterAdvertisement,
22 RouterSolicitation,
23};
24use packet_formats::icmp::{IcmpMessage, IcmpPacketBuilder, IcmpZeroCode};
25use packet_formats::ip::Ipv6Proto;
26use packet_formats::ipv6::Ipv6PacketBuilder;
27use packet_formats::testutil::parse_icmp_packet_in_ip_packet_in_ethernet_frame;
28use std::fmt::Debug;
29
30pub const MESSAGE_TTL: u8 = 255;
34
35pub fn create_message<M: IcmpMessage<net_types::ip::Ipv6, Code = IcmpZeroCode> + Debug>(
38 src_mac: net_types::ethernet::Mac,
39 dst_mac: net_types::ethernet::Mac,
40 src_ip: net_types::ip::Ipv6Addr,
41 dst_ip: net_types::ip::Ipv6Addr,
42 message: M,
43 options: &[NdpOptionBuilder<'_>],
44) -> crate::Result<Buf<Vec<u8>>> {
45 Ok(OptionSequenceBuilder::new(options.iter())
46 .into_serializer()
47 .encapsulate(IcmpPacketBuilder::<_, _>::new(src_ip, dst_ip, IcmpZeroCode, message))
48 .encapsulate(Ipv6PacketBuilder::new(src_ip, dst_ip, MESSAGE_TTL, Ipv6Proto::Icmpv6))
49 .encapsulate(EthernetFrameBuilder::new(
50 src_mac,
51 dst_mac,
52 EtherType::Ipv6,
53 ETHERNET_MIN_BODY_LEN_NO_TAG,
54 ))
55 .serialize_vec_outer()
56 .map_err(|e| anyhow::anyhow!("failed to serialize NDP packet: {:?}", e))?
57 .unwrap_b())
58}
59
60pub async fn write_message<M: IcmpMessage<net_types::ip::Ipv6, Code = IcmpZeroCode> + Debug>(
66 src_mac: net_types::ethernet::Mac,
67 dst_mac: net_types::ethernet::Mac,
68 src_ip: net_types::ip::Ipv6Addr,
69 dst_ip: net_types::ip::Ipv6Addr,
70 message: M,
71 options: &[NdpOptionBuilder<'_>],
72 ep: &netemul::TestFakeEndpoint<'_>,
73) -> crate::Result {
74 let ser = create_message(src_mac, dst_mac, src_ip, dst_ip, message, options)?;
75 ep.write(ser.as_ref()).await.context("failed to write to fake endpoint")
76}
77
78pub async fn send_ra<'a>(
80 fake_ep: &netemul::TestFakeEndpoint<'a>,
81 ra: RouterAdvertisement,
82 options: &[NdpOptionBuilder<'_>],
83 src_ip: net_types::ip::Ipv6Addr,
84) -> crate::Result {
85 write_message(
86 constants::eth::MAC_ADDR,
87 net_types::ethernet::Mac::from(
88 &net_types::ip::Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS,
89 ),
90 src_ip,
91 net_types::ip::Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get(),
92 ra,
93 options,
94 fake_ep,
95 )
96 .await
97}
98
99pub async fn send_ra_with_router_lifetime<'a>(
101 fake_ep: &netemul::TestFakeEndpoint<'a>,
102 lifetime: u16,
103 options: &[NdpOptionBuilder<'_>],
104 src_ip: net_types::ip::Ipv6Addr,
105) -> crate::Result {
106 let ra = RouterAdvertisement::new(
107 0, false, false, lifetime, 0, 0, );
114 send_ra(fake_ep, ra, options, src_ip).await
115}
116
117pub type DadState = Result<
120 fidl_fuchsia_net_interfaces::AddressAssignmentState,
121 fidl_fuchsia_net_interfaces_ext::admin::AddressStateProviderError,
122>;
123
124pub async fn expect_dad_neighbor_solicitation(fake_ep: &netemul::TestFakeEndpoint<'_>) -> Vec<u8> {
127 let ret = fake_ep
128 .frame_stream()
129 .try_filter_map(|(data, dropped)| {
130 assert_eq!(dropped, 0);
131 future::ok(
132 parse_icmp_packet_in_ip_packet_in_ethernet_frame::<
133 net_types::ip::Ipv6,
134 _,
135 NeighborSolicitation,
136 _,
137 >(&data, EthernetFrameLengthCheck::NoCheck, |p| {
138 assert_matches!(
139 &p.body().iter().collect::<Vec<_>>()[..],
140 [NdpOption::Nonce(_)]
141 );
142 })
143 .map_or(
144 None,
145 |(_src_mac, dst_mac, src_ip, dst_ip, ttl, message, _code)| {
146 if message.target_address() != &constants::ipv6::LINK_LOCAL_ADDR {
149 return None;
150 }
151
152 Some((dst_mac, src_ip, dst_ip, ttl, data))
153 },
154 ),
155 )
156 })
157 .try_next()
158 .map(|r| r.context("error getting OnData event"))
159 .on_timeout(crate::ASYNC_EVENT_POSITIVE_CHECK_TIMEOUT.after_now(), || {
160 Err(anyhow::anyhow!(
161 "timed out waiting for a neighbor solicitation targetting {}",
162 constants::ipv6::LINK_LOCAL_ADDR
163 ))
164 })
165 .await
166 .unwrap()
167 .expect("failed to get next OnData event");
168
169 let (dst_mac, src_ip, dst_ip, ttl, data) = ret;
170 let expected_dst = constants::ipv6::LINK_LOCAL_ADDR.to_solicited_node_address();
171 assert_eq!(src_ip, net_types::ip::Ipv6::UNSPECIFIED_ADDRESS);
172 assert_eq!(dst_ip, expected_dst.get());
173 assert_eq!(dst_mac, net_types::ethernet::Mac::from(&expected_dst));
174 assert_eq!(ttl, MESSAGE_TTL);
175
176 data
177}
178
179pub async fn fail_dad_with_ns(fake_ep: &netemul::TestFakeEndpoint<'_>) {
182 let snmc = constants::ipv6::LINK_LOCAL_ADDR.to_solicited_node_address();
183 write_message(
184 constants::eth::MAC_ADDR,
185 net_types::ethernet::Mac::from(&snmc),
186 net_types::ip::Ipv6::UNSPECIFIED_ADDRESS,
187 snmc.get(),
188 NeighborSolicitation::new(constants::ipv6::LINK_LOCAL_ADDR),
189 &[],
190 fake_ep,
191 )
192 .await
193 .expect("failed to write NDP message");
194}
195
196pub async fn fail_dad_with_na(fake_ep: &netemul::TestFakeEndpoint<'_>) {
199 write_message(
200 constants::eth::MAC_ADDR,
201 net_types::ethernet::Mac::from(
202 &net_types::ip::Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS,
203 ),
204 constants::ipv6::LINK_LOCAL_ADDR,
205 net_types::ip::Ipv6::ALL_NODES_LINK_LOCAL_MULTICAST_ADDRESS.get(),
206 NeighborAdvertisement::new(
207 false, false, false, constants::ipv6::LINK_LOCAL_ADDR,
211 ),
212 &[NdpOptionBuilder::TargetLinkLayerAddress(&constants::eth::MAC_ADDR.bytes())],
213 fake_ep,
214 )
215 .await
216 .expect("failed to write NDP message");
217}
218
219async fn dad_state(
220 state_stream: &mut (impl Stream<Item = DadState> + std::marker::Unpin),
221) -> DadState {
222 let state = match state_stream.by_ref().next().await.expect("state stream not ended") {
225 Ok(fidl_fuchsia_net_interfaces::AddressAssignmentState::Tentative) => {
226 state_stream.by_ref().next().await.expect("state stream not ended")
227 }
228 state => state,
229 };
230 match state {
232 Ok(_) => {}
233 Err(_) => {
234 assert_matches::assert_matches!(state_stream.by_ref().next().await, None)
235 }
236 }
237 state
238}
239
240pub async fn assert_dad_failed(
243 mut state_stream: (impl Stream<Item = DadState> + std::marker::Unpin),
244) {
245 assert_matches::assert_matches!(
246 dad_state(&mut state_stream).await,
247 Err(fidl_fuchsia_net_interfaces_ext::admin::AddressStateProviderError::AddressRemoved(
248 fidl_fuchsia_net_interfaces_admin::AddressRemovalReason::DadFailed
249 ))
250 );
251}
252
253pub async fn assert_dad_success(
256 state_stream: &mut (impl Stream<Item = DadState> + std::marker::Unpin),
257) {
258 assert_matches::assert_matches!(
259 dad_state(state_stream).await,
260 Ok(fidl_fuchsia_net_interfaces::AddressAssignmentState::Assigned)
261 );
262}
263
264pub async fn wait_for_router_solicitation(fake_ep: &netemul::TestFakeEndpoint<'_>) {
266 let () = fake_ep
267 .frame_stream()
268 .try_filter_map(|(data, dropped)| {
269 assert_eq!(dropped, 0);
270 future::ok(
271 parse_icmp_packet_in_ip_packet_in_ethernet_frame::<
272 net_types::ip::Ipv6,
273 _,
274 RouterSolicitation,
275 _,
276 >(&data, EthernetFrameLengthCheck::NoCheck, |_| {})
277 .map_or(None, |_| Some(())),
278 )
279 })
280 .try_next()
281 .map(|r| r.context("error getting OnData event"))
282 .on_timeout(crate::ASYNC_EVENT_POSITIVE_CHECK_TIMEOUT.after_now(), || {
283 Err(anyhow::anyhow!("timed out waiting for RS packet"))
284 })
285 .await
286 .unwrap()
287 .expect("failed to get next OnData event");
288}