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