3.3. UDP hole punching UDP打洞技术
The third technique, and the one of primary interest in this document, is widely known as "UDP Hole Punching." UDP hole punching relies on the properties of common firewalls and cone NATs to allow appropriately designed peer-to-peer applications to "punch holes" through the middlebox and establish direct connectivity with each other, even when both communicating hosts may lie behind middleboxes. This technique was mentioned briefly in section 5.1 of RFC 3027 [NAT-PROT], and has been informally described elsewhere on the Internet [KEGEL] and used in some recent protocols [TEREDO, ICE]. As the name implies, unfortunately, this technique works reliably only with UDP.
第三种技术,也是这篇文章主要要研究的,就是非常有名的“UDP打洞技术”,UDP打洞技术依赖于由公共防火墙和cone NAT,允许适当的有计划的端对端应用程序通过NAT“打洞”,即使当双方的主机都处于NAT之后。这种技术在 RFC3027的5.1节[NAT PROT] 中进行了重点介绍,并且在Internet[KEGEL]中进行了非正式的描叙,还应用到了最新的一些协议,例如[TEREDO,ICE]协议中。不过,我们要注意的是,“术”如其名,UDP打洞技术的可靠性全都要依赖于UDP。
We will consider two specific scenarios, and how applications can be designed to handle both of them gracefully. In the first situation, representing the common case, two clients desiring direct peer-to- peer communication reside behind two different NATs. In the second, the two clients actually reside behind the same NAT, but do not necessarily know that they do.
这里将考虑两种典型场景,来介绍连接的双方应用程序如何按照计划的进行通信的,第一种场景,我们假设两个客户端都处于不同的NAT之后;第二种场景,我们假设两个客户端都处于同一个NAT之后,但是它们彼此都不知道(他们在同一个NAT中)。
3.3.1. Peers behind different NATs 处于不同NAT之后的客户端通信
Suppose clients A and B both have private IP addresses and lie behind different network address translators. The peer-to-peer application running on clients A and B and on server S each use UDP port 1234.? A and B have each initiated UDP communication sessions with server S, causing NAT A to assign its own public UDP port 62000 for A's session with S, and causing NAT B to assign its port 31000 to B's session with S, respectively.
我们假设 Client A 和 Client B 都拥有自己的私有IP地址,并且都处在不同的NAT之后,端对端的程序运行于 CLIENT A,CLIENT B,S之间,并且它们都开放了UDP端口1234。 CLIENT A和CLIENT B首先分别与S建立通信会话,这时NAT A把它自己的UDP端口62000分配给CLIENT A与S的会话,NAT B也把自己的UDP端口31000分配给CLIENT B与S的会话。如下图所示:
Now suppose that client A wants to establish a UDP communication session directly with client B.? If A simply starts sending UDP messages to B's public address, 138.76.29.7:31000, then NAT B will typically discard these incoming messages (unless it is a full cone NAT), because the source address and port number does not match those of S, with which the original outgoing session was established. Similarly, if B simply starts sending UDP messages to A's public address, then NAT A will typically discard these messages.
假如这个时候 CLIENT A 想与 CLIENT B建立一条UDP通信直连,如果 CLIENT A只是简单的发送一个UDP信息到CLIENT B的公网地址138.76.29.7:31000的话,NAT B会不加考虑的将这个信息丢弃(除非NAT B是一个 full cone NAT),因为 这个UDP信息中所包含的地址信息,与CLIENT B和服务器S建立连接时存储在NAT B中的服务器S的地址信息不符。同样的,CLIENT B如果做同样的事情,发送的UDP信息也会被 NAT A 丢弃。
Suppose A starts sending UDP messages to B's public address, however, and simultaneously relays a request through server S to B, asking B to start sending UDP messages to A's public address.? A's outgoing messages directed to B's public address (138.76.29.7:31000) cause NAT A to open up a new communication session between A's private address and B's public address. At the same time, B's messages to A's public address (155.99.25.11:62000) cause NAT B to open up a new communication session between B's private address and A's public address. Once the new UDP sessions have been opened up in each direction, client A and B can communicate with each other directly without further burden on the "introduction" server S.
假如 CLIENT A 开始发送一个 UDP 信息到 CLIENT B 的公网地址上,与此同时,他又通过S中转发送了一个邀请信息给CLIENT B,请求CLIENT B也给CLIENT A发送一个UDP信息到 CLIENT A的公网地址上。这时CLIENT A向CLIENT B的公网IP(138.76.29.7:31000)发送的信息导致 NAT A 打开一个处于 CLIENT A的私有地址和CLIENT B的公网地址之间的新的通信会话,与此同时,NAT B 也打开了一个处于CLIENT B的私有地址和CLIENT A的公网地址(155.99.25.11:62000)之间的新的通信会话。一旦这个新的UDP会话各自向对方打开了,CLIENT A和CLIENT B之间就可以直接通信,而无需S来牵线搭桥了。(这就是所谓的打洞技术)!
The UDP hole punching technique has several useful properties. Once a direct peer-to-peer UDP connection has been established between two clients behind middleboxes, either party on that connection can in turn take over the role of "introducer" and help the other party establish peer-to-peer connections with additional peers, minimizing the load on the initial introduction server S. The application does not need to attempt to detect explicitly what kind of middlebox it is behind, if any [STUN], since the procedure above will establish peer- to-peer communication channels equally well if either or both clients do not happen to be behind a middlebox.? The hole punching technique even works automatically with multiple NATs, where one or both clients are removed from the public Internet via two or more levels of address translation.
UDP打洞技术有很多实用的地方:第一,一旦这种处于NAT之后的端对端的直连建立之后,连接的双方可以轮流担任 对方的“媒人”,把对方介绍给其他的客户端,这样就极大的降低了服务器S的工作量;第二,应用程序不用关心这个NAT是属于cone还是symmetric,即便要,如果连接的双方有一方或者双方都恰好不处于NAT之后,基于上叙的步骤,他们之间还是可以建立很好的通信通道;第三,打洞技术能够自动运作在多重NAT之后,不论连接的双方经过多少层NAT才到达Internet,都可以进行通信。
译后小记:本来已经翻译好了,是在网文快捕中翻译的,结果,一个全选把所有翻译的内容全部删除了(网文快捕的Bug?:),不得不痛苦的再翻一遍。不过,有失必有得,第二次翻译流畅多了,希望大家读来还顺口。
Peer-to-Peer (P2P) communication across middleboxes(翻译3)
原文版权:Copyright (C) The Internet Society (2003).All Rights Reserved.
原文地址:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
译文版权申明:请引用此文的作者或网站注明出处:http://blog.csdn.net/hxhbluestar,以尊重译者的劳动成果!
3.3.2. Peers behind the same NAT 客户端都处于相同的NAT之后
Now consider the scenario in which the two clients (probably unknowingly) happen to reside behind the same NAT, and are therefore located in the same private IP address space. Client A has established a UDP session with server S, to which the common NAT has assigned public port number 62000. Client B has similarly established a session with S, to which the NAT has assigned public port number 62001.
现在让我们来考虑一下两个客户端(很有可能不知不觉的就会)同时位于相同的NAT之后,而且是在同一个子网内部的情况, Client A与S之间的会话使用了NAT的62000端口,Client B与S之间的会话使用了62001端口,如下图所示:
Suppose that A and B use the UDP hole punching technique as outlined above to establish a communication channel using server S as an introducer. Then A and B will learn each other's public IP addresses and port numbers as observed by server S, and start sending each other messages at those public addresses.The two clients will be able to communicate with each other this way as long as the NAT allows hosts on the internal network to open translated UDP sessions with other internal hosts and not just with external hosts. We refer to this situation as "loopback translation," because packets arriving at the NAT from the private network are translated and then "looped back" to the private network rather than being passed through to the public network. For example, when A sends a UDP packet to B's public address, the packet initially has a source IP address and port number of 10.0.0.1:124 and a destination of 155.99.25.11:62001. The NAT receives this packet, translates it to have a source of 155.99.25.11:62000 (A's public address) and a destination of 10.1.1.3:1234, and then forwards it on to B. Even if loopback translation is supported by the NAT, this translation and forwarding step is obviously unnecessary in this situation, and is likely to add latency to the dialog between A and B as well as burdening the NAT.
我们假设,Client A 和 Client B 要使用上一节我们所描述的“UDP打洞技术”,并通过服务器S这个“媒人”来认识,这样Client A 和Client B首先从服务端S得到了彼此的公网IP地址和端口,然后就往对方的公网IP地址和端口上发送消息。在这种情况下,如果NAT 仅仅允许在内部网主机与其他内部网主机(处于同一个NAT之后的网络主机)之间打开UDP会话通信通道,而内部网主机与其他外部网主机就不允许的话,那么Client A 和Client B就可以通话了。我们把这种情形叫做“loopback translation”(“回环转换”),因为数据包首先从局域网的私有IP发送到NAT转换,然后“绕一圈”,再回到局域网中来,但是这样总比这些数据通过公网传送好。举例来说,当 Client A发送了一个UDP数据包到 Client B的公网IP地址,这个数据包的报头中就会有一个源地址10.0.0.1:124和一个目标地址155.99.25.11:62001。NAT接收到这个包以后,就会(进行地址转换)解析出这个包中有一个公网地址源地址155.99.25.11:62000和一个目标地址10.1.1.3:1234,然后再发送给B,虽说NAT支持“loopback translation”,我们也发现,在这种情形下,这个解析和发送的过程有些多余,并且这个Client A 和Client B 之间的对话可能潜在性地给NAT增加了负担。
The solution to this problem is straightforward, however. When A and B initially exchange address information through server S, they should include their own IP addresses and port numbers as "observed" by themselves, as well as their addresses as observed by S.The clients then simultaneously start sending packets to each other at each of the alternative addresses they know about, and use the first address that leads to successful communication. If the two clients are behind the same NAT, then the packets directed to their private addresses are likely to arrive first, resulting in a direct communication channel not involving the NAT. If the two clients are behind different NATs, then the packets directed to their private addresses will fail to reach each other at all, but the clients will hopefully establish connectivity using their respective public addresses. It is important that these packets be authenticated in some way, however, since in the case of different NATs it is entirely possible for A's messages directed at B's private address to reach some other, unrelated node on A's private network, or vice versa.
其实,解决这个问题的方案是显而易见的。当 Client A和ClientB 最初通过服务器S交换彼此的地址信息时,它们应该发现了自己的IP地址和端口,也就是自己的 Local IP,同时,加上Server S发现的它们的公网地址和端口(即NAT分配给它们的) 。两个客户端同时的发送数据包到对方的公网地址和私有地址上,然后选择首先使得通信成功的那个地址就可以了。如果两个客户端都位于同一个NAT之后,那么发往私有地址的数据包应该先于发往公网地址的数据包到达,这样就建立了一个不包括NAT的直连通信通道。如果两个客户端位于不同NAT之后,虽然发送到对方私有地址的数据包会毫无疑问的发送失败,但还是很有可能使用他们各自的公网IP地址来建立一条通信通道的。所以检测这些数据包的方法和工作就变得非常重要,不论如何,只要双方都处于不同NAT之后,就完全有可能 Client A 想发送到 Client B 的信息会被发到别的无关的地方去,反之亦然(Client B 想发送到 Client A的消息也会被发到别的无关的地方去)。
(最后一句“unrelated node on A's private network”没有完全理解是什么意思,总之,放到整个语境中,应该就是说,Client A 瞄准 Client B的私有地址端口的信息会被NAT转发到别的地方去,因为两者处于不同的NAT之后,NAT A如果在内部网络找到了一个拥有与Client B相同的私有地址的电脑,就会把信息发送过去,这样,就根本不会发送到 Client B 上去)
Peer-to-Peer (P2P) communication across middleboxes(翻译4)
原文版权:Copyright (C) The Internet Society (2003).All Rights Reserved.
原文地址:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
译文版权申明:请引用此文的作者或网站注明出处:http://blog.csdn.net/hxhbluestar,以尊重译者的劳动成果!
3.3.3. Peers separated by multiple NATs 客户端分别处于多层NAT之后
In some topologies involving multiple NAT devices, it is not possible for two clients to establish an "optimal" P2P route between them without specific knowledge of the topology. Consider for example the following situation.
在有些网络拓扑中就存在多层NAT设备,如果不熟悉网络拓扑的知识,要想建立一条“理想的”端对端连接基本上是不可能的。让我们来看看下图这种情况:
Suppose NAT X is a large industrial NAT deployed by an internet service provider (ISP) to multiplex many customers onto a few public IP addresses, and NATs A and B are small consumer NAT gateways deployed independently by two of the ISP's customers to multiplex their private home networks onto their respective ISP-provided IP addresses. Only server S and NAT X have globally routable IP addresses; the "public" IP addresses used by NAT A and NAT B are actually private to the ISP's addressing realm, while client A's and B's addresses in turn are private to the addressing realms of NAT A and B, respectively.
Each client initiates an outgoing connection to server S as before, causing NATs A and B each to create a single public/private translation, and causing NAT X to establish a public/private translation for each session.
假如 NAT X 是由 Internet服务供应商(ISP)配置的一个大型工业 NAT,它使用少量的公网IP地址来为一些客户群提供服务;NAT A 和 NAT B 则是为ISP的两个客户群所配置的小一点的独立NAT网关,它们为各自客户群的私人家庭网络提供IP地址。只有 Server S 和NAT X 拥有公网固定IP地址,而NAT A 和 NAT B所拥有的“公网”IP地址对于ISP的寻址域来说则实际上“私有”的,这时 Client A的地址对于NAT A的寻址领域来说是“私有”的,Client B的地址对于NAT B的寻址域来说同样是“私有”的。
还是跟以前一样,每个客户端都建立了一个“外出”的连接到服务器S,导致NATA 和 NAT B 分别进行一次公有/私有转换,并导致 NAT X 为每个会话都建立了一个公有/私有的转换。(也就是把私有地址转换成为公网地址的过程,NAT的本质工作)
Now suppose clients A and B attempt to establish a direct peer-to- peer UDP connection. The optimal method would be for client A to send messages to client B's public address at NAT B, 192.168.1.2:31000 in the ISP's addressing realm, and for client B to send messages to A's public address at NAT B, namely 192.168.1.1:30000. Unfortunately, A and B have no way to learn these addresses, because server S only sees the "global" public addresses of the clients, 155.99.25.11:62000 and 155.99.25.11:62001.Even if A and B had some way to learn these addresses, there is still no guarantee that they would be usable because the address assignments in the ISP's private addressing realm might conflict with unrelated address assignments in the clients' private realms. The clients therefore have no choice but to use their global public addresses as seen by S for their P2P communication, and rely on NAT X to provide loopback translation.
现在让我们假设 Client A 和 Client B 想要建立一条端对端的UDP 直连。理想的方法应该是 Client A 发送一条信息到 Client B 在NAT B的公网地址192.168.1.2:31000上,这个地址在ISP的寻址域内;同时 Client B也发送一条消息到Client A 在 NAT B的公网地址上,也就是192.168.1.1:30000;如果能这样发的话,问题就解决了。可惜Client A和 Client B根本就不可能知道对方的这个地址,因为Server S只记录了他们真正的公网地址155.99.25.11:62000和155.99.25.11:62001。即使 Client A 和 Client B 通过某种途径得知了这些地址,还是不能够保证这样就能进行通话了,因为这些地址是由ISP的私有寻址域分配的,可能会与私有域所分配的其他无关客户端地址相冲突因此,如果客户端之间想要进行端对端的通信的话,别无选择,只能通过他们真正的公网地址来进行;并且 NAT X必须还得支持“loopback translation”才行。
3.3.4. Consistent port bindings 保持端口绑定
The hole punching technique has one main caveat: it works only if both NATs are cone NATs (or non-NAT firewalls), which maintain a consistent port binding between a given (private IP, private UDP) pair and a (public IP, public UDP) pair for as long as that UDP port is in use. Assigning a new public port for each new session, as a symmetric NAT does, makes it impossible for a UDP application to reuse an already-established translation for communication with different external destinations. Since cone NATs are the most widespread, the UDP hole punching technique is fairly broadly applicable; nevertheless a substantial fraction of deployed NATs are symmetric and do not support the technique.
在使用“UDP打洞技术”时有一点必须要注意:它只能在双方的NAT都是cone NAT(或者干脆没有NAT)时才能正常工作;这些NAT在自己的公网UDP端口被使用时保持着端口的绑定——[私有IP,私有UDP端口对和[公网IP,公网UDP端口对的一一对应。如果像 symmetricNAT那样给每个新的会话都分配一个新的公网端口,那么UDP应用程序想要与其他外部客户端进行通话,就无法重复使用已经建立好的通信转换。
伴随着 cone NAT 的推广,“UDP打洞技术”也被越来越广泛的应用。然而,仍存在一小部分使用 symmetric NAT 的网络,那么在这小部分网络环境中,就不能使用“UDP打洞技术”。
(注:因为我国的国情,网络技术应用得比较晚,所以可以说绝大部分的网络都是cone NAT,所以 UDP打洞技术基本上可以畅通无阻的使用,只是还要注意对NAT是否支持“loopback translation”的测试)
Peer-to-Peer (P2P) communication across middleboxes(翻译5)
原文版权:Copyright (C) The Internet Society (2003).All Rights Reserved.
原文地址:http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
译文版权申明:请引用此文的作者或网站注明出处:http://blog.csdn.net/hxhbluestar,以尊重译者的劳动成果!
注:在看这篇文章之前,读者需要了解TCP的基础知识,了解TCP建立连接的基本步骤
3.5. Simultaneous TCP open 同时开放TCP连接
There is a method that can be used in some cases to establish direct peer-to-peer TCP connections between a pair of nodes that are both behind existing middleboxes. Most TCP sessions start with one endpoint sending a SYN packet, to which the other party responds with a SYN-ACK packet. It is possible and legal, however, for two endpoints to start a TCP session by simultaneously sending each other SYN packets, to which each party subsequently responds with a separate ACK. This procedure is known as a "simultaneous open."
这里有一种方法能够在某种情况下建立一个穿透NAT的端对端TCP直连。我们知道,绝大多数的TCP会话的建立,都是通过一端先发送一个SYN包开始,另一方则回发一个SYN-ACK包的过程。然而,这里确实存在另外一种情况,就是P2P的双方各自同时地发出一个SYN包到对方的公网地址上,然后各自都单独地返回一个ACK响应来建立一个TCP会话,这个过程被称之为:“Simultaneous open”(“同时开放连接”)。
If a middlebox receives a TCP SYN packet from outside the private network attempting to initiate an incoming TCP connection, the middlebox will normally reject the connection attempt by either dropping the SYN packet or sending back a TCP RST (connection reset) packet. If, however, the SYN packet arrives with source and destination addresses and port numbers that correspond to a TCP session that the middlebox believes is already active, then the middlebox will allow the packet to pass through. In particular, if the middlebox has just recently seen and transmitted an outgoing SYN packet with the same addresses and port numbers, then it will consider the session active and allow the incoming SYN through.
If clients A and B can each correctly predict the public port number that its respective middlebox will assign the next outgoing TCP connection, and if each client initiates an outgoing TCP connection with the other client timed so that each client's outgoing SYN passes through its local middlebox before either SYN reaches the opposite middlebox, then a working peer-to-peer TCP connection will result.
如果一个NAT接收到一个来自私有网络外面的 TCP SYN 包,这个包想发起一个“引入”的 TCP 连接,一般来说,NAT会拒绝这个连接请求并扔掉这个SYN 包,或者回送一个TCP RST(connection reset,重建连接)包给请求方。但是,有一种情况,当这个接收到的 SYN 包中的源IP地址和端口、目标IP地址和端口都与NAT登记的一个已经激活的TCP会话中的地址信息相符时,NAT将会放行这个SYN 包,让它进入NAT内部。特别要指出,如果NAT恰好看到一个刚刚发送出去的一个SYN包也和上面接收到的SYN包中的地址信息相符合的话,那么NAT将会认为这个TCP连接已经被激活,并将允许这个方向的SYN包进入NAT内部。
如果 Client A 和 Client B 能够彼此正确的预知对方的NAT将会给下一个TCP连接分配的公网TCP端口,并且两个客户端能够同时地发起一个“外出”的TCP连接,并在对方的 SYN 包到达之前,自己刚发送出去的SYN包都能顺利的穿过自己的NAT的话,一条端对端的TCP连接就成功地建立了。
(好似一个间谍活动)
Unfortunately, this trick may be even more fragile and timing-sensitive than the UDP port number prediction trick described above. First, unless both middleboxes are simple firewalls or implement cone NAT behavior on their TCP traffic, all the same things can go wrong with each side's attempt to predict the public port numbers that the respective NATs will assign to the new sessions. In addition, if either client's SYN arrives at the opposite middlebox too quickly, then the remote middlebox may reject the SYN with a RST packet, causing the local middlebox in turn to close the new session and make future SYN retransmission attempts using the same port numbers futile. Finally, even though support for simultaneous open is technically a mandatory part of the TCP specification [TCP], it is not implemented correctly in some common operating systems. For this reason, this trick is likewise mentioned here only for historical reasons; it is NOT recommended for use by applications. Applications that require efficient, direct peer-to-peer communication over existing NATs should use UDP.
不幸的是,这个诡计比3.4节所讲的UDP端口预言更容易被粉碎,并且对时间的敏感性的依赖更多!首先,除非双方的NAT都是Simple firewalls 或者都像cone NAT那样处理TCP通信,否则两个客户端还是无法建立这个TCP直连,因为它们无法预知对方的NAT会分配给新的会话的端口号是多少。 另外,如果双方的 SYN 到达对方的NAT 速度太快(举例来说,就是SYN A的还未穿过NAT A时,SYN B已经提早到达了NAT A),对方的NAT会将这个SYN扔掉,并返回一个 RST 包,这样就使得这个发快了的一方NAT关闭原来的会话,又重新建立一个新的会话,再利用这个新的会话重发一个SYN,这时端口预言就失效了,因为再次分配到相同的端口地址的几率太小了。
最后,尽管在“TCP规范”中说明了“Simultaneous open”是一种支持的标准技术,但是在一些公共操作系统中,对这种技术的支持并不好。基于这个原因,我们也在这里郑重申明,并不推荐使用这个技术。如果您的应用程序想要穿透NAT并进行高效率高性能的P2P的通信,应该使用UDP技术!
这里我将这个技术的详细过程描述如下,欢迎大家指正:
过程详细描述:
Client A发送一个TCP SYN 包给 Client B,我们把这个SYN包叫做SYN A,包含的信息如下:
SrcAddress:10.0.0.1 Tcp port :1234
DestAddress:138.76.29.7 Tcp port:310000
同时,Client B发送一个TCP SYN包给Client A,我们把这个包叫做SYN B,包含的信息如下:
SrcAddress:10.1.1.3 Tcp port :1234
DestAddress:155.99.25.11 Tcp port:620000
SYN A首先通过NAT A(必须在SYN B到达NAT A之前),NAT A看到这个包并将其地址信息进行转换为:
SrcAddress:155.99.25.11 Tcp port :620000
DestAddress:138.76.29.7 Tcp port:310000
我们把这个经过 NAT A转换的包叫做SYN A’
同样,SYN B首先通过NAT B(也必须在SYN A到达NAT B之前),NAT B看到这个包并进行地址转换为:
SrcAddress:138.76.29.7 Tcp port:310000
DestAddress:155.99.25.11 Tcp port :620000
我们把这个经过NAT B转换的包叫做SYN B’
这时,NAT A和NAT B都在自己的TCP连接表中存储了含有上面两个公网IP地址和端口信息,因此,只要看到包含这两个信息的SYN包,都会让其通过。
就在这个瞬间,SYN A’到达了NAT B,NAT B检查了一下SYN A’,发现它的地址信息和自己TCP连接表中的信息相符,便让SYN A’通过了,并将SYN A’的地址信息转换为:我们称这个包为SYN A’’
SrcAddress:155.99.25.11 Tcp port :620000
DestAddress:10.1.1.3 Tcp port:1234
以使这个包能够到达内部网中Client B上
也就在这个瞬间,SYN B’到达了NAT A,NAT A检查了一下SYN B’,发现它的地址信息和自己TCP连接表中的信息相符,便让SYN B’通过了,并将SYN B’的地址信息转换为:我们称这个包为SYN B’’
SrcAddress:138.76.29.7 Tcp port :310000
DestAddress:10.0.0.1 Tcp port:1234
以使这个包能够到达内部网中Client A上
这时,Client A收到了SYN B’’,Client B收到了SYN A’’,并返回给对方ACK,经过三次握手,这个P2P的TCP连接就建立了。![](https://i-blog.csdnimg.cn/blog_migrate/2f60c0bc47d4178ac5e2b807936153ff.gif)