Listen on both IPv4 and IPv6 in standalone mode by default
Some checks failed
Apache / Apache (push) Has been cancelled
DragonFlyBSD / DragonFlyBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
FreeBSD / FreeBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
FreeBSD / FreeBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
GhostBSD / GhostBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
GhostBSD / GhostBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Haiku / Haiku (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Haiku / Haiku (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Hurd / Hurd (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Linux / Linux (almalinux:latest) (push) Has been cancelled
Linux / Linux (alpine:latest) (push) Has been cancelled
Linux / Linux (archlinux:latest) (push) Has been cancelled
Linux / Linux (debian:latest) (push) Has been cancelled
Linux / Linux (fedora:latest) (push) Has been cancelled
Linux / Linux (gentoo/stage3) (push) Has been cancelled
Linux / Linux (kalilinux/kali) (push) Has been cancelled
Linux / Linux (opensuse/leap:latest) (push) Has been cancelled
Linux / Linux (oraclelinux:8) (push) Has been cancelled
Linux / Linux (ubuntu:latest) (push) Has been cancelled
MacOS / MacOS (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
MidnightBSD / MidnightBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
NetBSD / NetBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Nginx / Nginx (push) Has been cancelled
Omnios / Omnios (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Omnios / Omnios (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenBSD / OpenBSD (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenBSD / OpenBSD (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenEuler / OpenEuler (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenIndiana / OpenIndiana (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
OpenIndiana / OpenIndiana (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
PebbleStrict / PebbleStrict (push) Has been cancelled
PebbleStrict / PebbleStrict_IPCert (push) Has been cancelled
Solaris / Solaris (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Solaris / Solaris (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Tribblix / Tribblix (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Tribblix / Tribblix (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Ubuntu / Ubuntu (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Ubuntu / Ubuntu (1, , , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, ) (push) Has been cancelled
Ubuntu / Ubuntu (Smallstep Intermediate CA, Smallstep Intermediate CA, , 1, https://localhost:9000/acme/acme/directory, 1, , 172.17.0.1) (push) Has been cancelled
Ubuntu / Ubuntu (ZeroSSL RSA DV SSL CA 2, ZeroSSL ECC DV SSL CA 2, githubtest@acme.sh, ZeroSSL.com, ) (push) Has been cancelled
Windows / Windows (, , , LetsEncrypt.org_test, (STAGING)) (push) Has been cancelled
Build DockerHub / CheckToken (push) Has been cancelled
Shellcheck / ShellCheck (push) Has been cancelled
Shellcheck / shfmt (push) Has been cancelled
Build DockerHub / build (push) Has been cancelled

socat binds a single family unless told which one: up to 1.7.x the
default IP version for TCP-LISTEN is 4, and 1.8.0 made it "no
preference", which resolves to whatever getaddrinfo and bindv6only
happen to give. So an order carrying both an IPv4 and an IPv6
identifier could never pass both http-01 challenges.

Bind one socket per family instead, with ipv6only on the IPv6 one so
the two do not collide. IPv4-mapped IPv6 addresses are not a portable
alternative, OpenBSD does not support them at all. The IPv6 listener
is best effort, a host without IPv6 still gets the IPv4 one. The
python fallback does the same. --listen-v4 and --listen-v6 keep
forcing a single family, and passing both now means both.

Le_Listen_V4 and Le_Listen_V6 were mutually exclusive in the domain
conf, which silently dropped one of them on renewal, and
_starttlsserver let -4 win when both were set.

Fixes #7185
This commit is contained in:
neil 2026-08-08 13:09:18 +08:00
parent f1cbba05f6
commit 05367d3598

115
acme.sh
View file

@ -2715,6 +2715,21 @@ _clearcaconf() {
_clear_conf "$CA_CONF" "$1"
}
#Starts a socat listener in the background, the pid is set to _socat_pid.
#It uses the content, _content_len, _NC and _SOCAT_ERR of _startserver.
#options
_startsocat() {
_socat_opts="$1"
_debug "_NC" "$_NC $_socat_opts"
$_NC $_socat_opts SYSTEM:"sleep 1; \
echo 'HTTP/1.0 200 OK'; \
echo 'Content-Length\: $_content_len'; \
echo ''; \
printf '%s' '$content';" 2>>"$_SOCAT_ERR" &
_socat_pid="$!"
_debug "_socat_pid" "$_socat_pid"
}
# content localaddress
_startserver() {
content="$1"
@ -2728,16 +2743,24 @@ _startserver() {
_debug Le_Listen_V4 "$Le_Listen_V4"
_debug Le_Listen_V6 "$Le_Listen_V6"
_serverproc_v6=""
if _exists "socat"; then
_NC="socat"
if [ "$Le_Listen_V6" ]; then
SOCAT_OPTIONS6=""
if [ "$Le_Listen_V6" ] && [ -z "$Le_Listen_V4" ]; then
_NC="$_NC -6"
SOCAT_OPTIONS=TCP6-LISTEN
elif [ "$Le_Listen_V4" ]; then
elif [ "$Le_Listen_V4" ] && [ -z "$Le_Listen_V6" ]; then
_NC="$_NC -4"
SOCAT_OPTIONS=TCP4-LISTEN
else
elif [ "$ncaddr" ]; then
#a single local address belongs to a single family, let socat pick it
SOCAT_OPTIONS=TCP-LISTEN
else
#listen on both ipv4 and ipv6, with one socket for each family:
#ipv4-mapped ipv6 addresses are not available everywhere.
SOCAT_OPTIONS=TCP4-LISTEN
SOCAT_OPTIONS6=TCP6-LISTEN
fi
if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then
@ -2745,6 +2768,10 @@ _startserver() {
fi
SOCAT_OPTIONS=$SOCAT_OPTIONS:$Le_HTTPPort,crlf,reuseaddr,fork
if [ "$SOCAT_OPTIONS6" ]; then
#ipv6only keeps this socket from colliding with the ipv4 one
SOCAT_OPTIONS6=$SOCAT_OPTIONS6:$Le_HTTPPort,crlf,reuseaddr,fork,ipv6only=1
fi
#Adding bind to local-address
if [ "$ncaddr" ]; then
@ -2753,14 +2780,14 @@ _startserver() {
_content_len="$(printf "%s" "$content" | wc -c)"
_debug _content_len "$_content_len"
_debug "_NC" "$_NC $SOCAT_OPTIONS"
export _SOCAT_ERR="$(_mktemp)"
$_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; \
echo 'HTTP/1.0 200 OK'; \
echo 'Content-Length\: $_content_len'; \
echo ''; \
printf '%s' '$content';" 2>"$_SOCAT_ERR" &
serverproc="$!"
_startsocat "$SOCAT_OPTIONS"
serverproc="$_socat_pid"
if [ "$SOCAT_OPTIONS6" ]; then
#best effort, the host may have no ipv6 support at all
_startsocat "$SOCAT_OPTIONS6"
_serverproc_v6="$_socat_pid"
fi
else
_PYTHON=""
if _exists "python3"; then
@ -2772,21 +2799,40 @@ printf '%s' '$content';" 2>"$_SOCAT_ERR" &
fi
if [ "$_PYTHON" ]; then
_debug "Using python: $_PYTHON"
_AF="socket.AF_INET"
_BIND_ADDR="0.0.0.0"
if [ "$Le_Listen_V6" ]; then
_AF="socket.AF_INET6"
#a comma separated list of addresses to listen on, one socket for each
_BIND_ADDR="0.0.0.0,::"
if [ "$Le_Listen_V6" ] && [ -z "$Le_Listen_V4" ]; then
_BIND_ADDR="::"
elif [ "$Le_Listen_V4" ] && [ -z "$Le_Listen_V6" ]; then
_BIND_ADDR="0.0.0.0"
fi
if [ "$ncaddr" ]; then
_BIND_ADDR="$ncaddr"
fi
_debug "_BIND_ADDR" "$_BIND_ADDR"
export _SOCAT_ERR="$(_mktemp)"
$_PYTHON -c "import socket,sys;s=socket.socket($_AF,socket.SOCK_STREAM);s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1);s.bind((sys.argv[2],int(sys.argv[1])));s.listen(5);res='HTTP/1.0 200 OK\r\nContent-Length: '+str(len(sys.argv[3]))+'\r\n\r\n'+sys.argv[3];
$_PYTHON -c "import socket,sys,select
res='HTTP/1.0 200 OK\r\nContent-Length: '+str(len(sys.argv[3]))+'\r\n\r\n'+sys.argv[3]
ads=sys.argv[2].split(',')
ls=[]
for ad in ads:
try:
sk=socket.socket(socket.AF_INET6 if ':' in ad else socket.AF_INET,socket.SOCK_STREAM)
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
if ':' in ad and len(ads)>1:
sk.setsockopt(socket.IPPROTO_IPV6,socket.IPV6_V6ONLY,1)
sk.bind((ad,int(sys.argv[1])))
sk.listen(5)
ls.append(sk)
except Exception:
sys.stderr.write(str(sys.exc_info()[1])+'\n')
if not ls:
sys.exit(1)
while True:
c,a=s.accept()
c.sendall(res.encode() if hasattr(res, 'encode') else res)
c.close()" "$Le_HTTPPort" "$_BIND_ADDR" "$content" 2>"$_SOCAT_ERR" &
for sk in select.select(ls,[],[])[0]:
c,a=sk.accept()
c.sendall(res.encode() if hasattr(res, 'encode') else res)
c.close()" "$Le_HTTPPort" "$_BIND_ADDR" "$content" 2>"$_SOCAT_ERR" &
serverproc="$!"
_NC="$_PYTHON"
else
@ -2809,6 +2855,11 @@ while True:
_stopserver() {
pid="$1"
_debug "pid" "$pid"
if [ "$_serverproc_v6" ]; then
_debug "_serverproc_v6" "$_serverproc_v6"
kill $_serverproc_v6 >/dev/null 2>&1
_serverproc_v6=""
fi
if [ -z "$pid" ]; then
rm -f "$_SOCAT_ERR"
return
@ -2882,9 +2933,11 @@ _starttlsserver() {
_debug Le_Listen_V4 "$Le_Listen_V4"
_debug Le_Listen_V6 "$Le_Listen_V6"
if [ "$Le_Listen_V4" ]; then
#openssl s_server binds a single socket, so both options together can only
#mean: do not force a family, same as when neither of them is given.
if [ "$Le_Listen_V4" ] && [ -z "$Le_Listen_V6" ]; then
__S_OPENSSL="$__S_OPENSSL -4"
elif [ "$Le_Listen_V6" ]; then
elif [ "$Le_Listen_V6" ] && [ -z "$Le_Listen_V4" ]; then
__S_OPENSSL="$__S_OPENSSL -6"
fi
@ -5986,12 +6039,17 @@ $_authorizations_map"
_clearaccountconf "HTTPS_INSECURE"
fi
if [ "$Le_Listen_V4" ]; then
_savedomainconf "Le_Listen_V4" "$Le_Listen_V4"
_cleardomainconf Le_Listen_V6
elif [ "$Le_Listen_V6" ]; then
_savedomainconf "Le_Listen_V6" "$Le_Listen_V6"
_cleardomainconf Le_Listen_V4
if [ "$Le_Listen_V4" ] || [ "$Le_Listen_V6" ]; then
if [ "$Le_Listen_V4" ]; then
_savedomainconf "Le_Listen_V4" "$Le_Listen_V4"
else
_cleardomainconf Le_Listen_V4
fi
if [ "$Le_Listen_V6" ]; then
_savedomainconf "Le_Listen_V6" "$Le_Listen_V6"
else
_cleardomainconf Le_Listen_V6
fi
fi
if [ "$Le_ForceNewDomainKey" = "1" ]; then
@ -8172,8 +8230,9 @@ Parameters:
--ocsp, --ocsp-must-staple Generate OCSP-Must-Staple extension.
--always-force-new-domain-key Generate new domain key on renewal. Otherwise, the domain key is not changed by default.
--auto-upgrade [0|1] Valid for '--upgrade' command, indicating whether to upgrade automatically in future. Defaults to 1 if argument is omitted.
--listen-v4 Force standalone/tls server to listen at ipv4.
--listen-v6 Force standalone/tls server to listen at ipv6.
--listen-v4 Force standalone/tls server to listen at ipv4 only.
By default the standalone server listens on both ipv4 and ipv6.
--listen-v6 Force standalone/tls server to listen at ipv6 only.
--request-v4 Force client requests to use ipv4 to connect to the CA server.
--request-v6 Force client requests to use ipv6 to connect to the CA server.
--openssl-bin <file> Specifies a custom openssl bin location.