%# -*- coding: utf-8-unix -*-

\section{基于OpenFlow的SDN交换机}
\label{sec:c:sdn-ob:s:sdn}

\subsection{实验目的}
\label{subsec:c:sdn-ob:s:sdn_object}

本实验的主要目的是让学生了解SDN的概念及架构原理，掌握OpenFlow协议格式、内容、工作流程及对应功能作用。掌握SDN控制器北向API使用方法，可通过API编程获取和设置交换机数据。

\subsection{实验内容}
\label{subsec:c:sdn-ob:s:sdn_content}

使用可编程网络平台及SDN交换机示例框架代码，完成OpenFlow协议的部分功能逻辑。验证并分析SDN交换机的工作原理与处理流程。主要完成以下内容：

\begin{enumerate}
  \item \textbf{阅读协议：}阅读OpenFlow 1.3版本协议白皮书，
  掌握其协议内容及对应功能；
  \item \textbf{设计实现协议功能：}基于SDN交换机示例框架代码，
  选择开发OpenFlow协议的部分功能，完成逻辑设计与实现；
  \item \textbf{验证交换机功能：}运行自己的SDN交换机，与控制器建立连接，
  在控制器查询其工作状态与相关协议功能展示。
  如交换机基本信息描述、交换机流表状态、
  交换机端口状态及计数统计、网络拓扑展示效果；
  \item \textbf{检验流表：}验证SDN交换机连接主机间的通信功能，
  并观察流表内容及变化情况；
  \item \textbf{REST API应用：}使用实验提供的北向REST API接口查询控制器，
  获取或设置交换机的不同功能数据；
  \item \textbf{定义流表：}使用REST API接口修改流表内容，
  观察主机的通信情况是否受到影响。
\end{enumerate}

\subsection{实验原理、方法和手段}
\label{subsec:c:sdn-ob:s:sdn_principle}

\subsubsection{SDN交换机工作原理}
SDN是一种新型网络创新架构，用于解决传统网络架构中对于网络虚拟化、各类自动化部署以及新型网络协议的问题。

SDN将传统的紧耦合网络设备拆分成了应用、控制和转发三层，其中应用和控制层面能够控制网络中数据的流向以及协议控制，而底层的网络设备被抽象为了可自定义的转发逻辑实体，可被上层应用定义为不同类型的转发设备。

在应用层，用户可通过通用、简单的API获取网络信息，并可修改转发设备的工作逻辑，达到动态改变网络架构与功能的目的。

\subsection{SDN交换机分组处理流程}
\begin{enumerate}
  \item \textbf{启动协议通道：}
  SDN交换机运行OpenFlow协议代理程序与上层控制器建立通信连接。
  交换机解析处理OpenFlow协议，
  根据协议要求完成协议功能的相关数据收集和配置功能，
  构造相应的OpenFlow协议应答控制器；

  \item \textbf{建立连接：}OpenFlow协议代理运行时，
  交换机首先和控制器建立连接，
  并发送OFP\_HELLO消息用以确认双方都支持的OpenFlow协议版本信息；
  \item \textbf{查询或设置数据：}
  控制器通过OpenFlow的具体消息协议查询或设置交换机其他相关功能的数据，
  如OFPT\_HELLO、OFPT\_ECHO\_REQUEST、OFPT\_PACKET\_IN、OFPMP\_DESC、
  OFPT\_PACKET\_OUT、OFPT\_FLOW\_MOD、OFPT\_PORT\_MOD、OFPMP\_FLOW、OFPMP\_ PORT\_STATS等；
  \item \textbf{处理转发分组：}当交换机接收到一个数据转发分组时，
  提取分组数据中的相关字段组成查表关键字，然后在本地进行流表匹配。
  如果匹配成功，则根据流表的动作处理该分组，如从其他端口转发出去；
  如果匹配不成功，则会执行默认动作，将分组打包送到控制器；
  \item \textbf{PACKET\_IN分组上传：}交换机查表不命中或是接收到链路探测报文，
  则将该数据封装在OpenFlow的PACKET\_IN消息中，上传给控制器；
  \item \textbf{PACKET\_OUT分组下发：}通过PACKET\_IN上传的分组，
  经过控制器分析处理后，会将其封装在OpenFlow协议的PACKET\_OUT消息中，
  并携带输出端口信息下发到交换机。交换机接收到该消息数据后，
  根据输出信息指示，将该分组从指定端口输出。
  通过PACKET\_OUT发送的分组数据还包括链路探测协议报文；
  \item \textbf{FLOW\_MOD消息处理：}
  控制器通过下发FLOW\_MOD消息给交换机的流表设置转发表项内容，
  FLOW\_MOD消息中包括分组特征元组信息和动作执行列表。
  特征元组通常包括常用的MAC信息、帧类型和五元组信息等，
  动作通常包括输出端口或丢弃信息等。
\end{enumerate}

\subsection{实验条件}
\label{subsec:c:sdn-ob:s:sdn_requirement}

\begin{itemize}
  \item 可编程网络平台一个，测试主机两台，
  SDN控制器（安装SDN控制器软件Floodlight）一台。控制器主机没有两个网口时，中间加一个普通交换机；
  \item 测试主机与控制器主机的网络配置及连接拓扑如图\ref{fig:c:sdn-ob_sdn-topo}所示；
  \item 串口线一根，网线三根；
  \item SDN交换机框架开发源代码、流表配置Python脚本。
\end{itemize}

\begin{figure}[!ht]
  \centering
  \includegraphics[width=9cm]{sdn-topo}
  \caption{OpenFlow交换机实验拓扑图}
  \label{fig:c:sdn-ob_sdn-topo}
\end{figure}

\subsection{实验步骤}
\label{subsec:c:sdn-ob:s:sdn_procedure}

\subsubsection{编译运行SDN交换机，验证交换机基本功能；}

请参考附录\ref{app:openbox}：《FAST平台介绍与操作说明手册》完成。
\subsubsection{基于SDN交换机源码，选择实现自己的协议功能；}

\begin{enumerate}
  \item 修改源代码
  \begin{code}[c]
    enum ofperr handle_openflow_callback(struct ofp_buffer *ofpbuf,int len)
    {
      int oftype = ofpbuf->header.type;

      LOG_DBG("ofpbuf->header.type=%d\n",ofpbuf->header.type);
      switch(oftype)
      {
        case OFPT_HELLO:
          /*用户不实现该类消息处理，则直接break，函数返回CONTINUE，
          *由系统库完成后续处理；*/
          break;
        case OFPT_PACKET_OUT:
          /*若用户想实现该消息的处理，则在此处完成对消息的功能逻辑，
          *然后直接返回HANDLE，表示已经处理，库函数直接返回*/
          //TO DO  用户在些添加处理逻辑，完成该消息功能
          return HANDLE;
        //可再增加其他OpenFlow协议的消息类型
      }
      return CONTINUE;
    }
  \end{code}
  用户可以根据自己的需求选择实现OpenFlow协议的部分消息功能，一旦处理了该消息，一定要返回消息处理标识HANDLE，否则直接break即可。
  \item 编译源代码
  \begin{code}[console]
    root@HNXS-FAST:/home/hnxs/sdn# make
    gcc –o user_openflow –l reg –l ua –l rule –l ofp -lpthread
    root@HNXS-FAST:/home/hnxs/sdn# ls
    main_openflow.c  Makefile  user_openflow
    root@HNXS-FAST:/home/hnxs/sdn#
  \end{code}
\end{enumerate}
\subsubsection{运行修改后的交换机，再次验证SDN交换机的功能；}
\begin{enumerate}
  \item 运行SDN交换机，交换机正常工作后输出如下：
  \begin{code}[console]
    root@HNXS-FAST:/home/hnxs/sdn# ./ user_openflow -4 192.168.1.3 -i obx0,obx1,obx2,obx3
   \end{code}
  \begin{code}[text]
    fastU->REG Version:20180827,OpenBox HW Version:2030200722
    port_name:eth0,port:0
    port_name:obx0,port:0
    port_name:obx1,port:1
    port_name:obx2,port:2
    port_name:obx3,port:3
    fastU->librule version:20181015,Default Action:0x40000080
    xofp)Connect to SDN Controller [ 192.168.1.3:6653 ] OK!
    0_nms_of13)DELETE ALL RULE!
    obx0(0x29008) Start...
    obx2(0x2e100) Start...
    obx3(0x30958) Start...
    obx1(0x2b890) Start...
  \end{code}

  \item 查看控制器界面基本信息，在浏览器打开控制器WEB网站http://192.168.1.3/ui/index.html，控制界面如图\ref{fig:c:sdn-ob_sdn-main}所示：
  \begin{figure}[ht!]
    \centering
    \includegraphics[width=11cm]{sdn-main}
    \caption{SDN主界面信息}
    \label{fig:c:sdn-ob_sdn-main}
  \end{figure}
  
  \item 查看连接交换机信息，如图\ref{fig:c:sdn-ob_sdn-switch}所示：
  \begin{figure}[H]
    \centering
    \includegraphics[width=11cm]{sdn-switch}
    \caption{SDN交换机信息}
    \label{fig:c:sdn-ob_sdn-switch}
  \end{figure}
  
  \item 查看连接测试主机信息，SDN主机及连接信息如图\ref{fig:c:sdn-ob_sdn-host}所示：
  \begin{figure}[htp!]
    \centering
    \includegraphics[width=12cm]{sdn-host}
    \caption{SDN主机信息}
    \label{fig:c:sdn-ob_sdn-host}
  \end{figure}
  
  \item 查看端口状态与计数信息与流表信息，端口与流表信息如图\ref{fig:c:sdn-ob_port-flowtable}所示。交换机流表详细信息查询方法见下一小节描述。
  \begin{figure}[htp!]
    \centering
    \includegraphics[width=12cm]{port-flowtable}
    \caption{端口状态与流表计数信息}
    \label{fig:c:sdn-ob_port-flowtable}
  \end{figure}
  
  \item 查看网络拓扑信息，网络拓扑信息如图\ref{fig:c:sdn-ob_sdn-test-topo}所示：
  \begin{figure}[htp!]
    \centering
    \includegraphics[width=10cm]{sdn-test-topo}
    \caption{SDN网络拓扑信息}
    \label{fig:c:sdn-ob_sdn-test-topo}
  \end{figure}  
\end{enumerate}

\subsubsection{在测试主机ping的前后，分别观察流表变化；}
\begin{enumerate}
  \item ping之前的流表信息为空，如图\ref{fig:c:sdn-ob_empty-flowtable}所示：
  \begin{figure}[htp!]
    \centering
    \includegraphics[width=10cm]{empty-flowtable}
    \caption{空流表信息}
    \label{fig:c:sdn-ob_empty-flowtable}
  \end{figure}  

  \item ping之后的流表信息，如图\ref{fig:c:sdn-ob_flowtable-ping}所示：
  \begin{figure}[htp!]
    \centering
    \includegraphics[width=10cm]{flowtable-ping}
    \caption{ping通后流表信息}
    \label{fig:c:sdn-ob_flowtable-ping}
  \end{figure}  
  
  ping通之后在SDN交换机的流表里多了4条转发流表，分别是两个方向的ARP应答报文转发表和两个方向的IP报文转发表（具体内容在后面的REST API数据中查验）。由此可以说明：控制器向SDN交换机注入了四条转发流表，允许两边的主机相互ping通。
\end{enumerate}

\subsubsection{使用REST API接口查询交换机的相关功能数据；}
\begin{enumerate}
  \item 查询交换机基本信息
  
  使用curl命令和对应的REST API地址查询交换机的对应信息。在命令行终端输入如下命令，即可查询返回交换机的基本信息数据内容。
  \begin{code}[console]
    # curl http://192.168.1.3:8080/wm/core/switch/all/desc/json
  \end{code}
  \begin{code}[json]
    {
      "desc": {
        "version": "OF_13",
        "manufacturer_description": "HuNan XinShi NetWork",
        "hardware_description": "OpenBox HW 2017",
        "software_description": "OpenBox Driver 1.0.0",
        "serial_number": "None",
        "datapath_description": "None"
      }
    }
  \end{code}
  \item 查询端口计数信息
  \begin{code}[console]
    # curl http://192.168.1.3:8080/wm/core/switch/all/port/json
  \end{code}
  \begin{code}[json]
    {
      "port_reply": [{
        "version": "OF_13",
        "port": [{
          "port_number": "local",
          "receive_packets": "10957",
          "transmit_packets": "1180",
          "receive_bytes": "592738",
          "transmit_bytes": "143898",
          "receive_dropped": "0",
          "transmit_dropped": "0",
          "receive_errors": "0",
          "transmit_errors": "0",
          "receive_frame_errors": "0",
          "receive_overrun_errors": "0",
          "receive_CRC_errors": "0",
          "collisions": "0",
          "duration_sec": "4294966827",
          "duration_nsec": "573987"
        }, 
        
        ]
      }]
    }    
  \end{code}
  端口计数统计信息返回的内容中只摘取显示了一个端口的数据显示，其他端口数据内容已经省略。
  
  \item 查询交换机流表信息
  \begin{code}[console]
    # curl http://192.168.1.3:8080/wm/core/switch/all/flow/json
  \end{code}
  \begin{code}[json]
    {
      "flows": [{
        "version": "OF_13",
        "cookie": "9007199271518208",
        "table_id": "0x0",
        "packet_count": "162",
        "byte_count": "571400",
        "duration_sec": "0",
        "duration_nsec": "0",
        "priority": "1",
        "idle_timeout_s": "5",
        "hard_timeout_s": "0",
        "flags": [],
        "match": {
          "in_port": "3",
          "eth_dst": "b8:27:eb:d8:83:20",
          "eth_src": "b8:27:eb:c1:d1:39",
          "eth_type": "0x800",
          "ipv4_src": "192.168.2.119",
          "ipv4_dst": "192.168.2.111"
        },
        "instructions": {
          "instruction_apply_actions": {
            "actions": "output=1"
          }
        }
      },       
      ]
    }
  \end{code}
  交换机流表信息返回的内容中只摘取显示了一条流表的数据显示，其他流表数据内容已经省略。
\end{enumerate}

\subsubsection{修改Python脚本，手动配置流表，观察测试主机的通信变化；}

\begin{enumerate}
  \item 修改流表脚本，使其转发端口错误；
  
修改openflow\_flow-test.py，主要修改其流表定义内容，其他位置代码无需修改。流表定义内容如下：

  \begin{code}[python]    
    flowbe0 = {
      'switch':"00:00:00:0a:00:00:08:01",
      "table":"0",
      "name":"flow-0",
      "cookie":"60",
      "priority":"1",
      "active":"true",
      "eth_dst":" b8:27:eb:c1:d1:39",  #dmac
      "eth_src":" b8:27:eb:d8:83:20",  #smac
      "eth_type":"0x800",              #type
      "in_port":"1",                   #inport 
      "ipv4_src":"192.168.2.111",      #sip
      "ipv4_dst":"192.168.2.119",      #dip
      "actions":"output=2"             #执行动作，输出端口为2
    }
    flowbe1 = {
      'switch':"00:00:00:0a:00:00:08:01",
      "table":"0",
      "name":"flow-1",
      "cookie":"61",
      "priority":"1",
      "active":"true",
      "eth_dst":"b8:27:eb:d8:83:20",   #dmac
      "eth_src":"b8:27:eb:c1:d1:39",   #smac
      "eth_type":"0x800",              #type
      "in_port":"3",                   #inport 
      "ipv4_src":"192.168.2.119",      #sip
      "ipv4_dst":"192.168.2.111",      #dip
      "actions":"output=2"             #执行动作，输出端口为2
    }   
  \end{code}
  \item 重启SDN控制器，并停止测试主机的ping操作；
  \item 配置用户流表，脚本执行命令如下：
  \begin{code}[console]
    # python openflow_flow-test.py
    (200, 'OK', '{"status" : "Entry pushed"}')
    (200, 'OK', '{"status" : "Entry pushed"}')
  \end{code}

  脚本执行成功，两条流表添加成功，在测试主机两边进行ping测试，观察是否能够ping通？
  \item 修改流表脚本，使其转发端口正确，再次验证。
  \begin{enumerate}
    \item 两种动作分别修改为1进3出和3进1出。添加流表后，再重新ping测试，观察是否能够ping通？
    \item 由此说明：对于SDN交换机，
    可以通过用户修改流表的方式来定义某一条具体流的转发行为。
  \end{enumerate}
\end{enumerate}

\subsection{思考题}
\label{subsec:c:sdn-ob:s:sdn_rethink}

\begin{enumerate}
  \item WEB界面上显示的转发流表是如何生成的？分析其生成流程；
  \item 测试主机第一次ping通的数据处理流程与第二次ping通的数据处理流程有何不同？
  \item 控制器界面的拓扑图是如何生成的？需要哪些数据？
\end{enumerate}

\subsection{注意事项及有关说明}
\label{subsec:c:sdn-ob:s:sdn_notice}

\begin{enumerate}
  \item SDN控制器为JAVA实现，有时候WEB存在缓存，导致数据显示不及时，在流表更新显示（两次实验对比）时，先清空历史缓存数据后再显示；
  \item Python脚本实现了一个http协议的客户端脚本程序，通过http协议及REST API的路径请求或设置SDN控制器WEB的对应功能模块，获取或设置该模块的返回数据或输入参数，传输数据类型为json格式。
\end{enumerate}

\subsection{考核方法}
\label{subsec:c:sdn-ob:s:sdn_criterion}

完成本次实验，需要提交一份实验报告、一份程序源代码和一份程序输出日志。
\begin{enumerate}
  \item （20分）在规定时间内完成实验，并提交实验成果；
  \item （40分）根据实现OpenFlow消息类型的多少计分，简单类型5分，复杂类型10分；
  \item （10分）通过脚本设置可以实现两个测试主机ping通和不能ping通；
  \item （20分）通过网上搜索学习，
  使用其他多个REST API查询到了交换机更多的数据内容；
  \item （10分）实验报告与源代码内容完整、格式规范。
\end{enumerate}

