Open vSwitchのソースコードを読む(11) flow table のエントリ

2013-06-21 by Daisuke Kotani

エントリの追加、削除をする関数を探す

第8回で、Controllerから送られてきたOpenFlowのメッセージの処理は ofproto/ofproto.c の handle_openflow 関数で行われると説明しました。ここから、Flow Table を変更する flow_mod メッセージがどう処理されるのかを探していきます。

static bool
handle_openflow(struct ofconn *ofconn, struct ofpbuf *ofp_msg)
{
    int error = handle_openflow__(ofconn, ofp_msg);
    if (error && error != OFPROTO_POSTPONE) {
        ofconn_send_error(ofconn, ofp_msg->data, error);
    }
    COVERAGE_INC(ofproto_recv_openflow);
    return error != OFPROTO_POSTPONE;
}

handle_openflow__ 関数を呼んでいます。

static enum ofperr
handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
{
    const struct ofp_header *oh = msg->data;
    enum ofptype type;
    enum ofperr error;

    error = ofptype_decode(&type, oh);
    if (error) {
        return error;
    }

    switch (type) {
        /* OpenFlow requests. */
    case OFPTYPE_ECHO_REQUEST:
        return handle_echo_request(ofconn, oh);

    case OFPTYPE_FEATURES_REQUEST:
        return handle_features_request(ofconn, oh);

    (略)

    case OFPTYPE_FLOW_MOD:
        return handle_flow_mod(ofconn, oh);

    case OFPTYPE_BARRIER_REQUEST:
        return handle_barrier_request(ofconn, oh);

    (略)

    default:
        return OFPERR_OFPBRC_BAD_TYPE;
    }
}

flow_mod メッセージは handle_flow_mod 関数で処理されています。

static enum ofperr
handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
{
    struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
    struct ofputil_flow_mod fm;
    uint64_t ofpacts_stub[1024 / 8];
    struct ofpbuf ofpacts;
    enum ofperr error;
    long long int now;

    error = reject_slave_controller(ofconn);
    if (error) {
        goto exit;
    }

    ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
    error = ofputil_decode_flow_mod(&fm, oh, ofconn_get_protocol(ofconn),
                                    &ofpacts);
    if (!error) {
        error = ofpacts_check(fm.ofpacts, fm.ofpacts_len,
                              &fm.match.flow, ofproto->max_ports);
    }
    if (!error) {
        error = handle_flow_mod__(ofproto, ofconn, &fm, oh);
    }
    if (error) {
        goto exit_free_ofpacts;
    }

    /* Record the operation for logging a summary report. */
    (略)
}

最終的に、handle_flow_mod__ 関数を読んでいるようです。

static enum ofperr
handle_flow_mod__(struct ofproto *ofproto, struct ofconn *ofconn,
                  const struct ofputil_flow_mod *fm,
                  const struct ofp_header *oh)
{
    if (ofproto->n_pending >= 50) {
        ovs_assert(!list_is_empty(&ofproto->pending));
        return OFPROTO_POSTPONE;
    }

    switch (fm->command) {
    case OFPFC_ADD:
        return add_flow(ofproto, ofconn, fm, oh);

    case OFPFC_MODIFY:
        return modify_flows_loose(ofproto, ofconn, fm, oh);

    case OFPFC_MODIFY_STRICT:
        return modify_flow_strict(ofproto, ofconn, fm, oh);

    case OFPFC_DELETE:
        return delete_flows_loose(ofproto, ofconn, fm, oh);

    case OFPFC_DELETE_STRICT:
        return delete_flow_strict(ofproto, ofconn, fm, oh);

    default:
        if (fm->command > 0xff) {
            VLOG_WARN_RL(&rl, "%s: flow_mod has explicit table_id but "
                         "flow_mod_table_id extension is not enabled",
                         ofproto->name);
        }
        return OFPERR_OFPFMFC_BAD_COMMAND;
    }
}

やっと出てきました。 エントリの追加は add_flow, 変更は modify_flows_loose と modify_flow_strict, 削除は delete_flows_loose, delete_flow_strict で処理されているようです。

エントリの追加: add_flow 関数

add_flow 関数の中身は以下です。

/* Implements OFPFC_ADD and the cases for OFPFC_MODIFY and OFPFC_MODIFY_STRICT
 * in which no matching flow already exists in the flow table.
 *
 * Adds the flow specified by 'ofm', which is followed by 'n_actions'
 * ofp_actions, to the ofproto's flow table.  Returns 0 on success, an OpenFlow
 * error code on failure, or OFPROTO_POSTPONE if the operation cannot be
 * initiated now but may be retried later.
 *
 * Upon successful return, takes ownership of 'fm->ofpacts'.  On failure,
 * ownership remains with the caller.
 *
 * 'ofconn' is used to retrieve the packet buffer specified in ofm->buffer_id,
 * if any. */
static enum ofperr
add_flow(struct ofproto *ofproto, struct ofconn *ofconn,
         const struct ofputil_flow_mod *fm, const struct ofp_header *request)
{
    struct oftable *table;
    struct ofopgroup *group;
    struct rule *victim;
    struct cls_rule cr;
    struct rule *rule;
    int error;

    error = check_table_id(ofproto, fm->table_id);
    if (error) {
        return error;
    }

table id が有効なものかどうかチェックしているところ。

    /* Pick table. */
    if (fm->table_id == 0xff) {
        uint8_t table_id;
        if (ofproto->ofproto_class->rule_choose_table) {
            error = ofproto->ofproto_class->rule_choose_table(ofproto,
                                                              &fm->match,
                                                              &table_id);
            if (error) {
                return error;
            }
            ovs_assert(table_id < ofproto->n_tables);
            table = &ofproto->tables[table_id];
        } else {
            table = &ofproto->tables[0];
        }
    } else if (fm->table_id < ofproto->n_tables) {
        table = &ofproto->tables[fm->table_id];
    } else {
        return OFPERR_OFPBRC_BAD_TABLE_ID;
    }

table id が 0xff (OFPTT_ALL) だった場合、全てのテーブルを指します。下位層でそういったルールをどのテーブルに入れるのかを選択する処理がここです。

if (table->flags & OFTABLE_READONLY) {
    return OFPERR_OFPBRC_EPERM;
}

/* Allocate new rule and initialize classifier rule. */
rule = ofproto->ofproto_class->rule_alloc();
if (!rule) {
    VLOG_WARN_RL(&rl, "%s: failed to create rule (%s)",
                 ofproto->name, strerror(error));
    return ENOMEM;
}
cls_rule_init(&rule->cr, &fm->match, fm->priority);

新しいエントリを追加するための領域の確保と初期化。

/* Serialize against pending deletion. */
if (is_flow_deletion_pending(ofproto, &cr, table - ofproto->tables)) {
    cls_rule_destroy(&rule->cr);
    ofproto->ofproto_class->rule_dealloc(rule);
    return OFPROTO_POSTPONE;
}

/* Check for overlap, if requested. */
if (fm->flags & OFPFF_CHECK_OVERLAP
    && classifier_rule_overlaps(&table->cls, &rule->cr)) {
    cls_rule_destroy(&rule->cr);
    ofproto->ofproto_class->rule_dealloc(rule);
    return OFPERR_OFPFMFC_OVERLAP;
}

ここら辺までがエントリを追加する前のスイッチのチェックのようなものです。ここから実際にエントリを作っていきます。

/* FIXME: Implement OFPFF12_RESET_COUNTS */

rule->ofproto = ofproto;
rule->pending = NULL;
rule->flow_cookie = fm->new_cookie;
rule->created = rule->modified = rule->used = time_msec();
rule->idle_timeout = fm->idle_timeout;
rule->hard_timeout = fm->hard_timeout;
rule->table_id = table - ofproto->tables;
rule->send_flow_removed = (fm->flags & OFPFF_SEND_FLOW_REM) != 0;
/* FIXME: Implement OF 1.3 flags OFPFF13_NO_PKT_COUNTS
   and OFPFF13_NO_BYT_COUNTS */
rule->ofpacts = xmemdup(fm->ofpacts, fm->ofpacts_len);
rule->ofpacts_len = fm->ofpacts_len;
rule->evictable = true;
rule->eviction_group = NULL;
list_init(&rule->expirable);
rule->monitor_flags = 0;
rule->add_seqno = 0;
rule->modify_seqno = 0;

パラメータを設定したところ。

/* Insert new rule. */
victim = oftable_replace_rule(rule);
if (victim && !rule_is_modifiable(victim)) {
    error = OFPERR_OFPBRC_EPERM;
} else if (victim && victim->pending) {
    error = OFPROTO_POSTPONE;
} else {
    struct ofoperation *op;
    struct rule *evict;

    if (classifier_count(&table->cls) > table->max_flows) {
        bool was_evictable;

        was_evictable = rule->evictable;
        rule->evictable = false;
        evict = choose_rule_to_evict(table);
        rule->evictable = was_evictable;

        if (!evict) {
            error = OFPERR_OFPFMFC_TABLE_FULL;
            goto exit;
        } else if (evict->pending) {
            error = OFPROTO_POSTPONE;
            goto exit;
        }
    } else {
        evict = NULL;
    }

もし同じルールがあって書き換え不可ならエラー、そうでなければ、table の最大エントリするを超えないかどうかチェックしています。

    group = ofopgroup_create(ofproto, ofconn, request, fm->buffer_id);
    op = ofoperation_create(group, rule, OFOPERATION_ADD, 0);
    op->victim = victim;

    error = ofproto->ofproto_class->rule_construct(rule);
    if (error) {
        op->group->n_running--;
        ofoperation_destroy(rule->pending);
    } else if (evict) {
        delete_flow__(evict, group);
    }
    ofopgroup_submit(group);
}

ここが最終的にルールの追加などを行っている部分で、ofopgroup で1つのOpenFlowのリクエストに対する複数の処理をまとめてエラーなどの管理ができるようになっていて、ofoperationが個々の処理になっています。

exit:
    /* Back out if an error occurred. */
    if (error) {
        oftable_substitute_rule(rule, victim);
        ofproto_rule_destroy__(rule);
    }
    return error;
}

エラー処理。

ofopgroup

ofopgroup は ofoperation の group です。1つの OpenFlow の Request が Open vSwitch の中では複数の操作になることがあって、1つの Request に対応するのが ofopgroup、Open vSwitch の中の1つ1つの操作が ofoperation です。

ofopgroup 構造体の定義は以下のようになっています。

/* A single OpenFlow request can execute any number of operations.  The
 * ofopgroup maintain OpenFlow state common to all of the operations, e.g. the
 * ofconn to which an error reply should be sent if necessary.
 *
 * ofproto initiates some operations internally.  These operations are still
 * assigned to groups but will not have an associated ofconn. */
struct ofopgroup {
    struct ofproto *ofproto;    /* Owning ofproto. */
    struct list ofproto_node;   /* In ofproto's "pending" list. */
    struct list ops;            /* List of "struct ofoperation"s. */
    int n_running;              /* Number of ops still pending. */

    /* Data needed to send OpenFlow reply on failure or to send a buffered
     * packet on success.
     *
     * If list_is_empty(ofconn_node) then this ofopgroup never had an
     * associated ofconn or its ofconn's connection dropped after it initiated
     * the operation.  In the latter case 'ofconn' is a wild pointer that
     * refers to freed memory, so the 'ofconn' member must be used only if
     * !list_is_empty(ofconn_node).
     */
    struct list ofconn_node;    /* In ofconn's list of pending opgroups. */
    struct ofconn *ofconn;      /* ofconn for reply (but see note above). */
    struct ofp_header *request; /* Original request (truncated at 64 bytes). */
    uint32_t buffer_id;         /* Buffer id from original request. */
};

ofproto は OpenFlow Switch インスタンスの構造体へのポインタです。ops リストや n_running, ofconn, request, buffer_id の用途は予想がつくのですが、ofproto_node, ofconn_node は何なんでしょうか。しかもポインタではないし。

ofopgroup 構造体を生成する関数が ofopgroup_create 関数でした。

/* Creates and returns a new ofopgroup for 'ofproto'.
 *
 * If 'ofconn' is NULL, the new ofopgroup is not associated with any OpenFlow
 * connection.  The 'request' and 'buffer_id' arguments are ignored.
 *
 * If 'ofconn' is nonnull, then the new ofopgroup is associated with 'ofconn'.
 * If the ofopgroup eventually fails, then the error reply will include
 * 'request'.  If the ofopgroup eventually succeeds, then the packet with
 * buffer id 'buffer_id' on 'ofconn' will be sent by 'ofconn''s ofproto.
 *
 * The caller should add operations to the returned group with
 * ofoperation_create() and then submit it with ofopgroup_submit(). */
static struct ofopgroup *
ofopgroup_create(struct ofproto *ofproto, struct ofconn *ofconn,
                 const struct ofp_header *request, uint32_t buffer_id)
{
    struct ofopgroup *group = ofopgroup_create_unattached(ofproto);
    if (ofconn) {
        size_t request_len = ntohs(request->length);

        ovs_assert(ofconn_get_ofproto(ofconn) == ofproto);

        ofconn_add_opgroup(ofconn, &group->ofconn_node);
        group->ofconn = ofconn;
        group->request = xmemdup(request, MIN(request_len, 64));
        group->buffer_id = buffer_id;
    }
    return group;
}

ofopgroup_create_unattached 関数はメモリを確保して list の初期化をするだけです。 ofconn_add_opgroup は ofconn 構造体の pending ofopgroup に ofconn_node を入れるだけです。 何なんでしょう...

最終的に、add_flow 関数では ofopgroup_submit 関数が実行されていました。これはofproto/ofproto.c にあります。

/* Submits 'group' for processing.
 *
 * If 'group' contains no operations (e.g. none were ever added, or all of the
 * ones that were added completed synchronously), then it is destroyed
 * immediately.  Otherwise it is added to the ofproto's list of pending
 * groups. */
static void
ofopgroup_submit(struct ofopgroup *group)
{
    if (!group->n_running) {
        ofopgroup_complete(group);
    } else {
        list_push_back(&group->ofproto->pending, &group->ofproto_node);
        group->ofproto->n_pending++;
    }
}

もし running のものがなければ ofopgroup_complete を実行して、そうでなければ pending の list に追加しているようです。

ofoperation の多くは ofproto-dpif の層で実行されます。この層の中から operation が終了したかどうか、エラーがあったかどうかを ofproto に伝える手段として、ofoperation の中の error が使われているようです。ofproto-dpif の中で ofoperation_complete が実行されていて、この関数によって ofproto の層で必要となる ofoperation の終了処理をしているようです。

全ての ofoperation が実行される(group->n_running が 0 になる)と、ofopgroup_complete が呼ばれます。

static void
ofopgroup_complete(struct ofopgroup *group)
{
    struct ofproto *ofproto = group->ofproto;

    struct ofconn *abbrev_ofconn;
    ovs_be32 abbrev_xid;

    struct ofoperation *op, *next_op;
    int error;

    ovs_assert(!group->n_running);

    error = 0;
    LIST_FOR_EACH (op, group_node, &group->ops) {
        if (op->error) {
            error = op->error;
            break;
        }
    }

エラーが出ているかどうかを調べる処理。

    if (!error && group->ofconn && group->buffer_id != UINT32_MAX) {
        LIST_FOR_EACH (op, group_node, &group->ops) {
            if (op->type != OFOPERATION_DELETE) {
                struct ofpbuf *packet;
                uint16_t in_port;

                error = ofconn_pktbuf_retrieve(group->ofconn, group->buffer_id,
                                               &packet, &in_port);
                if (packet) {
                    ovs_assert(!error);
                    error = rule_execute(op->rule, in_port, packet);
                }
                break;
            }
        }
    }

もし buffer_id が OpenFlow のメッセージに含まれていれば、buffer_id で指定されるパケットを処理します。

    if (!error && !list_is_empty(&group->ofconn_node)) {
        abbrev_ofconn = group->ofconn;
        abbrev_xid = group->request->xid;
    } else {
        abbrev_ofconn = NULL;
        abbrev_xid = htonl(0);
    }
    LIST_FOR_EACH_SAFE (op, next_op, group_node, &group->ops) {
        struct rule *rule = op->rule;

        /* We generally want to report the change to active OpenFlow flow
           monitors (e.g. NXST_FLOW_MONITOR).  There are three exceptions:

              - The operation failed.

              - The affected rule is not visible to controllers.

              - The operation's only effect was to update rule->modified. */
        if (!(op->error
              || ofproto_rule_is_hidden(rule)
              || (op->type == OFOPERATION_MODIFY
                  && op->ofpacts
                  && rule->flow_cookie == op->flow_cookie))) {
            /* Check that we can just cast from ofoperation_type to
             * nx_flow_update_event. */
            BUILD_ASSERT_DECL((enum nx_flow_update_event) OFOPERATION_ADD
                              == NXFME_ADDED);
            BUILD_ASSERT_DECL((enum nx_flow_update_event) OFOPERATION_DELETE
                              == NXFME_DELETED);
            BUILD_ASSERT_DECL((enum nx_flow_update_event) OFOPERATION_MODIFY
                              == NXFME_MODIFIED);

            ofmonitor_report(ofproto->connmgr, rule,
                             (enum nx_flow_update_event) op->type,
                             op->reason, abbrev_ofconn, abbrev_xid);
        }

ofmonitor に変更内容を報告する処理。

        rule->pending = NULL;

        switch (op->type) {
        case OFOPERATION_ADD:
            if (!op->error) {
                uint16_t vid_mask;

                ofproto_rule_destroy__(op->victim);
                vid_mask = minimask_get_vid_mask(&rule->cr.match.mask);
                if (vid_mask == VLAN_VID_MASK) {
                    if (ofproto->vlan_bitmap) {
                        uint16_t vid = miniflow_get_vid(&rule->cr.match.flow);
                        if (!bitmap_is_set(ofproto->vlan_bitmap, vid)) {
                            bitmap_set1(ofproto->vlan_bitmap, vid);
                            ofproto->vlans_changed = true;
                        }
                    } else {
                        ofproto->vlans_changed = true;
                    }
                }
            } else {
                oftable_substitute_rule(rule, op->victim);
                ofproto_rule_destroy__(rule);
            }
            break;

vlan id 関係が変更されていれば次のループでvlan関係の設定変更をするためにフラグを立てている処理?

        case OFOPERATION_DELETE:
            ovs_assert(!op->error);
            ofproto_rule_destroy__(rule);
            op->rule = NULL;
            break;

        case OFOPERATION_MODIFY:
            if (!op->error) {
                rule->modified = time_msec();
            } else {
                rule->flow_cookie = op->flow_cookie;
                if (op->ofpacts) {
                    free(rule->ofpacts);
                    rule->ofpacts = op->ofpacts;
                    rule->ofpacts_len = op->ofpacts_len;
                    op->ofpacts = NULL;
                    op->ofpacts_len = 0;
                }
            }
            break;

        default:
            NOT_REACHED();
        }

        ofoperation_destroy(op);
    }

    ofmonitor_flush(ofproto->connmgr);

ここら辺までは OFOPERATION の処理が終わった事後処理みたいです。

    if (!list_is_empty(&group->ofproto_node)) {
        ovs_assert(ofproto->n_pending > 0);
        ofproto->n_pending--;
        list_remove(&group->ofproto_node);
    }
    if (!list_is_empty(&group->ofconn_node)) {
        list_remove(&group->ofconn_node);
        if (error) {
            ofconn_send_error(group->ofconn, group->request, error);
        }
        connmgr_retry(ofproto->connmgr);
    }

ofprotoやofconnのpendingになっているofopgroupのリストから削除する処理をしています。

    free(group->request);
    free(group);
}

終わり。

エントリの削除: delete_flow_strict, delete_flows_loose

どちらも ofproto/ofproto.c にあります。該当するルールを検索して削除しているだけです。

static void
delete_flow__(struct rule *rule, struct ofopgroup *group)
{
    struct ofproto *ofproto = rule->ofproto;

    ofproto_rule_send_removed(rule, OFPRR_DELETE);

    ofoperation_create(group, rule, OFOPERATION_DELETE, OFPRR_DELETE);
    oftable_remove_rule(rule);
    ofproto->ofproto_class->rule_destruct(rule);
}

/* Deletes the rules listed in 'rules'.
 *
 * Returns 0 on success, otherwise an OpenFlow error code. */
static enum ofperr
delete_flows__(struct ofproto *ofproto, struct ofconn *ofconn,
               const struct ofp_header *request, struct list *rules)
{
    struct rule *rule, *next;
    struct ofopgroup *group;

    group = ofopgroup_create(ofproto, ofconn, request, UINT32_MAX);
    LIST_FOR_EACH_SAFE (rule, next, ofproto_node, rules) {
        delete_flow__(rule, group);
    }
    ofopgroup_submit(group);

    return 0;
}

ofopgroup でグループを作って、ofoperationで1つ1つのルールを削除しています。終わったら ofopgroup_submit で事後処理をしています。

エントリの変更: modify_flows_loose, modify_flow_strict

エントリの削除の場合と同様、変更の対象となるルールを抽出して、ルールがなければ add_flow でルールを追加、ルールがあれば modify_flows__ 関数でルールの変更をしています。

よく分からない点

ofconn_node と ofproto_node がどのように使われているのか、という点がよく分かっていませんが、ソースコードを grep しているとあまり意味ないんじゃないかという気がします。特にリストになっている理由とか...

ofopgroup と ofoperation の設計が少し汚いような気がしますが、なぜこのような設計になっていえるのかもよく分かりません。



このエントリーをはてなブックマークに追加