Open vSwitch调试
本文介绍调试OvS的方法,主要集中在内核态查表流程的调试
1 调试程序分析
本调试程序主要调试内核态流表查找过程中有效关键字和哈希计算过程。
1.1 头文件
创建tdefine.h头文件,定义变量类型和数据结构体。
typedef unsigned char u8; // 一个字节
typedef unsigned short uint16, u16; // 两个字节
typedef uint16 __be16;
typedef unsigned int uint32, u32; // 四个字节
typedef uint32 __be32;
#define ETH_ALEN 6
struct sw_flow_key {
struct {
u8 src[ETH_ALEN]; // 源mac地址
u8 dst[ETH_ALEN]; //目的mac地址
} eth;
union {
struct {
u8 proto; // 协议类型
} ip;
};
struct {
__be16 src; // 源端口号
__be16 dst; // 目的端口号
} tp;
union {
struct {
__be32 src; // 源ip地址
__be32 dst; // 目的ip地址
} addr;
};
};
struct sw_flow_key_range {
unsigned short int start; // 有效关键字起始位
unsigned short int end; // 有效关键字结束位
};
struct sw_flow_mask {
struct sw_flow_key_range range; // 有效关键字范围
struct sw_flow_key key; // 与关键字相对应的掩码
};
struct sw_flow_actions {
u32 acts; // 动作索引
};
1.2 哈希算法
OvS调用的jhash2函数在linux内核源文件include/linux/jhash.h下。
#include <asm/types.h>
#include <stdio.h>
typedef unsigned int u32;
typedef unsigned char u8;
#define JHASH_INITVAL 0xdeadbeef
static inline __u32 rol32(__u32 word, unsigned int shift){
//printf("\n *** rol32 *** \n");
u32 results;
results = (word << shift) | (word >> ((-shift) & 31));
//printf("word = %#x; shift = %#x; results = %#x; \n", word, shift, results);
return (word << shift) | (word >> ((-shift) & 31));
}
#define __jhash_mix(a, b, c) \
{ \
a -= c; a ^= rol32(c, 4); c += b; \
b -= a; b ^= rol32(a, 6); a += c; \
c -= b; c ^= rol32(b, 8); b += a; \
a -= c; a ^= rol32(c, 16); c += b; \
b -= a; b ^= rol32(a, 19); a += c; \
c -= b; c ^= rol32(b, 4); b += a; \
}
#define __jhash_final(a, b, c) \
{ \
c ^= b; c -= rol32(b, 14); \
a ^= c; a -= rol32(c, 11); \
b ^= a; b -= rol32(a, 25); \
c ^= b; c -= rol32(b, 16); \
a ^= c; a -= rol32(c, 4); \
b ^= a; b -= rol32(a, 14); \
c ^= b; c -= rol32(b, 24); \
}
static inline u32 jhash2(const u32 *k, u32 length, u32 initval)
{
u32 a, b, c;
/* Set up the internal state */
a = b = c = JHASH_INITVAL + (length<<2) + initval;
printf("k[0] = %#x k[1] = %#x k[2] = %#x \n", k[0], k[1], k[2]);
/* Handle most of the key */
while (length > 3) {
a += k[0];
b += k[1];
c += k[2];
__jhash_mix(a, b, c);
length -= 3;
k += 3;
}
/* Handle the last 3 u32's */
switch (length) {
case 3: c += k[2]; /* fall through */ // 此处没有break,匹配到这一项也会执行后面的__jhash_final函数
case 2: b += k[1]; /* fall through */
case 1: a += k[0];
printf("k[0] = %#x \n", k[0]);
__jhash_final(a, b, c);
case 0: /* Nothing left to add */
break;
}
printf("hash = %#x \n", c);
return c;
}
1.3 主函数
#include<stdbool.h>
#include<string.h>
#include<stdio.h>
#include "tdefine.h"
#include "jhash2.c"
static u16 range_n_bytes(const struct sw_flow_key_range *range)
{
return range->end - range->start;
}
void ovs_flow_mask_key(struct sw_flow_key *dst, const struct sw_flow_key *src,
bool full, const struct sw_flow_mask *mask)
{
int start = full ? 0 : mask->range.start;
printf("mask->range.start = %d \n", mask->range.start);
printf("mask->range.end = %d \n", mask->range.end);
int len = full ? sizeof *dst : range_n_bytes(&mask->range);
printf("len = %d \n", len);
const long *m = (const long *)((const u8 *)&mask->key + start);
const long *s = (const long *)((const u8 *)src + start);
long *d = (long *)((u8 *)dst + start);
int i;
printf("mask size = %ld \n", sizeof(long));
for (i = 0; i < len; i += sizeof(long))
{
*d++ = *s++ & *m++;
}
printf("\n *** masked_key *** \n");
for (i=0; i<(len/sizeof(long)); i++) {
printf("d+%d = %#lx \n", i, *(d-(len/sizeof(long))+i));
}
}
static u32 flow_hash(const struct sw_flow_key *key,
const struct sw_flow_key_range *range)
{
const u32 *hash_key = (const u32 *)((const u8 *)key + range->start);
printf("hash_key = %#x \n", *hash_key);
/* Make sure number of hash bytes are multiple of u32. */
int hash_u32s = range_n_bytes(range) >> 2;
printf("hash_u32s = %#x \n", hash_u32s);
printf("\n *** jhash2 *** \n");
return jhash2(hash_key, hash_u32s, 0);
}
int main(){
struct sw_flow_key key;
memset(&key, 0, sizeof(key)); // 清零,不然有些存储位置初始不是0
int i;
for (i=0; i<6; i++) {
key.eth.src[i] = '1'; // 数组变量是指针
key.eth.dst[i] = '2';
}
key.ip.proto = 0x80;
key.tp.src = 0x1234;
key.tp.dst = 0x5678;
key.addr.src = 0x11223344;
key.addr.dst = 0x55667788;
struct sw_flow_key_range range;
range.start = 0x000c;
range.end = 0x001c;
printf("\n *** key *** \n");
const long *a = (const long *)((const u8 *)&key);
for (i=0; i<4; i++) {
printf("a+%d = %#lx \n", i, *(a+i));
}
struct sw_flow_mask mask;
memset(&mask, 0, sizeof(mask)); // 清零,不然有些存储位置初始不是0
for (i=0; i<6; i++) {
*(mask.key.eth.src+i) = 0x00; // 数组变量是指针
*(mask.key.eth.dst+i) = 0x00;
}
mask.key.ip.proto = 0xff;
mask.key.tp.src = 0x0000;
mask.key.tp.dst = 0xffff;
mask.key.addr.src = 0x00000000;
mask.key.addr.dst = 0xffffffff;
mask.range = range;
printf("\n *** mask->key *** \n");
const long *b = (const long *)((const u8 *)&mask.key);
for (i=0; i<4; i++) {
printf("b+%d = %#lx \n", i, *(b+i));
}
struct sw_flow_key masked_key;
printf("\n *** ovs_flow_mask_key *** \n");
ovs_flow_mask_key(&masked_key, &key, false, &mask);
u32 hash;
printf("\n *** flow_hash *** \n");
hash = flow_hash(&masked_key, &mask.range);
}
1.4 结果分析
取出mask->range定义范围内的key与mask->key进行与操作,获得masked_key。
jhash2函数利用masked_key计算得到32位的hash值,其中的rol32用于将一个32位的数循环左移,如0xdeadbeff循环左移4位为0xeadbeffd。
mask->key为0对应的key在计算hash值不起作用,如将源ip地址改为0x55667788,最后计算的哈希值一致的。
2 OvS查表流程分析
2.1 Mininet安装
Mininet是一个可以在有限资源的普通电脑上快速建立大规模SDN原型系统的网络仿真工具。Mininet 内置了一个方便的工具 mn,执行sudo mn后,Mininet自动帮助创建了两个节点,并让它们与虚拟交换机相连。
- 下载Mininet: 下载地址
- 安装Mininet
- 解压,进入文件夹/mininet/util
- 执行命令**./install.sh -n3v**,-n3v用于显示版本信息和进度
2.1 OvS卸载
安装新编译的OvS的之前需要停止运行中的OvS,卸载原来的OvS驱动。
echo 'export PATH=$PATH:/usr/local/share/openvswitch/scripts' > /etc/profile.d/ovs.sh &&
source /etc/profile.d/ovs.sh &&
ovs-ctl stop
ovs-dpctl del-dp ovs-system
rmmod openvswitch
2.2 OvS编译安装
编译OvS前,使用配置文件congfigure配置Makefile,使用内核编译OvS,内核文件在/lib/modules/$(uname -r)/build下。
tar -zxvf ./openvswitch-2.17.5.tar.gz &&
cd openvswitch-2.17.5 &&
./configure --prefix=/usr --localstatedir=/var --sysconfdir=/etc --with-linux=/lib/modules/$(uname -r)/build &&
make &&
make install &&
make modules_install ;
# /sbin/modprobe openvswitch &&
insmod datapath/linux/openvswitch.ko
/sbin/lsmod | grep openvswitch ;
echo 'export PATH=$PATH:/usr/local/share/openvswitch/scripts' > /etc/profile.d/ovs.sh &&
source /etc/profile.d/ovs.sh &&
ovs-ctl start &&
echo "openvswitch is installed and started successfully" &&
ps -e | grep ovs
执行sudo insmod datapath/linux/openvswitch.ko时提示“insmod: ERROR: could not insert module datapath/linux/openvswitch.ko: Unknown symbol in module”。执行modinfo datapath/linux/openvswitch.ko显示下图所示信息,从depends一项可以看出openvswitch.ko依赖其它驱动,依次执行sudo modprobe nf_conntrack,modprobe tunnel16…,然后再执行sudo insmod datapath/linux/openvswitch.ko安装驱动,驱动安装成功。
虚拟机下的Ubuntu系统在执行make命令时出现”/bin/bash: libtool: No such file or directory“。清除编译文件,先使用命令./condigure,执行make进行编译,编译可以顺利完成。清除编译文件,再执行命令./condigure --with-linux=/lib/modules/$(uname -r)/build,然后再执行make进行编译,此时编译也能成功。
2.3 OvS修改测试
内核驱动打印信息使用printk函数,用法与printf类似,使用dmesg命令打印内核的信息。
- 打开终端执行sudo mn,自动创建桥s1,创建端口s1、s1-eth1、s1-eth2,创建两个节点h1、h2,构成下图所示虚拟网络系统。
- 执行dmesg -c清除之前内核的打印信息。
- Mininet的终端执行pingall进行互ping测试。
- 执行dmesg打印内核信息。
-
一次匹配成功
-
多次匹配
-