diff --git a/README.md b/README.md index 7415b07..be23c78 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,8 @@ For more information, run `pretender --help`. - If you are not sure which interface to choose (especially on Windows), list all interfaces with names and addresses using `--interfaces` - If you want to exclude hosts from local name resolution spoofing, make sure to - also exclude their IPv6 addresses or use `--no-ipv6-lnr`/`main.vendorNoIPv6LNR` + also exclude their IPv6 addresses or use + `--no-ipv6-lnr`/`main.vendorNoIPv6LNR` - DHCPv6 messages usually contain a FQDN option (which can also sometimes contain a hostname which is not a FQDN). This option is used to filter out messages by hostname (`--spoof-for`/`--dont-spoof-for`). You can decide what @@ -109,7 +110,8 @@ For more information, run `pretender --help`. - By default, in order to limit disruption during a DHCPv6 DNS Takeover, the option `--delegate-ignored-to ` can be used to delegate ignored queries to a legitimate DNS server. - +- The option `--dry-with-dhcp` can be combined with `--delegate-ignored-to` to + monitor the name resolution queries in the network without disruption. --- ## Building and Vendoring @@ -160,6 +162,7 @@ vendorIgnoreDHCPv6NoFQDN vendorDelegateIgnoredTo vendorDontSendEmptyReplies vendorDryMode +vendorDryWithDHCPMode vendorTTL vendorLeaseLifetime vendorRARouterLifetime diff --git a/cli.go b/cli.go index 6cb9265..c6391bb 100644 --- a/cli.go +++ b/cli.go @@ -47,7 +47,7 @@ type Config struct { DelegateIgnoredTo string DontSendEmptyReplies bool DryMode bool - DryWithDHCPv6 bool + DryWithDHCPv6Mode bool StopAfter time.Duration Verbose bool @@ -88,13 +88,17 @@ func (c Config) PrintSummary() { //nolint:cyclop switch { case c.DryMode: - var raNotice string - + raNotice := "" if !c.NoRA && !c.NoDHCPv6DNSTakeover && !c.NoDHCPv6 { raNotice = " (RA is still enabled)" } - fmt.Println("Dry Mode: DHCPv6 and name resolution queries will not be answered" + raNotice) + drySubject := "DHCPv6 and name resolution queries" + if c.DryWithDHCPv6Mode { + drySubject = "Name resolution queries" + } + + fmt.Printf("Dry Mode: %s will not be answered%s\n", drySubject, raNotice) default: if len(c.Spoof) != 0 { fmt.Println("Answering queries for: " + joinDomains(c.Spoof)) @@ -128,7 +132,27 @@ func (c Config) PrintSummary() { //nolint:cyclop fmt.Println() } -//nolint:forbidigo,cyclop,maintidx +func (c *Config) setRedundantOptions() { + if c.DryWithDHCPv6Mode { + c.DryMode = true + } + + if c.NoDNS && c.NoDHCPv6 { + c.NoDHCPv6DNSTakeover = true + } + + if c.NoMDNS && c.NoLLMNR && c.NoNetBIOS { + c.NoLocalNameResolution = true + } + + if c.NoLocalNameResolution { + c.NoNetBIOS = true + c.NoLLMNR = true + c.NoMDNS = true + } +} + +//nolint:forbidigo,cyclop func configFromCLI() (config Config, logger *Logger, err error) { var ( interfaceName string @@ -178,8 +202,11 @@ func configFromCLI() (config Config, logger *Logger, err error) { pflag.BoolVar(&config.DontSendEmptyReplies, "dont-send-empty-replies", defaultDontSendEmptyReplies, "Don't reply at all to ignored DNS queries or failed delegated\nqueries instead of sending an empty reply") pflag.BoolVar(&config.DryMode, "dry", defaultDryMode, - "Do not spoof name resolution at all, only log queries (does not disable\nRA but it"+ - " can be combined with --no-ra)") + "Do not answer DHCPv6 or any name resolution queries, only log them\n(does not disable RA but it "+ + "can be combined with --no-ra)") + pflag.BoolVar(&config.DryWithDHCPv6Mode, "dry-with-dhcp", defaultDryWithDHCPMode, + "Send RA and answer DHCPv6 queries but only log name resolution\nqueries (can be"+ + " combined with --delegate-ignored-to, takes\nprecedence over --dry)") pflag.DurationVarP(&config.TTL, "ttl", "t", defaultTTL, "Time to live for name resolution responses") pflag.DurationVar(&config.LeaseLifetime, "lease-lifetime", defaultLeaseLifetime, "DHCPv6 IP lease lifetime") @@ -216,19 +243,7 @@ func configFromCLI() (config Config, logger *Logger, err error) { stdErr = os.Stdout } - if config.NoDNS && config.NoDHCPv6 { - config.NoDHCPv6DNSTakeover = true - } - - if config.NoMDNS && config.NoLLMNR && config.NoNetBIOS { - config.NoLocalNameResolution = true - } - - if config.NoLocalNameResolution { - config.NoNetBIOS = true - config.NoLLMNR = true - config.NoMDNS = true - } + config.setRedundantOptions() if printVersion { fmt.Println(longVersion()) diff --git a/defaults.go b/defaults.go index 8261eed..036afaf 100644 --- a/defaults.go +++ b/defaults.go @@ -38,6 +38,7 @@ var ( vendorDelegateIgnoredTo = "" vendorDontSendEmptyReplies = "" vendorDryMode = "" + vendorDryWithDHCPMode = "" vendorTTL = "" vendorLeaseLifetime = "" @@ -81,6 +82,7 @@ var ( defaultDelegateIgnoredTo = vendorDelegateIgnoredTo defaultDontSendEmptyReplies = forceBool(vendorDontSendEmptyReplies, false) defaultDryMode = forceBool(vendorDryMode, false) + defaultDryWithDHCPMode = forceBool(vendorDryWithDHCPMode, false) defaultTTL = forceDuration(vendorTTL, dnsDefaultTTL) defaultLeaseLifetime = forceDuration(vendorLeaseLifetime, dhcpv6DefaultValidLifetime) diff --git a/filter.go b/filter.go index 7a562bf..cb1cb07 100644 --- a/filter.go +++ b/filter.go @@ -94,7 +94,7 @@ func shouldRespondToNameResolutionQuery(config Config, host string, queryType ui } func shouldRespondToDHCP(config Config, from peerInfo) (bool, string) { - if config.DryMode { + if config.DryMode && !config.DryWithDHCPv6Mode { return false, "dry mode" } diff --git a/filter_test.go b/filter_test.go index 6904d2e..6ac9028 100644 --- a/filter_test.go +++ b/filter_test.go @@ -22,6 +22,7 @@ func TestFilterNameResolutionQuery(t *testing.T) { //nolint:maintidx,cyclop DontSpoof []string SpoofTypes []string DryMode bool + DryWithDHCPv6Mode bool NoRelayIPv4Configured bool NoRelayIPv6Configured bool @@ -53,6 +54,13 @@ func TestFilterNameResolutionQuery(t *testing.T) { //nolint:maintidx,cyclop From: someIP, ShouldRespond: false, }, + { + TestName: "dry with dhcp", + Host: "foo", + DryWithDHCPv6Mode: true, + From: someIP, + ShouldRespond: false, + }, { Host: "foo", Spoof: []string{"foo"}, @@ -332,15 +340,18 @@ func TestFilterNameResolutionQuery(t *testing.T) { //nolint:maintidx,cyclop } cfg := Config{ - SpoofFor: asHostMatchers(testCase.SpoofFor, defaultLookupTimeout), - DontSpoofFor: asHostMatchers(testCase.DontSpoofFor, defaultLookupTimeout), - Spoof: testCase.Spoof, - DontSpoof: testCase.DontSpoof, - DryMode: testCase.DryMode, - SpoofTypes: types, - SOAHostname: "test", + SpoofFor: asHostMatchers(testCase.SpoofFor, defaultLookupTimeout), + DontSpoofFor: asHostMatchers(testCase.DontSpoofFor, defaultLookupTimeout), + Spoof: testCase.Spoof, + DontSpoof: testCase.DontSpoof, + DryMode: testCase.DryMode, + DryWithDHCPv6Mode: testCase.DryWithDHCPv6Mode, + SpoofTypes: types, + SOAHostname: "test", } + cfg.setRedundantOptions() + switch { case !testCase.NoRelayIPv4Configured: cfg.RelayIPv4 = relayIPv4 @@ -376,6 +387,7 @@ func TestFilterDHCP(t *testing.T) { DontSpoofFor []string IgnoreDHCPv6NoFQDN bool DryMode bool + DryWithDHCPv6Mode bool PeerIP net.IP PeerHostnames []string @@ -395,6 +407,21 @@ func TestFilterDHCP(t *testing.T) { PeerHostnames: []string{"foo"}, ShouldRespond: false, }, + { + TestName: "dry with dhcp", + DryWithDHCPv6Mode: true, + PeerIP: someIP, + PeerHostnames: []string{"foo"}, + ShouldRespond: true, + }, + { + TestName: "dry and drywith dhcp", + DryMode: true, + DryWithDHCPv6Mode: true, + PeerIP: someIP, + PeerHostnames: []string{"foo"}, + ShouldRespond: true, + }, { TestName: "dry+spooffor", SpoofFor: []string{someIP.String(), "foo"}, @@ -469,9 +496,12 @@ func TestFilterDHCP(t *testing.T) { SpoofFor: asHostMatchers(testCase.SpoofFor, defaultLookupTimeout), DontSpoofFor: asHostMatchers(testCase.DontSpoofFor, defaultLookupTimeout), DryMode: testCase.DryMode, + DryWithDHCPv6Mode: testCase.DryWithDHCPv6Mode, IgnoreDHCPv6NoFQDN: testCase.IgnoreDHCPv6NoFQDN, } + cfg.setRedundantOptions() + shouldRespond, _ := shouldRespondToDHCP(cfg, peerInfo{IP: testCase.PeerIP, Hostnames: testCase.PeerHostnames}) if shouldRespond != testCase.ShouldRespond {