@@ -28,6 +28,18 @@ func BuildEmptyNoErrorResponseFromLite(request []byte, parsed LitePacket) ([]byt
2828 return buildResponseWithRCodeLite (request , parsed , Enums .DNSR_CODE_NO_ERROR )
2929}
3030
31+ func BuildNoDataResponse (request []byte ) ([]byte , error ) {
32+ parsed , err := ParseDNSRequestLite (request )
33+ if err != nil {
34+ return nil , err
35+ }
36+ return BuildNoDataResponseFromLite (request , parsed )
37+ }
38+
39+ func BuildNoDataResponseFromLite (request []byte , parsed LitePacket ) ([]byte , error ) {
40+ return buildNoDataResponseLite (request , parsed )
41+ }
42+
3143func BuildFormatErrorResponse (request []byte ) ([]byte , error ) {
3244 return buildResponseWithRCode (request , Enums .DNSR_CODE_FORMAT_ERROR )
3345}
@@ -52,6 +64,27 @@ func BuildNotImplementedResponseFromLite(request []byte, parsed LitePacket) ([]b
5264 return buildResponseWithRCodeLite (request , parsed , Enums .DNSR_CODE_NOT_IMPLEMENTED )
5365}
5466
67+ const (
68+ syntheticNoDataTTL = 60
69+ syntheticNoDataRefresh = 60
70+ syntheticNoDataRetry = 60
71+ syntheticNoDataExpire = 300
72+ syntheticNoDataMinimum = 60
73+ )
74+
75+ var (
76+ syntheticSOAMName = []byte {
77+ 0x02 , 'n' , 's' ,
78+ 0x07 , 'i' , 'n' , 'v' , 'a' , 'l' , 'i' , 'd' ,
79+ 0x00 ,
80+ }
81+ syntheticSOARName = []byte {
82+ 0x0a , 'h' , 'o' , 's' , 't' , 'm' , 'a' , 's' , 't' , 'e' , 'r' ,
83+ 0x07 , 'i' , 'n' , 'v' , 'a' , 'l' , 'i' , 'd' ,
84+ 0x00 ,
85+ }
86+ )
87+
5588func buildResponseWithRCode (request []byte , rcode uint8 ) ([]byte , error ) {
5689 if len (request ) < dnsHeaderSize {
5790 return nil , ErrPacketTooShort
@@ -70,7 +103,6 @@ func buildResponseWithRCode(request []byte, rcode uint8) ([]byte, error) {
70103 questionCount = header .QDCount
71104 }
72105
73-
74106 optStart , optLen := findOPTRecordRange (request , header , questionEndOffset )
75107
76108 response := make ([]byte , dnsHeaderSize + questionLen + optLen )
@@ -87,7 +119,6 @@ func buildResponseWithRCode(request []byte, rcode uint8) ([]byte, error) {
87119 copy (response [dnsHeaderSize + questionLen :], request [optStart :optStart + optLen ])
88120 }
89121
90-
91122 return response , nil
92123}
93124
@@ -122,14 +153,83 @@ func buildResponseWithRCodeLite(request []byte, parsed LitePacket, rcode uint8)
122153 return response , nil
123154}
124155
156+ func buildNoDataResponseLite (request []byte , parsed LitePacket ) ([]byte , error ) {
157+ if len (request ) < dnsHeaderSize {
158+ return nil , ErrPacketTooShort
159+ }
160+ if ! isLikelyDNSRequestHeader (parsed .Header ) {
161+ return nil , ErrNotDNSRequest
162+ }
163+
164+ optStart , optLen := findOPTRecordRange (request , parsed .Header , parsed .QuestionEndOffset )
165+
166+ questionLen := 0
167+ if parsed .QuestionEndOffset >= dnsHeaderSize && parsed .QuestionEndOffset <= len (request ) {
168+ questionLen = parsed .QuestionEndOffset - dnsHeaderSize
169+ }
170+
171+ authority := buildSyntheticSOAAuthority (parsed )
172+ response := make ([]byte , dnsHeaderSize + questionLen + len (authority )+ optLen )
173+ binary .BigEndian .PutUint16 (response [0 :2 ], parsed .Header .ID )
174+ binary .BigEndian .PutUint16 (response [2 :4 ], buildResponseFlags (parsed .Header .Flags , Enums .DNSR_CODE_NO_ERROR ))
175+ binary .BigEndian .PutUint16 (response [4 :6 ], parsed .Header .QDCount )
176+ if len (authority ) > 0 {
177+ binary .BigEndian .PutUint16 (response [8 :10 ], 1 )
178+ }
179+ binary .BigEndian .PutUint16 (response [10 :12 ], uint16 (getARCount (optLen )))
180+
181+ offset := dnsHeaderSize
182+ if questionLen > 0 {
183+ copy (response [offset :], request [dnsHeaderSize :parsed .QuestionEndOffset ])
184+ offset += questionLen
185+ }
186+ if len (authority ) > 0 {
187+ copy (response [offset :], authority )
188+ offset += len (authority )
189+ }
190+ if optLen > 0 {
191+ copy (response [offset :], request [optStart :optStart + optLen ])
192+ }
193+
194+ return response , nil
195+ }
196+
197+ func buildSyntheticSOAAuthority (parsed LitePacket ) []byte {
198+ owner := []byte {0 }
199+ if parsed .HasQuestion {
200+ // Reuse the first question name via compression pointer to keep the
201+ // synthetic authority compact and avoid re-encoding the qname.
202+ owner = []byte {0xC0 , 0x0C }
203+ }
204+
205+ rdataLen := len (syntheticSOAMName ) + len (syntheticSOARName ) + 20
206+ record := make ([]byte , len (owner )+ 10 + rdataLen )
207+
208+ offset := copy (record , owner )
209+ binary .BigEndian .PutUint16 (record [offset :offset + 2 ], Enums .DNS_RECORD_TYPE_SOA )
210+ binary .BigEndian .PutUint16 (record [offset + 2 :offset + 4 ], Enums .DNSQ_CLASS_IN )
211+ binary .BigEndian .PutUint32 (record [offset + 4 :offset + 8 ], syntheticNoDataTTL )
212+ binary .BigEndian .PutUint16 (record [offset + 8 :offset + 10 ], uint16 (rdataLen ))
213+ offset += 10
214+
215+ offset += copy (record [offset :], syntheticSOAMName )
216+ offset += copy (record [offset :], syntheticSOARName )
217+ binary .BigEndian .PutUint32 (record [offset :offset + 4 ], 1 )
218+ binary .BigEndian .PutUint32 (record [offset + 4 :offset + 8 ], syntheticNoDataRefresh )
219+ binary .BigEndian .PutUint32 (record [offset + 8 :offset + 12 ], syntheticNoDataRetry )
220+ binary .BigEndian .PutUint32 (record [offset + 12 :offset + 16 ], syntheticNoDataExpire )
221+ binary .BigEndian .PutUint32 (record [offset + 16 :offset + 20 ], syntheticNoDataMinimum )
222+
223+ return record
224+ }
225+
125226func getARCount (optLen int ) int {
126227 if optLen > 0 {
127228 return 1
128229 }
129230 return 0
130231}
131232
132-
133233func isLikelyDNSRequestHeader (header Header ) bool {
134234 if header .QR != 0 {
135235 return false
@@ -153,7 +253,29 @@ func isLikelyDNSRequestHeader(header Header) bool {
153253}
154254
155255func buildResponseFlags (requestFlags uint16 , rcode uint8 ) uint16 {
156- return (1 << 15 ) | (requestFlags & 0x7810 ) | uint16 (rcode & 0x0F )
256+ const (
257+ flagQR uint16 = 1 << 15
258+ flagAA uint16 = 1 << 10
259+ flagTC uint16 = 1 << 9
260+ flagRD uint16 = 1 << 8
261+ flagRA uint16 = 1 << 7
262+ flagCD uint16 = 1 << 4
263+ opcodeMask uint16 = 0x7800
264+ )
265+
266+ flags := flagQR | flagRA | (requestFlags & opcodeMask ) | uint16 (rcode & 0x0F )
267+ if requestFlags & flagRD != 0 {
268+ flags |= flagRD
269+ }
270+ if requestFlags & flagCD != 0 {
271+ flags |= flagCD
272+ }
273+
274+ // Resolver-generated local answers should look recursive, not authoritative.
275+ // AA/TC are intentionally cleared unless we are relaying an upstream answer
276+ // verbatim, in which case those bits come from the upstream packet itself.
277+ flags &^= flagAA | flagTC
278+ return flags
157279}
158280
159281func extractQuestionSection (request []byte , header Header ) ([]byte , uint16 , int ) {
@@ -244,7 +366,6 @@ func findFirstOPTRecordInAdditional(data []byte, offset int, count int) (int, in
244366 return 0 , 0
245367}
246368
247-
248369func skipQuestions (data []byte , offset int , count int ) (int , error ) {
249370 for range count {
250371 nextOffset , err := skipName (data , offset )
@@ -345,4 +466,3 @@ func skipName(data []byte, offset int) (int, error) {
345466 offset += length + 1
346467 }
347468}
348-
0 commit comments