github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/logql/log/ip.go (about)

     1  package log
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"unicode"
     7  
     8  	"github.com/prometheus/prometheus/model/labels"
     9  	"inet.af/netaddr"
    10  )
    11  
    12  var (
    13  	ErrIPFilterInvalidPattern   = errors.New("ip: invalid pattern")
    14  	ErrIPFilterInvalidOperation = errors.New("ip: invalid operation")
    15  )
    16  
    17  type IPMatchType int
    18  
    19  const (
    20  	IPv4Charset = "0123456789."
    21  	IPv6Charset = "0123456789abcdefABCDEF:."
    22  )
    23  
    24  // Should be one of the netaddr.IP, netaddr.IPRange, netadd.IPPrefix.
    25  type IPMatcher interface{}
    26  
    27  type IPLineFilter struct {
    28  	ip *ipFilter
    29  	ty labels.MatchType
    30  }
    31  
    32  // NewIPLineFilter is used to construct ip filter as a `LineFilter`
    33  func NewIPLineFilter(pattern string, ty labels.MatchType) (*IPLineFilter, error) {
    34  	// check if `ty` supported in ip matcher.
    35  	switch ty {
    36  	case labels.MatchEqual, labels.MatchNotEqual:
    37  	default:
    38  		return nil, ErrIPFilterInvalidOperation
    39  	}
    40  
    41  	ip, err := newIPFilter(pattern)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	return &IPLineFilter{
    46  		ip: ip,
    47  		ty: ty,
    48  	}, nil
    49  }
    50  
    51  // Filter implement `Filterer` interface. Used by `LineFilter`
    52  func (f *IPLineFilter) Filter(line []byte) bool {
    53  	return f.filterTy(line, f.ty)
    54  }
    55  
    56  // ToStage implements `Filterer` interface.
    57  func (f *IPLineFilter) ToStage() Stage {
    58  	return f
    59  }
    60  
    61  // `Process` implements `Stage` interface
    62  func (f *IPLineFilter) Process(_ int64, line []byte, _ *LabelsBuilder) ([]byte, bool) {
    63  	return line, f.filterTy(line, f.ty)
    64  }
    65  
    66  // `RequiredLabelNames` implements `Stage` interface
    67  func (f *IPLineFilter) RequiredLabelNames() []string {
    68  	return []string{} // empty for line filter
    69  }
    70  
    71  func (f *IPLineFilter) filterTy(line []byte, ty labels.MatchType) bool {
    72  	if ty == labels.MatchNotEqual {
    73  		return !f.ip.filter(line)
    74  	}
    75  	return f.ip.filter(line)
    76  }
    77  
    78  type IPLabelFilter struct {
    79  	ip *ipFilter
    80  	ty LabelFilterType
    81  
    82  	// if used as label matcher, this holds the identifier label name.
    83  	// e.g: (|remote_addr = ip("xxx")). Here labelName is `remote_addr`
    84  	label string
    85  
    86  	// patError records if given pattern is invalid.
    87  	patError error
    88  
    89  	// local copy of pattern to display it in errors, even though pattern matcher fails because of invalid pattern.
    90  	pattern string
    91  }
    92  
    93  // NewIPLabelFilter is used to construct ip filter as label filter for the given `label`.
    94  func NewIPLabelFilter(pattern string, label string, ty LabelFilterType) *IPLabelFilter {
    95  	ip, err := newIPFilter(pattern)
    96  	return &IPLabelFilter{
    97  		ip:       ip,
    98  		label:    label,
    99  		ty:       ty,
   100  		patError: err,
   101  		pattern:  pattern,
   102  	}
   103  }
   104  
   105  // `Process` implements `Stage` interface
   106  func (f *IPLabelFilter) Process(_ int64, line []byte, lbs *LabelsBuilder) ([]byte, bool) {
   107  	return line, f.filterTy(line, f.ty, lbs)
   108  }
   109  
   110  // `RequiredLabelNames` implements `Stage` interface
   111  func (f *IPLabelFilter) RequiredLabelNames() []string {
   112  	return []string{f.label}
   113  }
   114  
   115  // PatternError will be used `labelFilter.Stage()` method so that, if the given pattern is wrong
   116  // it returns proper 400 error to the client of LogQL.
   117  func (f *IPLabelFilter) PatternError() error {
   118  	return f.patError
   119  }
   120  
   121  func (f *IPLabelFilter) filterTy(_ []byte, ty LabelFilterType, lbs *LabelsBuilder) bool {
   122  	if lbs.HasErr() {
   123  		// why `true`?. if there's an error only the string matchers can filter out.
   124  		return true
   125  	}
   126  	input, ok := lbs.Get(f.label)
   127  	if !ok {
   128  		// we have not found the label.
   129  		return false
   130  	}
   131  
   132  	if f.ip == nil {
   133  		return false
   134  	}
   135  
   136  	switch ty {
   137  	case LabelFilterEqual:
   138  		return f.ip.filter([]byte(input))
   139  	case LabelFilterNotEqual:
   140  		return !f.ip.filter([]byte(input))
   141  	}
   142  	return false
   143  }
   144  
   145  // `String` implements fmt.Stringer inteface, by which also implements `LabelFilterer` inteface.
   146  func (f *IPLabelFilter) String() string {
   147  	eq := "=" // LabelFilterEqual -> "==", we don't want in string representation of ip label filter.
   148  	if f.ty == LabelFilterNotEqual {
   149  		eq = LabelFilterNotEqual.String()
   150  	}
   151  
   152  	return fmt.Sprintf("%s%sip(%q)", f.label, eq, f.pattern) // label filter
   153  }
   154  
   155  // ipFilter search for IP addresses of given `pattern` in the given `line`.
   156  // It returns true if pattern is matched with at least one IP in the `line`
   157  
   158  // pattern - can be of the following form for both IPv4 and IPv6.
   159  // 1. SINGLE-IP - "192.168.0.1"
   160  // 2. IP RANGE  - "192.168.0.1-192.168.0.23"
   161  // 3. CIDR      - "192.168.0.0/16"
   162  type ipFilter struct {
   163  	pattern string
   164  	matcher IPMatcher
   165  }
   166  
   167  func newIPFilter(pattern string) (*ipFilter, error) {
   168  	filter := &ipFilter{pattern: pattern}
   169  
   170  	matcher, err := getMatcher(pattern)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	filter.matcher = matcher
   175  
   176  	return filter, nil
   177  }
   178  
   179  // filter does the heavy lifting finding ip `pattern` in the givin `line`.
   180  // This is the function if you want to understand how the core logic how ip filter works!
   181  func (f *ipFilter) filter(line []byte) bool {
   182  	if len(line) == 0 {
   183  		return false
   184  	}
   185  
   186  	n := len(line)
   187  
   188  	filterFn := func(line []byte, start int, charset string) (bool, int) {
   189  		iplen := bytesSpan(line[start:], []byte(charset))
   190  		if iplen < 0 {
   191  			return false, 0
   192  		}
   193  		ip, err := netaddr.ParseIP(string(line[start : start+iplen]))
   194  		if err == nil {
   195  			if containsIP(f.matcher, ip) {
   196  				return true, 0
   197  			}
   198  		}
   199  		return false, iplen
   200  	}
   201  
   202  	// This loop try to extract IPv4 or IPv6 address from the arbitrary string.
   203  	// It uses IPv4 and IPv6 prefix hints to find the IP addresses faster without using regexp.
   204  	for i := 0; i < n; i++ {
   205  		if i+3 < n && ipv4Hint([4]byte{line[i], line[i+1], line[i+2], line[i+3]}) {
   206  			ok, iplen := filterFn(line, i, IPv4Charset)
   207  			if ok {
   208  				return true
   209  			}
   210  			i += iplen
   211  			continue
   212  		}
   213  
   214  		if i+4 < n && ipv6Hint([5]byte{line[i], line[i+1], line[i+2], line[i+3], line[i+4]}) {
   215  			ok, iplen := filterFn(line, i, IPv6Charset)
   216  			if ok {
   217  				return true
   218  			}
   219  			i += iplen
   220  			continue
   221  		}
   222  	}
   223  	return false
   224  }
   225  
   226  func containsIP(matcher IPMatcher, ip netaddr.IP) bool {
   227  	switch m := matcher.(type) {
   228  	case netaddr.IP:
   229  		return m.Compare(ip) == 0
   230  	case netaddr.IPRange:
   231  		return m.Contains(ip)
   232  	case netaddr.IPPrefix:
   233  		return m.Contains(ip)
   234  	}
   235  	return false
   236  }
   237  
   238  func getMatcher(pattern string) (IPMatcher, error) {
   239  	var (
   240  		matcher IPMatcher
   241  		err     error
   242  	)
   243  
   244  	matcher, err = netaddr.ParseIP(pattern) // is it simple single IP?
   245  	if err == nil {
   246  		return matcher, nil
   247  	}
   248  	matcher, err = netaddr.ParseIPPrefix(pattern) // is it cidr format? (192.168.0.1/16)
   249  	if err == nil {
   250  		return matcher, nil
   251  	}
   252  
   253  	matcher, err = netaddr.ParseIPRange(pattern) // is it IP range format? (192.168.0.1 - 192.168.4.5
   254  	if err == nil {
   255  		return matcher, nil
   256  	}
   257  
   258  	return nil, fmt.Errorf("%w: %q", ErrIPFilterInvalidPattern, pattern)
   259  }
   260  
   261  func ipv4Hint(prefix [4]byte) bool {
   262  	return unicode.IsDigit(rune(prefix[0])) && (prefix[1] == '.' || prefix[2] == '.' || prefix[3] == '.')
   263  }
   264  
   265  func ipv6Hint(prefix [5]byte) bool {
   266  	hint1 := prefix[0] == ':' && prefix[1] == ':' && isHexDigit(prefix[2])
   267  	hint2 := isHexDigit(prefix[0]) && prefix[1] == ':'
   268  	hint3 := isHexDigit(prefix[0]) && isHexDigit(prefix[1]) && prefix[2] == ':'
   269  	hint4 := isHexDigit(prefix[0]) && isHexDigit(prefix[1]) && isHexDigit(prefix[2]) && prefix[3] == ':'
   270  	hint5 := isHexDigit(prefix[0]) && isHexDigit(prefix[1]) && isHexDigit(prefix[2]) && isHexDigit(prefix[3]) && prefix[4] == ':'
   271  
   272  	return hint1 || hint2 || hint3 || hint4 || hint5
   273  }
   274  
   275  func isHexDigit(r byte) bool {
   276  	return unicode.IsDigit(rune(r)) || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')
   277  }
   278  
   279  // bytesSpan is same as C's `strcspan()` function.
   280  // It returns the number of chars in the initial segment of `s`
   281  // which consist only of chars from `accept`.
   282  func bytesSpan(s, accept []byte) int {
   283  	m := make(map[byte]bool)
   284  
   285  	for _, r := range accept {
   286  		m[r] = true
   287  	}
   288  
   289  	for i, r := range s {
   290  		if !m[r] {
   291  			return i
   292  		}
   293  	}
   294  
   295  	return len(s)
   296  }