Finishes up DNS compression by adding opt-out, tests, and documentation. Fixes trim routine.

This commit is contained in:
James Phillips 2016-08-11 16:24:44 -07:00
parent 41db005d0b
commit bcb0f71759
No known key found for this signature in database
GPG Key ID: 77183E682AC5FC11
5 changed files with 251 additions and 10 deletions

View File

@ -104,6 +104,11 @@ type DNSConfig struct {
// whose health checks are in any non-passing state. By // whose health checks are in any non-passing state. By
// default, only nodes in a critical state are excluded. // default, only nodes in a critical state are excluded.
OnlyPassing bool `mapstructure:"only_passing"` OnlyPassing bool `mapstructure:"only_passing"`
// DisableCompression is used to control whether DNS responses are
// compressed. In Consul 0.7 this was turned on by default and this
// config was added as an opt-out.
DisableCompression bool `mapstructure:"disable_compression"`
} }
// Telemetry is the telemetry configuration for the server // Telemetry is the telemetry configuration for the server
@ -1300,6 +1305,9 @@ func MergeConfig(a, b *Config) *Config {
if b.DNSConfig.OnlyPassing { if b.DNSConfig.OnlyPassing {
result.DNSConfig.OnlyPassing = true result.DNSConfig.OnlyPassing = true
} }
if b.DNSConfig.DisableCompression {
result.DNSConfig.DisableCompression = true
}
if b.CheckUpdateIntervalRaw != "" || b.CheckUpdateInterval != 0 { if b.CheckUpdateIntervalRaw != "" || b.CheckUpdateInterval != 0 {
result.CheckUpdateInterval = b.CheckUpdateInterval result.CheckUpdateInterval = b.CheckUpdateInterval
} }

View File

@ -608,6 +608,17 @@ func TestDecodeConfig(t *testing.T) {
t.Fatalf("bad: %#v", config) t.Fatalf("bad: %#v", config)
} }
// DNS disable compression
input = `{"dns_config": {"disable_compression": true}}`
config, err = DecodeConfig(bytes.NewReader([]byte(input)))
if err != nil {
t.Fatalf("err: %s", err)
}
if !config.DNSConfig.DisableCompression {
t.Fatalf("bad: %#v", config)
}
// CheckUpdateInterval // CheckUpdateInterval
input = `{"check_update_interval": "10m"}` input = `{"check_update_interval": "10m"}`
config, err = DecodeConfig(bytes.NewReader([]byte(input))) config, err = DecodeConfig(bytes.NewReader([]byte(input)))
@ -1371,6 +1382,7 @@ func TestMergeConfig(t *testing.T) {
DNSConfig: DNSConfig{ DNSConfig: DNSConfig{
AllowStale: false, AllowStale: false,
EnableTruncate: true, EnableTruncate: true,
DisableCompression: true,
MaxStale: 30 * time.Second, MaxStale: 30 * time.Second,
NodeTTL: 10 * time.Second, NodeTTL: 10 * time.Second,
ServiceTTL: map[string]time.Duration{ ServiceTTL: map[string]time.Duration{

View File

@ -180,7 +180,7 @@ func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) {
// Setup the message response // Setup the message response
m := new(dns.Msg) m := new(dns.Msg)
m.SetReply(req) m.SetReply(req)
m.Compress = true m.Compress = !d.config.DisableCompression
m.Authoritative = true m.Authoritative = true
m.RecursionAvailable = (len(d.recursors) > 0) m.RecursionAvailable = (len(d.recursors) > 0)
@ -250,7 +250,7 @@ func (d *DNSServer) handleQuery(resp dns.ResponseWriter, req *dns.Msg) {
// Setup the message response // Setup the message response
m := new(dns.Msg) m := new(dns.Msg)
m.SetReply(req) m.SetReply(req)
m.Compress = true m.Compress = !d.config.DisableCompression
m.Authoritative = true m.Authoritative = true
m.RecursionAvailable = (len(d.recursors) > 0) m.RecursionAvailable = (len(d.recursors) > 0)
@ -506,10 +506,17 @@ func trimUDPAnswers(config *DNSConfig, resp *dns.Msg) (trimmed bool) {
resp.Answer = resp.Answer[:maxAnswers] resp.Answer = resp.Answer[:maxAnswers]
} }
// This enforces the hard limit of 512 bytes per the RFC. // This enforces the hard limit of 512 bytes per the RFC. Note that we
// temporarily switch to uncompressed so that we limit to a response
// that will not exceed 512 bytes uncompressed, which is more
// conservative and will allow our responses to be compliant even if
// some downstream server uncompresses them.
compress := resp.Compress
resp.Compress = false
for len(resp.Answer) > 0 && resp.Len() > 512 { for len(resp.Answer) > 0 && resp.Len() > 512 {
resp.Answer = resp.Answer[:len(resp.Answer)-1] resp.Answer = resp.Answer[:len(resp.Answer)-1]
} }
resp.Compress = compress
return len(resp.Answer) < numAnswers return len(resp.Answer) < numAnswers
} }
@ -788,7 +795,11 @@ func (d *DNSServer) handleRecurse(resp dns.ResponseWriter, req *dns.Msg) {
for _, recursor := range d.recursors { for _, recursor := range d.recursors {
r, rtt, err = c.Exchange(req, recursor) r, rtt, err = c.Exchange(req, recursor)
if err == nil { if err == nil {
r.Compress = true // Compress the response; we don't know if the incoming
// response was compressed or not, so by not compressing
// we might generate an invalid packet on the way out.
r.Compress = !d.config.DisableCompression
// Forward the response // Forward the response
d.logger.Printf("[DEBUG] dns: recurse RTT for %v (%v)", q, rtt) d.logger.Printf("[DEBUG] dns: recurse RTT for %v (%v)", q, rtt)
if err := resp.WriteMsg(r); err != nil { if err := resp.WriteMsg(r); err != nil {
@ -804,7 +815,7 @@ func (d *DNSServer) handleRecurse(resp dns.ResponseWriter, req *dns.Msg) {
q, resp.RemoteAddr().String(), resp.RemoteAddr().Network()) q, resp.RemoteAddr().String(), resp.RemoteAddr().Network())
m := &dns.Msg{} m := &dns.Msg{}
m.SetReply(req) m.SetReply(req)
m.Compress = true m.Compress = !d.config.DisableCompression
m.RecursionAvailable = true m.RecursionAvailable = true
m.SetRcode(req, dns.RcodeServerFailure) m.SetRcode(req, dns.RcodeServerFailure)
resp.WriteMsg(m) resp.WriteMsg(m)

View File

@ -3200,3 +3200,209 @@ func TestDNS_PreparedQuery_AgentSource(t *testing.T) {
} }
} }
} }
func TestDNS_Compression_trimUDPAnswers(t *testing.T) {
config := &DefaultConfig().DNSConfig
m := dns.Msg{}
trimUDPAnswers(config, &m)
if m.Compress {
t.Fatalf("compression should be off")
}
// The trim function temporarily turns off compression, so we need to
// make sure the setting gets restored properly.
m.Compress = true
trimUDPAnswers(config, &m)
if !m.Compress {
t.Fatalf("compression should be on")
}
}
func TestDNS_Compression_Query(t *testing.T) {
dir, srv := makeDNSServer(t)
defer os.RemoveAll(dir)
defer srv.agent.Shutdown()
testutil.WaitForLeader(t, srv.agent.RPC, "dc1")
// Register a node with a service.
{
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo",
Address: "127.0.0.1",
Service: &structs.NodeService{
Service: "db",
Tags: []string{"master"},
Port: 12345,
},
}
var out struct{}
if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
}
// Register an equivalent prepared query.
var id string
{
args := &structs.PreparedQueryRequest{
Datacenter: "dc1",
Op: structs.PreparedQueryCreate,
Query: &structs.PreparedQuery{
Service: structs.ServiceQuery{
Service: "db",
},
},
}
if err := srv.agent.RPC("PreparedQuery.Apply", args, &id); err != nil {
t.Fatalf("err: %v", err)
}
}
// Look up the service directly and via prepared query.
questions := []string{
"db.service.consul.",
id + ".query.consul.",
}
for _, question := range questions {
m := new(dns.Msg)
m.SetQuestion(question, dns.TypeSRV)
addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS)
conn, err := dns.Dial("udp", addr.String())
if err != nil {
t.Fatalf("err: %v", err)
}
// Do a manual exchange with compression on (the default).
srv.config.DisableCompression = false
if err := conn.WriteMsg(m); err != nil {
t.Fatalf("err: %v", err)
}
p := make([]byte, dns.MaxMsgSize)
compressed, err := conn.Read(p)
if err != nil {
t.Fatalf("err: %v", err)
}
// Disable compression and try again.
srv.config.DisableCompression = true
if err := conn.WriteMsg(m); err != nil {
t.Fatalf("err: %v", err)
}
unc, err := conn.Read(p)
if err != nil {
t.Fatalf("err: %v", err)
}
// We can't see the compressed status given the DNS API, so we
// just make sure the message is smaller to see if it's
// respecting the flag.
if compressed == 0 || unc == 0 || compressed >= unc {
t.Fatalf("'%s' doesn't look compressed: %d vs. %d", question, compressed, unc)
}
}
}
func TestDNS_Compression_ReverseLookup(t *testing.T) {
dir, srv := makeDNSServer(t)
defer os.RemoveAll(dir)
defer srv.agent.Shutdown()
testutil.WaitForLeader(t, srv.agent.RPC, "dc1")
// Register node.
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: "foo2",
Address: "127.0.0.2",
}
var out struct{}
if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil {
t.Fatalf("err: %v", err)
}
m := new(dns.Msg)
m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY)
addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS)
conn, err := dns.Dial("udp", addr.String())
if err != nil {
t.Fatalf("err: %v", err)
}
// Do a manual exchange with compression on (the default).
if err := conn.WriteMsg(m); err != nil {
t.Fatalf("err: %v", err)
}
p := make([]byte, dns.MaxMsgSize)
compressed, err := conn.Read(p)
if err != nil {
t.Fatalf("err: %v", err)
}
// Disable compression and try again.
srv.config.DisableCompression = true
if err := conn.WriteMsg(m); err != nil {
t.Fatalf("err: %v", err)
}
unc, err := conn.Read(p)
if err != nil {
t.Fatalf("err: %v", err)
}
// We can't see the compressed status given the DNS API, so we just make
// sure the message is smaller to see if it's respecting the flag.
if compressed == 0 || unc == 0 || compressed >= unc {
t.Fatalf("doesn't look compressed: %d vs. %d", compressed, unc)
}
}
func TestDNS_Compression_Recurse(t *testing.T) {
recursor := makeRecursor(t, []dns.RR{dnsA("apple.com", "1.2.3.4")})
defer recursor.Shutdown()
dir, srv := makeDNSServerConfig(t, func(c *Config) {
c.DNSRecursor = recursor.Addr
}, nil)
defer os.RemoveAll(dir)
defer srv.agent.Shutdown()
m := new(dns.Msg)
m.SetQuestion("apple.com.", dns.TypeANY)
addr, _ := srv.agent.config.ClientListener("", srv.agent.config.Ports.DNS)
conn, err := dns.Dial("udp", addr.String())
if err != nil {
t.Fatalf("err: %v", err)
}
// Do a manual exchange with compression on (the default).
if err := conn.WriteMsg(m); err != nil {
t.Fatalf("err: %v", err)
}
p := make([]byte, dns.MaxMsgSize)
compressed, err := conn.Read(p)
if err != nil {
t.Fatalf("err: %v", err)
}
// Disable compression and try again.
srv.config.DisableCompression = true
if err := conn.WriteMsg(m); err != nil {
t.Fatalf("err: %v", err)
}
unc, err := conn.Read(p)
if err != nil {
t.Fatalf("err: %v", err)
}
// We can't see the compressed status given the DNS API, so we just make
// sure the message is smaller to see if it's respecting the flag.
if compressed == 0 || unc == 0 || compressed >= unc {
t.Fatalf("doesn't look compressed: %d vs. %d", compressed, unc)
}
}

View File

@ -516,6 +516,10 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
are considered. For example, if a node has a health check that is critical then all services on are considered. For example, if a node has a health check that is critical then all services on
that node will be excluded because they are also considered critical. that node will be excluded because they are also considered critical.
* <a name="disable_compression"></a><a href="#disable_compression">`disable_compression`</a> If
set to true, DNS responses will not be compressed. Compression was added and enabled by default
in Consul 0.7.
* <a name="udp_answer_limit"></a><a * <a name="udp_answer_limit"></a><a
href="#udp_answer_limit">`udp_answer_limit`</a> - Limit the number of href="#udp_answer_limit">`udp_answer_limit`</a> - Limit the number of
resource records contained in the answer section of a UDP-based DNS resource records contained in the answer section of a UDP-based DNS