Category Archives: SDN

Software-defined networking

软件定义安全一书勘误

写在篇首
《软件定义安全-SDN/NFV新型网络的安全揭秘》一书自推出后受到读者欢迎,很快就要重印,借此机会会修正文中一些错误。本文将持续更新,避免误导读者。

第一次印刷:

P44:本节阐述了OpenFlow等南北向协议中的安全问题
勘误:本节只讨论了南向协议。应为:“本节介绍了OpenFlow等SDN南向协议及其存在的安全问题,SDN北向协议目前还没有统一的标准,一般北向协议语义上采用如RESTful等HTTP协议,感兴趣读者可自行了解,其安全问题可参考2.1.3小节,本节不做详细讨论。”

P153: 该方案就是基于Openstack平台的,通过 Openstack 平台的 FWaaS 服务接口, 为云数据中心中的每个租户提供独立的虚拟防火墙,实现了租户间的安全隔离和保护,虚拟机间的东西流量也得到了有效地管控
勘误: FWaaS是实现南北向的访问控制,而非东西向,此处有误,应为“虚拟网络的南北向流量也得到有效管控”。具体如何实现南北向流量管控,可参见P67的“2.三层防火墙”小节,里面有介绍。

第一到第三重印

P127:5.3.2中第二段最后一句,应为:Web防护的策略为{match=”->S”, action=”redirect to WAF“, priority=5}
原文为ADS,此处应为WAF

Ubuntu下openvswitch添加tcp监听

本来这不应该是什么值得写的内容,不过有些需要hack一下,所以还是记一下为好。

Ubuntu下面的openvswitch默认是开启punix(被动监听unix)进行管理ovsdb的,那么就不能查看和控制远程主机的ovsdb,openvswitch其实还可以开启ptcp或pssl模式,就可以打开远程的访问(当然存在风险,需假设控制网络是可信的)。

不过配置文件(/etc/default/openvswitch-switch,/etc/init.d/openvswitch-switch)都没有该配置项,后来找了找,相关脚本在/usr/share/openvswitch/。(dpkg -L openvswitch-switch)

其中启动ovsdb-server的脚本在/usr/share/openvswitch/scripts/ovs-ctl里面。

187         set "$@" -vconsole:emer -vsyslog:err -vfile:info
188         set "$@" --remote=punix:"$DB_SOCK" --remote=ptcp:6640

这里面添加一个tcp端口即可。

p.s. 如果去掉punix应该不行,因为ovs-vsctl默认使用了punix的方式,所以这里使用了同时启用punix和ptcp模式。

另,这个py-ovsdb-client项目还不错,封装了多种ovsdb的api,直接在python中调用即可。

在宿主机中用ovs连接内网vm

经常需要做实验验证VM的性能,那么实验环境为:H是一个物理主机(物理网卡网段为192.168.19.0/24),H中启动一台VM,VM的一个管理口eth-M配有私网ip(网段100.100.100.0/24),并连接到一个OVS桥br-test上。现在需要在H中直接连接VM。

一个直观的想法是直接ping VM,但VM与宿主机的主IP不是一个网段,所以不能直接通信,需要一个网关GW。那就直接在br-test上新建一个类型为internal的接口做网关。

ovs-vsctl add-port br-test gw -- set Interface gw type=internal
ifconfig gw 100.100.100.1
iptables -t nat -I POSTROUTING -j SNAT  -s 192.168.19.0/24 --to-source 100.100.100.1
iptables -t nat -I POSTROUTING -j SNAT  -s 100.100.100.0/24 --to-source 192.168.19.13

并使用iptables做NAT,但发现还是ping不通。我印象中iptables的访问规则与openvswitch不兼容,不知道nat是不是也受到影响。

既然如此,gw的路由应该不能放在openvswitch中,只能在namespace或原linux系统中。换一个思路,不将网关直接放在br-test上,而是使用veth对的形式:

 ip link add gw type veth peer name gw-o
ovs-vsctl add-port br-test gw
ifconfig gw 100.100.100.1
iptables -t nat -I POSTROUTING -j SNAT  -s 192.168.19.0/24 --to-source 100.100.100.100
iptables -t nat -I POSTROUTING -j SNAT  -s 100.100.100.0/24 --to-source 192.168.19.13

那么数据从gw传到了gw-o,此时可以正常路由。

Openstack节点网卡连Cisco交换机出现环路的处理

前一阵子部署了两台Openstack的计算节点,奇怪的是每隔一段时间连管理网的网卡eth0灯不亮,交换机上对应的19端口灯变橙色,登陆上去一看,eth0显示:

2: eth0:  mtu 1500 qdisc mq master ovs−system state DOWN mode DEFAULT group default qlen 1000

运行ip link set eth0 up无效。

登陆交换机,查看端口状态:

Switch>enable 
Switch#show interfaces gigabitethernet 0/19 status 
Port    Name       Status       Vlan       Duplex  Speed Type
G0/19   err−disabled 100          full   1000 1000BaseSX

可以看到19端口出现问题,继续看日志

Switch#show logging
...
Mar 30 01:43:39.827: %ETHCNTR−3−LOOP_BACK_DETECTED: Loop−back detected on GigabitEthernet0/19.
Mar 30 01:43:39.827: %PM−4−ERR_DISABLE: loopback error detected on Gi0/19, putting Gi0/19 in err−disable state
Mar 30 01:43:40.833: %LINEPROTO−5−UPDOWN: Line protocol on Interface GigabitEthernet0/19, changed state to down
Mar 30 01:43:41.840: %LINK−3−UPDOWN: Interface GigabitEthernet0/19, changed state to down

发现原来是出现了环路,参考cisco的官方文档,一个可能的原因是交换机定时发送keepalive,这个数据包进入节点后又出来,到了同一个端口,则交换机认为可能出现环路,从而禁用。

解决办法是禁用keepalive:

Switch(config)#interface gigabitethernet 0/19 
Switch(config−if)#no keepalive

当然也可以升级到高版本的IOS,默认会禁用keeplive。

然后恢复出错的端口:

Switch(config)#errdisable recovery cause loopback 
Switch(config)#exit
Switch#show errdisable recovery
...
Interfaces that will be enabled at the next timeout:  
Interface       Errdisable reason       Time left(sec)
−−−−−−−−−       −−−−−−−−−−−−−−−−−       −−−−−−−−−−−−−−
Gi0/9                   loopback          205
Gi0/10                  loopback          205
Gi0/19                  loopback          205

过了300秒后,端口应该恢复正常,此时能够连接上主机了。

我觉得也可以在节点侧,在连接eth0的ovs桥br0加stp,以观后效

root@node4:~# ovs−vsctl get bridge br0 stp_enable
false
root@node4:~# ovs−vsctl set bridge br0 stp_enable=true

参考文章

Cisco交换机命令行配置(http://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus5000/sw/configuration/guide/cli_rel_4_0_1a/CLIConfigurationGuide/sm_syslog.html)

Cisco交换机端口出错处理(http://www.cisco.com/c/en/us/support/docs/lan-switching/spanning-tree-protocol/69980-errdisable-recovery.html)

openstack icehouse update errors

最近将Openstack从havana升级到icehouse,总体问题不大,但是还是有点小问题,花了一点时间。

1 密钥过期,keystone的密钥提示过期。

如nova提示:
2014-04-29 10:52:14.074 27081 WARNING keystoneclient.middleware.auth_token [-] Verify error: Command ‘openssl’ returned non-zero exit status 4

解决办法是1) 备份/etc/keystone/ssl目录并删除,运行keystone-manage pki_setup –keystone-user keystone –keystone-group keystone,上面的ssl目录会生成,这里的用户名和用户组为linux系统中的keystone对应的用户和组 3) 修改ssl目录属主和权限700.

neutron也可能由此提示,解决办法是删除/var/lib/neutron/keystone-signing目录,并重启neutron-server。

此外,可能还需要删除/tmp下的keystone-signing-xxxxx目录。

总体而言,上面的错误表面现象为keystone 401 Unauthorized的错误,但是解决问题需要定位到相应模块,用debug=true和verbose=true从调试信息中找出问题。

如nova list返回401的原因有可能是neutron的证书问题,所以大家在调错是一定要仔细。

2 数据库升级,这个也不算是问题,只是每个模块升级的时候几乎都要升级数据库,如 keystone-manage db_sync等。

Openstack中伪造源地址攻击的防御

背景:Openstack在H版之前的时候,租户可以租用虚拟机发动DoS攻击,很多攻击的方式是伪造源地址。还有别的攻击,如ARP攻击等等,都是通过伪造源MAC或IP地址实现的。本文简单介绍一下openstack中抵御虚假源地址的攻击。

这种攻击的特点是攻击者从虚拟化系统内部发起,攻击目标可能是公网主机,或者是内网的主机。对于前者的防御比较容易,只需要在物理网络边界就可以部署安全设施进行检测和清洗;但是后者的攻击流量大部分是存在于虚拟网络和虚拟网络设备中,物理的安全设备是无法观测或理解物理网络中的流量,无法进行有效的防护,所以对于始于内部的DoS攻击,应该在虚拟化环境中将检测并抵御。
在Openstack的架构下,预计一种检测DoS攻击的方式是依靠检测模块Ceilometer,等它成熟之后,就可及时通知管理员或系统,通过Ceilometer与Neutron协作堵住这种攻击。
不过现在Ceilometer还在初级阶段,所以还是需要依靠网络虚拟化模块Neutron进行防护,好在IaaS系统知道VM的真实IP和MAC,所以可以直接在L2防火墙上对数据包进行过滤,H版已经引入了这部分特性。

下面以一台vm为例:
# neutron port-list|grep 100.0.0.16
| 368d35e1-4db7-405f-af26-1f2b6f224bd8 | | fa:16:3e:34:c8:f8 | {“subnet_id”: “57b30eb5-e9ee-489f-85ea-77bcaa6249e5”, “ip_address”: “100.0.0.16”} |

2013.2.1引入了基于IPTABLES的过滤方案
iptables -L -nvx 可以看到:
Chain neutron-openvswi-s368d35e1-4 (1 references)
pkts bytes target prot opt in out source destination
212 29180 RETURN all — * * 100.0.0.16 0.0.0.0/0 MAC FA:16:3E:34:C8:F8
0 0 DROP all — * * 0.0.0.0/0 0.0.0.0/0
此处允许真实IP和mac的数据包经过,对其他数据包抛弃

2013.2.2 引入了基于EBTABLES的过滤方案
ebtables -t nat -L 可以发现下面项
Bridge chain: libvirt-I-tap368d35e1-4d, entries: 6, policy: ACCEPT
-j I-tap368d35e1-4d-mac
-p IPv4 -j I-tap368d35e1-4d-ipv4-ip
-p IPv4 -j I-tap368d35e1-4d-ipv4
-p ARP -j I-tap368d35e1-4d-arp-mac
-p ARP -j I-tap368d35e1-4d-arp-ip
Bridge chain: I-tap368d35e1-4d-mac, entries: 2, policy: ACCEPT
-s fa:16:3e:34:c8:f8 -j RETURN
-j DROP
Bridge chain: I-tap368d35e1-4d-ipv4-ip, entries: 3, policy: ACCEPT
-p IPv4 –ip-src 0.0.0.0 –ip-proto udp -j RETURN
-p IPv4 –ip-src 100.0.0.16 -j RETURN
-j DROP
Bridge chain: I-tap368d35e1-4d-arp-mac, entries: 2, policy: ACCEPT
-p ARP –arp-mac-src fa:16:3e:34:c8:f8 -j RETURN
-j DROP
Bridge chain: I-tap368d35e1-4d-arp-ip, entries: 2, policy: ACCEPT
-p ARP –arp-ip-src 100.0.0.16 -j RETURN
-j DROP
可见确实将VM的mac和ip进行了过滤,防止其他ip和mac的数据包经过

虽然这种防护可以抵御伪造源地址的攻击,但是无法抵御使用真实源地址的DoS攻击,遇到这种情况,还是采用ceilometer或是SDN的流检测的方式较好。

p.s.1 为什么我会知道这些Openstack版本的防护特性,我会告诉你这些特性让我不能测试DDoS攻击吗…
p.s.2 除了IPTABLES和EBTABLES之外,还有没有其他方法?当然有,设想一下这个场景,VM启动之后,neutron在建立port之后向SDN网络控制器发送通知,网络控制器向OVS发送流指令,允许IP、MAC绑定数据流通过VM所在的交换机端口,然后禁止其他数据流从该端口通过。那为什么不这么做呢,原因1:需要SDN控制器,原因2:数据流先经过Linux bridge,所以不如直接在Linux bridge上应用IPTABLES和EBTABLES。
p.s.4 在SDN+Openstack环境下,租户发起的DoS的第一个受害者将是网络控制器,所以Floodlight在流量控制中做了一些工作,可参见这篇文章

neutron中一种常见网络不通的现象,附修复脚本

neutron中经常发现vm ping不通网关,我发现很多情况是因为ovs-plugin没有将qvb连接到qbr桥上,所以解决办法也很简单,就是将其连上,即将qbr设为master。

具体的neutron、ovs、veth和iptables等就不展开讲了,具体可以看下图

ovs-neutron

当一个vm启动或被硬重启之后,tap接口(当使用gre模式时为tap)和qbr桥会被自动创建,而且tap的master为qbr。理论上qvb的master也应该是qbr,但我经常发现这个link的master没有被设置。那么设置脚本就很简单了:

#!/bin/bash

LINKS=`ip link|grep qvb |awk '{print $2}' |sed s'/.$//'|sed s'/^...//'`
for LINK in $LINKS
do
# a qbr link should appear after hard reboot an instance
ip link set "qbr$LINK" up
echo ip link set "qvb$LINK" master "qbr$LINK"
ip link set "qvb$LINK" master "qbr$LINK"
echo ip link set "tap$LINK" master "qbr$LINK"
ip link set "tap$LINK" master "qbr$LINK"
done

此外在SDN环境中还有几种vm连不上可能,比如

  1. br-tun每次在ovs-agent重启后controller就会消失,此时应该在重启脚本中添加:  ovs-vsctl set-controller br-tun tcp:30.0.0.1
  2. 确定br-int和br-tun没有多余的流,用ovs-ofctl dump-flows br-int/br-tun可看
  3. 确定网络控制器正常运行,有时候监控of控制流发现只有packet_in没有packet_out,此时重启一下网络控制器可能会解决问题

如果还是有问题,不妨对照上面的连接图,然后分别用tcpdump监听compute node的tap、qbr、qvb、qvo、br-int、br-tun、br-tun所对应的eth(如果用gre模式监听要加参数proto gre才能解gre包),再到network node的eth、br-tun、br-int、再到namespace的网关接口qr-xxx。

如果发现br-int有数据包,但br-tun没有数据包,不妨监听一下openflow的控制流:ovs-ofctl snoop br-int/br-tun,看看控制器的响应如

主动查询ovs的信息

openvswitch的flow信息获取途径现在通常是通过命令行CLI的方式(ovs-ofctl),或是部署agent,如neutron-ovs-agent,但通常这两种方法都需要在ovs所在的节点上部署,比较麻烦。

还有一种办法是网络controller通过openflow协议获取ovs的信息(如流信息),不过其负载就加重了。那么能否模拟网络controller的功能去获取流信息呢?当然可以,不过如果将ovs的控制器设置为主动模式连接我们的软件的话,那么这个软件就需要实现controller的功能,并且要与其他controller协作,更加麻烦。

其实openvswitch可以以服务端的形态存在,我们的软件可以连接ovs,从而获取其信息。要点在于

  1. 设置ovs为被动模式
  2. ovs-vsctl set-controller br-tun tcp:30.0.0.1 ptcp:6601
    这样ovs既可以主动连接网络controller(30.0.0.1:6633),又可以监听6601端口,接受客户端请求。此处br-tun与6601是一对,如果获取其他网桥的信息,则需要监听新的端口

  3. 设置ovs-ofctl为远程模式
  4. 在controller和node1两台机器上分别运行下面的命令:

    nsfocus@controller:/$ ovs-ofctl dump-flows tcp:127.0.0.1:6601
    NXST_FLOW reply (xid=0x4):
     cookie=0x20000000000000, duration=25911.957s, table=0, n_packets=25868, n_bytes=2535064, idle_timeout=5, idle_age=2, priority=0,icmp,in_port=4,vlan_tci=0x0000,dl_src=fa:16:3e:aa:30:94,dl_dst=fa:16:3e:be:25:7b,nw_src=100.0.0.19,nw_dst=192.168.1.1,icmp_type=8,icmp_code=0 actions=output:6
     cookie=0x20000000000000, duration=25911.953s, table=0, n_packets=25866, n_bytes=2534868, idle_timeout=5, idle_age=0, priority=0,icmp,in_port=6,vlan_tci=0x0000,dl_src=fa:16:3e:be:25:7b,dl_dst=fa:16:3e:aa:30:94,nw_src=192.168.1.1,nw_dst=100.0.0.19,icmp_type=0,icmp_code=0 actions=output:4
    
    nsfocus@node1:/$  ovs-ofctl dump-flows tcp:192.168.19.1:6601
    NXST_FLOW reply (xid=0x4):
     cookie=0x20000000000000, duration=25943.909s, table=0, n_packets=25900, n_bytes=2538200, idle_timeout=5, idle_age=2, priority=0,icmp,in_port=4,vlan_tci=0x0000,dl_src=fa:16:3e:aa:30:94,dl_dst=fa:16:3e:be:25:7b,nw_src=100.0.0.19,nw_dst=192.168.1.1,icmp_type=8,icmp_code=0 actions=output:6
     cookie=0x20000000000000, duration=25943.905s, table=0, n_packets=25898, n_bytes=2538004, idle_timeout=5, idle_age=0, priority=0,icmp,in_port=6,vlan_tci=0x0000,dl_src=fa:16:3e:be:25:7b,dl_dst=fa:16:3e:aa:30:94,nw_src=192.168.1.1,nw_dst=100.0.0.19,icmp_type=0,icmp_code=0 actions=output:4
    

    可见从不同主机都可以访问controller节点的br-tun桥,信息相同。

虽然ovs-ofctl不是自动化的方法,当时可以使用管道等技术实现程序化的办法。

在此感谢@Outlook的提示

用REST获得openvswitch ovsdb的信息

客户端可以通过ovsdb定义的协议访问openvswitch的数据库,协议在http://tools.ietf.org/html/draft-pfaff-ovsdb-proto-02,看来要成为ietf的标准了?怎么查询这些数据其实有一个样例,但是比较简单,我这里略作扩展,说明如何查询ovs的网桥、所连controller和流信息。

    1. 准备工作
    2. 因为ovs需要认证(公钥)才能访问其数据,我们为了简化直接在ovs所在节点上运行以下命令:

      ovs-appctl -t ovsdb-server ovsdb-server/add-remote ptcp:6632

      然后可以直接通过tcp的方式访问ovsdb了

    3. echo发送存活信息
    4. 客户端可以使用tcp方式与服务器保持长连接,所以可能定时需要发送echo信息与服务器确认存活。可编写以下脚本:


      import socket
      import json

      OVSDB_IP = '127.0.0.1'
      OVSDB_PORT = 6632
      BUFSIZE = 409600

      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      s.connect((OVSDB_IP, OVSDB_PORT))

      query = {"method":"echo", "params":[], "id": 0}
      s.send(json.dumps(query))
      response = s.recv(BUFSIZE)
      print response

      执行结果为:
      {“id”:0,”result”:[],”error”:null}
      这是最简单的获取信息方式了,我们接下来要看看OVSDB中到底有些什么数据

    5. 获得所有数据库名
    6. 脚本中其他不变,最后三行换为以下内容,以后步骤也是类似:

      query = {"method":"list_dbs", "params":[], "id": 0}
      s.send(json.dumps(query))
      response = s.recv(BUFSIZE)
      print response

      执行后结果为
      {“id”:0,”result”:[“Open_vSwitch”],”error”:null}
      可见现在OVSDB中只有一个数据库Open_vSwitch。接下来我们查询这个数据库有哪些表

    7. 获得数据库的所有表结构
    8. 同样,代码为

      query = {"method":"get_schema", "params":["Open_vSwitch"], "id": 0}
      s.send(json.dumps(query))
      response = s.recv(BUFSIZE)
      print response

      因为表结构字段较多,所以我们将结果格式化,执行./ovs.py |python -m json.tool
      返回的结果将是所有数据库的内容

      {
          "error": null, 
          "id": 0, 
          "result": {
              "cksum": "2180939265 17455", 
              "name": "Open_vSwitch", 
              "tables": {
                  "Bridge": {
                      "columns": {
                          "controller": {
                              "type": {
                                  "key": {
                                      "refTable": "Controller", 
                                      "type": "uuid"
                                  },   
                                  "max": "unlimited", 
                                  "min": 0
                              }    
                          },   
                          "datapath_id": {
                              "ephemeral": true, 
                              "type": {
                                  "key": "string", 
                                  "min": 0
                              }    
                          },   
                          "datapath_type": {
                              "type": "string"
                          },   
                          "external_ids": {
                              "type": {
                                  "key": "string", 
                                  "max": "unlimited", 
                                  "min": 0, 
                                  "value": "string"
                              }    
                          },   
                          "fail_mode": {
      .......................................

      后面还有很多内容,在这里就不显示了。数据库除了Bridge,还有Controller、Flow Table、Interface、Manager、Mirror、Netflow、Open vswitch、Port、QoS、Queue、SSL、sFlow。看来OVS默认的流Flow表应该是openflow,其他还支持netflow和sflow。

    9. 获得所有网桥
    10. 我们查看一下网桥信息

      query = {"method":"transact", "params":["Open_vSwitch", {"op":"select", "table": "Bridge", "where":[]}], "id": 0}
      s.send(json.dumps(query))
      response = s.recv(BUFSIZE)
      print response

      执行./ovs.py |python -m json.tool
      返回的结果将是所有数据库的内容

      {
          "error": null, 
          "id": 0,  
          "result": [
              {   
                  "rows": [
                      {   
                          "_uuid": [
                              "uuid", 
                              "fd924881-6a15-4a1b-a803-aa49efa38179"
                          ],  
                          "_version": [
                              "uuid", 
                              "5306417b-381e-49dd-9ac2-0d95c450919c"
                          ],  
                          "controller": [
                              "set", 
                              []  
                          ],  
                          "datapath_id": "0000e0db551f99b4", 
                          "datapath_type": "", 
                          "external_ids": [
                              "map", 
                              []  
                          ],  
                          "fail_mode": [
                              "set", 
                              []  
                          ],  
                          "flood_vlans": [
                              "set", 
                              []  
                          ],  
                          "flow_tables": [
                              "map", 
                              []  
                          ],  
                          "mirrors": [
       "set",
                              []
                          ],
                          "name": "br-ex",
                          "netflow": [
                              "set",
                              []
                          ],
                          "other_config": [
                              "map",
                              []
                          ],
                          "ports": [
                              "set",
                              [
                                  [
                                      "uuid",
                                      "28b44f02-7ecd-4135-9836-f1059ac1ec10"
                                  ],
                                  [
                                      "uuid",
                                      "596e6d23-ec51-4b56-bddc-5aa0805fe16c"
                                  ],
                                  [
                                      "uuid",
                                      "82cd970c-ac39-46e6-8aa7-b03529cc8916"
                                  ],
                                  [
                                      "uuid",
                                      "be77f10f-1a52-4be2-8b9e-c97b26c9acee"
                                  ],
                                  [
                                      "uuid",
                                      "f8ae4f3c-9d77-4f7e-8707-ee2e36c70e08"
                                  ]
                              ]
                          ],
                          "protocols": [  "set",
                              []
                          ],
                          "sflow": [
                              "set",
                              []
                          ],
                          "status": [
                              "map",
                              []
                          ],
                          "stp_enable": false
                      },
      {
                          "_uuid": [
                              "uuid",
                              "e29eac0c-a9ae-4108-a277-21388a24a2f1"
                          ],
                          "_version": [
                              "uuid",
                              "65e1bd22-6178-4b01-827f-8d64b0840e72"
                          ],
                          "controller": [
                              "uuid",
                              "15846c69-f014-4a5a-bc8d-3cb67cc3cb03"
                          ],
                          "datapath_id": "00000eac9ee20841",
                          "datapath_type": "",
                          "external_ids": [
                              "map",
                              []
                          ],
                          "fail_mode": [
                              "set",
                              []
                          ],
                          "flood_vlans": [
                              "set",
                              []
                          ],
                          "flow_tables": [
                              "map",
                              []
                          ],
                          "mirrors": [
                              "set",
                              []
                          ],
                          "name": "br-tun",
                          "netflow": [
                              "set",[]
       ],
                          "other_config": [
                              "map",
                              []
                          ],
                          "ports": [
                              "set",
                              [
                                  [
                                      "uuid",
                                      "aff50719-bd85-4b72-9283-656844b663aa"
                                  ],
                                  [
                                      "uuid",
                                      "bcd6e8f9-713f-4177-b730-caed455df9b7"
                                  ],
                                  [
                                      "uuid",
                                      "d8e93efc-7ebd-4d89-b7d2-b9b4b60c993a"
                                  ]
                              ]
                          ],
                          "protocols": [
                              "set",
                              []
                          ],
                          "sflow": [
                              "set",
                              []
                          ],
                          "status": [
                              "map",
                              []
                          ],
                          "stp_enable": false
                      },
      .......................................

      篇幅关系,这里展现了两个网桥br-ex和br-tun,其中port字段可以查询网桥上连接的端口,如果controller字段存在,说明该网桥受控制器控制。那我们再看一下其所连控制器的信息。

    11. 获得所有网桥
    12. 我们查看一下网桥信息

      query = {"method":"transact", "params":["Open_vSwitch", {"op":"select", "table": "Controller", "where":[]}], "id": 0}
      s.send(json.dumps(query))
      response = s.recv(BUFSIZE)
      print response

      执行./ovs.py |python -m json.tool
      返回的结果

      {
          "error": null,
          "id": 0,
          "result": [
              {
                  "rows": [
                      {
                          "_uuid": [
                              "uuid",
                              "15846c69-f014-4a5a-bc8d-3cb67cc3cb03"
                          ],
                          "_version": [
                              "uuid",
                              "3a6bf4ed-035b-4e57-beb3-8bd08997826e"
                          ],
                          "connection_mode": [
                              "set",
                              []
                          ],
                          "controller_burst_limit": [
                              "set",
                              []
                          ],
                      ....
       "role": "master",
                          "status": [
                              "map",
                              [
                                  [
                                      "last_error",
                                      "Connection refused"
                                  ],
                                  [
                                      "sec_since_connect",
                                      "423018"
                                  ],
                                  [
                                      "sec_since_disconnect",
                                      "423089"
                                  ],
                                  [
                                      "state",
                                      "ACTIVE"
                                  ]
                              ]
                          ],
                          "target": "tcp:30.0.0.1"
                      },
                      {
                          "_uuid": [
                              "uuid",
                              "77fab68b-c693-4c9e-aea7-ba847809376d"
                          ],
                          "_version": [
                              "uuid",
                              "5e1f25ec-ae35-43fe-9cf4-835ef5980056"
                          ],
                          "connection_mode": [
                              "set",
                              []
                          ],
                       .....
      "role": "master",
                          "status": [
                              "map",
                              [
                                  [
                                      "last_error",
                                      "Connection refused"
                                  ],
                                  [
                                      "sec_since_connect",
                                      "423018"
                                  ],
                                  [
                                      "sec_since_disconnect",
                                      "423089"
                                  ],
                                  [
                                      "state",
                                      "ACTIVE"
                                  ]
                              ]
                          ],
                          "target": "tcp:30.0.0.1"
                      }
                  ]
              }
          ]
      }
      
      这里列出了两个controller项,但目标地址都是30.0.0.1,说明都是同一个controller,但是这两项的uuid不同,如15846c69-f014-4a5a-bc8d-3cb67cc3cb03是br-tun设置的controller,而77fab68b-c693-4c9e-aea7-ba847809376d是br-in设置的controller。
    13. 获得所有openflow信息
    14. 代码为

      query = {"method":"transact", "params":["Open_vSwitch", {"op":"select", "table": "Flow_Table", "where":[]}], "id": 0}
      s.send(json.dumps(query))
      response = s.recv(BUFSIZE)
      print response

      执行./ovs.py |python -m json.tool
      返回的结果result为空,而且静态动态流都为空,开始很疑惑,后来了解ovsdb主要是存放静态信息,所以流信息是没有保留的。获取流应该从vswitchd中获取,具体怎么做看下一篇吧:-)
      ovsdb和vswitchd的关系如该图所示,还有一些ovs的命令。
      ovs关系图

最后说明一下,用REST访问ovsdb可以使用程序化的方法获得ovs的信息,为下一步工作打下基础

Floodlight路由机制解析

路由部分是floodlight最核心的机制,这两天仔细读了一下floodlight这部分的代码,总算有了大体上的了解,与各位分享。

本文中的floodlight(FL)与控制器/网络控制器(NC, nework controller ) 等术语等同,交换机(SW)默认为openflow-enabled switch,不再赘述。

首先谈一下SDN控制器的路由原理:当交换机收到一个不能被当前流表各条流匹配的数据包时,会把这个数据包以openflow的格式(PACKET_IN)发送给控制器。控制器经过路由决策后,同样以openflow的格式(PACKET_OUT)的方式将该数据包的下一跳信息回给该交换机。

如果学过最短路径计算的同学一定知道,两点之间的最短路径计算首先要知道任意两点之间是否相连,如果两者间有link相连还需知道其长度。所以总体而言,FL路由包括两部分:拓扑更新和路由计算,前者是定时事先完成的,形成一个全局的拓扑结构,后者是收到数据包后运行时完成的,根据拓扑生成路由策略。

首先看拓扑更新。SDN环境中拓扑是集中控制的,故FL需要了解全局的拓扑环境,所以FL在链路更新时自动更新拓扑。

    1 链路更新

当交换机加入SDN环境后,控制器通过LLDP协议定时地获得该交换机与其他设备连接的link信息,然后添加或更新这些link(LinkDiscoveryManager.addOrUpdateLink()),最后将这些link更新的事件添加到updates队列中(LinkDiscoveryManager.handleLldp())。

    2 拓扑更新

计算路由的关键在于路径更新和路由计算,该过程为:
2.1 启动 首先拓扑管理启动(TopologyManager.startUp())时新起一个线程UpdateTopologyWorker,间隔500ms重复运行 。
2.2 更新拓扑 如果之前在第一步中有一些链路、交换机等更新,那么LDUpdates队列中会有元素,那么依次取出这些链路更新的元素(TopologyManager.updateTopology()/TopologyManager.applyUpdates()),判断其类型(如链路更新/删除、端口增加/删除),进行响应处理。
以链路更新为例,调用TopologyManager.addOrUpdateLink()方法,判断链路类型(多跳、单跳或隧道),再把link添加到相应交换机的端口中,如果是多跳的则再删除可能的单跳link
2.3 计算拓扑路径 每次新建一个实例(TopologyManager.createNewInstance()),初始化过程中计算拓扑路径(TopologyInstance.compute()),具体地:
2.3.1 归类 将现有所有相连的link置于同一个簇中,从而形成多个簇结构(identifyOpenflowDomains())
2.3.2 建簇 遍历所有link(addLinksToOpenflowDomains()),如果link连接的是同一个簇,则将其加入该簇,所以该方法统计了所有簇内的link。FL这么做也是减少路由计算的开销,不然若干个大二层网络中共有n个节点,就需要n平方的存储开销,计算效率也会下降,如果将不相连的节点分开就减少了上述开销。
2.3.3 遍历所有簇 对于每个簇内sw,形成一个BroadcastTree结构(包括两个属性:cost记录到其他簇内sw的路径长度,links记录其他sw的link),最终将信息保存到destinationRootedTrees中(caculateShortestPathTreeInClusters ()和calculateBroadcastNodePortsInClusters()),核心思想就是使用dijkstra计算簇内任意两点的最短路径,保存到该点的cost和下一跳。
2.4 定义路径计算模式 这样,FL就获得了从一个点出发所有相连的所有路径和点。那么只要给定一个源SW节点和一个目的SW节点,那就能知道这两者间的最短路径长度和。早在2.3开始TopologyInstance在定期新建实例时,就定义了根据拓扑计算路径的方法:

 
pathcache = CacheBuilder.newBuilder().concurrencyLevel(4)
                    .maximumSize(1000L)
                    .build(
                            new CacheLoader() {
                                public Route load(RouteId rid) {
                                    return pathCacheLoader.load(rid);
                                }
                            });

这里的pathcache是一个类似hash表的结构,每当被调用get时执行pathCacheLoader.load(rid),所以这里没有真正计算路由路径,只是一个注册回调。具体运行时在后面3.6中的getRoute方法中被调用:

 
            result = pathcache.get(id);

在TopologyInstance.buildRoute方法实现该路由rid的计算:先确定目的sw,因为2.3.2中以获得源目的sw的最短路径,然后根据nexthoplinks迭代查找该路径上的所有sw,最终形成一个path。
虽然2.4中没有最终计算节点间的路径,但是2.3中使用dijkstra计算了任意两点间的距离,基本上已经完成了90%的路由计算功能,在大二层网络中这也是不小的开销。

    3 路由计算

当完成拓扑计算后,FL在运行时可计算输入的数据包应走的路由。这里还需说明一下,FL的某些模块监听PACKET_IN数据包,Controller收到这个数据包后,会逐个通知这些模块,调用其receive方法:

 
                    for (IOFMessageListener listener : listeners) {
                        pktinProcTime.recordStartTimeComp(listener);
                        cmd = listener.receive(sw, m, bc);
                        pktinProcTime.recordEndTimeComp(listener);

                        if (Command.STOP.equals(cmd)) {
                            break;
                        }
                    }

更详细的模块加载和监听机制可参考我之前写的这篇文章http://blog.marvelplanet.tk/?p=424
那么回到路由计算这部分来,Controller会依次调用以下模块:
3.1 LinkDiscoveryManager链路处理模块,如果数据包是LLDP处理该消息,如步骤1中在此处会有处理;如果是正常的数据包则略过,所以这里忽略
3.2 TopologyManager拓扑管理模块,查看源端口在TopologyInstance是否允许转发
3.3 DeviceManager设备模块,通过源目SW的id和port相连的device找到源设备和目的设备,如找不到则放弃,否则暂存入一个上下文结构context
3.4 Firewall防火墙模块,判断该数据包的源目的主机是否可以通信,如不在规则表中则继续
3.5 LoadBalancer负载均衡模块,如是ARP数据包则查看是否在vipIPToId中,如不在则继续。这里关系不大,忽略。
3.6 ForwardBase路由模块,首先在上下文context查找之前模块是否已有路由策略,如无则检查是否数据包是广播的,如是则Flood。否则调用doForwardFlow,我们主要看这个方法,具体的,从上下文中获取3.3中解析的源目的主机信息,如果不在同一个iland上(我的理解是这两台主机在FL的知识库中没有路径,也许中间有别的传统方法相连,也许根本不相连),则Flood;如果在同一个端口,那就是同一台主机,则放弃;否则调用routingEngine.getRoute获得路由。这个才是最重要的,也是2.4中没有完成的最终部分,这里使用了TopologyInstance的getRoute方法,使用2.4的方法计算出路由路径,然后调用将路由所对应的路径推送给交换机,如果是多跳路径,则将路由策略推送给该路径上的所有交换机(ForwardBase.pushRoute())。

最后给出一个场景吧,假定h1和h2是主机,s1、s2和s3是交换机,拓扑是h1-s1-s2-s3-h2。当h1 ping h2,首先h1发送ARP包,由于该数据包是广播的,那么最终ForwardBase会执行doFlood,所有交换机都会广播这个数据包,最后h2收到s3广播的的ARP请求后,向h1发送ARP响应。h1收到ARP响应后发送ping请求,该包在交换机以PACKET_IN的形式发送给floodlight,此时floodlight知道h1所在的交换机s3,所以调用doForwardFlow,计算出
s1到s3的路径s1->s2,s2->s3,然后将OFFlowMod命令发送给s1、s2和s3。最终数据包通过s1、s2和s3直接被发送到h2,搞定。

p.s.1 Floodlight在计算拓扑calculateShortestPathTreeInClusters时,首先会将所有的路径置为失效,然后在计算所有两点间距离,如果每次(间隔为500ms)存在一条链路变动,那么就需要重做所有计算,在大二层网络中会不会出现性能瓶颈?
p.s.2 Floodlight的计算出路由后,下发的流只包括源目的主机的mac,流的其他字段都是通配符,这么做的逻辑应该是“我只做路由和转发功能,别的不用控制”。这样会给北向的APP造成很大的麻烦,例如在做DDoS的防御时,需要检查每条流的目的端口,如果流的dst_transport_addr是*的话,显然无法检测攻击,甚至连FL自带的Firewall都力不从心。
p.s. 一般而言,交换机的端口与与其他端口相连,有两个对称的link。但TopologyManager中有一类特殊的端口broadcastDomainPorts,这类是广播端口,不是一般的端口,没有对称的link。