基于 Open vSwitch 的 OpenFlow 实践

  OpenFlow 是用于管理交换机流表的协议,ovs-ofctl 则是 OVS 提供的命令行工具。在没有配置 OpenFlow 控制器的模式下,用户可以使用 ovs-ofctl 命令通过 OpenFlow 协议去连接 OVS,创建、修改或删除 OVS 中的流表项,并对 OVS 的运行状况进行动态监控。

查看 OVS 支持的 OpenFlow 协议的版本

$ ovs-ofctl --version
ovs-ofctl (Open vSwitch) 1.11.0
Compiled Oct 28 2013 14:17:17
OpenFlow versions 0x1:0x4

ovs常用特性:

  ovs-dpctl 用来配置switch内核模块;
  ovs-vsctl 查询和更新ovs-vswitchd的配置;
  ovs-appctl 发送命令消息,运行相关daemon;
  ovs-controller:一个简单的OpenFlow控制器;
  ovs-ofctl 查询和控制OpenFlow交换机和控制器;

实验目标:

路由器视频缓存的初步实现。

实验操作:

wndr3800路由器刷成openWrt系统,详细教程见:
《Openwrt源码下载和交叉编译 》http://blog.csdn.net/qq_15437629/article/details/45765717


默认lan口可连上openWrt路由器,登录web界面配置network:

ssh 192.168.1.1 
vim /etc/config/network   //(见附录1)   

进行相关配置后wan口作为控制口,登上路由器:

ssh 10.0.0.200

//挂载U盘:

mount -t ext4 /dev/sda1 /mnt
cd /mnt
ls

//上传名为with_usb的文件夹到u盘安装ipk
scp -r /home/zlk/with_usb  root@10.0.0.200:/mnt
//登录openflow交换机:
cd /mnt/with_usb
opkg install *.ipk
cd openvswitch
opkg install *.ipk
cd /etc/init.d/
./openvswitch          //手动开启ovs服务

文件夹链接:http://pan.baidu.com/s/1jGL4XCi
直接带ovs的openwrt版本:http://pan.baidu.com/s/1eSuKahc

这里写图片描述

创建如上图所示bridge:

ovs-vsctl show
ovs-vsctl add-br br0
ovs-vsctl add-port br0 eth0.1 tag=1
ovs-vsctl add-port br0 eth0.2 tag=2
ovs-vsctl add-port br0 eth0.3 tag=3
ovs-vsctl add-port br0 eth0.4 tag=4
ifconfig br0 192.168.1.3  //对于 internal 类型的的网络接口,OVS 会同时在 Linux 系统中创建一个可以用来收发数据的模拟网络设备。我们可以为这个网络设备配置 IP 地址、进行数据监听等等。
ifconfig br0
ovs-vsctl show
ovs-ofctl dump-flows br0
ovs-ofctl del-flows br0
ovs-ofctl show br0     
OFPT_FEATURES_REPLY (xid=0x2): dpid:0000cab39202a6bd
n_tables:254, n_buffers:256
capabilities: FLOW_STATS TABLE_STATS PORT_STATS QUEUE_STATS ARP_MATCH_IP
actions: OUTPUT SET_VLAN_VID SET_VLAN_PCP STRIP_VLAN SET_DL_SRC SET_DL_DST SET_NW_SRC SET_NW_DST SET_NW_TOS SET_TP_SRC SET_TP_DST ENQUEUE
 1(eth0.1): addr:22:4e:7f:74:1f:7b
     config:     0
     state:      0
     speed: 0 Mbps now, 0 Mbps max
 2(eth0.2): addr:22:4e:7f:74:1f:7b
     config:     0
     state:      0
     speed: 0 Mbps now, 0 Mbps max
 3(eth0.3): addr:22:4e:7f:74:1f:7b
     config:     0
     state:      0
     speed: 0 Mbps now, 0 Mbps max
 4(eth0.4): addr:22:4e:7f:74:1f:7b
     config:     0
     state:      0
     speed: 0 Mbps now, 0 Mbps max
 LOCAL(br0): addr:ca:b3:92:02:a6:bd
     config:     0
     state:      0
     speed: 0 Mbps now, 0 Mbps max
OFPT_GET_CONFIG_REPLY (xid=0x4): frags=normal miss_send_len=0


交换机端直接添加流表:

ovs-ofctl add-flow br0 dl_type=0x0806,in_port=2,actions=local,output:1
ovs-ofctl add-flow br0 dl_type=0x0806,in_port=1,actions=local,output:2
ovs-ofctl add-flow br0 dl_type=0x0806,in_port=local,actions=output:1,2
ovs-ofctl add-flow br0 dl_type=0x0800,nw_proto=1,in_port=2,actions=local,output:1
ovs-ofctl add-flow br0 dl_type=0x0800,nw_proto=1,in_port=1,actions=local,output:2
ovs-ofctl add-flow br0 dl_type=0x0800,nw_proto=1,in_port=local,actions=output:1,2
ovs-ofctl add-flow br0 dl_type=0x0800,nw_proto=17,in_port=2,actions=output:1,mod_nw_dst:192.168.1.3,mod_dl_dst:2A:EF:FE:9A:43:E2,local
ovs-ofctl add-flow br0 dl_type=0x0800,nw_proto=17,in_port=local,actions=output:1

查看流表:

ovs-ofctl dump-flows br0
NXST_FLOW reply (xid=0x4):
 cookie=0x0, duration=9.350s, table=0, n_packets=0, n_bytes=0, idle_age=9, arp,in_port=1 actions=LOCAL,output:2
 cookie=0x0, duration=9.369s, table=0, n_packets=0, n_bytes=0, idle_age=9, arp,in_port=2 actions=LOCAL,output:1
 cookie=0x0, duration=9.331s, table=0, n_packets=0, n_bytes=0, idle_age=9, arp,in_port=LOCAL actions=output:1,output:2
 cookie=0x0, duration=9.311s, table=0, n_packets=0, n_bytes=0, idle_age=9, icmp,in_port=2 actions=LOCAL,output:1
 cookie=0x0, duration=9.292s, table=0, n_packets=0, n_bytes=0, idle_age=9, icmp,in_port=1 actions=LOCAL,output:2
 cookie=0x0, duration=4.969s, table=0, n_packets=0, n_bytes=0, idle_age=4, udp,in_port=LOCAL actions=output:1
 cookie=0x0, duration=9.250s, table=0, n_packets=0, n_bytes=0, idle_age=9, udp,in_port=2 actions=output:1,mod_nw_dst:192.168.1.3,mod_dl_dst:2a:ef:fe:9a:43:e2,LOCAL
 cookie=0x0, duration=9.272s, table=0, n_packets=0, n_bytes=0, idle_age=9, icmp,in_port=LOCAL actions=output:1,output:2

此时server与client和br0均能ping通

1. 进行UDP收发程序测试(程序见附录2)

路由器和客户端同时开始接收,当服务器向客户端发送视频文件时,发现路由器中也接受到了视频文件,缓存于U盘中。

2. VLC RTP串流实验:

serverVLC:

  媒体->流->添加->串流->目标设置:在本地显示,去掉激活转码,RTP->添加->地址:192.168.1.111->串流

clientVLC:

   rtp://@:5004 播放

openWrt:

    ./rtpdump -F dump -t 1 -o 1.dump /5004      //截取1分钟

     ./rtpplay -f 1.dump 192.168.1.111/5004

clientVLC:

   rtp://@:5004 播放

3 VLC RTSP串流实验:

//添加TCP流表项进行连接控制

ovs-ofctl add-flow br0 dl_type=0x0800,nw_proto=6,in_port=2,actions=output:1
ovs-ofctl add-flow br0 dl_type=0x0800,nw_proto=6,in_port=1,actions=output:2

serverVLC:

   媒体->流->添加->串流->目标设置:在本地显示,去掉激活转码,RTSP->添加->路径/1->串流

   启动wireshark,通过eth0端口得到Destinationport:44202和37500(视频与音频)

clientVLC:

  网络->rtsp://192.168.1.110:8554/1 播放

openWrt:

       ./rtpdump -F dump -t 1 -o 2.dump /44202

        ./rtpplay -f 2.dump 192.168.1.111/5004

clientVLC:

   rtp://@:5004 播放



添加floodlight-0.9控制器

Controller终端:

cd floodlight/src/main/resources/
vim floodlightdefault.properties 

删除下面这一行:
net.floodlightcontroller.forwarding.Forwarding,\

启动floodlight:

java -tar target/floodlight.jar

在浏览器中输入地址http://localhost:8080/ui/index.html可查看switch的DPID等信息。

switch终端:

ovs-vsctl set-controller br0 tcp:10.0.0.100:6633
ovs-vsctl set controller br0 connection-mode=out-of-band  //OpenvSwitch不仅仅是一个OpenFlow Switch,它的流表组成除了of流表外,还有其他一些(隐藏)流表。这些隐藏流表默认交换机和控制器在同一网络中(in-band),因此要保证两者互通,要关闭默认的inband.
ovs-vsctl show
ovs-vsctl get Interface br0 ofport  
65534         //获得br0网络接口的OpenFlow编号

运行flow.py调用StaticFlowPusher API添加流表,重复上述实验。

python flow.py                                  #添加流表
curl http://10.0.0.100:8080/wm/staticflowentrypusher/list/all/json |python -mjson.tool  #查看流表

flow.py代码如下:

# coding=utf-8  

import httplib    
import json    

class StaticFlowPusher(object):    

        def __init__(self, server):    
            self.server = server    

        def get(self, data):    
            ret = self.rest_call({}, 'GET')    
            return json.loads(ret[2])    

        def set(self, data):    
            ret = self.rest_call(data, 'POST')    
            return ret[0] == 200    

        def remove(self, objtype, data):    
            ret = self.rest_call(data, 'DELETE')    
            return ret[0] == 200    

        def rest_call(self, data, action):    
            path = '/wm/staticflowentrypusher/json'    
            headers = {    
                'Content-type': 'application/json',    
                'Accept': 'application/json',    
                }    
            body = json.dumps(data)    
            conn = httplib.HTTPConnection(self.server, 8080)    
            conn.request(action, path, body, headers)    
            response = conn.getresponse()    
            ret = (response.status, response.reason, response.read())    
            print ret    
            conn.close()    
            return ret    

pusher = StaticFlowPusher('10.0.0.100')  #控制器ip    
flow1 = {    
        'switch':"00:00:00:00:00:00:00:02",    
        "name":"flow-mod-1",    
        "cookie":"0",    
        "priority":"32768",             
        "active":"true",            
        "ether-type":"0x0806",   
        "ingress-port":"2",  
        "actions":"output=local,output=1"  
        }  
flow2 = {    
        'switch':"00:00:00:00:00:00:00:02",    
        "name":"flow-mod-2",    
        "cookie":"0",    
        "priority":"32768",             
        "active":"true",            
        "ether-type":"0x0806",  
        "ingress-port":"1",  
        "actions":"output=local,output=2"  
        }      
flow3 = {    
        'switch':"00:00:00:00:00:00:00:02",    
        "name":"flow-mod-3",    
        "cookie":"0",    
        "priority":"32768",             
        "active":"true",            
        "ether-type":"0x0806",  
        "ingress-port":"65534",  
        "actions":"output=1,output=2"  
        }                          
flow4 = {    
        'switch':"00:00:00:00:00:00:00:02",    
        "name":"flow-mod-4",    
        "cookie":"0",    
        "priority":"32768",             
        "active":"true",            
        "ether-type":"0x0800",  
         "protocol":"1",  
        "ingress-port":"2",  
        "actions":"output=local,output=1"  
        }      
flow5 = {    
        'switch':"00:00:00:00:00:00:00:02",    
        "name":"flow-mod-5",    
        "cookie":"0",    
        "priority":"32768",             
        "active":"true",            
        "ether-type":"0x0800",  
         "protocol":"1",  
        "ingress-port":"1",  
        "actions":"output=local,output=2"  
        }
flow6 = {    
        'switch':"00:00:00:00:00:00:00:02",    
        "name":"flow-mod-6",    
        "cookie":"0",    
        "priority":"32768",             
        "active":"true",            
        "ether-type":"0x0800",  
         "protocol":"1",  
        "ingress-port":"65534",  
        "actions":"output=1,output=2"  
        }     
flow7 = {    
        'switch':"00:00:00:00:00:00:00:02",    
        "name":"flow-mod-7",    
        "cookie":"0",    
        "priority":"32768",             
        "active":"true",            
        "ether-type":"0x0800",  
         "protocol":"17",  
        "ingress-port":"2",  
        "actions":"output=1,set-dst-ip=192.168.1.3,set-dst-mac=2a:ef:fe:9a:43:e2,output=local"  
        }   
flow8 = {    
        'switch':"00:00:00:00:00:00:00:02",    
        "name":"flow-mod-8",    
        "cookie":"0",    
        "priority":"32768",             
        "active":"true",            
        "ether-type":"0x0800",  
         "protocol":"17",  
        "ingress-port":"65534",  
        "actions":"output=1"  
        }                          
    #添加流表flow 
pusher.set(flow1)  
pusher.set(flow2)
pusher.set(flow3)
pusher.set(flow4)
pusher.set(flow5)
pusher.set(flow6)
pusher.set(flow7)
pusher.set(flow8)     



附1:

network配置

VLAN 技术是基于二层和三层之间的隔离,可以将不同的网络用户与网络资源进行分组并通过支持 VLAN 技术的交换机隔离不同组内网络设备间的数据交换来达到网络安全的目的。同一个 VALN 上的机器可以相互通信,不同的 VLAN 之前不可以通信,因为它们之间在数据链路层上是断开的,只能通过三层路由器才能访问。

openwrt中的vlan配置文件 /etc/config/network 如下:

config 'interface' 'loopback'                 #本地回环地址
        option 'ifname' 'lo'
        option 'proto' 'static'
        option 'ipaddr' '127.0.0.1'
        option 'netmask' '255.0.0.0'

config 'switch'                         #swith,用于Wndr3800,四个LAN口的IP映射
        option 'name' 'rtl8366s'
        option 'reset' '1'
        option 'enable_vlan' '1'
        option 'blinkrate' '2'
        option 'enable_learning' '0'
        option 'enable_vlan4k' '1'

config 'switch_vlan'                        #划分vlan
        option 'device' 'rtl8366s'
        option 'vlan' '1'
        option 'ports' '0 5t'

config 'switch_vlan'
        option 'device' 'rtl8366s'
        option 'vlan' '2'
        option 'ports' '1 5t'

config 'switch_vlan'
        option 'device' 'rtl8366s'
        option 'vlan' '3'
        option 'ports' '2 5t'

config 'switch_vlan'
        option 'device' 'rtl8366s'
        option 'vlan' '4'
        option 'ports' '3 5t'

config 'switch_vlan'
        option 'device' 'rtl8366s'
        option 'vlan' '5'
        option 'ports' '4 5t'

config 'interface' 'port1'                       #划分lan口
        option 'ifname' 'eth0.1'
        option 'proto' 'static'

config 'interface' 'port2'
        option 'ifname' 'eth0.2'
        option 'proto' 'static'   

config 'interface' 'port3'        
        option 'ifname' 'eth0.3'
        option 'proto' 'static' 

config 'interface' 'port4'      
        option 'ifname' 'eth0.4'
        option 'proto' 'static' 

config 'interface' 'controller'                 #WAN口
        option 'ifname' 'eth1'  
        option 'proto' 'static' 
        option 'ipaddr' '10.0.0.200'          #必须与lan口不同网段!
        option 'netmask' '255.255.255.0'

config interface 'mesh'               
        option mtu     1532           
        option proto   batadv         
        option mesh    bat0           

config 'switch_port'                  
        option 'device' 'rtl8366s'    
        option 'port' '1'             
        option 'led' '6'              

config 'switch_port'                  
        option 'device' 'rtl8366s'    
        option 'port' '2'             
        option 'led' '9'              

config 'switch_port'                  
        option 'device' 'rtl8366s'    
        option 'port' '5'             
        option 'led' '2'  



附2:

UDP收发程序

server.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define    FINISH_FLAG    "FILE_TRANSPORT_FINISH"
#define    MAXLINE        1024

void usage(char *command)
{
    printf("usage :%s ipaddr portnum filename\n", command);
    exit(0);
}
int main(int argc,char **argv)
{
    FILE                   *fp;
    struct sockaddr_in     serv_addr;
    char                   buf[MAXLINE];
    int                    sock_id;
    int                    read_len;
    int                    send_len;
    int                    serv_addr_len;
    int                    i_ret;
    int                    i;

    if (argc != 4) {
        usage(argv[0]);
    }
    /* open the file to be transported commanted */
    if ((fp = fopen(argv[3],"r")) == NULL) {
        perror("Open file failed\n");
        exit(0);
    }
    /* create the socket commanted by guoqingbo*/
    if ((sock_id = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("Create socket failed");
        exit(0);
    }
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[2]));
    inet_pton(AF_INET, argv[1], &serv_addr.sin_addr);
    serv_addr_len = sizeof(serv_addr);
    /* connect the server commanted */
    i_ret = connect(sock_id, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr));
    if (-1 == i_ret) {
        perror("Connect socket failed!\n");
        exit(0);
    }
    /* transport the file commented */
    bzero(buf, MAXLINE);
    while ( (read_len = fread(buf, sizeof(char), MAXLINE, fp)) > 0 ) {
        send_len = send(sock_id, buf, read_len, 0);
        if ( send_len < 0 ) {
            perror("Send data failed\n");
            exit(0);
        }
        bzero(buf, MAXLINE);
         usleep(8000);//!!!

    }
    fclose(fp);

 /* send the end_flag commented */
    bzero(buf, MAXLINE);
    strcpy(buf, FINISH_FLAG);
    buf[strlen(buf)] = '\0';
    for (i = 1000; i>0; i--) {
        send_len = send(sock_id, buf, strlen(buf)+1, 0);
        if ( send_len < 0 ) {
            printf("Finish send the end string\n");
            break;
        }
    }
    close(sock_id);
    printf("Send finish\n");
    return 0;
}



client.c:


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define    FINISH_FLAG    "FILE_TRANSPORT_FINISH"
#define    MAXLINE        1024

void usage(char *command)
{
    printf("usage :%s portnum filename\n", command);
    exit(0);
}
int main(int argc,char **argv)
{
    struct sockaddr_in     serv_addr;
    struct sockaddr_in     clie_addr;
    char                   buf[MAXLINE];
    int                    sockfd;
    int                    recv_len;
    int                    clie_addr_len;
    FILE                   *fp;

    if (argc != 3) {
        usage(argv[0]);
    }
       /* Create the the file commented */
    if ((fp = fopen(argv[2], "w")) == NULL) {
        perror("Creat file failed");
        exit(0);
    }
    if ((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0) {
        perror("Create socket failed\n");
        exit(0);
    }
       /*fill the server sockaddr_in struct commented */ 
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[1]));
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0 ) {
        perror("Bind socket faild\n");
        exit(0);
    }
    /* server part commented */ 
    clie_addr_len = sizeof(clie_addr);
    bzero(buf, MAXLINE);
    while (recv_len = recvfrom(sockfd, buf, MAXLINE, 0,(struct sockaddr *)&clie_addr, &clie_addr_len)) {
        if(recv_len < 0) {
            printf("Recieve data from client failed!\n");
            break;
        }
        printf("#");
        if ( strstr(buf, FINISH_FLAG) != NULL ) {
            printf("\nFinish receiver finish_flag\n");
            break;
        }
        int write_length = fwrite(buf, sizeof(char), recv_len, fp);
        if (write_length < recv_len) {
            printf("File write failed\n");
            break;
        }
        bzero(buf, MAXLINE);
    }

    printf("Finish recieve\n");
    fclose(fp); 
    close(sockfd); 
    return 0;
}




  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值