ofpmsg-js

0.0.7 • Public • Published

OpenFlow Message Library js

This is a library for manipulating OpenFlow messages. It provides basic facilities for message construction from sockets, files, or user programs. The library establishes a basic set of patterns for Type Length Value (TLV) message layer composition based on the paper Eliminating Network Protocol Vulnerabilities throught Abstraction and System Language Design. This library is useful for anyone wanting to quickly build OpenFlow based tools: testers, decoders, packet generators, controllers, or switch-agents.

Sample Usage

// import the library
var ofp = require('ofpmsg-js');

// import some symbols into easier local names
var view   = ofp.view;
var ofp1_2 = ofp["1.2"];

// Construct a version specific message and serialize
var error = ofp1_2.Error({ type: 1, code: 2, data: 'This is wrong' });
var v = new view.View(new Buffer(1024));
error.toView(v);

// Reset the view so we can deserialize ... useful for testing :)
v.reset();

// Deserialize the view to any version of OpenFlow
var msg = ofp.msg.fromView(view);

// Reset the view so we can deserialize ... useful for testing :)
v.reset();

// Deserialize the view to version 1.2 of OpenFlow
var msg1_2 = ofp["1.2"].fromView(view);

Development Setup

  • Prerequisites:
    • Install Node or IO.js
    • Install Grunt globally - sudo npm install -g grunt-cli
  • Dependencies:
    • Install local dependencies - npm install
  • Tests:
    • Execute unit tests - grunt

Types Supported

  • View - memory abstraction for serialization/deserialization
  • Header - openflow header type
  • Message - generic OpenFlow message type
  • Data - wrapper for arbitrary uninterpreted data
  • Payloads - version specific OpenFlow payload types
  • ethernet.MAC - ethernet MAC address type
  • ipv4.Address - IPv4 address type

Functions Supported

  • header.bytes() - return the size in bytes of a header
  • util.makePayload(Class, type, Base) - builds OpenFlow payload types
  • util.makeIndex(Type) - builds an object map from header.type to payload Type
  • util.fromView(Type, view) - constructs a Type, calls fromView and returns obj
  • util.Variant(Index) - builds a variant transformer given a type-Type map

Exceptions Generated

  • Available
  • Undefined

View

This type is a simple abstraction that wraps a memory object. In javascript this is the Buffer type. A view is critical for managing structural constraints and ensuring we are always operating within safe regions of memory during message binary based serialization or deserialization.

A view contains a Buffer object and tracks three locations within the buffer: head, tail, and offset. The head and tail are immutable and point to the boundaries of the underlying Buffer, while the offset is used as a cursor to track serialization/deserialization progress through the Buffer. Read or write operations against the view will advance the offset by the amount of bytes read or written. All read/write operations assume a Most Significant Byte First (MSBF) layer within memory.

Construction

A view can constructed from a Buffer object. The buffer may come from a socket, file, or memory location. There are no optional construction arguments. Additionally, a view may be constructed from another view. When decoding certain types of protocols it is often necessary to artificially constrain the protocol deserializers view of the input data. This can be achieved in two ways through: advance, or constrain. Both operations return a new view over a restricted subset of the existing Buffer object. Advance will advance the head pointer by the indicated number of bytes, while constrain will reduce the tail pointer to be the indicated number of bytes from the head.

// Construct a new view from a new Buffer
var view = new View(new Buffer(1024));

// Produce a new view without the 8 byte header
var newView = view.advance(8);

// Produce a new view constrained by the indicated payload length
var newView = view.constrain(100);

Operations

The view is the interface used for serialization/deserialization with a buffer. The operations provided allow for: serialization and deserialization of unsigned integer and byte array data types. Additionally, there are a few operations provided for determining how many bytes are available for additional serialization/deseriation. Finally, there is an operation reset that is useful for testing. It allows for serializations to a view, reseting the offset pointer, and then deserializing from the same view.

// Reset the offset pointer to be equal to the head
view.reset();

// Determine if enough bytes remain from `tail - offset` 
if(view.available() < 8) { ...

// Determine how many bytes used from `offset - head`
view.consumed()

// Deserialize a 1 byte unsigned integer
var version = view.readUInt8();

// Deserialize a 2 byte, MSBF, unsigned integer
var length  = view.readUInt16();

// Deserialize a 4 byte, MSBF, unsigned integer
var xid     = view.readUInt32();

// Deserialize a byte array from using all remaining buffer space
var data    = view.read();

// Deserialize a 32 element byte array
var name    = view.read(32);

// Serialize a 1 byte unsigned integer
view.writeUInt8(type);

// Serialize a 2 byte unsigned integer
view.writeUInt16(length);

// Serialize a 4 byte unsigned integer
view.writeUInt32(xid);

// Serialize a byte array
view.write(buffer);

Header

The OpenFlow header does not change across versions of the protocol. A header includes a version (uint8), type (uint8), length (uint16), and transaction id (uint32). All multi-byte unsigned integers are in Most Significant Byte First (MSBF) order. A header's length indicates the number of bytes in an OpenFlow message with is inclusive of the header and the message payload. A valid header must have a length field with at least 8 bytes.

Construction

There are two ways of constructing a header: from terms by a programmer, or through deserialization of a view.

// Construct a default state header and deserialize
var hdr = Header();
hdr.fromView(view);

// Construct a header given named terms
var hdr = Header({
  version: 1,
  type: 1,
  length: 8,
  xid: 1
});
 
// Construct a header from a view
var hdr = fromView(view);

Operations

Header operations support: serilaization, deserialization, validity checking, and string representation.

// Determine if the header is valid (length >= bytes of the header)
if(hdr.isValid()) { ...

// Determine how many bytes the header consumes in a view
if(view.available() < hdr.bytes()) { ...

// Deserialize a view into a header
hdr.fromView(view);

// Serilaize a header into a view
hdr.toView(view);

// Print a string representaiton of the header
console.log(hdr.toString());

Functions

The header module only exports one function, which returns the number of bytes required by a header for serialization/deserialization. This is a constant expression as the header is a fixed block of fields.

// Determine if the view has enough space for a Header
if(view.available() < bytes()) { ...

Message

This is a generic encapsulation for any OpenFlow message. It includes a header an payload property, where the header is constant regardless of verison of OpenFlow but the payload varies according to version.

Construction

The constrution interfaces supplied are fairly flexible. A user can just supply payloads and headers will be automatically constructed. Or the user can supply headers and payloads in construction. Construction from views is possible in a restricted and unrestricted fashion. You can construct from a view to any version supported by the library, or indicate that construction should be limited to a particular protocol

// Construct a message with the supplied payload, determine the header
var msg = Message({
  payload: ofp1_0.Hello()
});

// Construct a message with the supplied header and payload
var msg = Message({
  header:  hdr.Header(...),
  payload: ofp1_0.Hello()
});

// construct a default message and then deserialize from a view
var msg = Message();
msg.fromView(view);

// Construct a default message, deserialize from a view, but restrict payload
// type construction to OpenFlow 1.0
var msg = Message();
msg.fromView(view, 1);

// Construct a message from a view
var msg = fromView(view);

// Construct a message from a view, but restrict construction to OpenFlow 1.0
var msg = fromView(view, 1);

Operations

The operations provided are limited to supporting serialization/deserialization.

// Determine the current size in bytes of a message
if(view.available() < msg.bytes()) {
  throw 'Not enough bytes in view';
}
// Serialize the message to the view
msg.toView(view);

Data

The Data type is a simple base class for message payloads that wish to carry uninterpreted data. A typical pattern in OpenFlow payload types is to allow for arbitrary byte arrays to postfix any payload type. This mechanisms allows for a standard way of passing non-standard data in almost any message type. Several OpenFlow message types will inherit from this type. Uninterpreted data is stored as a byte array usign the Buffer type.

Construction

Data can be constructed explicitly for sending and recieving uninterpreted byte arrays. Most payload types that use the Data type will inherit from Data and call the super toView/fromView at the end of their toView/fromView implementations.

// Construct an empty data set
var data = new Data();

// Construct a data set from a utf8 encoding of a string
var data = new Data('asdfasdfasdf');

// Construct a data set from a Buffer
var data = new Data(new Buffer(100));

Operations

This type only exposed a few operations for: determining byte size, serizliation, and deserialization.

// Determine if enough bytes are available to serialize the data set
if(view.available() < data.bytes()) { ...

// Deserialize a data set from a view
data.fromView(view);

// Serialize a data set to a view
data.toView(view);

Functions

makePayload(Class, type, Base)

All OpenFlow payload types must have their OpenFlow header type value associated with their object definition. Some OpenFlow payloads allow for uninterpreted data to postfix the payload values, which allows for a standard way of passing non-standard data in any message sequence. The Data type provides for this generic uninterpreted data behavior. This is a helper function for assigning the header type value, and sometimes inheriting from a Base object prototype, for all OpenFlow payload type definitions.

// Import the Data type
var dt = require('./data');
var util = require('./util');

// Construct a new payload type
function Hello(data) {
  ...
  // Call the base class constructor
  dt.Data.call(this, data);
}
// Establish its header type value of '0' and inherit from Data
util.makePayload(Hello, 0, dt.Data);

function Error(type, code, data) {
  this.type = ...;
  this.code = ...;
  // Call the base class constructor
  dt.Data.call(this, data);
}
// Establish its header type value of '1' and inherit from Data
util.makePayload(Hello, 1, dt.Data);

Error.prototype.fromView = function(view) {
  // Perform error field deserialization
  ...
  // Call the base class deserializer
  dt.Data.prototype.fromView.call(this, view);
};

fromView(Type, view)

This is a simple helper function that constructs a default object of Type, immediately calls the fromView deserialization operation on that object, and then returns the object. This pattern can be quite useful for cutting down on code verbosity.

var msg = util.fromView(Message, view);
var hdr = util.fromView(Header, view);
var hello = util.fromView(Hello, view);

makeIndex(Type)

This is a builder function used by every OpenFlow versioned payload module to export a map of payload type values to payload Types. This map is a necessary input for generic message construction over any version of OpenFlow. In order for the map to work, each Type (Hello, Error, etc.) must export a Type value. As long as a payload type is built using the makePayload helper and a type value is supplied, this property will hold.

// constructs a map of the payload types
var map = util.makeIndex([
  Hello,
  Error,
  ...
]);

// Resultant object
// {
//   "0": Hello,
//   "1": Error,
//   ...
// }

Variant(Map)

This is a helper function that builds a function object for the construction of variant types. The input to the function is a Map from a type string to a Type. The returned value is a function that takes a type value and view and will construct and deserialize the appropriate Type at runtime.

// Construct a variant over a map of payload types
var variant = util.Variant({
  "0": Hello,
  "1": Error,
  ...
});
// Deserialize a header
var hdr = util.fromView(Header, view);
// Execute the variant deserializer based on input form the header
var payload = variant(hdr.type, view);

Readme

Keywords

none

Package Sidebar

Install

npm i ofpmsg-js

Weekly Downloads

11

Version

0.0.7

License

APL 2.0

Last publish

Collaborators

  • cchojnacki