[DNSMasq] 解决 No DNS Rebind 误杀内网域名A记录

前两天给自己的域名下添加了两条 A 记录,指向了内网的两个IP地址(192.168..)。当时是通过 VPN 连到了这个 192.168.. 的网段,两条记录在添加后直接就生效了。奇怪的是晚上回到家后,发现无法通过这两个域名访问内网的页面,Chrome 里的错误信息提示 “ERR_NAME_NOT_RESOLVED”,当时试着用路由器里配置的三个 DNS 解析了下自己的域名,发现都可以正常解析,但是在浏览器里却始终没法访问,重启了路由器无果,当时没什么思路,猜想可能是某些缓存机制影响,就先扔在了一边。

今天继续排查这个问题,之前在查看 DNSMasq 的配置时,发现默认开启了 “No DNS Rebind” 的选项,简单地查了一下,DNS Rebinding 的维基百科介绍中提到了

This attack can be used to breach a private network by causing the victim’s web browser to access machines at private IP addresses and returning the results to the attacker. It can also be used to use the victim machine for spamming, distributed denial-of-service attacks and other malicious activities.

这一段话提到了 DNS Rebind 可能和内网有关,于是打开路由器的 DD-WRT 管理界面,找到 Status 下的 Syslog 查看系统日志,看能否发现有关的线索,果然看到了一条相关的日志

Nov 30 00:21:10 DD-WRT daemon.warn dnsmasq[10942]: possible DNS-rebind attack detected: internal.record.bydell.com

也就是说,这两条指向到内网 IP 的记录被 DNSMasq 当成是 DNS Rebind 攻击被拦截掉了,这也就解释了为什么我路由器中配置的3个 DNS 服务器都可以正常解析,但是在终端上却无法得到正确的结果。于是 Google 了一下解决方案,DNSMasq 支持对特定域名记录加入白名单,使用方法如下:

可以添加静态记录

address=/abc.example.com/1.1.1.1

也可以对整个域名允许绕过 No DNS Rebind 的限制

–rebind-domain-ok=example.com

第一种方式不需要对域名进行解析,相当于在本地添加一条 hosts 记录,适合内部网络使用。

不过还是推荐使用第二种方式,这样不论任何时候记录发生变化,不需要维护 DNSMasq 里面的配置,直接修改对应的 DNS 记录,会灵活一些。

[DD-WRT] 为 Netgear R6300v2 交叉编译 shadowsocks-libev

在开头强烈推荐一篇介绍交叉编译概念的文章,对交叉编译不熟悉的同学建议先读一遍,可以帮助解决接下来可能会遇到的很多问题。

Building and cross-compile tutorial

家里用的路由器是 Netgear 的 R6300v2,刷的是K大去年停止维护前的最后一个版本

DD-WRT v24-sp2 (10/08/14) kongac – build 25100M

之前在学校的时候用它装了迅雷固件脱机下载,用内置的PPTP VPN建了个 VPN Server,跑了一年感觉性能强劲毫无压力,之后一直就这么在用着。到了北京之后情况有了点小变化,PPTP 在外网情况下很容易被探测出来并且被联通直接封锁。所以需要从 PPTP 换成 OpenVPN,在 DD-WRT 下折腾了两天配置,走了不少弯路,总算搞定了,家里的线路连接海外ip时十分稳定,所以在公司的时候也用 OpenVPN 连到家里将国外的流量转发出去,使用了几天感觉很理想,于是想进一步优化一下方案,将 Shadowsocks 部署在路由器上,这样所有可以通过 VPN 连接进来的设备就可以免客户端实现透明翻墙了,对 iOS 设备来说是一个比 VPN 直连更好的方案。

查了一下,Shadowsocks-libev 可以运行在 OpenWRT 上并且已经有前人成功为 DD-WRT 平台成功交叉编译,其中最为详细的一篇是在腾袭的博客里面提到的,不过文章里面提供的版本比较旧了,是1.4.6版本的,这也是公开资源里面能找到的最新的版本了。看了一下距离现在(2.3.1)之间已经相差了很多个版本,本着我用新不用旧的心理产生了重新编译一份新版本的想法。既然前人已经证实了在同样的硬件+软件平台下可以运行,那么基本新版本的代码也可以拿过来运行。

不过首先交叉编译的概念对我来说就很陌生,顺手搜了一发,根据百度百科的说法,就是在一个平台上生成另一个平台的可执行代码。那这句话很好理解了,比如嵌入式设备的存储空间和计算能力有限 ,可能需要在其他平台上直接使用嵌入式设备的运行环境编译好可执行文件拷贝过去直接运行。那么接下来的工作主要还是围绕编译进行的,基本上是“选择对应的工具链(toochain)—设置编译参数—排除问题—测试”,在第一次编译时,参照前面腾袭博客里的文章,除了作者提到的几个地方之外,又又一些新的问题出现了,所以整个排坑的过程持续了将近一天,关键的工作主要由我的同事完成,他在编译和调试方面的经验让我少走了很多弯路。我们一起编译成功了2.3.1版本的源码,已经在我的路由器成功运行将近3个星期的时间,v2.3.1可以在这里下载

下面我将以编译当前最新版本(2.3.3)为例,记录一下编译的过程,以便让有类似需求的同学进行参考。在阅读下面的内容之前建议首先阅读上面博客链接里的内容以便更好的理解整个过程。

假设当前的工作目录为 /home/shadowsocks-libev,首先下载 OpenSSL 和 shadowsocks-libev 的源代码,分别可以在链接里面找到最新版。下载后解压到工作目录。这里 OpenSSL 我选择了1.0.2d版本。接下来下载编译所需要的 toolchain,原文中的链接已经失效了,DD-WRT 官网的下载页面进行了调整,所有的 toolchain 都被打包到一个大的压缩包(1.8G)中,下载下来后经过漫长的解压得到很多不同的组合。

toolchain-arm_cortex-a9_gcc-4.8-linaro_musl-1.1.5_eabi
toolchain-arm_mpcore+vfp_gcc-4.8-linaro_musl-1.1.4_eabi
toolchain-armeb_xscale_gcc-4.8-linaro_musl-1.1.2
toolchain-i386_gcc-4.7-linaro_uClibc-0.9.33.2
toolchain-mips64_octeon_64_gcc-4.9-linaro_uClibc-0.9.33.2
toolchain-mips_34kc_gcc-5.1.0_musl-1.1.9
toolchain-mips_mips32_gcc-4.8-linaro_musl-1.1.6
toolchain-mipsel_24kec+dsp_gcc-4.8-linaro_uClibc-0.9.33.2
toolchain-mipsel_3.3.6_BRCM24
toolchain-mipsel_4.1.1_BRCM24
toolchain-mipsel_74kc+dsp2_gcc-5.1.0_musl-1.1.9
toolchain-mipsel_gcc4.1.2
toolchain-powerpc_e300c3_gcc-4.8-linaro_uClibc-0.9.33.2
toolchain-powerpc_gcc-4.6-linaro_uClibc-0.9.33.2
toolchain-x86_64_gcc-4.8-linaro_uClibc-0.9.33.2

我们最后选择最接近的一个 toolchain-arm_cortex-a9_gcc-4.8-linaro_musl-1.1.5_eabi ,拷贝到工作目录中来。然后将 toolchain 下的 bin 目录加入到环境变量中

export PATH="/home/shadowsocks-libev/toolchain-arm_cortex-a9_gcc-4.8-linaro_musl-1.1.5_eabi/bin:$PATH"

首先编译OpenSSL,腾袭在博客中提到需要首先解决缺少 uClibc 才需要的 termio.h 头文件的问题,我们现在使用的是musl,所以首先在 Configure 文件中将 -DTERMIO 改为 -DTERMIOS

sed -i 's/-DTERMIO/-DTERMIOS/g' Configure

然后修改 OpenSSL 源码中 ./crypto/ui/ui_openssl.c 文件,在下面一行(222行)

#ifdef TERMIOS

前添加以下内容来绕过这个问题。

#define TERMIOS
#undef  TERMIO
#undef  SGTTY

接下来可以进行配置和编译了

CC=arm-linux-gcc 
CXX=arm-linux-g++ 
AR=arm-linux-ar 
RANLIB=arm-linux-ranlib 
./Configure no-asm shared --prefix=/home/shadowsocks-libev/ssl linux-armv4
make
make install

编译成功后会在 /home/shadowsocks-libev/ssl 目录下得到编译好的 OpenSSL,接下来继续编译 Shadowsocks-libev。

编译 Shadowsocks 的时候是一样的思路,只不过中间出现的一点迷惑性的问题把思路带偏了,排查了很久才解决。在 Configure 的过程中,配置脚本会尝试对一些脚本进行尝试编译,这过程中如果出现问题就会终止脚本报错,我在编译2.3.1版本的代码时,出现了如下的错误提示

checking for linux/netfilter_ipv6/ip6_tables.h... no
configure: error: Missing netfilter headers

这里的提示 Missing netfilter headers 实际上是由Configure脚本在 try compile 的时候出错导致的,但是错误提示却让人以为是没有找到这个文件,所以一开始解决问题的方向错了, 耽误了很长的时间,最后找到错误的原因是 linux/netfilter_ipv6/ip6_tables.h 文件中使用了 u_int16_t 和 u_int8_t 类型却没有找到声明的原因,因此在编译的时候会因为 undefined type 而中止。我这里简单地在 /home/shadowsocks-libev/toolchain-arm_cortex-a9_gcc-4.8-linaro_musl-1.1.5_eabi/include/linux/netfilter_ipv6/ip6_tables.h 文件中添加了一行(可以添加在第25行)

#include <sys/types.h>

解决了问题,不知道这样的解决方案会不会带来什么其他的问题,不过在我后面的时候中一切正常。有更彻底地解决方案欢迎留言。

后记:在最近的更新中,可能增加了 pcre、zlib 等依赖,需要同 openssl 交叉编译的方法同样编译好后再使用 –with-xxx= 的方法在编译时传入,此处不再赘述,可以直接使用下面编译好的版本或者自行尝试~

CC=arm-openwrt-linux-muslgnueabi-gcc 
CXX=arm-openwrt-linux-muslgnueabi-g++
AR=arm-openwrt-linux-muslgnueabi-ar 
RANLIB=arm-openwrt-linux-muslgnueabi-ranlib 
./configure --prefix=/home/shadowsocks-libev/shadowsocks-libev-dd-wrt --with-openssl=/home/shadowsocks-libev/ssl --host=arm-linux --disable-ssp
make
make install

最后附上编译好的版本的下载链接吧,没有条件的可以直接用,只打包了可执行文件:

(我会在我的路由器服役周期内不定期进行新版本的编译并更新下载链接,如果有新版本发布后我没及时更新,可以在留言里喊我)

2016-11-16 更新:编译v2.5.6 –disable-ssp

2017-04-06 更新:在 R8000 下测试可以直接使用

  • 点击下载:v2.5.6
  • 点击下载:v3.0.5  3.0后改动较大,正在测试中

支持固件列表(经本人测试正常使用)

  • DD-WRT v24-sp2 (10/08/14) kongac – build 25100M
  • DD-WRT v24-sp2 (02/04/15) std – build 26138(推荐,感觉相比于25100M版本在5G下的表现稳定了许多,同时也是目前推荐使用的最新的 stable 版本)
  • DD-WRT v3.0-r28000M kongac (10/24/15)
  • DD-WRT v3.0-r31800M kongac (03/31/17)