Skip to content

Commit

Permalink
Bugfix in ECS forward (#1290)
Browse files Browse the repository at this point in the history
* fixed override bug in forward

* set prettier as default formatter for yaml

* added ecs to example config
  • Loading branch information
kwitsch authored Dec 3, 2023
1 parent ac54110 commit 1154335
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"[go]": {
"editor.defaultFormatter": "golang.go"
},
"[json][jsonc][github-actions-workflow]": {
"[yaml][json][jsonc][github-actions-workflow]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
Expand Down
7 changes: 7 additions & 0 deletions docs/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,10 @@ specialUseDomains:
# optional: block recomended private TLDs
# default: true
rfc6762-appendixG: true

# optional: configure extended client subnet (ECS) support
ecs:
# optional: if the request ecs option with a max sice mask the address will be used as client ip
useAsClient: true
# optional: if the request contains a ecs option it will be forwarded to the upstream resolver
forward: true
55 changes: 35 additions & 20 deletions resolver/ecs_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,19 @@ func (r *ECSResolver) Resolve(ctx context.Context, request *model.Request) (*mod
// Set the client IP from the Edns0 subnet option if the option is enabled and the correct subnet mask is set
if r.cfg.UseAsClient && so != nil && ((so.Family == ecsFamilyIPv4 && so.SourceNetmask == ecsMaskIPv4) ||
(so.Family == ecsFamilyIPv6 && so.SourceNetmask == ecsMaskIPv6)) {
request.Log.Debugf("using request's edns0 address as internal client IP: %s", so.Address)
request.ClientIP = so.Address
}

// Set the Edns0 subnet option if the client IP is IPv4 or IPv6 and the masks are set in the configuration
if r.cfg.IPv4Mask > 0 || r.cfg.IPv6Mask > 0 {
r.setSubnet(request)
r.setSubnet(so, request)
}

// Remove the Edns0 subnet option if the client IP is IPv4 or IPv6 and the corresponding mask is not set
// and the forwardEcs option is not enabled
if r.cfg.IPv4Mask == 0 && r.cfg.IPv6Mask == 0 && so != nil && !r.cfg.Forward {
request.Log.Debug("remove edns0 subnet option")
util.RemoveEdns0Option[*dns.EDNS0_SUBNET](request.Req)
}
}
Expand All @@ -73,28 +75,30 @@ func (r *ECSResolver) Resolve(ctx context.Context, request *model.Request) (*mod

// setSubnet appends the subnet information to the request as EDNS0 option
// if the client IP is IPv4 or IPv6 and the corresponding mask is set in the configuration
func (r *ECSResolver) setSubnet(request *model.Request) {
e := new(dns.EDNS0_SUBNET)
e.Code = dns.EDNS0SUBNET
e.SourceScope = ecsSourceScope

if ip := request.ClientIP.To4(); ip != nil && r.cfg.IPv4Mask > 0 {
mip, err := maskIP(ip, r.cfg.IPv4Mask)
if err == nil {
e.Family = ecsFamilyIPv4
e.SourceNetmask = uint8(r.cfg.IPv4Mask)
e.Address = mip
util.SetEdns0Option(request.Req, e)
func (r *ECSResolver) setSubnet(so *dns.EDNS0_SUBNET, request *model.Request) {
var subIP net.IP
if so != nil && r.cfg.Forward && so.Address != nil {
subIP = so.Address
} else {
subIP = request.ClientIP
}

var edsOption *dns.EDNS0_SUBNET

if ip := subIP.To4(); ip != nil && r.cfg.IPv4Mask > 0 {
if mip, err := maskIP(ip, r.cfg.IPv4Mask); err == nil {
edsOption = newEdnsSubnetOption(mip, ecsFamilyIPv4, r.cfg.IPv4Mask)
}
} else if ip := request.ClientIP.To16(); ip != nil && r.cfg.IPv6Mask > 0 {
mip, err := maskIP(ip, r.cfg.IPv6Mask)
if err == nil {
e.Family = ecsFamilyIPv6
e.SourceNetmask = uint8(r.cfg.IPv6Mask)
e.Address = mip
util.SetEdns0Option(request.Req, e)
} else if ip := subIP.To16(); ip != nil && r.cfg.IPv6Mask > 0 {
if mip, err := maskIP(ip, r.cfg.IPv6Mask); err == nil {
edsOption = newEdnsSubnetOption(mip, ecsFamilyIPv6, r.cfg.IPv6Mask)
}
}

if edsOption != nil {
request.Log.Debugf("set edns0 subnet option address: %s", edsOption.Address)
util.SetEdns0Option(request.Req, edsOption)
}
}

// maskIP masks the IP with the given mask and return an error if the mask is invalid
Expand All @@ -103,3 +107,14 @@ func maskIP[maskType ECSMask](ip net.IP, mask maskType) (net.IP, error) {

return mip.IP, err
}

// newEdnsSubnetOption( creates a new EDNS0 subnet option with the given IP, family and mask
func newEdnsSubnetOption[maskType ECSMask](ip net.IP, family uint16, mask maskType) *dns.EDNS0_SUBNET {
return &dns.EDNS0_SUBNET{
Code: dns.EDNS0SUBNET,
SourceScope: ecsSourceScope,
Family: family,
SourceNetmask: uint8(mask),
Address: ip,
}
}
115 changes: 98 additions & 17 deletions resolver/ecs_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ var _ = Describe("EcsResolver", func() {
err error
origIP net.IP
ecsIP net.IP

ctx context.Context
cancelFn context.CancelFunc
)

Describe("Type", func() {
Expand All @@ -38,15 +35,12 @@ var _ = Describe("EcsResolver", func() {
})

BeforeEach(func() {
ctx, cancelFn = context.WithCancel(context.Background())
DeferCleanup(cancelFn)

err = defaults.Set(&sutConfig)
Expect(err).Should(Succeed())

mockAnswer = new(dns.Msg)
origIP = net.ParseIP("1.2.3.4")
ecsIP = net.ParseIP("4.3.2.1")
origIP = net.ParseIP("1.2.3.4").To4()
ecsIP = net.ParseIP("4.3.2.1").To4()
})

JustBeforeEach(func() {
Expand All @@ -63,15 +57,15 @@ var _ = Describe("EcsResolver", func() {
sut.Next(m)
})

When("ecs is disabled", func() {
When("ECS is disabled", func() {
Describe("IsEnabled", func() {
It("is false", func() {
Expect(sut.IsEnabled()).Should(BeFalse())
})
})
})

When("ecs is enabled", func() {
When("ECS is enabled", func() {
BeforeEach(func() {
sutConfig.UseAsClient = true
})
Expand All @@ -82,12 +76,12 @@ var _ = Describe("EcsResolver", func() {
})
})

When("use ecs client ip is enabled", func() {
When("use ECS client ip is enabled", func() {
BeforeEach(func() {
sutConfig.UseAsClient = true
})

It("should change ClientIP with subnet 32", func() {
It("should change ClientIP with subnet 32", func(ctx context.Context) {
request := newRequest("example.com.", A)
request.ClientIP = origIP

Expand All @@ -108,7 +102,7 @@ var _ = Describe("EcsResolver", func() {
HaveReason("Test")))
})

It("shouldn't change ClientIP with subnet 24", func() {
It("shouldn't change ClientIP with subnet 24", func(ctx context.Context) {
request := newRequest("example.com.", A)
request.ClientIP = origIP

Expand All @@ -130,14 +124,13 @@ var _ = Describe("EcsResolver", func() {
})
})

When("forward ecs is enabled", func() {
When("add ECS information", func() {
BeforeEach(func() {
sutConfig.Forward = true
sutConfig.IPv4Mask = 32
sutConfig.IPv6Mask = 128
})

It("should add Ecs information with subnet 32", func() {
It("should add ECS information with subnet 32", func(ctx context.Context) {
request := newRequest("example.com.", A)
request.ClientIP = origIP

Expand All @@ -157,13 +150,101 @@ var _ = Describe("EcsResolver", func() {
HaveReason("Test")))
})

It("should add Ecs information with subnet 128", func() {
It("should add ECS information with subnet 128", func(ctx context.Context) {
request := newRequest("example.com.", AAAA)
request.ClientIP = net.ParseIP("2001:db8::68")

m.ResolveFn = func(ctx context.Context, req *Request) (*Response, error) {
Expect(req.Req).Should(HaveEdnsOption(dns.EDNS0SUBNET))

return respondWith(mockAnswer), nil
}

Expect(sut.Resolve(ctx, request)).
Should(
SatisfyAll(
HaveNoAnswer(),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
HaveReason("Test")))
})
})

When("forward ECS information", func() {
BeforeEach(func() {
sutConfig.IPv4Mask = 32
sutConfig.IPv6Mask = 128
sutConfig.Forward = true
})

It("should forward ECS information with subnet 32", func(ctx context.Context) {
request := newRequest("example.com.", A)
request.ClientIP = origIP

addEcsOption(request.Req, ecsIP, ecsMaskIPv4)

m.ResolveFn = func(ctx context.Context, req *Request) (*Response, error) {
Expect(req.ClientIP).Should(Equal(ecsIP))
Expect(req.Req).Should(HaveEdnsOption(dns.EDNS0SUBNET))

so := util.GetEdns0Option[*dns.EDNS0_SUBNET](req.Req)
Expect(so.Address).Should(Equal(ecsIP))

return respondWith(mockAnswer), nil
}

Expect(sut.Resolve(ctx, request)).
Should(
SatisfyAll(
HaveNoAnswer(),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
HaveReason("Test")))
})

When("subnet mask is 24", func() {
BeforeEach(func() {
sutConfig.IPv4Mask = 24
})

It("should modify ECS information", func(ctx context.Context) {
request := newRequest("example.com.", A)
request.ClientIP = origIP

addEcsOption(request.Req, ecsIP, ecsMaskIPv4)

m.ResolveFn = func(ctx context.Context, req *Request) (*Response, error) {
Expect(req.ClientIP).Should(Equal(ecsIP))
Expect(req.Req).Should(HaveEdnsOption(dns.EDNS0SUBNET))

so := util.GetEdns0Option[*dns.EDNS0_SUBNET](req.Req)
Expect(so.Address).Should(Equal(net.ParseIP("4.3.2.0").To4()))

return respondWith(mockAnswer), nil
}

Expect(sut.Resolve(ctx, request)).
Should(
SatisfyAll(
HaveNoAnswer(),
HaveResponseType(ResponseTypeRESOLVED),
HaveReturnCode(dns.RcodeSuccess),
HaveReason("Test")))
})
})

It("should forward ECS information with subnet 128", func(ctx context.Context) {
request := newRequest("example.com.", AAAA)
request.ClientIP = net.ParseIP("2001:db8::68")

addEcsOption(request.Req, net.ParseIP("2001:db8::68"), 128)

m.ResolveFn = func(ctx context.Context, req *Request) (*Response, error) {
Expect(req.Req).Should(HaveEdnsOption(dns.EDNS0SUBNET))

so := util.GetEdns0Option[*dns.EDNS0_SUBNET](req.Req)
Expect(so.Address).Should(Equal(net.ParseIP("2001:db8::68")))

return respondWith(mockAnswer), nil
}

Expand Down

0 comments on commit 1154335

Please sign in to comment.