Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions cmd/routedns/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type group struct {
Resolvers []string
Type string
Replace []rdns.ReplaceOperation // only used by "replace" type
Subdomain []rdns.SubDomainReplaceOperation // only used by "SubDomainReplace" type
GCPeriod int `toml:"gc-period"` // Time-period (seconds) used to expire cached items in the "cache" type
ECSOp string `toml:"ecs-op"` // ECS modifier operation, "add", "delete", "privacy"
ECSAddress net.IP `toml:"ecs-address"` // ECS address. If empty for "add", uses the client IP. Ignored for "privacy" and "delete"
Expand Down
31 changes: 31 additions & 0 deletions cmd/routedns/example-config/subdomain-replace.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

# into DNS-over-TLS queries to Cloudflare's DNS server.
[bootstrap-resolver]
address = "1.1.1.1:853"
protocol = "dot"

[resolvers.cloudflare-dot]
address = "gcp.ctptech.dev:853"
protocol = "dot"

[listeners.local-udp]
address = ":53"
protocol = "udp"
resolver = "sub-domain-replace"

[listeners.local-tcp]
address = ":53"
protocol = "tcp"
resolver = "sub-domain-replace"

# must have dots on both ends to work
[groups.sub-domain-replace]
type = "subdomain-replace"
resolvers = [ "cloudflare-dot" ]
subdomain = [

{ from = '.ctptech.dev.', to = '.whiskey.software.' },
{ from = '.whiskey.software.', to = '.charlesp.tech.' },
{ from = '.whisky.software.', to = '.whiskey.dev.' },
{ from = '.local.', to = '.' },
]
9 changes: 9 additions & 0 deletions cmd/routedns/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,16 @@ func instantiateGroup(id string, g group, resolvers map[string]rdns.Resolver) er
if err != nil {
return err
}
case "subdomain-replace":
if len(gr) != 1 {
return fmt.Errorf("type replace only supports one resolver in '%s'", id)
}
resolvers[id], err = rdns.NewSubDomainReplace(id, gr[0], g.Subdomain...)
if err != nil {
return err
}
case "ttl-modifier":

if len(gr) != 1 {
return fmt.Errorf("type ttl-modifier only supports one resolver in '%s'", id)
}
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/quic-go/quic-go v0.33.0
github.com/sirupsen/logrus v1.9.0
github.com/spf13/cobra v1.6.1
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.1
golang.org/x/net v0.7.0
)
Expand All @@ -24,7 +24,7 @@ require (
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20221219190121-3cb0bae90811 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/uuid v1.1.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/onsi/ginkgo/v2 v2.6.1 // indirect
Expand Down
8 changes: 3 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/pprof v0.0.0-20221219190121-3cb0bae90811 h1:wORs2YN3R3ona/CXYuTvLM31QlgoNKHvlCNuArCDDCU=
github.com/google/pprof v0.0.0-20221219190121-3cb0bae90811/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/heimdalr/dag v1.2.1 h1:XJOMaoWqJK1UKdp+4zaO2uwav9GFbHMGCirdViKMRIQ=
github.com/heimdalr/dag v1.2.1/go.mod h1:Of/wUB7Yoj4dwiOcGOOYIq6MHlPF/8/QMBKFJpwg+yc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jtacoma/uritemplates v1.0.0 h1:xwx5sBF7pPAb0Uj8lDC1Q/aBPpOFyQza7OC705ZlLCo=
Expand Down Expand Up @@ -67,8 +65,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
113 changes: 113 additions & 0 deletions subdomain-replace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package rdns

import (
"errors"
"strings"

"github.com/miekg/dns"
)

// Replace is a resolver that modifies queries according to regular expressions
// and forwards the modified queries to another resolver. Responses are then
// mapped back to the original query string.
type SubDomainReplace struct {
id string
resolver Resolver
exp subDomainReplaceExpressions
}

var _ Resolver = &SubDomainReplace{}

type subDomainReplaceExp struct {
from string
to string
}

type subDomainReplaceExpressions []subDomainReplaceExp

func (r subDomainReplaceExp) substitute(qname string) (string, bool) {
fromDomain := r.from
toDomain := r.to
// from {}.ctptech.dev to {}.charlesp.tech

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Suggested change
// from {}.ctptech.dev to {}.charlesp.tech
// from .ctptech.dev to .charlesp.tech

// from test.ctptech.dev to test.charlesp.tech
if strings.HasSuffix(qname, fromDomain) {
str := strings.Replace(qname, fromDomain, toDomain, 1)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I think there's a small issue with just doing a simple replace since the "fromDomain" string can show up more than once in the domain. Let's say you have fromDomain = .local. and the domain name being looked up is example.local.something.local. then it'd replace the first occurrance. It might be best to simply strip the fromDomain with strings.TrimSuffix(), then add the toDomain value to the result with + like so:

sub := strings.TrimSuffix(qname, fromDomain)
str := sub+toDomain
return str, true

Log.WithField("qname", qname).Debug("matches: modifying query")
return str, true
} else {
return "", false
}
Comment on lines +37 to +39

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Suggested change
} else {
return "", false
}
}
return "", false

}
func (r subDomainReplaceExpressions) apply(qname string) string {
for _, e := range r {
s, result := e.substitute(qname)
if result {
return s
}
}
return qname
}

type SubDomainReplaceOperation struct {
From string
To string
}

// NewReplace returns a new instance of a Replace resolver.
func NewSubDomainReplace(id string, resolver Resolver, list ...SubDomainReplaceOperation) (*SubDomainReplace, error) {
var exp subDomainReplaceExpressions
for _, o := range list {
if strings.Contains(o.From, "{}") && strings.Contains(o.To, "{}") {
exp = append(exp, subDomainReplaceExp{o.From, o.To})
} else {
return nil, errors.New("{} not found")
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Do you still need this since {} isn't necessary in the config anymore?

}
Log.WithField("exp", exp).Debug("exp query")
return &SubDomainReplace{id: id, resolver: resolver, exp: exp}, nil
}

// Resolve a DNS query by first replacing the query string with another
// sending the query upstream and replace the name in the response with
// the original query string again.
func (r *SubDomainReplace) Resolve(q *dns.Msg, ci ClientInfo) (*dns.Msg, error) {
if len(q.Question) < 1 {
return nil, errors.New("no question in query")
}

oldName := q.Question[0].Name
newName := r.exp.apply(oldName)
log := logger(r.id, q, ci)

// if nothing needs modifying, we can stop here and use the original query
if newName == oldName {
log.Debug("forwarding unmodified query to resolver")
return r.resolver.Resolve(q, ci)
}

// Modify the query string
q.Question[0].Name = newName

// Send the query upstream
log.WithField("new-qname", newName).WithField("resolver", r.resolver).Debug("forwarding modified query to resolver")
a, err := r.resolver.Resolve(q, ci)
if err != nil || a == nil {
return nil, err
}

// Set the question back to the original name
a.Question[0].Name = oldName

// Now put the original name in all answer records that have the
// new name
for _, answer := range a.Answer {
if answer.Header().Name == newName {
answer.Header().Name = oldName
}
}
return a, nil
}

func (r *SubDomainReplace) String() string {
return r.id
}