diff --git a/caddyconfig/httpcaddyfile/addresses.go b/caddyconfig/httpcaddyfile/addresses.go index 1121776d98f..db122e6beee 100644 --- a/caddyconfig/httpcaddyfile/addresses.go +++ b/caddyconfig/httpcaddyfile/addresses.go @@ -29,6 +29,7 @@ import ( "github.com/caddyserver/caddy/v2" "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile" "github.com/caddyserver/caddy/v2/modules/caddyhttp" + "go.uber.org/zap" ) // mapAddressToProtocolToServerBlocks returns a map of listener address to list of server @@ -307,29 +308,75 @@ func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Ad } // 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"])) + lnCfgVals := make([]bindOptions, 0, len(sblock.pile["bind"])) for _, cfgVal := range sblock.pile["bind"] { - if val, ok := cfgVal.Value.(addressesWithProtocols); ok { + if val, ok := cfgVal.Value.(bindOptions); ok { lnCfgVals = append(lnCfgVals, val) } } if len(lnCfgVals) == 0 { if defaultBindValues, ok := options["default_bind"].([]ConfigValue); ok { for _, defaultBindValue := range defaultBindValues { - lnCfgVals = append(lnCfgVals, defaultBindValue.Value.(addressesWithProtocols)) + lnCfgVals = append(lnCfgVals, defaultBindValue.Value.(bindOptions)) } } else { - lnCfgVals = []addressesWithProtocols{{ - addresses: []string{""}, - protocols: nil, + lnCfgVals = []bindOptions{{ + addresses: []string{""}, + interfaces: nil, + protocols: nil, }} } } // use a map to prevent duplication + interfaceAddresses := map[string][]string{} listeners := map[string]map[string]struct{}{} for _, lnCfgVal := range lnCfgVals { - for _, lnAddr := range lnCfgVal.addresses { + addresses := []string{} + addresses = append(addresses, lnCfgVal.addresses...) + for _, lnIface := range lnCfgVal.interfaces { + lnNetw, lnDevice, _, err := caddy.SplitNetworkAddress(lnIface) + if err != nil { + return nil, fmt.Errorf("splitting listener interface: %v", err) + } + + ifaceAddresses, ok := interfaceAddresses[lnDevice] + if !ok { + iface, err := net.InterfaceByName(lnDevice) + if err != nil { + return nil, fmt.Errorf("querying listener interface: %v: %v", lnDevice, err) + } + if iface == nil { + return nil, fmt.Errorf("querying listener interface: %v", lnDevice) + } + ifaceAddrs, err := iface.Addrs() + if err != nil { + return nil, fmt.Errorf("querying listener interface addresses: %v: %v", lnDevice, err) + } + for _, ifaceAddr := range ifaceAddrs { + var ip net.IP + switch ifaceAddrValue := ifaceAddr.(type) { + case *net.IPAddr: + ip = ifaceAddrValue.IP + case *net.IPNet: + ip = ifaceAddrValue.IP + default: + caddy.Log().Error("reading listener interface address", zap.String("device", lnDevice), zap.String("address", ifaceAddr.String())) + continue + } + + if len(ip) == net.IPv4len && caddy.IsIPv4Network(lnNetw) || len(ip) == net.IPv6len && caddy.IsIPv6Network(lnNetw) { + ifaceAddresses = append(ifaceAddresses, caddy.JoinNetworkAddress(lnNetw, ip.String(), "")) + } + } + if len(ifaceAddresses) == 0 { + return nil, fmt.Errorf("querying listener interface addresses for network: %v: %v", lnDevice, lnNetw) + } + interfaceAddresses[lnDevice] = ifaceAddresses + } + addresses = append(addresses, ifaceAddresses...) + } + for _, lnAddr := range addresses { lnNetw, lnHost, _, err := caddy.SplitNetworkAddress(lnAddr) if err != nil { return nil, fmt.Errorf("splitting listener address: %v", err) @@ -350,6 +397,12 @@ func (st *ServerType) listenersForServerBlockAddress(sblock serverBlock, addr Ad return listeners, nil } +type bindOptions struct { + addresses []string + interfaces []string + protocols []string +} + // addressesWithProtocols associates a list of listen addresses // with a list of protocols to serve them with type addressesWithProtocols struct { diff --git a/caddyconfig/httpcaddyfile/builtins.go b/caddyconfig/httpcaddyfile/builtins.go index 061aaa48b8d..a0eb770470b 100644 --- a/caddyconfig/httpcaddyfile/builtins.go +++ b/caddyconfig/httpcaddyfile/builtins.go @@ -57,16 +57,22 @@ func init() { // parseBind parses the bind directive. Syntax: // -// bind [{ -// protocols [h1|h2|h2c|h3] [...] -// }] +// bind [{ +// interfaces +// protocols [h1|h2|h2c|h3] [...] +// }] func parseBind(h Helper) ([]ConfigValue, error) { h.Next() // consume directive name - var addresses, protocols []string + var addresses, interfaces, protocols []string addresses = h.RemainingArgs() for h.NextBlock(0) { switch h.Val() { + case "interfaces": + interfaces = h.RemainingArgs() + if len(interfaces) == 0 { + return nil, h.Errf("interfaces requires one or more arguments") + } case "protocols": protocols = h.RemainingArgs() if len(protocols) == 0 { @@ -77,9 +83,10 @@ func parseBind(h Helper) ([]ConfigValue, error) { } } - return []ConfigValue{{Class: "bind", Value: addressesWithProtocols{ - addresses: addresses, - protocols: protocols, + return []ConfigValue{{Class: "bind", Value: bindOptions{ + addresses: addresses, + interfaces: interfaces, + protocols: protocols, }}}, nil }