* multisocket plugin improves performance in multiprocessor systems Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com> * - refactoring - update doc Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com> * remove port from reuseport plugin README Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com> * rename reuseport plugin to numsockets plugin Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com> * Add Recommendations to numsockets README Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com> * added numsockets test; made NUM_SOCKETS mandatory in doc Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com> * restart and whoami tests for numsockets plugin Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com> * default value for numsockets Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com> * caddy up Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com> * add numsockets to plugin.cfg Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com> * - rename numsockets plugin to multisocket - default as GOMAXPROCS - update README Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com> * resolve conflicts Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com> --------- Signed-off-by: Viktor Rodionov <33463837+Shmillerov@users.noreply.github.com>
368 lines
11 KiB
Go
368 lines
11 KiB
Go
package dnsserver
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/coredns/caddy"
|
|
"github.com/coredns/caddy/caddyfile"
|
|
"github.com/coredns/coredns/plugin"
|
|
"github.com/coredns/coredns/plugin/pkg/parse"
|
|
"github.com/coredns/coredns/plugin/pkg/transport"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
const serverType = "dns"
|
|
|
|
func init() {
|
|
caddy.RegisterServerType(serverType, caddy.ServerType{
|
|
Directives: func() []string { return Directives },
|
|
DefaultInput: func() caddy.Input {
|
|
return caddy.CaddyfileInput{
|
|
Filepath: "Corefile",
|
|
Contents: []byte(".:" + Port + " {\nwhoami\nlog\n}\n"),
|
|
ServerTypeName: serverType,
|
|
}
|
|
},
|
|
NewContext: newContext,
|
|
})
|
|
}
|
|
|
|
func newContext(i *caddy.Instance) caddy.Context {
|
|
return &dnsContext{keysToConfigs: make(map[string]*Config)}
|
|
}
|
|
|
|
type dnsContext struct {
|
|
keysToConfigs map[string]*Config
|
|
|
|
// configs is the master list of all site configs.
|
|
configs []*Config
|
|
}
|
|
|
|
func (h *dnsContext) saveConfig(key string, cfg *Config) {
|
|
h.configs = append(h.configs, cfg)
|
|
h.keysToConfigs[key] = cfg
|
|
}
|
|
|
|
// Compile-time check to ensure dnsContext implements the caddy.Context interface
|
|
var _ caddy.Context = &dnsContext{}
|
|
|
|
// InspectServerBlocks make sure that everything checks out before
|
|
// executing directives and otherwise prepares the directives to
|
|
// be parsed and executed.
|
|
func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddyfile.ServerBlock) ([]caddyfile.ServerBlock, error) {
|
|
// Normalize and check all the zone names and check for duplicates
|
|
for ib, s := range serverBlocks {
|
|
// Walk the s.Keys and expand any reverse address in their proper DNS in-addr zones. If the expansions leads for
|
|
// more than one reverse zone, replace the current value and add the rest to s.Keys.
|
|
zoneAddrs := []zoneAddr{}
|
|
for ik, k := range s.Keys {
|
|
trans, k1 := parse.Transport(k) // get rid of any dns:// or other scheme.
|
|
hosts, port, err := plugin.SplitHostPort(k1)
|
|
// We need to make this a fully qualified domain name to catch all errors here and not later when
|
|
// plugin.Normalize is called again on these strings, with the prime difference being that the domain
|
|
// name is fully qualified. This was found by fuzzing where "ȶ" is deemed OK, but "ȶ." is not (might be a
|
|
// bug in miekg/dns actually). But here we were checking ȶ, which is OK, and later we barf in ȶ. leading to
|
|
// "index out of range".
|
|
for ih := range hosts {
|
|
_, _, err := plugin.SplitHostPort(dns.Fqdn(hosts[ih]))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if port == "" {
|
|
switch trans {
|
|
case transport.DNS:
|
|
port = Port
|
|
case transport.TLS:
|
|
port = transport.TLSPort
|
|
case transport.QUIC:
|
|
port = transport.QUICPort
|
|
case transport.GRPC:
|
|
port = transport.GRPCPort
|
|
case transport.HTTPS:
|
|
port = transport.HTTPSPort
|
|
}
|
|
}
|
|
|
|
if len(hosts) > 1 {
|
|
s.Keys[ik] = hosts[0] + ":" + port // replace for the first
|
|
for _, h := range hosts[1:] { // add the rest
|
|
s.Keys = append(s.Keys, h+":"+port)
|
|
}
|
|
}
|
|
for i := range hosts {
|
|
zoneAddrs = append(zoneAddrs, zoneAddr{Zone: dns.Fqdn(hosts[i]), Port: port, Transport: trans})
|
|
}
|
|
}
|
|
|
|
serverBlocks[ib].Keys = s.Keys // important to save back the new keys that are potentially created here.
|
|
|
|
var firstConfigInBlock *Config
|
|
|
|
for ik := range s.Keys {
|
|
za := zoneAddrs[ik]
|
|
s.Keys[ik] = za.String()
|
|
// Save the config to our master list, and key it for lookups.
|
|
cfg := &Config{
|
|
Zone: za.Zone,
|
|
ListenHosts: []string{""},
|
|
Port: za.Port,
|
|
Transport: za.Transport,
|
|
}
|
|
|
|
// Set reference to the first config in the current block.
|
|
// This is used later by MakeServers to share a single plugin list
|
|
// for all zones in a server block.
|
|
if ik == 0 {
|
|
firstConfigInBlock = cfg
|
|
}
|
|
cfg.firstConfigInBlock = firstConfigInBlock
|
|
|
|
keyConfig := keyForConfig(ib, ik)
|
|
h.saveConfig(keyConfig, cfg)
|
|
}
|
|
}
|
|
return serverBlocks, nil
|
|
}
|
|
|
|
// MakeServers uses the newly-created siteConfigs to create and return a list of server instances.
|
|
func (h *dnsContext) MakeServers() ([]caddy.Server, error) {
|
|
// Copy parameters from first config in the block to all other config in the same block
|
|
propagateConfigParams(h.configs)
|
|
|
|
// we must map (group) each config to a bind address
|
|
groups, err := groupConfigsByListenAddr(h.configs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// then we create a server for each group
|
|
var servers []caddy.Server
|
|
for addr, group := range groups {
|
|
serversForGroup, err := makeServersForGroup(addr, group)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
servers = append(servers, serversForGroup...)
|
|
}
|
|
|
|
// For each server config, check for View Filter plugins
|
|
for _, c := range h.configs {
|
|
// Add filters in the plugin.cfg order for consistent filter func evaluation order.
|
|
for _, d := range Directives {
|
|
if vf, ok := c.registry[d].(Viewer); ok {
|
|
if c.ViewName != "" {
|
|
return nil, fmt.Errorf("multiple views defined in server block")
|
|
}
|
|
c.ViewName = vf.ViewName()
|
|
c.FilterFuncs = append(c.FilterFuncs, vf.Filter)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify that there is no overlap on the zones and listen addresses
|
|
// for unfiltered server configs
|
|
errValid := h.validateZonesAndListeningAddresses()
|
|
if errValid != nil {
|
|
return nil, errValid
|
|
}
|
|
|
|
return servers, nil
|
|
}
|
|
|
|
// AddPlugin adds a plugin to a site's plugin stack.
|
|
func (c *Config) AddPlugin(m plugin.Plugin) {
|
|
c.Plugin = append(c.Plugin, m)
|
|
}
|
|
|
|
// registerHandler adds a handler to a site's handler registration. Handlers
|
|
//
|
|
// use this to announce that they exist to other plugin.
|
|
func (c *Config) registerHandler(h plugin.Handler) {
|
|
if c.registry == nil {
|
|
c.registry = make(map[string]plugin.Handler)
|
|
}
|
|
|
|
// Just overwrite...
|
|
c.registry[h.Name()] = h
|
|
}
|
|
|
|
// Handler returns the plugin handler that has been added to the config under its name.
|
|
// This is useful to inspect if a certain plugin is active in this server.
|
|
// Note that this is order dependent and the order is defined in directives.go, i.e. if your plugin
|
|
// comes before the plugin you are checking; it will not be there (yet).
|
|
func (c *Config) Handler(name string) plugin.Handler {
|
|
if c.registry == nil {
|
|
return nil
|
|
}
|
|
if h, ok := c.registry[name]; ok {
|
|
return h
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Handlers returns a slice of plugins that have been registered. This can be used to
|
|
// inspect and interact with registered plugins but cannot be used to remove or add plugins.
|
|
// Note that this is order dependent and the order is defined in directives.go, i.e. if your plugin
|
|
// comes before the plugin you are checking; it will not be there (yet).
|
|
func (c *Config) Handlers() []plugin.Handler {
|
|
if c.registry == nil {
|
|
return nil
|
|
}
|
|
hs := make([]plugin.Handler, 0, len(c.registry))
|
|
for _, k := range Directives {
|
|
registry := c.Handler(k)
|
|
if registry != nil {
|
|
hs = append(hs, registry)
|
|
}
|
|
}
|
|
return hs
|
|
}
|
|
|
|
func (h *dnsContext) validateZonesAndListeningAddresses() error {
|
|
//Validate Zone and addresses
|
|
checker := newOverlapZone()
|
|
for _, conf := range h.configs {
|
|
for _, h := range conf.ListenHosts {
|
|
// Validate the overlapping of ZoneAddr
|
|
akey := zoneAddr{Transport: conf.Transport, Zone: conf.Zone, Address: h, Port: conf.Port}
|
|
var existZone, overlapZone *zoneAddr
|
|
if len(conf.FilterFuncs) > 0 {
|
|
// This config has filters. Check for overlap with other (unfiltered) configs.
|
|
existZone, overlapZone = checker.check(akey)
|
|
} else {
|
|
// This config has no filters. Check for overlap with other (unfiltered) configs,
|
|
// and register the zone to prevent subsequent zones from overlapping with it.
|
|
existZone, overlapZone = checker.registerAndCheck(akey)
|
|
}
|
|
if existZone != nil {
|
|
return fmt.Errorf("cannot serve %s - it is already defined", akey.String())
|
|
}
|
|
if overlapZone != nil {
|
|
return fmt.Errorf("cannot serve %s - zone overlap listener capacity with %v", akey.String(), overlapZone.String())
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// propagateConfigParams copies the necessary parameters from first config in the block
|
|
// to all other config in the same block. Doing this results in zones
|
|
// sharing the same plugin instances and settings as other zones in
|
|
// the same block.
|
|
func propagateConfigParams(configs []*Config) {
|
|
for _, c := range configs {
|
|
c.Plugin = c.firstConfigInBlock.Plugin
|
|
c.ListenHosts = c.firstConfigInBlock.ListenHosts
|
|
c.Debug = c.firstConfigInBlock.Debug
|
|
c.Stacktrace = c.firstConfigInBlock.Stacktrace
|
|
c.NumSockets = c.firstConfigInBlock.NumSockets
|
|
|
|
// Fork TLSConfig for each encrypted connection
|
|
c.TLSConfig = c.firstConfigInBlock.TLSConfig.Clone()
|
|
c.ReadTimeout = c.firstConfigInBlock.ReadTimeout
|
|
c.WriteTimeout = c.firstConfigInBlock.WriteTimeout
|
|
c.IdleTimeout = c.firstConfigInBlock.IdleTimeout
|
|
c.TsigSecret = c.firstConfigInBlock.TsigSecret
|
|
}
|
|
}
|
|
|
|
// groupConfigsByListenAddr groups site configs by their listen
|
|
// (bind) address, so sites that use the same listener can be served
|
|
// on the same server instance. The return value maps the listen
|
|
// address (what you pass into net.Listen) to the list of site configs.
|
|
// This function does NOT vet the configs to ensure they are compatible.
|
|
func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) {
|
|
groups := make(map[string][]*Config)
|
|
for _, conf := range configs {
|
|
for _, h := range conf.ListenHosts {
|
|
addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(h, conf.Port))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addrstr := conf.Transport + "://" + addr.String()
|
|
groups[addrstr] = append(groups[addrstr], conf)
|
|
}
|
|
}
|
|
|
|
return groups, nil
|
|
}
|
|
|
|
// makeServersForGroup creates servers for a specific transport and group.
|
|
// It creates as many servers as specified in the NumSockets configuration.
|
|
// If the NumSockets param is not specified, one server is created by default.
|
|
func makeServersForGroup(addr string, group []*Config) ([]caddy.Server, error) {
|
|
// that is impossible, but better to check
|
|
if len(group) == 0 {
|
|
return nil, fmt.Errorf("no configs for group defined")
|
|
}
|
|
// create one server by default if no NumSockets specified
|
|
numSockets := 1
|
|
if group[0].NumSockets > 0 {
|
|
numSockets = group[0].NumSockets
|
|
}
|
|
|
|
var servers []caddy.Server
|
|
for range numSockets {
|
|
// switch on addr
|
|
switch tr, _ := parse.Transport(addr); tr {
|
|
case transport.DNS:
|
|
s, err := NewServer(addr, group)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
servers = append(servers, s)
|
|
|
|
case transport.TLS:
|
|
s, err := NewServerTLS(addr, group)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
servers = append(servers, s)
|
|
|
|
case transport.QUIC:
|
|
s, err := NewServerQUIC(addr, group)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
servers = append(servers, s)
|
|
|
|
case transport.GRPC:
|
|
s, err := NewServergRPC(addr, group)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
servers = append(servers, s)
|
|
|
|
case transport.HTTPS:
|
|
s, err := NewServerHTTPS(addr, group)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
servers = append(servers, s)
|
|
}
|
|
}
|
|
return servers, nil
|
|
}
|
|
|
|
// DefaultPort is the default port.
|
|
const DefaultPort = transport.Port
|
|
|
|
// These "soft defaults" are configurable by
|
|
// command line flags, etc.
|
|
var (
|
|
// Port is the port we listen on by default.
|
|
Port = DefaultPort
|
|
|
|
// GracefulTimeout is the maximum duration of a graceful shutdown.
|
|
GracefulTimeout time.Duration
|
|
)
|
|
|
|
var _ caddy.GracefulServer = new(Server)
|