Option to handle FQDN only requests (#561)

This commit is contained in:
Kwitsch 2022-06-20 13:02:51 +02:00 committed by GitHub
parent e313ef7fe8
commit c912356740
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 164 additions and 2 deletions

View File

@ -425,6 +425,7 @@ type Config struct {
KeyFile string `yaml:"keyFile"`
BootstrapDNS BootstrapConfig `yaml:"bootstrapDns"`
HostsFile HostsFileConfig `yaml:"hostsFile"`
FqdnOnly bool `yaml:"fqdnOnly" default:"false"`
Filtering FilteringConfig `yaml:"filtering"`
}

View File

@ -134,7 +134,6 @@ Works only on Linux/\*nix OS due to golang limitations under Windows.
- 123.123.123.123
```
## Filtering
Under certain circumstances, it may be useful to filter some types of DNS queries. You can define one or more DNS query
@ -150,6 +149,18 @@ types, all queries with these types will be dropped (empty answer will be return
This configuration will drop all 'AAAA' (IPv6) queries.
## FQDN only
In domain environments, it may be usefull to only response to FQDN requests. If this option is enabled blocky respond immidiatly
with NXDOMAIN if the request is not a valid FQDN. The request is therfore not further processed by other options like custom or conditional.
Please be aware that by enabling it your hostname resolution will break unless every hostname is part of a domain.
!!! example
```yaml
fqdnOnly: true
```
## Custom DNS
You can define your own domain name to IP mappings. For example, you can use a user-friendly name for a network printer

View File

@ -17,6 +17,7 @@ import (
// CUSTOMDNS // the query was resolved by a custom rule
// HOSTSFILE // the query was resolved by looking up the hosts file
// FILTERED // the query was filtered by query type
// NOTFQDN // the query was filtered as it is not fqdn conform
// )
type ResponseType int

View File

@ -98,9 +98,12 @@ const (
// ResponseTypeFILTERED is a ResponseType of type FILTERED.
// the query was filtered by query type
ResponseTypeFILTERED
// ResponseTypeNOTFQDN is a ResponseType of type NOTFQDN.
// the query was filtered as it is not fqdn conform
ResponseTypeNOTFQDN
)
const _ResponseTypeName = "RESOLVEDCACHEDBLOCKEDCONDITIONALCUSTOMDNSHOSTSFILEFILTERED"
const _ResponseTypeName = "RESOLVEDCACHEDBLOCKEDCONDITIONALCUSTOMDNSHOSTSFILEFILTEREDNOTFQDN"
var _ResponseTypeNames = []string{
_ResponseTypeName[0:8],
@ -110,6 +113,7 @@ var _ResponseTypeNames = []string{
_ResponseTypeName[32:41],
_ResponseTypeName[41:50],
_ResponseTypeName[50:58],
_ResponseTypeName[58:65],
}
// ResponseTypeNames returns a list of possible string values of ResponseType.
@ -127,6 +131,7 @@ var _ResponseTypeMap = map[ResponseType]string{
ResponseTypeCUSTOMDNS: _ResponseTypeName[32:41],
ResponseTypeHOSTSFILE: _ResponseTypeName[41:50],
ResponseTypeFILTERED: _ResponseTypeName[50:58],
ResponseTypeNOTFQDN: _ResponseTypeName[58:65],
}
// String implements the Stringer interface.
@ -145,6 +150,7 @@ var _ResponseTypeValue = map[string]ResponseType{
_ResponseTypeName[32:41]: ResponseTypeCUSTOMDNS,
_ResponseTypeName[41:50]: ResponseTypeHOSTSFILE,
_ResponseTypeName[50:58]: ResponseTypeFILTERED,
_ResponseTypeName[58:65]: ResponseTypeNOTFQDN,
}
// ParseResponseType attempts to convert a string to a ResponseType.

View File

@ -0,0 +1,45 @@
package resolver
import (
"strings"
"github.com/0xERR0R/blocky/config"
"github.com/0xERR0R/blocky/model"
"github.com/0xERR0R/blocky/util"
"github.com/miekg/dns"
)
type FqdnOnlyResolver struct {
NextResolver
enabled bool
}
func NewFqdnOnlyResolver(cfg config.Config) ChainedResolver {
return &FqdnOnlyResolver{
enabled: cfg.FqdnOnly,
}
}
func (r *FqdnOnlyResolver) Resolve(request *model.Request) (*model.Response, error) {
if r.enabled {
domainFromQuestion := util.ExtractDomain(request.Req.Question[0])
if !strings.Contains(domainFromQuestion, ".") {
response := new(dns.Msg)
response.Rcode = dns.RcodeNameError
return &model.Response{Res: response, RType: model.ResponseTypeNOTFQDN, Reason: "NOTFQDN"}, nil
}
}
return r.next.Resolve(request)
}
func (r *FqdnOnlyResolver) Configuration() (result []string) {
if r.enabled {
result = []string{"activated"}
} else {
result = []string{"deactivated"}
}
return result
}

View File

@ -0,0 +1,97 @@
package resolver
import (
"github.com/0xERR0R/blocky/config"
. "github.com/0xERR0R/blocky/model"
"github.com/miekg/dns"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/mock"
)
var _ = Describe("FqdnOnlyResolver", func() {
var (
sut *FqdnOnlyResolver
sutConfig config.Config
m *MockResolver
mockAnswer *dns.Msg
)
BeforeEach(func() {
mockAnswer = new(dns.Msg)
})
JustBeforeEach(func() {
sut = NewFqdnOnlyResolver(sutConfig).(*FqdnOnlyResolver)
m = &MockResolver{}
m.On("Resolve", mock.Anything).Return(&Response{Res: mockAnswer}, nil)
sut.Next(m)
})
When("Fqdn only is activated", func() {
BeforeEach(func() {
sutConfig = config.Config{
FqdnOnly: true,
}
})
It("Should delegate to next resolver if request query is fqdn", func() {
resp, err := sut.Resolve(newRequest("example.com", dns.Type(dns.TypeA)))
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
Expect(resp.Res.Answer).Should(BeEmpty())
// delegated to next resolver
Expect(m.Calls).Should(HaveLen(1))
})
It("Should return NXDOMAIN if request query is not fqdn", func() {
resp, err := sut.Resolve(newRequest("example", dns.Type(dns.TypeAAAA)))
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeNameError))
Expect(resp.RType).Should(Equal(ResponseTypeNOTFQDN))
Expect(resp.Res.Answer).Should(BeEmpty())
// no call of next resolver
Expect(m.Calls).Should(BeZero())
})
It("Configure should output activated", func() {
c := sut.Configuration()
Expect(c).Should(HaveLen(1))
Expect(c[0]).Should(Equal("activated"))
})
})
When("Fqdn only is deactivated", func() {
BeforeEach(func() {
sutConfig = config.Config{
FqdnOnly: false,
}
})
It("Should delegate to next resolver if request query is fqdn", func() {
resp, err := sut.Resolve(newRequest("example.com", dns.Type(dns.TypeA)))
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
Expect(resp.Res.Answer).Should(BeEmpty())
// delegated to next resolver
Expect(m.Calls).Should(HaveLen(1))
})
It("Should delegate to next resolver if request query is not fqdn", func() {
resp, err := sut.Resolve(newRequest("example", dns.Type(dns.TypeAAAA)))
Expect(err).Should(Succeed())
Expect(resp.Res.Rcode).Should(Equal(dns.RcodeSuccess))
Expect(resp.RType).Should(Equal(ResponseTypeRESOLVED))
Expect(resp.Res.Answer).Should(BeEmpty())
// delegated to next resolver
Expect(m.Calls).Should(HaveLen(1))
})
It("Configure should output deactivated", func() {
c := sut.Configuration()
Expect(c).Should(HaveLen(1))
Expect(c[0]).Should(Equal("deactivated"))
})
})
})

View File

@ -397,6 +397,7 @@ func createQueryResolver(
r = resolver.Chain(
resolver.NewFilteringResolver(cfg.Filtering),
resolver.NewFqdnOnlyResolver(*cfg),
clientNamesResolver,
resolver.NewQueryLoggingResolver(cfg.QueryLog),
resolver.NewMetricsResolver(cfg.Prometheus),