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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +36 -0
  4. data/examples/packet_out_new.rb +18 -0
  5. data/examples/packet_out_read.rb +6 -0
  6. data/features/packet_out_read.feature +5 -0
  7. data/lib/pio.rb +1 -0
  8. data/lib/pio/echo.rb +5 -6
  9. data/lib/pio/echo/message.rb +39 -13
  10. data/lib/pio/echo/reply.rb +1 -6
  11. data/lib/pio/echo/request.rb +3 -5
  12. data/lib/pio/enqueue.rb +62 -0
  13. data/lib/pio/features.rb +5 -15
  14. data/lib/pio/features/reply.rb +28 -48
  15. data/lib/pio/features/request.rb +11 -8
  16. data/lib/pio/hello.rb +24 -2
  17. data/lib/pio/monkey_patch/integer.rb +6 -0
  18. data/lib/pio/monkey_patch/integer/ranges.rb +22 -0
  19. data/lib/pio/open_flow.rb +1 -0
  20. data/lib/pio/open_flow/flags.rb +40 -26
  21. data/lib/pio/open_flow/message.rb +28 -0
  22. data/lib/pio/open_flow/parser.rb +22 -0
  23. data/lib/pio/open_flow/phy_port.rb +34 -50
  24. data/lib/pio/open_flow/port_number.rb +39 -0
  25. data/lib/pio/open_flow/type.rb +1 -0
  26. data/lib/pio/packet_in.rb +42 -9
  27. data/lib/pio/packet_out.rb +102 -0
  28. data/lib/pio/send_out_port.rb +74 -0
  29. data/lib/pio/set_eth_addr.rb +52 -0
  30. data/lib/pio/set_ip_addr.rb +49 -0
  31. data/lib/pio/set_ip_tos.rb +42 -0
  32. data/lib/pio/set_transport_port.rb +58 -0
  33. data/lib/pio/set_vlan.rb +37 -0
  34. data/lib/pio/set_vlan_priority.rb +18 -0
  35. data/lib/pio/set_vlan_vid.rb +30 -0
  36. data/lib/pio/strip_vlan_header.rb +19 -0
  37. data/lib/pio/type/ip_address.rb +11 -2
  38. data/lib/pio/type/ipv4_header.rb +1 -0
  39. data/lib/pio/type/mac_address.rb +2 -0
  40. data/lib/pio/version.rb +1 -1
  41. data/pio.gemspec +4 -4
  42. data/spec/pio/echo/reply_spec.rb +1 -4
  43. data/spec/pio/echo/request_spec.rb +5 -8
  44. data/spec/pio/enqueue_spec.rb +67 -0
  45. data/spec/pio/features/request_spec.rb +4 -4
  46. data/spec/pio/features_spec.rb +1 -2
  47. data/spec/pio/hello_spec.rb +1 -4
  48. data/spec/pio/packet_out_spec.rb +290 -0
  49. data/spec/pio/send_out_port_spec.rb +121 -0
  50. data/spec/pio/set_eth_dst_addr_spec.rb +28 -0
  51. data/spec/pio/set_eth_src_addr_spec.rb +28 -0
  52. data/spec/pio/set_ip_dst_addr_spec.rb +25 -0
  53. data/spec/pio/set_ip_src_addr_spec.rb +25 -0
  54. data/spec/pio/set_ip_tos_spec.rb +30 -0
  55. data/spec/pio/set_transport_dst_port_spec.rb +42 -0
  56. data/spec/pio/set_transport_src_port_spec.rb +42 -0
  57. data/spec/pio/set_vlan_priority_spec.rb +42 -0
  58. data/spec/pio/set_vlan_vid_spec.rb +42 -0
  59. data/spec/pio/strip_vlan_header_spec.rb +20 -0
  60. metadata +55 -13
  61. data/lib/pio/echo/format.rb +0 -22
  62. data/lib/pio/hello/format.rb +0 -18
  63. 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 'pio/hello/format'
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,6 @@
1
+ require 'pio/monkey_patch/integer/ranges'
2
+
3
+ # Monkey patch to Ruby's Integer class.
4
+ class Integer
5
+ include MonkeyPatch::Integer::Ranges
6
+ 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
@@ -2,4 +2,5 @@ require 'pio/open_flow/flags'
2
2
  require 'pio/open_flow/message'
3
3
  require 'pio/open_flow/open_flow_header'
4
4
  require 'pio/open_flow/phy_port'
5
+ require 'pio/open_flow/port_number'
5
6
  require 'pio/open_flow/type'
@@ -1,38 +1,52 @@
1
1
  # bitmap functions.
2
2
  module Flags
3
- def flags(name, flags)
4
- def_field name
5
- def_get name, flags
6
- def_set name, flags
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
- def def_field(name)
10
- module_eval("uint32 :#{name}")
11
- end
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
- def def_get(name, flags)
14
- str = %{
15
- def get
16
- list = #{flags.inspect}
17
- list.each_with_object([]) do |(key, value), result|
18
- result << key if #{name} & value != 0
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
- def def_set(name, flags)
27
- str = %{
28
- def set(v)
29
- list = #{flags.inspect}
30
- v.each do |each|
31
- fail "Invalid state flag: \#{v}" unless list.keys.include?(each)
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 str
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
- # enum ofp_port_config
8
- class PortConfig < BinData::Primitive
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
- endian :big
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
@@ -8,6 +8,7 @@ module Pio
8
8
  FEATURES_REQUEST = 5
9
9
  FEATURES_REPLY = 6
10
10
  PACKET_IN = 10
11
+ PACKET_OUT = 13
11
12
  end
12
13
  end
13
14
  end
data/lib/pio/packet_in.rb CHANGED
@@ -1,9 +1,49 @@
1
- require 'pio/packet_in/format'
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