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