一:实验环境搭建
(一)设置网络拓扑

(二)实验目的
当两个主机通信,网络中可能存在多条路径可以进行数据传递,那么可能出现一个主机接收到多个相同的packet(从不同的路径传送过来的),我们需要进行packet去重,使得主机只收到一个有用的数据包。
例如:h1向h2发送消息,其中有两条路径可以将数据包送入h2中,h1->s1->s2->s3->s4->h2和h1->s1->s5->s4->h2。我们需要在s4交换机中实现:将所有送入h2的数据包进行去重处理
(三)使用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('s1')
switch2 = self.addSwitch('s2')
switch3 = self.addSwitch('s3')
switch4 = self.addSwitch('s4')
switch5 = self.addSwitch('s5')
self.addLink(Host1,switch1)
self.addLink(switch1,switch2)
self.addLink(switch2,switch3)
self.addLink(switch3,switch4)
self.addLink(switch1,switch5)
self.addLink(switch5,switch4)
self.addLink(switch4,Host2)
topos = {"mytopo":(lambda : MyTopo())}
(四)使用ryu控制器实现流表下发,控制每个交换机的数据包转发方式---(实现代码和之前的hub相似,只需要修改s1、s4即可)
对于s2、s3、s5使用泛洪即可,对于s1和s4交换机,我们需要将接受的数据包全部转发给对应主机端口
from ryu.base import app_manager
from ryu.ofproto import ofproto_v1_3
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER,CONFIG_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
class Hub(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
def __init__(self,*args,**kwargs):
super(Hub,self).__init__(*args,**kwargs)
self.Dst_Src_Table={}
@set_ev_cls(ofp_event.EventOFPSwitchFeatures,CONFIG_DISPATCHER)
def switch_features_handler(self,ev):
datapath = ev.msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
match = ofp_parser.OFPMatch()
actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,ofproto.OFPCML_NO_BUFFER)]
self.add_flow(datapath,0,match,actions,"default flow entry")
def add_flow(self,datapath,priority,match,actions,remind_content):
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
inst = [ofp_parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS,
actions)]
mod = ofp_parser.OFPFlowMod(datapath=datapath,priority=priority,
match=match,instructions=inst);
print("install to datapath,"+remind_content)
datapath.send_msg(mod);
@set_ev_cls(ofp_event.EventOFPPacketIn,MAIN_DISPATCHER)
def packet_in_handler(self,ev):
'''
最终测试得 s4 是 1,2端口进入,3端口出去 不知道s1和s4哪个端口是发送数据给主机的,可以进行简单测试
'''
msg = ev.msg
datapath = msg.datapath
ofproto = datapath.ofproto
ofp_parser = datapath.ofproto_parser
in_port = msg.match['in_port']
out_port_s4 = 3 #其中s4是通过端口3发送给主机h2的
out_port_s1 = 1 #其中s1是通过端口1发送给主机h1的
print("get packet in, install flow entry,and lookback parket to datapath")
match = ofp_parser.OFPMatch(in_port=in_port)
if datapath.id==4 and (in_port==1 or in_port==2): #其中s4的端口1和端口2是用于和交换机s3和s5通信的
actions = [ofp_parser.OFPActionOutput(out_port_s4)] #当接收到s3和s5交换机的信息,我们设置动作为,输出端口为3,发送给主机h2
else:
if datapath.id==1 and (in_port==2 or in_port==3): #其中s1的端口2和端口3是用于和交换机s2和s5通信的
actions = [ofp_parser.OFPActionOutput(out_port_s1)] #当接收到s2和s5交换机的信息,我们设置动作为,输出端口为1,发送给主机h1
else:
actions = [ofp_parser.OFPActionOutput(ofproto.OFPP_FLOOD)] #其他的交换机则是泛洪处理
if(datapath.id == 4 or datapath.id==1): #用于测试
print("datapath id:%d-----in_port:%d"%(datapath.id,in_port))
self.add_flow(datapath,1,match,actions,"hub flow entry")
out = ofp_parser.OFPPacketOut(datapath=datapath,buffer_id=msg.buffer_id,
in_port=in_port,actions=actions)
datapath.send_msg(out)
补充:我们若是不知道端口信息,可以先使用之前的hub代码,先进行测试。只需要加上这个代码
if(datapath.id == 4 or datapath.id==1): #用于测试
print("datapath id:%d-----in_port:%d"%(datapath.id,in_port))
使用h1 ping h2和h2 ping h1则知道s4和s1的哪个端口对应主机了。对应的其他交换机端口也可以知晓
注意:一定要修改s1和s4的动作,不然在该拓扑中会导致网络风暴
(五)python实现socket网络通信
使用select实现服务端:

import socket
import select
import queue
ip_port = ("0.0.0.0",8080)
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.setblocking(0)
sk.bind(ip_port)
sk.listen(5)
inputs = [sk,]
outputs = []
message_queue = {}
while True:
r_list, w_list,e_list = select.select(inputs,outputs,inputs)
for r in r_list:
if r == sk:
conn,addr = r.accept()
print("New Connection from %s:%s"%(addr[0],addr[1]))
conn.setblocking(0)
inputs.append(conn)
message_queue[conn] = queue.Queue()
else:
data = r.recv(1024)
ip_addr = r.getpeername()[0]
port_num = r.getpeername()[1]
if data:
print("%s(%s): %s"%(ip_addr, port_num,data.decode("utf-8")))
message_queue[r].put("rs:%s"%data.decode("utf-8"))
if r not in outputs:
outputs.append(r)
else:
print("%s closing"%ip_addr)
if r in outputs:
outputs.remove(r)
r.close()
del message_queue[r]
for w in w_list:
try:
rep_msg = message_queue[w].get_nowait()
except queue.Empty:
print("%s queue empty"%w.getpeername()[0])
outputs.remove(w)
else:
print("Sending %s to %s"%(rep_msg,w.getpeername()[0]))
w.sendall(rep_msg.encode("utf-8"))
for e in e_list:
print("%s exception error"%e.getperrname()[0])
inputs.remove(e)
if e in outputs:
outputs.remove(e)
e.close()
del message_queue[e]
View Code
功能:接收客户端发送的消息,并回复 rs:原消息
客户端发送:aaaa
服务端接收:aaaa
服务端回复:rs:aaaa
客户端发送消息:
import socket
ip_port = ("10.0.0.2",8080) #注意:这里必须是h2的ip地址,可以在mininet中查看到h2的ip
sk = socket.socket()
sk.connect(ip_port)
while True:
data = input(">>>:").strip()
if not data:
continue
sk.sendall(data.encode("utf-8"))
ser_data = sk.recv(1024)
print(ser_data.decode("utf-8"))
sk.close()
二:推文
(一)linux内核网络部分
(二)ovs介绍
(三)由于文章整理稍晚,部分好文无法推荐,待补充
(四)推荐使用sublime进行ovs和linux源码分析
三:实现环境前景显示(未修改ovs源码)
(一)使得h1和h2进行通信

(二)分析tcp包
1.查看第一个捕获的报文

2.查看第二个捕获的报文(可以看到有一条报文的info是一致的,并且出现TCP Spurious Retransmission状态)
TCP Spurious Retransmission----TCP虚假重传。 当抓到2次同一包数据时,wireshark判断网络发生了重传

四:修改ovs源码,实现packet去重(多搜源码)
这里主要是实现了tcp报文去重,其实可以直接对所有报文去重,应该会更简单,未去检查。大致思路是一致的
(一)实现思路
获取tcp/ip报文的一些特殊字段,如源ip,目的ip,源端口,目的端口,tcp请求seq,tcp确认ack,以及应用层数据,当然还包括每个网桥的标识(若是针对所有数据包,我们可以直接获取网桥标识和ip层或者链路层数据即可)
struct skb_packet_map {
unsigned int src_ip;
unsigned int dest_ip;
unsigned int src_port;
unsigned int dest_port;
unsigned int seq;
unsigned int ack_seq;
char vport_name[12];
char data[1024];
};
1.获取ip信息
struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb);
unsigned int src_ip = (unsigned int)ip_header->saddr;
unsigned int dest_ip = (unsigned int)ip_header->daddr;
2.获取端口信息
tcp_header = (struct tcphdr *)skb_transport_header(skb);
src_port = (unsigned int)ntohs(tcp_header->source);
dest_port = (unsigned int)ntohs(tcp_header->dest);
3.获取seq和ack信息
seq = (unsigned int)ntohs(tcp_header->ack); //这里写错了,应该是tcp_header->seq
ack_seq = (unsigned int)ntohs(tcp_header->ack_seq);
4.获取tcp数据
iph = ip_hdr(skb);
th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
data = (char *)th + (th->doff << 2);
5.获取网桥信息
const char *vp_name = ovs_vport_name(vport); 这里获取的是端口信息,如s1-eth0,我们还需要进行字符串处理,可以获取网桥信息,如:s1
(二)全部代码

int hook_func_out(struct vport *, struct sk_buff *);
struct skb_packet_map {
unsigned int src_ip;
unsigned int dest_ip;
unsigned int src_port;
unsigned int dest_port;
unsigned int seq;
unsigned int ack_seq;
char vport_name[12];
char data[1024];
};
vport.h

#define MAX_SPM_LEN 65535
#define TCP_CONNECT_COUNT 12
static struct skb_packet_map spm[MAX_SPM_LEN];
static unsigned int len=0;
int hook_func_out(struct vport *vport,struct sk_buff *skb)
{
struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb);
struct udphdr *udp_header;
struct tcphdr *tcp_header;
struct list_head *p;
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 = 0;
unsigned int ack_seq = 0;
char *data;
struct iphdr *iph;
struct tcphdr *th;
const char *vp_name = ovs_vport_name(vport);
pr_info("---- 6666666666666666 hook_func_out start 6666666666666666 -----\n");
if (ip_header->protocol==17) {
udp_header = (struct udphdr *)skb_transport_header(skb);
src_port = (unsigned int)ntohs(udp_header->source);
dest_port = (unsigned int)ntohs(udp_header->dest);
} else if (ip_header->protocol == 6) {
tcp_header = (struct tcphdr *)skb_transport_header(skb);
src_port = (unsigned int)ntohs(tcp_header->source);
dest_port = (unsigned int)ntohs(tcp_header->dest);
seq = (unsigned int)ntohs(tcp_header->ack);
ack_seq = (unsigned int)ntohs(tcp_header->ack_seq);
/*
iph=ip_hdr(skb);
th=(struct tcphdr*)((__u32*)iph+iph->ihl);
data=(char*)((__u32*)th+th->doff);
*/
iph = ip_hdr(skb);
th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
data = (char *)th + (th->doff << 2);
}
if (ip_header->protocol == 6 && strlen(data)!=0 && seq != 0){
pr_info("--------tcp info start---------");
pr_info("-----Tcp Comming----Vport name = %s IP addres = %pI4 DEST = %pI4 source port = %u dest port = %u seq = %u ack_seq = %u data = %s len = %d\n\n",
vp_name,&src_ip, &dest_ip,src_port, dest_port,seq, ack_seq,data,strlen(data));
pr_info("-----map info start-----");
for(i=0 ; i<len ; i++){
//find match data
pr_info("IP addres = %pI4 DEST = %pI4\n\n",&spm[i].src_ip, &spm[i].dest_ip);
pr_info("source port = %u dest port port = %u\n\n",spm[i].src_port, spm[i].dest_port);
pr_info("seq = %u ack_seq = %u\n\n",spm[i].seq, spm[i].ack_seq);
pr_info("data = %s data len = %d\n\n",spm[i].data,strlen(spm[i].data));
pr_info("vport name = %s\n\n",spm[i].vport_name);
if((spm[i].src_ip == src_ip)&&(spm[i].dest_ip == dest_ip)&&
(spm[i].src_port == src_port)&&(spm[i].dest_port == dest_port)&&
(spm[i].seq == seq)&&(spm[i].ack_seq == ack_seq) && !strcmp(spm[i].data,data)&&
strstr(vp_name,spm[i].vport_name)!=NULL){
pr_info("return 0------ len %u\n\n",len);
return 0;
goto flag;
}
}
spm[i].src_ip = src_ip;
spm[i].dest_ip = dest_ip;
spm[i].src_port = src_port;
spm[i].dest_port = dest_port;
spm[i].seq = seq;
spm[i].ack_seq = ack_seq;
memcpy(spm[i].data,data,strlen(data)+1);
memset(spm[i].vport_name,0,sizeof(spm[i].vport_name));
memcpy(spm[i].vport_name,vp_name,strchr(vp_name,'-')-vp_name);
if(++len > MAX_SPM_LEN)
len=0;
pr_info("-----map info end-----");
flag:
pr_info("------ len %u\n\n",len);
pr_info("--------tcp info end---------");
}
pr_info("---- 6666666666666666 hook_func_out end 6666666666666666 -----\n");
return 1;
}
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 = hook_func_out(vport,skb);
//pr_info("---- vport info -----\n");
//pr_info("***************datapath name: %s \n\n",ovs_dp_name(vport->dp)); //无用
//pr_info("***************vport name: %s \n\n",ovs_vport_name(vport)); //有用
//pr_info("---- vport info -----\n");
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;
}
vport.c
其中为了防止三次握手信息被干扰,并未处理seq=0的信息和len=0的数据报文
(三)部分代码讲解
int hook_func_out(struct vport *vport,struct sk_buff *skb)
{
struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb);
struct udphdr *udp_header;
struct tcphdr *tcp_header;
struct list_head *p;
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 = 0;
unsigned int ack_seq = 0;
char *data;
struct iphdr *iph;
struct tcphdr *th;
const char *vp_name = ovs_vport_name(vport);
pr_info("---- 6666666666666666 hook_func_out start 6666666666666666 -----\n");
if (ip_header->protocol==17) { //udp信息
udp_header = (struct udphdr *)skb_transport_header(skb);
src_port = (unsigned int)ntohs(udp_header->source);
dest_port = (unsigned int)ntohs(udp_header->dest);
} else if (ip_header->protocol == 6) { //tcp信息
tcp_header = (struct tcphdr *)skb_transport_header(skb);
src_port = (unsigned int)ntohs(tcp_header->source);
dest_port = (unsigned int)ntohs(tcp_header->dest);
seq = (unsigned int)ntohs(tcp_header->ack);
ack_seq = (unsigned int)ntohs(tcp_header->ack_seq);
/*
iph=ip_hdr(skb);
th=(struct tcphdr*)((__u32*)iph+iph->ihl);
data=(char*)((__u32*)th+th->doff);
*/
iph = ip_hdr(skb);
th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
data = (char *)th + (th->doff << 2);
}
if (ip_header->protocol == 6 && strlen(data)!=0 && seq != 0){ //有tcp数据到来(不是三次握手信息)
pr_info("--------tcp info start---------");
pr_info("-----Tcp Comming----Vport name = %s IP addres = %pI4 DEST = %pI4 source port = %u dest port = %u seq = %u ack_seq = %u data = %s len = %d\n\n",
vp_name,&src_ip, &dest_ip,src_port, dest_port,seq, ack_seq,data,strlen(data));
pr_info("-----map info start-----");
for(i=0 ; i<len ; i++){ //循环我们已经保存的信息,进行比较
//find match data
pr_info("IP addres = %pI4 DEST = %pI4\n\n",&spm[i].src_ip, &spm[i].dest_ip);
pr_info("source port = %u dest port port = %u\n\n",spm[i].src_port, spm[i].dest_port);
pr_info("seq = %u ack_seq = %u\n\n",spm[i].seq, spm[i].ack_seq);
pr_info("data = %s data len = %d\n\n",spm[i].data,strlen(spm[i].data));
pr_info("vport name = %s\n\n",spm[i].vport_name);
if((spm[i].src_ip == src_ip)&&(spm[i].dest_ip == dest_ip)&&
(spm[i].src_port == src_port)&&(spm[i].dest_port == dest_port)&&
(spm[i].seq == seq)&&(spm[i].ack_seq == ack_seq) && !strcmp(spm[i].data,data)&&
strstr(vp_name,spm[i].vport_name)!=NULL){ //如果出现重复,则返回0,去销毁这个skb信息
pr_info("return 0------ len %u\n\n",len);
return 0;
}
}
spm[i].src_ip = src_ip; //如果经过上面的检查,发现没有重复,则把这个消息添加到全局数组中
spm[i].dest_ip = dest_ip;
spm[i].src_port = src_port;
spm[i].dest_port = dest_port;
spm[i].seq = seq;
spm[i].ack_seq = ack_seq;
memcpy(spm[i].data,data,strlen(data)+1);
memset(spm[i].vport_name,0,sizeof(spm[i].vport_name));
memcpy(spm[i].vport_name,vp_name,strchr(vp_name,'-')-vp_name);
if(++len > MAX_SPM_LEN)
len=0;
pr_info("-----map info end-----");
pr_info("------ len %u\n\n",len);
pr_info("--------tcp info end---------");
}
pr_info("---- 6666666666666666 hook_func_out end 6666666666666666 -----\n");
return 1;
}
(四)重新编译ovs内核
五:修改源码后,实验显示
(一)启动mininet和ryu

(二)h1和h2主机进行通信

(三)发送消息,分析tcp报文---《重点》
1.查看我们发送的消息

2.查看上图,并没有一样的其他报文(看seq和ack即可),但是出现一条黑色的,可疑的消息,去看看

3.因此,并没有出现重复的报文,实现功能完成
六:总结
(一)由于我们修改的是内核文件,需要加载到Linux内核,所有一些函数是不能用的,内存分配上面尤其小心,不然容易死机
(二)需要去学习linux内核编程
(三)需要去深入了解Linux网络内核数据结构
(四)......