Let Google Hangouts contacts appear offline
[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         if proto == "IPv6" and ipStr:find('.', 1, true) then
19                 local changed;
20                 ipStr, changed = ipStr:gsub(":(%d+)%.(%d+)%.(%d+)%.(%d+)$", function(a,b,c,d)
21                         return (":%04X:%04X"):format(a*256+b,c*256+d);
22                 end);
23                 if changed ~= 1 then return nil, "invalid-address"; end
24         end
25
26         return setmetatable({ addr = ipStr, proto = proto }, ip_mt);
27 end
28
29 local function toBits(ip)
30         local result = "";
31         local fields = {};
32         if ip.proto == "IPv4" then
33                 ip = ip.toV4mapped;
34         end
35         ip = (ip.addr):upper();
36         ip:gsub("([^:]*):?", function (c) fields[#fields + 1] = c end);
37         if not ip:match(":$") then fields[#fields] = nil; end
38         for i, field in ipairs(fields) do
39                 if field:len() == 0 and i ~= 1 and i ~= #fields then
40                         for i = 1, 16 * (9 - #fields) do
41                                 result = result .. "0";
42                         end
43                 else
44                         for i = 1, 4 - field:len() do
45                                 result = result .. "0000";
46                         end
47                         for i = 1, field:len() do
48                                 result = result .. hex2bits[field:sub(i,i)];
49                         end
50                 end
51         end
52         return result;
53 end
54
55 local function commonPrefixLength(ipA, ipB)
56         ipA, ipB = toBits(ipA), toBits(ipB);
57         for i = 1, 128 do
58                 if ipA:sub(i,i) ~= ipB:sub(i,i) then
59                         return i-1;
60                 end
61         end
62         return 128;
63 end
64
65 local function v4scope(ip)
66         local fields = {};
67         ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
68         -- Loopback:
69         if fields[1] == 127 then
70                 return 0x2;
71         -- Link-local unicast:
72         elseif fields[1] == 169 and fields[2] == 254 then
73                 return 0x2;
74         -- Global unicast:
75         else
76                 return 0xE;
77         end
78 end
79
80 local function v6scope(ip)
81         -- Loopback:
82         if ip:match("^[0:]*1$") then
83                 return 0x2;
84         -- Link-local unicast:
85         elseif ip:match("^[Ff][Ee][89ABab]") then 
86                 return 0x2;
87         -- Site-local unicast:
88         elseif ip:match("^[Ff][Ee][CcDdEeFf]") then
89                 return 0x5;
90         -- Multicast:
91         elseif ip:match("^[Ff][Ff]") then
92                 return tonumber("0x"..ip:sub(4,4));
93         -- Global unicast:
94         else
95                 return 0xE;
96         end
97 end
98
99 local function label(ip)
100         if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
101                 return 0;
102         elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
103                 return 2;
104         elseif commonPrefixLength(ip, new_ip("2001::", "IPv6")) >= 32 then
105                 return 5;
106         elseif commonPrefixLength(ip, new_ip("fc00::", "IPv6")) >= 7 then
107                 return 13;
108         elseif commonPrefixLength(ip, new_ip("fec0::", "IPv6")) >= 10 then
109                 return 11;
110         elseif commonPrefixLength(ip, new_ip("3ffe::", "IPv6")) >= 16 then
111                 return 12;
112         elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
113                 return 3;
114         elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
115                 return 4;
116         else
117                 return 1;
118         end
119 end
120
121 local function precedence(ip)
122         if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
123                 return 50;
124         elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
125                 return 30;
126         elseif commonPrefixLength(ip, new_ip("2001::", "IPv6")) >= 32 then
127                 return 5;
128         elseif commonPrefixLength(ip, new_ip("fc00::", "IPv6")) >= 7 then
129                 return 3;
130         elseif commonPrefixLength(ip, new_ip("fec0::", "IPv6")) >= 10 then
131                 return 1;
132         elseif commonPrefixLength(ip, new_ip("3ffe::", "IPv6")) >= 16 then
133                 return 1;
134         elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
135                 return 1;
136         elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
137                 return 35;
138         else
139                 return 40;
140         end
141 end
142
143 local function toV4mapped(ip)
144         local fields = {};
145         local ret = "::ffff:";
146         ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
147         ret = ret .. ("%02x"):format(fields[1]);
148         ret = ret .. ("%02x"):format(fields[2]);
149         ret = ret .. ":"
150         ret = ret .. ("%02x"):format(fields[3]);
151         ret = ret .. ("%02x"):format(fields[4]);
152         return new_ip(ret, "IPv6");
153 end
154
155 function ip_methods:toV4mapped()
156         if self.proto ~= "IPv4" then return nil, "No IPv4 address" end
157         local value = toV4mapped(self.addr);
158         self.toV4mapped = value;
159         return value;
160 end
161
162 function ip_methods:label()
163         local value;
164         if self.proto == "IPv4" then
165                 value = label(self.toV4mapped);
166         else
167                 value = label(self);
168         end
169         self.label = value;
170         return value;
171 end
172
173 function ip_methods:precedence()
174         local value;
175         if self.proto == "IPv4" then
176                 value = precedence(self.toV4mapped);
177         else
178                 value = precedence(self);
179         end
180         self.precedence = value;
181         return value;
182 end
183
184 function ip_methods:scope()
185         local value;
186         if self.proto == "IPv4" then
187                 value = v4scope(self.addr);
188         else
189                 value = v6scope(self.addr);
190         end
191         self.scope = value;
192         return value;
193 end
194
195 return {new_ip = new_ip,
196         commonPrefixLength = commonPrefixLength};