# tcp proxy 的调研

## 1. 主题

本文主要是想介绍一些关于 tcp proxy 的一些技术; 比如有 envoy、vpn、目前比较流向的技 (fan) 术 (qian) 等; 会偏向科普系列，细节可能禁不起推敲，主要是为自己而记录的文章;

## 2. 最近遇到的问题

之前遇到了 hook connect 之后导致 connect 不支持了 non-blocking 语义，关于这个问题目前我们已经找到了一种解决方式，但是我觉得真心不优美，需要客户端也进行改造，没办法把这个跨网络的组件变成通用化；所以自己还是去研究了各种技术，看能不能提出更加友好的方式来进行的解决。当然最后的结果是如果不想有超级大的改动，目前我们的方式是比较优的，但是比较丑的；其实遇到的问题的本质就是 connect 的语义问题，那如何让它支持原来的语义呢；通常计算机界有一个通用的解决方法:"再加入一层用来解偶两层之间的关系"，在我们这个场景中，这个方式是可以被接受的，不可接受的其实在于成本问题。

## 3. 我的调研之路

### 1. [envoy](https://github.com/envoyproxy/envoy)

之前在了解 k8s 的时候其实理解了一个概念叫做 sidecar 模型，而且出了一个比较流行的模型叫做 service-mesh，先不管这是什么；关键点在于 service-mesh 中的每一个 pod 有两个 container，主业务进程的 container 的所有流量都是走另外一个进程的 container，另外一个进程就叫做 sidecar 进程；下面这个图来源于[istio 的官方](https://istio.io/latest/docs/ops/deployment/architecture/)，从图中就可以看出所有的进程的网络通信都是需要通过 proxy 这个组件的，而这种模式就是目前比较流行的 sidecar 模型；

![](https://istio.io/latest/docs/ops/deployment/architecture/arch.svg)

对于这个架构，其实有几个疑问一直困恼着我，因此这次在解决这个架构的同时我也尝试解决我的疑问；疑问点在于：

* envoy 做网络哪一层的数据转发
* 在通过个 pod 里面如何做到用户**无感知 ** 的情况下将所有的流量转移到了 proxy；无感知很重要，对用户很重要的；
* 如何借鉴到我们目前遇到的问题上来;

接下来针对三个问题我说一下我在调研过程中的找到的答案；

* 首先 envoy 是做 tcp 及以上层次的数据转发；所以 tcp-proxy 是最为基本的功能；

* 如何做到数据无感知的转发的，这里面有一个关键技术叫做 iptables；

    我大概讲一下思路，技术核心点我会贴几篇文章；在 pod 启动的时候会有初始化步骤，这个初始化步骤主要做的事情就是配置当前 pod 的 iptables,iptables 是一个内核 hook，在网络数据链路上进行一些人为的介入和修改；而在当前的场景下，主要做的就是将用户 container 产生的所有的流量全部转移到 envoy 的 container 中，这样就做到了用户无感知的，因为默认就会将数据转移到 envoy 中，这个过程中可能会修改目的地址和端口，但是因为在同一个 host 层面，envoy 通过一些系统结果可以知道 iptables 转发前的真实 host/port; 

    例子：比如创建链接的过程; tcp 的三次握手其实也是 5 tuple 的模型，但是经过 iptables 的转化之后会变成目的地址和端口为本地的 envoy 的监听端口上，其实最后创建的 tcp 连接是与 envoy 创建的连接；当然 envoy 做的主要处理就是真实的 host/port 然后根据规则去找到对应的后段进行通信; 下面有几篇文章都非常好，对 iptables 和 envoy 有比较好的介绍，可以 i 详细看一下：

    * [Istio 流量管理实现机制深度解析](https://zhaohuabing.com/post/2018-09-25-istio-traffic-management-impl-intro/#virtual-listener)

    * [Envoy proxy 配置详解](https://jimmysong.io/istio-handbook/data-plane/envoy-proxy-config-deep-dive.html)

    * [Service Mesh 中的 iptables 流量劫持](http://rui0.cn/archives/1619)

    * [netfilter/iptables 原理](https://xie.infoq.cn/article/b0cfe588251d024d9114c84f3)

    * [How does a transparent SOCKS proxy know which destination IP to use?](https://unix.stackexchange.com/questions/166692/how-does-a-transparent-socks-proxy-know-which-destination-ip-to-use)

    * [What does iptables -j REDIRECT *actually* do to packet headers?](https://unix.stackexchange.com/questions/413545/what-does-iptables-j-redirect-actually-do-to-packet-headers)

        上面几个文章看下来基本上能回到很多问题，最后两个文章主要是解释在目标地址和端口被修改的情况下如何知道真实的目的地址和端口的问题; 

        > 这个问题其实本来我没多想，不过因为组内小伙伴问了我就去搜索了一下；tcp 协议本质上就是通过 5 tuple 来找到对方了，当你的 5tuple 被修改了之后，理论上就找不到对方了；看来这个小伙伴对 tcp 的协议还是有不错的掌握的；不过在同一个机器上的如果做了类似于 nat 的操作，操作系统会在内存中保存这个信息，所以可以通过特定的方式来获得；

* 如何借鉴

    目前看下来 envoy 就是基本功能是 tcp-proxy，那其实和我们之前想到的方案是类似的，通过启动一个 proxy 来做流量的转发；如果是这样的话，那就会遇到一个问题，可能是所有的 tcp-proxy 的问题：

    > 当一个连接被创建的时候，并不能表示这条连接已经是可用的；因为你只是和本地的 proxy 创建了连接；当然其实这个重要吗？我觉得可能也不咋重要，就是将 connect 断开的 event 转化成了 read/write failure 的 event，甚至于在 proxy 环境下面可能还可以做到对后端业务变更有这一定的冗余性；
    > 另外一个问题是健康度的问题，因为 connect 的健康程度无法反应整条链路的现状；

    当然用 proxy 的方案就必须要思考生命周期的问题，envoy 的好处在于用了 pod 的管理机制，业务进程和 envoy 是同生命周期管理的；但是目前在我们的场景上可能做不到容器化改造，所以不大行；但是一》个同事说直接写到一个进程里面不就行了吗，哈哈哈说的也是哦，当然就导致复杂度变高的，但是理论上是有可行性的；

### 2. vpn 

因为脑洞开的有点大，思考到了如何做网络层包的转发；通过网络层包的转发来做到 tcp 层的无感知，这个就让我想到了 vpn； 

> vpn 的本质：对 ip 包进行重新打包成 tcp 包，然后发送到特定的 vpn 的 service；vpn-service 知道如何解析这个包；就是将包中的 data 拿出来再作为 ip 包往内网中路由即可；

能实现这个的本质还是在于 tcp/ip 的分层模型，下层不关心上层的任何东西，上层的 package 变成下层的 data 然后添加头尾继续往下层发送; 所以 vpn 的原理就非常的清楚，基于对 ip 包的重新打包之后用 tcp 协议发送到对端服务；真的很厉害；在这之前我不咋清楚这个东西；还有一个问题是目前我们编程模型中如何去获得 ip 包呢，这块我没有太研究，具体可以看下面两文章，我大概清楚可以通过创建虚拟网卡的方式来做，具体咋做还没试过：

  * [Linux 平台 VPN 技术概论](https://juejin.cn/post/6844903736716820494)
  * [VPN 原理以及实现](https://paper.seebug.org/1648/)

那么 vpn 技术在我们的这个场景有用吗？目前场景可能没什么用，而且通过 VPN 转一层的消耗应该不小;

### 3. socks5

这是一种新的 proxy 模型，只是做 tcp/udp 和之上的代理；目前大部分用在技 (fan) 术 (qian) 领域；其实和 envoy 的架构很类似，有 client(proxy), server(proxy-server)，通过 proxy 进行数据的转发，当然过程中会通过 socks5 协议的加密和解密的过程来保证数据的安全性，通过这样将数据发送的 proxy-service 上，proxy-service 再将请求去访问真正的远程的服务；这么看的话，envoy = SOCKS5 中的 client + server 的功能；

目前这个技术被应用在我们的场景中，但是就是因为引入了这个，在构建 socks 的 auth 过程会导致阻塞；但是这又是不得不做的环节；

### 4. 脑洞大开系列

* hook server 的 accept 系统调度，最后发现不行，因为不知道真实的 host/port 的地址；

## 4. 最后

权衡最后依然还会使用一开始的方案进行，目前在我们场景用进程内部 proxy 是比较合适的，但是成本略高，而且需要关心的东西会变多了，可控性上会差不少；但是唯一的优点是无感知吧；
