From 0dd8ce8550c60771c874829014c6001a442dd916 Mon Sep 17 00:00:00 2001 From: Pit Kleyersburg Date: Sat, 14 Sep 2019 16:43:37 +0200 Subject: [PATCH 1/2] Support container-port in expose-port strings --- src/types.rs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/types.rs b/src/types.rs index e81a5cab..5544ed78 100644 --- a/src/types.rs +++ b/src/types.rs @@ -438,6 +438,19 @@ pub struct ExposePort { } impl ExposePortBuilder { + fn client_and_host_port(&mut self, value: &str) -> Result<&mut Self, String> { + let split: Vec<&str> = value.split(':').collect(); + match split.len() { + 1 => self.host_port = Some(split[0].parse().map_err(|e| format!("{}", e))?), + 2 => { + self.host_port = Some(split[0].parse().map_err(|e| format!("{}", e))?); + self.container_port = Some(Some(split[1].parse().map_err(|e| format!("{}", e))?)); + } + _ => return Err(format!("port string has invalid format '{}'", value)), + } + Ok(self) + } + fn default_container_port(&self) -> Result, String> { Ok(None) } @@ -452,8 +465,9 @@ impl FromStr for ExposePort { /// Convert a formatted string into a [`ExposePort`](struct.ExposePort.html). /// - /// The string has to be in the format `/`, i.e. `80/tcp`. Specifying the - /// container-port is not (yet) possible. + /// The string has to be in the format `[:]/`, i.e. + /// `80:8080/tcp`. If you don't specify the container-port, it is assumed to be identical to the + /// host-port. /// /// # Example /// @@ -472,14 +486,22 @@ impl FromStr for ExposePort { /// assert_eq!(port.container_port, None); /// assert_eq!(port.family, "udp"); /// ``` + /// + /// ``` + /// # use dfw::types::ExposePort; + /// let port: ExposePort = "80:8080/tcp".parse().unwrap(); + /// assert_eq!(port.host_port, 80); + /// assert_eq!(port.container_port, Some(8080)); + /// assert_eq!(port.family, "tcp"); + /// ``` fn from_str(s: &str) -> Result { let split: Vec<&str> = s.split('/').collect(); Ok(match split.len() { 1 => ExposePortBuilder::default() - .host_port(split[0].parse().map_err(|e| format!("{}", e))?) + .client_and_host_port(split[0])? .build()?, 2 => ExposePortBuilder::default() - .host_port(split[0].parse().map_err(|e| format!("{}", e))?) + .client_and_host_port(split[0])? .family(split[1].to_owned()) .build()?, _ => return Err(format!("port string has invalid format '{}'", s)), From 234298bf439f91f4506e15cda8578256d8c49d64 Mon Sep 17 00:00:00 2001 From: Pit Kleyersburg Date: Sat, 14 Sep 2019 16:43:52 +0200 Subject: [PATCH 2/2] Document IPv6 support --- README.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b8f84e49..1239eb34 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ For further information, look at the README in the `iptables` branch, but in sho 1. [Preparing your host](#installation-preparingyourhost) 2. [Running DFW](#installation-runningdfw) 3. [Configuration](#configuration) + 1. [IPv6 support](#configuration-ipv6) 4. [Troubleshooting](#troubleshooting) ----- @@ -54,7 +55,7 @@ While DFW is running, Docker container events will be monitored and the rules re One of the key-features of DFW (and DFWFW before it) is to not require the running containers to publish their ports on the host (à la `docker container run --publish 80:8080`), but rather use the network-address translation (NAT) features of the host-firewall to forward packets directly to the port in the container. _(Note: this only applies if you use IPv4 on your host. If you want to have IPv6-support, you still need to publish the ports. -See [IPv6 setup](#ipv6-setup) for more information.)_ +See [IPv6 support](#configuration-ipv6) for more information.)_ See [DFWFW's README][dfwfw-readme] for more insight. Most of what you will read there will be applicable to DFW. @@ -224,6 +225,57 @@ One category which DFWFW covers that is not (yet) implemented in DFW is `contain **See the [examples][examples] and [configuration types][types.rs] for detailed descriptions and examples of every configuration section.** +### IPv6 support + +If you make a container publicly available, DFW will use "destination NATting" and "masquerading" to redirect incoming packets to the correct internal IP of the container, and then correctly redirect the reponses back to the original requester. +Every default installation of Docker does _not_ assign private IPv6 addresses to networks and containers, it only assigns private IPv4s. + +Generally there is also no need for private IPv6 addresses: Docker uses a proxy-binary when host-binding a container-port to perform the translation of traffic from the host to the container. +This host-binding is compatible with both IPv4 and IPv6, which means internally a single IPv4 is sufficient. + +As mentioned, DFW does work differently: since it uses NAT to manage traffic, it effectively would have to translate incoming packets from IPv6 to IPv4 and the responses from IPv4 to IPv6, something that nftables does not support. + +The consequence of this is that if you want your services to be reachable via IPv6, you have to ensure the following things: + +1. You _have to_ publish the ports of the containers you want to be able to reach on your host through the Docker-integrated run-option `--publish`. + + The host-port you select here is the one under which it will be reachable publicly later, i.e. if you want your webserver to be reachable from host-ports 80 and 443, you need to publish the container ports under 80 and 443. + +2. In your wider-world-to-container rule, the host-port part of your exposed port _must match_ the port you published the container ports under. + + As part of the wider-world-to-container rule DFW will create the firewall-rules necessary for the host-bound ports to be reachable via IPv6. + For this to work the ports need to match the ports you have selected when publishing the container-ports. + +#### Example: webserver reachable via IPv6 + +Let's assume you want to run a webserver as a Docker container and want ports 80 for HTTP and 443 for HTTPS on your host to forward to this container. +The container you use internally uses ports 8080 and 8443 for HTTP and HTTPS respectively. + +The following is how you have to configure the container: + +``` +$ docker run \ + --name "your_container" \ + --network "your_network" \ + --publish 80:8080 \ + --publish 443:8443 \ + ... +``` + +This is how you'd configure your rule: + +```toml +[[wider_world_to_container.rules]] +network = "your_network" +dst_container = "your_container" +expose_port = [ + "80:8080", + "443:8443", +] +``` + +The result of this is that your container will be reachable from the host-ports 80 and 443, from both IPv4 and IPv6. + ## Troubleshooting todo: describe managing of custom nftables table, re: priority