smartdns icon indicating copy to clipboard operation
smartdns copied to clipboard

按客户端分组并设定差异化的DNS解析策略

Open sskaje opened this issue 2 years ago • 9 comments

需求应用场景 路由,多路网络连接,但是会需要根据客户端设定不同的DNS策略,允许特定分组使用特定 DNS 服务器解析特定请求,也需要针对不同设备设定不同的 DNS 劫持规则。

可以起多个smartdns 实例,加载不同配置,但是那就没有讨论的意义了。

此场景不使用 smartdns 的 speed-check,仅仅是用于流量分流。

类型: A 高自由度; B 严控; C 默认但是不暴露网络架构。

例如, 家庭网络:自己的设备 A,子女的设备 B,其他设备 C。

子女设备可能过滤成人、游戏、短视频等域名,也可能不允许使用 VPN。其他设备,例如来客,不暴露家里的 VPN。

公司网段:研发、内网服务器A、访客BC、一般员工BC。

  • 访客可能限定不允许解析内网域名,也可能限定只允许使用运营商dns解析,不暴露网络架构,或兼有;
  • 一般员工允许解析内网域名,也可能限定例如购物、视频平台等的域名,也可能不允许使用公司 VPN 或兼有;
  • 研发或服务器可能不限制,又要允许使用公司 VPN。

本地默认 DNS 使用运营商 DNS。

流量选择: 1 ipset + nat ,将不同请求转给不同的 bind 端口,bind 需要新增参数标记流量 2 新增配置命令,根据客户端 IP 匹配

全局: A B C 都开启国内的白名单,例如主流域名强制使用运营商 DNS 解析,domain-set + nameserver 已解决 A B C 都开启去广告策略。domain-set + address # 已解决。

分组: A 可以开激进策略,全局之后所有交给 VPN 处理,例如可能直接交给 clash 去远程取,或者speed check B 按客户端分组执行域名或列表的策略,其他列表交本地默认DNS,不走VPN; C 全局之后,所有都交给本地默认 DNS

建议的方案

我理想中是类似 nginx 这种风格的配置,相互之间隔离,但是看了源码发现是 getopt,也是个相当好的方案。

实现层面,相当于规则匹配的配置需要增加 ACL。

# 默认 src group = default
bind :xxxx [-src-group 来源组] 

# 假定 % 是 ANY

## 第一种,全 inline
domain-rules /domain/ -allow-src-group xxx -allow-src-group yyy -deny-src-group % 
domain-rules /domain/-disallow-src-group xxx -allow-src-group %

address  /domain/   -allow-src-group xxx -allow-src-group yyy -deny-src-group %
nameserver /domain/   -allow-src-group xxx -allow-src-group yyy -deny-src-group %
...

## 第二种,ACL
acl -name xxxyyy -include-src-group xxx -include-src-group yyy -exclude-src-group %
acl -name xxxyyy_blacklist -include-src-group % -exclude-src-group xxx -exclude-src-group yyy

domain-rules /domain/ -acl xxxyyy
nameserver /domain/ -acl xxxyyy_blacklist 
address
...


设备信息 Linux

sskaje avatar Jan 21 '23 11:01 sskaje

就是不同客户端不同规则吧

pymumu avatar Jan 22 '23 03:01 pymumu

是的。

一开始我想用 nameserver /domain/group- 去试,但是试了几种组合都没成功。看了下源码,_config_domain_rule_add 里 域名的各类规则只有一条,毕竟设定是服务器组,逻辑上也最多也就适合和 bind 的组做一个简单的排除。

sskaje avatar Jan 22 '23 08:01 sskaje

要增加一个client-group参数,用于指定客户端查询时,使用的group组。 对应的address, nameserver等,也要增加group组。

这个要细化设计下配置的方法。

pymumu avatar Jan 27 '23 15:01 pymumu

我想到的场景中,客户的 IP 可能出现在多个 client group 里,所以我在原始 issue 里提了两种命令行参数方式,一种全inline,一种预定义 ACL。我自己会倾向于后者,因为我担心前一种会导致树的空间占用过大,也会需要每次去匹配各种参数组合。

针对 ACL 的方案,我有个可能的算法逻辑,不知道是不是适合。

1,客户在使用的时候同一个 ip 或网段可能会出现在多个组合里。但是我猜测 smartdns 的绝大多数用户还是会在局域网场景下,家庭用户可能个位数数量的组合就能满足需求,企业用户可能两位数,所以我假定最多支持 63 个 ACL,加一个预设的默认。

每个客户端来请求的时候,可以先去匹配所有 ACL,计算是否匹配 0/1;前边我假定了63 + 1 个 ACL,所以假定有个 int64 的 mask 变量,启动时,可以按 ACL 的某些字段排序,例如 name,编号 0~62,default 是 63,将匹配的结果 set 为这个int64 的各位。

那在程序运行中,客户 IP 和 这个 int64 的关系应该是确定的;如果匹配的计算消耗过大,也可以考虑 cache 这个数据。

这里 default 也可以考虑预设 int64_max;或者不要 default,就用现有的默认规则。

这里我不确定实现层面能不能支持让客户去配置最大多少个字节的 int 去管理 ACL,例如 4/8/16 。

client.ip -> ACL 1, 3, 5 -> ACL mask bits [00101010]

2,域名配置的树里,增加一个额外的 ACL mask 的列表。配置加载时,根据配置命令的 ACL 参数,把域名的 mask bits 也存下来。于是域名的配置节点里可能会多一个不定长的数组。

从存储的角度,实际上大量域名会有重复的 ACL mask 列表,所以再加一层映射,把不定长的数组拿出来,配置节点上只存大数组的索引或者节点的指针。但是这个感觉会是牺牲启动的速度换运行的速度。

这里还会遇到一个问题,客户可能会配置错误,例如一个域名出现在两个 domain-set 里,而这两个domain-set 配置了不同的 ACL,导致一个客户IP + 请求域名的组合有多个 server group 的选项。我的想法是配置不检查,执行时匹配到什么算什么,只记录匹配日志,客户自己开日志去查原因。

这个列表我觉得应该是有序的,例如按 mask 升序,default 理论上永远是最大的一个,而匹配逻辑是 domain.group-acl-rules[x].mask & client.acl > 0,匹配到就返回对应的组;如果不需要 default,ACL mask 列表为空或者匹配不上就执行现有的默认规则。


stored_rules: Combinations of ACLs
{
# index => sorted masks
[0] => [mask 1, mask 2, mask 4, mask n],
[1] => [mask 2],
...
}

mask n object:
{
  .mask = 0x0000,
  .group = "xxxxx"
}


# config tree 
domain node .group-acl-rule -> stored_rules[index] 


sskaje avatar Jan 27 '23 16:01 sskaje

我最近也碰到了类似需求。我感觉可以先让bind支持下ip-transparent,这样用户自己就能通过iptable/nftables的tproxy灵活解决这个需求了。

THMonster avatar Oct 29 '23 08:10 THMonster

我最近也碰到了类似需求。我感觉可以先让bind支持下ip-transparent,这样用户自己就能通过iptable/nftables的tproxy灵活解决这个需求了。

如果用户可以用iptable,可以根据客户端ip将dns请求重定向到不同端口,服务端用多进程多配置来解决

PikuZheng avatar Oct 29 '23 10:10 PikuZheng

我最近也碰到了类似需求。我感觉可以先让bind支持下ip-transparent,这样用户自己就能通过iptable/nftables的tproxy灵活解决这个需求了。

如果用户可以用iptable,可以根据客户端ip将dns请求重定向到不同端口,服务端用多进程多配置来解决

如果你说的“重定向“是指iptables/nftables里的redirect的话,这东西不支持udp吧?而且用这个的话dns server好像要改的更多,tproxy支持udp而且对dns server的改动很小,基本上只需要在bind的时候往socket option里面加个IP_TRANSPARENT。 多进程大概也是不需要的,因为目前smartdns已经支持bind多个地址,而且每个bind支持设置不同-group来分流。

THMonster avatar Oct 29 '23 10:10 THMonster

但是tproxy,可能有回流问题(请求的服务器ip和应答的服务器ip不符

PikuZheng avatar Oct 30 '23 08:10 PikuZheng

最新代码添加了相关的功能。

配置方法:

方法一:

group-begin client
server 127.0.0.1:61053 -e 
client-rules 127.0.0.1 
address /a.com/1.1.1.1
domain-rules /b.com/ -address 4.5.6.7
group-end

方案二:

conf-file /path/to/conf.conf -group client
# /path/to/conf.conf文件内容
client-rules 127.0.0.1 
address /a.com/1.1.1.1
domain-rules /b.com/ -address 4.5.6.7

pymumu avatar Jan 07 '24 16:01 pymumu