golang nat 打洞

2022-08-03 13:57:57 浏览数 (1)

首先介绍下基础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都可以打洞。

0 人点赞