Skip to content

Commit

Permalink
refactor: cleanup isDNSLinkName mess
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias committed Feb 3, 2023
1 parent c091fd6 commit 05642b0
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 141 deletions.
5 changes: 5 additions & 0 deletions examples/gateway-car/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ func (api *blocksGateway) ResolvePath(ctx context.Context, p ifacepath.Path) (if
return ifacepath.NewResolvedPath(ipath, node, root, gopath.Join(rest...)), nil
}

func (api *blocksGateway) HasDNSLinkRecord(ctx context.Context, host string) bool {
// Not implemented.
return false
}

func (api *blocksGateway) resolveNode(ctx context.Context, p ifacepath.Path) (format.Node, error) {
rp, err := api.ResolvePath(ctx, p)
if err != nil {
Expand Down
15 changes: 0 additions & 15 deletions examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/ipfs/bbloom v0.0.4 // indirect
github.com/ipfs/go-bitfield v1.0.0 // indirect
Expand All @@ -55,20 +52,12 @@ require (
github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/ipfs/go-metrics-interface v0.0.1 // indirect
github.com/ipfs/go-namesys v0.7.0 // indirect
github.com/ipfs/go-verifcid v0.0.2 // indirect
github.com/ipld/go-car v0.5.0 // indirect
github.com/jbenet/goprocess v0.1.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.3 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/libp2p/go-cidranger v1.1.0 // indirect
github.com/libp2p/go-libp2p v0.23.4 // indirect
github.com/libp2p/go-libp2p-asn-util v0.2.0 // indirect
github.com/libp2p/go-libp2p-kad-dht v0.19.0 // indirect
github.com/libp2p/go-libp2p-kbucket v0.5.0 // indirect
github.com/libp2p/go-libp2p-record v0.2.0 // indirect
github.com/libp2p/go-msgio v0.2.0 // indirect
github.com/libp2p/go-netroute v0.2.0 // indirect
github.com/libp2p/go-openssl v0.1.0 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-pointer v0.0.1 // indirect
Expand All @@ -79,7 +68,6 @@ require (
github.com/multiformats/go-base32 v0.1.0 // indirect
github.com/multiformats/go-base36 v0.2.0 // indirect
github.com/multiformats/go-multiaddr v0.8.0 // indirect
github.com/multiformats/go-multiaddr-dns v0.3.1 // indirect
github.com/multiformats/go-multibase v0.1.1 // indirect
github.com/multiformats/go-multicodec v0.7.0 // indirect
github.com/multiformats/go-multihash v0.2.1 // indirect
Expand All @@ -96,11 +84,8 @@ require (
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb // indirect
github.com/whyrusleeping/base32 v0.0.0-20170828182744-c30ac30633cc // indirect
github.com/whyrusleeping/cbor v0.0.0-20171005072247-63513f603b11 // indirect
github.com/whyrusleeping/cbor-gen v0.0.0-20230126041949-52956bd4c9aa // indirect
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
go.opencensus.io v0.23.0 // indirect
go.opentelemetry.io/otel v1.12.0 // indirect
go.opentelemetry.io/otel/trace v1.12.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
Expand Down
43 changes: 0 additions & 43 deletions examples/go.sum

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ type API interface {
// exist due to a missing link, it should return an error of type:
// https://pkg.go.dev/github.com/ipfs/[email protected]/resolver#ErrNoLink
ResolvePath(context.Context, path.Path) (path.Resolved, error)

// HasDNSLinkRecord returns if the provided path has a DNSLink TXT record.
// It does not perform any validation, only checks for the existence of
// a DNSLink TXT record.
HasDNSLinkRecord(context.Context, string) bool
}

// A helper function to clean up a set of headers:
Expand Down
105 changes: 105 additions & 0 deletions gateway/gateway_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package gateway

import (
"context"
"errors"
"strings"

cid "github.com/ipfs/go-cid"
"github.com/ipfs/go-datastore"
syncds "github.com/ipfs/go-datastore/sync"
"github.com/ipfs/go-libipfs/blocks"
"github.com/ipfs/go-libipfs/files"
"github.com/ipfs/go-namesys"
path "github.com/ipfs/go-path"
iface "github.com/ipfs/interface-go-ipfs-core"
nsopts "github.com/ipfs/interface-go-ipfs-core/options/namesys"
ipath "github.com/ipfs/interface-go-ipfs-core/path"
"github.com/libp2p/go-libp2p/core/crypto"
)

type mockNamesys map[string]path.Path

func (m mockNamesys) Resolve(ctx context.Context, name string, opts ...nsopts.ResolveOpt) (value path.Path, err error) {
cfg := nsopts.DefaultResolveOpts()
for _, o := range opts {
o(&cfg)
}
depth := cfg.Depth
if depth == nsopts.UnlimitedDepth {
// max uint
depth = ^uint(0)
}
for strings.HasPrefix(name, "/ipns/") {
if depth == 0 {
return value, namesys.ErrResolveRecursion
}
depth--

var ok bool
value, ok = m[name]
if !ok {
return "", namesys.ErrResolveFailed
}
name = value.String()
}
return value, nil
}

func (m mockNamesys) ResolveAsync(ctx context.Context, name string, opts ...nsopts.ResolveOpt) <-chan namesys.Result {
out := make(chan namesys.Result, 1)
v, err := m.Resolve(ctx, name, opts...)
out <- namesys.Result{Path: v, Err: err}
close(out)
return out
}

func (m mockNamesys) Publish(ctx context.Context, name crypto.PrivKey, value path.Path, opts ...nsopts.PublishOption) error {
return errors.New("not implemented for mockNamesys")
}

func (m mockNamesys) GetResolver(subs string) (namesys.Resolver, bool) {
return nil, false
}

type mockApi struct {
ns mockNamesys
}

func newMockApi() *mockApi {

syncds.MutexWrap(datastore.NewMapDatastore())

return &mockApi{
ns: mockNamesys{},
}
}

func (m *mockApi) GetUnixFsNode(context.Context, ipath.Resolved) (files.Node, error) {
return nil, errors.New("not implemented")
}

func (m *mockApi) LsUnixFsDir(context.Context, ipath.Resolved) (<-chan iface.DirEntry, error) {
return nil, errors.New("not implemented")
}

func (m *mockApi) GetBlock(context.Context, cid.Cid) (blocks.Block, error) {
return nil, errors.New("not implemented")
}

func (m *mockApi) GetIPNSRecord(context.Context, cid.Cid) ([]byte, error) {
return nil, errors.New("not implemented")
}

func (m *mockApi) IsCached(context.Context, ipath.Path) bool {
return false
}

func (m *mockApi) ResolvePath(context.Context, ipath.Path) (ipath.Resolved, error) {
return nil, errors.New("not implemented")
}

func (m *mockApi) HasDNSLinkRecord(ctx context.Context, hostname string) bool {
_, err := m.ns.Resolve(ctx, "/ipns/"+hostname, nsopts.Depth(1))
return err == nil || err == namesys.ErrResolveRecursion
}
23 changes: 9 additions & 14 deletions gateway/hostname.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
"strings"

cid "github.com/ipfs/go-cid"
"github.com/ipfs/go-namesys"
"github.com/ipfs/interface-go-ipfs-core/path"
"github.com/libp2p/go-libp2p/core/peer"
dns "github.com/miekg/dns"

Expand Down Expand Up @@ -113,7 +111,7 @@ func WithHostname(next http.Handler, api API, publicGateways map[string]*Specifi
// Not a whitelisted path

// Try DNSLink, if it was not explicitly disabled for the hostname
if !gw.NoDNSLink && isDNSLinkName(r.Context(), api, host) {
if !gw.NoDNSLink && hasDNSLinkRecord(r.Context(), api, host) {
// rewrite path and handle as DNSLink
r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
next.ServeHTTP(w, withHostnameContext(r, host))
Expand Down Expand Up @@ -184,7 +182,6 @@ func WithHostname(next http.Handler, api API, publicGateways map[string]*Specifi
}
}
} else { // rootID is not a CID..

// Check if rootID is a single DNS label with an inlined
// DNSLink FQDN a single DNS label. We support this so
// loading DNSLink names over TLS "just works" on public
Expand All @@ -200,10 +197,10 @@ func WithHostname(next http.Handler, api API, publicGateways map[string]*Specifi
// https://my-v--long-example-com.ipns.dweb.link
if ns == "ipns" && !strings.Contains(rootID, ".") {
// if there is no TXT recordfor rootID
if !isDNSLinkName(r.Context(), api, rootID) {
if !hasDNSLinkRecord(r.Context(), api, rootID) {
// my-v--long-example-com → my.v-long.example.com
dnslinkFQDN := toDNSLinkFQDN(rootID)
if isDNSLinkName(r.Context(), api, dnslinkFQDN) {
if hasDNSLinkRecord(r.Context(), api, dnslinkFQDN) {
// update path prefix to use real FQDN with DNSLink
pathPrefix = "/ipns/" + dnslinkFQDN
}
Expand All @@ -218,13 +215,14 @@ func WithHostname(next http.Handler, api API, publicGateways map[string]*Specifi
next.ServeHTTP(w, withHostnameContext(r, gwHostname))
return
}

// We don't have a known gateway. Fallback on DNSLink lookup

// Wildcard HTTP Host check:
// 1. is wildcard DNSLink enabled (Gateway.NoDNSLink=false)?
// 2. does Host header include a fully qualified domain name (FQDN)?
// 3. does DNSLink record exist in DNS?
if !noDNSLink && isDNSLinkName(r.Context(), api, host) {
if !noDNSLink && hasDNSLinkRecord(r.Context(), api, host) {
// rewrite path and handle as DNSLink
r.URL.Path = "/ipns/" + stripPort(host) + r.URL.Path
ctx := context.WithValue(r.Context(), DNSLinkHostnameKey, host)
Expand Down Expand Up @@ -262,18 +260,15 @@ func isDomainNameAndNotPeerID(hostname string) bool {
return ok
}

// isDNSLinkName returns bool if a valid DNS TXT record exist for provided host
func isDNSLinkName(ctx context.Context, api API, host string) bool {
// hasDNSLinkRecord returns if a DNS TXT record exists for the provided host.
func hasDNSLinkRecord(ctx context.Context, api API, host string) bool {
dnslinkName := stripPort(host)

if !isDomainNameAndNotPeerID(dnslinkName) {
return false
}

name := "/ipns/" + dnslinkName
_, err := api.ResolvePath(ctx, path.New(name))
// check if DNSLink exists
return err == nil || err == namesys.ErrResolveRecursion
return api.HasDNSLinkRecord(ctx, dnslinkName)
}

func isSubdomainNamespace(ns string) bool {
Expand Down Expand Up @@ -456,7 +451,7 @@ func toSubdomainURL(hostname, path string, r *http.Request, inlineDNSLink bool,
// represented as a single DNS label:
// https://my-v--long-example-com.ipns.dweb.link
if (inlineDNSLink || isHTTPS) && ns == "ipns" && strings.Contains(rootID, ".") {
if isDNSLinkName(r.Context(), api, rootID) {
if hasDNSLinkRecord(r.Context(), api, rootID) {
// my.v-long.example.com → my-v--long-example-com
dnsLabel, err := toDNSLinkDNSLabel(rootID)
if err != nil {
Expand Down
Loading

0 comments on commit 05642b0

Please sign in to comment.