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在bigswitch退出opendaylight后算是活过来了,新的版本在负载均衡中加入了一些机制,可以参见net.floodlight.loadbalancer

不过我关心的是FL对流量的控制,特别是判断packet_in速率的机制,这个在前几个月的Floodlight是没有的。

现在FL的处理机制是在收到packet_in数据包后,首先判断是否超过阈值,在inputThrottled方法中,计算每1000个数据包所花的时间,得出速率。如果速率超过packetInRateThresholdHigh阈值,则进入监控限制流量模式(enablePacketInThrottle)

在该模式下,依次检查packet_in数据包的src mac(频率为每50个packet_in一次)和in port(频率为每100个packet_in一次),如果近期(1s内)有该类数据包则将其禁止。

这种方法的初衷应该是防止controller的负载过高,例如禁止mac是为了防止host发送过多包,禁止in port是为了禁止sw转发过多包。但后者有可能出现在恶意vm发动DDoS攻击(一个in port,但无数src mac),客观上却使控制器免受DDoS攻击,自然也让域内的VM被DDoS。

贴一下主干代码:

 
/**
     * Determine if this message should be dropped.
     *
     * We compute the current rate by taking a timestamp every 100 messages.
     * Could change to a more complex scheme if more accuracy is needed.
     *
     * Enable throttling if the rate goes above packetInRateThresholdHigh
     * Disable throttling when the rate drops below packetInRateThresholdLow
     *
     * While throttling is enabled, we do the following:
     *  - Remove duplicate packetIn's mapped to the same OFMatch
     *  - After filtering, if packetIn rate per host (mac) is above
     *    packetInRatePerMacThreshold, push a flow mod to block mac on port
     *  - After filtering, if packetIn rate per port is above
     *    packetInRatePerPortThreshold, push a flow mod to block port
     *  - Allow blocking flow mods have a hard timeout and expires automatically
     *
     * TODO: keep a history of all events related in input throttling
     *
     * @param ofm
     * @return
     */
    @Override
    public boolean inputThrottled(OFMessage ofm) {
        if (ofm.getType() != OFType.PACKET_IN) {
            return false;
        }
        ctrSwitchPktin.updateCounterNoFlush();
        // Compute current packet in rate
        messageCount++;
        if (messageCount % 1000 == 0) {
            long now = System.currentTimeMillis();
            if (now != lastMessageTime) {
                currentRate = (int) (1000000 / (now - lastMessageTime));
                lastMessageTime = now;
            } else {
                currentRate = Integer.MAX_VALUE;
            }
        }
        if (!packetInThrottleEnabled) {
            if (currentRate <= packetInRateThresholdHigh) {
                return false; // most common case
            }
            enablePacketInThrottle();
        } else if (currentRate < packetInRateThresholdLow) {
            disablePacketInThrottle();
            return false;
        }

        // Now we are in the slow path where we need to do filtering
        // First filter based on OFMatch
        OFPacketIn pin = (OFPacketIn)ofm;
        OFMatch match = new OFMatch();
        match.loadFromPacket(pin.getPacketData(), pin.getInPort());
        if (ofMatchCache.update(match)) {
           ctrSwitchPktinDrops.updateCounterNoFlush();
            return true;
        }

        // We have packet in with a distinct flow, check per mac rate
        messageCountUniqueOFMatch++;
        if ((messageCountUniqueOFMatch % packetInRatePerMacThreshold) == 1) {
            checkPerSourceMacRate(pin);
        }

        // Check per port rate
        if ((messageCountUniqueOFMatch % packetInRatePerPortThreshold) == 1) {
            checkPerPortRate(pin);
        }
        return false;
    }

让我们计算一下这种机制的效率,由于阈值packetInRateThresholdHigh设置的是1000,那么now-lastMessageTime应为1000ms=1s;考虑到controller每1000个packet_in进行判断,也就是说入包的极限是每秒1000个packet_in数据包,即1000条新流。这个在小范围内是没问题的,但Nick McKeown等人在经典文章“OpenFlow: Enabling Innovation in Campus Networks”中提到Preliminary results suggested that an Ethane controller based on a low-cost desktop PC could process over 10,000 new flows per second — enough for a large college campus. 显然每秒1k条流的处理规模还是太小,那么单单增加阈值也不能解决问题:因为controller并不知道那些流是正常哪些是异常的,当前场景是小规模的还是大规模的,特别是在一些突然场景下。所以简单的流量控制在一定程度上是有效的,但专门的安全设备也是必需的。

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。

raspberry wlan0 ifconfig fail

树莓派的wifi网卡总是上不了网,搞了很久都找不到解决办法

提示错误是当运行ifconfig wlan0分配ip时出错:

[20014.112395] wlan0: deauthenticating from xxx by local choice (reason=3)

但是lsusb时发现设备还在,应该不是电源的问题,百思不得其解

google了很久终于发现当前居然有wpa的进程(我的wlan0是无密码的)
pi@raspberrypi ~ $ ps aux|grep wpa
root 2071 0.0 0.3 5952 860 ? Ss 23:47 0:00 /sbin/wpa_supplicant -s -B -P /var/run/wpa_supplicant.wlan0.pid -i wlan0 -W -D nl80211,wext -c /etc/wpa_supplicant/wpa_supplicant.conf
root 2153 0.0 0.1 2276 380 ? Ss 23:47 0:00 /sbin/wpa_cli -B -P /var/run/wpa_action.wlan0.pid -i wlan0 -p /var/run/wpa_supplicant -a /sbin/wpa_action
pi 2726 0.0 0.3 3544 868 pts/1 S+ 23:51 0:00 grep –color=auto wpa

所以解决办法就是杀wpa
pi@raspberrypi ~ $ sudo killall wpa_supplicant

发现重新连接ap就没问题了
[ 322.419859] wlan0: RX AssocResp from 4c:e6:76:a3:d5:82 (capab=0x401 status=0 aid=4)
[ 322.435926] wlan0: associated

谨慎怀疑是因为 /etc/network/interface中出现的一段wpa的问题:

allow-hotplug wlan0
iface wlan0 inet manual
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
那么解决办法就是将其注释掉即可。。。

iface default inet dhcp

ubuntu 12.04上安装puppet

发现官方的说明问题很多,不过全做一个参考吧

puppet的agent端安装没有问题,但是master涉及到postgresql数据库的配置,简单运行puppet-enterprise-installer会出错

我在安装目录的answers目录下发现很多配置文件,其中full_suite_existing_postgres.sample最符合master的需求,于是将其复制到安装的根目录,修改其中的一些配置项,主要是将其中数据库的host设置为master的域名,对应的数据库设置用户名密码。

然后配置postgreSQL数据库,可以参考这篇文章,设置好postgres密码后,进入
nsfocus@controller:~$ psql -U postgres -W -h 192.168.19.1
Password for user postgres:
psql (9.1.9)
SSL connection (cipher: DHE-RSA-AES256-SHA, bits: 256)
Type "help" for help.

postgres=# CREATE USER "pe-puppetdb" PASSWORD 'onemorestrongpass';
CREATE ROLE
postgres=# CREATE DATABASE "pe-puppetdb" OWNER "pe-puppetdb" ENCODING 'utf8' LC_CTYPE 'en_US.utf8' LC_COLLATE 'en_US.utf8' template template0;
CREATE DATABASE
postgres=# CREATE DATABASE "console_auth" OWNER "auth_user" ENCODING 'utf8' LC_CTYPE 'en_US.utf8' LC_COLLATE 'en_US.utf8' template template0;
CREATE DATABASE

然后运行./puppet-enterprise-installer -a full_suite_existing_postgres.sample

如果还有错误,可以查看当前目录下的install_log.lastrun.[hostname]文件,看看最后FATAL的信息

General Design Issues of the Internet

刚刚看到RFC1958上面关于Internet设计的一些原则,觉得对现在的系统设计都很有启发,摘录如下:

3.1 Heterogeneity is inevitable and must be supported by design.
Multiple types of hardware must be allowed for, e.g. transmission
speeds differing by at least 7 orders of magnitude, various computer
word lengths, and hosts ranging from memory-starved microprocessors
up to massively parallel supercomputers. Multiple types of
application protocol must be allowed for, ranging from the simplest
such as remote login up to the most complex such as distributed
databases.

3.2 If there are several ways of doing the same thing, choose one.
If a previous design, in the Internet context or elsewhere, has
successfully solved the same problem, choose the same solution unless
there is a good technical reason not to. Duplication of the same
protocol functionality should be avoided as far as possible, without
of course using this argument to reject improvements.

3.3 All designs must scale readily to very many nodes per site and to
many millions of sites.

3.4 Performance and cost must be considered as well as functionality.

3.5 Keep it simple. When in doubt during design, choose the simplest
solution.

3.6 Modularity is good. If you can keep things separate, do so.

3.7 In many cases it is better to adopt an almost complete solution
now, rather than to wait until a perfect solution can be found.

3.8 Avoid options and parameters whenever possible. Any options and
parameters should be configured or negotiated dynamically rather than
manually.

3.9 Be strict when sending and tolerant when receiving.
Implementations must follow specifications precisely when sending to
the network, and tolerate faulty input from the network. When in
doubt, discard faulty input silently, without returning an error
message unless this is required by the specification.

3.10 Be parsimonious with unsolicited packets, especially multicasts
and broadcasts.

3.11 Circular dependencies must be avoided.

For example, routing must not depend on look-ups in the Domain
Name System (DNS), since the updating of DNS servers depends on
successful routing.

3.12 Objects should be self decribing (include type and size), within
reasonable limits. Only type codes and other magic numbers assigned
by the Internet Assigned Numbers Authority (IANA) may be used.

3.13 All specifications should use the same terminology and notation,
and the same bit- and byte-order convention.

3.14 And perhaps most important: Nothing gets standardised until
there are multiple instances of running code.

nova实例僵死的解决办法

openstack grizzly的nova模块还是有时候不稳定,异常时可能会出现一些实例僵死在soft reboot和hard reboot中,就算过几天还是这个状态,在horizon界面中唯一的办法是terminate这些实例,但是如果有数据怎么办?

这个问题还不能直接从命令行搞定:
# nova reboot –hard 366a2e92-9483-4074-984d-7ec0de130c14
ERROR: Cannot ‘reboot’ while instance is in task_state rebooting_hard (HTTP 409) (Request-ID: req-1bf39445-5bb5-49d5-8dd6-dcf12aeceae1)

解决办法不是没有,下面是我搜索到的:

1 代码入手,分析问题,然后提交patch到mainstream中,例如

但是我发现我的nova版本是最新的,没有解决我的问题

2 手动重置,如

# nova reset-state –active 366a2e92-9483-4074-984d-7ec0de130c14
# nova reboot 366a2e92-9483-4074-984d-7ec0de130c14

如此即可重启实例

在此吐槽一下nova不够稳定、horizon的功能太少,同时期待更多人和组织贡献代码进来