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;
}