generated from ipfs/ipfs-repository-template
-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #671 from ipfs/add-protocol-filtering
feat: add routing filtering to delegated routing server IPIP-484
- Loading branch information
Showing
9 changed files
with
874 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
package server | ||
|
||
import ( | ||
"reflect" | ||
"slices" | ||
"strings" | ||
|
||
"github.com/ipfs/boxo/routing/http/types" | ||
"github.com/ipfs/boxo/routing/http/types/iter" | ||
"github.com/multiformats/go-multiaddr" | ||
) | ||
|
||
// filters implements IPIP-0484 | ||
|
||
func parseFilter(param string) []string { | ||
if param == "" { | ||
return nil | ||
} | ||
return strings.Split(strings.ToLower(param), ",") | ||
} | ||
|
||
// applyFiltersToIter applies the filters to the given iterator and returns a new iterator. | ||
// | ||
// The function iterates over the input iterator, applying the specified filters to each record. | ||
// It supports both positive and negative filters for both addresses and protocols. | ||
// | ||
// Parameters: | ||
// - recordsIter: An iterator of types.Record to be filtered. | ||
// - filterAddrs: A slice of strings representing the address filter criteria. | ||
// - filterProtocols: A slice of strings representing the protocol filter criteria. | ||
func applyFiltersToIter(recordsIter iter.ResultIter[types.Record], filterAddrs, filterProtocols []string) iter.ResultIter[types.Record] { | ||
mappedIter := iter.Map(recordsIter, func(v iter.Result[types.Record]) iter.Result[types.Record] { | ||
if v.Err != nil || v.Val == nil { | ||
return v | ||
} | ||
|
||
switch v.Val.GetSchema() { | ||
case types.SchemaPeer: | ||
record, ok := v.Val.(*types.PeerRecord) | ||
if !ok { | ||
logger.Errorw("problem casting find providers record", "Schema", v.Val.GetSchema(), "Type", reflect.TypeOf(v).String()) | ||
// drop failed type assertion | ||
return iter.Result[types.Record]{} | ||
} | ||
|
||
record = applyFilters(record, filterAddrs, filterProtocols) | ||
if record == nil { | ||
return iter.Result[types.Record]{} | ||
} | ||
v.Val = record | ||
|
||
//lint:ignore SA1019 // ignore staticcheck | ||
case types.SchemaBitswap: | ||
//lint:ignore SA1019 // ignore staticcheck | ||
record, ok := v.Val.(*types.BitswapRecord) | ||
if !ok { | ||
logger.Errorw("problem casting find providers record", "Schema", v.Val.GetSchema(), "Type", reflect.TypeOf(v).String()) | ||
// drop failed type assertion | ||
return iter.Result[types.Record]{} | ||
} | ||
peerRecord := types.FromBitswapRecord(record) | ||
peerRecord = applyFilters(peerRecord, filterAddrs, filterProtocols) | ||
if peerRecord == nil { | ||
return iter.Result[types.Record]{} | ||
} | ||
v.Val = peerRecord | ||
} | ||
return v | ||
}) | ||
|
||
// filter out nil results and errors | ||
filteredIter := iter.Filter(mappedIter, func(v iter.Result[types.Record]) bool { | ||
return v.Err == nil && v.Val != nil | ||
}) | ||
|
||
return filteredIter | ||
} | ||
|
||
// 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 len(filterAddrs) == 0 && len(filterProtocols) == 0 { | ||
return provider | ||
} | ||
|
||
if !protocolsAllowed(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 | ||
} | ||
|
||
// applyAddrFilter filters a list of multiaddresses based on the provided filter query. | ||
// | ||
// Parameters: | ||
// - addrs: A slice of types.Multiaddr to be filtered. | ||
// - filterAddrsQuery: A slice of strings representing the filter criteria. | ||
// | ||
// The function supports both positive and negative filters: | ||
// - Positive filters (e.g., "tcp", "udp") include addresses that match the specified protocols. | ||
// - Negative filters (e.g., "!tcp", "!udp") exclude addresses that match the specified protocols. | ||
// | ||
// If no filters are provided, the original list of addresses is returned unchanged. | ||
// If only negative filters are provided, addresses not matching any negative filter are included. | ||
// If positive filters are provided, only addresses matching at least one positive filter (and no negative filters) are included. | ||
// If both positive and negative filters are provided, the address must match at least one positive filter and no negative filters to be included. | ||
// | ||
// Returns: | ||
// A new slice of types.Multiaddr containing only the addresses that pass the filter criteria. | ||
func applyAddrFilter(addrs []types.Multiaddr, filterAddrsQuery []string) []types.Multiaddr { | ||
if len(filterAddrsQuery) == 0 { | ||
return addrs | ||
} | ||
|
||
var filteredAddrs []types.Multiaddr | ||
var positiveFilters, negativeFilters []multiaddr.Protocol | ||
|
||
// Separate positive and negative filters | ||
for _, filter := range filterAddrsQuery { | ||
if strings.HasPrefix(filter, "!") { | ||
negativeFilters = append(negativeFilters, multiaddr.ProtocolWithName(filter[1:])) | ||
} else { | ||
positiveFilters = append(positiveFilters, multiaddr.ProtocolWithName(filter)) | ||
} | ||
} | ||
|
||
for _, addr := range addrs { | ||
protocols := addr.Protocols() | ||
|
||
// Check negative filters | ||
if containsAny(protocols, negativeFilters) { | ||
continue | ||
} | ||
|
||
// If no positive filters or matches a positive filter, include the address | ||
if len(positiveFilters) == 0 || containsAny(protocols, positiveFilters) { | ||
filteredAddrs = append(filteredAddrs, addr) | ||
} | ||
} | ||
|
||
return filteredAddrs | ||
} | ||
|
||
// Helper function to check if protocols contain any of the filters | ||
func containsAny(protocols []multiaddr.Protocol, filters []multiaddr.Protocol) bool { | ||
for _, filter := range filters { | ||
if containsProtocol(protocols, filter) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func containsProtocol(protos []multiaddr.Protocol, proto multiaddr.Protocol) bool { | ||
for _, p := range protos { | ||
if p.Code == proto.Code { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// protocolsAllowed returns true if the peerProtocols are allowed by the filter protocols. | ||
func protocolsAllowed(peerProtocols []string, filterProtocols []string) bool { | ||
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 { | ||
if strings.EqualFold(peerProtocol, filterProtocol) { | ||
return true | ||
} | ||
|
||
} | ||
} | ||
return false | ||
} |
Oops, something went wrong.