1use crate::{errors::*, nftables::DFW_MARK, types::RuleVerdict};
11use derive_builder::Builder;
12use failure::bail;
13
14#[derive(Debug, Clone, Builder)]
15#[builder(derive(Debug), pattern = "mutable", build_fn(skip))]
16#[allow(dead_code)]
17pub(crate) struct Rule {
18 #[builder(setter(into))]
19 pub in_interface: String,
20 #[builder(setter(into))]
21 pub out_interface: String,
22 #[builder(setter(into))]
23 pub source_address: String,
24 #[builder(setter(into))]
25 pub destination_address: String,
26 #[builder(setter(into))]
27 pub source_address_v6: String,
28 #[builder(setter(into))]
29 pub destination_address_v6: String,
30 #[builder(setter(into))]
31 pub protocol: String,
32 #[builder(setter(into))]
33 pub source_port: String,
34 #[builder(setter(into))]
35 pub destination_port: String,
36 #[builder(setter(into))]
37 pub matches: String,
38 #[builder(setter(into))]
39 pub comment: String,
40 #[builder(setter(into))]
41 pub verdict: RuleVerdict,
42 #[builder(setter(into))]
43 pub dnat: String,
44}
45
46impl RuleBuilder {
47 pub(crate) fn build(&self) -> Result<String> {
48 let mut args: Vec<String> = Vec::new();
49
50 if self.source_port.is_some() || self.destination_port.is_some() {
52 args.push(self.protocol.clone().unwrap_or_else(|| "tcp".to_owned()));
53 if let Some(source_port) = &self.source_port {
54 args.push("sport".to_owned());
55 args.push(source_port.to_owned());
56 }
57 if let Some(destination_port) = &self.destination_port {
58 args.push("dport".to_owned());
59 args.push(destination_port.to_owned());
60 }
61 }
62
63 if let Some(source_address) = &self.source_address {
65 args.push("ip".to_owned());
66 args.push("saddr".to_owned());
67 args.push(source_address.to_owned());
68 }
69 if let Some(destination_address) = &self.destination_address {
70 args.push("ip".to_owned());
71 args.push("daddr".to_owned());
72 args.push(destination_address.to_owned());
73 }
74
75 if let Some(source_address) = &self.source_address_v6 {
77 args.push("ip6".to_owned());
78 args.push("saddr".to_owned());
79 args.push(source_address.to_owned());
80 }
81 if let Some(destination_address) = &self.destination_address_v6 {
82 args.push("ip6".to_owned());
83 args.push("daddr".to_owned());
84 args.push(destination_address.to_owned());
85 }
86
87 if self.in_interface.is_some() || self.out_interface.is_some() {
89 args.push("meta".to_owned());
90 if let Some(in_interface) = &self.in_interface {
91 args.push("iifname".to_owned());
92 args.push(in_interface.to_owned());
93 }
94 if let Some(out_interface) = &self.out_interface {
95 args.push("oifname".to_owned());
96 args.push(out_interface.to_owned());
97 }
98 }
99
100 if args.is_empty() {
102 bail!("one of `{source,destination}_{port,address{,_v6}}`, `{in,out}_interface` must be initialized");
103 }
104
105 args.push("meta".to_owned());
107 args.push("mark".to_owned());
108 args.push("set".to_owned());
109 args.push(DFW_MARK.to_owned());
110
111 if let Some(matches) = &self.matches {
112 args.push(matches.to_owned());
113 }
114
115 if let Some(verdict) = &self.verdict {
116 args.push(verdict.to_string());
117 } else if let Some(dnat) = &self.dnat {
118 args.push("dnat".to_owned());
119 args.push(dnat.to_owned());
120 }
121
122 if let Some(comment) = &self.comment {
123 args.push(format!(r#"comment "{}""#, comment));
124 }
125
126 Ok(args.join(" "))
127 }
128}
129
130#[cfg(test)]
131mod test {
132 use super::*;
133
134 #[test]
135 fn builder_should_fail() {
136 let rule = RuleBuilder::default();
137 assert!(rule.build().is_err());
138 }
139
140 #[test]
141 fn builder_should_succeed() {
142 let mut rule = RuleBuilder::default();
143 rule.source_port("1");
144 assert!(rule.build().is_ok());
145 }
146}