pio 0.9.0 → 0.10.0
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/CHANGELOG.md +6 -0
- data/README.md +36 -0
- data/examples/packet_out_new.rb +18 -0
- data/examples/packet_out_read.rb +6 -0
- data/features/packet_out_read.feature +5 -0
- data/lib/pio.rb +1 -0
- data/lib/pio/echo.rb +5 -6
- data/lib/pio/echo/message.rb +39 -13
- data/lib/pio/echo/reply.rb +1 -6
- data/lib/pio/echo/request.rb +3 -5
- data/lib/pio/enqueue.rb +62 -0
- data/lib/pio/features.rb +5 -15
- data/lib/pio/features/reply.rb +28 -48
- data/lib/pio/features/request.rb +11 -8
- data/lib/pio/hello.rb +24 -2
- data/lib/pio/monkey_patch/integer.rb +6 -0
- data/lib/pio/monkey_patch/integer/ranges.rb +22 -0
- data/lib/pio/open_flow.rb +1 -0
- data/lib/pio/open_flow/flags.rb +40 -26
- data/lib/pio/open_flow/message.rb +28 -0
- data/lib/pio/open_flow/parser.rb +22 -0
- data/lib/pio/open_flow/phy_port.rb +34 -50
- data/lib/pio/open_flow/port_number.rb +39 -0
- data/lib/pio/open_flow/type.rb +1 -0
- data/lib/pio/packet_in.rb +42 -9
- data/lib/pio/packet_out.rb +102 -0
- data/lib/pio/send_out_port.rb +74 -0
- data/lib/pio/set_eth_addr.rb +52 -0
- data/lib/pio/set_ip_addr.rb +49 -0
- data/lib/pio/set_ip_tos.rb +42 -0
- data/lib/pio/set_transport_port.rb +58 -0
- data/lib/pio/set_vlan.rb +37 -0
- data/lib/pio/set_vlan_priority.rb +18 -0
- data/lib/pio/set_vlan_vid.rb +30 -0
- data/lib/pio/strip_vlan_header.rb +19 -0
- data/lib/pio/type/ip_address.rb +11 -2
- data/lib/pio/type/ipv4_header.rb +1 -0
- data/lib/pio/type/mac_address.rb +2 -0
- data/lib/pio/version.rb +1 -1
- data/pio.gemspec +4 -4
- data/spec/pio/echo/reply_spec.rb +1 -4
- data/spec/pio/echo/request_spec.rb +5 -8
- data/spec/pio/enqueue_spec.rb +67 -0
- data/spec/pio/features/request_spec.rb +4 -4
- data/spec/pio/features_spec.rb +1 -2
- data/spec/pio/hello_spec.rb +1 -4
- data/spec/pio/packet_out_spec.rb +290 -0
- data/spec/pio/send_out_port_spec.rb +121 -0
- data/spec/pio/set_eth_dst_addr_spec.rb +28 -0
- data/spec/pio/set_eth_src_addr_spec.rb +28 -0
- data/spec/pio/set_ip_dst_addr_spec.rb +25 -0
- data/spec/pio/set_ip_src_addr_spec.rb +25 -0
- data/spec/pio/set_ip_tos_spec.rb +30 -0
- data/spec/pio/set_transport_dst_port_spec.rb +42 -0
- data/spec/pio/set_transport_src_port_spec.rb +42 -0
- data/spec/pio/set_vlan_priority_spec.rb +42 -0
- data/spec/pio/set_vlan_vid_spec.rb +42 -0
- data/spec/pio/strip_vlan_header_spec.rb +20 -0
- metadata +55 -13
- data/lib/pio/echo/format.rb +0 -22
- data/lib/pio/hello/format.rb +0 -18
- data/lib/pio/packet_in/format.rb +0 -55
data/lib/pio/hello.rb
CHANGED
@@ -1,11 +1,28 @@
|
|
1
1
|
require 'English'
|
2
|
-
require '
|
3
|
-
require 'pio/parse_error'
|
2
|
+
require 'bindata'
|
4
3
|
require 'pio/open_flow'
|
4
|
+
require 'pio/parse_error'
|
5
5
|
|
6
6
|
module Pio
|
7
7
|
# OpenFlow 1.0 Hello message
|
8
8
|
class Hello < Pio::OpenFlow::Message
|
9
|
+
# Message body of Hello
|
10
|
+
class Body < BinData::Record
|
11
|
+
endian :big
|
12
|
+
|
13
|
+
string :body # ignored
|
14
|
+
|
15
|
+
def length
|
16
|
+
0
|
17
|
+
end
|
18
|
+
|
19
|
+
def empty?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def_format Pio::OpenFlow::Type::HELLO
|
25
|
+
|
9
26
|
# Parses +raw_data+ binary string into a Hello message object.
|
10
27
|
#
|
11
28
|
# @example
|
@@ -41,6 +58,7 @@ module Pio
|
|
41
58
|
# @option user_options [Number] :xid An alias to transaction_id.
|
42
59
|
#
|
43
60
|
# @reek This method smells of :reek:FeatureEnvy
|
61
|
+
# rubocop:disable MethodLength
|
44
62
|
def initialize(user_options = {})
|
45
63
|
options = if user_options.respond_to?(:to_i)
|
46
64
|
{ transaction_id: user_options.to_i }
|
@@ -50,7 +68,11 @@ module Pio
|
|
50
68
|
else
|
51
69
|
fail TypeError
|
52
70
|
end
|
71
|
+
if options[:transaction_id] >= 2**32
|
72
|
+
fail ArgumentError, 'Transaction ID >= 2**32'
|
73
|
+
end
|
53
74
|
@format = Format.new(open_flow_header: options)
|
54
75
|
end
|
76
|
+
# rubocop:enable MethodLength
|
55
77
|
end
|
56
78
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MonkeyPatch
|
2
|
+
module Integer
|
3
|
+
# Defines Integer#nbit? methods.
|
4
|
+
module Ranges
|
5
|
+
def unsigned_8bit?
|
6
|
+
_within_range? 8
|
7
|
+
end
|
8
|
+
|
9
|
+
def unsigned_16bit?
|
10
|
+
_within_range? 16
|
11
|
+
end
|
12
|
+
|
13
|
+
def unsigned_32bit?
|
14
|
+
_within_range? 32
|
15
|
+
end
|
16
|
+
|
17
|
+
def _within_range?(nbit)
|
18
|
+
(0 <= self) && (self < 2**nbit)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/pio/open_flow.rb
CHANGED
data/lib/pio/open_flow/flags.rb
CHANGED
@@ -1,38 +1,52 @@
|
|
1
1
|
# bitmap functions.
|
2
2
|
module Flags
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
# rubocop:disable MethodLength
|
4
|
+
# This method smells of :reek:TooManyStatements
|
5
|
+
def def_flags(name, flags)
|
6
|
+
flag_value = case flags
|
7
|
+
when Array
|
8
|
+
shift = 0
|
9
|
+
flags.each_with_object({}) do |each, result|
|
10
|
+
result[each] = 1 << shift
|
11
|
+
shift += 1
|
12
|
+
result
|
13
|
+
end
|
14
|
+
when Hash
|
15
|
+
flags
|
16
|
+
end
|
17
|
+
_def_class name, flag_value
|
7
18
|
end
|
19
|
+
# rubocop:enable MethodLength
|
8
20
|
|
9
|
-
|
10
|
-
|
11
|
-
|
21
|
+
# rubocop:disable MethodLength
|
22
|
+
def _def_class(name, flags)
|
23
|
+
klass_name = name.to_s.split('_').map(&:capitalize).join
|
24
|
+
flags_hash = flags.inspect
|
25
|
+
|
26
|
+
code = %{
|
27
|
+
class #{klass_name} < BinData::Primitive
|
28
|
+
endian :big
|
29
|
+
|
30
|
+
uint32 :#{name}
|
12
31
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
result
|
32
|
+
def get
|
33
|
+
list = #{flags_hash}
|
34
|
+
list.each_with_object([]) do |(key, value), result|
|
35
|
+
result << key if #{name} & value != 0
|
36
|
+
result
|
37
|
+
end
|
20
38
|
end
|
21
|
-
end
|
22
|
-
}
|
23
|
-
module_eval str
|
24
|
-
end
|
25
39
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
40
|
+
def set(v)
|
41
|
+
list = #{flags_hash}
|
42
|
+
v.each do |each|
|
43
|
+
fail "Invalid state flag: \#{v}" unless list.keys.include?(each)
|
44
|
+
end
|
45
|
+
self.#{name} = v.empty? ? 0 : v.map { |each| list[each] }.inject(:|)
|
32
46
|
end
|
33
|
-
self.#{name} = v.empty? ? 0 : v.map { |each| list[each] }.inject(:|)
|
34
47
|
end
|
35
48
|
}
|
36
|
-
module_eval
|
49
|
+
module_eval code
|
37
50
|
end
|
51
|
+
# rubocop:enable MethodLength
|
38
52
|
end
|
@@ -15,6 +15,34 @@ module Pio
|
|
15
15
|
def_delegators :@format, :body
|
16
16
|
def_delegator :@format, :body, :user_data
|
17
17
|
def_delegator :@format, :to_binary_s, :to_binary
|
18
|
+
|
19
|
+
# rubocop:disable MethodLength
|
20
|
+
def self.def_format(message_type)
|
21
|
+
str = %(
|
22
|
+
class Format < BinData::Record
|
23
|
+
endian :big
|
24
|
+
|
25
|
+
open_flow_header :open_flow_header,
|
26
|
+
message_type_value: #{message_type}
|
27
|
+
virtual assert: -> do
|
28
|
+
open_flow_header.message_type == #{message_type}
|
29
|
+
end
|
30
|
+
|
31
|
+
body :body
|
32
|
+
end
|
33
|
+
)
|
34
|
+
module_eval str
|
35
|
+
end
|
36
|
+
# rubocop:enable MethodLength
|
37
|
+
|
38
|
+
# @reek This method smells of :reek:FeatureEnvy
|
39
|
+
def initialize(user_options)
|
40
|
+
header_options = { transaction_id: user_options[:transaction_id] ||
|
41
|
+
user_options[:xid] || 0 }
|
42
|
+
format_klass = self.class.const_get(:Format)
|
43
|
+
@format = format_klass.new(open_flow_header: header_options,
|
44
|
+
body: user_options)
|
45
|
+
end
|
18
46
|
end
|
19
47
|
end
|
20
48
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'pio/open_flow'
|
2
|
+
require 'pio/parse_error'
|
3
|
+
|
4
|
+
module Pio
|
5
|
+
module OpenFlow
|
6
|
+
# OpenFlow message parser interface.
|
7
|
+
module Parser
|
8
|
+
# This method smells of :reek:TooManyStatements
|
9
|
+
# This method smells of :reek:FeatureEnvy
|
10
|
+
def read(raw_data)
|
11
|
+
header = Pio::Type::OpenFlow::OpenFlowHeader.read(raw_data)
|
12
|
+
klass = const_get(:KLASS).fetch(header.message_type)
|
13
|
+
format = klass.const_get(:Format).read(raw_data)
|
14
|
+
message = klass.allocate
|
15
|
+
message.instance_variable_set :@format, format
|
16
|
+
message
|
17
|
+
rescue KeyError
|
18
|
+
raise Pio::ParseError, 'Invalid OpenFlow message.'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -4,59 +4,43 @@ require 'pio/type/mac_address'
|
|
4
4
|
module Pio
|
5
5
|
module Type
|
6
6
|
module OpenFlow
|
7
|
-
#
|
8
|
-
class
|
9
|
-
extend Flags
|
10
|
-
|
11
|
-
endian :big
|
12
|
-
|
13
|
-
flags :config,
|
14
|
-
port_down: 1 << 0,
|
15
|
-
no_stp: 1 << 1,
|
16
|
-
no_recv: 1 << 2,
|
17
|
-
no_recv_stp: 1 << 3,
|
18
|
-
no_flood: 1 << 4,
|
19
|
-
no_fwd: 1 << 5,
|
20
|
-
no_packet_in: 1 << 6
|
21
|
-
end
|
22
|
-
|
23
|
-
# enum ofp_port_state
|
24
|
-
class PortState < BinData::Primitive
|
25
|
-
extend Flags
|
26
|
-
|
27
|
-
endian :big
|
28
|
-
|
29
|
-
flags :state,
|
30
|
-
link_down: 1 << 0,
|
31
|
-
stp_listen: 0 << 8,
|
32
|
-
stp_learn: 1 << 8,
|
33
|
-
stp_forward: 2 << 8,
|
34
|
-
stp_block: 3 << 8
|
35
|
-
end
|
36
|
-
|
37
|
-
# enum ofp_port_features
|
38
|
-
class PortFeature < BinData::Primitive
|
7
|
+
# Description of a physical port
|
8
|
+
class PhyPort < BinData::Record
|
39
9
|
extend Flags
|
40
10
|
|
41
|
-
|
11
|
+
# enum ofp_port_config
|
12
|
+
def_flags :port_config,
|
13
|
+
[:port_down,
|
14
|
+
:no_stp,
|
15
|
+
:no_recv,
|
16
|
+
:no_recv_stp,
|
17
|
+
:no_flood,
|
18
|
+
:no_fwd,
|
19
|
+
:no_packet_in]
|
20
|
+
|
21
|
+
# enum ofp_port_state
|
22
|
+
def_flags :port_state,
|
23
|
+
link_down: 1 << 0,
|
24
|
+
stp_listen: 0 << 8,
|
25
|
+
stp_learn: 1 << 8,
|
26
|
+
stp_forward: 2 << 8,
|
27
|
+
stp_block: 3 << 8
|
28
|
+
|
29
|
+
# enum ofp_port_features
|
30
|
+
def_flags :port_feature,
|
31
|
+
[:port_10mb_hd,
|
32
|
+
:port_10mb_fd,
|
33
|
+
:port_100mb_hd,
|
34
|
+
:port_100mb_fd,
|
35
|
+
:port_1gb_hd,
|
36
|
+
:port_1gb_fd,
|
37
|
+
:port_10gb_fd,
|
38
|
+
:port_copper,
|
39
|
+
:port_fiber,
|
40
|
+
:port_autoneg,
|
41
|
+
:port_pause,
|
42
|
+
:port_pause_asym]
|
42
43
|
|
43
|
-
flags :features,
|
44
|
-
port_10mb_hd: 1 << 0,
|
45
|
-
port_10mb_fd: 1 << 1,
|
46
|
-
port_100mb_hd: 1 << 2,
|
47
|
-
port_100mb_fd: 1 << 3,
|
48
|
-
port_1gb_hd: 1 << 4,
|
49
|
-
port_1gb_fd: 1 << 5,
|
50
|
-
port_10gb_fd: 1 << 6,
|
51
|
-
port_copper: 1 << 7,
|
52
|
-
port_fiber: 1 << 8,
|
53
|
-
port_autoneg: 1 << 9,
|
54
|
-
port_pause: 1 << 10,
|
55
|
-
port_pause_asym: 1 << 11
|
56
|
-
end
|
57
|
-
|
58
|
-
# Description of a physical port
|
59
|
-
class PhyPort < BinData::Record
|
60
44
|
endian :big
|
61
45
|
|
62
46
|
uint16 :port_no
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Pio
|
2
|
+
# Port numbering.
|
3
|
+
class PortNumber < BinData::Primitive
|
4
|
+
NUMBERS = {
|
5
|
+
in_port: 0xfff8,
|
6
|
+
table: 0xfff9,
|
7
|
+
normal: 0xfffa,
|
8
|
+
flood: 0xfffb,
|
9
|
+
all: 0xfffc,
|
10
|
+
controller: 0xfffd,
|
11
|
+
local: 0xfffe,
|
12
|
+
none: 0xffff
|
13
|
+
}
|
14
|
+
MAX = 0xff00
|
15
|
+
|
16
|
+
endian :big
|
17
|
+
|
18
|
+
uint16 :port_number
|
19
|
+
|
20
|
+
def get
|
21
|
+
NUMBERS.invert.fetch(port_number)
|
22
|
+
rescue KeyError
|
23
|
+
port_number
|
24
|
+
end
|
25
|
+
|
26
|
+
def set(value)
|
27
|
+
if NUMBERS.key?(value)
|
28
|
+
self.port_number = NUMBERS.fetch(value)
|
29
|
+
else
|
30
|
+
port_number = value.to_i
|
31
|
+
fail ArgumentError, 'The port_number should be > 0' if port_number < 1
|
32
|
+
if port_number >= MAX
|
33
|
+
fail ArgumentError, 'The port_number should be < 0xff00'
|
34
|
+
end
|
35
|
+
self.port_number = port_number
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/pio/open_flow/type.rb
CHANGED
data/lib/pio/packet_in.rb
CHANGED
@@ -1,9 +1,49 @@
|
|
1
|
-
require '
|
1
|
+
require 'bindata'
|
2
|
+
require 'pio/open_flow'
|
2
3
|
require 'pio/parse_error'
|
3
4
|
|
4
5
|
module Pio
|
5
6
|
# OpenFlow 1.0 Packet-In message
|
6
|
-
class PacketIn
|
7
|
+
class PacketIn < Pio::OpenFlow::Message
|
8
|
+
# Why is this packet being sent to the controller?
|
9
|
+
# (enum ofp_packet_in_reason)
|
10
|
+
class Reason < BinData::Primitive
|
11
|
+
REASONS = { no_match: 0, action: 1 }
|
12
|
+
|
13
|
+
uint8 :reason
|
14
|
+
|
15
|
+
def get
|
16
|
+
REASONS.invert.fetch(reason)
|
17
|
+
end
|
18
|
+
|
19
|
+
def set(value)
|
20
|
+
self.reason = REASONS.fetch(value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Message body of Packet-In.
|
25
|
+
class Body < BinData::Record
|
26
|
+
endian :big
|
27
|
+
|
28
|
+
uint32 :buffer_id
|
29
|
+
uint16 :total_len, value: -> { data.length }
|
30
|
+
uint16 :in_port
|
31
|
+
reason :reason
|
32
|
+
uint8 :padding
|
33
|
+
hide :padding
|
34
|
+
string :data, read_length: :total_len
|
35
|
+
|
36
|
+
def empty?
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def length
|
41
|
+
10 + data.length
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def_format Pio::OpenFlow::Type::PACKET_IN
|
46
|
+
|
7
47
|
def self.read(raw_data)
|
8
48
|
packet_in = allocate
|
9
49
|
packet_in.instance_variable_set :@format, Format.read(raw_data)
|
@@ -17,12 +57,5 @@ module Pio
|
|
17
57
|
def_delegators :body, :in_port
|
18
58
|
def_delegators :body, :reason
|
19
59
|
def_delegators :body, :data
|
20
|
-
|
21
|
-
# @reek This method smells of :reek:FeatureEnvy
|
22
|
-
def initialize(user_options)
|
23
|
-
header_options = { transaction_id: user_options[:transaction_id] ||
|
24
|
-
user_options[:xid] || 0 }
|
25
|
-
@format = Format.new(open_flow_header: header_options, body: user_options)
|
26
|
-
end
|
27
60
|
end
|
28
61
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'bindata'
|
2
|
+
require 'pio/enqueue'
|
3
|
+
require 'pio/open_flow'
|
4
|
+
require 'pio/send_out_port'
|
5
|
+
require 'pio/set_eth_addr'
|
6
|
+
require 'pio/set_ip_addr'
|
7
|
+
require 'pio/set_ip_tos'
|
8
|
+
require 'pio/set_transport_port'
|
9
|
+
require 'pio/set_vlan_priority'
|
10
|
+
require 'pio/set_vlan_vid'
|
11
|
+
require 'pio/strip_vlan_header'
|
12
|
+
|
13
|
+
module Pio
|
14
|
+
# OpenFlow 1.0 Packet-Out message
|
15
|
+
class PacketOut < Pio::OpenFlow::Message
|
16
|
+
# Actions list.
|
17
|
+
class Actions < BinData::Primitive
|
18
|
+
ACTION_CLASS = {
|
19
|
+
0 => Pio::SendOutPort,
|
20
|
+
1 => Pio::SetVlanVid,
|
21
|
+
2 => Pio::SetVlanPriority,
|
22
|
+
3 => Pio::StripVlanHeader,
|
23
|
+
4 => Pio::SetEthSrcAddr,
|
24
|
+
5 => Pio::SetEthDstAddr,
|
25
|
+
6 => Pio::SetIpSrcAddr,
|
26
|
+
7 => Pio::SetIpDstAddr,
|
27
|
+
8 => Pio::SetIpTos,
|
28
|
+
9 => Pio::SetTransportSrcPort,
|
29
|
+
10 => Pio::SetTransportDstPort,
|
30
|
+
11 => Pio::Enqueue
|
31
|
+
}
|
32
|
+
|
33
|
+
endian :big
|
34
|
+
|
35
|
+
uint16 :actions_len, initial_value: -> { binary.length }
|
36
|
+
string :binary, read_length: :actions_len
|
37
|
+
|
38
|
+
def set(value)
|
39
|
+
self.binary = [value].flatten.map(&:to_binary).join
|
40
|
+
end
|
41
|
+
|
42
|
+
# rubocop:disable MethodLength
|
43
|
+
# This method smells of :reek:TooManyStatements
|
44
|
+
def get
|
45
|
+
actions = []
|
46
|
+
tmp = binary
|
47
|
+
while tmp.length > 0
|
48
|
+
type = BinData::Uint16be.read(tmp)
|
49
|
+
begin
|
50
|
+
action = ACTION_CLASS.fetch(type).read(tmp)
|
51
|
+
tmp = tmp[action.message_length..-1]
|
52
|
+
actions << action
|
53
|
+
rescue KeyError
|
54
|
+
raise "action #{type} is not supported." unless ACTION_CLASS[type]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
actions
|
58
|
+
end
|
59
|
+
# rubocop:enable MethodLength
|
60
|
+
|
61
|
+
def [](index)
|
62
|
+
get[index]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Message body of Packet-Out
|
67
|
+
class Body < BinData::Record
|
68
|
+
endian :big
|
69
|
+
|
70
|
+
uint32 :buffer_id
|
71
|
+
uint16 :in_port
|
72
|
+
actions :actions
|
73
|
+
rest :data
|
74
|
+
|
75
|
+
def empty?
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
def length
|
80
|
+
8 + actions_len + data.length
|
81
|
+
end
|
82
|
+
|
83
|
+
def actions_len
|
84
|
+
actions.actions_len
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def_format Pio::OpenFlow::Type::PACKET_OUT
|
89
|
+
|
90
|
+
def self.read(raw_data)
|
91
|
+
packet_out = allocate
|
92
|
+
packet_out.instance_variable_set :@format, Format.read(raw_data)
|
93
|
+
packet_out
|
94
|
+
end
|
95
|
+
|
96
|
+
def_delegators :body, :buffer_id
|
97
|
+
def_delegators :body, :in_port
|
98
|
+
def_delegators :body, :actions_len
|
99
|
+
def_delegators :body, :actions
|
100
|
+
def_delegators :body, :data
|
101
|
+
end
|
102
|
+
end
|