DNS 代理?Pipy:这我也可以

Pipy 是个可编程代理,曾经我们做过 TCP/HTTP 代理、MQTT 代理、Dubbo 代理、Redis 代理、Thrift 代理。前几天有人问 DNS[1] 的代理能不能做?当然可以,而且 DNS 代理已经应用在 跨集群流量调度 中,文末经对此进行简单地介绍。

阅读本文将了解到:

  • • DNS 的基本介绍以及 DNS 的处理流程

  • • 使用编码实现一个 DNS 代理

  • • 在代理中增加智能线路解析功能

DNS 介绍

DNS(Domain Name System,域名系统)是互联网的一项服务。它将域名和 IP 地址相互映射为一个分布式数据库,能够使人更方便地访问互联网。DNS 使用 TCP 和 UDP 端口 53。

-- 摘自维基百科

024619bc6bbb13fe70935448ba24c9ff.png
dns procedure

简化版的 DNS 处理流程:

  1. 1. DNS 客户端(如浏览器、应用程序或者设备)发送域名 example.com 的查询请求。

  2. 2. DNS 解析器收到请求,查询本地缓存,如果本地有记录且未过期会返回本地的记录。

  3. 3. 如果本地缓存未命中,DNS 解析器将从 DNS 根服务器开始向下查询,首先是顶级域名(Top Level Domain, TLD) DNS 服务器(这里是 .com),一直向下直到可以解析 example.com 的服务器。

  4. 4. 能够解析 example.com 的服务器成为权威 DNS 名称服务器(Authoritative DNS name server),解析器访问该服务器并收到 IP 地址等相关信息,然后返回给给客户端。解析完成。

相信在工作的时候会遇到需要改 DNS 记录来更新域名的真实指向,比如切换运行环境、流量拦截,DNS 也经常作为服务发现的手段之一。通常 DNS 服务器要么是服务提供商业维护,要么就是企业内部的网络团队,导致修改 DNS 的解析记录不够便利。而且由于 DNS 的缓存设计,每条记录都有个 TTL 的设置,在缓存失效前都不会再去更新记录。TTL 过长过短,都不合适。

引入 DNS 代理,可以在解决这个问题的同时,实现更多的功能。

接下来通过案例来演示如何使用 Pipy 实现 DNS 的代理(准确来讲,应该是代理和服务器的合体),这个代理会从自定义的记录中返回 DNS 查询请求。同时我们还会加入特性:根据客户端 IP 的地址返回不同的 DNS 记录,来实现智能线路解析。演示中所使用的脚本,都可以从 这里[2] 下载。

方案

8d5a38fb2f7741d8ccc8c02360621be5.png
dns-proxy

如上图所示,DNS 代理与原来的解析器,提供类似的功能。但是在缓存失效或者未命中时,会查询自定义的解析记录。如果有自定义记录,就返回自定义记录;如果没有,按照原来的流程去 DNS 服务器上查询。

实现

在开始之前,借助 wireshark 的网络抓包来看下 DNS 消息的格式,DNS 查询和应答的消息格式是一样的,都包含一下四个部分:

  • • 头部:包含了 ID、标记、查询的条目数、应答的条目数、权威资源条目数以及附加资源条目数。

  • • 标记部分:这部分标识消息类型、名称服务器是否权威、 查询是否递归、请求是否被截断,以及状态。

  • • 请求部分:包含正在/需要解析的域名和记录类型(A、AAAA、MX、TXT 等)。域名中的每个标签都以其长度为前缀。

  • • 应答部分:包含查询域名的资源记录。

60df5cfd26cd90fd60e524df241623b8.png
dns-message-format

在 Pipy 0.70.0 的更新 中,假如了 DNS 的解码器。使用 DNS 解码器,可以对 DNS 消息进行解码,解码出上面的四个部分。

PipJS 编码

实现的脚本逻辑很简单,为了方便阅读将其按功能分成了几个模块,实现了 AAAAACNAMEMXTXTNS 几个常见类型的记录解析。

├── cache.js #缓存
├── main.js #主入口脚本
├── records.js #自定义记录的逻辑
├── records.json #自定义记录的内容
├── smart-line.js #智能线路解析的逻辑
└── smart-line.json #智能线路解析的配置

这里列出 main.js[3] 的部分核心代码,并对代码进行了注解:

  1. 1. 首先使用 DNS.decode() 对数据流进行解码

  2. 2. 然后从结果中找到要查询域名和类型

  3. 3. 查询缓存

  4. 4. 缓存未命中,查询自定义的记录。

  5. 5. 智能线路解析

  6. 6. 返回响应

  7. 7. 如果 3、4 均查询不到,会请求上游的 DNS 服务器,然后缓存并返回响应

.listen(5300, { protocol: 'udp' })
.replaceMessage(msg => ((query, res, record) => (query = DNS.decode(msg.body), //1query?.question?.[0]?.name && query?.question?.[0]?.type && ( //2record = getDNS(query.question[0].type + '#' + query.question[0].name) //3|| local.query(query.question[0].name, query.question[0].type) //4),record ? (record = line.filter(__inbound.remoteAddress, record), //5res = {},res.qr = res.rd = res.ra = res.aa = 1,res.id = query.id,res.question = [{'name': query.question[0].name,'type': query.question[0].type}],record.status === 'deny' ? (res.rcode = local.code.REFUSED) : (res.answer = record.rr),new Message(DNS.encode(res)) //6) : (_forward = true,msg)))()
)
.branch(() => _forward, $ => $.connect(() => `${config.upstreamDNSServer}:53`, { protocol: 'udp' }) //7.handleMessage(msg => ((res = DNS.decode(msg.body)) => (res?.question?.[0]?.name && res?.question?.[0]?.type &&!res?.rcode && (setDNS(res.question[0].type + '#' + res.question[0].name,{rr: res.answer,status: res.rcode == local.code.REFUSED ? 'deny' : null}))))()),$ => $
)

自定义记录

下面是自定义记录的内容,与 DNS 应答的格式类似。为了支持智能线路解析,部分记录增加了标签信息:"labels": ["line1"]

[{"name": "example.com","type": "A","ttl": 60,"rdata": "192.168.139.10","labels": ["line1"]},{"name": "example.com","type": "A","ttl": 60,"rdata": "192.168.139.11","labels": ["line2"]},...{"name": "example.com","type": "MX","ttl": 600,"rdata": {"preference": 10,"exchange": "mail2.example.com"}},{"name": "example.com","type": "TXT","ttl": 600,"rdata": "hi.pipy!"},{"name": "example.com","type": "NS","ttl": 600,"rdata": "ns1.example.com"},...
]

智能线路解析

智能线路解析的逻辑比较简单,为不同的 IP 范围设置线路标签,在应答时如果记录带有标签就只返回对应标签的记录。

{"line1": ["192.168.1.110/32"],"line2": ["127.0.0.1/32"]
}

测试

启动代理:

$ pipy main.js

如上面配置所示,127.0.0.1 是本机回环网卡的地址,192.168.1.110 本机以太网卡的地址,代理监听在 5300 端口。

首先使用 localhost 访问代理,这样代理获取的客户端 IP 地址为 127.0.0.1,在查询 example.com 的记录时,直返回来地址对应的线路 line2 的记录 192.168.139.11

$ dig @localhost -p 5300 a example.com; <<>> DiG 9.10.6 <<>> @localhost -p 5300 a example.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 25868
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0;; QUESTION SECTION:
;example.com.   IN A;; ANSWER SECTION:
example.com.  60 IN A 192.168.139.11;; Query time: 0 msec
;; SERVER: 127.0.0.1#5300(127.0.0.1)
;; WHEN: Tue Dec 13 21:09:38 CST 2022
;; MSG SIZE  rcvd: 56

接着使用 192.168.1.110 访问代理,这次客户端的地址为 192.168.1.110,返回的是线路 line1 的记录 192.168.139.10

$ dig @192.168.1.110 -p 5300 a example.com; <<>> DiG 9.10.6 <<>> @192.168.1.110 -p 5300 a example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54165
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0;; QUESTION SECTION:
;example.com.   IN A;; ANSWER SECTION:
example.com.  60 IN A 192.168.139.10;; Query time: 0 msec
;; SERVER: 192.168.1.110#5300(192.168.1.110)
;; WHEN: Tue Dec 13 21:12:37 CST 2022
;; MSG SIZE  rcvd: 56

假如我从另外一台机器上访问,因为没有设置线路,会返回两条记录。

$ dig @192.168.1.110 -p 5300 a example.com; <<>> DiG 9.16.1-Ubuntu <<>> @192.168.1.110 -p 5300 a example.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 64873
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0;; QUESTION SECTION:
;example.com.   IN A;; ANSWER SECTION:
example.com.  60 IN A 192.168.139.10
example.com.  60 IN A 192.168.139.11;; Query time: 0 msec
;; SERVER: 192.168.1.110#5300(192.168.1.110)
;; WHEN: Tue Dec 13 13:15:24 UTC 2022
;; MSG SIZE  rcvd: 83

因为只设置了 A 记录的线路,其他类型的记录不受影响。

$ $dig @localhost -p 5300 mx example.com; <<>> DiG 9.10.6 <<>> @localhost -p 5300 mx example.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33492
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0;; QUESTION SECTION:
;example.com.   IN MX;; ANSWER SECTION:
example.com.  600 IN MX 10 mail1.example.com.
example.com.  600 IN MX 10 mail2.example.com.;; Query time: 0 msec
;; SERVER: 127.0.0.1#5300(127.0.0.1)
;; WHEN: Tue Dec 13 21:18:27 CST 2022
;; MSG SIZE  rcvd: 117

进阶

对 Pipy 有一定了解的小伙伴可能知道 Repo 模式[4],有兴趣的可以参考这篇文章 快速入门 Pipy Repo(文章一年前发布,界面和 API 接口有更新,但是原理不变)。

使用 Repo 模式,所有主机上的代理(或者称之为 DNS 服务器)都从 Repo 中实时获取自定义记录的更新,并刷新缓存。

碍于篇幅,这里就不深入。有兴趣的小伙伴可以尝试自己实现。

5a6b8d5b916b13d43b63bf7f2e85515e.png
dynamic-dns-resolve

总结

至此 Pipy 可以实现的代理又增加了一种。DNS 的应用无处不在,也正因如此从 DNS 层面可以解决问题。

让我们再回到开头提到的问题,在 跨集群流量调度实战 的 demo 中,我们将轻松将请求流量调度到了其他集群进行处理。请求的地址是 http://httpbin.httpbin:8080/,这里的 httpbin.httbin 是命名空间 httpbin 下 K8s Service httpbin 的域名。但是在集群 cluster-2 中并没有这个 Service,仅在集群 cluster-1 和 cluster-3 中部署,这个地址在集群 cluster-2 中无法解析。

这里使用了个小手段,在网格的初始化容器设置 iptables 规则拦截流量时,也 DNS 的流量也拦截到 sidecar 实现的 DNS 代理(监听在 127.0.0.153:5300),通过自定义 DNS 记录实现业务流量的拦截。

ff11eb3d7159c89d386a9f1f8f98d660.png
fsm-multi-cluster

引用链接

[1] DNS: https://en.wikipedia.org/wiki/Domain_Name_System
[2] 这里: https://github.com/flomesh-io/pipy-demos/tree/main/pipy-dns-demo
[3] main.jshttps://github.com/flomesh-io/pipy-demos/blob/main/pipy-dns-demo/main.js
[4] Repo 模式: https://flomesh.io/pipy/docs/en/operating/repo/0-intro

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/280292.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在Windows中快速轻松地将文件发送到SkyDrive

We have already shown you how you can share external folders with your SkyDrive, but what if you actually want to copy a file or folder into your SkyDrive folder? Of course copying and pasting is nowhere near geeky enough, so here’s how to add a SkyDrive…

windows删除桌面ie_从Windows 8“开始”屏幕启动IE的桌面版本

windows删除桌面ieThere are two versions of Internet Explorer in Windows 8, one you can only launch from the Start Screen and the Desktop version which you can only launch from the Desktop. Lets look at how we can launch the Desktop version from the Start S…

.NET Conf China 2022 圆满落幕,明年再见!

时光飞快&#xff0c;还记得本月的第一个周末吗&#xff1f;12月3日-12月4日&#xff0c;相信对于 .NET 开发者来说一定记忆犹新&#xff01;.NET Conf China 2022 于12月4日圆满落幕。八方助力共谱大会盛宴.NET Conf China 2022 是一个社区性质的技术峰会&#xff0c;本次大会…

马哥linux高薪中级-DNS

第一章 简介一、DNSdomain name server&#xff0c;用来将计算机名称或者域名解析成ip地址的服务协议。用户在使用域名访问时会先通过DNS服务请求域名对应的ip地址&#xff0c;然后缓存下来&#xff0c;然后才通过ip地址进行通信。最初域名解析是通过HOSTS文件来静态绑定的。DN…

愚蠢的怪胎技巧:通过命令行管理SkyDrive

Originally launched as an April Fools prank by the Microsoft SkyDrive team, SkyCMD turned out to be a really geeky way to manage files and folders on your SkyDrive from the command line. Lets take a quick look. SkyCMD最初是由Microsoft SkyDrive团队以愚人节恶…

关于vue父子组件之间事件触发及数据传递问题

父组件&#xff1a;1&#xff0c;引入子组件2&#xff0c;ref 3&#xff0c;需要更新数据操作的地方 子组件&#xff1a;1&#xff0c;定义同名事件&#xff0c;拿到数据执行相关操作

.NET Core如何通过认证机制访问Kafka?

【.NET Core】| 总结/Edison Zhou大家好&#xff0c;我是Edison。最近有一个ASP.NET Core使用认证机制访问Kafka的需求&#xff0c;加之我们又使用了CAP这个开源项目使用的Kafka&#xff0c;于是网上寻找了一番发现对应资料太少&#xff0c;于是调查了一番&#xff0c;做了如下…

JQuery框架2.位置属性|筛选方法|事件

1、位置属性 jquery的css position获取匹配元素相对父元素的偏移位置&#xff1b;offset获取匹配元素在当前视口的相对偏移,返回的对象包含两个整型属性&#xff1a;top 和 left $("p").offset() $(div).offset().top $("p").offset().left scrollTop获取匹…

jenkins没安装git报错

Jenkins新建项目中源码管理使用Git时遇到如下问题&#xff1a; 在安装jenkins服务器上查看一下git版本&#xff0c;可能没有安装git 也可能是git版本太低 [rootlocalhost nnnnn]# git --version git version 1.8.3.1 yum安装的版本太低了 打开Jenkins的 主页面 > 系统管理 …

mac 不能连接wi-fi_如何在Mac OS X中查看当前的Wi-Fi连接速度

mac 不能连接wi-fiEver since I’ve been using my new MacBook Air, I’ve been befuddled by how to do some of the simplest tasks in Mac OS X that I would normally do from my Windows laptop—like show the connection speed for the current Wi-Fi network. So am I…

User Stories - 最佳实践 (Best Practices)

在转向敏捷之后&#xff0c;很多团队开始使用“用户故事”一词。用户故事是一种简单而优雅的技术&#xff0c;可以收集客户需求。然而&#xff0c;它需要一定的理解和实践才能用User Stories构建出色的软件。 让我们仔细看看用户故事是什么以及如何使用这种技术取得成功。 什么…

聊一聊promise的前世今生

promise的概念已经出现很久了&#xff0c;浏览器、nodejs都已经全部实现promise了。现在来聊&#xff0c;是不是有点过时了&#xff1f; 确实&#xff0c;如果不扯淡&#xff0c;这篇随笔根本不会有太多内容。所以&#xff0c;我就尽可能的&#xff0c;多扯一扯&#xff0c;聊一…

chromebook刷机_如何在Chromebook上切换(或离开)Canary频道

chromebook刷机Just like Chrome, Google offers multiple channels of the Chrome OS operating system. In addition to the standard Stable, Beta, and Developer channels you can choose from on the About page, there’s a special bleeding-edge Canary channel. The …

C++--day05

目录: 1. C的提高 1-131P 时间七天 2. C的基础 132-286P 时间八天 3. C的提高 287-378P 时间五天 4. C/C的数据结构 379-482P 时间五天 5. C/C的设计模式基础 483-540P 时间三天 视频资料&#xff1a;https://www.bilibili.com/video/av27904891?fromsearch&seid108915144…

libreoffice_如何更改您在LibreOffice中可以撤消的操作数

libreofficeIn LibreOffice programs, you can undo one action after another…to a point. The default number of actions you can undo is 100, but that number is easy to change. 在LibreOffice程序中&#xff0c;您可以撤消一个动作&#xff0c;直到某个点。 您可以撤消…

远程连接服务器出现身份验证错误 要求的函数不受支持

来源&#xff1a;https://www.cnblogs.com/lindajia/p/9021082.html 以往发布程序到服务器都没问题。今天远程桌面连接到服务器&#xff0c;突然出现了异常&#xff01;异常信息为&#xff1a; 在网上看到有多种解决方案&#xff1a;发现有种修改注册表的方式很简单。 详细步骤…

CDH集群安装配置(五)- Cloudera Manager Server

在线安装 sudo yum install cloudera-manager-daemons cloudera-manager-server 离线安装 资源下载地址 https://archive.cloudera.com/cm6/6.1.0/redhat7/yum/RPMS/x86_64/ 上次下面资源包到cdh1节点 cloudera-manager-server-6.1.0-769885.el7.x86_64.rpm cloudera-manager-s…

c++简单程序设计-5

编程实验部分1.vector3.cpp #include <iostream> #include <vector> #include <string> using namespace std;// 函数声明 void output1(vector<string> &); void output2(vector<string> &); int main() {vector<string>like…

关于JavaScript的编译原理

引擎&#xff1a;负责整个js程序的编译和执行过程编译器&#xff1a;负责语法分析和代码生成作用域&#xff1a;收集和维护一系列查询&#xff08;由所有声明的标识符组成&#xff09; 【例子&#xff1a;声明一个变量并赋值 var a value&#xff1b;】 Step1.编译器对该程序段…

safari检查元素_如何防止Safari检查是否使用Apple Pay

safari检查元素Apple Pay’s incorporation into macOS Sierra makes it really easy to pay using the service on your Mac with your iPhone or iPad. But that doesn’t mean just because you can, you will, or will want to use Apple Pay in the future. 通过将Apple P…