Skip to content

Commit

Permalink
Add Pio::PacketOut class.
Browse files Browse the repository at this point in the history
  • Loading branch information
yasuhito committed Jun 29, 2015
1 parent 7002b26 commit 8aea8f5
Show file tree
Hide file tree
Showing 10 changed files with 251 additions and 56 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

## develop (unreleased)
### New features
* [#183](https://github.com/trema/pio/pull/183): Add new class `Pio::PacketIn` (OpenFlow1.3).
* [#185](https://github.com/trema/pio/pull/185): Add new classes `Pio::Match::TunnelId` and `Pio::Match::MaskedTunnelId`
* [#184](https://github.com/trema/pio/pull/184): Add new classes `Pio::Match::ArpOp`, `Pio::Match::ArpSenderProtocolAddress`, `Pio::Match::ArpTargetProtocolAddress`, `Pio::Match::ArpSenderHardwareAddress`, `Pio::Match::ArpTargetHardwareAddress`, `Pio::Match::MaskedArpSenderProtocolAddress`,`Pio::Match::MaskedArpTargetProtocolAddress`, `Pio::Match::MaskedArpSenderHardwareAddress` and `Pio::Match::MaskedArpTargetHardwareAddress`

Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -36,6 +36,7 @@ supports the following packet formats:
- [Features Request](https://relishapp.com/trema/pio/docs/open-flow13/pio-features-request)
- [Features Reply](https://relishapp.com/trema/pio/docs/open-flow13/pio-features-reply)
- [Packet In](https://relishapp.com/trema/pio/docs/open-flow13/pio-packetin)
- [Packet Out](https://relishapp.com/trema/pio/docs/open-flow13/pio-packetout)
- [Flow Mod](https://relishapp.com/trema/pio/docs/open-flow13/pio-flowmod)

## Features Overview
Expand Down
2 changes: 1 addition & 1 deletion Rakefile
@@ -1,7 +1,7 @@
require 'bundler/gem_tasks'

RELISH_PROJECT = 'trema/pio'
FLAY_THRESHOLD = 398
FLAY_THRESHOLD = 452

task default: :travis
task test: [:spec, :cucumber]
Expand Down
96 changes: 96 additions & 0 deletions features/open_flow13/packet_out.feature
@@ -0,0 +1,96 @@
Feature: Pio::PacketOut
Background:
Given I use OpenFlow 1.3

Scenario: new
When I try to create an OpenFlow message with:
"""
Pio::PacketOut.new
"""
Then it should finish successfully
And the message have the following fields and values:
| field | value |
| class | Pio::PacketOut |
| ofp_version | 4 |
| message_type | 13 |
| message_length | 24 |
| transaction_id | 0 |
| xid | 0 |
| buffer_id | :no_buffer |
| in_port | 0 |
| actions_length | 0 |
| raw_data.length | 0 |

Scenario: new (actions = SendOutPort(1), raw_data = ARP Request)
When I try to create an OpenFlow message with:
"""
data_dump = [
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xac, 0x5d, 0x10, 0x31, 0x37,
0x79, 0x08, 0x06, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01,
0xac, 0x5d, 0x10, 0x31, 0x37, 0x79, 0xc0, 0xa8, 0x02, 0xfe, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xa8, 0x02, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00
].pack('C*')
Pio::PacketOut.new(raw_data: data_dump, actions: Pio::SendOutPort.new(1))
"""
Then it should finish successfully
And the message have the following fields and values:
| field | value |
| class | Pio::PacketOut |
| ofp_version | 4 |
| message_type | 13 |
| message_length | 100 |
| transaction_id | 0 |
| xid | 0 |
| buffer_id | :no_buffer |
| in_port | 0 |
| actions_length | 16 |
| raw_data.length | 60 |
| data.class | Pio::Arp::Request |
| destination_mac | ff:ff:ff:ff:ff:ff |
| source_mac | ac:5d:10:31:37:79 |
| ether_type | 2054 |
| hardware_type | 1 |
| protocol_type | 2048 |
| hardware_length | 6 |
| protocol_length | 4 |
| operation | 1 |
| sender_hardware_address | ac:5d:10:31:37:79 |
| sender_protocol_address | 192.168.2.254 |
| target_hardware_address | ff:ff:ff:ff:ff:ff |
| target_protocol_address | 192.168.2.5 |

Scenario: read
When I try to parse a file named "open_flow13/packet_out.raw" with "PacketOut" class
Then it should finish successfully
And the message have the following fields and values:
| field | value |
| class | Pio::PacketOut |
| ofp_version | 4 |
| message_type | 13 |
| message_length | 100 |
| transaction_id | 123 |
| xid | 123 |
| buffer_id | :no_buffer |
| in_port | :controller |
| actions_length | 16 |
| actions.first.class | Pio::SendOutPort |
| actions.first.port | 1 |
| actions.first.max_length | :no_buffer |
| raw_data.length | 60 |
| data.class | Pio::Arp::Request |
| destination_mac | ff:ff:ff:ff:ff:ff |
| source_mac | ac:5d:10:31:37:79 |
| ether_type | 2054 |
| hardware_type | 1 |
| protocol_type | 2048 |
| hardware_length | 6 |
| protocol_length | 4 |
| operation | 1 |
| sender_hardware_address | ac:5d:10:31:37:79 |
| sender_protocol_address | 192.168.2.254 |
| target_hardware_address | ff:ff:ff:ff:ff:ff |
| target_protocol_address | 192.168.2.5 |

1 change: 1 addition & 0 deletions lib/pio/open_flow13.rb
Expand Up @@ -16,6 +16,7 @@ module OpenFlow
require 'pio/open_flow13/match'
require 'pio/open_flow13/meter'
require 'pio/open_flow13/packet_in'
require 'pio/open_flow13/packet_out'
require 'pio/open_flow13/send_out_port'
require 'pio/open_flow13/write_metadata'

Expand Down
42 changes: 42 additions & 0 deletions lib/pio/open_flow13/actions.rb
@@ -0,0 +1,42 @@
require 'bindata'

module Pio
# Actions not yet implemented.
class UnsupportedAction < BinData::Record
endian :big

uint16 :action_type
uint16 :action_length
end

# Actions list of actions-apply instruction.
class Actions < BinData::Primitive
mandatory_parameter :length

endian :big

string :binary, read_length: :length

def set(value)
self.binary = [value].flatten.map(&:to_binary).join
end

# rubocop:disable MethodLength
def get
actions = []
tmp = binary
while tmp.length > 0
action = case BinData::Uint16be.read(tmp)
when 0
SendOutPort.read(tmp)
else
UnsupportedAction.read(tmp)
end
tmp = tmp[action.action_length..-1]
actions << action
end
actions
end
# rubocop:enable MethodLength
end
end
40 changes: 1 addition & 39 deletions lib/pio/open_flow13/apply.rb
@@ -1,48 +1,10 @@
require 'bindata'
require 'forwardable'
require 'pio/open_flow13/actions'

module Pio
# An instruction to apply a list of actions to a packet in-order.
class Apply
# Actions not yet implemented.
class UnsupportedAction < BinData::Record
endian :big

uint16 :action_type
uint16 :action_length
end

# Actions list of actions-apply instruction.
class Actions < BinData::Primitive
mandatory_parameter :length

endian :big

string :binary, read_length: :length

def set(value)
self.binary = [value].flatten.map(&:to_binary).join
end

# rubocop:disable MethodLength
def get
actions = []
tmp = binary
while tmp.length > 0
action = case BinData::Uint16be.read(tmp)
when 0
SendOutPort.read(tmp)
else
UnsupportedAction.read(tmp)
end
tmp = tmp[action.action_length..-1]
actions << action
end
actions
end
# rubocop:enable MethodLength
end

# OpenFlow 1.3.4 OFPIT_APPLY_ACTIONS instruction format.
class Format < BinData::Record
endian :big
Expand Down
17 changes: 17 additions & 0 deletions lib/pio/open_flow13/buffer_id.rb
@@ -0,0 +1,17 @@
module Pio
# Buffered packet to apply to, or :no_buffer.
class BufferId < BinData::Primitive
NO_BUFFER = 0xffffffff

endian :big
uint32 :buffer_id, initial_value: NO_BUFFER

def get
(buffer_id == NO_BUFFER) ? :no_buffer : buffer_id
end

def set(value)
self.buffer_id = (value == :no_buffer ? NO_BUFFER : value)
end
end
end
17 changes: 1 addition & 16 deletions lib/pio/open_flow13/flow_mod.rb
@@ -1,5 +1,6 @@
require 'forwardable'
require 'pio/open_flow'
require 'pio/open_flow13/buffer_id'
require 'pio/open_flow13/match'

# Base module.
Expand Down Expand Up @@ -29,22 +30,6 @@ def set(value)
end
end

# Buffered packet to apply to, or :no_buffer.
class BufferId < BinData::Primitive
NO_BUFFER = 0xffffffff

endian :big
uint32 :buffer_id, initial_value: NO_BUFFER

def get
(buffer_id == NO_BUFFER) ? :no_buffer : buffer_id
end

def set(value)
self.buffer_id = (value == :no_buffer ? NO_BUFFER : value)
end
end

# For delete commands, require matching entries to include this as
# an output port.
class OutPort < BinData::Primitive
Expand Down
90 changes: 90 additions & 0 deletions lib/pio/open_flow13/packet_out.rb
@@ -0,0 +1,90 @@
require 'bindata'
require 'pio/open_flow13/actions'
require 'pio/open_flow13/buffer_id'

# Base module.
module Pio
remove_const :PacketOut

# OpenFlow 1.3 PacketOut message parser and generator
class PacketOut
# Packet's input port or :controller
class InPort < BinData::Primitive
CONTROLLER = 0xfffffffd

endian :big
uint32 :in_port

def get
(in_port == CONTROLLER) ? :controller : in_port
end

def set(value)
self.in_port = (value == :controller ? NO_CONTROLLER : value)
end
end

# OpenFlow 1.3 PacketOut message body
class Body < BinData::Record
endian :big

buffer_id :buffer_id
in_port :in_port
uint16 :actions_length, initial_value: -> { actions.binary.length }
string :padding, length: 6
actions :actions, length: :actions_length
string :raw_data, read_length: -> { message_length - 24 - actions_length }

def length
10 + padding.length + actions_length + raw_data.length
end

def data
@data ||= Pio::Parser.read(raw_data)
end

def method_missing(method, *args)
data.__send__(method, *args).snapshot
end
end

# OpenFlow 1.3 PacketOut message body
class Format < BinData::Record
extend Forwardable

endian :big

open_flow_header :open_flow_header,
ofp_version_value: 4, message_type_value: 13
body :body

def_delegators :open_flow_header, :ofp_version
def_delegators :open_flow_header, :message_type
def_delegators :open_flow_header, :message_length
def_delegators :open_flow_header, :transaction_id
def_delegator :open_flow_header, :transaction_id, :xid

def method_missing(method, *args, &block)
body.__send__ method, *args, &block
end
end

def self.read(raw_data)
allocate.tap do |message|
message.instance_variable_set(:@format, Format.read(raw_data))
end
end

def initialize(user_attrs = {})
header_attrs = OpenFlowHeader::Options.parse(user_attrs)
body_attrs = { actions: user_attrs[:actions],
raw_data: user_attrs[:raw_data] }
@format = Format.new(open_flow_header: header_attrs,
body: body_attrs)
end

def method_missing(method, *args, &block)
@format.__send__ method, *args, &block
end
end
end

0 comments on commit 8aea8f5

Please sign in to comment.