如何才能安全地使用端对端加密(比如Signal):准则

 https://iyouport.substack.com/p/signal?s=r

应该使用端对端加密,这不必细说了;幸运的是如今很多人已经理解到使用端对端加密的重要性,尤其是,人们已经知道,安全不是哪个软件能完全解决的问题,安全必须是一套科学的思维习惯 ➕ 科学的操作方法有效的协作

Signal 也一样。作为一个安全工具,如今它是许多群体相互联系和沟通的首选渠道。用户团体的意图和目的可以有很大的不同,从制定晚餐计划到组织下一次反抗运动。

但是,仅仅使用 Signal 还不足以保证您和您的朋友的安全 —— 安全只有在堵住最薄弱环节之后才能有保障,在通讯软件上,“最薄弱环节” 就是:您与之交流的所有人中最不注重安全性的那个/那些人

因此本文将概述一些准则和协议,以帮助加强您的个人和集体安全实践。

请注意:端对端加密工具不只有Signal!本文中强调的大部分准则适用于所有端对端加密通信应用

当创建一个新群组时

加密通信的技术能力本身会给人们一种误解,似乎 “我终于可以随便说话了”,并不是这样的!还记得英国间谍搞出来的入侵 Signal 的方法吗?是的,最危险的部分在于人,在这里看到《关于幽灵用户和开裆裤》。

创建群组要有目的性,添加成员同样需要有目的性。在将任何一个人添加到您的联系人列表之前要知道:一旦某人成为群组的一部分,您就不可能简单地将其删除了。您不可能在完全放弃这个群组或者要求此人离开之前删除他。

📌 您随时应该知道您加进来的人都是谁。

考虑一下您会怎么处理一个一开始就被单方面添加进来的人,单方面添加指的是比如无法双向认证的,或者是让群组中其他人对此有顾虑的。另外,在没有事先征得群组中其他人同意的情况下,不要把新人加到敏感的群组里。

群组建立后,您需要说明此群组的目的,并立即点名 —— 可以完全根据您的需求,不论是不是实名,或者所有成员都使用代号,只要彼此知道其他成员都是谁就可以了。这些应该在对话开始之前完成

不要在没有问过群组成员的情况下添加新的人进入,并给人们一个指定的时间来表达意见,比如24小时,因为许多人不可能每时每刻都在手机上活动。鼓励原成员对新成员提出问题或进行阐述,这比简单地说 “您担保” 更有助于创造良好的对话。

每次增加新成员时,都要重新点名,这样您的群组其他成员就会知道自己正在和谁说话。

担保

这里的的担保就是它本来的意思 —— 即 支持是真实的、确定的、可靠的,或 证明、保证、认证。在政治背景下,为某人担保指的是,您相信此人致力于团队的目标,值得信赖,可靠和负责任

📌 请记住,担保是对您个人意见的反映,建议谨慎使用担保,因为如果是不负责任的担保,就会影响群体的安全、信任和凝聚力。

担保是某人获得一种凭证,这样的 “凭证” 对于以前可能没有合作经验的群体来说非常重要,他们需要一定的信任和安全感才能舒适有效地合作

担保制度可以让1个人或多个人(越多越好)使用他们从团体中获得的信任,并将其扩展到他们想要带入的人身上。担保的必要性根据您工作的内容而异,越是敏感的工作,这个过程就越是需要谨慎。

担保某人是否应该能够进入一个群组的对话、是否合乎该群组的目标和目的,应该结合群组的整体目标和风险来考虑。群里的其他成员在选择是否相信您提供的担保时,都会有自己的判断力。

📌 使用担保的一些标准包括:

  • 认识此人有一段时间了,对此人有一定程度上的了解;尤其是,了解此人在该群组的目标方面的契合度;

  • 您与此人合作过一段时间,对此人的工作方式、品格和原则立场有一定了解;

  • 您的熟人中有一些人在一定时间内与此人合作过,他们对此人的信任加之您对他们的信任,可以在您的心目中给此人一个信任度的基础分数;

  • 您了解此人的优势和缺点,以及知道此人在面临镇压的情况下的反应;

  • 您知道此人如何回应批评或反馈,以及此人对自己的行为负责的程度;

  • 您了解此人的大家庭、儿时的朋友、以及整个人生故事(开个玩笑…也许是,但比较少见…)

总之,您应为以下人员提供担保:您认识和信任的人,您知道他们了解特定群体的目标和安全要求,您知道他们参加过专业的扎实的安全培训,并可以熟练使用安全操作。

不应该仅仅因为 “知道这个人的存在和其工作内容” 或 “有过一两次愉快的谈话” 就给予此人担保,虽然这些东西是了解和信任某人的一部分,但它仅仅是一部分!并非全部。

担保的严格程度会根据你们的对话的敏感度和团体承担的风险等级而有所不同。如果这是你没有考虑过的问题,现在就需要考虑(我们在不久后会更详细介绍安全文化的构建)。

📌 无论您用什么标准来担保他人,都要提前跟群组里的每个人沟通,而且每个人都需要站在同一条线上。担保这个词已经被用烂了,几乎没有详细地说明,人们对它的含义往往有不同的想法。这就是您需要沟通的地方,让所有人统一对这个概念的理解标准

请注意,数字安全不能代替关系安全!如果您决定信任的任何一个人在敏感的信息对话中,结果此人本来是恶意的、鲁莽的、粗心的、或不负责任的,那么世界上所有安全软件和安全协议都无法帮助您。

规则

当重新安装 Signal 或获得新的电话号码时,请事先通知各群组,让他们知道要接受新的安全密钥 —— 他们会看到您的 “安全码已改变” 的提示,如果他们不知道您在做什么,他们会因此害怕。

或者,如果您无法事先通知其他人,也要在事后立即让他们知道。📌 根据安全级别不同,您应该选择亲自见面、或发送安全号码的截图,来验证新的安全码。

使用安卓系统时,一定要在 Signal 应用上设置密码和超时功能。可惜iOS还没有这个安全功能。至少要为整个手机设置一个复杂的字母数字密码。从法律的角度来看,形状、图案和拇指指纹/生物识别技术都是不安全的,维稳官员可以依法强制您出示指纹来解锁手机。此外也要对您的手机进行加密。

一定要使用阅后即焚。时间设置长短根据您的具体危险状况来判断,您需要想象威胁会如何发生,据此来判断多长时间的销毁是合适的。如果您的群组中任何一个人的手机被警察抢走,却没来得及锁屏,你们所有人都会被暴露。

并且,您需要时刻关注阅后即焚时间的稳定性,有时,当群组中有人重新安装应用程序或添加了新的人员进入时,阅后即焚可能会被自动关闭。您需要确保及时地重新激活该功能。

📌 如果您正在参加反抗行动、或处于可能导致逮捕的任何一种情况下,包括过境,而您又带着手机,请事先离开所有的群组并卸载 Signal。不论如何,非常建议不要把您的个人手机带入上述这些情况中。您可以在下面阅读详细解释,以及您可以如何处理这种情况:

如果您在跨越国界,必须首先离开所有群组,并卸载 Signal。因为通常保护您免受搜查和扣押的法律基本都不适用于国际边境。如果您在国外旅行,强烈建议不要携带您自己的手机/平板电脑/笔记本电脑/等。

如果出现安全漏洞,比如您带着手机被警察抓到,或者您的家被警察突袭,您需要指定一个人开始一个新的群组,并立即离开旧的群组(也就是烧掉这个群组)。确保威胁不会转移到新的循环中。指定一个人留在旧群组上,确保所有人都完全离开了。最后删除这个群组。

在手机丢失/被盗或被警察没收的情况下,立即向与您在同一个群组中的其他人报告,让他们提醒所有成员需要抛弃旧群组并重新开始。

签入政策

有些群组是短暂的,是为了特定的短期目的而存在的,在目的完成后应予以删除。生命期不确定的群组应定期检查成员情况,以帮助消除松散的问题。

考虑建立签到/参与小组讨论的制度。要求群组成员定期重新判断自己在邮件列表或名单上的兴趣,并确保没有出现安全问题。📌 有些群组也可以选择对成员实行定期签到的要求 —— 如果在某段指定时间内没有收到某人的签到,可能表明存在安全漏洞。这在那些经常出现强迫失踪的国家来说,是很有必要的,如果您的群组成员被失踪,您需要做最坏的考虑,尤其是需要及时知道这件事。

📌 请记住。有时即使一个成员离开了群组,他们也会继续收到该群组的消息(由于软件故障)。如果该群组的信息是敏感的,某人离开此群组应被视为安全漏洞,该群组应立刻被烧毁。重建

讨论敏感资料 —— 不论是过去的、现在的或将来的 —— 应该只在所有成员 *需要知道* 的基础上进行,而不是 “我相信你” 或 “我觉得这个很酷”。吹嘘、八卦和传言都是危险的行为,在您的行动中应该没有地位。

尤其是!具体的行动规划讨论应该面对面地进行 —— 决不要以数字方式进行!所有的数字安全都有弱点和漏洞。您越了解数字安全的弱点和局限性,您就越能保护自己。

一次性手机用户指南:关于刻录机电话您应该知道的重要原则

 https://iyouport.substack.com/p/6b2?s=w

近期新闻:据《华尔街日报》报道,美国奥运会和残奥会委员会正在告诉运动员们,在参加中国的冬奥会之前放弃他们的个人手机,改用刻录机电话。

据报道,该公告去年曾两次发出,警告运动员在中国时可能受到数字监控。公告指出:“每台设备、通信、交易和在线活动都可能被监控。你的设备也可能被恶意软件破坏,这可能对未来的使用产生负面影响”。该报道指出,英国、加拿大和荷兰也提醒运动员不要将他们的个人电子设备带入中国。

委员会的担心并不是没有根据的。2019年,中国被发现在从新疆地区入境的游客的手机上秘密安装间谍软件加拿大公民实验室发现,中国的My2022奥运会应用程序(所有与会者都必须安装)充满了安全漏洞,可能导致隐私泄露、监控和黑客攻击。

早在北京举行2008年夏季奥运会时,美国国土安全部就向前往中国的任何旅行者发出了类似的警告,警告说携带任何电子设备都有可能面临 “犯罪分子或外国政府人员未经授权的访问和数据盗窃”。然而,这次的情况有点不同,由于对COVID-19的担忧,中国已经禁止所有外国观众。运动员们可能会依靠他们的移动设备与朋友和家人保持联系,这可能会在移动数据、短信和通话受限制的一次性手机上更加复杂。

但是,刻录机电话真的就能安全了吗?这是一个重要问题。

刻录机电话就是一次性使用的电话,与您的身份无关,理论上可以用来在可能被监控的情况下进行匿名通信。如您所知,非常多的行动主义安全性建议中都会提到刻录机电话。比如这里《指南:当您准备参加抗议活动…》;以及这里《买不起/买不到一次性手机怎么办?这里是您在参加敏感行动之前需要了解的事》,等等。

但仅仅建议使用,依旧是有问题的。此前我们借助1月6日国会暴动的大抓捕过程分析了这件事,所有暴徒都在使用一次性手机,但间谍机构依然实现了连锅端,关于他们是如何做到的,见《警察/间谍如何定位抓捕抗议者:连锅端的标准操作模式》。

使用一次性手机本身究竟是否是一种 “最佳做法”,还有待商榷,但如果您选择使用一次性手机,有几件事您应该牢记。

1、刻录机电话与寻常所说的 “一次性手机” 不完全一样。

如上所述,刻录机电话是一种专门为匿名通信而采购的、绝对只用一次的手机。它通常被认为是一种私密通信的手段,其功效的前提是要有完美的安全做法!

寻常说的 “一次性手机” 是指您购买并正常使用的手机(并不强调安全操作),因为您知道它很可能会丢失或损坏。

2、您的刻录机电话只能与其他刻录机电话通话。

如果您使用您的一次性手机与某人日常使用的电话通话,就会打破私密。在您和您的联系人之间留下可追踪的痕迹。

为了您通信圈内每个人的安全,您的刻录机电话应该只用于联系其他刻录机电话,这样您的关系网络就不会危及您的安全。

有很多方法可以安排,但最好的方法可能是记住您自己的号码,并与您希望沟通的人*当面*分享号码。事先约定让联系人给您发一条非常普遍的短信,这样当您打开手机时,您就可以根据他们发送的信息来识别他们,而不是用其他方法。

如果您是在与一大群人合作,在您的手机开机的情况下完成这个过程可能也是可以的。

在这两种情况下,除非您有非常重要的信息需要传递,否则没有必要回复接收到的信息。也请记住,您应该尽可能少地通信,以尽量减少对您的安全的潜在风险。

3、永远不要在您的家里打开刻录机电话!

这是最常见的也是最严重的暴露身份的误操作。我们此前介绍过这点,见《到底有没有无法被监听的电话?:变得难以被追踪的简单方法(8)》。

由于手机随时都在记录和传输位置数据,您不应该在任何已知您的身份的地方打开一次性手机。这显然包括您的家,也应该扩展到您的工作场所,您的学校,您经常去的健身房,以及您经常去的任何其他地方。

4、永远不要在您的常用手机附近打开您的刻录机电话!

如上所述,手机就是一个带有额外的酷炫功能和特性的跟踪监视设备。正因为如此,您不应该在您的 “真实” 手机附近打开一个刻录机电话。因为这会导致形成一个数据线索  — — 将您表面上匿名的刻录机电话与您的个人身份相关的手机同时放在同一个地方,对于监视者来说就很容易破解您的身份。

这也意味着,除非您在一个很大规模的人群中,否则您不应该在您的重要联系人的常用手机附近打开您的刻录机电话。

5、通信时永远不要提及自己或任何联系人的名字。

鉴于使用一次性手机的目的是为了保持您的匿名性和您周围人的匿名性,那就永远不要说出名字和常用昵称/绰号。

通过一次性手机通信时,不要使用任何人的合法姓名,也不要使用您在其他地方使用过的假名。如果您必须使用一个假名,那么这个假名必须是唯一的,是事先确定的,并且只使用那一次,绝不重复使用。

您可以考虑使用一个无害的暗语进行交流,而不是使用任何名字。比如: “嘿,你想在星期二吃早午餐吗?” 而不是 “嘿,我是李大头”。这也允许呼叫和回应作为彼此间的身份认证。例如,如果您的联系人对你的早午餐邀请的回应是:“当然,让我检查一下日程,然后再给你答复”,您就会知道您打算联系的人是正确的人了。

此外,这种认证做法允许使用紧急求救代码  — — 比如 “我不能参加早午餐了,我要去上瑜伽课”,如果您试图协调的人遇到了麻烦,可以使用这个紧急求救代码,收到这样的消息时您就会知道出事了。

6、谨防黄貂鱼(IMSI捕手)。

必须保持您的身份认证暗语和紧急求救暗语尽可能无害的一个原因是,世界各地的执法机构正越来越多地使用IMSI捕手,也被称为 “黄貂鱼” 或 “基站模拟器”,在其范围内捕捉任何短信和电话。这些设备假装成手机信号塔,拦截并记录您的所有通信,然后将它们传递给真正的手机信号塔,这样您的目标联系人也会收到这些信息。在这里看到

正因为如此,您不应该使用刻录机电话发送直言不讳的短信,比如 “嘿,你在抗议活动现场吗?”或者 “你带了燃烧瓶吗?” 这是非常愚蠢的。

在正常情况下,使用 Signal 等加密通信可以相当有效地规避黄貂鱼的威胁,但由于刻录机电话通常不具备加密信息的能力(除非您购买的是刻录机智能手机),因此有必要对您所说的任何话加以编码 — — 变成暗语。

请注意!使用Signal 等加密通信是有规则的,遵守这些规则才能实现安全,否则端对端也无法保护您。具体措施见《如何才能安全地使用端对端加密(比如Signal):准则》。

7、刻录机电话必须是一次性使用的。

刻录机电话是为了使用一次,然后被视为 “烧毁”。这有很多原因,但主要原因是,避免您的秘密行动相关的线索被联系起来。如果同样的刻录机电话开始出现在同类的反抗行动中,追踪调查这些事件的人就会有更多的数据来建立档案。

这意味着,如果您所做的事真的需要一个一次性手机,那么您所做的事每次都需要一个全新的、安全干净的一次性手机。不要让安全措施的草率执行否定了您的所有努力。

8、购买刻录机电话时应该非常小心。

您的一次性手机应该是不可追踪的。这意味着您应该用现金支付;不要使用任何银行卡。

问问自己:在您购买手机的地方或周围是否有监控摄像头?不要把您的私人电话带到您购买刻录机电话的地方。

考虑步行或骑自行车去购买,不要开车,您的车牌就是您的身份证;用非常朴素的衣服或化妆品遮盖您身上容易被识别的特征(比如明显的痣、疤痕、纹身等细节);不要在您经常去的任何地方购买刻录机电话,以免工作人员认出您  — — 您永远都想不到有多少人认识您的脸,尽管您从未和他们说过任何一句话。

非常建议您阅读《完美隐身》系列教程,这其中介绍了很多隐藏自己的方法。此处链接为最后一集,其中有本系列完整列表。

9、千万不要认为刻录机电话是 “绝对安全的” 或 “绝对可靠的”。

为了使刻录机电话能够保护您的隐私,参与通信圈的每个人都必须保持良好的安全文化

安全地使用刻录机电话要求联系人网络中的每个人都采取适当的预防措施并保持良好的隐私状况:因为一个人的失败会危及你们所有人。

因此,既要确保与您交流的每个人都能在安全和正确使用刻录机电话方面保持一致,也要假设有人可能会粗心大意。这也正是为什么要强调即使在使用一次性电话时也要非常小心通信内容的另一个重要缘由。

始终对自己的安全负责,并在必要时毫不犹豫地删除和丢弃您的一次性手机。⭕️

使用 TUN 的模式

https://zu1k.com/posts/coding/tun-mode/

TUN 是内核提供的三层虚拟网络设备,由软件实现来替代真实的硬件,相当于在系统网络栈的三层(网络层)位置开了一个口子,将符合条件(路由匹配)的三层数据包交由相应的用户空间软件来处理,用户空间软件也可以通过TUN设备向系统网络栈注入数据包。可以说,TUN设备是用户空间软件和系统网络栈之间的一个通道。

TAP 是二层(以太网)虚拟网络设备,处理的是以太帧,更加底层可以拿到更多信息,但不在本文的讨论范围。

我们想要利用TUN来做一些事情,实际上就是要编写一个用户态程序,拿到 TUN 设备句柄,向其写入序列化的IP数据包,从中读取数据并还原成IP数据包进行处理,必要时需要取出其payload继续解析为相应传输层协议。

通常使用 TUN 技术的是 VPN 和代理程序,然而这两类程序在对待 TUN 中传递的 IP 数据包时通常有不同的行为:

  • VPN 通常做网络层的封装:将拿到的 IP 包进行加密和封装,然后通过某个连接传输到另一个网络中,在解封装和解密后,将 IP 包发送到该网络。在这个过程中,对 IP 包本身的修改是非常小的,不会涉及到整体结构的变动,通常仅会修改一下源 IP 和目标 IP ,做一下 NAT。

  • 代理程序 通常是传输层的代理:在从 TUN 设备拿到 IP 包后,需要继续解析其 payload,还原出 TCP 或者 UDP 结构,然后加密和封装传输层 (TCP或UDP) 的 payload。网络层的 IP 和传输层的端口信息通常会作为该连接的元数据进行处理,使用额外的加密和封装手段。

简单来说,VPN 不需要解析 IP 包的 payload,而代理程序需要解析出传输层信息并处理,特别是像 TCP 这样复杂的协议,对其处理更是需要非常小心和严谨。对于代理程序这样的需求,如果我们使用 TUN 技术,通常有两种模式:在用户态实现网络栈,或者直接利用操作系统网络栈实现。

第一种选择是在用户态实现网络栈,这是不小的工程啊,特别是实现 TCP 协议,因为其协议非常复杂,实现起来有很多细节需要注意,所以自己实现非常容易犯错。所以我们一般会直接找现成的实现来用,现有不少比较成熟且高效的实现,我相信肯定比我自己写的要好几个数量级。

  • 如果使用 C 语言,lwIP 是一个非常不错的选择,由瑞典计算机科学研究所科学院开源,这是一个轻量级的 TCP/IP 栈实现,在占用超少内存的情况下,实现了完整的 TCP,被广泛应用到嵌入式设备中,稳定性有保证。同时,lwIP 有很多其他语言的绑定,包括 go 和 rust,这使我们在使用其他语言开发时也可以选择 lwIP 作为用户态网络栈实现。

  • 如果选择使用 Go 语言开发 TUN 的用户态程序(其实这也是大多数人的选择),可以选择 Google 开源的 gVisor 中的实现,gVisor项目目的是为容器提供自己的应用程序内核,其中 tcpip 的实现有 Google 背书,质量有保证。

  • 如果选择使用 Rust 进行开发,我们的选择就会困难一点,并没有一个饱经风霜、经过时间检验的实现,在广泛对比之后我推荐 smoltcp,这是为裸机实时系统开发的独立的、事件驱动的 TCP/IP 栈,其设计目标是简单和健壮,应该可以信任吧。

  • 当然,我觉得还有一个可以期待的实现,就是 Google 为 Fuchsia 操作系统开发的 Netstack3,之前是由 Go 实现的,不过现在 Google 用 Rust 重新实现了一个新的,谷歌背书,可以期待。

在看完可供选择的实现后,我们来看一下在用户空间实现的网络栈如何使用。虽然不同在不同实现下,各个库有不同的编程接口和使用方法,但基本的思路都是一致的,这里我们便仅讨论基本使用流程。

从原理上来讲,用户态网络栈就是要不断通过协议解析,从 IPv4 数据包中不断解析出 TCP 流中的载荷数据;将传输层载荷通过不断的协议封装,拿到最终的 IPv4 数据包。

从 TUN 往外读

从 TUN 设备所对应的句柄中读出了一段字节序列,便是需要处理的IP数据包,一般是 IPv4 协议,不过还是需要先根据字节序列的第一个字节进行判断。

如果判断为 IPv4 包,就将整个字节序列扔到 IPv4 的 Packet Parser 实现中,还原出 IPv4 数据包结构。根据 IPv4 Header 中的 protocol 字段,判断 payload 应该使用哪个上层协议解析。rfc791

一般仅需要处理 ICMP、TCP、UDP 这三种协议,拿 TCP 为例,只需要将 IPv4 的 payload 扔到 TCP 的 Parser 中,即可取出我们想要的传输层载荷。(实际情况当然没有说的这么简单)

向 TUN 写数据

写的过程其实就是读的过程反过来,拿到的是某个传输层协议的 payload,就拿UDP为例,根据该数据报的元信息,构建出完整的 UDP Header,然后将 payload 内容拼接进去。

接下来构建 IPv4 Header,然后将 UDP 报文拼接进 IPv4 payload 中。在拿到 IPv4 数据包后,即可序列化为字节序列,写入 TUN 句柄了。

上面的读、写过程看起来简单,但实际需要考虑的东西非常多,包括但不限于分片、丢包、重传、流量控制等等,TCP 作为一个极其复杂的传输层协议,有巨多情况需要考虑,很明显用上面的基本思路是非常繁琐并且难以使用的。

众多用户态网络栈肯定考虑到了这一点,实现都提供了非常友好且直接的接口,可以直接创建一个 TCP/IP 网络栈实例,拿到两个句柄,一端负责读取和写入网络层 IP 数据包,另一端负责接收和写入传输层载荷,中间的复杂转换关系和特殊情况都被内部屏蔽掉了。

根据我们的需求,实际就是在 IPv4 和 TCP payload 之间进行转换,而操作系统的网络栈正好就有这个功能,我们无法简单的直接使用操作系统的网络栈代码,但是可以想办法复用操作系统网络栈提供的功能。TUN 在网络层已经打开了一个口子,还需要在传输层也打开一个口子,其实可以利用操作系统提供的 socket。

我们使用操作系统提供的 Socket 创建一个传输层的 Listener,将某个 IPv4 数据包的目标 IP 和目标端口修改为我们监听的 IP 和端口,然后通过 TUN 将该 IPv4 数据包注入到操作系统的网络栈中,操作系统就会自动的进行相应的解析,并将所需要的传输层 payload 通过前面创建的 Socket 发送给 Listener,由此便利用操作系统网络栈完成了 “往外读” 的操作。

对于“向里写”的操作,只需要向刚刚创建的传输层连接句柄写入即可,操作系统的网络栈同样会进行相应的封包,最后形成 IPv4 数据包。很明显,需要考虑反向的数据包,当向传输层连接的句柄中写入数据、操作系统的网络栈封包时,源 IP 和源端口会被视为新的目标 IP 和目标端口,因为我们需要使返回的 IPv4 数据包能够被 TUN 接口捕获到,在上面步骤中就不能只修改目标 IP 和目标端口,同时还要修改源 IP 和源端口,源 IP 应该限制为 TUN 网段中的 IP。

在利用操作系统网络栈时,通常是以下步骤,这里拿 TCP 协议举例。

在我们的例子中, TUN网络的配置为 198.10.0.1/16,主机IP为 198.10.0.1,代理客户端监听 198.10.0.1:1313,App想要访问 google.com:80,自定义的DNS服务返回google.com的 Fake IP 198.10.2.2

1. Proxy 创建 TCP Socket Listener

这里首先要在系统网络栈的传输层开个口子,创建一个 TCP Socket Listener,监听 198.10.0.1:1313

2. 某 App 发起连接

当某需要代理的App发起连接,访问 google.com:80,我们通过自定义的 DNS 服务返回一个 Fake IP (198.10.2.2),使流量被路由到 TUN 设备上。

当然这里也可以不使用 Fake IP 方式来捕获流量,通过配置路由规则或者流量重定向也可以将流量导向 TUN 设备,不过 Fake IP 是最常用的方法,所以这里以此举例。

 

3. 将 TUN 读取到的 IPv4 解析为 TCP 载荷数据

TUN 设备捕获到流量,也就是 IPv4 数据包,在读取出来后,需要利用系统网络栈解析出 TCP 载荷数据。

这一步,需要将读取到的IPv4数据包进行修改,也就是我们上面说的 源IP、源端口,目标IP和目标端口,还有相应的 checksum 也需要重新计算。修改的目的是让 IPv4 数据包通过 TUN 注入到操作系统网络栈后,能够被正确路由并通过一开始监听的TCP Socket将最里层的 TCP payload 返还给我们。

 

这里为了方便,直接将源 IP 和源端口设置为初始的目标 IP 和目标端口,在实际编程时,有更多的设置策略,也就是 NAT 策略。

4. 代理客户端请求代理服务器

此时代理客户端已经拿到了请求的真实 TCP 载荷,并且可以通过获取 TCP 连接的 peer 信息得到在第3步修改的源 IP 和源端口,通过这些信息可以通过查 NAT 表得到 App 真正想要访问的 IP 和 端口(甚至可以通过查 DNS 请求记录拿到域名信息),因此代理客户端可以根据自己的协议进行加密和封装等操作,然后发送给代理服务端,由代理服务端进行真实的请求操作。

Google

5. 将返回数据封包回 IPv4 并写入 TUN

通过代理客户端与代理服务端、代理服务端与谷歌的通信,拿到谷歌真正的返回数据,现在需要重新封装回 IPv4 数据包,还是利用系统网络栈:将数据写入 TCP Socket (198.10.0.1:1313) 中,便可以在 TUN 侧拿到封装好的 IPv4,就是这么轻松。

Kernel

6. App 拿到返回数据

Kernel

上面的过程便是利用操作系统网络栈完成 IPv4 到 TCP 载荷数据及其反方向转变的过程。通过这种办法,可以充分利用操作系统的实现,都是饱经检验,质量可靠,且满足各种复杂情况。但是也有缺点,数据需要拷贝多次,增加了性能损耗和延迟。

我这里想说的 NAT 策略不是指常说的那四种 NAT 类型,当然你可以去实现不同的NAT类型来满足各种各样的需求,但那是更深入的话题,不在本文讨论。

在刚刚的流程的第3步中,你应该发现对源 IP 和源端口的修改是有限制的,我们需要将 IP 限定为 TUN 网段,从而使返回的数据包可以重新被 TUN 设备捕获。但是这种限制是非常宽松的,在我们的例子对 TUN 设备网段的配置中,你有 2^16 个 IP 可供选择,每一个 IP 又有 2^16 个端口可供选择。

但是如果你仔细观察,你会发现上面的例子并没有充分利用这些资源,我们仅仅是将 Fake IP 作为源 IP、真实目标端口作为源端口,而这个 IP 的其他端口都被闲置了。同时我也在其他人写的某些程序中发现,他们仅选择一个 IP 设置为源 IP,通过合理的分配该 IP 的端口作为源端口,在这种情况下, TUN 网段中其余的 IP 资源就被浪费了。

以上两种 NAT 策略在个人电脑上没啥问题,但是如果代理客户端运行在网关上,网络中访问的 IP 数量超过网段中 IP 数量上限,或者 hash(ip:port) 数量超过端口总数(2^16),就会难以继续分配 NAT 项。因此我们应该专门编写一个 NAT 管理组件,合理分配 IP 和端口资源,争取做到利用最大化。

抛开事实不谈,如果我们想要代理全部流量,就是要通过路由规则将所有流量导向我们的 TUN 设备,这是很直观且朴素的想法,就像下面的命令一样单纯:

1
sudo route add -net 0.0.0.0/0 dev tun0

如果你真的这么写,你就会发现你上不了网了。这是因为出现了环路。

如果稍微思考一下,你就会发现,虽然我们想要代理所有流量,但是代理客户端与代理服务端的流量却是需要跳过的,如果用上面的路由,就会导致代理客户端发出的流量经过路由然后从 TUN 重新回到了代理客户端,这是一个死环,没有流量可以走出去。流量只近不出,来回转圈,你的文件打开数爆炸,操作系统不再给你分配更多的句柄,数据来回拷贝,你的CPU风扇猛转,电脑开始变卡。

这是我们不想看到的,需要采取一些措施避免环路的产生。在实践中有不少方法可以避免这种情况的发生,例如通过合理的配置路由规则,使连接代理服务器的流量可以顺利匹配到外部网络接口。只不过这种方法不够灵活,如果代理服务器 IP 发生变化则需要及时改变路由规则,非常麻烦,所以我们接下来介绍其他的方法。

Fake IP 就是我们上面例子中用到的方法,这是一种限制进入流量的方法。基本思路是自己实现一个 DNS 服务器,对用户的查询返回一个假的 IP 地址,我们可以将返回的 IP 地址限制为 TUN 设备的网络段,这样应用发起的流量其实便是发给 TUN 网络的流量,自然的被路由匹配,而无需像前面那样路由全部的流量,其余的流量包括代理客户端发起的请求便不会被路由,可以保证不产生环路。

当代理客户端需要知道应用真正想要请求的地址时,就通过一些接口向自己实现的 DNS 服务器进行反向查询即可。

通过前面的分析,可以发现产生环路是因为代理客户端本身发出的流量被系统路由到 TUN 设备导致的,因此我们可以想办法让代理客户端本身发起的流量不走 TUN 而是从真实的物理网络接口出去。

在 (类)Unix 系统中,可以对代理客户端的流量打上 fwmark 防火墙标记,然后通过策略路由使带有标记的流量走单独的路由表出去,从而绕过全局的流量捕获。

cgroup

cgroup 是 Linux 内核的功能,可以用来限制、隔离进程的资源,其中 net_cls 子系统可以限制网络的访问。在网络控制层面,可以通过 class ID 确定流量是否属于某个 cgroup,因此可以对来自特定 cgroup 的流量打上 fwmark,使其能够被策略路由控制。

我们可以创建一个用于绕过代理的 cgroup ,对该 cgroup 下进程的流量使用默认的路由规则,而不在该 cgroup 的其余进程的流量都要路由到 TUN 设备进行代理。

TAP 在2层,读取和写入的数据需要是以太帧结构

TUN 在3层,读取和写入的数据需要是IP数据包结构

在给网卡配置IP时,其实是修改内核网络栈中的某些参数,而不是修改网卡。虽然网卡也会有一些可供修改的配置项,但一般情况是通过其他方法进行修改的(驱动程序)。

物理网卡会有 DMA 功能,在启用 DMA 时网卡和网络栈(内存中的缓冲区)的通讯由 DMA 控制器管理,因此性能更高延迟也更低。

在Linux下一切皆文件,/dev/net/tun 是特殊的字符(char)设备文件,通过打开这个文件获得一个文件句柄,然后通过 ioctl() 系统调用对其进行配置。在这里可以选择打开TUN设备还是TAP设备,可以设置设备名称。

详见:Network device allocation

BPF 是一种高级数据包过滤器,可以附加到现有的网络接口,但其本身不提供虚拟网络接口。 TUN/TAP 驱动程序提供虚拟网络接口,可以将 BPF 附加到该接口。