1#[cfg(target_os = "fuchsia")]
15pub mod sync;
16
17use std::collections::HashMap;
18use std::fmt::Debug;
19use std::num::NonZeroU16;
20use std::ops::RangeInclusive;
21
22use async_utils::fold::FoldWhile;
23use fidl::marker::SourceBreaking;
24use futures::{Stream, StreamExt as _, TryStreamExt as _};
25use thiserror::Error;
26use {
27 fidl_fuchsia_ebpf as febpf, fidl_fuchsia_net as fnet, fidl_fuchsia_net_filter as fnet_filter,
28 fidl_fuchsia_net_interfaces_ext as fnet_interfaces_ext,
29 fidl_fuchsia_net_matchers_ext as fnet_matchers_ext, fidl_fuchsia_net_root as fnet_root,
30};
31
32#[derive(Debug, Error, PartialEq)]
35pub enum FidlConversionError {
36 #[error("union is of an unknown variant: {0}")]
37 UnknownUnionVariant(&'static str),
38 #[error("namespace ID not provided")]
39 MissingNamespaceId,
40 #[error("namespace domain not provided")]
41 MissingNamespaceDomain,
42 #[error("routine ID not provided")]
43 MissingRoutineId,
44 #[error("routine type not provided")]
45 MissingRoutineType,
46 #[error("IP installation hook not provided")]
47 MissingIpInstallationHook,
48 #[error("NAT installation hook not provided")]
49 MissingNatInstallationHook,
50 #[error("interface matcher specified an invalid ID of 0")]
51 ZeroInterfaceId,
52 #[error("invalid address range (start must be <= end)")]
53 InvalidAddressRange,
54 #[error("address range start and end addresses are not the same IP family")]
55 AddressRangeFamilyMismatch,
56 #[error("prefix length of subnet is longer than number of bits in IP address")]
57 SubnetPrefixTooLong,
58 #[error("host bits are set in subnet network")]
59 SubnetHostBitsSet,
60 #[error("invalid port matcher range (start must be <= end)")]
61 InvalidPortMatcherRange,
62 #[error("transparent proxy action specified an invalid local port of 0")]
63 UnspecifiedTransparentProxyPort,
64 #[error("NAT action specified an invalid rewrite port of 0")]
65 UnspecifiedNatPort,
66 #[error("invalid port range (start must be <= end)")]
67 InvalidPortRange,
68 #[error("non-error result variant could not be converted to an error")]
69 NotAnError,
70}
71
72impl From<fnet_matchers_ext::PortError> for FidlConversionError {
73 fn from(value: fnet_matchers_ext::PortError) -> Self {
74 match value {
75 fnet_matchers_ext::PortError::InvalidPortRange => {
76 FidlConversionError::InvalidPortMatcherRange
77 }
78 }
79 }
80}
81
82impl From<fnet_matchers_ext::InterfaceError> for FidlConversionError {
83 fn from(value: fnet_matchers_ext::InterfaceError) -> Self {
84 match value {
85 fnet_matchers_ext::InterfaceError::ZeroId => FidlConversionError::ZeroInterfaceId,
86 fnet_matchers_ext::InterfaceError::UnknownUnionVariant => {
87 FidlConversionError::UnknownUnionVariant(type_names::INTERFACE_MATCHER)
88 }
89 fidl_fuchsia_net_matchers_ext::InterfaceError::UnknownPortClass(
90 unknown_port_class_error,
91 ) => match unknown_port_class_error {
92 fnet_interfaces_ext::UnknownPortClassError::NetInterfaces(_) => {
93 FidlConversionError::UnknownUnionVariant(type_names::NET_INTERFACES_PORT_CLASS)
94 }
95 fnet_interfaces_ext::UnknownPortClassError::HardwareNetwork(_) => {
96 FidlConversionError::UnknownUnionVariant(
97 type_names::HARDWARE_NETWORK_PORT_CLASS,
98 )
99 }
100 },
101 }
102 }
103}
104
105impl From<fnet_matchers_ext::AddressError> for FidlConversionError {
106 fn from(value: fnet_matchers_ext::AddressError) -> Self {
107 match value {
108 fnet_matchers_ext::AddressError::AddressMatcherType(address_matcher_type_error) => {
109 address_matcher_type_error.into()
110 }
111 }
112 }
113}
114
115impl From<fnet_matchers_ext::AddressMatcherTypeError> for FidlConversionError {
116 fn from(value: fnet_matchers_ext::AddressMatcherTypeError) -> Self {
117 match value {
118 fnet_matchers_ext::AddressMatcherTypeError::Subnet(subnet_error) => subnet_error.into(),
119 fnet_matchers_ext::AddressMatcherTypeError::AddressRange(address_range_error) => {
120 address_range_error.into()
121 }
122 fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant => {
123 FidlConversionError::UnknownUnionVariant(type_names::ADDRESS_MATCHER_TYPE)
124 }
125 }
126 }
127}
128
129impl From<fnet_matchers_ext::AddressRangeError> for FidlConversionError {
130 fn from(value: fnet_matchers_ext::AddressRangeError) -> Self {
131 match value {
132 fnet_matchers_ext::AddressRangeError::Invalid => {
133 FidlConversionError::InvalidAddressRange
134 }
135 fnet_matchers_ext::AddressRangeError::FamilyMismatch => {
136 FidlConversionError::AddressRangeFamilyMismatch
137 }
138 }
139 }
140}
141
142impl From<fnet_matchers_ext::SubnetError> for FidlConversionError {
143 fn from(value: fnet_matchers_ext::SubnetError) -> Self {
144 match value {
145 fnet_matchers_ext::SubnetError::PrefixTooLong => {
146 FidlConversionError::SubnetPrefixTooLong
147 }
148 fnet_matchers_ext::SubnetError::HostBitsSet => FidlConversionError::SubnetHostBitsSet,
149 }
150 }
151}
152
153impl From<fnet_matchers_ext::TransportProtocolError> for FidlConversionError {
154 fn from(value: fnet_matchers_ext::TransportProtocolError) -> Self {
155 match value {
156 fnet_matchers_ext::TransportProtocolError::Port(port_matcher_error) => {
157 port_matcher_error.into()
158 }
159 fnet_matchers_ext::TransportProtocolError::UnknownUnionVariant => {
160 FidlConversionError::UnknownUnionVariant(type_names::TRANSPORT_PROTOCOL)
161 }
162 }
163 }
164}
165
166mod type_names {
169 pub(super) const RESOURCE_ID: &str = "fuchsia.net.filter/ResourceId";
170 pub(super) const DOMAIN: &str = "fuchsia.net.filter/Domain";
171 pub(super) const IP_INSTALLATION_HOOK: &str = "fuchsia.net.filter/IpInstallationHook";
172 pub(super) const NAT_INSTALLATION_HOOK: &str = "fuchsia.net.filter/NatInstallationHook";
173 pub(super) const ROUTINE_TYPE: &str = "fuchsia.net.filter/RoutineType";
174 pub(super) const INTERFACE_MATCHER: &str = "fuchsia.net.matchers/Interface";
175 pub(super) const ADDRESS_MATCHER_TYPE: &str = "fuchsia.net.filter/AddressMatcherType";
176 pub(super) const TRANSPORT_PROTOCOL: &str = "fuchsia.net.matchers/TransportProtocol";
177 pub(super) const ACTION: &str = "fuchsia.net.filter/Action";
178 pub(super) const MARK_ACTION: &str = "fuchsia.net.filter/MarkAction";
179 pub(super) const TRANSPARENT_PROXY: &str = "fuchsia.net.filter/TransparentProxy";
180 pub(super) const RESOURCE: &str = "fuchsia.net.filter/Resource";
181 pub(super) const EVENT: &str = "fuchsia.net.filter/Event";
182 pub(super) const CHANGE: &str = "fuchsia.net.filter/Change";
183 pub(super) const CHANGE_VALIDATION_ERROR: &str = "fuchsia.net.filter/ChangeValidationError";
184 pub(super) const CHANGE_VALIDATION_RESULT: &str = "fuchsia.net.filter/ChangeValidationResult";
185 pub(super) const COMMIT_ERROR: &str = "fuchsia.net.filter/CommitError";
186 pub(super) const COMMIT_RESULT: &str = "fuchsia.net.filter/CommitResult";
187 pub(super) const NET_INTERFACES_PORT_CLASS: &str = "fuchsia.net.interfaces/PortClass";
188 pub(super) const HARDWARE_NETWORK_PORT_CLASS: &str = "fuchsia.hardware.network/PortClass";
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
193pub struct NamespaceId(pub String);
194
195#[derive(Debug, Clone, PartialEq, Eq, Hash)]
197pub struct RoutineId {
198 pub namespace: NamespaceId,
199 pub name: String,
200}
201
202impl From<fnet_filter::RoutineId> for RoutineId {
203 fn from(id: fnet_filter::RoutineId) -> Self {
204 let fnet_filter::RoutineId { namespace, name } = id;
205 Self { namespace: NamespaceId(namespace), name }
206 }
207}
208
209impl From<RoutineId> for fnet_filter::RoutineId {
210 fn from(id: RoutineId) -> Self {
211 let RoutineId { namespace, name } = id;
212 let NamespaceId(namespace) = namespace;
213 Self { namespace, name }
214 }
215}
216
217#[derive(Debug, Clone, PartialEq, Eq, Hash)]
219pub struct RuleId {
220 pub routine: RoutineId,
221 pub index: u32,
222}
223
224impl From<fnet_filter::RuleId> for RuleId {
225 fn from(id: fnet_filter::RuleId) -> Self {
226 let fnet_filter::RuleId { routine, index } = id;
227 Self { routine: routine.into(), index }
228 }
229}
230
231impl From<RuleId> for fnet_filter::RuleId {
232 fn from(id: RuleId) -> Self {
233 let RuleId { routine, index } = id;
234 Self { routine: routine.into(), index }
235 }
236}
237
238#[derive(Debug, Clone, PartialEq, Eq, Hash)]
240pub enum ResourceId {
241 Namespace(NamespaceId),
242 Routine(RoutineId),
243 Rule(RuleId),
244}
245
246impl TryFrom<fnet_filter::ResourceId> for ResourceId {
247 type Error = FidlConversionError;
248
249 fn try_from(id: fnet_filter::ResourceId) -> Result<Self, Self::Error> {
250 match id {
251 fnet_filter::ResourceId::Namespace(id) => Ok(Self::Namespace(NamespaceId(id))),
252 fnet_filter::ResourceId::Routine(id) => Ok(Self::Routine(id.into())),
253 fnet_filter::ResourceId::Rule(id) => Ok(Self::Rule(id.into())),
254 fnet_filter::ResourceId::__SourceBreaking { .. } => {
255 Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE_ID))
256 }
257 }
258 }
259}
260
261impl From<ResourceId> for fnet_filter::ResourceId {
262 fn from(id: ResourceId) -> Self {
263 match id {
264 ResourceId::Namespace(NamespaceId(id)) => fnet_filter::ResourceId::Namespace(id),
265 ResourceId::Routine(id) => fnet_filter::ResourceId::Routine(id.into()),
266 ResourceId::Rule(id) => fnet_filter::ResourceId::Rule(id.into()),
267 }
268 }
269}
270
271#[derive(Debug, Clone, PartialEq)]
273pub enum Domain {
274 Ipv4,
275 Ipv6,
276 AllIp,
277}
278
279impl From<Domain> for fnet_filter::Domain {
280 fn from(domain: Domain) -> Self {
281 match domain {
282 Domain::Ipv4 => fnet_filter::Domain::Ipv4,
283 Domain::Ipv6 => fnet_filter::Domain::Ipv6,
284 Domain::AllIp => fnet_filter::Domain::AllIp,
285 }
286 }
287}
288
289impl TryFrom<fnet_filter::Domain> for Domain {
290 type Error = FidlConversionError;
291
292 fn try_from(domain: fnet_filter::Domain) -> Result<Self, Self::Error> {
293 match domain {
294 fnet_filter::Domain::Ipv4 => Ok(Self::Ipv4),
295 fnet_filter::Domain::Ipv6 => Ok(Self::Ipv6),
296 fnet_filter::Domain::AllIp => Ok(Self::AllIp),
297 fnet_filter::Domain::__SourceBreaking { .. } => {
298 Err(FidlConversionError::UnknownUnionVariant(type_names::DOMAIN))
299 }
300 }
301 }
302}
303
304#[derive(Debug, Clone, PartialEq)]
306pub struct Namespace {
307 pub id: NamespaceId,
308 pub domain: Domain,
309}
310
311impl From<Namespace> for fnet_filter::Namespace {
312 fn from(namespace: Namespace) -> Self {
313 let Namespace { id, domain } = namespace;
314 let NamespaceId(id) = id;
315 Self { id: Some(id), domain: Some(domain.into()), __source_breaking: SourceBreaking }
316 }
317}
318
319impl TryFrom<fnet_filter::Namespace> for Namespace {
320 type Error = FidlConversionError;
321
322 fn try_from(namespace: fnet_filter::Namespace) -> Result<Self, Self::Error> {
323 let fnet_filter::Namespace { id, domain, __source_breaking } = namespace;
324 let id = NamespaceId(id.ok_or(FidlConversionError::MissingNamespaceId)?);
325 let domain = domain.ok_or(FidlConversionError::MissingNamespaceDomain)?.try_into()?;
326 Ok(Self { id, domain })
327 }
328}
329
330#[derive(Debug, Clone, Copy, PartialEq)]
332pub enum IpHook {
333 Ingress,
334 LocalIngress,
335 Forwarding,
336 LocalEgress,
337 Egress,
338}
339
340impl From<IpHook> for fnet_filter::IpInstallationHook {
341 fn from(hook: IpHook) -> Self {
342 match hook {
343 IpHook::Ingress => Self::Ingress,
344 IpHook::LocalIngress => Self::LocalIngress,
345 IpHook::Forwarding => Self::Forwarding,
346 IpHook::LocalEgress => Self::LocalEgress,
347 IpHook::Egress => Self::Egress,
348 }
349 }
350}
351
352impl TryFrom<fnet_filter::IpInstallationHook> for IpHook {
353 type Error = FidlConversionError;
354
355 fn try_from(hook: fnet_filter::IpInstallationHook) -> Result<Self, Self::Error> {
356 match hook {
357 fnet_filter::IpInstallationHook::Ingress => Ok(Self::Ingress),
358 fnet_filter::IpInstallationHook::LocalIngress => Ok(Self::LocalIngress),
359 fnet_filter::IpInstallationHook::Forwarding => Ok(Self::Forwarding),
360 fnet_filter::IpInstallationHook::LocalEgress => Ok(Self::LocalEgress),
361 fnet_filter::IpInstallationHook::Egress => Ok(Self::Egress),
362 fnet_filter::IpInstallationHook::__SourceBreaking { .. } => {
363 Err(FidlConversionError::UnknownUnionVariant(type_names::IP_INSTALLATION_HOOK))
364 }
365 }
366 }
367}
368
369#[derive(Debug, Clone, Copy, PartialEq)]
371pub enum NatHook {
372 Ingress,
373 LocalIngress,
374 LocalEgress,
375 Egress,
376}
377
378impl From<NatHook> for fnet_filter::NatInstallationHook {
379 fn from(hook: NatHook) -> Self {
380 match hook {
381 NatHook::Ingress => Self::Ingress,
382 NatHook::LocalIngress => Self::LocalIngress,
383 NatHook::LocalEgress => Self::LocalEgress,
384 NatHook::Egress => Self::Egress,
385 }
386 }
387}
388
389impl TryFrom<fnet_filter::NatInstallationHook> for NatHook {
390 type Error = FidlConversionError;
391
392 fn try_from(hook: fnet_filter::NatInstallationHook) -> Result<Self, Self::Error> {
393 match hook {
394 fnet_filter::NatInstallationHook::Ingress => Ok(Self::Ingress),
395 fnet_filter::NatInstallationHook::LocalIngress => Ok(Self::LocalIngress),
396 fnet_filter::NatInstallationHook::LocalEgress => Ok(Self::LocalEgress),
397 fnet_filter::NatInstallationHook::Egress => Ok(Self::Egress),
398 fnet_filter::NatInstallationHook::__SourceBreaking { .. } => {
399 Err(FidlConversionError::UnknownUnionVariant(type_names::NAT_INSTALLATION_HOOK))
400 }
401 }
402 }
403}
404
405#[derive(Debug, Clone, PartialEq)]
407pub struct InstalledIpRoutine {
408 pub hook: IpHook,
409 pub priority: i32,
410}
411
412impl From<InstalledIpRoutine> for fnet_filter::InstalledIpRoutine {
413 fn from(routine: InstalledIpRoutine) -> Self {
414 let InstalledIpRoutine { hook, priority } = routine;
415 Self {
416 hook: Some(hook.into()),
417 priority: Some(priority),
418 __source_breaking: SourceBreaking,
419 }
420 }
421}
422
423impl TryFrom<fnet_filter::InstalledIpRoutine> for InstalledIpRoutine {
424 type Error = FidlConversionError;
425
426 fn try_from(routine: fnet_filter::InstalledIpRoutine) -> Result<Self, Self::Error> {
427 let fnet_filter::InstalledIpRoutine { hook, priority, __source_breaking } = routine;
428 let hook = hook.ok_or(FidlConversionError::MissingIpInstallationHook)?;
429 let priority = priority.unwrap_or(fnet_filter::DEFAULT_ROUTINE_PRIORITY);
430 Ok(Self { hook: hook.try_into()?, priority })
431 }
432}
433
434#[derive(Debug, Clone, PartialEq)]
436pub struct InstalledNatRoutine {
437 pub hook: NatHook,
438 pub priority: i32,
439}
440
441impl From<InstalledNatRoutine> for fnet_filter::InstalledNatRoutine {
442 fn from(routine: InstalledNatRoutine) -> Self {
443 let InstalledNatRoutine { hook, priority } = routine;
444 Self {
445 hook: Some(hook.into()),
446 priority: Some(priority),
447 __source_breaking: SourceBreaking,
448 }
449 }
450}
451
452impl TryFrom<fnet_filter::InstalledNatRoutine> for InstalledNatRoutine {
453 type Error = FidlConversionError;
454
455 fn try_from(routine: fnet_filter::InstalledNatRoutine) -> Result<Self, Self::Error> {
456 let fnet_filter::InstalledNatRoutine { hook, priority, __source_breaking } = routine;
457 let hook = hook.ok_or(FidlConversionError::MissingNatInstallationHook)?;
458 let priority = priority.unwrap_or(fnet_filter::DEFAULT_ROUTINE_PRIORITY);
459 Ok(Self { hook: hook.try_into()?, priority })
460 }
461}
462
463#[derive(Debug, Clone, PartialEq)]
465pub enum RoutineType {
466 Ip(Option<InstalledIpRoutine>),
467 Nat(Option<InstalledNatRoutine>),
468}
469
470impl RoutineType {
471 pub fn is_installed(&self) -> bool {
472 match self {
475 Self::Ip(Some(_)) | Self::Nat(Some(_)) => true,
476 Self::Ip(None) | Self::Nat(None) => false,
477 }
478 }
479}
480
481impl From<RoutineType> for fnet_filter::RoutineType {
482 fn from(routine: RoutineType) -> Self {
483 match routine {
484 RoutineType::Ip(installation) => Self::Ip(fnet_filter::IpRoutine {
485 installation: installation.map(Into::into),
486 __source_breaking: SourceBreaking,
487 }),
488 RoutineType::Nat(installation) => Self::Nat(fnet_filter::NatRoutine {
489 installation: installation.map(Into::into),
490 __source_breaking: SourceBreaking,
491 }),
492 }
493 }
494}
495
496impl TryFrom<fnet_filter::RoutineType> for RoutineType {
497 type Error = FidlConversionError;
498
499 fn try_from(type_: fnet_filter::RoutineType) -> Result<Self, Self::Error> {
500 match type_ {
501 fnet_filter::RoutineType::Ip(fnet_filter::IpRoutine {
502 installation,
503 __source_breaking,
504 }) => Ok(RoutineType::Ip(installation.map(TryInto::try_into).transpose()?)),
505 fnet_filter::RoutineType::Nat(fnet_filter::NatRoutine {
506 installation,
507 __source_breaking,
508 }) => Ok(RoutineType::Nat(installation.map(TryInto::try_into).transpose()?)),
509 fnet_filter::RoutineType::__SourceBreaking { .. } => {
510 Err(FidlConversionError::UnknownUnionVariant(type_names::ROUTINE_TYPE))
511 }
512 }
513 }
514}
515
516#[derive(Debug, Clone, PartialEq)]
518pub struct Routine {
519 pub id: RoutineId,
520 pub routine_type: RoutineType,
521}
522
523impl From<Routine> for fnet_filter::Routine {
524 fn from(routine: Routine) -> Self {
525 let Routine { id, routine_type: type_ } = routine;
526 Self { id: Some(id.into()), type_: Some(type_.into()), __source_breaking: SourceBreaking }
527 }
528}
529
530impl TryFrom<fnet_filter::Routine> for Routine {
531 type Error = FidlConversionError;
532
533 fn try_from(routine: fnet_filter::Routine) -> Result<Self, Self::Error> {
534 let fnet_filter::Routine { id, type_, __source_breaking } = routine;
535 let id = id.ok_or(FidlConversionError::MissingRoutineId)?;
536 let type_ = type_.ok_or(FidlConversionError::MissingRoutineType)?;
537 Ok(Self { id: id.into(), routine_type: type_.try_into()? })
538 }
539}
540
541#[derive(Default, Clone, PartialEq)]
543pub struct Matchers {
544 pub in_interface: Option<fnet_matchers_ext::Interface>,
545 pub out_interface: Option<fnet_matchers_ext::Interface>,
546 pub src_addr: Option<fnet_matchers_ext::Address>,
547 pub dst_addr: Option<fnet_matchers_ext::Address>,
548 pub transport_protocol: Option<fnet_matchers_ext::TransportProtocol>,
549 pub ebpf_program: Option<febpf::ProgramId>,
550}
551
552impl From<Matchers> for fnet_filter::Matchers {
553 fn from(matchers: Matchers) -> Self {
554 let Matchers {
555 in_interface,
556 out_interface,
557 src_addr,
558 dst_addr,
559 transport_protocol,
560 ebpf_program,
561 } = matchers;
562 Self {
563 in_interface: in_interface.map(Into::into),
564 out_interface: out_interface.map(Into::into),
565 src_addr: src_addr.map(Into::into),
566 dst_addr: dst_addr.map(Into::into),
567 transport_protocol: transport_protocol.map(Into::into),
568 ebpf_program: ebpf_program.map(Into::into),
569 __source_breaking: SourceBreaking,
570 }
571 }
572}
573
574impl TryFrom<fnet_filter::Matchers> for Matchers {
575 type Error = FidlConversionError;
576
577 fn try_from(matchers: fnet_filter::Matchers) -> Result<Self, Self::Error> {
578 let fnet_filter::Matchers {
579 in_interface,
580 out_interface,
581 src_addr,
582 dst_addr,
583 transport_protocol,
584 ebpf_program,
585 __source_breaking,
586 } = matchers;
587 Ok(Self {
588 in_interface: in_interface.map(TryInto::try_into).transpose()?,
589 out_interface: out_interface.map(TryInto::try_into).transpose()?,
590 src_addr: src_addr.map(TryInto::try_into).transpose()?,
591 dst_addr: dst_addr.map(TryInto::try_into).transpose()?,
592 transport_protocol: transport_protocol.map(TryInto::try_into).transpose()?,
593 ebpf_program: ebpf_program.map(Into::into),
594 })
595 }
596}
597
598impl Debug for Matchers {
599 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
600 let mut debug_struct = f.debug_struct("Matchers");
601
602 let Matchers {
603 in_interface,
604 out_interface,
605 src_addr,
606 dst_addr,
607 transport_protocol,
608 ebpf_program,
609 } = &self;
610
611 if let Some(matcher) = in_interface {
613 let _ = debug_struct.field("in_interface", matcher);
614 }
615
616 if let Some(matcher) = out_interface {
617 let _ = debug_struct.field("out_interface", matcher);
618 }
619
620 if let Some(matcher) = src_addr {
621 let _ = debug_struct.field("src_addr", matcher);
622 }
623
624 if let Some(matcher) = dst_addr {
625 let _ = debug_struct.field("dst_addr", matcher);
626 }
627
628 if let Some(matcher) = transport_protocol {
629 let _ = debug_struct.field("transport_protocol", matcher);
630 }
631
632 if let Some(matcher) = ebpf_program {
633 let _ = debug_struct.field("ebpf_program", matcher);
634 }
635
636 debug_struct.finish()
637 }
638}
639
640#[derive(Debug, Clone, PartialEq)]
642pub enum Action {
643 Accept,
644 Drop,
645 Jump(String),
646 Return,
647 TransparentProxy(TransparentProxy),
648 Redirect { dst_port: Option<PortRange> },
649 Masquerade { src_port: Option<PortRange> },
650 Mark { domain: fnet::MarkDomain, action: MarkAction },
651 None,
652}
653
654#[derive(Debug, Clone, PartialEq)]
655pub enum MarkAction {
656 SetMark { clearing_mask: fnet::Mark, mark: fnet::Mark },
657}
658
659#[derive(Debug, Clone, PartialEq)]
661pub enum TransparentProxy {
662 LocalAddr(fnet::IpAddress),
663 LocalPort(NonZeroU16),
664 LocalAddrAndPort(fnet::IpAddress, NonZeroU16),
665}
666
667#[derive(Debug, Clone, PartialEq)]
668pub struct PortRange(pub RangeInclusive<NonZeroU16>);
669
670impl From<PortRange> for fnet_filter::PortRange {
671 fn from(range: PortRange) -> Self {
672 let PortRange(range) = range;
673 Self { start: range.start().get(), end: range.end().get() }
674 }
675}
676
677impl TryFrom<fnet_filter::PortRange> for PortRange {
678 type Error = FidlConversionError;
679
680 fn try_from(range: fnet_filter::PortRange) -> Result<Self, Self::Error> {
681 let fnet_filter::PortRange { start, end } = range;
682 if start > end {
683 Err(FidlConversionError::InvalidPortRange)
684 } else {
685 let start = NonZeroU16::new(start).ok_or(FidlConversionError::UnspecifiedNatPort)?;
686 let end = NonZeroU16::new(end).ok_or(FidlConversionError::UnspecifiedNatPort)?;
687 Ok(Self(start..=end))
688 }
689 }
690}
691
692impl From<Action> for fnet_filter::Action {
693 fn from(action: Action) -> Self {
694 match action {
695 Action::Accept => Self::Accept(fnet_filter::Empty {}),
696 Action::Drop => Self::Drop(fnet_filter::Empty {}),
697 Action::Jump(target) => Self::Jump(target),
698 Action::Return => Self::Return_(fnet_filter::Empty {}),
699 Action::TransparentProxy(proxy) => Self::TransparentProxy(match proxy {
700 TransparentProxy::LocalAddr(addr) => {
701 fnet_filter::TransparentProxy_::LocalAddr(addr)
702 }
703 TransparentProxy::LocalPort(port) => {
704 fnet_filter::TransparentProxy_::LocalPort(port.get())
705 }
706 TransparentProxy::LocalAddrAndPort(addr, port) => {
707 fnet_filter::TransparentProxy_::LocalAddrAndPort(fnet_filter::SocketAddr {
708 addr,
709 port: port.get(),
710 })
711 }
712 }),
713 Action::Redirect { dst_port } => Self::Redirect(fnet_filter::Redirect {
714 dst_port: dst_port.map(Into::into),
715 __source_breaking: SourceBreaking,
716 }),
717 Action::Masquerade { src_port } => Self::Masquerade(fnet_filter::Masquerade {
718 src_port: src_port.map(Into::into),
719 __source_breaking: SourceBreaking,
720 }),
721 Action::Mark { domain, action } => {
722 Self::Mark(fnet_filter::Mark { domain, action: action.into() })
723 }
724 Action::None => Self::None(fnet_filter::Empty {}),
725 }
726 }
727}
728
729impl TryFrom<fnet_filter::Action> for Action {
730 type Error = FidlConversionError;
731
732 fn try_from(action: fnet_filter::Action) -> Result<Self, Self::Error> {
733 match action {
734 fnet_filter::Action::Accept(fnet_filter::Empty {}) => Ok(Self::Accept),
735 fnet_filter::Action::Drop(fnet_filter::Empty {}) => Ok(Self::Drop),
736 fnet_filter::Action::Jump(target) => Ok(Self::Jump(target)),
737 fnet_filter::Action::Return_(fnet_filter::Empty {}) => Ok(Self::Return),
738 fnet_filter::Action::TransparentProxy(proxy) => {
739 Ok(Self::TransparentProxy(match proxy {
740 fnet_filter::TransparentProxy_::LocalAddr(addr) => {
741 TransparentProxy::LocalAddr(addr)
742 }
743 fnet_filter::TransparentProxy_::LocalPort(port) => {
744 let port = NonZeroU16::new(port)
745 .ok_or(FidlConversionError::UnspecifiedTransparentProxyPort)?;
746 TransparentProxy::LocalPort(port)
747 }
748 fnet_filter::TransparentProxy_::LocalAddrAndPort(fnet_filter::SocketAddr {
749 addr,
750 port,
751 }) => {
752 let port = NonZeroU16::new(port)
753 .ok_or(FidlConversionError::UnspecifiedTransparentProxyPort)?;
754 TransparentProxy::LocalAddrAndPort(addr, port)
755 }
756 fnet_filter::TransparentProxy_::__SourceBreaking { .. } => {
757 return Err(FidlConversionError::UnknownUnionVariant(
758 type_names::TRANSPARENT_PROXY,
759 ));
760 }
761 }))
762 }
763 fnet_filter::Action::Redirect(fnet_filter::Redirect {
764 dst_port,
765 __source_breaking,
766 }) => Ok(Self::Redirect { dst_port: dst_port.map(TryInto::try_into).transpose()? }),
767 fnet_filter::Action::Masquerade(fnet_filter::Masquerade {
768 src_port,
769 __source_breaking,
770 }) => Ok(Self::Masquerade { src_port: src_port.map(TryInto::try_into).transpose()? }),
771 fnet_filter::Action::Mark(fnet_filter::Mark { domain, action }) => {
772 Ok(Self::Mark { domain, action: action.try_into()? })
773 }
774 fnet_filter::Action::__SourceBreaking { .. } => {
775 Err(FidlConversionError::UnknownUnionVariant(type_names::ACTION))
776 }
777 fnet_filter::Action::None(fnet_filter::Empty {}) => Ok(Self::None),
778 }
779 }
780}
781
782impl From<MarkAction> for fnet_filter::MarkAction {
783 fn from(action: MarkAction) -> Self {
784 match action {
785 MarkAction::SetMark { clearing_mask, mark } => {
786 Self::SetMark(fnet_filter::SetMark { clearing_mask, mark })
787 }
788 }
789 }
790}
791
792impl TryFrom<fnet_filter::MarkAction> for MarkAction {
793 type Error = FidlConversionError;
794 fn try_from(action: fnet_filter::MarkAction) -> Result<Self, Self::Error> {
795 match action {
796 fnet_filter::MarkAction::SetMark(fnet_filter::SetMark { clearing_mask, mark }) => {
797 Ok(Self::SetMark { clearing_mask, mark })
798 }
799 fnet_filter::MarkAction::__SourceBreaking { .. } => {
800 Err(FidlConversionError::UnknownUnionVariant(type_names::MARK_ACTION))
801 }
802 }
803 }
804}
805
806#[derive(Debug, Clone, PartialEq)]
808pub struct Rule {
809 pub id: RuleId,
810 pub matchers: Matchers,
811 pub action: Action,
812}
813
814impl From<Rule> for fnet_filter::Rule {
815 fn from(rule: Rule) -> Self {
816 let Rule { id, matchers, action } = rule;
817 Self { id: id.into(), matchers: matchers.into(), action: action.into() }
818 }
819}
820
821impl TryFrom<fnet_filter::Rule> for Rule {
822 type Error = FidlConversionError;
823
824 fn try_from(rule: fnet_filter::Rule) -> Result<Self, Self::Error> {
825 let fnet_filter::Rule { id, matchers, action } = rule;
826 Ok(Self { id: id.into(), matchers: matchers.try_into()?, action: action.try_into()? })
827 }
828}
829
830#[derive(Debug, Clone, PartialEq)]
832pub enum Resource {
833 Namespace(Namespace),
834 Routine(Routine),
835 Rule(Rule),
836}
837
838impl Resource {
839 pub fn id(&self) -> ResourceId {
840 match self {
841 Self::Namespace(Namespace { id, domain: _ }) => ResourceId::Namespace(id.clone()),
842 Self::Routine(Routine { id, routine_type: _ }) => ResourceId::Routine(id.clone()),
843 Self::Rule(Rule { id, matchers: _, action: _ }) => ResourceId::Rule(id.clone()),
844 }
845 }
846}
847
848impl From<Resource> for fnet_filter::Resource {
849 fn from(resource: Resource) -> Self {
850 match resource {
851 Resource::Namespace(namespace) => Self::Namespace(namespace.into()),
852 Resource::Routine(routine) => Self::Routine(routine.into()),
853 Resource::Rule(rule) => Self::Rule(rule.into()),
854 }
855 }
856}
857
858impl TryFrom<fnet_filter::Resource> for Resource {
859 type Error = FidlConversionError;
860
861 fn try_from(resource: fnet_filter::Resource) -> Result<Self, Self::Error> {
862 match resource {
863 fnet_filter::Resource::Namespace(namespace) => {
864 Ok(Self::Namespace(namespace.try_into()?))
865 }
866 fnet_filter::Resource::Routine(routine) => Ok(Self::Routine(routine.try_into()?)),
867 fnet_filter::Resource::Rule(rule) => Ok(Self::Rule(rule.try_into()?)),
868 fnet_filter::Resource::__SourceBreaking { .. } => {
869 Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE))
870 }
871 }
872 }
873}
874
875#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
877pub struct ControllerId(pub String);
878
879#[derive(Debug, Clone, PartialEq)]
881pub enum Event {
882 Existing(ControllerId, Resource),
883 Idle,
884 Added(ControllerId, Resource),
885 Removed(ControllerId, ResourceId),
886 EndOfUpdate,
887}
888
889impl From<Event> for fnet_filter::Event {
890 fn from(event: Event) -> Self {
891 match event {
892 Event::Existing(controller, resource) => {
893 let ControllerId(id) = controller;
894 Self::Existing(fnet_filter::ExistingResource {
895 controller: id,
896 resource: resource.into(),
897 })
898 }
899 Event::Idle => Self::Idle(fnet_filter::Empty {}),
900 Event::Added(controller, resource) => {
901 let ControllerId(id) = controller;
902 Self::Added(fnet_filter::AddedResource {
903 controller: id,
904 resource: resource.into(),
905 })
906 }
907 Event::Removed(controller, resource) => {
908 let ControllerId(id) = controller;
909 Self::Removed(fnet_filter::RemovedResource {
910 controller: id,
911 resource: resource.into(),
912 })
913 }
914 Event::EndOfUpdate => Self::EndOfUpdate(fnet_filter::Empty {}),
915 }
916 }
917}
918
919impl TryFrom<fnet_filter::Event> for Event {
920 type Error = FidlConversionError;
921
922 fn try_from(event: fnet_filter::Event) -> Result<Self, Self::Error> {
923 match event {
924 fnet_filter::Event::Existing(fnet_filter::ExistingResource {
925 controller,
926 resource,
927 }) => Ok(Self::Existing(ControllerId(controller), resource.try_into()?)),
928 fnet_filter::Event::Idle(fnet_filter::Empty {}) => Ok(Self::Idle),
929 fnet_filter::Event::Added(fnet_filter::AddedResource { controller, resource }) => {
930 Ok(Self::Added(ControllerId(controller), resource.try_into()?))
931 }
932 fnet_filter::Event::Removed(fnet_filter::RemovedResource { controller, resource }) => {
933 Ok(Self::Removed(ControllerId(controller), resource.try_into()?))
934 }
935 fnet_filter::Event::EndOfUpdate(fnet_filter::Empty {}) => Ok(Self::EndOfUpdate),
936 fnet_filter::Event::__SourceBreaking { .. } => {
937 Err(FidlConversionError::UnknownUnionVariant(type_names::EVENT))
938 }
939 }
940 }
941}
942
943#[derive(Debug, Error)]
945pub enum WatcherCreationError {
946 #[error("failed to create filter watcher proxy: {0}")]
947 CreateProxy(fidl::Error),
948 #[error("failed to get filter watcher: {0}")]
949 GetWatcher(fidl::Error),
950}
951
952#[derive(Debug, Error)]
954pub enum WatchError {
955 #[error("the call to `Watch()` failed: {0}")]
957 Fidl(fidl::Error),
958 #[error("failed to convert event returned by `Watch()`: {0}")]
960 Conversion(FidlConversionError),
961 #[error("the call to `Watch()` returned an empty batch of events")]
963 EmptyEventBatch,
964}
965
966pub fn event_stream_from_state(
973 state: fnet_filter::StateProxy,
974) -> Result<impl Stream<Item = Result<Event, WatchError>>, WatcherCreationError> {
975 let (watcher, server_end) = fidl::endpoints::create_proxy::<fnet_filter::WatcherMarker>();
976 state
977 .get_watcher(&fnet_filter::WatcherOptions::default(), server_end)
978 .map_err(WatcherCreationError::GetWatcher)?;
979
980 let stream = futures::stream::try_unfold(watcher, |watcher| async {
981 let events = watcher.watch().await.map_err(WatchError::Fidl)?;
982 if events.is_empty() {
983 return Err(WatchError::EmptyEventBatch);
984 }
985
986 let event_stream = futures::stream::iter(events).map(Ok).and_then(|event| {
987 futures::future::ready(event.try_into().map_err(WatchError::Conversion))
988 });
989 Ok(Some((event_stream, watcher)))
990 })
991 .try_flatten();
992
993 Ok(stream)
994}
995
996#[derive(Debug, Error)]
998pub enum GetExistingResourcesError {
999 #[error("there was an error in the event stream: {0}")]
1001 ErrorInStream(WatchError),
1002 #[error("there was an unexpected event in the event stream: {0:?}")]
1005 UnexpectedEvent(Event),
1006 #[error("a duplicate existing resource was reported")]
1008 DuplicateResource(Resource),
1009 #[error("the event stream unexpectedly ended")]
1011 StreamEnded,
1012}
1013
1014pub trait Update {
1017 fn add(&mut self, controller: ControllerId, resource: Resource) -> Option<Resource>;
1022
1023 fn remove(&mut self, controller: ControllerId, resource: &ResourceId) -> Option<Resource>;
1027}
1028
1029impl Update for HashMap<ControllerId, HashMap<ResourceId, Resource>> {
1030 fn add(&mut self, controller: ControllerId, resource: Resource) -> Option<Resource> {
1031 self.entry(controller).or_default().insert(resource.id(), resource)
1032 }
1033
1034 fn remove(&mut self, controller: ControllerId, resource: &ResourceId) -> Option<Resource> {
1035 self.get_mut(&controller)?.remove(resource)
1036 }
1037}
1038
1039pub async fn get_existing_resources<C: Update + Default>(
1042 stream: impl Stream<Item = Result<Event, WatchError>>,
1043) -> Result<C, GetExistingResourcesError> {
1044 async_utils::fold::fold_while(
1045 stream,
1046 Ok(C::default()),
1047 |resources: Result<C, GetExistingResourcesError>, event| {
1048 let mut resources =
1049 resources.expect("`resources` must be `Ok`, because we stop folding on err");
1050 futures::future::ready(match event {
1051 Err(e) => FoldWhile::Done(Err(GetExistingResourcesError::ErrorInStream(e))),
1052 Ok(e) => match e {
1053 Event::Existing(controller, resource) => {
1054 if let Some(resource) = resources.add(controller, resource) {
1055 FoldWhile::Done(Err(GetExistingResourcesError::DuplicateResource(
1056 resource,
1057 )))
1058 } else {
1059 FoldWhile::Continue(Ok(resources))
1060 }
1061 }
1062 Event::Idle => FoldWhile::Done(Ok(resources)),
1063 e @ (Event::Added(_, _) | Event::Removed(_, _) | Event::EndOfUpdate) => {
1064 FoldWhile::Done(Err(GetExistingResourcesError::UnexpectedEvent(e)))
1065 }
1066 },
1067 })
1068 },
1069 )
1070 .await
1071 .short_circuited()
1072 .map_err(|_resources| GetExistingResourcesError::StreamEnded)?
1073}
1074
1075#[derive(Debug, Error)]
1077pub enum WaitForConditionError {
1078 #[error("there was an error in the event stream: {0}")]
1080 ErrorInStream(WatchError),
1081 #[error("observed an added event for an already existing resource: {0:?}")]
1083 AddedAlreadyExisting(Resource),
1084 #[error("observed a removed event for a non-existent resource: {0:?}")]
1086 RemovedNonExistent(ResourceId),
1087 #[error("the event stream unexpectedly ended")]
1089 StreamEnded,
1090}
1091
1092pub async fn wait_for_condition<
1098 C: Update,
1099 S: Stream<Item = Result<Event, WatchError>>,
1100 F: Fn(&C) -> bool,
1101>(
1102 event_stream: S,
1103 initial_state: &mut C,
1104 predicate: F,
1105) -> Result<(), WaitForConditionError> {
1106 async_utils::fold::try_fold_while(
1107 event_stream.map_err(WaitForConditionError::ErrorInStream),
1108 initial_state,
1109 |resources: &mut C, event| {
1110 futures::future::ready(match event {
1111 Event::Existing(controller, resource) | Event::Added(controller, resource) => {
1112 if let Some(resource) = resources.add(controller, resource) {
1113 Err(WaitForConditionError::AddedAlreadyExisting(resource))
1114 } else {
1115 Ok(FoldWhile::Continue(resources))
1116 }
1117 }
1118 Event::Removed(controller, resource) => resources
1119 .remove(controller, &resource)
1120 .map(|_| FoldWhile::Continue(resources))
1121 .ok_or(WaitForConditionError::RemovedNonExistent(resource)),
1122 Event::Idle | Event::EndOfUpdate => {
1126 if predicate(&resources) {
1127 Ok(FoldWhile::Done(()))
1128 } else {
1129 Ok(FoldWhile::Continue(resources))
1130 }
1131 }
1132 })
1133 },
1134 )
1135 .await?
1136 .short_circuited()
1137 .map_err(|_resources: &mut C| WaitForConditionError::StreamEnded)
1138}
1139
1140#[derive(Debug, Error)]
1142pub enum ControllerCreationError {
1143 #[error("failed to create namespace controller proxy: {0}")]
1144 CreateProxy(fidl::Error),
1145 #[error("failed to open namespace controller: {0}")]
1146 OpenController(fidl::Error),
1147 #[error("server did not emit OnIdAssigned event")]
1148 NoIdAssigned,
1149 #[error("failed to observe ID assignment event: {0}")]
1150 IdAssignment(fidl::Error),
1151}
1152
1153#[derive(Debug, Error, PartialEq)]
1157pub enum ChangeValidationError {
1158 #[error("change contains a resource that is missing a required field")]
1159 MissingRequiredField,
1160 #[error("rule specifies an invalid interface matcher")]
1161 InvalidInterfaceMatcher,
1162 #[error("rule specifies an invalid address matcher")]
1163 InvalidAddressMatcher,
1164 #[error("rule specifies an invalid port matcher")]
1165 InvalidPortMatcher,
1166 #[error("rule specifies an invalid transparent proxy action")]
1167 InvalidTransparentProxyAction,
1168 #[error("rule specifies an invalid NAT action")]
1169 InvalidNatAction,
1170 #[error("rule specifies an invalid port range")]
1171 InvalidPortRange,
1172}
1173
1174impl TryFrom<fnet_filter::ChangeValidationError> for ChangeValidationError {
1175 type Error = FidlConversionError;
1176
1177 fn try_from(error: fnet_filter::ChangeValidationError) -> Result<Self, Self::Error> {
1178 match error {
1179 fnet_filter::ChangeValidationError::MissingRequiredField => {
1180 Ok(Self::MissingRequiredField)
1181 }
1182 fnet_filter::ChangeValidationError::InvalidInterfaceMatcher => {
1183 Ok(Self::InvalidInterfaceMatcher)
1184 }
1185 fnet_filter::ChangeValidationError::InvalidAddressMatcher => {
1186 Ok(Self::InvalidAddressMatcher)
1187 }
1188 fnet_filter::ChangeValidationError::InvalidPortMatcher => Ok(Self::InvalidPortMatcher),
1189 fnet_filter::ChangeValidationError::InvalidTransparentProxyAction => {
1190 Ok(Self::InvalidTransparentProxyAction)
1191 }
1192 fnet_filter::ChangeValidationError::InvalidNatAction => Ok(Self::InvalidNatAction),
1193 fnet_filter::ChangeValidationError::InvalidPortRange => Ok(Self::InvalidPortRange),
1194 fnet_filter::ChangeValidationError::Ok
1195 | fnet_filter::ChangeValidationError::NotReached => {
1196 Err(FidlConversionError::NotAnError)
1197 }
1198 fnet_filter::ChangeValidationError::__SourceBreaking { unknown_ordinal: _ } => {
1199 Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE_VALIDATION_ERROR))
1200 }
1201 }
1202 }
1203}
1204
1205#[derive(Debug, Error)]
1206pub enum RegisterEbpfProgramError {
1207 #[error("failed to call FIDL method: {0}")]
1208 CallMethod(fidl::Error),
1209
1210 #[error("failed to link the program")]
1211 LinkFailed,
1212
1213 #[error("failed to initialize a map")]
1214 MapFailed,
1215
1216 #[error("the program is already registered")]
1217 AlreadyRegistered,
1218
1219 #[error("the request is missing a required field")]
1220 MissingRequiredField,
1221}
1222
1223impl From<fnet_filter::RegisterEbpfProgramError> for RegisterEbpfProgramError {
1224 fn from(error: fnet_filter::RegisterEbpfProgramError) -> Self {
1225 match error {
1226 fnet_filter::RegisterEbpfProgramError::LinkFailed => Self::LinkFailed,
1227 fnet_filter::RegisterEbpfProgramError::MapFailed => Self::MapFailed,
1228 fnet_filter::RegisterEbpfProgramError::AlreadyRegistered => Self::AlreadyRegistered,
1229 fnet_filter::RegisterEbpfProgramError::MissingRequiredField => {
1230 Self::MissingRequiredField
1231 }
1232 }
1233 }
1234}
1235
1236#[derive(Debug, Error)]
1238pub enum PushChangesError {
1239 #[error("failed to call FIDL method: {0}")]
1240 CallMethod(fidl::Error),
1241 #[error("too many changes were pushed to the server")]
1242 TooManyChanges,
1243 #[error("invalid change(s) pushed: {0:?}")]
1244 ErrorOnChange(Vec<(Change, ChangeValidationError)>),
1245 #[error("unknown FIDL type: {0}")]
1246 FidlConversion(#[from] FidlConversionError),
1247}
1248
1249#[derive(Debug, Error, PartialEq)]
1253pub enum ChangeCommitError {
1254 #[error("the change referred to an unknown namespace")]
1255 NamespaceNotFound,
1256 #[error("the change referred to an unknown routine")]
1257 RoutineNotFound,
1258 #[error("the change referred to an unknown rule")]
1259 RuleNotFound,
1260 #[error("the specified resource already exists")]
1261 AlreadyExists,
1262 #[error("the change includes a rule that jumps to an installed routine")]
1263 TargetRoutineIsInstalled,
1264 #[error("the change includes an eBPF matcher with an invalid program ID")]
1265 InvalidEbpfProgramId,
1266}
1267
1268impl TryFrom<fnet_filter::CommitError> for ChangeCommitError {
1269 type Error = FidlConversionError;
1270
1271 fn try_from(error: fnet_filter::CommitError) -> Result<Self, Self::Error> {
1272 match error {
1273 fnet_filter::CommitError::NamespaceNotFound => Ok(Self::NamespaceNotFound),
1274 fnet_filter::CommitError::RoutineNotFound => Ok(Self::RoutineNotFound),
1275 fnet_filter::CommitError::RuleNotFound => Ok(Self::RuleNotFound),
1276 fnet_filter::CommitError::AlreadyExists => Ok(Self::AlreadyExists),
1277 fnet_filter::CommitError::TargetRoutineIsInstalled => {
1278 Ok(Self::TargetRoutineIsInstalled)
1279 }
1280 fnet_filter::CommitError::InvalidEbpfProgramId => Ok(Self::InvalidEbpfProgramId),
1281 fnet_filter::CommitError::Ok | fnet_filter::CommitError::NotReached => {
1282 Err(FidlConversionError::NotAnError)
1283 }
1284 fnet_filter::CommitError::__SourceBreaking { unknown_ordinal: _ } => {
1285 Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_ERROR))
1286 }
1287 }
1288 }
1289}
1290
1291#[derive(Debug, Error)]
1293pub enum CommitError {
1294 #[error("failed to call FIDL method: {0}")]
1295 CallMethod(fidl::Error),
1296 #[error("rule has a matcher that is unavailable in its context: {0:?}")]
1297 RuleWithInvalidMatcher(RuleId),
1298 #[error("rule has an action that is invalid for its routine: {0:?}")]
1299 RuleWithInvalidAction(RuleId),
1300 #[error("rule has a TransparentProxy action but not a valid transport protocol matcher: {0:?}")]
1301 TransparentProxyWithInvalidMatcher(RuleId),
1302 #[error(
1303 "rule has a Redirect action that specifies a destination port but not a valid transport \
1304 protocol matcher: {0:?}"
1305 )]
1306 RedirectWithInvalidMatcher(RuleId),
1307 #[error(
1308 "rule has a Masquerade action that specifies a source port but not a valid transport \
1309 protocol matcher: {0:?}"
1310 )]
1311 MasqueradeWithInvalidMatcher(RuleId),
1312 #[error("routine forms a cycle {0:?}")]
1313 CyclicalRoutineGraph(RoutineId),
1314 #[error("invalid change was pushed: {0:?}")]
1315 ErrorOnChange(Vec<(Change, ChangeCommitError)>),
1316 #[error("unknown FIDL type: {0}")]
1317 FidlConversion(#[from] FidlConversionError),
1318}
1319
1320#[derive(Debug, Clone, PartialEq)]
1322pub enum Change {
1323 Create(Resource),
1324 Remove(ResourceId),
1325}
1326
1327impl From<Change> for fnet_filter::Change {
1328 fn from(change: Change) -> Self {
1329 match change {
1330 Change::Create(resource) => Self::Create(resource.into()),
1331 Change::Remove(resource) => Self::Remove(resource.into()),
1332 }
1333 }
1334}
1335
1336impl TryFrom<fnet_filter::Change> for Change {
1337 type Error = FidlConversionError;
1338
1339 fn try_from(change: fnet_filter::Change) -> Result<Self, Self::Error> {
1340 match change {
1341 fnet_filter::Change::Create(resource) => Ok(Self::Create(resource.try_into()?)),
1342 fnet_filter::Change::Remove(resource) => Ok(Self::Remove(resource.try_into()?)),
1343 fnet_filter::Change::__SourceBreaking { .. } => {
1344 Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE))
1345 }
1346 }
1347 }
1348}
1349
1350pub struct Controller {
1352 controller: fnet_filter::NamespaceControllerProxy,
1353 id: ControllerId,
1357 pending_changes: Vec<Change>,
1361}
1362
1363impl Controller {
1364 pub async fn new_root(
1365 root: &fnet_root::FilterProxy,
1366 ControllerId(id): &ControllerId,
1367 ) -> Result<Self, ControllerCreationError> {
1368 let (controller, server_end) = fidl::endpoints::create_proxy();
1369 root.open_controller(id, server_end).map_err(ControllerCreationError::OpenController)?;
1370
1371 let fnet_filter::NamespaceControllerEvent::OnIdAssigned { id } = controller
1372 .take_event_stream()
1373 .next()
1374 .await
1375 .ok_or(ControllerCreationError::NoIdAssigned)?
1376 .map_err(ControllerCreationError::IdAssignment)?;
1377 Ok(Self { controller, id: ControllerId(id), pending_changes: Vec::new() })
1378 }
1379
1380 pub async fn new(
1386 control: &fnet_filter::ControlProxy,
1387 ControllerId(id): &ControllerId,
1388 ) -> Result<Self, ControllerCreationError> {
1389 let (controller, server_end) = fidl::endpoints::create_proxy();
1390 control.open_controller(id, server_end).map_err(ControllerCreationError::OpenController)?;
1391
1392 let fnet_filter::NamespaceControllerEvent::OnIdAssigned { id } = controller
1393 .take_event_stream()
1394 .next()
1395 .await
1396 .ok_or(ControllerCreationError::NoIdAssigned)?
1397 .map_err(ControllerCreationError::IdAssignment)?;
1398 Ok(Self { controller, id: ControllerId(id), pending_changes: Vec::new() })
1399 }
1400
1401 pub fn id(&self) -> &ControllerId {
1402 &self.id
1403 }
1404
1405 pub async fn register_ebpf_program(
1406 &mut self,
1407 handle: febpf::ProgramHandle,
1408 program: febpf::VerifiedProgram,
1409 ) -> Result<(), RegisterEbpfProgramError> {
1410 self.controller
1411 .register_ebpf_program(handle, program)
1412 .await
1413 .map_err(RegisterEbpfProgramError::CallMethod)?
1414 .map_err(RegisterEbpfProgramError::from)
1415 }
1416
1417 pub async fn push_changes(&mut self, changes: Vec<Change>) -> Result<(), PushChangesError> {
1418 let fidl_changes = changes.iter().cloned().map(Into::into).collect::<Vec<_>>();
1419 let result = self
1420 .controller
1421 .push_changes(&fidl_changes)
1422 .await
1423 .map_err(PushChangesError::CallMethod)?;
1424 handle_change_validation_result(result, &changes)?;
1425 self.pending_changes.extend(changes);
1429 Ok(())
1430 }
1431
1432 async fn commit_with_options(
1433 &mut self,
1434 options: fnet_filter::CommitOptions,
1435 ) -> Result<(), CommitError> {
1436 let committed_changes = std::mem::take(&mut self.pending_changes);
1437 let result = self.controller.commit(options).await.map_err(CommitError::CallMethod)?;
1438 handle_commit_result(result, committed_changes)
1439 }
1440
1441 pub async fn commit(&mut self) -> Result<(), CommitError> {
1442 self.commit_with_options(fnet_filter::CommitOptions::default()).await
1443 }
1444
1445 pub async fn commit_idempotent(&mut self) -> Result<(), CommitError> {
1446 self.commit_with_options(fnet_filter::CommitOptions {
1447 idempotent: Some(true),
1448 __source_breaking: SourceBreaking,
1449 })
1450 .await
1451 }
1452}
1453
1454pub(crate) fn handle_change_validation_result(
1455 change_validation_result: fnet_filter::ChangeValidationResult,
1456 changes: &Vec<Change>,
1457) -> Result<(), PushChangesError> {
1458 match change_validation_result {
1459 fnet_filter::ChangeValidationResult::Ok(fnet_filter::Empty {}) => Ok(()),
1460 fnet_filter::ChangeValidationResult::TooManyChanges(fnet_filter::Empty {}) => {
1461 Err(PushChangesError::TooManyChanges)
1462 }
1463 fnet_filter::ChangeValidationResult::ErrorOnChange(results) => {
1464 let errors: Result<_, PushChangesError> =
1465 changes.iter().zip(results).try_fold(Vec::new(), |mut errors, (change, result)| {
1466 match result {
1467 fnet_filter::ChangeValidationError::Ok
1468 | fnet_filter::ChangeValidationError::NotReached => Ok(errors),
1469 error @ (fnet_filter::ChangeValidationError::MissingRequiredField
1470 | fnet_filter::ChangeValidationError::InvalidInterfaceMatcher
1471 | fnet_filter::ChangeValidationError::InvalidAddressMatcher
1472 | fnet_filter::ChangeValidationError::InvalidPortMatcher
1473 | fnet_filter::ChangeValidationError::InvalidTransparentProxyAction
1474 | fnet_filter::ChangeValidationError::InvalidNatAction
1475 | fnet_filter::ChangeValidationError::InvalidPortRange) => {
1476 let error = error
1477 .try_into()
1478 .expect("`Ok` and `NotReached` are handled in another arm");
1479 errors.push((change.clone(), error));
1480 Ok(errors)
1481 }
1482 fnet_filter::ChangeValidationError::__SourceBreaking { .. } => {
1483 Err(FidlConversionError::UnknownUnionVariant(
1484 type_names::CHANGE_VALIDATION_ERROR,
1485 )
1486 .into())
1487 }
1488 }
1489 });
1490 Err(PushChangesError::ErrorOnChange(errors?))
1491 }
1492 fnet_filter::ChangeValidationResult::__SourceBreaking { .. } => {
1493 Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE_VALIDATION_RESULT)
1494 .into())
1495 }
1496 }
1497}
1498
1499pub(crate) fn handle_commit_result(
1500 commit_result: fnet_filter::CommitResult,
1501 committed_changes: Vec<Change>,
1502) -> Result<(), CommitError> {
1503 match commit_result {
1504 fnet_filter::CommitResult::Ok(fnet_filter::Empty {}) => Ok(()),
1505 fnet_filter::CommitResult::RuleWithInvalidMatcher(rule_id) => {
1506 Err(CommitError::RuleWithInvalidMatcher(rule_id.into()))
1507 }
1508 fnet_filter::CommitResult::RuleWithInvalidAction(rule_id) => {
1509 Err(CommitError::RuleWithInvalidAction(rule_id.into()))
1510 }
1511 fnet_filter::CommitResult::TransparentProxyWithInvalidMatcher(rule_id) => {
1512 Err(CommitError::TransparentProxyWithInvalidMatcher(rule_id.into()))
1513 }
1514 fnet_filter::CommitResult::RedirectWithInvalidMatcher(rule_id) => {
1515 Err(CommitError::RedirectWithInvalidMatcher(rule_id.into()))
1516 }
1517 fnet_filter::CommitResult::MasqueradeWithInvalidMatcher(rule_id) => {
1518 Err(CommitError::MasqueradeWithInvalidMatcher(rule_id.into()))
1519 }
1520 fnet_filter::CommitResult::CyclicalRoutineGraph(routine_id) => {
1521 Err(CommitError::CyclicalRoutineGraph(routine_id.into()))
1522 }
1523 fnet_filter::CommitResult::ErrorOnChange(results) => {
1524 let errors: Result<_, CommitError> = committed_changes
1525 .into_iter()
1526 .zip(results)
1527 .try_fold(Vec::new(), |mut errors, (change, result)| match result {
1528 fnet_filter::CommitError::Ok | fnet_filter::CommitError::NotReached => {
1529 Ok(errors)
1530 }
1531 error @ (fnet_filter::CommitError::NamespaceNotFound
1532 | fnet_filter::CommitError::RoutineNotFound
1533 | fnet_filter::CommitError::RuleNotFound
1534 | fnet_filter::CommitError::AlreadyExists
1535 | fnet_filter::CommitError::TargetRoutineIsInstalled
1536 | fnet_filter::CommitError::InvalidEbpfProgramId) => {
1537 let error = error
1538 .try_into()
1539 .expect("`Ok` and `NotReached` are handled in another arm");
1540 errors.push((change, error));
1541 Ok(errors)
1542 }
1543 fnet_filter::CommitError::__SourceBreaking { .. } => {
1544 Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_ERROR)
1545 .into())
1546 }
1547 });
1548 Err(CommitError::ErrorOnChange(errors?))
1549 }
1550 fnet_filter::CommitResult::__SourceBreaking { .. } => {
1551 Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_RESULT).into())
1552 }
1553 }
1554}
1555
1556#[cfg(test)]
1557mod tests {
1558
1559 use assert_matches::assert_matches;
1560 use fidl_fuchsia_net_matchers as fnet_matchers;
1561 use futures::channel::mpsc;
1562 use futures::task::Poll;
1563 use futures::{FutureExt as _, SinkExt as _};
1564 use test_case::test_case;
1565
1566 use {
1567 fidl_fuchsia_hardware_network as fhardware_network,
1568 fidl_fuchsia_net_interfaces as fnet_interfaces,
1569 };
1570
1571 use super::*;
1572
1573 #[test_case(
1574 fnet_filter::ResourceId::Namespace(String::from("namespace")),
1575 ResourceId::Namespace(NamespaceId(String::from("namespace")));
1576 "NamespaceId"
1577 )]
1578 #[test_case(fnet_filter::Domain::Ipv4, Domain::Ipv4; "Domain")]
1579 #[test_case(
1580 fnet_filter::Namespace {
1581 id: Some(String::from("namespace")),
1582 domain: Some(fnet_filter::Domain::Ipv4),
1583 ..Default::default()
1584 },
1585 Namespace { id: NamespaceId(String::from("namespace")), domain: Domain::Ipv4 };
1586 "Namespace"
1587 )]
1588 #[test_case(fnet_filter::IpInstallationHook::Egress, IpHook::Egress; "IpHook")]
1589 #[test_case(fnet_filter::NatInstallationHook::Egress, NatHook::Egress; "NatHook")]
1590 #[test_case(
1591 fnet_filter::InstalledIpRoutine {
1592 hook: Some(fnet_filter::IpInstallationHook::Egress),
1593 priority: Some(1),
1594 ..Default::default()
1595 },
1596 InstalledIpRoutine {
1597 hook: IpHook::Egress,
1598 priority: 1,
1599 };
1600 "InstalledIpRoutine"
1601 )]
1602 #[test_case(
1603 fnet_filter::RoutineType::Ip(fnet_filter::IpRoutine {
1604 installation: Some(fnet_filter::InstalledIpRoutine {
1605 hook: Some(fnet_filter::IpInstallationHook::LocalEgress),
1606 priority: Some(1),
1607 ..Default::default()
1608 }),
1609 ..Default::default()
1610 }),
1611 RoutineType::Ip(Some(InstalledIpRoutine { hook: IpHook::LocalEgress, priority: 1 }));
1612 "RoutineType"
1613 )]
1614 #[test_case(
1615 fnet_filter::Routine {
1616 id: Some(fnet_filter::RoutineId {
1617 namespace: String::from("namespace"),
1618 name: String::from("routine"),
1619 }),
1620 type_: Some(fnet_filter::RoutineType::Nat(fnet_filter::NatRoutine::default())),
1621 ..Default::default()
1622 },
1623 Routine {
1624 id: RoutineId {
1625 namespace: NamespaceId(String::from("namespace")),
1626 name: String::from("routine"),
1627 },
1628 routine_type: RoutineType::Nat(None),
1629 };
1630 "Routine"
1631 )]
1632 #[test_case(
1633 fnet_filter::Matchers {
1634 in_interface: Some(fnet_matchers::Interface::Name(String::from("wlan"))),
1635 transport_protocol: Some(fnet_matchers::PacketTransportProtocol::Tcp(fnet_matchers::TcpPacket {
1636 src_port: None,
1637 dst_port: Some(fnet_matchers::Port { start: 22, end: 22, invert: false }),
1638 ..Default::default()
1639 })),
1640 ..Default::default()
1641 },
1642 Matchers {
1643 in_interface: Some(fnet_matchers_ext::Interface::Name(String::from("wlan"))),
1644 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
1645 src_port: None,
1646 dst_port: Some(fnet_matchers_ext::Port::new(22, 22, false).unwrap()),
1647 }),
1648 ..Default::default()
1649 };
1650 "Matchers"
1651 )]
1652 #[test_case(
1653 fnet_filter::Action::Accept(fnet_filter::Empty {}),
1654 Action::Accept;
1655 "Action"
1656 )]
1657 #[test_case(
1658 fnet_filter::Rule {
1659 id: fnet_filter::RuleId {
1660 routine: fnet_filter::RoutineId {
1661 namespace: String::from("namespace"),
1662 name: String::from("routine"),
1663 },
1664 index: 1,
1665 },
1666 matchers: fnet_filter::Matchers {
1667 transport_protocol: Some(fnet_matchers::PacketTransportProtocol::Icmp(
1668 fnet_matchers::IcmpPacket::default()
1669 )),
1670 ..Default::default()
1671 },
1672 action: fnet_filter::Action::Drop(fnet_filter::Empty {}),
1673 },
1674 Rule {
1675 id: RuleId {
1676 routine: RoutineId {
1677 namespace: NamespaceId(String::from("namespace")),
1678 name: String::from("routine"),
1679 },
1680 index: 1,
1681 },
1682 matchers: Matchers {
1683 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Icmp),
1684 ..Default::default()
1685 },
1686 action: Action::Drop,
1687 };
1688 "Rule"
1689 )]
1690 #[test_case(
1691 fnet_filter::Resource::Namespace(fnet_filter::Namespace {
1692 id: Some(String::from("namespace")),
1693 domain: Some(fnet_filter::Domain::Ipv4),
1694 ..Default::default()
1695 }),
1696 Resource::Namespace(Namespace {
1697 id: NamespaceId(String::from("namespace")),
1698 domain: Domain::Ipv4
1699 });
1700 "Resource"
1701 )]
1702 #[test_case(
1703 fnet_filter::Event::EndOfUpdate(fnet_filter::Empty {}),
1704 Event::EndOfUpdate;
1705 "Event"
1706 )]
1707 #[test_case(
1708 fnet_filter::Change::Remove(fnet_filter::ResourceId::Namespace(String::from("namespace"))),
1709 Change::Remove(ResourceId::Namespace(NamespaceId(String::from("namespace"))));
1710 "Change"
1711 )]
1712 fn convert_from_fidl_and_back<F, E>(fidl_type: F, local_type: E)
1713 where
1714 E: TryFrom<F> + Clone + Debug + PartialEq,
1715 <E as TryFrom<F>>::Error: Debug + PartialEq,
1716 F: From<E> + Clone + Debug + PartialEq,
1717 {
1718 assert_eq!(fidl_type.clone().try_into(), Ok(local_type.clone()));
1719 assert_eq!(<_ as Into<F>>::into(local_type), fidl_type.clone());
1720 }
1721
1722 #[test]
1723 fn resource_id_try_from_unknown_variant() {
1724 assert_eq!(
1725 ResourceId::try_from(fnet_filter::ResourceId::__SourceBreaking { unknown_ordinal: 0 }),
1726 Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE_ID))
1727 );
1728 }
1729
1730 #[test]
1731 fn domain_try_from_unknown_variant() {
1732 assert_eq!(
1733 Domain::try_from(fnet_filter::Domain::__SourceBreaking { unknown_ordinal: 0 }),
1734 Err(FidlConversionError::UnknownUnionVariant(type_names::DOMAIN))
1735 );
1736 }
1737
1738 #[test]
1739 fn namespace_try_from_missing_properties() {
1740 assert_eq!(
1741 Namespace::try_from(fnet_filter::Namespace {
1742 id: None,
1743 domain: Some(fnet_filter::Domain::Ipv4),
1744 ..Default::default()
1745 }),
1746 Err(FidlConversionError::MissingNamespaceId)
1747 );
1748 assert_eq!(
1749 Namespace::try_from(fnet_filter::Namespace {
1750 id: Some(String::from("namespace")),
1751 domain: None,
1752 ..Default::default()
1753 }),
1754 Err(FidlConversionError::MissingNamespaceDomain)
1755 );
1756 }
1757
1758 #[test]
1759 fn ip_installation_hook_try_from_unknown_variant() {
1760 assert_eq!(
1761 IpHook::try_from(fnet_filter::IpInstallationHook::__SourceBreaking {
1762 unknown_ordinal: 0
1763 }),
1764 Err(FidlConversionError::UnknownUnionVariant(type_names::IP_INSTALLATION_HOOK))
1765 );
1766 }
1767
1768 #[test]
1769 fn nat_installation_hook_try_from_unknown_variant() {
1770 assert_eq!(
1771 NatHook::try_from(fnet_filter::NatInstallationHook::__SourceBreaking {
1772 unknown_ordinal: 0
1773 }),
1774 Err(FidlConversionError::UnknownUnionVariant(type_names::NAT_INSTALLATION_HOOK))
1775 );
1776 }
1777
1778 #[test]
1779 fn installed_ip_routine_try_from_missing_hook() {
1780 assert_eq!(
1781 InstalledIpRoutine::try_from(fnet_filter::InstalledIpRoutine {
1782 hook: None,
1783 ..Default::default()
1784 }),
1785 Err(FidlConversionError::MissingIpInstallationHook)
1786 );
1787 }
1788
1789 #[test]
1790 fn installed_nat_routine_try_from_missing_hook() {
1791 assert_eq!(
1792 InstalledNatRoutine::try_from(fnet_filter::InstalledNatRoutine {
1793 hook: None,
1794 ..Default::default()
1795 }),
1796 Err(FidlConversionError::MissingNatInstallationHook)
1797 );
1798 }
1799
1800 #[test]
1801 fn routine_type_try_from_unknown_variant() {
1802 assert_eq!(
1803 RoutineType::try_from(fnet_filter::RoutineType::__SourceBreaking {
1804 unknown_ordinal: 0
1805 }),
1806 Err(FidlConversionError::UnknownUnionVariant(type_names::ROUTINE_TYPE))
1807 );
1808 }
1809
1810 #[test]
1811 fn routine_try_from_missing_properties() {
1812 assert_eq!(
1813 Routine::try_from(fnet_filter::Routine { id: None, ..Default::default() }),
1814 Err(FidlConversionError::MissingRoutineId)
1815 );
1816 assert_eq!(
1817 Routine::try_from(fnet_filter::Routine {
1818 id: Some(fnet_filter::RoutineId {
1819 namespace: String::from("namespace"),
1820 name: String::from("routine"),
1821 }),
1822 type_: None,
1823 ..Default::default()
1824 }),
1825 Err(FidlConversionError::MissingRoutineType)
1826 );
1827 }
1828
1829 #[test_case(
1830 fnet_matchers_ext::PortError::InvalidPortRange =>
1831 FidlConversionError::InvalidPortMatcherRange
1832 )]
1833 #[test_case(
1834 fnet_matchers_ext::InterfaceError::ZeroId =>
1835 FidlConversionError::ZeroInterfaceId
1836 )]
1837 #[test_case(
1838 fnet_matchers_ext::InterfaceError::UnknownUnionVariant =>
1839 FidlConversionError::UnknownUnionVariant(type_names::INTERFACE_MATCHER)
1840 )]
1841 #[test_case(
1842 {
1843 let invalid_port_class = fnet_interfaces::PortClass::__SourceBreaking {
1844 unknown_ordinal: 0
1845 };
1846 let error = fnet_interfaces_ext::PortClass::try_from(
1847 invalid_port_class
1848 ).unwrap_err();
1849 fnet_matchers_ext::InterfaceError::UnknownPortClass(error)
1850 } =>
1851 FidlConversionError::UnknownUnionVariant(type_names::NET_INTERFACES_PORT_CLASS)
1852 )]
1853 #[test_case(
1854 {
1855 let invalid_port_class = fhardware_network::PortClass::__SourceBreaking {
1856 unknown_ordinal: 0
1857 };
1858 let error = fnet_interfaces_ext::PortClass::try_from(
1859 invalid_port_class
1860 ).unwrap_err();
1861 fnet_matchers_ext::InterfaceError::UnknownPortClass(
1862 fnet_interfaces_ext::UnknownPortClassError::HardwareNetwork(error))
1863 } =>
1864 FidlConversionError::UnknownUnionVariant(type_names::HARDWARE_NETWORK_PORT_CLASS)
1865 )]
1866 #[test_case(
1867 fnet_matchers_ext::SubnetError::PrefixTooLong =>
1868 FidlConversionError::SubnetPrefixTooLong
1869 )]
1870 #[test_case(
1871 fnet_matchers_ext::SubnetError::HostBitsSet =>
1872 FidlConversionError::SubnetHostBitsSet
1873 )]
1874 #[test_case(
1875 fnet_matchers_ext::AddressRangeError::Invalid =>
1876 FidlConversionError::InvalidAddressRange
1877 )]
1878 #[test_case(
1879 fnet_matchers_ext::AddressRangeError::FamilyMismatch =>
1880 FidlConversionError::AddressRangeFamilyMismatch
1881 )]
1882 #[test_case(
1883 fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1884 fnet_matchers_ext::SubnetError::PrefixTooLong) =>
1885 FidlConversionError::SubnetPrefixTooLong
1886 )]
1887 #[test_case(
1888 fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1889 fnet_matchers_ext::SubnetError::HostBitsSet) =>
1890 FidlConversionError::SubnetHostBitsSet
1891 )]
1892 #[test_case(
1893 fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1894 fnet_matchers_ext::AddressRangeError::Invalid) =>
1895 FidlConversionError::InvalidAddressRange
1896 )]
1897 #[test_case(
1898 fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1899 fnet_matchers_ext::AddressRangeError::FamilyMismatch) =>
1900 FidlConversionError::AddressRangeFamilyMismatch
1901 )]
1902 #[test_case(
1903 fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant =>
1904 FidlConversionError::UnknownUnionVariant(type_names::ADDRESS_MATCHER_TYPE)
1905 )]
1906 #[test_case(
1907 fnet_matchers_ext::AddressError::AddressMatcherType(
1908 fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1909 fnet_matchers_ext::SubnetError::PrefixTooLong)) =>
1910 FidlConversionError::SubnetPrefixTooLong
1911 )]
1912 #[test_case(
1913 fnet_matchers_ext::AddressError::AddressMatcherType(
1914 fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1915 fnet_matchers_ext::SubnetError::HostBitsSet)) =>
1916 FidlConversionError::SubnetHostBitsSet
1917 )]
1918 #[test_case(
1919 fnet_matchers_ext::AddressError::AddressMatcherType(
1920 fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1921 fnet_matchers_ext::AddressRangeError::Invalid)) =>
1922 FidlConversionError::InvalidAddressRange
1923 )]
1924 #[test_case(
1925 fnet_matchers_ext::AddressError::AddressMatcherType(
1926 fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1927 fnet_matchers_ext::AddressRangeError::FamilyMismatch)) =>
1928 FidlConversionError::AddressRangeFamilyMismatch
1929 )]
1930 #[test_case(
1931 fnet_matchers_ext::AddressError::AddressMatcherType(
1932 fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant) =>
1933 FidlConversionError::UnknownUnionVariant(type_names::ADDRESS_MATCHER_TYPE)
1934 )]
1935 #[test_case(
1936 fnet_matchers_ext::TransportProtocolError::Port(
1937 fnet_matchers_ext::PortError::InvalidPortRange) =>
1938 FidlConversionError::InvalidPortMatcherRange
1939 )]
1940 #[test_case(
1941 fnet_matchers_ext::TransportProtocolError::UnknownUnionVariant =>
1942 FidlConversionError::UnknownUnionVariant(type_names::TRANSPORT_PROTOCOL)
1943 )]
1944 fn fidl_error_from_matcher_error<E: Into<FidlConversionError>>(
1945 error: E,
1946 ) -> FidlConversionError {
1947 error.into()
1948 }
1949
1950 #[test]
1951 fn action_try_from_unknown_variant() {
1952 assert_eq!(
1953 Action::try_from(fnet_filter::Action::__SourceBreaking { unknown_ordinal: 0 }),
1954 Err(FidlConversionError::UnknownUnionVariant(type_names::ACTION))
1955 );
1956 }
1957
1958 #[test]
1959 fn resource_try_from_unknown_variant() {
1960 assert_eq!(
1961 Resource::try_from(fnet_filter::Resource::__SourceBreaking { unknown_ordinal: 0 }),
1962 Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE))
1963 );
1964 }
1965
1966 #[test]
1967 fn event_try_from_unknown_variant() {
1968 assert_eq!(
1969 Event::try_from(fnet_filter::Event::__SourceBreaking { unknown_ordinal: 0 }),
1970 Err(FidlConversionError::UnknownUnionVariant(type_names::EVENT))
1971 );
1972 }
1973
1974 #[test]
1975 fn change_try_from_unknown_variant() {
1976 assert_eq!(
1977 Change::try_from(fnet_filter::Change::__SourceBreaking { unknown_ordinal: 0 }),
1978 Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE))
1979 );
1980 }
1981
1982 fn test_controller_a() -> ControllerId {
1983 ControllerId(String::from("test-controller-a"))
1984 }
1985
1986 fn test_controller_b() -> ControllerId {
1987 ControllerId(String::from("test-controller-b"))
1988 }
1989
1990 pub(crate) fn test_resource_id() -> ResourceId {
1991 ResourceId::Namespace(NamespaceId(String::from("test-namespace")))
1992 }
1993
1994 pub(crate) fn test_resource() -> Resource {
1995 Resource::Namespace(Namespace {
1996 id: NamespaceId(String::from("test-namespace")),
1997 domain: Domain::AllIp,
1998 })
1999 }
2000
2001 pub(crate) fn pretend_invalid_resource() -> Resource {
2004 Resource::Namespace(Namespace {
2005 id: NamespaceId(String::from("pretend-invalid-namespace")),
2006 domain: Domain::AllIp,
2007 })
2008 }
2009
2010 pub(crate) fn unknown_resource_id() -> ResourceId {
2011 ResourceId::Namespace(NamespaceId(String::from("does-not-exist")))
2012 }
2013
2014 #[fuchsia_async::run_singlethreaded(test)]
2015 async fn event_stream_from_state_conversion_error() {
2016 let (proxy, mut request_stream) =
2017 fidl::endpoints::create_proxy_and_stream::<fnet_filter::StateMarker>();
2018 let stream = event_stream_from_state(proxy).expect("get event stream");
2019 futures::pin_mut!(stream);
2020
2021 let send_invalid_event = async {
2022 let fnet_filter::StateRequest::GetWatcher { options: _, request, control_handle: _ } =
2023 request_stream
2024 .next()
2025 .await
2026 .expect("client should call state")
2027 .expect("request should not error");
2028 let fnet_filter::WatcherRequest::Watch { responder } = request
2029 .into_stream()
2030 .next()
2031 .await
2032 .expect("client should call watch")
2033 .expect("request should not error");
2034 responder
2035 .send(&[fnet_filter::Event::Added(fnet_filter::AddedResource {
2036 controller: String::from("controller"),
2037 resource: fnet_filter::Resource::Namespace(fnet_filter::Namespace {
2038 id: None,
2039 domain: None,
2040 ..Default::default()
2041 }),
2042 })])
2043 .expect("send batch with invalid event");
2044 };
2045 let ((), result) = futures::future::join(send_invalid_event, stream.next()).await;
2046 assert_matches!(
2047 result,
2048 Some(Err(WatchError::Conversion(FidlConversionError::MissingNamespaceId)))
2049 );
2050 }
2051
2052 #[fuchsia_async::run_singlethreaded(test)]
2053 async fn event_stream_from_state_empty_event_batch() {
2054 let (proxy, mut request_stream) =
2055 fidl::endpoints::create_proxy_and_stream::<fnet_filter::StateMarker>();
2056 let stream = event_stream_from_state(proxy).expect("get event stream");
2057 futures::pin_mut!(stream);
2058
2059 let send_empty_batch = async {
2060 let fnet_filter::StateRequest::GetWatcher { options: _, request, control_handle: _ } =
2061 request_stream
2062 .next()
2063 .await
2064 .expect("client should call state")
2065 .expect("request should not error");
2066 let fnet_filter::WatcherRequest::Watch { responder } = request
2067 .into_stream()
2068 .next()
2069 .await
2070 .expect("client should call watch")
2071 .expect("request should not error");
2072 responder.send(&[]).expect("send empty batch");
2073 };
2074 let ((), result) = futures::future::join(send_empty_batch, stream.next()).await;
2075 assert_matches!(result, Some(Err(WatchError::EmptyEventBatch)));
2076 }
2077
2078 #[fuchsia_async::run_singlethreaded(test)]
2079 async fn get_existing_resources_success() {
2080 let event_stream = futures::stream::iter([
2081 Ok(Event::Existing(test_controller_a(), test_resource())),
2082 Ok(Event::Existing(test_controller_b(), test_resource())),
2083 Ok(Event::Idle),
2084 Ok(Event::Removed(test_controller_a(), test_resource_id())),
2085 ]);
2086 futures::pin_mut!(event_stream);
2087
2088 let existing = get_existing_resources::<HashMap<_, _>>(event_stream.by_ref())
2089 .await
2090 .expect("get existing resources");
2091 assert_eq!(
2092 existing,
2093 HashMap::from([
2094 (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2095 (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2096 ])
2097 );
2098
2099 let trailing_events = event_stream.collect::<Vec<_>>().await;
2100 assert_matches!(
2101 &trailing_events[..],
2102 [Ok(Event::Removed(controller, resource))] if controller == &test_controller_a() &&
2103 resource == &test_resource_id()
2104 );
2105 }
2106
2107 #[fuchsia_async::run_singlethreaded(test)]
2108 async fn get_existing_resources_error_in_stream() {
2109 let event_stream =
2110 futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
2111 futures::pin_mut!(event_stream);
2112 assert_matches!(
2113 get_existing_resources::<HashMap<_, _>>(event_stream).await,
2114 Err(GetExistingResourcesError::ErrorInStream(WatchError::EmptyEventBatch))
2115 )
2116 }
2117
2118 #[fuchsia_async::run_singlethreaded(test)]
2119 async fn get_existing_resources_unexpected_event() {
2120 let event_stream = futures::stream::once(futures::future::ready(Ok(Event::EndOfUpdate)));
2121 futures::pin_mut!(event_stream);
2122 assert_matches!(
2123 get_existing_resources::<HashMap<_, _>>(event_stream).await,
2124 Err(GetExistingResourcesError::UnexpectedEvent(Event::EndOfUpdate))
2125 )
2126 }
2127
2128 #[fuchsia_async::run_singlethreaded(test)]
2129 async fn get_existing_resources_duplicate_resource() {
2130 let event_stream = futures::stream::iter([
2131 Ok(Event::Existing(test_controller_a(), test_resource())),
2132 Ok(Event::Existing(test_controller_a(), test_resource())),
2133 ]);
2134 futures::pin_mut!(event_stream);
2135 assert_matches!(
2136 get_existing_resources::<HashMap<_, _>>(event_stream).await,
2137 Err(GetExistingResourcesError::DuplicateResource(resource))
2138 if resource == test_resource()
2139 )
2140 }
2141
2142 #[fuchsia_async::run_singlethreaded(test)]
2143 async fn get_existing_resources_stream_ended() {
2144 let event_stream = futures::stream::once(futures::future::ready(Ok(Event::Existing(
2145 test_controller_a(),
2146 test_resource(),
2147 ))));
2148 futures::pin_mut!(event_stream);
2149 assert_matches!(
2150 get_existing_resources::<HashMap<_, _>>(event_stream).await,
2151 Err(GetExistingResourcesError::StreamEnded)
2152 )
2153 }
2154
2155 #[fuchsia_async::run_singlethreaded(test)]
2156 async fn wait_for_condition_add_remove() {
2157 let mut state = HashMap::new();
2158
2159 let has_resource = |resources: &HashMap<_, HashMap<_, _>>| {
2162 resources.get(&test_controller_a()).map_or(false, |controller| {
2163 controller
2164 .get(&test_resource_id())
2165 .map_or(false, |resource| resource == &test_resource())
2166 })
2167 };
2168 assert_matches!(
2169 wait_for_condition(futures::stream::pending(), &mut state, has_resource).now_or_never(),
2170 None
2171 );
2172 assert!(state.is_empty());
2173 assert_matches!(
2174 wait_for_condition(
2175 futures::stream::iter([
2176 Ok(Event::Added(test_controller_b(), test_resource())),
2177 Ok(Event::EndOfUpdate),
2178 Ok(Event::Added(test_controller_a(), test_resource())),
2179 Ok(Event::EndOfUpdate),
2180 ]),
2181 &mut state,
2182 has_resource
2183 )
2184 .now_or_never(),
2185 Some(Ok(()))
2186 );
2187 assert_eq!(
2188 state,
2189 HashMap::from([
2190 (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2191 (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2192 ])
2193 );
2194
2195 assert_matches!(
2197 wait_for_condition(
2198 futures::stream::iter([
2199 Ok(Event::Added(test_controller_a(), test_resource())),
2200 Ok(Event::EndOfUpdate),
2201 ]),
2202 &mut state,
2203 has_resource
2204 )
2205 .now_or_never(),
2206 Some(Err(WaitForConditionError::AddedAlreadyExisting(r))) if r == test_resource()
2207 );
2208 assert_eq!(
2209 state,
2210 HashMap::from([
2211 (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2212 (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2213 ])
2214 );
2215
2216 let does_not_have_resource = |resources: &HashMap<_, HashMap<_, _>>| {
2219 resources.get(&test_controller_a()).map_or(false, |controller| controller.is_empty())
2220 };
2221 assert_matches!(
2222 wait_for_condition(futures::stream::pending(), &mut state, does_not_have_resource)
2223 .now_or_never(),
2224 None
2225 );
2226 assert_eq!(
2227 state,
2228 HashMap::from([
2229 (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2230 (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2231 ])
2232 );
2233 assert_matches!(
2234 wait_for_condition(
2235 futures::stream::iter([
2236 Ok(Event::Removed(test_controller_b(), test_resource_id())),
2237 Ok(Event::EndOfUpdate),
2238 Ok(Event::Removed(test_controller_a(), test_resource_id())),
2239 Ok(Event::EndOfUpdate),
2240 ]),
2241 &mut state,
2242 does_not_have_resource
2243 )
2244 .now_or_never(),
2245 Some(Ok(()))
2246 );
2247 assert_eq!(
2248 state,
2249 HashMap::from([
2250 (test_controller_a(), HashMap::new()),
2251 (test_controller_b(), HashMap::new()),
2252 ])
2253 );
2254
2255 assert_matches!(
2257 wait_for_condition(
2258 futures::stream::iter([
2259 Ok(Event::Removed(test_controller_a(), test_resource_id())),
2260 Ok(Event::EndOfUpdate),
2261 ]),
2262 &mut state,
2263 does_not_have_resource
2264 ).now_or_never(),
2265 Some(Err(WaitForConditionError::RemovedNonExistent(r))) if r == test_resource_id()
2266 );
2267 assert_eq!(
2268 state,
2269 HashMap::from([
2270 (test_controller_a(), HashMap::new()),
2271 (test_controller_b(), HashMap::new()),
2272 ])
2273 );
2274 }
2275
2276 #[test]
2277 fn predicate_not_tested_until_update_complete() {
2278 let mut state = HashMap::new();
2279 let (mut tx, rx) = mpsc::unbounded();
2280
2281 let wait = wait_for_condition(rx, &mut state, |state| !state.is_empty()).fuse();
2282 futures::pin_mut!(wait);
2283
2284 let mut exec = fuchsia_async::TestExecutor::new();
2288 exec.run_singlethreaded(async {
2289 tx.send(Ok(Event::Added(test_controller_a(), test_resource())))
2290 .await
2291 .expect("receiver should not be closed");
2292 });
2293 assert_matches!(exec.run_until_stalled(&mut wait), Poll::Pending);
2294
2295 exec.run_singlethreaded(async {
2296 tx.send(Ok(Event::EndOfUpdate)).await.expect("receiver should not be closed");
2297 wait.await.expect("condition should be satisfied once update is complete");
2298 });
2299 }
2300
2301 #[fuchsia_async::run_singlethreaded(test)]
2302 async fn wait_for_condition_error_in_stream() {
2303 let mut state = HashMap::new();
2304 let event_stream =
2305 futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
2306 assert_matches!(
2307 wait_for_condition(event_stream, &mut state, |_| true).await,
2308 Err(WaitForConditionError::ErrorInStream(WatchError::EmptyEventBatch))
2309 );
2310 assert!(state.is_empty());
2311 }
2312
2313 #[fuchsia_async::run_singlethreaded(test)]
2314 async fn wait_for_condition_stream_ended() {
2315 let mut state = HashMap::new();
2316 let event_stream = futures::stream::empty();
2317 assert_matches!(
2318 wait_for_condition(event_stream, &mut state, |_| true).await,
2319 Err(WaitForConditionError::StreamEnded)
2320 );
2321 assert!(state.is_empty());
2322 }
2323
2324 pub(crate) async fn handle_open_controller(
2325 mut request_stream: fnet_filter::ControlRequestStream,
2326 ) -> fnet_filter::NamespaceControllerRequestStream {
2327 let (id, request, _control_handle) = request_stream
2328 .next()
2329 .await
2330 .expect("client should open controller")
2331 .expect("request should not error")
2332 .into_open_controller()
2333 .expect("client should open controller");
2334 let (stream, control_handle) = request.into_stream_and_control_handle();
2335 control_handle.send_on_id_assigned(&id).expect("send assigned ID");
2336
2337 stream
2338 }
2339
2340 pub(crate) async fn handle_push_changes(
2341 stream: &mut fnet_filter::NamespaceControllerRequestStream,
2342 push_changes_result: fnet_filter::ChangeValidationResult,
2343 ) {
2344 let (_changes, responder) = stream
2345 .next()
2346 .await
2347 .expect("client should push changes")
2348 .expect("request should not error")
2349 .into_push_changes()
2350 .expect("client should push changes");
2351 responder.send(push_changes_result).expect("send empty batch");
2352 }
2353
2354 pub(crate) async fn handle_commit(
2355 stream: &mut fnet_filter::NamespaceControllerRequestStream,
2356 commit_result: fnet_filter::CommitResult,
2357 ) {
2358 let (_options, responder) = stream
2359 .next()
2360 .await
2361 .expect("client should commit")
2362 .expect("request should not error")
2363 .into_commit()
2364 .expect("client should commit");
2365 responder.send(commit_result).expect("send commit result");
2366 }
2367
2368 #[fuchsia_async::run_singlethreaded(test)]
2369 async fn controller_push_changes_reports_invalid_change() {
2370 let (control, request_stream) =
2371 fidl::endpoints::create_proxy_and_stream::<fnet_filter::ControlMarker>();
2372 let push_invalid_change = async {
2373 let mut controller = Controller::new(&control, &ControllerId(String::from("test")))
2374 .await
2375 .expect("create controller");
2376 let result = controller
2377 .push_changes(vec![
2378 Change::Create(test_resource()),
2379 Change::Create(pretend_invalid_resource()),
2382 Change::Remove(test_resource_id()),
2383 ])
2384 .await;
2385 assert_matches!(
2386 result,
2387 Err(PushChangesError::ErrorOnChange(errors)) if errors == vec![(
2388 Change::Create(pretend_invalid_resource()),
2389 ChangeValidationError::InvalidPortMatcher
2390 )]
2391 );
2392 };
2393
2394 let handle_controller = async {
2395 let mut stream = handle_open_controller(request_stream).await;
2396 handle_push_changes(
2397 &mut stream,
2398 fnet_filter::ChangeValidationResult::ErrorOnChange(vec![
2399 fnet_filter::ChangeValidationError::Ok,
2400 fnet_filter::ChangeValidationError::InvalidPortMatcher,
2401 fnet_filter::ChangeValidationError::NotReached,
2402 ]),
2403 )
2404 .await;
2405 };
2406
2407 let ((), ()) = futures::future::join(push_invalid_change, handle_controller).await;
2408 }
2409
2410 #[fuchsia_async::run_singlethreaded(test)]
2411 async fn controller_commit_reports_invalid_change() {
2412 let (control, request_stream) =
2413 fidl::endpoints::create_proxy_and_stream::<fnet_filter::ControlMarker>();
2414 let commit_invalid_change = async {
2415 let mut controller = Controller::new(&control, &ControllerId(String::from("test")))
2416 .await
2417 .expect("create controller");
2418 controller
2419 .push_changes(vec![
2420 Change::Create(test_resource()),
2421 Change::Remove(unknown_resource_id()),
2422 Change::Remove(test_resource_id()),
2423 ])
2424 .await
2425 .expect("push changes");
2426 let result = controller.commit().await;
2427 assert_matches!(
2428 result,
2429 Err(CommitError::ErrorOnChange(errors)) if errors == vec![(
2430 Change::Remove(unknown_resource_id()),
2431 ChangeCommitError::NamespaceNotFound,
2432 )]
2433 );
2434 };
2435 let handle_controller = async {
2436 let mut stream = handle_open_controller(request_stream).await;
2437 handle_push_changes(
2438 &mut stream,
2439 fnet_filter::ChangeValidationResult::Ok(fnet_filter::Empty {}),
2440 )
2441 .await;
2442 handle_commit(
2443 &mut stream,
2444 fnet_filter::CommitResult::ErrorOnChange(vec![
2445 fnet_filter::CommitError::Ok,
2446 fnet_filter::CommitError::NamespaceNotFound,
2447 fnet_filter::CommitError::Ok,
2448 ]),
2449 )
2450 .await;
2451 };
2452 let ((), ()) = futures::future::join(commit_invalid_change, handle_controller).await;
2453 }
2454}