Merge with Florob
[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         -- Global unicast:
68         else
69                 return 0xE;
70         end
71 end
72
73 local function v6scope(ip)
74         -- Loopback:
75         if ip:match("^[0:]*1$") then
76                 return 0x2;
77         -- Link-local unicast:
78         elseif ip:match("^[Ff][Ee][89ABab]") then 
79                 return 0x2;
80         -- Site-local unicast:
81         elseif ip:match("^[Ff][Ee][CcDdEeFf]") then
82                 return 0x5;
83         -- Multicast:
84         elseif ip:match("^[Ff][Ff]") then
85                 return tonumber("0x"..ip:sub(4,4));
86         -- Global unicast:
87         else
88                 return 0xE;
89         end
90 end
91
92 local function label(ip)
93         if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
94                 return 0;
95         elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
96                 return 2;
97         elseif commonPrefixLength(ip, new_ip("2001::", "IPv6")) >= 32 then
98                 return 5;
99         elseif commonPrefixLength(ip, new_ip("fc00::", "IPv6")) >= 7 then
100                 return 13;
101         elseif commonPrefixLength(ip, new_ip("fec0::", "IPv6")) >= 10 then
102                 return 11;
103         elseif commonPrefixLength(ip, new_ip("3ffe::", "IPv6")) >= 16 then
104                 return 12;
105         elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
106                 return 3;
107         elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
108                 return 4;
109         else
110                 return 1;
111         end
112 end
113
114 local function precedence(ip)
115         if commonPrefixLength(ip, new_ip("::1", "IPv6")) == 128 then
116                 return 50;
117         elseif commonPrefixLength(ip, new_ip("2002::", "IPv6")) >= 16 then
118                 return 30;
119         elseif commonPrefixLength(ip, new_ip("2001::", "IPv6")) >= 32 then
120                 return 5;
121         elseif commonPrefixLength(ip, new_ip("fc00::", "IPv6")) >= 7 then
122                 return 3;
123         elseif commonPrefixLength(ip, new_ip("fec0::", "IPv6")) >= 10 then
124                 return 1;
125         elseif commonPrefixLength(ip, new_ip("3ffe::", "IPv6")) >= 16 then
126                 return 1;
127         elseif commonPrefixLength(ip, new_ip("::", "IPv6")) >= 96 then
128                 return 1;
129         elseif commonPrefixLength(ip, new_ip("::ffff:0:0", "IPv6")) >= 96 then
130                 return 35;
131         else
132                 return 40;
133         end
134 end
135
136 local function toV4mapped(ip)
137         local fields = {};
138         local ret = "::ffff:";
139         ip:gsub("([^.]*).?", function (c) fields[#fields + 1] = tonumber(c) end);
140         ret = ret .. ("%02x"):format(fields[1]);
141         ret = ret .. ("%02x"):format(fields[2]);
142         ret = ret .. ":"
143         ret = ret .. ("%02x"):format(fields[3]);
144         ret = ret .. ("%02x"):format(fields[4]);
145         return new_ip(ret, "IPv6");
146 end
147
148 function ip_methods:toV4mapped()
149         if self.proto ~= "IPv4" then return nil, "No IPv4 address" end
150         local value = toV4mapped(self.addr);
151         self.toV4mapped = value;
152         return value;
153 end
154
155 function ip_methods:label()
156         local value;
157         if self.proto == "IPv4" then
158                 value = label(self.toV4mapped);
159         else
160                 value = label(self);
161         end
162         self.label = value;
163         return value;
164 end
165
166 function ip_methods:precedence()
167         local value;
168         if self.proto == "IPv4" then
169                 value = precedence(self.toV4mapped);
170         else
171                 value = precedence(self);
172         end
173         self.precedence = value;
174         return value;
175 end
176
177 function ip_methods:scope()
178         local value;
179         if self.proto == "IPv4" then
180                 value = v4scope(self.addr);
181         else
182                 value = v6scope(self.addr);
183         end
184         self.scope = value;
185         return value;
186 end
187
188 return {new_ip = new_ip,
189         commonPrefixLength = commonPrefixLength};