180 lines
4.1 KiB
Go
180 lines
4.1 KiB
Go
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
|
|
|
package nodes
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"github.com/iwind/TeaGo/Tea"
|
|
"github.com/iwind/TeaGo/types"
|
|
"github.com/miekg/dns"
|
|
"net"
|
|
"net/http"
|
|
"reflect"
|
|
)
|
|
|
|
type HTTPWriter struct {
|
|
rawConn net.Conn
|
|
rawWriter http.ResponseWriter
|
|
contentType string
|
|
}
|
|
|
|
func NewHTTPWriter(rawWriter http.ResponseWriter, rawConn net.Conn, contentType string) *HTTPWriter {
|
|
return &HTTPWriter{
|
|
rawWriter: rawWriter,
|
|
rawConn: rawConn,
|
|
contentType: contentType,
|
|
}
|
|
}
|
|
|
|
func (this *HTTPWriter) LocalAddr() net.Addr {
|
|
return this.rawConn.LocalAddr()
|
|
}
|
|
|
|
func (this *HTTPWriter) RemoteAddr() net.Addr {
|
|
return this.rawConn.RemoteAddr()
|
|
}
|
|
|
|
func (this *HTTPWriter) WriteMsg(msg *dns.Msg) error {
|
|
if msg == nil {
|
|
return errors.New("'msg' should not be nil")
|
|
}
|
|
|
|
msgData, err := this.encodeMsg(msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
this.rawWriter.Header().Set("Content-Length", types.String(len(msgData)))
|
|
this.rawWriter.Header().Set("Content-Type", this.contentType)
|
|
|
|
// cache-control
|
|
if len(msg.Answer) > 0 {
|
|
var minTtl uint32
|
|
for _, answer := range msg.Answer {
|
|
var header = answer.Header()
|
|
if header != nil && header.Ttl > 0 && (minTtl == 0 || header.Ttl < minTtl) {
|
|
minTtl = header.Ttl
|
|
}
|
|
}
|
|
if minTtl > 0 {
|
|
this.rawWriter.Header().Set("Cache-Control", "max-age="+types.String(minTtl))
|
|
}
|
|
}
|
|
|
|
this.rawWriter.WriteHeader(http.StatusOK)
|
|
_, err = this.rawWriter.Write(msgData)
|
|
return err
|
|
}
|
|
|
|
func (this *HTTPWriter) Write(p []byte) (int, error) {
|
|
this.rawWriter.Header().Set("Content-Length", types.String(len(p)))
|
|
this.rawWriter.WriteHeader(http.StatusOK)
|
|
return this.rawWriter.Write(p)
|
|
}
|
|
|
|
func (this *HTTPWriter) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (this *HTTPWriter) TsigStatus() error {
|
|
return nil
|
|
}
|
|
|
|
func (this *HTTPWriter) TsigTimersOnly(timersOnly bool) {
|
|
}
|
|
|
|
func (this *HTTPWriter) Hijack() {
|
|
hijacker, ok := this.rawWriter.(http.Hijacker)
|
|
if ok {
|
|
_, _, _ = hijacker.Hijack()
|
|
}
|
|
}
|
|
|
|
func (this *HTTPWriter) encodeMsg(msg *dns.Msg) ([]byte, error) {
|
|
if this.contentType == "application/x-javascript" || this.contentType == "application/json" {
|
|
var result = map[string]any{
|
|
"Status": 0,
|
|
"TC": msg.Truncated,
|
|
"RD": msg.RecursionDesired,
|
|
"RA": msg.RecursionAvailable,
|
|
"AD": msg.AuthenticatedData,
|
|
"CD": msg.CheckingDisabled,
|
|
}
|
|
|
|
// questions
|
|
var questionMaps = []map[string]any{}
|
|
for _, question := range msg.Question {
|
|
questionMaps = append(questionMaps, map[string]any{
|
|
"name": question.Name,
|
|
"type": question.Qtype,
|
|
})
|
|
}
|
|
result["Question"] = questionMaps
|
|
|
|
// answers
|
|
var answerMaps = []map[string]any{}
|
|
for _, answer := range msg.Answer {
|
|
var answerMap = map[string]any{
|
|
"name": answer.Header().Name,
|
|
"type": answer.Header().Rrtype,
|
|
"TTL": answer.Header().Ttl,
|
|
}
|
|
|
|
switch x := answer.(type) {
|
|
case *dns.A:
|
|
answerMap["data"] = x.A.String()
|
|
case *dns.AAAA:
|
|
answerMap["data"] = x.AAAA.String()
|
|
case *dns.CNAME:
|
|
answerMap["data"] = x.Target
|
|
case *dns.TXT:
|
|
answerMap["data"] = x.Txt
|
|
case *dns.NS:
|
|
answerMap["data"] = x.Ns
|
|
case *dns.MX:
|
|
answerMap["data"] = x.Mx
|
|
answerMap["preference"] = x.Preference
|
|
default:
|
|
var answerValue = reflect.ValueOf(answer).Elem()
|
|
var answerType = answerValue.Type()
|
|
|
|
var countFields = answerType.NumField()
|
|
for i := 0; i < countFields; i++ {
|
|
var fieldName = answerType.Field(i).Name
|
|
var fieldValue = answerValue.FieldByName(fieldName)
|
|
if !fieldValue.IsValid() {
|
|
continue
|
|
}
|
|
var fieldInterface = fieldValue.Interface()
|
|
if fieldInterface == nil {
|
|
continue
|
|
}
|
|
|
|
_, ok := fieldInterface.(dns.RR_Header)
|
|
if ok {
|
|
continue
|
|
}
|
|
|
|
if countFields == 2 {
|
|
answerMap["data"] = fieldValue.Interface()
|
|
} else {
|
|
answerMap[fieldName] = fieldValue.Interface()
|
|
}
|
|
}
|
|
}
|
|
|
|
answerMaps = append(answerMaps, answerMap)
|
|
}
|
|
result["Answer"] = answerMaps
|
|
|
|
if Tea.IsTesting() {
|
|
return json.MarshalIndent(result, "", " ")
|
|
} else {
|
|
return json.Marshal(result)
|
|
}
|
|
} else {
|
|
return msg.Pack()
|
|
}
|
|
}
|