0;^PKG3nVub-Lw~
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
hHTnN1Tdd=UsC4au^K7nrL;nV9U(_
Date: Tue, 23 Feb 2021 21:40:27 +0800
Subject: [PATCH 03/11] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=AC=AC=E4=BA=94?=
=?UTF-8?q?=E5=8D=95=E5=85=83?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 1 +
data/ch_router-openbox/sec_networking.tex | 2 +-
data/ch_router-openbox/sec_router.tex | 2 +-
data/ch_sdn-openbox/sec_ns.tex | 201 +++++++
data/ch_sdn-openbox/sec_sdn.tex | 564 ++++++++++++++++++
data/ch_sdn-openbox/sec_topo.tex | 145 +++++
data/preface.tex | 4 +-
.../router-openbox/networking-topo.pdf | Bin 0 -> 88841 bytes
.../chapters/router-openbox/router-topo.pdf | Bin 0 -> 85621 bytes
.../chapters/router-openbox/switch-topo.pdf | Bin 0 -> 83718 bytes
.../chapters/router-openbox/switch-topo.png | Bin 23421 -> 0 bytes
.../chapters/sdn-openbox/empty-flowtable.jpg | Bin 0 -> 224898 bytes
.../chapters/sdn-openbox/flowtable-ping.jpg | Bin 0 -> 204314 bytes
figure/chapters/sdn-openbox/ns-topo.pdf | Bin 0 -> 82082 bytes
figure/chapters/sdn-openbox/openflow-topo.pdf | Bin 0 -> 93008 bytes
.../chapters/sdn-openbox/port-flowtable.jpg | Bin 0 -> 227902 bytes
figure/chapters/sdn-openbox/sdn-host.jpg | Bin 0 -> 146820 bytes
figure/chapters/sdn-openbox/sdn-switch.jpg | Bin 0 -> 164317 bytes
figure/chapters/sdn-openbox/sdn-test-topo.jpg | Bin 0 -> 104963 bytes
figure/chapters/sdn-openbox/sdn-topo.pdf | Bin 0 -> 91906 bytes
figure/src/openbox_topo.vsd | Bin 0 -> 434688 bytes
21 files changed, 915 insertions(+), 4 deletions(-)
create mode 100644 figure/chapters/router-openbox/networking-topo.pdf
create mode 100644 figure/chapters/router-openbox/router-topo.pdf
create mode 100644 figure/chapters/router-openbox/switch-topo.pdf
delete mode 100644 figure/chapters/router-openbox/switch-topo.png
create mode 100644 figure/chapters/sdn-openbox/empty-flowtable.jpg
create mode 100644 figure/chapters/sdn-openbox/flowtable-ping.jpg
create mode 100644 figure/chapters/sdn-openbox/ns-topo.pdf
create mode 100644 figure/chapters/sdn-openbox/openflow-topo.pdf
create mode 100644 figure/chapters/sdn-openbox/port-flowtable.jpg
create mode 100644 figure/chapters/sdn-openbox/sdn-host.jpg
create mode 100644 figure/chapters/sdn-openbox/sdn-switch.jpg
create mode 100644 figure/chapters/sdn-openbox/sdn-test-topo.jpg
create mode 100644 figure/chapters/sdn-openbox/sdn-topo.pdf
create mode 100755 figure/src/openbox_topo.vsd
diff --git a/.gitignore b/.gitignore
index 7d445d1..040b98c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,4 @@ _minted-*
*.exe
*.obj
*.listing
+settings.json
diff --git a/data/ch_router-openbox/sec_networking.tex b/data/ch_router-openbox/sec_networking.tex
index 3ed9b55..04d5746 100644
--- a/data/ch_router-openbox/sec_networking.tex
+++ b/data/ch_router-openbox/sec_networking.tex
@@ -64,7 +64,7 @@ BGP是自治系统间的路由协议,是一种外部网关协议,
\begin{figure}[!ht]
\centering
- \includegraphics[width=8cm]{switch-topo}
+ \includegraphics[width=12cm]{networking-topo}
\caption{三层路由组网实验拓扑图}
\label{fig:c:router-ob_networking-topo}
\end{figure}
diff --git a/data/ch_router-openbox/sec_router.tex b/data/ch_router-openbox/sec_router.tex
index 2deeef2..fae5866 100644
--- a/data/ch_router-openbox/sec_router.tex
+++ b/data/ch_router-openbox/sec_router.tex
@@ -78,7 +78,7 @@
\begin{figure}[!ht]
\centering
- \includegraphics[width=8cm]{switch-topo}
+ \includegraphics[width=8cm]{router-topo}
\caption{三层路由器实验拓扑图}
\label{fig:c:router-ob_router-topo}
\end{figure}
diff --git a/data/ch_sdn-openbox/sec_ns.tex b/data/ch_sdn-openbox/sec_ns.tex
index 3c44e3a..2ef32f8 100644
--- a/data/ch_sdn-openbox/sec_ns.tex
+++ b/data/ch_sdn-openbox/sec_ns.tex
@@ -6,23 +6,224 @@
\subsection{实验目的}
\label{subsec:c:sdn-ob:s:ns_object}
+本实验的主要目的是让学生掌握网络测试的常用原理及方法,
+了解在测试过程中影响测量精度的原因及如何提高测量精度。
+了解软硬件结果的测量方法与优势。
+
\subsection{实验内容}
\label{subsec:c:sdn-ob:s:ns_content}
+使用可编程网络平台及用户应用AMS(自动测量系统)软件源代码,
+对网络远端的服务器进行RTT延时测量和带宽测量,
+测量不同长度网线的数据传输时长。主要完成以下内容:
+
+\begin{enumerate}
+ \item 测量RTT时长:使用AMS软件测量一台校园网内服务器的RTT时间,
+ 测量报文使用ping协议文本;
+ \item 测量带宽:在测量网络中添加一台可编程网络平台,
+ 配置为带宽响应测量模式,测量这两个节点之间的实时网络流量,
+ 并用不同测量报文大小来验证测量结果;
+ \item 测量网线时延:测量不同长度网线的数据传输时延,
+ 根据测量结果分析本系统的测量优势与局限;
+ \item 添加软件计时测量:修改测量报文代码,
+ 在开始发送时取系统时间做为软件发送时间,
+ 在接收到报文后为每个报文设置系统接收时间,计算软件的RTT时间。
+\end{enumerate}
+
\subsection{实验原理、方法和手段}
\label{subsec:c:sdn-ob:s:ns_principle}
+\subsubsection{测量原理}
+网络测量主要是通过时间和数据量相关和相对的关系来反映网络中的各种特性,
+如带宽、延时、抖动和丢包等。
+
+网络测量的分类标准有多种。根据测量的方式分为:
+主动测量和被动测量;根据测量点的多少分为:单点测量与多点测量;
+根据被测量者知情与否分为:协作式测量与非协作式测量;
+根据测量所采用的协议分为:基于BGP协议的测量、
+基于TCP/IP协议的测量以及基于SNMP协议的测量;
+根据测量的内容分为:拓扑测量与性能测量。
+\subsubsection{可编程网络平台的测量特征}
+
+本实验的测量最主要方法是由软件构造测量报文和设定发送报文的时刻表,
+由硬件在指定时间进行报文发送,并在接收获得回应报文后标记硬件的接收时刻。
+接收数据由软件汇总统计计数,与原发送数据进行时间关联,
+计算测量结果。硬件的时间控制精度为8ns,
+故测量数据的收发时间均控制在8ns误差范围内。
+根据时间的精准控制,扩展出硬件背靠背发包测试网络带宽,
+精准周期发包测量精准抖动。
+
+\subsubsection{测量流程}
+
+\begin{enumerate}
+ \item \textbf{软件构造测量报文:}根据测量要求,软件构造测量报文,
+ 报文内容由用户决定。如果测量通用服务器,可以使用ping报文;
+ \item \textbf{软件设置测量报文的发送间隔:}软件设备每个测量报文的发送间隔,
+ 间隔时间可以是零,表示硬件背靠背发送报文;
+ \item \textbf{软件发送测量报文到硬件缓存:}软件将所有测量报文构造好后,
+ 通过分组发送函数发送到硬件缓存。硬件最大缓存32个MTU为1500的分组数据;
+ \item \textbf{软件启动发送:}所有测量分组发送到硬件完成后,
+ 软件可以随时启动发送,通过写硬件状态寄存器的方式通知硬件开始发送测量报文;
+ \item \textbf{硬件根据时间参数严格输出测量报文:}
+ 硬件从开始接收到发送标记时刻开始发送第一个报文,
+ 然后根据报文携带的间隔时间,循环等待,直到下一个报文发送时刻,
+ 将该报文依次发送出去;
+ \item \textbf{硬件接收测量响应报文,标记硬件时间戳:}
+ 等待测量响应服文返回,当接收到任何响应报文后,
+ 硬件均会在报文的头部添加本址硬件时间戳信息,然后将该报文发送给软件;
+ \item \textbf{软件接收报文:}软件根据测量要求,循环接收测量响应报文,
+ 如ping 32个报文后需要接收到32个响应报文;
+ \item \textbf{软件计算测量结果:}测量响应报文接收完成后,
+ 将之前设置的测量配置参数(各报文发送间隔)、
+ 硬件开始发送报文时间戳和接收报文内的时间戳等信息汇总起来进行计算,
+ 得到测量结果后打印输出。
+\end{enumerate}
+
\subsection{实验条件}
\label{subsec:c:sdn-ob:s:ns_requirement}
+\begin{itemize}
+ \item 可编程网络平台两个,被测试服务器一台;
+ 连接拓扑如图\ref{fig:c:sdn-ob_ns-topo}所示;
+ \item 串口线一根,网线多根;
+ \item AMS软件源代码;
+\end{itemize}
+
+\begin{figure}[!ht]
+ \centering
+ \includegraphics[width=9cm]{ns-topo}
+ \caption{高精度网络测量实验拓扑图}
+ \label{fig:c:sdn-ob_ns-topo}
+\end{figure}
+
\subsection{实验步骤}
\label{subsec:c:sdn-ob:s:ns_procedure}
+\begin{enumerate}
+ \item 测试平台与被测量主机间的连接性,在平台\texttt{ping 192.168.1.111};
+ \begin{code}[console]
+ root@HNXS-FAST:/home/hnxs/ams# ping 192.168.1.111
+ PING 192.168.1.111 (192.168.1.111) 56(84) bytes of data.
+ 64 bytes from 192.168.1.111: icmp_seq=1 ttl=64 time=1.57 ms
+ 64 bytes from 192.168.1.111: icmp_seq=2 ttl=64 time=1.60 ms
+ 64 bytes from 192.168.1.111: icmp_seq=3 ttl=64 time=1.74 ms
+ ^C
+ --- 192.168.1.111 ping statistics ---
+ 3 packets transmitted, 3 received, 0% packet loss, time 2003ms
+ rtt min/avg/max/mdev = 1.576/1.640/1.746/0.088 ms
+ \end{code}
+
+ \item 运行AMS测量命令,测量服务RTT的命令如下:
+ \begin{code}[console]
+ root@HNXS-FAST:/home/hnxs/ams# ams 192.168.1.111 5 70 1000000000
+ fastU->REG Version:20180827,OpenBox HW Version:2030200722
+ FAST UA REG->from pid:1068,state:21,mid:131
+ fastU->Register UA to FAST Kernel! Wait Reply......
+ fastU->UA->pid:1068,mid:131,Register OK!
+ fastU->libua version:20180827
+ fastU->librule version:20181015,Default Action:0x40000080
+ wait for ams pkts...
+ fastU->fast_ua_recv......
+ \end{code}
+ 系统输出如上信息说明AMS运行成功,并发送5个70字节大小的ping报文出去,默认间隔为1秒。等待ping响应回来。
+ \item 观察RTT测量结果
+ \begin{code}[text]
+ AMS ID PKT_SIZE TS(ns)
+ 0, 70, 1583184
+ 1, 70, 1625224
+ 2, 70, 1707240
+ 3, 70, 1492256
+ 4, 70, 1650272
+ \end{code}
+ 观察测量结果,计算结果在1.5ms左右,与之前的ping测试结果相差不大。
+
+ \item 测试两个测量平台之间的实时网络带宽
+ \begin{code}[console]
+ root@HNXS-FAST:/home/hnxs/ams# ams 192.168.1.111 5 100 0
+ fastU->REG Version:20180827,OpenBox HW Version:2030200722
+ FAST UA REG->from pid:1068,state:21,mid:131
+ fastU->Register UA to FAST Kernel! Wait Reply......
+ fastU->UA->pid:1068,mid:131,Register OK!
+ fastU->libua version:20180827
+ fastU->librule version:20181015,Default Action:0x40000080
+ wait for ams pkts...
+ fastU->fast_ua_recv......
+ \end{code}
+
+ 测试之前,要在此链路中间增加一个背景流,使用iperf的UDP流大小为600Mbps,
+ 系统给服务器发送5个100字节大小的ping报文出去,默认间隔为1秒。
+
+\item 观察带宽测试结果
+ \begin{code}[text]
+ AMS ID PKT_SIZE BW(bps)
+ 0, 70, 341467254
+ \end{code}
+ Iperf的背景流设置参数为600M时,其实际带宽会跑到648Mbps左右。
+ 链路上只有两种数据流,该测量值只是某个测量小时段的带宽反馈,
+ UDP流也存在不稳定性,故其结果相加不可能完全是1000Mbps。
+ 其相加结果与千兆带宽基本相等。
+
+\item 测量网线传输延时
+ \begin{code}[console]
+ root@HNXS-FAST:/home/hnxs/ams# ams
+ fastU->REG Version:20180827,OpenBox HW Version:2030200722
+ FAST UA REG->from pid:1068,state:21,mid:131
+ fastU->Register UA to FAST Kernel! Wait Reply......
+ fastU->UA->pid:1068,mid:131,Register OK!
+ fastU->libua version:20180827
+ fastU->librule version:20181015,Default Action:0x40000080
+ wait for ams pkts...
+ fastU->fast_ua_recv......
+ \end{code}
+
+ 网络测量时需要连接在设备的0和1号端口上,默认测量32组数据。
+
+ \item 观察网线测量结果
+ \begin{code}[text]
+ AMS ID PKT_SIZE TS(ns)
+ 0, 100, 20194
+ 1, 100, 20214
+ 2, 100, 20210
+ 3, 100, 20216
+ 4, 100, 20202
+ \end{code}
+
+ 从数据分析每组测量结果有些误差,但基本
+ \item 分析测量数据的差异
+ \begin{enumerate}
+ \item 对相同测试案例进行多组测量,观察数据变化。
+ \item 分析RTT测量时,软件计时测量与硬件计时测量的差异。
+ \end{enumerate}
+\end{enumerate}
\subsection{思考题}
\label{subsec:c:sdn-ob:s:ns_rethink}
+\begin{enumerate}
+ \item 硬件背靠背报文发送是什么意思?其测量的原理是什么?
+ \item 通过硬件方式测量多组延时数据,为什么仍有较大波动?
+ \item 如何测量网络的抖动?
+\end{enumerate}
+
\subsection{注意事项及有关说明}
\label{subsec:c:sdn-ob:s:ns_notice}
+\begin{enumerate}
+ \item 测量服务器的PING延时,需要修改发包内容为正确的PING报文内容。
+ \item 测量报文的间隔可以设置为零,但真正发包时的发包时刻计算却不是零,
+ 是因为硬件报文是逐个逐个发送的,且每个报文发送时间长短与其报文长度相关。
+ \item 时间计算与带宽计算中均用到了除法,引入了误差。
+ 硬件时钟本身是存在误差(时钟飘移)的,也会引入计数的误差。
+\end{enumerate}
+
\subsection{考核方法}
\label{subsec:c:sdn-ob:s:ns_criterion}
+
+完成本次实验,需要提交一份实验报告和一分程序输出日志。
+\begin{enumerate}
+ \item (20分)在规定时间内完成实验,并提交实验成果;
+ \item (25分)实现软件计时测量与硬件测量对比;
+ \item (25分)将多线测量结果以图形曲线形式汇总并做分析总结;
+ \item (20分)完成网络抖动测量,并做图形化汇总统计;
+ \item (10分)实验报告与源代码内容完整、格式整洁。
+\end{enumerate}
+
diff --git a/data/ch_sdn-openbox/sec_sdn.tex b/data/ch_sdn-openbox/sec_sdn.tex
index adab807..98dca17 100644
--- a/data/ch_sdn-openbox/sec_sdn.tex
+++ b/data/ch_sdn-openbox/sec_sdn.tex
@@ -6,23 +6,587 @@
\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}
+\subsection{SDN交换机工作原理}
+SDN即Software Defined Network(软件定义网络),
+是一种新型网络创新架构,用于解决传统网络架构中对于网络虚拟化、
+各类自动化部署以及新型网络协议的问题。
+
+SDN将传统的紧耦合网络设备被拆分成了应用、控制、转发三层,
+其中应用和控制层面能够控制网络中数据的流向以及协议控制,
+而底层的网络设备被抽象为了可自定义的转发逻辑实体,
+可被上层应用定义为不同类型的转发设备。
+
+在应用层,用户可通过通用、简单的API获取网络信息,
+并可修改转发设备的工作逻辑,达到动态改变网络架构的目的。
+
+\subsection{SDN交换机分组处理流程}
+\begin{enumerate}
+ \item \textbf{启动协议通道:}
+ SDN交换机运行OpenFlow协议代理程序与上层控制器建立通信连接。
+ 交换机解析处理OpenFlow协议,
+ 根据协议要求完成协议功能的相关数据收集和配置功能,
+ 构造相应的OpenFlow协议应答控制器。
+
+ \item \textbf{建立连接:}OpenFlow协议代理运行时,
+ 交换机首先和控制器建立连接,
+ 并发送OFP\_HELLO消息用以确认双方都支持的OFP协议版本信息;
+ \item \textbf{查询或设置数据:}
+ 控制器通过OpenFlow的具体消息协议查询或设置交换机其他相关功能的数据,
+ 如OFPT\_HELLO、OFPT\_ECHO\_REQUEST、OFPT\_PACKET\_IN、OFPT\_PACKET\_OUT、
+ OFPT\_FLOW\_MOD、OFPT\_PORT\_MOD、OFPMP\_DESC、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)一台。
+ 网络配置及连接拓扑如图\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}:《可编程网络平台-操作手册》完成。
+
+\subsubsection{基于SDN交换机源码,选择实现自己的协议功能;}
+
+\begin{enumerate}
+ \item 修改源代码
+ \begin{code}[c]
+ enum ofperr handle_openflow_callback(struct ofp_buffer *ofpbuf,int len)
+ {
+ int oftype = ofpbuf->header.type;
+
+ SHOW_FUN(0);
+ 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
+ fastU->REG Version:20180827,OpenBox HW Version:2030200722
+ port_name:eth0,port:0
+ port_name:obx0,port:0
+ xofp uses obsolete (PF_INET,SOCK_PACKET)
+ 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!
+ eth0 ADD!
+ obx0 ADD!
+ obx1 ADD!
+ obx2 ADD!
+ obx0(0x29008) Start...
+ obx2(0x2e100) Start...
+ obx3(0x30958) Start...
+ obx1(0x2b890) Start...
+ obx3 ADD!
+ \end{code}
+
+ \item 查看控制器界面基本信息,
+ 在浏览器打开控制器WEB网站\url{http://192.168.1.3/ui/index.html},
+ 控制界面如图\ref{fig:c:sdn-ob_sdn-switch}所示:
+ \begin{figure}[!htp]
+ \centering
+ \includegraphics[width=14cm]{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=14cm]{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=14cm]{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=14cm]{sdn-test-topo}
+ \caption{SDN网络拓扑信息}
+ \label{fig:c:sdn-ob_sdn-test-topo}
+ \end{figure}
+\end{enumerate}
+
+\subsubsection{在测试主机ping的前后,分别观察流表变化;}
+\begin{enumerate}
+ \item ping之前的流表信息(使用REST API方式获取其json数据),
+ 如图\ref{fig:c:sdn-ob_empty-flowtable}所示:
+ \begin{figure}[!htp]
+ \centering
+ \includegraphics[width=14cm]{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=14cm]{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 查询交换机基本信息
+ \begin{code}[console]
+ # curl http://192.168.1.3:8080/wm/core/switch/\
+ 00:00:00:0a:00:00:08:01/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/\
+ 00:00:00:0a:00:00:08:01/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"
+ }, {
+ "port_number": "1",
+ "receive_packets": "86",
+ "transmit_packets": "205",
+ "receive_bytes": "11427",
+ "transmit_bytes": "22632",
+ "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": "573991"
+ }, {
+ "port_number": "2",
+ "receive_packets": "0",
+ "transmit_packets": "0",
+ "receive_bytes": "0",
+ "transmit_bytes": "0",
+ "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": "573992"
+ }, {
+ "port_number": "3",
+ "receive_packets": "616",
+ "transmit_packets": "220",
+ "receive_bytes": "56948",
+ "transmit_bytes": "24659",
+ "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": "573994"
+ }, {
+ "port_number": "4",
+ "receive_packets": "0",
+ "transmit_packets": "0",
+ "receive_bytes": "0",
+ "transmit_bytes": "0",
+ "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": "573995"
+ }]
+ }]
+ }
+ \end{code}
+ \item 查询交换机流表信息
+ \begin{code}[console]
+ # curl http://192.168.1.3:8080/wm/core/switch/\
+ 00:00:00:0a:00:00:08:01/flow/json
+ \end{code}
+ \begin{code}[json]
+ {
+ "flows": [{
+ "version": "OF_13",
+ "cookie": "9007199254740992",
+ "table_id": "0x0",
+ "packet_count": "5",
+ "byte_count": "571392",
+ "duration_sec": "0",
+ "duration_nsec": "0",
+ "priority": "1",
+ "idle_timeout_s": "5",
+ "hard_timeout_s": "0",
+ "flags": [],
+ "match": {
+ "in_port": "1",
+ "eth_dst": "b8:27:eb:c1:d1:39",
+ "eth_src": "b8:27:eb:d8:83:20",
+ "eth_type": "0x806"
+ },
+ "instructions": {
+ "instruction_apply_actions": {
+ "actions": "output=3"
+ }
+ }
+ }, {
+ "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"
+ }
+ }
+ }, {
+ "version": "OF_13",
+ "cookie": "9007199288295424",
+ "table_id": "0x0",
+ "packet_count": "161",
+ "byte_count": "571408",
+ "duration_sec": "0",
+ "duration_nsec": "0",
+ "priority": "1",
+ "idle_timeout_s": "5",
+ "hard_timeout_s": "0",
+ "flags": [],
+ "match": {
+ "in_port": "1",
+ "eth_dst": "b8:27:eb:c1:d1:39",
+ "eth_src": "b8:27:eb:d8:83:20",
+ "eth_type": "0x800",
+ "ipv4_src": "192.168.2.111",
+ "ipv4_dst": "192.168.2.119"
+ },
+ "instructions": {
+ "instruction_apply_actions": {
+ "actions": "output=3"
+ }
+ }
+ }, {
+ "version": "OF_13",
+ "cookie": "9007199305072640",
+ "table_id": "0x0",
+ "packet_count": "2",
+ "byte_count": "571416",
+ "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": "0x806"
+ },
+ "instructions": {
+ "instruction_apply_actions": {
+ "actions": "output=1"
+ }
+ }
+ }]
+ }
+ \end{code}
+\end{enumerate}
+
+\subsubsection*{修改Python脚本,手动配置流表,观察测试主机的通信变化;}
+
+\begin{enumerate}
+ \item 修改流表脚本,使其转发端口错误
+ \begin{code}[console]
+ #vim openflow_flow-test.py
+ \end{code}
+ \begin{code}[python]
+ 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/staticflowpusher/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('192.168.1.3')
+
+ 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
+ #"ip_proto":"0x11", #proto
+ "in_port":"1", #inport
+ "ipv4_src":"192.168.2.111", #sip
+ "ipv4_dst":"192.168.2.119", #dip
+ #"tp_src":"50001", #sport
+ #"tp_dst":"50001", #dport
+ "actions":"output=2" #正确值应该为3
+ }
+ flowbe1 = {
+ 'switch':"00:00:00:0a:00:00:08:01",
+ "table":"1",
+ "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
+ #"ip_proto":"0x11", #proto
+ "in_port":"3", #inport
+ "ipv4_src":"192.168.2.119", #sip
+ "ipv4_dst":"192.168.2.111", #dip
+ #"tp_src":"50001", #sport
+ #"tp_dst":"50001", #dport
+ "actions":"output=2" #正确值应该为1
+ }
+
+ pusher.set(flowbe0)
+ pusher.set(flowbe1)
+ \end{code}
+
+ 脚本执行命令如下:
+ \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 (20分)通过脚本设置可以实现两个测试主机ping能和不能ping通;
+ \item (20分)通过网上搜索学习,
+ 使用其他多个REST~API查询到了交换机更多的数据内容;
+ \item (10分)实验报告与源代码内容完整、格式整洁。
+\end{enumerate}
+
diff --git a/data/ch_sdn-openbox/sec_topo.tex b/data/ch_sdn-openbox/sec_topo.tex
index c61cacc..184f9b5 100644
--- a/data/ch_sdn-openbox/sec_topo.tex
+++ b/data/ch_sdn-openbox/sec_topo.tex
@@ -6,23 +6,168 @@
\subsection{实验目的}
\label{subsec:c:sdn-ob:s:topo_object}
+本实验的主要目的是让学生了解OpenFlow协议中PACKET\_IN和链路探测协议
+(LLDP)的工作原理和网络拓扑构建方法。
+
\subsection{实验内容}
\label{subsec:c:sdn-ob:s:topo_content}
+使用可编程网络平台及SDN交换机源码,分析LLDP的工作原理。
+通过PACKET\_IN消息收集网络设备节点信息。
+掌握网络拓扑的构建方法。主要完成以下内容:
+
+\begin{enumerate}
+ \item \textbf{打印连接主机信息:}基于SDN交换机源码,
+ 在端口分组接收回调函数中添加相关逻辑,
+ 解析输入分组数据,提取设备MAC与IP信息,打印显示其内容;
+ \item \textbf{打印接收LLDP消息:}基于SDN交换机源码,
+ 在端口分组接收回调函数中添加相关逻辑,
+ 解析输入分组数据,打印LLDP分组的相关协议字段;
+ \item \textbf{打印发送LLDP消息:}基于SDN交换机源码,
+ 在OpenFlow协议回调函数中添加相关逻辑,
+ 提取SDN控制器下发的PACKET\_OUT消息,且消息封装的分组协议为LLDP格式;
+ 打印输出LLDP分组中的相关协议字段;
+ \item \textbf{提取控制器链路与设备信息:}
+ 使用REST~API接口获取SDN控制器的网络链路信息和设备连接信息;
+ \item \textbf{构建拓扑图:}以WEB形式展示网络拓扑结构图;
+ \item \textbf{修改网络验证:}改变物理网络拓扑结构后,
+ 重新展示学习到的网络拓扑结构图。
+\end{enumerate}
+
\subsection{实验原理、方法和手段}
\label{subsec:c:sdn-ob:s:topo_principle}
+\subsubsection{LLDP工作原理}
+
+LLDP是一个用于信息通告和获取的协议,但是需要注意的一点是,
+LLDP发送的信息通告不需要确认,不能发送一个请求来请求获取某些信息,
+也就是说LLDP是一个单向的协议,只有主动通告一种工作方式,
+无需确认,不能查询、请求。
+
+LLDP协议主要完成的功能有:初始化并维护本地MIB 库中的信息;
+从本地MIB 库中提取信息,并将信息封装到LLDP帧中。
+LLDP帧的发送有两种触发方式,一是定时器到期触发,一是设备状态发生了变化触发;
+识别并处理接收到的LLDPDU帧;维护远端设备LLDP MIB 信息库;
+当本地或远端设备MIB信息库中有信息发生变化时,发出通告事件。
+
+网络拓扑的测量是通过一些链路探测协议在各交换节点中相互收发来完成的链路的拓扑测量功能。
+SDN交换机使用LLDP链路探测协议主要是通过PACKET\_OUT消息发送LLDP分组,
+通过PACKET\_IN接收LLDP分组,通过计算获取交换机之间的连通关系和端口连接关系。
+
+\subsubsection{SDN交换机中LLDP的分组处理流程}
+\begin{enumerate}
+ \item 主动发送LLDP:
+ SDN控制器主动构造特定的LLDP报文(含交换机标记与输出端口信息),
+ 封装在OpenFlow的PACKET\_OUT方法中,从指定的交换机各端口发送出去;
+ \item 接收处理LLDP:如果端口连接的是主机,则主机会丢弃该报文,
+ 若同样是OpenFlow交换机,则会通过PACKET\_IN的方式将该LLDP报文转发给控制器;
+ \item LLDP学习:控制器收到LLDP报文后,
+ 则会学习到这两个交换机之间存在一条连接链路;
+ \item 网络设备信息学习:主机连接到交换机后,
+ 主机端会主动构造一些广播报文发送到网络上,
+ 这些报文会携带上本机的MAC地址和IP地址等信息
+ 交换机接收到主机发送报文后,在其交换流表中找不到转发表项,
+ 则会将该报文完整内容和其所输入的端口信息封装在OpenFlow协议的PACKET\_IN方法中,
+ 将报文送到SDN控制器;控制器接收到该消息后,
+ 通过学习对比发现是一台新主机入网,则会将该报文中的源MAC地址、
+ IP地址和接收交换机信息及输入端口信息等均记录在一张网络设备信息数据表中;
+ \item 构建设备连接关系:
+ 交换机之间的链路信息加上交换机与主机之间的链路信息就完整的构成了全网的拓扑连接关系。
+\end{enumerate}
+
\subsection{实验条件}
\label{subsec:c:sdn-ob:s:topo_requirement}
+\begin{itemize}
+ \item 网络创新实验平台两个,交换测试主机两台,SDN控制器一台。
+ 连接拓扑如图\ref{fig:c:sdn-ob_openflow-topo}所示;
+ \item 串口线一根,网线五根;
+ \item 网络创新实验平台使用手册、开发环境;
+ \item OpenFlow拓扑实验环境配置手册
+\end{itemize}
+
+\begin{figure}[!ht]
+ \centering
+ \includegraphics[width=10cm]{openflow-topo}
+ \caption{OpenFlow拓扑测量实验拓扑图}
+ \label{fig:c:sdn-ob_openflow-topo}
+\end{figure}
+
\subsection{实验步骤}
\label{subsec:c:sdn-ob:s:topo_procedure}
+\begin{enumerate}
+ \item 运行SDN交换机
+
+ 请参考附件《可编程网络平台-操作手册》完成。
+ \item 基于SDN交换机开源代码,在端口分组接收回调函数中添加相应逻辑;
+ \begin{code}[c]
+ int port_recv_callback(int inport,struct fast_packet *pkt,int pkt_len)
+ {
+ //解析分组字段,提取LLDP分组,并打印输出其相关协议字段
+
+ //解析分组字段,判断帧类型为0x0800,源IP为单播地址的分组,打印其源MAC地址与IP地址
+ }
+ \end{code}
+ \begin{enumerate}
+ \item 解析接收数据,判断以太网帧类型,
+ 如果帧类型为0x88CC,则说明是LLDP协议分组,根据LLDP协议打印相关协议字段内容;
+ \item 解析分组,提取输入帧类型为0x0800的分组,
+ 打印输出其源MAC地址与源IP地址(仅输出IP地址为单播类型的条目);
+ \end{enumerate}
+ \item 编译SDN交换机,验证添加逻辑功能,观察是否正确打印需要的消息内容;
+ \item 分析端口接收回调函数中打印的消息内容;
+ \begin{enumerate}
+ \item 从LLDP分组中可解析得知对端交换机的某个端口发送到本交换机的某个端口
+ \item 从普通IPv4报文中可以获得连接到该交换机上的主机节点信息
+ (MAC地址和IPv4地址)
+ \end{enumerate}
+ \item 验证并观察SDN控制器自带的拓扑展示界面
+ \begin{enumerate}
+ \item 在控制器主机打开WEB界面,输入地址:
+ \texttt{127.0.0.1/ui/index.html}。查看浏览器界面数据;
+ \item 切换浏览器的拓扑显示版块,
+ 查看网络拓扑展示效果可以看到两台交换机中间用线连接;
+ \item 连接上一台测试主机,刷新拓扑界面,
+ 查看到界面新增了一台主机,并显示其IP地址信息;
+ \item 再连接上另一台主机,刷新拓扑界面,
+ 查看到界面又新增了一台主机,并显示其IP地址信息;
+ \item 断开两台交换机之间的网线,新拓扑界面,
+ 查看两个交换机之间的连线消失了;
+ \item 断开一台测试主机,新拓扑界面,查看其对应的主机从界面消失;
+ \end{enumerate}
+ \item 使用控制器的北向REST API接口查询网络的所有链路连接关系数据,
+ API接口为:\texttt{/wm/topology/links/json};
+ \item 使用控制器的北向REST API接口查询全网所有设备节点信息,
+ API接口为:\texttt{/wm/device/};
+ \item 自己开发网络拓扑展示效果图;
+ \item 修改网络拓扑后验证自己开发程序的效果。
+\end{enumerate}
+
\subsection{思考题}
\label{subsec:c:sdn-ob:s:topo_rethink}
+\begin{enumerate}
+ \item 网络拓扑中的设备节点信息学习,使用广播报文中的信息会带来哪些影响?
+ \item OpenFlow交换机之间用一台普通交换机连接,能学习到全网的拓扑结构信息吗?
+ 分析原因。
+\end{enumerate}
+
\subsection{注意事项及有关说明}
\label{subsec:c:sdn-ob:s:topo_notice}
+\begin{enumerate}
+ \item JAVA的WEB程序显示的缓存,每新刷新操作前,请清理浏览器历史缓存数据。
+\end{enumerate}
\subsection{考核方法}
\label{subsec:c:sdn-ob:s:topo_criterion}
+
+完成本次实验,需要提交一份实验报告和一分程序输出日志。
+\begin{enumerate}
+ \item (20分)在规定时间内完成实验,并提交实验成果;
+ \item (20分)正确打印输入与输出LLDP消息内容和连接主机信息
+ \item (20分)通过REST API编程获取链路状态信息与设备信息,并能生成拓扑关系图;
+ \item (20分)以图形界面形式展示拓扑图;
+ \item (10分)动态修改物理拓扑后,程序可以及时刷新出正确拓扑图状态;
+ \item (10分)实验报告与源代码内容完整、格式整洁。
+\end{enumerate}
diff --git a/data/preface.tex b/data/preface.tex
index e25cf20..165382f 100644
--- a/data/preface.tex
+++ b/data/preface.tex
@@ -24,8 +24,8 @@
\item 第一单元:谢怡、陈建发、洪劼超、雷蕴奇,厦门大学
\item 第二单元:吴荻、周丽涛,国防科技大学
\item 第三单元:张晓丽,昆明理工大学
- \item 第四单元:胡罡、徐东来、徐明、夏竟,国防科技大学
- \item 第五单元:胡罡、徐东来、蔡志平、徐明、夏竟,国防科技大学
+ \item 第四单元:胡罡、孙志刚,国防科技大学
+ \item 第五单元:夏竟、蔡志平、徐明,国防科技大学
\item 统筹规划:徐明、夏竟,国防科技大学
\end{itemize}
diff --git a/figure/chapters/router-openbox/networking-topo.pdf b/figure/chapters/router-openbox/networking-topo.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..b4a67e6ed6dca1db881e077a7e5b890322113337
GIT binary patch
literal 88841
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