基于 iptables 实现一个 https/http 一键 proxy 工具

489 阅读28分钟

引子

在上一篇文章 《浅入浅出 iptables 原理:在内核里骚一把 netfilter~》 中,我们简单地介绍了 iptables 的使用和实现原理,今天我们尝试来通过 iptables 去做一些真的在业务中可能会用到的东西~

我们在日常开发中经常会碰到需要对真实服务做代理的场景,比如本地 mock 数据,比如多项目同步开发需要将线上 API 临时转发到本地开发中的服务等等场景。

我个人在最近的工作中,正好在开发一套客户端的 cli 工具 + web 网页应用 + 相应的服务端接口,仨玩意儿一块儿搞,其中就涉及到要将线上的 url 地址代理到本地开发中的服务器的场景。

我们公司内部有一个叫做 eden-proxy (基于阿里的 anyproxy)的代理工具,做得相当不错,一个配置文件加一条命令就能把代理搞起,在浏览器中访问线上地址直接干到本地。不过当我尝试在客户端用 nodejs 实现的 cli 工具中访问线上地址的时候却发现代理并没有生效,查了一下 eden-proxy 的文档,发现其默认是只对浏览器做代理转发,想要对整体系统做代理的话还需要另行配置。

虽然从文档来看使用该工具也可以解决 cli 中无法代理的问题,但是本着 “好玩儿” 的精神,决定自己实现一把,期望的目标就是开发一个命令行工具能做到 “一键!将 https/http 服务给干到本地~”。


先上效果

首先我们本地启动一个简单的 nodejs 服务,监听 13190 端口,访问后返回 “Hello World!”,之后 curl 一下 127.0.0.1:13190 确实返回了你好世界。

shipin1.gif

首先我们本地启动一个简单的 nodejs 服务,监听 13190 端口,访问后返回 “Hello World!”,之后 curl 一下 127.0.0.1:13190 确实返回了你好世界。

之后我们尝试直接 curl 一下 “www.baidu.com”,发现会拿到百度的 html 文档的字符串。

然后主角登场,我们通过执行一条命令 “easy-proxy set -s www.baidu.com -t 127.0.0.1:13190”,等运行命令成功后,再去尝试 curl “www.baidu.com”,发现果然已经被代理到了本地的 13190 端口,我们拿到了 “你好世界!”。

随后使用 “easy-proxy list” 可以看到已经生效的柜子,最后演示中执行了 “easy-proxy fresh”,清空了所有规则,然后再 curl “www.baidu.com”,一切已经回归正常。

源码地址

我先贴上源码地址,大家可以一边看文章一边看代码~

github.com/y805939188/…


实现思路

在思考思路之前,我们可以先根据我们要实现的效果想一想,如果想实现这种将远程服务代理到本地的功能,可能会涉及到哪些问题。

首先,我们要做的功能就是 “代理”,所以我们第一个要解决的问题就是“通过什么方式去做代理”。

其次,我们希望这个“代理”可以代理哪些内容?我们希望最好可以能代理 ip、http ,最好还能代理 https 协议到本地。这几种协议中,其中 ip 和 http 是很单纯的明文协议,所以“应该”不用做过多的操心,但是其中的 https 协议可能就要 “hack” 一下了,因为我们如果想要对 https 做代理的话,可能中间会涉及到对于安全证书的卸载等操作。所以我们要考虑的第二个问题就是“如何解决 https 证书的问题”。

所以接下来我们就可以带着以上几个问题去思考接下来的思路。

通过什么方式代理?

其实我们标题就已经剧透了,我们计划通过 iptables 去做代理。当然能够实现代理的方式可能不止 iptables 一种,不过 iptables 应该是一种最常用的方式(其实是我不知道其他代理方式了),日常工作中接触到的概率也会比较大,所以这里我们就采取 ip tables 的方式去做代理。

不过,如果你有看到我们上一篇文章《浅入浅出 iptables 原理:在内核里骚一把 netfilter~》的话,可能你会想到一个问题,我们在上面的演示视频中,是直接对 “www.baidu.com” 去做代理的,而 “www.baidu.com” 是个域名诶!不知道你有没有想到什么问题?

是的,上篇文章中我们说过,iptables 的实现原理是基于 Linux 内核中提供的 netfilter 框架去实现的,而这个 netfilter 框架,主要是工作在网络模型中的 “三层网络层” 前后的,我们都知道,“http” 和 “https” 协议却是上层应用层的协议,所以如果 iptables 只是工作在内核态的 “三层” 左右的话,那它可以成功拿到我们应用层的协议么?大家可以思考一下。

答案肯定是可以的!

为啥呢,其实所谓的应用层协议,甭管它多长多复杂,它其实本质就是个 payload,说白了就是串字符,这串字符是用户态定义好了之后,交给内核态的 socket 去往下一层层的传下去(收包的时候反着来),所以当我们发包的时候,甭管在内核态的下几层,我们其实都是可以拿到用户态的协议的。

ただし!不过!

内核态 “可以” 做这个事儿,但是却 “不能” 做这个事儿。因为我们的用户态协议千千万,而内核要做拆包查找上层协议的话,必须要知道上层来的是哪种协议才能知道每次要往上拆多少字节才是 payload,如果你要是让内核去感知这些乱七八糟的用户态的东西的话,那耦合实在就太严重了,所以我们不可能让内核去帮我们解包,再让内核去看 “诶让我 netfilter 看看到底上层传进来的是哪个域名的小婊砸”。

那我们已经明白了不可能直接让内核去拿到域名,那我们该怎么让 iptables 知道到底要代理哪些域名呢?

其实方法不唯一,这里咱们可以随便举几个例子:

iptables -I OUTPUT -p tcp -m string --string "qq.com" --algo bm -j DROP

上面这行代码就做到了拦截所有发往 “qq.com” 的请求。

那它是怎么做的呢?

其实你看它里面用了一个 “-m string --string qq.com” 这么一行命令,“-m” 是表示要给 iptables 使用哪个高级模块,所谓高级模块就是能给 iptables 提供一些高级点功能的方式,这里使用了一个 “string” 模块。“string” 可以对数据包携带的应用层的 payload 做分析,我们刚才说其实应用层的协议也就是一个个字符被放在数据包的屁股后头,所以我们就可以通过这种 “匹配 payload” 中字符串的方式去让 iptables 知道,我们到底要对哪些域名做拦截。

ただし!但是!

不知道你有没有想到另外一个问题,“string” 模块虽然也许能知道应用层协议中有个 “qq.com”,但是它可能无法分辨出这玩意儿到底是 http head 中的域名还是 http body 中的随便一个数据。如果我们要是往 “www.baidu.com” 发个请求但是往 body 里带个 “www.qq.com” 的话,那没准这个方法就凉了。

所以该方法不建议使用。

哪还有无其他办法呢?

方法还有,不唯一,比如我们知道域名要做 dns 解析对吧,而 dns 解析的默认端口号是 53,所以我们可以做一条 iptables 规则去拦截 53 端口的请求,然后就可以拿到域名。但是这样其实也会有些问题,比如这样要拦截所有的域名请求,成本略大,一个处理不好所有请求就都凉。另外最重要的一点就是!这种命令,丫太难写!我给你举个例子:

iptables -A INPUT -p udp --dport 53 -m string --hex-string "|03|www|05|baidu|03|com|" --algo bm -j DROP

你看那个恶心的 "|03|www|05|baidu|03|com|" 是什么鬼,我反正是看到这个东西的第一眼就放弃它了。

那么还有没有其他办法能够处理域名呢?

其实也有,不唯一,这里我给大家提供一种我使用的思路,当然我不能保证该方法是最好的或者最完美的,它可能也会存在一些问题,不过思路十分简单。

我们知道访问域名,实际访问的也还是 ip 地址,只不过计算机会提前根据域名通过 dns 去解析一下 ip,所以我们就可以利用参考这个思路,我们可以在用户创建了规则的一瞬间,就自己手动隐式地帮用户想要请求的域名去做一下 dns 的解析,然后能拿到一个 ip list,之后只要将 list 中所有的 ip 都传给 iptables 就 ok 了。

不过正如我所说的,该方式也并不完美,比如 dns 服务器刷新了 ip 地址怎么办(当然你可以通过定时刷新 ip list 的方式解决)?比如解析回来的 ip 地址里有 ipv6 要怎么处理(iptables 其实也可以处理 ipv6,就是可能麻烦点)?总之,每种方法都各有千秋,也都可能会存在某些问题或盲区,大家可以再去思考有什么方式能够去处理域名的代理,只要有道理我觉得就都 ok,另外如果大家有什么好的思路也请你分享给我,十分感激~

如何代理 https?

好了我们在上面提供了一些思路用以解决如果让 iptables 能够代理域名,接下来该解决这个更为棘手的 “如何代理” https 的问题了。

What is https?

我们这里不对 https 的握手原理,加密原理等方面做过多的介绍(我也不太懂),这里我们就只简单的介绍一下,如果说一个客户端想要和服务端建立 https 链接的话,都需要哪些东西以及大概要做哪些事儿:

  • 服务端需要有俩东西,一个 “数字证书”,一个 “私钥”
  • 客户端要向服务端去下载服务端所持有的那个 “数字证书”
  • 该证书一般都得是牛逼的机构颁发的,不然客户端(比如浏览器)就会抛出 “不受信任的证书”
  • 当然也可以把这个 “不受信任” 的证书加入到系统受信任中,一般操作系统都会提供这个口子
  • 客户端下载到这个 “数字证书” 后,服务端用自己的 “私钥” 加密数据传给客户端,客户端用拿到的 “数字证书” 中的 “公钥” 去解密服务端传过来的数据
  • 如果客户端想和服务端进行双向的 https 通信,那客户端需要准备的东西以及服务端要做的事情也都差不多

以上就是建立一套 https 的通信大概需要的东西以及要做的事儿,更详细的过程或者其中原理,大家可以自行通过一些方式去了解,我这里给大家推荐知乎上一个大佬的回答,我个人觉得回答得十分通俗易懂,地址如下:

数字证书的原理是什么?612 赞同 · 119 评论回答

那么我们该如何代理 https?

我们在上面简单介绍了一下 https 的通信过程,其中说到之所以能建立 https 的通信是因为服务端提前准备好了一个用于加密数据的 “私钥”,然后客户端会先请求回来一个能够解密数据的 “证书”。而我们一般本地开发中的服务都是直接挂载什么 “http://127.0.0.1:xxx” 或者 “http://localhost:xxx” 之类的 http 服务上,也就是说我们的本地服务其实是没有上边说的那俩东西的。

那么我们现在是不是就很容易能想到,我们可以干一件事情,什么事儿呢?这事儿就叫做 “中间商赚差价!”。

没错了,我们可以通过本地临时启动一个 https 的服务器,该服务器使用我们提前准备好的 “数字证书” 和 “私钥” 启动 https 的服务,然后我们不直接让我们要代理的 https 服务的 ip 直接和我们真正想要使用的本地服务做通信,而是让 iptables 把 https 的服务请求,转发到我们临时启动的这个用户看不见的 https 本地服务的端口上,然后再通过一些方式,让这个临时启动的隐藏的本地 https 服务原封不动地把用户要代理的 https 服务里的什么请求头,请求体啥的,都通过 http 的方式给无缝转发到我们真正的跑在本地的后端服务上,之后等真正的服务响应请求以后,我们再原封不动地转发给客户端,这样,我们代理 https 请求到本地的目的就达到了!怎么样,是不是通透了~

接下来,我们就尝试自己动手实现这一套活儿~

这套东西全弄下来,虽然从思路上来讲是比较容易的,但是真的干下来,还是会或多或少踩一些坑儿的,我会把我实现的思路尽量完整并且简单地给大家介绍一下,大家也可以根据自己的思路和想法进行自己的修改。


开干!

操作环境

这里我先给大家说一下我的代码实现环境:

  • Linux Ubuntu 20.04
  • go version 1.13.8

大家不一定非得和我这个环境完全一模一样,可以用其他系统比如 centos 之类的。不过一会儿的代码实现中,有些系统的绝对路径可能需要进行更改,大家遇到需要使用系统某些绝对路径的时候可以自行查阅自己的环境中的路径。

另外大家也可以用 mac 之类的操作系统,不过由于其他系统不一定支持 iptables,所以你可能需要再去查阅一下你的操作系统有没有类似 iptables 的系统防火墙(比如 mac 下的叫 pfctl)。

另外我的 go version 稍微有点点低,大家可以自行使用新版本,其实我当时用这个 1.13 版本的时候就遇到了一个我想要实现的效果但是该版本不支持这个能力的情况。大家也可以自行使用其他语言去做,不过建议在选择一个语言之前,一定提前先了解好,该语言下是否有足够成熟的可以直接操作 “代理软件(如 iptables)” 或 “证书加密软件(比如 x509)”的第三方工具。我之所以选择 golang 也是因为它操作 iptables 和 x509 的第三方库比较成熟并且 demo 比较多,说白了就是 “好抄!”。

当然了,如果你头铁都自己手写的话我也没意见。如果你都自己手写的话,请一定开源一下让我舔一舔,感激不尽~

整!初始化命令行

我们要实现的效果是 “一行命令搞定”,所以理所当然需要我们的程序是一个命令行程序,这里我使用的是一个叫 “dcommand” 的用于 golang 中的命令行解析工具:

这个 “dcommand” 建议大家不要用,为啥呢,因为这玩意儿是以前我为了实现我们自己的一个比较特制的命令行功能临时写的,所以当时设计时候也没考虑太多,现在来看设计上会稍稍有一些小问题,所以建议大家不要用。不过我这里用了,因为对其代码相对来说比较熟悉,而且也能实现我们想要的功能(反正都是自己的项目可以随便玩儿~)。

我们来简单看一下这段代码,其实意思很简单,就是注册了一个名为 “easy-proxy” 的命令,然后该命令可以传 “set”,“del”,“fresh”,“list” 等四个操作符,每个操作符有自己的一些 flag,比如 “set” 命令,我希望通过 “--source” 或者 “-s” 表示我想代理哪个 https 地址,“--target” 或者 “-t” 表示我想给它代理到哪个真正的服务上。

总之这段代码很简单,就是定义了一些 “增”,“删”,“查” 的命令行使用方式。

封装 iptables

通过一些成熟的第三方库实现 iptables 功能调用其实蛮简单的,我这里也没有做过多的操作,基本就是封装了一个函数用来把 ip 代理到 ip:

使用方法可以参考我的测试文件,表示 “将发往 1.2.3.4 的请求都代理到本地的 13190 端口”,然后会返回一个 “取消规则” 的函数:

总之我们只需要能够封装一个 iptables 能让其把一个 ip:port 给代理到另外一个 ip:port 就 ok 了~

搞个 ”中间商“

其实到这里,我们已经可以实现对 http 以及 ip 请求的代理了,对于域名的东西只需要先用 dns 提前解析一下好了。我们重点是要实现一个 https 的代理功能。

我们在上面说想要实现 https 的代理则需要有个 “中间商”,这个中间商就是一个对用户不可见的隐藏的本地 https 服务器,该服务器的作用就是承接真正的 https 服务的流量然后转发到我们本地真实服务后,再响应回给客户端。所以接下来我们就尝试实现这么个 “中间商”。

我们想要实现这个中间商,就需要像我上面说的,需要提供能够 “创建数字证书” 以及 “创建私钥” 的能力。但是光有这俩能力还不够,还记得我上面说,你的服务端证书必须是牛逼公司认证的,否则类似浏览器啊,curl 啊,或者 nodejs 里,他们都会认为你这个证书不可信,所以解决这个问题的办法就是要将你自己的 “假证” 给想办法干到 “系统受信任” 证书列表中。

这个将其添加到 “受信任” 中的操作,每种操作系统可能不一样,比如我在 Ubuntu 下的话,将一个证书给搞成 “受信任” 的话,需要将你自己创建出来的证书里的内容,给手动添加到某个固定的路径下的某个文件中,然后 Linux 会认为这个文件里记录的所有证书都是可信任的,所以当 curl 的时候或者 nodejs 等方式去解析证书的时候,才能发现 “嗷,原来这个服务端的证书是可信任的”。

对于这个文件的路径,每种 Linux 发行版可能不太一样,比如 Ubuntu 的话该文件的路径就是在 “/etc/ssl/certs/ca-certificates.crt” 这里面,该文件中的所有证书都可信任,而其他发行版可能会有些不一样,大家可以自行去修改路径,或者如果你有兴趣和我一起共建的话,也可以给我这个 repo 提 issue 或者 pr,我们一起来支持全平台~

好,现在回到我们要实现的证书相关的功能介绍:

该代码主要实现几个功能:

  • GenCertificate:该方法要创建一个 “数字证书” 和一个 “私钥”,其主要接收一个 domain 参数,也就是你要代理的 https 服务的域名,比如百度的,这里 domain 就是 “www.baidu.com”,第二个参数 path 表示要将 “证书” 和 “公钥” 输出到哪个路径下。

    这里简单解释下为啥一定需要一个 domain 做参数,因为在创建证书时,证书内容需要你指定 “该证书要颁发给谁”,如果客户端访问了一个域名,但是拿到证书后发现这个证书中 “颁发给谁” 这项和自己刚刚发出去的请求中的域名对不上的话,那本次通信也会凉凉,所以如果你想代理 www.baidu.com,就一定要给百度自己单独准备一张唯一的证书。

    该方法具体是如何创建证书和私钥的,大家可以自行研究,其中我简单嘱咐一下,在创建证书时候,证书模板不能瞎写,证书分为两种,一种 OV 类型,一种 DV 类型,OV 类型的证书像 curl 程序或者 nodejs 发出来的请求可能都解析不了,具体原理我也没太仔细去了解,如果大家感兴趣的话也可以和我分享一下,反正我当时是不知道加了哪个参数导致创建出来的证书是 OV 类型,我的客户端死活解析不了,可愁秃了我了:

  • SetCertficateToSystemByCertPath 和 ExecCertificateUpdateBash 这俩方法,联合起来干一件事儿,就是把上面那个 GenCertificate 方法创建出来的证书添加到系统默认的受信任根证书的目录下,并更新系统的证书服务让其能识别这个新添加的证书。其实简单来讲就是通过一些命令和一些脚本把证书内容添加进上面咱们说的那个文件里了:

  • RemoveCertificateFromSystemByCertName 方法,当然也别忘我们除了创建证书,还要有能干掉证书的能力,否则这个证书一直存在于系统中,也会占用一定资源,删除原理也很简单,干掉生成的证书文件,然后从上面那个 “系统信任” 的文件中把添加进去的证书内容移除就 ok:

到这里我们可能会用到的证书相关的函数大概都介绍完了,具体用法可以参考我的测试文件。然后我们继续来实现一个 “中间商” 的能力。

接下来我们实现一个启动本地 https 服务的能力,因为我们说过,要有个隐藏的,用户看不见的,本地的 https 服务器,它才是这个 “中间商” 的真正实体:

这里我们给这个要启 https 服务的功能,单独做成一个子项目出来,同样也是通过命令行的方式启动,具体使用方式可以参考测试文件:

上面的意思表示,该功能是通过命令行启动一个服务器,命令的名字叫 “easy-proxy-service”,然后 “start” 指令表示要启动一个服务器,“-ip” 表示要监听本地的 “127.0.0.1”,“-port” 表示要监听哪个端口,“-user-ip” 表示这个 https 后面真正要代理到那个用户的真实服务 ip,“-user-port” 表示用户的真正目标服务的端口号,“-cert” 表示我们这隐藏 https 服务器要使用的证书,“-key” 表示这个隐藏 https 服务器要使用的私钥。

这里你可能会疑惑,为啥这里我们要通过这种单起一个子项目,并且用命令行的方式创建本地 https 服务呢?

其实这是由于某些语言(比如我用的 golang)的一些特点导致。golang 中创建的服务,一般都是 “多路复用的 IO” 模型,并且创建一个服务后会直接 hang 在创建服务的那行代码那里。

简单来讲,就是你在 golang 里创建一个 socket 套接字来收发请求,它内部会启动一个死循环来不停地看你当前进程中有哪几个 socket 此时此刻身上有要发送的或者要接收的数据。因此它会卡壳在那里。而我们要做的是一个命令行工具,如果我们直接在命令行的进程中去创建这么个服务器,那整个命令行的功能就会卡死在终端。

有同学可能会说那直接用 gorutinue 启动一个协程去做不也不会 hang 住么。确实使用协程可以不 hang 住,但是协程是一个用户态的 “进程”,它会随着你的主进程退出而退出,也就是说你的命令行执行完了,退出之后,你的协程也凉了,根本起不到能代理你后面服务的工作。

并且,还有个原因,我们需要让这个 https 服务器在后台运行,不代表它要一直运行,一旦当我们的命令行提供了 “删除规则” 之类的功能,那么相对的这条 https 代理的那个本地 https 服务器也要退出的,否则就是浪费资源。因此我们一定需要一种能够让服务在后台运行,并且还能够拿到这个后台进程 pid,以便我们后面根据这个 pid 干掉没用的服务的方式。

所以这里我们选择将 “创建本地隐藏的 https 服务器” 的能力也做成命令行工具,这样的话我们就可以使用 golang 提供的 exec.Command 来执行命令让其在后台运行并且还能拿到它的 pid 号。

当然你也可以使用其他语言,有些语言的特性就是你创建服务器不会卡壳,比如 nodejs 这种基于事件循环的异步 IO 模型,就可以避免使用 golang 如此麻烦的问题。

然后至于这个命令行工具的能力,我们之前说过,它要做的事情就是承受来自客户端发出的 https 请求,并将其无缝转给真正的后端服务器,这里我们看下代码,我是直接使用了 golang 提供的 http/httputil 这个官方库的转发功能,非常方便,这里我们不细说了,大家可以自己看:

好了,到目前为止,我们需要的 “中间商” 的能力基本上就算是都完事儿了,相当于我们现在已经提供了基本的 “代理 ip” 能力,“创建证书和私钥” 能力,“创建本地 https 服务器” 能力,我们需要的几种基本能力都已经 ojk 了,剩下的就只有去组合这些能力,以达到我们最终 “一键代理” 的目的。

组合能力

其实到这里我们主要的东西都弄得差不多了,只剩下组合这些能力以做到一个更牛逼的上层能力,我相信就算我不讲代码,大家也一定能轻车熟路地自己完成。

所以这里我就只简单看一下 “set” 命令,也就是创建一条代理规则的大概实现,以及其中可能需要大家注意的一点点小问题:

该命令实现的东西稍微有些多,无法一一给大家解释,咱们直说其中一些关键函数的大概意思:

  • ProxyDomainToIp:顾名思义,他要解析域名,然后把域名对应的 ip 给代理到对应的 ip 上去:

它主要其实就是先通过一些方法去解析一下域名,获取到一个 ip list,然后调用一个核心的方法 “proxyIpsToIp”

  • proxyIpToIp:该方法主要是做一下分流,把 http 服务和 https 服务区分开,因为 https 的服务中间需要加一层,要做的事情相对来说多一些,所以对于 https 走 “proxyHttpsIpsToIp”,对于 http 走 “proxyHttpIpToIp”:

  • proxyHttpsIpToIp:该函数略长,大家不想看也没关系,大家先简单看一下,我后面再简单解释一下它做了什么:

其实也没啥,就是由于一个域名可能会同时对应多个 ip,并且有可能用户想代理的并不是域名场景,而只是单纯的一个 https 的 ip 场景,所以这里对于域名场景的处理和 ip 场景的处理稍微有些区别。

区别主要就在于,对于域名场景,要把多个域名解析出来的 ip 给代理到同一个本地隐藏的 https 服务上,就比如百度的域名,假设对应 10 个 ip,这是个 ip 都能访问到百度,那我们是不是没有必要对每个 ip 分别都创建一个本地的 https 中间商呢,假设真的创建了 10 个中间商,但是 10 个中间商做的事情完全一样而占用的资源确实实打实的,因此我们一定要考虑到这点,不能造成太多的资源浪费。

而对于单纯的 https://ip 的场景,我们就要为每个 ip 分别创建一个本地的 https 中间商了,因为我们无法判断每个 ip 背后对应的服务到底是不是同一个,因此这种情况下,资源消耗的多点就多点吧,谁叫用户爸爸需求量大呢。

其它补充

OK,再具体的细节我就不多说了,大家感兴趣的话可以自己在源码中查看。另外我再说一点,可能有同学注意到上面截图中的代码里有一些奇怪的看似没啥用的代码,类似 “p.setCaInfo” 或者 “p.setPort” 以及 “p.setPID” 之类的。看上去去掉他们好像也不影响主流程。这里我给大家简单解释一下。其实这些代码是为了后面能对我们创建的规则做 “删除” 或者是 “查询” 等功能准备的。不知道你有没有注意到这里:

这是我们 “set” 命令最开始执行的那个 “ProxyDomainIpToIp” 函数中的一段逻辑,其中我们创建了一个 Identifiter 数据结构,以及最后调用了一个叫做 “p.writeProxyInfoToDisk” 的函数,这个函数会把我们本次创建的一些代理的信息刷到计算机本地磁盘上,大概长这样:

也就是说,我们每创建一条代理规则,都会往本地的某个目录下写入一个本次代理规则的一些信息。由于我们的是命令行工具,所以我们如果想要删除某条规则或者查询的话,就只能通过这种方式来做了,包括后面清掉 iptables 规则,关掉 https 中间商服务器等等。

到这里,”set“ 命令的主要功能就都说完了,基本上实现完了这个命令后,我们就已经可以对 https 和 http 服务做一键代理了。

只不过,后面我们一定要考虑好去和去清除这些规则,不然这些东西在机器上留着是很耗费资源的。至于如何清除以及如何查询,我们就不一一贴代码了,都是很简单的事情,大家可以自行查看代码,也可以自己去实现一把,只要记住,iptables,证书,还有代理服务器,这三个东西,一定都要好好清除掉,记住这点,就可以很好地去继续拓展这个命令行工具了。


结尾

到这里,我们手把手实现了一个,基于 iptables 的,可以一键对 https、http、ip 等代理的命令行工具,相信结合着前两篇关于 iptables 实现原理的文章来看,大家应该能对 iptables 有个比较粗浅的认识了。

并且此次我们实现的这个 ”easy-proxy“ 工具,在业务场景中其实也有很多的用处,只不过现在它仍然有些问题亟待解决,其中最重要的就是当前我只是实现了 Ubuntu 系统下的 ”一键代理“,对于其他平台暂时还没有过多的精力去支持,如果大家感兴趣的话,可以自己尝试去支持一下其他平台,或者你可以给我的 repo 提 pr,咱们可以一起共建,一起去完善这个 ”easy-proxy“,相信它或多或少都能给你平常的学习或者开发中,带来一些新的思路。起码我期待如此~

最后再次贴一下源码地址,在线乞讨一下星星,如果各位大佬感觉我写得还算是用心,希望能给哥们儿点颗星星,如果对于代码中觉得哪里实现的有问题,或者哪里可以有更妙的实现方案或者思路的话,都可以随时告诉我,小弟将不胜感激~

源码地址:

github.com/y805939188/…