mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-30 17:53:21 -04:00 
			
		
		
		
	plugin/template : add support for extended DNS errors (#5659)
* plugin/template : add support for extended DNS errors Signed-off-by: Ondřej Benkovský <ondrej.benkovsky@jamf.com>
This commit is contained in:
		| @@ -17,6 +17,7 @@ template CLASS TYPE [ZONE...] { | |||||||
|     additional RR |     additional RR | ||||||
|     authority RR |     authority RR | ||||||
|     rcode CODE |     rcode CODE | ||||||
|  |     ederror EXTENDED_ERROR_CODE [EXTRA_REASON] | ||||||
|     fallthrough [FALLTHROUGH-ZONE...] |     fallthrough [FALLTHROUGH-ZONE...] | ||||||
| } | } | ||||||
| ~~~ | ~~~ | ||||||
| @@ -31,6 +32,8 @@ template CLASS TYPE [ZONE...] { | |||||||
|   in a response with an empty answer section. |   in a response with an empty answer section. | ||||||
| * `rcode` **CODE** A response code (`NXDOMAIN, SERVFAIL, ...`). The default is `NOERROR`. Valid response code values are | * `rcode` **CODE** A response code (`NXDOMAIN, SERVFAIL, ...`). The default is `NOERROR`. Valid response code values are | ||||||
|   per the `RcodeToString` map defined by the `miekg/dns` package in `msg.go`. |   per the `RcodeToString` map defined by the `miekg/dns` package in `msg.go`. | ||||||
|  | * `ederror` **EXTENDED_ERROR_CODE** is an extended DNS error code as a number defined in `RFC8914` (0, 1, 2,..., 24). | ||||||
|  |               **EXTRA_REASON** is an additional string explaining the reason for returning the error. | ||||||
| * `fallthrough` Continue with the next _template_ instance if the _template_'s **ZONE** matches a query name but no regex match. | * `fallthrough` Continue with the next _template_ instance if the _template_'s **ZONE** matches a query name but no regex match. | ||||||
|   If there is no next _template_, continue resolution with the next plugin. If **[FALLTHROUGH-ZONE...]** are listed (for example |   If there is no next _template_, continue resolution with the next plugin. If **[FALLTHROUGH-ZONE...]** are listed (for example | ||||||
|   `in-addr.arpa` and `ip6.arpa`), then only queries for those zones will be subject to fallthrough. Without |   `in-addr.arpa` and `ip6.arpa`), then only queries for those zones will be subject to fallthrough. Without | ||||||
| @@ -104,6 +107,7 @@ The `.invalid` domain is a reserved TLD (see [RFC 2606 Reserved Top Level DNS Na | |||||||
|     template ANY ANY invalid { |     template ANY ANY invalid { | ||||||
|       rcode NXDOMAIN |       rcode NXDOMAIN | ||||||
|       authority "invalid. 60 {{ .Class }} SOA ns.invalid. hostmaster.invalid. (1 60 60 60 60)" |       authority "invalid. 60 {{ .Class }} SOA ns.invalid. hostmaster.invalid. (1 60 60 60 60)" | ||||||
|  |       ederror 21 "Blocked according to RFC2606" | ||||||
|     } |     } | ||||||
| } | } | ||||||
| ~~~ | ~~~ | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package template | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"regexp" | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
| 	gotmpl "text/template" | 	gotmpl "text/template" | ||||||
|  |  | ||||||
| 	"github.com/coredns/caddy" | 	"github.com/coredns/caddy" | ||||||
| @@ -123,6 +124,22 @@ func templateParse(c *caddy.Controller) (handler Handler, err error) { | |||||||
| 				} | 				} | ||||||
| 				t.rcode = rcode | 				t.rcode = rcode | ||||||
|  |  | ||||||
|  | 			case "ederror": | ||||||
|  | 				args := c.RemainingArgs() | ||||||
|  | 				if len(args) != 1 && len(args) != 2 { | ||||||
|  | 					return handler, c.ArgErr() | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				code, err := strconv.ParseUint(args[0], 10, 16) | ||||||
|  | 				if err != nil { | ||||||
|  | 					return handler, c.Errf("error parsing extended DNS error code %s, %v\n", c.Val(), err) | ||||||
|  | 				} | ||||||
|  | 				if len(args) == 2 { | ||||||
|  | 					t.ederror = &ederror{code: uint16(code), reason: args[1]} | ||||||
|  | 				} else { | ||||||
|  | 					t.ederror = &ederror{code: uint16(code)} | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 			case "fallthrough": | 			case "fallthrough": | ||||||
| 				t.fall.SetZonesFromArgs(c.RemainingArgs()) | 				t.fall.SetZonesFromArgs(c.RemainingArgs()) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -161,6 +161,30 @@ func TestSetupParse(t *testing.T) { | |||||||
| 				}`, | 				}`, | ||||||
| 			false, | 			false, | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			`template ANY ANY invalid { | ||||||
|  | 					rcode NXDOMAIN | ||||||
|  | 					authority "invalid. 60 {{ .Class }} SOA ns.invalid. hostmaster.invalid. (1 60 60 60 60)" | ||||||
|  | 					ederror 21 "Blocked according to RFC2606" | ||||||
|  | 			  	}`, | ||||||
|  | 			false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			`template ANY ANY invalid { | ||||||
|  | 					rcode NXDOMAIN | ||||||
|  | 					authority "invalid. 60 {{ .Class }} SOA ns.invalid. hostmaster.invalid. (1 60 60 60 60)" | ||||||
|  | 					ederror invalid "Blocked according to RFC2606" | ||||||
|  | 			  	}`, | ||||||
|  | 			true, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			`template ANY ANY invalid { | ||||||
|  | 					rcode NXDOMAIN | ||||||
|  | 					authority "invalid. 60 {{ .Class }} SOA ns.invalid. hostmaster.invalid. (1 60 60 60 60)" | ||||||
|  | 					ederror too many arguments | ||||||
|  | 			  	}`, | ||||||
|  | 			true, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 	for i, test := range tests { | 	for i, test := range tests { | ||||||
| 		c := caddy.NewTestController("dns", test.inputFileRules) | 		c := caddy.NewTestController("dns", test.inputFileRules) | ||||||
|   | |||||||
| @@ -33,10 +33,16 @@ type template struct { | |||||||
| 	authority  []*gotmpl.Template | 	authority  []*gotmpl.Template | ||||||
| 	qclass     uint16 | 	qclass     uint16 | ||||||
| 	qtype      uint16 | 	qtype      uint16 | ||||||
|  | 	ederror    *ederror | ||||||
| 	fall       fall.F | 	fall       fall.F | ||||||
| 	upstream   Upstreamer | 	upstream   Upstreamer | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type ederror struct { | ||||||
|  | 	code   uint16 | ||||||
|  | 	reason string | ||||||
|  | } | ||||||
|  |  | ||||||
| // Upstreamer looks up targets of CNAME templates | // Upstreamer looks up targets of CNAME templates | ||||||
| type Upstreamer interface { | type Upstreamer interface { | ||||||
| 	Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error) | 	Lookup(ctx context.Context, state request.Request, name string, typ uint16) (*dns.Msg, error) | ||||||
| @@ -125,6 +131,12 @@ func (h Handler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) | |||||||
| 			msg.Ns = append(msg.Ns, rr) | 			msg.Ns = append(msg.Ns, rr) | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		if template.ederror != nil { | ||||||
|  | 			msg = msg.SetEdns0(4096, true) | ||||||
|  | 			ede := dns.EDNS0_EDE{InfoCode: template.ederror.code, ExtraText: template.ederror.reason} | ||||||
|  | 			msg.IsEdns0().Option = append(msg.IsEdns0().Option, &ede) | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		w.WriteMsg(msg) | 		w.WriteMsg(msg) | ||||||
| 		return template.rcode, nil | 		return template.rcode, nil | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -143,6 +143,16 @@ func TestHandler(t *testing.T) { | |||||||
| 		fall:   fall.Root, | 		fall:   fall.Root, | ||||||
| 		zones:  []string{"."}, | 		zones:  []string{"."}, | ||||||
| 	} | 	} | ||||||
|  | 	templateWithEDE := template{ | ||||||
|  | 		rcode:     dns.RcodeNameError, | ||||||
|  | 		regex:     []*regexp.Regexp{regexp.MustCompile(".*")}, | ||||||
|  | 		authority: []*gotmpl.Template{gotmpl.Must(newTemplate("authority", "invalid. 60 {{ .Class }} SOA ns.invalid. hostmaster.invalid. (1 60 60 60 60)"))}, | ||||||
|  | 		qclass:    dns.ClassANY, | ||||||
|  | 		qtype:     dns.TypeANY, | ||||||
|  | 		fall:      fall.Root, | ||||||
|  | 		zones:     []string{"."}, | ||||||
|  | 		ederror:   &ederror{code: 21, reason: "Blocked due to RFC2606"}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	tests := []struct { | 	tests := []struct { | ||||||
| 		tmpl           template | 		tmpl           template | ||||||
| @@ -442,6 +452,33 @@ func TestHandler(t *testing.T) { | |||||||
| 				"foo": "myfoo", | 				"foo": "myfoo", | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "EDNS error", | ||||||
|  | 			tmpl:         templateWithEDE, | ||||||
|  | 			qclass:       dns.ClassINET, | ||||||
|  | 			qtype:        dns.TypeA, | ||||||
|  | 			qname:        "test.invalid.", | ||||||
|  | 			expectedCode: dns.RcodeNameError, | ||||||
|  | 			verifyResponse: func(r *dns.Msg) error { | ||||||
|  | 				if opt := r.IsEdns0(); opt != nil { | ||||||
|  | 					matched := false | ||||||
|  | 					for _, ednsopt := range opt.Option { | ||||||
|  | 						if ede, ok := ednsopt.(*dns.EDNS0_EDE); ok { | ||||||
|  | 							if ede.InfoCode != dns.ExtendedErrorCodeNotSupported { | ||||||
|  | 								return fmt.Errorf("unexpected EDE code = %v, want %v", ede.InfoCode, dns.ExtendedErrorCodeNotSupported) | ||||||
|  | 							} | ||||||
|  | 							matched = true | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 					if !matched { | ||||||
|  | 						t.Error("Error: acl.ServeDNS() missing Extended DNS Error option") | ||||||
|  | 					} | ||||||
|  | 				} else { | ||||||
|  | 					return fmt.Errorf("expected EDNS enabled") | ||||||
|  | 				} | ||||||
|  | 				return nil | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ctx := context.TODO() | 	ctx := context.TODO() | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user