How to manually convert an integer into an IP address in Ruby
An IP is just a 32-bit integer representing a 4-byte array:
[631271850].pack('N').unpack('CCCC').join('.')
=> "37.160.113.170"
Just for fun, another way to convert IP to int:
"37.160.113.170".split(".").map(&:to_i).pack('CCCC').unpack('N')[0]
=> 631271850
Trying to convert existing IPS to integers
You can include your conversion code in a migration. Do the actual conversion from string into integer in a separate conversion.
def up
User.all.each do
[:current_sign_in_ip, :last_sign_in_ip].each do |field|
quads = user.read_attribute(field).split('.')
if quads.length == 4
as_int = (quads[0].to_i * (2**24)) + (quads[1].to_i * (2**16)) + (quads[2].to_i * (2**8)) + quads[3].to_i
as_int -= 4_294_967_296 if as_int > 2147483647 # Convert to 2's complement
else
as_int = nil
end
user.write_attribute(field, "#{as_int}")
end
end
end
def down
User.all.each do |user|
[:current_sign_in_ip, :last_sign_in_ip].each do |field|
ip = user.read_attribute(field).to_i
return nil unless ip
ip += 4_294_967_296 if ip < 0 # Convert from 2's complement
user.write_attribute(field, "#{(ip & 0xFF000000) >> 24}.#{(ip & 0x00FF0000) >> 16}.#{(ip & 0x0000FF00) >> 8}.#{ip & 0x000000FF}")
end
end
end
Ruby: How to convert ipv6 uo address to ipv4 ip address?
Firstly, it's important to understand that it's impossible to convert most IPv6 addresses to IPv4 addresses (for several reasons, the most obvious being that there's no way to fit a 128-bit number into 32 bits). The only IPv6 addresses that can be converted to IPv4 are ones that are mapped from IPv4 address (such as those produced by IPAddr#ipv4_mapped
). For that, we have IPAddr#native
:
require "ipaddr"
ip = IPAddr.new("192.168.2.128")
ipv6 = ip.ipv4_mapped
puts ipv6.native
# => 192.168.2.128
Convert a list of arrays into ip ranges
Convert the IP address to 32 bit integer (Assume you're dealing with IPv4 address according to your post), remove the duplicates, sort them, and do the merge. After that, convert the integers back to IP string:
require 'ipaddr'
def to_ranges(ips)
ips = ips.map{|ip| IPAddr.new(ip).to_i }.uniq.sort
prev = ips[0]
ips
.slice_before {|e|
prev2, prev = prev, e
prev2 + 1 != e
}
.map {|addrs| if addrs.length > 1 then [addrs[0], addrs[-1]] else addrs end }
.map {|addrs| addrs.map{|ip| IPAddr.new(ip, Socket::AF_INET)}.join("-") }
end
# some ip samples
ips = (0..255).map{|i| ["192.168.0.#{i}", "192.168.1.#{i}", "192.168.2.#{i}"] }.reduce(:+)
ips += ["192.168.3.0", "192.168.3.1"]
ips += ["192.168.3.5", "192.168.3.6"]
ips += ["192.168.5.1"]
ips += ["192.168.6.255", "192.168.7.0", "192.168.7.1"]
p to_ranges(ips)
# => ["192.168.0.0-192.168.3.1", "192.168.3.5-192.168.3.6", "192.168.5.1", "192.168.6.255-192.168.7.1"]
Reading IP addresses from file and storing them in an array should be relatively easy. 2 million IP addresses is a small set. You don't need to worry to much about the memory usage. (If it really matters, you may need to implement a algorithm to incrementally convert and merge the addresses)
BTW, I found the handy method Enumerable#slice_before when solving your problem.
Convert an IP string to a number and vice versa
converting an IP string to long integer:
import socket, struct
def ip2long(ip):
"""
Convert an IP string to long
"""
packedIP = socket.inet_aton(ip)
return struct.unpack("!L", packedIP)[0]
the other way around:
>>> socket.inet_ntoa(struct.pack('!L', 2130706433))
'127.0.0.1'
Is there a way I can mutate an ip address in decimal format into normal human readable in logstash?
You should be able to do this with a ruby filter like this:
filter {
ruby {
code => 'event["ip_as_dotted_quad"] = [event["your_field_here"].to_i].pack("N").unpack("C4").join(".")'
}
}
You'll just need to fill in your_field_here
. If you already converted the field to an integer, you can leave out the .to_i
.
(credit where credit is due -- I found the ip address snippet at the bottom of this page: http://basic70tech.wordpress.com/2007/04/13/32-bit-ip-address-to-dotted-notation-in-ruby/ but adapted it to use here.)
Count IP addresses
If you need to convert an IP to a range, you'll need a function that converts an IPv4 value to an integer, then do math on those:
require 'ipaddr'
def ip(string)
IPAddr.new(string).to_i
end
def parse_ip(string)
string.split(/\s+\-\s+/).collect { |v| ip(v) }
end
def ip_range(string)
ips = parse_ip(string)
ips.last - ips.first + 1
end
ip_range("10.2.3.10 - 10.2.3.15")
# => 6
That should do it.
Netmask to CIDR in ruby
Here is the quick and dirty way
require 'ipaddr'
puts IPAddr.new("255.255.255.0").to_i.to_s(2).count("1")
There should be proper function for that, I couldn't find that, so I just count "1"
If you're going to be using the function in a number of places and don't mind monkeypatching, this could help:
IPAddr.class_eval
def to_cidr
"/" + self.to_i.to_s(2).count("1")
end
end
Then you get
IPAddr.new('255.255.255.0').to_cidr
# => "/24"
Related Topics
Ruby on Rails - Activerecord::Relation Count Method Is Wrong
Rails 5 - Using Polymorphic Associations - Rendering the Views
Why Can't I Create an Array as a Column in a Table in Rails
Test (With Rspec) a Controller Outside of a Rails Environment
Error Building Ruby in Sublime Text Editor
How to Change Ruby to Version 1.9.3 (Again) with Rvm
Rails Error Message Displays Key and I Only Want Value
Ruby: Cannot Install Watir Gem on Windows
Ruby Private Attr_Accessor and Unexpected Nil
Triple Single Quote VS Triple Double Quote in Ruby
Why Is My Ruby Git Script Hook Run with the Wrong $Path
Ruby, Generate a Random Hex Color (Only Light Colors)
Pg::Error: Error: Invalid Byte Sequence for Encoding "Utf8": 0Xfc
How to Ssh Interactive Session
Ruby -- Capitalize First Letter of Every Sentence in a Paragraph