From aa5b14f3817d746f56ebc4ce8b38cf72f4794414 Mon Sep 17 00:00:00 2001 From: Marvin Winkler Date: Mon, 28 Sep 2026 10:43:49 +0200 Subject: [PATCH] listen on IPv6 when the container has it - entrypoint: inet_protocols=all if the container has IPv6 (Docker network with enable_ipv6), else ipv4; INET_PROTOCOLS overrides - mynetworks default gains [::1]/128 when IPv6 is on - list-available-networks.sh: use connected routes from `ip route` instead of parsing ifconfig; emits IPv4 and IPv6 (Postfix [prefix]/len notation). The old parser turned inet6 lines into invalid mynetworks entries on IPv6-enabled networks. - README: INET_PROTOCOLS and an IPv6 section Co-Authored-By: Claude Opus 5.5 --- Dockerfile | 1 + README.md | 25 ++++++++++++++ scripts/entrypoint.sh | 18 ++++++++++ scripts/list-available-networks.sh | 54 +++++++++--------------------- 4 files changed, 60 insertions(+), 38 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0823d22..a81d648 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ RUN apt-get -q -y update \ && apt-get -q -y install --no-install-recommends runit \ telnet \ net-tools \ + iproute2 \ postfix \ libsasl2-modules \ rsyslog \ diff --git a/README.md b/README.md index b7a3fd5..ec807f8 100644 --- a/README.md +++ b/README.md @@ -108,8 +108,12 @@ __OFFICIAL ENVIRONMENT VARIABLES__ - POSTFIX_SMTPD_BANNER - alter the SMTPD Banner of postfix e.g. _mailserver.example.local ESMTP_ +- INET_PROTOCOLS + - which IP versions postfix listens on and uses: `all`, `ipv4` or `ipv6` + - _default: auto_ — `all` if the container has IPv6 (Docker network with `enable_ipv6`), otherwise `ipv4` - AUTO_TRUST_NETWORKS - add all networks this container is connected to and trust them to send mails + - includes IPv6 networks (as `[prefix]/len`) when the container has IPv6 - _set to any value to enable_ - ADDITIONAL_MYNETWORKS - add this specific network to the automatically trusted onces @@ -188,6 +192,27 @@ _some characters might brake your configuration!_ _for example: to set_ ___mynetworks_style = subnet___ _just add a environment variable_ ___POSTFIX_RAW_CONFIG_MYNETWORKS_STYLE=subnet___ + +## IPv6 + +Postfix listens on IPv6 whenever the container has it. With +plain Docker defaults a container is IPv4-only, and published ports reach it +over IPv6 through `docker-proxy`, which connects to the container over IPv4 — +so every IPv6 client shows up as the Docker network gateway (`172.x.0.1`). +To see real IPv6 client addresses, give the network IPv6 and let Docker NAT +IPv6 itself (`/etc/docker/daemon.json`: `"ip6tables": true`, plus +`"experimental": true` on Docker < 27): + +```yaml +networks: + default: + enable_ipv6: true + ipam: + config: + - subnet: 172.19.0.0/16 + - subnet: fd00:d0c:25::/64 +``` + ## Volumes - /etc/postfix/tls diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index db1d251..31a3226 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -25,7 +25,25 @@ EOF # cleanup/remove amavis pidfile rm -f /run/amavis/amavisd.pid 2> /dev/null > /dev/null +## +# POSTFIX IP PROTOCOLS +## + +# Listen on IPv6 too whenever the container has it (Docker network with +# enable_ipv6). IPv4-only containers stay on ipv4 — Postfix would otherwise +# warn about missing IPv6 support on every start. INET_PROTOCOLS overrides. +if [ -z ${INET_PROTOCOLS+x} ]; then + if grep -q . /proc/net/if_inet6 2>/dev/null; then + INET_PROTOCOLS=all + else + INET_PROTOCOLS=ipv4 + fi +fi +echo ">> postfix inet_protocols: $INET_PROTOCOLS" +postconf -e "inet_protocols=$INET_PROTOCOLS" + AVAILABLE_NETWORKS="127.0.0.0/8" +[ "$INET_PROTOCOLS" = "ipv4" ] || AVAILABLE_NETWORKS="$AVAILABLE_NETWORKS,[::1]/128" if [ ! -z ${AUTO_TRUST_NETWORKS+x} ]; then AVAILABLE_NETWORKS=$(list-available-networks.sh | tr '\n' ',' | sed 's/,$//g') echo ">> trust all available networks: $AVAILABLE_NETWORKS" diff --git a/scripts/list-available-networks.sh b/scripts/list-available-networks.sh index cc109c7..03448d7 100755 --- a/scripts/list-available-networks.sh +++ b/scripts/list-available-networks.sh @@ -1,40 +1,18 @@ -#!/bin/bash +#!/bin/sh +# Print every network this container is directly attached to, one per line, +# in Postfix mynetworks notation (IPv4 a.b.c.d/n, IPv6 [prefix]/n). +# Used by AUTO_TRUST_NETWORKS. +# +# Connected routes are exactly the attached networks, with the host bits +# already zeroed — no netmask arithmetic, and IPv6 works the same way. +# Link-local and multicast IPv6 prefixes are never trusted. -function getNetworkFromAddressAndNetmask { - IFS=. read -r i1 i2 i3 i4 <<< "$1" - IFS=. read -r m1 m2 m3 m4 <<< "$2" - printf "%d.%d.%d.%d\n" "$((i1 & m1))" "$((i2 & m2))" "$((i3 & m3))" "$((i4 & m4))" -} +echo "127.0.0.0/8" +ip -4 route show 2>/dev/null \ + | awk '$1 ~ /\// && $1 != "default" && !/ via / { print $1 }' -function getCidrSuffixFromNetmask { - nbits=0 - IFS=. - for dec in $1 ; do - case $dec in - 255) let nbits+=8;; - 254) let nbits+=7;; - 252) let nbits+=6;; - 248) let nbits+=5;; - 240) let nbits+=4;; - 224) let nbits+=3;; - 192) let nbits+=2;; - 128) let nbits+=1;; - 0);; - *) echo "Error: $dec is not recognised"; exit 1 - esac - done - echo "$nbits" -} - - -IFS=$'\n' # make newlines the only separator -for j in $(ifconfig | grep inet | tr ' ' '\n' | grep 'Mask\|add' | tr '\n' ' ' | sed 's/addr/\naddr/g' | grep . | sed 's/[^0-9. ]//g') -do - ADDR=$(echo "$j" | cut -d' ' -f1) - MASK=$(echo "$j" | cut -d' ' -f2) - - NETWORK=$(getNetworkFromAddressAndNetmask "$ADDR" "$MASK") - CIDR=$(getCidrSuffixFromNetmask "$MASK") - - echo "$NETWORK/$CIDR" -done +grep -q . /proc/net/if_inet6 2>/dev/null || exit 0 +echo "[::1]/128" +ip -6 route show 2>/dev/null \ + | awk '$1 ~ /\// && $1 != "default" && !/ via / && $1 !~ /^(fe80|ff[0-9a-f][0-9a-f]):/ { + split($1, p, "/"); print "[" p[1] "]/" p[2] }'