1 |
|
2 | ipaddr = {}
|
3 |
|
4 | root = this
|
5 |
|
6 |
|
7 | if module? && module.exports
|
8 | module.exports = ipaddr
|
9 | else
|
10 | root['ipaddr'] = ipaddr
|
11 |
|
12 |
|
13 | matchCIDR = (first, second, partSize, cidrBits) ->
|
14 | if first.length != second.length
|
15 | throw new Error "ipaddr: cannot match CIDR for objects with different lengths"
|
16 |
|
17 | part = 0
|
18 | while cidrBits > 0
|
19 | shift = partSize - cidrBits
|
20 | shift = 0 if shift < 0
|
21 |
|
22 | if first[part] >> shift != second[part] >> shift
|
23 | return false
|
24 |
|
25 | cidrBits -= partSize
|
26 | part += 1
|
27 |
|
28 | return true
|
29 |
|
30 |
|
31 | ipaddr.subnetMatch = (address, rangeList, defaultName='unicast') ->
|
32 | for rangeName, rangeSubnets of rangeList
|
33 |
|
34 | if rangeSubnets[0] && !(rangeSubnets[0] instanceof Array)
|
35 | rangeSubnets = [ rangeSubnets ]
|
36 |
|
37 | for subnet in rangeSubnets
|
38 | return rangeName if address.match.apply(address, subnet)
|
39 |
|
40 | return defaultName
|
41 |
|
42 |
|
43 | class ipaddr.IPv4
|
44 |
|
45 |
|
46 |
|
47 | constructor: (octets) ->
|
48 | if octets.length != 4
|
49 | throw new Error "ipaddr: ipv4 octet count should be 4"
|
50 |
|
51 | for octet in octets
|
52 | if !(0 <= octet <= 255)
|
53 | throw new Error "ipaddr: ipv4 octet should fit in 8 bits"
|
54 |
|
55 | @octets = octets
|
56 |
|
57 |
|
58 | kind: ->
|
59 | return 'ipv4'
|
60 |
|
61 |
|
62 | toString: ->
|
63 | return @octets.join "."
|
64 |
|
65 |
|
66 | toByteArray: ->
|
67 | return @octets.slice(0)
|
68 |
|
69 |
|
70 | match: (other, cidrRange) ->
|
71 | if cidrRange == undefined
|
72 | [other, cidrRange] = other
|
73 |
|
74 | if other.kind() != 'ipv4'
|
75 | throw new Error "ipaddr: cannot match ipv4 address with non-ipv4 one"
|
76 |
|
77 | return matchCIDR(this.octets, other.octets, 8, cidrRange)
|
78 |
|
79 |
|
80 | SpecialRanges:
|
81 | unspecified: [
|
82 | [ new IPv4([0, 0, 0, 0]), 8 ]
|
83 | ]
|
84 | broadcast: [
|
85 | [ new IPv4([255, 255, 255, 255]), 32 ]
|
86 | ]
|
87 | multicast: [
|
88 | [ new IPv4([224, 0, 0, 0]), 4 ]
|
89 | ]
|
90 | linkLocal: [
|
91 | [ new IPv4([169, 254, 0, 0]), 16 ]
|
92 | ]
|
93 | loopback: [
|
94 | [ new IPv4([127, 0, 0, 0]), 8 ]
|
95 | ]
|
96 | private: [
|
97 | [ new IPv4([10, 0, 0, 0]), 8 ]
|
98 | [ new IPv4([172, 16, 0, 0]), 12 ]
|
99 | [ new IPv4([192, 168, 0, 0]), 16 ]
|
100 | ]
|
101 | reserved: [
|
102 | [ new IPv4([192, 0, 0, 0]), 24 ]
|
103 | [ new IPv4([192, 0, 2, 0]), 24 ]
|
104 | [ new IPv4([192, 88, 99, 0]), 24 ]
|
105 | [ new IPv4([198, 51, 100, 0]), 24 ]
|
106 | [ new IPv4([203, 0, 113, 0]), 24 ]
|
107 | [ new IPv4([240, 0, 0, 0]), 4 ]
|
108 | ]
|
109 |
|
110 |
|
111 | range: ->
|
112 | return ipaddr.subnetMatch(this, @SpecialRanges)
|
113 |
|
114 |
|
115 | toIPv4MappedAddress: ->
|
116 | return ipaddr.IPv6.parse "::ffff:#{@toString()}"
|
117 |
|
118 |
|
119 |
|
120 |
|
121 | prefixLengthFromSubnetMask: ->
|
122 |
|
123 | zerotable =
|
124 | 0: 8
|
125 | 128: 7
|
126 | 192: 6
|
127 | 224: 5
|
128 | 240: 4
|
129 | 248: 3
|
130 | 252: 2
|
131 | 254: 1
|
132 | 255: 0
|
133 |
|
134 | cidr = 0
|
135 |
|
136 | stop = false
|
137 | for i in [3..0] by -1
|
138 | octet = @octets[i]
|
139 | if octet of zerotable
|
140 | zeros = zerotable[octet]
|
141 | if stop and zeros != 0
|
142 | return null
|
143 | unless zeros == 8
|
144 | stop = true
|
145 | cidr += zeros
|
146 | else
|
147 | return null
|
148 | return 32 - cidr
|
149 |
|
150 |
|
151 |
|
152 |
|
153 | ipv4Part = "(0?\\d+|0x[a-f0-9]+)"
|
154 | ipv4Regexes =
|
155 | fourOctet: new RegExp "^#{ipv4Part}\\.#{ipv4Part}\\.#{ipv4Part}\\.#{ipv4Part}$", 'i'
|
156 | longValue: new RegExp "^#{ipv4Part}$", 'i'
|
157 |
|
158 |
|
159 |
|
160 |
|
161 | ipaddr.IPv4.parser = (string) ->
|
162 | parseIntAuto = (string) ->
|
163 | if string[0] == "0" && string[1] != "x"
|
164 | parseInt(string, 8)
|
165 | else
|
166 | parseInt(string)
|
167 |
|
168 |
|
169 | if match = string.match(ipv4Regexes.fourOctet)
|
170 | return (parseIntAuto(part) for part in match[1..5])
|
171 | else if match = string.match(ipv4Regexes.longValue)
|
172 | value = parseIntAuto(match[1])
|
173 | if value > 0xffffffff || value < 0
|
174 | throw new Error "ipaddr: address outside defined range"
|
175 | return ((value >> shift) & 0xff for shift in [0..24] by 8).reverse()
|
176 | else
|
177 | return null
|
178 |
|
179 |
|
180 | class ipaddr.IPv6
|
181 |
|
182 |
|
183 |
|
184 | constructor: (parts) ->
|
185 | if parts.length == 16
|
186 | @parts = []
|
187 | for i in [0..14] by 2
|
188 | @parts.push((parts[i] << 8) | parts[i + 1])
|
189 | else if parts.length == 8
|
190 | @parts = parts
|
191 | else
|
192 | throw new Error "ipaddr: ipv6 part count should be 8 or 16"
|
193 |
|
194 | for part in @parts
|
195 | if !(0 <= part <= 0xffff)
|
196 | throw new Error "ipaddr: ipv6 part should fit in 16 bits"
|
197 |
|
198 |
|
199 | kind: ->
|
200 | return 'ipv6'
|
201 |
|
202 |
|
203 |
|
204 | toString: ->
|
205 | stringParts = (part.toString(16) for part in @parts)
|
206 |
|
207 | compactStringParts = []
|
208 | pushPart = (part) -> compactStringParts.push part
|
209 |
|
210 | state = 0
|
211 | for part in stringParts
|
212 | switch state
|
213 | when 0
|
214 | if part == '0'
|
215 | pushPart('')
|
216 | else
|
217 | pushPart(part)
|
218 |
|
219 | state = 1
|
220 | when 1
|
221 | if part == '0'
|
222 | state = 2
|
223 | else
|
224 | pushPart(part)
|
225 | when 2
|
226 | unless part == '0'
|
227 | pushPart('')
|
228 | pushPart(part)
|
229 | state = 3
|
230 | when 3
|
231 | pushPart(part)
|
232 |
|
233 | if state == 2
|
234 | pushPart('')
|
235 | pushPart('')
|
236 |
|
237 | return compactStringParts.join ":"
|
238 |
|
239 |
|
240 | toByteArray: ->
|
241 | bytes = []
|
242 | for part in @parts
|
243 | bytes.push(part >> 8)
|
244 | bytes.push(part & 0xff)
|
245 |
|
246 | return bytes
|
247 |
|
248 |
|
249 |
|
250 | toNormalizedString: ->
|
251 | return (part.toString(16) for part in @parts).join ":"
|
252 |
|
253 |
|
254 | match: (other, cidrRange) ->
|
255 | if cidrRange == undefined
|
256 | [other, cidrRange] = other
|
257 |
|
258 | if other.kind() != 'ipv6'
|
259 | throw new Error "ipaddr: cannot match ipv6 address with non-ipv6 one"
|
260 |
|
261 | return matchCIDR(this.parts, other.parts, 16, cidrRange)
|
262 |
|
263 |
|
264 | SpecialRanges:
|
265 | unspecified: [ new IPv6([0, 0, 0, 0, 0, 0, 0, 0]), 128 ]
|
266 | linkLocal: [ new IPv6([0xfe80, 0, 0, 0, 0, 0, 0, 0]), 10 ]
|
267 | multicast: [ new IPv6([0xff00, 0, 0, 0, 0, 0, 0, 0]), 8 ]
|
268 | loopback: [ new IPv6([0, 0, 0, 0, 0, 0, 0, 1]), 128 ]
|
269 | uniqueLocal: [ new IPv6([0xfc00, 0, 0, 0, 0, 0, 0, 0]), 7 ]
|
270 | ipv4Mapped: [ new IPv6([0, 0, 0, 0, 0, 0xffff, 0, 0]), 96 ]
|
271 | rfc6145: [ new IPv6([0, 0, 0, 0, 0xffff, 0, 0, 0]), 96 ]
|
272 | rfc6052: [ new IPv6([0x64, 0xff9b, 0, 0, 0, 0, 0, 0]), 96 ]
|
273 | '6to4': [ new IPv6([0x2002, 0, 0, 0, 0, 0, 0, 0]), 16 ]
|
274 | teredo: [ new IPv6([0x2001, 0, 0, 0, 0, 0, 0, 0]), 32 ]
|
275 | reserved: [
|
276 | [ new IPv6([ 0x2001, 0xdb8, 0, 0, 0, 0, 0, 0]), 32 ]
|
277 | ]
|
278 |
|
279 |
|
280 | range: ->
|
281 | return ipaddr.subnetMatch(this, @SpecialRanges)
|
282 |
|
283 |
|
284 | isIPv4MappedAddress: ->
|
285 | return @range() == 'ipv4Mapped'
|
286 |
|
287 |
|
288 |
|
289 | toIPv4Address: ->
|
290 | unless @isIPv4MappedAddress()
|
291 | throw new Error "ipaddr: trying to convert a generic ipv6 address to ipv4"
|
292 |
|
293 | [high, low] = @parts[-2..-1]
|
294 |
|
295 | return new ipaddr.IPv4([high >> 8, high & 0xff, low >> 8, low & 0xff])
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 | ipv6Part = "(?:[0-9a-f]+::?)+"
|
302 | ipv6Regexes =
|
303 | native: new RegExp "^(::)?(#{ipv6Part})?([0-9a-f]+)?(::)?$", 'i'
|
304 | transitional: new RegExp "^((?:#{ipv6Part})|(?:::)(?:#{ipv6Part})?)" +
|
305 | "#{ipv4Part}\\.#{ipv4Part}\\.#{ipv4Part}\\.#{ipv4Part}$", 'i'
|
306 |
|
307 |
|
308 | expandIPv6 = (string, parts) ->
|
309 |
|
310 | if string.indexOf('::') != string.lastIndexOf('::')
|
311 | return null
|
312 |
|
313 |
|
314 | colonCount = 0
|
315 | lastColon = -1
|
316 | while (lastColon = string.indexOf(':', lastColon + 1)) >= 0
|
317 | colonCount++
|
318 |
|
319 |
|
320 | colonCount-- if string.substr(0, 2) == '::'
|
321 | colonCount-- if string.substr(-2, 2) == '::'
|
322 |
|
323 |
|
324 | if colonCount > parts
|
325 | return null
|
326 |
|
327 |
|
328 | replacementCount = parts - colonCount
|
329 | replacement = ':'
|
330 | while replacementCount--
|
331 | replacement += '0:'
|
332 |
|
333 |
|
334 | string = string.replace('::', replacement)
|
335 |
|
336 |
|
337 |
|
338 | string = string[1..-1] if string[0] == ':'
|
339 | string = string[0..-2] if string[string.length-1] == ':'
|
340 |
|
341 | return (parseInt(part, 16) for part in string.split(":"))
|
342 |
|
343 |
|
344 | ipaddr.IPv6.parser = (string) ->
|
345 | if string.match(ipv6Regexes['native'])
|
346 | return expandIPv6(string, 8)
|
347 |
|
348 | else if match = string.match(ipv6Regexes['transitional'])
|
349 | parts = expandIPv6(match[1][0..-2], 6)
|
350 | if parts
|
351 | octets = [parseInt(match[2]), parseInt(match[3]),
|
352 | parseInt(match[4]), parseInt(match[5])]
|
353 | for octet in octets
|
354 | if !(0 <= octet <= 255)
|
355 | return null
|
356 |
|
357 | parts.push(octets[0] << 8 | octets[1])
|
358 | parts.push(octets[2] << 8 | octets[3])
|
359 | return parts
|
360 |
|
361 | return null
|
362 |
|
363 |
|
364 | ipaddr.IPv4.isIPv4 = ipaddr.IPv6.isIPv6 = (string) ->
|
365 | return @parser(string) != null
|
366 |
|
367 |
|
368 | ipaddr.IPv4.isValid = (string) ->
|
369 | try
|
370 | new this(@parser(string))
|
371 | return true
|
372 | catch e
|
373 | return false
|
374 |
|
375 | ipaddr.IPv6.isValid = (string) ->
|
376 |
|
377 |
|
378 | if typeof string == "string" and string.indexOf(":") == -1
|
379 | return false
|
380 |
|
381 | try
|
382 | new this(@parser(string))
|
383 | return true
|
384 | catch e
|
385 | return false
|
386 |
|
387 |
|
388 |
|
389 | ipaddr.IPv4.parse = ipaddr.IPv6.parse = (string) ->
|
390 | parts = @parser(string)
|
391 | if parts == null
|
392 | throw new Error "ipaddr: string is not formatted like ip address"
|
393 |
|
394 | return new this(parts)
|
395 |
|
396 | ipaddr.IPv4.parseCIDR = (string) ->
|
397 | if match = string.match(/^(.+)\/(\d+)$/)
|
398 | maskLength = parseInt(match[2])
|
399 | if maskLength >= 0 and maskLength <= 32
|
400 | return [@parse(match[1]), maskLength]
|
401 |
|
402 | throw new Error "ipaddr: string is not formatted like an IPv4 CIDR range"
|
403 |
|
404 | ipaddr.IPv6.parseCIDR = (string) ->
|
405 | if match = string.match(/^(.+)\/(\d+)$/)
|
406 | maskLength = parseInt(match[2])
|
407 | if maskLength >= 0 and maskLength <= 128
|
408 | return [@parse(match[1]), maskLength]
|
409 |
|
410 | throw new Error "ipaddr: string is not formatted like an IPv6 CIDR range"
|
411 |
|
412 |
|
413 | ipaddr.isValid = (string) ->
|
414 | return ipaddr.IPv6.isValid(string) || ipaddr.IPv4.isValid(string)
|
415 |
|
416 |
|
417 | ipaddr.parse = (string) ->
|
418 | if ipaddr.IPv6.isValid(string)
|
419 | return ipaddr.IPv6.parse(string)
|
420 | else if ipaddr.IPv4.isValid(string)
|
421 | return ipaddr.IPv4.parse(string)
|
422 | else
|
423 | throw new Error "ipaddr: the address has neither IPv6 nor IPv4 format"
|
424 |
|
425 | ipaddr.parseCIDR = (string) ->
|
426 | try
|
427 | return ipaddr.IPv6.parseCIDR(string)
|
428 | catch e
|
429 | try
|
430 | return ipaddr.IPv4.parseCIDR(string)
|
431 | catch e
|
432 | throw new Error "ipaddr: the address has neither IPv6 nor IPv4 CIDR format"
|
433 |
|
434 |
|
435 | ipaddr.fromByteArray = (bytes) ->
|
436 | length = bytes.length
|
437 | if length == 4
|
438 | return new ipaddr.IPv4(bytes)
|
439 | else if length == 16
|
440 | return new ipaddr.IPv6(bytes)
|
441 | else
|
442 | throw new Error "ipaddr: the binary input is neither an IPv6 nor IPv4 address"
|
443 |
|
444 |
|
445 | ipaddr.process = (string) ->
|
446 | addr = @parse(string)
|
447 | if addr.kind() == 'ipv6' && addr.isIPv4MappedAddress()
|
448 | return addr.toIPv4Address()
|
449 | else
|
450 | return addr
|