package option import ( "encoding/base64" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json/badoption" "github.com/miekg/dns" ) type PredefinedDNSServerOptions struct { Responses []DNSResponseOptions `json:"responses,omitempty"` } type DNSResponseOptions struct { Query badoption.Listable[string] `json:"query,omitempty"` QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"` RCode *DNSRCode `json:"rcode,omitempty"` Answer badoption.Listable[DNSRecordOptions] `json:"answer,omitempty"` Ns badoption.Listable[DNSRecordOptions] `json:"ns,omitempty"` Extra badoption.Listable[DNSRecordOptions] `json:"extra,omitempty"` } type DNSRCode int func (r DNSRCode) MarshalJSON() ([]byte, error) { rCodeValue, loaded := dns.RcodeToString[int(r)] if loaded { return json.Marshal(rCodeValue) } return json.Marshal(int(r)) } func (r *DNSRCode) UnmarshalJSON(bytes []byte) error { var intValue int err := json.Unmarshal(bytes, &intValue) if err == nil { *r = DNSRCode(intValue) return nil } var stringValue string err = json.Unmarshal(bytes, &stringValue) if err != nil { return err } rCodeValue, loaded := dns.StringToRcode[stringValue] if !loaded { return E.New("unknown rcode: " + stringValue) } *r = DNSRCode(rCodeValue) return nil } func (r *DNSRCode) Build() int { if r == nil { return dns.RcodeSuccess } return int(*r) } func (o DNSResponseOptions) Build() ([]dns.Question, *dns.Msg, error) { var questions []dns.Question if len(o.Query) == 0 && len(o.QueryType) == 0 { questions = []dns.Question{{Qclass: dns.ClassINET}} } else if len(o.Query) == 0 { for _, queryType := range o.QueryType { questions = append(questions, dns.Question{ Qtype: uint16(queryType), Qclass: dns.ClassINET, }) } } else if len(o.QueryType) == 0 { for _, domain := range o.Query { questions = append(questions, dns.Question{ Name: dns.Fqdn(domain), Qclass: dns.ClassINET, }) } } else { for _, queryType := range o.QueryType { for _, domain := range o.Query { questions = append(questions, dns.Question{ Name: dns.Fqdn(domain), Qtype: uint16(queryType), Qclass: dns.ClassINET, }) } } } return questions, &dns.Msg{ MsgHdr: dns.MsgHdr{ Response: true, Rcode: o.RCode.Build(), }, Answer: common.Map(o.Answer, DNSRecordOptions.build), Ns: common.Map(o.Ns, DNSRecordOptions.build), Extra: common.Map(o.Extra, DNSRecordOptions.build), }, nil } type DNSRecordOptions struct { dns.RR fromBase64 bool } func (o DNSRecordOptions) MarshalJSON() ([]byte, error) { if o.fromBase64 { buffer := buf.Get(dns.Len(o.RR)) defer buf.Put(buffer) offset, err := dns.PackRR(o.RR, buffer, 0, nil, false) if err != nil { return nil, err } return json.Marshal(base64.StdEncoding.EncodeToString(buffer[:offset])) } return json.Marshal(o.RR.String()) } func (o *DNSRecordOptions) UnmarshalJSON(data []byte) error { var stringValue string err := json.Unmarshal(data, &stringValue) if err != nil { return err } binary, err := base64.StdEncoding.DecodeString(stringValue) if err == nil { return o.unmarshalBase64(binary) } record, err := dns.NewRR(stringValue) if err != nil { return err } o.RR = record return nil } func (o *DNSRecordOptions) unmarshalBase64(binary []byte) error { record, _, err := dns.UnpackRR(binary, 0) if err != nil { return E.New("parse binary DNS record") } o.RR = record o.fromBase64 = true return nil } func (o DNSRecordOptions) build() dns.RR { return o.RR }