SDN功能实现(三)--- 实现自定义action(1)修改OVS源码<随机丢包>

实现功能:设计一个新的action,实现可以自己指定丢包概率

一:定义openflow action

(一)lib / ofp-actions.c:引入添加自己的操作代码,作为OpenFlow的扩展

enum ofp_raw_action_type {
    /* ... */

    /* NX1.3+(47): struct nx_action_decap, ... */
    NXAST_RAW_DECAP,

    /* OF1.0+(29): uint32_t. */
  OFPAT_RAW_PROBDROP,

/* ... */
}

定义了一个新操作:OFPAT_RAW_PROBDROP。

(二)注释说明

注释非常重要,说明了协议版本,序号,构造openflow消息所需参数
有些函数头是根据协议版本、您选择的代码和操作所需的参数类型自动生成的。 后面的序号是独一无二的,不能在同一协议版本中出现两个一样的序号

(三)补充:自动生成函数

put_OFPAT_action构造openflow消息
put_OFPAT_PROBDROP: 根据 uint32_t构造出openflow消息

二:定义openvswitch action

(一)所有action定义在include/openvswitch/ofp-actions.h中添加

    OFPACT(GOTO_TABLE,      ofpact_goto_table,  ofpact, "goto_table") \
    OFPACT(PROBDROP,        ofpact_probdrop,    ofpact, "probdrop")
/* ..., after "struct ofpact_decap { ... }" */

/* OFPACT_PROBDROP.
 *
 * Used for OFPAT_PROBDROP */
struct ofpact_probdrop {
    OFPACT_PADDED_MEMBERS(
        struct ofpact ofpact;
        uint32_t prob;           /* Uint probability, "covers" 0->1 range. */
    );
    uint8_t data[];
};

(二)补充自动生成函数

ofpact_put_PROBDROP: 将ofpbuf转化为struct ofpact_probdrop: openflow消息转化openvswitch action

ofpact_get_PROBDROP: 从ofpact获取为struct ofpact_probdrop: 获取openvswitch action

三:在内核级定义action

(一)在datapath/linux/compat/include/linux/openvswitch.h:中添加:

enum ovs_action_attr {
    /* ... */

    /*
    * after #ifndef __KERNEL__ ... #endif.
    * the equals is thus ABSOLUTELY NECESSARY
    */

    OVS_ACTION_ATTR_PROBDROP = 23, /* unit32_t, prob in [0,2^32 -1] */

    __OVS_ACTION_ATTR_MAX, /* Nothing past this will be accepted
                            * from userspace. */

    /* ... */

}

(二)注意:指定显示值

OVS_ACTION_ATTR_PROBDROP = 23 如果我们不为该枚举条目指定显式值,则内核和用户区部分 ovs-vswitchd将对新操作使用不同的代码(这里不加会出错)

四:openflow action与openvswitch action转化

(一)ofpact_decode---->decode_OFPAT_RAW_PROBDROP: 解openflow消息生成openvswitch action

(二)ofpact_encode---->encode_PROBDROP: 从ofpact_type构造openflow消息

(三)ofpact_parse---->parse_PROBDROP: 从字符串解析构造openvswitch action

(四)ofpact_format---->format_PROBDROP: 将openvswitch action转化为string

(五)ofpact_format---->check_PROBDROP:校验openvswitch action

(六)在lib/ofp-actions.c中修改代码:定义新的动作,编码,解码,形式化和校验

/* 在 check_PROBDROP 函数后面添加*/

/* Okay, the new stuff! */

/* Encoding the action packet to put on the wire. */
static void
encode_PROBDROP(const struct ofpact_probdrop *prob,
                    enum ofp_version ofp_version OVS_UNUSED,
                    struct ofpbuf *out)
{
    uint32_t p = prob->prob;

    put_OFPAT_PROBDROP(out, p);
}

/* Reversing the process. */
static enum ofperr
decode_OFPAT_RAW_PROBDROP(uint32_t prob,
                            enum ofp_version ofp_version OVS_UNUSED,
                            struct ofpbuf *out)
{
    struct ofpact_probdrop *op;
    op = ofpact_put_PROBDROP(out);
    op->prob = prob;

    return 0;
}

/* Helper for below. */
static char * OVS_WARN_UNUSED_RESULT
parse_prob(char *arg, struct ofpbuf *ofpacts)
{
    struct ofpact_probdrop *probdrop;
    uint32_t prob;
    char *error;

    error = str_to_u32(arg, &prob);
    if (error) return error;

    probdrop = ofpact_put_PROBDROP(ofpacts);
    probdrop->prob = prob;
    return NULL;
}

/* Go from string-formatted args into an action struct.
e.g. ovs-ofctl add-flow ... actions=probdrop:3000000000,output:"s2-eth0"
*/
static char * OVS_WARN_UNUSED_RESULT
parse_PROBDROP(char *arg, const struct ofpact_parse_params *pp)
{
    return parse_prob(arg, pp->ofpacts);
}

/* Used when printing info to console. */
static void
format_PROBDROP(const struct ofpact_probdrop *a,
                const struct ofpact_format_params *fp)
{
    /* Feel free to use e.g. colors.param,
    colors.end around parameter names */
    ds_put_format(fp->s, "probdrop:%"PRIu32, a->prob);
}

/* ... */

static enum ofperr
check_PROBDROP(const struct ofpact_probdrop *a OVS_UNUSED,
                const struct ofpact_check_params *cp OVS_UNUSED)
{
    /* My method needs no checking. Probably. */
    return 0;
}

五:定义action实现函数

(一)在内核模块中实现action函数,并调用执行,datapath/actions.c:

/* Ask for a random number.
   "p" is the amount we should let through, here true means drop,
   false means let it pass on */
static bool prob_drop(uint32_t prob)
{
    /* since we can't use rand() in the kernel */
    return prandom_u32() > prob;  
}

static int do_execute_actions(/* ... */)
{
    /* ... */
    switch (nla_type(a)) {
    /* ... */
    case OVS_ACTION_ATTR_PROBDROP:
        /* No need to free, taken care of for us
           This function just reads the attribute to
           know if we should drop. */
        if(prob_drop(nla_get_u32(a)))
        {
            while (rem) {
                a = nla_next(a, &rem);
            }
        }
        break;
    }
    /* ... */
}

  prob_drop函数用于判断是否应该丢弃该数据包,返回true则丢弃,false则正常传递该数据包。其中prandom_u32返回一个32为随机数0-4,294,967,296

我们实验中传递的参数是uint32_t prob,值为1,000,000,000。则由prandom_u32() > prob可知,有1/4概率不丢包。

(二)在用户态空间实现action操作(可以不实现用户态,在odp_execute_actions中进行OVS_NOT_REACHED操作)

要在用户空间数据路径中执行操作,则应添加/修改这些文件。

 lib/packets.h:

/* ... */

bool prob_drop(uint32_t prob);

#endif /* packets.h */

lib/packets.c:

/* Ask for a random number.
   "p" is the amount we should let through, here true means drop,
   false means let it pass on */
bool
prob_drop(uint32_t prob)
{
    unsigned int roll_i;
    random_bytes(&roll_i, sizeof(roll_i));
    return roll_i > prob;
}

lib/dpif-netdev.c:

static void
dp_execute_cb( /* ... */ )
{
    /* ... */
    switch ((enum ovs_action_attr)type) {
    /* ... */
    case OVS_ACTION_ATTR_PROBDROP:
        OVS_NOT_REACHED();
    }
}

lib/dpif.c:

static void
dpif_execute_helper_cb( /* ... */ )
{
    /* ... */
    switch ((enum ovs_action_attr)type) {
    /* ... */
    case OVS_ACTION_ATTR_PROBDROP:
        OVS_NOT_REACHED();
    }
}

ofproto/ofproto-dpif-ipfix.c:

void
dpif_ipfix_read_actions( /* ... */ )
{
    /* ... */
    switch (type) {
    /* ... */
    case OVS_ACTION_ATTR_PROBDROP:
        /* Again, ignore for now. Not needed. */
        break;
    }
}

ofproto/ofproto-dpif-sflow.c:

void
dpif_sflow_read_actions( /* ... */ )
{
    switch (type) {
    /* ... */
    case OVS_ACTION_ATTR_PROBDROP:
        /* Ignore sFlow for now, unless needed. */
        break;
    }
}

lib/odp-execute.c:用户态调用,执行action

static bool
requires_datapath_assistance(const struct nlattr *a)
{
    enum ovs_action_attr type = nl_attr_type(a);

    switch (type) {
    /* ... */
    case OVS_ACTION_ATTR_PROBDROP:
        return false;
    /* ... */
    }
}
void
odp_execute_actions( /* ... */ )
{
    /* ... */
    switch ((enum ovs_action_attr)type) {
    /* ... */
    case OVS_ACTION_ATTR_PROBDROP: {    
        size_t i;
        const size_t num = dp_packet_batch_size(batch);

        DP_PACKET_BATCH_REFILL_FOR_EACH (i, num, packet, batch) {
            if (!prob_drop(nl_attr_get_u32(a))) {
                dp_packet_batch_refill(batch, packet, i);
            } else {
                dp_packet_delete(packet);
            }
        }
        break;
    }

    }
}

六:action处理

为了帮助ovs-vswitchd处理新动作,我们需要修改大量的switch语句,以便它知道该动作具有什么特征,如何正确地对其进行迭代以及如何验证所包含的任何参数。

其中的一部分包括对其进行分类,以便ovs知道动作如何与所使用的动作集进行交互WRITE_ACTIONS。

lib/ofp-actions.c:

struct ofpact *
ofpact_next_flattened(const struct ofpact *ofpact)
{
    switch (ofpact->type) {
        /* ... */
        case OFPACT_PROBDROP:
            return ofpact_next(ofpact);
    }
    /* ... */
}

/* ... */

enum ovs_instruction_type
ovs_instruction_type_from_ofpact_type(enum ofpact_type type)
{
    switch (type) {
    /* ... */
    case OFPACT_PROBDROP:
    default:
        return OVSINST_OFPIT11_APPLY_ACTIONS;
    /* ... */
    }
}

/* ... */

static bool
ofpact_outputs_to_port(const struct ofpact *ofpact, ofp_port_t port)
{
    switch (ofpact->type) {
    /* ... */
    case OFPACT_PROBDROP:
    default:
        return false;
    }
}

lib/ofp-actions.c:

static enum action_set_class
action_set_classify(const struct ofpact a*)
{
    switch (a->type) {
    /* ... */


    /* NEVER */
    /* ... */
    case OFPACT_PROBDROP:
        return ACTION_SLOT_INVALID;

    /* ... */
    }
}

lib/odp-util.c:定义动作的字节长度

static void
format_odp_action( /* ... */ )
{
    /* ... */
    switch (type) {
    /* ... */

    case OVS_ACTION_ATTR_PROBDROP: 
        ds_put_format(ds, "pdrop(%"PRIu32")", nl_attr_get_u32(a));
        break;

    /* ... */
    }
}

static int
odp_action_len(uint16_t type)
{
    /* ... */

    switch ((enum ovs_action_attr) type) {
    /* ... */
    case OVS_ACTION_ATTR_PROBDROP: return sizeof(uint32_t);
    }
}

七:处理内核数据路径和用户级守护程序之间的通信

在某些情况下,守护程序和内核模块通过Netlink套接字相互通信。

守护程序在到达时将流操作向下发送到内核(用于数据包处理),并在到达时轮询来自内核的任何上行调用。

通常,当到达的数据包与任何已知条目都不匹配时(即必须将该数据包发送到控制器,或者需要具体实例化通配符规则),就会发生这种情况。

ofproto/ofproto-dpif-xlate.c:

/* Put this with the other "compose" functions. */
static void
compose_probdrop_action(struct xlate_ctx *ctx, struct ofpact_probdrop *op)
{
    uint32_t prob = op->prob;

    nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_PROBDROP, prob);
}

/* ... */

static void
do_xlate_actions( /* ... */ )
{
    switch (a->type) {
    /* ... */

    case OFPACT_PROBDROP:
        compose_probdrop_action(ctx, ofpact_get_PROBDROP(a));
        break;
    }
}
/* ... */

/* No action can undo the packet drop: reflect this. */
static bool
reversible_actions(const struct ofpact *ofpacts, size_t ofpacts_len)
{
    const struct ofpact *a;

    OFPACT_FOR_EACH (a, ofpacts, ofpacts_len) {
        switch (a->type) {
        /*... */
        case OFPACT_PROBDROP:
            return false;
        }
    }
    return true;
}

/* ... */

/* PROBDROP likely doesn't require explicit thawing. */
static void
freeze_unroll_actions( /* ... */ )
{
    /* ... */
    switch (a->type) {
        case OFPACT_PROBDROP:
            /* These may not generate PACKET INs. */
            break;
    }
}

/* ... */

/* Naturally, don't need to recirculate since we don't change packets. */
static void
recirc_for_mpls(const struct ofpact *a, struct xlate_ctx *ctx)
{
    /* ... */

    switch (a->type) {
    case OFPACT_PROBDROP:
    default:
        break;
    }
}

转换的最重要部分是do_xlate_actions和 compose_probdrop_action,它们在一个动作集上进行迭代并probdrop分别传输一个动作。

沿套接字nl_msg_put_u32写入操 作类型(OVS_ACTION_ATTR_PROBDROP)和参数值,以供数据路径模块读取。

datapath/flow_netlink.c:内核部分,是对参数长度和值的最后检查

static int __ovs_nla_copy_actions( /*...*/ )
{
    /* ... */
    static const u32 action_lens[OVS_ACTION_ATTR_MAX + 1] = {
        /* ... */
        [OVS_ACTION_ATTR_PROBDROP] = sizeof(u32),
    };
    /* ... */

    /* Be careful here, your compiler may not catch this one
    * even with -Werror */
    switch (type) {
    /* ... */
    case OVS_ACTION_ATTR_PROBDROP:
        /* Finalest sanity checks in the kernel. */
        break;
    /* ... */
    }
    /* ... */
}

八:使用mininet进行测试

sh ovs-ofctl add-flow s1 in_port=1,actions=probdrop:1000000000,2

注意:不要在Ubuntu18下进行调试

补充:日志信息输出

1.在内核模式代码中,我们可以如上,使用pr_info等函数,将日志信息输出到dmesg中。(内核态不错)

2.在用户态下,虽然提供了VLOG_XXX函数,但是不太好查找(不太友好)

3.通过自定义日志,使用syslog函数进行日志输出(用户态不错)

https://blog.csdn.net/cpongo3/article/details/93995796

https://blog.csdn.net/jun2016425/article/details/79035566

posted @ 2020-02-10 16:04  山上有风景  阅读(1351)  评论(3)    收藏  举报