Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ The following emojis are used to highlight certain changes:

### Added

- `routing/http`: ✨ Support for HTTP(S) URLs alongside multiaddrs in Delegated Routing API ([IPIP-518](https://github.com/ipfs/specs/pull/518))
- The `Addrs` field in the Peer schema now accepts both multiaddr strings (starting with `/`) and HTTP(S) URLs
- Addresses are parsed with defensive programming: unsupported addresses are skipped, and processing continues with remaining addresses
- Special protocol filtering logic: `tls` filter matches both `/tls` multiaddrs and `https://` URLs, `http` filter matches both multiaddrs and URLs with HTTP semantics
- Schema-agnostic implementation allows any URI scheme (not just http/https) for future extensibility
- Includes `ToMultiaddr()` method for backward compatibility during transition period: converts HTTP(S) URLs to multiaddrs using `/https` (matching IPNI convention) and `/dns` (for dual-stack support)

### Changed

- upgrade to `go-libp2p` [v0.44.0](https://github.com/libp2p/go-libp2p/releases/tag/v0.44.0)
Expand Down
5 changes: 3 additions & 2 deletions routing/http/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ type Client struct {
accepts string

peerID peer.ID
addrs []types.Multiaddr
addrs types.Addresses
identity crypto.PrivKey

// Called immediately after signing a provide request. It is used
Expand Down Expand Up @@ -182,8 +182,9 @@ func WithUserAgent(ua string) Option {
func WithProviderInfo(peerID peer.ID, addrs []multiaddr.Multiaddr) Option {
return func(c *Client) error {
c.peerID = peerID
c.addrs = make(types.Addresses, 0, len(addrs))
for _, a := range addrs {
c.addrs = append(c.addrs, types.Multiaddr{Multiaddr: a})
c.addrs = append(c.addrs, *types.NewAddressFromMultiaddr(a))
}
return nil
}
Expand Down
15 changes: 9 additions & 6 deletions routing/http/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,18 +146,21 @@ func makeCID() cid.Cid {
return c
}

func drAddrsToAddrs(drmas []types.Multiaddr) (addrs []multiaddr.Multiaddr) {
for _, a := range drmas {
addrs = append(addrs, a.Multiaddr)
func drAddrsToAddrs(draddrs types.Addresses) (addrs []multiaddr.Multiaddr) {
for _, a := range draddrs {
if a.IsMultiaddr() {
addrs = append(addrs, a.Multiaddr())
}
}
return
}

func addrsToDRAddrs(addrs []multiaddr.Multiaddr) (drmas []types.Multiaddr) {
func addrsToDRAddrs(addrs []multiaddr.Multiaddr) types.Addresses {
draddrs := make(types.Addresses, 0, len(addrs))
for _, a := range addrs {
drmas = append(drmas, types.Multiaddr{Multiaddr: a})
draddrs = append(draddrs, *types.NewAddressFromMultiaddr(a))
}
return
return draddrs
}

func makePeerRecord(protocols []string) types.PeerRecord {
Expand Down
18 changes: 15 additions & 3 deletions routing/http/contentrouter/contentrouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,11 @@ func readProviderResponses(ctx context.Context, iter iter.ResultIter[types.Recor

var addrs []multiaddr.Multiaddr
for _, a := range result.Addrs {
addrs = append(addrs, a.Multiaddr)
// Try to convert to multiaddr for backward compatibility
if ma := a.ToMultiaddr(); ma != nil {
addrs = append(addrs, ma)
}
// Note: Non-HTTP URLs are skipped as they can't be represented as multiaddrs
}

select {
Expand Down Expand Up @@ -167,7 +171,11 @@ func readProviderResponses(ctx context.Context, iter iter.ResultIter[types.Recor

var addrs []multiaddr.Multiaddr
for _, a := range result.Addrs {
addrs = append(addrs, a.Multiaddr)
// Try to convert to multiaddr for backward compatibility
if ma := a.ToMultiaddr(); ma != nil {
addrs = append(addrs, ma)
}
// Note: Non-HTTP URLs are skipped as they can't be represented as multiaddrs
}

select {
Expand Down Expand Up @@ -216,7 +224,11 @@ func (c *contentRouter) FindPeer(ctx context.Context, pid peer.ID) (peer.AddrInf

var addrs []multiaddr.Multiaddr
for _, a := range res.Val.Addrs {
addrs = append(addrs, a.Multiaddr)
// Try to convert to multiaddr for backward compatibility
if ma := a.ToMultiaddr(); ma != nil {
addrs = append(addrs, ma)
}
// Note: Non-HTTP URLs are skipped as they can't be represented as multiaddrs
}

// If there are no addresses there's nothing of value to return
Expand Down
2 changes: 1 addition & 1 deletion routing/http/contentrouter/contentrouter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func TestFindPeer(t *testing.T) {
{
Schema: types.SchemaPeer,
ID: &p1,
Addrs: []types.Multiaddr{{Multiaddr: multiaddr.StringCast("/ip4/1.2.3.4/tcp/1234")}},
Addrs: types.Addresses{*types.NewAddressFromMultiaddr(multiaddr.StringCast("/ip4/1.2.3.4/tcp/1234"))},
Protocols: []string{"transport-bitswap"},
},
}
Expand Down
70 changes: 37 additions & 33 deletions routing/http/filters/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/ipfs/boxo/routing/http/types"
"github.com/ipfs/boxo/routing/http/types/iter"
logging "github.com/ipfs/go-log/v2"
"github.com/multiformats/go-multiaddr"
)

var logger = logging.Logger("routing/http/filters")
Expand Down Expand Up @@ -159,76 +158,81 @@ func applyFilters(provider *types.PeerRecord, filterAddrs, filterProtocols []str
return provider
}

// applyAddrFilter filters a list of multiaddresses based on the provided filter query.
// applyAddrFilter filters a list of addresses based on the provided filter query.
//
// Parameters:
// - addrs: A slice of types.Multiaddr to be filtered.
// - addrs: A slice of types.Address 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.
// - Positive filters (e.g., "tcp", "udp", "http") include addresses that match the specified protocols.
// - Negative filters (e.g., "!tcp", "!udp") exclude addresses that match the specified protocols.
// - "unknown" can be passed to include providers whose addresses are unknown or cannot be parsed.
//
// 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 {
// A new slice of types.Address containing only the addresses that pass the filter criteria.
func applyAddrFilter(addrs types.Addresses, filterAddrsQuery []string) types.Addresses {
if len(filterAddrsQuery) == 0 {
return addrs
}

var filteredAddrs []types.Multiaddr
var positiveFilters, negativeFilters []multiaddr.Protocol
var filteredAddrs types.Addresses
var positiveFilters, negativeFilters []string
var includeUnknown bool

// Separate positive and negative filters
for _, filter := range filterAddrsQuery {
if strings.HasPrefix(filter, "!") {
negativeFilters = append(negativeFilters, multiaddr.ProtocolWithName(filter[1:]))
if filter == "unknown" {
includeUnknown = true
} else if strings.HasPrefix(filter, "!") {
negativeFilters = append(negativeFilters, filter[1:])
} else {
positiveFilters = append(positiveFilters, multiaddr.ProtocolWithName(filter))
positiveFilters = append(positiveFilters, filter)
}
}

for _, addr := range addrs {
protocols := addr.Protocols()
// Handle unknown (unparseable) addresses
if !addr.IsValid() {
if includeUnknown {
filteredAddrs = append(filteredAddrs, addr)
}
continue
}

// Check negative filters
if containsAny(protocols, negativeFilters) {
shouldExclude := false
for _, filter := range negativeFilters {
if addr.HasProtocol(filter) {
shouldExclude = true
break
}
}
if shouldExclude {
continue
}

// If no positive filters or matches a positive filter, include the address
if len(positiveFilters) == 0 || containsAny(protocols, positiveFilters) {
if len(positiveFilters) == 0 {
filteredAddrs = append(filteredAddrs, addr)
} else {
for _, filter := range positiveFilters {
if addr.HasProtocol(filter) {
filteredAddrs = append(filteredAddrs, addr)
break
}
}
}
}

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 {
Expand Down
Loading
Loading