Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement issue #6296 passing FDs / socket activation #6543

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c1da1df
caddy adapt for listen_protocols
MayCXC Aug 23, 2024
8c5705b
adapt listen_socket
MayCXC Aug 23, 2024
071679e
allow multiple listen sockets for port ranges and readd socket fd lis…
MayCXC Aug 26, 2024
e1e4a11
readd logic to start servers according to listener protocols
MayCXC Aug 27, 2024
256eee1
gofmt
MayCXC Aug 27, 2024
9d66463
adapt caddytest
MayCXC Aug 27, 2024
152c42c
Merge branch 'caddyserver:master' into master
MayCXC Aug 27, 2024
e20a504
gosec
MayCXC Aug 27, 2024
b34b11a
fmt and rename listen to listenWithSocket
MayCXC Aug 27, 2024
6e9001d
fmt and rename listen to listenWithSocket
MayCXC Aug 27, 2024
db0d780
more consistent error msg
MayCXC Aug 27, 2024
f4bdfd1
non unix listenReusableWithSocketFile
MayCXC Aug 27, 2024
5b339c6
remove unused func
MayCXC Aug 27, 2024
6db7264
Merge branch 'master' into master
MayCXC Aug 27, 2024
3ba23c7
doc comment typo
MayCXC Aug 28, 2024
646a0b3
Merge branch 'master' of github.com:MayCXC/caddy
MayCXC Aug 28, 2024
bce0058
nonosec
MayCXC Aug 29, 2024
86c39ba
commit
MayCXC Aug 29, 2024
b006895
Merge branch 'master' into master
MayCXC Aug 29, 2024
dc89c77
doc comments
MayCXC Aug 30, 2024
8a9aed3
Merge branch 'master' of github.com:MayCXC/caddy
MayCXC Aug 30, 2024
d6d6e6c
more doc comments
MayCXC Aug 30, 2024
af576b1
Merge branch 'master' into master
MayCXC Aug 30, 2024
8ba019f
Merge branch 'master' into master
MayCXC Aug 30, 2024
891499b
Merge branch 'master' into master
MayCXC Sep 4, 2024
7217db8
comment was misleading, cardinality did not change
MayCXC Sep 12, 2024
7852daf
Merge branch 'master' into master
MayCXC Sep 12, 2024
1086e61
addressesWithProtocols
MayCXC Sep 14, 2024
d01cafc
Merge branch 'master' of github.com:MayCXC/caddy
MayCXC Sep 14, 2024
f7ea6cf
update test
MayCXC Sep 14, 2024
f19e686
fd/ and fdgram/
MayCXC Sep 15, 2024
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
279 changes: 175 additions & 104 deletions caddyconfig/httpcaddyfile/addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,38 +77,64 @@ import (
// repetition may be undesirable, so call consolidateAddrMappings() to map
// multiple addresses to the same lists of server blocks (a many:many mapping).
// (Doing this is essentially a map-reduce technique.)
func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock,
func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []serverBlock,
options map[string]any,
) (map[string][]serverBlock, error) {
sbmap := make(map[string][]serverBlock)
) (map[string]map[string][]serverBlock, error) {
addrToProtocolToServerBlocks := map[string]map[string][]serverBlock{}

type keyWithParsedKey struct {
key caddyfile.Token
parsedKey Address
}

for i, sblock := range originalServerBlocks {
// within a server block, we need to map all the listener addresses
// implied by the server block to the keys of the server block which
// will be served by them; this has the effect of treating each
// key of a server block as its own, but without having to repeat its
// contents in cases where multiple keys really can be served together
addrToKeys := make(map[string][]caddyfile.Token)
addrToProtocolToKeyWithParsedKeys := map[string]map[string][]keyWithParsedKey{}
for j, key := range sblock.block.Keys {
parsedKey, err := ParseAddress(key.Text)
if err != nil {
return nil, fmt.Errorf("parsing key: %v", err)
}
parsedKey = parsedKey.Normalize()

// a key can have multiple listener addresses if there are multiple
// arguments to the 'bind' directive (although they will all have
// the same port, since the port is defined by the key or is implicit
// through automatic HTTPS)
addrs, err := st.listenerAddrsForServerBlockKey(sblock, key.Text, options)
listeners, err := st.listenersForServerBlockAddress(sblock, parsedKey, options)
if err != nil {
return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key.Text, err)
}

// associate this key with each listener address it is served on
for _, addr := range addrs {
addrToKeys[addr] = append(addrToKeys[addr], key)
// associate this key with its protocols and each listener address served with them
kwpk := keyWithParsedKey{key, parsedKey}
for addr, protocols := range listeners {
protocolToKeyWithParsedKeys, ok := addrToProtocolToKeyWithParsedKeys[addr]
if !ok {
protocolToKeyWithParsedKeys = map[string][]keyWithParsedKey{}
addrToProtocolToKeyWithParsedKeys[addr] = protocolToKeyWithParsedKeys
}

// an empty protocol indicates the default, a nil or empty value in the ListenProtocols array
if len(protocols) == 0 {
protocols[""] = struct{}{}
}
for prot := range protocols {
protocolToKeyWithParsedKeys[prot] = append(
protocolToKeyWithParsedKeys[prot],
kwpk)
}
}
}

// make a slice of the map keys so we can iterate in sorted order
addrs := make([]string, 0, len(addrToKeys))
for k := range addrToKeys {
addrs = append(addrs, k)
addrs := make([]string, 0, len(addrToProtocolToKeyWithParsedKeys))
for addr := range addrToProtocolToKeyWithParsedKeys {
addrs = append(addrs, addr)
}
sort.Strings(addrs)

Expand All @@ -118,85 +144,130 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
// server block are only the ones which use the address; but
// the contents (tokens) are of course the same
for _, addr := range addrs {
keys := addrToKeys[addr]
// parse keys so that we only have to do it once
parsedKeys := make([]Address, 0, len(keys))
for _, key := range keys {
addr, err := ParseAddress(key.Text)
if err != nil {
return nil, fmt.Errorf("parsing key '%s': %v", key.Text, err)
protocolToKeyWithParsedKeys := addrToProtocolToKeyWithParsedKeys[addr]

prots := make([]string, 0, len(protocolToKeyWithParsedKeys))
for prot := range protocolToKeyWithParsedKeys {
prots = append(prots, prot)
}
sort.Strings(prots)

protocolToServerBlocks, ok := addrToProtocolToServerBlocks[addr]
if !ok {
protocolToServerBlocks = map[string][]serverBlock{}
addrToProtocolToServerBlocks[addr] = protocolToServerBlocks
}

for _, prot := range prots {
keyWithParsedKeys := protocolToKeyWithParsedKeys[prot]

keys := make([]caddyfile.Token, len(keyWithParsedKeys))
parsedKeys := make([]Address, len(keyWithParsedKeys))

for k, keyWithParsedKey := range keyWithParsedKeys {
keys[k] = keyWithParsedKey.key
parsedKeys[k] = keyWithParsedKey.parsedKey
}
parsedKeys = append(parsedKeys, addr.Normalize())

protocolToServerBlocks[prot] = append(protocolToServerBlocks[prot], serverBlock{
block: caddyfile.ServerBlock{
Keys: keys,
Segments: sblock.block.Segments,
},
pile: sblock.pile,
parsedKeys: parsedKeys,
})
}
sbmap[addr] = append(sbmap[addr], serverBlock{
block: caddyfile.ServerBlock{
Keys: keys,
Segments: sblock.block.Segments,
},
pile: sblock.pile,
keys: parsedKeys,
})
}
}

return sbmap, nil
return addrToProtocolToServerBlocks, nil
}

// consolidateAddrMappings eliminates repetition of identical server blocks in a mapping of
// single listener addresses to lists of server blocks. Since multiple addresses may serve
// identical sites (server block contents), this function turns a 1:many mapping into a
// many:many mapping. Server block contents (tokens) must be exactly identical so that
// reflect.DeepEqual returns true in order for the addresses to be combined. Identical
// entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
// single listener addresses to protocols to lists of server blocks. Since multiple addresses
// may serve multiple protocols to identical sites (server block contents), this function turns
// a 1:many mapping into a many:many mapping. Server block contents (tokens) must be
// exactly identical so that reflect.DeepEqual returns true in order for the addresses to be combined.
// Identical entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
// association from multiple addresses to multiple server blocks; i.e. each element of
// the returned slice) becomes a server definition in the output JSON.
func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation {
sbaddrs := make([]sbAddrAssociation, 0, len(addrToServerBlocks))
for addr, sblocks := range addrToServerBlocks {
// we start with knowing that at least this address
// maps to these server blocks
a := sbAddrAssociation{
addresses: []string{addr},
serverBlocks: sblocks,
func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[string]map[string][]serverBlock) []sbAddrAssociation {
sbaddrs := make([]sbAddrAssociation, 0, len(addrToProtocolToServerBlocks))

addrs := make([]string, 0, len(addrToProtocolToServerBlocks))
for addr := range addrToProtocolToServerBlocks {
addrs = append(addrs, addr)
}
sort.Strings(addrs)

for _, addr := range addrs {
protocolToServerBlocks := addrToProtocolToServerBlocks[addr]

prots := make([]string, 0, len(protocolToServerBlocks))
for prot := range protocolToServerBlocks {
prots = append(prots, prot)
}
sort.Strings(prots)

for _, prot := range prots {
serverBlocks := protocolToServerBlocks[prot]

// now find other addresses that map to identical
// server blocks and add them to our map of listener
// addresses and protocols, while removing them from
// the original map
listeners := map[string]map[string]struct{}{}

for otherAddr, otherProtocolToServerBlocks := range addrToProtocolToServerBlocks {
for otherProt, otherServerBlocks := range otherProtocolToServerBlocks {
if addr == otherAddr && prot == otherProt || reflect.DeepEqual(serverBlocks, otherServerBlocks) {
listener, ok := listeners[otherAddr]
if !ok {
listener = map[string]struct{}{}
listeners[otherAddr] = listener
}
listener[otherProt] = struct{}{}
delete(otherProtocolToServerBlocks, otherProt)
}
}
}

addresses := make([]string, 0, len(listeners))

// now find other addresses that map to identical
// server blocks and add them to our list of
// addresses, while removing them from the map
for otherAddr, otherSblocks := range addrToServerBlocks {
if addr == otherAddr {
continue
for lnAddr := range listeners {
addresses = append(addresses, lnAddr)
}
if reflect.DeepEqual(sblocks, otherSblocks) {
a.addresses = append(a.addresses, otherAddr)
delete(addrToServerBlocks, otherAddr)
sort.Strings(addresses)

protocols := make([][]string, len(listeners))

for i, lnAddr := range addresses {
lnProts := listeners[lnAddr]
prots := make([]string, 0, len(lnProts))
for prot := range lnProts {
prots = append(prots, prot)
}
sort.Strings(prots)
protocols[i] = prots
}
}
sort.Strings(a.addresses)

sbaddrs = append(sbaddrs, a)
sbaddrs = append(sbaddrs, sbAddrAssociation{
addresses: addresses,
protocols: protocols,
serverBlocks: serverBlocks,
})
}
}

// sort them by their first address (we know there will always be at least one)
// to avoid problems with non-deterministic ordering (makes tests flaky)
sort.Slice(sbaddrs, func(i, j int) bool {
return sbaddrs[i].addresses[0] < sbaddrs[j].addresses[0]
})

return sbaddrs
}

// listenerAddrsForServerBlockKey essentially converts the Caddyfile
// site addresses to Caddy listener addresses for each server block.
func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
// listenersForServerBlockAddress essentially converts the Caddyfile site addresses to a map from
// Caddy listener addresses and the protocols to serve them with to the parsed address for each server block.
func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Address,
options map[string]any,
) ([]string, error) {
addr, err := ParseAddress(key)
if err != nil {
return nil, fmt.Errorf("parsing key: %v", err)
}
addr = addr.Normalize()

) (map[string]map[string]struct{}, error) {
switch addr.Scheme {
case "wss":
return nil, fmt.Errorf("the scheme wss:// is only supported in browsers; use https:// instead")
Expand Down Expand Up @@ -230,55 +301,55 @@ func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key str

// error if scheme and port combination violate convention
if (addr.Scheme == "http" && lnPort == httpsPort) || (addr.Scheme == "https" && lnPort == httpPort) {
return nil, fmt.Errorf("[%s] scheme and port violate convention", key)
return nil, fmt.Errorf("[%s] scheme and port violate convention", addr.String())
}

// the bind directive specifies hosts (and potentially network), but is optional
lnHosts := make([]string, 0, len(sblock.pile["bind"]))
// the bind directive specifies hosts (and potentially network), and the protocols to serve them with, but is optional
lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.pile["bind"]))
for _, cfgVal := range sblock.pile["bind"] {
lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
if val, ok := cfgVal.Value.(addressesWithProtocols); ok {
lnCfgVals = append(lnCfgVals, val)
}
}
if len(lnHosts) == 0 {
if len(lnCfgVals) == 0 {
if defaultBind, ok := options["default_bind"].([]string); ok {
lnHosts = defaultBind
lnCfgVals = []addressesWithProtocols{{
addresses: defaultBind,
protocols: nil,
}}
} else {
lnHosts = []string{""}
lnCfgVals = []addressesWithProtocols{{
addresses: []string{""},
protocols: nil,
}}
}
}

// use a map to prevent duplication
listeners := make(map[string]struct{})
for _, lnHost := range lnHosts {
// normally we would simply append the port,
// but if lnHost is IPv6, we need to ensure it
// is enclosed in [ ]; net.JoinHostPort does
// this for us, but lnHost might also have a
// network type in front (e.g. "tcp/") leading
// to "[tcp/::1]" which causes parsing failures
// later; what we need is "tcp/[::1]", so we have
// to split the network and host, then re-combine
network, host, ok := strings.Cut(lnHost, "/")
if !ok {
host = network
network = ""
}
host = strings.Trim(host, "[]") // IPv6
networkAddr := caddy.JoinNetworkAddress(network, host, lnPort)
addr, err := caddy.ParseNetworkAddress(networkAddr)
if err != nil {
return nil, fmt.Errorf("parsing network address: %v", err)
listeners := map[string]map[string]struct{}{}
for _, lnCfgVal := range lnCfgVals {
for _, lnHost := range lnCfgVal.addresses {
networkAddr, err := caddy.ParseNetworkAddressFromHostPort(lnHost, lnPort)
if err != nil {
return nil, fmt.Errorf("parsing network address: %v", err)
}
if _, ok := listeners[addr.String()]; !ok {
listeners[networkAddr.String()] = map[string]struct{}{}
}
for _, protocol := range lnCfgVal.protocols {
listeners[networkAddr.String()][protocol] = struct{}{}
}
}
listeners[addr.String()] = struct{}{}
}

// now turn map into list
listenersList := make([]string, 0, len(listeners))
for lnStr := range listeners {
listenersList = append(listenersList, lnStr)
}
sort.Strings(listenersList)
return listeners, nil
}

return listenersList, nil
// addressesWithProtocols associates a list of listen addresses
// with a list of protocols to serve them with
type addressesWithProtocols struct {
addresses []string
protocols []string
}

// Address represents a site address. It contains
Expand Down
24 changes: 22 additions & 2 deletions caddyconfig/httpcaddyfile/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,30 @@ func init() {

// parseBind parses the bind directive. Syntax:
//
// bind <addresses...>
// bind <addresses...> [{
// protocols [h1|h2|h2c|h3] [...]
// }]
func parseBind(h Helper) ([]ConfigValue, error) {
h.Next() // consume directive name
return []ConfigValue{{Class: "bind", Value: h.RemainingArgs()}}, nil
var addresses, protocols []string
addresses = h.RemainingArgs()

for h.NextBlock(0) {
switch h.Val() {
case "protocols":
protocols = h.RemainingArgs()
if len(protocols) == 0 {
return nil, h.Errf("protocols requires one or more arguments")
}
default:
return nil, h.Errf("unknown subdirective: %s", h.Val())
}
}

return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{
addresses: addresses,
protocols: protocols,
}}}, nil
}

// parseTLS parses the tls directive. Syntax:
Expand Down
Loading
Loading