从硬件的角度来看,控制海底光缆等基础互联网设施,就控制了全世界;从软件的角度来看,控制了 DNS,也就控制了互联网。
互联网虽然叫国际互联网
,但也分国家,全部控制,谈何容易?硬件层面的安全问题,会成为国家层面的安全问题,这是所有人可以看见的,所以这也无限拔升了控制的难度。
从软件的层面出发,控制 DNS 的可能性反倒会更高。一个 DNS client 上有没有后门留着,是很难知道的,特别是习以为常之后;万一有后门的话,且这个后门被激活,差不多就是毁天灭地的力量了。我们来看看市面上一般有哪些 DNS client,有微软 Windows 自带的、苹果 MacOS 自带的、苹果 iOS 自带的、谷歌安卓自带的、Linux 自带的,除了 Linux 之外 (开源的很难留后门,无强迫、没动机、有监督),其它要一次性控制,想想还是难度系数超高的,几乎不可能
。但是,跟 控制所有互联网设备
相比,这个几乎不可能
的难度系数就不值一提了。
控制
会带来破坏性,但另外一方面,也会伴随着创造性。如果有新的协议,在整个 DNS 生态中获得通用性承认,就能在潜移默化之中推动整个互联网的进化,但这也非常困难,之前确实有这方面的尝试,比如 EDNS 这个接近理所当然的改进,但要做到 通用性,还需要很长很长很长的时间吧。
控制 DNS
,站在整个互联网的角度来看,痴人说梦;但我们换一个角度,网络
是分层的,小的分层,可以是一个家庭内部的局域网,或者一个学校内的局域网,这个难度系数就直线降低,别的不说,如果就控制我们当前电脑上的 DNS,那是信手拈来的事情呀。
我们接手自己电脑上的 DNS 解析服务器,然后让所有的域名解析直接指向自己的某个 Web 服务器 (也可以是本地设备本身),那所有过来的 request,可以将其 proxy (代理),也可以 proxy 之后进行调整、优化。但这也仅限于 HTTP 的请求, HTTPS 的请求是加密的,需要更复杂些的 『授权』。
进一步发展,就是介入浏览器的行为,比如 UC 浏览器在低速互联网的时代中,就是通过介入对 request/response 的流程控制,才能提升页面的访问速度。此处就不具体展开说明了。
如果是通过 DNS 的手段预先拦截,再对一个 HTTP 的 Web请求
进行再处理,诈骗犯的骗术可以做到严丝合缝,所以有些网银在自己的宣传单中,会特别强调一个地址栏上的绿色锁 (表示当前是 HTTPS 访问)。但接管计算机设备的 DNS,还是蛮难的,所以市面上出现的是 钓鱼网站
这种诈骗方式。
但我们不会在此做什么特别的演示,本是很简单的事情,而且还是自己电脑设备上的操作,但外行的人看来,会是恐怖的事情。有心人要折腾你的话,那么,解释扩大化之后,联网之后的所有行为都是犯罪行为,请参考刑法第二百八十五条以及之后的几条内容。
我们要呈现的是 URL 转发
,也是一些 国际域名服务商 会在 DNS 解析记录中提供的一种记录类型。它并不是真正的 DNS 记录类型,是一种为了用户体验而增加的逻辑。
之前已经大概介绍过 DNS 解析记录,我们应该很清楚 domain.com
和 www.domain.com
分别是两个不同的东西,一个是 根域名
,一个是二级域名,但对于绝大多数的网民而言,两者应该是一样的。URL 转发
就是解决类似问题的, 比如当你访问 domain.com
的时候,会自动跳转到 www.domain.com
,如果 domain.com
上设置了 URL 转发
的话。
我们先用 Flask 完成一个 Web 服务器,它的用途很简单,就是处理 URL 的跳转,跳转的规则也很简单:比如访问 hello-domain.com 的时候,会自动跳转到 domain.com 上。
from flask import Flask, redirect, request, abort
app = Flask(__name__)
@app.route('/')
@app.route('/<path:path>')
def auto_redirect(path=''):
domain = request.host
if domain.startswith('hello-'):
original_domain = domain.replace('hello-', '', 1)
new_url = 'http://%s/%s' % (original_domain, path.lstrip())
return redirect(new_url)
else:
abort(404, '%s is not supported' % domain)
app.run(port=80)
也很简单,就是在 simple_dns_server.py
里,把 ip = get_ip_from_domain(domain)
这行改为:
if domain.startswith('hello-'):
ip = '127.0.0.1'
else:
ip = get_ip_from_domain(domain)
注意: 如果源代码中还有 time.sleep
的逻辑,请务必先移除,或者注释掉。
方便起见,DNS 服务器对应的 .py
文件为 simple_dns_server.py
,Web 服务器对应的 .py
文件为 simple_web_server.py
。
新开两个命令行窗口,进入本篇对应的两个 .py
文件的所在目录,然后分别执行:
sudo python simple_dns_server.py
以及
sudo python simple_web_server.py
不论 DNS 还是 Web Server,都需要 sudo
权限,因为 Web Server 的 80 端口也是默认被 MacOS 限制的。
先打开两个命令行窗口,进入 pipenv 对应的项目根目录
,然后执行 pipenv shell
,进入命令行对应的虚拟环境。
然后,进入本篇对应的两个 .py
文件的所在目录,然后分别执行:
sudo python simple_dns_server.py
以及
sudo python simple_web_server.py
具体参考《DNS 基础 · DNS 是怎么工作的》中的 『当前电脑的 DNS 服务器』这部分内容,我们将本地的 DNS 设置为 127.0.0.1
,并且,不能再有其它的记录。
现在我们要开始测试了。但在此之前,仍然有两点需要确认下:
127.0.0.1
则未必会去请求。
好了,现在打开浏览器,访问 hello-google.com
、hello-baidu.com
、hello-任何你想要访问的域名
,看看效果是否已经达到了?这就是基于 DNS 的 URL 转发了,在互联网的环境中,只要别人将域名的 DNS 指向你的解析服务器,就能实现同样的效果。
除了 redirect 之外,你还可以对一个 HTTP 的 Web 请求直接 proxy,所谓的 proxy 就是 client 的 request 过来时,分两步走: 1, 从真正的外部获得内容;2,把第 1 步中获得的内容返回给 client。
如果跟 DNS 服务器继续合在一起,那么你需要注意如何获得 真正的外部 IP
。比如说对 domain.com
进行了 proxy,那么 client 的 DNS 请求过来,你返回给 client 的是 127.0.0.1
,那在自己的服务端,则不能去向 127.0.0.1
发送 Web 请求了,否则就成了无源之水。需要找到这个域名外部对应的真正 IP,这应该不难,在 《写一个 DNS 服务器》中就有现成的代码。
而我们对外发送一个 Web 请求时,表面上是访问某个域名下的 URL,实际上是请求到域名对应的 IP,而域名本身只是作为 request 的 headers (名为 Host) 传输过去。再回过头看看最开始的 Socket 编程,一个 Web 请求,就是连接 socket 的行为。
所有的技术瓶颈,都已经松动了,想怎么扩充,现在都是自由的了。