Convert Ip Address to 32 Bit Integer in Ruby

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



Leave a reply



Submit