curl 中文简介

原文地址:http://www.donews.net/sogoo/articles/385625.aspx

CURL? 嗯,说来话长了~~~~

这东西现在已经是苹果机上内置的命令行工具之一了,可见其魅力之一斑

1)
二话不说,先从这里开始吧!

curl http://www.yahoo.com

回车之后,www.yahoo.com 的html就稀里哗啦地显示在屏幕上了~~~~~

2)
嗯,要想把读过来页面存下来,是不是要这样呢?
curl http://www.yahoo.com > page.html

当然可以,但不用这么麻烦的!
用curl的内置option就好,存下http的结果,用这个option: -o
curl -o page.html http://www.yahoo.com

这样,你就可以看到屏幕上出现一个下载页面进度指示。等进展到100%,自然就OK咯

3)
什么什么?!访问不到?肯定是你的proxy没有设定了。
使用curl的时候,用这个option可以指定http访问所使用的proxy服务器及其端口: -x
curl -x 123.45.67.89:1080 -o page.html http://www.yahoo.com

4)
访问有些网站的时候比较讨厌,他使用cookie来记录session信息。
像IE/NN这样的浏览器,当然可以轻易处理cookie信息,但我们的curl呢?…..
我们来学习这个option: -D <– 这个是把http的response里面的cookie信息存到一个特别的文件中去
curl -x 123.45.67.89:1080 -o page.html -D cookie0001.txt http://www.yahoo.com

这样,当页面被存到page.html的同时,cookie信息也被存到了cookie0001.txt里面了

5)
那么,下一次访问的时候,如何继续使用上次留下的cookie信息呢?要知道,很多网站都是靠监视你的cookie信息,
来判断你是不是不按规矩访问他们的网站的。
这次我们使用这个option来把上次的cookie信息追加到http request里面去: -b
curl -x 123.45.67.89:1080 -o page1.html -D cookie0002.txt -b cookie0001.txt http://www.yahoo.com

这样,我们就可以几乎模拟所有的IE操作,去访问网页了!

6)
稍微等等~~~~~我好像忘记什么了~~~~~
对了!是浏览器信息~~~~

有些讨厌的网站总要我们使用某些特定的浏览器去访问他们,有时候更过分的是,还要使用某些特定的版本~~~~
NND,哪里有时间为了它去找这些怪异的浏览器呢!?

好在curl给我们提供了一个有用的option,可以让我们随意指定自己这次访问所宣称的自己的浏览器信息: -A
curl -A "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)" -x 123.45.67.89:1080 -o page.html -D cookie0001.txt http://www.yahoo.com

这样,服务器端接到访问的要求,会认为你是一个运行在Windows 2000上的IE6.0,嘿嘿嘿,其实也许你用的是苹果机呢!

而"Mozilla/4.73 [en] (X11; U; Linux 2.2; 15 i686"则可以告诉对方你是一台PC上跑着的Linux,用的是Netscape 4.73,呵呵呵

7)
另外一个服务器端常用的限制方法,就是检查http访问的referer。比如你先访问首页,再访问里面所指定的下载页,这第二次访问的referer地址就是第一次访问成功后的页面地址。这样,服务器端只要发现对下载页面某次访问的referer地址不 是首页的地址,就可以断定那是个盗连了~~~~~

讨厌讨厌~~~我就是要盗连~~~~~!!
幸好curl给我们提供了设定referer的option: -e
curl -A "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)" -x 123.45.67.89:1080 -e "mail.yahoo.com" -o page.html -D cookie0001.txt http://www.yahoo.com

这样,就可以骗对方的服务器,你是从mail.yahoo.com点击某个链接过来的了,呵呵呵

8)
写着写着发现漏掉什么重要的东西了!—– 利用curl 下载文件

刚才讲过了,下载页面到一个文件里,可以使用 -o ,下载文件也是一样。
比如, curl -o 1.jpg http://cgi2.tky.3web.ne.jp/~zzh/screen1.JPG
这里教大家一个新的option: -O
大写的O,这么用: curl -O http://cgi2.tky.3web.ne.jp/~zzh/screen1.JPG
这样,就可以按照服务器上的文件名,自动存在本地了!

再来一个更好用的。
如果screen1.JPG以外还有screen2.JPG、screen3.JPG、….、screen10.JPG需要下载,难不成还要让我们写一个script来完成这些操作?
不干!
在curl里面,这么写就可以了:
curl -O http://cgi2.tky.3web.ne.jp/~zzh/screen[1-10].JPG

呵呵呵,厉害吧?!~~~

9)
再来,我们继续讲解下载!
curl -O http://cgi2.tky.3web.ne.jp/~{zzh,nick}/[001-201].JPG

这样产生的下载,就是
~zzh/001.JPG
~zzh/002.JPG

~zzh/201.JPG
~nick/001.JPG
~nick/002.JPG

~nick/201.JPG

够方便的了吧?哈哈哈

咦?高兴得太早了。
由于zzh/nick下的文件名都是001,002…,201,下载下来的文件重名,后面的把前面的文件都给覆盖掉了~~~

没关系,我们还有更狠的!
curl -o #2_#1.jpg http://cgi2.tky.3web.ne.jp/~{zzh,nick}/[001-201].JPG

–这是…..自定义文件名的下载?
–对头,呵呵!

#1是变量,指的是{zzh,nick}这部分,第一次取值zzh,第二次取值nick
#2代表的变量,则是第二段可变部分—[001-201],取值从001逐一加到201
这样,自定义出来下载下来的文件名,就变成了这样:
原来: ~zzh/001.JPG —> 下载后: 001-zzh.JPG
原来: ~nick/001.JPG —> 下载后: 001-nick.JPG

这样一来,就不怕文件重名啦,呵呵

9)
继续讲下载
我们平时在windows平台上,flashget这样的工具可以帮我们分块并行下载,还可以断线续传。
curl在这些方面也不输给谁,嘿嘿

比如我们下载screen1.JPG中,突然掉线了,我们就可以这样开始续传
curl -c -O http://cgi2.tky.3wb.ne.jp/~zzh/screen1.JPG

当然,你不要拿个flashget下载了一半的文件来糊弄我~~~~别的下载软件的半截文件可不一定能用哦~~~

分块下载,我们使用这个option就可以了: -r
举例说明
比如我们有一个http://cgi2.tky.3web.ne.jp/~zzh/zhao1.mp3 要下载(赵老师的电话朗诵 😀 )
我们就可以用这样的命令:
curl -r 0-10240 -o "zhao.part1" http:/cgi2.tky.3web.ne.jp/~zzh/zhao1.mp3 &\
curl -r 10241-20480 -o "zhao.part1" http:/cgi2.tky.3web.ne.jp/~zzh/zhao1.mp3 &\
curl -r 20481-40960 -o "zhao.part1" http:/cgi2.tky.3web.ne.jp/~zzh/zhao1.mp3 &\
curl -r 40961- -o "zhao.part1" http:/cgi2.tky.3web.ne.jp/~zzh/zhao1.mp3

这样就可以分块下载啦。
不过你需要自己把这些破碎的文件合并起来
如果你用UNIX或苹果,用 cat zhao.part* > zhao.mp3就可以
如果用的是Windows,用copy /b 来解决吧,呵呵

上面讲的都是http协议的下载,其实ftp也一样可以用。
用法嘛,
curl -u name:passwd ftp://ip:port/path/file
或者大家熟悉的
curl ftp://name:passwd@ip:port/path/file

10)
说完了下载,接下来自然该讲上传咯
上传的option是 -T

比如我们向ftp传一个文件: curl -T localfile -u name:passwd ftp://upload_site:port/path/

当然,向http服务器上传文件也可以
比如 curl -T localfile http://cgi2.tky.3web.ne.jp/~zzh/abc.cgi
注意,这时候,使用的协议是HTTP的PUT method

刚才说到PUT,嘿嘿,自然让老服想起来了其他几种methos还没讲呢!
GET和POST都不能忘哦。

http提交一个表单,比较常用的是POST模式和GET模式

GET模式什么option都不用,只需要把变量写在url里面就可以了
比如:
curl http://www.yahoo.com/login.cgi?user=nickwolfe&password=12345

而POST模式的option则是 -d

比如,curl -d "user=nickwolfe&password=12345" http://www.yahoo.com/login.cgi
就相当于向这个站点发出一次登陆申请~~~~~

到底该用GET模式还是POST模式,要看对面服务器的程序设定。

一点需要注意的是,POST模式下的文件上的文件上传,比如
<form method="POST" enctype="multipar/form-data" action="http://cgi2.tky.3web.ne.jp/~zzh/up_file.cgi">
<input type=file name=upload>
<input type=submit name=nick value="go">
</form>
这样一个HTTP表单,我们要用curl进行模拟,就该是这样的语法:
curl -F upload=@localfile -F nick=go http://cgi2.tky.3web.ne.jp/~zzh/up_file.cgi

罗罗嗦嗦讲了这么多,其实curl还有很多很多技巧和用法
比如 https的时候使用本地证书,就可以这样
curl -E localcert.pem https://remote_server

再比如,你还可以用curl通过dict协议去查字典~~~~~
curl dict://dict.org/d:computer

今天就先讲到这里吧,呵呵。疯狂的curl功能,需要你—一起来发掘。

copyright by nickwolfe@CCF
2004.08.24 21:24

相关链接:

字典协议的rfc:http://tools.ietf.org/html/rfc2229

字典协议服务器列表:http://luetzschena-stahmeln.de/dictd/index.php

路由检查脚本

对于一个大网络来讲,一般都有内网,有内网就有复杂的路由问题,一个机器要和很多其它机器通信,往往需要在机器上添加大量的路由,到一定程度,连自己都不知道那些路由有效,那些路由无效了,下面给出一个检查路由的php脚本:

<?php
// filename: checkroute.php
// check route use ping command
// eg : route -n | egrep -v "IP|De|0\.0\.0\.0" |cut -d " " -f 1 | php checkroute.php
$arropt getopt("f:v"
);
if(isset(
$arropt[‘f’
])) {
    if(!(
$fp fopen($arropt[‘f’],‘r’
))){
        die(
"open file $f error\n"
);
    }
} else {
    
$fp STDIN
;
}
$debug false
;
if(isset(
$arropt[‘v’])) $debug true

while(!feof($fp)) {
    
$line trim(fgets($fp
));
    if(
$line == 
) continue;
    
$ip preg_replace("/\.0$/",".1",$line
);
    
$cmd "/bin/ping -c 1 -W 1 $ip 2>1 >/dev/null"
;
    
debug($cmd
);    
    
$result = @exec($cmd,$arr_ret,$retcode
);
    if(
$retcode != 0
){
        echo 
$line."\n"
;
    }
}

function debug($msg) {
    global 
$debug

    if(
$debug
) {
        echo 
$msg."\n"
;
    }
}
?>

关于keepalive的解释

Apache配置文件中有三个关于KeepAlive的配置指令:
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 15

一般来讲,http请求是一次请求完毕就关闭连接的,但是有些时候,一个apache只有少数的固定的几个或几十个机器频繁的访问,这时,每次访问都重新建立tcp连接,感觉有些无聊,这时就用到KeepAlive的相关设置了:
KeepAlive 为 On,如果http请求头明确说明:Connection:keep-alive ;则一次请求结束后,如果该连接使用的次数没有超过MaxKeepAliveRequests ,服务器并不立即主动断开连接,而是等待KeepAliveTimeout 指定的时间,这时,如果客户端没有再发起http请求,则关闭连接;如果在KeepAliveTimeout时间内,客户端又使用该连接做http请求,则省去了重新建立tcp连接的消耗了。

就上面的设置而言:

———————————-

如果客户端发送http请求头:


Connection:keep-alive

则服务器端相应的http头为:


Keep-Alive: timeout=15, max=100
Connection: Keep-Alive

————————————

如果客户端发送http请求头:


Connection:close

则服务器端响应的http头为:


Connection:close

并且服务器端主动关闭连接

================================================

如果服务器端设置为:KeepAlive Off

则不管客户端的http请求头如何设置,服务器端总是每次连接只处理一个请求,并立即主动关闭,响应的http头为:


Connection:close

———————————————-

下面附一段代码:

<?php
main
();
function 
main() {
    
$host "ljj.com";
    
$port 80;
    
$url "/";
    
$fp fsockopen($host$port ,$errno$errstr10);
    if(
$fp === false) {
        die(
$errstr);
    }
    
$str "GET / HTTP/1.1\nHost: ljj.cn\nConnection:keep-alive\n\n";
    
$i 0;
    while(
$i++ < 3) {
        
$len fputs($fp,$str);
        if(
$len === false) { // 这里这么判断是没有用的,因为如果服务器端单向关闭,该连接没有完全断开,则还是可以写数据的,只是没有响应而已
            
die("fp error\n");
        }
        
$out fread($fp,2048);
        echo 
"$i:$out\n";
    }
    
fclose($fp);
}

?>

这段代码说明了多次请求可以使用同一条连接(在服务器端KeepAlive On的情况下

VMWARE序列号大全

【VMWARE序列号大全】 
=====VMware Workstation4.5.2 Build 8848 for Windows===== 

ZHDH1-UR90N-W844G-4PTN6 

G1NP0-T88AL-M016F-4P8N2 

=====VMware-workstation-4.5.2-8848 for Linux ===== 

ZC14J-4U16A-0A04G-4MEZP 

J1WF8-58LDE-881DG-4M8Q3 

=====VMware Workstation 5 For Windows ===== 

LUXRM-WP0DN-A256U-4M9Q3 

DJXDR-NDT27-Y2NDU-4YTZK 

=====VMware Workstation 5 for Linux ===== 

DA925-HP80U-Z8HDC-4WXXP 

3KW2W-AYR2C-88M6F-4MDQ2 

=====VMware.GSX.Server.3.1.0-9089.for.Windows===== 

98XY4-54VA4-4216V-4PDZ6 

WH0M5-XW50J-WA4FU-4MTZ3 

=====VMware-gsx-server-3.1.0-9089.for Linux===== 

N218N-NR66R-F0JDF-4P9NP 

H2W8H-X82DC-J8N6G-4M9Q6 

=====VMWare ESX Server 2.x===== 

0VHD0-0P86V-0C0AH-4018N 
SMP 0KR08-02CAH-0DJ2H-48124 

02M8R-0HA2N-0CK8M-42JAN 
SMP 0AMDE-0UWFP-0E601-42J84 

=====VMware Ace 1.0.0===== 

admin:8H18E-8U505-HA14Z-4P8QL 

RCT0X-XHN8U-H20FY-4W9ZP 

player:WC94X-3H90R-K05DU-4MTN3 

DHHF8-K0P6P-Z25DY-4YENK 

workstation:TKN8T-1UD2Q-70N4C-4YXXQ 


AJMMN-3YUDP-8ANFG-4WWX2 

=====VMware VirtualCenterV1.2.0===== 


点击浏览该文件 

VC管理GSX的SN: 

QU9HW-NM5FT-VAJ4G-5W9N3 

VHM05-U0Q27-E0HFA-5WRXL 

virtualcenter 最全和最强的license 


virtualcenter : 921H4-7WR4V-T2NDC-5YDZK 

vmotion : 8VN44-Q8PD9-P0NFY-5PWZL 

esx: XL584-9PH2Z-02H4C-5P9XL 

gsx: EH8M0-5RP8Y-M804F-5MRN3 

=====VMware.P2V.Assistant.v2.0.0===== 

L80ME-56Q6F-U8M4F-5WEQQ 

4KE0R-0A04Q-H05DZ-5WEZL

什么是线程安全

版权声明:如有转载请求,请注明出处:http://blog.csdn.net/yzhz 杨争

1、什么是线程安全(thread safe)?
        一个类要成为线程安全的类,就是在该类被多个线程访问时,不管运行环境中执行这些线程有什么样的时序安排或者交错,它仍然执行正确行为,并且在调用的代码中没有任何额外的同步。

2、什么时候考虑线程安全问题?
       当一个类的实例为singleton的时候,你就要考虑该实例在调用的时候是否是线程安全的。
       最熟悉的例子就是servlet, 每个servlet在servlet engineer中只有一个实例。除非它实现SingleThreaded接口。所以我们一般要求在servlet中不要定义成员变量,以避免线程不安全。
       是不是凡是singleton的对象都不是线程安全的呢?答案是No。准确的表达应该是:只有该类中定义了有状态的成员时该类才是线程不安全的
举个例子:
public class A{
    String id ;
    public void process(){
       print(id);
       …
    }
}
       id是一个有状态的变量。什么是有状态,就是指每次调用该类的时候如果该id值可能存在不同的值,那么这个id就是有状态的。

我们再看看下面的例子。
public class B{
    public void process(){
     int i;
     int j;
          
     println(i*y);
    }
}
        这个class B在单实例的情况下就是线程安全的。原因是:该类没有有状态的成员。i,j是局部变量,某个线程都会有自己的stack保存这些局部变量。所以对于不同线程来说,这些变量是相互不影响的。

        对于存在线程不安全的类,如何避免出现线程安全问题呢?
1、采用synchronized同步。缺点就是存在堵塞问题。
2、使用ThreadLocal(实际上就是一个HashMap),这样不同的线程维护自己的对象,线程之间相互不干扰。

总结:
1、我们一般要求商业逻辑的BO为线程安全的类,这样就可以将该BO创建成一个单实例的对象,提高访问的效率。为了使BO为线程安全的对象,我们所要做的很简单,就是该类中不要有与状态相关的成员变量。

iptables下udp穿越结尾篇—-iptables和socks5

文章标题 iptables下udp穿越结尾篇—-iptables和socks5
张贴者: shixudong (member)
张贴日期 10/31/04 10:36 PM
iptables和socks5
从“iptables和natcheck”一文可知,只要在两端都采用了iptables作NAT后,即使两侧都通过了natcheck的兼容性测试,但iptables两侧永远也不能互相穿越。
怎么办呢,一种办法是在公网上添加中转服务器,两侧内网机器之间的UDP通讯都由中转服务器来中转(其实只要中转一侧足矣)。这种方法的好处是,因为中转服
务器在公网,所有NAT后面的机器都能和中转服务器建立连接,也就是说,不同内网之间的机器总是能通过中转服务器实现双向通信的。然而,该办法的缺点
是,对中转服务器的需求比较高,包括CPU处理能力和网络带宽两方面,而且客户机之间的通讯延迟也是不可避免的(目前网上最为盛行的Skype是个例外,
他采用了分布式中转技术,直接挂在互连网上不在防火墙后面的Skype客户端都能为他人提供中转服务,因此Skype在提供非常高呼叫成功率的同时还能确
保超高质量的语音效果)。更有一个更为重要的因素,即中转服务器的标准不统一,导致每种不同类型的P2P程式都需要一个专用的中转服务器。倘若这些中转服
务器之间不能做到资源共享的话,必然存在资源浪费现象(标准的中转协议似乎正在推出,名称为Traversal Using Relay NAT
即TURN)。
另一种比较好的办法就是采用Socks5(Rfc1928)代理服务器取代专用的中转服务器,一是因为Socks5能够非常好的
支持UDP,二是Socks5代理服务器的品种及在公网上部署的数量都比较多,而且最重要的是Socks5是个已标准化了的协议。客户端采用
Socks5代理后,其UDP通信通过Socks5中转出去,在对方的P2P程式看来,使用Socks5代理后的客户就像直接连在公网上,也就是说,只要有一方使用Socks5代理,则另一方不论采用何种NAT,都不会受Stun或natcheck的限制。因此,iptables和Socks5理论上应该合作愉快,但在实现Socks5代理时,如果对Socks5协议理解不够透彻,在和iptables合作时,还是有一些不愉快的。下文试举两例说明之。
假设一端采用iptables,另一端采用Socks5,目前Socks5后面的QQ要向iptables后面的在线(不隐身)QQ发送消息。当QQ通过Socks5代理登陆QQ服务器时,首先要和Socks5服务器建立一个基于TCP连接的Socks5通道,用于控制QQ和Socks5服务器之间的后续UDP联结。一旦该通道成功建立,Socks5服务器将动态分配一个UDP端口为该QQ担任UDP中转的任务,当QQ给远端iptables后面在线QQ发送消息时,先将消息(UDP包)发送到Socks5服务器上先前分配的UDP端口,再由服务器将该UDP包转发到远端QQ。当UDP包到达远端QQ时,
由iptables端口受限的属性可知,除非使用Socks5代理的QQ在线且在不久前(三分钟)原来收到过iptables后面QQ主动发来的UDP包,否则这个包无法被转发到iptables后面的QQ上。
那么这个UDP包最终又往哪里去了呢?我们来分析一下,该UDP包到达iptables所在那台Linux主机后的前进流程。当一个UDP包到达Linux时,先交由iptables处理,iptables则先看该UDP包在/proc/net/ip_conntrack文件里有无匹配项,如有,则进行匹配处理;如无,再看PREROUTING链里有无匹配规则,如有,则继续进行匹配处理;如无,由于该包目的地址就是Linux本身,所以iptables将其放入INPUT链。由于INPUT链的默认策略为ACCEPT,则该UDP包将被转交给Linux的本地进程处理,但事实上由于不存在这样的本地进程,结果是Linux向产生该UDP包的机器发回一个icmp-port-unreachable包(注意,这个包是Linux系统产生的,不是iptables的REJECT目标产生的)。如果INPUT链的策略为DROP,则该UDP包就被DROP。
由上分析可知,一般情况下,最终iptables所在机器将向发送QQ方返回一个icmp-port-unreachable包。当Socks5服务器收到这个icmp-port-unreachable包后,不同的Socks5服务器有不同的处理方式。
一些Socks5服务器(如Ccproxy带的Socks5)简单的忽略这个icmp-port-unreachable包,而QQ则由于没有发送成功
(没有收到应答信息),继续重复上述过程若干次,但都无法发送成功,最后只能通过服务器中转,虽然由于中转造成发送速度非常慢,但总是能够成功发送。而另外
一些Socks5服务器(如Wingate带的Socks5)收到远端返回的icmp-port-unreachable包后,认为先前QQ发起的UDP
联结无效,通过先前建立的Socks5通道(TCP)给客户端返回一个general SOCKS server failure(Reply
Code‘01’),随后即时关闭该通道,同时还关闭了Socks5服务器上担任中转任务的UDP端口。然而QQ似乎并不知道他和Socks5服务器之间
的联系已被切断,由于没有发送成功(没有收到应答信息),仍然向Socks5服务器上已关闭的UDP端口发送消息。此时由于Socks5服务器已关闭了相
应的UDP端口,所以也向QQ返回一个icmp-port-unreachable包。QQ收到这个包后,继续重复上述过程若干次,最终因超时而失败,并
且因为同样的原因也不能通过服务器中转。更为严重的是,即便到了此时,QQ仍然不知道他和Socks5服务器之间的联系已被切断,因此,即使当QQ向其他
离线或隐身QQ或不在NAT后面的在线QQ发送消息时,也不能成功。
上面这个例子表明,由于某些Socks5服务器和Socks5客户端对Socks5协议理解不够透彻,即使采用了Socks5,在和iptables互通时也会导致通讯不畅。
另一个例子,考虑这样一种情形,Socks5服务器位于公网,内网客户端先通过iptables进行NAT,然后再去连接Socks5服务器。
先引用Socks5协议(Rfc1928)里如下一段文字:
The
UDP ASSOCIATE request is used to establish an association within the
UDP relay process to handle UDP datagrams. The DST.ADDR and DST.PORT
fields contain the address and port that the client expects to use to
send UDP datagrams on for the association. The server MAY use this
information to limit access to the association. If the client is not in
possesion of the information at the time of the UDP ASSOCIATE, the
client MUST use a port number and address of all zeros.
Socks5客户端在进行UDPASSOCIATE时(通过TCP通道),需求用自己将来发送UDP包的地址和端口填充DST.ADDR和DST.PORT字段,以便Socks5服务器确保其分配的UDP端口仅为对应的Socks5客户端提供中转服务。然而,当Socks5客户端跨越NAT去连接Socks5服务器时,考虑到要经过
NAT地址转换环节,Socks5客户端无法预知将来Socks5服务器看到的自己用来发送UDP包的地址和端口。针对这种情况,Rfc1928建议
Socks5客户端使用0填充DST.ADDR和DST.PORT字段,这样一来,Socks5服务器就不会限制Socks5客户端对中转端口的使用了。
然而,目前好多Socks5客户端似乎没有意识到这一点,像最通用的NEC公司的e-Border
Driver,便使用了客户端的真实IP地址和端口填充DST.ADDR和DST.PORT字段;而其他一些P2P程式自带的Socks5客户端,仅用0
填充DST.ADDR字段。鉴于跨越NAT后,IP地址必然发生变化,导致e-Border
Driver永远不能用于跨越NAT的场合,而像iptables,由于具有Preserves port
number的属性,所以,那些仅用0填充DST.ADDR字段的Socks5客户端,能用于跨越iptables的场合。此外,跨越NAT还需要
Socks5服务器的配合,遗憾的是,仍然有些Socks5服务器并没有充分理解Rfc1928的这一建议。在这点上,Wingate带的Socks5做
得比较好,支持客户端用0填充DST.ADDR和DST.PORT字段;而Ccproxy带的Socks5,在客户端进行UDP
ASSOCIATE时,虽然也支持客户端用0填充DST.ADDR和DST.PORT字段,并能在服务器上顺利为客户端分配一个UDP端口,但当后来该
UDP端口和客户端通信时,不是发往客户端对应的UDP端口,而是发往客户端前述用0填充的DST.PORT,导致无法实现中转任务。至于NEC公司提供
的广为流传的免费Socks5服务器,则干脆申明“if the destination port is 0, we will assume
the same port as tcp’s”,也将导致Socks5客户端无法跨越NAT。
在这个例子里,iptables对
Socks5代理没有所有影响,纯粹是因为Socks5本身的原因导致通讯不畅。相反,利用iptables的保护源端口特性,还能确保部分Socks5
客户端能够顺利跨越用iptables实现的NAT去连接公网上的Socks5服务器。
希望本文两个例子能对即将或已实现Socks5代理的研发者有所帮助。

iptables下udp穿越实用篇—-iptables与natcheck

文章标题 iptables下udp穿越实用篇—-iptables与natcheck
张贴者: shixudong (member)
张贴日期 10/16/04 05:51 PM

iptables与natcheck

Stun协议(Rfc3489、详见http://www.ietf.org/rfc/rfc3489.txt) 提出了4种NAT类型的定义及其分类,并给出了如何检测在用的NAT究竟属于哪种分类的标准。但是,具体到P2P程序如何应用Stun协议及其分类法穿越NAT,则是仁者见仁、智者见智。(因为Stun协议并没有给出也没有必要给出如何穿越NAT的标准)
在拙作“iptables与stun”一文中,笔者花大幅精力阐述了iptables理论上属于Symmetric NAT而非Port Restricted Cone。对此,很多人(包括笔者最初学习Stun协议时)心中都有一个疑惑,即仅就Stun协议本身来说,Port Restricted Cone和Symmetric NAT的区别似乎不大,虽然两者的映射机制是有点不同,但他们都具有端口受限的属性。初看起来,这两者在穿越NAT方面的特性也差不多,尤其是对于外部地址欲往NAT内部地址发包的情况。既然如此,又为何有必要把iptables分得这么清呢,本文顺带解决了读者在这一方面的疑惑。
网站
http://midcom-p2p.sourceforge.net/给出了P2P程序具体如何穿越NAT的一个思路,并提供了一个P2P协议穿越NAT兼容性的测试工具natcheck。让我们仍旧用实例(例1)来说明这一思路吧!
A机器在私网(192.168.0.4)
A侧NAT服务器(210.21.12.140)
B机器在另一个私网(192.168.0.5)
B侧NAT服务器(210.15.27.140)
C机器在公网(210.15.27.166)作为A和B之间的中介
A机器连接过C机器,假使是 A(192.168.0.4:5000)-> A侧NAT(转换后210.21.12.140:8000)-> C(210.15.27.166:2000)
B机器也连接过C机器,假使是 B(192.168.0.5:5000)-> B侧NAT(转换后210.15.27.140:8000)-> C(210.15.27.166:2000)
A 机器连接过C机器后,A向C报告了自己的内部地址(192.168.0.4:5000),此时C不仅知道了A的外部地址(C通过自己看到的 210.21.12.140:8000)、也知道了A的内部地址。同理C也知道了B的外部地址(210.15.27.140:8000)和内部地址(192.168.0.5:5000)。之后,C作为中介,把A的两个地址告诉了B,同时也把B的两个地址告诉了A。
假设A先知道了B的两个地址,则A从192.168.0.4:5000处同时向B的两个地址192.168.0.5:5000和210.15.27.140:8000发包,由于 A和B在两个不同的NAT后面,故从A(192.168.0.4:5000)到B(192.168.0.5:5000)的包肯定不通,现在看A (192.168.0.4:5000)到B(210.15.27.140:8000)的包,分如下两种情况:
1、B侧NAT属于Full Cone NAT
则无论A侧NAT属于Cone NAT还是Symmetric NAT,包都能顺利到达B。如果P2P程序设计得好,使得B主动到A的包也能借用A主动发起建立的通道的话,则即使A侧NAT属于Symmetric NAT,B发出的包也能顺利到达A。
结论1:只要单侧NAT属于Full Cone NAT,即可实现双向通信。
2、B侧NAT属于Restricted Cone或Port Restricted Cone
则包不能到达B。再细分两种情况
(1)、A侧NAT属于Restricted Cone或Port Restricted Cone
虽然先前那个初始包不曾到达B,但该发包过程已经在A侧NAT上留下了足够的记录:A(192.168.0.4:5000)-> (210.21.12.140:8000)->B(210.15.27.140:8000)。如果在这个记录没有超时之前,B也重复和A一样的动作,即向A(210.21.12.140:8000)发包,虽然A侧NAT属于Restricted Cone或Port Restricted Cone,但先前A侧NAT已经认为A已经向B(210.15.27.140:8000)发过包,故B向A(210.21.12.140:8000)发包能够顺利到达A。同理,此后A到B的包,也能顺利到达。
结论2:只要两侧NAT都不属于Symmetric NAT,也可双向通信。换种说法,只要两侧NAT都属于Cone NAT,即可双向通信。
(2)、A侧NAT属于Symmetric NAT
因为A侧NAT属于Symmetric NAT,且最初A到C发包的过程在A侧NAT留下了如下记录:A(192.168.0.4:5000)->(210.21.12.140: 8000)-> C(210.15.27.166:2000),故A到B发包过程在A侧NAT上留下的记录为:A(192.168.0.4:5000)-> (210.21.12.140:8001)->B(210.15.27.140:8000)(注意,转换后端口产生了变化)。而B向A的发包,只能根据C给他的关于A的信息,发往A(210.21.12.140:8000),因为A端口受限,故此路不通。再来看B侧NAT,由于B也向A发过了包,且 B侧NAT属于Restricted Cone或Port Restricted Cone,故在B侧NAT上留下的记录为:B(192.168.0.5:5000)->(210.15.27.140:8000)->A (210.21.12.140:8000),此后,如果A还继续向B发包的话(因为同一目标,故仍然使用前面的映射),如果B侧NAT属于 Restricted Cone,则从A(210.21.12.140:8001)来的包能够顺利到达B;如果B侧NAT属于Port Restricted Cone,则包永远无法到达B。
结论3:一侧NAT属于Symmetric NAT,另一侧NAT属于Restricted Cone,也可双向通信。
显然,还可得出另一个不幸的结论4,两个都是Symmetric NAT或者一个是Symmetric NAT、另一个是Port Restricted Cone,则不能双向通信。
上面的例子虽然只是分析了最初发包是从A到B的情况,但是,鉴于两者的对称性,并且如果P2P程序设计得足够科学,则前面得出的几条结论都是没有方向性,双向都适用的。
通过上述分析,我们得知,在穿越NAT方面,Symmetric NAT和Port Restricted Cone是有本质区别的,尽管他们表面上看起来相似。我们上面得出了四条结论,而natcheck网站则把他归结为一条:只要两侧NAT都属于Cone NAT(含Full Cone、Restricted Cone和Port Restricted Cone三者),即可双向通信。而且natcheck网站还建议尽量使用Port Restricted Cone,以充分利用其端口受限的属性确保安全性。目前,国内充分利用了上述思路的具有代表性的P2P软件是“E话通”(
www.et66.com)。
在对natcheck提供的思路进行详细分析后,开始探讨本文主题:iptables与natcheck。
Natcheck 脱胎于Stun协议,由拙作“iptables与stun”一文可知,其对iptables进行的穿越NAT兼容性测试结果必然是GOOD。此外,我在该文中还提到一句,如果在每个NAT后面仅有一个客户端这种特殊情况下,iptables就是一个标准的Port restricted Cone。根据前面natcheck的结论,这样两个iptables后面的客户端应该可以互相穿越对方的NAT。让我们来看一下实际情况(例2)呢?
仍然参考前例,只是两侧都使用iptables来进行地址转换。(因为采用了iptables,故此处和前例稍有点区别,即转换后源端口不变)
A 与B通过C交换对方地址的初始化环节此处略去,我们从A(192.168.0.4:5000)向B(210.15.27.140:5000)(注意因使用 iptables而导致端口和前例不一样)发包开始分析,因为在本例中,两侧均只有一个客户端,我们姑且把iptables简化成Port restricted Cone看待。
如前例一样,从A(192.168.0.4:5000)到B(210.15.27.140: 5000)的第一个包必不能到达B,但其会在A侧iptables上留下记录,在这条记录没有超时之前(iptables下默认30秒),如果B也向A (210.21.12.140:5000)发包,如前所述,按理该包应该能够到达A,但事实上却是永远到不了。
难道是natcheck的结论错了,或者是特殊情况下iptables并不是Port restricted Cone(即仍然是Symmetric NAT),我们还是别忙着再下结论,先来看看来两侧iptables上留下的记录吧:
A侧:cat /proc/net/ip_conntrack | grep 192.168.0.4 | grep udp
udp 17 18 src=192.168.0.4 dst=210.15.27.140 sport=5000 dport=5000 [UNREPLIED] src=210.15.27.140 dst=210.21.12.140 sport=5000 dport=5000 use=1
B侧:cat /proc/net/ip_conntrack | grep 192.168.0.5 | grep udp
udp 17 26 src=192.168.0.5 dst=210.21.12.140 sport=5000 dport=5000 [UNREPLIED] src=210.21.12.140 dst=210.15.27.140 sport=5000 dport=1026 use=1
把两条记录翻译如下:(关于ip_conntrack文件的分析,请见
http://www.sns.ias.edu/~jns/secu … bles_conntrack.html
A(192.168.0.4:5000)-> A侧NAT(转换后210.21.12.140:5000)-> B(210.15.27.140:5000)
B(192.168.0.5:5000)-> B侧NAT(转换后210.15.27.140:1026)-> A(210.21.12.140:5000)
奇怪,B到A的包在映射后源端口号怎么变了呢,按理不应该呀?因为按照iptables转换原则(详见“iptables与stun”),要求尽量保持源端口号不变,除非socket有重复。难道B侧NAT上还有重复记录,再cat一下呢?
B侧:cat /proc/net/ip_conntrack | grep 210.21.12.140 | grep udp
udp 17 10 src=210.21.12.140 dst=210.15.27.140 sport=5000 dport=5000 [UNREPLIED] src=210.15.27.140 dst=210.21.12.140 sport=5000 dport=5000 use=1
udp 17 16 src=192.168.0.5 dst=210.21.12.140 sport=5000 dport=5000 [UNREPLIED] src=210.21.12.140 dst=210.15.27.140 sport=5000 dport=1026 use=1
操!还果真有两条差不多的记录,第一条与NAT无关,是A到B的包在B侧iptables上留下的记录,产生时间上略早于第二条记录,其构成的socket是(210.21.12.140:5000,210.15.27.140:5000)。第二条即B到A的包产生的记录,其构成的socket是(210.15.27.140:1026,210.21.12.140:5000),如果其源端口不改动,即是(210.15.27.140:5000,210.21.12.140:5000),还真和第一条记录重复了呢,怪不得转换后需要修改源端口,也怪不得B 发包到不了A。
为什么是这样的结果呢?我们知道,iptables是一个有状态的防火墙,他通过连接跟踪模块来实现状态检测的功能,该模块检查所有到来的数据包,也就是说,该模块不仅对NAT起作用,而且对普通的包过滤也起作用。显然,在上述例子里,A到B的包就是作为普通的包过滤而被记载在 B侧iptables的连接跟踪表里,导致后来B到A的包为避免socket重复而不得不改换端口号,从而导致无法实现双向通信。看来,natcheck 的结论并没有错,只是由于iptables具有状态检测的新特性导致即使在特殊情况下iptables又从Port restricted Cone变成了Symmetric NAT而已。
那么,有办法解决这一问题吗?根据连接跟踪的特性,在iptables下,只要启用了 NAT,就肯定要启用连接跟踪功能,而只要启用了连接跟踪功能,就必然顺带跟踪普通包过滤(启用连接跟踪后,似乎无法控制不让跟踪普通包过滤),也就是说,只要用NAT,就无法避免上述情况,真残酷!然而,这却是事实,即只要两端都采用了iptables作为NAT,则尽管两侧都通过了natcheck 的兼容性测试,但iptables两侧永远也不能互相穿越。
在“iptables与stun”一文中曾经附带提到,Win2000下的ics 或nat在Stun协议下的表现和iptables是完全一样的。那么,在natcheck下,表现是否还一致呢?答案是否定的,虽然Win2000下的 ics或nat也具有状态检测的功能,但该状态检测,仅对NAT起作用,不对普通包过滤起作用。所以在两侧都是Win2000下的ics或nat可以作为 Port restricted Cone的特殊情况下,是允许被穿越的。另外,在一侧使用iptables,另一侧使用Win2000下的ics或nat,但两者都表现为Port restricted Cone的特殊情况下,从某个方向发起,最终是允许互相穿越的,但是这种穿越不具有对称性,即从另一个方向发起,则永远无法穿越,具体原理,读者可以参考例2自行分析。

iptables下udp穿越基础篇—-iptables与stun

摘自:http://www.360doc.com/content/070205/13/17255_356485.html

iptables与stun

Stun协议(Rfc3489、详见http://www.ietf.org/rfc/rfc3489.txt)将NAT粗略分为4种类型,即Full Cone、Restricted Cone、Port Restricted Cone和Symmetric。举个实际例子(例1)来说明这四种NAT的区别:
A机器在私网(192.168.0.4)
NAT服务器(210.21.12.140)
B机器在公网(210.15.27.166)
C机器在公网(210.15.27.140)
现在,A机器连接过B机器,假设是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:8000)-> B(210.15.27.166:2000)。
同时A从来没有和C通信过。
则对于不同类型的NAT,有下列不同的结果:
Full Cone NAT:C发数据到210.21.12.140:8000,NAT会将数据包送到A(192.168.0.4:5000)。因为NAT上已经有了192.168.0.4:5000到210.21.12.140:8000的映射。
Restricted Cone:C无法和A通信,因为A从来没有和C通信过,NAT将拒绝C试图与A连接的动作。但B可以通过210.21.12.140:8000与A的192.168.0.4:5000通信,且这里B可以使用任何端口与A通信。如:210.15.27.166:2001 -> 210.21.12.140:8000,NAT会送到A的5000端口上。
Port Restricted Cone:C无法与A通信,因为A从来没有和C通信过。而B也只能用它的210.15.27.166:2000与A的192.168.0.4:5000通信,因为A也从来没有和B的其他端口通信过。该类型NAT是端口受限的。
Symmetric NAT:上面3种类型,统称为Cone NAT,有一个共同点:只要是从同一个内部地址和端口出来的包,NAT都将它转换成同一个外部地址和端口。但是Symmetric有点不同,具体表现在:只要是从同一个内部地址和端口出来,且到同一个外部目标地址和端口,则NAT也都将它转换成同一个外部地址和端口。但如果从同一个内部地址和端口出来,是到另一个外部目标地址和端口,则NAT将使用不同的映射,转换成不同的端口(外部地址只有一个,故不变)。而且和Port Restricted Cone一样,只有曾经收到过内部地址发来包的外部地址,才能通过NAT映射后的地址向该内部地址发包。
现针对Symmetric NAT举例说明(例2):
A机器连接过B机器,假使是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:8000)-> B(210.15.27.166:2000)
如果此时A机器(192.168.0.4:5000)还想连接C机器(210.15.27.140:2000),则NAT上产生一个新的映射,对应的转换可能为A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:8001)-> C(210.15.27.140:2000)。此时,B只能用它的210.15.27.166:2000通过NAT的210.21.12.140:8000与A的192.168.0.4:5000通信, C也只能用它的210.15.27.140:2000通过NAT的210.21.12.140:8001与A的192.168.0.4:5000通信,而B或者C的其他端口则均不能和A的192.168.0.4:5000通信。
通过上面的例子,我们清楚了Stun协议对NAT进行分类的依据。那么,我们现在根据上述分类标准(或例子),来简要分析一下iptables的工作原理(仅指MASQUERADE、下同),看看他又是属于哪种NAT呢?
首先,我们去网上下载一个使用Stun协议检测NAT的工具,网址在http://sourceforge.net/projects/stun/,使用该工具对iptables的检测结果是Port restricted NAT detected。
我们先不要急着接受这个检测结果,还是先来分析一下iptables的工作原理吧!
iptables在转换地址时,遵循如下两个原则:
1、尽量不去修改源端口,也就是说,ip伪装后的源端口尽可能保持不变。(即所谓的Preserves port number)
2、更为重要的是,ip伪装后只需保证伪装后的源地址/端口与目标地址/端口(即所谓的socket)唯一即可。
仍以前例说明如下(例3):
A机器连接过B机器,假使是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5000)-> B(210.15.27.166:2000)。(注意,此处NAT遵循原则1、故转换后端口没有改变)
如果此时A机器(192.168.0.4:5000)还想连接C机器(210.15.27.140:2000),则NAT上产生一个新的映射,但对应的转换仍然有可能为A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5000)-> C(210.15.27.140:2000)。这是因为NAT(转换后210.21.12.140:5000)-> B(210.15.27.166:2000)和NAT(转换后210.21.12.140:5000)-> C(210.15.27.140:2000)这两个socket不重复。因此,对于iptables来说,这既是允许的(第2条原则)、也是必然的(第1条原则)。
在该例中,表面上看起来iptables似乎不属于Symmetric NAT,因为它看起来不符合Symmetric NAT的要求:如果从同一个内部地址和端口出来,是到另一个目标地址和端口,则NAT将使用不同的映射,转换成不同的端口(外部地址只有一个,故不变)。相反,倒是符合除Symmetric NAT外的三种Cone NAT的要求:从同一个内部地址和端口出来的包,NAT都将它转换成同一个外部地址和端口。加上iptables具有端口受限的属性(这一点不容置疑,后面举反例证明之),所以好多检测工具就把iptables报告为Port restricted NAT类型了。
下面仍以前例接着分析(例4):
在前例中增加D机器在A同一私网(192.168.0.5)
A机器连接过B机器,假使是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5000)-> B(210.15.27.166:2000)
D机器连接过C机器,假使是 D(192.168.0.5:5000)-> NAT(转换后210.21.12.140:5000)-> C(210.15.27.140:2000)
由iptables转换原则可知,上述两个转换是允许且必然的。
如果此时A机器(192.168.0.4:5000)还想连接C机器(210.15.27.140:2000),则NAT上产生一个新的映射,但对应的转换则变为A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5001)-> C(210.15.27.140:2000)。这是因为,如果仍然将其转换为210.21.12.140:5000的话,则其所构成的socket(210.21.12.140:5000->210.15.27.140:2000)将和D->C的socket一致,产生冲突,不符合iptables的第2条原则(注意,此处以5001表示转换后不同的端口,但事实上,iptables却并不按照内部端口+1的原则来产生新的端口)。在本例中我们注意到,从同一个内部地址和端口A(192.168.0.4:5000)出来,到不同的目标地址和端口,则NAT使用了不同的映射,转换成不同的端口。
上面这个例子在实际环境中比较少见,我们再以QQ为例举一个真实且常见的例子(例5)。
假设
A(192.168.0.4)和 D(192.168.0.5)是同一NAT服务器(210.21.12.140)保护的两台私网机器,都运行了QQ客户端程序。
B机器在公网(210.15.27.166),运行QQ服务器程序。
C机器在公网(210.15.27.140),运行QQ客户端程序。
A上QQ先登陆到B,按照原则1,使用如下映射:
A(192.168.0.4:4000)-> NAT(转换后210.21.12.140:4000)-> B(210.15.27.166:8000)(原则1,端口不变)
接着D上QQ也登陆到B,按照原则2,使用如下映射:
D(192.168.0.5:4000)-> NAT(转换后210.21.12.140:4001)-> B(210.15.27.166:8000)(原则2,scoket不能有重复,此处4001仅表示转换后不同的端口,实际环境中决不是4001)
然后D欲和公网C(210.15.27.140)上的QQ通信,按照iptables转换原则,使用如下映射:
D(192.168.0.5:4000)-> NAT(转换后210.21.12.140:4000)-> C(210.15.27.140:4000)
到此我们发现,和上例一样,从同一个内部地址和端口D(192.168.0.5:4000)出来,到不同的目标地址和端口,则NAT使用了不同的映射,转换成不同的端口。但和上例不一样的是,本例显然普遍存在于实际环境中。
上面所举两例表明,结论刚好和例3相反,即iptables应该属于Symmetric NAT。
为什么会出现彼此矛盾的情况呢?首先从NAT分类的定义上来看, Stun协议和iptables对映射的理解不同。Stun协议认为,一个映射的要素是:内部地址端口和NAT转换后地址端口的组合。而在iptables看来,一个映射的要素是:NAT转换后地址端口和外部目标地址端口的组合。另一方面则是Stun协议里Discovery Process给出的测试环境不够全面之故,他只考虑了NAT后面仅有一台私网机器的特例(例3),没有考虑NAT后面可以有多台私网机器的普遍例子(例5)。正是由于这两个原因,直接导致了上述矛盾的发生。所以,凡按照Stun协议标准设计的NAT分类检测工具对iptables的检测结果必然是Port restricted NAT。(事实上,在例3那样的特例下,iptables确实是一个标准的Port restricted NAT)
那么,iptables究竟属于哪种NAT呢?我们再来回顾一下Stun协议对Cone NAT的要求:所有(或只要是)从同一个内部地址和端口出来的包,NAT都将它转换成同一个外部地址和端口。虽然iptables在部分情况下满足“从同一个内部地址和端口出来的包,都将把他转换成同一个外部地址和端口”这一要求,但它不能在所有情况下满足这一要求。所以理论上,我们就只能把iptables归为Symmetric NAT了。
下面,我们再来分析一下iptables的端口受限的属性,我们举一个反例证明之(例6),仍以前例说明如下:
A机器连接过B机器,假使是 A(192.168.0.4:5000)-> NAT(转换后210.21.12.140:5000)-> B(210.15.27.166:2000)
D机器连接过C机器,假使是 D(192.168.0.5:5000)-> NAT(转换后210.21.12.140:5000)-> C(210.15.27.140:2000)
现假设iptables不具有端口受限的属性,则另一E机器在公网(210.15.27.153:2000)或C(210.15.27.140:2001)向210.21.12.140:5000发包的话,应该能够发到内部机器上。但事实是,当这个包到达NAT(210.21.12.140:5000)时,NAT将不知道把这个包发给A(192.168.0.4:5000)还是D(192.168.0.5:5000)。显然,该包只能丢弃,至此,足以证明iptables具有端口受限的属性。
所以,iptables是货真价实的Symmetric NAT。

附:
1、Stun全称Simple Traversal of UDP Through NATs,所以本文所涉及的包,皆为UDP包。
2、本文虽然是针对linux下iptables的分析,但倘若把本文中关键词“iptables”换成“Win2000下的ics或nat”,则本文的分析过程完全适用于Win2000下的ics或nat。即理论上Win2000下的ics或nat也是货真价实的Symmetric NAT,但实际上,凡按照Stun协议标准设计的NAT分类检测工具对其检测结果也必然是Port restricted NAT。其实,不光是linux下iptables、或者Win2000下的ics或nat、再或者任何其他NAT产品,只要他们遵循和iptables一样的两条转换原则,那么他们在Stun协议下的表现是完全一样的。
3、虽然Win2000下的ics或nat在Stun协议下的表现和iptables完全一样,但其NAT时所遵循的原则1和iptables还是略有差别:iptables对内部私网机器来的所有源端口都将适用Preserves port number,除非确因原则2发生冲突而不得不更换源端口号,但在更换端口号时并不遵循内部端口+1原则(似乎没有规律)。Win2000下的ics或nat则仅对内部私网机器来的部分源端口(1025–3000)适用Preserves port number,对于那些超过3000的源端口号或者因原则2发生冲突的端口号,系统从1025开始重新按序分配端口,在此过程中,仍然遵循如前两个原则,只是原则1不再Preserves port number而已(在不与原则2发生冲突的前提下,尽量重复使用小的端口号,故使用1025的几率远远大于1026、1027…)。

P2P网络“自由”穿越NAT的“秘密”

穿越NAT的意义:
  NAT是为了节省IP地址而设计的,但它隐藏了内网机器的地址,“意外”起到了安全的作用。对外不可见,不透明的内部网络也与互联网的“公平”应用,“相互共享”的思想所不容,尤其是P2P网络中“相互服务”的宗旨,所以穿越NAT,让众多内部网络的机器也参与到P2P网络中的大集体中来,一直是P2P开发者的所希望的。穿越NAT需要借助外部的支持,说白了就是“内外勾结”,骗过NAT。很多P2P网络成功地实现了这一目标,但还是有一些“遗憾”—并非所有的情况下都可以。由于客户端是主动登录P2P网络才可穿越,所以P2P的方式也没有违背企业的内部管理原则,毕竟“自由世界”的加入都是自觉自愿的。
  NAT原理:
  NAT(Network Address Translation)网络地址转换/网络地址翻译。
  工作原理:NAT主要的通过对数据包头的地址替换来完成内网计算机访问外网服务的。当内部机器要访问外部网络时,NAT设备把内部的IP1与端口号1(网络层地址与传输层地址),转换成NAT的外部IP2与新的端口号2,再送给外部网络,数据返回时,再把目的为IP2:端口2的数据包替换为IP1:端口1,送给内网机器。若通讯协议的内容中有IP地址的传递,如FTP协议,NAT在翻译时还要注意数据包内涉及协议地址交互的地方也要替换,否则协议就会出现地址混乱。在NAT设备中维护了这个要替换地址的映射表,并根据内部计算机的通讯需求维护该表。外部网络来数据包能否进入NAT,主要是看是否已经有可映射的表项,若没有就会丢弃。
  
  NAT的外部公网地址可以是一个IP,也可以是一个网段,形成地址池。NAT还可以把某个外网地址直接影射给内网的某个服务器,让外网的用户可以直接访问到这台服务器。NAT的工作的隐藏内网的机器,但允许内网主动打开到外网的通讯“通道”,也就是建立映射表项。
  NAT给P2P带来的问题是:NAT只允许单方面发起连接,通讯的双方不是平等的,P2P网络的基础有了问题,具体的表现为:
  内网主机IP是私有的,外部主机看不到,也无法主动发起连接
  即使知道了内网IP,但NAT会丢弃没有在影射表的数据包
  内网主机可以作为客户端访问外网,但不能作为服务器提供服务
  当两个主机都位于各自的NAT之后,要实现P2P的连接,就不仅是谁主动的问题,而是如何解决在两个NAT上同时有对方映射表项的问题。
STUN协议(IETF RFC 3489):
  STUN协议是一种通道协议,可以作为正式通讯前的通路建立,它采用的是用户终端干预的一种方法,可以解决应用协议内部传递IP地址给NAT带来的麻烦。用户通过其他方法得到其地址对应在NAT出口上的对外地址,然后在报文负载中所描述的地址信息就直接填写NAT上对外地址,而不是内网的私有IP,这样报文的内容在经过NAT时就按普通的NAT流程转换报文头部的IP地址即可,负载内的IP地址信息无需再修改。利用STUN的思路可以穿越NAT。STUN协议是客户端/服务器协议,分两种请求方式:一是UDP发送的绑定请求(Binding Requests),二是TCP发送的秘密请求(Shared Secret Requests)。绑定请求用于确定NAT分配的绑定地址。
  STUN标准中,根据内部终端的地址(P:p)到NAT出口的公网地址(A:b)的影射方式,把NAT分为四种类型:
  
  1. Full Cone:来自相同的内部地址的请求消息映射为相同的外部地址,与外部地址(目的地址)无关。映射关系为P:p↔A:b,任何外部主机可通过(A:b)发送到数据到(P:p)上。
  2. Restricted Cone:来自相同的内部地址的请求消息映射为相同的外部地址,返回的数据只接受该内部节点曾发数据的那个目的计算机地址X。映射关系为P:p↔A:b↔X,只有来自X的数据包才可通过(A:b)发送到数据到(P:p)上。
  3. Port Restricted Cone:来自相同的内部地址的请求消息映射为相同的外部地址,返回的数据只接受该内部节点曾发数据的那个目的地址X:x。映射关系为P:p↔A:b↔X:x,只有来自X:x的数据包才可通过(A:b)发送到数据到(P:p)上。
  4. Symmetric(对称) NAT:只有来自相同的内部地址(P:p),并且发送到同一个地址(X:x) 的请求消息,才被映射为相同的外部地址(A:b),返回的数据只接受该内部节点曾发数据的那个目的地址X:x。映射关系为P:p↔A:b↔X:x,当(P:p)访问(Y:y)时,映射为P:p↔B:c↔Y:y。
  P2P利用STUN穿越NAT:
  位于NAT后面终端A与B要穿越NAT直接通讯,可以借助在公网上的第三者Server来帮助。
  穿越NAT的情况分为为两种方式:1、一方在NAT之后,一方在公网上。这种情况相对简单,只要让NAT之后的终端先发起通讯,NAT就没有作用了,它可以从Server上取得另一个Peer的地址,主动连接,回来的数据包就可以方便地穿越NAT。2、双方都在NAT之后,连接的成功与否与两个NAT的类型有关。主要的思路的先通过终端与Server的连接,获得两个终端在NAT外部的地址(IP与端口号),再由终端向对方的外部地址发邀请包,获取自己与对方通讯的外部地址,俗称为“打洞”。关键是获取了NAT外部映射的地址,就可以发包直接沟通,建立连接。但当一方是对称型,另一方是Port Restricted或对称型时,无法有效获取外部地址,邀请包无法到达对方,也就无法穿越NAT。具体的分析可以根据两个NAT的类型分成若干情况分析,这里给一般的穿越例子。
  
实例:UDP穿越NAT:
  A登录Server,NAT A分配端口11000,Server得到A的地址为100.10.10.10:11000
  B登录Server,NAT B分配端口22000,Server得到B的地址为200.20.20.20:22000
  此时B会把直接来自A的包丢弃,所以要在NAT B上打一个方向为A的洞,那么A就可以向200.20.20.20:22000发送数据了
  打洞的指令来自Server。B向A的地址100.10.10.10:11000发一个UDP报文,被NAT A丢弃,但在NAT B上建立映射记录,NAT B不在丢弃来自A的报文。
  Server通知A可以通讯,A发起数据UDP包给B,NAT B放行,B收到A的包,双方开始通讯
  注:若是对称NAT,当B向A打洞的端口要重新分配(NAT A不会再分配11000端口),B无法获取这个端口,所以不适用本方法。
  实例:TCP穿越NAT:
  A登录Server,NAT A分配端口11000,Server得到A的地址为100.10.10.10:11000
  B登录Server,NAT B分配端口22000,Server得到B的地址为200.20.20.20:22000
  A向B发送TCP数据包SYN:192.168.10.11:1234=>200.20.20.20:22000,在NAT A上打洞
  B向A发送TCP数据包SYN:192.168.20.22:1234=>100.10.10.10:11000,在NAT B上打洞
  通道建立,A与B三次握手建立TCP连接

IP协议的CheckSum函数之理解

IP 协议中的checksum
Author:zfive5(zhaozidong)
Email :zfive5@yahoo.com.cn
 
最近一段时间,对网络又开始追根溯源,最好的办法就是打开开源协议栈看一个究竟,不求写一个完整的ip协议栈,但求通达解惑!
 
众所周知,IP头定义如下:
struct IPHeader
{
   unsigned char ver_hlen;  
   unsigned char tos;
   unsigned short  len;
   unsigned short  id;
   unsigned short  offset;
   unsigned char  ttl;
   unsigned char  type;
   unsigned short   cksum_header;
   unsigned long  ipsrc;
   unsigned long  ipdest;
   /*
     后面可能存在option数据
   */
}
IP头中的大多字段都好理解,只要一本TCP/IP入门的书就可以明明白白了,对cksum字段理解,如果只是看书,到头来很可能还不清楚怎么算它!
 
关于cksum_header的描述在《tcp/ip卷一》中是这样描述的:
   首部检验和字段是根据I P 首部计算的检验和码。它不对首部后面的数据进行计算。I C M P I G M P U D P T C P 在它们各自的首部中均含有同时覆盖首部和数据检验和码。
   为了计算一份数据报的I P 检验和,首先把检验和字段置为0 。然后,对首部中每个16 bit进行二进制反码求和(整个首部看成是由一串16 bit 的字组成),结果存在检验和字段中。当收到一份I P 数据报后,同样对首部中每个16 bit 进行二进制反码的求和。由于接收方在计算过,程中包含了发送方存在首部中的检验和,因此,如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该为全1 。如果结果不是全1 (即检验和错误),那么I P 就丢弃收到的数据报。但是不生成差错报文,由上层去发现丢失的数据报并进行重传。
 
不知道有多少能人看完此描述后,写出算法或函数来!
 
正确的函数如下:
unsigned short CheckSum(unsigned short *szBUF,int iSize)
{       
       unsigned long ckSum=0;
    for(;iSize>1;iSize-=sizeof(unsigned short))
              ckSum+=*szBUF++;
 
       if(iSize==1)
              ckSum+=*(unsigned char *)szBUF;
 
       ckSum=(ckSum>>16)+(ckSum&0xffff);
       ckSum+=(ckSum>>16);
       return(unsigned short )(~ckSum);
}
 
让我们假设一个IP头数据,来解cksum的惑!
 
IP头数据:
 
01000101   / ver_hlen;
00000000   /tos*/
00000000 00000010/*len*/
00000000 00000000/*id*/
00000000 00000000/*offset*/
00000001/*ttl*/
00010001/*type*/
00000000 00000000/*cksum0)*/
01111111 00000000 00000000 0000001/*sip*/
01111111 00000000 00000000 0000001/*dip*/
 
运算过程(注意是大端格式加):
 
01000101 00000000
01000101 00000000
00000000 00000010
01000101 00000010
00000000 00000000
01000101 00000010
00000000 00000000
01000101 00000010
00000100 00010001
01001001 00010011
00000000 00000000
01001001 00010011
01111111 00000000
11001000 00010011
00000000 00000001
11001000 00010100
01111111 00000000
01000111 00010100
00000000 00000001
和: 101000111 00010101
                  
ckSum=(ckSum>>16)+(ckSum&0xffff)后:
 
   00000000 00000001                1
 01000111 00010101
和: 01000111 00010110
 
ckSum+=(ckSum>>16)后:
   
   01000111 00010110
   00000000 00000000
和: 01000111 00010110
 
~: 10111000 11101000(效检和)
 
 
运算过程(注意用小端格式加):
 
00000000 01000101
00000000 01000101
00000010 00000000
00000010 01000101
00000000 00000000
00000010 01000101
00000000 00000000
00000010 01000101
00010001 00000100
00010011 01001001
00000000 00000000  
00010011 01001001
00000000 01111111  
00010011 11001000
00000001 00000000  
00010100 11001000
00000000 01111111  
00010101 01000111
00000001 00000000  
和: 00010110 01000111
ckSum=(ckSum>>16)+(ckSum&0xffff)后:
 
   00000000 00000000                1
 00010110 01000111
和: 00010110 01000111
 
ckSum+=(ckSum>>16)后:
   
   00010110 01000111
   00000000 00000000
和: 00010110 01000111
 
~: 11101001 10111000(效检和)
 
 
checksum211101000 10111000(小端)
 
checksum110111000 11101000(大端)
 
算法一样,说白了就是循环加,加到没有进位为止,然后在取反!
 
在现在《TCPIP卷二》中的cksum实现有以下语句:
while(sum>>16)
 sum=(sum&0xffff)+(sum>>16);
 
通过它更能说明就是在作循环加操作,现在又有一个疑问:
       ckSum=(ckSum>>16)+(ckSum&0xffff);
       ckSum+=(ckSum>>16);         
while(sum>>16)
   sum=(sum&0xffff)+(sum>>16);
等价吗?
 
等价,在一定条件下等价,大家都知道ip理论上最长是0xffff
那么
checksum最大不会超过:0xffff0000
这样>>16后为0xffff
0xffff+checksum最大0x1fffe
0x1fffe >>16+0x1fffe=0xffff
注意了没有了进位
所以得到等价的结论!
 
 
以前每读到cksum注解时,书上只是草草曰16位反码和之云云,没有强调进位也要参加运算一点征兆,直到最近写了不少程序才看清楚这个细节!
 
源码之下必解惑!