SagerNet / sing-box

The universal proxy platform
https://sing-box.sagernet.org/
Other
18.41k stars 2.21k forks source link

make it possible to assign outbound on dns configuration #1988

Open A-Shahbazi opened 1 month ago

A-Shahbazi commented 1 month ago

Operating system

Linux

System version

ArchLinux

Installation type

Others

If you are using a graphical client, please provide the version of the client.

nekoray 4.0.beta3

Version

1.9.3

Description

The official DNS servers in my country return a known constant ip address for blocked websites. So, I wanted to write a config which assigns the route based on dns server used(or ip address returned by a specific dns server). I was able to seperate dns server used for resolving names but it doesn't seem to be possible to assign route used after resolving the name.

Reproduction

Here's the config mentioned on the above Description where $poisonedDnsServer always returns $blockedIP for blocked websites and $nonPoisonedDnsServer returns correct ip for any website.

dns: {
    "rules": [
        {
            "invert": true,
            "ip_cidr": [
                "$blockedIP"
            ],
            "server": "dns-poisoned"
        },
        {
            "ip_cidr": [
                "$poisonedDnsServer"
            ],
            "server": "dns-block"
        }
    ],
    "servers": [
        {
            "address": "$nonPoisonedDnsServer",
            "detour": "direct",
            "tag": "dns-remote"
        },
        {
            "address": "$poisonedDnsServer",
            "detour": "direct",
            "tag": "dns-poisoned"
        },
        {
            "address": "rcode://success",
            "tag": "dns-block"
        },
        {
            "address": "$poisonedDnsServer",
            "tag": "dns-local"
        }
    ]
}

Logs

No response

Supporter

Integrity requirements

simplerick-simplefun commented 1 month ago

In Chinese proxy community, we use geosite and geoip for dns/routing strategy. You should always try your best to avoid querying known blocked domain name to local Dns Server, as it reveals the fact that you are trying to break GFW (or whatever internet enforcement put in your area). It's called dns leakage.

The more people rely on "dns expected IP" feature, the more dangerous it becomes for the whole proxy community. Developing such feature is not good for the whole scene. Instead, keep a record of blocked domains/IPs and use them for routing/dns strategy like geosite/geoip.

A-Shahbazi commented 1 month ago

In Chinese proxy community, we use geosite and geoip for dns/routing strategy. You should always try your best to avoid querying known blocked domain name to local Dns Server, as it reveals the fact that you are trying to break GFW (or whatever internet enforcement put in your area). It's called dns leakage.

The more people rely on "dns expected IP" feature, the more dangerous it becomes for the whole proxy community. Developing such feature is not good for the whole scene. Instead, keep a record of blocked domains/IPs and use them for routing/dns strategy like geosite/geoip.

I'm not that much familiar with GFW, but it might be the nature of censorship that makes using geoip and geosite useful in that case. Geographic location might mostly define GFW's censorship but in my country that is not useful as the censorship is based on content and sometimes even erratic.

Regarding the DNS leakage, there's the problem of hijacked public dns addresses and high overhead of using solutions like dnscrypt and DOH. So I guess even if we accept that the best approach is to have a database of blocked websites for routing purposes, I think the feature I requested makes building such database easier if not viable at all.

BTW, I don't get why querying known blocked domain names, although obviously not desirable, is such dangerous. The user has to somehow find out a website is blocked; Either through prior knowledge, which again is not what this feature request is about, or through querying the blocked website at least once and caching the result. So this feature seems to be the least dangerous way to handle such cases.

molink36 commented 3 weeks ago

Dear @A-Shahbazi,

Currently to deal with the issue that you have mentioned, I run to instances of sing-box. The first instance sends connections which are destined to "$blockedIP" to an inbound by the second instance of running sing-box. In the second sing-box, the option "sniff_override_destination" is set to "true" in the inbound and DNS queries and all connections are sent to the remote proxy server.

One suggestion to accomplish this without having to run two instances of sing-box is implementing "sniff_override_destination" option (currently implemented in inbounds) in the outbounds.

simplerick-simplefun commented 3 weeks ago

In Chinese proxy community, we use geosite and geoip for dns/routing strategy. You should always try your best to avoid querying known blocked domain name to local Dns Server, as it reveals the fact that you are trying to break GFW (or whatever internet enforcement put in your area). It's called dns leakage. The more people rely on "dns expected IP" feature, the more dangerous it becomes for the whole proxy community. Developing such feature is not good for the whole scene. Instead, keep a record of blocked domains/IPs and use them for routing/dns strategy like geosite/geoip.

I'm not that much familiar with GFW, but it might be the nature of censorship that makes using geoip and geosite useful in that case. Geographic location might mostly define GFW's censorship but in my country that is not useful as the censorship is based on content and sometimes even erratic.

Regarding the DNS leakage, there's the problem of hijacked public dns addresses and high overhead of using solutions like dnscrypt and DOH. So I guess even if we accept that the best approach is to have a database of blocked websites for routing purposes, I think the feature I requested makes building such database easier if not viable at all.

BTW, I don't get why querying known blocked domain names, although obviously not desirable, is such dangerous. The user has to somehow find out a website is blocked; Either through prior knowledge, which again is not what this feature request is about, or through querying the blocked website at least once and caching the result. So this feature seems to be the least dangerous way to handle such cases.

  1. check your info before replying that geosite does not work for you: https://github.com/v2fly/domain-list-community
  2. querying a blocked domain from your local dns provider is called "dns leak". it tells your government that you are bypassing the block/wall.
  3. in route module, proxy all the connection of "known constant ip address for blocked websites" to your proxy server. in you proxy server, use sniff and sniff_override_destination to reset the wrong ip to correct ip.
A-Shahbazi commented 2 weeks ago

Dear @A-Shahbazi,

Currently to deal with the issue that you have mentioned, I run to instances of sing-box. The first instance sends connections which are destined to "$blockedIP" to an inbound by the second instance of running sing-box. In the second sing-box, the option "sniff_override_destination" is set to "true" in the inbound and DNS queries and all connections are sent to the remote proxy server.

One suggestion to accomplish this without having to run two instances of sing-box is implementing "sniff_override_destination" option (currently implemented in inbounds) in the outbounds.

Very nice idea! Actually there is no need to run two instances of sing-box. We can define another inbound and use it as an intermediate proxy. Here's a minimal config which worked for me:

{
    "dns": {
        "disable_cache": true,
        "independent_cache": true,
        "reverse_mapping": true,
        "rules": [
            {
                "query_type": [
                    32,
                    33
                ],
                "server": "dns-block"
            },
            {
                "inbound": [
                    "intermediateInbound"
                ],
                "server": "dns-remote"
            },
            {
                "outbound": "proxy",
                "server": "dns-remote"
            }
        ],
        "servers": [
            {
                "address": "$poisonedDnsServer",
                "detour": "direct",
                "tag": "dns-direct"
            },
            {
                "address": "$nonPoisonedDnsServer",
                "detour": "direct",
                "tag": "dns-remote"
            },
            {
                "address": "rcode://success",
                "tag": "dns-block"
            },
            {
                "address": "rcode://refused",
                "tag": "dns-serverfail"
            },
            {
                "address": "$poisonedDnsServer",
                "detour": "direct",
                "tag": "dns-local"
            }
        ],
        "strategy": "ipv4_only"
    },
    "inbounds": [
        {
            "domain_strategy": "ipv4_only",
            "listen": "127.0.0.1",
            "listen_port": 2080,
            "sniff": true,
            "sniff_override_destination": true,
            "tag": "mixed-in",
            "type": "mixed"
        },
        {
            "listen": "127.0.0.1",
            "listen_port": 2090,
            "sniff": true,
            "sniff_override_destination": true,
            "tag": "intermediateInbound",
            "type": "mixed"
        }
    ],
    "log": {
        "disabled": false,
        "level": "warn",
        "timestamp": false
    },
    "outbounds": [
        {
            "tag": "direct",
            "type": "direct"
        },
        {
            "tag": "bypass",
            "type": "direct"
        },
        {
            "tag": "block",
            "type": "block"
        },
        {
            "tag": "dns-out",
            "type": "dns"
        },
        {
            "server": "$finalProxyIP",
            "server_port": $finalProxyPort,
            "tag": "proxy",
            "type": "socks"
        },
        {
            "server": "127.0.0.1",
            "server_port": 2090,
            "tag": "intermediateOutbound",
            "type": "socks"
        }
    ],
    "route": {
        "auto_detect_interface": false,
        "final": "bypass",
        "geoip": {
            "path": "/usr/share/sing-geoip/geoip.db"
        },
        "geosite": {
            "path": "/usr/share/sing-geosite/geosite.db"
        },
        "rules": [
            {
                "ip_cidr": [
                    "$blockedIP"
                ],
                "outbound": "intermediateOutbound"
            },
            {
                "inbound": [
                    "intermediateInbound"
                ],
                "outbound": "proxy"
            }
        ]
    }
}
A-Shahbazi commented 2 weeks ago
  1. check your info before replying that geosite does not work for you: https://github.com/v2fly/domain-list-community

  2. querying a blocked domain from your local dns provider is called "dns leak". it tells your government that you are bypassing the block/wall.

  3. in route module, proxy all the connection of "known constant ip address for blocked websites" to your proxy server. in you proxy server, use sniff and sniff_override_destination to reset the wrong ip to correct ip.

  1. Been using geosite and geoip databases for at least several months. It hasn't been very useful.
  2. I know what DNS Leakage is. I just disagree on your assessment of its danger for our use case. As I explained on last part of my comment
  3. The problem here, I guess, is that IP address is resolved only once for each inbound. Basically that is after routing unless sniffing and domain_strategy for the inbound are set. But once the address is resolved there's no option to do the resolution a second time. Hence the need for a second inbound/proxy/...
molink36 commented 5 days ago

Dear @A-Shahbazi, Currently to deal with the issue that you have mentioned, I run to instances of sing-box. The first instance sends connections which are destined to "$blockedIP" to an inbound by the second instance of running sing-box. In the second sing-box, the option "sniff_override_destination" is set to "true" in the inbound and DNS queries and all connections are sent to the remote proxy server. One suggestion to accomplish this without having to run two instances of sing-box is implementing "sniff_override_destination" option (currently implemented in inbounds) in the outbounds.

Very nice idea! Actually there is no need to run two instances of sing-box. We can define another inbound and use it as an intermediate proxy. Here's a minimal config which worked for me:

{
    "dns": {
        "disable_cache": true,
        "independent_cache": true,
        "reverse_mapping": true,
        "rules": [
            {
                "query_type": [
                    32,
                    33
                ],
                "server": "dns-block"
            },
            {
                "inbound": [
                    "intermediateInbound"
                ],
                "server": "dns-remote"
            },
            {
                "outbound": "proxy",
                "server": "dns-remote"
            }
        ],
        "servers": [
            {
                "address": "$poisonedDnsServer",
                "detour": "direct",
                "tag": "dns-direct"
            },
            {
                "address": "$nonPoisonedDnsServer",
                "detour": "direct",
                "tag": "dns-remote"
            },
            {
                "address": "rcode://success",
                "tag": "dns-block"
            },
            {
                "address": "rcode://refused",
                "tag": "dns-serverfail"
            },
            {
                "address": "$poisonedDnsServer",
                "detour": "direct",
                "tag": "dns-local"
            }
        ],
        "strategy": "ipv4_only"
    },
    "inbounds": [
        {
            "domain_strategy": "ipv4_only",
            "listen": "127.0.0.1",
            "listen_port": 2080,
            "sniff": true,
            "sniff_override_destination": true,
            "tag": "mixed-in",
            "type": "mixed"
        },
        {
            "listen": "127.0.0.1",
            "listen_port": 2090,
            "sniff": true,
            "sniff_override_destination": true,
            "tag": "intermediateInbound",
            "type": "mixed"
        }
    ],
    "log": {
        "disabled": false,
        "level": "warn",
        "timestamp": false
    },
    "outbounds": [
        {
            "tag": "direct",
            "type": "direct"
        },
        {
            "tag": "bypass",
            "type": "direct"
        },
        {
            "tag": "block",
            "type": "block"
        },
        {
            "tag": "dns-out",
            "type": "dns"
        },
        {
            "server": "$finalProxyIP",
            "server_port": $finalProxyPort,
            "tag": "proxy",
            "type": "socks"
        },
        {
            "server": "127.0.0.1",
            "server_port": 2090,
            "tag": "intermediateOutbound",
            "type": "socks"
        }
    ],
    "route": {
        "auto_detect_interface": false,
        "final": "bypass",
        "geoip": {
            "path": "/usr/share/sing-geoip/geoip.db"
        },
        "geosite": {
            "path": "/usr/share/sing-geosite/geosite.db"
        },
        "rules": [
            {
                "ip_cidr": [
                    "$blockedIP"
                ],
                "outbound": "intermediateOutbound"
            },
            {
                "inbound": [
                    "intermediateInbound"
                ],
                "outbound": "proxy"
            }
        ]
    }
}

Fantastic! This solution achieves the exact desired outcome.