ONOS group multicast in SDN

0x00 OpenFlow组表

功能

实现更高级的数据包转发特性,如:组播、负载均衡、容灾备份和聚合

组表项

  • 组表号groupId:标识组表,用于组表区分
  • 组表类型type:与流表转发类型相似,表示对应的动作(action):IndirectAllSelectIndirectFast failover

    • Inidirct:执行该group中一个已定义的bucket, 该组仅支持一个bucket。 允许多个流表项或组表项指向一个公共的组(例如IP转发的下一跳)。 这是最简单的group类型,交换机通常比较支持这种类型的group

    • All:执行该group中所有的bucket。这种类型的group用来进行multicastbroadcast。为每个bucket克隆一份数据包,然后分别执行每个bucket中的actions

    • Select:执行该group中的一个bucket。基于一种选择算法(用户定义的哈希算法或者轮询算法)选择group中的一个bucket对数据包执行actions。这种选择算法应该尽量支持负载均衡并且为每个bucket提供一个权重用于分配。当一个bucket指定的端口down掉,交换机应该将选择限制在剩下的正常的bucket中而不是丢掉,这是为了减少链路中断。
    • Fast failover:执行第一个活动的bucket。 每个action bucket都与控制其活动性的特定端口和/或组相关联。 按照group定义的顺序评估bucket,并选择与活动端口/组关联的第一个bucket。 这个group类型使交换机可以更改转发行为而无需往返于控制器。 如果没有bucket,则丢弃数据包。
  • 计数器counters:记录处理过的报文数
  • 动作桶action buckets:动作桶(action bucket)列表,每个动作桶包含相关动作和参数

0x01 实验拓扑

目的

组播组外的主机向任一组播主机发送的数据包,能够被组播组所有主机接收。

组成

  • h0为非组播主机,负责发送数据包到组播段。
  • h1 h2 h3为组播段主机,到任一主机的数据包会被复制到其他主机。
  • s7为组播交换机,负责连接组播段和网络拓扑。

效果

0x02 脚本编写

处理拓扑信息

  • 找到重复挂载交换机

    1
    2
    3
    4
    # get device
    for i in resp_port["hosts"]:
    if i["ipAddresses"][0] == host_ip:
    group_deviceId = i["locations"][0]["elementId"]
  • 找到组播交换机对应端口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # get ports
    for i in resp_port["hosts"]:
    if i["locations"][0]["elementId"] == group_deviceId:
    # get multicast host
    group_ports.append(i["locations"][0]["port"])
    else:
    # get link port
    link_port = i["locations"][0]["port"]
    return group_deviceId, group_ports, link_port

流表组表内容

  • 组表项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    data = {
    "type": "ALL",
    "appCookie": "0x1234abcd",
    "groupId": 1,
    "buckets": []
    }
    for i in range(len(host_ports)):
    instruction = [{"type": "OUTPUT"}]
    instruction[0]["port"]= str(host_ports[i])
    treatment = {"instructions": instruction}
    data["buckets"].append({"weight": 1,"treatment": treatment})
  • 流表项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    params = {"appId": "myApp"}
    data = {
    "priority": 10,
    "timeout": 0,
    "isPermanent": True,
    "deviceId": deviceId,
    "treatment": {
    "instructions": [
    {
    "type": "GROUP",
    "groupId": 1
    }
    ]
    },
    "selector": {
    "criteria": [
    {
    "type": "ETH_TYPE",
    "ethType": "0x800"
    },
    {
    "type": "IN_PORT",
    "port": link_port
    }
    ]
    }
    }

0x03 存在问题

和组播实现有一定距离

  • 只是简单完成了数据包的复制转发,没有落实组播段的真实意义。
  • 组播的目的仍为单播形式,目的地为单个主机ip

拓扑过于理想

  • 所有组播主机连在同一个交换机上,端口处理固定化。
  • 组播交换机上没有非组播主机。

0x04 解决方案

建立实际拓扑

  • 将组播主机分布在网络拓扑的各个分支。
  • 组播主机需和至少一个非组播主机位于同一个分支。

基于图生成组播树

  • dijkstra算法选路的基础上,寻找组播树的上确界。

修改组表

  • 将转发命令精确到每个组播主机。

0x05 方案实践

添加组播组

  • 将组播组按照规定格式存入GROUPS

    1
    GROUPS = [{"deviceId":"","port":"","vlan":"192.168.0.1","ip":["10.0.1.1","10.0.1.2"],"HostPort":[],"mac": {}}]
  • 在获取拓扑过程中完善GROUPS中信息

修改数据目的信息

  • 通过组表的动作桶修改数据包目的IPMAC,使得转发后目的主机能够接收响应

    1
    2
    3
    instruction = [{"type": "L2MODIFICATION","subtype": "ETH_DST","mac": GROUPS[groupId]["mac"][GROUPS[groupId]["ip"][i]]},
    {"type": "L3MODIFICATION","subtype":"IPV4_DST","ip":GROUPS[groupId]["ip"][i]},
    {"type": "OUTPUT","port":GROUPS[groupId]["HostPort"][i]},]

设置vlan

  • ping的组播段IPGROUPS中组播段匹配

    1
    vlanIp = "192.168.0.1"
  • vlan默认挂载在组播组第一个主机直连的SDN交换机端口,组播的信息需要经过该端口进行复制转发

    1
    2
    3
    # Source to vlan
    dstPort = GROUPS[groupId]["deviceId"]+" "+GROUPS[groupId]["port"]
    path = sn.get_path(srcIp, dstPort)

0x06 改进效果展示

ping组播段内主机

  • 得到重复回复

  • 说明10.0.1.2能够接收10.0.0.1发送给10.0.1.1的报文

基于VLC实现视频流转发

  • 源(10.0.0.1)发送视频流至目的(10.0.1.1)

  • 组播段其余主机(10.0.1.2)也收到视频流,效果实现