SDN Experiment4

1 实验题目

假如你是生活在1972年维护ARPAnet的网络管理员,在前面的实验中你学会了如何建立最短路径,下发了一条SDC到MIT跳数最少的路径(图中绿色的路径)。你的同事Bob某天接到了一个新的需求,要求UTAH到ILLINOIS之间的所有流量必须经过部署于TINKER的流量分析器以进行进一步研究,粗心大意的Bob没有检查当前的网络状态就很快下发了一条新的路径(图中红色的路径)。聪明又机智的你很快意识到Bob下发的流表很可能造成转发的环路。

现要求你运行VeriFlow工具,对上述两条转发路径进行检查,完成下面两部分实验内容:

  • 基础实验部分
  1. 输出每次影响EC的数量
  2. 打印出环路路径的信息
  3. 进一步打印出环路对应的EC的相关信息
  4. 分析原始代码与补丁代码的区别,思考为何需要添加补丁
  • 拓展实验部分
  1. 若修改waypoint_path.py代码中被添加规则的优先级字段,VeriFlow的检测结果会出错,试描述错误是什么,并解释出错的原因
  2. 在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名字书写错误

猜你喜欢

转载自blog.csdn.net/qq_49588762/article/details/117699068
SDN