首先介绍下基础nat的四种方式,在进行nat转换的时候,我们在网关路由表上记录了映射关系,这个映射关系可以用六元祖表示
代码语言:javascript复制本地ip:本地port
外网ip:外网port
目标ip:目标port
其中目标ip:目标port可以通配。可以分4种类型
1、全锥形 full cone:映射完成后的外网ip:外网port 可以被任何外网ip端口访问
2、ip 受限制:映射完成后只能目标ip能够访问
3、端口受限制:映射完成后,只能固定的目标ip和端口能够访问。
4、对称型:每次打洞都生成新的外网ip和端口,不能被外网主动访问。
nat穿透
局域网的A访问一个外部主机,这个主机返回A它的nat转换后的ip1 port1。另一个局域网B访问外部主机(stun),外部主机返回B被nat转换后的ip2,port2。A访问B,就是A去访问ip2,port2。B访问A,就是访问ip1,port1。
非对称与非对称:由于A(客户机)无论访问哪个主机,A的nat都是将它转换为ip1 port1(ip1 与port1都不会变化),因此A B都去访问stun,得到的ip与port不会变化。因此可以打洞。
一端对称,一端非对称:A(非对称Nat,且只能是ip不变,port变化的那种情况),一端非对称B,且只能是 full cone 或者ip受限。首先B访问A,B记录A的ip1(只要ip1的信息发过来,就能收到),B的数据必然被A的nat丢掉,但是A就可以访问B了
对称与对称:A去访问stun 得到的为Aip1 Aport1。B去访问stun,得到Bip1 Bport1。A去访问B, A net记录Bip1 Bport1, B去访问A,自身nat将其转换为BIp2, Bport2(Bip1,Bport1是访问stun得到的),但是A的洞只为Bip1,Bport1留着,Bip2,Bport2根本链接不上A,所以2个都改为对称Nat,根本没法打穿。
基于上述理论我们可以用go来测试我们的nat类型,为打洞做准备。
代码语言:javascript复制var (
addrStrPtr = flag.String("server", "stun.voip.blackberry.com:3478", "STUN server address")
)
// RFC5780: 4.3. Determining NAT Mapping Behavior
func mappingTests(addrStr string) error {
mapTestConn, err := connect(addrStr)
if err != nil {
log.Warnf("Error creating STUN connection: %sn", err.Error())
return err
}
// Test I: Regular binding request
log.Info("Mapping Test I: Regular binding request")
request := stun.MustBuild(stun.TransactionID, stun.BindingRequest)
resp, err := mapTestConn.roundTrip(request, mapTestConn.RemoteAddr)
if err != nil {
return err
}
// Parse response message for XOR-MAPPED-ADDRESS and make sure OTHER-ADDRESS valid
resps1 := parse(resp)
if resps1.xorAddr == nil || resps1.otherAddr == nil {
log.Info("Error: NAT discovery feature not supported by this server")
return errNoOtherAddress
}
addr, err := net.ResolveUDPAddr("udp4", resps1.otherAddr.String())
if err != nil {
log.Infof("Failed resolving OTHER-ADDRESS: %vn", resps1.otherAddr)
return err
}
mapTestConn.OtherAddr = addr
log.Infof("Received XOR-MAPPED-ADDRESS: %vn", resps1.xorAddr)
// Assert mapping behavior
if resps1.xorAddr.String() == mapTestConn.LocalAddr.String() {
log.Warn("=> NAT mapping behavior: endpoint independent (no NAT)")
return nil
}
// Test II: Send binding request to the other address but primary port
log.Info("Mapping Test II: Send binding request to the other address but primary port")
oaddr := *mapTestConn.OtherAddr
oaddr.Port = mapTestConn.RemoteAddr.Port
resp, err = mapTestConn.roundTrip(request, &oaddr)
if err != nil {
return err
}
// Assert mapping behavior
resps2 := parse(resp)
log.Infof("Received XOR-MAPPED-ADDRESS: %vn", resps2.xorAddr)
if resps2.xorAddr.String() == resps1.xorAddr.String() {
log.Warn("=> NAT mapping behavior: endpoint independent")
return nil
}
// Test III: Send binding request to the other address and port
log.Info("Mapping Test III: Send binding request to the other address and port")
resp, err = mapTestConn.roundTrip(request, mapTestConn.OtherAddr)
if err != nil {
return err
}
// Assert mapping behavior
resps3 := parse(resp)
log.Infof("Received XOR-MAPPED-ADDRESS: %vn", resps3.xorAddr)
if resps3.xorAddr.String() == resps2.xorAddr.String() {
log.Warn("=> NAT mapping behavior: address dependent")
} else {
log.Warn("=> NAT mapping behavior: address and port dependent")
}
return mapTestConn.Close()
}
我们尝试链接stun服务器:
I,如果连接失败,说明不支持NAT;如果连接成功,说明支持NAT
II,同时切换ip和端口,如果返回的外网ip和端口都没有变,说明是完全锥形NAT;如果失败,说明不是
III,只切换ip能成功,但是II失败,说明是端口限制NAT;如果失败,说明是ip端口限制NAT,这三种形式的nat都可以打洞。