datalink-socket 0.0.1
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/README.rdoc +58 -0
- data/lib/arp.rb +87 -0
- data/lib/dl_socket.rb +1 -0
- data/lib/dl_socket_darwin.rb +113 -0
- data/lib/dl_socket_linux.rb +83 -0
- data/lib/ethernet.rb +165 -0
- data/lib/ruby-ext.rb +108 -0
- data/test/arp_test.rb +18 -0
- data/test/ethernet_test.rb +71 -0
- metadata +78 -0
data/README.rdoc
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
= A Datalink-Socket for OSX and Linux
|
2
|
+
|
3
|
+
To receive and send ethernet frames using ruby.
|
4
|
+
|
5
|
+
* Datalink::Socket class
|
6
|
+
* Ethernet::Socket class < Datalink::Socket
|
7
|
+
* Ethernet::Frame class
|
8
|
+
* Arp class
|
9
|
+
|
10
|
+
== Arp
|
11
|
+
|
12
|
+
> Arp.methods false
|
13
|
+
=> [:new_request, :new_reply]
|
14
|
+
|
15
|
+
> Arp.new_request :hw_src=> '00:50:56:c0:00:01',
|
16
|
+
:proto_src=> '192.168.1.10',
|
17
|
+
:proto_tgt=> '192.168.1.13'
|
18
|
+
=> ARP, Request who-has 192.168.1.13 tell 192.168.1.10
|
19
|
+
|
20
|
+
== Frame
|
21
|
+
|
22
|
+
> data, _ = s.recv
|
23
|
+
=>
|
24
|
+
> frame = Ethernet::Frame.new data
|
25
|
+
=> #<Ethernet::Frame:0x00000100c94b08 ... >
|
26
|
+
> frame.src
|
27
|
+
=> 00:24:b2:51:a8:8e
|
28
|
+
> frame.dst
|
29
|
+
=> 00:1f:5b:ce:bd:6a
|
30
|
+
> frame.ether_type
|
31
|
+
=> 2048
|
32
|
+
> frame.payload.unpack('H*')
|
33
|
+
=> ["45200034d5ae400035061ad .... 08195fba"]
|
34
|
+
|
35
|
+
|
36
|
+
== Receiving
|
37
|
+
|
38
|
+
require 'ethernet'
|
39
|
+
s = Ethernet::Socket.new "en1"
|
40
|
+
s.open
|
41
|
+
s.recv
|
42
|
+
|
43
|
+
|
44
|
+
== Sending
|
45
|
+
|
46
|
+
request = Arp.new_request :hw_src=> '00:50:56:c0:00:01',
|
47
|
+
:proto_src=> '192.168.1.10',
|
48
|
+
:proto_tgt=> '192.168.1.13'
|
49
|
+
|
50
|
+
s.send request
|
51
|
+
|
52
|
+
= Installation
|
53
|
+
|
54
|
+
gem install datalink-socket
|
55
|
+
|
56
|
+
= License
|
57
|
+
|
58
|
+
Copyright (c) 2011 Jean-Michel Esnault. Released under the same license as Ruby
|
data/lib/arp.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'ethernet'
|
2
|
+
require 'ipaddr'
|
3
|
+
|
4
|
+
class Arp
|
5
|
+
include Args
|
6
|
+
|
7
|
+
HwSrc = Class.new(Ethernet::Address)
|
8
|
+
HwTgt = Class.new(Ethernet::Address)
|
9
|
+
ProtoSrc = Class.new(IPAddr)
|
10
|
+
ProtoTgt = Class.new(IPAddr)
|
11
|
+
|
12
|
+
attr_reader :hw_src, :hw_tgt, :proto_src, :proto_tgt, :opcode
|
13
|
+
|
14
|
+
def initialize(arg={})
|
15
|
+
if arg.is_a?(Hash)
|
16
|
+
@hw_tgt = HwTgt.new
|
17
|
+
@opcode, @hw_src, @proto_src, @proto_tgt = 1, nil, nil, nil
|
18
|
+
set arg
|
19
|
+
elsif arg.is_a?(String)
|
20
|
+
parse arg
|
21
|
+
else
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def encode
|
26
|
+
s=[]
|
27
|
+
hw_src = @hw_src.encode
|
28
|
+
proto_src = @proto_src.hton
|
29
|
+
hw_tgt = @hw_tgt.encode
|
30
|
+
proto_tgt = @proto_tgt.hton
|
31
|
+
s << eth_hdr
|
32
|
+
s << [1,0x0800, 6, 4, @opcode, hw_src, proto_src, hw_tgt, proto_tgt].pack("nnCCn"+"a6a4"*2)
|
33
|
+
s.join
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse(s)
|
37
|
+
# p s.unpack('H*')
|
38
|
+
# p s.size
|
39
|
+
s.slice!(0,14) if s.size>32
|
40
|
+
htype, ptype, hlen, plen, opcode = s.unpack('nnccn')
|
41
|
+
# p htype
|
42
|
+
# p ptype
|
43
|
+
hw_src, proto_src, hw_tgt, proto_tgt = s[8..-1].unpack("a#{hlen}a#{plen}"*2)
|
44
|
+
raise RuntimeError, "Unsupported Hardware Type" unless htype == 1 && ptype == 0x0800
|
45
|
+
@hw_src = HwSrc.new(hw_src)
|
46
|
+
@hw_tgt = HwTgt.new(hw_tgt)
|
47
|
+
@proto_src = ProtoSrc.ntop(proto_src)
|
48
|
+
@proto_tgt = ProtoTgt.ntop(proto_tgt)
|
49
|
+
@opcode = opcode
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
case @opcode
|
54
|
+
when 1 ; "ARP, Request who-has #{@proto_tgt} tell #{@proto_src}"
|
55
|
+
when 2 ; "ARP, Reply #{@proto_src} is-at #{@hw_src}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def reply(hw_src)
|
60
|
+
self.class.new_reply :hw_src=> hw_src, :proto_src=> "#{@proto_tgt}",
|
61
|
+
:hw_tgt => "#{@hw_src}", :proto_tgt => "#{@proto_src}"
|
62
|
+
end
|
63
|
+
|
64
|
+
class << self
|
65
|
+
def Arp.new_request(arg={})
|
66
|
+
if arg.is_a?(Hash)
|
67
|
+
arg[:opcode]=1
|
68
|
+
new arg
|
69
|
+
end
|
70
|
+
end
|
71
|
+
def Arp.new_reply(arg={})
|
72
|
+
if arg.is_a?(Hash)
|
73
|
+
arg[:opcode]=2
|
74
|
+
new arg
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def eth_hdr
|
82
|
+
src = @hw_src.to_s
|
83
|
+
dst = @opcode == 1 ? 'ffff.ffff.ffff' : @hw_tgt.to_s
|
84
|
+
f = ::Ethernet::Frame.new :dst=> dst, :src=> src
|
85
|
+
f.encode(::Ethernet::Type::ETH_ARP)
|
86
|
+
end
|
87
|
+
end
|
data/lib/dl_socket.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "dl_socket_#{`uname`.chomp.downcase}"
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Datalink
|
2
|
+
|
3
|
+
BIOCGBLEN = 0x40044266
|
4
|
+
BIOCSBLEN = 0xc0044266
|
5
|
+
BIOCSETF = 0x80084267
|
6
|
+
BIOCFLUSH = 0x20004268
|
7
|
+
BIOCPROMISC = 0x20004269
|
8
|
+
BIOCGDLT = 0x4004426a
|
9
|
+
BIOCGETIF = 0x4020426b
|
10
|
+
BIOCSETIF = 0x8020426c
|
11
|
+
BIOCSRTIMEOUT = 0x8008426d
|
12
|
+
BIOCGRTIMEOUT = 0x4008426e
|
13
|
+
BIOCGSTATS = 0x4008426f
|
14
|
+
BIOCIMMEDIATE = 0x80044270
|
15
|
+
BIOCVERSION = 0x40044271
|
16
|
+
BIOCGRSIG = 0x40044272
|
17
|
+
BIOCSRSIG = 0x80044273
|
18
|
+
BIOCGHDRCMPLT = 0x40044274
|
19
|
+
BIOCSHDRCMPLT = 0x80044275
|
20
|
+
BIOCGSEESENT = 0x40044276
|
21
|
+
BIOCSSEESENT = 0x80044277
|
22
|
+
BIOCSDLT = 0x80044278
|
23
|
+
BIOCGDLTLIST = 0xc00c4279
|
24
|
+
|
25
|
+
class Socket
|
26
|
+
|
27
|
+
attr_accessor :if_name
|
28
|
+
attr_reader :device_name, :device
|
29
|
+
|
30
|
+
def initialize(if_name)
|
31
|
+
@if_name = if_name
|
32
|
+
end
|
33
|
+
|
34
|
+
def open(if_name=@if_name)
|
35
|
+
num=0
|
36
|
+
@if_name = if_name
|
37
|
+
begin
|
38
|
+
bpf_name = "/dev/bpf#{num}"
|
39
|
+
@file_handle = File.open(bpf_name, "w+")
|
40
|
+
rescue => error
|
41
|
+
if num < 30 and not defined?(@file_handle)
|
42
|
+
num +=1
|
43
|
+
retry
|
44
|
+
else
|
45
|
+
raise RuntimeError, "could not open #{bpf_name}: #{error}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
@file_handle_name = bpf_name
|
50
|
+
@file_handle.ioctl(BIOCSETIF, [@if_name].pack("a#{@if_name.size+1}"))
|
51
|
+
@file_handle.ioctl(BIOCIMMEDIATE, [1].pack('I'))
|
52
|
+
@file_handle.ioctl(BIOCGHDRCMPLT, [0].pack('N'))
|
53
|
+
buf = [0].pack('i')
|
54
|
+
@file_handle.ioctl(BIOCGBLEN, buf)
|
55
|
+
@buf_size = buf.unpack('i')[0]
|
56
|
+
timeout = [5,0].pack('LL')
|
57
|
+
@file_handle.ioctl(BIOCSRTIMEOUT, timeout)
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def send(obj)
|
62
|
+
@file_handle.write_nonblock(obj.respond_to?(:encode) ? obj.encode : obj)
|
63
|
+
end
|
64
|
+
|
65
|
+
def recv
|
66
|
+
__recv
|
67
|
+
end
|
68
|
+
|
69
|
+
def close
|
70
|
+
@file_handle.close
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def sysread_nb
|
76
|
+
buf=''
|
77
|
+
@file_handle.sysread(@buf_size, buf)
|
78
|
+
buf
|
79
|
+
end
|
80
|
+
|
81
|
+
def sysread
|
82
|
+
begin
|
83
|
+
buf = sysread_nb
|
84
|
+
return buf
|
85
|
+
rescue EOFError, Errno::EAGAIN => e
|
86
|
+
sleep(0.25)
|
87
|
+
retry
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def __recv
|
92
|
+
@recvQueue ||=[]
|
93
|
+
@packet_size ||= lambda { |n| n+3 & ~3 }
|
94
|
+
@header_decode ||= lambda { |hdr|
|
95
|
+
datalen = hdr.slice(12,4).unpack('L')[0]
|
96
|
+
hdrlen = hdr.slice(16,2).unpack('v')[0]
|
97
|
+
size = @packet_size.call(datalen+hdrlen)
|
98
|
+
[size, hdrlen]
|
99
|
+
}
|
100
|
+
if @recvQueue.empty?
|
101
|
+
bpf_frames = sysread
|
102
|
+
while bpf_frames.size>0
|
103
|
+
size, hdrlen = @header_decode.call(bpf_frames)
|
104
|
+
@recvQueue << bpf_frames.slice!(0,size)[hdrlen..-1]
|
105
|
+
end
|
106
|
+
__recv
|
107
|
+
else
|
108
|
+
@recvQueue.shift
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Datalink
|
4
|
+
class Socket < ::Socket
|
5
|
+
|
6
|
+
IFF_UP = 0x0001
|
7
|
+
IFF_BROADCAST = 0x0002
|
8
|
+
IFF_DEBUG = 0x0004
|
9
|
+
IFF_LOOPBACK = 0x0008
|
10
|
+
IFF_P2P = 0x0010
|
11
|
+
IFF_NOTRAILERS = 0x0020
|
12
|
+
IFF_RUNNING = 0x0040
|
13
|
+
IFF_NOARP = 0x0080
|
14
|
+
IFF_PROMISC = 0x0100
|
15
|
+
IFF_ALLMULTI = 0x0200
|
16
|
+
IFF_MASTER = 0x0400
|
17
|
+
IFF_SLAVE = 0x0800
|
18
|
+
IFF_MULTICAST = 0x1000
|
19
|
+
SIOCGIFINDEX = 0x8933
|
20
|
+
SIOCGIFFLAGS = 0x8913
|
21
|
+
SIOCSIFFLAGS = 0x8914
|
22
|
+
PF_PACKET = 17
|
23
|
+
AF_PACKET = PF_PACKET
|
24
|
+
IFNAMSIZ = 16
|
25
|
+
|
26
|
+
def flags
|
27
|
+
read_flags
|
28
|
+
end
|
29
|
+
self.class.constants.reject { |c| !( c =~/^IFF_/) }.collect { |f| f.to_s.split('_')[1].downcase}.each do |f|
|
30
|
+
define_method("#{f}?") do
|
31
|
+
flags & self.class.const_get("IFF_#{f.upcase}") > 0
|
32
|
+
end
|
33
|
+
define_method("set_#{f}") do
|
34
|
+
flags = read_flags
|
35
|
+
flags |= self.class.const_get("IFF_#{f.upcase}")
|
36
|
+
write_flags(flags)
|
37
|
+
end
|
38
|
+
define_method("unset_#{f}") do
|
39
|
+
flags = read_flags
|
40
|
+
flags &= ~self.class.const_get("IFF_#{f.upcase}")
|
41
|
+
write_flags(flags)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(if_name, proto=0x003)
|
46
|
+
@if_name = if_name
|
47
|
+
super(PF_PACKET, Socket::SOCK_RAW, proto)
|
48
|
+
ifreq = [if_name.dup].pack 'a32'
|
49
|
+
ioctl(SIOCGIFINDEX, ifreq)
|
50
|
+
bind [AF_PACKET].pack('s') + [proto].pack('n') + ifreq[16..20]+ ("\x00" * 12)
|
51
|
+
end
|
52
|
+
|
53
|
+
def recv
|
54
|
+
data, from = recvfrom(2048)
|
55
|
+
ether_type = data[12,2].unpack('n')[0]
|
56
|
+
[data, ether_type, Time.now]
|
57
|
+
end
|
58
|
+
|
59
|
+
def send(obj)
|
60
|
+
if obj.respond_to?(:encode)
|
61
|
+
super(obj.encode,0)
|
62
|
+
else
|
63
|
+
super(obj,0)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def open
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def read_flags
|
73
|
+
ifreq = [@if_name.dup].pack "a#{IFNAMSIZ}"
|
74
|
+
ioctl(SIOCGIFFLAGS,ifreq)
|
75
|
+
ifreq[IFNAMSIZ,2].unpack('s')[0]
|
76
|
+
end
|
77
|
+
|
78
|
+
def write_flags(flags)
|
79
|
+
ifreq = [@if_name.dup].pack("a#{IFNAMSIZ}") + [flags].pack('s')
|
80
|
+
ioctl(SIOCSIFFLAGS,ifreq)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/ethernet.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'ruby-ext'
|
2
|
+
require 'dl_socket'
|
3
|
+
require 'rubygems'
|
4
|
+
# require 'oui'
|
5
|
+
|
6
|
+
module Ethernet
|
7
|
+
class Socket < Datalink::Socket
|
8
|
+
def recv(*args)
|
9
|
+
eth_frame = super
|
10
|
+
eth_type = eth_frame.slice(12,2).unpack('n')[0]
|
11
|
+
[eth_frame, eth_type]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Type
|
16
|
+
ETH_IPv4 = 0x0800
|
17
|
+
ETH_ARP = 0x0806
|
18
|
+
ETH_RARP = 0x8035
|
19
|
+
ETH_Ethertalk = 0x809B
|
20
|
+
ETH_AARP = 0x80F3
|
21
|
+
ETH_IEEE_802_1Q = 0x8100
|
22
|
+
ETH_IPv6 = 0x86DD
|
23
|
+
def self.to_s(type)
|
24
|
+
case type
|
25
|
+
when ETH_IPv4 ; 'IPv4'
|
26
|
+
when ETH_IPv6 ; 'IPv6'
|
27
|
+
when ETH_ARP ; 'ARP'
|
28
|
+
when ETH_RARP ; 'RARP'
|
29
|
+
when ETH_Ethertalk ; 'AppleTalk Ethertalk'
|
30
|
+
when ETH_AARP ; 'AppleTalk Address Resolution Protocol'
|
31
|
+
when ETH_IEEE_802_1Q ; 'IEEE_802_1Q'
|
32
|
+
else
|
33
|
+
format("0x%04x",type)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
class Address
|
39
|
+
attr_reader :mac
|
40
|
+
|
41
|
+
def initialize(arg=nil)
|
42
|
+
if arg.is_a?(String) and arg.size==6
|
43
|
+
parse(arg)
|
44
|
+
elsif arg.is_a?(String) and arg.size==14
|
45
|
+
parse [arg.split('.').join].pack('H*')
|
46
|
+
elsif arg.is_a?(String)
|
47
|
+
@mac = arg.split(/[-:]/).collect { |n| n.to_i(16) }
|
48
|
+
elsif arg.is_a?(String) and size==14
|
49
|
+
elsif arg.kind_of?(self.class)
|
50
|
+
parse arg.encode
|
51
|
+
elsif arg.kind_of?(self.class)
|
52
|
+
parse arg.encode
|
53
|
+
elsif ! arg
|
54
|
+
@mac = [0,0,0,0,0,0]
|
55
|
+
else
|
56
|
+
raise ArgumentError, "Argument error: #{self.class} #{arg.inspect}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s(delim=":")
|
61
|
+
case delim
|
62
|
+
when ':'
|
63
|
+
(format (["%02x"]*6).join(delim), *@mac)
|
64
|
+
when '.'
|
65
|
+
(format (["%04x"]*3).join(delim), *(@mac.pack('C6').unpack('n3')))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# def to_s_oui
|
70
|
+
# comp_id = Ethernet::OUI.company_id_from_arr(@mac[0..2])
|
71
|
+
# if comp_id == 'Unknown'
|
72
|
+
# to_s.downcase
|
73
|
+
# else
|
74
|
+
# s = []
|
75
|
+
# s << comp_id
|
76
|
+
# s << (format (["%02x"]*3).join(":"), *@mac[3..5])
|
77
|
+
# s.join('_')
|
78
|
+
# end
|
79
|
+
# end
|
80
|
+
|
81
|
+
def encode
|
82
|
+
@mac.pack('C*')
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_hash
|
86
|
+
to_s
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def parse(s)
|
92
|
+
@mac = s.unpack('C6')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
include Type
|
97
|
+
|
98
|
+
def self.packet_factory(frame, ether_type, time)
|
99
|
+
s = frame.dup
|
100
|
+
case ether_type
|
101
|
+
when IPv4 ; puts "IPv4:"
|
102
|
+
when IPv6 ; puts "IPv6:"
|
103
|
+
when ARP ; puts "ARP:"
|
104
|
+
when RARP ; puts "RARP:"
|
105
|
+
else
|
106
|
+
puts "Ether_type: #{ether_type}:"
|
107
|
+
end
|
108
|
+
puts s.unpack('H*')
|
109
|
+
end
|
110
|
+
|
111
|
+
class Frame
|
112
|
+
include Args
|
113
|
+
Src = Class.new(Address)
|
114
|
+
Dst = Class.new(Address)
|
115
|
+
attr_reader :src, :dst, :ether_type, :payload
|
116
|
+
attr_writer_delegate :src, :dst
|
117
|
+
def initialize(arg={})
|
118
|
+
if arg.is_a?(Hash)
|
119
|
+
@src = Src.new
|
120
|
+
@dst = Dst.new
|
121
|
+
@ether_type=0
|
122
|
+
set(arg)
|
123
|
+
elsif arg.is_a?(String)
|
124
|
+
parse arg
|
125
|
+
else
|
126
|
+
raise ArgumentError, "Invalid argument :#{arg.inspect}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
def encode(ether_type=@ether_type, payload=nil)
|
130
|
+
@ether_type = ether_type if ether_type
|
131
|
+
@payload = payload if payload
|
132
|
+
frame = []
|
133
|
+
frame << @dst.encode
|
134
|
+
frame << @src.encode
|
135
|
+
frame << [ether_type].pack('n')
|
136
|
+
if @payload
|
137
|
+
if @payload.respond_to?(:encode)
|
138
|
+
frame << @payload.encode if @payload.respond_to?(:encode)
|
139
|
+
else
|
140
|
+
frame << @payload
|
141
|
+
end
|
142
|
+
end
|
143
|
+
frame.join
|
144
|
+
end
|
145
|
+
def parse(s)
|
146
|
+
self.dst = s.slice!(0,6)
|
147
|
+
self.src = s.slice!(0,6)
|
148
|
+
@ether_type = s.slice!(0,2).unpack('n')[0]
|
149
|
+
@payload = s
|
150
|
+
end
|
151
|
+
def ieee_802_3?
|
152
|
+
type? == :ieee_802_3
|
153
|
+
end
|
154
|
+
def ethernet_v2?
|
155
|
+
type? == :ethernet_v2
|
156
|
+
end
|
157
|
+
def type?
|
158
|
+
if @ether_type < 0x600
|
159
|
+
:ieee_802_3
|
160
|
+
else
|
161
|
+
:ethernet_v2
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
data/lib/ruby-ext.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
|
2
|
+
class Object
|
3
|
+
def to_shex(*args)
|
4
|
+
self.respond_to?(:encode) ? self.encode(*args).unpack('H*')[0] : ""
|
5
|
+
end
|
6
|
+
alias to_s_hexlify to_shex
|
7
|
+
end
|
8
|
+
|
9
|
+
class Class
|
10
|
+
def attr_checked(attribute, &validation)
|
11
|
+
define_method "#{attribute}=" do |val|
|
12
|
+
raise "Invalid attribute #{val.inspect}" unless validation.call(val)
|
13
|
+
instance_variable_set("@#{attribute}", val)
|
14
|
+
end
|
15
|
+
define_method attribute do
|
16
|
+
instance_variable_get "@#{attribute}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
def attr_writer_delegate(*args)
|
20
|
+
args.each do |name|
|
21
|
+
define_method "#{name}=" do |value|
|
22
|
+
instance_variable_set("@#{name}", self.class.const_get(name.to_s.to_camel.to_sym).new(value))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class String
|
29
|
+
def to_underscore
|
30
|
+
gsub(/([A-Z]+|[A-Z][a-z])/) {|x| ' ' + x }.gsub(/[A-Z][a-z]+/) {|x| ' ' + x }.split.collect{|x| x.downcase}.join('_')
|
31
|
+
end
|
32
|
+
def to_camel
|
33
|
+
split('_').collect {|x| x.capitalize}.join
|
34
|
+
end
|
35
|
+
def hexlify
|
36
|
+
l,n,ls,s=0,0,[''],self.dup
|
37
|
+
while s.size>0
|
38
|
+
l = s.slice!(0,16)
|
39
|
+
ls << format("0x%4.4x: %s", n, l.unpack("n#{l.size/2}").collect { |x| format("%4.4x",x) }.join(' '))
|
40
|
+
n+=1
|
41
|
+
end
|
42
|
+
if l.size%2 >0
|
43
|
+
ns = l.size>1 ? 1 : 0
|
44
|
+
ls.last << format("%s%2.2x",' '*ns,l[-1].pack('C')[0])
|
45
|
+
end
|
46
|
+
ls
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Symbol
|
51
|
+
def to_klass
|
52
|
+
to_s.to_camel.to_sym
|
53
|
+
end
|
54
|
+
def to_setter
|
55
|
+
(to_s + "=").to_sym
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module Args
|
60
|
+
def set(h)
|
61
|
+
for key in [ivars].flatten
|
62
|
+
if h.has_key?(key) and ! h[key].nil?
|
63
|
+
begin
|
64
|
+
klassname = key.to_klass
|
65
|
+
if self.class.const_defined?(klassname)
|
66
|
+
instance_variable_set("@#{key.to_s}", self.class.const_get(klassname).new(h[key]))
|
67
|
+
elsif self.respond_to?(key.to_setter)
|
68
|
+
self.send key.to_setter, h[key]
|
69
|
+
else
|
70
|
+
instance_variable_set("@#{key.to_s}", h[key])
|
71
|
+
end
|
72
|
+
rescue ArgumentError => e
|
73
|
+
raise
|
74
|
+
ensure
|
75
|
+
#h.delete(key)
|
76
|
+
end
|
77
|
+
else
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_hash
|
83
|
+
h = {}
|
84
|
+
for key in [ivars].flatten
|
85
|
+
ivar = instance_variable_get("@#{key.to_s}")
|
86
|
+
if ivar.respond_to?(:to_hash)
|
87
|
+
h.store(key,ivar.to_hash)
|
88
|
+
elsif ivar.is_a?(Array)
|
89
|
+
h.store(key, ivar.collect { |x|
|
90
|
+
if x.respond_to?(:to_hash)
|
91
|
+
x.to_hash
|
92
|
+
else
|
93
|
+
# x.to_s
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
}
|
97
|
+
)
|
98
|
+
else
|
99
|
+
h.store(key,ivar) unless ivar.nil?
|
100
|
+
end
|
101
|
+
end
|
102
|
+
h
|
103
|
+
end
|
104
|
+
|
105
|
+
def ivars
|
106
|
+
instance_variables.reject { |x| x =~ /^@_/ }.collect { |x| x[1..-1].to_sym }
|
107
|
+
end
|
108
|
+
end
|
data/test/arp_test.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
require "arp"
|
3
|
+
class TestNetArp2 < Test::Unit::TestCase
|
4
|
+
def test_arp_request
|
5
|
+
req = Arp.new_request :hw_src=> '1:2:3:4:5:6', :proto_src=> '10.0.0.1', :proto_tgt=>'10.0.0.2'
|
6
|
+
assert_equal(1, req.opcode)
|
7
|
+
assert_equal('01:02:03:04:05:06', req.hw_src.to_s)
|
8
|
+
assert_equal('00:00:00:00:00:00', req.hw_tgt.to_s)
|
9
|
+
assert_equal('10.0.0.1', req.proto_src.to_s)
|
10
|
+
assert_equal('10.0.0.2', req.proto_tgt.to_s)
|
11
|
+
assert_equal('ffffffffffff010203040506080600010800060400010102030405060a0000010000000000000a000002', req.to_shex)
|
12
|
+
end
|
13
|
+
def test_arp_reply
|
14
|
+
req = Arp.new_request :hw_src=> '1:2:3:4:5:6', :proto_src=> '10.0.0.1', :proto_tgt=>'10.0.0.2'
|
15
|
+
reply = req.reply '00:50:56:c0:00:01'
|
16
|
+
#TODO finish test
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
|
3
|
+
require "ethernet"
|
4
|
+
|
5
|
+
class TestEthernetIeAddress < Test::Unit::TestCase
|
6
|
+
def test_create_address
|
7
|
+
assert Ethernet::Address.new
|
8
|
+
assert Ethernet::Address.new '00:01:02:03:04:05'
|
9
|
+
assert_equal '01:02:03:04:05:06', Ethernet::Address.new('1:2:3:4:5:6').to_s
|
10
|
+
assert_equal '0a:0b:0c:0d:0e:0f', Ethernet::Address.new('a:b:c:d:e:f').to_s
|
11
|
+
assert_equal '00:b0:64:fd:4f:6c', Ethernet::Address.new('00b0.64fd.4f6c').to_s
|
12
|
+
end
|
13
|
+
def test_to_s
|
14
|
+
assert_equal '0102.0304.0506', Ethernet::Address.new('1:2:3:4:5:6').to_s('.')
|
15
|
+
assert_equal '0a0b.0c0d.0e0f', Ethernet::Address.new('a:b:c:d:e:f').to_s('.')
|
16
|
+
assert_equal '00b0.64fd.4f6c', Ethernet::Address.new('00b0.64fd.4f6c').to_s('.')
|
17
|
+
end
|
18
|
+
def test_encode
|
19
|
+
assert_equal '010203040506', Ethernet::Address.new('1:2:3:4:5:6').to_shex
|
20
|
+
assert_equal '0a0b0c0d0e0f', Ethernet::Address.new('a:b:c:d:e:f').to_shex
|
21
|
+
end
|
22
|
+
def test_parse
|
23
|
+
assert_equal Ethernet::Address, Ethernet::Address.new(['010203040506'].pack('H*')).class
|
24
|
+
assert_equal '010203040506', Ethernet::Address.new(['010203040506'].pack('H*')).to_shex
|
25
|
+
end
|
26
|
+
# def test_to_s_oui
|
27
|
+
# assert_equal 'Apple_d8:93:a4', Ethernet::Address.new('00:1f:f3:d8:93:a4').to_s_oui
|
28
|
+
# assert_equal 'Dell_01:3e:3d', Ethernet::Address.new('00:21:9b:01:3e:3d').to_s_oui
|
29
|
+
# assert_equal 'Hewlett_64:66:73', Ethernet::Address.new('00:50:8b:64:66:73').to_s_oui
|
30
|
+
# assert_equal 'Intel_40:b8:8f', Ethernet::Address.new('0:2:b3:40:b8:8f').to_s_oui
|
31
|
+
# assert_equal 'Cisco_fd:4f:6c', Ethernet::Address.new('00b0.64fd.4f6c').to_s_oui
|
32
|
+
# end
|
33
|
+
end
|
34
|
+
|
35
|
+
class TestEthernetType < Test::Unit::TestCase
|
36
|
+
def test_case_name
|
37
|
+
assert_equal 2054, Ethernet::Type::ETH_ARP
|
38
|
+
assert_equal 2048, Ethernet::Type::ETH_IPv4
|
39
|
+
assert_equal 34525, Ethernet::Type::ETH_IPv6
|
40
|
+
assert_equal 32821, Ethernet::Type::ETH_RARP
|
41
|
+
assert_equal 'ARP',Ethernet::Type.to_s(2054)
|
42
|
+
assert_equal 'RARP',Ethernet::Type.to_s(32821)
|
43
|
+
assert_equal 'IPv4',Ethernet::Type.to_s(2048)
|
44
|
+
assert_equal 'AppleTalk Ethertalk',Ethernet::Type.to_s(0x809B)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class TestEthernetFrame < Test::Unit::TestCase
|
49
|
+
include Ethernet
|
50
|
+
def test_create_a_frame
|
51
|
+
frame = Ethernet::Frame.new :src=> '0001.0002.0003', :dst=>'0004.0005.0006', :ether_type=>0x800
|
52
|
+
assert_equal '00:01:00:02:00:03', frame.src.to_s
|
53
|
+
assert_equal '00:04:00:05:00:06', frame.dst.to_s
|
54
|
+
assert_equal 2048, frame.ether_type
|
55
|
+
frame = Ethernet::Frame.new(['0004000500060001000200030800'].pack('H*'))
|
56
|
+
assert_equal '00:01:00:02:00:03', frame.src.to_s
|
57
|
+
assert_equal '00:04:00:05:00:06', frame.dst.to_s
|
58
|
+
assert_equal 2048, frame.ether_type
|
59
|
+
end
|
60
|
+
def test_create_ntop_ip
|
61
|
+
s = '01005e00000500b064fd4f6c0800' +
|
62
|
+
'45c0004084fe000001599131c0a801c8e0000005' +
|
63
|
+
'0201002c0200000000000000fa1f00000000000000000000ffffff00000a0280000000280000000000000000'
|
64
|
+
sbin = [s].pack('H*')
|
65
|
+
frame = Frame.new(sbin)
|
66
|
+
# assert_equal 'Cisco_fd:4f:6c', frame.src.to_s_oui
|
67
|
+
# assert_equal '01:00:5e:00:00:05', frame.dst.to_s_oui
|
68
|
+
assert_equal 2048, frame.ether_type
|
69
|
+
assert_equal '45c0004084fe000001599131c0a801c8e000000502', frame.payload[0..20].unpack('H*')[0]
|
70
|
+
end
|
71
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: datalink-socket
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Jean-Michel Esnault
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-01-05 00:00:00 -08:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Datalink Socket
|
22
|
+
email: jesnault@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- README.rdoc
|
31
|
+
- lib/arp.rb
|
32
|
+
- lib/dl_socket.rb
|
33
|
+
- lib/dl_socket_darwin.rb
|
34
|
+
- lib/dl_socket_linux.rb
|
35
|
+
- lib/ethernet.rb
|
36
|
+
- lib/ruby-ext.rb
|
37
|
+
- test/arp_test.rb
|
38
|
+
- test/ethernet_test.rb
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://github.com/jesnault/dl
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --quiet
|
46
|
+
- --title
|
47
|
+
- Datalink Socket
|
48
|
+
- --line-numbers
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 1
|
58
|
+
- 8
|
59
|
+
- 6
|
60
|
+
version: 1.8.6
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
requirements: []
|
70
|
+
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.3.7
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: Datalink Socket
|
76
|
+
test_files:
|
77
|
+
- test/arp_test.rb
|
78
|
+
- test/ethernet_test.rb
|