
docker-mailserver 的 IPv6 配置与排障完整指南从 Docker 网关 IP 问题到 ULA/GUA 网络落地【免费下载链接】docker-mailserverProduction-ready fullstack but simple mail server (SMTP, IMAP, LDAP, Antispam, Antivirus, etc.) running inside a container.项目地址: https://gitcode.com/gh_mirrors/do/docker-mailserver本篇技术指南围绕 docker-mailserverDMS官方高级配置文档展开系统讲解在容器化邮件服务器场景下 IPv6 接入的两种路线如何彻底禁止 IPv6 连接以及如何正确开启 Docker IPv6 支持ip6tables、用户自定义网络、ULA/GUA 子网规划并给出可落地的daemon.json与compose.yaml配置、远端 IP 验证方法以及 DMS 侧*_INET_PROTOCOLS环境变量的配合用法。读完本文你将能够在 IPv6 环境下保住真实的客户端源 IP避免 Fail2Ban 误封、SPF 校验失败、退信导致发件方信誉受损等一连串问题。阅读前提与适用范围本文面向使用 Docker推荐 Compose部署 docker-mailserver 的生产环境。DMS 官方文档明确提醒过去针对 IPv6 问题提交过大量 bug 报告因此在使用 IPv6 时必须确保整个部署链路正确。需要强调的是IPv6 支持并非 DMS 容器本身的问题而是 Docker 网络层的能力问题——DMS 官方在 调试文档的 Compatibility 小节 中明确指出IPv6 需要额外配置才能防止或正确支持 IPv6 连接例如保留客户端 IP截至 2023 年该支持仍被视为实验性官方建议至少使用 Docker Engine v232023Q1 发布自 Docker Engine v20.10.02020Q4引入初始 IPv6 支持以来已有多个网络缺陷被陆续修复。Docker 默认网络下的 IPv6 问题会出现什么问题如果宿主机支持 IPv6并且存在指向 DMS 的AAAADNS 记录那么当客户端发起 IPv6 连接时你可能会遇到两类异常原始客户端 IP 被替换为 Docker 网络的网关 IP例如172.17.0.1连接失败或挂起hang。第一条对邮件服务的影响最为致命邮件服务器几乎一切安全机制都依赖连接方 IP 的准确性。为什么网关 IP 会顶替客户端 IP连接从宿主网络进入容器发布的端口时会先被路由到 Docker 管理的容器内部网络典型为 bridge 网络。关键在于 Docker 守护进程的默认行为默认情况下Docker 只为容器分配 IPv4 地址因此只接受 IPv4 连接但当守护进程配置了userland-proxy: true默认开启时宿主机上会运行一个docker-proxy进程它同样接受 IPv6 连接并将其转发给只有 IPv4 地址的容器若将userland-proxy设为false则要求容器至少分配一个 IPv6 地址否则无法转发。问题的根源在于经docker-proxy代理后容器内看到的连接来源变成了它所在网络的 IPv4 或 IPv6 网关地址原始客户端 IPv6 地址在转发过程中丢失。Docker 默认从一组[默认子网池]中为网络分配地址常见的网关地址形如172.17.0.1。源 IP 丢失对 DMS 的连锁影响文档明确列出了丢失真实客户端 IP 会给 DMS 带来的四项负面影响用户无法登录多次登录失败被 Fail2Ban 视为来自同一个内部网关 IP从而触发封禁动作波及所有用户入站邮件被拒由于 SPF 校验失败IP 不匹配来自 IPv6 客户端的入站邮件被拒绝发件方信誉受损导致的投递失败因拒绝来自 IPv6 客户端的入站邮件而触发退信bounce进而拉低发件方信誉评分安全措施被绕过部分服务会配置为信任容器子网内的连接方其中包含网关 IP这可能绕过或放松安全措施例如暴露出开放中继open relay风险。解决方案有两条路要么让 Docker 网络为容器分配 IPv6 地址并配合额外配置保留真实 IP要么反其道而行禁止 IPv6 连接进入。方案一彻底禁止 IPv6 连接如果暂时不想处理 IPv6可以双管齐下不为 DMS 的 FQDN 配置AAAADNS 记录这样客户端无法解析出 IPv6 地址来建立连接IPv6 流量自然进不来设置userland-proxy: false在没有给容器分配 IPv6 地址的前提下宿主机将无法与 DMS 建立远端连接。一个值得注意的提示如果启用了UFW 或 firewalld等防火墙前端远端客户端应当会连接失败而不是被伪装成 Docker 网络网关 IP。但要记住这只影响远端客户端不影响源自同一宿主机内部的本地 IPv6 连接。另外调试文档还补充了一个历史教训在 Docker v23 之前修改userland-proxy设置并不能可靠地移除 NAT 规则升级到较新版本可以规避这类旧缺陷。方案二正确开启 IPv6 支持正确开启 IPv6 支持分为三步配置 Docker 守护进程、创建带 IPv6 子网的网络、验证远端 IP。第一步开启守护进程的 ip6tables在/etc/docker/daemon.json中配置以下内容使 Docker 一并管理 IPv6 网络规则让 IPv6 NAT 像现有 IPv4 NAT 一样工作从而避免外部连接被当作容器网络网关 IP前提是容器也被分配了 IPv6 地址{ ip6tables: true, experimental : true, userland-proxy: true }参数说明ip6tables: true让 Docker 管理 IPv6 的 iptables 规则IPv6 的 NAT 行为将与 IPv4 对齐experimental: true目前是ip6tables: true生效的必需前提userland-proxy: true该设置可能影响本地连接的连接行为需要结合自身网络拓扑权衡。修改后如果守护进程正在运行需要重启生效systemctl restart docker值得注意如果验证时发现仍然显示网关 IP多半就是daemon.json配置有误或未在更新配置后执行systemctl restart docker详见下文验证远端 IP一节。第二步创建带 IPv6 子网的网络接下来的示例都聚焦于IPv6 ULA 子网Unique Local Address它适合大多数用户具体原因见下一节如何选择 IPv6 子网。有几个通用要点如果更想要比/64更小的子网例如/112仍可提供超过 6.5 万个 IPv6 地址可以自行调整尤其是当你在为 IPv6 GUA 子网做配置时该网络还会隐式获得一个 IPv4 子网来自 Docker 守护进程配置中的default-address-pools。Docker 官方提供了两种建网途径DMS 文档推荐用户自定义网络User-defined Network并提供了 Compose 与 CLI 两种写法。方式 A用户自定义网络 —— Compose推荐在compose.yaml中创建网络并把服务挂上去services: mailserver: networks: - dms-ipv6 networks: dms-ipv6: enable_ipv6: true ipam: config: - subnet: fd00:cafe:face:feed::/64两个实用小技巧覆盖隐式default网络如果不希望显式给服务指派网络可以直接把上面的dms-ipv6替换为default覆盖 Docker Compose 自动生成的default用户自定义网络。注意Compose 的defaultbridge不受/etc/docker/daemon.json中默认bridge即docker0设置的影响跨文件复用该网络如果想在其他 compose 文件或docker run中引用这个网络需要在compose.yaml中为网络指定name键把它变成可外部引用的命名网络。方式 B用户自定义网络 —— CLI先用命令行创建网络之后即可配合docker run --network dms-ipv6使用docker network create --ipv6 --subnet fd00:cafe:face:feed::/64 dms-ipv6如果希望一个或多个compose.yaml引用它用external: true声明外部网络services: mailserver: networks: - dms-ipv6 networks: dms-ipv6: external: true方式 C默认 bridgedaemon 配置——不推荐bridge网络已被 Docker 官方视为遗留legacy方案仅当使用docker run且未显式指定网络时容器才会挂到默认bridge即docker0。若执意使用可在 daemon 配置中追加两个 IPv6 设置它们只对默认bridge网络生效{ ipv6: true, fixed-cidr-v6: fd00:cafe:face:feed::/64, }Compose 项目也可以通过network_mode使用该网络services: mailserver: network_mode: bridge禁区不要使用2001:db8:1::/64⚠️2001:db8地址前缀是专为文档保留的RFC 3849 文档前缀绝对不要用这个前缀创建私有子网。Docker 官方 IPv6 文档示例中恰好使用它作为占位符并与私有 IPv4 地址混用容易造成误导。如何选择 IPv6 子网ULA 还是 GUA如果你已经在/etc/docker/daemon.json中配置了 IPv6 地址池default-address-pools那么无需显式指定子网。否则可以参考下面的速查指南——优先选用 IPv6 ULA它最省事。IPv6 ULA推荐fd00:cafe:face:feed::/64就是一个 ULA 子网示例格式为fd00::/8前缀范围内ULA 地址相当于你熟悉的私有 IPv4 子网如10.0.0.0/8、172.16.0.0/12、192.168.0.0/16在 IPv6 中的对应物可以直接使用示例值也可以自行选择 ULA 地址这是让 Docker 容器网络像 IPv4 默认那样通过 NAT 支持 IPv6 的好选择容器不直接暴露于公网。IPv6 GUA全局单播地址如果你的服务器本身被分配了公网地址空间那它属于 IPv6 GUA 子网Global Unicast Address通常宿主机拿到的是/64块但不同提供商差异很大这类地址不需要发布容器端口到另一个 IP 就能被公网访问因为不涉及 NAT因此ip6tables: true也不是必需的此时更关键的是配置防火墙来管理哪些端口可被访问。但要注意如果希望容器同时也能通过宿主机的 IPv4 公网地址访问这种方案可能并不合适你可能想把/64细分为更小的子网只让 Docker 使用/64的一部分。这会削弱部分路由能力并且需要额外的 NDP Proxy邻居发现代理设置/管理让公网接口获知 Docker 管理的 IPv6 地址分配并接受外部流量。验证远端 IP 是否正确配置完成后需要验证客户端真实 IP 是否被保留。用 Docker CLI 或 Compose 启动一个traefik/whoami容器挂到 IPv6 网络并发布 80 端口然后从另一台具备 IPv6 网络的主机发起请求docker run --rm -d --network dms-ipv6 -p 80:80 traefik/whoami # 在另一台主机上执行将 2001:db8::1 替换为你的 DMS 宿主机 IPv6 地址 curl --max-time 5 http://[2001:db8::1]:80返回结果中的RemoteAddr应当与你的客户端 IPv6 地址一致。如果返回的并不是远端 IPv6 地址而是 DMS 容器所属 IPv6 子网的网关 IP说明配置有问题在 DMS 已分配 IPv6 地址的情况下出现网关 IP 与 IPv4 场景下userland-proxy: true的原因相同它表明你的daemon.json没有正确配置或更新后的配置ip6tables: trueexperimental: true没有被应用请确认在修改daemon.json后执行了systemctl restart docker。关于 IPv6 ULA 地址优先级的提示同时拥有 IPv4 和 IPv6 记录的域名例如localhost在 DNS 解析时私有地址可能优先选择 IPv4而公网地址优先选择 IPv6。这意味着在宿主机本机测试 IPv6 配置时可能会遇到意外但从远端客户端测试则不会。这一偏好可以通过/etc/gai.conf控制它源于IPv6 ULA 不会与 NAT 联用的假设。该偏好只影响出站连接的解析目标对 IPv6 ULA 而言实际上只会影响容器与宿主机之间的连接。未来 IPv6 ULA 也可能获得更高优先级相关 RFC 草案仍在推进中。DMS 侧的 IPv6 配合*_INET_PROTOCOLS环境变量除 Docker 网络层外DMS 自身也提供两个环境变量用于控制 Postfix 与 Dovecot 监听哪种协议族这对 IPv6 场景至关重要POSTFIX_INET_PROTOCOLSall默认监听所有接口/ipv4仅监听 IPv4 接口Docker 场景下通常最需要/ipv6仅监听 IPv6 接口。详见 环境变量文档DOVECOT_INET_PROTOCOLS取值同上默认all。详见 环境变量文档。两者默认值均为all双栈对应源码位于 target/scripts/startup/variables-stack.sh。从源码实现看Postfix 侧在 target/scripts/startup/setup.d/postfix.sh 中直接执行postconf inet_protocols ${POSTFIX_INET_PROTOCOLS}Dovecot 侧在 target/scripts/startup/setup.d/dovecot.sh 中按取值写入监听地址ipv4对应listen *ipv6对应listen [::]非法值会触发_dms_panic__invalid_value报错退出。配套的集成测试位于 test/tests/parallel/set1/network_inet_protocols.bats覆盖三种场景双栈默认Dovecot 的#listen *, ::默认注释即双栈、Postfix 的inet_protocols allIPv4-onlyDovecotlisten *、Postfixinet_protocols ipv4IPv6-onlyDovecotlisten [::]、Postfixinet_protocols ipv6。实际部署时可参考仓库根目录的 compose.yaml 示例它展示了标准 DMS 服务的端口发布25/143/465/587/993与卷挂载结构若你的主机只在 IPv6 单栈网络内运行可以像测试用例那样把两个环境变量都设为ipv6让 Postfix 与 Dovecot 只监听 IPv6 接口。总结Docker 默认的userland-proxy机制会让 IPv6 连接丢失真实客户端 IP这在邮件场景中会引发 Fail2Ban 误封、SPF 校验失败、退信损害信誉乃至开放中继风险。解决路径二选一不想用 IPv6不配置AAAA记录 userland-proxy: false有 UFW/firewalld 时远端连接会直接失败想用好 IPv6在/etc/docker/daemon.json开启ip6tables需experimental: true并重启守护进程 → 用 Compose 或 CLI 创建 ULA 子网fd00:cafe:face:feed::/64切勿使用2001:db8文档前缀→ 用traefik/whoami从远端主机验证RemoteAddr。同时记得利用 DMS 的POSTFIX_INET_PROTOCOLS/DOVECOT_INET_PROTOCOLS环境变量明确控制监听协议族并用systemctl restart docker确保 daemon 配置生效。整个链路正确后DMS 在 IPv6 下看到的客户端 IP 将与 IPv4 一样真实可靠。【免费下载链接】docker-mailserverProduction-ready fullstack but simple mail server (SMTP, IMAP, LDAP, Antispam, Antivirus, etc.) running inside a container.项目地址: https://gitcode.com/gh_mirrors/do/docker-mailserver创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考