// Package match finds the address in a log line: patterns are regular // expressions (Go's, RE2) with one , which matches an IPv4 or IPv6 // address (bracketed, IPv4-mapped, with a zone). The address is checked // with netip, so what only looks like one (a time, 12:34:56) never is. package match import ( "fmt" "net/netip" "regexp" "strings" ) // Placeholder is where a pattern's address is. const Placeholder = "" // ipRe matches an address: IPv4, or IPv6 (which may end in IPv4, // ::ffff:203.0.113.9), bracketed or not, with an optional zone. const ipRe = `\[?(?P[0-9]{1,3}(?:\.[0-9]{1,3}){3}|[0-9A-Fa-f]{0,4}(?::[0-9A-Fa-f]{0,4}){1,7}(?:[0-9]{1,3}(?:\.[0-9]{1,3}){3})?(?:%[0-9A-Za-z_.-]+)?)\]?` // Matcher holds a watch's patterns. type Matcher struct { match []*regexp.Regexp ignore []*regexp.Regexp } // Compile makes a Matcher. Each match pattern must hold exactly // once; ignore patterns are plain regular expressions. func Compile(match, ignore []string) (*Matcher, error) { m := &Matcher{} for _, p := range match { if n := strings.Count(p, Placeholder); n != 1 { return nil, fmt.Errorf("pattern %q has %d %s; it needs exactly one, where the address is", p, n, Placeholder) } re, err := regexp.Compile(strings.Replace(p, Placeholder, ipRe, 1)) if err != nil { return nil, fmt.Errorf("pattern %q: %v", p, err) } m.match = append(m.match, re) } for _, p := range ignore { re, err := regexp.Compile(p) if err != nil { return nil, fmt.Errorf("ignore pattern %q: %v", p, err) } m.ignore = append(m.ignore, re) } return m, nil } // Match returns the address of the first pattern a line matches, unless // an ignore pattern matches it too. func (m *Matcher) Match(line string) (netip.Addr, bool) { for _, re := range m.match { sub := re.FindStringSubmatch(line) if sub == nil { continue } text := sub[re.SubexpIndex("ip")] if i := strings.IndexByte(text, '%'); i >= 0 { text = text[:i] } a, err := netip.ParseAddr(text) if err != nil { continue } for _, ig := range m.ignore { if ig.MatchString(line) { return netip.Addr{}, false } } return a.Unmap(), true } return netip.Addr{}, false } // Group is the prefix an address is counted and banned by: an IPv4 // address alone, an IPv6 address by its /v6 (64: what one customer // usually has). func Group(a netip.Addr, v6 int) netip.Prefix { if a.Is4() || v6 <= 0 || v6 > 128 { if a.Is4() { return netip.PrefixFrom(a, 32) } v6 = 128 } p, _ := a.Prefix(v6) return p }