PageRenderTime 33ms CodeModel.GetById 5ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/ipaddress/ipv6.rb

https://gitlab.com/intruxxer/ipaddress
Ruby | 886 lines | 187 code | 55 blank | 644 comment | 21 complexity | 6d0be2c6922ab3c39a327241d7a33b36 MD5 | raw file
  1. require 'ipaddress/prefix'
  2. module IPAddress;
  3. #
  4. # =Name
  5. #
  6. # IPAddress::IPv6 - IP version 6 address manipulation library
  7. #
  8. # =Synopsis
  9. #
  10. # require 'ipaddress'
  11. #
  12. # =Description
  13. #
  14. # Class IPAddress::IPv6 is used to handle IPv6 type addresses.
  15. #
  16. # == IPv6 addresses
  17. #
  18. # IPv6 addresses are 128 bits long, in contrast with IPv4 addresses
  19. # which are only 32 bits long. An IPv6 address is generally written as
  20. # eight groups of four hexadecimal digits, each group representing 16
  21. # bits or two octect. For example, the following is a valid IPv6
  22. # address:
  23. #
  24. # 2001:0db8:0000:0000:0008:0800:200c:417a
  25. #
  26. # Letters in an IPv6 address are usually written downcase, as per
  27. # RFC. You can create a new IPv6 object using uppercase letters, but
  28. # they will be converted.
  29. #
  30. # === Compression
  31. #
  32. # Since IPv6 addresses are very long to write, there are some
  33. # semplifications and compressions that you can use to shorten them.
  34. #
  35. # * Leading zeroes: all the leading zeroes within a group can be
  36. # omitted: "0008" would become "8"
  37. #
  38. # * A string of consecutive zeroes can be replaced by the string
  39. # "::". This can be only applied once.
  40. #
  41. # Using compression, the IPv6 address written above can be shorten into
  42. # the following, equivalent, address
  43. #
  44. # 2001:db8::8:800:200c:417a
  45. #
  46. # This short version is often used in human representation.
  47. #
  48. # === Network Mask
  49. #
  50. # As we used to do with IPv4 addresses, an IPv6 address can be written
  51. # using the prefix notation to specify the subnet mask:
  52. #
  53. # 2001:db8::8:800:200c:417a/64
  54. #
  55. # The /64 part means that the first 64 bits of the address are
  56. # representing the network portion, and the last 64 bits are the host
  57. # portion.
  58. #
  59. #
  60. class IPv6
  61. include IPAddress
  62. include Enumerable
  63. include Comparable
  64. #
  65. # Format string to pretty print IPv6 addresses
  66. #
  67. IN6FORMAT = ("%.4x:"*8).chop
  68. #
  69. # Creates a new IPv6 address object.
  70. #
  71. # An IPv6 address can be expressed in any of the following forms:
  72. #
  73. # * "2001:0db8:0000:0000:0008:0800:200C:417A": IPv6 address with no compression
  74. # * "2001:db8:0:0:8:800:200C:417A": IPv6 address with leading zeros compression
  75. # * "2001:db8::8:800:200C:417A": IPv6 address with full compression
  76. #
  77. # In all these 3 cases, a new IPv6 address object will be created, using the default
  78. # subnet mask /128
  79. #
  80. # You can also specify the subnet mask as with IPv4 addresses:
  81. #
  82. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  83. #
  84. def initialize(str)
  85. ip, netmask = str.split("/")
  86. if str =~ /:.+\./
  87. raise ArgumentError, "Please use #{self.class}::Mapped for IPv4 mapped addresses"
  88. end
  89. if IPAddress.valid_ipv6?(ip)
  90. @groups = self.class.groups(ip)
  91. @address = IN6FORMAT % @groups
  92. @compressed = compress_address
  93. else
  94. raise ArgumentError, "Invalid IP #{ip.inspect}"
  95. end
  96. @prefix = Prefix128.new(netmask ? netmask : 128)
  97. end # def initialize
  98. #
  99. # Returns the IPv6 address in uncompressed form:
  100. #
  101. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  102. #
  103. # ip6.address
  104. # #=> "2001:0db8:0000:0000:0008:0800:200c:417a"
  105. #
  106. def address
  107. @address
  108. end
  109. #
  110. # Returns an array with the 16 bits groups in decimal
  111. # format:
  112. #
  113. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  114. #
  115. # ip6.groups
  116. # #=> [8193, 3512, 0, 0, 8, 2048, 8204, 16762]
  117. #
  118. def groups
  119. @groups
  120. end
  121. #
  122. # Returns an instance of the prefix object
  123. #
  124. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  125. #
  126. # ip6.prefix
  127. # #=> 64
  128. #
  129. def prefix
  130. @prefix
  131. end
  132. #
  133. # Set a new prefix number for the object
  134. #
  135. # This is useful if you want to change the prefix
  136. # to an object created with IPv6::parse_u128 or
  137. # if the object was created using the default prefix
  138. # of 128 bits.
  139. #
  140. # ip6 = IPAddress("2001:db8::8:800:200c:417a")
  141. #
  142. # puts ip6.to_string
  143. # #=> "2001:db8::8:800:200c:417a/128"
  144. #
  145. # ip6.prefix = 64
  146. # puts ip6.to_string
  147. # #=> "2001:db8::8:800:200c:417a/64"
  148. #
  149. def prefix=(num)
  150. @prefix = Prefix128.new(num)
  151. end
  152. #
  153. # Unlike its counterpart IPv6#to_string method, IPv6#to_string_uncompressed
  154. # returns the whole IPv6 address and prefix in an uncompressed form
  155. #
  156. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  157. #
  158. # ip6.to_string_uncompressed
  159. # #=> "2001:0db8:0000:0000:0008:0800:200c:417a/64"
  160. #
  161. def to_string_uncompressed
  162. "#@address/#@prefix"
  163. end
  164. #
  165. # Returns the IPv6 address in a human readable form,
  166. # using the compressed address.
  167. #
  168. # ip6 = IPAddress "2001:0db8:0000:0000:0008:0800:200c:417a/64"
  169. #
  170. # ip6.to_string
  171. # #=> "2001:db8::8:800:200c:417a/64"
  172. #
  173. def to_string
  174. "#@compressed/#@prefix"
  175. end
  176. #
  177. # Returns the IPv6 address in a human readable form,
  178. # using the compressed address.
  179. #
  180. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  181. #
  182. # ip6.to_s
  183. # #=> "2001:db8::8:800:200c:417a"
  184. #
  185. def to_s
  186. @compressed
  187. end
  188. #
  189. # Returns a decimal format (unsigned 128 bit) of the
  190. # IPv6 address
  191. #
  192. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  193. #
  194. # ip6.to_i
  195. # #=> 42540766411282592856906245548098208122
  196. #
  197. def to_i
  198. to_hex.hex
  199. end
  200. alias_method :to_u128, :to_i
  201. #
  202. # True if the IPv6 address is a network
  203. #
  204. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  205. #
  206. # ip6.network?
  207. # #=> false
  208. #
  209. # ip6 = IPAddress "2001:db8:8:800::/64"
  210. #
  211. # ip6.network?
  212. # #=> true
  213. #
  214. def network?
  215. to_u128 | @prefix.to_u128 == @prefix.to_u128
  216. end
  217. #
  218. # Returns the 16-bits value specified by index
  219. #
  220. # ip = IPAddress("2001:db8::8:800:200c:417a/64")
  221. #
  222. # ip[0]
  223. # #=> 8193
  224. # ip[1]
  225. # #=> 3512
  226. # ip[2]
  227. # #=> 0
  228. # ip[3]
  229. # #=> 0
  230. #
  231. def [](index)
  232. @groups[index]
  233. end
  234. alias_method :group, :[]
  235. #
  236. # Returns a Base16 number representing the IPv6
  237. # address
  238. #
  239. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  240. #
  241. # ip6.to_hex
  242. # #=> "20010db80000000000080800200c417a"
  243. #
  244. def to_hex
  245. hexs.join("")
  246. end
  247. # Returns the address portion of an IPv6 object
  248. # in a network byte order format.
  249. #
  250. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  251. #
  252. # ip6.data
  253. # #=> " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
  254. #
  255. # It is usually used to include an IP address
  256. # in a data packet to be sent over a socket
  257. #
  258. # a = Socket.open(params) # socket details here
  259. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  260. # binary_data = ["Address: "].pack("a*") + ip.data
  261. #
  262. # # Send binary data
  263. # a.puts binary_data
  264. #
  265. def data
  266. @groups.pack("n8")
  267. end
  268. #
  269. # Returns an array of the 16 bits groups in hexdecimal
  270. # format:
  271. #
  272. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  273. #
  274. # ip6.hexs
  275. # #=> ["2001", "0db8", "0000", "0000", "0008", "0800", "200c", "417a"]
  276. #
  277. # Not to be confused with the similar IPv6#to_hex method.
  278. #
  279. def hexs
  280. @address.split(":")
  281. end
  282. #
  283. # Returns the IPv6 address in a DNS reverse lookup
  284. # string, as per RFC3172 and RFC2874.
  285. #
  286. # ip6 = IPAddress "3ffe:505:2::f"
  287. #
  288. # ip6.reverse
  289. # #=> "f.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.2.0.0.0.5.0.5.0.e.f.f.3.ip6.arpa"
  290. #
  291. def reverse
  292. to_hex.reverse.gsub(/./){|c| c+"."} + "ip6.arpa"
  293. end
  294. alias_method :arpa, :reverse
  295. #
  296. # Returns the network number in Unsigned 128bits format
  297. #
  298. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  299. #
  300. # ip6.network_u128
  301. # #=> 42540766411282592856903984951653826560
  302. #
  303. def network_u128
  304. to_u128 & @prefix.to_u128
  305. end
  306. #
  307. # Returns the broadcast address in Unsigned 128bits format
  308. #
  309. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  310. #
  311. # ip6.broadcast_u128
  312. # #=> 42540766411282592875350729025363378175
  313. #
  314. # Please note that there is no Broadcast concept in IPv6
  315. # addresses as in IPv4 addresses, and this method is just
  316. # an helper to other functions.
  317. #
  318. def broadcast_u128
  319. network_u128 + size - 1
  320. end
  321. #
  322. # Returns the number of IP addresses included
  323. # in the network. It also counts the network
  324. # address and the broadcast address.
  325. #
  326. # ip6 = IPAddress("2001:db8::8:800:200c:417a/64")
  327. #
  328. # ip6.size
  329. # #=> 18446744073709551616
  330. #
  331. def size
  332. 2 ** @prefix.host_prefix
  333. end
  334. #
  335. # Checks whether a subnet includes the given IP address.
  336. #
  337. # Example:
  338. #
  339. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  340. # addr = IPAddress "2001:db8::8:800:200c:1/128"
  341. #
  342. # ip6.include? addr
  343. # #=> true
  344. #
  345. # ip6.include? IPAddress("2001:db8:1::8:800:200c:417a/76")
  346. # #=> false
  347. #
  348. def include?(oth)
  349. @prefix <= oth.prefix and network_u128 == self.class.new(oth.address+"/#@prefix").network_u128
  350. end
  351. #
  352. # Compressed form of the IPv6 address
  353. #
  354. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  355. #
  356. # ip6.compressed
  357. # #=> "2001:db8::8:800:200c:417a"
  358. #
  359. def compressed
  360. @compressed
  361. end
  362. #
  363. # Returns true if the address is an unspecified address
  364. #
  365. # See IPAddress::IPv6::Unspecified for more information
  366. #
  367. def unspecified?
  368. @prefix == 128 and @compressed == "::"
  369. end
  370. #
  371. # Returns true if the address is a loopback address
  372. #
  373. # See IPAddress::IPv6::Loopback for more information
  374. #
  375. def loopback?
  376. @prefix == 128 and @compressed == "::1"
  377. end
  378. #
  379. # Returns true if the address is a mapped address
  380. #
  381. # See IPAddress::IPv6::Mapped for more information
  382. #
  383. def mapped?
  384. to_u128 >> 32 == 0xffff
  385. end
  386. #
  387. # Iterates over all the IP addresses for the given
  388. # network (or IP address).
  389. #
  390. # The object yielded is a new IPv6 object created
  391. # from the iteration.
  392. #
  393. # ip6 = IPAddress("2001:db8::4/125")
  394. #
  395. # ip6.each do |i|
  396. # p i.compressed
  397. # end
  398. # #=> "2001:db8::"
  399. # #=> "2001:db8::1"
  400. # #=> "2001:db8::2"
  401. # #=> "2001:db8::3"
  402. # #=> "2001:db8::4"
  403. # #=> "2001:db8::5"
  404. # #=> "2001:db8::6"
  405. # #=> "2001:db8::7"
  406. #
  407. # WARNING: if the host portion is very large, this method
  408. # can be very slow and possibly hang your system!
  409. #
  410. def each
  411. (network_u128..broadcast_u128).each do |i|
  412. yield self.class.parse_u128(i, @prefix)
  413. end
  414. end
  415. #
  416. # Spaceship operator to compare IPv6 objects
  417. #
  418. # Comparing IPv6 addresses is useful to ordinate
  419. # them into lists that match our intuitive
  420. # perception of ordered IP addresses.
  421. #
  422. # The first comparison criteria is the u128 value.
  423. # For example, 2001:db8:1::1 will be considered
  424. # to be less than 2001:db8:2::1, because, in a ordered list,
  425. # we expect 2001:db8:1::1 to come before 2001:db8:2::1.
  426. #
  427. # The second criteria, in case two IPv6 objects
  428. # have identical addresses, is the prefix. An higher
  429. # prefix will be considered greater than a lower
  430. # prefix. This is because we expect to see
  431. # 2001:db8:1::1/64 come before 2001:db8:1::1/65
  432. #
  433. # Example:
  434. #
  435. # ip1 = IPAddress "2001:db8:1::1/64"
  436. # ip2 = IPAddress "2001:db8:2::1/64"
  437. # ip3 = IPAddress "2001:db8:1::1/65"
  438. #
  439. # ip1 < ip2
  440. # #=> true
  441. # ip1 < ip3
  442. # #=> false
  443. #
  444. # [ip1,ip2,ip3].sort.map{|i| i.to_string}
  445. # #=> ["2001:db8:1::1/64","2001:db8:1::1/65","2001:db8:2::1/64"]
  446. #
  447. def <=>(oth)
  448. return prefix <=> oth.prefix if to_u128 == oth.to_u128
  449. to_u128 <=> oth.to_u128
  450. end
  451. #
  452. # Returns the address portion of an IP in binary format,
  453. # as a string containing a sequence of 0 and 1
  454. #
  455. # ip6 = IPAddress("2001:db8::8:800:200c:417a")
  456. #
  457. # ip6.bits
  458. # #=> "0010000000000001000011011011100000 [...] "
  459. #
  460. def bits
  461. data.unpack("B*").first
  462. end
  463. #
  464. # Expands an IPv6 address in the canocical form
  465. #
  466. # IPAddress::IPv6.expand "2001:0DB8:0:CD30::"
  467. # #=> "2001:0DB8:0000:CD30:0000:0000:0000:0000"
  468. #
  469. def self.expand(str)
  470. self.new(str).address
  471. end
  472. #
  473. # Compress an IPv6 address in its compressed form
  474. #
  475. # IPAddress::IPv6.compress "2001:0DB8:0000:CD30:0000:0000:0000:0000"
  476. # #=> "2001:db8:0:cd30::"
  477. #
  478. def self.compress(str)
  479. self.new(str).compressed
  480. end
  481. #
  482. # Literal version of the IPv6 address
  483. #
  484. # ip6 = IPAddress "2001:db8::8:800:200c:417a/64"
  485. #
  486. # ip6.literal
  487. # #=> "2001-0db8-0000-0000-0008-0800-200c-417a.ipv6-literal.net"
  488. #
  489. def literal
  490. @address.gsub(":","-") + ".ipv6-literal.net"
  491. end
  492. #
  493. # Returns a new IPv6 object with the network number
  494. # for the given IP.
  495. #
  496. # ip = IPAddress "2001:db8:1:1:1:1:1:1/32"
  497. #
  498. # ip.network.to_string
  499. # #=> "2001:db8::/32"
  500. #
  501. def network
  502. self.class.parse_u128(network_u128, @prefix)
  503. end
  504. #
  505. # Extract 16 bits groups from a string
  506. #
  507. def self.groups(str)
  508. l, r = if str =~ /^(.*)::(.*)$/
  509. [$1,$2].map {|i| i.split ":"}
  510. else
  511. [str.split(":"),[]]
  512. end
  513. (l + Array.new(8-l.size-r.size, '0') + r).map {|i| i.hex}
  514. end
  515. #
  516. # Creates a new IPv6 object from binary data,
  517. # like the one you get from a network stream.
  518. #
  519. # For example, on a network stream the IP
  520. #
  521. # "2001:db8::8:800:200c:417a"
  522. #
  523. # is represented with the binary data
  524. #
  525. # " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
  526. #
  527. # With that data you can create a new IPv6 object:
  528. #
  529. # ip6 = IPAddress::IPv6::parse_data " \001\r\270\000\000\000\000\000\b\b\000 \fAz"
  530. # ip6.prefix = 64
  531. #
  532. # ip6.to_s
  533. # #=> "2001:db8::8:800:200c:417a/64"
  534. #
  535. def self.parse_data(str)
  536. self.new(IN6FORMAT % str.unpack("n8"))
  537. end
  538. #
  539. # Creates a new IPv6 object from an
  540. # unsigned 128 bits integer.
  541. #
  542. # ip6 = IPAddress::IPv6::parse_u128(42540766411282592856906245548098208122)
  543. # ip6.prefix = 64
  544. #
  545. # ip6.to_string
  546. # #=> "2001:db8::8:800:200c:417a/64"
  547. #
  548. # The +prefix+ parameter is optional:
  549. #
  550. # ip6 = IPAddress::IPv6::parse_u128(42540766411282592856906245548098208122, 64)
  551. #
  552. # ip6.to_string
  553. # #=> "2001:db8::8:800:200c:417a/64"
  554. #
  555. def self.parse_u128(u128, prefix=128)
  556. str = IN6FORMAT % (0..7).map{|i| (u128>>(112-16*i))&0xffff}
  557. self.new(str + "/#{prefix}")
  558. end
  559. #
  560. # Creates a new IPv6 object from a number expressed in
  561. # hexdecimal format:
  562. #
  563. # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a")
  564. # ip6.prefix = 64
  565. #
  566. # ip6.to_string
  567. # #=> "2001:db8::8:800:200c:417a/64"
  568. #
  569. # The +prefix+ parameter is optional:
  570. #
  571. # ip6 = IPAddress::IPv6::parse_hex("20010db80000000000080800200c417a", 64)
  572. #
  573. # ip6.to_string
  574. # #=> "2001:db8::8:800:200c:417a/64"
  575. #
  576. def self.parse_hex(hex, prefix=128)
  577. self.parse_u128(hex.hex, prefix)
  578. end
  579. private
  580. def compress_address
  581. str = @groups.map{|i| i.to_s 16}.join ":"
  582. loop do
  583. break if str.sub!(/\A0:0:0:0:0:0:0:0\Z/, '::')
  584. break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':')
  585. break if str.sub!(/\b0:0:0:0:0:0\b/, ':')
  586. break if str.sub!(/\b0:0:0:0:0\b/, ':')
  587. break if str.sub!(/\b0:0:0:0\b/, ':')
  588. break if str.sub!(/\b0:0:0\b/, ':')
  589. break if str.sub!(/\b0:0\b/, ':')
  590. break
  591. end
  592. str.sub(/:{3,}/, '::')
  593. end
  594. end # class IPv6
  595. #
  596. # The address with all zero bits is called the +unspecified+ address
  597. # (corresponding to 0.0.0.0 in IPv4). It should be something like this:
  598. #
  599. # 0000:0000:0000:0000:0000:0000:0000:0000
  600. #
  601. # but, with the use of compression, it is usually written as just two
  602. # colons:
  603. #
  604. # ::
  605. #
  606. # or, specifying the netmask:
  607. #
  608. # ::/128
  609. #
  610. # With IPAddress, create a new unspecified IPv6 address using its own
  611. # subclass:
  612. #
  613. # ip = IPAddress::IPv6::Unspecified.new
  614. #
  615. # ip.to_s
  616. # #=> => "::/128"
  617. #
  618. # You can easily check if an IPv6 object is an unspecified address by
  619. # using the IPv6#unspecified? method
  620. #
  621. # ip.unspecified?
  622. # #=> true
  623. #
  624. # An unspecified IPv6 address can also be created with the wrapper
  625. # method, like we've seen before
  626. #
  627. # ip = IPAddress "::"
  628. #
  629. # ip.unspecified?
  630. # #=> true
  631. #
  632. # This address must never be assigned to an interface and is to be used
  633. # only in software before the application has learned its host's source
  634. # address appropriate for a pending connection. Routers must not forward
  635. # packets with the unspecified address.
  636. #
  637. class IPAddress::IPv6::Unspecified < IPAddress::IPv6
  638. #
  639. # Creates a new IPv6 unspecified address
  640. #
  641. # ip = IPAddress::IPv6::Unspecified.new
  642. #
  643. # ip.to_s
  644. # #=> => "::/128"
  645. #
  646. def initialize
  647. @address = ("0000:"*8).chop
  648. @groups = Array.new(8,0)
  649. @prefix = Prefix128.new(128)
  650. @compressed = compress_address
  651. end
  652. end # class IPv6::Unspecified
  653. #
  654. # The loopback address is a unicast localhost address. If an
  655. # application in a host sends packets to this address, the IPv6 stack
  656. # will loop these packets back on the same virtual interface.
  657. #
  658. # Loopback addresses are expressed in the following form:
  659. #
  660. # ::1
  661. #
  662. # or, with their appropriate prefix,
  663. #
  664. # ::1/128
  665. #
  666. # As for the unspecified addresses, IPv6 loopbacks can be created with
  667. # IPAddress calling their own class:
  668. #
  669. # ip = IPAddress::IPv6::Loopback.new
  670. #
  671. # ip.to_string
  672. # #=> "::1/128"
  673. #
  674. # or by using the wrapper:
  675. #
  676. # ip = IPAddress "::1"
  677. #
  678. # ip.to_string
  679. # #=> "::1/128"
  680. #
  681. # Checking if an address is loopback is easy with the IPv6#loopback?
  682. # method:
  683. #
  684. # ip.loopback?
  685. # #=> true
  686. #
  687. # The IPv6 loopback address corresponds to 127.0.0.1 in IPv4.
  688. #
  689. class IPAddress::IPv6::Loopback < IPAddress::IPv6
  690. #
  691. # Creates a new IPv6 unspecified address
  692. #
  693. # ip = IPAddress::IPv6::Loopback.new
  694. #
  695. # ip.to_string
  696. # #=> "::1/128"
  697. #
  698. def initialize
  699. @address = ("0000:"*7)+"0001"
  700. @groups = Array.new(7,0).push(1)
  701. @prefix = Prefix128.new(128)
  702. @compressed = compress_address
  703. end
  704. end # class IPv6::Loopback
  705. #
  706. # It is usually identified as a IPv4 mapped IPv6 address, a particular
  707. # IPv6 address which aids the transition from IPv4 to IPv6. The
  708. # structure of the address is
  709. #
  710. # ::ffff:w.y.x.z
  711. #
  712. # where w.x.y.z is a normal IPv4 address. For example, the following is
  713. # a mapped IPv6 address:
  714. #
  715. # ::ffff:192.168.100.1
  716. #
  717. # IPAddress is very powerful in handling mapped IPv6 addresses, as the
  718. # IPv4 portion is stored internally as a normal IPv4 object. Let's have
  719. # a look at some examples. To create a new mapped address, just use the
  720. # class builder itself
  721. #
  722. # ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128"
  723. #
  724. # or just use the wrapper method
  725. #
  726. # ip6 = IPAddress "::ffff:172.16.10.1/128"
  727. #
  728. # Let's check it's really a mapped address:
  729. #
  730. # ip6.mapped?
  731. # #=> true
  732. #
  733. # ip6.to_string
  734. # #=> "::FFFF:172.16.10.1/128"
  735. #
  736. # Now with the +ipv4+ attribute, we can easily access the IPv4 portion
  737. # of the mapped IPv6 address:
  738. #
  739. # ip6.ipv4.address
  740. # #=> "172.16.10.1"
  741. #
  742. # Internally, the IPv4 address is stored as two 16 bits
  743. # groups. Therefore all the usual methods for an IPv6 address are
  744. # working perfectly fine:
  745. #
  746. # ip6.to_hex
  747. # #=> "00000000000000000000ffffac100a01"
  748. #
  749. # ip6.address
  750. # #=> "0000:0000:0000:0000:0000:ffff:ac10:0a01"
  751. #
  752. # A mapped IPv6 can also be created just by specify the address in the
  753. # following format:
  754. #
  755. # ip6 = IPAddress "::172.16.10.1"
  756. #
  757. # That is, two colons and the IPv4 address. However, as by RFC, the ffff
  758. # group will be automatically added at the beginning
  759. #
  760. # ip6.to_string
  761. # => "::ffff:172.16.10.1/128"
  762. #
  763. # making it a mapped IPv6 compatible address.
  764. #
  765. class IPAddress::IPv6::Mapped < IPAddress::IPv6
  766. # Access the internal IPv4 address
  767. attr_reader :ipv4
  768. #
  769. # Creates a new IPv6 IPv4-mapped address
  770. #
  771. # ip6 = IPAddress::IPv6::Mapped.new "::ffff:172.16.10.1/128"
  772. #
  773. # ipv6.ipv4.class
  774. # #=> IPAddress::IPv4
  775. #
  776. # An IPv6 IPv4-mapped address can also be created using the
  777. # IPv6 only format of the address:
  778. #
  779. # ip6 = IPAddress::IPv6::Mapped.new "::0d01:4403"
  780. #
  781. # ip6.to_string
  782. # #=> "::ffff:13.1.68.3"
  783. #
  784. def initialize(str)
  785. string, netmask = str.split("/")
  786. if string =~ /\./ # IPv4 in dotted decimal form
  787. @ipv4 = IPAddress::IPv4.extract(string)
  788. else # IPv4 in hex form
  789. groups = IPAddress::IPv6.groups(string)
  790. @ipv4 = IPAddress::IPv4.parse_u32((groups[-2]<< 16)+groups[-1])
  791. end
  792. super("::ffff:#{@ipv4.to_ipv6}/#{netmask}")
  793. end
  794. #
  795. # Similar to IPv6#to_s, but prints out the IPv4 address
  796. # in dotted decimal format
  797. #
  798. # ip6 = IPAddress "::ffff:172.16.10.1/128"
  799. #
  800. # ip6.to_s
  801. # #=> "::ffff:172.16.10.1"
  802. #
  803. def to_s
  804. "::ffff:#{@ipv4.address}"
  805. end
  806. #
  807. # Similar to IPv6#to_string, but prints out the IPv4 address
  808. # in dotted decimal format
  809. #
  810. #
  811. # ip6 = IPAddress "::ffff:172.16.10.1/128"
  812. #
  813. # ip6.to_string
  814. # #=> "::ffff:172.16.10.1/128"
  815. #
  816. def to_string
  817. "::ffff:#{@ipv4.address}/#@prefix"
  818. end
  819. #
  820. # Checks if the IPv6 address is IPv4 mapped
  821. #
  822. # ip6 = IPAddress "::ffff:172.16.10.1/128"
  823. #
  824. # ip6.mapped?
  825. # #=> true
  826. #
  827. def mapped?
  828. true
  829. end
  830. end # class IPv6::Mapped
  831. end # module IPAddress