一:实现功能
(一)前提
1.无分组
2.首次实验:两条链路无时延迟;后面实验:考虑时延
3.使用UDP协议实现
(二)实验要求
1.只针对关键流进行,冗余链路传输,实现可靠传输
2.在目标主机的边缘交换机中实现去重功能
3.对非关键流,不进行冗余传输,正常单链路传输即可
4.根据源、目的IP,源、目的端口,还有上面的协议类型(UDP)进行区分是否为关键流
5.根据源、目的IP,源、目的端口,协议类型,以及id标识进行去冗余
6.重点--注意:我们对于关键流,第一个到达的直接发送给目标主机,将5中唯一标识(多个组合而成)记录,用于对后面到达的重复字段去冗余
(三)实验所需数据结构
1.队列进行去冗余,需要实现一个队列(这里使用数组即可,使用循环队列)----不太恰当
2.使用链表实现类似路由表功能(顺序查找)----恰当些
(四)所需实验
1.修改缓存队列大小,比如:4,8,16
2.修改两条链路时延,比如:下面链路时延是下面两倍
二:实验环境搭建
(一)网络拓扑实现

(二)OVS引擎实现

注意:这里的去重模块只是其中队列大小不同,是各自独立的实验
三:mininet拓扑、UDP通信实验开始

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define MAX_LEN 1000
//#define SER_IP "10.0.0.1"
#define MAXSIZE 40
#define IPOPT_TAG 0x21 //IP选项标志字段
#define IPOPT_LEN 8 //IP选项长度字段
int str_to_number(const char* str);
int main(int argc, char** argv)
{
int sk;
char buf[MAX_LEN];
struct sockaddr_in ser_addr; //是用于指定对方(目的主机)信息
struct sockaddr_in loc_addr; //可以用来指定一些本地的信息,比如指定端口进行通信,而不是让系统随机分配
int ser_addr_len,loc_addr_len;
int ret,count;
struct in_addr addr;
unsigned int SeqID=0;
unsigned char opt[MAXSIZE]; //ip option选项
opt[0] = 0x21;
opt[1] = IPOPT_LEN;
if (argc != 3)
{
printf("Error: the number of args must be 3\n");
exit(0);
}
//配置服务器信息
bzero(&ser_addr, sizeof(ser_addr));
ser_addr.sin_family = AF_INET; //设置为IPV4通信
//ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ser_addr.sin_addr.s_addr = inet_addr(argv[1]); //设置目的ip
ser_addr.sin_port = htons(str_to_number(argv[2])); //设置目的端口去链接服务器
ser_addr_len = sizeof(ser_addr);
//配置本地信息
bzero(&loc_addr, sizeof(loc_addr));
loc_addr.sin_family = AF_INET; //设置为IPV4通信
//loc_addr.sin_addr.s_addr = htonl(INADDR_ANY);
loc_addr.sin_addr.s_addr = htonl(INADDR_ANY); //设置目的ip
loc_addr.sin_port = htons(8080); //设置本地端口去链接服务器
loc_addr_len = sizeof(loc_addr);
sk = socket(AF_INET, SOCK_DGRAM, 0); //设置UDP报文传输 0表示默认 SOCK_DGRAM 默认使用UDP
//其中第三位 0 是调用方式标志位,设置socket通方式,比如非阻塞
if(sk<0)
{
printf("socket create failure\n");
return -1;
}
//将本地配置使用bind绑定
ret = bind(sk,(struct sockaddr*)&loc_addr,loc_addr_len);
if(ret < 0)
{
printf("socket bind failure\n");
return -1;
}
for (;;)
{
printf("Input info:>>>");
scanf("%s", buf);
if (!strcmp(buf, "quit"))
break;
*(int *)(opt + 2) = htonl(++SeqID);
setsockopt(sk,IPPROTO_IP,IP_OPTIONS,(void *)opt,IPOPT_LEN);
sendto(sk, buf, strlen(buf)+1, 0, (struct sockaddr*)&ser_addr, ser_addr_len);
}
printf("communicate end\n");
close(sk);
return 0;
}
int str_to_number(const char* str)
{
int i,len, num = 0;
len= strlen(str);
for (i = 0; i < len;i++)
num = num * 10 + str[i] - '0';
return num;
}
UDP客户端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define MAX_LEN 1000
int str_to_number(const char* str);
int main(int argc, char** argv)
{
char message[MAX_LEN];
int sk;
struct sockaddr_in src_addr; //用于指定本地监听信息
struct sockaddr_in cli_addr; //獲取客戶端地址信息
int src_addr_len,cli_addr_len;
int count,ret;
struct in_addr addr;
if (argc != 2) //获取监听端口
{
printf("Error: you must enter port to monite\n");
exit(0);
}
bzero(&src_addr, sizeof(src_addr));
src_addr.sin_family = AF_INET;
src_addr.sin_port = htons(str_to_number(argv[1]));
printf("port:%d\n",str_to_number(argv[1]));
src_addr_len = sizeof(src_addr);
cli_addr_len = sizeof(cli_addr);
sk = socket(AF_INET, SOCK_DGRAM, 0);
if(sk<0)
{
printf("socket create failure\n");
return -1;
}
ret = bind(sk, (struct sockaddr*)&src_addr, src_addr_len);
if(ret < 0)
{
printf("socket bind failure\n");
return -1;
}
while (1)
{
printf("Waiting for data from sender \n");
count = recvfrom(sk, message, MAX_LEN, 0, (struct sockaddr*)&cli_addr, &cli_addr_len);
if(count==-1)
{
printf("receive data failure\n");
return -1;
}
addr.s_addr = cli_addr.sin_addr.s_addr;
printf("Receive info: %s from %s %d\n", message,inet_ntoa(addr),cli_addr.sin_port);
}
close(sk);
return 0;
}
int str_to_number(const char* str)
{
int i,len, num = 0;
len= strlen(str);
for (i = 0; i < len;i++)
num = num * 10 + str[i] - '0';
return num;
}
UDP服务端

(二)mininet创建拓扑
from mininet.topo import Topo
from mininet.net import Mininet
from mininet.node import RemoteController
from mininet.link import TCLink
from mininet.util import dumpNodeConnections
class MyTopo(Topo):
def __init__(self):
super(MyTopo,self).__init__()
#add host
Host1 = self.addHost('h1')
Host2 = self.addHost('h2')
switch1 = self.addSwitch('e1')
switch2 = self.addSwitch('s1')
switch3 = self.addSwitch('s2')
switch4 = self.addSwitch('s3')
switch5 = self.addSwitch('s4')
switch6 = self.addSwitch('e2')
self.addLink(Host1,switch1)
self.addLink(switch1,switch2)
self.addLink(switch1,switch4)
self.addLink(switch2,switch3)
self.addLink(switch4,switch5)
self.addLink(switch3,switch6)
self.addLink(switch5,switch6)
self.addLink(switch6,Host2)
topos = {"mytopo":(lambda : MyTopo())}
注意:我们的通信网口从1开始,递增。在代码中我们对于同一个交换机按照代码顺序递增
例一:
self.addLink(Host1,switch1) 对于交换机e1,网口1,对应主机1
self.addLink(switch1,switch2) 对于交换机e1,网口2,对应s1
self.addLink(switch1,switch4) 对于交换机e1,网口3,对应s3
例二:
self.addLink(switch3,switch6) 对于交换机e2,网口1,对应s2
self.addLink(switch5,switch6) 对于交换机e2,网口2,对应s4
self.addLink(switch6,Host2) 对于交换机e2,网口3,对应主机2

1.删除原有流表

红色表示链路不通,绿色通路
2.添加全局流表(所有交换机都有---对应边缘e1 e2需要特殊处理)
dpctl add-flow in_port=1,actions=output:2
dpctl add-flow in_port=2,actions=output:1

3.修改E1流表
sh ovs-ofctl add-flow e1 in_port=1,actions=output:2,3 这里output:2是重复的额,可以要,可以不要,只写3,不影响
sh ovs-ofctl add-flow e1 in_port=3,actions=output:1 增加了从网口3进入,从网口1出去

4.修改E2交换机流表项解决网络风暴
sh ovs-ofctl del-flows e2 删除原来的错误流表
sh ovs-ofctl add-flow e2 in_port=3,actions=output:1,2 添加流表,使得主机2发送数据可以从两条链路发送出去
sh ovs-ofctl add-flow e2 in_port=1,actions=output:3 添加流表,使得s2交换机传递过来的数据,发送给主机2
sh ovs-ofctl add-flow e2 in_port=2,actions=output:3 添加流表,使得s4交换机传递过来的数据,发送给主机2


dpctl del-flows
dpctl add-flow in_port=1,actions=output:2
dpctl add-flow in_port=2,actions=output:1
sh ovs-ofctl add-flow e1 in_port=1,actions=output:2,3
sh ovs-ofctl add-flow e1 in_port=3,actions=output:1
sh ovs-ofctl del-flows e2
sh ovs-ofctl add-flow e2 in_port=3,actions=output:1,2
sh ovs-ofctl add-flow e2 in_port=1,actions=output:3
sh ovs-ofctl add-flow e2 in_port=2,actions=output:3
全部流表
5.流表配置完毕,网络可以通信

可以看到,按我们预期所想,使得主机B接收了冗余数据
重点:流表覆盖问题******
第一种
sh ovs-ofctl add-flow e2 in_port=3,actions=output:1,2
不等于
第二种
sh ovs-ofctl add-flow e2 in_port=3,actions=output:1
sh ovs-ofctl add-flow e2 in_port=3,actions=output:2
第一种正确,第二种出现第二条流表覆盖第一条流表项的情况,导致出错
五:实现队列、关键流数据结构
(一)队列实现

#ifndef __QUEUE_H__
#define __QUEUE_H__
#include <stdio.h>
#include <stdlib.h>
typedef struct
{
int *queue; //全局队列
int NUM; //队列大小
int TOP, REAR; //队首队尾标识
int EmpFlag; //队列判空标识
}Queue;
void InitQueue(Queue* q, int n); //队列初始
int EmptyOrFullQueue(Queue q); //队列判空以及判断满
int QueueLength(Queue q); //获取队列大小
void PushQueue(Queue* q,int ele); //入队操作
int PopQueue(Queue* q); //出队操作
int RePushQueue(Queue* q, int ele); //当一个数据第二次到达时对数据进行匹配出队操作
int FindElePos(Queue q, int ele, int* n); //查找元素位置
void ShowData(Queue q); //显示队列数据
#endif
queue.h

#include "queue.h"
void InitQueue(Queue* q,int n)
{
q->queue = (int *)malloc(sizeof(int)*n);
memset(q->queue, 0, sizeof(int)*n);
q->NUM = n;
q->TOP = q->REAR = 0;
q->EmpFlag = 1; //空队列
}
int EmptyOrFullQueue(Queue q)
{
return q.EmpFlag;
}
int QueueLength(Queue q)
{
if (EmptyOrFullQueue(q) == 1)
return 0;
if (EmptyOrFullQueue(q) == 2)
return q.NUM;
if (q.TOP > q.REAR)
return q.REAR + q.NUM - q.TOP;
else
return q.REAR - q.TOP;
}
void PushQueue(Queue* q, int ele)
{
if (RePushQueue(q, ele) == 1)
return;
if (q->EmpFlag == 2) //队列满的情况入队
PopQueue(q); //先出队队首,再入队
q->queue[q->REAR] = ele;
q->REAR = (q->REAR + 1) % q->NUM;
if (q->TOP == q->REAR)
q->EmpFlag = 2; //为满队列
else
q->EmpFlag = 3;
}
void ShowData(Queue q)
{
int i;
for (i = 0; i < QueueLength(q); i++)
printf("%d ", q.queue[(i + q.TOP) % q.NUM]); //打印数据,可能需要修改
printf("\n");
}
int PopQueue(Queue* q)
{
int temp = q->queue[q->TOP];
if (q->EmpFlag == 1) //队列空的情况入队
return -1;
q->TOP++;
if (q->TOP == q->NUM)
q->TOP = 0;
if (q->TOP == q->REAR)
q->EmpFlag = 1; //为空队列
else
q->EmpFlag = 3;
return temp;
}
int RePushQueue(Queue* q, int ele)
{
int n; //用于记录元素个数
int pos = FindElePos(*q, ele, &n);
if (pos == -1)
return 0; //可以直接插入
q->TOP = pos;
if (QueueLength(*q) == 0)
q->EmpFlag = 1;
else
q->EmpFlag = 3;
return 1; //不允许插入
}
int FindElePos(Queue q, int ele,int* n)
{
int i;
for (i = 0; i < QueueLength(q); i++)
if (q.queue[(i + q.TOP) % q.NUM] == ele)
{
*n = i + 1; //返回队首到该元素,一共几个数据
return (i + q.TOP + 1) % q.NUM; //返回该元素位置的下一个位置,新的队首
}
return -1;
}
queue.c
队列操作:对于第二次到达的数据,若是在队列中匹配到,则将该数据以及前面的数据全部出队
(二)关键流操作实现

#ifndef __KEY_FLOW_H__
#define __KEY_FLOW_H__
#include <string.h>
#include "queue.h"
#define IP_MAX_LEN 16
#define IP_GROUP_MAP 2
char FlowInfo[IP_GROUP_MAP][4][IP_MAX_LEN];
typedef struct
{
char src_ip[IP_MAX_LEN];
char dst_ip[IP_MAX_LEN];
int src_port;
int dst_port;
Queue que;
}Flows;
typedef struct
{
Flows flow[IP_GROUP_MAP];
}KeyFlows;
void InitKeyFlow(KeyFlows* kf, int n); //初始化静态关键流
int Str2Num(char* str); //字符串转数字
unsigned int getIPNumber(char *s); //IP字符串转数字
#endif // !__KEY_FLOW_H__
keyflows.h

#define _CRT_SECURE_NO_WARNINGS
#include "queue.h"
#include "keyflow.h"
#include <stdlib.h>
#include <string.h>
char FlowInfo[IP_GROUP_MAP][4][IP_MAX_LEN] = {
{ "10.0.0.1", "10.0.0.2", "8080", "9090" },
{ "10.0.0.1", "10.0.0.2", "7070", "8080" }
};
void InitKeyFlow(KeyFlows* kf, int n)
{
int i;
memset(kf, 0, sizeof(KeyFlows));
for (i = 0; i < IP_GROUP_MAP;i++)
{
strcpy(kf->flow[i].src_ip, FlowInfo[i][0]);
strcpy(kf->flow[i].dst_ip, FlowInfo[i][1]);
kf->flow[i].src_port = Str2Num(FlowInfo[i][2]);
kf->flow[i].dst_port = Str2Num(FlowInfo[i][3]);
InitQueue(&kf->flow[i].que,n);
}
}
int Str2Num(char* str)
{
int i,num = 0;
int len = strlen(str);
for (i = 0; i <= len - 1; i++)
{
num = num * 10 + str[i]-'0';
}
return num;
}
unsigned int getIPNumber(char *s)
{
char Ip_Part[4] = { 0 };
unsigned int num=0,i=0,j=0,k=3,nums[4];
while (s[i]!='\0'&&s[i]!=' ')
{
if (s[i] == '.'||s[i]=='\0')
{
nums[k--] = Str2Num(Ip_Part);
memset(Ip_Part, 0, sizeof(Ip_Part));
j = 0,i++;
}
else
{
Ip_Part[j++] = s[i++];
}
}
nums[k--] = Str2Num(Ip_Part);
for (i = 0; i < 4; i++)
num = num * 256 + nums[i];
return num;
}
keyflows.c
注意:其中内存分配上使用malloc在linux内核中需要使用kmalloc(物理上连续)或者vmalloc(逻辑上连续)替换
六:修改OVS源码实现队列去冗余

//-------------------queue start---------------------
typedef struct
{
int *queue; //È«¾Ö¶ÓÁÐ
int NUM; //¶ÓÁдóС
int TOP, REAR; //¶ÓÊ×¶Óβ±êʶ
int EmpFlag; //¶ÓÁÐÅпձêʶ
}Queue;
void InitQueue(Queue* q, int n); //¶ÓÁгõʼ
int EmptyOrFullQueue(Queue q); //¶ÓÁÐÅпÕÒÔ¼°ÅжÏÂú
int QueueLength(Queue q); //»ñÈ¡¶ÓÁдóС
int PushQueue(Queue* q,int ele); //Èë¶Ó²Ù×÷
int PopQueue(Queue* q); //³ö¶Ó²Ù×÷
int RePushQueue(Queue* q, int ele); //µ±Ò»¸öÊý¾ÝµÚ¶þ´Îµ½´ïʱ¶ÔÊý¾Ý½øÐÐÆ¥Åä³ö¶Ó²Ù×÷
int FindElePos(Queue q, int ele,int* n); //²éÕÒÔªËØÎ»ÖÃ
void ShowData(Queue q); //ÏÔʾ¶ÓÁÐÊý¾Ý
//-----------------queue end----------------------------
//----------------keyflow start------------------------
#define IP_MAX_LEN 16
#define IP_GROUP_MAP 2
typedef struct
{
unsigned int src_ip;
unsigned int dst_ip;
int src_port;
int dst_port;
Queue que;
}Flows;
typedef struct
{
Flows flow[IP_GROUP_MAP];
}KeyFlows;
void InitKeyFlow(KeyFlows* kf, int n); //³õʼ»¯¾²Ì¬¹Ø¼üÁ÷
int Str2Num(char* str); //×Ö·û´®×ªÊý×Ö
unsigned int getIPNumber(char *s); //IP×Ö·û´®×ªÊý×Ö
int getFlowSeq(KeyFlows kf,unsigned int sip,unsigned int dip,int sport,int dport);
//----------------keyflow end------------------------
int Key_flows_pattarn(struct vport *, struct sk_buff *);
vport.h

//-------------------queue start---------------------
void InitQueue(Queue* q,int n)
{
q->queue = (int *)kmalloc(sizeof(int)*n,GFP_KERNEL);
memset(q->queue, 0, sizeof(int)*n);
q->NUM = n;
q->TOP = q->REAR = 0;
q->EmpFlag = 1; //¿Õ¶ÓÁÐ
}
int EmptyOrFullQueue(Queue q)
{
return q.EmpFlag;
}
int QueueLength(Queue q)
{
if (EmptyOrFullQueue(q) == 1)
return 0;
if (EmptyOrFullQueue(q) == 2)
return q.NUM;
if (q.TOP > q.REAR)
return q.REAR + q.NUM - q.TOP;
else
return q.REAR - q.TOP;
}
int PushQueue(Queue* q, int ele)
{
if (RePushQueue(q, ele) == 1)
return 1;
if (q->EmpFlag == 2) //¶ÓÁÐÂúµÄÇé¿öÈë¶Ó
PopQueue(q); //Ïȳö¶Ó¶ÓÊ×£¬ÔÙÈë¶Ó
q->queue[q->REAR] = ele;
q->REAR = (q->REAR + 1) % q->NUM;
if (q->TOP == q->REAR)
q->EmpFlag = 2; //ΪÂú¶ÓÁÐ
else
q->EmpFlag = 3;
return 0;
}
int PopQueue(Queue* q)
{
int temp = q->queue[q->TOP];
if (q->EmpFlag == 1) //¶ÓÁпյÄÇé¿öÈë¶Ó
return -1;
q->TOP++;
if (q->TOP == q->NUM)
q->TOP = 0;
if (q->TOP == q->REAR)
q->EmpFlag = 1; //Ϊ¿Õ¶ÓÁÐ
else
q->EmpFlag = 3;
return temp;
}
int RePushQueue(Queue* q, int ele)
{
int n; //ÓÃÓÚ¼ÇÂ¼ÔªËØ¸öÊý
int pos = FindElePos(*q, ele, &n);
if (pos == -1)
return 0; //¿ÉÒÔÖ±½Ó²åÈë
q->TOP = pos;
if (QueueLength(*q) == 0)
q->EmpFlag = 1;
else
q->EmpFlag = 3;
return 1; //²»ÔÊÐí²åÈë
}
int FindElePos(Queue q, int ele,int* n)
{
int i;
for (i = 0; i < QueueLength(q); i++)
if (q.queue[(i + q.TOP) % q.NUM] == ele)
{
*n = i + 1; //·µ»Ø¶ÓÊ×µ½¸ÃÔªËØ£¬Ò»¹²¼¸¸öÊý¾Ý
return (i + q.TOP + 1) % q.NUM; //·µ»Ø¸ÃÔªËØÎ»ÖõÄÏÂÒ»¸öλÖã¬ÐµĶÓÊ×
}
return -1;
}
//-----------------queue end----------------------------
//----------------keyflow start------------------------
char FlowInfo[IP_GROUP_MAP][4][IP_MAX_LEN] = {
{ "10.0.0.1", "10.0.0.2", "8080", "9090" },
{ "10.0.0.1", "10.0.0.2", "7070", "8080" }
};
void InitKeyFlow(KeyFlows* kf, int n)
{
int i;
memset(kf, 0, sizeof(KeyFlows));
for (i = 0; i < IP_GROUP_MAP;i++)
{
kf->flow[i].src_ip = getIPNumber(FlowInfo[i][0]);
kf->flow[i].dst_ip = getIPNumber(FlowInfo[i][1]);
kf->flow[i].src_port = Str2Num(FlowInfo[i][2]);
kf->flow[i].dst_port = Str2Num(FlowInfo[i][3]);
InitQueue(&kf->flow[i].que,n);
}
}
int Str2Num(char* str)
{
int i,num = 0;
int len = strlen(str);
for (i = 0; i <= len - 1; i++)
{
num = num * 10 + str[i]-'0';
}
return num;
}
unsigned int getIPNumber(char *s)
{
char Ip_Part[4] = { 0 };
unsigned int num=0,i=0,j=0,k=3,nums[4];
while (s[i]!='\0'&&s[i]!=' ')
{
if (s[i] == '.'||s[i]=='\0')
{
nums[k--] = Str2Num(Ip_Part);
memset(Ip_Part, 0, sizeof(Ip_Part));
j = 0,i++;
}
else
{
Ip_Part[j++] = s[i++];
}
}
nums[k--] = Str2Num(Ip_Part);
for (i = 0; i < 4; i++)
num = num * 256 + nums[i];
return num;
}
int getFlowSeq(KeyFlows kf,unsigned int sip,unsigned int dip,int sport,int dport)
{
int i;
for(i=0;i<IP_GROUP_MAP;i++)
{
if(kf.flow[i].src_ip==sip && kf.flow[i].dst_ip==dip &&
kf.flow[i].src_port==sport && kf.flow[i].dst_port == dport)
return i;
}
return -1;
}
//----------------keyflow end------------------------
KeyFlows KF;
int Initflag=0;
vport.c前部分
vport.c后部分
int Key_flows_pattarn(struct vport *vport,struct sk_buff *skb)
{
struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb);
struct udphdr *udp_header;
unsigned int i=0;
unsigned int src_ip = (unsigned int)ip_header->saddr;
unsigned int dest_ip = (unsigned int)ip_header->daddr;
unsigned int src_port = 0;
unsigned int dest_port = 0;
unsigned int Seq_no=0;
int flows_no=-1,reput_flag=0;
char* IP_OptVal;
const char *vp_name = ovs_vport_name(vport);
if (ip_header->protocol==17 && strstr(vp_name,"e2")!=NULL) {
if(!Initflag){ //Init queue
InitKeyFlow(&KF, 4);
Initflag=1;
}
udp_header = (struct udphdr *)skb_transport_header(skb);
src_port = (unsigned int)ntohs(udp_header->source);
dest_port = (unsigned int)ntohs(udp_header->dest);
IP_OptVal=(char*)((char*)ip_header+(ip_header->ihl*4-8));for(i=2;i<6;i++)
Seq_no = Seq_no*256+(unsigned int)IP_OptVal[i];//match key flows
for(i=0;i<IP_GROUP_MAP;i++)
{if(KF.flow[i].src_ip==src_ip && KF.flow[i].dst_ip==dest_ip &&
KF.flow[i].src_port==src_port && KF.flow[i].dst_port == dest_port)
{
flows_no = i;
break;
}
}
pr_info("flow_no:%d\n",flows_no);
if(flows_no!=-1) //key flows matched
{
reput_flag = PushQueue(&((KF.flow[flows_no]).que), Seq_no);return reput_flag;
}
}return reput_flag;
}
int ovs_vport_receive(struct vport *vport, struct sk_buff *skb,
const struct ip_tunnel_info *tun_info)
{
struct sw_flow_key key;
int error=1;
struct ovs_frag_data *data = this_cpu_ptr(&ovs_frag_data_storage);
if (data->mac_proto == MAC_PROTO_ETHERNET) {
pr_info("---- MAC_PROTO_ETHERNET -----\n");
}
error = Key_flows_pattarn(vport,skb);
pr_info("ovs_vport_receive-----return :%d\n",error);
if (error) {
kfree_skb(skb);
return -ENOMEM;
}
OVS_CB(skb)->input_vport = vport;
OVS_CB(skb)->mru = 0;
OVS_CB(skb)->cutlen = 0;
if (unlikely(dev_net(skb->dev) != ovs_dp_get_net(vport->dp))) {
u32 mark;
mark = skb->mark;
skb_scrub_packet(skb, true);
skb->mark = mark;
tun_info = NULL;
}
ovs_skb_init_inner_protocol(skb);
skb_clear_ovs_gso_cb(skb);
/* Extract flow from 'skb' into 'key'. */
error = ovs_flow_key_extract(tun_info, skb, &key);
if (unlikely(error)) {
kfree_skb(skb);
return error;
}
ovs_dp_process_packet(skb, &key);
return 0;
}

对于关键流,实现去冗余,只收到一条数据。因为发送的是第二条数据,故选项字段数据为2.
