修ns3的ofswitch13源码之实现Sketch

实现Sketch

Posted by MZ on March 12, 2024

NS3模拟实现Sketch查找TOP-K

由于sketch相关实现采用的是P4原语,而在ns3中没有这方面的支持,所以现在采用模拟sketch的方案来实现sketch相关功能。具体实现是在交换机经过数据包时解析数据包获取四元组,然后计算hash值,并存入到自己设置的sketch数据结构中。最后ns3运行采取定期上传sketch中的数据,以供控制器区分长短流。

1.从openflow交换机的 pipeline中获取数据包四元组

每个数据包都要经过pipeline进行流表匹配,所以在pipeline_process_packet函数中我们加一个处理就是获取每个数据包的四元组packet_get_four_tuple,然后进行hash,判断是否进入sketch

/* Extracting the four-tuple from a data packet. */
/**
 * @brief 获取数据包的四元组
 * 
 * @param pkt 数据包元数据
 */
bool 
packet_get_four_tuple(struct packet *pkt){
    struct ofl_match_tlv *f;
    int packet_header;
    uint8_t *packet_val;
    uint8_t flag = 0;
    enum {
        FLAG_IP_SRC = 1,
        FLAG_IP_DST = 2,
        FLAG_TCP_SRC = 4,
        FLAG_TCP_DST = 8,
        FLAG_FOUR_TUPLE = 15
    };
    struct packet_handle_std *handle = pkt->handle_std;

    struct four_tuple *ft = malloc(sizeof(struct four_tuple));

    if (ft == NULL) {
        // 处理内存分配失败的情况
        return false;
    }

    if (!handle->valid){
        //判断数据包里面的match是否有数据,没有就解析并保存到match中
        packet_handle_std_validate(handle);
        if (!handle->valid){
            free(ft);
            return false;
        }
    }
    struct ofl_match *match = &handle->match;
    //循环查找四元组
    HMAP_FOR_EACH(f, struct ofl_match_tlv, hmap_node, &match->match_fields){
        packet_header = f->header;
        packet_val = f->value;
        if(packet_header != 0 && *packet_val != 0){
            switch (packet_header)
            {
            case OXM_OF_IPV4_SRC:
                ft->ip_src = *((uint32_t *)packet_val);
                flag |= FLAG_IP_SRC;
                break;
            case OXM_OF_IPV4_DST:
                ft->ip_dst = *((uint32_t *)packet_val);
                flag |= FLAG_IP_DST;
                break;
            case OXM_OF_TCP_SRC:
                ft->tcp_src = *((uint16_t *)packet_val);
                flag |= FLAG_TCP_SRC;
                break;
            case OXM_OF_TCP_DST:
                ft->tcp_dst = *((uint16_t *)packet_val);
                flag |= FLAG_TCP_DST;
                break;
            default:
                break;
            }
        }
    }
    if(ft != NULL && flag == FLAG_FOUR_TUPLE){
        VLOG_DBG_RL(LOG_MODULE, &rl, "--ft->ip_src: %u, --ft->ip_dst: %u, --ft->tcp_src: %u, --ft->tcp_dst: %u, ", 
                    ft->ip_src, ft->ip_dst, ft->tcp_src, ft->tcp_dst);
        record_sketch(ft);
        free(ft);
        return true;
    }else{
        free(ft);
        return false;
    }
}

2.计算hash,并记录到sketch中

与sketch有关代码,写在packet.c中

/**
 * @brief 计算hash值
 * 
 * @param ft 数据包四元组
 * @return uint8_t 返回hash值
 */
uint8_t sketch_hash(const struct four_tuple *ft){
    // 对每个成员进行取模操作
    uint32_t ip_src_mod = (ft->ip_src/3) % MOD;
    uint32_t ip_dst_mod = (ft->ip_dst/4) % MOD;
    uint32_t tcp_src_mod = (ft->tcp_src/5) % MOD;
    uint32_t tcp_dst_mod = (ft->tcp_dst/6) % MOD;

    // 求和
    uint32_t sum = ip_src_mod + ip_dst_mod + tcp_src_mod + tcp_dst_mod;

    // 对和再取模
    uint8_t hash_value = sum % MOD;
    return hash_value;
}

/* Compute the hash of a data packet and record it in a sketch. */
/**
 * @brief 计算数据包四元组的hash值并把它记录到sketch中
 * 
 * @param ft 数据包四元组
 */
void record_sketch(const struct four_tuple *ft){
    uint8_t hash_value = sketch_hash(ft);
    
    if(top_k_array[hash_value].exist_flow_flag == 0){
        top_k_array[hash_value].ft.ip_src = ft->ip_src;
        top_k_array[hash_value].ft.ip_dst = ft->ip_dst;
        top_k_array[hash_value].ft.tcp_src = ft->tcp_src;
        top_k_array[hash_value].ft.tcp_dst = ft->tcp_dst;
        top_k_array[hash_value].vote_yes++;
        top_k_array[hash_value].exist_flow_flag = 1;
    }else{
        if(four_tuple_compare(ft, &top_k_array[hash_value].ft) == 0){
            top_k_array[hash_value].vote_yes++;
        }else{
            top_k_array[hash_value].vote_no++;
        }
        if(top_k_array[hash_value].vote_no >= LAMDA * top_k_array[hash_value].vote_yes){
            clear_elephant_node(&top_k_array[hash_value]);
        }
    }  
}
/**
 * @brief 初始化sketch数据结构
 * 
 * @param array 大象流数组
 * @param length 数组长度
 */
void initialize_elephant_array(struct elephant_node *array, uint8_t length){
    for(uint8_t i = 0; i < length; i++){
        clear_elephant_node(&array[i]);
    }
}

/**
 * @brief 清除数组中某一项
 * 
 * @param node 
 */
void clear_elephant_node(struct elephant_node *node){
    node->ft.ip_src = 0;
    node->ft.ip_dst = 0;
    node->ft.tcp_src = 0;
    node->ft.tcp_dst = 0;
    node->vote_no = 0;
    node->vote_yes = 0;
    node->exist_flow_flag = 0;
}

/**
 * @brief 比较两个四元组是否相同
 * 
 * @param a 第一个四元组
 * @param b 第二个四元组
 * @return int 0相同,-1不同
 */
int four_tuple_compare(const struct four_tuple *a, const struct four_tuple *b) {
    if (a->ip_src != b->ip_src) return -1;
    if (a->ip_dst != b->ip_dst) return -1;
    if (a->tcp_src != b->tcp_src) return -1;
    if (a->tcp_dst != b->tcp_dst) return -1;
    return 0; // equal
}

3.Sketch定期上传数据(构建OpenFlow消息)

2023-9-23-修改适应ns3的bofuss库源码自定义NewMessage,包括构建新OpenFlow消息,交换机发送消息,控制器处理消息,这里只给出在pipeline中获取sketch数据封装成openflow消息并调用dp_send_message发送到控制器,其他步骤在另一篇文章已给出。

void
pipeline_handle_sketch_data(struct pipeline *pl, const struct sender *sender){
    struct ofl_msg_sketch_data msg;
    msg.header.type = OFPT_SKETCH_DATA;
    if(isInitialFlag == 0){
        initialize_elephant_array(top_k_array, TOP_K);
        isInitialFlag = 1;
    }
    for(uint8_t i = 0; i < TOP_K; i++){
        msg.elephant_flow[i].ip_src = top_k_array[i].ft.ip_src;
        msg.elephant_flow[i].ip_dst = top_k_array[i].ft.ip_dst;
        msg.elephant_flow[i].tcp_src = top_k_array[i].ft.tcp_src;
        msg.elephant_flow[i].tcp_dst = top_k_array[i].ft.tcp_dst; 
        if(top_k_array[i].ft.tcp_src != 0){
            VLOG_DBG_RL(LOG_MODULE, &rl, "pipeline_handle_sketch_data--ft->ip_src: %u, --ft->ip_dst: %u, --ft->tcp_src: %u, --ft->tcp_dst: %u, ", 
                    top_k_array[i].ft.ip_src, top_k_array[i].ft.ip_dst, top_k_array[i].ft.tcp_src, top_k_array[i].ft.tcp_dst);
        }
    }
    initialize_elephant_array(top_k_array, TOP_K);
    dp_send_message (pl->dp, (struct ofl_msg_header *)&msg, sender);
    return;
}