Class: NATPMP

Inherits:
Object
  • Object
show all
Defined in:
lib/natpmp.rb,
lib/natpmp/version.rb

Constant Summary collapse

PMP_VERSION =
0
DEFAULT_LIFETIME =
7200
DELAY_MSEC =
250
MAX_WAIT_SEC =
64
SERVER_PORT =
5351
CLIENT_PORT =
5350
OPCODE =
{ addr: 0, udp: 1, tcp: 2 }
RETCODE =

Return codes

{ success: 0,     # Success
  unsupported: 1, # Unsupported Version
  refused: 2,     # Not Authorized/Refused (e.g., box supports mapping, but user has turned feature off)
  failed: 3,      # Network Failure (e.g., NAT box itself has not obtained a DHCP lease)
  exhausted: 4,   # Out of resources (NAT box cannot create any more mappings at this time)
  opnotsupp: 5    # Unsupported opcode
}
VERSION =
'0.8'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(priv, pub, maxlife, type) ⇒ NATPMP

Returns a new instance of NATPMP.



92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/natpmp.rb', line 92

def initialize priv, pub, maxlife, type
  @priv = priv
  @pub = pub
  @maxlife = maxlife
  raise "Time must be >= 0" if maxlife < 0
  @type = type

  # These are filled in when a request is made
  #
  @life = 0
  @mapped = 0
end

Instance Attribute Details

#lifeObject (readonly)

Returns the value of attribute life.



90
91
92
# File 'lib/natpmp.rb', line 90

def life
  @life
end

#mappedObject (readonly)

Returns the value of attribute mapped.



90
91
92
# File 'lib/natpmp.rb', line 90

def mapped
  @mapped
end

#maxlifeObject (readonly)

Returns the value of attribute maxlife.



90
91
92
# File 'lib/natpmp.rb', line 90

def maxlife
  @maxlife
end

#privObject (readonly)

Returns the value of attribute priv.



90
91
92
# File 'lib/natpmp.rb', line 90

def priv
  @priv
end

#pubObject (readonly)

Returns the value of attribute pub.



90
91
92
# File 'lib/natpmp.rb', line 90

def pub
  @pub
end

#typeObject (readonly)

Returns the value of attribute type.



90
91
92
# File 'lib/natpmp.rb', line 90

def type
  @type
end

Class Method Details

.addrObject

Return the externally facing IPv4 address



83
84
85
86
87
88
# 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

.GWObject

Determine the default gateway



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 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

.map(priv, pub, maxlife = DEFAULT_LIFETIME, type = :tcp, &block) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/natpmp.rb', line 123

def self.map priv, pub, maxlife = DEFAULT_LIFETIME, type = :tcp, &block

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

end

.send(msg) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# 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 = true) ⇒ Object



44
45
46
# File 'lib/natpmp.rb', line 44

def self.verbose flag = true
  @verbose = flag
end

.verbose?Boolean

Returns:

  • (Boolean)


48
49
50
# File 'lib/natpmp.rb', line 48

def self.verbose?
  return @verbose
end

Instance Method Details

#inspectObject



119
120
121
# File 'lib/natpmp.rb', line 119

def inspect
  "#{NATPMP.GW}:#{@mapped}->#{@type}:#{@priv} (#{@life} sec)"
end

#request!Object

See section 3.3



106
107
108
109
110
111
# File 'lib/natpmp.rb', line 106

def request!
  rsp = NATPMP.send [0, OPCODE[@type], 0, @priv, @pub, @maxlife].pack("CCnnnN")
  (sssoe, priv, @mapped, @life) = rsp.unpack("x4NnnN")
  raise "Port mismatch: requested #{@priv} received #{priv}" if @priv != priv
  STDERR.puts "Mapped #{inspect}" if NATPMP.verbose
end

#revoke!Object

See section 3.4



114
115
116
117
# File 'lib/natpmp.rb', line 114

def revoke!
  rsp = NATPMP.send [0, OPCODE[@type], 0, @priv, 0, 0].pack("CCnnnN")
  STDERR.puts "Revoked #{inspect}" if NATPMP.verbose
end