mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-30 17:53:21 -04:00 
			
		
		
		
	* dnstap: add 'extra' field Signed-off-by: chenyuheng <chenyuheng99@qq.com> * dnstap: add setup_test for 'extra' field Signed-off-by: chenyuheng <chenyuheng99@qq.com> * udnstap: update document and test Signed-off-by: chenyuheng <chenyuheng99@qq.com> * dnstap: update setup_test for more coverage Signed-off-by: chenyuheng <chenyuheng99@qq.com> * dnstap: add TapMessageWithMetadata function to Dnstap Signed-off-by: chenyuheng <chenyuheng99@qq.com> * dnstap: adapt dnstap and forward plugins to use TapMessageWithMetadata Signed-off-by: chenyuheng <chenyuheng99@qq.com> * change TapMessageWithMetadata function Signed-off-by: chenyuheng <chenyuheng99@qq.com> * tab inconsistency fix Signed-off-by: chenyuheng <chenyuheng99@qq.com> * fix replacer to support empty state Signed-off-by: chenyuheng <chenyuheng99@qq.com> * add replacer test for empty status parameter Signed-off-by: chenyuheng <chenyuheng99@qq.com> * dnstap: update unit test for 'extra' field Signed-off-by: chenyuheng <chenyuheng99@qq.com> * clean up code Signed-off-by: chenyuheng <chenyuheng99@qq.com> * gofmt fix & static analysis fix Signed-off-by: chenyuheng <chenyuheng99@qq.com> * dnstap: refactor Signed-off-by: chenyuheng <chenyuheng99@qq.com> --------- Signed-off-by: chenyuheng <chenyuheng99@qq.com>
		
			
				
	
	
		
			285 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			285 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package replacer
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/coredns/coredns/plugin/metadata"
 | |
| 	"github.com/coredns/coredns/plugin/pkg/dnstest"
 | |
| 	"github.com/coredns/coredns/request"
 | |
| 
 | |
| 	"github.com/miekg/dns"
 | |
| )
 | |
| 
 | |
| // Replacer replaces labels for values in strings.
 | |
| 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)
 | |
| }
 | |
| 
 | |
| const (
 | |
| 	headerReplacer = "{>"
 | |
| 	// EmptyValue is the default empty value.
 | |
| 	EmptyValue = "-"
 | |
| )
 | |
| 
 | |
| // labels are all supported labels that can be used in the default Replacer.
 | |
| var labels = map[string]struct{}{
 | |
| 	"{type}":   {},
 | |
| 	"{name}":   {},
 | |
| 	"{class}":  {},
 | |
| 	"{proto}":  {},
 | |
| 	"{size}":   {},
 | |
| 	"{remote}": {},
 | |
| 	"{port}":   {},
 | |
| 	"{local}":  {},
 | |
| 	// Header values.
 | |
| 	headerReplacer + "id}":      {},
 | |
| 	headerReplacer + "opcode}":  {},
 | |
| 	headerReplacer + "do}":      {},
 | |
| 	headerReplacer + "bufsize}": {},
 | |
| 	// Recorded replacements.
 | |
| 	"{rcode}":                  {},
 | |
| 	"{rsize}":                  {},
 | |
| 	"{duration}":               {},
 | |
| 	headerReplacer + "rflags}": {},
 | |
| }
 | |
| 
 | |
| // appendValue appends the current value of label.
 | |
| func appendValue(b []byte, state request.Request, rr *dnstest.Recorder, label string) []byte {
 | |
| 	switch label {
 | |
| 	// Recorded replacements.
 | |
| 	case "{rcode}":
 | |
| 		if rr == nil || rr.Msg == nil {
 | |
| 			return append(b, EmptyValue...)
 | |
| 		}
 | |
| 		if rcode := dns.RcodeToString[rr.Rcode]; rcode != "" {
 | |
| 			return append(b, rcode...)
 | |
| 		}
 | |
| 		return strconv.AppendInt(b, int64(rr.Rcode), 10)
 | |
| 	case "{rsize}":
 | |
| 		if rr == nil {
 | |
| 			return append(b, EmptyValue...)
 | |
| 		}
 | |
| 		return strconv.AppendInt(b, int64(rr.Len), 10)
 | |
| 	case "{duration}":
 | |
| 		if rr == nil {
 | |
| 			return append(b, EmptyValue...)
 | |
| 		}
 | |
| 		secs := time.Since(rr.Start).Seconds()
 | |
| 		return append(strconv.AppendFloat(b, secs, 'f', -1, 64), 's')
 | |
| 	case headerReplacer + "rflags}":
 | |
| 		if rr != nil && rr.Msg != nil {
 | |
| 			return appendFlags(b, rr.Msg.MsgHdr)
 | |
| 		}
 | |
| 		return append(b, EmptyValue...)
 | |
| 	}
 | |
| 
 | |
| 	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)
 | |
| 	default:
 | |
| 		return append(b, EmptyValue...)
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // appendFlags checks all header flags and appends those
 | |
| // that are set as a string separated with commas
 | |
| func appendFlags(b []byte, h dns.MsgHdr) []byte {
 | |
| 	origLen := len(b)
 | |
| 	if h.Response {
 | |
| 		b = append(b, "qr,"...)
 | |
| 	}
 | |
| 	if h.Authoritative {
 | |
| 		b = append(b, "aa,"...)
 | |
| 	}
 | |
| 	if h.Truncated {
 | |
| 		b = append(b, "tc,"...)
 | |
| 	}
 | |
| 	if h.RecursionDesired {
 | |
| 		b = append(b, "rd,"...)
 | |
| 	}
 | |
| 	if h.RecursionAvailable {
 | |
| 		b = append(b, "ra,"...)
 | |
| 	}
 | |
| 	if h.Zero {
 | |
| 		b = append(b, "z,"...)
 | |
| 	}
 | |
| 	if h.AuthenticatedData {
 | |
| 		b = append(b, "ad,"...)
 | |
| 	}
 | |
| 	if h.CheckingDisabled {
 | |
| 		b = append(b, "cd,"...)
 | |
| 	}
 | |
| 	if n := len(b); n > origLen {
 | |
| 		return b[:n-1] // trim trailing ','
 | |
| 	}
 | |
| 	return b
 | |
| }
 | |
| 
 | |
| // 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...)
 | |
| 	}
 | |
| 	return b
 | |
| }
 | |
| 
 | |
| type nodeType int
 | |
| 
 | |
| const (
 | |
| 	typeLabel    nodeType = iota // "{type}"
 | |
| 	typeLiteral                  // "foo"
 | |
| 	typeMetadata                 // "{/metadata}"
 | |
| )
 | |
| 
 | |
| // 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)
 | |
| 	//nolint:staticcheck
 | |
| 	bufPool.Put(b[:0])
 | |
| 	return s
 | |
| }
 |