class NATPMP

Constants

CLIENT_PORT
DEFAULT_LIFETIME
DELAY_MSEC
MAX_WAIT_SEC
OPCODE
PMP_VERSION
RETCODE

Return codes

SERVER_PORT
Version

Attributes

life[R]
mapped[R]
maxlife[R]
priv[R]
pub[R]
type[R]

Public Class Methods

GW() click to toggle source

Determine the default gateway

# File lib/natpmp.rb, line 28
def self.GW
  return @gw if @gw
  @gw = case RUBY_PLATFORM
  when /darwin/
    routes = `netstat -nrf inet`.split("\n").select{|l| l=~/^default/}
    raise "Can't find default route" unless routes.size > 0
    routes.first.split(/\s+/)[1]
  when /linux/
    routes = `ip route list match 0.0.0.0`.split("\n").select{|l| l =~ /^default/}
    raise "Can't find default route" unless routes.size > 0
    routes.first.split(/\s+/)[2]
  else
    raise "Platform not supported!"
  end
end
addr() click to toggle source

Return the externally facing IPv4 address

# File lib/natpmp.rb, line 83
def self.addr
  reply = self.send [0, OPCODE[:addr]].pack("CC")
  sssoe = reply.unpack("x4N").first
  addr = reply.unpack("x8CCCC")
  return addr.join('.')
end
map(priv, pub, maxlife = DEFAULT_LIFETIME, type = :tcp) { |map| ... } click to toggle source
# File lib/natpmp.rb, line 125
def self.map priv, pub, maxlife = DEFAULT_LIFETIME, type = :tcp, &block

  map = NATPMP.new(priv, pub, block_given? ? nil: maxlife, type)
  map.request!
  if block_given?
    begin
      yield map
    ensure
      map.revoke!
      map = nil
    end
  end
  return map

end
new(priv, pub, maxlife, type) click to toggle source
# File lib/natpmp.rb, line 92
def initialize priv, pub, maxlife, type
  @priv = priv
  @pub = pub
  @maxlife = maxlife
  @type = type
  @renew = nil # Renewal thread

  # These are filled in when a request is made
  #
  @life = 0
  @mapped = 0
end
send(msg) click to toggle source
# File lib/natpmp.rb, line 52
def self.send msg
  sop = msg.unpack("xC").first
  sock = UDPSocket.open
  sock.connect(NATPMP.GW, SERVER_PORT)
  cb = sock.send(msg, 0)
  raise "Couldn't send!" unless cb == msg.size
  delay = DELAY_MSEC/1000.0
  begin
    sleep delay # to give time for the response to arrive!
    (reply, sendinfo) = sock.recvfrom_nonblock(16)
    sender = Addrinfo.new sendinfo
    raise "Being spoofed!" unless sender.ip_address == NATPMP.GW
    (ver,op,res) = reply.unpack("CCn")
    raise "Invalid version #{ver}" unless ver == PMP_VERSION
    raise "Invalid reply opcode #{op}" unless op == 128 + sop
    raise "Request failed (code #{RETCODE.key(res)})" unless res == RETCODE[:success]
    return reply
  rescue IO::WaitReadable
    if delay < MAX_WAIT_SEC
      puts "Retrying after #{delay}..." if NATPMP.verbose?
      delay *= 2
      retry
    end
    raise "Waited too long, got no response"
  rescue Errno::ECONNREFUSED
    raise "Remote NATPMP server not found"
  end
end
verbose(flag) click to toggle source
# File lib/natpmp.rb, line 44
def self.verbose flag
  @verbose = flag
end
verbose?() click to toggle source
# File lib/natpmp.rb, line 48
def self.verbose?
  return @verbose
end

Public Instance Methods

inspect() click to toggle source
# File lib/natpmp.rb, line 121
def inspect
  "#{NATPMP.GW}:#{@mapped}->#{@type}:#{@priv} (#{@life} sec)"
end
request!() click to toggle source

See section 3.3

# File lib/natpmp.rb, line 106
def request!
  rsp = NATPMP.send [0, OPCODE[@type], 0, @priv, @pub, @maxlife||DEFAULT_LIFETIME].pack("CCnnnN")
  (sssoe, priv, @mapped, @life) = rsp.unpack("x4NnnN")
  raise "Port mismatch: requested #{@priv} received #{priv}" if @priv != priv
  STDERR.puts "Mapped (at #{sssoe}) #{inspect}"
  schedule_renew! if @maxlife.nil?
end
revoke!() click to toggle source

See section 3.4

# File lib/natpmp.rb, line 115
def revoke!
  @renew.exit if @renew
  rsp = NATPMP.send [0, OPCODE[@type], 0, @priv, 0, 0].pack("CCnnnN")
  STDERR.puts "Revoked #{inspect}" if NATPMP.verbose?
end

Private Instance Methods

schedule_renew!() click to toggle source

As per section 3.3 re-issue the request using the actual mapped port

# File lib/natpmp.rb, line 144
def schedule_renew!
  Thread.abort_on_exception = true
  # As per section 3.3 re-issue the request using the actual mapped port
  @renew = Thread.new @life do |life|
    wait_time = life/2
    STDERR.puts "Renewal in #{wait_time} seconds" if NATPMP.verbose?
    sleep wait_time
    rsp = NATPMP.send [0, OPCODE[@type], 0, @priv, @mapped, @life].pack("CCnnnN")
    (sssoe, priv, @mapped, @life) = rsp.unpack("x4NnnN")
    STDERR.puts "Renewed (at #{sssoe}) #{inspect}" if NATPMP.verbose?
    schedule_renew!
  end
end