package multiaddr import ( "net" "sync" ) // Action is an enum modelling all possible filter actions. type Action int32 const ( ActionNone Action = iota // zero value. ActionAccept ActionDeny ) type filterEntry struct { f net.IPNet action Action } // Filters is a structure representing a collection of accept/deny // net.IPNet filters, together with the DefaultAction flag, which // represents the default filter policy. // // Note that the last policy added to the Filters is authoritative. type Filters struct { DefaultAction Action mu sync.RWMutex filters []*filterEntry } // NewFilters constructs and returns a new set of net.IPNet filters. // By default, the new filter accepts all addresses. func NewFilters() *Filters { return &Filters{ DefaultAction: ActionAccept, filters: make([]*filterEntry, 0), } } func (fs *Filters) find(ipnet net.IPNet) (int, *filterEntry) { s := ipnet.String() for idx, ft := range fs.filters { if ft.f.String() == s { return idx, ft } } return -1, nil } // AddFilter adds a rule to the Filters set, enforcing the desired action for // the provided IPNet mask. func (fs *Filters) AddFilter(ipnet net.IPNet, action Action) { fs.mu.Lock() defer fs.mu.Unlock() if _, f := fs.find(ipnet); f != nil { f.action = action } else { fs.filters = append(fs.filters, &filterEntry{ipnet, action}) } } // RemoveLiteral removes the first filter associated with the supplied IPNet, // returning whether something was removed or not. It makes no distinction // between whether the rule is an accept or a deny. func (fs *Filters) RemoveLiteral(ipnet net.IPNet) (removed bool) { fs.mu.Lock() defer fs.mu.Unlock() if idx, _ := fs.find(ipnet); idx != -1 { fs.filters = append(fs.filters[:idx], fs.filters[idx+1:]...) return true } return false } // AddrBlocked parses a ma.Multiaddr and, if a valid netip is found, it applies the // Filter set rules, returning true if the given address should be denied, and false if // the given address is accepted. // // If a parsing error occurs, or no filter matches, the Filters' // default is returned. // // TODO: currently, the last filter to match wins always, but it shouldn't be that way. // Instead, the highest-specific last filter should win; that way more specific filters // override more general ones. func (fs *Filters) AddrBlocked(a Multiaddr) (deny bool) { var ( netip net.IP found bool ) ForEach(a, func(c Component) bool { switch c.Protocol().Code { case P_IP6ZONE: return true case P_IP6, P_IP4: found = true netip = net.IP(c.RawValue()) return false default: return false } }) if !found { return fs.DefaultAction == ActionDeny } fs.mu.RLock() defer fs.mu.RUnlock() action := fs.DefaultAction for _, ft := range fs.filters { if ft.f.Contains(netip) { action = ft.action } } return action == ActionDeny } func (fs *Filters) ActionForFilter(ipnet net.IPNet) (action Action, ok bool) { if _, f := fs.find(ipnet); f != nil { return f.action, true } return ActionNone, false } // FiltersForAction returns the filters associated with the indicated action. func (fs *Filters) FiltersForAction(action Action) (result []net.IPNet) { fs.mu.RLock() defer fs.mu.RUnlock() for _, ff := range fs.filters { if ff.action == action { result = append(result, ff.f) } } return result }