It is used by VoIP and some crypto currencies to make able connections between peers under NAT firewalls (your WiFi routers).
Usage:
stun = dofile("stun.lua")
stun(function(ip, port)
print("My ext. IP/port:", ip, port)
end)
stun(function(ip, port)
print("My ext. IP/port:", ip, port)
end, "stun.sipgate.net")
stun(function(ip, port)
print("My ext. IP/port:", ip, port)
end, "stun.sipgate.net", 3478)
Callback gets ip/port on success or nil/nil on failure.
Code of module - IT LEAKS! Feel free to fix)
return function(callback, serverAddr, serverPort)
if type(callback) ~= "function" then
return false
else
if not serverAddr or serverAddr == "" then
serverAddr = "stun.wtfismyip.com"
end
serverPort = tonumber(serverPort or 3478)
end
local stun = {}
stun.BIND_REQUEST = string.char(0x00, 0x01)
stun.BIND_SUCCESS = string.char(0x01, 0x01)
stun.MAGIC_COOKIE = string.char(0x21, 0x12, 0xA4, 0x42)
stun.ATTR_MAPPEDADDR = 0x0001
local function r16u(data, pos)
return string.byte(data, pos) * 256 + string.byte(data, pos + 1)
end
local function w16u(val)
local lo = val % 256
local hi = (val - lo) / 256
return string.char(hi, lo)
end
local function parseIp(data, pos)
local ip = string.format("%d.%d.%d.%d",
string.byte(data, pos),
string.byte(data, pos + 1),
string.byte(data, pos + 2),
string.byte(data, pos + 3))
return ip
end
local function createTxId()
local tid = ""
for i = 1, 12 do
tid = tid .. string.char(node.random(0, 255))
end
return tid
end
local function createStunRequest()
local request = ""
request = request .. stun.BIND_REQUEST
request = request .. w16u(0)
request = request .. stun.MAGIC_COOKIE
request = request .. stun.txid
return request
end
local function parseStunPayload(payload)
local pos = 1
while pos < #payload do
local attrType = r16u(payload, pos)
local attrLength = r16u(payload, pos + 2)
if attrType == stun.ATTR_MAPPEDADDR then
local port = r16u(payload, pos + 6)
local ip = parseIp(payload, pos + 8)
return ip, port
end
pos = pos + attrLength + 4
end
return nil, nil
end
local function parseStunResponse(data)
local response = string.sub(data, 1, 2)
local payloadLength = r16u(data, 3)
local cookie = string.sub(data, 5, 8)
local txid = string.sub(data, 9, 20)
local payload = string.sub(data, 21, 21 + payloadLength)
if response ~= stun.BIND_SUCCESS
or cookie ~= stun.MAGIC_COOKIE
or txid ~= stun.txid
then
return nil, nil
else
return parseStunPayload(payload)
end
end
net.dns.resolve(serverAddr, function(_, ip)
if not ip then
pcall(callback, nil, nil)
stun = nil
else
stun.socket = net.createUDPSocket()
stun.socket:on("receive", function(socket, data, port, ip)
stun.timer:unregister()
stun.socket:close()
local extIp, extPort = parseStunResponse(data)
pcall(callback, extIp, extPort)
stun = nil
end)
stun.txid = createTxId()
stun.socket:send(serverPort, ip, createStunRequest())
stun.timer = tmr.create()
stun.timer:alarm(3000, tmr.ALARM_SINGLE, function()
stun.socket:close()
pcall(callback, nil, nil)
stun = nil
end)
end
end)
return ok
end