diff options
author | Aaron Paterson <[email protected]> | 2024-09-30 12:55:03 -0400 |
---|---|---|
committer | GitHub <[email protected]> | 2024-09-30 10:55:03 -0600 |
commit | 4b1a9b6cc1aa521e21289afa276d29952a97d8f3 (patch) | |
tree | c4e28391860ed003aebaca340deb764b6f532e94 /caddyconfig/httpcaddyfile/addresses.go | |
parent | 1a345b4fa620dfb0909a3b086bd76e35dfdbefa5 (diff) | |
download | caddy-4b1a9b6cc1aa521e21289afa276d29952a97d8f3.tar.gz caddy-4b1a9b6cc1aa521e21289afa276d29952a97d8f3.zip |
core: Implement socket activation listeners (#6573)
* caddy adapt for listen_protocols
* adapt listen_socket
* allow multiple listen sockets for port ranges and readd socket fd listen logic
* readd logic to start servers according to listener protocols
* gofmt
* adapt caddytest
* gosec
* fmt and rename listen to listenWithSocket
* fmt and rename listen to listenWithSocket
* more consistent error msg
* non unix listenReusableWithSocketFile
* remove unused func
* doc comment typo
* nonosec
* commit
* doc comments
* more doc comments
* comment was misleading, cardinality did not change
* addressesWithProtocols
* update test
* fd/ and fdgram/
* rm addr
* actually write...
* i guess we doin' "skip": now
* wrong var in placeholder
* wrong var in placeholder II
* update param name in comment
* dont save nil file pointers
* windows
* key -> parsedKey
* osx
* multiple default_bind with protocols
* check for h1 and h2 listener netw
Diffstat (limited to 'caddyconfig/httpcaddyfile/addresses.go')
-rw-r--r-- | caddyconfig/httpcaddyfile/addresses.go | 282 |
1 files changed, 177 insertions, 105 deletions
diff --git a/caddyconfig/httpcaddyfile/addresses.go b/caddyconfig/httpcaddyfile/addresses.go index da51fe9b0..1c331eadc 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 |