pio 0.1.1 → 0.2.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.
- 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:
|