Skip to content

Defining New Messages

Eder Leão Fernandes edited this page Sep 8, 2018 · 2 revisions

In this example we implement a hypothetical OpenFlow feature to request and reply the maximum number of queues supported by the switch. It is a toy example that focus on a direct extension to OpenFlow. New messages could also be created through the use of experimenter messages in a similar fashion to the following example.

Add new types

The code below shows the initial code to define the new messages. First, the types OFPT_MAX_QUEUE_REQUEST and OFPT_MAX_QUEUE_REPLY are added to the OpenFlow message enum in the file openflow.h.

/* file:include/openflow/openflow.h 
 * Add OFPT_MAX_QUEUE_REPLY 
 * after the last defined type
 */
enum ofp_type {
     OFPT_HELLO = 0,
     ...
     OFPT_METER_MOD = 29,
     OFPT_MAX_QUEUE_REQUEST = 30,
     OFPT_MAX_QUEUE_REPLY = 31,
}

Define OpenFlow message format

Next, each message needs a representation in the OpenFlow format and the internal format of the switch. The OpenFlow format is simply how the message should look like in the wire. When defining the message format, it is important to follow the specification OpenFlow specification

All structures are packed with padding and 8-byte aligned, as checked by the assertion statements. All OpenFlow messages are sent in big-endian format.

Knowing that, we can define both of our structs in openflow.h

The request contains only the OpenFlow header, as it does not require any other information. Since the OpenFlow header is already aligned to 8 bytes, there is no need for padding.

The reply is more interesting, and contains the requested data. The max_queues is a 2 bytes unsigned integer. Because the total size of the struct now is 10 bytes, there is need to add 6 padding bytes, so the struct is aligned.

/* file:include/openflow/openflow.h 
 * Struct representing the request. 
 */
struct ofp_max_queues_request{
    struct ofp_header header;
}

/* file:include/openflow/openflow.h 
 * Struct representing the reply. 
 * All OpenFlow messages are 8 bytes 
 * aligned, thus padding is added
 */
struct ofp_max_queues_reply{
    struct ofp_header header;
    uint16_t max_queues;
    uint8_t pad[6];
}

Define internal struct representation

Now the internal structs shall be defined in the oflib. The oflib contains multiple files defining structs, functions to pack and unpack plus printing utilities. The file that defines the struct for all OpenFlow messages is ofl-messages.h. The other struct files (e.g: ofl-structs.h and ofl-actions.h)

Notice that there is only need to specify the reply message, since the request message does not contain any other information and can be represented simply by the struct ofl_msg_header header (presented below for completeness).

Another interesting point is the fact the reply does not have any padding byte since they are not relevant information for the message processing.

/* file:oflib/ofl-messages.h
 * The common header for messages. All message structures start with this
 * header, therefore they can be safely cast back and forth */
struct ofl_msg_header {
    enum ofp_type   type;   /* One of the OFPT_ constants. */
};

/* file:oflib/ofl-messages.h
 * Internal representation of 
 * OFPT_MAX_QUEUE_REPLY.
 */ 
struct ofl_msg_max_queue_reply {
    struct ofl_msg_header header;
    uint16_t max_queues;
}

Implement Pack and Unpacking.

The following step is to implement the packing and unpacking of the messages. Once the request is only the OpenFlow header, there is no need to implement a new function since function to pack and unpack already exists. So, the code below presents only the code to pack and unpack the reply message.

An essential detail for pack and unpacking code is struct casting. It is widely used and if you whether do not understand or remember very well how it works, it is recommended to learn or refresh your knowledge about it.

The packing code shall be implemented in ofl-messages-pack.c. It is important to ensure that fields larger than 1 byte are correctly added to the buffer in the Network byte order, so for 2, 4 and 8 bytes, one should use htons, htonl and hton64.

/* file:oflib/ofl-messages-pack.c
 * Pack: convert the internal message to an 
 * OpenFlow binary message
 * to  be sent across the network
 */
static int
ofl_msg_pack_max_queues_reply(struct ofl_msg_max_queue_reply *msg, uint8_t **buf, size_t *buf_len) {
        struct ofp_max_queues_reply *rep;
        /* Allocates memory for the OpenFlow message
        *buf_len = sizeof(struct ofp_max_queues_reply);
        *buf     = (uint8_t *)malloc(*buf_len);
        /* Point the struct to the buffer address 
         * By doing it we can simply set the struct fields.
         */
        rep = (struct ofp_max_queues_reply *)(*buf);
        /* It is important to set the variables in network order */
        rep->max_queues =  htons(msg->max_queues);
        memset(rep->pad,0,sizeof(rep->pad));
        return 0;
}

The packing code shall be implemented in ofl-messages-unpack.c. Since the function handles OpenFlow errors, it receives a pointer to pointer of the struct ofl_msg_header which is used as a return parameter of the internal struct.

/* file:oflib/ofl-messages-unpack.c
 * Pack: convert the received OpenFlow network message 
 * to the switch internal struct
 */
static ofl_err
ofl_msg_unpack_max_queue_reply(struct ofp_header *src, size_t *len, struct ofl_msg_header **msg) {

    struct ofp_max_queues_reply *rep;
    struct ofl_max_queues_reply *irep;

    /* Check if the message has the expected size */
    if (*len < sizeof(struct  ofp_max_queues_reply)){
        OFL_LOG_WARN(LOG_MODULE, "Received MAX_QUEUE_REQUEST message has invalid length (%zu).", *len);
        return ofl_error(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
    }
    *len -= sizeof(struct ofp_max_queues_reply);

    /* Extract the field from the OF message */
    rep = (struct ofp_role_request *) src;
    irep = (struct ofl_msg_role_request *) malloc(sizeof(struct ofl_msg_role_request));

    irep->max_queues = ntohs(rep->max_queues);
    *msg = (struct ofl_msg_header *)drl;
    return 0;
}

Handle the new types in the main pack and unpack functions.

The final step to add a new Openflow message is to handle the new types in the main pack and unpacking functions. Because every message can be cast from and to their respective headers, packing and unpacking handle all message types in the functions ofl_msg_pack ofl_msg_unpack respectively.

The code below adds handles the new messages for packing in the switch statement:

int
ofl_msg_pack(struct ofl_msg_header *msg, uint32_t xid, uint8_t **buf, size_t *buf_len, struct ofl_exp *exp) {
    struct ofp_header *oh;
    int error = 0;
    switch (msg->type) {

        case OFPT_HELLO: {
            error = ofl_msg_pack_empty(msg, buf, buf_len);
            break;
        }
        /* Most messages are omited for space */
        .
        case OFPT_MAX_QUEUE_REQUEST: {
            error = ofl_msg_pack_empty(msg, buf, buf_len);
         }
        case OFPT_MAX_QUEUE_REPLY: {
            error = ofl_msg_pack_max_queue_reply(msg, buf, buf_len);
         }
        default: {
            OFL_LOG_WARN(LOG_MODULE, "Trying to pack unknown message type.");
            error = -1;
        }
        }
    }

    if (error) {
        return error;
    }

    oh = (struct ofp_header *)(*buf);
    oh->version =        OFP_VERSION;
    oh->type    =        msg->type;
    oh->length  = htons(*buf_len);
    oh->xid     = htonl(xid);
    return 0;
}

Again, for unpacking just handle the new types in the switch statement.

ofl_err
ofl_msg_unpack(uint8_t *buf, size_t buf_len, struct ofl_msg_header **msg, uint32_t *xid, struct ofl_exp *exp) {
    struct ofp_header *oh;
    size_t len = buf_len;
    ofl_err error = 0;
    if (len < sizeof(struct ofp_header)) {
        OFL_LOG_WARN(LOG_MODULE, "Received message is shorter than ofp_header.");
        if (xid != NULL) {
            *xid = 0x00000000;
        }
        return ofl_error(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
    }

    oh = (struct ofp_header *)buf;

    if (oh->version != OFP_VERSION) {
        OFL_LOG_WARN(LOG_MODULE, "Received message has wrong version.");
        return ofl_error(OFPET_HELLO_FAILED, OFPHFC_INCOMPATIBLE);
    }

    if (xid != NULL) {
        *xid = ntohl(oh->xid);
    }

    if (len != ntohs(oh->length)) {
        OFL_LOG_WARN(LOG_MODULE, "Received message length does not match the length field.");
        return ofl_error(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
    }

    switch (oh->type) {
        case OFPT_HELLO:{
            error = ofl_msg_unpack_empty(oh, &len, msg);
            break;
        }
        /* Most messages omited for space */
        case OFPT_MAX_QUEUE_REQUEST:{
            error = ofl_msg_unpack_empty(oh, &len, msg);
            break;
         }
        case OFPT_MAX_QUEUE_REPLY: {
            error = ofl_msg_unpack_max_queue_reply(oh, &len, msg);
            break;
        }
        default: {
            error = ofl_error(OFPET_BAD_REQUEST, OFPGMFC_BAD_TYPE);
        }
      ...
    (*msg)->type = (enum ofp_type)oh->type;
    return 0
}