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 +2 -3
- data/Gemfile.lock +3 -2
- data/README.rdoc +9 -1
- data/VERSION +1 -1
- data/ethernet.gemspec +9 -5
- data/lib/ethernet.rb +8 -0
- data/lib/ethernet/devices.rb +87 -22
- data/lib/ethernet/devices_darwin.rb +31 -0
- data/lib/ethernet/devices_linux.rb +48 -0
- data/lib/ethernet/frame_socket.rb +4 -3
- data/lib/ethernet/provisioning.rb +11 -5
- data/lib/ethernet/raw_socket_factory.rb +10 -68
- data/lib/ethernet/raw_socket_factory_darwin.rb +177 -0
- data/lib/ethernet/raw_socket_factory_linux.rb +44 -0
- data/spec/ethernet/frame_socket_spec.rb +2 -1
- data/spec/ethernet/raw_socket_factory_spec.rb +1 -1
- data/spec/ethernet_spec.rb +1 -1
- data/spec/support/ifconfig_cli.rb +17 -5
- metadata +14 -10
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 '
|
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
|
-
==
|
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
|
+
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.
|
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-
|
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<
|
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<
|
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<
|
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
|
data/lib/ethernet/devices.rb
CHANGED
@@ -1,23 +1,13 @@
|
|
1
|
-
|
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
|
-
#
|
8
|
+
# Array containing device names.
|
14
9
|
def self.all
|
15
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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 =
|
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 =
|
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
|
-
|
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
|
24
|
+
case OS
|
19
25
|
when /darwin/
|
20
|
-
return false unless Kernel.system("chmod o+
|
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
|
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
|
19
|
-
|
20
|
-
|
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
|
-
[
|
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
|
])
|
data/spec/ethernet_spec.rb
CHANGED
@@ -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
|
-
|
6
|
-
|
7
|
-
|
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
|
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:
|
4
|
+
hash: 31
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.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-
|
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:
|
28
|
+
hash: 23
|
29
29
|
segments:
|
30
|
-
- 0
|
31
30
|
- 1
|
32
|
-
-
|
33
|
-
|
31
|
+
- 0
|
32
|
+
- 0
|
33
|
+
version: 1.0.0
|
34
34
|
prerelease: false
|
35
35
|
version_requirements: *id001
|
36
|
-
name:
|
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
|