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