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}
1265
1266impl TryFrom<fnet_filter::CommitError> for ChangeCommitError {
1267 type Error = FidlConversionError;
1268
1269 fn try_from(error: fnet_filter::CommitError) -> Result<Self, Self::Error> {
1270 match error {
1271 fnet_filter::CommitError::NamespaceNotFound => Ok(Self::NamespaceNotFound),
1272 fnet_filter::CommitError::RoutineNotFound => Ok(Self::RoutineNotFound),
1273 fnet_filter::CommitError::RuleNotFound => Ok(Self::RuleNotFound),
1274 fnet_filter::CommitError::AlreadyExists => Ok(Self::AlreadyExists),
1275 fnet_filter::CommitError::TargetRoutineIsInstalled => {
1276 Ok(Self::TargetRoutineIsInstalled)
1277 }
1278 fnet_filter::CommitError::Ok | fnet_filter::CommitError::NotReached => {
1279 Err(FidlConversionError::NotAnError)
1280 }
1281 fnet_filter::CommitError::__SourceBreaking { unknown_ordinal: _ } => {
1282 Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_ERROR))
1283 }
1284 }
1285 }
1286}
1287
1288#[derive(Debug, Error)]
1290pub enum CommitError {
1291 #[error("failed to call FIDL method: {0}")]
1292 CallMethod(fidl::Error),
1293 #[error("rule has a matcher that is unavailable in its context: {0:?}")]
1294 RuleWithInvalidMatcher(RuleId),
1295 #[error("rule has an action that is invalid for its routine: {0:?}")]
1296 RuleWithInvalidAction(RuleId),
1297 #[error("rule has a TransparentProxy action but not a valid transport protocol matcher: {0:?}")]
1298 TransparentProxyWithInvalidMatcher(RuleId),
1299 #[error(
1300 "rule has a Redirect action that specifies a destination port but not a valid transport \
1301 protocol matcher: {0:?}"
1302 )]
1303 RedirectWithInvalidMatcher(RuleId),
1304 #[error(
1305 "rule has a Masquerade action that specifies a source port but not a valid transport \
1306 protocol matcher: {0:?}"
1307 )]
1308 MasqueradeWithInvalidMatcher(RuleId),
1309 #[error("routine forms a cycle {0:?}")]
1310 CyclicalRoutineGraph(RoutineId),
1311 #[error("invalid change was pushed: {0:?}")]
1312 ErrorOnChange(Vec<(Change, ChangeCommitError)>),
1313 #[error("unknown FIDL type: {0}")]
1314 FidlConversion(#[from] FidlConversionError),
1315}
1316
1317#[derive(Debug, Clone, PartialEq)]
1319pub enum Change {
1320 Create(Resource),
1321 Remove(ResourceId),
1322}
1323
1324impl From<Change> for fnet_filter::Change {
1325 fn from(change: Change) -> Self {
1326 match change {
1327 Change::Create(resource) => Self::Create(resource.into()),
1328 Change::Remove(resource) => Self::Remove(resource.into()),
1329 }
1330 }
1331}
1332
1333impl TryFrom<fnet_filter::Change> for Change {
1334 type Error = FidlConversionError;
1335
1336 fn try_from(change: fnet_filter::Change) -> Result<Self, Self::Error> {
1337 match change {
1338 fnet_filter::Change::Create(resource) => Ok(Self::Create(resource.try_into()?)),
1339 fnet_filter::Change::Remove(resource) => Ok(Self::Remove(resource.try_into()?)),
1340 fnet_filter::Change::__SourceBreaking { .. } => {
1341 Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE))
1342 }
1343 }
1344 }
1345}
1346
1347pub struct Controller {
1349 controller: fnet_filter::NamespaceControllerProxy,
1350 id: ControllerId,
1354 pending_changes: Vec<Change>,
1358}
1359
1360impl Controller {
1361 pub async fn new_root(
1362 root: &fnet_root::FilterProxy,
1363 ControllerId(id): &ControllerId,
1364 ) -> Result<Self, ControllerCreationError> {
1365 let (controller, server_end) = fidl::endpoints::create_proxy();
1366 root.open_controller(id, server_end).map_err(ControllerCreationError::OpenController)?;
1367
1368 let fnet_filter::NamespaceControllerEvent::OnIdAssigned { id } = controller
1369 .take_event_stream()
1370 .next()
1371 .await
1372 .ok_or(ControllerCreationError::NoIdAssigned)?
1373 .map_err(ControllerCreationError::IdAssignment)?;
1374 Ok(Self { controller, id: ControllerId(id), pending_changes: Vec::new() })
1375 }
1376
1377 pub async fn new(
1383 control: &fnet_filter::ControlProxy,
1384 ControllerId(id): &ControllerId,
1385 ) -> Result<Self, ControllerCreationError> {
1386 let (controller, server_end) = fidl::endpoints::create_proxy();
1387 control.open_controller(id, server_end).map_err(ControllerCreationError::OpenController)?;
1388
1389 let fnet_filter::NamespaceControllerEvent::OnIdAssigned { id } = controller
1390 .take_event_stream()
1391 .next()
1392 .await
1393 .ok_or(ControllerCreationError::NoIdAssigned)?
1394 .map_err(ControllerCreationError::IdAssignment)?;
1395 Ok(Self { controller, id: ControllerId(id), pending_changes: Vec::new() })
1396 }
1397
1398 pub fn id(&self) -> &ControllerId {
1399 &self.id
1400 }
1401
1402 pub async fn register_ebpf_program(
1403 &mut self,
1404 handle: febpf::ProgramHandle,
1405 program: febpf::VerifiedProgram,
1406 ) -> Result<(), RegisterEbpfProgramError> {
1407 self.controller
1408 .register_ebpf_program(handle, program)
1409 .await
1410 .map_err(RegisterEbpfProgramError::CallMethod)?
1411 .map_err(RegisterEbpfProgramError::from)
1412 }
1413
1414 pub async fn push_changes(&mut self, changes: Vec<Change>) -> Result<(), PushChangesError> {
1415 let fidl_changes = changes.iter().cloned().map(Into::into).collect::<Vec<_>>();
1416 let result = self
1417 .controller
1418 .push_changes(&fidl_changes)
1419 .await
1420 .map_err(PushChangesError::CallMethod)?;
1421 handle_change_validation_result(result, &changes)?;
1422 self.pending_changes.extend(changes);
1426 Ok(())
1427 }
1428
1429 async fn commit_with_options(
1430 &mut self,
1431 options: fnet_filter::CommitOptions,
1432 ) -> Result<(), CommitError> {
1433 let committed_changes = std::mem::take(&mut self.pending_changes);
1434 let result = self.controller.commit(options).await.map_err(CommitError::CallMethod)?;
1435 handle_commit_result(result, committed_changes)
1436 }
1437
1438 pub async fn commit(&mut self) -> Result<(), CommitError> {
1439 self.commit_with_options(fnet_filter::CommitOptions::default()).await
1440 }
1441
1442 pub async fn commit_idempotent(&mut self) -> Result<(), CommitError> {
1443 self.commit_with_options(fnet_filter::CommitOptions {
1444 idempotent: Some(true),
1445 __source_breaking: SourceBreaking,
1446 })
1447 .await
1448 }
1449}
1450
1451pub(crate) fn handle_change_validation_result(
1452 change_validation_result: fnet_filter::ChangeValidationResult,
1453 changes: &Vec<Change>,
1454) -> Result<(), PushChangesError> {
1455 match change_validation_result {
1456 fnet_filter::ChangeValidationResult::Ok(fnet_filter::Empty {}) => Ok(()),
1457 fnet_filter::ChangeValidationResult::TooManyChanges(fnet_filter::Empty {}) => {
1458 Err(PushChangesError::TooManyChanges)
1459 }
1460 fnet_filter::ChangeValidationResult::ErrorOnChange(results) => {
1461 let errors: Result<_, PushChangesError> =
1462 changes.iter().zip(results).try_fold(Vec::new(), |mut errors, (change, result)| {
1463 match result {
1464 fnet_filter::ChangeValidationError::Ok
1465 | fnet_filter::ChangeValidationError::NotReached => Ok(errors),
1466 error @ (fnet_filter::ChangeValidationError::MissingRequiredField
1467 | fnet_filter::ChangeValidationError::InvalidInterfaceMatcher
1468 | fnet_filter::ChangeValidationError::InvalidAddressMatcher
1469 | fnet_filter::ChangeValidationError::InvalidPortMatcher
1470 | fnet_filter::ChangeValidationError::InvalidTransparentProxyAction
1471 | fnet_filter::ChangeValidationError::InvalidNatAction
1472 | fnet_filter::ChangeValidationError::InvalidPortRange) => {
1473 let error = error
1474 .try_into()
1475 .expect("`Ok` and `NotReached` are handled in another arm");
1476 errors.push((change.clone(), error));
1477 Ok(errors)
1478 }
1479 fnet_filter::ChangeValidationError::__SourceBreaking { .. } => {
1480 Err(FidlConversionError::UnknownUnionVariant(
1481 type_names::CHANGE_VALIDATION_ERROR,
1482 )
1483 .into())
1484 }
1485 }
1486 });
1487 Err(PushChangesError::ErrorOnChange(errors?))
1488 }
1489 fnet_filter::ChangeValidationResult::__SourceBreaking { .. } => {
1490 Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE_VALIDATION_RESULT)
1491 .into())
1492 }
1493 }
1494}
1495
1496pub(crate) fn handle_commit_result(
1497 commit_result: fnet_filter::CommitResult,
1498 committed_changes: Vec<Change>,
1499) -> Result<(), CommitError> {
1500 match commit_result {
1501 fnet_filter::CommitResult::Ok(fnet_filter::Empty {}) => Ok(()),
1502 fnet_filter::CommitResult::RuleWithInvalidMatcher(rule_id) => {
1503 Err(CommitError::RuleWithInvalidMatcher(rule_id.into()))
1504 }
1505 fnet_filter::CommitResult::RuleWithInvalidAction(rule_id) => {
1506 Err(CommitError::RuleWithInvalidAction(rule_id.into()))
1507 }
1508 fnet_filter::CommitResult::TransparentProxyWithInvalidMatcher(rule_id) => {
1509 Err(CommitError::TransparentProxyWithInvalidMatcher(rule_id.into()))
1510 }
1511 fnet_filter::CommitResult::RedirectWithInvalidMatcher(rule_id) => {
1512 Err(CommitError::RedirectWithInvalidMatcher(rule_id.into()))
1513 }
1514 fnet_filter::CommitResult::MasqueradeWithInvalidMatcher(rule_id) => {
1515 Err(CommitError::MasqueradeWithInvalidMatcher(rule_id.into()))
1516 }
1517 fnet_filter::CommitResult::CyclicalRoutineGraph(routine_id) => {
1518 Err(CommitError::CyclicalRoutineGraph(routine_id.into()))
1519 }
1520 fnet_filter::CommitResult::ErrorOnChange(results) => {
1521 let errors: Result<_, CommitError> = committed_changes
1522 .into_iter()
1523 .zip(results)
1524 .try_fold(Vec::new(), |mut errors, (change, result)| match result {
1525 fnet_filter::CommitError::Ok | fnet_filter::CommitError::NotReached => {
1526 Ok(errors)
1527 }
1528 error @ (fnet_filter::CommitError::NamespaceNotFound
1529 | fnet_filter::CommitError::RoutineNotFound
1530 | fnet_filter::CommitError::RuleNotFound
1531 | fnet_filter::CommitError::AlreadyExists
1532 | fnet_filter::CommitError::TargetRoutineIsInstalled) => {
1533 let error = error
1534 .try_into()
1535 .expect("`Ok` and `NotReached` are handled in another arm");
1536 errors.push((change, error));
1537 Ok(errors)
1538 }
1539 fnet_filter::CommitError::__SourceBreaking { .. } => {
1540 Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_ERROR)
1541 .into())
1542 }
1543 });
1544 Err(CommitError::ErrorOnChange(errors?))
1545 }
1546 fnet_filter::CommitResult::__SourceBreaking { .. } => {
1547 Err(FidlConversionError::UnknownUnionVariant(type_names::COMMIT_RESULT).into())
1548 }
1549 }
1550}
1551
1552#[cfg(test)]
1553mod tests {
1554
1555 use assert_matches::assert_matches;
1556 use fidl_fuchsia_net_matchers as fnet_matchers;
1557 use futures::channel::mpsc;
1558 use futures::task::Poll;
1559 use futures::{FutureExt as _, SinkExt as _};
1560 use test_case::test_case;
1561
1562 use {
1563 fidl_fuchsia_hardware_network as fhardware_network,
1564 fidl_fuchsia_net_interfaces as fnet_interfaces,
1565 };
1566
1567 use super::*;
1568
1569 #[test_case(
1570 fnet_filter::ResourceId::Namespace(String::from("namespace")),
1571 ResourceId::Namespace(NamespaceId(String::from("namespace")));
1572 "NamespaceId"
1573 )]
1574 #[test_case(fnet_filter::Domain::Ipv4, Domain::Ipv4; "Domain")]
1575 #[test_case(
1576 fnet_filter::Namespace {
1577 id: Some(String::from("namespace")),
1578 domain: Some(fnet_filter::Domain::Ipv4),
1579 ..Default::default()
1580 },
1581 Namespace { id: NamespaceId(String::from("namespace")), domain: Domain::Ipv4 };
1582 "Namespace"
1583 )]
1584 #[test_case(fnet_filter::IpInstallationHook::Egress, IpHook::Egress; "IpHook")]
1585 #[test_case(fnet_filter::NatInstallationHook::Egress, NatHook::Egress; "NatHook")]
1586 #[test_case(
1587 fnet_filter::InstalledIpRoutine {
1588 hook: Some(fnet_filter::IpInstallationHook::Egress),
1589 priority: Some(1),
1590 ..Default::default()
1591 },
1592 InstalledIpRoutine {
1593 hook: IpHook::Egress,
1594 priority: 1,
1595 };
1596 "InstalledIpRoutine"
1597 )]
1598 #[test_case(
1599 fnet_filter::RoutineType::Ip(fnet_filter::IpRoutine {
1600 installation: Some(fnet_filter::InstalledIpRoutine {
1601 hook: Some(fnet_filter::IpInstallationHook::LocalEgress),
1602 priority: Some(1),
1603 ..Default::default()
1604 }),
1605 ..Default::default()
1606 }),
1607 RoutineType::Ip(Some(InstalledIpRoutine { hook: IpHook::LocalEgress, priority: 1 }));
1608 "RoutineType"
1609 )]
1610 #[test_case(
1611 fnet_filter::Routine {
1612 id: Some(fnet_filter::RoutineId {
1613 namespace: String::from("namespace"),
1614 name: String::from("routine"),
1615 }),
1616 type_: Some(fnet_filter::RoutineType::Nat(fnet_filter::NatRoutine::default())),
1617 ..Default::default()
1618 },
1619 Routine {
1620 id: RoutineId {
1621 namespace: NamespaceId(String::from("namespace")),
1622 name: String::from("routine"),
1623 },
1624 routine_type: RoutineType::Nat(None),
1625 };
1626 "Routine"
1627 )]
1628 #[test_case(
1629 fnet_filter::Matchers {
1630 in_interface: Some(fnet_matchers::Interface::Name(String::from("wlan"))),
1631 transport_protocol: Some(fnet_matchers::PacketTransportProtocol::Tcp(fnet_matchers::TcpPacket {
1632 src_port: None,
1633 dst_port: Some(fnet_matchers::Port { start: 22, end: 22, invert: false }),
1634 ..Default::default()
1635 })),
1636 ..Default::default()
1637 },
1638 Matchers {
1639 in_interface: Some(fnet_matchers_ext::Interface::Name(String::from("wlan"))),
1640 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Tcp {
1641 src_port: None,
1642 dst_port: Some(fnet_matchers_ext::Port::new(22, 22, false).unwrap()),
1643 }),
1644 ..Default::default()
1645 };
1646 "Matchers"
1647 )]
1648 #[test_case(
1649 fnet_filter::Action::Accept(fnet_filter::Empty {}),
1650 Action::Accept;
1651 "Action"
1652 )]
1653 #[test_case(
1654 fnet_filter::Rule {
1655 id: fnet_filter::RuleId {
1656 routine: fnet_filter::RoutineId {
1657 namespace: String::from("namespace"),
1658 name: String::from("routine"),
1659 },
1660 index: 1,
1661 },
1662 matchers: fnet_filter::Matchers {
1663 transport_protocol: Some(fnet_matchers::PacketTransportProtocol::Icmp(
1664 fnet_matchers::IcmpPacket::default()
1665 )),
1666 ..Default::default()
1667 },
1668 action: fnet_filter::Action::Drop(fnet_filter::Empty {}),
1669 },
1670 Rule {
1671 id: RuleId {
1672 routine: RoutineId {
1673 namespace: NamespaceId(String::from("namespace")),
1674 name: String::from("routine"),
1675 },
1676 index: 1,
1677 },
1678 matchers: Matchers {
1679 transport_protocol: Some(fnet_matchers_ext::TransportProtocol::Icmp),
1680 ..Default::default()
1681 },
1682 action: Action::Drop,
1683 };
1684 "Rule"
1685 )]
1686 #[test_case(
1687 fnet_filter::Resource::Namespace(fnet_filter::Namespace {
1688 id: Some(String::from("namespace")),
1689 domain: Some(fnet_filter::Domain::Ipv4),
1690 ..Default::default()
1691 }),
1692 Resource::Namespace(Namespace {
1693 id: NamespaceId(String::from("namespace")),
1694 domain: Domain::Ipv4
1695 });
1696 "Resource"
1697 )]
1698 #[test_case(
1699 fnet_filter::Event::EndOfUpdate(fnet_filter::Empty {}),
1700 Event::EndOfUpdate;
1701 "Event"
1702 )]
1703 #[test_case(
1704 fnet_filter::Change::Remove(fnet_filter::ResourceId::Namespace(String::from("namespace"))),
1705 Change::Remove(ResourceId::Namespace(NamespaceId(String::from("namespace"))));
1706 "Change"
1707 )]
1708 fn convert_from_fidl_and_back<F, E>(fidl_type: F, local_type: E)
1709 where
1710 E: TryFrom<F> + Clone + Debug + PartialEq,
1711 <E as TryFrom<F>>::Error: Debug + PartialEq,
1712 F: From<E> + Clone + Debug + PartialEq,
1713 {
1714 assert_eq!(fidl_type.clone().try_into(), Ok(local_type.clone()));
1715 assert_eq!(<_ as Into<F>>::into(local_type), fidl_type.clone());
1716 }
1717
1718 #[test]
1719 fn resource_id_try_from_unknown_variant() {
1720 assert_eq!(
1721 ResourceId::try_from(fnet_filter::ResourceId::__SourceBreaking { unknown_ordinal: 0 }),
1722 Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE_ID))
1723 );
1724 }
1725
1726 #[test]
1727 fn domain_try_from_unknown_variant() {
1728 assert_eq!(
1729 Domain::try_from(fnet_filter::Domain::__SourceBreaking { unknown_ordinal: 0 }),
1730 Err(FidlConversionError::UnknownUnionVariant(type_names::DOMAIN))
1731 );
1732 }
1733
1734 #[test]
1735 fn namespace_try_from_missing_properties() {
1736 assert_eq!(
1737 Namespace::try_from(fnet_filter::Namespace {
1738 id: None,
1739 domain: Some(fnet_filter::Domain::Ipv4),
1740 ..Default::default()
1741 }),
1742 Err(FidlConversionError::MissingNamespaceId)
1743 );
1744 assert_eq!(
1745 Namespace::try_from(fnet_filter::Namespace {
1746 id: Some(String::from("namespace")),
1747 domain: None,
1748 ..Default::default()
1749 }),
1750 Err(FidlConversionError::MissingNamespaceDomain)
1751 );
1752 }
1753
1754 #[test]
1755 fn ip_installation_hook_try_from_unknown_variant() {
1756 assert_eq!(
1757 IpHook::try_from(fnet_filter::IpInstallationHook::__SourceBreaking {
1758 unknown_ordinal: 0
1759 }),
1760 Err(FidlConversionError::UnknownUnionVariant(type_names::IP_INSTALLATION_HOOK))
1761 );
1762 }
1763
1764 #[test]
1765 fn nat_installation_hook_try_from_unknown_variant() {
1766 assert_eq!(
1767 NatHook::try_from(fnet_filter::NatInstallationHook::__SourceBreaking {
1768 unknown_ordinal: 0
1769 }),
1770 Err(FidlConversionError::UnknownUnionVariant(type_names::NAT_INSTALLATION_HOOK))
1771 );
1772 }
1773
1774 #[test]
1775 fn installed_ip_routine_try_from_missing_hook() {
1776 assert_eq!(
1777 InstalledIpRoutine::try_from(fnet_filter::InstalledIpRoutine {
1778 hook: None,
1779 ..Default::default()
1780 }),
1781 Err(FidlConversionError::MissingIpInstallationHook)
1782 );
1783 }
1784
1785 #[test]
1786 fn installed_nat_routine_try_from_missing_hook() {
1787 assert_eq!(
1788 InstalledNatRoutine::try_from(fnet_filter::InstalledNatRoutine {
1789 hook: None,
1790 ..Default::default()
1791 }),
1792 Err(FidlConversionError::MissingNatInstallationHook)
1793 );
1794 }
1795
1796 #[test]
1797 fn routine_type_try_from_unknown_variant() {
1798 assert_eq!(
1799 RoutineType::try_from(fnet_filter::RoutineType::__SourceBreaking {
1800 unknown_ordinal: 0
1801 }),
1802 Err(FidlConversionError::UnknownUnionVariant(type_names::ROUTINE_TYPE))
1803 );
1804 }
1805
1806 #[test]
1807 fn routine_try_from_missing_properties() {
1808 assert_eq!(
1809 Routine::try_from(fnet_filter::Routine { id: None, ..Default::default() }),
1810 Err(FidlConversionError::MissingRoutineId)
1811 );
1812 assert_eq!(
1813 Routine::try_from(fnet_filter::Routine {
1814 id: Some(fnet_filter::RoutineId {
1815 namespace: String::from("namespace"),
1816 name: String::from("routine"),
1817 }),
1818 type_: None,
1819 ..Default::default()
1820 }),
1821 Err(FidlConversionError::MissingRoutineType)
1822 );
1823 }
1824
1825 #[test_case(
1826 fnet_matchers_ext::PortError::InvalidPortRange =>
1827 FidlConversionError::InvalidPortMatcherRange
1828 )]
1829 #[test_case(
1830 fnet_matchers_ext::InterfaceError::ZeroId =>
1831 FidlConversionError::ZeroInterfaceId
1832 )]
1833 #[test_case(
1834 fnet_matchers_ext::InterfaceError::UnknownUnionVariant =>
1835 FidlConversionError::UnknownUnionVariant(type_names::INTERFACE_MATCHER)
1836 )]
1837 #[test_case(
1838 {
1839 let invalid_port_class = fnet_interfaces::PortClass::__SourceBreaking {
1840 unknown_ordinal: 0
1841 };
1842 let error = fnet_interfaces_ext::PortClass::try_from(
1843 invalid_port_class
1844 ).unwrap_err();
1845 fnet_matchers_ext::InterfaceError::UnknownPortClass(error)
1846 } =>
1847 FidlConversionError::UnknownUnionVariant(type_names::NET_INTERFACES_PORT_CLASS)
1848 )]
1849 #[test_case(
1850 {
1851 let invalid_port_class = fhardware_network::PortClass::__SourceBreaking {
1852 unknown_ordinal: 0
1853 };
1854 let error = fnet_interfaces_ext::PortClass::try_from(
1855 invalid_port_class
1856 ).unwrap_err();
1857 fnet_matchers_ext::InterfaceError::UnknownPortClass(
1858 fnet_interfaces_ext::UnknownPortClassError::HardwareNetwork(error))
1859 } =>
1860 FidlConversionError::UnknownUnionVariant(type_names::HARDWARE_NETWORK_PORT_CLASS)
1861 )]
1862 #[test_case(
1863 fnet_matchers_ext::SubnetError::PrefixTooLong =>
1864 FidlConversionError::SubnetPrefixTooLong
1865 )]
1866 #[test_case(
1867 fnet_matchers_ext::SubnetError::HostBitsSet =>
1868 FidlConversionError::SubnetHostBitsSet
1869 )]
1870 #[test_case(
1871 fnet_matchers_ext::AddressRangeError::Invalid =>
1872 FidlConversionError::InvalidAddressRange
1873 )]
1874 #[test_case(
1875 fnet_matchers_ext::AddressRangeError::FamilyMismatch =>
1876 FidlConversionError::AddressRangeFamilyMismatch
1877 )]
1878 #[test_case(
1879 fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1880 fnet_matchers_ext::SubnetError::PrefixTooLong) =>
1881 FidlConversionError::SubnetPrefixTooLong
1882 )]
1883 #[test_case(
1884 fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1885 fnet_matchers_ext::SubnetError::HostBitsSet) =>
1886 FidlConversionError::SubnetHostBitsSet
1887 )]
1888 #[test_case(
1889 fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1890 fnet_matchers_ext::AddressRangeError::Invalid) =>
1891 FidlConversionError::InvalidAddressRange
1892 )]
1893 #[test_case(
1894 fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1895 fnet_matchers_ext::AddressRangeError::FamilyMismatch) =>
1896 FidlConversionError::AddressRangeFamilyMismatch
1897 )]
1898 #[test_case(
1899 fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant =>
1900 FidlConversionError::UnknownUnionVariant(type_names::ADDRESS_MATCHER_TYPE)
1901 )]
1902 #[test_case(
1903 fnet_matchers_ext::AddressError::AddressMatcherType(
1904 fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1905 fnet_matchers_ext::SubnetError::PrefixTooLong)) =>
1906 FidlConversionError::SubnetPrefixTooLong
1907 )]
1908 #[test_case(
1909 fnet_matchers_ext::AddressError::AddressMatcherType(
1910 fnet_matchers_ext::AddressMatcherTypeError::Subnet(
1911 fnet_matchers_ext::SubnetError::HostBitsSet)) =>
1912 FidlConversionError::SubnetHostBitsSet
1913 )]
1914 #[test_case(
1915 fnet_matchers_ext::AddressError::AddressMatcherType(
1916 fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1917 fnet_matchers_ext::AddressRangeError::Invalid)) =>
1918 FidlConversionError::InvalidAddressRange
1919 )]
1920 #[test_case(
1921 fnet_matchers_ext::AddressError::AddressMatcherType(
1922 fnet_matchers_ext::AddressMatcherTypeError::AddressRange(
1923 fnet_matchers_ext::AddressRangeError::FamilyMismatch)) =>
1924 FidlConversionError::AddressRangeFamilyMismatch
1925 )]
1926 #[test_case(
1927 fnet_matchers_ext::AddressError::AddressMatcherType(
1928 fnet_matchers_ext::AddressMatcherTypeError::UnknownUnionVariant) =>
1929 FidlConversionError::UnknownUnionVariant(type_names::ADDRESS_MATCHER_TYPE)
1930 )]
1931 #[test_case(
1932 fnet_matchers_ext::TransportProtocolError::Port(
1933 fnet_matchers_ext::PortError::InvalidPortRange) =>
1934 FidlConversionError::InvalidPortMatcherRange
1935 )]
1936 #[test_case(
1937 fnet_matchers_ext::TransportProtocolError::UnknownUnionVariant =>
1938 FidlConversionError::UnknownUnionVariant(type_names::TRANSPORT_PROTOCOL)
1939 )]
1940 fn fidl_error_from_matcher_error<E: Into<FidlConversionError>>(
1941 error: E,
1942 ) -> FidlConversionError {
1943 error.into()
1944 }
1945
1946 #[test]
1947 fn action_try_from_unknown_variant() {
1948 assert_eq!(
1949 Action::try_from(fnet_filter::Action::__SourceBreaking { unknown_ordinal: 0 }),
1950 Err(FidlConversionError::UnknownUnionVariant(type_names::ACTION))
1951 );
1952 }
1953
1954 #[test]
1955 fn resource_try_from_unknown_variant() {
1956 assert_eq!(
1957 Resource::try_from(fnet_filter::Resource::__SourceBreaking { unknown_ordinal: 0 }),
1958 Err(FidlConversionError::UnknownUnionVariant(type_names::RESOURCE))
1959 );
1960 }
1961
1962 #[test]
1963 fn event_try_from_unknown_variant() {
1964 assert_eq!(
1965 Event::try_from(fnet_filter::Event::__SourceBreaking { unknown_ordinal: 0 }),
1966 Err(FidlConversionError::UnknownUnionVariant(type_names::EVENT))
1967 );
1968 }
1969
1970 #[test]
1971 fn change_try_from_unknown_variant() {
1972 assert_eq!(
1973 Change::try_from(fnet_filter::Change::__SourceBreaking { unknown_ordinal: 0 }),
1974 Err(FidlConversionError::UnknownUnionVariant(type_names::CHANGE))
1975 );
1976 }
1977
1978 fn test_controller_a() -> ControllerId {
1979 ControllerId(String::from("test-controller-a"))
1980 }
1981
1982 fn test_controller_b() -> ControllerId {
1983 ControllerId(String::from("test-controller-b"))
1984 }
1985
1986 pub(crate) fn test_resource_id() -> ResourceId {
1987 ResourceId::Namespace(NamespaceId(String::from("test-namespace")))
1988 }
1989
1990 pub(crate) fn test_resource() -> Resource {
1991 Resource::Namespace(Namespace {
1992 id: NamespaceId(String::from("test-namespace")),
1993 domain: Domain::AllIp,
1994 })
1995 }
1996
1997 pub(crate) fn pretend_invalid_resource() -> Resource {
2000 Resource::Namespace(Namespace {
2001 id: NamespaceId(String::from("pretend-invalid-namespace")),
2002 domain: Domain::AllIp,
2003 })
2004 }
2005
2006 pub(crate) fn unknown_resource_id() -> ResourceId {
2007 ResourceId::Namespace(NamespaceId(String::from("does-not-exist")))
2008 }
2009
2010 #[fuchsia_async::run_singlethreaded(test)]
2011 async fn event_stream_from_state_conversion_error() {
2012 let (proxy, mut request_stream) =
2013 fidl::endpoints::create_proxy_and_stream::<fnet_filter::StateMarker>();
2014 let stream = event_stream_from_state(proxy).expect("get event stream");
2015 futures::pin_mut!(stream);
2016
2017 let send_invalid_event = async {
2018 let fnet_filter::StateRequest::GetWatcher { options: _, request, control_handle: _ } =
2019 request_stream
2020 .next()
2021 .await
2022 .expect("client should call state")
2023 .expect("request should not error");
2024 let fnet_filter::WatcherRequest::Watch { responder } = request
2025 .into_stream()
2026 .next()
2027 .await
2028 .expect("client should call watch")
2029 .expect("request should not error");
2030 responder
2031 .send(&[fnet_filter::Event::Added(fnet_filter::AddedResource {
2032 controller: String::from("controller"),
2033 resource: fnet_filter::Resource::Namespace(fnet_filter::Namespace {
2034 id: None,
2035 domain: None,
2036 ..Default::default()
2037 }),
2038 })])
2039 .expect("send batch with invalid event");
2040 };
2041 let ((), result) = futures::future::join(send_invalid_event, stream.next()).await;
2042 assert_matches!(
2043 result,
2044 Some(Err(WatchError::Conversion(FidlConversionError::MissingNamespaceId)))
2045 );
2046 }
2047
2048 #[fuchsia_async::run_singlethreaded(test)]
2049 async fn event_stream_from_state_empty_event_batch() {
2050 let (proxy, mut request_stream) =
2051 fidl::endpoints::create_proxy_and_stream::<fnet_filter::StateMarker>();
2052 let stream = event_stream_from_state(proxy).expect("get event stream");
2053 futures::pin_mut!(stream);
2054
2055 let send_empty_batch = async {
2056 let fnet_filter::StateRequest::GetWatcher { options: _, request, control_handle: _ } =
2057 request_stream
2058 .next()
2059 .await
2060 .expect("client should call state")
2061 .expect("request should not error");
2062 let fnet_filter::WatcherRequest::Watch { responder } = request
2063 .into_stream()
2064 .next()
2065 .await
2066 .expect("client should call watch")
2067 .expect("request should not error");
2068 responder.send(&[]).expect("send empty batch");
2069 };
2070 let ((), result) = futures::future::join(send_empty_batch, stream.next()).await;
2071 assert_matches!(result, Some(Err(WatchError::EmptyEventBatch)));
2072 }
2073
2074 #[fuchsia_async::run_singlethreaded(test)]
2075 async fn get_existing_resources_success() {
2076 let event_stream = futures::stream::iter([
2077 Ok(Event::Existing(test_controller_a(), test_resource())),
2078 Ok(Event::Existing(test_controller_b(), test_resource())),
2079 Ok(Event::Idle),
2080 Ok(Event::Removed(test_controller_a(), test_resource_id())),
2081 ]);
2082 futures::pin_mut!(event_stream);
2083
2084 let existing = get_existing_resources::<HashMap<_, _>>(event_stream.by_ref())
2085 .await
2086 .expect("get existing resources");
2087 assert_eq!(
2088 existing,
2089 HashMap::from([
2090 (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2091 (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2092 ])
2093 );
2094
2095 let trailing_events = event_stream.collect::<Vec<_>>().await;
2096 assert_matches!(
2097 &trailing_events[..],
2098 [Ok(Event::Removed(controller, resource))] if controller == &test_controller_a() &&
2099 resource == &test_resource_id()
2100 );
2101 }
2102
2103 #[fuchsia_async::run_singlethreaded(test)]
2104 async fn get_existing_resources_error_in_stream() {
2105 let event_stream =
2106 futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
2107 futures::pin_mut!(event_stream);
2108 assert_matches!(
2109 get_existing_resources::<HashMap<_, _>>(event_stream).await,
2110 Err(GetExistingResourcesError::ErrorInStream(WatchError::EmptyEventBatch))
2111 )
2112 }
2113
2114 #[fuchsia_async::run_singlethreaded(test)]
2115 async fn get_existing_resources_unexpected_event() {
2116 let event_stream = futures::stream::once(futures::future::ready(Ok(Event::EndOfUpdate)));
2117 futures::pin_mut!(event_stream);
2118 assert_matches!(
2119 get_existing_resources::<HashMap<_, _>>(event_stream).await,
2120 Err(GetExistingResourcesError::UnexpectedEvent(Event::EndOfUpdate))
2121 )
2122 }
2123
2124 #[fuchsia_async::run_singlethreaded(test)]
2125 async fn get_existing_resources_duplicate_resource() {
2126 let event_stream = futures::stream::iter([
2127 Ok(Event::Existing(test_controller_a(), test_resource())),
2128 Ok(Event::Existing(test_controller_a(), test_resource())),
2129 ]);
2130 futures::pin_mut!(event_stream);
2131 assert_matches!(
2132 get_existing_resources::<HashMap<_, _>>(event_stream).await,
2133 Err(GetExistingResourcesError::DuplicateResource(resource))
2134 if resource == test_resource()
2135 )
2136 }
2137
2138 #[fuchsia_async::run_singlethreaded(test)]
2139 async fn get_existing_resources_stream_ended() {
2140 let event_stream = futures::stream::once(futures::future::ready(Ok(Event::Existing(
2141 test_controller_a(),
2142 test_resource(),
2143 ))));
2144 futures::pin_mut!(event_stream);
2145 assert_matches!(
2146 get_existing_resources::<HashMap<_, _>>(event_stream).await,
2147 Err(GetExistingResourcesError::StreamEnded)
2148 )
2149 }
2150
2151 #[fuchsia_async::run_singlethreaded(test)]
2152 async fn wait_for_condition_add_remove() {
2153 let mut state = HashMap::new();
2154
2155 let has_resource = |resources: &HashMap<_, HashMap<_, _>>| {
2158 resources.get(&test_controller_a()).map_or(false, |controller| {
2159 controller
2160 .get(&test_resource_id())
2161 .map_or(false, |resource| resource == &test_resource())
2162 })
2163 };
2164 assert_matches!(
2165 wait_for_condition(futures::stream::pending(), &mut state, has_resource).now_or_never(),
2166 None
2167 );
2168 assert!(state.is_empty());
2169 assert_matches!(
2170 wait_for_condition(
2171 futures::stream::iter([
2172 Ok(Event::Added(test_controller_b(), test_resource())),
2173 Ok(Event::EndOfUpdate),
2174 Ok(Event::Added(test_controller_a(), test_resource())),
2175 Ok(Event::EndOfUpdate),
2176 ]),
2177 &mut state,
2178 has_resource
2179 )
2180 .now_or_never(),
2181 Some(Ok(()))
2182 );
2183 assert_eq!(
2184 state,
2185 HashMap::from([
2186 (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2187 (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2188 ])
2189 );
2190
2191 assert_matches!(
2193 wait_for_condition(
2194 futures::stream::iter([
2195 Ok(Event::Added(test_controller_a(), test_resource())),
2196 Ok(Event::EndOfUpdate),
2197 ]),
2198 &mut state,
2199 has_resource
2200 )
2201 .now_or_never(),
2202 Some(Err(WaitForConditionError::AddedAlreadyExisting(r))) if r == test_resource()
2203 );
2204 assert_eq!(
2205 state,
2206 HashMap::from([
2207 (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2208 (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2209 ])
2210 );
2211
2212 let does_not_have_resource = |resources: &HashMap<_, HashMap<_, _>>| {
2215 resources.get(&test_controller_a()).map_or(false, |controller| controller.is_empty())
2216 };
2217 assert_matches!(
2218 wait_for_condition(futures::stream::pending(), &mut state, does_not_have_resource)
2219 .now_or_never(),
2220 None
2221 );
2222 assert_eq!(
2223 state,
2224 HashMap::from([
2225 (test_controller_a(), HashMap::from([(test_resource_id(), test_resource())])),
2226 (test_controller_b(), HashMap::from([(test_resource_id(), test_resource())])),
2227 ])
2228 );
2229 assert_matches!(
2230 wait_for_condition(
2231 futures::stream::iter([
2232 Ok(Event::Removed(test_controller_b(), test_resource_id())),
2233 Ok(Event::EndOfUpdate),
2234 Ok(Event::Removed(test_controller_a(), test_resource_id())),
2235 Ok(Event::EndOfUpdate),
2236 ]),
2237 &mut state,
2238 does_not_have_resource
2239 )
2240 .now_or_never(),
2241 Some(Ok(()))
2242 );
2243 assert_eq!(
2244 state,
2245 HashMap::from([
2246 (test_controller_a(), HashMap::new()),
2247 (test_controller_b(), HashMap::new()),
2248 ])
2249 );
2250
2251 assert_matches!(
2253 wait_for_condition(
2254 futures::stream::iter([
2255 Ok(Event::Removed(test_controller_a(), test_resource_id())),
2256 Ok(Event::EndOfUpdate),
2257 ]),
2258 &mut state,
2259 does_not_have_resource
2260 ).now_or_never(),
2261 Some(Err(WaitForConditionError::RemovedNonExistent(r))) if r == test_resource_id()
2262 );
2263 assert_eq!(
2264 state,
2265 HashMap::from([
2266 (test_controller_a(), HashMap::new()),
2267 (test_controller_b(), HashMap::new()),
2268 ])
2269 );
2270 }
2271
2272 #[test]
2273 fn predicate_not_tested_until_update_complete() {
2274 let mut state = HashMap::new();
2275 let (mut tx, rx) = mpsc::unbounded();
2276
2277 let wait = wait_for_condition(rx, &mut state, |state| !state.is_empty()).fuse();
2278 futures::pin_mut!(wait);
2279
2280 let mut exec = fuchsia_async::TestExecutor::new();
2284 exec.run_singlethreaded(async {
2285 tx.send(Ok(Event::Added(test_controller_a(), test_resource())))
2286 .await
2287 .expect("receiver should not be closed");
2288 });
2289 assert_matches!(exec.run_until_stalled(&mut wait), Poll::Pending);
2290
2291 exec.run_singlethreaded(async {
2292 tx.send(Ok(Event::EndOfUpdate)).await.expect("receiver should not be closed");
2293 wait.await.expect("condition should be satisfied once update is complete");
2294 });
2295 }
2296
2297 #[fuchsia_async::run_singlethreaded(test)]
2298 async fn wait_for_condition_error_in_stream() {
2299 let mut state = HashMap::new();
2300 let event_stream =
2301 futures::stream::once(futures::future::ready(Err(WatchError::EmptyEventBatch)));
2302 assert_matches!(
2303 wait_for_condition(event_stream, &mut state, |_| true).await,
2304 Err(WaitForConditionError::ErrorInStream(WatchError::EmptyEventBatch))
2305 );
2306 assert!(state.is_empty());
2307 }
2308
2309 #[fuchsia_async::run_singlethreaded(test)]
2310 async fn wait_for_condition_stream_ended() {
2311 let mut state = HashMap::new();
2312 let event_stream = futures::stream::empty();
2313 assert_matches!(
2314 wait_for_condition(event_stream, &mut state, |_| true).await,
2315 Err(WaitForConditionError::StreamEnded)
2316 );
2317 assert!(state.is_empty());
2318 }
2319
2320 pub(crate) async fn handle_open_controller(
2321 mut request_stream: fnet_filter::ControlRequestStream,
2322 ) -> fnet_filter::NamespaceControllerRequestStream {
2323 let (id, request, _control_handle) = request_stream
2324 .next()
2325 .await
2326 .expect("client should open controller")
2327 .expect("request should not error")
2328 .into_open_controller()
2329 .expect("client should open controller");
2330 let (stream, control_handle) = request.into_stream_and_control_handle();
2331 control_handle.send_on_id_assigned(&id).expect("send assigned ID");
2332
2333 stream
2334 }
2335
2336 pub(crate) async fn handle_push_changes(
2337 stream: &mut fnet_filter::NamespaceControllerRequestStream,
2338 push_changes_result: fnet_filter::ChangeValidationResult,
2339 ) {
2340 let (_changes, responder) = stream
2341 .next()
2342 .await
2343 .expect("client should push changes")
2344 .expect("request should not error")
2345 .into_push_changes()
2346 .expect("client should push changes");
2347 responder.send(push_changes_result).expect("send empty batch");
2348 }
2349
2350 pub(crate) async fn handle_commit(
2351 stream: &mut fnet_filter::NamespaceControllerRequestStream,
2352 commit_result: fnet_filter::CommitResult,
2353 ) {
2354 let (_options, responder) = stream
2355 .next()
2356 .await
2357 .expect("client should commit")
2358 .expect("request should not error")
2359 .into_commit()
2360 .expect("client should commit");
2361 responder.send(commit_result).expect("send commit result");
2362 }
2363
2364 #[fuchsia_async::run_singlethreaded(test)]
2365 async fn controller_push_changes_reports_invalid_change() {
2366 let (control, request_stream) =
2367 fidl::endpoints::create_proxy_and_stream::<fnet_filter::ControlMarker>();
2368 let push_invalid_change = async {
2369 let mut controller = Controller::new(&control, &ControllerId(String::from("test")))
2370 .await
2371 .expect("create controller");
2372 let result = controller
2373 .push_changes(vec![
2374 Change::Create(test_resource()),
2375 Change::Create(pretend_invalid_resource()),
2378 Change::Remove(test_resource_id()),
2379 ])
2380 .await;
2381 assert_matches!(
2382 result,
2383 Err(PushChangesError::ErrorOnChange(errors)) if errors == vec![(
2384 Change::Create(pretend_invalid_resource()),
2385 ChangeValidationError::InvalidPortMatcher
2386 )]
2387 );
2388 };
2389
2390 let handle_controller = async {
2391 let mut stream = handle_open_controller(request_stream).await;
2392 handle_push_changes(
2393 &mut stream,
2394 fnet_filter::ChangeValidationResult::ErrorOnChange(vec![
2395 fnet_filter::ChangeValidationError::Ok,
2396 fnet_filter::ChangeValidationError::InvalidPortMatcher,
2397 fnet_filter::ChangeValidationError::NotReached,
2398 ]),
2399 )
2400 .await;
2401 };
2402
2403 let ((), ()) = futures::future::join(push_invalid_change, handle_controller).await;
2404 }
2405
2406 #[fuchsia_async::run_singlethreaded(test)]
2407 async fn controller_commit_reports_invalid_change() {
2408 let (control, request_stream) =
2409 fidl::endpoints::create_proxy_and_stream::<fnet_filter::ControlMarker>();
2410 let commit_invalid_change = async {
2411 let mut controller = Controller::new(&control, &ControllerId(String::from("test")))
2412 .await
2413 .expect("create controller");
2414 controller
2415 .push_changes(vec![
2416 Change::Create(test_resource()),
2417 Change::Remove(unknown_resource_id()),
2418 Change::Remove(test_resource_id()),
2419 ])
2420 .await
2421 .expect("push changes");
2422 let result = controller.commit().await;
2423 assert_matches!(
2424 result,
2425 Err(CommitError::ErrorOnChange(errors)) if errors == vec![(
2426 Change::Remove(unknown_resource_id()),
2427 ChangeCommitError::NamespaceNotFound,
2428 )]
2429 );
2430 };
2431 let handle_controller = async {
2432 let mut stream = handle_open_controller(request_stream).await;
2433 handle_push_changes(
2434 &mut stream,
2435 fnet_filter::ChangeValidationResult::Ok(fnet_filter::Empty {}),
2436 )
2437 .await;
2438 handle_commit(
2439 &mut stream,
2440 fnet_filter::CommitResult::ErrorOnChange(vec![
2441 fnet_filter::CommitError::Ok,
2442 fnet_filter::CommitError::NamespaceNotFound,
2443 fnet_filter::CommitError::Ok,
2444 ]),
2445 )
2446 .await;
2447 };
2448 let ((), ()) = futures::future::join(commit_invalid_change, handle_controller).await;
2449 }
2450}