Skip to content

Commit b3d4fd5

Browse files
authored
improve padding (#4)
* ensures that padding of request URLs sends only as many characters as is required
1 parent d393b5f commit b3d4fd5

3 files changed

Lines changed: 86 additions & 16 deletions

File tree

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ consider the following:
5757
## Caveats/TODO
5858

5959
* Currently only the following records are supported: `A, AAAA, CNAME, MX`
60-
* Padding is not very smart; it just always pads to 1024 characters, and fails
61-
if the URL would've been larger than that
6260
* More thorough tests should be written
6361
* No caching is implemented, and probably never will. If you need caching, put
6462
your `secure-operator` server behind another DNS server which provides

provider_google.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import (
66
"net/http"
77
)
88

9-
var (
10-
targetPaddedLength = 1024
11-
paddingParameter = "random_padding"
9+
const (
10+
// DNSNameMaxBytes is the maximum number of bytes a DNS name may contain
11+
DNSNameMaxBytes = 253
12+
// max number of characters in a 16-bit uint integer, converted to string
13+
extraPad = 5
14+
paddingParameter = "random_padding"
1215
)
1316

1417
// GDNSQuestion represents a question response item from Google's DNS service
@@ -95,23 +98,24 @@ func (g GDNSProvider) Query(q DNSQuestion) (*DNSResponse, error) {
9598
}
9699

97100
qry := httpreq.URL.Query()
101+
dnsType := fmt.Sprintf("%v", q.Type)
102+
103+
l := len([]byte(q.Name))
104+
if l > DNSNameMaxBytes {
105+
return nil, fmt.Errorf("name length of %v exceeds DNS name max length", l)
106+
}
98107

99108
qry.Add("name", q.Name)
100-
qry.Add("type", fmt.Sprintf("%v", q.Type))
109+
qry.Add("type", dnsType)
101110
qry.Add("edns_client_subnet", "0.0.0.0/0")
102111

103112
httpreq.URL.RawQuery = qry.Encode()
104113

105-
// if padding was requested, pad to the target padding length
106-
// TODO: this needs to be smarter; should be padding to more sane lengths
107-
// except for very large name requests
108114
if g.Pad {
109-
l := len(httpreq.URL.String()) + len(paddingParameter) + 1
110-
111-
if l > targetPaddedLength {
112-
return nil, fmt.Errorf("failed to pad; query was already of length: %v", l)
113-
}
114-
pad := randSeq(targetPaddedLength - l)
115+
// pad to the maximum size a valid request could be. we add `1` because
116+
// Google's DNS service ignores a trailing period, increasing the
117+
// possible size of a name by 1
118+
pad := randSeq(DNSNameMaxBytes + extraPad - l - len(dnsType) + 1)
115119
qry.Add(paddingParameter, pad)
116120

117121
httpreq.URL.RawQuery = qry.Encode()

provider_google_test.go

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/http"
66
"net/http/httptest"
77
"strconv"
8+
"strings"
89
"testing"
910

1011
"github.com/miekg/dns"
@@ -27,6 +28,7 @@ func TestQuery(t *testing.T) {
2728
w.Header().Set("Content-Type", "application/json")
2829
fmt.Fprint(w, gresp)
2930
}))
31+
defer ts.Close()
3032

3133
g := GDNSProvider{
3234
Endpoint: ts.URL,
@@ -45,7 +47,7 @@ func TestQuery(t *testing.T) {
4547
if a.Name != "example.com." {
4648
t.Errorf("unexpected name %v", a.Name)
4749
}
48-
if a.Type != 1 {
50+
if a.Type != dns.TypeA {
4951
t.Errorf("unexpected type %v", a.Type)
5052
}
5153
if a.Data != "93.184.216.34" {
@@ -59,3 +61,69 @@ func TestQuery(t *testing.T) {
5961
t.Errorf("unexpected response code %v", resp.ResponseCode)
6062
}
6163
}
64+
65+
func TestPadding(t *testing.T) {
66+
var expected int
67+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
68+
l := len([]byte(r.URL.String()))
69+
// first request, set the expectation to the length of the first URL we
70+
// see; if any others don't match it, our padding function fails
71+
if expected == 0 {
72+
expected = l
73+
}
74+
75+
if l != expected {
76+
t.Errorf("unexpected URL length: %v, expected: %v", l, expected)
77+
}
78+
79+
w.WriteHeader(http.StatusOK)
80+
w.Header().Set("Content-Type", "application/json")
81+
fmt.Fprint(w, gresp)
82+
}))
83+
defer ts.Close()
84+
85+
g := GDNSProvider{
86+
Endpoint: ts.URL,
87+
Pad: true,
88+
}
89+
90+
questions := []DNSQuestion{
91+
DNSQuestion{Name: "whatever.yo", Type: dns.TypeA},
92+
// ensure differing types result in the same padded length
93+
DNSQuestion{Name: "sure.yep", Type: dns.TypeA},
94+
DNSQuestion{Name: "sure.yep", Type: dns.TypeMX},
95+
DNSQuestion{Name: "sure.yep", Type: dns.TypeTA},
96+
// ensure very long domains are fine
97+
DNSQuestion{Name: strings.Repeat("a", 253), Type: dns.TypeA},
98+
DNSQuestion{Name: strings.Repeat("a", 253), Type: dns.TypeTA},
99+
}
100+
101+
for _, q := range questions {
102+
if _, err := g.Query(q); err != nil {
103+
t.Errorf(err.Error())
104+
}
105+
106+
}
107+
108+
// a name longer that 253 bytes should be an error
109+
if _, err := g.Query(DNSQuestion{Name: strings.Repeat("a", 254)}); err == nil {
110+
t.Errorf("expected an error for a too-long DNS name")
111+
}
112+
}
113+
114+
func TestNameTooLong(t *testing.T) {
115+
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
116+
t.Errorf("no request should be made")
117+
}))
118+
defer ts.Close()
119+
120+
g := GDNSProvider{
121+
Endpoint: ts.URL,
122+
Pad: true,
123+
}
124+
125+
// a name longer that 253 bytes should be an error
126+
if _, err := g.Query(DNSQuestion{Name: strings.Repeat("a", 254)}); err == nil {
127+
t.Errorf("expected an error for a too-long DNS name")
128+
}
129+
}

0 commit comments

Comments
 (0)