Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add routing filtering to delegated routing server IPIP-484 #671

Merged
merged 21 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions routing/http/server/filters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package server

import (
"reflect"
"slices"
"strings"

"github.com/ipfs/boxo/routing/http/types"
"github.com/multiformats/go-multiaddr"
)

// filters implements IPIP-0484

func parseFilter(param string) []string {
if param == "" {
return nil
}
return strings.Split(strings.ToLower(param), ",")
}

func filterProviders(providers []types.Record, filterAddrs, filterProtocols []string) []types.Record {
if len(filterAddrs) == 0 && len(filterProtocols) == 0 {
return providers
}

filtered := make([]types.Record, 0, len(providers))

for _, provider := range providers {
if schema := provider.GetSchema(); schema == types.SchemaPeer {
peer, ok := provider.(*types.PeerRecord)
if !ok {
logger.Errorw("problem casting find providers result", "Schema", provider.GetSchema(), "Type", reflect.TypeOf(provider).String())
// if the type assertion fails, we exlude record from results
continue
}

record := applyFilters(peer, filterAddrs, filterProtocols)

if record != nil {
filtered = append(filtered, record)
}

} else {
// Will we ever encounter the SchemaBitswap type? Evidence seems to suggest that no longer
logger.Errorw("encountered unknown provider schema", "Schema", provider.GetSchema(), "Type", reflect.TypeOf(provider).String())
}
}
return filtered
}

// Applies the filters. Returns nil if the provider does not pass the protocols filter
// The address filter is more complicated because it potentially modifies the Addrs slice.
func applyFilters(provider *types.PeerRecord, filterAddrs, filterProtocols []string) *types.PeerRecord {
if !applyProtocolFilter(provider.Protocols, filterProtocols) {
// If the provider doesn't match any of the passed protocols, the provider is omitted from the response.
return nil
}

// return untouched if there's no filter or filterAddrsQuery contains "unknown" and provider has no addrs
if len(filterAddrs) == 0 || (len(provider.Addrs) == 0 && slices.Contains(filterAddrs, "unknown")) {
return provider
}

filteredAddrs := applyAddrFilter(provider.Addrs, filterAddrs)

// If filtering resulted in no addrs, omit the provider
if len(filteredAddrs) == 0 {
return nil
}

provider.Addrs = filteredAddrs
return provider
}

// If there are only negative filters, no addresses will be included in the result. The function will return an empty list.
// For an address to be included, it must pass all negative filters AND match at least one positive filter.
gammazero marked this conversation as resolved.
Show resolved Hide resolved
func applyAddrFilter(addrs []types.Multiaddr, filterAddrsQuery []string) []types.Multiaddr {
if len(filterAddrsQuery) == 0 {
return addrs
}

filteredAddrs := make([]types.Multiaddr, 0, len(addrs))

2color marked this conversation as resolved.
Show resolved Hide resolved
for _, addr := range addrs {
2color marked this conversation as resolved.
Show resolved Hide resolved
protocols := addr.Protocols()
includeAddr := true

// First, check all negative filters
for _, filter := range filterAddrsQuery {
if strings.HasPrefix(filter, "!") {
protocolStringFromFilter := strings.TrimPrefix(filter, "!")
protocolFromFilter := multiaddr.ProtocolWithName(protocolStringFromFilter)
2color marked this conversation as resolved.
Show resolved Hide resolved
if containsProtocol(protocols, protocolFromFilter) {
includeAddr = false
break
}
}
}

// If the address passed all negative filters, check positive filters
if includeAddr {
for _, filter := range filterAddrsQuery {
if !strings.HasPrefix(filter, "!") {
protocolFromFilter := multiaddr.ProtocolWithName(filter)
if containsProtocol(protocols, protocolFromFilter) {
filteredAddrs = append(filteredAddrs, addr)
break
}
}
}
}
}
return filteredAddrs
}

func containsProtocol(protos []multiaddr.Protocol, proto multiaddr.Protocol) bool {
for _, p := range protos {
if p.Code == proto.Code {
return true
}
}
return false
}

func applyProtocolFilter(peerProtocols []string, filterProtocols []string) bool {
2color marked this conversation as resolved.
Show resolved Hide resolved
if len(filterProtocols) == 0 {
// If no filter is passed, do not filter
return true
}

for _, filterProtocol := range filterProtocols {
if filterProtocol == "unknown" && len(peerProtocols) == 0 {
return true
}

for _, peerProtocol := range peerProtocols {
return peerProtocol == filterProtocol
}
}
return false
}
Loading
Loading