Source Code Exploration

From OpenFlow Wiki

Jump to: navigation, search

This page is intended for sharing notes on the reference implementation - including tools setup and tracing events.

Hopefully it can reduce the learning burden for new OpenFlow developers, by providing notes on the more important pieces of code and functionality - like control and data path forwarding.

Contents

Mapping Code with Doxygen

First, install doxygen.

Then generate the default doxygen configuration file:

doxygen -g

If you look at the resulting file, Doxyfile, every option is listed with extensive comments


The default Doxyfile does not recursively search the source tree, so you'll want to modify the RECURSIVE variable to YES.

To generate the source:

doxygen

Here are some other suggested changes:

Mart's Doxyfile changes:

EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_LOCAL_METHODS = YES
STRIP_CODE_COMMENTS = NO
HAVE_DOT = YES
UML_LOOK = YES
TEMPLATE_RELATIONS = YES
CALL_GRAPH = YES
CALLER_GRAPH = YES
DOT_GRAPH_MAX_NODES = 100

KK's Doxyfile changes:

INPUT=../../src
RECURSIVE = YES
GENERATE_HTML = YES
HTML_OUTPUT = html/
HTML_FILE_EXTENSION = .html
EXTRACT_ALL = YES
EXTRACT_LOCAL_CLASSES  = YES
HIDE_UNDOC_MEMBERS     = NO
HIDE_UNDOC_CLASSES     = NO
HIDE_IN_BODY_DOCS      = NO

If doxygen complains about epstopdf, add the package texlive-extra-utils in Ubuntu.

User-space Datapath Code Walkthrough

This walkthrough shows how data and control packets flow through the user-space datapath available since the v0.9 release. The code is in openflow/udatapath.

Main Loop

In udatapath/udatapath.c, after starting up, the main() loop runs forever:

int main(...) {
    ...
    for (;;) {
        dp_run(dp);
        dp_wait(dp);
        poll_block();
    }

Now head to udatapath/datapath.c, where the majority of the user-space datapath code is implemented.

dp_run() handles flow timeouts, checks for data or control packets, then dispatches each packet to the associated processing code.

dp_wait() calls netdev_recv_wait(p->netdev), which registers with the poll loop to wake up from the next call to poll_block() when a packet is ready to be received with netdev_recv() on a particular 'netdev'.

poll_block is defined in lib/poll-loop.c. It blocks until until a registered event occurs, or until the the minimum duration registered with poll_timer_wait() elapses.

Control Path Forwarding

remote_run() called in dp_run() in udatapath/datapath.c checks for a connected remote (secchan or direct controller), then calls fwd_control_input(), which identifies the message type and calls the appropriate handler.

Data Path Forwarding

In dp_run(), netdev_recv() is called to check for a waiting packet buffer. This function is defined in lib/netdev.c:

/* Attempts to receive a packet from 'netdev' into 'buffer', which the caller
 * must have initialized with sufficient room for the packet.  The space
 * required to receive any packet is ETH_HEADER_LEN bytes, plus VLAN_HEADER_LEN
 * bytes, plus the device's MTU (which may be retrieved via netdev_get_mtu()).
 * (Some devices do not allow for a VLAN header, in which case VLAN_HEADER_LEN
 * need not be included.)
 *
 * If a packet is successfully retrieved, returns 0.  In this case 'buffer' is
 * guaranteed to contain at least ETH_TOTAL_MIN bytes.  Otherwise, returns a
 * positive errno value.  Returns EAGAIN immediately if no packet is ready to
 * be returned.
 */
int
netdev_recv(struct netdev *netdev, struct ofpbuf *buffer)
 ...

If there has been no error receiving the packet, then fwd_port_input() gets called to handle a data packet:

void fwd_port_input(struct datapath *dp, struct ofpbuf *buffer,
                    struct sw_port *p)
{
    if (run_flow_through_tables(dp, buffer, p)) {
        dp_output_control(dp, buffer, p->port_no,
                          dp->miss_send_len, OFPR_NO_MATCH);
    }
}

The switch then runs through the flow tables, attempting to find a match:

/* 'buffer' was received on 'p', which may be a a physical switch port or a
 * null pointer.  Process it according to 'dps flow table.  Returns 0 if
 * successful, in which case 'buffer' is destroyed, or -ESRCH if there is no
 * matching flow, in which case 'buffer' still belongs to the caller. */
int run_flow_through_tables(struct datapath *dp, struct ofpbuf *buffer,
                            struct sw_port *p)
    ...
    flow = chain_lookup(dp->chain, &key, 0);
    if (flow != NULL) {
        flow_used(flow, buffer);
        execute_actions(dp, buffer, &key, flow->sf_acts->actions,
                        flow->sf_acts->actions_len, false);

Chain lookup is in chain.c. It searches through the chain of active tables for a match, and return a flow if one is found:

/* Searches 'chain' for a flow matching 'key', which must not have any wildcard
 * fields.  Returns the flow if successful, otherwise a null pointer. */
struct sw_flow *
chain_lookup(struct sw_chain *chain, const struct sw_flow_key *key)
{
 ...

Going back to run_flow_through_tables, flow_used (defined in switch-flow.c) updates the last-used time for the flow.

Then the output actions are called:

execute_actions(dp, buffer, &key,
                    ofm->actions, actions_len, false);

execute_actions is defined in dp_act.c:

/* Execute a list of actions against 'buffer'. */
void execute_actions(struct datapath *dp, struct ofpbuf *buffer,
             struct sw_flow_key *key,
             const struct ofp_action_header *actions, size_t actions_len,
             int ignore_no_fwd)

OFPAT_OUTPUT is the expected case, and has been optimized. It is directly processed in in execute_actions. For other actions, execute_ofpat() is called, which extracts a function pointer to the action type, then runs its associated execute() function:

execute_ofpat(struct ofpbuf *buffer, struct sw_flow_key *key,
        const struct ofp_action_header *ah, uint16_t type)
{
    const struct openflow_action *act = &of_actions[type];

    if (act->execute) {
        act->execute(buffer, key, ah);
    }
}

Here is an example action struct with its size and a function to execute:

static const struct openflow_action of_actions[] = {
    [OFPAT_OUTPUT] = {
        sizeof(struct ofp_action_output),
        sizeof(struct ofp_action_output),
        validate_output,
        NULL                   /* This is optimized into execute_actions */

Back in execute_actions, do_output() in dp_act.c is the important function. The last output port is remembered (prev_port) to ensure that cloned buffers are used for all but the last action.

static void
do_output(struct datapath *dp, struct ofpbuf *buffer, int in_port,
          size_t max_len, int out_port, bool ignore_no_fwd)
{
    if (out_port != OFPP_CONTROLLER) {
        dp_output_port(dp, buffer, in_port, out_port, ignore_no_fwd);
    } else {
        dp_output_control(dp, buffer, in_port, max_len, OFPR_ACTION);
    }
}

For forwarding, dp_output_port is called. If the output port is not a special virtual port, then the default of output_packet() is called (back in datapath.c).:

static void
output_packet(struct datapath *dp, struct ofpbuf *buffer, uint16_t out_port)

output_packet() checks that the output port is up, then sends the packet, increments some counters, and deletes the buffer.

Fairness in packet handling

Each netdev can receive one packet

Each remote can process up to 50 buffers

New remotes created

...repeat