2630: Improved IPv6 support #2 r=mergify[bot] a=ghostwheel42

## What type of PR?

enhancement, bug-fix, documentation

## What does this PR do?

This is based on #2272 and adds some more fixes.

### Related issue(s)
- closes #1789
- closes #2392


Co-authored-by: Chris <chris@niduroki.net>
Co-authored-by: Chris Schäpers <chris@niduroki.net>
Co-authored-by: Alexander Graf <ghostwheel42@users.noreply.github.com>
main
bors[bot] 2 years ago committed by GitHub
commit 9bd76536a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -52,15 +52,21 @@ def test_DNS():
test_DNS() test_DNS()
start_command=" ".join([ cmdline = [
"gunicorn", "gunicorn",
f"--threads {str(os.cpu_count())}", "--threads", f"{os.cpu_count()}",
"-b :80", # If SUBNET6 is defined, gunicorn must listen on IPv6 as well as IPv4
"-b", f"{'[::]' if os.environ.get('SUBNET6') else ''}:80",
"--logger-class mailu.Logger", "--logger-class mailu.Logger",
"--worker-tmp-dir /dev/shm", "--worker-tmp-dir /dev/shm",
"--access-logfile -" if (log.root.level<=log.INFO) else "", "--error-logfile", "-",
"--error-logfile -", "--preload"
"--preload", ]
"'mailu:create_app()'"])
os.system(start_command) # logging
if log.root.level <= log.INFO:
cmdline.extend(["--access-logfile", "-"])
cmdline.append("'mailu:create_app()'")
os.system(" ".join(cmdline))

@ -59,7 +59,9 @@ http {
server { server {
# Listen over HTTP # Listen over HTTP
listen 80; listen 80;
{% if SUBNET6 %}
listen [::]:80; listen [::]:80;
{% endif %}
{% if TLS_FLAVOR == 'letsencrypt' %} {% if TLS_FLAVOR == 'letsencrypt' %}
location ^~ /.well-known/acme-challenge/ { location ^~ /.well-known/acme-challenge/ {
proxy_pass http://127.0.0.1:8008; proxy_pass http://127.0.0.1:8008;
@ -91,13 +93,17 @@ http {
# Listen on HTTP only in kubernetes or behind reverse proxy # Listen on HTTP only in kubernetes or behind reverse proxy
{% if KUBERNETES_INGRESS == 'true' or TLS_FLAVOR in [ 'mail-letsencrypt', 'notls', 'mail' ] %} {% if KUBERNETES_INGRESS == 'true' or TLS_FLAVOR in [ 'mail-letsencrypt', 'notls', 'mail' ] %}
listen 80; listen 80;
{% if SUBNET6 %}
listen [::]:80; listen [::]:80;
{% endif %}
{% endif %} {% endif %}
# Only enable HTTPS if TLS is enabled with no error and not on kubernetes # Only enable HTTPS if TLS is enabled with no error and not on kubernetes
{% if KUBERNETES_INGRESS != 'true' and TLS and not TLS_ERROR %} {% if KUBERNETES_INGRESS != 'true' and TLS and not TLS_ERROR %}
listen 443 ssl http2; listen 443 ssl http2;
{% if SUBNET6 %}
listen [::]:443 ssl http2; listen [::]:443 ssl http2;
{% endif %}
include /etc/nginx/tls.conf; include /etc/nginx/tls.conf;
ssl_stapling on; ssl_stapling on;
@ -341,7 +347,9 @@ mail {
# SMTP is always enabled, to avoid losing emails when TLS is failing # SMTP is always enabled, to avoid losing emails when TLS is failing
server { server {
listen 25; listen 25;
{% if SUBNET6 %}
listen [::]:25; listen [::]:25;
{% endif %}
{% if TLS and not TLS_ERROR %} {% if TLS and not TLS_ERROR %}
{% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %} {% if TLS_FLAVOR in ['letsencrypt','mail-letsencrypt'] %}
ssl_certificate /certs/letsencrypt/live/mailu/fullchain.pem; ssl_certificate /certs/letsencrypt/live/mailu/fullchain.pem;
@ -363,7 +371,9 @@ mail {
{% if not TLS_ERROR %} {% if not TLS_ERROR %}
server { server {
listen 143; listen 143;
{% if SUBNET6 %}
listen [::]:143; listen [::]:143;
{% endif %}
{% if TLS %} {% if TLS %}
starttls only; starttls only;
{% endif %} {% endif %}
@ -376,7 +386,9 @@ mail {
server { server {
listen 110; listen 110;
{% if SUBNET6 %}
listen [::]:110; listen [::]:110;
{% endif %}
{% if TLS %} {% if TLS %}
starttls only; starttls only;
{% endif %} {% endif %}
@ -389,7 +401,9 @@ mail {
server { server {
listen 587; listen 587;
{% if SUBNET6 %}
listen [::]:587; listen [::]:587;
{% endif %}
{% if TLS %} {% if TLS %}
starttls only; starttls only;
{% endif %} {% endif %}
@ -401,7 +415,9 @@ mail {
{% if TLS %} {% if TLS %}
server { server {
listen 465 ssl; listen 465 ssl;
{% if SUBNET6 %}
listen [::]:465 ssl; listen [::]:465 ssl;
{% endif %}
protocol smtp; protocol smtp;
smtp_auth plain login; smtp_auth plain login;
auth_http_header Auth-Port 465; auth_http_header Auth-Port 465;
@ -409,7 +425,9 @@ mail {
server { server {
listen 993 ssl; listen 993 ssl;
{% if SUBNET6 %}
listen [::]:993 ssl; listen [::]:993 ssl;
{% endif %}
protocol imap; protocol imap;
imap_auth plain; imap_auth plain;
auth_http_header Auth-Port 993; auth_http_header Auth-Port 993;
@ -419,7 +437,9 @@ mail {
server { server {
listen 995 ssl; listen 995 ssl;
{% if SUBNET6 %}
listen [::]:995 ssl; listen [::]:995 ssl;
{% endif %}
protocol pop3; protocol pop3;
pop3_auth plain; pop3_auth plain;
auth_http_header Auth-Port 995; auth_http_header Auth-Port 995;

@ -19,7 +19,7 @@ HEALTHCHECK --start-period=60s CMD echo PING|nc -q1 127.0.0.1 11343|grep "PONG"
USER nobody:nobody USER nobody:nobody
ENV \ ENV \
OLEFY_BINDADDRESS="0.0.0.0" \ OLEFY_BINDADDRESS="" \
OLEFY_BINDPORT="11343" \ OLEFY_BINDPORT="11343" \
OLEFY_OLEVBA_PATH="/app/venv/bin/olevba" \ OLEFY_OLEVBA_PATH="/app/venv/bin/olevba" \
OLEFY_PYTHON_PATH="/app/venv/bin/python3" \ OLEFY_PYTHON_PATH="/app/venv/bin/python3" \

@ -14,7 +14,7 @@ queue_directory = /queue
message_size_limit = {{ MESSAGE_SIZE_LIMIT }} message_size_limit = {{ MESSAGE_SIZE_LIMIT }}
# Relayed networks # Relayed networks
mynetworks = 127.0.0.1/32 [::1]/128 {{ SUBNET }} {% if SUBNET6 %}{{ "[{}]/{}".format(*SUBNET6.split("/")) }}{% endif %} {% if RELAYNETS %}{{ RELAYNETS.split(",") | join(" ") }}{% endif %} mynetworks = 127.0.0.1/32 {{ SUBNET }} {% if SUBNET6 %}[::1]/128 {{ "[{}]/{}".format(*SUBNET6.translate({91: None, 93: None}).split("/")) }}{% endif %} {% if RELAYNETS %}{{ RELAYNETS.split(",") | join(" ") }}{% endif %}
# Empty alias list to override the configuration variable and disable NIS # Empty alias list to override the configuration variable and disable NIS
alias_maps = alias_maps =
@ -121,7 +121,7 @@ smtpd_relay_restrictions =
unverified_recipient_reject_reason = Address lookup failure unverified_recipient_reject_reason = Address lookup failure
smtpd_authorized_xclient_hosts={{ SUBNET }}{% if SUBNET6 %},[{{ SUBNET6 }}]{% endif %} smtpd_authorized_xclient_hosts={{ SUBNET }}{% if SUBNET6 %},{{ "[{}]/{}".format(*SUBNET6.translate({91: None, 93: None}).split("/")) }}{% endif %}
############### ###############
# Milter # Milter

@ -145,51 +145,43 @@ Your mail service will be reachable for IMAP, POP3, SMTP and Webmail at the addr
How to make IPv6 work? How to make IPv6 work?
`````````````````````` ``````````````````````
Docker currently does not expose the IPv6 ports properly, as it does not interface with ``ip6tables``. Docker IPv6 interfacing with ``ip6tables``, which is required for proper IPv6 support, is currently considered experimental.
Lets start with quoting everything that's wrong:
Unfortunately, initially Docker was not created with IPv6 in mind. Although the supposed way to enable IPv6 would be to give each container a publicly routable address, docker's IPv6 support
It was added later and, while it has come a long way, is still not as usable as one would want. uses NAT to pass outside connections to the containers.
Much discussion is still going on as to how IPv6 should be used in a containerized world;
See the various GitHub issues linked below:
- Giving each container a publicly routable address means all ports (even unexposed / unpublished ports) are suddenly Currently we recommend to use `docker-ipv6nat` by `Robert Klarenbeek <https://github.com/robbertkl>` instead of docker's
reachable by everyone, if no additional filtering is done experimental support.
(`docker/docker#21614 <https://github.com/docker/docker/issues/21614>`_)
- By default, each container gets a random IPv6, making it impossible to do properly do DNS;
the alternative is to assign a specific IPv6 address to each container,
still an administrative hassle (`docker/docker#13481 <https://github.com/docker/docker/issues/13481>`_)
- Published ports won't work on IPv6, unless you have the userland proxy enabled
(which, for now, is enabled by default in Docker)
- The userland proxy, however, seems to be on its way out
(`docker/docker#14856 <https://github.com/docker/docker/issues/14856>`_) and has various issues, like:
- It can use a lot of RAM (`docker/docker#11185 <https://github.com/docker/docker/issues/11185>`_) Before enabling IPv6 you **MUST** disable the userland-proxy in your ``/etc/docker/daemon.json`` to not create an Open Relay!
- Source IP addresses are rewritten, making it completely unusable for many purposes, e.g. mail servers
(`docker/docker#17666 <https://github.com/docker/docker/issues/17666>`_),
(`docker/libnetwork#1099 <https://github.com/docker/libnetwork/issues/1099>`_).
-- `Robbert Klarenbeek <https://github.com/robbertkl>`_ (docker-ipv6nat author) .. code-block:: json
Okay, but I still want to use IPv6! Can I just use the installers IPv6 checkbox? **NO, YOU SHOULD NOT DO THAT!** Why you ask? {
Mailu has its own trusted IPv4 network, every container inside this network can use e.g. the SMTP container without further "userland-proxy": false
authentication. If you enabled IPv6 inside the setup assistant (and fixed the ports to also be exposed on IPv6) Docker will }
still rewrite any incoming IPv6 requests to an IPv4 address, *which is located inside the trusted network*. Therefore any
incoming connection to the SMTP container will bypass the authentication stage by the front container regardless of your
settings and causes an Open Relay. And you really don't want this!
So, how to make it work? Well, by using `docker-ipv6nat`_! This nifty container will set up ``ip6tables``, You can enable `docker-ipv6nat` like this:
just as Docker would do for IPv4. We know that NAT-ing is not advised in IPv6,
however exposing all containers to public network neither. The choice is ultimately yous.
Mailu `setup utility`_ generates a safe IPv6 ULA subnet by default. So when you run the following command, docker run -d --name ipv6nat --privileged --network host --restart unless-stopped -v /var/run/docker.sock:/var/run/docker.sock:ro -v /lib/modules:/lib/modules:ro robbertkl/ipv6nat
Mailu will start to function on IPv6:
.. code-block:: bash If you want to try docker's experimental IPv6 support, it can be enabled like this:
docker run -d --restart=always -v /var/run/docker.sock:/var/run/docker.sock:ro --privileged --net=host robbertkl/ipv6nat .. code-block:: json
{
"userland-proxy": false,
"ipv6": true,
"experimental": true,
"fixed-cidr-v6": "fd00:1234:abcd::/48",
"ip6tables": true
}
and enabling the IPv6 checkbox in the `setup utility`_.
This setup however is not officially supported, and might result in unforeseen issues.
With bad misconfiguration you might even cause your instance to become an Open Relay, you have been warned!
.. _`docker-ipv6nat`: https://github.com/robbertkl/docker-ipv6nat
.. _`setup utility`: https://setup.mailu.io .. _`setup utility`: https://setup.mailu.io
How does Mailu scale up? How does Mailu scale up?

@ -1,5 +1,5 @@
[server] [server]
hosts = 0.0.0.0:5232, [::]:5232 hosts = :5232
ssl = False ssl = False
[encoding] [encoding]

@ -33,7 +33,7 @@ services:
- "{{ bind4 }}:{{ port }}:{{ port }}" - "{{ bind4 }}:{{ port }}:{{ port }}"
{% endif %} {% endif %}
{% if ipv6_enabled and bind6 %} {% if ipv6_enabled and bind6 %}
- "{{ bind6 }}:{{ port }}:{{ port }}" - "[{{ bind6 }}]:{{ port }}:{{ port }}"
{% endif %} {% endif %}
{% endfor %} {% endfor %}
networks: networks:

@ -0,0 +1 @@
Improved IPv6 support

@ -1,6 +1,8 @@
server { server {
listen 80 default_server; listen 80 default_server;
{% if SUBNET6 %}
listen [::]:80 default_server; listen [::]:80 default_server;
{% endif %}
resolver {{ RESOLVER }} valid=30s; resolver {{ RESOLVER }} valid=30s;
{% if WEBMAIL == 'roundcube' %} {% if WEBMAIL == 'roundcube' %}

Loading…
Cancel
Save