再試 SDN:利用 RYU 控制 OpenFlow 交換器

上次我們略略了解到 SDN 和 OpenFlow,也試過用 mininet 模擬一個簡單的網絡。我試過用 FloodlightOpenDaylight 這兩種基於 Java 的 OpenFlow 控制器,但感覺太複雜。這次找來了 RYU 這種以 Python 寫成的 OpenFlow 控制器,利用它的 Rest API 去做 Flow 實驗。

Flow 表是支援 OpenFlow 的交換器用來決定封包的流量用,我嘗試用 Rest API 去移除它的預設規則,然後再加回,看看有什麼效果。

我先按著這頁的指示,將需要的組件和 Ryu 下載回來在另一台虛擬機器上安裝:
sudo apt-get install python-eventlet python-routes python-webob python-paramiko python-oslo.config

git clone git://github.com/osrg/ryu.git
cd ryu

然後啟動它,使用 simple_switch_13 是想支援 OpenFlow 1.3 標準:
PYTHONPATH=. ./bin/ryu-manager --verbose ryu/app/simple_switch_13.py ryu/app/ofctl_rest.py ryu/app/rest_topology.py

可見它已在 8080 埠上聆聽 HTTP RESTful 要求:
Capture-ryu1

在安裝了 mininet 的電腦上執行以下指令,還是選用直線的拓樸:
$ sudo mn --topo linear,5 --mac --switch ovsk,protocols=OpenFlow13 --controller remote,ip=<控制器的 IP 地址>,port=6633

mininet 的畫面:
Capture-ryu3

啟動後可見 RYU 控制器不斷跟 OpenFlow 交換器溝通:
Capture-ryu2

利用 Chrome 的 Postman 程式,我們可以發送 RESTful 要求,去看看現在的拓樸:
GET http://<控制器的 IP 地址>:8080/v1.0/topology/switches

結果:
[
  {
    "ports": [
      {
        "hw_addr": "ca:a5:f0:9e:15:65",
        "name": "s1-eth1",
        "port_no": "00000001",
        "dpid": "0000000000000001"
      },
      {
        "hw_addr": "52:8e:d8:12:de:07",
        "name": "s1-eth2",
        "port_no": "00000002",
        "dpid": "0000000000000001"
      }
    ],
    "dpid": "0000000000000001"
  },
  {
    "ports": [
      {
        "hw_addr": "3e:0a:77:94:58:c8",
        "name": "s2-eth1",
        "port_no": "00000001",
        "dpid": "0000000000000002"
      },
      {
        "hw_addr": "e2:c6:cd:61:6d:f0",
        "name": "s2-eth2",
        "port_no": "00000002",
        "dpid": "0000000000000002"
      },
      {
        "hw_addr": "8e:ce:4d:0a:54:14",
        "name": "s2-eth3",
        "port_no": "00000003",
        "dpid": "0000000000000002"
      }
    ],
    "dpid": "0000000000000002"
  },
  {
    "ports": [
      {
        "hw_addr": "52:6e:de:2f:69:d1",
        "name": "s3-eth1",
        "port_no": "00000001",
        "dpid": "0000000000000003"
      },
      {
        "hw_addr": "92:19:fa:b1:aa:3d",
        "name": "s3-eth2",
        "port_no": "00000002",
        "dpid": "0000000000000003"
      },
      {
        "hw_addr": "5e:d7:25:e6:59:00",
        "name": "s3-eth3",
        "port_no": "00000003",
        "dpid": "0000000000000003"
      }
    ],
    "dpid": "0000000000000003"
  },
  {
    "ports": [
      {
        "hw_addr": "fa:88:fa:db:85:b0",
        "name": "s4-eth1",
        "port_no": "00000001",
        "dpid": "0000000000000004"
      },
      {
        "hw_addr": "2a:3c:fb:a0:d7:2b",
        "name": "s4-eth2",
        "port_no": "00000002",
        "dpid": "0000000000000004"
      },
      {
        "hw_addr": "b6:80:d6:d7:a0:4b",
        "name": "s4-eth3",
        "port_no": "00000003",
        "dpid": "0000000000000004"
      }
    ],
    "dpid": "0000000000000004"
  },
  {
    "ports": [
      {
        "hw_addr": "d6:10:2e:f4:c0:19",
        "name": "s5-eth1",
        "port_no": "00000001",
        "dpid": "0000000000000005"
      },
      {
        "hw_addr": "b6:93:ba:61:c7:af",
        "name": "s5-eth2",
        "port_no": "00000002",
        "dpid": "0000000000000005"
      }
    ],
    "dpid": "0000000000000005"
  }
]

我們見到 dpid 是交換器的 ID,它們有著不同的 Port ID,網絡上共有五台交換器。

我們去查去 dpid 為 1 的交換器的 Flow 表:
GET http://<控制器的 IP 地址>:8080/stats/flow/1

Ping 之前,任何封包都要 output 到 4294967293,這代表將封包傳送到 RYU 控制器去判斷流向:
{
  "1": [
    {
      "actions": [
        "OUTPUT:4294967293"
      ],
      "idle_timeout": 0,
      "cookie": 0,
      "packet_count": 462,
      "hard_timeout": 0,
      "byte_count": 96104,
      "length": 80,
      "duration_nsec": 737000000,
      "priority": 0,
      "duration_sec": 439,
      "table_id": 0,
      "flags": 0,
      "match": {
        
      }
    }
  ]
}

我嘗試由 h1 Ping 到 h5 後,控制器給了它兩條新的 Flow,就是目的地 MAC 地址為 00:00:00:00:00:01 從 Port 2 輸入的封包會傳到 Port 1,而目的地 MAC 地址為 00:00:00:00:00:05 從 Port 1 輸入的封包會 傳到 Port 2,這某程度就是 Ethernet 交換器的原理,防止封包廣播。而這個機制就是由 simple_switch_13.py 去進行:
{
  "1": [
    {
      "actions": [
        "OUTPUT:4294967293"
      ],
      "idle_timeout": 0,
      "cookie": 0,
      "packet_count": 543,
      "hard_timeout": 0,
      "byte_count": 112371,
      "length": 80,
      "duration_nsec": 151000000,
      "priority": 0,
      "duration_sec": 486,
      "table_id": 0,
      "flags": 0,
      "match": {
        
      }
    },
    {
      "actions": [
        "OUTPUT:1"
      ],
      "idle_timeout": 0,
      "cookie": 0,
      "packet_count": 4,
      "hard_timeout": 0,
      "byte_count": 280,
      "length": 96,
      "duration_nsec": 448000000,
      "priority": 1,
      "duration_sec": 18,
      "table_id": 0,
      "flags": 0,
      "match": {
        "dl_dst": "00:00:00:00:00:01",
        "in_port": 2
      }
    },
    {
      "actions": [
        "OUTPUT:2"
      ],
      "idle_timeout": 0,
      "cookie": 0,
      "packet_count": 3,
      "hard_timeout": 0,
      "byte_count": 238,
      "length": 96,
      "duration_nsec": 440000000,
      "priority": 1,
      "duration_sec": 18,
      "table_id": 0,
      "flags": 0,
      "match": {
        "dl_dst": "00:00:00:00:00:05",
        "in_port": 1
      }
    }
  ]
}

Capture-ryu4

我輸入以下 Flow 規則,如果封包目的地 MAC 地址為 00:00:00:00:00:01,又從 Port 2 輸入,就什麼都不做,即是取消封包傳到 Port 1:
POST http://<控制器的 IP 地址>:8080/stats/flowentry/modify

{
  "dpid": 1,
  "match":{
     "dl_dst": "00:00:00:00:00:01",
     "in_port":2
  },
  "actions":[]
}

結果回應封包即時不能傳送到 h1:
Capture-ryu5

如果要重新給 Port 1,可以輸入以下 Flow 規則(其他 action 可以是 Group、Queue 等):
POST http://<控制器的 IP 地址>:8080/stats/flowentry/modify

{
  "dpid": 1,
  "match":{
     "dl_dst": "00:00:00:00:00:01",
     "in_port":2
  },
  "actions":[{"type":"OUTPUT","port":1}]
}

這樣網絡又再次打通了:
Capture-ryu6

這也不過是最簡單的規則改動,RYU 更可以自訂一些 Python 程式去改動 Flow 表。RYU 不足的地方就是它沒有清楚說明 REST 的用法,最多不過是打開那堆在 app 資料夾內名字帶有 "rest" 的 Python 程式碼去查看,而我也不過只能試試這個簡單功能(程式碼)。當然開發者可以利用這些 RESTful API 或它本身的 Pyhton API 去建立程式。

參考資料:
RYU Version OpenFlow Tutorial
OpenFlow
OpenFlow Technical Library
SDN Lab 5$ REST and Ryu
RYU SDN Framework - ebook
RYU SDN Framework
RYU RESTAPI 的使用
在RYU上使用Rest API控制flow entry part 1/2 (match field 沒有mask)
Instructions & Actions

本文連結