Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion caddyconfig/httpcaddyfile/addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Ad
if err != nil {
return nil, fmt.Errorf("parsing network address: %v", err)
}
if _, ok := listeners[addr.String()]; !ok {
if _, ok := listeners[networkAddr.String()]; !ok {
listeners[networkAddr.String()] = map[string]struct{}{}
}
for _, protocol := range lnCfgVal.protocols {
Expand Down
101 changes: 101 additions & 0 deletions caddyconfig/httpcaddyfile/httptype_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package httpcaddyfile

import (
"encoding/json"
"slices"
"strings"
"testing"

Expand Down Expand Up @@ -298,3 +299,103 @@ func TestDefaultSNIWithoutHTTPS(t *testing.T) {
t.Errorf("Expected default_sni 'my-sni.com' in TLS connection policies, but it was missing. Generated JSON: %s", string(result))
}
}

// TestBindProtocolsMergedAcrossDirectives ensures that the protocols declared
// by every bind directive resolving to the same listener address are unioned
// together, instead of each bind directive discarding the ones before it.
func TestBindProtocolsMergedAcrossDirectives(t *testing.T) {
for i, tc := range []struct {
name string
input string
expectListen []string
expectProtocols [][]string
}{
{
name: "two bind directives on the same address",
input: `example.com {
bind 127.0.0.1 {
protocols h1
}
bind 127.0.0.1 {
protocols h2
}
}`,
expectListen: []string{"127.0.0.1:443"},
expectProtocols: [][]string{{"h1", "h2"}},
},
{
name: "later bind without protocols does not clear earlier ones",
input: `example.com {
bind 127.0.0.1 {
protocols h1 h3
}
bind 127.0.0.1
}`,
expectListen: []string{"127.0.0.1:443"},
expectProtocols: [][]string{{"h1", "h3"}},
},
{
name: "a single bind directive is unaffected",
input: `example.com {
bind 127.0.0.1 {
protocols h1
}
}`,
expectListen: []string{"127.0.0.1:443"},
expectProtocols: [][]string{{"h1"}},
},
{
name: "bind directives on distinct addresses stay separate",
input: `example.com {
bind 127.0.0.1 {
protocols h1
}
bind 127.0.0.2 {
protocols h2
}
}`,
expectListen: []string{"127.0.0.1:443", "127.0.0.2:443"},
expectProtocols: [][]string{{"h1"}, {"h2"}},
},
} {
t.Run(tc.name, func(t *testing.T) {
adapter := caddyfile.Adapter{ServerType: ServerType{}}
result, _, err := adapter.Adapt([]byte(tc.input), nil)
if err != nil {
t.Fatalf("Failed to adapt Caddyfile: %v", err)
}

var config struct {
Apps struct {
HTTP struct {
Servers map[string]*caddyhttp.Server `json:"servers"`
} `json:"http"`
} `json:"apps"`
}
if err := json.Unmarshal(result, &config); err != nil {
t.Fatalf("Failed to unmarshal JSON config: %v", err)
}

server, ok := config.Apps.HTTP.Servers["srv0"]
if !ok {
t.Fatalf("Expected server 'srv0' to be created; got JSON: %s", result)
}

if !slices.Equal(server.Listen, tc.expectListen) {
t.Errorf("Test %d: expected listen addresses %v, got %v", i, tc.expectListen, server.Listen)
}
if len(server.ListenProtocols) != len(tc.expectProtocols) {
t.Errorf("Test %d: expected listen protocols %v, got %v; generated JSON: %s",
i, tc.expectProtocols, server.ListenProtocols, result)
} else {
for j := range tc.expectProtocols {
if !slices.Equal(server.ListenProtocols[j], tc.expectProtocols[j]) {
t.Errorf("Test %d: expected listen protocols %v, got %v; generated JSON: %s",
i, tc.expectProtocols, server.ListenProtocols, result)
break
}
}
}
})
}
}