summaryrefslogtreecommitdiff
path: root/caddyconfig/httpcaddyfile/addresses.go
diff options
context:
space:
mode:
Diffstat (limited to 'caddyconfig/httpcaddyfile/addresses.go')
-rw-r--r--caddyconfig/httpcaddyfile/addresses.go282
1 files changed, 177 insertions, 105 deletions
diff --git a/caddyconfig/httpcaddyfile/addresses.go b/caddyconfig/httpcaddyfile/addresses.go
index da51fe9b..1c331ead 100644
--- a/caddyconfig/httpcaddyfile/addresses.go
+++ b/caddyconfig/httpcaddyfile/addresses.go
@@ -77,10 +77,15 @@ 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
@@ -88,27 +93,48 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
// 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)
@@ -118,85 +144,132 @@ 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)
+ }
+ }
+ }
- // 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
+ addresses := make([]string, 0, len(listeners))
+ 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)
+
+ addressesWithProtocols := make([]addressWithProtocols, 0, len(listeners))
+
+ for _, lnAddr := range addresses {
+ lnProts := listeners[lnAddr]
+ prots := make([]string, 0, len(lnProts))
+ for prot := range lnProts {
+ prots = append(prots, prot)
+ }
+ sort.Strings(prots)
+
+ addressesWithProtocols = append(addressesWithProtocols, addressWithProtocols{
+ address: lnAddr,
+ protocols: prots,
+ })
}
- }
- sort.Strings(a.addresses)
- sbaddrs = append(sbaddrs, a)
+ sbaddrs = append(sbaddrs, sbAddrAssociation{
+ addressesWithProtocols: addressesWithProtocols,
+ 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")
@@ -230,55 +303,54 @@ 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 defaultBind, ok := options["default_bind"].([]string); ok {
- lnHosts = defaultBind
+ if len(lnCfgVals) == 0 {
+ if defaultBindValues, ok := options["default_bind"].([]ConfigValue); ok {
+ for _, defaultBindValue := range defaultBindValues {
+ lnCfgVals = append(lnCfgVals, defaultBindValue.Value.(addressesWithProtocols))
+ }
} 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