pio 0.1.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/Gemfile +3 -2
- data/Guardfile +4 -1
- data/README.md +44 -6
- data/Rakefile +5 -23
- data/lib/pio.rb +15 -0
- data/lib/pio/arp.rb +31 -0
- data/lib/pio/arp/frame.rb +38 -0
- data/lib/pio/arp/message.rb +66 -0
- data/lib/pio/arp/reply.rb +55 -0
- data/lib/pio/arp/request.rb +55 -0
- data/lib/pio/ip.rb +90 -0
- data/lib/pio/lldp.rb +74 -12
- data/lib/pio/lldp/frame.rb +53 -4
- data/lib/pio/lldp/management-address-value.rb +7 -1
- data/lib/pio/lldp/optional-tlv.rb +3 -1
- data/lib/pio/lldp/organizationally-specific-value.rb +23 -0
- data/lib/pio/mac.rb +156 -0
- data/lib/pio/type/ethernet-header.rb +16 -0
- data/lib/pio/type/ip-address.rb +28 -0
- data/lib/pio/{lldp → type}/mac-address.rb +4 -3
- data/lib/pio/version.rb +1 -19
- data/pio.org +518 -0
- data/pio.org_archive +389 -0
- data/spec/pio/arp/reply_spec.rb +150 -0
- data/spec/pio/arp/request_spec.rb +131 -0
- data/spec/pio/arp_spec.rb +136 -0
- data/spec/pio/ip_spec.rb +38 -0
- data/spec/pio/lldp_spec.rb +195 -38
- data/spec/pio/mac_spec.rb +82 -0
- data/spec/spec_helper.rb +9 -0
- metadata +27 -17
- data/spec/pio/lldp/chassis-id-tlv_spec.rb +0 -27
- data/spec/pio/lldp/end-of-lldpdu-value_spec.rb +0 -20
- data/spec/pio/lldp/frame_spec.rb +0 -82
- data/spec/pio/lldp/mac-address_spec.rb +0 -20
- data/spec/pio/lldp/optional-tlv_spec.rb +0 -81
- data/spec/pio/lldp/port-id-tlv_spec.rb +0 -27
- data/spec/pio/lldp/ttl-tlv_spec.rb +0 -26
data/lib/pio/ip.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
module Pio
|
2
|
+
#
|
3
|
+
# A wrapper class to IPAddr
|
4
|
+
#
|
5
|
+
class IP
|
6
|
+
require "ipaddr"
|
7
|
+
|
8
|
+
|
9
|
+
#
|
10
|
+
# @return [IPAddr] value object instance of proxied IPAddr.
|
11
|
+
#
|
12
|
+
attr_reader :value
|
13
|
+
|
14
|
+
|
15
|
+
attr_reader :prefixlen
|
16
|
+
|
17
|
+
|
18
|
+
#
|
19
|
+
# Creates a {IP} instance object as a proxy to IPAddr class.
|
20
|
+
#
|
21
|
+
# @overload initialize(addr)
|
22
|
+
#
|
23
|
+
# @param [String, Number] addr
|
24
|
+
# an IPv4 address specified either as a String or Number.
|
25
|
+
#
|
26
|
+
# @param [Number] prefixlen
|
27
|
+
# masking IPv4 address with given prefixlen.
|
28
|
+
#
|
29
|
+
# @raise [ArgumentError] invalid address if supplied argument is invalid
|
30
|
+
# IPv4 address.
|
31
|
+
#
|
32
|
+
# @return [IP] self
|
33
|
+
# a proxy to IPAddr.
|
34
|
+
#
|
35
|
+
def initialize addr, prefixlen = 32
|
36
|
+
@prefixlen = prefixlen
|
37
|
+
case addr
|
38
|
+
when Integer
|
39
|
+
@value = IPAddr.new( addr, Socket::AF_INET )
|
40
|
+
when String
|
41
|
+
@value = IPAddr.new( addr )
|
42
|
+
else
|
43
|
+
raise TypeError, "Invalid IP address: #{ addr.inspect }"
|
44
|
+
end
|
45
|
+
if prefixlen < 32
|
46
|
+
@value = @value.mask( prefixlen )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
#
|
52
|
+
# @return [String] the IPv4 address in its text representation.
|
53
|
+
#
|
54
|
+
def to_s
|
55
|
+
@value.to_s
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
#
|
60
|
+
# @return [Number] the IPv4 address in its numeric representation.
|
61
|
+
#
|
62
|
+
def to_i
|
63
|
+
@value.to_i
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
def == other
|
68
|
+
to_s == other.to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
#
|
73
|
+
# @return [Array]
|
74
|
+
# an array of decimal numbers converted from IP address.
|
75
|
+
#
|
76
|
+
def to_a
|
77
|
+
to_s.split( "." ).collect do | each |
|
78
|
+
each.to_i
|
79
|
+
end
|
80
|
+
end
|
81
|
+
alias :to_array :to_a
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
### Local variables:
|
87
|
+
### mode: Ruby
|
88
|
+
### coding: utf-8-unix
|
89
|
+
### indent-tabs-mode: nil
|
90
|
+
### End:
|
data/lib/pio/lldp.rb
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
require "rubygems"
|
2
|
-
|
3
|
-
require "bindata"
|
4
1
|
require "forwardable"
|
5
2
|
require "pio/lldp/frame"
|
6
3
|
|
@@ -8,27 +5,92 @@ require "pio/lldp/frame"
|
|
8
5
|
module Pio
|
9
6
|
# LLDP frame parser and generator.
|
10
7
|
class Lldp
|
8
|
+
# User options for creating an LLDP frame.
|
9
|
+
class Options
|
10
|
+
def initialize options
|
11
|
+
@options = options
|
12
|
+
|
13
|
+
unless dpid
|
14
|
+
raise TypeError, "Invalid DPID: #{ dpid.inspect }"
|
15
|
+
end
|
16
|
+
unless port_id
|
17
|
+
raise TypeError, "Invalid port number: #{ port_id.inspect }"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def to_hash
|
23
|
+
{
|
24
|
+
:destination_mac => Mac.new( destination_mac ).to_a,
|
25
|
+
:source_mac => Mac.new( source_mac ).to_a,
|
26
|
+
:chassis_id => dpid,
|
27
|
+
:port_id => port_id
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
##########################################################################
|
33
|
+
private
|
34
|
+
##########################################################################
|
35
|
+
|
36
|
+
|
37
|
+
def dpid
|
38
|
+
@options[ :dpid ]
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def port_id
|
43
|
+
@options[ :port_number ]
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def destination_mac
|
48
|
+
@options[ :destination_mac ] || "01:80:c2:00:00:0e"
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def source_mac
|
53
|
+
@options[ :source_mac ] || "01:02:03:04:05:06"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
11
58
|
extend Forwardable
|
12
59
|
|
13
60
|
|
14
61
|
def self.read raw_data
|
15
|
-
|
16
|
-
|
62
|
+
begin
|
63
|
+
frame = Frame.read( raw_data )
|
64
|
+
rescue
|
65
|
+
raise Pio::ParseError, $!.message
|
66
|
+
end
|
67
|
+
lldp = allocate
|
68
|
+
lldp.instance_variable_set :@frame, frame
|
69
|
+
lldp
|
17
70
|
end
|
18
71
|
|
19
72
|
|
20
|
-
def initialize
|
21
|
-
@frame = Frame.new
|
22
|
-
@frame.destination_mac = destination_mac
|
23
|
-
@frame.source_mac = "11:22:33:44:55:66" # FIXME
|
24
|
-
@frame.chassis_id = dpid
|
25
|
-
@frame.port_id = port_number
|
73
|
+
def initialize options
|
74
|
+
@frame = Frame.new( Options.new( options ).to_hash )
|
26
75
|
end
|
27
76
|
|
28
77
|
|
78
|
+
def_delegator :@frame, :destination_mac
|
79
|
+
def_delegator :@frame, :source_mac
|
80
|
+
def_delegator :@frame, :ether_type
|
81
|
+
def_delegator :@frame, :chassis_id
|
29
82
|
def_delegator :@frame, :dpid
|
30
83
|
def_delegator :@frame, :optional_tlv
|
31
|
-
def_delegator :@frame, :port_id
|
84
|
+
def_delegator :@frame, :port_id
|
85
|
+
alias :port_number :port_id
|
86
|
+
def_delegator :@frame, :ttl
|
87
|
+
def_delegator :@frame, :port_description
|
88
|
+
def_delegator :@frame, :system_name
|
89
|
+
def_delegator :@frame, :system_description
|
90
|
+
def_delegator :@frame, :system_capabilities
|
91
|
+
def_delegator :@frame, :enabled_capabilities
|
92
|
+
def_delegator :@frame, :management_address
|
93
|
+
def_delegator :@frame, :organizationally_specific
|
32
94
|
|
33
95
|
|
34
96
|
def to_binary
|
data/lib/pio/lldp/frame.rb
CHANGED
@@ -2,21 +2,21 @@ require "rubygems"
|
|
2
2
|
require "bindata"
|
3
3
|
|
4
4
|
require "pio/lldp/chassis-id-tlv"
|
5
|
-
require "pio/lldp/mac-address"
|
6
5
|
require "pio/lldp/optional-tlv"
|
7
6
|
require "pio/lldp/port-id-tlv"
|
8
7
|
require "pio/lldp/ttl-tlv"
|
8
|
+
require "pio/type/ethernet-header"
|
9
9
|
|
10
10
|
|
11
11
|
module Pio
|
12
12
|
class Lldp
|
13
13
|
# LLDP frame
|
14
14
|
class Frame < BinData::Record
|
15
|
+
extend Type::EthernetHeader
|
16
|
+
|
15
17
|
endian :big
|
16
18
|
|
17
|
-
|
18
|
-
mac_address :source_mac
|
19
|
-
uint16 :ethertype, :value => 0x88cc
|
19
|
+
ethernet_header :ether_type => 0x88cc
|
20
20
|
chassis_id_tlv :chassis_id
|
21
21
|
port_id_tlv :port_id
|
22
22
|
ttl_tlv :ttl, :initial_value => 120
|
@@ -26,6 +26,55 @@ module Pio
|
|
26
26
|
def dpid
|
27
27
|
chassis_id
|
28
28
|
end
|
29
|
+
|
30
|
+
|
31
|
+
def port_description
|
32
|
+
get_tlv_field 4, "port_description"
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def system_name
|
37
|
+
get_tlv_field 5, "system_name"
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def system_description
|
42
|
+
get_tlv_field 6, "system_description"
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def system_capabilities
|
47
|
+
get_tlv 7
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def management_address
|
52
|
+
get_tlv_field 8, "management_address"
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def organizationally_specific
|
57
|
+
get_tlv 127
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
##########################################################################
|
62
|
+
private
|
63
|
+
##########################################################################
|
64
|
+
|
65
|
+
|
66
|
+
def get_tlv tlv_type
|
67
|
+
tlv = optional_tlv.find do | each |
|
68
|
+
each[ "tlv_type" ] == tlv_type
|
69
|
+
end
|
70
|
+
tlv[ "tlv_value" ] if tlv
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def get_tlv_field tlv_type, name
|
75
|
+
tlv = get_tlv( tlv_type )
|
76
|
+
tlv[ name ] if tlv
|
77
|
+
end
|
29
78
|
end
|
30
79
|
end
|
31
80
|
end
|
@@ -8,7 +8,13 @@ module Pio
|
|
8
8
|
class ManagementAddressValue < BinData::Record
|
9
9
|
endian :big
|
10
10
|
|
11
|
-
|
11
|
+
uint8 :string_length
|
12
|
+
uint8 :subtype
|
13
|
+
string :management_address, :read_length => lambda { string_length - 1 }
|
14
|
+
uint8 :interface_numbering_subtype
|
15
|
+
uint32 :interface_number
|
16
|
+
uint8 :oid_string_length
|
17
|
+
string :object_identifier, :read_length => lambda { oid_string_length }
|
12
18
|
end
|
13
19
|
end
|
14
20
|
end
|
@@ -3,6 +3,7 @@ require "pio/lldp/system-name-value"
|
|
3
3
|
require "pio/lldp/system-description-value"
|
4
4
|
require "pio/lldp/system-capabilities-value"
|
5
5
|
require "pio/lldp/management-address-value"
|
6
|
+
require "pio/lldp/organizationally-specific-value"
|
6
7
|
require "pio/lldp/end-of-lldpdu-value"
|
7
8
|
|
8
9
|
|
@@ -24,6 +25,7 @@ module Pio
|
|
24
25
|
system_description_value 6
|
25
26
|
system_capabilities_value 7
|
26
27
|
management_address_value 8
|
28
|
+
organizationally_specific_value 127
|
27
29
|
string "unknown"
|
28
30
|
end
|
29
31
|
|
@@ -54,7 +56,7 @@ module Pio
|
|
54
56
|
|
55
57
|
def optional_tlv?
|
56
58
|
tmp_tlv_type = tlv_type
|
57
|
-
4 <= tmp_tlv_type and tmp_tlv_type <=
|
59
|
+
4 <= tmp_tlv_type and tmp_tlv_type <= 127
|
58
60
|
end
|
59
61
|
|
60
62
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "bindata"
|
3
|
+
|
4
|
+
|
5
|
+
module Pio
|
6
|
+
class Lldp
|
7
|
+
# The V of Organizationally specfic TLV.
|
8
|
+
class OrganizationallySpecificValue < BinData::Record
|
9
|
+
endian :big
|
10
|
+
|
11
|
+
uint24be :oui
|
12
|
+
uint8 :subtype
|
13
|
+
stringz :information
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
### Local variables:
|
20
|
+
### mode: Ruby
|
21
|
+
### coding: utf-8-unix
|
22
|
+
### indent-tabs-mode: nil
|
23
|
+
### End:
|
data/lib/pio/mac.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
|
4
|
+
module Pio
|
5
|
+
#
|
6
|
+
# Ethernet address class
|
7
|
+
#
|
8
|
+
class Mac
|
9
|
+
extend Forwardable
|
10
|
+
def_delegator :@value, :hash
|
11
|
+
|
12
|
+
|
13
|
+
#
|
14
|
+
# Returns an Ethernet address in its numeric presentation.
|
15
|
+
#
|
16
|
+
# @example
|
17
|
+
# Mac.new("11:22:33:44:55:66") #=> 18838586676582
|
18
|
+
#
|
19
|
+
# @return [Number] the Ethernet address in numeric format
|
20
|
+
#
|
21
|
+
attr_reader :value
|
22
|
+
|
23
|
+
|
24
|
+
#
|
25
|
+
# Creates a {Mac} instance that encapsulates Ethernet addresses.
|
26
|
+
#
|
27
|
+
# @overload initialize(value)
|
28
|
+
#
|
29
|
+
# @param [String,Integer] value
|
30
|
+
# the Ethernet address to set to.
|
31
|
+
#
|
32
|
+
# @example address as a hexadecimal string
|
33
|
+
# Mac.new("11:22:33:44:55:66")
|
34
|
+
#
|
35
|
+
# @example address as a hexadecimal number
|
36
|
+
# Mac.new(0xffffffffffff)
|
37
|
+
#
|
38
|
+
# @raise [ArgumentError] if invalid format is detected.
|
39
|
+
# @raise [TypeError] if supplied argument is not a String or Integer.
|
40
|
+
#
|
41
|
+
def initialize value
|
42
|
+
case value
|
43
|
+
when String
|
44
|
+
@value = create_from( value )
|
45
|
+
when Integer
|
46
|
+
@value = value
|
47
|
+
validate_value_range
|
48
|
+
when Mac
|
49
|
+
@value = create_from( value.to_s )
|
50
|
+
else
|
51
|
+
raise TypeError, "Invalid MAC address: #{ value.inspect }"
|
52
|
+
end
|
53
|
+
@string = string_format
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
#
|
58
|
+
# Returns the Ethernet address as 6 pairs of hexadecimal digits
|
59
|
+
# delimited by colons.
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# Mac.new(18838586676582).to_s #=> "11:22:33:44:55:66"
|
63
|
+
#
|
64
|
+
# @return [String] the Ethernet address in String format
|
65
|
+
#
|
66
|
+
def to_s
|
67
|
+
@string
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
#
|
72
|
+
# Returns an array of decimal numbers converted from Ethernet's
|
73
|
+
# address string format.
|
74
|
+
#
|
75
|
+
# @example
|
76
|
+
# Mac.new("11:22:33:44:55:66").to_a #=> [ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 ]
|
77
|
+
#
|
78
|
+
# @return [Array] the Ethernet address in Array format
|
79
|
+
#
|
80
|
+
def to_a
|
81
|
+
@string.split( ":" ).collect do | each |
|
82
|
+
each.hex
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
#
|
88
|
+
# @private
|
89
|
+
#
|
90
|
+
def == other
|
91
|
+
to_s == other.to_s
|
92
|
+
end
|
93
|
+
alias :eql? :==
|
94
|
+
|
95
|
+
|
96
|
+
#
|
97
|
+
# Returns true if Ethernet address is a multicast address.
|
98
|
+
#
|
99
|
+
# @example
|
100
|
+
# Mac.new("01:00:00:00:00:00").multicast? #=> true
|
101
|
+
# Mac.new("00:00:00:00:00:00").multicast? #=> false
|
102
|
+
#
|
103
|
+
# @return [Boolean] whether the Ethernet address is multicast
|
104
|
+
#
|
105
|
+
def multicast?
|
106
|
+
to_a[ 0 ] & 1 == 1
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
#
|
111
|
+
# Returns true if Ethernet address is a broadcast address.
|
112
|
+
#
|
113
|
+
# @example
|
114
|
+
# Mac.new("ff:ff:ff:ff:ff:ff").broadcast? #=> true
|
115
|
+
#
|
116
|
+
# @return [Boolean] whether the Ethernet address is broadcast
|
117
|
+
#
|
118
|
+
def broadcast?
|
119
|
+
to_a.all? { | each | each == 0xff }
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
################################################################################
|
124
|
+
private
|
125
|
+
################################################################################
|
126
|
+
|
127
|
+
|
128
|
+
def create_from string
|
129
|
+
octet_regex = "[0-9a-fA-F][0-9a-fA-F]"
|
130
|
+
if /^(#{ octet_regex }:){5}(#{ octet_regex })$/=~ string
|
131
|
+
string.gsub( ":", "" ).hex
|
132
|
+
else
|
133
|
+
raise ArgumentError, %{Invalid MAC address: "#{ string }"}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def validate_value_range
|
139
|
+
unless ( @value >= 0 and @value <= 0xffffffffffff )
|
140
|
+
raise ArgumentError, "Invalid MAC address: #{ @value }"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
def string_format
|
146
|
+
sprintf( "%012x", @value ).unpack( "a2" * 6 ).join( ":" )
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
### Local variables:
|
153
|
+
### mode: Ruby
|
154
|
+
### coding: utf-8-unix
|
155
|
+
### indent-tabs-mode: nil
|
156
|
+
### End:
|