返回列表 发帖

[技术文章] 当NAT遇到PPTP

  我建了一个PPTP的帐号,想访问一下内网资源,结果发现,没法传输数据,这是怎么回事?于是在Google里面输入rfc pptp,直接就点进去了第一条带有RFC 2637的链接,开始了今天的穿越。。。。。。

  问题出在哪里

  首先稍微介绍一下,PPTP有两个流,一个是控制流(RFC2637定义),另外一个数据流(GRE,RFC2784)。和一般的ALG不同的是(比如FTP),NAT遇到PPTP的时候,不是端口或者IP惹的祸,而是PPTP里面邪恶的callID(抛一个球给各位,为何PPTP把call ID整到PPTP控制流里面?)。在PPTP协议里面,有一个call ID的概念,客户端向服务器发起连接,会告诉服务端我的call ID是多少,服务端也会回应客户端它的call ID是多少,并且更重要的一点是,这个call ID在以后的某些(注意不是全部哦)控制传输中需要用到,在所有的数据传输中(GRE)需要用到。而接下来我们从两个方面一起来看一下问题是如何产生,以及如何去解决:

  从内网向外网发起PPTP连接请求

  从外网向内网发起PPTP连接请求(俗称静态映射)

  走,咱们去看看外面的世界

  (注:下面的结构是1)提一下PPTP协议;2)提一下GRE协议;3)如何搞定GRE数据流;4)如何搞定PPTP控制流。)

  PPTP服务器是监听在TCP/1723端口。客户端发送的第一个携带call ID的数据包是被称为Outgoing-Call-Request的数据包,一个示例如下图所示(忽略了前面的以太网头,IP头,以及TCP头):

可以看到我们这边的示例客户端的call ID是16384(这个call ID是给自己分配的),那我就以Client-Call-ID表示吧。

服务端收到这个数据包之后,然后会回应一个称为Outgoing-Call-Reply的数据包,示例如下:

  这个说明服务端自己的call ID是107(这个服务端是给自己分配的,我就以Server-Call-ID表示), 同时也把Client-Call-ID返回给客户端。咦,这样看来好像没有什么问题嘛,反正这个call ID改不改,对于这一条连接都没有影响,NAT也可以正常运作。但是,各位看官,PPTP还需要用到一个协议,那就是GRE(RFC 2784)。而GRE是一个和TCP以及UDP处在同一个水平线的协议。但是和TCP/UDP之流不同的是,GRE里面不携带端口信息,而它利用的就是call ID来作multiplex以及demultiplex的。我们看一下客户端发给服务端的一个GRE数据包(提示一点,GRE头后面的是PPP协议封装,不过这里不需要关心):

  里面有call ID,这个call ID不是说自己的,而是说对方的(对,就是PPTP里面的那个Server-Call-ID),也就是服务端的,也就是我这个GRE数据要发给服务端的107“端口”。

  而服务端也采用同样的策略,只不过call ID表明的是客户端的,一个示例如下:

  (注:上面的都是一些协议的原理,如果你读了RFC,未必不懂这些。插一个情景,来自一个电影:C开一辆车在公路上,前面有一个人D骑一辆自行车。C在追D的,C紧张的说:“你。。。你骑自行车未必比我开车快!”)


  问题是这些个数据包也要经过我们可怜的NAT啊,GRE会问NAT:“我这个call ID,管还是不管?”,NAT答曰:”不管。”话虽如此,但是谈何容易,因为GRE里面没有端口,如果NAT不伪造一个端口的话,下面的情形就不堪设想了:

  一个主机A的IP地址是192.168.1.2,向1.1.1.1(声明:这里仅仅是假设,没有对其进行真正的试验,1.1.1.1请不要见怪)发起PPTP连接请求,Client-Call-ID是16384,Server-Call-ID是107;

  还有一个主机B在内网,IP地址是192.168.1.3,也向1.1.1.1发起PPTP连接请求,Client-Call-ID2是16385, Server-Call-ID2是108。

  NAT如果仅仅是转换IP地址(假设NAT的公网IP是116.238.184.159),那么数据到能够到达1.1.1.1,可是GRE数据包回到116.238.184.159的时候,NAT这个时候是该把它发给192.168.1.2呢,还是192.168.1.3呢?在这种情况下,NAT必须要找一个“端口”,来决定这个数据包是发给谁,而“端口”需要满足以下特性:

  在一台主机上,这个是唯一的;

  必须每个数据包中都存在。

  而扫描整个GRE数据包,只有call ID具有此特性。所以这个关系是这样的,由于GRE数据包里面没有像TCP/UDP的端口信息,所以需要这样一个信息,而call ID的存在恰恰满足了这样一个条件。但是call ID和TCP/UDP端口有不一样的地方:

  TCP/UDP的端口在传输层中的位置是一样的,相对偏移量是0,而call ID在传输层里面的偏移量是6;

  TCP/UDP的端口在每个数据包里面都会携带源端口和目的端口,但是call ID仅仅携带一个,并且是指对方的。

  意识到这些不同点非常重要,这会影响到NAT对待TCP/UDP和GRE要完全不同,而也正是PPTP ALG的原因之一!

  嘿嘿,是否对为何需要针对GRE作特殊处理的原因了吧?好,那我们看如果保证GRE数据包能够顺利通过NAT。

  我们知道,有一个NAT会话的概念(不要嫌我罗嗦。。。),里面有以下几个关键的元素:

  内网地址(innerip)

  内网端口(innerport)

  NAT公网地址(outerip)

  NAT公网端口(outerport)

  目的地址(destip)

  目的端口(destport)

  协议(Protocol)

  而从内网的数据包,至少需要innerip以及innerport才能NAT出去(请读者思考protocol在什么情况下也需要作为一个依据;destip在symmetric NAT的情况下也需要考虑,不过这里我们就不关心了),而数据包返回的时候需要outerip, outerport。他们两者虽然查找的依据不一样,但是查找到的会话对于一条连接必须是同一个,这是保证通信畅通的前提。为了要使GRE顺利通过NAT,我们以主机A为例就把其分解为从里面到外面,从外面到里面两个方向。

  从里面到外面。这个时候,call ID是Server-Call-ID(107)。innerip是192.168.1.2,可以把Server-Call-ID作为innerport,outerip是116.238.184.159,outerport假设是1234(NAT分配的呢)。OK这个数据包可以发送给服务端;

  从外面到里面。这个时候,call ID是Client-Call-ID(16384),数据包的目的是116.238.184.159,那么NAT如何将其发送给192.168.1.2?大家可以看到这个时候16384帮不上任何的忙,因为outerport是1234啊。哦,等等,如果再创建一条NAT会话,把16384作为outerport,innerip作为192.168.1.2,innerport随便了(反正不用),这样不就可以发送给192.168.1.2呢?

  所以,为了能够使GRE数据顺利来回,需要创建两条GRE的NAT会话,如下:

  维护从里面到外面的数据传输(GRE-SESSION-1):

维护从外面到里面的数据传输(GRE-SESSION-2):

  各位看到问题没有,outerport是16384,如果内网还有一台PC也是使用16384的call ID,那么这样就不出现两个outerport为16384的NAT会话呢?呵呵,这个后果很严重。所以outerport需要有NAT自己来分配一个唯一的(比如1244),这样就算内网还有一台PC使用16384,NAT也可以识别。不过既然outerport不是16384了,也就意味着一个非常重要的话题,就是NAT必须要让服务端知道Client-Call-ID是1244,而不是16384,这是PPTP ALG的另外的原因。这个带来的影响:


  在PPTP所有的控制包里面,如果牵扯了Client-Call-ID,都需要修改为1244,如果不修改,服务端就会认为Client-Call-ID是16384了;

  从服务端过来的GRE数据包中的call ID从1244还原为16384;

  从外面到里面的NAT会话(GRE-SESSION-2)需要存储16384,咦,上面不是说Innerport没有用到吗?我们就可以把1244存储在innerport这个位置,不过仅仅是用来作为call ID从1244还原为16384的依据。

  更新后的GRE-SESSION-2应该是这个样子的:

  这样子的话,GRE就是通行无阻了,从里面到外面匹配GRE-SESSION-1,从外面到里面匹配GRE-SESSION-2。接下来我们看看这两条会话是在什么时候生成的以及控制流中需要做的工作。

  前面说到,Client-Call-ID在第一个Outgoing-Call-Request里面首次亮相,那么就可以先生成GRE-SESSION-2(获取outerport,即1244),然后根据GRE-SESSION-2中的outerport再把这个数据包中的call ID改成1244,之后就放了它。

  收到Outgoing-Call-Request之后,服务端会返回Outgoing-Call-Reply,此时就可以:

  生成GRE-SESSION-1;

  把里面的Client-Call-ID从1244还原成16384。

  在其后的控制连接中,再次强调,NAT你得记住有一点,只要是牵扯到Client-Call-ID,你就要动手脚。如果是从客户端到服务端,那么得将其修改成1244;如果是从服务端到客户端,那么你就得把它还原成16384。而GRE数据流,就可以让它自己匹配GRE-SESSION-1和GRE-SESSION-2了。

  哦,回城了

  如果是从外面主动发起请求,希望读者能够自己分析一下。给一点点提示:

  Outgoing-Call-Request里面的Call ID(Client-Call-ID)就不用修改了,因为这个是外面的;

  而Outgoing-Call-Reply里面的Server-Call-ID需要修改,因为这个是属于里面的。

  结语

  造成PPTP需要做ALG的原因,是GRE数据里面没有携带端口,而又只能通过call ID来模拟“端口”作NAT,而由于需要模拟‘端口”,进而需要修改PPTP控制流中的相关call ID。

  还有一个特点是,call ID的修改是单方面,而决定其是否要修改的原因就是这个call ID是由内网的机器产生还是由外网的机器产生。

  由于call ID是固定2个字节,这个修改在PPTP中只需要更新TCP和IP校验和,不需要处理seq/ack问题;在GRE中只需要关心IP校验和(由于修改了IP地址),因为GRE自己压根就没有校验和。



补发2015-4-25 12:25的
欢迎光临:逐梦论坛

返回列表

Powered by Discuz! 7.2   论坛QQ群:逐梦论坛群

© 2001-2021 Comsenz Inc. 鲁公网安备 37120302000001号