0x00 OpenFlow组表
功能
实现更高级的数据包转发特性,如:组播、负载均衡、容灾备份和聚合
组表项
- 组表号
groupId
:标识组表,用于组表区分 组表类型
type
:与流表转发类型相似,表示对应的动作(action
):Indirect
、All
、Select
、Indirect
、Fast failover
Inidirct
:执行该group
中一个已定义的bucket
, 该组仅支持一个bucket
。 允许多个流表项或组表项指向一个公共的组(例如IP
转发的下一跳)。 这是最简单的group
类型,交换机通常比较支持这种类型的group
。All
:执行该group
中所有的bucket
。这种类型的group
用来进行multicast
和broadcast
。为每个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
11data = {
"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
27params = {"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
中信息
修改数据目的信息
通过组表的动作桶修改数据包目的
IP
和MAC
,使得转发后目的主机能够接收响应1
2
3instruction = [{"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
的组播段IP
与GROUPS
中组播段匹配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
)也收到视频流,效果实现