pio 0.9.0 → 0.10.0

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