ethernet 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -3,8 +3,7 @@ source 'http://rubygems.org'
3
3
  # Example:
4
4
  # gem 'activesupport', '>= 2.3.5'
5
5
 
6
- gem 'system-getifaddrs', '~> 0.1.1', :platform => :ruby,
7
- :require => 'system/getifaddrs'
6
+ gem 'ffi', '>= 1.0.0', :platform => :mri
8
7
 
9
8
  # Add dependencies to develop your gem here.
10
9
  # Include everything needed to run rake, tests, features, etc.
@@ -13,5 +12,5 @@ group :development do
13
12
  gem 'rspec', '~> 2.6.0'
14
13
  gem 'bundler', '~> 1.0.0'
15
14
  gem 'jeweler', '~> 1.6.0'
16
- gem 'rcov', '>= 0'
15
+ gem 'rcov', '>= 0', :platform => :mri
17
16
  end
data/Gemfile.lock CHANGED
@@ -2,6 +2,7 @@ GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
4
  diff-lcs (1.1.2)
5
+ ffi (1.0.9)
5
6
  git (1.2.5)
6
7
  jeweler (1.6.0)
7
8
  bundler (~> 1.0.0)
@@ -18,15 +19,15 @@ GEM
18
19
  rspec-expectations (2.6.0)
19
20
  diff-lcs (~> 1.1.2)
20
21
  rspec-mocks (2.6.0)
21
- system-getifaddrs (0.1.1)
22
22
 
23
23
  PLATFORMS
24
+ java
24
25
  ruby
25
26
 
26
27
  DEPENDENCIES
27
28
  bundler (~> 1.0.0)
29
+ ffi (>= 1.0.0)
28
30
  jeweler (~> 1.6.0)
29
31
  rcov
30
32
  rdoc (>= 3.6.1)
31
33
  rspec (~> 2.6.0)
32
- system-getifaddrs (~> 0.1.1)
data/README.rdoc CHANGED
@@ -4,6 +4,14 @@ A Ruby Gem Library for Ethernet Communication
4
4
 
5
5
  This project is a low-level ethernet communication rubygems library. Its initial intension is to set up a communication API between the FPGA chip and the server for MIT's Trusted Computing Secure Storage project http://projects.csail.mit.edu/tc/.
6
6
 
7
+ == Supported Platforms
8
+
9
+ ethernet currently works on Linux and Mac OSX. It should work on other OSes that have kernel support for BPF (Berkely Packet Filter), with minor hacking.
10
+
11
+ ethernet does not currently work on Windows, and is unlikely to do so in the near future. Microsoft, in its infinite wisdom, decided to remove low-level Ethernet support from Windows starting from XP SP2, so the gem would have to ship a pre-compiled miniport driver. Patches are welcome!
12
+
13
+ ethernet is tested on MRI, but should run on all Ruby VMs that have FFI support, including JRuby and Rubinius. Please file bugs if that is not the case.
14
+
7
15
  == Contributing to ethernet
8
16
 
9
17
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
@@ -14,7 +22,7 @@ This project is a low-level ethernet communication rubygems library. Its initial
14
22
  * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
15
23
  * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
16
24
 
17
- == Author
25
+ == Authors
18
26
 
19
27
  Victor Costan victor@costan.us
20
28
  HaoQi Li haoqili@mit.edu
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
data/ethernet.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ethernet}
8
- s.version = "0.1.1"
8
+ s.version = "0.1.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = [%q{Victor Costan}, %q{HaoQi Li}]
12
- s.date = %q{2011-05-27}
12
+ s.date = %q{2011-05-30}
13
13
  s.description = %q{Provides a Socket-like API that bypasses TCP/IP. Useful for exotic devices and FPGA development.}
14
14
  s.email = %q{victor@costan.us}
15
15
  s.extra_rdoc_files = [
@@ -29,9 +29,13 @@ Gem::Specification.new do |s|
29
29
  "ethernet.gemspec",
30
30
  "lib/ethernet.rb",
31
31
  "lib/ethernet/devices.rb",
32
+ "lib/ethernet/devices_darwin.rb",
33
+ "lib/ethernet/devices_linux.rb",
32
34
  "lib/ethernet/frame_socket.rb",
33
35
  "lib/ethernet/provisioning.rb",
34
36
  "lib/ethernet/raw_socket_factory.rb",
37
+ "lib/ethernet/raw_socket_factory_darwin.rb",
38
+ "lib/ethernet/raw_socket_factory_linux.rb",
35
39
  "spec/ethernet/devices_spec.rb",
36
40
  "spec/ethernet/frame_socket_spec.rb",
37
41
  "spec/ethernet/raw_socket_factory_spec.rb",
@@ -50,14 +54,14 @@ Gem::Specification.new do |s|
50
54
  s.specification_version = 3
51
55
 
52
56
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
53
- s.add_runtime_dependency(%q<system-getifaddrs>, ["~> 0.1.1"])
57
+ s.add_runtime_dependency(%q<ffi>, [">= 1.0.0"])
54
58
  s.add_development_dependency(%q<rdoc>, [">= 3.6.1"])
55
59
  s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
56
60
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
57
61
  s.add_development_dependency(%q<jeweler>, ["~> 1.6.0"])
58
62
  s.add_development_dependency(%q<rcov>, [">= 0"])
59
63
  else
60
- s.add_dependency(%q<system-getifaddrs>, ["~> 0.1.1"])
64
+ s.add_dependency(%q<ffi>, [">= 1.0.0"])
61
65
  s.add_dependency(%q<rdoc>, [">= 3.6.1"])
62
66
  s.add_dependency(%q<rspec>, ["~> 2.6.0"])
63
67
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
@@ -65,7 +69,7 @@ Gem::Specification.new do |s|
65
69
  s.add_dependency(%q<rcov>, [">= 0"])
66
70
  end
67
71
  else
68
- s.add_dependency(%q<system-getifaddrs>, ["~> 0.1.1"])
72
+ s.add_dependency(%q<ffi>, [">= 1.0.0"])
69
73
  s.add_dependency(%q<rdoc>, [">= 3.6.1"])
70
74
  s.add_dependency(%q<rspec>, ["~> 2.6.0"])
71
75
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
data/lib/ethernet.rb CHANGED
@@ -42,3 +42,11 @@ require 'ethernet/devices.rb'
42
42
  require 'ethernet/frame_socket.rb'
43
43
  require 'ethernet/provisioning.rb'
44
44
  require 'ethernet/raw_socket_factory.rb'
45
+ case Ethernet::Provisioning::OS
46
+ when /darwin/
47
+ require 'ethernet/devices_darwin.rb'
48
+ require 'ethernet/raw_socket_factory_darwin.rb'
49
+ when /linux/
50
+ require 'ethernet/devices_linux.rb'
51
+ require 'ethernet/raw_socket_factory_linux.rb'
52
+ end
@@ -1,23 +1,13 @@
1
- # OS-dependent gem includes.
2
- case RUBY_PLATFORM
3
- when /linux/
4
- when /darwin/
5
- require 'system/getifaddrs'
6
- end
1
+ require 'ffi'
7
2
 
8
3
  # :nodoc: namespace
9
4
  module Ethernet
10
5
 
11
6
  # Information about the available Ethernet devices.
12
7
  module Devices
13
- # An array of device names for the machine's Ethernet devices.
8
+ # Array containing device names.
14
9
  def self.all
15
- case RUBY_PLATFORM
16
- when /linux/, /darwin/
17
- System.get_ifaddrs.keys.map(&:to_s)
18
- else
19
- raise "Unsupported platform #{RUBY_PLATFORM}"
20
- end
10
+ info.keys
21
11
  end
22
12
 
23
13
  # The MAC address for an Ethernet device.
@@ -25,16 +15,91 @@ module Devices
25
15
  # Args:
26
16
  # eth_device:: device name for the Ethernet card, e.g. 'eth0'
27
17
  def self.mac(eth_device)
28
- case RUBY_PLATFORM
29
- when /linux/
30
- # /usr/include/net/if.h, structure ifreq
31
- ifreq = [eth_device].pack 'a32'
32
- # 0x8927 is SIOCGIFHWADDR in /usr/include/bits/ioctls.h
33
- RawSocketFactory.socket.ioctl 0x8927, ifreq
34
- ifreq[18, 6]
35
- else
36
- raise "Unsupported platform #{RUBY_PLATFORM}"
18
+ info[eth_device][:mac]
19
+ end
20
+
21
+ # The interface number for an Ethernet interface.
22
+ #
23
+ # Args:
24
+ # eth_device:: device name for the Ethernet card, e.g. 'eth0'
25
+ def self.interface_index(eth_device)
26
+ info[eth_device][:index]
27
+ end
28
+
29
+ # Hash mapping device names to information about devices.
30
+ def self.info
31
+ # array of struct ifreq in /usr/include/net/if.h
32
+ buffer_size = ifreq_size * 128
33
+ buffer_ptr = FFI::MemoryPointer.new :uchar, buffer_size, 0
34
+ # struct ifconf in /usr/include/net/if.h
35
+ ifconf = [buffer_size, buffer_ptr.address].pack ifconf_packspec
36
+ ioctl_socket.ioctl siocgifconf_ioctl, ifconf
37
+
38
+ output_size = ifconf.unpack('l').first
39
+ offset = 0
40
+ devices = {}
41
+ while offset < output_size
42
+ name = (buffer_ptr + offset).read_string_to_null
43
+ devices[name] ||= {}
44
+ # struct sockaddr
45
+ addr_length, addr_family = *(buffer_ptr + offset + 16).read_string(2).
46
+ unpack('CC')
47
+ addr_length = ifreq_size - 16 if addr_length < ifreq_size - 16
48
+ if addr_family == ll_address_family
49
+ # struct sockaddr_dl in /usr/include/net/if_dl.h
50
+ devices[name][:index], blah, skip, length =
51
+ *(buffer_ptr + offset + 18).read_string(4).unpack('SCCC')
52
+ devices[name][:mac] =
53
+ (buffer_ptr + offset + 24 + skip).read_string(length)
54
+ end
55
+
56
+ offset += 16 + addr_length
57
+ end
58
+
59
+ if devices.all? { |k, v| v[:mac].nil? }
60
+ # Linux only provides IP addresses in SIOCGIFCONF.
61
+ devices.keys.each do |device|
62
+ devices[device][:mac] ||= mac device
63
+ devices[device][:index] ||= interface_index device
64
+ end
65
+ end
66
+ devices.delete_if { |k, v| v[:mac].nil? || v[:mac].empty? }
67
+
68
+ devices
69
+ end
70
+
71
+ class <<self
72
+ # SIOCGIFCONF ioctl number.
73
+ def siocgifconf_ioctl
74
+ raise "Unsupported os #{Ethernet::Provisioning::OS}"
75
+ end
76
+
77
+
78
+ # Array#pack specification for struct ifconf.
79
+ #
80
+ # The specification converts a [size, pointer] array into a valid struct.
81
+ def ifconf_packspec
82
+ raise "Unsupported os #{Ethernet::Provisioning::OS}"
83
+ end
84
+ private :ifconf_packspec
85
+
86
+ # Size of a struct ifreq.
87
+ def ifreq_size
88
+ raise "Unsupported os #{Ethernet::Provisioning::OS}"
89
+ end
90
+ private :ifreq_size
91
+
92
+ # The link layer address number for raw sockets.
93
+ def ll_address_family
94
+ raise "Unsupported os #{Ethernet::Provisioning::OS}"
95
+ end
96
+ private :ll_address_family
97
+
98
+ # Socket that is solely used for issuing ioctls.
99
+ def ioctl_socket
100
+ Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0)
37
101
  end
102
+ private :ioctl_socket
38
103
  end
39
104
  end # class Ethernet::Devices
40
105
 
@@ -0,0 +1,31 @@
1
+ # :nodoc: namespace
2
+ module Ethernet
3
+
4
+ # :nodoc: darwin-specific implementation
5
+ module Devices
6
+ # :nodoc: darwin implementation
7
+ def self.ll_address_family
8
+ 18 # cat /usr/include/sys/socket.h | grep AF_PACKET
9
+ end
10
+
11
+ # :nodoc: darwin implementation
12
+ def self.siocgifconf_ioctl
13
+ # SIOCGIFCONF in /usr/include/sys/sockio.h
14
+ # _IOW in /usr/include/sys/ioccom.h
15
+ 0xc00c6924
16
+ end
17
+
18
+ # :nodoc: darwin implementation
19
+ def self.ifconf_packspec
20
+ # struct ifconf in /usr/include/net/if.h
21
+ Ethernet::Provisioning::POINTER_SIZE == 8 ? 'LQ' : 'LL'
22
+ end
23
+
24
+ # :nodoc: darwin implementation
25
+ def self.ifreq_size
26
+ # struct ifreq in /usr/include/net/if.h
27
+ 32
28
+ end
29
+ end # class Ethernet::Devices
30
+
31
+ end # namespace Ethernet
@@ -0,0 +1,48 @@
1
+ # :nodoc: namespace
2
+ module Ethernet
3
+
4
+ # :nodoc: linux-specific implementation
5
+ module Devices
6
+ # :nodoc: linux implementation
7
+ def self.mac(eth_device)
8
+ # structure ifreq in /usr/include/net/if.h
9
+ ifreq = [eth_device].pack 'a32'
10
+ # 0x8927 is SIOCGIFHWADDR in /usr/include/bits/ioctls.h
11
+ ioctl_socket.ioctl 0x8927, ifreq
12
+ ifreq[18, 6]
13
+ end
14
+
15
+ # :nodoc: linux implementation
16
+ def self.interface_index(eth_device)
17
+ # /usr/include/net/if.h, structure ifreq
18
+ ifreq = [eth_device].pack 'a32'
19
+ # 0x8933 is SIOCGIFINDEX in /usr/include/bits/ioctls.h
20
+ ioctl_socket.ioctl 0x8933, ifreq
21
+ ifreq[16, 4].unpack('I').first
22
+ end
23
+
24
+ # :nodoc: linux-specific implementation
25
+ def self.ll_address_family
26
+ 17 # cat /usr/include/bits/socket.h | grep PF_PACKET
27
+ end
28
+
29
+ # :nodoc: linux-specific implementation
30
+ def self.siocgifconf_ioctl
31
+ # SIOCGIFCONF in /usr/include/bits/ioctls.h
32
+ 0x8912
33
+ end
34
+
35
+ # :nodoc: linux-specific implementation
36
+ def self.ifconf_packspec
37
+ # struct ifconf in /usr/include/net/if.h
38
+ Ethernet::Provisioning::POINTER_SIZE == 8 ? 'QQ' : 'LL'
39
+ end
40
+
41
+ # :nodoc: linux-specific implementation
42
+ def self.ifreq_size
43
+ # struct ifreq in /usr/include/net/if.h
44
+ Ethernet::Provisioning::POINTER_SIZE == 8 ? 40 : 32
45
+ end
46
+ end # class Ethernet::Devices
47
+
48
+ end # namespace Ethernet
@@ -85,7 +85,7 @@ class FrameSocket
85
85
  #
86
86
  # This will discard incoming frames that don't match the MAC address that the
87
87
  # socket is connected to, or the Ethernet packet type.
88
- def recv(buffer_size = 8192)
88
+ def recv(buffer_size = 4096)
89
89
  raise "Not connected" unless @dest_mac
90
90
  loop do
91
91
  data, mac_address = recv_from buffer_size
@@ -103,11 +103,12 @@ class FrameSocket
103
103
  #
104
104
  # This will discard incoming frames that don't match the MAC address that the
105
105
  # socket is connected to, or the Ethernet packet type.
106
- def recv_from(buffer_size = 8192)
106
+ def recv_from(buffer_size = 4096)
107
107
  loop do
108
108
  packet = @socket.recv buffer_size
109
109
  next unless packet[12, 2] == @ether_type
110
- next unless packet[0, 6] == @source_mac
110
+ # The last part of the condition accepts multicast packets.
111
+ next if packet[0, 6] != @source_mac && packet.unpack('C').first & 1 == 0
111
112
  return packet[14..-1], packet[6, 6]
112
113
  end
113
114
  end
@@ -6,6 +6,12 @@ module Ethernet
6
6
 
7
7
  # Setup issues such as assigning permissions for Ethernet-level transmission.
8
8
  module Provisioning
9
+ # The kernel that the VM is running on (e.g. "darwin", "linux")
10
+ OS = Config::CONFIG['target_os']
11
+
12
+ # Number of bytes taken by a pointer on the Machine.
13
+ POINTER_SIZE = 1.size
14
+
9
15
  # Allow non-root users to create low-level Ethernet sockets.
10
16
  #
11
17
  # This is a security risk, because Ethernet sockets can be used to spy on all
@@ -15,13 +21,13 @@ module Provisioning
15
21
  # Returns true for success, false otherwise. If the call fails, it is most
16
22
  # likely because it is not run by root / Administrator.
17
23
  def self.usermode_sockets
18
- case RUBY_PLATFORM
24
+ case OS
19
25
  when /darwin/
20
- return false unless Kernel.system("chmod o+r /dev/bpf*")
26
+ return false unless Kernel.system("sudo chmod o+rw /dev/bpf*")
21
27
  when /linux/
22
28
  ruby = File.join Config::CONFIG['bindir'],
23
29
  Config::CONFIG['ruby_install_name']
24
- unless Kernel.system("setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' #{ruby}")
30
+ unless Kernel.system("sudo setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' #{ruby}")
25
31
  return false
26
32
  end
27
33
 
@@ -29,13 +35,13 @@ module Provisioning
29
35
  # No big deal if this fails.
30
36
  dumpcap = '/usr/bin/dumpcap'
31
37
  if File.exist? dumpcap
32
- Kernel.system("setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' #{dumpcap}")
38
+ Kernel.system("sudo setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' #{dumpcap}")
33
39
  end
34
40
  when /win/
35
41
  # NOTE: this might not work
36
42
  return false unless Kernel.system("sc config npf start= auto")
37
43
  else
38
- raise "Unsupported platform #{RUBY_PLATFORM}"
44
+ raise "Unsupported os #{Ethernet::Provisioning.platform}"
39
45
  end
40
46
  true
41
47
  end
@@ -1,10 +1,5 @@
1
1
  require 'socket'
2
2
 
3
- case RUBY_PLATFORM
4
- when /linux/
5
- require 'system/getifaddrs' # for listing
6
- end
7
-
8
3
  # :nodoc: namespace
9
4
  module Ethernet
10
5
 
@@ -15,77 +10,24 @@ module RawSocketFactory
15
10
  # Args:
16
11
  # eth_device:: device name for the Ethernet card, e.g. 'eth0'
17
12
  # ether_type:: only receive Ethernet frames with this protocol number
18
- def self.socket(eth_device = nil, ether_type = nil)
19
- ether_type ||= all_ethernet_protocols
20
- socket = Socket.new raw_address_family, Socket::SOCK_RAW, htons(ether_type)
21
- socket.setsockopt Socket::SOL_SOCKET, Socket::SO_BROADCAST, true
22
- set_socket_eth_device(socket, eth_device, ether_type) if eth_device
23
- socket
13
+ def self.socket(eth_device, ether_type = nil)
14
+ # This method is redefined in platform-specific implementations.
15
+ raise "Unsupported os #{Ethernet::Provisioning::OS}"
24
16
  end
25
-
17
+
26
18
  class <<self
27
- # Sets the Ethernet interface and protocol type for a socket.
28
- def set_socket_eth_device(socket, eth_device, ether_type)
29
- case RUBY_PLATFORM
30
- when /linux/
31
- if_number = get_interface_number eth_device
32
- # struct sockaddr_ll in /usr/include/linux/if_packet.h
33
- socket_address = [raw_address_family, htons(ether_type), if_number,
34
- 0xFFFF, 0, 0, ""].pack 'SSISCCa8'
35
- socket.bind socket_address
36
- else
37
- raise "Unsupported platform #{RUBY_PLATFORM}"
38
- end
39
- socket
40
- end
41
- private :set_socket_eth_device
42
-
43
- # The interface number for an Ethernet interface.
44
- def get_interface_number(eth_device)
45
- case RUBY_PLATFORM
46
- when /linux/
47
- # /usr/include/net/if.h, structure ifreq
48
- ifreq = [eth_device].pack 'a32'
49
- # 0x8933 is SIOCGIFINDEX in /usr/include/bits/ioctls.h
50
- socket.ioctl 0x8933, ifreq
51
- ifreq[16, 4].unpack('I').first
52
- else
53
- raise "Unsupported platform #{RUBY_PLATFORM}"
54
- end
55
- end
56
- private :get_interface_number
57
-
58
- # The protocol number for listening to all ethernet protocols.
59
- def all_ethernet_protocols
60
- case RUBY_PLATFORM
61
- when /linux/
62
- 3 # cat /usr/include/linux/if_ether.h | grep ETH_P_ALL
63
- else
64
- raise "Unsupported platform #{RUBY_PLATFORM}"
65
- end
66
- end
67
- private :all_ethernet_protocols
68
-
69
- # The AF / PF number for raw sockets.
70
- def raw_address_family
71
- case RUBY_PLATFORM
72
- when /linux/
73
- 17 # cat /usr/include/bits/socket.h | grep PF_PACKET
74
- when /darwin/
75
- 18 # cat /usr/include/sys/socket.h | grep AF_LINK
76
- else
77
- raise "Unsupported platform #{RUBY_PLATFORM}"
78
- end
79
- end
80
- private :raw_address_family
81
-
82
19
  # Converts a 16-bit integer from host-order to network-order.
83
20
  def htons(short_integer)
84
21
  [short_integer].pack('n').unpack('S').first
85
22
  end
86
23
  private :htons
24
+
25
+ # Converts a 32-bit integer from host-order to network-order.
26
+ def htonl(long_integer)
27
+ [long_integer].pack('N').unpack('L').first
28
+ end
29
+ private :htonl
87
30
  end
88
31
  end # module Ethernet::RawSocketFactory
89
32
 
90
33
  end # namespace Ethernet
91
-
@@ -0,0 +1,177 @@
1
+ require 'socket'
2
+
3
+ # :nodoc: namespace
4
+ module Ethernet
5
+
6
+ # Low-level socket creation functionality.
7
+ module RawSocketFactory
8
+ # A raw socket sends and receives raw Ethernet frames.
9
+ #
10
+ # Args:
11
+ # eth_device:: device name for the Ethernet card, e.g. 'eth0'
12
+ # ether_type:: only receive Ethernet frames with this protocol number
13
+ def self.socket(eth_device, ether_type = nil)
14
+ bpf = bpf_pseudo_socket
15
+ set_bpf_eth_device bpf, eth_device, ether_type
16
+ Ethernet::RawSocketFactory::BpfSocketWrapper.new bpf
17
+ end
18
+
19
+ class <<self
20
+ # Returns a BPF file descriptor that acts almost like a link-layer socket.
21
+ #
22
+ # BPF means Berkeley Packet Filter, and works on FreeBSD-like kernels,
23
+ # including Darwin.
24
+ def bpf_pseudo_socket
25
+ 3.times do
26
+ Dir['/dev/bpf*'].sort.each do |name|
27
+ begin
28
+ s = File.open name, 'r+b'
29
+ s.sync = true
30
+ return s
31
+ rescue Errno::EBUSY
32
+ # Move to the next BPF device.
33
+ end
34
+ end
35
+ end
36
+ return nil
37
+ end
38
+ private :bpf_pseudo_socket
39
+
40
+ # Binds a BPF file descriptor to a device and limits packet capture.
41
+ #
42
+ # BPF means Berkeley Packet Filter, and works on FreeBSD-like kernels,
43
+ # including Darwin.
44
+ #
45
+ # This method also sets flags so that the socket behaves as much as possible
46
+ # like a Linux PF_PACKET raw socket.
47
+ def set_bpf_eth_device(bpf, eth_device, ether_type)
48
+ # BIOCSETIF in /usr/include/net/bpf.h
49
+ # _IOW in /usr/include/sys/ioccom.h
50
+ # struct ifreq in /usr/include/net/if.h
51
+ bpf.ioctl 0x8020426C, [eth_device].pack('a32')
52
+
53
+ # Receive packets as soon as they're available.
54
+ # BIOCIMMEDIATE in /usr/include/net/bpf.h
55
+ # _IOW in /usr/include/sys/ioccom.h
56
+ bpf.ioctl 0x80044270, [1].pack('L')
57
+
58
+ # Don't automatically set the Ethernet header.
59
+ # BIOCSHDRCMPLT in /usr/include/net/bpf.h
60
+ # _IOW in /usr/include/sys/ioccom.h
61
+ bpf.ioctl 0x80044275, [1].pack('L')
62
+
63
+ # Don't receive the packets that we sent ourselves.
64
+ # BIOCSSEESENT in /usr/include/net/bpf.h
65
+ # _IOW in /usr/include/sys/ioccom.h
66
+ bpf.ioctl 0x80044275, [0].pack('L')
67
+
68
+ # BPF filter programming constants in /usr/include/net/bpf.h
69
+ if ether_type
70
+ filter = [
71
+ # A <- packet Ethernet type
72
+ [0x28, 0, 0, 12], # BPF_LD + BPF_H + BPF_ABS
73
+ # if A == ether_type jump above next instruction
74
+ [0x15, 1, 0, ether_type], # BPF_JMP + BPF_JEQ + BPF_K
75
+ # drop packet (ret K = 0)
76
+ [0x06, 0, 0, 0] # BPF_RET + BPF_K
77
+ ]
78
+ else
79
+ filter = []
80
+ end
81
+
82
+ ether_mac = Ethernet::Devices.mac eth_device
83
+ filter += [
84
+ # A <- first byte of destination MAC address
85
+ [0x30, 0, 0, 0], # BPF_LD + BPF_B + BPF_ABS
86
+ # if A & 1 (multicast MAC address) jump above exact MAC match
87
+ [0x45, 5, 0, 1], # BPF_JMP + BPF_JSET + BPF_K
88
+
89
+ # A <- first 4 bytes of destination MAC addres
90
+ [0x20, 0, 0, 0], # BPF_LD + BPF_W + BPF_ABS
91
+ # if A != first 4 bytes of local MAC address jump to drop instruction
92
+ [0x15, 0, 2, ether_mac.unpack('N').first], # BPF_JMP + BPF_JEQ + BPF_K
93
+ # A <- last 2 bytes of destination MAC address
94
+ [0x28, 0, 0, 4], # BPF_LD + BPF_H + BPF_ABS
95
+ # if A == last 2 bytes of local MAC address jump above next instruction
96
+ [0x15, 1, 0,
97
+ ether_mac.unpack('@4n').first], # BPF_JMP + BPF_JEQ + BPF_K
98
+ # drop packet (ret K = 0)
99
+ [0x06, 0, 0, 0], # BPF_RET + BPF_K
100
+
101
+ # A <- packet length
102
+ [0x80, 0, 0, 0], # BPF_LD + BPF_W + BPF_LEN
103
+ # ret A (accept the entire packet)
104
+ [0x16, 0, 0, 0] # BPF_RET + BPF_A
105
+ ]
106
+ filter_code = filter.map { |i| i.pack('SCCL') }.join('')
107
+ # struct bpf_program in /usr/include/net/bpf.h
108
+ filter_code_ptr = FFI::MemoryPointer.new :char, filter_code.length + 1
109
+ filter_code_ptr.write_string filter_code
110
+ if Ethernet::Provisioning::POINTER_SIZE == 8
111
+ pack_spec = 'QQ'
112
+ else
113
+ pack_spec = 'LL'
114
+ end
115
+ bpf_program = [filter.length, filter_code_ptr.address].pack pack_spec
116
+ # BIOCSETF in /usr/include/net/bpf.h
117
+ # _IOW in /usr/include/sys/iocom.h
118
+ bpf.ioctl 0x80104267, bpf_program
119
+ end
120
+ private :set_bpf_eth_device
121
+ end
122
+ end # module Ethernet::RawSocketFactory
123
+
124
+ # :nodoc: namespace
125
+ module RawSocketFactory
126
+
127
+ # Wraps a BPF file descriptor into a socket-like interface.
128
+ class BpfSocketWrapper
129
+ # Creates a wrapper for a BPF file descriptor.
130
+ def initialize(bpf)
131
+ @bpf = bpf
132
+ @read_size = read_buffer_length
133
+ @queue = []
134
+ end
135
+
136
+ # Implements Socket#recv.
137
+ def recv(buffer_size)
138
+ while @queue.empty?
139
+ read_buffer = @bpf.sysread @read_size
140
+ bytes_read = read_buffer.length
141
+ offset = 0
142
+ while offset < bytes_read
143
+ # struct bpf_hdr in /usr/include/net/bpf.h
144
+ timestamp, captured_size, original_size, header_size =
145
+ *read_buffer.unpack('QLLS')
146
+ @queue.push read_buffer[header_size, captured_size]
147
+ # BPF_WORDALIGN in /usr/include/net/bpf.h
148
+ offset += (header_size + captured_size + 3) & 0xFFF4
149
+ end
150
+ end
151
+ @queue.shift
152
+ end
153
+
154
+ # Implements Socket#send.
155
+ def send(buffer, flags)
156
+ @bpf.write buffer
157
+ end
158
+
159
+ # Implements Socket#close.
160
+ def close
161
+ @bpf.close
162
+ end
163
+
164
+ # The required length of the buffer passed to the read command.
165
+ def read_buffer_length
166
+ io_buffer = [0].pack('L')
167
+ # BIOCGBLEN in /usr/include/net/bpf.h
168
+ # _IOR in /usr/include/sys/ioccom.h
169
+ @bpf.ioctl 0x40044266, io_buffer
170
+ io_buffer.unpack('L').first
171
+ end
172
+ private :read_buffer_length
173
+ end # class Ethernet::RawSocketFactory::BpfSocketWrapper
174
+
175
+ end # namespace Ethernet::RawSocketFactory
176
+
177
+ end # namespace Ethernet
@@ -0,0 +1,44 @@
1
+ require 'socket'
2
+
3
+ # :nodoc: namespace
4
+ module Ethernet
5
+
6
+ # Low-level socket creation functionality.
7
+ module RawSocketFactory
8
+ # :nodoc: Linux-specific implementation
9
+ def self.socket(eth_device, ether_type = nil)
10
+ ether_type ||= all_ethernet_protocols
11
+ socket = Socket.new raw_address_family, Socket::SOCK_RAW,
12
+ htons(ether_type)
13
+ socket.setsockopt Socket::SOL_SOCKET, Socket::SO_BROADCAST, true
14
+ set_socket_eth_device socket, eth_device, ether_type
15
+ socket
16
+ end
17
+
18
+ class <<self
19
+ # Sets the Ethernet interface and protocol type for a socket.
20
+ def set_socket_eth_device(socket, eth_device, ether_type)
21
+ if_number = Ethernet::Devices.interface_index eth_device
22
+ # struct sockaddr_ll in /usr/include/linux/if_packet.h
23
+ socket_address = [raw_address_family, htons(ether_type), if_number,
24
+ 0xFFFF, 0, 0, ''].pack 'SSISCCa8'
25
+ socket.bind socket_address
26
+ socket
27
+ end
28
+ private :set_socket_eth_device
29
+
30
+ # The protocol number for listening to all ethernet protocols.
31
+ def all_ethernet_protocols
32
+ 3 # cat /usr/include/linux/if_ether.h | grep ETH_P_ALL
33
+ end
34
+ private :all_ethernet_protocols
35
+
36
+ # The AF / PF number for raw sockets.
37
+ def raw_address_family
38
+ 17 # cat /usr/include/bits/socket.h | grep PF_PACKET
39
+ end
40
+ end
41
+ end # module Ethernet::RawSocketFactory
42
+
43
+ end # namespace Ethernet
44
+
@@ -5,6 +5,7 @@ describe Ethernet::FrameSocket do
5
5
  let(:eth_type) { 0x0800 }
6
6
  let(:mac) { Ethernet::Devices.mac eth_device }
7
7
  let(:dest_mac) { "\x00\x11\x22\x33\x44\x55" }
8
+ let(:wrong_mac) { "\x02\x11\x22\x33\x44\x55" }
8
9
  let(:bcast_mac) { "\xff" * 6 }
9
10
 
10
11
  shared_examples_for 'a real socket' do
@@ -38,7 +39,7 @@ describe Ethernet::FrameSocket do
38
39
  let(:socket_stub) do
39
40
  RawSocketStub.new([
40
41
  [mac, dest_mac, "\x88\xB7", 'Wrong Ethernet type'].join,
41
- [bcast_mac, dest_mac, [eth_type].pack('n'), 'Wrong dest MAC'].join,
42
+ [wrong_mac, dest_mac, [eth_type].pack('n'), 'Wrong dest MAC'].join,
42
43
  [mac, bcast_mac, [eth_type].pack('n'), 'Bcast'].join,
43
44
  [mac, dest_mac, [eth_type].pack('n'), 'Correct'].join,
44
45
  ])
@@ -20,7 +20,7 @@ describe Ethernet::RawSocketFactory do
20
20
  end
21
21
 
22
22
  it 'should receive some network noise' do
23
- @socket.recv(8192).should_not be_empty
23
+ @socket.recv(4096).should_not be_empty
24
24
  end
25
25
  end
26
26
  end
@@ -16,7 +16,7 @@ describe Ethernet do
16
16
  Ethernet.devices.should == golden
17
17
  end
18
18
 
19
- let (:devices) { Ethernet.devices }
19
+ let(:devices) { Ethernet.devices }
20
20
 
21
21
  it 'contains at least one device' do
22
22
  devices.keys.should have_at_least(1).name
@@ -1,14 +1,25 @@
1
+ require 'English'
2
+
1
3
  # Wraps ifconfig calls useful for testing.
2
4
  module IfconfigCli
3
5
  # Runs ifconfig and parses its output.
4
6
  def self.run
5
- case RUBY_PLATFORM
6
- when /win/
7
- else
7
+ output = nil
8
+ windows = false
9
+ begin
8
10
  output = `ifconfig -a`
11
+ rescue Errno::ENOENT
12
+ output = `ipconfig /a`
13
+ windows = true
14
+ end
15
+
16
+ if windows
17
+ raise 'Windows support not yet implemented'
18
+ else
9
19
  info_blocks = output.split /\n(?=\w)/
10
- Hash[info_blocks.map { |i|
20
+ devices = Hash[info_blocks.map { |i|
11
21
  name = i.split(' ', 2).first
22
+ name = name[0...-1] if name[-1, 1] == ':' # OSX
12
23
  mac = if match = /hwaddr\s([0-9a-f:]+)\s/i.match(i)
13
24
  # Linux ifconfig output.
14
25
  match[1].gsub(':', '').downcase
@@ -21,10 +32,11 @@ module IfconfigCli
21
32
  else
22
33
  nil
23
34
  end
24
- active = /inet\s/.match(i) ? true : false
35
+ active = (/inet\s/i.match(i) || /inet6\s/i.match(i)) ? true : false
25
36
  [name, { :mac => mac, :active => active }]
26
37
  }]
27
38
  end
39
+ devices.delete_if { |k, v| v[:mac].nil? }
28
40
  end
29
41
 
30
42
  # The name of the first active Ethernet device.
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ethernet
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 1
10
- version: 0.1.1
9
+ - 2
10
+ version: 0.1.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Victor Costan
@@ -16,24 +16,24 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2011-05-27 00:00:00 Z
19
+ date: 2011-05-30 00:00:00 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  type: :runtime
23
23
  requirement: &id001 !ruby/object:Gem::Requirement
24
24
  none: false
25
25
  requirements:
26
- - - ~>
26
+ - - ">="
27
27
  - !ruby/object:Gem::Version
28
- hash: 25
28
+ hash: 23
29
29
  segments:
30
- - 0
31
30
  - 1
32
- - 1
33
- version: 0.1.1
31
+ - 0
32
+ - 0
33
+ version: 1.0.0
34
34
  prerelease: false
35
35
  version_requirements: *id001
36
- name: system-getifaddrs
36
+ name: ffi
37
37
  - !ruby/object:Gem::Dependency
38
38
  type: :development
39
39
  requirement: &id002 !ruby/object:Gem::Requirement
@@ -134,9 +134,13 @@ files:
134
134
  - ethernet.gemspec
135
135
  - lib/ethernet.rb
136
136
  - lib/ethernet/devices.rb
137
+ - lib/ethernet/devices_darwin.rb
138
+ - lib/ethernet/devices_linux.rb
137
139
  - lib/ethernet/frame_socket.rb
138
140
  - lib/ethernet/provisioning.rb
139
141
  - lib/ethernet/raw_socket_factory.rb
142
+ - lib/ethernet/raw_socket_factory_darwin.rb
143
+ - lib/ethernet/raw_socket_factory_linux.rb
140
144
  - spec/ethernet/devices_spec.rb
141
145
  - spec/ethernet/frame_socket_spec.rb
142
146
  - spec/ethernet/raw_socket_factory_spec.rb