moduleapi: in module:provides(), add the name of the module in item._provided_by
[prosody.git] / util / ip.lua
1 -- Prosody IM
2 -- Copyright (C) 2008-2011 Florian Zeitz
3 --
4 -- This project is MIT/X11 licensed. Please see the
5 -- COPYING file in the source package for more information.
6 --
7
8 local ip_methods = {};
9 local ip_mt = { __index = function (ip, key) return (ip_methods[key])(ip); end,
10                 __tostring = function (ip) return ip.addr; end,
11                 __eq = function (ipA, ipB) return ipA.addr == ipB.addr; end};
12 local hex2bits = { ["0"] = "0000", ["1"] = "0001", ["2"] = "0010", ["3"] = "0011", ["4"] = "0100", ["5"] = "0101", ["6"] = "0110", ["7"] = "0111", ["8"] = "1000", ["9"] = "1001", ["A"] = "1010", ["B"] = "1011", ["C"] = "1100", ["D"] = "1101", ["E"] = "1110", ["F"] = "1111" };
13
14 local function new_ip(ipStr, proto)
15         if proto ~= "IPv4" and proto ~= "IPv6" then
16                 return nil, "invalid protocol";
17         end
18
19         return setmetatable({ addr = ipStr, proto = proto }, ip_mt);
20 end
21
22 local function toBits(ip)
23         local result = "";
24         local fields = {};
25         if ip.proto == "IPv4" then
26                 ip = ip.toV4mapped;
27         end
28         ip = (ip.addr):upper();
29         ip:gsub("([^:]*):?", function (c) fields[#fields + 1] = c end);
30         if not ip:match(":$") then fields[#fields] = nil; end
31         for i, field in ipairs(fields) do
32                 if field:len() == 0 and i ~= 1 and i ~= #fields then
33                         for i = 1, 16 * (9 - #fields) do
34                                 result = result .. "0";
35                         end
36                 else
37                         for i = 1, 4 - field:len() do
38                                 result = result .. "0000";
39                         end
40                         for i = 1, field:len() do
41                                 result = result .. hex2bits[field:sub(i,i)];
42                         end
43                 end
44         end
45         return result;
46 end
47
48 local function commonPrefixLength(ipA, ipB)
49         ipA, ipB = toBits(ipA), toBits(ipB);
50         for i = 1, 128 do
51                 if ipA:sub(i,i) ~= ipB:sub(i,i) then
52                         return i-1;
53                 end
54         end
55         return 128;
56 end
57
58 local function v4scope(ip)
59         local fields = {};
60         ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
61         -- Loopback:
62         if fields[1] == 127 then
63                 return 0x2;
64         -- Link-local unicast:
65         elseif fields[1] == 169 and fields[2] == 254 then
66                 return 0x2;
67         -- Site-local unicast:
68         elseif (fields[1] == 10) or (fields[1] == 192 and fields[2] == 168) or (fields[1] == 172 and (fields[2] >= 16 and fields[2] < 32)) then
69                 return 0x5;
70         -- Global unicast:
71         else
72                 return 0xE;
73         end
74 end
75
76 local function v6scope(ip)
77         -- Loopback:
78         if ip:match("^[0:]*1$") then
79                 return 0x2;
80         -- Link-local unicast:
81         elseif ip:match("^[Ff][Ee][89ABab]") then 
82                 return 0x2;
83         -- Site-local unicast:
84         elseif ip:match("^[Ff][Ee][CcDdEeFf]") then
85                 return 0x5;
86         -- Multicast:
87         elseif ip:match("^[Ff][Ff]") then
88                 return tonumber("0x"..ip:sub(4,4));
89         -- Global unicast:
90         else
91                 return 0xE;
92         end
93 end
94
95 local function label(ip)
96         if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
97                 return 0;
98         elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
99                 return 2;
100         elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
101                 return 3;
102         elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
103                 return 4;
104         else
105                 return 1;
106         end
107 end
108
109 local function precedence(ip)
110         if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
111                 return 50;
112         elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
113                 return 30;
114         elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
115                 return 20;
116         elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
117                 return 10;
118         else
119                 return 40;
120         end
121 end
122
123 local function toV4mapped(ip)
124         local fields = {};
125         local ret = "::ffff:";
126         ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
127         ret = ret .. ("%02x"):format(fields[1]);
128         ret = ret .. ("%02x"):format(fields[2]);
129         ret = ret .. ":"
130         ret = ret .. ("%02x"):format(fields[3]);
131         ret = ret .. ("%02x"):format(fields[4]);
132         return new_ip(ret, "IPv6");
133 end
134
135 function ip_methods:toV4mapped()
136         if self.proto ~= "IPv4" then return nil, "No IPv4 address" end
137         local value = toV4mapped(self.addr);
138         self.toV4mapped = value;
139         return value;
140 end
141
142 function ip_methods:label()
143         local value;
144         if self.proto == "IPv4" then
145                 value = label(self.toV4mapped);
146         else
147                 value = label(self);
148         end
149         self.label = value;
150         return value;
151 end
152
153 function ip_methods:precedence()
154         local value;
155         if self.proto == "IPv4" then
156                 value = precedence(self.toV4mapped);
157         else
158                 value = precedence(self);
159         end
160         self.precedence = value;
161         return value;
162 end
163
164 function ip_methods:scope()
165         local value;
166         if self.proto == "IPv4" then
167                 value = v4scope(self.addr);
168         else
169                 value = v6scope(self.addr);
170         end
171         self.scope = value;
172         return value;
173 end
174
175 return {new_ip = new_ip,
176         commonPrefixLength = commonPrefixLength};