Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

使用DOH方式查询DNS,并且DOH服务器经过代理,当有并发DNS查询请求时,第一个DNS查询请求返回结果后,其余DNS查询请求均会失败。 #152

Closed
badO1a5A90 opened this issue Aug 31, 2020 · 11 comments
Labels

Comments

@badO1a5A90
Copy link
Contributor

badO1a5A90 commented Aug 31, 2020

  1. 你正在使用哪个版本的 V2Ray?
    4.27.4

  2. 你看到的不正常的现象是什么?(请描述具体现象,比如访问超时,TLS 证书错误等)
    使用DOH方式查询DNS,并且DOH服务器经过代理,当有并发DNS查询请求时,第一个DNS查询请求返回结果后,其余DNS查询请求均会失败。

测试了以下方式不会有此错误:
使用DOH方式查询DNS,并且DOH服务器经过代理,无并发DNS查询请求
使用DOH方式查询DNS,DOH服务器直连模式(https+local:)
使用UDP方式查询DNS

  1. 你期待看到的正确表现是怎样的?
    使用DOH方式查询DNS,并且DOH服务器经过代理,当有并发DNS查询请求时,所有DNS查询请求均能成功完成。

  2. 请附上你的配置(提交 Issue 前请隐藏服务器端IP地址)。

略去了一些无关配置,
此配置进入dns-in的dns查询请求会经过路由dns-out,然后使用内置dns服务器查询dns,
内置DNS服务器是https://1.1.1.1/dns-query 非直连模式(直连模式不会有错误),目前非直连DOH服务器无视路由规则,直接走第一个outbound到服务器端,由服务端查询dns请求并返回结果(服务器为正常翻墙配置)

客户端配置:

  "dns": {
    "servers": [
      {
        "address": "https://1.1.1.1/dns-query",
        "port": 53
      }
    ]
  },
  "inbounds": [
    {
      "port": 53,
      "listen": "0.0.0.0",
      "tag": "dns-in",
      "protocol": "dokodemo-door",
      "settings": {
        "network": "tcp,udp",
        "address": "8.8.8.8",
        "port": 53,
        "timeout": 300,
        "followRedirect": false,
        "userLevel": 0
      }
    }
  ],
  "outbounds": [
    {
      "protocol": "vless",
      "settings": {
        "vnext": [
          {
            "address": "代理IP",
            "port": 443,
            "users": [
              {
                "id": "UUID",
                "alterId": 0,
                "encryption": "none",
              }
            ]
          }
        ]
      },
      "tag": "proxy",
      "streamSettings": {
        "network": "tcp",
        "security": "tls",
        "tlsSettings": {
          "serverName": "xxx.com",
          "allowInsecure": false
        }
      },
      "mux": {
        "enabled": false,
        "concurrency": -1
      }
    }
  ],
  "rules": [
      {
        "type": "field",
        "inboundTag": [
          "dns-in"
        ],
        "outboundTag": "dns-out"
      }
    ]
  1. 请附上出错时软件输出的错误日志。在 Linux 中,日志通常在 /var/log/v2ray/error.log 文件中。

(无并发时不会有错误)
当有并发dns查询请求时,典型的错误log如下,貌似第一个DNS查询请求完成时,导致了其他未完成的DNS查询请求直接失败。

2020/08/31 17:54:21 [Info] v2ray.com/core/app/dns: DOH//1.1.1.1 querying: github.com.
2020/08/31 17:54:21 [Info] v2ray.com/core/app/dispatcher: taking detour [proxy] for [tcp:1.1.1.1:443]
2020/08/31 17:54:21 [Info] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:代理ip:443
2020/08/31 17:54:21 [Debug] [2537953682] v2ray.com/core/proxy/dokodemo: processing connection from: 127.0.0.1:17580
2020/08/31 17:54:21 [Info] [2537953682] v2ray.com/core/proxy/dokodemo: received request for 127.0.0.1:17580
2020/08/31 17:54:21 [Info] [2537953682] v2ray.com/core/app/dispatcher: taking detour [dns-out] for [udp:8.8.8.8:53]
2020/08/31 17:54:21 [Info] [2537953682] v2ray.com/core/proxy/dns: handling DNS traffic to udp:8.8.8.8:53
2020/08/31 17:54:21 [Info] v2ray.com/core/app/dns: DOH//1.1.1.1 querying: github.com.
2020/08/31 17:54:21 [Info] v2ray.com/core/app/dispatcher: taking detour [proxy] for [tcp:1.1.1.1:443]
2020/08/31 17:54:21 [Info] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:代理ip:443
2020/08/31 17:54:21 [Debug] [701371869] v2ray.com/core/proxy/dokodemo: processing connection from: 127.0.0.1:7662
2020/08/31 17:54:21 [Info] [701371869] v2ray.com/core/proxy/dokodemo: received request for 127.0.0.1:7662
2020/08/31 17:54:21 [Info] [701371869] v2ray.com/core/app/dispatcher: taking detour [dns-out] for [udp:8.8.8.8:53]
2020/08/31 17:54:21 [Info] [701371869] v2ray.com/core/proxy/dns: handling DNS traffic to udp:8.8.8.8:53
2020/08/31 17:54:21 [Info] v2ray.com/core/app/dns: DOH//1.1.1.1 querying: github.githubassets.com.
2020/08/31 17:54:21 [Info] v2ray.com/core/app/dispatcher: taking detour [proxy] for [tcp:1.1.1.1:443]
2020/08/31 17:54:21 [Info] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:代理ip:443
2020/08/31 17:54:21 [Debug] [967603878] v2ray.com/core/proxy/dokodemo: processing connection from: 127.0.0.1:9044
2020/08/31 17:54:21 [Info] [967603878] v2ray.com/core/proxy/dokodemo: received request for 127.0.0.1:9044
2020/08/31 17:54:21 [Info] [967603878] v2ray.com/core/app/dispatcher: taking detour [dns-out] for [udp:8.8.8.8:53]
2020/08/31 17:54:21 [Info] [967603878] v2ray.com/core/proxy/dns: handling DNS traffic to udp:8.8.8.8:53
2020/08/31 17:54:21 [Info] v2ray.com/core/app/dns: DOH//1.1.1.1 querying: github.githubassets.com.
2020/08/31 17:54:21 [Info] v2ray.com/core/app/dispatcher: taking detour [proxy] for [tcp:1.1.1.1:443]
2020/08/31 17:54:21 [Info] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:代理ip:443
2020/08/31 17:54:21 [Info] v2ray.com/core/proxy/vless/outbound: tunneling request to tcp:1.1.1.1:443 via tcp:代理ip:443
2020/08/31 17:54:21 [Info] v2ray.com/core/proxy/vless/outbound: tunneling request to tcp:1.1.1.1:443 via tcp:代理ip:443
2020/08/31 17:54:21 [Info] v2ray.com/core/proxy/vless/outbound: tunneling request to tcp:1.1.1.1:443 via tcp:代理ip:443
2020/08/31 17:54:21 [Info] v2ray.com/core/proxy/vless/outbound: tunneling request to tcp:1.1.1.1:443 via tcp:代理ip:443
2020/08/31 17:54:21 [Info] v2ray.com/core/app/dns: DOH//1.1.1.1 got answer: github.githubassets.com. TypeA -> [185.199.110.154 185.199.111.154 185.199.109.154 185.199.108.154] 203.073371ms
2020/08/31 17:54:21 [Info] v2ray.com/core/app/proxyman/outbound: failed to process outbound traffic > v2ray.com/core/proxy/vless/outbound: connection ends > context canceled
2020/08/31 17:54:21 [Info] v2ray.com/core/app/dns: DOH//1.1.1.1 got answer: github.githubassets.com. TypeAAAA -> [] 203.179089ms
2020/08/31 17:54:21 [Info] v2ray.com/core/app/proxyman/outbound: failed to process outbound traffic > v2ray.com/core/proxy/vless/outbound: connection ends > context canceled
2020/08/31 17:54:21 [Info] v2ray.com/core/app/dns: failed to lookup ip for domain github.githubassets.com at server DOH//1.1.1.1 > empty response
2020/08/31 17:54:21 [Error] v2ray.com/core/app/dns: failed to retrieve response for github.com. > Post "https://1.1.1.1/dns-query": io: read/write on closed pipe
2020/08/31 17:54:21 [Error] v2ray.com/core/app/dns: failed to retrieve response for github.com. > Post "https://1.1.1.1/dns-query": io: read/write on closed pipe
2020/08/31 17:54:21 [Info] v2ray.com/core/app/proxyman/outbound: failed to process outbound traffic > v2ray.com/core/proxy/vless/outbound: connection ends > context canceled
2020/08/31 17:54:21 [Info] v2ray.com/core/app/proxyman/outbound: failed to process outbound traffic > v2ray.com/core/proxy/vless/outbound: connection ends > context canceled

@Loyalsoldier
Copy link
Contributor

试一试 VMess 看看会不会有一样的问题?

@badO1a5A90
Copy link
Contributor Author

试一试 VMess 看看会不会有一样的问题?

忘记补充了,已经试过,一样的

@badO1a5A90
Copy link
Contributor Author

badO1a5A90 commented Aug 31, 2020

试一试 VMess 看看会不会有一样的问题?

v2ray-core/app/dns/dohdns.go 的 282 行 resp, err := s.httpClient.Do(req.WithContext(ctx))

好像是因为一个DNS请求完成时,Dispatched connection关闭,导致了其他并发请求失败
NewDoHNameServer和NewDoHLNameServer的httpClient不同的http.Transport。

我简单把这行改为了

	tr := &http.Transport{
		IdleConnTimeout:   90 * time.Second,
		ForceAttemptHTTP2: true,
		DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
			dest, err := net.ParseDestination(network + ":" + addr)
			if err != nil {
				return nil, err
			}
			conn, err := internet.DialSystem(ctx, dest, nil)
			if err != nil {
				return nil, err
			}
			return conn, nil
		},
	}

	hc := &http.Client{
		Timeout:   time.Second * 180,
		Transport: tr,
	}

	resp, err := hc.Do(req.WithContext(ctx))

这样问题是可以解决的。
但是这样的改法应该是不OK的。

@badO1a5A90
Copy link
Contributor Author

badO1a5A90 commented Aug 31, 2020

试一试 VMess 看看会不会有一样的问题?

v2ray-core/app/dns/dohdns.go 的 282 行 resp, err := s.httpClient.Do(req.WithContext(ctx))

好像是因为一个DNS请求完成时,Dispatched connection关闭,导致了其他并发请求失败
NewDoHNameServer和NewDoHLNameServer的httpClient不同的http.Transport。

我简单把这行改为了

	tr := &http.Transport{
		IdleConnTimeout:   90 * time.Second,
		ForceAttemptHTTP2: true,
		DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
			dest, err := net.ParseDestination(network + ":" + addr)
			if err != nil {
				return nil, err
			}
			conn, err := internet.DialSystem(ctx, dest, nil)
			if err != nil {
				return nil, err
			}
			return conn, nil
		},
	}

	hc := &http.Client{
		Timeout:   time.Second * 180,
		Transport: tr,
	}

	resp, err := hc.Do(req.WithContext(ctx))

这样问题是可以解决的。
但是这样的改法应该是不OK的。

我傻了,这样就是直连没过代理所以没问题了。。。

@badO1a5A90
Copy link
Contributor Author

badO1a5A90 commented Aug 31, 2020

原因似乎就是因为一个DNS请求完成时,Dispatched connection关闭,导致了其他并发请求失败了?

重新改了一下
给DoHNameServer加了个dispatcher routing.Dispatcher

type DoHNameServer struct {
	dispatcher routing.Dispatcher
	sync.RWMutex
	ips        map[string]record
	pub        *pubsub.Service
	cleanup    *task.Periodic
	reqID      uint32
	clientIP   net.IP
	httpClient *http.Client
	dohURL     string
	name       string
}

func NewDoHNameServer 中增加了一行

s.dispatcher = dispatcher

282 行 resp, err := s.httpClient.Do(req.WithContext(ctx)) 改成了下方代码

即判断dispatcher是否为空,不为空则是DOH而不是DOHL
DOHL还是原来的处理方式
如果是DOH的话,每个request都是独立的httpClient,就不会因为一个请求完成而中断了connection导致其他的请求失败了。

	hc := s.httpClient

	if s.dispatcher != nil {
		tr := &http.Transport{
			MaxIdleConns:        30,
			IdleConnTimeout:     90 * time.Second,
			TLSHandshakeTimeout: 30 * time.Second,
			ForceAttemptHTTP2:   true,
			DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
				dest, err := net.ParseDestination(network + ":" + addr)
				if err != nil {
					return nil, err
				}

				link, err := s.dispatcher.Dispatch(ctx, dest)
				if err != nil {
					return nil, err
				}
				return net.NewConnection(
					net.ConnectionInputMulti(link.Writer),
					net.ConnectionOutputMulti(link.Reader),
				), nil
			},
		}
		hc = &http.Client{
			Timeout:   time.Second * 180,
			Transport: tr,
		}
	}

	resp, err := hc.Do(req.WithContext(ctx))

这样能解决问题。
但是这样处理看起来似乎有点笨拙。。。

@Loyalsoldier
Copy link
Contributor

ping @vcptr

@darhwa
Copy link
Contributor

darhwa commented Sep 1, 2020

dnsCtx = session.ContextWithMuxPrefered(dnsCtx, true)

你把这行删掉试试还有没有问题?如果没有的话我就提交补丁

@badO1a5A90
Copy link
Contributor Author

dnsCtx = session.ContextWithMuxPrefered(dnsCtx, true)

你把这行删掉试试还有没有问题?如果没有的话我就提交补丁

不行,依然是一堆io: read/write on closed pipe

@bbi8
Copy link

bbi8 commented Sep 5, 2020

有客户端1使用远程doh,客户端2不使用远程dns
在客户端1dns解析失败的时候服务端可以看到日志127.0.0.1:60352 accepted tcp:dns.google:443
此时用客户端2下的浏览器访问https://dns.google/dns-query 也是无法打开的,但是访问其他网站没有毛病,服务端日志正常
再同时vps上运行curl https://dns.google/dns-query 也没有问题
感觉问题可能出在服务端而未必是客户端

@bbi8
Copy link

bbi8 commented Sep 5, 2020

测试了段时间,可能跟Freedom的"domainStrategy": "UseIP"参数有关,vps上ipv6只是局域网没有公网出口,现在改成UseIPv4,有问题再说

@github-actions
Copy link
Contributor

github-actions bot commented Feb 7, 2021

This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants