Tuesday, April 26, 2011

Listing all network interfaces/devices and their MAC Addresses (for Linux with Ruby)

I used this gem to get System.get_ifaddrs() working on Ruby.

(Testing in ether_shell because my ethernet still can't pass rspec.)

For the 'system/getifaddrs' require, have to let user install it before they can use it, so do:
1. Add into the Gemfile, like
  2 # Add dependencies required to use your gem here.
  3 # Example:
  4 #   gem "activesupport", ">= 2.3.5"
  5   gem "system-getifaddrs", "~> 0.1.1"
2. Do NOT add gem.add_runtime_dependency 'rcov', '>= 0.1.1' to Rakefile because it's automatic now
3. bundle
4. sudo rake install


Add in lib/ethernet/raw_socket.rb:

require 'system/getifaddrs' # for listing

...

  # returns a symbols array of all network devices
  def self.list_all_devices()
    case RUBY_PLATFORM
    when /linux/ #TODO: test to see if works on MacOS as well 
      devicehash = System.get_ifaddrs # {:eth0=>{:inet_addr=>"18.248.7.89", :netmask=>"18.248.7.89"}, :vmnet1=>{:inet_addr=>"172.16.76.1", :netmask=>"172.16.76.1"}}
      devicehash.keys
    end 
  end 

  # returns a hash of all network devices => MAC Address
  def self.list_all_devices_macaddr()
    case RUBY_PLATFORM
    when /linux/ 
      devices = self.list_all_devices
      device_macaddrs = {}
   
      devices.each do |eth_device|
        device_macaddrs[eth_device] = self.mac(eth_device.to_s).unpack('H*')[0]
      end 
      device_macaddrs
    end 
  end 

Add in spec/ethernet/raw_socket_spec.rb
describe 'testing listing all network devices' do
     it 'should list the devices' do
       device_macaddrs = Ethernet::RawSocket.list_all_devices_macaddr()
       device_macaddrs.each do |key, value|
         puts "\nDevice is: "
         puts key.to_s
         puts "MAC Addrs: "
         puts value
         puts "\n"
       end 
     end 
  end 

Results:
$ sudo rspec raw_socket_spec.rb 
.....
Device is: 
vmnet1
MAC Addrs: 
005056c00001


Device is: 
vmnet8
MAC Addrs: 
005056c00008


Device is: 
lo
MAC Addrs: 
000000000000


Device is: 
eth0
MAC Addrs: 
002170925bc5

.

Finished in 0.11353 seconds
6 examples, 0 failures

Details of how I found this solution, very boring
I could run ether_shell's specs. Specifically, sudo rspec raw_socket_spec.rb, which currently has eth0 as the device
3 describe 'RawSocket' do
  4   let(:eth_device) { 'eth0' } 

Now I want to see if the outputting mac address function works.

In raw_socket.rb, .mac() is the function that gets the mac address of a device:
28   # The MAC address for an Ethernet card.
 29   #
 30   # Args:
 31   # eth_device:: device name for the Ethernet card, e.g. 'eth0'
 32   def self.mac(eth_device)
 33     case RUBY_PLATFORM 
 34     when /linux/
 35       # /usr/include/net/if.h, structure ifreq
 36       ifreq = [eth_device].pack 'a32'
 37       # 0x8927 is SIOCGIFHWADDR in /usr/include/bits/ioctls.h
 38       socket.ioctl 0x8927, ifreq
 39       ifreq[18, 6] #in scratchpad's raw_ethernet, then says .unpack('H*').first
 40     else 
 41       raise "Unsupported platform #{RUBY_PLATFORM}"
 42     end
 43   end

I changed it so that it prints out the mac: (line numbers don't match)
28       # /usr/include/net/if.h, structure ifreq
 29       ifreq = [eth_device].pack 'a32'
 30       puts "\ndevice name: "
 31       puts ifreq
 32       # 0x8927 is SIOCGIFHWADDR in /usr/include/bits/ioctls.h
 33       socket.ioctl 0x8927, ifreq
 34       puts "mac address output: "
 35       puts ifreq[18, 6].unpack('H*')[0]
 36       puts "-----------"
 37       ifreq[18, 6]


$ sudo rspec raw_socket_spec.rb

device name:
eth0
mac address output:
002170925bc5
-----------
.
device name:
eth0
mac address output:
002170925bc5
-----------
..
device name:
eth0
mac address output:
002170925bc5
-----------
..

Finished in 0.30495 seconds
5 examples, 0 failures

yup, the mac address of my eth0 is indeed that. (matched HWaddr in ifconfig -a of eth0)

the ".unpack('H*')[0]" part changes the mac address into hex, without it, it would look like:
!p�[


hmmm.... so let's see if it gets the right mac address if I change the device to vmnet1
so, raw_socket_spec.rb:
3 describe 'RawSocket' do
  4   let(:eth_device) { 'vmnet1' }

$ sudo rspec raw_socket_spec.rb

device name:
vmnet1
mac address output:
005056c00001
-----------
.
device name:
vmnet1
mac address output:
005056c00001
-----------
..
device name:
vmnet1
mac address output:
005056c00001
-----------

a match! yipee!!! (the rspec hung, but that we don't care)

Also worked for wlan0, even though I'm not plugged in to it

device name:
wlan0
mac address output:
001c26640fac
-----------


============================================

So now, I need just get the list of names and then run .mac() for each of them.
I need to look at the code for systemgetifaddr

man 3 getifaddrs
http://comments.gmane.org/gmane.os.cygwin/123705 ioctl vs getifaddrs

The rspec of https://github.com/bbcoimbra/system-getifaddrs/blob/master/spec/system-ifaddrs_spec.rb compares it to ifconfig's result

added to raw_socket.rb:
2 require 'rubygems' # for listing
  3 require 'system/getifaddrs' # for listing
...
 45   # listing all network devices's MAC addresses
 46   def self.list_all_devices()
 47     case RUBY_PLATFORM
 48     when /linux/ #test to see if works on MacOS as well 
 49       devicehash = System.get_ifaddrs # {:eth0=>{:inet_addr=>"18.248.7.89", :netmask=>"18.    248.7.89"}, :vmnet1=>{:inet_addr=>"172.16.76.1", :netmask=>"172.16.76.1"}}
 50       devicehash.each_key do |key|
 51         puts self.mac(key.to_s).unpack('H*')[0]
 52         puts " - - - - -"
 53       end
 54     end
 55   end

added to raw_socket_spec.rb:
42   describe 'testing listing all network devices' do
 43      it 'should list the devices' do
 44          EtherShell::RawSocket.list_all_devices().should_not be_empty
 45      end
 46   end

good results:
$ sudo rspec  raw_socket_spec.rb 

device name: 
eth0
mac address output: 
002170925bc5
-----------
.
device name: 
eth0
mac address output: 
002170925bc5
-----------
..
device name: 
eth0
mac address output: 
002170925bc5
-----------
..
device name: 
vmnet1
mac address output: 
005056c00001
-----------
005056c00001
 - - - - -

device name: 
vmnet8
mac address output: 
005056c00008
-----------
005056c00008
 - - - - -

device name: 
lo
mac address output: 
000000000000
-----------
000000000000
 - - - - -

device name: 
eth0
mac address output: 
002170925bc5
-----------
002170925bc5
 - - - - -
.

Finished in 0.21653 seconds
6 examples, 0 failures

now I have to make it into 2 functions, one the lists the devices, one that lists their mac addresses (see way top of blog)

No comments:

Post a Comment