webtorrent / instant.io

🚀 Streaming file transfer over WebTorrent (torrents on the web)
https://instant.io
MIT License
3.43k stars 436 forks source link

Add NAT checker in client.js #366

Open zackees opened 2 years ago

zackees commented 2 years ago

Users behind a "Symetric NAT" will fail to use instant.io to seed content (and possibly download).

Here is an implementation on how to check the getNATtype(cb) will invoke the callback with either "Permissive NAT" or "Symmetric NAT"

NATtype.js

/* global RTCPeerConnection */

const ICE_SERVERS = [
  { urls: 'stun:stun1.l.google.com:19302' },
  { urls: 'stun:stun2.l.google.com:19302' }
]

function parseCandidate (line) {
  let parts
  // Parse both variants.
  if (line.indexOf('a=candidate:') === 0) {
    parts = line.substring(12).split(' ')
  } else {
    parts = line.substring(10).split(' ')
  }

  const candidate = {
    foundation: parts[0],
    component: parts[1],
    protocol: parts[2].toLowerCase(),
    priority: parseInt(parts[3], 10),
    ip: parts[4],
    port: parseInt(parts[5], 10),
    // skip parts[6] == 'typ'
    type: parts[7]
  }

  for (let i = 8; i < parts.length; i += 2) {
    switch (parts[i]) {
      case 'raddr':
        candidate.relatedAddress = parts[i + 1]
        break
      case 'rport':
        candidate.relatedPort = parseInt(parts[i + 1], 10)
        break
      case 'tcptype':
        candidate.tcpType = parts[i + 1]
        break
      default: // Unknown extensions are silently ignored.
        break
    }
  }
  return candidate
};

exports.getNATtype = function getNATtype (cb) {
  cb = cb || console.log
  const candidates = {}
  const rtcOptions = { iceServers: ICE_SERVERS }
  const pc = new RTCPeerConnection(rtcOptions)
  pc.createDataChannel('foo')
  pc.onicecandidate = function (e) {
    if (e.candidate && e.candidate.candidate.indexOf('srflx') !== -1) {
      const cand = parseCandidate(e.candidate.candidate)
      if (!candidates[cand.relatedPort]) candidates[cand.relatedPort] = []
      candidates[cand.relatedPort].push(cand.port)
    } else if (!e.candidate) {
      if (Object.keys(candidates).length === 1) {
        const ports = candidates[Object.keys(candidates)[0]]
        if (ports.length === 1) {
          cb('Permissive NAT')  // eslint-disable-line
        } else {
          cb('Symmetric NAT')  // eslint-disable-line
        }
      }
    }
  }
  pc.createOffer()
    .then(offer => pc.setLocalDescription(offer))
}
drobin04 commented 1 year ago

I think I've just run into this issue; I live in Mexico and most of us are on Carrier Grade NAT (CGNAT), which is also becoming more common with newer ISP's in the US (Starlink, T-Mobile's home internet, etc). I'm able to use the site to move items between devices in my own network, but not crossing outside of the network. Ran into this issue after setting up a webpage with similar code, testing it, and realizing it didn't work outside the network... Then realizing I get the same error(s) on instant.IO... Haha. Still fascinating code though. Would be interesting to see if there's a way around this.

zackees commented 1 year ago

I've had an idea where certain users with permissive nats become peer relay points.