Skip to content

Commit 70b350d

Browse files
committed
Support Multiple answers.
1 parent 02859cf commit 70b350d

2 files changed

Lines changed: 122 additions & 17 deletions

File tree

internal/dnsparser/transport.go

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
var (
2222
ErrTXTAnswerMissing = errors.New("dns txt answer missing")
2323
ErrTXTAnswerMalformed = errors.New("dns txt answer malformed")
24+
ErrTXTAnswerTooLarge = errors.New("dns txt answer too large")
2425
)
2526

2627
const (
@@ -89,8 +90,13 @@ func BuildTXTResponsePacket(questionPacket []byte, answerName string, answerPayl
8990
}
9091

9192
answerLen := 0
92-
for _, payload := range answerPayloads {
93-
answerLen += len(nameBytes) + 10 + len(payload)
93+
useAnswerNameCompression := len(answerPayloads) > 1
94+
for i, payload := range answerPayloads {
95+
nameLen := len(nameBytes)
96+
if useAnswerNameCompression && i > 0 {
97+
nameLen = 2
98+
}
99+
answerLen += nameLen + 10 + len(payload)
94100
}
95101

96102
response := make([]byte, dnsHeaderSize+len(questionBytes)+answerLen+rawRecordsLen(optRecords))
@@ -103,9 +109,15 @@ func BuildTXTResponsePacket(questionPacket []byte, answerName string, answerPayl
103109

104110
offset := dnsHeaderSize
105111
offset += copy(response[offset:], questionBytes)
112+
firstAnswerNameOffset := offset
106113

107-
for _, payload := range answerPayloads {
108-
offset += copy(response[offset:], nameBytes)
114+
for i, payload := range answerPayloads {
115+
if useAnswerNameCompression && i > 0 && firstAnswerNameOffset <= 0x3FFF {
116+
binary.BigEndian.PutUint16(response[offset:offset+2], uint16(0xC000|firstAnswerNameOffset))
117+
offset += 2
118+
} else {
119+
offset += copy(response[offset:], nameBytes)
120+
}
109121
binary.BigEndian.PutUint16(response[offset:offset+2], enums.DNSRecordTypeTXT)
110122
binary.BigEndian.PutUint16(response[offset+2:offset+4], enums.DNSQClassIN)
111123
binary.BigEndian.PutUint32(response[offset+4:offset+8], 0)
@@ -137,7 +149,10 @@ func BuildVPNResponsePacket(questionPacket []byte, answerName string, packet vpn
137149
return nil, err
138150
}
139151

140-
answerPayloads := buildTXTAnswerChunks(rawFrame, baseEncode)
152+
answerPayloads, err := buildTXTAnswerChunks(rawFrame, baseEncode)
153+
if err != nil {
154+
return nil, err
155+
}
141156
return BuildTXTResponsePacket(questionPacket, answerName, answerPayloads)
142157
}
143158

@@ -212,14 +227,14 @@ func BuildTunnelQuestionName(domain string, encodedFrame string) (string, error)
212227
return name, nil
213228
}
214229

215-
func buildTXTAnswerChunks(rawFrame []byte, baseEncode bool) [][]byte {
230+
func buildTXTAnswerChunks(rawFrame []byte, baseEncode bool) ([][]byte, error) {
216231
maxChunk := maxTXTAnswerPayload
217232
if baseEncode {
218233
maxChunk = maxTXTEncodedChunk
219234
}
220235

221236
if len(rawFrame) == 0 {
222-
return [][]byte{appendLengthPrefixedTXT(nil)}
237+
return [][]byte{appendLengthPrefixedTXT(nil)}, nil
223238
}
224239

225240
encodeChunk := func(raw []byte) []byte {
@@ -231,12 +246,12 @@ func buildTXTAnswerChunks(rawFrame []byte, baseEncode bool) [][]byte {
231246
}
232247

233248
if len(rawFrame) <= maxChunk {
234-
return [][]byte{encodeChunk(rawFrame)}
249+
return [][]byte{encodeChunk(rawFrame)}, nil
235250
}
236251

237252
header, err := vpnproto.Parse(rawFrame)
238253
if err != nil {
239-
return [][]byte{encodeChunk(rawFrame)}
254+
return [][]byte{encodeChunk(rawFrame)}, nil
240255
}
241256

242257
headerLen := header.HeaderLength
@@ -252,6 +267,9 @@ func buildTXTAnswerChunks(rawFrame []byte, baseEncode bool) [][]byte {
252267
if remaining > 0 {
253268
totalChunks += (remaining + maxChunkNData - 1) / maxChunkNData
254269
}
270+
if totalChunks > 255 {
271+
return nil, ErrTXTAnswerTooLarge
272+
}
255273

256274
chunks := make([][]byte, 0, totalChunks)
257275
rawChunk0 := make([]byte, 0, 2+len(rawFrame))
@@ -273,7 +291,7 @@ func buildTXTAnswerChunks(rawFrame []byte, baseEncode bool) [][]byte {
273291
cursor = end
274292
}
275293

276-
return chunks
294+
return chunks, nil
277295
}
278296

279297
func appendLengthPrefixedTXT(data []byte) []byte {
@@ -351,8 +369,9 @@ func assembleVPNResponse(rawAnswers [][]byte, baseEncoded bool) (vpnproto.Packet
351369
return vpnproto.Parse(raw)
352370
}
353371

354-
chunks := make(map[int][]byte, len(rawAnswers))
372+
var chunks [256][]byte
355373
totalExpected := 0
374+
seenChunks := 0
356375
var header vpnproto.Packet
357376
headerSeen := false
358377

@@ -373,35 +392,53 @@ func assembleVPNResponse(rawAnswers [][]byte, baseEncoded bool) (vpnproto.Packet
373392
return vpnproto.Packet{}, ErrTXTAnswerMalformed
374393
}
375394
totalExpected = int(raw[1])
395+
if totalExpected <= 0 || totalExpected > len(chunks) {
396+
return vpnproto.Packet{}, ErrTXTAnswerMalformed
397+
}
376398
parsed, err := vpnproto.ParseAtOffset(raw, 2)
377399
if err != nil {
378400
return vpnproto.Packet{}, err
379401
}
380402
header = parsed
381403
headerSeen = true
404+
if chunks[0] == nil {
405+
seenChunks++
406+
}
382407
chunks[0] = parsed.Payload
383408
continue
384409
}
385410

386-
chunks[int(raw[0])] = raw[1:]
411+
chunkID := int(raw[0])
412+
if chunkID >= len(chunks) {
413+
return vpnproto.Packet{}, ErrTXTAnswerMalformed
414+
}
415+
if chunks[chunkID] == nil {
416+
seenChunks++
417+
}
418+
chunks[chunkID] = raw[1:]
387419
}
388420

389-
if !headerSeen || totalExpected <= 0 || len(chunks) != totalExpected {
421+
if !headerSeen || totalExpected <= 0 || seenChunks != totalExpected {
390422
return vpnproto.Packet{}, ErrTXTAnswerMalformed
391423
}
392-
for i := 0; i < totalExpected; i++ {
393-
if _, ok := chunks[i]; !ok {
424+
for i := range totalExpected {
425+
if chunks[i] == nil {
426+
return vpnproto.Packet{}, ErrTXTAnswerMalformed
427+
}
428+
}
429+
for i := totalExpected; i < len(chunks); i++ {
430+
if chunks[i] != nil {
394431
return vpnproto.Packet{}, ErrTXTAnswerMalformed
395432
}
396433
}
397434

398435
payloadLen := 0
399-
for i := 0; i < totalExpected; i++ {
436+
for i := range totalExpected {
400437
payloadLen += len(chunks[i])
401438
}
402439

403440
payload := make([]byte, 0, payloadLen)
404-
for i := 0; i < totalExpected; i++ {
441+
for i := range totalExpected {
405442
payload = append(payload, chunks[i]...)
406443
}
407444
header.Payload = payload

internal/dnsparser/transport_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package dnsparser
99

1010
import (
1111
"bytes"
12+
"errors"
1213
"testing"
1314

1415
"masterdnsvpn-go/internal/enums"
@@ -82,6 +83,73 @@ func TestBuildAndExtractVPNResponsePacketChunked(t *testing.T) {
8283
}
8384
}
8485

86+
func TestExtractVPNResponseReordersChunkedAnswers(t *testing.T) {
87+
query, err := BuildTXTQuestionPacket("x.v.example.com", enums.DNSRecordTypeTXT, 4096)
88+
if err != nil {
89+
t.Fatalf("BuildTXTQuestionPacket returned error: %v", err)
90+
}
91+
92+
rawFrame, err := vpnproto.BuildRaw(vpnproto.BuildOptions{
93+
SessionID: 7,
94+
PacketType: enums.PacketMTUDownRes,
95+
StreamID: 1,
96+
SequenceNum: 2,
97+
Payload: bytes.Repeat([]byte{0xCD}, 700),
98+
})
99+
if err != nil {
100+
t.Fatalf("BuildRaw returned error: %v", err)
101+
}
102+
103+
chunks, err := buildTXTAnswerChunks(rawFrame, false)
104+
if err != nil {
105+
t.Fatalf("buildTXTAnswerChunks returned error: %v", err)
106+
}
107+
if len(chunks) < 3 {
108+
t.Fatalf("expected chunked answers, got=%d", len(chunks))
109+
}
110+
111+
reordered := make([][]byte, len(chunks))
112+
copy(reordered, chunks)
113+
reordered[1], reordered[2] = reordered[2], reordered[1]
114+
115+
response, err := BuildTXTResponsePacket(query, "x.v.example.com", reordered)
116+
if err != nil {
117+
t.Fatalf("BuildTXTResponsePacket returned error: %v", err)
118+
}
119+
120+
packet, err := ExtractVPNResponse(response, false)
121+
if err != nil {
122+
t.Fatalf("ExtractVPNResponse returned error: %v", err)
123+
}
124+
if packet.PacketType != enums.PacketMTUDownRes {
125+
t.Fatalf("unexpected packet type: got=%d want=%d", packet.PacketType, enums.PacketMTUDownRes)
126+
}
127+
if len(packet.Payload) != 700 {
128+
t.Fatalf("unexpected payload len: got=%d want=%d", len(packet.Payload), 700)
129+
}
130+
}
131+
132+
func TestBuildTXTAnswerChunksRejectsTooManyChunks(t *testing.T) {
133+
rawFrame, err := vpnproto.BuildRaw(vpnproto.BuildOptions{
134+
SessionID: 7,
135+
PacketType: enums.PacketMTUDownRes,
136+
StreamID: 1,
137+
SequenceNum: 2,
138+
Payload: bytes.Repeat([]byte{0xEF}, 70000),
139+
})
140+
if err != nil {
141+
t.Fatalf("BuildRaw returned error: %v", err)
142+
}
143+
144+
_, err = buildTXTAnswerChunks(rawFrame, false)
145+
if err == nil {
146+
t.Fatal("expected chunk overflow error, got nil")
147+
}
148+
if !errors.Is(err, ErrTXTAnswerTooLarge) {
149+
t.Fatalf("unexpected error: %v", err)
150+
}
151+
}
152+
85153
func stringsOf(ch byte, count int) string {
86154
buf := make([]byte, count)
87155
for i := range buf {

0 commit comments

Comments
 (0)