1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-11-02 09:17:55 -04:00
v2fly/app/dns/nameserver_udp.go
yuhan6665 afb8385a7e
Feat: routing and freedom outbound ignore Fake DNS (#696)
Turn off fake DNS for request sent from Routing and Freedom outbound.
Fake DNS now only apply to DNS outbound.
This is important for Android, where VPN service take over all system DNS
traffic and pass it to core.  "UseIp" option can be used in Freedom outbound
to avoid getting fake IP and fail connection.

Co-authored-by: loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com>
2021-02-23 10:17:20 +08:00

295 lines
6.7 KiB
Go

// +build !confonly
package dns
import (
"context"
"strings"
"sync"
"sync/atomic"
"time"
"golang.org/x/net/dns/dnsmessage"
"github.com/v2fly/v2ray-core/v4/common"
"github.com/v2fly/v2ray-core/v4/common/net"
"github.com/v2fly/v2ray-core/v4/common/protocol/dns"
udp_proto "github.com/v2fly/v2ray-core/v4/common/protocol/udp"
"github.com/v2fly/v2ray-core/v4/common/session"
"github.com/v2fly/v2ray-core/v4/common/signal/pubsub"
"github.com/v2fly/v2ray-core/v4/common/task"
dns_feature "github.com/v2fly/v2ray-core/v4/features/dns"
"github.com/v2fly/v2ray-core/v4/features/routing"
"github.com/v2fly/v2ray-core/v4/transport/internet/udp"
)
// ClassicNameServer implemented traditional UDP DNS.
type ClassicNameServer struct {
sync.RWMutex
name string
address net.Destination
ips map[string]record
requests map[uint16]dnsRequest
pub *pubsub.Service
udpServer *udp.Dispatcher
cleanup *task.Periodic
reqID uint32
}
// NewClassicNameServer creates udp server object for remote resolving.
func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher) *ClassicNameServer {
// default to 53 if unspecific
if address.Port == 0 {
address.Port = net.Port(53)
}
s := &ClassicNameServer{
address: address,
ips: make(map[string]record),
requests: make(map[uint16]dnsRequest),
pub: pubsub.NewService(),
name: strings.ToUpper(address.String()),
}
s.cleanup = &task.Periodic{
Interval: time.Minute,
Execute: s.Cleanup,
}
s.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse)
newError("DNS: created UDP client initialized for ", address.NetAddr()).AtInfo().WriteToLog()
return s
}
// Name implements Server.
func (s *ClassicNameServer) Name() string {
return s.name
}
// Cleanup clears expired items from cache
func (s *ClassicNameServer) Cleanup() error {
now := time.Now()
s.Lock()
defer s.Unlock()
if len(s.ips) == 0 && len(s.requests) == 0 {
return newError(s.name, " nothing to do. stopping...")
}
for domain, record := range s.ips {
if record.A != nil && record.A.Expire.Before(now) {
record.A = nil
}
if record.AAAA != nil && record.AAAA.Expire.Before(now) {
record.AAAA = nil
}
if record.A == nil && record.AAAA == nil {
delete(s.ips, domain)
} else {
s.ips[domain] = record
}
}
if len(s.ips) == 0 {
s.ips = make(map[string]record)
}
for id, req := range s.requests {
if req.expire.Before(now) {
delete(s.requests, id)
}
}
if len(s.requests) == 0 {
s.requests = make(map[uint16]dnsRequest)
}
return nil
}
// HandleResponse handles udp response packet from remote DNS server.
func (s *ClassicNameServer) HandleResponse(ctx context.Context, packet *udp_proto.Packet) {
ipRec, err := parseResponse(packet.Payload.Bytes())
if err != nil {
newError(s.name, " fail to parse responded DNS udp").AtError().WriteToLog()
return
}
s.Lock()
id := ipRec.ReqID
req, ok := s.requests[id]
if ok {
// remove the pending request
delete(s.requests, id)
}
s.Unlock()
if !ok {
newError(s.name, " cannot find the pending request").AtError().WriteToLog()
return
}
var rec record
switch req.reqType {
case dnsmessage.TypeA:
rec.A = ipRec
case dnsmessage.TypeAAAA:
rec.AAAA = ipRec
}
elapsed := time.Since(req.start)
newError(s.name, " got answer: ", req.domain, " ", req.reqType, " -> ", ipRec.IP, " ", elapsed).AtInfo().WriteToLog()
if len(req.domain) > 0 && (rec.A != nil || rec.AAAA != nil) {
s.updateIP(req.domain, rec)
}
}
func (s *ClassicNameServer) updateIP(domain string, newRec record) {
s.Lock()
newError(s.name, " updating IP records for domain:", domain).AtDebug().WriteToLog()
rec := s.ips[domain]
updated := false
if isNewer(rec.A, newRec.A) {
rec.A = newRec.A
updated = true
}
if isNewer(rec.AAAA, newRec.AAAA) {
rec.AAAA = newRec.AAAA
updated = true
}
if updated {
s.ips[domain] = rec
}
if newRec.A != nil {
s.pub.Publish(domain+"4", nil)
}
if newRec.AAAA != nil {
s.pub.Publish(domain+"6", nil)
}
s.Unlock()
common.Must(s.cleanup.Start())
}
func (s *ClassicNameServer) newReqID() uint16 {
return uint16(atomic.AddUint32(&s.reqID, 1))
}
func (s *ClassicNameServer) addPendingRequest(req *dnsRequest) {
s.Lock()
defer s.Unlock()
id := req.msg.ID
req.expire = time.Now().Add(time.Second * 8)
s.requests[id] = *req
}
func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx))
reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
for _, req := range reqs {
s.addPendingRequest(req)
b, _ := dns.PackMessage(req.msg)
udpCtx := context.Background()
if inbound := session.InboundFromContext(ctx); inbound != nil {
udpCtx = session.ContextWithInbound(udpCtx, inbound)
}
udpCtx = session.ContextWithContent(udpCtx, &session.Content{
Protocol: "dns",
})
s.udpServer.Dispatch(udpCtx, s.address, b)
}
}
func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
s.RLock()
record, found := s.ips[domain]
s.RUnlock()
if !found {
return nil, errRecordNotFound
}
var ips []net.Address
var lastErr error
if option.IPv4Enable {
a, err := record.A.getIPs()
if err != nil {
lastErr = err
}
ips = append(ips, a...)
}
if option.IPv6Enable {
aaaa, err := record.AAAA.getIPs()
if err != nil {
lastErr = err
}
ips = append(ips, aaaa...)
}
if len(ips) > 0 {
return toNetIP(ips)
}
if lastErr != nil {
return nil, lastErr
}
return nil, dns_feature.ErrEmptyResponse
}
// QueryIP implements Server.
func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) ([]net.IP, error) {
fqdn := Fqdn(domain)
ips, err := s.findIPsForDomain(fqdn, option)
if err != errRecordNotFound {
newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
return ips, err
}
// ipv4 and ipv6 belong to different subscription groups
var sub4, sub6 *pubsub.Subscriber
if option.IPv4Enable {
sub4 = s.pub.Subscribe(fqdn + "4")
defer sub4.Close()
}
if option.IPv6Enable {
sub6 = s.pub.Subscribe(fqdn + "6")
defer sub6.Close()
}
done := make(chan interface{})
go func() {
if sub4 != nil {
select {
case <-sub4.Wait():
case <-ctx.Done():
}
}
if sub6 != nil {
select {
case <-sub6.Wait():
case <-ctx.Done():
}
}
close(done)
}()
s.sendQuery(ctx, fqdn, clientIP, option)
for {
ips, err := s.findIPsForDomain(fqdn, option)
if err != errRecordNotFound {
return ips, err
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-done:
}
}
}