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 }