@marcel91200 / caddy Mirror · read-only / commits / 6628c4a9
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 11:00 UTC

cmd: support caddy start on IPv6-only hosts (#7744)

Zen Dodd committed May 21, 2026 at 00:17 UTC 6628c4a9de5588e43430b285f6f4de376aaafe70
2 files changed +93 -1
cmd/commandfuncs.go
+17 -1
@@ -58,7 +58,7 @@ func cmdStart(fl Flags) (int, error) {
58
59 // open a listener to which the child process will connect when
60 // it is ready to confirm that it has successfully started
61 - ln, err := net.Listen("tcp", "127.0.0.1:0")
61 + ln, err := listenTCPForPingback(net.Listen)
62 if err != nil {
63 return caddy.ExitCodeFailedStartup,
64 fmt.Errorf("opening listener for success confirmation: %v", err)
@@ -169,6 +169,22 @@ func cmdStart(fl Flags) (int, error) {
169 return caddy.ExitCodeSuccess, nil
170 }
171
172 +type tcpListenFunc func(network, address string) (net.Listener, error)
173 +
174 +func listenTCPForPingback(listen tcpListenFunc) (net.Listener, error) {
175 + ln, ipv4Err := listen("tcp4", "127.0.0.1:0")
176 + if ipv4Err == nil {
177 + return ln, nil
178 + }
179 +
180 + ln, ipv6Err := listen("tcp6", "[::1]:0")
181 + if ipv6Err == nil {
182 + return ln, nil
183 + }
184 +
185 + return nil, fmt.Errorf("listen on 127.0.0.1:0: %v; listen on [::1]:0: %v", ipv4Err, ipv6Err)
186 +}
187 +
188 func cmdRun(fl Flags) (int, error) {
189 caddy.TrapSignals()
190
cmd/main_test.go
+76
@@ -1,6 +1,8 @@
1 package caddycmd
2
3 import (
4 + "errors"
5 + "net"
6 "reflect"
7 "strings"
8 "testing"
@@ -169,6 +171,80 @@ here"
171 }
172 }
173
174 +func TestListenTCPForPingbackUsesIPv4Loopback(t *testing.T) {
175 + var calls []string
176 + expected := &stubListener{addr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 1234}}
177 +
178 + actual, err := listenTCPForPingback(func(network, address string) (net.Listener, error) {
179 + calls = append(calls, network+" "+address)
180 + return expected, nil
181 + })
182 + if err != nil {
183 + t.Fatalf("listenTCPForPingback returned error: %v", err)
184 + }
185 + if actual != expected {
186 + t.Fatalf("expected listener %p, got %p", expected, actual)
187 + }
188 +
189 + expectCalls := []string{"tcp4 127.0.0.1:0"}
190 + if !reflect.DeepEqual(calls, expectCalls) {
191 + t.Fatalf("expected calls %v, got %v", expectCalls, calls)
192 + }
193 +}
194 +
195 +func TestListenTCPForPingbackFallsBackToIPv6Loopback(t *testing.T) {
196 + var calls []string
197 + expected := &stubListener{addr: &net.TCPAddr{IP: net.ParseIP("::1"), Port: 1234}}
198 +
199 + actual, err := listenTCPForPingback(func(network, address string) (net.Listener, error) {
200 + calls = append(calls, network+" "+address)
201 + if len(calls) == 1 {
202 + return nil, errors.New("ipv4 unavailable")
203 + }
204 + return expected, nil
205 + })
206 + if err != nil {
207 + t.Fatalf("listenTCPForPingback returned error: %v", err)
208 + }
209 + if actual != expected {
210 + t.Fatalf("expected listener %p, got %p", expected, actual)
211 + }
212 +
213 + expectCalls := []string{"tcp4 127.0.0.1:0", "tcp6 [::1]:0"}
214 + if !reflect.DeepEqual(calls, expectCalls) {
215 + t.Fatalf("expected calls %v, got %v", expectCalls, calls)
216 + }
217 +}
218 +
219 +func TestListenTCPForPingbackReportsBothFailures(t *testing.T) {
220 + _, err := listenTCPForPingback(func(network, address string) (net.Listener, error) {
221 + return nil, errors.New(network + " failed")
222 + })
223 + if err == nil {
224 + t.Fatal("expected error")
225 + }
226 + if !strings.Contains(err.Error(), "tcp4 failed") ||
227 + !strings.Contains(err.Error(), "tcp6 failed") {
228 + t.Fatalf("expected both listener errors, got: %v", err)
229 + }
230 +}
231 +
232 +type stubListener struct {
233 + addr net.Addr
234 +}
235 +
236 +func (sl *stubListener) Accept() (net.Conn, error) {
237 + return nil, net.ErrClosed
238 +}
239 +
240 +func (sl *stubListener) Close() error {
241 + return nil
242 +}
243 +
244 +func (sl *stubListener) Addr() net.Addr {
245 + return sl.addr
246 +}
247 +
248 func Test_isCaddyfile(t *testing.T) {
249 type args struct {
250 configFile string