SDN功能实现(一)---修改ovs源码实现packet去重

一:实验环境搭建

(一)设置网络拓扑

(二)实验目的

当两个主机通信,网络中可能存在多条路径可以进行数据传递,那么可能出现一个主机接收到多个相同的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内核网络部分

https://blog.csdn.net/yuzhihui_no1/article/details/38666589(sk_buff详解一)

https://www.cnblogs.com/zfyouxi/p/4560841.html(sk_buff详解二)

https://blog.csdn.net/zhuhuibeishadiao/article/details/51500720(sk_buff详解三)

http://blog.chinaunix.net/uid-23207633-id-267569.html(从sk_buff中获取数据,代码可能不对,去下载linux源码(参考虚拟机内核版本下载),查看实例即可)

https://stackoverflow.com/questions/1184274/read-write-files-within-a-linux-kernel-module(内核文件操作,可不看)

(二)ovs介绍

https://opengers.github.io/openstack/openstack-base-use-openvswitch/(ovs详解)---------重点推荐

https://tonydeng.github.io/sdn-handbook/ovs/internal.html(ovs原理)------------重点推荐

https://www.jianshu.com/p/bf112793d658(ovs源码分析整理)-----------重点推荐

http://vinllen.com/ovs-datapathbi-ji/(ovs datapath分析)-----------重点推荐

openvswitch2.11.0修改源码后重新编译(2)(注意重新编译步骤,以及前面提及的调试方法dmesg)

https://blog.csdn.net/yuzhihui_no1/category_9263048.html(openVswitch(OVS)源代码分析,可看)

OpenvSwitch2.4.0源码解读(可不看)

https://www.cnblogs.com/mfrbuaa/p/4737648.html(内核部分结构,可不看)

https://www.linuxidc.com/Linux/2014-09/106760.htm(内核部分结构,可不看)

(三)由于文章整理稍晚,部分好文无法推荐,待补充

(四)推荐使用sublime进行ovs和linux源码分析

(五)wireshark安装

三:实现环境前景显示(未修改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网络内核数据结构

(四)......

posted @ 2019-11-30 16:49  山上有风景  阅读(422)  评论(1)    收藏  举报