ethernet 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.project ADDED
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <projectDescription>
3
+ <name>ethernet</name>
4
+ <comment></comment>
5
+ <projects>
6
+ </projects>
7
+ <buildSpec>
8
+ </buildSpec>
9
+ <natures>
10
+ </natures>
11
+ </projectDescription>
data/Gemfile CHANGED
@@ -1,13 +1,17 @@
1
- source "http://rubygems.org"
1
+ source 'http://rubygems.org'
2
2
  # Add dependencies required to use your gem here.
3
3
  # Example:
4
- # gem "activesupport", ">= 2.3.5"
4
+ # gem 'activesupport', '>= 2.3.5'
5
+
6
+ gem 'system-getifaddrs', '~> 0.1.1', :platform => :ruby,
7
+ :require => 'system/getifaddrs'
5
8
 
6
9
  # Add dependencies to develop your gem here.
7
10
  # Include everything needed to run rake, tests, features, etc.
8
11
  group :development do
9
- gem "rspec", "~> 2.3.0"
10
- gem "bundler", "~> 1.0.0"
11
- gem "jeweler", "~> 1.5.2"
12
- gem "rcov", ">= 0"
12
+ gem 'rdoc', '~> 3.6.1'
13
+ gem 'rspec', '~> 2.6.0'
14
+ gem 'bundler', '~> 1.0.0'
15
+ gem 'jeweler', '~> 1.6.0'
16
+ gem 'rcov', '>= 0'
13
17
  end
data/Gemfile.lock CHANGED
@@ -3,26 +3,30 @@ GEM
3
3
  specs:
4
4
  diff-lcs (1.1.2)
5
5
  git (1.2.5)
6
- jeweler (1.5.2)
6
+ jeweler (1.6.0)
7
7
  bundler (~> 1.0.0)
8
8
  git (>= 1.2.5)
9
9
  rake
10
- rake (0.8.7)
10
+ rake (0.9.0)
11
11
  rcov (0.9.9)
12
- rspec (2.3.0)
13
- rspec-core (~> 2.3.0)
14
- rspec-expectations (~> 2.3.0)
15
- rspec-mocks (~> 2.3.0)
16
- rspec-core (2.3.1)
17
- rspec-expectations (2.3.0)
12
+ rdoc (3.6.1)
13
+ rspec (2.6.0)
14
+ rspec-core (~> 2.6.0)
15
+ rspec-expectations (~> 2.6.0)
16
+ rspec-mocks (~> 2.6.0)
17
+ rspec-core (2.6.3)
18
+ rspec-expectations (2.6.0)
18
19
  diff-lcs (~> 1.1.2)
19
- rspec-mocks (2.3.0)
20
+ rspec-mocks (2.6.0)
21
+ system-getifaddrs (0.1.1)
20
22
 
21
23
  PLATFORMS
22
24
  ruby
23
25
 
24
26
  DEPENDENCIES
25
27
  bundler (~> 1.0.0)
26
- jeweler (~> 1.5.2)
28
+ jeweler (~> 1.6.0)
27
29
  rcov
28
- rspec (~> 2.3.0)
30
+ rdoc (~> 3.6.1)
31
+ rspec (~> 2.6.0)
32
+ system-getifaddrs (~> 0.1.1)
data/README.rdoc CHANGED
@@ -16,10 +16,10 @@ This project is a low-level ethernet communication rubygems library. Its initial
16
16
 
17
17
  == Author
18
18
 
19
+ Victor Costan victor@costan.us
19
20
  HaoQi Li haoqili@mit.edu
20
21
 
21
22
  == Copyright
22
23
 
23
24
  Copyright (c) 2011 Massachusetts Institute of Technology.
24
25
  See LICENSE.txt for further details.
25
-
data/Rakefile CHANGED
@@ -13,16 +13,13 @@ require 'jeweler'
13
13
  Jeweler::Tasks.new do |gem|
14
14
  # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
15
  gem.name = "ethernet"
16
- gem.homepage = "http://github.com/haoqili/ethernet"
16
+ gem.homepage = "http://github.com/costan/ethernet"
17
17
  gem.license = "MIT"
18
- gem.summary = %Q{A ruby gem library for ethernet communication.}
19
- gem.description = %Q{This project is a low-level ethernet communication rubygems library.}
20
- gem.email = "haoqili@mit.edu"
21
- gem.authors = ["HaoQi Li"]
22
- # Include your dependencies below. Runtime dependencies are required when using your gem,
23
- # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
- # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
- # gem.add_development_dependency 'rspec', '> 1.2.3'
18
+ gem.summary = %Q{Ethernet (link-layer level) sockets.}
19
+ gem.description = %Q{Provides a Socket-like API that bypasses TCP/IP. Useful for exotic devices and FPGA development.}
20
+ gem.email = "victor@costan.us"
21
+ gem.authors = ["Victor Costan", "HaoQi Li"]
22
+ # dependencies defined in Gemfile
26
23
  end
27
24
  Jeweler::RubygemsDotOrgTasks.new
28
25
 
@@ -39,7 +36,7 @@ end
39
36
 
40
37
  task :default => :spec
41
38
 
42
- require 'rake/rdoctask'
39
+ require 'rdoc/task'
43
40
  Rake::RDocTask.new do |rdoc|
44
41
  version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
42
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.1.0
data/bin/ethernet_ping ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'ethernet' #'scratchpad'
4
+
5
+ if ARGV.length < 4
6
+ print <<END_USAGE
7
+ Usage: #{$0} net_interface ether_type dest_mac data
8
+ net_interface: name of the Ethernet interface, e.g. eth0
9
+ ether_type: packet type for the Ethernet II frame, in hex (2 bytes)
10
+ dest_mac: destination MAC for the ping packets, in hex (6 bytes)
11
+ data: ping packet data, in hex (0-padded to 64 bytes)
12
+ END_USAGE
13
+ exit 1
14
+ end
15
+
16
+ interface = ARGV[0]
17
+ ether_type = [ARGV[1]].pack('H*').unpack('n').first
18
+ dest_mac = ARGV[2]
19
+ data = [ARGV[3]].pack('H*')
20
+
21
+ #client = Scratchpad::Ethernet::PingClient.new interface, ether_type, dest_mac
22
+ client = Ethernet::PingClient.new interface, ether_type, dest_mac
23
+
24
+ loop do
25
+ print "Pinging #{dest_mac}... "
26
+ STDOUT.flush
27
+ puts client.ping(data) ? "OK" : "Failed"
28
+ STDOUT.flush
29
+ sleep 1
30
+ end
data/ethernet.gemspec CHANGED
@@ -5,19 +5,21 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ethernet}
8
- s.version = "0.0.0"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["HaoQi Li"]
12
- s.date = %q{2011-03-11}
13
- s.description = %q{This project is a low-level ethernet communication rubygems library.}
14
- s.email = %q{haoqili@mit.edu}
11
+ s.authors = [%q{Victor Costan}, %q{HaoQi Li}]
12
+ s.date = %q{2011-05-27}
13
+ s.description = %q{Provides a Socket-like API that bypasses TCP/IP. Useful for exotic devices and FPGA development.}
14
+ s.email = %q{victor@costan.us}
15
+ s.executables = [%q{ethernet_ping}]
15
16
  s.extra_rdoc_files = [
16
17
  "LICENSE.txt",
17
18
  "README.rdoc"
18
19
  ]
19
20
  s.files = [
20
21
  ".document",
22
+ ".project",
21
23
  ".rspec",
22
24
  "Gemfile",
23
25
  "Gemfile.lock",
@@ -25,39 +27,52 @@ Gem::Specification.new do |s|
25
27
  "README.rdoc",
26
28
  "Rakefile",
27
29
  "VERSION",
30
+ "bin/ethernet_ping",
28
31
  "ethernet.gemspec",
29
32
  "lib/ethernet.rb",
33
+ "lib/ethernet/devices.rb",
34
+ "lib/ethernet/frame_socket.rb",
35
+ "lib/ethernet/ping.rb",
36
+ "lib/ethernet/provisioning.rb",
37
+ "lib/ethernet/raw_socket_factory.rb",
38
+ "spec/ethernet/devices_spec.rb",
39
+ "spec/ethernet/frame_socket_spec.rb",
40
+ "spec/ethernet/raw_socket_factory_spec.rb",
30
41
  "spec/ethernet_spec.rb",
31
- "spec/spec_helper.rb"
32
- ]
33
- s.homepage = %q{http://github.com/haoqili/ethernet}
34
- s.licenses = ["MIT"]
35
- s.require_paths = ["lib"]
36
- s.rubygems_version = %q{1.6.2}
37
- s.summary = %q{A ruby gem library for ethernet communication.}
38
- s.test_files = [
39
- "spec/ethernet_spec.rb",
40
- "spec/spec_helper.rb"
42
+ "spec/spec_helper.rb",
43
+ "spec/support/ifconfig_cli.rb",
44
+ "spec/support/raw_socket_stub.rb"
41
45
  ]
46
+ s.homepage = %q{http://github.com/costan/ethernet}
47
+ s.licenses = [%q{MIT}]
48
+ s.require_paths = [%q{lib}]
49
+ s.rubygems_version = %q{1.8.4}
50
+ s.summary = %q{Ethernet (link-layer level) sockets.}
42
51
 
43
52
  if s.respond_to? :specification_version then
44
53
  s.specification_version = 3
45
54
 
46
55
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
47
- s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
56
+ s.add_runtime_dependency(%q<system-getifaddrs>, ["~> 0.1.1"])
57
+ s.add_development_dependency(%q<rdoc>, ["~> 3.6.1"])
58
+ s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
48
59
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
49
- s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
60
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.0"])
50
61
  s.add_development_dependency(%q<rcov>, [">= 0"])
51
62
  else
52
- s.add_dependency(%q<rspec>, ["~> 2.3.0"])
63
+ s.add_dependency(%q<system-getifaddrs>, ["~> 0.1.1"])
64
+ s.add_dependency(%q<rdoc>, ["~> 3.6.1"])
65
+ s.add_dependency(%q<rspec>, ["~> 2.6.0"])
53
66
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
54
- s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
67
+ s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
55
68
  s.add_dependency(%q<rcov>, [">= 0"])
56
69
  end
57
70
  else
58
- s.add_dependency(%q<rspec>, ["~> 2.3.0"])
71
+ s.add_dependency(%q<system-getifaddrs>, ["~> 0.1.1"])
72
+ s.add_dependency(%q<rdoc>, ["~> 3.6.1"])
73
+ s.add_dependency(%q<rspec>, ["~> 2.6.0"])
59
74
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
60
- s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
75
+ s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
61
76
  s.add_dependency(%q<rcov>, [">= 0"])
62
77
  end
63
78
  end
@@ -0,0 +1,41 @@
1
+ # OS-dependent gem includes.
2
+ case RUBY_PLATFORM
3
+ when /linux/
4
+ when /darwin/
5
+ require 'system/getifaddrs'
6
+ end
7
+
8
+ # :nodoc: namespace
9
+ module Ethernet
10
+
11
+ # Information about the available Ethernet devices.
12
+ module Devices
13
+ # An array of device names for the machine's Ethernet devices.
14
+ 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
21
+ end
22
+
23
+ # The MAC address for an Ethernet device.
24
+ #
25
+ # Args:
26
+ # eth_device:: device name for the Ethernet card, e.g. 'eth0'
27
+ 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}"
37
+ end
38
+ end
39
+ end # class Ethernet::Devices
40
+
41
+ end # namespace Ethernet
@@ -0,0 +1,122 @@
1
+ # :nodoc: namespace
2
+ module Ethernet
3
+
4
+ # Wraps an Ethernet socket and abstracts away the Ethernet II frame.
5
+ class FrameSocket
6
+ # Creates a wrapper around a raw Ethernet socket.
7
+ #
8
+ # Args:
9
+ # raw_socket_or_device:: a raw Ethernet socket or a string containing an
10
+ # Ethernet device name
11
+ # ether_type:: 2-byte Ethernet packet type number
12
+ # mac_address:: 6-byte MAC address for the Ethernet socket (optional if
13
+ # raw_socket_or_device is an Ethernet device name)
14
+ #
15
+ # Raises:
16
+ # RuntimeError:: if mac isn't exactly 6-bytes long
17
+ def initialize(raw_socket_or_device, ether_type, mac_address = nil)
18
+ check_mac mac_address if mac_address
19
+
20
+ if raw_socket_or_device.respond_to? :to_str
21
+ @source_mac = mac_address || Ethernet::Devices.mac(raw_socket_or_device)
22
+ @socket = RawSocketFactory.socket raw_socket_or_device, ether_type
23
+ else
24
+ raise 'MAC address needed with raw socket' unless mac_address
25
+ @source_mac = mac_address.dup
26
+ @socket = raw_socket_or_device
27
+ end
28
+
29
+ @dest_mac = nil
30
+ @ether_type = [ether_type].pack('n')
31
+ end
32
+
33
+ # Sets the destination MAC address for future calls to send.
34
+ #
35
+ # Args:
36
+ # mac:: 6-byte MAC address for the Ethernet socket
37
+ #
38
+ # Raises:
39
+ # RuntimeError:: if mac isn't exactly 6-bytes long
40
+ def connect(mac_address)
41
+ check_mac mac_address
42
+ @dest_mac = mac_address
43
+ end
44
+
45
+ # Closes the underlying socket.
46
+ def close
47
+ @socket.close
48
+ end
49
+
50
+ # Sends an Ethernet II frame.
51
+ #
52
+ # Args:
53
+ # data:: the data bytes to be sent
54
+ #
55
+ # Raises:
56
+ # RuntimeError:: if connect wasn' previously called
57
+ def send(data, send_flags = 0)
58
+ raise "Not connected" unless @dest_mac
59
+ send_to @dest_mac, data, send_flags
60
+ end
61
+
62
+ # Sends an Ethernet II frame.
63
+ #
64
+ # Args:
65
+ # mac_address:: the destination MAC address
66
+ # data:: the data bytes to be sent
67
+ #
68
+ # Raises:
69
+ # RuntimeError:: if connect wasn' previously called
70
+ def send_to(mac_address, data, send_flags = 0)
71
+ check_mac mac_address
72
+
73
+ padding = (data.length < 46) ? "\0" * (46 - data.length) : ''
74
+ packet = [mac_address, @source_mac, @ether_type, data, padding].join
75
+ @socket.send packet, send_flags
76
+ end
77
+
78
+ # Receives an Ethernet II frame.
79
+ #
80
+ # Args:
81
+ # buffer_size:: optional maximum packet size argument passed to the raw
82
+ # socket's recv method
83
+ #
84
+ # Returns the data and the source MAC address in the frame.
85
+ #
86
+ # This will discard incoming frames that don't match the MAC address that the
87
+ # socket is connected to, or the Ethernet packet type.
88
+ def recv(buffer_size = 8192)
89
+ raise "Not connected" unless @dest_mac
90
+ loop do
91
+ data, mac_address = recv_from buffer_size
92
+ return data if @dest_mac == mac_address
93
+ end
94
+ end
95
+
96
+ # Receives an Ethernet II frame.
97
+ #
98
+ # Args:
99
+ # buffer_size:: optional maximum packet size argument passed to the raw
100
+ # socket's recv method
101
+ #
102
+ # Returns the data in the frame.
103
+ #
104
+ # This will discard incoming frames that don't match the MAC address that the
105
+ # socket is connected to, or the Ethernet packet type.
106
+ def recv_from(buffer_size = 8192)
107
+ loop do
108
+ packet = @socket.recv buffer_size
109
+ next unless packet[12, 2] == @ether_type
110
+ next unless packet[0, 6] == @source_mac
111
+ return packet[14..-1], packet[6, 6]
112
+ end
113
+ end
114
+
115
+ # Raises an exception if the given MAC address is invalid.
116
+ def check_mac(mac_address)
117
+ raise "Invalid MAC address" unless mac_address.length == 6
118
+ end
119
+ private :check_mac
120
+ end # class Ethernet::FrameSocket
121
+
122
+ end # namespace Ethernet
@@ -0,0 +1,89 @@
1
+ # TODO(pwnall): move to separate gem
2
+
3
+
4
+ # copied from victor's scratchpad/lib/scratchpad/ethernet/ping.rb
5
+ # changes see raw_socket.rb
6
+ # :%s/if_name/eth_device/g
7
+ # .get_interface_mac() --> .mac()
8
+ # Ethernet.socket --> Ethernet::RawSocket.socket
9
+ # Ethernet.mac --> Ethernet::RawSocket.mac
10
+
11
+ # :nodoc: namespace # module Scratchpad
12
+
13
+ # :nodoc: namespace
14
+ module Ethernet
15
+
16
+ # Responder for ping utility using raw Ethernet sockets.
17
+ class PingServer
18
+ module Connection
19
+ def receive_data(packet)
20
+ source_mac = packet[0, 6].unpack('H*')
21
+ dest_mac = packet[6, 6].unpack('H*')
22
+ ether_type = packet[12, 2].unpack('H*')
23
+
24
+ puts "Src: #{source_mac} Dst: #{dest_mac} Eth: #{ether_type}\n"
25
+ puts packet[14..-1].unpack('H*')
26
+
27
+ # Exchange the source and destination ARP addresses.
28
+ packet[0, 6], packet[6, 6] = packet[6, 6], packet[0, 6]
29
+ send_data packet
30
+ end
31
+ end
32
+
33
+ class ConnectionWrapper
34
+ include Connection
35
+
36
+ def initialize(socket)
37
+ @socket = socket
38
+ end
39
+
40
+ def send_data(data)
41
+ @socket.send data, 0
42
+ end
43
+ end
44
+
45
+ def run
46
+ connection = ConnectionWrapper.new @socket
47
+ loop do
48
+ packet = @socket.recv 65536
49
+ connection.receive_data packet
50
+ end
51
+ end
52
+
53
+ def initialize(eth_device, ether_type)
54
+ @socket = Ethernet::RawSocket.socket eth_device, ether_type
55
+ end
56
+ end # module Ethernet::PingServer
57
+
58
+ # Ping utility
59
+ class PingClient
60
+ def initialize(eth_device, ether_type, destination_mac)
61
+ @socket = Ethernet::RawSocket.socket eth_device, ether_type
62
+ @source_mac = [Ethernet::RawSocket.mac(eth_device).unpack('H*').first].pack('H*')[0, 6]
63
+ @dest_mac = [destination_mac].pack('H*')[0, 6]
64
+ @ether_type = [ether_type].pack('n')
65
+ end
66
+
67
+ attr_reader :socket
68
+ attr_reader :source_mac
69
+ attr_reader :dest_mac
70
+
71
+ # Pings over raw Ethernet sockets.
72
+ #
73
+ # Returns true if the ping receives a response, false otherwise.
74
+ def ping(data, timeout = 1)
75
+ data = data.clone
76
+ # Pad data to have at least 64 bytes.
77
+ data += "\0" * (64 - data.length) if data.length < 64
78
+
79
+ ping_packet = @dest_mac + @source_mac + @ether_type + data
80
+ @socket.send ping_packet, 0
81
+
82
+ response_packet = @source_mac + @dest_mac + @ether_type + data
83
+ response = @socket.recv response_packet.length * 2
84
+
85
+ response == response_packet
86
+ end
87
+ end # module Ethernet::PingClient
88
+
89
+ end # namespace Ethernet
@@ -0,0 +1,44 @@
1
+ require 'rbconfig'
2
+
3
+
4
+ # :nodoc: namespace
5
+ module Ethernet
6
+
7
+ # Setup issues such as assigning permissions for Ethernet-level transmission.
8
+ module Provisioning
9
+ # Allow non-root users to create low-level Ethernet sockets.
10
+ #
11
+ # This is a security risk, because Ethernet sockets can be used to spy on all
12
+ # traffic on the machine's network. This should not be called on production
13
+ # machines.
14
+ #
15
+ # Returns true for success, false otherwise. If the call fails, it is most
16
+ # likely because it is not run by root / Administrator.
17
+ def self.usermode_sockets
18
+ case RUBY_PLATFORM
19
+ when /darwin/
20
+ return false unless Kernel.system("chmod o+r /dev/bpf*")
21
+ when /linux/
22
+ ruby = File.join Config::CONFIG['bindir'],
23
+ Config::CONFIG['ruby_install_name']
24
+ unless Kernel.system("setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' #{ruby}")
25
+ return false
26
+ end
27
+
28
+ # Try to enable Wireshark packet capture for debugging.
29
+ # No big deal if this fails.
30
+ dumpcap = '/usr/bin/dumpcap'
31
+ if File.exist? dumpcap
32
+ Kernel.system("setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' #{dumpcap}")
33
+ end
34
+ when /win/
35
+ # NOTE: this might not work
36
+ return false unless Kernel.system("sc config npf start= auto")
37
+ else
38
+ raise "Unsupported platform #{RUBY_PLATFORM}"
39
+ end
40
+ true
41
+ end
42
+ end # class Ethernet::Provisioning
43
+
44
+ end # namespace Ethernet
@@ -0,0 +1,91 @@
1
+ require 'socket'
2
+
3
+ case RUBY_PLATFORM
4
+ when /linux/
5
+ require 'system/getifaddrs' # for listing
6
+ end
7
+
8
+ # :nodoc: namespace
9
+ module Ethernet
10
+
11
+ # Low-level socket creation functionality.
12
+ module RawSocketFactory
13
+ # A raw socket sends and receives raw Ethernet frames.
14
+ #
15
+ # Args:
16
+ # eth_device:: device name for the Ethernet card, e.g. 'eth0'
17
+ # 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
24
+ end
25
+
26
+ 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
+ # Converts a 16-bit integer from host-order to network-order.
83
+ def htons(short_integer)
84
+ [short_integer].pack('n').unpack('S').first
85
+ end
86
+ private :htons
87
+ end
88
+ end # module Ethernet::RawSocketFactory
89
+
90
+ end # namespace Ethernet
91
+
data/lib/ethernet.rb CHANGED
@@ -0,0 +1,48 @@
1
+ # Facade methods for the library.
2
+ #
3
+ # See the inner classes for more advanced functionality.
4
+ module Ethernet
5
+ # Hash mapping Ethernet device names to their MAC addresses.
6
+ def self.devices
7
+ Hash[Ethernet::Devices.all.map { |dev| [dev, Ethernet::Devices.mac(dev)] }]
8
+ end
9
+
10
+ # Ethernet socket that abstracts away frames.
11
+ #
12
+ # Args:
13
+ # eth_device:: device name for the Ethernet card, e.g. 'eth0'
14
+ # ether_type:: 2-byte Ethernet packet type number
15
+ def self.socket(eth_device, ether_type)
16
+ Ethernet::FrameSocket.new eth_device, ether_type
17
+ end
18
+
19
+ # Allow non-root users to create low-level Ethernet sockets.
20
+ #
21
+ # This is a security risk, because Ethernet sockets can be used to spy on all
22
+ # traffic on the machine's network. This should not be called on production
23
+ # machines.
24
+ #
25
+ # Returns true for success, false otherwise. If the call fails, it is most
26
+ # likely because it is not run by root / Administrator.
27
+ def self.provision
28
+ Ethernet::Provisioning.usermode_sockets
29
+ end
30
+
31
+ # A socket that sends and receives raw Ethernet frames.
32
+ #
33
+ # Args:
34
+ # eth_device:: device name for the Ethernet card, e.g. 'eth0'
35
+ # ether_type:: only receive Ethernet frames with this protocol number
36
+ def self.raw_socket(eth_device = nil, ether_type = nil)
37
+ Ethernet::RawSocketFactory.socket eth_device, ether_type
38
+ end
39
+ end
40
+
41
+ require 'ethernet/devices.rb'
42
+ require 'ethernet/frame_socket.rb'
43
+ require 'ethernet/provisioning.rb'
44
+ require 'ethernet/raw_socket_factory.rb'
45
+
46
+
47
+ # TODO(pwnall): move to separate gem
48
+ require 'ethernet/ping.rb'
@@ -0,0 +1,39 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Ethernet::Devices do
4
+ let(:golden_devices) { IfconfigCli.run }
5
+ let(:golden_active_devices) do
6
+ golden_devices.keys.select { |device| golden_devices[device][:active] }
7
+ end
8
+
9
+ describe 'testing environment' do
10
+ it 'should have at least one active ethernet device' do
11
+ golden_active_devices.should have_at_least(1).device
12
+ end
13
+ end
14
+
15
+ describe 'all' do
16
+ let(:devices) { Ethernet::Devices.all }
17
+
18
+ it "should find active ethernet devices" do
19
+ golden_active_devices.each do |device|
20
+ devices.should include(device)
21
+ end
22
+ end
23
+ end
24
+
25
+ let(:eth_device) { IfconfigCli.live_device }
26
+ let(:mac) { Ethernet::Devices.mac eth_device }
27
+
28
+ describe 'mac' do
29
+ let(:golden_mac) { [golden_devices[eth_device][:mac]].pack('H*') }
30
+
31
+ it 'should have 6 bytes' do
32
+ mac.length.should == 6
33
+ end
34
+
35
+ it 'should match ifconfig output' do
36
+ mac.should == golden_mac
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,102 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Ethernet::FrameSocket do
4
+ let(:eth_device) { IfconfigCli.live_device }
5
+ let(:eth_type) { 0x0800 }
6
+ let(:mac) { Ethernet::Devices.mac eth_device }
7
+ let(:dest_mac) { "\x00\x11\x22\x33\x44\x55" }
8
+ let(:bcast_mac) { "\xff" * 6 }
9
+
10
+ shared_examples_for 'a real socket' do
11
+ it 'should output a packet' do
12
+ @socket.send_to dest_mac, "\r\n"
13
+ end
14
+
15
+ it 'should receive some network noise' do
16
+ @socket.recv_from.first.should_not be_empty
17
+ end
18
+ end
19
+
20
+ describe 'on eth0' do
21
+ before { @socket = Ethernet::FrameSocket.new eth_device, eth_type }
22
+ after { @socket.close }
23
+
24
+ it_should_behave_like 'a real socket'
25
+ end
26
+
27
+ describe 'from raw socket' do
28
+ before do
29
+ raw_socket = Ethernet::RawSocketFactory.socket eth_device, eth_type
30
+ @socket = Ethernet::FrameSocket.new raw_socket, eth_type, mac
31
+ end
32
+ after { @socket.close }
33
+
34
+ it_should_behave_like 'a real socket'
35
+ end
36
+
37
+ describe 'stubbed' do
38
+ let(:socket_stub) do
39
+ RawSocketStub.new([
40
+ [mac, dest_mac, "\x88\xB7", 'Wrong Ethernet type'].join,
41
+ [bcast_mac, dest_mac, [eth_type].pack('n'), 'Wrong dest MAC'].join,
42
+ [mac, bcast_mac, [eth_type].pack('n'), 'Bcast'].join,
43
+ [mac, dest_mac, [eth_type].pack('n'), 'Correct'].join,
44
+ ])
45
+ end
46
+ let(:socket) { Ethernet::FrameSocket.new socket_stub, eth_type, mac }
47
+
48
+ shared_examples_for 'after a small send call' do
49
+ it 'should send a single packet' do
50
+ socket_stub.sends.length.should == 1
51
+ end
52
+ it 'should pad the packet' do
53
+ socket_stub.sends.first.length.should == 60
54
+ end
55
+ it 'should assemble packet correctly in send' do
56
+ gold = [dest_mac, mac, [eth_type].pack('n'), 'Send data'].join
57
+ socket_stub.sends.first[0, gold.length].should == gold
58
+ end
59
+ end
60
+
61
+ describe 'send_to' do
62
+ before { socket.send_to dest_mac, 'Send data' }
63
+ it_should_behave_like 'after a small send call'
64
+ end
65
+
66
+ describe 'recv_from' do
67
+ it 'should filter down to the correct packet' do
68
+ socket.recv_from.should == ['Bcast', bcast_mac]
69
+ end
70
+ end
71
+
72
+ describe 'unconnected' do
73
+ it 'should complain in recv' do
74
+ lambda { socket.recv }.should raise_error(RuntimeError)
75
+ end
76
+
77
+ it 'should complain in send' do
78
+ lambda { socket.send 'Send data' }.should raise_error(RuntimeError)
79
+ end
80
+ end
81
+
82
+ describe 'connected' do
83
+ before { socket.connect dest_mac }
84
+
85
+ describe 'send' do
86
+ before { socket.send 'Send data' }
87
+ it_should_behave_like 'after a small send call'
88
+ end
89
+
90
+ describe 'recv' do
91
+ it 'should filter down to the correct packet' do
92
+ socket.recv.should == 'Correct'
93
+ end
94
+ end
95
+ end
96
+
97
+ it 'should delegate close' do
98
+ socket_stub.should_receive(:close).once
99
+ socket.close
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Ethernet::RawSocketFactory do
4
+ let(:eth_device) { IfconfigCli.live_device }
5
+ let(:mac) { Ethernet::Devices.mac eth_device }
6
+
7
+ describe 'socket' do
8
+ let(:eth_type) { 0x88B7 }
9
+
10
+ before { @socket = Ethernet::RawSocketFactory.socket eth_device }
11
+ after { @socket.close }
12
+
13
+ it 'should be able to receive data' do
14
+ @socket.should respond_to(:recv)
15
+ end
16
+
17
+ it 'should output a packet' do
18
+ packet = [mac, mac, [eth_type].pack('n'), "\r\n" * 32].join
19
+ @socket.send packet, 0
20
+ end
21
+
22
+ it 'should receive some network noise' do
23
+ @socket.recv(8192).should_not be_empty
24
+ end
25
+ end
26
+ end
@@ -1,7 +1,45 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
- describe "Ethernet" do
4
- it "fails" do
5
- fail "hey buddy, you should probably rename this file and start specing for real"
3
+ describe Ethernet do
4
+ describe 'provision' do
5
+ it 'delegates to Provisioning' do
6
+ Ethernet::Provisioning.should_receive(:usermode_sockets).and_return(:yay)
7
+ Ethernet.provision.should == :yay
8
+ end
9
+ end
10
+
11
+ describe 'devices' do
12
+ it 'delegates to Devices' do
13
+ Ethernet::Devices.should_receive(:all).and_return(['yay'])
14
+ Ethernet::Devices.should_receive(:mac).with('yay').and_return('42')
15
+ golden = { 'yay' => '42' }
16
+ Ethernet.devices.should == golden
17
+ end
18
+
19
+ let (:devices) { Ethernet.devices }
20
+
21
+ it 'contains at least one device' do
22
+ devices.keys.should have_at_least(1).name
23
+ end
24
+
25
+ it 'has a MAC for the first device' do
26
+ devices[devices.keys.first].length.should == 6
27
+ end
28
+ end
29
+
30
+ describe 'socket' do
31
+ it 'delegates to FrameSocket' do
32
+ Ethernet::FrameSocket.should_receive(:new).with('yay', 1234).
33
+ and_return(:yay)
34
+ Ethernet.socket('yay', 1234).should == :yay
35
+ end
36
+ end
37
+
38
+ describe 'raw_socket' do
39
+ it 'delegates to RawSocketFactory' do
40
+ Ethernet::RawSocketFactory.should_receive(:socket).with('yay', 1234).
41
+ and_return(:yay)
42
+ Ethernet.raw_socket('yay', 1234).should == :yay
43
+ end
6
44
  end
7
45
  end
@@ -0,0 +1,37 @@
1
+ # Wraps ifconfig calls useful for testing.
2
+ module IfconfigCli
3
+ # Runs ifconfig and parses its output.
4
+ def self.run
5
+ case RUBY_PLATFORM
6
+ when /win/
7
+ else
8
+ output = `ifconfig -a`
9
+ info_blocks = output.split /\n(?=\w)/
10
+ Hash[info_blocks.map { |i|
11
+ name = i.split(' ', 2).first
12
+ mac = if match = /hwaddr\s([0-9a-f:]+)\s/i.match(i)
13
+ # Linux ifconfig output.
14
+ match[1].gsub(':', '').downcase
15
+ elsif match = /ether\s([0-9a-f:]+)\s/i.match(i)
16
+ # OSX ifconfig output.
17
+ match[1].gsub(':', '').downcase
18
+ elsif match = /\s(([0-9a-f]{2}:){5}[0-9a-f]{2})\s/i.match(i)
19
+ # First thing that looks like a MAC address.
20
+ match[1].gsub(':', '').downcase
21
+ else
22
+ nil
23
+ end
24
+ active = /inet\s/.match(i) ? true : false
25
+ [name, { :mac => mac, :active => active }]
26
+ }]
27
+ end
28
+ end
29
+
30
+ # The name of the first active Ethernet device.
31
+ #
32
+ # The return value will most likely be eth0 on Linux and en0 on OSX.
33
+ def self.live_device
34
+ run_result = run
35
+ run_result.keys.sort.find { |device| run_result[device][:active] }
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ class RawSocketStub
2
+ def initialize(recv_data)
3
+ @packets = recv_data
4
+ @sends = []
5
+ end
6
+
7
+ def recv(buffer_size)
8
+ raise 'recv called too many times' if @packets.empty?
9
+ @packets.shift
10
+ end
11
+
12
+ def send(data, flags)
13
+ raise 'Weird flags' if flags != 0
14
+ @sends << data
15
+ end
16
+
17
+ def close
18
+ @packets = nil
19
+ @sends = nil
20
+ end
21
+
22
+ attr_reader :sends
23
+ end
metadata CHANGED
@@ -1,66 +1,121 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ethernet
3
3
  version: !ruby/object:Gem::Version
4
+ hash: 27
4
5
  prerelease:
5
- version: 0.0.0
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
6
11
  platform: ruby
7
12
  authors:
13
+ - Victor Costan
8
14
  - HaoQi Li
9
15
  autorequire:
10
16
  bindir: bin
11
17
  cert_chain: []
12
18
 
13
- date: 2011-03-11 00:00:00 -05:00
14
- default_executable:
19
+ date: 2011-05-27 00:00:00 Z
15
20
  dependencies:
16
21
  - !ruby/object:Gem::Dependency
17
- name: rspec
22
+ type: :runtime
18
23
  requirement: &id001 !ruby/object:Gem::Requirement
19
24
  none: false
20
25
  requirements:
21
26
  - - ~>
22
27
  - !ruby/object:Gem::Version
23
- version: 2.3.0
24
- type: :development
28
+ hash: 25
29
+ segments:
30
+ - 0
31
+ - 1
32
+ - 1
33
+ version: 0.1.1
25
34
  prerelease: false
26
35
  version_requirements: *id001
36
+ name: system-getifaddrs
27
37
  - !ruby/object:Gem::Dependency
28
- name: bundler
38
+ type: :development
29
39
  requirement: &id002 !ruby/object:Gem::Requirement
30
40
  none: false
31
41
  requirements:
32
42
  - - ~>
33
43
  - !ruby/object:Gem::Version
34
- version: 1.0.0
35
- type: :development
44
+ hash: 29
45
+ segments:
46
+ - 3
47
+ - 6
48
+ - 1
49
+ version: 3.6.1
36
50
  prerelease: false
37
51
  version_requirements: *id002
52
+ name: rdoc
38
53
  - !ruby/object:Gem::Dependency
39
- name: jeweler
54
+ type: :development
40
55
  requirement: &id003 !ruby/object:Gem::Requirement
41
56
  none: false
42
57
  requirements:
43
58
  - - ~>
44
59
  - !ruby/object:Gem::Version
45
- version: 1.5.2
46
- type: :development
60
+ hash: 23
61
+ segments:
62
+ - 2
63
+ - 6
64
+ - 0
65
+ version: 2.6.0
47
66
  prerelease: false
48
67
  version_requirements: *id003
68
+ name: rspec
49
69
  - !ruby/object:Gem::Dependency
50
- name: rcov
70
+ type: :development
51
71
  requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ hash: 23
77
+ segments:
78
+ - 1
79
+ - 0
80
+ - 0
81
+ version: 1.0.0
82
+ prerelease: false
83
+ version_requirements: *id004
84
+ name: bundler
85
+ - !ruby/object:Gem::Dependency
86
+ type: :development
87
+ requirement: &id005 !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ~>
91
+ - !ruby/object:Gem::Version
92
+ hash: 15
93
+ segments:
94
+ - 1
95
+ - 6
96
+ - 0
97
+ version: 1.6.0
98
+ prerelease: false
99
+ version_requirements: *id005
100
+ name: jeweler
101
+ - !ruby/object:Gem::Dependency
102
+ type: :development
103
+ requirement: &id006 !ruby/object:Gem::Requirement
52
104
  none: false
53
105
  requirements:
54
106
  - - ">="
55
107
  - !ruby/object:Gem::Version
108
+ hash: 3
109
+ segments:
110
+ - 0
56
111
  version: "0"
57
- type: :development
58
112
  prerelease: false
59
- version_requirements: *id004
60
- description: This project is a low-level ethernet communication rubygems library.
61
- email: haoqili@mit.edu
62
- executables: []
63
-
113
+ version_requirements: *id006
114
+ name: rcov
115
+ description: Provides a Socket-like API that bypasses TCP/IP. Useful for exotic devices and FPGA development.
116
+ email: victor@costan.us
117
+ executables:
118
+ - ethernet_ping
64
119
  extensions: []
65
120
 
66
121
  extra_rdoc_files:
@@ -68,6 +123,7 @@ extra_rdoc_files:
68
123
  - README.rdoc
69
124
  files:
70
125
  - .document
126
+ - .project
71
127
  - .rspec
72
128
  - Gemfile
73
129
  - Gemfile.lock
@@ -75,12 +131,22 @@ files:
75
131
  - README.rdoc
76
132
  - Rakefile
77
133
  - VERSION
134
+ - bin/ethernet_ping
78
135
  - ethernet.gemspec
79
136
  - lib/ethernet.rb
137
+ - lib/ethernet/devices.rb
138
+ - lib/ethernet/frame_socket.rb
139
+ - lib/ethernet/ping.rb
140
+ - lib/ethernet/provisioning.rb
141
+ - lib/ethernet/raw_socket_factory.rb
142
+ - spec/ethernet/devices_spec.rb
143
+ - spec/ethernet/frame_socket_spec.rb
144
+ - spec/ethernet/raw_socket_factory_spec.rb
80
145
  - spec/ethernet_spec.rb
81
146
  - spec/spec_helper.rb
82
- has_rdoc: true
83
- homepage: http://github.com/haoqili/ethernet
147
+ - spec/support/ifconfig_cli.rb
148
+ - spec/support/raw_socket_stub.rb
149
+ homepage: http://github.com/costan/ethernet
84
150
  licenses:
85
151
  - MIT
86
152
  post_install_message:
@@ -93,7 +159,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
93
159
  requirements:
94
160
  - - ">="
95
161
  - !ruby/object:Gem::Version
96
- hash: -231959181
162
+ hash: 3
97
163
  segments:
98
164
  - 0
99
165
  version: "0"
@@ -102,14 +168,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
168
  requirements:
103
169
  - - ">="
104
170
  - !ruby/object:Gem::Version
171
+ hash: 3
172
+ segments:
173
+ - 0
105
174
  version: "0"
106
175
  requirements: []
107
176
 
108
177
  rubyforge_project:
109
- rubygems_version: 1.6.2
178
+ rubygems_version: 1.8.4
110
179
  signing_key:
111
180
  specification_version: 3
112
- summary: A ruby gem library for ethernet communication.
113
- test_files:
114
- - spec/ethernet_spec.rb
115
- - spec/spec_helper.rb
181
+ summary: Ethernet (link-layer level) sockets.
182
+ test_files: []
183
+