cabeggar

Software Defined Network in Data Center III - SDN in two-linear topology

When we are implementing a fat-tree topology datacenter, we cannot use a simple learning switch to connect the network since simple switch does flooding for unknown destination thus in fat-tree there will be packets stuck in the loop. So we need to use openflow to program the controller to controll switching.

In this article, we first implement a simple example where a controller controlls packet switching in a 2-linear network like the figure below.

linear 2 network topology

In this topology, when we do “h1 ping h2”, h1 first send ARP packet to s1 and s1 ask controller to get flow entries if it’s a simple learning switch and that’s not what we want. This time, we try to make it a dumb switch and when controller senses ARP message, it installs flow entry to s1 and s2 immediately so that there won’t be any flooding.

Psuedo Code:

1
2
3
4
5
6
7
PacketIn:
1. If packet is ARP request:
2. install flow entries in both switches
2. send ARP response message
3. Else:
4. If ICMP Ping: Error
5. Else: Do nothing

Again we use mininet to simulate the network and use ryu to implement openflow controller.

First part is to read header of packet to examine whether it’s a ARP request. If so, we first add flow entries to switches then we send ARP response back.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
pkt = packet.Packet(msg.data)
eth = pkt.get_protocol(ethernet.ethernet)
# self.logger.info("packet in type: %s iptype: %s arptype: %s", eth.ethertype, ether_types.ETH_TYPE_IP, ether_types.ETH_TYPE_ARP)
if eth.ethertype == ether_types.ETH_TYPE_ARP:
for datapath in self.datapaths:
self.add_flow(datapath)
datapath = msg.datapath
if eth.ethertype == ether_types.ETH_TYPE_ARP:
self._handle_arp(datapath, msg.in_port, eth, pkt.get_protocol(arp.arp))
return
if eth.ethertype != ether_types.ETH_TYPE_LLDP:
self.logger.info("Unexpected packets received!")

In add_flow function, we use the dumb way to add 4 flow entries into 2 switches separately. Note that we need to implement a L3 layer switch for fat-tree data center. Thus we need to contain IP messages inside flow entries, not hardware addresss.

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def add_flow(self, datapath):
ofproto = datapath.ofproto
if datapath.id == 1:
actions = [datapath.ofproto_parser.OFPActionOutput(2)]
match = datapath.ofproto_parser.OFPMatch(
in_port=1, dl_type=0x0800, nw_src="10.0.0.1", nw_dst="10.0.0.2")
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath, match=match, cookie=0,
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
priority=ofproto.OFP_DEFAULT_PRIORITY,
flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
datapath.send_msg(mod)
actions = [datapath.ofproto_parser.OFPActionOutput(1)]
match = datapath.ofproto_parser.OFPMatch(
in_port=2, dl_type=0x0800, nw_src="10.0.0.2", nw_dst="10.0.0.1")
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath, match=match, cookie=0,
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
priority=ofproto.OFP_DEFAULT_PRIORITY,
flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
datapath.send_msg(mod)
self.logger.info(str(datapath.id) + " " + str(datapath) + " flow entry added!")
if datapath.id == 2:
actions = [datapath.ofproto_parser.OFPActionOutput(2)]
match = datapath.ofproto_parser.OFPMatch(
in_port=1, dl_type=0x0800, nw_src="10.0.0.2", nw_dst="10.0.0.1")
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath, match=match, cookie=0,
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
priority=ofproto.OFP_DEFAULT_PRIORITY,
flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
datapath.send_msg(mod)
actions = [datapath.ofproto_parser.OFPActionOutput(1)]
match = datapath.ofproto_parser.OFPMatch(
in_port=2, dl_type=0x0800, nw_src="10.0.0.1", nw_dst="10.0.0.2")
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath, match=match, cookie=0,
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
priority=ofproto.OFP_DEFAULT_PRIORITY,
flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
datapath.send_msg(mod)
self.logger.info(str(datapath.id) + " " + str(datapath) + " flow entry added!")

While sending ARP response, we need to compose an ARP packet by ourselves and write header and content of the packet properly.

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
28
29
30
def _handle_arp(self, datapath, port, pkt_ethernet, pkt_arp):
if pkt_arp.opcode != arp.ARP_REQUEST:
return
reply_src_hwaddr = "00:00:00:00:00:01" if pkt_arp.dst_ip == "10.0.0.1" else "00:00:00:00:00:02"
pkt = packet.Packet()
pkt.add_protocol(ethernet.ethernet(ethertype=pkt_ethernet.ethertype,
dst=pkt_ethernet.src,
src=reply_src_hwaddr))
pkt.add_protocol(arp.arp(opcode=arp.ARP_REPLY,
src_mac=reply_src_hwaddr,
src_ip=pkt_arp.dst_ip,
dst_mac=pkt_arp.src_mac,
dst_ip=pkt_arp.src_ip))
self._send_packet(datapath, port, pkt)
def _send_packet(self, datapath, port, pkt):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
pkt.serialize()
self.logger.info("packet-out %s" % (pkt,))
data = pkt.data
actions = [parser.OFPActionOutput(port=port)]
out = parser.OFPPacketOut(datapath=datapath,
buffer_id=ofproto.OFP_NO_BUFFER,
in_port=ofproto.OFPP_CONTROLLER,
actions=actions,
data=data)
datapath.send_msg(out)

The whole source code should look like this.

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_0
from ryu.lib.mac import haddr_to_bin
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet
from ryu.lib.packet import ether_types
from ryu.lib.packet import arp
from ryu.topology import event, switches
from ryu.topology.api import get_switch
class SimpleSwitch(app_manager.RyuApp):
OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]
def __init__(self, *args, **kwargs):
super(SimpleSwitch, self).__init__(*args, **kwargs)
self.mac_to_port = {}
self.ip_to_port = {"1":{"10.0.0.1":"1", "10.0.0.2":"2"}, "2":{"10.0.0.2":"1", "10.0.0.1":"2"}}
self.datapaths = []
self.topology_api_app = self
def add_flow(self, datapath):
ofproto = datapath.ofproto
if datapath.id == 1:
actions = [datapath.ofproto_parser.OFPActionOutput(2)]
match = datapath.ofproto_parser.OFPMatch(
in_port=1, dl_type=0x0800, nw_src="10.0.0.1", nw_dst="10.0.0.2")
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath, match=match, cookie=0,
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
priority=ofproto.OFP_DEFAULT_PRIORITY,
flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
datapath.send_msg(mod)
actions = [datapath.ofproto_parser.OFPActionOutput(1)]
match = datapath.ofproto_parser.OFPMatch(
in_port=2, dl_type=0x0800, nw_src="10.0.0.2", nw_dst="10.0.0.1")
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath, match=match, cookie=0,
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
priority=ofproto.OFP_DEFAULT_PRIORITY,
flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
datapath.send_msg(mod)
self.logger.info(str(datapath.id) + " " + str(datapath) + " flow entry added!")
if datapath.id == 2:
actions = [datapath.ofproto_parser.OFPActionOutput(2)]
match = datapath.ofproto_parser.OFPMatch(
in_port=1, dl_type=0x0800, nw_src="10.0.0.2", nw_dst="10.0.0.1")
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath, match=match, cookie=0,
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
priority=ofproto.OFP_DEFAULT_PRIORITY,
flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
datapath.send_msg(mod)
actions = [datapath.ofproto_parser.OFPActionOutput(1)]
match = datapath.ofproto_parser.OFPMatch(
in_port=2, dl_type=0x0800, nw_src="10.0.0.1", nw_dst="10.0.0.2")
mod = datapath.ofproto_parser.OFPFlowMod(
datapath=datapath, match=match, cookie=0,
command=ofproto.OFPFC_ADD, idle_timeout=0, hard_timeout=0,
priority=ofproto.OFP_DEFAULT_PRIORITY,
flags=ofproto.OFPFF_SEND_FLOW_REM, actions=actions)
datapath.send_msg(mod)
self.logger.info(str(datapath.id) + " " + str(datapath) + " flow entry added!")
@set_ev_cls(event.EventSwitchEnter)
def get_topology_data(self, ev):
switch_list = get_switch(self.topology_api_app, None)
self.datapaths = [switch.dp for switch in switch_list]
@set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
def _packet_in_handler(self, ev):
msg = ev.msg
pkt = packet.Packet(msg.data)
eth = pkt.get_protocol(ethernet.ethernet)
# self.logger.info("packet in type: %s iptype: %s arptype: %s", eth.ethertype, ether_types.ETH_TYPE_IP, ether_types.ETH_TYPE_ARP)
if eth.ethertype == ether_types.ETH_TYPE_ARP:
for datapath in self.datapaths:
self.add_flow(datapath)
datapath = msg.datapath
if eth.ethertype == ether_types.ETH_TYPE_ARP:
self._handle_arp(datapath, msg.in_port, eth, pkt.get_protocol(arp.arp))
return
if eth.ethertype != ether_types.ETH_TYPE_LLDP:
self.logger.info("Unexpected packets received!")
def _handle_arp(self, datapath, port, pkt_ethernet, pkt_arp):
if pkt_arp.opcode != arp.ARP_REQUEST:
return
reply_src_hwaddr = "00:00:00:00:00:01" if pkt_arp.dst_ip == "10.0.0.1" else "00:00:00:00:00:02"
pkt = packet.Packet()
pkt.add_protocol(ethernet.ethernet(ethertype=pkt_ethernet.ethertype,
dst=pkt_ethernet.src,
src=reply_src_hwaddr))
pkt.add_protocol(arp.arp(opcode=arp.ARP_REPLY,
src_mac=reply_src_hwaddr,
src_ip=pkt_arp.dst_ip,
dst_mac=pkt_arp.src_mac,
dst_ip=pkt_arp.src_ip))
self._send_packet(datapath, port, pkt)
def _send_packet(self, datapath, port, pkt):
ofproto = datapath.ofproto
parser = datapath.ofproto_parser
pkt.serialize()
self.logger.info("packet-out %s" % (pkt,))
data = pkt.data
actions = [parser.OFPActionOutput(port=port)]
out = parser.OFPPacketOut(datapath=datapath,
buffer_id=ofproto.OFP_NO_BUFFER,
in_port=ofproto.OFPP_CONTROLLER,
actions=actions,
data=data)
datapath.send_msg(out)
@set_ev_cls(ofp_event.EventOFPPortStatus, MAIN_DISPATCHER)
def _port_status_handler(self, ev):
msg = ev.msg
reason = msg.reason
port_no = msg.desc.port_no
ofproto = msg.datapath.ofproto
if reason == ofproto.OFPPR_ADD:
self.logger.info("port added %s", port_no)
elif reason == ofproto.OFPPR_DELETE:
self.logger.info("port deleted %s", port_no)
elif reason == ofproto.OFPPR_MODIFY:
self.logger.info("port modified %s", port_no)
else:
self.logger.info("Illeagal port state %s %s", port_no, reason)