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

gateway: attempt to resolve hostname to ipfs path #726

Merged
merged 4 commits into from
Feb 13, 2015
Merged

gateway: attempt to resolve hostname to ipfs path #726

merged 4 commits into from
Feb 13, 2015

Conversation

kevinwallace
Copy link
Contributor

This allows someone to host a static site by pointing a TXT record at their
content in IPFS, and a CNAME record at an IPFS gateway.

Note that such a setup technically violates RFC1912 (section 2.4; "A CNAME
record is not allowed to coexist with any other data."), but tends to work in
practice.

We may want to consider changing the DNS->IPFS resolution scheme to allow this
scenario to be RFC-compliant (e.g. store the mapping on a well-known subdomain
to allow CNAME records on the domain itself).

Also note that while this has been tested by hand, I don't see a good way to
test it with the existing sharness gateway tests -- how do you mock a DNS entry
from a shell script?

@whyrusleeping
Copy link
Member

Unless im misunderstanding the change, this was already possible through ipns. but maybe im missing something, the gateway isnt a super familiar part of the codebase for me.

@whyrusleeping
Copy link
Member

Ah, wait, i got it, if you make (for example) jero.my point to gateway.ipfs.io via CNAME and have a TEXT record QmX it will open up gateway.ipfs.io/ipfs/QmX. Got it. Neat

@jbenet
Copy link
Member

jbenet commented Feb 2, 2015

@kevinwallace thanks for the PR! I'll get to reviewing this in more detail in the next few hrs. I'll add a couple comments now.

@@ -12,8 +12,7 @@ func GatewayOption(writable bool) ServeOption {
if err != nil {
return err
}
mux.Handle("/ipfs/", gateway)
mux.Handle("/ipns/", gateway)
mux.Handle("/", gateway)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, I think we want to keep using the muxer. We're going towards:

mux.Handle("/ipfs/", ipfsHandler)
mux.Handle("/ipns/", ipnsHandler)  // ipnsHandler is sort of middleware that uses ipfsHandler
mux.Handle("/", base)

where baseHandler can be mux between:

base.Handle("/webui", redirectToWebUIHandler)
base.Handle("/", rootHandler) // give the user some information about IPFS + links (e.g. to WebUI, ipfs.io, etc)
// base should handle 404s with custom error pages.

cc @mappum

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. In that case, this is probably best expressed as a pre-muxer request processing stage, which tries to resolve req.Host as an IPNS name, and if successful, rewrites the request path to /ipfs/<resolved name><path> and allows the muxer/gateway to process the rewritten request. I'd need to extend ServeOption to support registering such a stage.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be doable by having two muxers:

  • one mounted on the server at "/". performs the check, possibly rewriting the path, and then calls the other:
  • one which handles everything else.

@kevinwallace
Copy link
Contributor Author

PTAL -- I've updated my approach per @jbenet's comments.

A ServeOption now has an additional return value, and can now mediate requests to future options by chaining muxes. A new option, IPNSHostnameOption, uses this to rewrite incoming requests for hostnames that resolve as IPNS names. This option is only installed on the gateway server, rather than any server that installs GatewayOption.

@jbenet
Copy link
Member

jbenet commented Feb 3, 2015

Thanks @kevinwallace -- beautiful, this LGTM.

On tests, maybe the right way is to test it in Go with a mock Namesys the test controls. The full test is hard to do without messing with hosts files/etc. I think the mock covers us 80% of the way.

If we were to go for a well-known subdomain, we could do something like ipfs-gateway.<domain>. (Know examples of similar setups?)

@jbenet
Copy link
Member

jbenet commented Feb 3, 2015

Lets go for that last addition and then merge this?

@kevinwallace
Copy link
Contributor Author

@jbenet Sounds good. Are you imagining something that tests overall gateway functionality end-to-end (including IPNS hostnames), or just the request rewriting option in isolation from the gateway?

About the well-known subdomain: I was thinking about what this would look like if it were done DNS-SD style (which uses names like _http._tcp.example.com along with SRV attributes to point at specific TCP/UDP endpoints and TXT records to specify other key/value attributes). I'd like to read the DNS-SD RFC more closely before making a concrete proposal in this vein, but here's a spitball:

_ipns._ipfs.example.com IN TXT "path=/ipfs/QmX..." points /ipns/example.com to /ipfs/QmX....

@jbenet
Copy link
Member

jbenet commented Feb 3, 2015

I'm imagining testing with http requests to the gateway (in go), with a mocked naming system.

Yeah, i need to read the spec closely too. Wish dns-sd records didnt have the ugly _, but oh well. Maybe use _ipns.example.com directly, as ipns is a thing unto itself. (Also since this is dns-sd, btw, could be the same identifiers to use when advertising link-local services through mdns and so on.)

@kevinwallace
Copy link
Contributor Author

Just as a heads-up, I'm likely going to be preoccupied with other things for the next couple of days, so I'm not sure I'll be able to get to this until this weekend. It's not forgotten, though!

@whyrusleeping
Copy link
Member

@kevinwallace, no worries! you wont be forgotten

Each option now additionally returns the mux to be used by future options. If
every options returns the mux it was passed, the current behavior is unchanged.

However, if the option returns an a new mux, it can mediate requests to handlers
provided by future options:

    return func(n *core.IpfsNode, mux *http.ServeMux) (*http.ServeMux, error) {
      childMux := http.NewServeMux()
      mux.Handle("/", handlerThatDelegatesToChildMux)
      return childMux, nil
    }

License: MIT
Signed-off-by: Kevin Wallace <[email protected]>
This allows someone to host a static site by pointing a TXT record at their
content in IPFS, and a CNAME record at an IPFS gateway.

Note that such a setup technically violates RFC1912 (section 2.4; "A CNAME
record is not allowed to coexist with any other data."), but tends to work in
practice.

We may want to consider changing the DNS->IPFS resolution scheme to allow this
scenario to be RFC-compliant (e.g. store the mapping on a well-known subdomain
to allow CNAME records on the domain itself).

License: MIT
Signed-off-by: Kevin Wallace <[email protected]>
License: MIT
Signed-off-by: Kevin Wallace <[email protected]>
License: MIT
Signed-off-by: Kevin Wallace <[email protected]>
@kevinwallace
Copy link
Contributor Author

OK, finally got around to this. Sorry it took so long!

Added core/corehttp/gateway_test.go, which brings up a gateway server with a mocked namesys, then makes various requests to test its functionality.

func (m mockNamesys) CanResolve(name string) bool {
_, ok := m[name]
return ok
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably just return true. this is likely a doc failure, but the CanResolve func is supposed to return whether "a name is resolvable by the system" i.e. a DNSResolver would check if the string is a domain.

(can we think of a clearer name for this func?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LooksResolvable, LooksApplicable? I like the Looks prefix to indicate that we're only examining the name on a surface level, but I don't like the way either of those names read...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matches, NameMatches?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like Looks as well... CanResolve implies a little more action than we are actually doing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will be written ns.<word>(name)

ns.LooksApplicable(name)
ns.LooksResolvable(name)
ns.Compatible(name)
ns.CompatibleWith(name)
ns.MayResolve(name)
ns.CanResolve(name)
ns.IsResolvable(name)
ns.Resolvable(name)

@jbenet
Copy link
Member

jbenet commented Feb 13, 2015

This LGTM. thanks again for the PR!

jbenet added a commit that referenced this pull request Feb 13, 2015
gateway: attempt to resolve hostname to ipfs path
@jbenet jbenet merged commit 2476499 into ipfs:master Feb 13, 2015
@whyrusleeping
Copy link
Member

👍

@kevinwallace kevinwallace deleted the gateway_hostname branch February 13, 2015 03:34
@kevinwallace
Copy link
Contributor Author

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants