pio 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +33 -2
  4. data/examples/echo_read.rb +1 -1
  5. data/examples/features_read.rb +1 -1
  6. data/examples/flow_mod_new.rb +13 -0
  7. data/examples/flow_mod_read.rb +6 -0
  8. data/features/echo_read.feature +27 -3
  9. data/features/features_read.feature +46 -2
  10. data/features/flow_mod_read.feature +186 -0
  11. data/features/hello_read.feature +9 -0
  12. data/features/packet_data/flow_mod_add.raw +0 -0
  13. data/features/packet_data/flow_mod_delete.raw +0 -0
  14. data/features/packet_data/flow_mod_delete_strict.raw +0 -0
  15. data/features/packet_data/flow_mod_modify.raw +0 -0
  16. data/features/packet_data/flow_mod_modify_strict.raw +0 -0
  17. data/features/packet_in_read.feature +13 -0
  18. data/features/packet_out_read.feature +16 -0
  19. data/features/step_definitions/packet_data_steps.rb +10 -1
  20. data/lib/pio.rb +1 -0
  21. data/lib/pio/echo.rb +10 -8
  22. data/lib/pio/enqueue.rb +1 -1
  23. data/lib/pio/features.rb +64 -7
  24. data/lib/pio/flow_mod.rb +86 -0
  25. data/lib/pio/hello.rb +4 -74
  26. data/lib/pio/ipv4_address.rb +1 -1
  27. data/lib/pio/match.rb +167 -0
  28. data/lib/pio/open_flow.rb +1 -0
  29. data/lib/pio/open_flow/actions.rb +65 -0
  30. data/lib/pio/open_flow/flags.rb +12 -9
  31. data/lib/pio/open_flow/message.rb +105 -21
  32. data/lib/pio/open_flow/phy_port.rb +31 -27
  33. data/lib/pio/open_flow/type.rb +1 -0
  34. data/lib/pio/packet_in.rb +2 -12
  35. data/lib/pio/packet_out.rb +4 -74
  36. data/lib/pio/send_out_port.rb +1 -0
  37. data/lib/pio/type/ip_address.rb +2 -7
  38. data/lib/pio/version.rb +1 -1
  39. data/pio.gemspec +8 -8
  40. data/spec/pio/echo/reply_spec.rb +61 -6
  41. data/spec/pio/echo/request_spec.rb +61 -6
  42. data/spec/pio/features/reply_spec.rb +81 -4
  43. data/spec/pio/features/request_spec.rb +88 -41
  44. data/spec/pio/flow_mod_spec.rb +151 -0
  45. data/spec/pio/hello_spec.rb +73 -56
  46. data/spec/pio/match_spec.rb +194 -0
  47. data/spec/pio/packet_in_spec.rb +35 -38
  48. data/spec/pio/packet_out_spec.rb +243 -182
  49. data/spec/pio/wildcards_spec.rb +115 -0
  50. metadata +37 -30
  51. data/features/packet_data/echo.raw +0 -0
  52. data/lib/pio/echo/message.rb +0 -49
  53. data/lib/pio/echo/reply.rb +0 -41
  54. data/lib/pio/echo/request.rb +0 -43
  55. data/lib/pio/features/reply.rb +0 -88
  56. data/lib/pio/features/request.rb +0 -68
  57. data/lib/pio/open_flow/parser.rb +0 -22
  58. data/spec/pio/echo_spec.rb +0 -49
  59. data/spec/pio/features_spec.rb +0 -96
@@ -3,3 +3,19 @@ Feature: Pio::PacketOut.read
3
3
  Given a packet data file "packet_out.raw"
4
4
  When I try to parse the file with "PacketOut" class
5
5
  Then it should finish successfully
6
+ And the parsed data have the following field and value:
7
+ | field | value |
8
+ | class | Pio::PacketOut |
9
+ | ofp_version | 1 |
10
+ | message_type | 13 |
11
+ | message_length | 88 |
12
+ | transaction_id | 22 |
13
+ | xid | 22 |
14
+ | buffer_id | 4294967295 |
15
+ | in_port | 65535 |
16
+ | actions.length | 1 |
17
+ | actions.first.class | Pio::SendOutPort |
18
+ | actions.first.port_number | 2 |
19
+ | actions.first.max_len | 65535 |
20
+ | data.length | 64 |
21
+
@@ -14,7 +14,7 @@ end
14
14
  When(/^I try to parse the file with "(.*?)" class$/) do |parser|
15
15
  parser_klass = Pio.const_get(parser)
16
16
  if @raw
17
- parser_klass.read IO.read(@raw)
17
+ @result = parser_klass.read(IO.read(@raw))
18
18
  elsif @pcap
19
19
  File.open(@pcap) do |file|
20
20
  pcap = Pio::Pcap::Frame.read(file)
@@ -28,3 +28,12 @@ end
28
28
  Then(/^it should finish successfully$/) do
29
29
  # Noop.
30
30
  end
31
+
32
+ Then(/^the parsed data have the following field and value:$/) do |table|
33
+ table.hashes.each do |each|
34
+ output = each['field'].split('.').inject(@result) do |memo, method|
35
+ memo.__send__(method)
36
+ end
37
+ expect(output.to_s).to eq(each['value'])
38
+ end
39
+ end
data/lib/pio.rb CHANGED
@@ -4,6 +4,7 @@ require 'pio/arp'
4
4
  require 'pio/dhcp'
5
5
  require 'pio/echo'
6
6
  require 'pio/features'
7
+ require 'pio/flow_mod'
7
8
  require 'pio/hello'
8
9
  require 'pio/icmp'
9
10
  require 'pio/lldp'
data/lib/pio/echo.rb CHANGED
@@ -1,14 +1,16 @@
1
- require 'pio/echo/reply'
2
- require 'pio/echo/request'
3
1
  require 'pio/open_flow'
4
- require 'pio/open_flow/parser'
5
2
 
6
3
  module Pio
7
- # OpenFlow 1.0 Echo Request and Reply message parser.
8
- class Echo
9
- KLASS = { Pio::OpenFlow::Type::ECHO_REQUEST => Pio::Echo::Request,
10
- Pio::OpenFlow::Type::ECHO_REPLY => Pio::Echo::Reply }
4
+ # OpenFlow 1.0 Echo Request and Reply message.
5
+ module Echo
6
+ # @!parse
7
+ # # OpenFlow 1.0 Echo Request message.
8
+ # class Request < OpenFlow::Message; end
9
+ class Request < OpenFlow::Message.factory(OpenFlow::Type::ECHO_REQUEST); end
11
10
 
12
- extend Pio::OpenFlow::Parser
11
+ # @!parse
12
+ # # OpenFlow 1.0 Echo Reply message.
13
+ # class Reply < OpenFlow::Message; end
14
+ class Reply < OpenFlow::Message.factory(OpenFlow::Type::ECHO_REPLY); end
13
15
  end
14
16
  end
data/lib/pio/enqueue.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'bindata'
2
2
  require 'forwardable'
3
3
  require 'pio/monkey_patch/integer'
4
- require 'pio/open_flow'
4
+ require 'pio/open_flow/port_number'
5
5
 
6
6
  module Pio
7
7
  # An action to enqueue the packet on the specified queue attached to
data/lib/pio/features.rb CHANGED
@@ -1,14 +1,71 @@
1
- require 'pio/features/reply'
2
- require 'pio/features/request'
3
1
  require 'pio/open_flow'
4
- require 'pio/open_flow/parser'
5
2
 
6
3
  module Pio
7
- # OpenFlow 1.0 Features Request and Reply message parser.
4
+ # OpenFlow 1.0 Features Request and Reply message.
8
5
  class Features
9
- KLASS = { Pio::OpenFlow::Type::FEATURES_REQUEST => Pio::Features::Request,
10
- Pio::OpenFlow::Type::FEATURES_REPLY => Pio::Features::Reply }
6
+ # OpenFlow 1.0 Features Request message.
7
+ class Request < OpenFlow::Message.factory(OpenFlow::Type::FEATURES_REQUEST)
8
+ end
11
9
 
12
- extend Pio::OpenFlow::Parser
10
+ # OpenFlow 1.0 Features Reply message
11
+ class Reply < OpenFlow::Message.factory(OpenFlow::Type::FEATURES_REPLY)
12
+ # Message body of features reply.
13
+ class ReplyBody < BinData::Record
14
+ extend Flags
15
+
16
+ # enum ofp_capabilities
17
+ flags_32bit :capabilities,
18
+ [:flow_stats,
19
+ :table_stats,
20
+ :port_stats,
21
+ :stp,
22
+ :reserved,
23
+ :ip_reasm,
24
+ :queue_stats,
25
+ :arp_match_ip]
26
+
27
+ # enum ofp_action_type
28
+ flags_32bit :actions_flag,
29
+ [:output,
30
+ :set_vlan_vid,
31
+ :set_vlan_pcp,
32
+ :strip_vlan,
33
+ :set_dl_src,
34
+ :set_dl_dst,
35
+ :set_nw_src,
36
+ :set_nw_dst,
37
+ :set_nw_tos,
38
+ :set_tp_src,
39
+ :set_tp_dst,
40
+ :enqueue]
41
+
42
+ endian :big
43
+
44
+ uint64 :datapath_id
45
+ uint32 :n_buffers
46
+ uint8 :n_tables
47
+ uint24 :padding
48
+ hide :padding
49
+ capabilities :capabilities
50
+ actions_flag :actions
51
+ array :ports, type: :phy_port, read_until: :eof
52
+
53
+ def empty?
54
+ false
55
+ end
56
+
57
+ def length
58
+ 24 + ports.to_binary_s.length
59
+ end
60
+ end
61
+
62
+ def_delegators :body, :datapath_id
63
+ def_delegator :body, :datapath_id, :dpid
64
+ def_delegators :body, :n_buffers
65
+ def_delegators :body, :n_tables
66
+ def_delegators :body, :capabilities
67
+ def_delegators :body, :actions
68
+ def_delegators :body, :ports
69
+ end
13
70
  end
14
71
  end
@@ -0,0 +1,86 @@
1
+ require 'pio/match'
2
+ require 'pio/open_flow'
3
+
4
+ module Pio
5
+ # OpenFlow 1.0 flow setup and teardown message.
6
+ class FlowMod < OpenFlow::Message.factory(OpenFlow::Type::FLOW_MOD)
7
+ # enum ofp_flow_mod_command
8
+ class Command < BinData::Primitive
9
+ COMMANDS = {
10
+ add: 0,
11
+ modify: 1,
12
+ modify_strict: 2,
13
+ delete: 3,
14
+ delete_strict: 4
15
+ }
16
+
17
+ endian :big
18
+ uint16 :command
19
+
20
+ def get
21
+ COMMANDS.invert.fetch(command)
22
+ end
23
+
24
+ def set(value)
25
+ self.command = COMMANDS.fetch(value)
26
+ end
27
+ end
28
+
29
+ # Message body of FlowMod.
30
+ class FlowModBody < BinData::Record
31
+ # Pio::MatchFormat wrapper.
32
+ class Match < BinData::Primitive
33
+ endian :big
34
+
35
+ string :match, read_length: 40
36
+
37
+ def set(object)
38
+ self.match = object.to_binary_s
39
+ end
40
+
41
+ def get
42
+ Pio::Match.read match
43
+ end
44
+ end
45
+
46
+ extend Flags
47
+
48
+ flags_16bit :flags,
49
+ [:send_flow_rem,
50
+ :check_overwrap,
51
+ :emerg]
52
+
53
+ endian :big
54
+
55
+ match :match
56
+ uint64 :cookie
57
+ command :command
58
+ uint16 :idle_timeout
59
+ uint16 :hard_timeout
60
+ uint16 :priority
61
+ uint32 :buffer_id
62
+ uint16 :out_port
63
+ flags :flags
64
+ actions :actions, length: -> { open_flow_header.message_length - 72 }
65
+
66
+ def empty?
67
+ false
68
+ end
69
+
70
+ def length
71
+ 64 + actions.binary.length
72
+ end
73
+ end
74
+
75
+ def_delegators :body, :match
76
+ def_delegators :body, :cookie
77
+ def_delegators :body, :command
78
+ def_delegators :body, :idle_timeout
79
+ def_delegators :body, :hard_timeout
80
+ def_delegators :body, :priority
81
+ def_delegators :body, :buffer_id
82
+ def_delegators :body, :out_port
83
+ def_delegators :body, :flags
84
+ def_delegators :body, :actions
85
+ end
86
+ end
data/lib/pio/hello.rb CHANGED
@@ -1,78 +1,8 @@
1
- require 'English'
2
- require 'bindata'
3
1
  require 'pio/open_flow'
4
- require 'pio/parse_error'
5
2
 
6
3
  module Pio
7
- # OpenFlow 1.0 Hello message
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
-
26
- # Parses +raw_data+ binary string into a Hello message object.
27
- #
28
- # @example
29
- # Pio::Hello.read("\x01\x00\x00\b\x00\x00\x00\x00")
30
- # @return [Pio::Hello]
31
- def self.read(raw_data)
32
- hello = allocate
33
- hello.instance_variable_set :@format, Format.read(raw_data)
34
- hello
35
- rescue BinData::ValidityError
36
- raise Pio::ParseError, $ERROR_INFO.message
37
- end
38
-
39
- # Creates a Hello OpenFlow message.
40
- #
41
- # @overload initialize()
42
- # @example
43
- # Pio::Hello.new
44
- #
45
- # @overload initialize(transaction_id)
46
- # @example
47
- # Pio::Hello.new(123)
48
- # @param [Number] transaction_id
49
- # An unsigned 32-bit integer number associated with this
50
- # message.
51
- #
52
- # @overload initialize(user_options)
53
- # @example
54
- # Pio::Hello.new(transaction_id: 123)
55
- # Pio::Hello.new(xid: 123)
56
- # @param [Hash] user_options The options to create a message with.
57
- # @option user_options [Number] :transaction_id
58
- # @option user_options [Number] :xid An alias to transaction_id.
59
- #
60
- # @reek This method smells of :reek:FeatureEnvy
61
- # rubocop:disable MethodLength
62
- def initialize(user_options = {})
63
- options = if user_options.respond_to?(:to_i)
64
- { transaction_id: user_options.to_i }
65
- elsif user_options.respond_to?(:fetch)
66
- { transaction_id: user_options[:transaction_id] ||
67
- user_options[:xid] || 0 }
68
- else
69
- fail TypeError
70
- end
71
- if options[:transaction_id] >= 2**32
72
- fail ArgumentError, 'Transaction ID >= 2**32'
73
- end
74
- @format = Format.new(open_flow_header: options)
75
- end
76
- # rubocop:enable MethodLength
77
- end
4
+ # @!parse
5
+ # # OpenFlow 1.0 Hello message
6
+ # class Hello < OpenFlow::Message; end
7
+ class Hello < OpenFlow::Message.factory(Pio::OpenFlow::Type::HELLO); end
78
8
  end
@@ -29,7 +29,7 @@ module Pio
29
29
  when IPv4Address
30
30
  @value = addr.value
31
31
  else
32
- fail TypeError, "Invalid IPv4 address: #{ addr.inspect }"
32
+ fail TypeError, "Invalid IPv4 address: #{addr.inspect}"
33
33
  end
34
34
  end
35
35
 
data/lib/pio/match.rb ADDED
@@ -0,0 +1,167 @@
1
+ require 'English'
2
+ require 'bindata'
3
+ require 'pio/open_flow'
4
+ require 'pio/type/ip_address'
5
+ require 'pio/type/mac_address'
6
+ require 'forwardable'
7
+
8
+ module Pio
9
+ # Fields to match against flows
10
+ class Match
11
+ # Flow wildcards
12
+ class Wildcards < BinData::Primitive
13
+ BITS = {
14
+ in_port: 1 << 0,
15
+ dl_vlan: 1 << 1,
16
+ dl_src: 1 << 2,
17
+ dl_dst: 1 << 3,
18
+ dl_type: 1 << 4,
19
+ nw_proto: 1 << 5,
20
+ tp_src: 1 << 6,
21
+ tp_dst: 1 << 7,
22
+ nw_src: 0,
23
+ nw_src0: 1 << 8,
24
+ nw_src1: 1 << 9,
25
+ nw_src2: 1 << 10,
26
+ nw_src3: 1 << 11,
27
+ nw_src4: 1 << 12,
28
+ nw_src_all: 1 << 13,
29
+ nw_dst: 0,
30
+ nw_dst0: 1 << 14,
31
+ nw_dst1: 1 << 15,
32
+ nw_dst2: 1 << 16,
33
+ nw_dst3: 1 << 17,
34
+ nw_dst4: 1 << 18,
35
+ nw_dst_all: 1 << 19,
36
+ dl_vlan_pcp: 1 << 20,
37
+ nw_tos: 1 << 21
38
+ }
39
+ NW_FLAGS = [:nw_src, :nw_dst]
40
+ FLAGS = BITS.keys.select { |each| !(/^nw_(src|dst)/=~ each) }
41
+
42
+ endian :big
43
+
44
+ uint32 :flags
45
+
46
+ # This method smells of :reek:FeatureEnvy
47
+ def get
48
+ BITS.each_with_object(Hash.new(0)) do |(key, bit), memo|
49
+ next if flags & bit == 0
50
+ if /(nw_src|nw_dst)(\d)/=~ key
51
+ memo[$LAST_MATCH_INFO[1].intern] |= 1 << $LAST_MATCH_INFO[2].to_i
52
+ else
53
+ memo[key] = true
54
+ end
55
+ end
56
+ end
57
+
58
+ def set(params)
59
+ self.flags = params.inject(0) do |memo, (key, val)|
60
+ memo | case key
61
+ when :nw_src, :nw_dst
62
+ (params.fetch(key) & 31) << (key == :nw_src ? 8 : 14)
63
+ else
64
+ val ? BITS.fetch(key) : 0
65
+ end
66
+ end
67
+ end
68
+
69
+ def nw_src
70
+ get.fetch(:nw_src)
71
+ rescue KeyError
72
+ 0
73
+ end
74
+
75
+ def nw_dst
76
+ get.fetch(:nw_dst)
77
+ rescue KeyError
78
+ 0
79
+ end
80
+ end
81
+
82
+ # IP address
83
+ class MatchIpAddress < BinData::Primitive
84
+ default_parameter bitcount: 0
85
+
86
+ array :octets, type: :uint8, initial_length: 4
87
+
88
+ def set(value)
89
+ self.octets = IPv4Address.new(value).to_a
90
+ end
91
+
92
+ def get
93
+ ipaddr = octets.map { |each| format('%d', each) }.join('.')
94
+ prefixlen = 32 - eval_parameter(:bitcount)
95
+ IPv4Address.new(ipaddr + "/#{prefixlen}")
96
+ end
97
+
98
+ def ==(other)
99
+ get == other
100
+ end
101
+ end
102
+
103
+ # ofp_match format
104
+ class MatchFormat < BinData::Record
105
+ endian :big
106
+
107
+ wildcards :wildcards
108
+ uint16 :in_port
109
+ mac_address :dl_src
110
+ mac_address :dl_dst
111
+ uint16 :dl_vlan
112
+ uint8 :dl_vlan_pcp
113
+ uint8 :padding1
114
+ hide :padding1
115
+ uint16 :dl_type
116
+ uint8 :nw_tos
117
+ uint8 :nw_proto
118
+ uint16 :padding2
119
+ hide :padding2
120
+ match_ip_address :nw_src, bitcount: -> { wildcards.nw_src }
121
+ match_ip_address :nw_dst, bitcount: -> { wildcards.nw_dst }
122
+ uint16 :tp_src
123
+ uint16 :tp_dst
124
+ end
125
+
126
+ def self.read(binary)
127
+ MatchFormat.read binary
128
+ end
129
+
130
+ extend Forwardable
131
+
132
+ def_delegators :@format, :wildcards
133
+ def_delegators :@format, :in_port
134
+ def_delegators :@format, :dl_vlan
135
+ def_delegators :@format, :dl_src
136
+ def_delegators :@format, :dl_dst
137
+ def_delegators :@format, :dl_type
138
+ def_delegators :@format, :nw_proto
139
+ def_delegators :@format, :tp_src
140
+ def_delegators :@format, :tp_dst
141
+ def_delegators :@format, :nw_src
142
+ def_delegators :@format, :nw_src_all
143
+ def_delegators :@format, :nw_dst
144
+ def_delegators :@format, :nw_dst_all
145
+ def_delegators :@format, :dl_vlan_pcp
146
+ def_delegators :@format, :nw_tos
147
+ def_delegators :@format, :to_binary_s
148
+
149
+ # rubocop:disable MethodLength
150
+ # This method smells of :reek:FeatureEnvy
151
+ # This method smells of :reek:DuplicateMethodCall
152
+ def initialize(user_options)
153
+ flags = Wildcards::FLAGS.each_with_object({}) do |each, memo|
154
+ memo[each] = true unless user_options.key?(each)
155
+ end
156
+ Wildcards::NW_FLAGS.each_with_object(flags) do |each, memo|
157
+ if user_options.key?(each)
158
+ memo[each] = 32 - IPv4Address.new(user_options[each]).prefixlen
159
+ else
160
+ memo["#{each}_all".intern] = true
161
+ end
162
+ end
163
+ @format = MatchFormat.new({ wildcards: flags }.merge user_options)
164
+ end
165
+ # rubocop:enable MethodLength
166
+ end
167
+ end