| 
									
										
										
										
											2016-09-07 11:10:16 +01:00
										 |  |  | package replacer | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2018-11-13 14:20:49 -05:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-13 14:20:49 -05:00
										 |  |  | 	"github.com/coredns/coredns/plugin/metadata" | 
					
						
							| 
									
										
										
										
											2017-09-21 15:15:47 +01:00
										 |  |  | 	"github.com/coredns/coredns/plugin/pkg/dnstest" | 
					
						
							| 
									
										
										
										
											2017-02-21 22:51:47 -08:00
										 |  |  | 	"github.com/coredns/coredns/request" | 
					
						
							| 
									
										
										
										
											2016-09-07 11:10:16 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	"github.com/miekg/dns" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-12 07:38:49 +00:00
										 |  |  | // Replacer replaces labels for values in strings. | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | type Replacer struct{} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // New makes a new replacer. This only needs to be called once in the setup and | 
					
						
							|  |  |  | // then call Replace for each incoming message. A replacer is safe for concurrent use. | 
					
						
							|  |  |  | func New() Replacer { | 
					
						
							|  |  |  | 	return Replacer{} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Replace performs a replacement of values on s and returns the string with the replaced values. | 
					
						
							|  |  |  | func (r Replacer) Replace(ctx context.Context, state request.Request, rr *dnstest.Recorder, s string) string { | 
					
						
							|  |  |  | 	return loadFormat(s).Replace(ctx, state, rr) | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | const ( | 
					
						
							|  |  |  | 	headerReplacer = "{>" | 
					
						
							|  |  |  | 	// EmptyValue is the default empty value. | 
					
						
							|  |  |  | 	EmptyValue = "-" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-12 07:38:49 +00:00
										 |  |  | // labels are all supported labels that can be used in the default Replacer. | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | var labels = map[string]struct{}{ | 
					
						
							|  |  |  | 	"{type}":   {}, | 
					
						
							|  |  |  | 	"{name}":   {}, | 
					
						
							|  |  |  | 	"{class}":  {}, | 
					
						
							|  |  |  | 	"{proto}":  {}, | 
					
						
							|  |  |  | 	"{size}":   {}, | 
					
						
							|  |  |  | 	"{remote}": {}, | 
					
						
							|  |  |  | 	"{port}":   {}, | 
					
						
							|  |  |  | 	"{local}":  {}, | 
					
						
							| 
									
										
										
										
											2019-02-12 07:38:49 +00:00
										 |  |  | 	// Header values. | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 	headerReplacer + "id}":      {}, | 
					
						
							|  |  |  | 	headerReplacer + "opcode}":  {}, | 
					
						
							|  |  |  | 	headerReplacer + "do}":      {}, | 
					
						
							|  |  |  | 	headerReplacer + "bufsize}": {}, | 
					
						
							| 
									
										
										
										
											2019-02-12 07:38:49 +00:00
										 |  |  | 	// Recorded replacements. | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 	"{rcode}":                  {}, | 
					
						
							|  |  |  | 	"{rsize}":                  {}, | 
					
						
							|  |  |  | 	"{duration}":               {}, | 
					
						
							|  |  |  | 	headerReplacer + "rflags}": {}, | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | // appendValue appends the current value of label. | 
					
						
							|  |  |  | func appendValue(b []byte, state request.Request, rr *dnstest.Recorder, label string) []byte { | 
					
						
							| 
									
										
										
										
											2019-02-12 07:38:49 +00:00
										 |  |  | 	switch label { | 
					
						
							|  |  |  | 	// Recorded replacements. | 
					
						
							|  |  |  | 	case "{rcode}": | 
					
						
							| 
									
										
										
										
											2021-07-09 13:15:34 +02:00
										 |  |  | 		if rr == nil || rr.Msg == nil { | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 			return append(b, EmptyValue...) | 
					
						
							| 
									
										
										
										
											2019-02-12 07:38:49 +00:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		if rcode := dns.RcodeToString[rr.Rcode]; rcode != "" { | 
					
						
							|  |  |  | 			return append(b, rcode...) | 
					
						
							| 
									
										
										
										
											2016-03-19 20:17:44 +00:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		return strconv.AppendInt(b, int64(rr.Rcode), 10) | 
					
						
							| 
									
										
										
										
											2019-02-12 07:38:49 +00:00
										 |  |  | 	case "{rsize}": | 
					
						
							|  |  |  | 		if rr == nil { | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 			return append(b, EmptyValue...) | 
					
						
							| 
									
										
										
										
											2019-02-12 07:38:49 +00:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		return strconv.AppendInt(b, int64(rr.Len), 10) | 
					
						
							| 
									
										
										
										
											2019-02-12 07:38:49 +00:00
										 |  |  | 	case "{duration}": | 
					
						
							|  |  |  | 		if rr == nil { | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 			return append(b, EmptyValue...) | 
					
						
							| 
									
										
										
										
											2019-02-12 07:38:49 +00:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		secs := time.Since(rr.Start).Seconds() | 
					
						
							|  |  |  | 		return append(strconv.AppendFloat(b, secs, 'f', -1, 64), 's') | 
					
						
							| 
									
										
										
										
											2019-02-21 07:13:05 +00:00
										 |  |  | 	case headerReplacer + "rflags}": | 
					
						
							| 
									
										
										
										
											2019-02-12 07:38:49 +00:00
										 |  |  | 		if rr != nil && rr.Msg != nil { | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 			return appendFlags(b, rr.Msg.MsgHdr) | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		return append(b, EmptyValue...) | 
					
						
							| 
									
										
										
										
											2023-08-14 14:01:13 -04:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (request.Request{}) == state { | 
					
						
							|  |  |  | 		return append(b, EmptyValue...) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch label { | 
					
						
							|  |  |  | 	case "{type}": | 
					
						
							|  |  |  | 		return append(b, state.Type()...) | 
					
						
							|  |  |  | 	case "{name}": | 
					
						
							|  |  |  | 		return append(b, state.Name()...) | 
					
						
							|  |  |  | 	case "{class}": | 
					
						
							|  |  |  | 		return append(b, state.Class()...) | 
					
						
							|  |  |  | 	case "{proto}": | 
					
						
							|  |  |  | 		return append(b, state.Proto()...) | 
					
						
							|  |  |  | 	case "{size}": | 
					
						
							|  |  |  | 		return strconv.AppendInt(b, int64(state.Req.Len()), 10) | 
					
						
							|  |  |  | 	case "{remote}": | 
					
						
							|  |  |  | 		return appendAddrToRFC3986(b, state.IP()) | 
					
						
							|  |  |  | 	case "{port}": | 
					
						
							|  |  |  | 		return append(b, state.Port()...) | 
					
						
							|  |  |  | 	case "{local}": | 
					
						
							|  |  |  | 		return appendAddrToRFC3986(b, state.LocalIP()) | 
					
						
							|  |  |  | 	// Header placeholders (case-insensitive). | 
					
						
							|  |  |  | 	case headerReplacer + "id}": | 
					
						
							|  |  |  | 		return strconv.AppendInt(b, int64(state.Req.Id), 10) | 
					
						
							|  |  |  | 	case headerReplacer + "opcode}": | 
					
						
							|  |  |  | 		return strconv.AppendInt(b, int64(state.Req.Opcode), 10) | 
					
						
							|  |  |  | 	case headerReplacer + "do}": | 
					
						
							|  |  |  | 		return strconv.AppendBool(b, state.Do()) | 
					
						
							|  |  |  | 	case headerReplacer + "bufsize}": | 
					
						
							|  |  |  | 		return strconv.AppendInt(b, int64(state.Size()), 10) | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 	default: | 
					
						
							|  |  |  | 		return append(b, EmptyValue...) | 
					
						
							| 
									
										
										
										
											2016-03-18 20:57:35 +00:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-04-03 17:16:46 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | // appendFlags checks all header flags and appends those | 
					
						
							| 
									
										
										
										
											2017-08-07 03:49:40 -07:00
										 |  |  | // that are set as a string separated with commas | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | func appendFlags(b []byte, h dns.MsgHdr) []byte { | 
					
						
							|  |  |  | 	origLen := len(b) | 
					
						
							| 
									
										
										
										
											2017-08-07 03:49:40 -07:00
										 |  |  | 	if h.Response { | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		b = append(b, "qr,"...) | 
					
						
							| 
									
										
										
										
											2017-08-07 03:49:40 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if h.Authoritative { | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		b = append(b, "aa,"...) | 
					
						
							| 
									
										
										
										
											2017-08-07 03:49:40 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if h.Truncated { | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		b = append(b, "tc,"...) | 
					
						
							| 
									
										
										
										
											2017-08-07 03:49:40 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if h.RecursionDesired { | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		b = append(b, "rd,"...) | 
					
						
							| 
									
										
										
										
											2017-08-07 03:49:40 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if h.RecursionAvailable { | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		b = append(b, "ra,"...) | 
					
						
							| 
									
										
										
										
											2017-08-07 03:49:40 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if h.Zero { | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		b = append(b, "z,"...) | 
					
						
							| 
									
										
										
										
											2017-08-07 03:49:40 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if h.AuthenticatedData { | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		b = append(b, "ad,"...) | 
					
						
							| 
									
										
										
										
											2017-08-07 03:49:40 -07:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	if h.CheckingDisabled { | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 		b = append(b, "cd,"...) | 
					
						
							| 
									
										
										
										
											2017-08-07 03:49:40 -07:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 	if n := len(b); n > origLen { | 
					
						
							|  |  |  | 		return b[:n-1] // trim trailing ',' | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return b | 
					
						
							| 
									
										
										
										
											2017-08-07 03:49:40 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | // appendAddrToRFC3986 will add brackets to the address if it is an IPv6 address. | 
					
						
							|  |  |  | func appendAddrToRFC3986(b []byte, addr string) []byte { | 
					
						
							|  |  |  | 	if strings.IndexByte(addr, ':') != -1 { | 
					
						
							|  |  |  | 		b = append(b, '[') | 
					
						
							|  |  |  | 		b = append(b, addr...) | 
					
						
							|  |  |  | 		b = append(b, ']') | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		b = append(b, addr...) | 
					
						
							| 
									
										
										
										
											2018-02-28 18:15:12 -08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 	return b | 
					
						
							| 
									
										
										
										
											2018-02-28 18:15:12 -08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | type nodeType int | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-13 14:20:49 -05:00
										 |  |  | const ( | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 	typeLabel    nodeType = iota // "{type}" | 
					
						
							|  |  |  | 	typeLiteral                  // "foo" | 
					
						
							|  |  |  | 	typeMetadata                 // "{/metadata}" | 
					
						
							| 
									
										
										
										
											2018-11-13 14:20:49 -05:00
										 |  |  | ) | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | // A node represents a segment of a parsed format.  For example: "A {type}" | 
					
						
							|  |  |  | // contains two nodes: "A " (literal); and "{type}" (label). | 
					
						
							|  |  |  | type node struct { | 
					
						
							|  |  |  | 	value string // Literal value, label or metadata label | 
					
						
							|  |  |  | 	typ   nodeType | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // A replacer is an ordered list of all the nodes in a format. | 
					
						
							|  |  |  | type replacer []node | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func parseFormat(s string) replacer { | 
					
						
							|  |  |  | 	// Assume there is a literal between each label - its cheaper to over | 
					
						
							|  |  |  | 	// allocate once than allocate twice. | 
					
						
							|  |  |  | 	rep := make(replacer, 0, strings.Count(s, "{")*2) | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		// We find the right bracket then backtrack to find the left bracket. | 
					
						
							|  |  |  | 		// This allows us to handle formats like: "{ {foo} }". | 
					
						
							|  |  |  | 		j := strings.IndexByte(s, '}') | 
					
						
							|  |  |  | 		if j < 0 { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		i := strings.LastIndexByte(s[:j], '{') | 
					
						
							|  |  |  | 		if i < 0 { | 
					
						
							|  |  |  | 			// Handle: "A } {foo}" by treating "A }" as a literal | 
					
						
							|  |  |  | 			rep = append(rep, node{ | 
					
						
							|  |  |  | 				value: s[:j+1], | 
					
						
							|  |  |  | 				typ:   typeLiteral, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 			s = s[j+1:] | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		val := s[i : j+1] | 
					
						
							|  |  |  | 		var typ nodeType | 
					
						
							|  |  |  | 		switch _, ok := labels[val]; { | 
					
						
							|  |  |  | 		case ok: | 
					
						
							|  |  |  | 			typ = typeLabel | 
					
						
							|  |  |  | 		case strings.HasPrefix(val, "{/"): | 
					
						
							|  |  |  | 			// Strip "{/}" from metadata labels | 
					
						
							|  |  |  | 			val = val[2 : len(val)-1] | 
					
						
							|  |  |  | 			typ = typeMetadata | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			// Given: "A {X}" val is "{X}" expand it to the whole literal. | 
					
						
							|  |  |  | 			val = s[:j+1] | 
					
						
							|  |  |  | 			typ = typeLiteral | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Append any leading literal.  Given "A {type}" the literal is "A " | 
					
						
							|  |  |  | 		if i != 0 && typ != typeLiteral { | 
					
						
							|  |  |  | 			rep = append(rep, node{ | 
					
						
							|  |  |  | 				value: s[:i], | 
					
						
							|  |  |  | 				typ:   typeLiteral, | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		rep = append(rep, node{ | 
					
						
							|  |  |  | 			value: val, | 
					
						
							|  |  |  | 			typ:   typ, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		s = s[j+1:] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if len(s) != 0 { | 
					
						
							|  |  |  | 		rep = append(rep, node{ | 
					
						
							|  |  |  | 			value: s, | 
					
						
							|  |  |  | 			typ:   typeLiteral, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return rep | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var replacerCache sync.Map // map[string]replacer | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func loadFormat(s string) replacer { | 
					
						
							|  |  |  | 	if v, ok := replacerCache.Load(s); ok { | 
					
						
							|  |  |  | 		return v.(replacer) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	v, _ := replacerCache.LoadOrStore(s, parseFormat(s)) | 
					
						
							|  |  |  | 	return v.(replacer) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // bufPool stores pointers to scratch buffers. | 
					
						
							|  |  |  | var bufPool = sync.Pool{ | 
					
						
							|  |  |  | 	New: func() interface{} { | 
					
						
							|  |  |  | 		return make([]byte, 0, 256) | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (r replacer) Replace(ctx context.Context, state request.Request, rr *dnstest.Recorder) string { | 
					
						
							|  |  |  | 	b := bufPool.Get().([]byte) | 
					
						
							|  |  |  | 	for _, s := range r { | 
					
						
							|  |  |  | 		switch s.typ { | 
					
						
							|  |  |  | 		case typeLabel: | 
					
						
							|  |  |  | 			b = appendValue(b, state, rr, s.value) | 
					
						
							|  |  |  | 		case typeLiteral: | 
					
						
							|  |  |  | 			b = append(b, s.value...) | 
					
						
							|  |  |  | 		case typeMetadata: | 
					
						
							|  |  |  | 			if fm := metadata.ValueFunc(ctx, s.value); fm != nil { | 
					
						
							|  |  |  | 				b = append(b, fm()...) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				b = append(b, EmptyValue...) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	s := string(b) | 
					
						
							| 
									
										
										
										
											2022-07-10 20:06:33 +02:00
										 |  |  | 	//nolint:staticcheck | 
					
						
							| 
									
										
										
										
											2019-07-17 02:57:46 -04:00
										 |  |  | 	bufPool.Put(b[:0]) | 
					
						
							|  |  |  | 	return s | 
					
						
							|  |  |  | } |