Wildcard fallback?
Opened this issue · 7 comments
@holic fantastic tool, thanks. The power:simplicity here is wonderful.
How do you feel about a PR to handle catch-all wildcards before the fallback
function?
e.g.
IN ALIAS alias.redirect.name ; domain apex using a non-standard record
_redirect IN TXT "Redirects permanently to https://example.com/*"
* IN CNAME alias.redirect.name
_redirect.* IN TXT "Redirects permanently to https://example.com/*"
My use-case is extremely lazily redirecting miss-spelled domain names. This functionality would allow for a standard set of records to be added for each without care to past or future sub-domain changes.
Currently this would be handled by defining a _redirect
TXT
record for every possible subdomain.
(Also, if it's relevant, I'm self hosting via the published docker image - swift and stateless is great)
Interestingly, this is non-terminal wildcard notation is a bit contentious.
https://tools.ietf.org/html/rfc4592#section-2.1.3 allows it. AWS's Route53 treats it as a single record, which is what I based my proposal on.
Golang's net
package doesn't recognise it as a valid domain.
In lieu of discovering if Go's net
package has a bug, an alternative that makes this explicit is probably a better interface - i.e. catch-all
or default
subdomain at each level.
A PoC to illustrate is below.
; example.net
* IN CNAME alias.redirect.name
_redirect.default IN TXT "Redirects to https://example.com/*"
In this example request is made to foo.example.net
so _redirect.foo.example.net TXT
is looked up.
This does not exist so _redirect.default.example.net TXT
is looked up, a result is found and the redirection occurs. The order of execution allows explicit redirections to occur and the default only happens for the subdomains of the same level.
diff --git a/server.go b/server.go
index 846ee27..f39feac 100644
--- a/server.go
+++ b/server.go
@@ -7,6 +7,7 @@ import (
"net/http"
"net/url"
"os"
+ "regexp"
"strings"
"time"
)
@@ -19,12 +20,22 @@ func fallback(w http.ResponseWriter, r *http.Request, reason string) {
http.Redirect(w, r, location, 302)
}
+func hostnameLookup(host string) ([]string, error) {
+ hostname := fmt.Sprintf("_redirect.%s", host)
+ return net.LookupTXT(hostname)
+}
+
func handler(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(r.Host, ":")
host := parts[0]
- hostname := fmt.Sprintf("_redirect.%s", host)
- txt, err := net.LookupTXT(hostname)
+ txt, err := hostnameLookup(host)
+ if err != nil {
+ pattern := regexp.MustCompile("^(.*?)\\.(.*)$")
+ recursiveHost := pattern.ReplaceAllString(host, "default.$2")
+ txt, err = hostnameLookup(recursiveHost)
+ }
+
if err != nil {
fallback(w, r, fmt.Sprintf("Could not resolve hostname (%v)", err))
return
I'm keen to hear your thoughts before moving further ahead. If/when appropriate I'll send this over as a proper PR with the relevant tests and documentation.
Interesting idea! I think it would make sense to just fall back to the next level's _redirect
configuration instead of a special name. For example:
* IN CNAME alias.redirect.name
_redirect.zombo IN TXT "Redirects to http://zombo.com/"
_redirect IN TXT "Redirects to https://example.com/*"
Requests to zombo.yourdomain.com
would redirect to http://zombo.com/
while all other requests (e.g. any.yourdomain.com
or www.yourdomain.com
) would redirect to https://example.com/
.
That said, I'm not sure I actually want to implement this for a couple of reasons:
- I would have to traverse the domain by level until it finds a match or runs out of hostnames. This would at least triple the number of DNS queries made for every request.
- Alternatively, I would need to maintain a list of top-level domains (e.g.
.com
and.co.nz
) to know when to "stop" looking to save DNS queries (since we know that_redirect.com
and_redirect.co.nz
will never resolve).
Both seem like a large burden on the server for a feature/side-effect that is unlikely to see significant usage.
I think the main blocker of a simple implementation is the responding server doesn't know if it was an explicit domain request or a wildcard that got it there.
If we did know, then when a wildcard was requested respond with the explicit _redirect
if it exists, or the _redirect
on the same level.
This could be achieved by an additional lookup to r.Host
ala dig +short *.example.com
to see if this record exists.
The negative of this is the 3 requests - first to explicit, second to dig-ish
and third to default.
A named default mode has the advantage of only 2 requests - first to explicit and second to default.
I don't think traversal is a good idea is you could have unintended redirects with no way of opting out.
I don't think a list of domains needs to be maintained (though publicsuffix
could work) because the failure mode of net.LookupTXT
is enough - com
or co.nz
is never going to pass domain validation in net.LookupTXT
so will return err
and fail out.
I'm going to stick a named default behind a flag in a fork for my usage. If this or some remix of the feature is useful I'm always happy to PR :)
Sounds good - curious to hear how well it works in practice.
One worry I have with relying on publicsuffix is that the current TLD landscape is wildly in flux (new TLDs released all the time). I would assume that each time I want to pick up new TLD changes, I would need to recompile and rerelease the Go binary?
So I guess we don't see wildcard fallback anymore? Thought I could simplify adding CNAME entries to your site. But I guess I have to manually add them anyway
@rjocoleman You made a fork with a catch-all?
This is exactly what I was looking for and I'm trying to see if its possible
I want to redirect *.blog.domain.com to blog.domain.com