Sunday, July 19, 2015

How to determine the time zone of an IP in Ruby

In ruby / Rails, you may need to get the timezone of an ip address.

One of the best options is the Maxmind.com GeoIP . The solution provided here applies to geolocate an IP address to get the timezone, country, city etc

Local or Remote

For better results I recommend to use their web service to geolocate a city : http://dev.maxmind.com/geoip/geoip2/web-services .
The service is not free but you can pay 20$ for 50K resolutions. Fair enough.
I write a new post on how to query the web service.

Another option is to resolve the geolocation locally. Maxmind provides a free database that is updated every month. As a free product, it is less accurate than the web service but gives good results. http://dev.maxmind.com/geoip/legacy/geolite/

Local set up

We need:
- download the database
- include the `geoip` gem, that will read the database
- our code to query the geoip gem
- link back to Maxmind

Download database

Read http://dev.maxmind.com/geoip/legacy/install/city/ for instructions. It boils down to:

cd /tmp
wget -N http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
gunzip GeoLiteCity.dat.gz
mv GeoLiteCity.dat /usr/local/share/GeoIP/

I recommend you to make a bash script with these commands and run a cron once a month

Required gem


To read the legacy GeoIP database I recommend the pure ruby geoip gem. Just include in your Gemfile
 gem 'geoip'

or
 gem install geoip

Boilerplate

The interface of GeoiP gem is really simple however, as we are using a database with less than 100% accuracy, we should account for cases where timezone is not available.
When a ip resolves to a location without timezone, the best trick I found was to try with 'enclosing' networks (bigger supernets) until a result has timezone. That is: first try a perfect match, then try for the network with the last bit of the address masked, then mask 2 bits ...  https://en.wikipedia.org/wiki/Subnetwork#Subnet_and_host_counts


class Ip2Geo

  DB_PATH = '/usr/local/share/GeoIP/GeoLiteCity.dat'

  def timezone(ipv4)
    if ipv4.present?
      addr = IPAddr.new(ipv4.to_s.strip)
      32.downto(16).map do |mask| #if an address fails, test with enclosing network X.X.X.X , X.X.X.0 , X.X.0.0 ...
        network = addr.mask(mask).to_i
        c = db.city(network)
        if c && c.timezone
          return c.timezone
        end
      end
    end
    nil
  end
#a similar pattern can be re-used
  def country_code(ipv4)
    if ipv4.present?
      candidate = ipv4.to_s.strip
      addr = IPAddr.new(ipv4.to_s.strip)
      32.downto(16).map do |mask|
        network = addr.mask(mask).to_i
        c = db.city(network)
        if c && c.country_code2
          return c.country_code2
        end
      end
    end
    nil
  end

  def location(ipv4)
    if ipv4.present?
      candidate = ipv4.to_s.strip
      addr = IPAddr.new(ipv4.to_s.strip)
      32.downto(16).map do |mask|
        network = addr.mask(mask).to_i
        c = db.city(network)
        return c if c
      end
    end
    nil
  end


  def db
    @db ||= GeoIP.new(DB_PATH)
  end

end

To resolve the ip

locator = Ip2Geo.new
locator.timezone('193.110.128.199')

Attribution

Remember that the use of the free database requires an attribution: The attribution requirement may be met by including the following in all advertising and documentation mentioning features of or use of this database 
This product includes GeoLite data created by MaxMind, available from http://www.maxmind.com



Friday, June 12, 2015

Canon lide 220 in Ubuntu 14.04

Last week I bought a Canon lide 220 scanner. You get a decent device for the price.

(since I had to read several posts) Here are in a single page all the steps I needed to make it work in Ubuntu 14.04 (via xsane).

connect the scanner

ensure it is recognized

    lsusb

in teh output, search for the Canon device

...
Bus 003 Device 003: ID 04a9:190f Canon, Inc.
...

ensure your user has access to the bus (otherwise  you'll have to launch everytime  xsane as root)

    sudo chmod u+w /dev/bus/usb//

in my case

 sudo chmod u+w /dev/bus/usb/003/003

Then, add this ppa to install latest version of xsane

sudo add-apt-repository ppa:rolfbensch/sane-git

and install xsane

    sudo apt-get update && sudo apt-get install gocr libsane-extras xsane-common xsane

if you have problems, install a couple of additional packets with

    sudo apt-get update && sudo apt-get install gocr libusb-dev libsane-dev libsane-extras xsane-common xsane

now you can run

  xsane


 and the scanner is working!