@marcel91200 / caddy Mirror · read-only / commits / 4b1a9b6c
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 12, 08:15 UTC

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

Aaron Paterson committed Sep 30, 2024 at 12:55 UTC 4b1a9b6cc1aa521e21289afa276d29952a97d8f3
21 files changed +925 -343
admin.go
+2 -2
@@ -313,7 +313,7 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
313 }
314 if admin.Origins == nil {
315 if addr.isLoopback() {
316 - if addr.IsUnixNetwork() {
316 + if addr.IsUnixNetwork() || addr.IsFdNetwork() {
317 // RFC 2616, Section 14.26:
318 // "A client MUST include a Host header field in all HTTP/1.1 request
319 // messages. If the requested URI does not include an Internet host
@@ -351,7 +351,7 @@ func (admin AdminConfig) allowedOrigins(addr NetworkAddress) []*url.URL {
351 uniqueOrigins[net.JoinHostPort("127.0.0.1", addr.port())] = struct{}{}
352 }
353 }
354 - if !addr.IsUnixNetwork() {
354 + if !addr.IsUnixNetwork() && !addr.IsFdNetwork() {
355 uniqueOrigins[addr.JoinHostPort(0)] = struct{}{}
356 }
357 }
caddyconfig/httpcaddyfile/addresses.go
+177 -105
@@ -77,10 +77,15 @@ import (
77 // repetition may be undesirable, so call consolidateAddrMappings() to map
78 // multiple addresses to the same lists of server blocks (a many:many mapping).
79 // (Doing this is essentially a map-reduce technique.)
80 -func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBlock,
80 +func (st *ServerType) mapAddressToProtocolToServerBlocks(originalServerBlocks []serverBlock,
81 options map[string]any,
82 -) (map[string][]serverBlock, error) {
83 - sbmap := make(map[string][]serverBlock)
82 +) (map[string]map[string][]serverBlock, error) {
83 + addrToProtocolToServerBlocks := map[string]map[string][]serverBlock{}
84 +
85 + type keyWithParsedKey struct {
86 + key caddyfile.Token
87 + parsedKey Address
88 + }
89
90 for i, sblock := range originalServerBlocks {
91 // within a server block, we need to map all the listener addresses
@@ -88,27 +93,48 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
93 // will be served by them; this has the effect of treating each
94 // key of a server block as its own, but without having to repeat its
95 // contents in cases where multiple keys really can be served together
91 - addrToKeys := make(map[string][]caddyfile.Token)
96 + addrToProtocolToKeyWithParsedKeys := map[string]map[string][]keyWithParsedKey{}
97 for j, key := range sblock.block.Keys {
98 + parsedKey, err := ParseAddress(key.Text)
99 + if err != nil {
100 + return nil, fmt.Errorf("parsing key: %v", err)
101 + }
102 + parsedKey = parsedKey.Normalize()
103 +
104 // a key can have multiple listener addresses if there are multiple
105 // arguments to the 'bind' directive (although they will all have
106 // the same port, since the port is defined by the key or is implicit
107 // through automatic HTTPS)
97 - addrs, err := st.listenerAddrsForServerBlockKey(sblock, key.Text, options)
108 + listeners, err := st.listenersForServerBlockAddress(sblock, parsedKey, options)
109 if err != nil {
110 return nil, fmt.Errorf("server block %d, key %d (%s): determining listener address: %v", i, j, key.Text, err)
111 }
112
102 - // associate this key with each listener address it is served on
103 - for _, addr := range addrs {
104 - addrToKeys[addr] = append(addrToKeys[addr], key)
113 + // associate this key with its protocols and each listener address served with them
114 + kwpk := keyWithParsedKey{key, parsedKey}
115 + for addr, protocols := range listeners {
116 + protocolToKeyWithParsedKeys, ok := addrToProtocolToKeyWithParsedKeys[addr]
117 + if !ok {
118 + protocolToKeyWithParsedKeys = map[string][]keyWithParsedKey{}
119 + addrToProtocolToKeyWithParsedKeys[addr] = protocolToKeyWithParsedKeys
120 + }
121 +
122 + // an empty protocol indicates the default, a nil or empty value in the ListenProtocols array
123 + if len(protocols) == 0 {
124 + protocols[""] = struct{}{}
125 + }
126 + for prot := range protocols {
127 + protocolToKeyWithParsedKeys[prot] = append(
128 + protocolToKeyWithParsedKeys[prot],
129 + kwpk)
130 + }
131 }
132 }
133
134 // make a slice of the map keys so we can iterate in sorted order
109 - addrs := make([]string, 0, len(addrToKeys))
110 - for k := range addrToKeys {
111 - addrs = append(addrs, k)
135 + addrs := make([]string, 0, len(addrToProtocolToKeyWithParsedKeys))
136 + for addr := range addrToProtocolToKeyWithParsedKeys {
137 + addrs = append(addrs, addr)
138 }
139 sort.Strings(addrs)
140
@@ -118,85 +144,132 @@ func (st *ServerType) mapAddressToServerBlocks(originalServerBlocks []serverBloc
144 // server block are only the ones which use the address; but
145 // the contents (tokens) are of course the same
146 for _, addr := range addrs {
121 - keys := addrToKeys[addr]
122 - // parse keys so that we only have to do it once
123 - parsedKeys := make([]Address, 0, len(keys))
124 - for _, key := range keys {
125 - addr, err := ParseAddress(key.Text)
126 - if err != nil {
127 - return nil, fmt.Errorf("parsing key '%s': %v", key.Text, err)
147 + protocolToKeyWithParsedKeys := addrToProtocolToKeyWithParsedKeys[addr]
148 +
149 + prots := make([]string, 0, len(protocolToKeyWithParsedKeys))
150 + for prot := range protocolToKeyWithParsedKeys {
151 + prots = append(prots, prot)
152 + }
153 + sort.Strings(prots)
154 +
155 + protocolToServerBlocks, ok := addrToProtocolToServerBlocks[addr]
156 + if !ok {
157 + protocolToServerBlocks = map[string][]serverBlock{}
158 + addrToProtocolToServerBlocks[addr] = protocolToServerBlocks
159 + }
160 +
161 + for _, prot := range prots {
162 + keyWithParsedKeys := protocolToKeyWithParsedKeys[prot]
163 +
164 + keys := make([]caddyfile.Token, len(keyWithParsedKeys))
165 + parsedKeys := make([]Address, len(keyWithParsedKeys))
166 +
167 + for k, keyWithParsedKey := range keyWithParsedKeys {
168 + keys[k] = keyWithParsedKey.key
169 + parsedKeys[k] = keyWithParsedKey.parsedKey
170 }
129 - parsedKeys = append(parsedKeys, addr.Normalize())
171 +
172 + protocolToServerBlocks[prot] = append(protocolToServerBlocks[prot], serverBlock{
173 + block: caddyfile.ServerBlock{
174 + Keys: keys,
175 + Segments: sblock.block.Segments,
176 + },
177 + pile: sblock.pile,
178 + parsedKeys: parsedKeys,
179 + })
180 }
131 - sbmap[addr] = append(sbmap[addr], serverBlock{
132 - block: caddyfile.ServerBlock{
133 - Keys: keys,
134 - Segments: sblock.block.Segments,
135 - },
136 - pile: sblock.pile,
137 - keys: parsedKeys,
138 - })
181 }
182 }
183
142 - return sbmap, nil
184 + return addrToProtocolToServerBlocks, nil
185 }
186
187 // consolidateAddrMappings eliminates repetition of identical server blocks in a mapping of
146 -// single listener addresses to lists of server blocks. Since multiple addresses may serve
147 -// identical sites (server block contents), this function turns a 1:many mapping into a
148 -// many:many mapping. Server block contents (tokens) must be exactly identical so that
149 -// reflect.DeepEqual returns true in order for the addresses to be combined. Identical
150 -// entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
188 +// single listener addresses to protocols to lists of server blocks. Since multiple addresses
189 +// may serve multiple protocols to identical sites (server block contents), this function turns
190 +// a 1:many mapping into a many:many mapping. Server block contents (tokens) must be
191 +// exactly identical so that reflect.DeepEqual returns true in order for the addresses to be combined.
192 +// Identical entries are deleted from the addrToServerBlocks map. Essentially, each pairing (each
193 // association from multiple addresses to multiple server blocks; i.e. each element of
194 // the returned slice) becomes a server definition in the output JSON.
153 -func (st *ServerType) consolidateAddrMappings(addrToServerBlocks map[string][]serverBlock) []sbAddrAssociation {
154 - sbaddrs := make([]sbAddrAssociation, 0, len(addrToServerBlocks))
155 - for addr, sblocks := range addrToServerBlocks {
156 - // we start with knowing that at least this address
157 - // maps to these server blocks
158 - a := sbAddrAssociation{
159 - addresses: []string{addr},
160 - serverBlocks: sblocks,
195 +func (st *ServerType) consolidateAddrMappings(addrToProtocolToServerBlocks map[string]map[string][]serverBlock) []sbAddrAssociation {
196 + sbaddrs := make([]sbAddrAssociation, 0, len(addrToProtocolToServerBlocks))
197 +
198 + addrs := make([]string, 0, len(addrToProtocolToServerBlocks))
199 + for addr := range addrToProtocolToServerBlocks {
200 + addrs = append(addrs, addr)
201 + }
202 + sort.Strings(addrs)
203 +
204 + for _, addr := range addrs {
205 + protocolToServerBlocks := addrToProtocolToServerBlocks[addr]
206 +
207 + prots := make([]string, 0, len(protocolToServerBlocks))
208 + for prot := range protocolToServerBlocks {
209 + prots = append(prots, prot)
210 }
211 + sort.Strings(prots)
212 +
213 + for _, prot := range prots {
214 + serverBlocks := protocolToServerBlocks[prot]
215 +
216 + // now find other addresses that map to identical
217 + // server blocks and add them to our map of listener
218 + // addresses and protocols, while removing them from
219 + // the original map
220 + listeners := map[string]map[string]struct{}{}
221 +
222 + for otherAddr, otherProtocolToServerBlocks := range addrToProtocolToServerBlocks {
223 + for otherProt, otherServerBlocks := range otherProtocolToServerBlocks {
224 + if addr == otherAddr && prot == otherProt || reflect.DeepEqual(serverBlocks, otherServerBlocks) {
225 + listener, ok := listeners[otherAddr]
226 + if !ok {
227 + listener = map[string]struct{}{}
228 + listeners[otherAddr] = listener
229 + }
230 + listener[otherProt] = struct{}{}
231 + delete(otherProtocolToServerBlocks, otherProt)
232 + }
233 + }
234 + }
235
163 - // now find other addresses that map to identical
164 - // server blocks and add them to our list of
165 - // addresses, while removing them from the map
166 - for otherAddr, otherSblocks := range addrToServerBlocks {
167 - if addr == otherAddr {
168 - continue
236 + addresses := make([]string, 0, len(listeners))
237 + for lnAddr := range listeners {
238 + addresses = append(addresses, lnAddr)
239 }
170 - if reflect.DeepEqual(sblocks, otherSblocks) {
171 - a.addresses = append(a.addresses, otherAddr)
172 - delete(addrToServerBlocks, otherAddr)
240 + sort.Strings(addresses)
241 +
242 + addressesWithProtocols := make([]addressWithProtocols, 0, len(listeners))
243 +
244 + for _, lnAddr := range addresses {
245 + lnProts := listeners[lnAddr]
246 + prots := make([]string, 0, len(lnProts))
247 + for prot := range lnProts {
248 + prots = append(prots, prot)
249 + }
250 + sort.Strings(prots)
251 +
252 + addressesWithProtocols = append(addressesWithProtocols, addressWithProtocols{
253 + address: lnAddr,
254 + protocols: prots,
255 + })
256 }
174 - }
175 - sort.Strings(a.addresses)
257
177 - sbaddrs = append(sbaddrs, a)
258 + sbaddrs = append(sbaddrs, sbAddrAssociation{
259 + addressesWithProtocols: addressesWithProtocols,
260 + serverBlocks: serverBlocks,
261 + })
262 + }
263 }
264
180 - // sort them by their first address (we know there will always be at least one)
181 - // to avoid problems with non-deterministic ordering (makes tests flaky)
182 - sort.Slice(sbaddrs, func(i, j int) bool {
183 - return sbaddrs[i].addresses[0] < sbaddrs[j].addresses[0]
184 - })
185 -
265 return sbaddrs
266 }
267
189 -// listenerAddrsForServerBlockKey essentially converts the Caddyfile
190 -// site addresses to Caddy listener addresses for each server block.
191 -func (st *ServerType) listenerAddrsForServerBlockKey(sblock serverBlock, key string,
268 +// listenersForServerBlockAddress essentially converts the Caddyfile site addresses to a map from
269 +// Caddy listener addresses and the protocols to serve them with to the parsed address for each server block.
270 +func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Address,
271 options map[string]any,
193 -) ([]string, error) {
194 - addr, err := ParseAddress(key)
195 - if err != nil {
196 - return nil, fmt.Errorf("parsing key: %v", err)
197 - }
198 - addr = addr.Normalize()
199 -
272 +) (map[string]map[string]struct{}, error) {
273 switch addr.Scheme {
274 case "wss":
275 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
303
304 // error if scheme and port combination violate convention
305 if (addr.Scheme == "http" && lnPort == httpsPort) || (addr.Scheme == "https" && lnPort == httpPort) {
233 - return nil, fmt.Errorf("[%s] scheme and port violate convention", key)
306 + return nil, fmt.Errorf("[%s] scheme and port violate convention", addr.String())
307 }
308
236 - // the bind directive specifies hosts (and potentially network), but is optional
237 - lnHosts := make([]string, 0, len(sblock.pile["bind"]))
309 + // the bind directive specifies hosts (and potentially network), and the protocols to serve them with, but is optional
310 + lnCfgVals := make([]addressesWithProtocols, 0, len(sblock.pile["bind"]))
311 for _, cfgVal := range sblock.pile["bind"] {
239 - lnHosts = append(lnHosts, cfgVal.Value.([]string)...)
312 + if val, ok := cfgVal.Value.(addressesWithProtocols); ok {
313 + lnCfgVals = append(lnCfgVals, val)
314 + }
315 }
241 - if len(lnHosts) == 0 {
242 - if defaultBind, ok := options["default_bind"].([]string); ok {
243 - lnHosts = defaultBind
316 + if len(lnCfgVals) == 0 {
317 + if defaultBindValues, ok := options["default_bind"].([]ConfigValue); ok {
318 + for _, defaultBindValue := range defaultBindValues {
319 + lnCfgVals = append(lnCfgVals, defaultBindValue.Value.(addressesWithProtocols))
320 + }
321 } else {
245 - lnHosts = []string{""}
322 + lnCfgVals = []addressesWithProtocols{{
323 + addresses: []string{""},
324 + protocols: nil,
325 + }}
326 }
327 }
328
329 // use a map to prevent duplication
250 - listeners := make(map[string]struct{})
251 - for _, lnHost := range lnHosts {
252 - // normally we would simply append the port,
253 - // but if lnHost is IPv6, we need to ensure it
254 - // is enclosed in [ ]; net.JoinHostPort does
255 - // this for us, but lnHost might also have a
256 - // network type in front (e.g. "tcp/") leading
257 - // to "[tcp/::1]" which causes parsing failures
258 - // later; what we need is "tcp/[::1]", so we have
259 - // to split the network and host, then re-combine
260 - network, host, ok := strings.Cut(lnHost, "/")
261 - if !ok {
262 - host = network
263 - network = ""
264 - }
265 - host = strings.Trim(host, "[]") // IPv6
266 - networkAddr := caddy.JoinNetworkAddress(network, host, lnPort)
267 - addr, err := caddy.ParseNetworkAddress(networkAddr)
268 - if err != nil {
269 - return nil, fmt.Errorf("parsing network address: %v", err)
330 + listeners := map[string]map[string]struct{}{}
331 + for _, lnCfgVal := range lnCfgVals {
332 + for _, lnHost := range lnCfgVal.addresses {
333 + networkAddr, err := caddy.ParseNetworkAddressFromHostPort(lnHost, lnPort)
334 + if err != nil {
335 + return nil, fmt.Errorf("parsing network address: %v", err)
336 + }
337 + if _, ok := listeners[addr.String()]; !ok {
338 + listeners[networkAddr.String()] = map[string]struct{}{}
339 + }
340 + for _, protocol := range lnCfgVal.protocols {
341 + listeners[networkAddr.String()][protocol] = struct{}{}
342 + }
343 }
271 - listeners[addr.String()] = struct{}{}
344 }
345
274 - // now turn map into list
275 - listenersList := make([]string, 0, len(listeners))
276 - for lnStr := range listeners {
277 - listenersList = append(listenersList, lnStr)
278 - }
279 - sort.Strings(listenersList)
346 + return listeners, nil
347 +}
348
281 - return listenersList, nil
349 +// addressesWithProtocols associates a list of listen addresses
350 +// with a list of protocols to serve them with
351 +type addressesWithProtocols struct {
352 + addresses []string
353 + protocols []string
354 }
355
356 // Address represents a site address. It contains
caddyconfig/httpcaddyfile/builtins.go
+22 -2
@@ -56,10 +56,30 @@ func init() {
56
57 // parseBind parses the bind directive. Syntax:
58 //
59 -// bind <addresses...>
59 +// bind <addresses...> [{
60 +// protocols [h1|h2|h2c|h3] [...]
61 +// }]
62 func parseBind(h Helper) ([]ConfigValue, error) {
63 h.Next() // consume directive name
62 - return []ConfigValue{{Class: "bind", Value: h.RemainingArgs()}}, nil
64 + var addresses, protocols []string
65 + addresses = h.RemainingArgs()
66 +
67 + for h.NextBlock(0) {
68 + switch h.Val() {
69 + case "protocols":
70 + protocols = h.RemainingArgs()
71 + if len(protocols) == 0 {
72 + return nil, h.Errf("protocols requires one or more arguments")
73 + }
74 + default:
75 + return nil, h.Errf("unknown subdirective: %s", h.Val())
76 + }
77 + }
78 +
79 + return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{
80 + addresses: addresses,
81 + protocols: protocols,
82 + }}}, nil
83 }
84
85 // parseTLS parses the tls directive. Syntax:
caddyconfig/httpcaddyfile/directives.go
+7 -7
@@ -516,9 +516,9 @@ func sortRoutes(routes []ConfigValue) {
516 // a "pile" of config values, keyed by class name,
517 // as well as its parsed keys for convenience.
518 type serverBlock struct {
519 - block caddyfile.ServerBlock
520 - pile map[string][]ConfigValue // config values obtained from directives
521 - keys []Address
519 + block caddyfile.ServerBlock
520 + pile map[string][]ConfigValue // config values obtained from directives
521 + parsedKeys []Address
522 }
523
524 // hostsFromKeys returns a list of all the non-empty hostnames found in
@@ -535,7 +535,7 @@ type serverBlock struct {
535 func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
536 // ensure each entry in our list is unique
537 hostMap := make(map[string]struct{})
538 - for _, addr := range sb.keys {
538 + for _, addr := range sb.parsedKeys {
539 if addr.Host == "" {
540 if !loggerMode {
541 // server block contains a key like ":443", i.e. the host portion
@@ -567,7 +567,7 @@ func (sb serverBlock) hostsFromKeys(loggerMode bool) []string {
567 func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
568 // ensure each entry in our list is unique
569 hostMap := make(map[string]struct{})
570 - for _, addr := range sb.keys {
570 + for _, addr := range sb.parsedKeys {
571 if addr.Host == "" {
572 continue
573 }
@@ -588,7 +588,7 @@ func (sb serverBlock) hostsFromKeysNotHTTP(httpPort string) []string {
588 // hasHostCatchAllKey returns true if sb has a key that
589 // omits a host portion, i.e. it "catches all" hosts.
590 func (sb serverBlock) hasHostCatchAllKey() bool {
591 - return slices.ContainsFunc(sb.keys, func(addr Address) bool {
591 + return slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool {
592 return addr.Host == ""
593 })
594 }
@@ -596,7 +596,7 @@ func (sb serverBlock) hasHostCatchAllKey() bool {
596 // isAllHTTP returns true if all sb keys explicitly specify
597 // the http:// scheme
598 func (sb serverBlock) isAllHTTP() bool {
599 - return !slices.ContainsFunc(sb.keys, func(addr Address) bool {
599 + return !slices.ContainsFunc(sb.parsedKeys, func(addr Address) bool {
600 return addr.Scheme != "http"
601 })
602 }
caddyconfig/httpcaddyfile/directives_test.go
+1 -1
@@ -78,7 +78,7 @@ func TestHostsFromKeys(t *testing.T) {
78 []string{"example.com:2015"},
79 },
80 } {
81 - sb := serverBlock{keys: tc.keys}
81 + sb := serverBlock{parsedKeys: tc.keys}
82
83 // test in normal mode
84 actual := sb.hostsFromKeys(false)
caddyconfig/httpcaddyfile/httptype.go
+66 -14
@@ -171,7 +171,7 @@ func (st ServerType) Setup(
171 }
172
173 // map
174 - sbmap, err := st.mapAddressToServerBlocks(originalServerBlocks, options)
174 + sbmap, err := st.mapAddressToProtocolToServerBlocks(originalServerBlocks, options)
175 if err != nil {
176 return nil, warnings, err
177 }
@@ -402,6 +402,20 @@ func (ServerType) evaluateGlobalOptionsBlock(serverBlocks []serverBlock, options
402 options[opt] = append(existingOpts, logOpts...)
403 continue
404 }
405 + // Also fold multiple "default_bind" options together into an
406 + // array so that server blocks can have multiple binds by default.
407 + if opt == "default_bind" {
408 + existingOpts, ok := options[opt].([]ConfigValue)
409 + if !ok {
410 + existingOpts = []ConfigValue{}
411 + }
412 + defaultBindOpts, ok := val.([]ConfigValue)
413 + if !ok {
414 + return nil, fmt.Errorf("unexpected type from 'default_bind' global options: %T", val)
415 + }
416 + options[opt] = append(existingOpts, defaultBindOpts...)
417 + continue
418 + }
419
420 options[opt] = val
421 }
@@ -543,8 +557,40 @@ func (st *ServerType) serversFromPairings(
557 }
558 }
559
560 + var (
561 + addresses []string
562 + protocols [][]string
563 + )
564 +
565 + for _, addressWithProtocols := range p.addressesWithProtocols {
566 + addresses = append(addresses, addressWithProtocols.address)
567 + protocols = append(protocols, addressWithProtocols.protocols)
568 + }
569 +
570 srv := &caddyhttp.Server{
547 - Listen: p.addresses,
571 + Listen: addresses,
572 + ListenProtocols: protocols,
573 + }
574 +
575 + // remove srv.ListenProtocols[j] if it only contains the default protocols
576 + for j, lnProtocols := range srv.ListenProtocols {
577 + srv.ListenProtocols[j] = nil
578 + for _, lnProtocol := range lnProtocols {
579 + if lnProtocol != "" {
580 + srv.ListenProtocols[j] = lnProtocols
581 + break
582 + }
583 + }
584 + }
585 +
586 + // remove srv.ListenProtocols if it only contains the default protocols for all listen addresses
587 + listenProtocols := srv.ListenProtocols
588 + srv.ListenProtocols = nil
589 + for _, lnProtocols := range listenProtocols {
590 + if lnProtocols != nil {
591 + srv.ListenProtocols = listenProtocols
592 + break
593 + }
594 }
595
596 // handle the auto_https global option
@@ -566,7 +612,7 @@ func (st *ServerType) serversFromPairings(
612 // See ParseAddress() where parsing should later reject paths
613 // See https://github.com/caddyserver/caddy/pull/4728 for a full explanation
614 for _, sblock := range p.serverBlocks {
569 - for _, addr := range sblock.keys {
615 + for _, addr := range sblock.parsedKeys {
616 if addr.Path != "" {
617 caddy.Log().Named("caddyfile").Warn("Using a path in a site address is deprecated; please use the 'handle' directive instead", zap.String("address", addr.String()))
618 }
@@ -584,7 +630,7 @@ func (st *ServerType) serversFromPairings(
630 var iLongestPath, jLongestPath string
631 var iLongestHost, jLongestHost string
632 var iWildcardHost, jWildcardHost bool
587 - for _, addr := range p.serverBlocks[i].keys {
633 + for _, addr := range p.serverBlocks[i].parsedKeys {
634 if strings.Contains(addr.Host, "*") || addr.Host == "" {
635 iWildcardHost = true
636 }
@@ -595,7 +641,7 @@ func (st *ServerType) serversFromPairings(
641 iLongestPath = addr.Path
642 }
643 }
598 - for _, addr := range p.serverBlocks[j].keys {
644 + for _, addr := range p.serverBlocks[j].parsedKeys {
645 if strings.Contains(addr.Host, "*") || addr.Host == "" {
646 jWildcardHost = true
647 }
@@ -711,7 +757,7 @@ func (st *ServerType) serversFromPairings(
757 }
758 }
759
714 - for _, addr := range sblock.keys {
760 + for _, addr := range sblock.parsedKeys {
761 // if server only uses HTTP port, auto-HTTPS will not apply
762 if listenersUseAnyPortOtherThan(srv.Listen, httpPort) {
763 // exclude any hosts that were defined explicitly with "http://"
@@ -886,8 +932,7 @@ func (st *ServerType) serversFromPairings(
932 servers[fmt.Sprintf("srv%d", i)] = srv
933 }
934
889 - err := applyServerOptions(servers, options, warnings)
890 - if err != nil {
935 + if err := applyServerOptions(servers, options, warnings); err != nil {
936 return nil, fmt.Errorf("applying global server options: %v", err)
937 }
938
@@ -932,7 +977,7 @@ func detectConflictingSchemes(srv *caddyhttp.Server, serverBlocks []serverBlock,
977 }
978
979 for _, sblock := range serverBlocks {
935 - for _, addr := range sblock.keys {
980 + for _, addr := range sblock.parsedKeys {
981 if addr.Scheme == "http" || addr.Port == httpPort {
982 if err := checkAndSetHTTP(addr); err != nil {
983 return err
@@ -1322,7 +1367,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod
1367 var matcherPairs []*hostPathPair
1368
1369 var catchAllHosts bool
1325 - for _, addr := range sblock.keys {
1370 + for _, addr := range sblock.parsedKeys {
1371 // choose a matcher pair that should be shared by this
1372 // server block; if none exists yet, create one
1373 var chosenMatcherPair *hostPathPair
@@ -1594,12 +1639,19 @@ type namedCustomLog struct {
1639 noHostname bool
1640 }
1641
1642 +// addressWithProtocols associates a listen address with
1643 +// the protocols to serve it with
1644 +type addressWithProtocols struct {
1645 + address string
1646 + protocols []string
1647 +}
1648 +
1649 // sbAddrAssociation is a mapping from a list of
1598 -// addresses to a list of server blocks that are
1599 -// served on those addresses.
1650 +// addresses with protocols, and a list of server
1651 +// blocks that are served on those addresses.
1652 type sbAddrAssociation struct {
1601 - addresses []string
1602 - serverBlocks []serverBlock
1653 + addressesWithProtocols []addressWithProtocols
1654 + serverBlocks []serverBlock
1655 }
1656
1657 const (
caddyconfig/httpcaddyfile/options.go
+25 -6
@@ -31,7 +31,7 @@ func init() {
31 RegisterGlobalOption("debug", parseOptTrue)
32 RegisterGlobalOption("http_port", parseOptHTTPPort)
33 RegisterGlobalOption("https_port", parseOptHTTPSPort)
34 - RegisterGlobalOption("default_bind", parseOptStringList)
34 + RegisterGlobalOption("default_bind", parseOptDefaultBind)
35 RegisterGlobalOption("grace_period", parseOptDuration)
36 RegisterGlobalOption("shutdown_delay", parseOptDuration)
37 RegisterGlobalOption("default_sni", parseOptSingleString)
@@ -284,13 +284,32 @@ func parseOptSingleString(d *caddyfile.Dispenser, _ any) (any, error) {
284 return val, nil
285 }
286
287 -func parseOptStringList(d *caddyfile.Dispenser, _ any) (any, error) {
287 +func parseOptDefaultBind(d *caddyfile.Dispenser, _ any) (any, error) {
288 d.Next() // consume option name
289 - val := d.RemainingArgs()
290 - if len(val) == 0 {
291 - return "", d.ArgErr()
289 +
290 + var addresses, protocols []string
291 + addresses = d.RemainingArgs()
292 +
293 + if len(addresses) == 0 {
294 + addresses = append(addresses, "")
295 }
293 - return val, nil
296 +
297 + for d.NextBlock(0) {
298 + switch d.Val() {
299 + case "protocols":
300 + protocols = d.RemainingArgs()
301 + if len(protocols) == 0 {
302 + return nil, d.Errf("protocols requires one or more arguments")
303 + }
304 + default:
305 + return nil, d.Errf("unknown subdirective: %s", d.Val())
306 + }
307 + }
308 +
309 + return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{
310 + addresses: addresses,
311 + protocols: protocols,
312 + }}}, nil
313 }
314
315 func parseOptAdmin(d *caddyfile.Dispenser, _ any) (any, error) {
caddyconfig/httpcaddyfile/tlsapp.go
+9 -5
@@ -57,13 +57,13 @@ func (st ServerType) buildTLSApp(
57 if autoHTTPS != "off" {
58 for _, pair := range pairings {
59 for _, sb := range pair.serverBlocks {
60 - for _, addr := range sb.keys {
60 + for _, addr := range sb.parsedKeys {
61 if addr.Host != "" {
62 continue
63 }
64 // this server block has a hostless key, now
65 // go through and add all the hosts to the set
66 - for _, otherAddr := range sb.keys {
66 + for _, otherAddr := range sb.parsedKeys {
67 if otherAddr.Original == addr.Original {
68 continue
69 }
@@ -93,7 +93,11 @@ func (st ServerType) buildTLSApp(
93
94 for _, p := range pairings {
95 // avoid setting up TLS automation policies for a server that is HTTP-only
96 - if !listenersUseAnyPortOtherThan(p.addresses, httpPort) {
96 + var addresses []string
97 + for _, addressWithProtocols := range p.addressesWithProtocols {
98 + addresses = append(addresses, addressWithProtocols.address)
99 + }
100 + if !listenersUseAnyPortOtherThan(addresses, httpPort) {
101 continue
102 }
103
@@ -183,8 +187,8 @@ func (st ServerType) buildTLSApp(
187 if acmeIssuer.Challenges.BindHost == "" {
188 // only binding to one host is supported
189 var bindHost string
186 - if bindHosts, ok := cfgVal.Value.([]string); ok && len(bindHosts) > 0 {
187 - bindHost = bindHosts[0]
190 + if asserted, ok := cfgVal.Value.(addressesWithProtocols); ok && len(asserted.addresses) > 0 {
191 + bindHost = asserted.addresses[0]
192 }
193 acmeIssuer.Challenges.BindHost = bindHost
194 }
caddytest/integration/caddyfile_adapt/bind_fd_fdgram_h123.caddyfiletest new
+142
@@ -0,0 +1,142 @@
1 +{
2 + auto_https disable_redirects
3 + admin off
4 +}
5 +
6 +http://localhost {
7 + bind fd/{env.CADDY_HTTP_FD} {
8 + protocols h1
9 + }
10 + log
11 + respond "Hello, HTTP!"
12 +}
13 +
14 +https://localhost {
15 + bind fd/{env.CADDY_HTTPS_FD} {
16 + protocols h1 h2
17 + }
18 + bind fdgram/{env.CADDY_HTTP3_FD} {
19 + protocols h3
20 + }
21 + log
22 + respond "Hello, HTTPS!"
23 +}
24 +----------
25 +{
26 + "admin": {
27 + "disabled": true
28 + },
29 + "apps": {
30 + "http": {
31 + "servers": {
32 + "srv0": {
33 + "listen": [
34 + "fd/{env.CADDY_HTTPS_FD}",
35 + "fdgram/{env.CADDY_HTTP3_FD}"
36 + ],
37 + "routes": [
38 + {
39 + "match": [
40 + {
41 + "host": [
42 + "localhost"
43 + ]
44 + }
45 + ],
46 + "handle": [
47 + {
48 + "handler": "subroute",
49 + "routes": [
50 + {
51 + "handle": [
52 + {
53 + "body": "Hello, HTTPS!",
54 + "handler": "static_response"
55 + }
56 + ]
57 + }
58 + ]
59 + }
60 + ],
61 + "terminal": true
62 + }
63 + ],
64 + "automatic_https": {
65 + "disable_redirects": true
66 + },
67 + "logs": {
68 + "logger_names": {
69 + "localhost": [
70 + ""
71 + ]
72 + }
73 + },
74 + "listen_protocols": [
75 + [
76 + "h1",
77 + "h2"
78 + ],
79 + [
80 + "h3"
81 + ]
82 + ]
83 + },
84 + "srv1": {
85 + "automatic_https": {
86 + "disable_redirects": true
87 + }
88 + },
89 + "srv2": {
90 + "listen": [
91 + "fd/{env.CADDY_HTTP_FD}"
92 + ],
93 + "routes": [
94 + {
95 + "match": [
96 + {
97 + "host": [
98 + "localhost"
99 + ]
100 + }
101 + ],
102 + "handle": [
103 + {
104 + "handler": "subroute",
105 + "routes": [
106 + {
107 + "handle": [
108 + {
109 + "body": "Hello, HTTP!",
110 + "handler": "static_response"
111 + }
112 + ]
113 + }
114 + ]
115 + }
116 + ],
117 + "terminal": true
118 + }
119 + ],
120 + "automatic_https": {
121 + "disable_redirects": true,
122 + "skip": [
123 + "localhost"
124 + ]
125 + },
126 + "logs": {
127 + "logger_names": {
128 + "localhost": [
129 + ""
130 + ]
131 + }
132 + },
133 + "listen_protocols": [
134 + [
135 + "h1"
136 + ]
137 + ]
138 + }
139 + }
140 + }
141 + }
142 +}
cmd/commandfuncs.go
+7 -5
@@ -660,6 +660,8 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
660 return nil, err
661 }
662 parsedAddr.Host = addr
663 + } else if parsedAddr.IsFdNetwork() {
664 + origin = "http://127.0.0.1"
665 }
666
667 // form the request
@@ -667,13 +669,13 @@ func AdminAPIRequest(adminAddr, method, uri string, headers http.Header, body io
669 if err != nil {
670 return nil, fmt.Errorf("making request: %v", err)
671 }
670 - if parsedAddr.IsUnixNetwork() {
672 + if parsedAddr.IsUnixNetwork() || parsedAddr.IsFdNetwork() {
673 // We used to conform to RFC 2616 Section 14.26 which requires
674 // an empty host header when there is no host, as is the case
673 - // with unix sockets. However, Go required a Host value so we
674 - // used a hack of a space character as the host (it would see
675 - // the Host was non-empty, then trim the space later). As of
676 - // Go 1.20.6 (July 2023), this hack no longer works. See:
675 + // with unix sockets and socket fds. However, Go required a
676 + // Host value so we used a hack of a space character as the host
677 + // (it would see the Host was non-empty, then trim the space later).
678 + // As of Go 1.20.6 (July 2023), this hack no longer works. See:
679 // https://github.com/golang/go/issues/60374
680 // See also the discussion here:
681 // https://github.com/golang/go/issues/61431
cmd/main_test.go
+3 -3
@@ -235,7 +235,7 @@ func Test_isCaddyfile(t *testing.T) {
235 wantErr: false,
236 },
237 {
238 -
238 +
239 name: "json is not caddyfile but not error",
240 args: args{
241 configFile: "./Caddyfile.json",
@@ -245,7 +245,7 @@ func Test_isCaddyfile(t *testing.T) {
245 wantErr: false,
246 },
247 {
248 -
248 +
249 name: "prefix of Caddyfile and ./ with any extension is Caddyfile",
250 args: args{
251 configFile: "./Caddyfile.prd",
@@ -255,7 +255,7 @@ func Test_isCaddyfile(t *testing.T) {
255 wantErr: false,
256 },
257 {
258 -
258 +
259 name: "prefix of Caddyfile without ./ with any extension is Caddyfile",
260 args: args{
261 configFile: "Caddyfile.prd",
listen.go
+68 -12
@@ -18,7 +18,11 @@ package caddy
18
19 import (
20 "context"
21 + "fmt"
22 "net"
23 + "os"
24 + "slices"
25 + "strconv"
26 "sync"
27 "sync/atomic"
28 "time"
@@ -31,10 +35,49 @@ func reuseUnixSocket(network, addr string) (any, error) {
35 }
36
37 func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
34 - switch network {
35 - case "udp", "udp4", "udp6", "unixgram":
38 + var socketFile *os.File
39 +
40 + fd := slices.Contains([]string{"fd", "fdgram"}, network)
41 + if fd {
42 + socketFd, err := strconv.ParseUint(address, 0, strconv.IntSize)
43 + if err != nil {
44 + return nil, fmt.Errorf("invalid file descriptor: %v", err)
45 + }
46 +
47 + func() {
48 + socketFilesMu.Lock()
49 + defer socketFilesMu.Unlock()
50 +
51 + socketFdWide := uintptr(socketFd)
52 + var ok bool
53 +
54 + socketFile, ok = socketFiles[socketFdWide]
55 +
56 + if !ok {
57 + socketFile = os.NewFile(socketFdWide, lnKey)
58 + if socketFile != nil {
59 + socketFiles[socketFdWide] = socketFile
60 + }
61 + }
62 + }()
63 +
64 + if socketFile == nil {
65 + return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd)
66 + }
67 + }
68 +
69 + datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network)
70 + if datagram {
71 sharedPc, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
37 - pc, err := config.ListenPacket(ctx, network, address)
72 + var (
73 + pc net.PacketConn
74 + err error
75 + )
76 + if fd {
77 + pc, err = net.FilePacketConn(socketFile)
78 + } else {
79 + pc, err = config.ListenPacket(ctx, network, address)
80 + }
81 if err != nil {
82 return nil, err
83 }
@@ -44,20 +87,27 @@ func listenReusable(ctx context.Context, lnKey string, network, address string,
87 return nil, err
88 }
89 return &fakeClosePacketConn{sharedPacketConn: sharedPc.(*sharedPacketConn)}, nil
90 + }
91
48 - default:
49 - sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
50 - ln, err := config.Listen(ctx, network, address)
51 - if err != nil {
52 - return nil, err
53 - }
54 - return &sharedListener{Listener: ln, key: lnKey}, nil
55 - })
92 + sharedLn, _, err := listenerPool.LoadOrNew(lnKey, func() (Destructor, error) {
93 + var (
94 + ln net.Listener
95 + err error
96 + )
97 + if fd {
98 + ln, err = net.FileListener(socketFile)
99 + } else {
100 + ln, err = config.Listen(ctx, network, address)
101 + }
102 if err != nil {
103 return nil, err
104 }
59 - return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil
105 + return &sharedListener{Listener: ln, key: lnKey}, nil
106 + })
107 + if err != nil {
108 + return nil, err
109 }
110 + return &fakeCloseListener{sharedListener: sharedLn.(*sharedListener), keepAlivePeriod: config.KeepAlive}, nil
111 }
112
113 // fakeCloseListener is a private wrapper over a listener that
@@ -260,3 +310,9 @@ var (
310 Unwrap() net.PacketConn
311 }) = (*fakeClosePacketConn)(nil)
312 )
313 +
314 +// socketFiles is a fd -> *os.File map used to make a FileListener/FilePacketConn from a socket file descriptor.
315 +var socketFiles = map[uintptr]*os.File{}
316 +
317 +// socketFilesMu synchronizes socketFiles insertions
318 +var socketFilesMu sync.Mutex
listen_unix.go
+121 -51
@@ -22,10 +22,14 @@ package caddy
22 import (
23 "context"
24 "errors"
25 + "fmt"
26 "io"
27 "io/fs"
28 "net"
29 "os"
30 + "slices"
31 + "strconv"
32 + "sync"
33 "sync/atomic"
34 "syscall"
35
@@ -34,12 +38,9 @@ import (
38 )
39
40 // reuseUnixSocket copies and reuses the unix domain socket (UDS) if we already
37 -// have it open; if not, unlink it so we can have it. No-op if not a unix network.
41 +// have it open; if not, unlink it so we can have it.
42 +// No-op if not a unix network.
43 func reuseUnixSocket(network, addr string) (any, error) {
39 - if !IsUnixNetwork(network) {
40 - return nil, nil
41 - }
42 -
44 socketKey := listenerKey(network, addr)
45
46 socket, exists := unixSockets[socketKey]
@@ -71,7 +72,7 @@ func reuseUnixSocket(network, addr string) (any, error) {
72 return nil, err
73 }
74 atomic.AddInt32(unixSocket.count, 1)
74 - unixSockets[socketKey] = &unixConn{pc.(*net.UnixConn), addr, socketKey, unixSocket.count}
75 + unixSockets[socketKey] = &unixConn{pc.(*net.UnixConn), socketKey, unixSocket.count}
76 }
77
78 return unixSockets[socketKey], nil
@@ -89,56 +90,107 @@ func reuseUnixSocket(network, addr string) (any, error) {
90 return nil, nil
91 }
92
93 +// listenReusable creates a new listener for the given network and address, and adds it to listenerPool.
94 func listenReusable(ctx context.Context, lnKey string, network, address string, config net.ListenConfig) (any, error) {
93 - // wrap any Control function set by the user so we can also add our reusePort control without clobbering theirs
94 - oldControl := config.Control
95 - config.Control = func(network, address string, c syscall.RawConn) error {
96 - if oldControl != nil {
97 - if err := oldControl(network, address, c); err != nil {
98 - return err
99 - }
100 - }
101 - return reusePort(network, address, c)
102 - }
103 -
95 // even though SO_REUSEPORT lets us bind the socket multiple times,
96 // we still put it in the listenerPool so we can count how many
97 // configs are using this socket; necessary to ensure we can know
98 // whether to enforce shutdown delays, for example (see #5393).
108 - var ln io.Closer
109 - var err error
110 - switch network {
111 - case "udp", "udp4", "udp6", "unixgram":
112 - ln, err = config.ListenPacket(ctx, network, address)
113 - default:
114 - ln, err = config.Listen(ctx, network, address)
115 - }
116 - if err == nil {
117 - listenerPool.LoadOrStore(lnKey, nil)
99 + var (
100 + ln io.Closer
101 + err error
102 + socketFile *os.File
103 + )
104 +
105 + fd := slices.Contains([]string{"fd", "fdgram"}, network)
106 + if fd {
107 + socketFd, err := strconv.ParseUint(address, 0, strconv.IntSize)
108 + if err != nil {
109 + return nil, fmt.Errorf("invalid file descriptor: %v", err)
110 + }
111 +
112 + func() {
113 + socketFilesMu.Lock()
114 + defer socketFilesMu.Unlock()
115 +
116 + socketFdWide := uintptr(socketFd)
117 + var ok bool
118 +
119 + socketFile, ok = socketFiles[socketFdWide]
120 +
121 + if !ok {
122 + socketFile = os.NewFile(socketFdWide, lnKey)
123 + if socketFile != nil {
124 + socketFiles[socketFdWide] = socketFile
125 + }
126 + }
127 + }()
128 +
129 + if socketFile == nil {
130 + return nil, fmt.Errorf("invalid socket file descriptor: %d", socketFd)
131 + }
132 + } else {
133 + // wrap any Control function set by the user so we can also add our reusePort control without clobbering theirs
134 + oldControl := config.Control
135 + config.Control = func(network, address string, c syscall.RawConn) error {
136 + if oldControl != nil {
137 + if err := oldControl(network, address, c); err != nil {
138 + return err
139 + }
140 + }
141 + return reusePort(network, address, c)
142 + }
143 }
144
120 - // if new listener is a unix socket, make sure we can reuse it later
121 - // (we do our own "unlink on close" -- not required, but more tidy)
122 - one := int32(1)
123 - if unix, ok := ln.(*net.UnixListener); ok {
124 - unix.SetUnlinkOnClose(false)
125 - ln = &unixListener{unix, lnKey, &one}
126 - unixSockets[lnKey] = ln.(*unixListener)
145 + datagram := slices.Contains([]string{"udp", "udp4", "udp6", "unixgram", "fdgram"}, network)
146 + if datagram {
147 + if fd {
148 + ln, err = net.FilePacketConn(socketFile)
149 + } else {
150 + ln, err = config.ListenPacket(ctx, network, address)
151 + }
152 + } else {
153 + if fd {
154 + ln, err = net.FileListener(socketFile)
155 + } else {
156 + ln, err = config.Listen(ctx, network, address)
157 + }
158 }
159
129 - // TODO: Not 100% sure this is necessary, but we do this for net.UnixListener in listen_unix.go, so...
130 - if unix, ok := ln.(*net.UnixConn); ok {
131 - ln = &unixConn{unix, address, lnKey, &one}
132 - unixSockets[lnKey] = ln.(*unixConn)
160 + if err == nil {
161 + listenerPool.LoadOrStore(lnKey, nil)
162 }
163
135 - // lightly wrap the listener so that when it is closed,
136 - // we can decrement the usage pool counter
137 - switch specificLn := ln.(type) {
138 - case net.Listener:
139 - return deleteListener{specificLn, lnKey}, err
140 - case net.PacketConn:
141 - return deletePacketConn{specificLn, lnKey}, err
164 + if datagram {
165 + if !fd {
166 + // TODO: Not 100% sure this is necessary, but we do this for net.UnixListener, so...
167 + if unix, ok := ln.(*net.UnixConn); ok {
168 + one := int32(1)
169 + ln = &unixConn{unix, lnKey, &one}
170 + unixSockets[lnKey] = ln.(*unixConn)
171 + }
172 + }
173 + // lightly wrap the connection so that when it is closed,
174 + // we can decrement the usage pool counter
175 + if specificLn, ok := ln.(net.PacketConn); ok {
176 + ln = deletePacketConn{specificLn, lnKey}
177 + }
178 + } else {
179 + if !fd {
180 + // if new listener is a unix socket, make sure we can reuse it later
181 + // (we do our own "unlink on close" -- not required, but more tidy)
182 + if unix, ok := ln.(*net.UnixListener); ok {
183 + unix.SetUnlinkOnClose(false)
184 + one := int32(1)
185 + ln = &unixListener{unix, lnKey, &one}
186 + unixSockets[lnKey] = ln.(*unixListener)
187 + }
188 + }
189 + // lightly wrap the listener so that when it is closed,
190 + // we can decrement the usage pool counter
191 + if specificLn, ok := ln.(net.Listener); ok {
192 + ln = deleteListener{specificLn, lnKey}
193 + }
194 }
195
196 // other types, I guess we just return them directly
@@ -170,12 +222,18 @@ type unixListener struct {
222 func (uln *unixListener) Close() error {
223 newCount := atomic.AddInt32(uln.count, -1)
224 if newCount == 0 {
225 + file, err := uln.File()
226 + var name string
227 + if err == nil {
228 + name = file.Name()
229 + }
230 defer func() {
174 - addr := uln.Addr().String()
231 unixSocketsMu.Lock()
232 delete(unixSockets, uln.mapKey)
233 unixSocketsMu.Unlock()
178 - _ = syscall.Unlink(addr)
234 + if err == nil {
235 + _ = syscall.Unlink(name)
236 + }
237 }()
238 }
239 return uln.UnixListener.Close()
@@ -183,19 +241,25 @@ func (uln *unixListener) Close() error {
241
242 type unixConn struct {
243 *net.UnixConn
186 - filename string
187 - mapKey string
188 - count *int32 // accessed atomically
244 + mapKey string
245 + count *int32 // accessed atomically
246 }
247
248 func (uc *unixConn) Close() error {
249 newCount := atomic.AddInt32(uc.count, -1)
250 if newCount == 0 {
251 + file, err := uc.File()
252 + var name string
253 + if err == nil {
254 + name = file.Name()
255 + }
256 defer func() {
257 unixSocketsMu.Lock()
258 delete(unixSockets, uc.mapKey)
259 unixSocketsMu.Unlock()
198 - _ = syscall.Unlink(uc.filename)
260 + if err == nil {
261 + _ = syscall.Unlink(name)
262 + }
263 }()
264 }
265 return uc.UnixConn.Close()
@@ -211,6 +275,12 @@ var unixSockets = make(map[string]interface {
275 File() (*os.File, error)
276 })
277
278 +// socketFiles is a fd -> *os.File map used to make a FileListener/FilePacketConn from a socket file descriptor.
279 +var socketFiles = map[uintptr]*os.File{}
280 +
281 +// socketFilesMu synchronizes socketFiles insertions
282 +var socketFilesMu sync.Mutex
283 +
284 // deleteListener is a type that simply deletes itself
285 // from the listenerPool when it closes. It is used
286 // solely for the purpose of reference counting (i.e.
listeners.go
+77 -36
@@ -58,7 +58,7 @@ type NetworkAddress struct {
58 EndPort uint
59 }
60
61 -// ListenAll calls Listen() for all addresses represented by this struct, i.e. all ports in the range.
61 +// ListenAll calls Listen for all addresses represented by this struct, i.e. all ports in the range.
62 // (If the address doesn't use ports or has 1 port only, then only 1 listener will be created.)
63 // It returns an error if any listener failed to bind, and closes any listeners opened up to that point.
64 func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig) ([]any, error) {
@@ -106,7 +106,8 @@ func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig)
106 // portOffset to the start port. (For network types that do not use ports, the
107 // portOffset is ignored.)
108 //
109 -// The provided ListenConfig is used to create the listener. Its Control function,
109 +// First Listen checks if a plugin can provide a listener from this address. Otherwise,
110 +// the provided ListenConfig is used to create the listener. Its Control function,
111 // if set, may be wrapped by an internally-used Control function. The provided
112 // context may be used to cancel long operations early. The context is not used
113 // to close the listener after it has been created.
@@ -129,6 +130,8 @@ func (na NetworkAddress) ListenAll(ctx context.Context, config net.ListenConfig)
130 // Unix sockets will be unlinked before being created, to ensure we can bind to
131 // it even if the previous program using it exited uncleanly; it will also be
132 // unlinked upon a graceful exit (or when a new config does not use that socket).
133 +// Listen synchronizes binds to unix domain sockets to avoid race conditions
134 +// while an existing socket is unlinked.
135 func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
136 if na.IsUnixNetwork() {
137 unixSocketsMu.Lock()
@@ -146,54 +149,53 @@ func (na NetworkAddress) Listen(ctx context.Context, portOffset uint, config net
149
150 func (na NetworkAddress) listen(ctx context.Context, portOffset uint, config net.ListenConfig) (any, error) {
151 var (
149 - ln any
150 - err error
151 - address string
152 - unixFileMode fs.FileMode
153 - isAbstractUnixSocket bool
152 + ln any
153 + err error
154 + address string
155 + unixFileMode fs.FileMode
156 )
157
158 // split unix socket addr early so lnKey
159 // is independent of permissions bits
160 if na.IsUnixNetwork() {
159 - var err error
161 address, unixFileMode, err = internal.SplitUnixSocketPermissionsBits(na.Host)
162 if err != nil {
163 return nil, err
164 }
164 - isAbstractUnixSocket = strings.HasPrefix(address, "@")
165 + } else if na.IsFdNetwork() {
166 + address = na.Host
167 } else {
168 address = na.JoinHostPort(portOffset)
169 }
170
169 - // if this is a unix socket, see if we already have it open,
170 - // force socket permissions on it and return early
171 - if socket, err := reuseUnixSocket(na.Network, address); socket != nil || err != nil {
172 - if !isAbstractUnixSocket {
173 - if err := os.Chmod(address, unixFileMode); err != nil {
174 - return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
175 - }
176 - }
177 - return socket, err
178 - }
179 -
180 - lnKey := listenerKey(na.Network, address)
181 -
171 if strings.HasPrefix(na.Network, "ip") {
172 ln, err = config.ListenPacket(ctx, na.Network, address)
173 } else {
185 - ln, err = listenReusable(ctx, lnKey, na.Network, address, config)
186 - }
187 - if err != nil {
188 - return nil, err
174 + if na.IsUnixNetwork() {
175 + // if this is a unix socket, see if we already have it open
176 + ln, err = reuseUnixSocket(na.Network, address)
177 + }
178 +
179 + if ln == nil && err == nil {
180 + // otherwise, create a new listener
181 + lnKey := listenerKey(na.Network, address)
182 + ln, err = listenReusable(ctx, lnKey, na.Network, address, config)
183 + }
184 }
185 +
186 if ln == nil {
187 return nil, fmt.Errorf("unsupported network type: %s", na.Network)
188 }
189
190 + if err != nil {
191 + return nil, err
192 + }
193 +
194 if IsUnixNetwork(na.Network) {
195 + isAbstractUnixSocket := strings.HasPrefix(address, "@")
196 if !isAbstractUnixSocket {
196 - if err := os.Chmod(address, unixFileMode); err != nil {
197 + err = os.Chmod(address, unixFileMode)
198 + if err != nil {
199 return nil, fmt.Errorf("unable to set permissions (%s) on %s: %v", unixFileMode, address, err)
200 }
201 }
@@ -208,13 +210,19 @@ func (na NetworkAddress) IsUnixNetwork() bool {
210 return IsUnixNetwork(na.Network)
211 }
212
213 +// IsUnixNetwork returns true if na.Network is
214 +// fd or fdgram.
215 +func (na NetworkAddress) IsFdNetwork() bool {
216 + return IsFdNetwork(na.Network)
217 +}
218 +
219 // JoinHostPort is like net.JoinHostPort, but where the port
220 // is StartPort + offset.
221 func (na NetworkAddress) JoinHostPort(offset uint) string {
214 - if na.IsUnixNetwork() {
222 + if na.IsUnixNetwork() || na.IsFdNetwork() {
223 return na.Host
224 }
217 - return net.JoinHostPort(na.Host, strconv.Itoa(int(na.StartPort+offset)))
225 + return net.JoinHostPort(na.Host, strconv.FormatUint(uint64(na.StartPort+offset), 10))
226 }
227
228 // Expand returns one NetworkAddress for each port in the port range.
@@ -248,7 +256,7 @@ func (na NetworkAddress) PortRangeSize() uint {
256 }
257
258 func (na NetworkAddress) isLoopback() bool {
251 - if na.IsUnixNetwork() {
259 + if na.IsUnixNetwork() || na.IsFdNetwork() {
260 return true
261 }
262 if na.Host == "localhost" {
@@ -292,6 +300,30 @@ func IsUnixNetwork(netw string) bool {
300 return strings.HasPrefix(netw, "unix")
301 }
302
303 +// IsFdNetwork returns true if the netw is a fd network.
304 +func IsFdNetwork(netw string) bool {
305 + return strings.HasPrefix(netw, "fd")
306 +}
307 +
308 +// normally we would simply append the port,
309 +// but if host is IPv6, we need to ensure it
310 +// is enclosed in [ ]; net.JoinHostPort does
311 +// this for us, but host might also have a
312 +// network type in front (e.g. "tcp/") leading
313 +// to "[tcp/::1]" which causes parsing failures
314 +// later; what we need is "tcp/[::1]", so we have
315 +// to split the network and host, then re-combine
316 +func ParseNetworkAddressFromHostPort(host, port string) (NetworkAddress, error) {
317 + network, addr, ok := strings.Cut(host, "/")
318 + if !ok {
319 + addr = network
320 + network = ""
321 + }
322 + addr = strings.Trim(addr, "[]") // IPv6
323 + networkAddr := JoinNetworkAddress(network, addr, port)
324 + return ParseNetworkAddress(networkAddr)
325 +}
326 +
327 // ParseNetworkAddress parses addr into its individual
328 // components. The input string is expected to be of
329 // the form "network/host:port-range" where any part is
@@ -322,6 +354,12 @@ func ParseNetworkAddressWithDefaults(addr, defaultNetwork string, defaultPort ui
354 Host: host,
355 }, err
356 }
357 + if IsFdNetwork(network) {
358 + return NetworkAddress{
359 + Network: network,
360 + Host: host,
361 + }, nil
362 + }
363 var start, end uint64
364 if port == "" {
365 start = uint64(defaultPort)
@@ -362,7 +400,7 @@ func SplitNetworkAddress(a string) (network, host, port string, err error) {
400 network = strings.ToLower(strings.TrimSpace(beforeSlash))
401 a = afterSlash
402 }
365 - if IsUnixNetwork(network) {
403 + if IsUnixNetwork(network) || IsFdNetwork(network) {
404 host = a
405 return
406 }
@@ -393,7 +431,7 @@ func JoinNetworkAddress(network, host, port string) string {
431 if network != "" {
432 a = network + "/"
433 }
396 - if (host != "" && port == "") || IsUnixNetwork(network) {
434 + if (host != "" && port == "") || IsUnixNetwork(network) || IsFdNetwork(network) {
435 a += host
436 } else if port != "" {
437 a += net.JoinHostPort(host, port)
@@ -401,9 +439,11 @@ func JoinNetworkAddress(network, host, port string) string {
439 return a
440 }
441
404 -// ListenQUIC returns a quic.EarlyListener suitable for use in a Caddy module.
405 -// The network will be transformed into a QUIC-compatible type (if unix, then
406 -// unixgram will be used; otherwise, udp will be used).
442 +// ListenQUIC returns a http3.QUICEarlyListener suitable for use in a Caddy module.
443 +//
444 +// The network will be transformed into a QUIC-compatible type if the same address can be used with
445 +// different networks. Currently this just means that for tcp, udp will be used with the same
446 +// address instead.
447 //
448 // NOTE: This API is EXPERIMENTAL and may be changed or removed.
449 func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config net.ListenConfig, tlsConf *tls.Config) (http3.QUICEarlyListener, error) {
@@ -617,7 +657,8 @@ func RegisterNetwork(network string, getListener ListenerFunc) {
657 if network == "tcp" || network == "tcp4" || network == "tcp6" ||
658 network == "udp" || network == "udp4" || network == "udp6" ||
659 network == "unix" || network == "unixpacket" || network == "unixgram" ||
620 - strings.HasPrefix("ip:", network) || strings.HasPrefix("ip4:", network) || strings.HasPrefix("ip6:", network) {
660 + strings.HasPrefix("ip:", network) || strings.HasPrefix("ip4:", network) || strings.HasPrefix("ip6:", network) ||
661 + network == "fd" || network == "fdgram" {
662 panic("network type " + network + " is reserved")
663 }
664
modules/caddyhttp/app.go
+155 -77
@@ -18,6 +18,7 @@ import (
18 "context"
19 "crypto/tls"
20 "fmt"
21 + "maps"
22 "net"
23 "net/http"
24 "strconv"
@@ -203,15 +204,73 @@ func (app *App) Provision(ctx caddy.Context) error {
204 }
205 }
206
207 + // if no protocols configured explicitly, enable all except h2c
208 + if len(srv.Protocols) == 0 {
209 + srv.Protocols = []string{"h1", "h2", "h3"}
210 + }
211 +
212 + srvProtocolsUnique := map[string]struct{}{}
213 + for _, srvProtocol := range srv.Protocols {
214 + srvProtocolsUnique[srvProtocol] = struct{}{}
215 + }
216 + _, h1ok := srvProtocolsUnique["h1"]
217 + _, h2ok := srvProtocolsUnique["h2"]
218 + _, h2cok := srvProtocolsUnique["h2c"]
219 +
220 // the Go standard library does not let us serve only HTTP/2 using
221 // http.Server; we would probably need to write our own server
208 - if !srv.protocol("h1") && (srv.protocol("h2") || srv.protocol("h2c")) {
222 + if !h1ok && (h2ok || h2cok) {
223 return fmt.Errorf("server %s: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName)
224 }
225
212 - // if no protocols configured explicitly, enable all except h2c
213 - if len(srv.Protocols) == 0 {
214 - srv.Protocols = []string{"h1", "h2", "h3"}
226 + if srv.ListenProtocols != nil {
227 + if len(srv.ListenProtocols) != len(srv.Listen) {
228 + return fmt.Errorf("server %s: listener protocols count does not match address count: %d != %d",
229 + srvName, len(srv.ListenProtocols), len(srv.Listen))
230 + }
231 +
232 + for i, lnProtocols := range srv.ListenProtocols {
233 + if lnProtocols != nil {
234 + // populate empty listen protocols with server protocols
235 + lnProtocolsDefault := false
236 + var lnProtocolsInclude []string
237 + srvProtocolsInclude := maps.Clone(srvProtocolsUnique)
238 +
239 + // keep existing listener protocols unless they are empty
240 + for _, lnProtocol := range lnProtocols {
241 + if lnProtocol == "" {
242 + lnProtocolsDefault = true
243 + } else {
244 + lnProtocolsInclude = append(lnProtocolsInclude, lnProtocol)
245 + delete(srvProtocolsInclude, lnProtocol)
246 + }
247 + }
248 +
249 + // append server protocols to listener protocols if any listener protocols were empty
250 + if lnProtocolsDefault {
251 + for _, srvProtocol := range srv.Protocols {
252 + if _, ok := srvProtocolsInclude[srvProtocol]; ok {
253 + lnProtocolsInclude = append(lnProtocolsInclude, srvProtocol)
254 + }
255 + }
256 + }
257 +
258 + lnProtocolsIncludeUnique := map[string]struct{}{}
259 + for _, lnProtocol := range lnProtocolsInclude {
260 + lnProtocolsIncludeUnique[lnProtocol] = struct{}{}
261 + }
262 + _, h1ok := lnProtocolsIncludeUnique["h1"]
263 + _, h2ok := lnProtocolsIncludeUnique["h2"]
264 + _, h2cok := lnProtocolsIncludeUnique["h2c"]
265 +
266 + // check if any listener protocols contain h2 or h2c without h1
267 + if !h1ok && (h2ok || h2cok) {
268 + return fmt.Errorf("server %s, listener %d: cannot enable HTTP/2 or H2C without enabling HTTP/1.1; add h1 to protocols or remove h2/h2c", srvName, i)
269 + }
270 +
271 + srv.ListenProtocols[i] = lnProtocolsInclude
272 + }
273 + }
274 }
275
276 // if not explicitly configured by the user, disallow TLS
@@ -344,7 +403,7 @@ func (app *App) Validate() error {
403 // check that every address in the port range is unique to this server;
404 // we do not use <= here because PortRangeSize() adds 1 to EndPort for us
405 for i := uint(0); i < listenAddr.PortRangeSize(); i++ {
347 - addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.Itoa(int(listenAddr.StartPort+i)))
406 + addr := caddy.JoinNetworkAddress(listenAddr.Network, listenAddr.Host, strconv.FormatUint(uint64(listenAddr.StartPort+i), 10))
407 if sn, ok := lnAddrs[addr]; ok {
408 return fmt.Errorf("server %s: listener address repeated: %s (already claimed by server '%s')", srvName, addr, sn)
409 }
@@ -422,99 +481,118 @@ func (app *App) Start() error {
481 srv.server.Handler = h2c.NewHandler(srv, h2server)
482 }
483
425 - for _, lnAddr := range srv.Listen {
484 + for lnIndex, lnAddr := range srv.Listen {
485 listenAddr, err := caddy.ParseNetworkAddress(lnAddr)
486 if err != nil {
487 return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err)
488 }
489 +
490 srv.addresses = append(srv.addresses, listenAddr)
491
492 + protocols := srv.Protocols
493 + if srv.ListenProtocols != nil && srv.ListenProtocols[lnIndex] != nil {
494 + protocols = srv.ListenProtocols[lnIndex]
495 + }
496 +
497 + protocolsUnique := map[string]struct{}{}
498 + for _, protocol := range protocols {
499 + protocolsUnique[protocol] = struct{}{}
500 + }
501 + _, h1ok := protocolsUnique["h1"]
502 + _, h2ok := protocolsUnique["h2"]
503 + _, h2cok := protocolsUnique["h2c"]
504 + _, h3ok := protocolsUnique["h3"]
505 +
506 for portOffset := uint(0); portOffset < listenAddr.PortRangeSize(); portOffset++ {
433 - // create the listener for this socket
507 hostport := listenAddr.JoinHostPort(portOffset)
435 - lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)})
436 - if err != nil {
437 - return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err)
438 - }
439 - ln := lnAny.(net.Listener)
440 -
441 - // wrap listener before TLS (up to the TLS placeholder wrapper)
442 - var lnWrapperIdx int
443 - for i, lnWrapper := range srv.listenerWrappers {
444 - if _, ok := lnWrapper.(*tlsPlaceholderWrapper); ok {
445 - lnWrapperIdx = i + 1 // mark the next wrapper's spot
446 - break
447 - }
448 - ln = lnWrapper.WrapListener(ln)
449 - }
508
509 // enable TLS if there is a policy and if this is not the HTTP port
510 useTLS := len(srv.TLSConnPolicies) > 0 && int(listenAddr.StartPort+portOffset) != app.httpPort()
453 - if useTLS {
454 - // create TLS listener - this enables and terminates TLS
455 - ln = tls.NewListener(ln, tlsCfg)
456 -
457 - // enable HTTP/3 if configured
458 - if srv.protocol("h3") {
459 - // Can't serve HTTP/3 on the same socket as HTTP/1 and 2 because it uses
460 - // a different transport mechanism... which is fine, but the OS doesn't
461 - // differentiate between a SOCK_STREAM file and a SOCK_DGRAM file; they
462 - // are still one file on the system. So even though "unixpacket" and
463 - // "unixgram" are different network types just as "tcp" and "udp" are,
464 - // the OS will not let us use the same file as both STREAM and DGRAM.
465 - if len(srv.Protocols) > 1 && listenAddr.IsUnixNetwork() {
466 - app.logger.Warn("HTTP/3 disabled because Unix can't multiplex STREAM and DGRAM on same socket",
467 - zap.String("file", hostport))
468 - for i := range srv.Protocols {
469 - if srv.Protocols[i] == "h3" {
470 - srv.Protocols = append(srv.Protocols[:i], srv.Protocols[i+1:]...)
471 - break
472 - }
473 - }
474 - } else {
475 - app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
476 - if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
477 - return err
478 - }
479 - }
511 +
512 + // enable HTTP/3 if configured
513 + if h3ok && useTLS {
514 + app.logger.Info("enabling HTTP/3 listener", zap.String("addr", hostport))
515 + if err := srv.serveHTTP3(listenAddr.At(portOffset), tlsCfg); err != nil {
516 + return err
517 }
518 }
519
483 - // finish wrapping listener where we left off before TLS
484 - for i := lnWrapperIdx; i < len(srv.listenerWrappers); i++ {
485 - ln = srv.listenerWrappers[i].WrapListener(ln)
520 + if h3ok && !useTLS {
521 + // Can only serve h3 with TLS enabled
522 + app.logger.Warn("HTTP/3 skipped because it requires TLS",
523 + zap.String("network", listenAddr.Network),
524 + zap.String("addr", hostport))
525 }
526
488 - // handle http2 if use tls listener wrapper
489 - if useTLS {
490 - http2lnWrapper := &http2Listener{
491 - Listener: ln,
492 - server: srv.server,
493 - h2server: h2server,
527 + if h1ok || h2ok && useTLS || h2cok {
528 + // create the listener for this socket
529 + lnAny, err := listenAddr.Listen(app.ctx, portOffset, net.ListenConfig{KeepAlive: time.Duration(srv.KeepAliveInterval)})
530 + if err != nil {
531 + return fmt.Errorf("listening on %s: %v", listenAddr.At(portOffset), err)
532 + }
533 + ln, ok := lnAny.(net.Listener)
534 + if !ok {
535 + return fmt.Errorf("network '%s' cannot handle HTTP/1 or HTTP/2 connections", listenAddr.Network)
536 }
495 - srv.h2listeners = append(srv.h2listeners, http2lnWrapper)
496 - ln = http2lnWrapper
497 - }
537
499 - // if binding to port 0, the OS chooses a port for us;
500 - // but the user won't know the port unless we print it
501 - if !listenAddr.IsUnixNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 {
502 - app.logger.Info("port 0 listener",
503 - zap.String("input_address", lnAddr),
504 - zap.String("actual_address", ln.Addr().String()))
505 - }
538 + if useTLS {
539 + // create TLS listener - this enables and terminates TLS
540 + ln = tls.NewListener(ln, tlsCfg)
541 + }
542 +
543 + // wrap listener before TLS (up to the TLS placeholder wrapper)
544 + var lnWrapperIdx int
545 + for i, lnWrapper := range srv.listenerWrappers {
546 + if _, ok := lnWrapper.(*tlsPlaceholderWrapper); ok {
547 + lnWrapperIdx = i + 1 // mark the next wrapper's spot
548 + break
549 + }
550 + ln = lnWrapper.WrapListener(ln)
551 + }
552 +
553 + // finish wrapping listener where we left off before TLS
554 + for i := lnWrapperIdx; i < len(srv.listenerWrappers); i++ {
555 + ln = srv.listenerWrappers[i].WrapListener(ln)
556 + }
557 +
558 + // handle http2 if use tls listener wrapper
559 + if h2ok {
560 + http2lnWrapper := &http2Listener{
561 + Listener: ln,
562 + server: srv.server,
563 + h2server: h2server,
564 + }
565 + srv.h2listeners = append(srv.h2listeners, http2lnWrapper)
566 + ln = http2lnWrapper
567 + }
568 +
569 + // if binding to port 0, the OS chooses a port for us;
570 + // but the user won't know the port unless we print it
571 + if !listenAddr.IsUnixNetwork() && !listenAddr.IsFdNetwork() && listenAddr.StartPort == 0 && listenAddr.EndPort == 0 {
572 + app.logger.Info("port 0 listener",
573 + zap.String("input_address", lnAddr),
574 + zap.String("actual_address", ln.Addr().String()))
575 + }
576
507 - app.logger.Debug("starting server loop",
508 - zap.String("address", ln.Addr().String()),
509 - zap.Bool("tls", useTLS),
510 - zap.Bool("http3", srv.h3server != nil))
577 + app.logger.Debug("starting server loop",
578 + zap.String("address", ln.Addr().String()),
579 + zap.Bool("tls", useTLS),
580 + zap.Bool("http3", srv.h3server != nil))
581
512 - srv.listeners = append(srv.listeners, ln)
582 + srv.listeners = append(srv.listeners, ln)
583 +
584 + // enable HTTP/1 if configured
585 + if h1ok {
586 + //nolint:errcheck
587 + go srv.server.Serve(ln)
588 + }
589 + }
590
514 - // enable HTTP/1 if configured
515 - if srv.protocol("h1") {
516 - //nolint:errcheck
517 - go srv.server.Serve(ln)
591 + if h2ok && !useTLS {
592 + // Can only serve h2 with TLS enabled
593 + app.logger.Warn("HTTP/2 skipped because it requires TLS",
594 + zap.String("network", listenAddr.Network),
595 + zap.String("addr", hostport))
596 }
597 }
598 }
modules/caddyhttp/proxyprotocol/listenerwrapper.go
+1 -1
@@ -72,7 +72,7 @@ func (pp *ListenerWrapper) Provision(ctx caddy.Context) error {
72
73 pp.policy = func(options goproxy.ConnPolicyOptions) (goproxy.Policy, error) {
74 // trust unix sockets
75 - if network := options.Upstream.Network(); caddy.IsUnixNetwork(network) {
75 + if network := options.Upstream.Network(); caddy.IsUnixNetwork(network) || caddy.IsFdNetwork(network) {
76 return goproxy.USE, nil
77 }
78 ret := pp.FallbackPolicy
modules/caddyhttp/reverseproxy/addresses.go
+1 -1
@@ -137,7 +137,7 @@ func parseUpstreamDialAddress(upstreamAddr string) (parsedAddr, error) {
137 }
138 // we can assume a port if only a hostname is specified, but use of a
139 // placeholder without a port likely means a port will be filled in
140 - if port == "" && !strings.Contains(host, "{") && !caddy.IsUnixNetwork(network) {
140 + if port == "" && !strings.Contains(host, "{") && !caddy.IsUnixNetwork(network) && !caddy.IsFdNetwork(network) {
141 port = "80"
142 }
143 }
modules/caddyhttp/reverseproxy/healthchecks.go
+2 -2
@@ -330,7 +330,7 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
330 return
331 }
332 if hcp := uint(upstream.activeHealthCheckPort); hcp != 0 {
333 - if addr.IsUnixNetwork() {
333 + if addr.IsUnixNetwork() || addr.IsFdNetwork() {
334 addr.Network = "tcp" // I guess we just assume TCP since we are using a port??
335 }
336 addr.StartPort, addr.EndPort = hcp, hcp
@@ -345,7 +345,7 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
345 }
346 hostAddr := addr.JoinHostPort(0)
347 dialAddr := hostAddr
348 - if addr.IsUnixNetwork() {
348 + if addr.IsUnixNetwork() || addr.IsFdNetwork() {
349 // this will be used as the Host portion of a http.Request URL, and
350 // paths to socket files would produce an error when creating URL,
351 // so use a fake Host value instead; unix sockets are usually local
modules/caddyhttp/server.go
+35 -9
@@ -220,6 +220,10 @@ type Server struct {
220 // Default: `[h1 h2 h3]`
221 Protocols []string `json:"protocols,omitempty"`
222
223 + // ListenProtocols overrides Protocols for each parallel address in Listen.
224 + // A nil value or element indicates that Protocols will be used instead.
225 + ListenProtocols [][]string `json:"listen_protocols,omitempty"`
226 +
227 // If set, metrics observations will be enabled.
228 // This setting is EXPERIMENTAL and subject to change.
229 Metrics *Metrics `json:"metrics,omitempty"`
@@ -597,7 +601,11 @@ func (s *Server) findLastRouteWithHostMatcher() int {
601 // not already done, and then uses that server to serve HTTP/3 over
602 // the listener, with Server s as the handler.
603 func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error {
600 - addr.Network = getHTTP3Network(addr.Network)
604 + h3net, err := getHTTP3Network(addr.Network)
605 + if err != nil {
606 + return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err)
607 + }
608 + addr.Network = h3net
609 h3ln, err := addr.ListenQUIC(s.ctx, 0, net.ListenConfig{}, tlsCfg)
610 if err != nil {
611 return fmt.Errorf("starting HTTP/3 QUIC listener: %v", err)
@@ -849,7 +857,21 @@ func (s *Server) logRequest(
857
858 // protocol returns true if the protocol proto is configured/enabled.
859 func (s *Server) protocol(proto string) bool {
852 - return slices.Contains(s.Protocols, proto)
860 + if s.ListenProtocols == nil {
861 + if slices.Contains(s.Protocols, proto) {
862 + return true
863 + }
864 + } else {
865 + for _, lnProtocols := range s.ListenProtocols {
866 + for _, lnProtocol := range lnProtocols {
867 + if lnProtocol == "" && slices.Contains(s.Protocols, proto) || lnProtocol == proto {
868 + return true
869 + }
870 + }
871 + }
872 + }
873 +
874 + return false
875 }
876
877 // Listeners returns the server's listeners. These are active listeners,
@@ -1089,9 +1111,14 @@ const (
1111 )
1112
1113 var networkTypesHTTP3 = map[string]string{
1092 - "unix": "unixgram",
1093 - "tcp4": "udp4",
1094 - "tcp6": "udp6",
1114 + "unixgram": "unixgram",
1115 + "udp": "udp",
1116 + "udp4": "udp4",
1117 + "udp6": "udp6",
1118 + "tcp": "udp",
1119 + "tcp4": "udp4",
1120 + "tcp6": "udp6",
1121 + "fdgram": "fdgram",
1122 }
1123
1124 // RegisterNetworkHTTP3 registers a mapping from non-HTTP/3 network to HTTP/3
@@ -1106,11 +1133,10 @@ func RegisterNetworkHTTP3(originalNetwork, h3Network string) {
1133 networkTypesHTTP3[originalNetwork] = h3Network
1134 }
1135
1109 -func getHTTP3Network(originalNetwork string) string {
1136 +func getHTTP3Network(originalNetwork string) (string, error) {
1137 h3Network, ok := networkTypesHTTP3[strings.ToLower(originalNetwork)]
1138 if !ok {
1112 - // TODO: Maybe a better default is to not enable HTTP/3 if we do not know the network?
1113 - return "udp"
1139 + return "", fmt.Errorf("network '%s' cannot handle HTTP/3 connections", originalNetwork)
1140 }
1115 - return h3Network
1141 + return h3Network, nil
1142 }
modules/caddyhttp/staticresp.go
+1 -1
@@ -387,7 +387,7 @@ func cmdRespond(fl caddycmd.Flags) (int, error) {
387 return caddy.ExitCodeFailedStartup, err
388 }
389
390 - if !listenAddr.IsUnixNetwork() {
390 + if !listenAddr.IsUnixNetwork() && !listenAddr.IsFdNetwork() {
391 listenAddrs := make([]string, 0, listenAddr.PortRangeSize())
392 for offset := uint(0); offset < listenAddr.PortRangeSize(); offset++ {
393 listenAddrs = append(listenAddrs, listenAddr.JoinHostPort(offset))
replacer.go
+3 -3
@@ -299,11 +299,11 @@ func ToString(val any) string {
299 case int64:
300 return strconv.Itoa(int(v))
301 case uint:
302 - return strconv.Itoa(int(v))
302 + return strconv.FormatUint(uint64(v), 10)
303 case uint32:
304 - return strconv.Itoa(int(v))
304 + return strconv.FormatUint(uint64(v), 10)
305 case uint64:
306 - return strconv.Itoa(int(v))
306 + return strconv.FormatUint(v, 10)
307 case float32:
308 return strconv.FormatFloat(float64(v), 'f', -1, 32)
309 case float64: