tsdns: initial implementation of a Tailscale DNS resolver (#396)

Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>
This commit is contained in:
Dmytro Shynkevych
2020-06-08 18:19:26 -04:00
committed by GitHub
parent 5e1ee4be53
commit 511840b1f6
12 changed files with 583 additions and 109 deletions

View File

@@ -6,7 +6,6 @@
package filter
import (
"fmt"
"sync"
"time"
@@ -137,7 +136,7 @@ func maybeHexdump(flag RunFlags, b []byte) string {
var acceptBucket = rate.NewLimiter(rate.Every(10*time.Second), 3)
var dropBucket = rate.NewLimiter(rate.Every(5*time.Second), 10)
func (f *Filter) logRateLimit(runflags RunFlags, b []byte, q *packet.ParsedPacket, r Response, why string) {
func (f *Filter) logRateLimit(runflags RunFlags, q *packet.ParsedPacket, r Response, why string) {
var verdict string
if r == Drop && (runflags&LogDrops) != 0 && dropBucket.Allow() {
@@ -151,36 +150,33 @@ func (f *Filter) logRateLimit(runflags RunFlags, b []byte, q *packet.ParsedPacke
// Note: it is crucial that q.String() be called only if {accept,drop}Bucket.Allow() passes,
// since it causes an allocation.
if verdict != "" {
var qs string
if q == nil {
qs = fmt.Sprintf("(%d bytes)", len(b))
} else {
qs = q.String()
}
f.logf("%s: %s %d %s\n%s", verdict, qs, len(b), why, maybeHexdump(runflags, b))
b := q.Buffer()
f.logf("%s: %s %d %s\n%s", verdict, q.String(), len(b), why, maybeHexdump(runflags, b))
}
}
func (f *Filter) RunIn(b []byte, q *packet.ParsedPacket, rf RunFlags) Response {
r := f.pre(b, q, rf)
// RunIn determines whether this node is allowed to receive q from a Tailscale peer.
func (f *Filter) RunIn(q *packet.ParsedPacket, rf RunFlags) Response {
r := f.pre(q, rf)
if r == Accept || r == Drop {
// already logged
return r
}
r, why := f.runIn(q)
f.logRateLimit(rf, b, q, r, why)
f.logRateLimit(rf, q, r, why)
return r
}
func (f *Filter) RunOut(b []byte, q *packet.ParsedPacket, rf RunFlags) Response {
r := f.pre(b, q, rf)
// RunOut determines whether this node is allowed to send q to a Tailscale peer.
func (f *Filter) RunOut(q *packet.ParsedPacket, rf RunFlags) Response {
r := f.pre(q, rf)
if r == Drop || r == Accept {
// already logged
return r
}
r, why := f.runOut(q)
f.logRateLimit(rf, b, q, r, why)
f.logRateLimit(rf, q, r, why)
return r
}
@@ -251,29 +247,28 @@ func (f *Filter) runOut(q *packet.ParsedPacket) (r Response, why string) {
return Accept, "ok out"
}
func (f *Filter) pre(b []byte, q *packet.ParsedPacket, rf RunFlags) Response {
if len(b) == 0 {
func (f *Filter) pre(q *packet.ParsedPacket, rf RunFlags) Response {
if len(q.Buffer()) == 0 {
// wireguard keepalive packet, always permit.
return Accept
}
if len(b) < 20 {
f.logRateLimit(rf, b, nil, Drop, "too short")
if len(q.Buffer()) < 20 {
f.logRateLimit(rf, q, Drop, "too short")
return Drop
}
q.Decode(b)
switch q.IPProto {
case packet.Unknown:
// Unknown packets are dangerous; always drop them.
f.logRateLimit(rf, b, q, Drop, "unknown")
f.logRateLimit(rf, q, Drop, "unknown")
return Drop
case packet.IPv6:
f.logRateLimit(rf, b, q, Drop, "ipv6")
f.logRateLimit(rf, q, Drop, "ipv6")
return Drop
case packet.Fragment:
// Fragments after the first always need to be passed through.
// Very small fragments are considered Junk by ParsedPacket.
f.logRateLimit(rf, b, q, Accept, "fragment")
f.logRateLimit(rf, q, Accept, "fragment")
return Accept
}

View File

@@ -144,11 +144,12 @@ func TestNoAllocs(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got := int(testing.AllocsPerRun(1000, func() {
var q ParsedPacket
q := &ParsedPacket{}
q.Decode(test.packet)
if test.in {
acl.RunIn(test.packet, &q, 0)
acl.RunIn(q, 0)
} else {
acl.RunOut(test.packet, &q, 0)
acl.RunOut(q, 0)
}
}))
@@ -187,12 +188,13 @@ func BenchmarkFilter(b *testing.B) {
for _, bench := range benches {
b.Run(bench.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
var q ParsedPacket
q := &ParsedPacket{}
q.Decode(bench.packet)
// This branch seems to have no measurable impact on performance.
if bench.in {
acl.RunIn(bench.packet, &q, 0)
acl.RunIn(q, 0)
} else {
acl.RunOut(bench.packet, &q, 0)
acl.RunOut(q, 0)
}
}
})
@@ -215,7 +217,9 @@ func TestPreFilter(t *testing.T) {
}
f := NewAllowNone(t.Logf)
for _, testPacket := range packets {
got := f.pre([]byte(testPacket.b), &ParsedPacket{}, LogDrops|LogAccepts)
p := &ParsedPacket{}
p.Decode(testPacket.b)
got := f.pre(p, LogDrops|LogAccepts)
if got != testPacket.want {
t.Errorf("%q got=%v want=%v packet:\n%s", testPacket.desc, got, testPacket.want, packet.Hexdump(testPacket.b))
}