From 7a57b61da980aca29bae9a695babb29e44ff4f61 Mon Sep 17 00:00:00 2001 From: Badreddin Aboubakr Date: Mon, 1 Jul 2024 15:59:48 +0200 Subject: [PATCH 1/3] feat(mdns): allow specifying iface for mDNS --- cmd/serf/command/agent/command.go | 22 ++++++++++++++++++++-- cmd/serf/command/agent/config.go | 25 ++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/cmd/serf/command/agent/command.go b/cmd/serf/command/agent/command.go index dbbe860a7..9ebe8070c 100644 --- a/cmd/serf/command/agent/command.go +++ b/cmd/serf/command/agent/command.go @@ -88,6 +88,7 @@ func (c *Command) readConfig() *Config { "tag pair, specified as key=value") cmdFlags.StringVar(&cmdConfig.Discover, "discover", "", "mDNS discovery name") cmdFlags.StringVar(&cmdConfig.Interface, "iface", "", "interface to bind to") + cmdFlags.StringVar(&cmdConfig.MDNS.Interface, "mdns-iface", "", "interface to use for mDNS") cmdFlags.StringVar(&cmdConfig.TagsFile, "tags-file", "", "tag persistence file") cmdFlags.BoolVar(&cmdConfig.EnableSyslog, "syslog", false, "enable logging to syslog facility") @@ -176,6 +177,18 @@ func (c *Command) readConfig() *Config { return nil } + if config.MDNS.Interface != "" { + if config.Discover == "" { + c.Ui.Error("mDNS interface specified without enabling mDNS discovery") + return nil + } + + if _, err := net.InterfaceByName(config.MDNS.Interface); err != nil { + c.Ui.Error(fmt.Sprintf("Invalid mDNS network interface: %s", err)) + return nil + } + } + // Backward compatibility hack for 'Role' if config.Role != "" { c.Ui.Output("Deprecation warning: 'Role' has been replaced with 'Tags'") @@ -432,7 +445,9 @@ func (c *Command) startAgent(config *Config, agent *Agent, local := agent.Serf().Memberlist().LocalNode() // Get the bind interface if any - iface, _ := config.NetworkInterface() + iface, _ := config.MDNSNetworkInterface() + + c.logger.Printf("[INFO] agent: Starting mDNS listener on interface %s", iface.Name) _, err := NewAgentMDNS(agent, logOutput, config.ReplayOnJoin, config.NodeName, config.Discover, iface, local.Addr, int(local.Port)) @@ -734,7 +749,10 @@ Options: -bind if the interface is known but not the address. If both are provided, then Serf verifies that the interface has the bind address that is provided. This - flag also sets the multicast device used for -discover. + flag also sets the multicast device used for -discover, + if mdns-iface is not specified. + -mdns-iface Network interface to use for mDNS. If not provided, the + -iface value is used. -advertise=0.0.0.0 Address to advertise to the other cluster members -config-file=foo Path to a JSON file to read configuration from. This can be specified multiple times. diff --git a/cmd/serf/command/agent/config.go b/cmd/serf/command/agent/config.go index 49186c529..abf2460e6 100644 --- a/cmd/serf/command/agent/config.go +++ b/cmd/serf/command/agent/config.go @@ -45,6 +45,12 @@ func DefaultConfig() *Config { type dirEnts []os.FileInfo +type MDNSConfig struct { + // Interface is used to provide a binding interface to use for mDNS. + // if not set, iface will be used. + Interface string `mapstructure:"interface"` +} + // Config is the configuration that can be set for an Agent. Some of these // configurations are exposed as command-line flags to `serf agent`, whereas // many of the more advanced configurations can only be set by creating @@ -154,10 +160,12 @@ type Config struct { // allows Serf agents to join each other with zero configuration. Discover string `mapstructure:"discover"` + MDNS MDNSConfig `mapstructure:"mdns"` + // Interface is used to provide a binding interface to use. It can be // used instead of providing a bind address, as Serf will discover the // address of the provided interface. It is also used to set the multicast - // device used with `-discover`. + // device used with `-discover`, if `mdns-iface` is not set Interface string `mapstructure:"interface"` // ReconnectIntervalRaw is the string reconnect interval time. This interval @@ -292,6 +300,16 @@ func (c *Config) NetworkInterface() (*net.Interface, error) { return net.InterfaceByName(c.Interface) } +func (c *Config) MDNSNetworkInterface() (*net.Interface, error) { + if c.MDNS.Interface == "" && c.Interface == "" { + return nil, nil + } else if c.MDNS.Interface != "" { + return net.InterfaceByName(c.MDNS.Interface) + } else { + return net.InterfaceByName(c.Interface) + } +} + // DecodeConfig reads the configuration from the given reader in JSON // format and decodes it into a proper Config structure. func DecodeConfig(r io.Reader) (*Config, error) { @@ -436,6 +454,11 @@ func MergeConfig(a, b *Config) *Config { if b.Interface != "" { result.Interface = b.Interface } + + if b.MDNS.Interface != "" { + result.MDNS.Interface = b.MDNS.Interface + } + if b.ReconnectInterval != 0 { result.ReconnectInterval = b.ReconnectInterval } From 2f94f0ef0d48d9bfa1575a6e71c1d5c58f017092 Mon Sep 17 00:00:00 2001 From: Badreddin Aboubakr Date: Mon, 1 Jul 2024 18:32:14 +0200 Subject: [PATCH 2/3] chore(mdns): update to v1.0.5 in go.mod v1.0.5 allows disable ipv4 or ipv6 when binding to an interface to query --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index de6245959..25e726881 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/hashicorp/go-msgpack/v2 v2.1.2 github.com/hashicorp/go-syslog v1.0.0 github.com/hashicorp/logutils v1.0.0 - github.com/hashicorp/mdns v1.0.4 + github.com/hashicorp/mdns v1.0.5 github.com/hashicorp/memberlist v0.5.1 github.com/mitchellh/cli v1.1.5 github.com/mitchellh/mapstructure v1.5.0 diff --git a/go.sum b/go.sum index 1cfeb6fda..9b4e57bd5 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= -github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/mdns v1.0.5 h1:1M5hW1cunYeoXOqHwEb/GBDDHAFo0Yqb/uz/beC6LbE= +github.com/hashicorp/mdns v1.0.5/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.5.1 h1:mk5dRuzeDNis2bi6LLoQIXfMH7JQvAzt3mQD0vNZZUo= github.com/hashicorp/memberlist v0.5.1/go.mod h1:zGDXV6AqbDTKTM6yxW0I4+JtFzZAJVoIPvss4hV8F24= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= From 9ef33a945b0fb33a5dc89bbdc797ef1bbce0af74 Mon Sep 17 00:00:00 2001 From: Badreddin Aboubakr Date: Mon, 1 Jul 2024 18:33:50 +0200 Subject: [PATCH 3/3] feat(mdns): add option to disable either IPv4 or IPv6 stack allow the user to disable v4 or v6 if needed. This solves the issue when a user is using discover option but having only one IP stack --- cmd/serf/command/agent/command.go | 12 ++++++++- cmd/serf/command/agent/config.go | 12 ++++++++- cmd/serf/command/agent/mdns.go | 43 ++++++++++++++++++------------- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/cmd/serf/command/agent/command.go b/cmd/serf/command/agent/command.go index 9ebe8070c..e8b7b60dd 100644 --- a/cmd/serf/command/agent/command.go +++ b/cmd/serf/command/agent/command.go @@ -89,6 +89,8 @@ func (c *Command) readConfig() *Config { cmdFlags.StringVar(&cmdConfig.Discover, "discover", "", "mDNS discovery name") cmdFlags.StringVar(&cmdConfig.Interface, "iface", "", "interface to bind to") cmdFlags.StringVar(&cmdConfig.MDNS.Interface, "mdns-iface", "", "interface to use for mDNS") + cmdFlags.BoolVar(&cmdConfig.MDNS.DisableIPv4, "mdns-disable-ipv4", false, "disable IPv4 for mDNS") + cmdFlags.BoolVar(&cmdConfig.MDNS.DisableIPv6, "mdns-disable-ipv6", false, "disable IPv6 for mDNS") cmdFlags.StringVar(&cmdConfig.TagsFile, "tags-file", "", "tag persistence file") cmdFlags.BoolVar(&cmdConfig.EnableSyslog, "syslog", false, "enable logging to syslog facility") @@ -187,6 +189,12 @@ func (c *Command) readConfig() *Config { c.Ui.Error(fmt.Sprintf("Invalid mDNS network interface: %s", err)) return nil } + + // Check for a valid mdns ip mode + if config.MDNS.DisableIPv4 && config.MDNS.DisableIPv6 { + c.Ui.Error("Invalid mDNS configuration: both IPv4 and IPv6 are disabled") + return nil + } } // Backward compatibility hack for 'Role' @@ -450,7 +458,7 @@ func (c *Command) startAgent(config *Config, agent *Agent, c.logger.Printf("[INFO] agent: Starting mDNS listener on interface %s", iface.Name) _, err := NewAgentMDNS(agent, logOutput, config.ReplayOnJoin, - config.NodeName, config.Discover, iface, local.Addr, int(local.Port)) + config.NodeName, config.Discover, iface, local.Addr, int(local.Port), config.MDNS) if err != nil { c.Ui.Error(fmt.Sprintf("Error starting mDNS listener: %s", err)) return nil @@ -753,6 +761,8 @@ Options: if mdns-iface is not specified. -mdns-iface Network interface to use for mDNS. If not provided, the -iface value is used. + -mdns-disable-ipv4 Disable IPv4 for mDNS. + -mdns-disable-ipv6 Disable IPv6 for mDNS. -advertise=0.0.0.0 Address to advertise to the other cluster members -config-file=foo Path to a JSON file to read configuration from. This can be specified multiple times. diff --git a/cmd/serf/command/agent/config.go b/cmd/serf/command/agent/config.go index abf2460e6..47e1a637d 100644 --- a/cmd/serf/command/agent/config.go +++ b/cmd/serf/command/agent/config.go @@ -48,7 +48,9 @@ type dirEnts []os.FileInfo type MDNSConfig struct { // Interface is used to provide a binding interface to use for mDNS. // if not set, iface will be used. - Interface string `mapstructure:"interface"` + Interface string `mapstructure:"interface"` + DisableIPv4 bool `mapstructure:"disable_ipv4"` + DisableIPv6 bool `mapstructure:"disable_ipv6"` } // Config is the configuration that can be set for an Agent. Some of these @@ -459,6 +461,14 @@ func MergeConfig(a, b *Config) *Config { result.MDNS.Interface = b.MDNS.Interface } + if b.MDNS.DisableIPv4 == true { + result.MDNS.DisableIPv4 = true + } + + if b.MDNS.DisableIPv6 == true { + result.MDNS.DisableIPv6 = true + } + if b.ReconnectInterval != 0 { result.ReconnectInterval = b.ReconnectInterval } diff --git a/cmd/serf/command/agent/mdns.go b/cmd/serf/command/agent/mdns.go index 6ee6f5f4d..57407500f 100644 --- a/cmd/serf/command/agent/mdns.go +++ b/cmd/serf/command/agent/mdns.go @@ -21,18 +21,21 @@ const ( // AgentMDNS is used to advertise ourself using mDNS and to // attempt to join peers periodically using mDNS queries. type AgentMDNS struct { - agent *Agent - discover string - logger *log.Logger - seen map[string]struct{} - server *mdns.Server - replay bool - iface *net.Interface + agent *Agent + discover string + logger *log.Logger + seen map[string]struct{} + server *mdns.Server + replay bool + iface *net.Interface + ipModes []string + disableIPv4 bool + disableIPv6 bool } // NewAgentMDNS is used to create a new AgentMDNS func NewAgentMDNS(agent *Agent, logOutput io.Writer, replay bool, - node, discover string, iface *net.Interface, bind net.IP, port int) (*AgentMDNS, error) { + node, discover string, iface *net.Interface, bind net.IP, port int, MDNSConfig MDNSConfig) (*AgentMDNS, error) { // Create the service service, err := mdns.NewMDNSService( node, @@ -60,13 +63,15 @@ func NewAgentMDNS(agent *Agent, logOutput io.Writer, replay bool, // Initialize the AgentMDNS m := &AgentMDNS{ - agent: agent, - discover: discover, - logger: log.New(logOutput, "", log.LstdFlags), - seen: make(map[string]struct{}), - server: server, - replay: replay, - iface: iface, + agent: agent, + discover: discover, + logger: log.New(logOutput, "", log.LstdFlags), + seen: make(map[string]struct{}), + server: server, + replay: replay, + iface: iface, + disableIPv4: MDNSConfig.DisableIPv4, + disableIPv6: MDNSConfig.DisableIPv6, } // Start the background workers @@ -123,9 +128,11 @@ func (m *AgentMDNS) run() { // poll is invoked periodically to check for new hosts func (m *AgentMDNS) poll(hosts chan *mdns.ServiceEntry) { params := mdns.QueryParam{ - Service: mdnsName(m.discover), - Interface: m.iface, - Entries: hosts, + Service: mdnsName(m.discover), + Interface: m.iface, + Entries: hosts, + DisableIPv4: m.disableIPv4, + DisableIPv6: m.disableIPv6, } if err := mdns.Query(¶ms); err != nil { m.logger.Printf("[ERR] agent.mdns: Failed to poll for new hosts: %v", err)