ethernet 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+