1use crate::{errors::*, types::*, util::FutureExt, FirewallBackend};
13use bollard::{
14 container::ListContainersOptions,
15 models::{ContainerSummary, Network, NetworkContainer},
16 Docker,
17};
18use failure::{bail, format_err};
19use maplit::hashmap;
20use slog::{debug, o, trace, Logger};
21use std::collections::HashMap as Map;
22
23pub trait Process<B: FirewallBackend>
63where
64 DFW<B>: Process<B>,
65{
66 fn process(&self, ctx: &ProcessContext<B>) -> Result<Option<Vec<B::Rule>>>;
71}
72
73impl<B, T> Process<B> for Option<T>
74where
75 B: FirewallBackend,
76 DFW<B>: Process<B>,
77 T: Process<B>,
78{
79 fn process(&self, ctx: &ProcessContext<B>) -> Result<Option<Vec<B::Rule>>> {
80 match self {
81 Some(t) => t.process(ctx),
82 None => Ok(None),
83 }
84 }
85}
86
87impl<B, T> Process<B> for Vec<T>
88where
89 B: FirewallBackend,
90 DFW<B>: Process<B>,
91 T: Process<B>,
92{
93 fn process(&self, ctx: &ProcessContext<B>) -> Result<Option<Vec<B::Rule>>> {
94 let mut rules = Vec::new();
95 for rule in self {
96 if let Some(mut sub_rules) = rule.process(ctx)? {
97 rules.append(&mut sub_rules);
98 }
99 }
100
101 Ok(Some(rules))
102 }
103}
104
105pub struct ProcessContext<'a, B>
107where
108 B: FirewallBackend,
109 DFW<B>: Process<B>,
110{
111 pub(crate) docker: &'a Docker,
112 pub(crate) dfw: &'a DFW<B>,
113 pub(crate) container_map: Map<String, ContainerSummary>,
114 pub(crate) network_map: Map<String, Network>,
115 pub(crate) external_network_interfaces: Option<Vec<String>>,
116 pub(crate) primary_external_network_interface: Option<String>,
117 pub(crate) logger: Logger,
118 pub(crate) dry_run: bool,
119}
120
121impl<'a, B> ProcessContext<'a, B>
122where
123 B: FirewallBackend,
124 DFW<B>: Process<B>,
125{
126 pub fn new(
128 docker: &'a Docker,
129 dfw: &'a DFW<B>,
130 processing_options: &'a ProcessingOptions,
131 logger: &'a Logger,
132 dry_run: bool,
133 ) -> Result<ProcessContext<'a, B>> {
134 let logger = logger.new(o!());
135
136 let list_containers_options = match processing_options.container_filter {
137 ContainerFilter::All => None,
138 ContainerFilter::Running => Some(ListContainersOptions {
139 filters: hashmap! { "status" => vec!["running"]},
140 ..Default::default()
141 }),
142 };
143 let containers = docker.list_containers(list_containers_options).sync()?;
144 debug!(logger, "Got list of containers";
145 o!("containers" => format!("{:#?}", containers)));
146
147 let container_map = get_container_map(&containers);
148 trace!(logger, "Got map of containers";
149 o!("container_map" => format!("{:#?}", container_map)));
150
151 let networks = docker.list_networks::<String>(None).sync()?;
152 debug!(logger, "Got list of networks";
153 o!("networks" => format!("{:#?}", networks)));
154
155 let network_map =
156 get_network_map(&networks).ok_or_else(|| format_err!("no networks found"))?;
157 trace!(logger, "Got map of networks";
158 o!("container_map" => format!("{:#?}", container_map)));
159
160 let external_network_interfaces = dfw
161 .global_defaults
162 .external_network_interfaces
163 .as_ref()
164 .cloned();
165 let primary_external_network_interface = external_network_interfaces
166 .as_ref()
167 .and_then(|v| v.first())
168 .map(|s| s.to_owned());
169
170 Ok(ProcessContext {
171 docker,
172 dfw,
173 container_map,
174 network_map,
175 external_network_interfaces,
176 primary_external_network_interface,
177 logger,
178 dry_run,
179 })
180 }
181
182 pub fn process(&mut self) -> Result<()> {
184 let rules = Process::<B>::process(self.dfw, self)?;
185 if let Some(rules) = rules {
186 B::apply(rules, self)?;
187 }
188
189 Ok(())
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
195pub enum ContainerFilter {
196 All,
198 Running,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq)]
204pub struct ProcessingOptions {
205 pub container_filter: ContainerFilter,
208}
209
210impl Default for ProcessingOptions {
211 fn default() -> Self {
212 ProcessingOptions {
213 container_filter: ContainerFilter::All,
214 }
215 }
216}
217
218pub(crate) fn get_bridge_name(network_id: &str) -> Result<String> {
219 if network_id.len() < 12 {
220 bail!("network has to be longer than 12 characters");
221 }
222 Ok(format!("br-{}", &network_id[..12]))
223}
224
225pub(crate) fn docker_bridge_name(network: &Network) -> Result<Option<String>> {
226 if network.driver.as_deref() != Some("bridge") {
227 return Ok(None);
228 }
229 if let Some(name) = network
230 .options
231 .as_ref()
232 .and_then(|options| options.get("com.docker.network.bridge.name"))
233 .filter(|name| !name.is_empty())
234 {
235 return Ok(Some(name.clone()));
236 }
237 network.id.as_deref().map(get_bridge_name).transpose()
238}
239
240pub(crate) struct ContainerDnatEndpoints {
241 pub source_bridges: Vec<String>,
242 pub source_ip: Option<std::net::Ipv4Addr>,
243 pub destination_ip: std::net::Ipv4Addr,
244}
245
246pub(crate) fn container_dnat_endpoints<B>(
247 rule: &ContainerDNATRule,
248 ctx: &ProcessContext<B>,
249) -> Result<Option<ContainerDnatEndpoints>>
250where
251 B: FirewallBackend,
252 DFW<B>: Process<B>,
253{
254 let (source_bridges, source_ip) = if let Some(network_name) = &rule.src_network {
255 let Some(network) = ctx.network_map.get(network_name) else {
256 return Ok(None);
257 };
258 let Some(bridge) = docker_bridge_name(network)? else {
259 return Ok(None);
260 };
261 let source_ip = if let Some(container) = &rule.src_container {
262 let Some(network_id) = network.id.as_deref() else {
263 return Ok(None);
264 };
265 let Some(ip) =
266 get_network_for_container(ctx.docker, &ctx.container_map, container, network_id)?
267 .as_ref()
268 .and_then(container_ipv4)
269 else {
270 return Ok(None);
271 };
272 Some(ip)
273 } else {
274 None
275 };
276 (vec![bridge], source_ip)
277 } else {
278 if rule.src_container.is_some() {
279 bail!("a container-DNAT rule with src_container requires src_network");
280 }
281 let mut bridges = Vec::new();
282 for network in ctx.network_map.values() {
283 if let Some(name) = docker_bridge_name(network)? {
284 bridges.push(name);
285 }
286 }
287 bridges.sort_unstable();
288 bridges.dedup();
289 (bridges, None)
290 };
291
292 let Some(network_id) = ctx
293 .network_map
294 .get(&rule.dst_network)
295 .and_then(|n| n.id.as_deref())
296 else {
297 return Ok(None);
298 };
299 let Some(destination_ip) = get_network_for_container(
300 ctx.docker,
301 &ctx.container_map,
302 &rule.dst_container,
303 network_id,
304 )?
305 .as_ref()
306 .and_then(container_ipv4) else {
307 return Ok(None);
308 };
309
310 Ok(Some(ContainerDnatEndpoints {
311 source_bridges,
312 source_ip,
313 destination_ip,
314 }))
315}
316
317pub(crate) fn get_network_for_container(
318 docker: &Docker,
319 container_map: &Map<String, ContainerSummary>,
320 container_name: &str,
321 network_id: &str,
322) -> Result<Option<NetworkContainer>> {
323 if let Some(container) = container_map.get(container_name) {
324 let network = match docker.inspect_network::<String>(network_id, None).sync() {
325 Ok(network) => network,
326 Err(bollard::errors::Error::DockerResponseServerError {
327 status_code: 404, ..
328 }) => return Ok(None),
329 Err(error) => return Err(error.into()),
330 };
331 Ok(network.containers.and_then(|containers| {
332 container
333 .id
334 .as_ref()
335 .and_then(|container_id| containers.get(container_id).cloned())
336 }))
337 } else {
338 Ok(None)
339 }
340}
341
342pub(crate) fn container_ipv4(endpoint: &NetworkContainer) -> Option<std::net::Ipv4Addr> {
343 endpoint
344 .ipv4_address
345 .as_deref()?
346 .split('/')
347 .next()?
348 .parse()
349 .ok()
350}
351
352pub(crate) fn get_container_map(containers: &[ContainerSummary]) -> Map<String, ContainerSummary> {
353 let mut container_map: Map<String, ContainerSummary> = Map::new();
354 for container in containers {
355 if let Some(names) = &container.names {
356 for name in names {
357 container_map.insert(
358 name.clone().trim_start_matches('/').to_owned(),
359 container.clone(),
360 );
361 }
362 }
363 }
364
365 container_map
366}
367
368pub(crate) fn get_network_map(networks: &[Network]) -> Option<Map<String, Network>> {
369 let mut network_map: Map<String, Network> = Map::new();
370 for network in networks {
371 if let Some(name) = &network.name {
372 network_map.insert(name.clone(), network.clone());
373 }
374 }
375
376 if network_map.is_empty() {
377 None
378 } else {
379 Some(network_map)
380 }
381}
382
383pub(crate) fn generate_marker(components: &[&str]) -> String {
384 format!("DFW-MARKER:{}", components.join(";"))
385}