iodine初步研究

Posted by Xiaoxi on December 16, 2016

iodine初步研究

Iodine 可以让我们通过DNS来隧道传输IPv4的数据。它适用于某些只能使用DNS服务的网络情况下,比如在某些防火墙的隔离下、某些Wifi登陆认证前等等。此外,它的使用需要一个TUN/TAP.其带宽是不对称的,在LAN网络下,上传速度为3Mbit/s,下载速度为680kbit/s;Wifi网络下,分别为200kbit/s与50kbit/s.

注:使用的时候,服务端和客户端需使用同样的协议,在大多数情况下,两者使用同样版本的iodine即可。不同版本的协议之间的向前兼容和向后兼容尚不能解决。


整个流程:

  1. 服务器打开本机dns0,设定内网IP(192.168.99.1),设定MTU,开始监听域名xiaoxi.pegasusux.xyz

  2. 客户端以root权限运行,通过密码开始尝试查询xiaoxi.pegasusux.xyz

  3. 自动检测寻找适用的DNS类型,顺序依次为NULL、PRIVATE、TXT、SRV、MX、CNAME和A类型

  4. 检测客户端与服务端的版本信息是否匹配。若匹配,得到当前客户端userid,并得到登陆验证信息;否则,退出;(此外,若服务器端在线数量达上限,也会匹配失败)

  5. 客户端依据收到的登陆验证信息,计算出最终验证信息,发送到服务端。服务端通过验证后,返回x.x.x.x-y.y.y.y-mtu-netmask (server ip-client ip-mtu-netmask)信息,进入下一步。

  6. 设定tun虚拟网卡的ip(如192.168.99.2),建立路由(Adding route 192.168.99.0/27 to 192.168.99.2

    add net 192.168.99.0: gateway 192.168.99.2 )

  7. 协调设定TUN的MTU值,并得到服务端IP(192.168.99.1)

  8. 尝试直接连接模式,即尝试直接使用IP请求连接到服务器IP(注:这里的IP为服务器伪装的IP,而非服务器的公网IP)。如果可达,就采用直接模式进行通信;如果不可达,尝试迭代模式;注:直接模式下使用的是Raw UDP协议

  9. 使用EDNS0插件,并依次进行上传检查、下载检查、探测适合的编码器格式。

  10. 首先,尝试lazy模式进行通信;若lazy模式失败,则适用传统模式

  11. 整个DNS tunnel 建立完毕。


“lazy-mode”(延迟模式)

iodine 支持lazy-mode(延迟模式):使用延迟模式以提高性能和减少延迟。但是,少部分的DNS中继可能无法处理延迟模式下的流量模式,导致没有或很有的数据通过。

客户端 - 服务器DNS流量序列重新排序用以提供增强的(交互式)性能与大大降低传输延迟。

想法起源于lucas Nussbaum在IFIP国际安全会议,2009上PPT(http://www.loria.fr/~lnussbau/tuns.html)

在开发过程中,没有参考任何其他项目的代码或文档。

服务器:

上游数据立即ack,以保持慢上行数据尽可能的快(客户端等待ack来发送下一个数据片段)

ping请求被应答,要么是下游的数据到达了tun,要么是新的上游ping/数据来到了客户端。

在大多数情况下,这意味着我们要回答以前的DNS查询,而不是当前一个。当前查询保留在队列中,并在下行数据必须被发送时使用。

上游数据包通常作为对先前ping包的回复,并且上游数据包本身保持在队列中。

客户端:

立即下载下游数据,以保持其快速流动(包括在最后下游段之后的ping)。

此外,在所有可用的上游数据由服务器发送(在某些情况下用尽最后一个查询)之后,发送附加的ping以为下一个下游数据填充服务器。


Raw UDP协议

所有Raw UDP protol消息都以3字节的标头开始:0x10d19e 这不是有效的DNS消息的开始,因此很容易识别。 第四个字节包含命令和用户ID。

 7654 3210
+----+----+
|CCCC|UUUU|
+----+----+

命令:

  1. 登录消息(command = 1):

    标头后跟一个与DNS登录中相同密码的MD5哈希。 客户端通过发送此消息启动原始模式,并使用登录验证信息+1,服务器使用登录验证信息-1进行响应。 在交换登录消息之后,服务器和客户端切换到原始udp模式以用于其余连接。

  2. 数据消息(command= 2):

    在头部得到有效载荷数据之后,其可以被压缩。

  3. Ping(command= 3):

    从客户端发送到服务器,然后返回,主要用以保持会话的持续。


握手流程

  1. 默认情况下,握手的dns类型是自动检测的。(检测结果一般取最大的带宽速率)
  2. 确认版本匹配情况
  3. 握手包,登陆验证
  4. 尝试能否使用EDNS0协议,以便于传输大于 512 字节
  5. 确认传输编码方式
  6. 确认下载速度
  7. 尝试延迟模式
  8. 设置片段大小
  9. 握手结束

DNS tunnel 传输类型:

默认情况下,自动检测技术会自动探测合适的DNS请求类型,以达到最大的带宽速率。带宽从高到低依次是:NULL、PRIVATE、TXT、SRV、MX、CNAME和A类型。注意,SRV,MX和A可能将通过“智能”高速缓存名称服务器获取实际IP地址的额外查找,这可能会减慢或完全失败。(其中NULL和PRIVATE类型,还不是很清楚)然而,有些DNS中继服务器会施加了偏移图片的限制,这可能导致某些DNS请求类型会失败????这不是很懂

iodine 提供多种编解码器,分别是base32、base64、base64u、base128。base128的传输效率最高,但是某些情况下会不适用。自动检测模式下,iodine会依次从base32—》base128的顺序检测,最终取一个最高级的编解码器。


其余需要注意的点

  1. iodine 支持直接转发和中继两种模式。客户端和服务端建立通信后,可以看到客户机上多出一块名为dns0的虚拟网卡。iodine支持NULL,TXT,SRV,MX,CNAME,A等多种查询请求类型,并且支持EDNS,支持base32,base64,base128等多种编码规范。iodine在直连模式下,速度相当可观。在中继模式下,使用谷歌的dns,也是Dns Tunneling工具中速度最快的。
  2. 后台会持续ping数据,保证数据连同
  3. 客户端会查询一个外部的IP用以发送数据。

OPTIONS

帮助文件

共有的选项
-v  打印版本然后退出
-h  打印使用信息然后退出
-f  保持前台运行
-4  强制使用IPv4的DNS请求
-6  强制使用IPv6的DNS请求
-u user 在建立隧道后,放弃特权,以user用户运行(建立隧道一般需要root权限)
-t chrootdir 建立隧道后,切换root的目录,用于隔离
-d device 使用TUN设备来替代通常的设备,在Linux上是dnsX或者tunX。在Mac OS X 10.6,为utunX,它会尝试使用内置在OS系统中的utun设备
-P password 用于认证的密码。如果没加这个指令,连接过程中会让你输入密码。密码最长为32个字符
-z context 在初始化后,应用SELinux的环境(未理解)
-F pidfile 新建一个pidfile,写入其进程号
客户端选项
-r 跳过原始UDP模式。如果没有使用,iodine会尝试直接连接到运行iodined服务器的公网IP上,测试其是否可以直接访问。如果可以直接访问,流量就将会直接发送到服务器而不是DNS中继
-R rdomain 使用OpenBSD路由域用于DNS连接
-m fragsize 控制最大下载速度的片大小。如果没有设置,客户端会自动测试最大的可接受下载速度
-M namelen 最大的上传域名长度,默认为255. 范围为100-255.使用此选项可缩减上行带宽,有利于下行带宽。对于在使用全长主机名执行不可靠的DNS服务器时也很有用,当片段大小自动探测器每次返回非常不同的结果时,这是值得注意的。
-T dnstype
DNS请求类型覆盖。 默认情况下,自动检测将探测工作的DNS请求类型,并将选择预期提供最多带宽的请求类型。 然而,可能发现DNS中继施加了偏移图片的限制,这可能导致提供更多带宽的“意外”DNS请求类型。
在这种情况下,使用此选项覆盖自动检测。 在(预期)减少带宽顺序,支持的DNS请求类型是:NULL,PRIVATE,TXT,SRV,MX,CNAME和A(返回CNAME)。 请注意,SRV,MX和A可能将通过“智能”高速缓存名称服务器获取实际IP地址的额外查找,这可能会减慢或完全失败。 PRIVATE类型使用值65399(在“私有使用”范围内),并且需要实现RFC 3597的服务器
-O downenc 强制所有查询类型响应(NULL除外)的下游编码类型。 默认为自动检测,但可能无法发现更高级编解码器的所有问题。 使用此选项覆盖自动检测。 Base32是最低级的编解码器,应该始终工作; 当自动检测失败时使用。 Base64提供更多带宽,但可能无法在所有名称服务器上工作。 Base64u等于Base64,除了使用下划线('_')而不是加号('+'),可能在Base64不工作的地方工作。 Base128使用高字节值(大多数在iso8859-1中的重音字母),这可能与一些名称服务器工作。 对于TXT查询,Raw将提供最大的性能,但只有当名称服务器路径对于被认为是“可读文本”的响应是完全8位清理时,这才会起作用。
-L 0|1 惰性模式开关。 -L1(默认):使用延迟模式以提高性能和减少延迟。 少部分DNS中继似乎无法处理延迟模式下的流量模式,导致没有或很少的数据通过。 iodine客户端将检测到这一点,并尝试切换回传统模式,但这可能总是工作。 在这些情况下,使用-L0强制在传统模式下运行。
-I interval 请求之间的最大间隔(ping),以便中间DNS服务器不会超时。 默认为4,在延迟模式,在大多数情况会工作正常。 当太多SERVFAIL错误发生时,iodine会自动将其降低到1.为了获得绝对最小DNS流量,增加到4以上,但不要高到SERVFAIL错误开始发生。 有一些DNS中继具有非常小的超时,特别是dnsadvantage.com(ultradns),即使与-I1将给SERVFAIL错误; 数据仍然会有波谷,这些误差可以忽略。 最大有用值为59,因为iodined将在不活动60秒后关闭客户端的连接。

服务端选项
-c 禁用检查所有传入请求的客户端IP地址。 默认情况下,来自不匹配的IP地址的请求将被拒绝,但是当通过DNS服务器集群路由请求时,这将导致问题
-s 不尝试配置IP地址或MTU。 只有在您已经配置了将要使用的设备时,才应使用此选项
-D 提高debug级别。 级别1会打印每个RX/TX数据包的信息。使用-f选项。在级别2(-DD)或更高版本上,将打印出DNS详细包信息。 当使用Base128上行编码时,最好将其视为ISO Latin-1文本,而不是(非法)UTF-8。 这很容易做到:使用命令行"LC_ALL = C luit iodined -DD ..."(见luit(1))
-m mtu 设定tun设备的mtu(最大传输单元)大小.它会在登陆的时候,发送到客户端,然后客户端会对其tun设备使用同样的mtu。默认为1130,注意如果需要的话,DNS网络会自动适应这个值。
-l listen_ip4 服务端仅接受某个特定的ip4地址发来的数据。默认情况它会接受来送任何来源的数据
-L listen_ip6 服务端仅接受某个特定IP6地址的数据。默认会接受任何来源的数据
-p port 让服务端监听port端口,而非默认的53端口。如果'listen_ip4'不包括本地主机,这个'端口'可以与'dnsport'相同。 注意:您必须自己将dns请求转发到此端口。
-n auto|external_ip 在NS响应中返回的IP地址。 默认值是返回在查询中用作目标的地址。 如果external_ip是'auto',iodined将使用ipify.org web服务来检索主机的外部IP,并将其用于NS响应。(检索ipify获取(对外ip)公网的ip)
-d dnsport 如果指定此端口,则不在隧道域内的所有传入请求将转发到localhost上的此端口,以由真实DNS处理。 如果'listen_ip'不包括localhost,这个'dnsport'可以和'port'相同。 注意:转发不完全透明,不建议在生产环境中使用。
-i max_idle_time 如果没有接收到流量,请使服务器在max_idle_time秒后停止。 这应该与systemd或upstart按需激活相结合才能有效。
客户端参数:
nameserver:用于中继dns流量的名称服务器。 这可以是任何中继名称服务器或运行iodined如果可达的服务器。 此字段可以作为IPv4 / IPv6地址或作为主机名。 此参数是可选的,如果未指定,将从/etc/resolv.conf文件读取名称服务器。
topodomain:dns流量将作为“topdomain”下的子域的查询发送。 这通常是您拥有的域的子域。 使用短的域名获得更好的吞吐量。 如果名称服务器是iodined服务器,则可以自由选择顶级域。 此参数必须在客户端和服务器上相同。
服务端参数:
tunnel_ip[/netmask]
这是tun接口上的服务器的IP地址。 客户端将被给予范围中的下一个ip号码。 建议使用10.0.0.0或172.16.0.0范围。 默认网络掩码为/27,可以通过在此处指定来覆盖。 使用较小的网络将限制并发用户数。
topdomain
dns流量预计到达作为'topdomain'下的子域的查询。 这通常是您拥有的域的子域。 使用短的域名获得更好的吞吐量。 此参数必须在客户端和服务器上相同。 除了“topdomain”之外的域的查询将在给出-b选项时转发,否则它们将被删除。
安全
登录是一个相对安全的地方-密码为MD5哈希,该密码在传输前从未在之前传输过。 但是,所有其他数据不以任何方式加密。 DNS流量也容易受到重放,注入和中间人攻击,特别是当使用-c选项时。 强烈建议使用ssh或vpn隧道。 在服务器和客户端上,使用iptables,pf或其他防火墙阻止从tun接口进入的所有流量,除了使用的ssh或vpn端口。
环境
IODINE_PASS
如果环境变量IODINE_PASS被设置,iodine将使用它设置为密码的值,而不是请求一个。 -P选项仍然优先。
IODINED_PASS
如果设置了环境变量IODINED_PASS,iodined将使用它设置为密码的值,而不是请求一个。 -P选项仍然优先。

iodine的协议数据信息

1. iodine中相关的DNS 协议

快速字母索引/寄存器

索引 寄存器
0-9 数据包
A-F 数据包
I IP地址
L 登陆
N 下载速度(NS.topdomain A型回复)
O 选项
P ping
R 下游压缩探头
S 切换上游编解码器
V 版本
W (WWW.topdomain A型回复)
Y 下行编解码检查
Z 上行编解码器检查

(Cache Miss Counter)CMC = 2 byte 缓存不命中计数器,每次缓存不命中时会增加数据

一、版本验证数据包:(Version)
  • 客户端发送包格式:

    • 第一个字节为v或V
    • 其余的字节以base32编码:4字节的大端协议版本信息+CMC(缓存计数器)
  • 服务端回应包格式:

    1. 4字节:

      VACK: 表示版本验证正常,后接登陆验证数据

      ​VNACK: 表示版本不同,后接服务器版本信息

      ​VFUL: 表示服务端连接的用户数已满,后接最大用户数量

    2. 4 字节的信息:分别代表先前所说的登陆验证数据/版本信息/最大用户数量

    3. 1字节的新用户的userid(如果不是VACK,则为任意数据)

二、登陆验证数据包(Login)
  • 客户端发送数据包格式:
    • 第一个字节是l或L
    • 其余字节以base32编码方式编码:1字节的userid,16字节的md5哈希值:(前面32字节的password)xor(8次重复的登陆验证信息)+再后接CMC
  • 客户端回应包格式:
    • LNACK 表示没有被接受,登陆验证失败
    • x.x.x.x-y.y.y.y-mtu-netmask 表示登陆验证成功(server ip-client ip-mtu-netmask)
三、IP请求(用于尝试原始登陆)
  • 客户端发送数据包格式:
    • 第一个字节是i或I
    • 编码为base32的5个比特userid信息,3个比特的CMC信息
  • 服务器回应包格式:
    • BADIP(如果userid非法)
    • 正常包:
      • 第一个字节为I
      • 后接iodined 服务器的DNS ip(非服务器自身的IP,其伪装的内网IP,如192.168.11.1):可能为4字节的IPv4或16字节的IPv6
四、上传检查/反射
  • 客户端发送数据包格式:
    • 第一个字节是z或Z
    • 大量不能被解码的数据
  • 服务器回应包格式:
    • 被请求的域复制为原始数据,在可用于请求类型的最低级下游编解码器中。(还未理解!)
五、下载检查
  • 客户端发送数据包格式:
    • 第一个字节是y或Y
    • 1个字节,意味着使用下行编码检查
    • 以base32格式编码的5位检查变量,3位CMC信息
    • 可能会有额外的数据,依赖于检查变量
  • 服务端发送数据包格式

  • 所请求的下载的数据;数据内容取决于检查变量编号。
  • BADCODEC 如果请求的数据,不可达。
    • BADLEN 如果验证变量不可达或者其它额外的数据
    • 在O选项下,下载编码字符是一样的。
    • 检查变量信息:发送一个编码的字符DOWNCODECCHECK1 定义在encoding.h
    • 其它的一些编码格式,比如base64等
六、选择编码器
  • 客户端发送

    • 第一个字节是s或S
    • 以base32编码的5位userid信息
    • 以base32编码的5位表示要选择的编码格式

      • 5:base32

      • 6:base64

      • 26:base64u

      • 7: base128

    • 以base32编码的3字节的CMC
  • 服务端发送:

    • 确认后,回复编码格式的编号信息。(接下来,所有的上传数据必须以这种格式来编码)
    • BADCODEC 更改编码没有被确认。客户端必须回退到一开始的编码方式
    • BADLEN 如果发送的请求长度太短
七、选项
  • 客户端发送包格式:

    • 第一个字节是s或S
    • base32格式的5位userid
    • 1字节的option
    • base32格式的3字节CMC
  • 服务端发送包格式

    • 接受后,option的全名.接下来,option会马上开始在服务端生效。

    • BADCODEC 如果没有被接受,先前的模式继续执行

    • 注:所有的options仅影响与其所请求的客户端的连接

    • options的字符:

      t or T: Downstream encoding Base32, for TXT/CNAME/A/MX (default)
      s or S: Downstream encoding Base64, for TXT/CNAME/A/MX
      u or U: Downstream encoding Base64u, for TXT/CNAME/A/MX
      v or V: Downstream encoding Base128, for TXT/CNAME/A/MX
      r or R: Downstream encoding Raw, for PRIVATE/TXT/NULL (default for
      	PRIVATE and NULL)
      
    • 如果编解码器不支持请求类型,服务器将使用Base32; 请注意,服务器将回答客户端发送的任何请求类型的混合。服务器可以忽略此选项; 客户端必须始终使用每个下游DNS数据包中指示的下游编码类型。

    • l或L:延迟模式,服务器将保持一个请求未应答,直到下一个进来。仅适用于数据传输; 握手包总是立即回答。 i或I:立即(非延迟)模式,服务器将立即应答所有请求(几乎)。

八、探针下游片段大小(探测下载速度)
  • 客户端发送包格式:
    • 第一个字节是r或R
    • base32编码的15位信息(UUUUF FFFFF FFFFF 4位的userid信息,11位的片大小)
    • 接下来接一长串随机的数据,其内容不必关心
  • 服务端发送

    • 请求的字节数作为响应。 前两个字节包含请求的长度。 第三个字节是107(0x6B)。 第四个字节是随机值,每个后续字节以107递增。这由客户端检查以确定损坏。
    • BADFRAG 如果请求的长度不被接受,则拒绝。
九、设置下载片段大小(设置下载速度)
  • 客户端发送:
    • 第一个字节为n或N
    • 接下来以base32编码:
      • 1字节的userid
      • 2字节的下载片段大小
      • CMC
  • 服务端发送
    • 2字节的新下载片段大小。这之后,所有的下载有效载荷的长度为max(fragsize+2)字节长
    • 如果不被接受,返回BADFRAG
十、数据

上传数据头

 3210 432 10 43 210 4321 0 43210
+----+---+--+--+---+----+-+-----+
|UUUU|SSS|FF|FF|DDD|GGGG|L|UDCMC|
+----+---+--+--+---+----+-+-----+ 下载数据头

 7 654 3210 765 4321 0
+-+---+----+---+----+-+
|C|SSS|FFFF|DDD|GGGG|L|
+-+---+----+---+----+-+ UUUU = Userid L = Last fragment in packet flag 数据包标志中的最后一个片段 SS = Upstream packet sequence number 上传包的序列号 FFFF = Upstream fragment number 上传片段数 DDD = Downstream packet sequence number 下载包序列号 GGGG = Downstream fragment number 下载片段数 C = Compression enabled for downstream packet 对下行数据包启用压缩 UDCMC = Upstream Data CMC, 36 steps a-z0-9, case-insensitive 上游数据CMC 36个步骤a-z0-9,不区分大小写

上行数据包以1字节ASCII十六进制编码用户字节开始; 然后3个字节Base32编码头; 然后1 char data-CMC; 然后得到有效载荷数据,用选择的上游编解码器编码。

下载数据以2字节的数据头开始,接下来是有效载荷数据,它可能会被压缩

在NULL或PRIVATE的回应中,下载数据一般都是原生的(未编码的)。其它的类型下,一般都会被编码。

编码格式从第一个字节的数据得到。

TXT类型:

t or T: Base32	 encoded before chop, decoded after un-chop
s or S: Base64	 encoded before chop, decoded after un-chop
u or U: Base64u	 encoded before chop, decoded after un-chop
v or V: Base128	 encoded before chop, decoded after un-chop
r or R: Raw	 no encoding, only DNS-chop SRV/MX/CNAME/A类型:
h or H: Hostname encoded with Base32
i or I: Hostname encoded with Base64
j or J: Hostname encoded with Base64u
k or K: Hostname encoded with Base128 SRV和MX可以回复多个主机名,每个主机名分别编码。 每个具有10多个优先级,并且在严格地增加优先级序列10,20,30等中没有间隙地进行编码/解码。 注意,一些DNS中继将随机响应中的答案记录。
十一、Ping
  • 客户端发送
    • 第一个字节为p或P
    • 其余以base32编码
      • 1字节 4位的userid
      • 1字节
        • 3位的下载序列号
        • 4位的下载片段大小
      • CMC
  • 服务器对Ping和数据包的响应是DNS NULL / TXT / ..类型响应,始终从2字节下行数据头开始,如上所示。 如果服务器没有任何发送,则在标头之后不添加数据。如果服务器有要发送的东西,它将在报头之后添加下游数据分组(或其一些片段)。