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

Questions about IPIfNonMatch and IPOnDemand #770

Closed
Vigilans opened this issue Jul 5, 2020 · 13 comments
Closed

Questions about IPIfNonMatch and IPOnDemand #770

Vigilans opened this issue Jul 5, 2020 · 13 comments
Labels

Comments

@Vigilans
Copy link

Vigilans commented Jul 5, 2020

在网上查了很多这两者的区别,感觉缺少一些关键性细节。去看了一下源码,根据IPIfNonMatchIPOnDemand策略实现

func (r *Router) pickRouteInternal(ctx context.Context) (*Rule, error) {
	sessionContext := &Context{
		Inbound:  session.InboundFromContext(ctx),
		Outbound: session.OutboundFromContext(ctx),
		Content:  session.ContentFromContext(ctx),
	}

	if r.domainStrategy == Config_IpOnDemand {
		sessionContext.dnsClient = r.dns
	}

	for _, rule := range r.rules {
		if rule.Apply(sessionContext) {
			return rule, nil
		}
	}

	if r.domainStrategy != Config_IpIfNonMatch || !isDomainOutbound(sessionContext.Outbound) {
		return nil, common.ErrNoClue
	}

	sessionContext.dnsClient = r.dns

	// Try applying rules again if we have IPs.
	for _, rule := range r.rules {
		if rule.Apply(sessionContext) {
			return rule, nil
		}
	}

	return nil, common.ErrNoClue
}

如果我没有理解错的话,SessionContext只有设置了内置DNS,在rule.Apply(sessionContext)时才有能力获取IP,从而能验证带IP条件的规则。准确的流程是这样的:

  1. 如果是IPOnDemand,则一开始就设置好DNS,然后应用所有规则;
  2. 如果是IPIfNonMatch,则先应用每一条规则(不管这个规则有没有domain字段),然后设置DNS ,再次应用每一条规则。

这样的话,一种广泛流传的V2ray配置版本(例子):

  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      ...,
      {
        "type": "field",
        "outboundTag": "Proxy",
        "network": "tcp,udp"
      }
    ]
  }

这种配置有一个“兜底”规则,可以接受所有链接。这种情况下使用IPIfNonMatch的话,所有没匹配到domain规则的都会因为成功匹配这条规则,而根本没有后续的设置DNS——再次匹配过程,也即既没有匹配任何IP规则,也没有任何的DNS查询,和AsIs没有区别。

这个情况在我这边得到验证过。在接上v2ray试下载腾讯会议时,所使用的CDN网址(730c748b4701068c9abe2cba635b8b8d.dlied1.cdntips.com)没有匹配任何域名规则,如果使用IPIfNonMatch就会从Proxy下载,而使用IPOnDemand就会直连下载。

所以,如果这个情况属实,不能使用IPIfNonMatch减少DNS查询次数提高性能,只能用IPOnDemand的话,那么想要提高性能,是否最好将所有domain相关规则提到所有IP相关规则前面?

另外,如果属实的话,是否值得在一些手册里提及(想了一下优化貌似很难……),以免其他流传的配置对他人产生误解?比如其他手册中有这样的说法:

简而言之,根据某开发者的暴论发言:

AsIs:快速解析,不精确分流;
IPIfNonMatch:解析稍慢,分流精确;
IPOnDemand:没卵用。
笔者注:IPOnDemand 理应是最慢但最精确的,但大多数情况下,IPIfNonMatch 已经足够,因此 IPOnDemand 的实际效果并不明显。

你可以根据你的实际需求,选择相应的域名策略。通常来说,IPIfNonMatch 是大多数情况下的理想选择。
@qqqaadd
Copy link

qqqaadd commented Jul 5, 2020

我是这样兜底的

//放在路由最后
{
    "type": "field",
    "outboundTag": "giaOut",
    "ip": ["0.0.0.0/0"]
}

减少dns查询的话,我的方法是每次启动前都会读取上一次运行的acce.log,其中有这样

2020/07/04 04:18:17 tcp:127.0.0.1:41298 accepted tcp:api.vc.bilibili.com:443 [drOut] 
2020/07/04 04:18:43 tcp:127.0.0.1:41313 accepted tcp:google.com:443 [giaOut] 

通过自动提取giaOut行中的域名字段,合并到之前的域名文件中,重新生成dat文件,再启动v2ray。

//结合一下这个路由
{
    "type": "field",
    "outboundTag": "giaOut",
    "domain": [
        "ext:proxy.dat:giaOut"
    ]
}

这样之前代理过的网站就不会再查询dns了呢。相当个人定制个域名库
你可以参考一下

@qqqaadd
Copy link

qqqaadd commented Jul 5, 2020

补充,对于一些奇奇怪怪的域名,无论如何都无法匹配时(使用下述配置,dns 查询失败)

//好像由于没点,所以3条dns规则都过不去,不过最后由freedom路由从而绕过内置了吧(?)
2020/07/06 06:09:56 [Warning] v2ray.com/core/app/router: resolve ip for aaa > v2ray.com/core/app/dns: invalid domain name
2020/07/06 06:09:56 [Warning] v2ray.com/core/app/router: resolve ip for aaa > v2ray.com/core/app/dns: invalid domain name
2020/07/06 06:09:56 [Warning] v2ray.com/core/app/router: resolve ip for aaa > v2ray.com/core/app/dns: invalid domain name
2020/07/06 06:09:56 [Info] [1899280274] v2ray.com/core/app/dispatcher: default route for tcp:aaa:80
2020/07/06 06:09:56 [Info] [1899280274] v2ray.com/core/proxy/freedom: opening connection to tcp:aaa:80
2020/07/06 06:09:56 [Info] [1899280274] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:aaa:80
2020/07/06 06:09:56 [Info] [1899280274] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:aaa:80
2020/07/06 06:09:56 [Info] [1899280274] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:aaa:80
2020/07/06 06:09:56 [Info] [1899280274] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:aaa:80
2020/07/06 06:09:57 [Info] [1899280274] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:aaa:80
2020/07/06 06:09:57 [Info] [1899280274] v2ray.com/core/app/proxyman/outbound: failed to process outbound traffic > v2ray.com/core/proxy/freedom: failed to open connection to tcp:aaa:80 > v2ray.com/core/common/retry: [dial tcp: lookup aaa on 192.168.50.1:53: no such host] > v2ray.com/core/common/retry: all retry attempts failed

默认走第一个出口(出口被设为freedom直连,直接向此域名发出连接)

//不过好像也可以通过路由的域名判断进行匹配(路由判断条件里没有ip判断->在路由环节就不会查询dns吧)
2020/07/06 07:41:42 [Info] [3299587105] v2ray.com/core/proxy/socks: TCP Connect request to tcp:cn:80
2020/07/06 07:41:42 [Info] [675194820] v2ray.com/core/app/dispatcher: taking detour [drOut] for [tcp:cn:80]
2020/07/06 07:41:42 [Info] [675194820] v2ray.com/core/proxy/freedom: opening connection to tcp:cn:80
2020/07/06 07:41:43 [Info] [675194820] v2ray.com/core/app/proxyman/outbound: failed to process outbound traffic > v2ray.com/core/proxy/freedom: failed to open connection to tcp:cn:80 > v2ray.com/core/common/retry: [dial tcp: lookup cn on 192.168.50.1:53: no such host] > v2ray.com/core/common/retry: all retry attempts failed

ps 不过无点域应该怎么响应 https://features.icann.org/dotless-domains

顺便,关于第一次访问某域名时,如何区分是国内还是国外的域名

"servers": [
    {
        "address": "192.168.50.1",
        "expectIPs": ["geoip:cn"]
        //使用默认dns
        //若解析出国外ip,则继续解析,避免dns污染
    },{
        "address": "https://cloudflare-dns.com/dns-query"
        //解析出国外ip,走兜底路由
    },
    "localhost"
    //使用系统dns
    //无论如何都解析不出ip,走第一个出口
]
//cloudflare-dns.com域名在路由里设置走代理

有时国内dns解析域名会超时错误,默认会进行下一条dns规则解析(对于上述规则,或许会导致一些国内域名解析到国外ip去)。为了解决,对v2ray的dns进行了修改,出现超时错误时返回0.0.0.0。0.0.0.0被路由到直连(此规则不位于最后),直连出口domainStrategy为默认AsIs(不使用内置dns进行域名解析),直接向此域名发出连接

@qqqaadd
Copy link

qqqaadd commented Jul 5, 2020

感觉IPIfNonMatch目的是简化路由书写要求而不是减少dns查询

@Vigilans
Copy link
Author

Vigilans commented Jul 6, 2020

@qqqaadd

//放在路由最后
{
    "type": "field",
    "outboundTag": "giaOut",
    "ip": ["0.0.0.0/0"]
}

感觉十分巧妙,学到了……

减少dns查询的话,我的方法是每次启动前都会读取上一次运行的acce.log,其中有这样

2020/07/04 04:18:17 tcp:127.0.0.1:41298 accepted tcp:api.vc.bilibili.com:443 [drOut] 
2020/07/04 04:18:43 tcp:127.0.0.1:41313 accepted tcp:google.com:443 [giaOut] 

通过自动提取giaOut行中的域名字段,合并到之前的域名文件中,重新生成dat文件,再启动v2ray。

这样之前代理过的网站就不会再查询dns了呢。相当个人定制个域名库
你可以参考一下

我有打算在每次启动时自动拉取一些公开维护的dat的更新,这个定制dat的工作正好十分契合,学到了……

顺便,关于第一次访问某域名时,如何区分是国内还是国外的域名

"servers": [
    {
        "address": "192.168.50.1",
        "expectIPs": ["geoip:cn"]
        //使用默认dns
        //若解析出国外ip,则继续解析,避免dns污染
    },{
        "address": "https://cloudflare-dns.com/dns-query"
        //解析出国外ip,走兜底路由
    },
    "localhost"
    //使用系统dns
    //无论如何都解析不出ip,走第一个出口
]
//cloudflare-dns.com域名在路由里设置走代理

有时国内dns解析域名会超时错误,默认会进行下一条dns规则解析(对于上述规则,或许会导致一些国内域名解析到国外ip去)。为了解决,对v2ray的dns进行了修改,出现超时错误时返回0.0.0.0。0.0.0.0被路由到直连(此规则不位于最后),直连出口domainStrategy为默认AsIs(不使用内置dns进行域名解析),直接向此域名发出连接

我的DNS解析策略是参考的这篇文章,以国外白名单为主,不过这里对expectIPs的利用感觉很有意思,学到了……

参考了expectIPs后,我的DNS配置大概是这样的:

    "servers": [
      {
        "address": "localhost",
        "expectIPs": [“geoip:private", "geoip:cn"] // localhost可以解析主机名
      },
      {
        "address": "223.5.5.5",
        "expectIPs": ["geoip:cn"]
      },
      "1.1.1.1",
      {
        "address": "114.114.114.114",
        "domains": ["geosite:cn"],
        "expectIPs": ["geoip:cn"]
      },
      {
        "address": "8.8.8.8",
        "domains": ["ext:h2y.dat:gfw", "geosite:geolocation-!cn"]
      },
      { 
        "address": "119.29.29.29", // DNSPod
        "domains": ["ntp.org", "domain:{my vps}"]
      }
    ]

这样看下来,相比于您的方案,我多了三个domain规则,以及一个国内DNS。也许您的设计里没有domain规则是因为route里已经有了,这样可以避免重复,不过由于我在做透明代理,需要开放内置DNS作服务器,因此DNS里还是需要再写一遍_(:з」∠)_

稍微对比了一下,我的规则可以让大多数知名国外网站只用在8.8.8.8做一次DNS,而不知名网站则仿照您的规则跳过两个国内DNS后由1.1.1.1兜底。同时当8.8.8.8的DNS timeout时,也会fallback到1.1.1.1再做一次查询,若失败则再次查询8.8.8.8。国内网站则至少有2-3次保底查询,从而作为您的0.0.0.0方案的替代。

@Vigilans
Copy link
Author

Vigilans commented Jul 6, 2020

补充,对于一些奇奇怪怪的域名,无论如何都无法匹配时(使用下述配置,dns 查询失败)

//好像由于没点,所以3条dns规则都过不去,不过最后由freedom路由从而绕过内置了吧(?)
2020/07/06 06:09:56 [Warning] v2ray.com/core/app/router: resolve ip for aaa > v2ray.com/core/app/dns: invalid domain name
2020/07/06 06:09:56 [Warning] v2ray.com/core/app/router: resolve ip for aaa > v2ray.com/core/app/dns: invalid domain name
2020/07/06 06:09:56 [Warning] v2ray.com/core/app/router: resolve ip for aaa > v2ray.com/core/app/dns: invalid domain name
2020/07/06 06:09:56 [Info] [1899280274] v2ray.com/core/app/dispatcher: default route for tcp:aaa:80
2020/07/06 06:09:56 [Info] [1899280274] v2ray.com/core/proxy/freedom: opening connection to tcp:aaa:80
2020/07/06 06:09:56 [Info] [1899280274] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:aaa:80
2020/07/06 06:09:56 [Info] [1899280274] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:aaa:80
2020/07/06 06:09:56 [Info] [1899280274] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:aaa:80
2020/07/06 06:09:56 [Info] [1899280274] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:aaa:80
2020/07/06 06:09:57 [Info] [1899280274] v2ray.com/core/transport/internet/tcp: dialing TCP to tcp:aaa:80
2020/07/06 06:09:57 [Info] [1899280274] v2ray.com/core/app/proxyman/outbound: failed to process outbound traffic > v2ray.com/core/proxy/freedom: failed to open connection to tcp:aaa:80 > v2ray.com/core/common/retry: [dial tcp: lookup aaa on 192.168.50.1:53: no such host] > v2ray.com/core/common/retry: all retry attempts failed

默认走第一个出口(出口被设为freedom直连,直接向此域名发出连接)

//不过好像也可以通过路由的域名判断进行匹配(路由判断条件里没有ip判断->在路由环节就不会查询dns吧)
2020/07/06 07:41:42 [Info] [3299587105] v2ray.com/core/proxy/socks: TCP Connect request to tcp:cn:80
2020/07/06 07:41:42 [Info] [675194820] v2ray.com/core/app/dispatcher: taking detour [drOut] for [tcp:cn:80]
2020/07/06 07:41:42 [Info] [675194820] v2ray.com/core/proxy/freedom: opening connection to tcp:cn:80
2020/07/06 07:41:43 [Info] [675194820] v2ray.com/core/app/proxyman/outbound: failed to process outbound traffic > v2ray.com/core/proxy/freedom: failed to open connection to tcp:cn:80 > v2ray.com/core/common/retry: [dial tcp: lookup cn on 192.168.50.1:53: no such host] > v2ray.com/core/common/retry: all retry attempts failed

ps 不过无点域应该怎么响应 https://features.icann.org/dotless-domains

这个问题我同样遇到了,请参考这个issue:v2ray/v2ray-core#2618

直接原因是V2Ray DNS从最开始就拒绝了不带点的域名:

 if !strings.Contains(domain, ".") { 
 	return nil, newError("invalid domain name").AtWarning() 
 } 

这个问题是我在透明代理后运行sudo时遇到的。sudo会查询当前主机名的DNS(一个典型的无点域),通过iptables转发到v2ray dns,从而被拒绝,造成了很大的卡顿。由于所有DNS请求都会走v2ray dns,而DNS请求里的域名没法通过v2ray routing防范,拒绝无点域带来的问题还是挺大的……

感觉IPIfNonMatch目的是简化路由书写要求而不是减少dns查询

看了一下自己的配置,把所有域名提到前面貌似没有明显的影响,或许用IPOnDemand就可以了……不过ip: 0.0.0.0/0这样的兜底方式着实值得参考。

@IceCodeNew
Copy link

我不太理解为什么你们会在路由配置里面放一个兜底规则的需要。在我的理解看来没有被路由规则匹配的请求会走 outbounds 数组里首选的出口(也就是第一个出口),所以只要把想要用来兜底的那个出口调整到最前就好了。

Refer: https://www.v2fly.org/config/multiple_config.html#%E8%A7%84%E5%88%99%E8%AF%B4%E6%98%8E

在 json 配置中的inbounds和outbounds是数组结构,他们有特殊的规则:

当配置中的数组元素有 2 或以上,覆盖前者的 inbounds/oubounds;
当配置中的数组元素只有 1 个时,查找原有tag相同的元素进行覆盖;若无法找到:
对于 inbounds,添加至最后(inbounds 内元素顺序无关)
对于 outbounds,添加至最前(outbounds 默认首选出口);但如果文件名含有 tail(大小写均可),添加至最后。

@Vigilans
Copy link
Author

@IceCodeNew 需求不太一样,我的兜底规则是一个BalancerTag,没法用outbound的这个隐式规则。

@fbion

This comment has been minimized.

@qqqaadd
Copy link

qqqaadd commented Aug 15, 2020

当有多个InboundObject,且不同InboundObject需要的默认出口不同时,要对每个InboundObject设置对应不同的兜底出口。

@Vigilans
Copy link
Author

@qqqaadd 还可以补充一个,tcp默认走proxy,udp默认直连,这种时候也需要设置不同的兜底出口_(:з」∠)_

话说,这些需求我计划通过改进Balancer来实现……已经有一些感觉可行的想法了

@Loyalsoldier
Copy link

这样的话,一种广泛流传的V2ray配置版本(例子):

  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      ...,
      {
        "type": "field",
        "outboundTag": "Proxy",
        "network": "tcp,udp"
      }
    ]
  }

这种配置有一个“兜底”规则,可以接受所有链接。这种情况下使用IPIfNonMatch的话,所有没匹配到domain规则的都会因为成功匹配这条规则,而根本没有后续的设置DNS——再次匹配过程,也即既没有匹配任何IP规则,也没有任何的DNS查询,和AsIs没有区别。

怪不得这样配置之后,DOH https://223.5.5.5/dns-query 会走代理,而必须使用 https+local://223.5.5.5/dns-query 才能直连。

@Robot-DaneelOlivaw
Copy link

我的做法是选择“没什么用”的IPOnDemand。在优先级较高处放置domain相关规则,节省DNS解析次数;其次是domain和IP皆相关的规则,如果domain不满足并不会进行DNS解析;最后才是IP相关规则,以保证会进行DNS解析,并根据IP选择对应出站方式。

怪不得这样配置之后,DOH https://223.5.5.5/dns-query 会走代理,而必须使用 https+local://223.5.5.5/dns-query 才能直连。

v2ray/v2ray-core#2305 所述,DoH选择何种连接方式应该与路由规则无关,而仅与是否+local://有关。

@github-actions
Copy link

This issue is stale because it has been open 90 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

6 participants