1 实验题目
假如你是生活在1972年维护ARPAnet的网络管理员,在前面的实验中你学会了如何建立最短路径,下发了一条SDC到MIT跳数最少的路径(图中绿色的路径)。你的同事Bob某天接到了一个新的需求,要求UTAH到ILLINOIS之间的所有流量必须经过部署于TINKER的流量分析器以进行进一步研究,粗心大意的Bob没有检查当前的网络状态就很快下发了一条新的路径(图中红色的路径)。聪明又机智的你很快意识到Bob下发的流表很可能造成转发的环路。
现要求你运行VeriFlow工具,对上述两条转发路径进行检查,完成下面两部分实验内容:
- 基础实验部分
- 输出每次影响EC的数量
- 打印出环路路径的信息
- 进一步打印出环路对应的EC的相关信息
- 分析原始代码与补丁代码的区别,思考为何需要添加补丁
- 拓展实验部分
- 若修改
waypoint_path.py
代码中被添加规则的优先级字段,VeriFlow的检测结果会出错,试描述错误是什么,并解释出错的原因 - 在VeriFlow支持的14个域中,挑选多个域(不少于5个)进行验证,输出并分析结果
- 实验资料下载
https://www.aliyundrive.com/s/iA9A9BWijz7
2 实验内容
2.1 preparation
2.1.1 观察转发环路问题
-
启动最短路控制程序
ryu-manager ofctl_rest.py shortest_path.py --observe-links
-
启动拓扑
sudo python Arpanet19723.py
-
在拓扑中SDC ping MIT建立连接
SDC ping MIT
-
下发从UTAH途经TINKER到达ILLINOIS的路径
sudo python waypoint_path.py
-
再次在拓扑中SDC ping MIT建立连接
SDC ping MIT
-
查看路径上某一个交换机,如USC的流表
sudo ovs-ofctl dump-flows s22
-
打开wireshark观察该端口
2.1.2 使用VeriFlow
-
从github下载VeriFlow并打上实验补丁
git clone https://github.com/samueljero/BEADS.git cd BEADS git am 0001-for-xjtu-sdn-exp-2020.patch
-
编译VeriFlow
cd veriflow/VeriFlow make clean all
-
在自定义端口开启远程控制器,运行最短路程序
ryu-manager ofctl_rest.py shortest_path.py --ofp-tcp-listen-port 1998 --observe-links
-
运行VeriFlow的proxy模式
./VeriFlow 6633 127.0.0.1 1998 Arpanet19723.txt log_file.txt
-
启动拓扑
sudo python Arpanet19723.py
-
在拓扑中SDC ping MIT建立连接
SDC ping MIT
-
下发从UTAH途经TINKER到达ILLINOIS的路径,在log文件中观察VeriFlow检测到的环路信息
sudo python waypoint_path.py
2.2 基础实验部分
2.2.1 EC数目的打印
-
VeriFlow::verifyRule()
为执行VeriFlow核心算法的函数,包括对等价类的划分、转发图的构造与不变量的验证 。函数中变量ecCount
为EC数目。 -
仅需要将
ecCount
打印输出到日志文件即可。 -
VeriFlow::verifyRule()
修改前:if(ecCount == 0) { fprintf(stderr, "[VeriFlow::verifyRule] Error in rule: %s\n", rule.toString().c_str()); fprintf(stderr, "[VeriFlow::verifyRule] Error: (ecCount = vFinalPacketClasses.size() = 0). Terminating process.\n"); exit(1); } else { // fprintf(stdout, "\n"); // fprintf(stdout, "[VeriFlow::verifyRule] ecCount: %lu\n", ecCount); }
-
VeriFlow::verifyRule()
修改后:fprintf(fp, "[VeriFlow::verifyRule] verifying this rule: %s\n", rule.toString().c_str()); if(ecCount == 0) { fprintf(stderr, "[VeriFlow::verifyRule] Error in rule: %s\n", rule.toString().c_str()); fprintf(stderr, "[VeriFlow::verifyRule] Error: (ecCount = vFinalPacketClasses.size() = 0). Terminating process.\n"); exit(1); } else { fprintf(stdout, "\n"); fprintf(stdout, "[VeriFlow::verifyRule] ecCount: %lu\n", ecCount); fprintf(fp, "[VeriFlow::verifyRule] ecCount: %lu\n", ecCount);//输出到日志文件 }
-
重新编译,日志文件截图
2.2.2 环路路径的打印
-
VeriFlow::traverseForwardingGraph()
为遍历某个特定EC的转发图,验证是否存在环路或黑洞。仅需要增加一个变量vector<string> loop_path
记录环路路径即可。 -
VeriFlow::traverseForwardingGraph()
修改后:bool VeriFlow::traverseForwardingGraph(const EquivalenceClass& packetClass, ForwardingGraph* graph, const string& currentLocation, const string& lastHop, unordered_set< string > visited, FILE* fp,vector<string> loop_path) { ... if(visited.find(currentLocation) != visited.end()) { // Found a loop. fprintf(fp, "\n"); fprintf(fp, "[VeriFlow::traverseForwardingGraph] Found a LOOP for the following packet class at node %s.\n", currentLocation.c_str()); fprintf(fp, "[VeriFlow::traverseForwardingGraph] PacketClass: %s\n", packetClass.toString().c_str()); fprintf(fp, "[VeriFlow::traverseForwardingGraph] Loop path is:\n"); bool flag=false; for(unsigned int i = 0; i < loop_path.size()-1; i++) { if(loop_path[i]==currentLocation){ flag=true; } if(flag){ fprintf(fp, "%s --> ", loop_path[i].c_str()); } } fprintf(fp, "%s\n", currentLocation.c_str()); for(unsigned int i = 0; i < faults.size(); i++) { if (packetClass.subsumes(faults[i])) { faults.erase(faults.begin() + i); i--; } } faults.push_back(packetClass); return false; } visited.insert(currentLocation); loop_path.push_back(currentLocation); ... return this->traverseForwardingGraph(packetClass, graph, itr->rule.nextHop, currentLocation, visited, fp,loop_path); }
-
重新编译,日志文件截图
2.2.3 相关数据包信息的打印
-
EC的基本信息显示为14个域的区间形式,为方便Bob查错,现简化EC信息的表示形式,仅从14个域中提取TCP/IP五元组作为主要信息显示
-
现有EC的基本信息打印代码
VeriFlow::traverseForwardingGraph()
:fprintf(fp, "[VeriFlow::traverseForwardingGraph] PacketClass: %s\n", packetClass.toString().c_str());
-
仿照
EquivalenceClass::toString()
函数,向EquivalenceClass
中添加TcpIptoString()
函数string EquivalenceClass::TcpIptoString() const { char buffer[1024]; sprintf(buffer, "[ nw_src(%s-%s), nw_dst(%s-%s)", ::getIpValueAsString(this->lowerBound[NW_SRC].c_str), ::getIpValueAsString(this->upperBound[NW_SRC].c_str), ::getIpValueAsString(this->lowerBound[NW_DST].c_str), ::getIpValueAsString(this->upperBound[NW_DST].c_str); string retVal = buffer; retVal += ", "; sprintf(buffer,"nw_proto(%lu-%lu)",this->lowBound[NW_PROTO],this->upperBound[NW_PROTO]); retVal+=buffer; retVal+=","; sprintf(buffer,"tp_src(%lu-%lu)",this->lowBound[TP_SRC],this->upperBound[TP_SRC]); retVal+=buffer; retVal+=","; sprintf(buffer,"tp_dst(%lu-%lu)",this->lowBound[TP_DST],this->upperBound[TP_DST]); retVal+=buffer; return retVal; }
-
重新编译,日志文件截图
2.2.4 分析原始代码与补丁代码的区别,思考为何需要添加补丁
-
更改文件
-
veriflow/VeriFlow/Network.cpp | 1 +
-
veriflow/VeriFlow/OpenFlowProtocolMessage.cpp | 17 +++±–
-
veriflow/VeriFlow/Rule.cpp | 8 ++±
-
veriflow/VeriFlow/Rule.h | 1 +
-
veriflow/VeriFlow/VeriFlow.cpp | 45 +++++++++++++++±–
-
veriflow/VeriFlow/VeriFlow.h | 2 ±
-
-
查看补丁修改内容与分析
-
使用命令
git diff HEAD origin/HEAD
-
向Rule中增加了
in_port
属性,增加了对in_port
的存储和处理 -
存储lastHop ,提升了查找BlackHole的能力
-
完善了判断黑洞的情况
在github中开源代码中,给出了两种判断黑洞的情况:
- 当前交换机或主机并不在当前网络中
- 当前交换机或主机在网络中,但是无链路与其他交换机或主机相连
补丁中增加了一种判断黑洞的情况:
- 当前的交换机或者主机在网络的拓扑结构中,也存在与它相连的链路,但由于网络结构变化,使得从当前的交换机或者主机所在位置和相应端口(in_port),找不到上一跳的交换机或者主机。
-
完善了对环路的判断
选择下一跳避免了与上一跳相同
-
2.3 拓展实验部分
2.3.1 若修改waypoint_path.py
代码中被添加规则的优先级字段,VeriFlow的检测结果会出错,试描述错误是什么,并解释出错的原因
-
修改
waypoint_path.py
代码中被添加规则的优先级字段改为1,发现日志文件中无环路,且无法SDC ping MIT
不通 -
观察流表
-
修改优先级字段前
-
s22流表
-
s25流表
-
-
修改优先级字段后
-
s22流表
-
s25流表
-
-
当匹配域相同时,新流表项覆盖了旧的流表项,事实上存在环路,但VeriFlow并没有检测出来环路。
-
-
无法发现环路原因
-
判断环路选择下一跳时,veriflow利用priority字段进行了排序,当出现priority相同的规则时,就会出现问题。
graph->links[currentLocation].sort(compareForwardingLink);
-
当出现规则完全匹配需要对原来的规则进行覆盖的时候, VeriFlow 并没有将原来的规则删除并加上新的规则,而是保留了原来的规则并抛弃了新加的规则。
-
2.3.2 在VeriFlow支持的14个域中,挑选多个域(不少于5个)进行验证,输出并分析结果
-
可进行验证的域
enum FieldIndex { IN_PORT, // 0 DL_SRC, DL_DST, DL_TYPE, DL_VLAN, DL_VLAN_PCP, MPLS_LABEL, MPLS_TC, NW_SRC, NW_DST, NW_PROTO, NW_TOS, TP_SRC, TP_DST, ALL_FIELD_INDEX_END_MARKER, // 14 METADATA, // 15, not used in this version. WILDCARDS // 16 };
-
选取验证的域
- DL_SRC
- DL_DST
- DL_TYPE
- NW_SRC
- NW_DST
- IN_PORT
-
验证代码
import requests import json def add_flow(dpid, src_ip, dst_ip, in_port, out_port, src_mac,dst_mac,priority=10): flow = { "dpid": dpid, "idle_timeout": 0, "hard_timeout": 0, "priority": priority, "match":{ "dl_type": 2048, "in_port": in_port, "nw_src": src_ip, "nw_dst": dst_ip, "dl_src":src_mac, "dl_dst":dst_mac }, "actions":[ { "type":"OUTPUT", "port": out_port } ] } url = 'http://localhost:8080/stats/flowentry/add' ret = requests.post( url, headers={ 'Accept': 'application/json'}, data=json.dumps(flow)) print(ret) def show_path(src, dst, port_path): print('install mywaypoint path: {} -> {}'.format(src, dst)) path = str(src) + ' -> ' for node in port_path: path += '{}:s{}:{}'.format(*node) + ' -> ' path += str(dst) path += '\n' print(path) def install_path(): '23 -> 4:s22:2 -> 2:s9:3 -> 3:s16:2 -> 3:s7:2 -> 3:25:2 -> 1' src_sw, dst_sw = 23, 1 waypoint_sw = 9 # Tinker 10.0.0.21, s9 path = [(4, 22, 2), (2, 9, 3), (3, 16, 2), (3, 7, 2), (3, 25, 2)] # path = [(3, 7 , 2)] MIT_mac="00:00:00:00:00:01" SDC_mac="00:00:00:00:00:02" # send flow mod for node in path: in_port, dpid, out_port = node add_flow(dpid, '10.0.0.0/8', '10.0.0.0/8', in_port, out_port,SDC_mac,MIT_mac) add_flow(dpid, '10.0.0.0/8', '10.0.0.0/8', out_port, in_port,MIT_mac,SDC_mac) show_path(src_sw, dst_sw, path) if __name__ == '__main__': install_path()
-
结果
-
分析
-
匹配规则:
规则1 规则2 src_ip 10.0.0.0/8 10.0.0.0/8 dst_ip 10.0.0.0/8 10.0.0.0/8 src_mac 00:00:00:00:00:01 00:00:00:00:00:02 dst_mac 00:00:00:00:00:02 00:00:00:00:00:01 in_port 下发路径路径上的入端口 下发路径路径上的入端口 dl_type 2048 2048 -
EC数目为3,与基础部分数目相同,符合预期
-
会出现转发环路20.0.0.5–>20.0.0.7–>20.0.0.16–>20.0.09–>20.0.0.22–>20.0.0.23–>20.0.0.25,与预期相同
-
3 问题整理
-
基础实验部分仅仅是环境配置的问题,比如打补丁失败问题,修改源码需要在打补丁后的代码上修改。
-
在进行域匹配时,src_mac可以匹配成功,而dst_mac没有匹配
src_mac为源主机mac地址;dst_mac为目的主机mac地址
出错原因:匹配域
dl_dst
名字书写错误