backing up
[vsorcdistro/.git] / ryu / build / lib.linux-armv7l-2.7 / ryu / app / sdnhub_apps / learning_switch.py
1 # Copyright (C) 2014 SDN Hub
2 #
3 # Licensed under the GNU GENERAL PUBLIC LICENSE, Version 3.
4 # You may not use this file except in compliance with this License.
5 # You may obtain a copy of the License at
6 #
7 #    http://www.gnu.org/licenses/gpl-3.0.txt
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12 # implied.
13
14 import logging
15 import random
16
17 from ryu.base import app_manager
18 from ryu.controller import ofp_event
19 from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER, DEAD_DISPATCHER
20 from ryu.controller.handler import set_ev_cls
21 from ryu.ofproto import ofproto_v1_3
22 from ryu.lib import ofctl_v1_3
23 from ryu.ofproto import ether, inet
24 from ryu.lib.packet import packet
25 from ryu.lib.packet import ethernet
26 from ryu.lib.packet import ipv4
27 from ryu.lib.packet import tcp
28 from ryu.lib.packet import arp
29
30 DEFAULT_IDLE_TIMEOUT = 60
31 DEFAULT_HARD_TIMEOUT = 300
32
33 LOG = logging.getLogger('ryu.app.sdnhub_apps.learning_switch')
34
35 class L2LearningSwitch(app_manager.RyuApp):
36     OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
37
38     def __init__(self, *args, **kwargs):
39         super(L2LearningSwitch, self).__init__(*args, **kwargs)
40         self.mac_to_port = {}
41         self.exemption = []
42         self.switch_flows = {}
43
44     def get_switch_flows(self):
45         return self.switch_flows
46
47     def get_switch_flows(self, dpid):
48         return self.switch_flows[dpid]
49
50     def add_exemption(self, match=None):
51         if match != None:
52             self.exemption.append(match)
53
54     def clear_exemption(self):
55         del self.exemption[:]
56
57     def get_attachment_port(self, dpid, mac):
58         if dpid in self.mac_to_port:
59             table = self.mac_to_port[dpid]
60             if mac in table:
61                 return table[mac]
62
63         return None
64
65     def is_packet_exempted(self, pkt):
66         fields = {}
67         eth = pkt.get_protocols(ethernet.ethernet)[0]
68         
69         fields['dl_src'] = eth.src
70         fields['dl_dst'] = eth.dst
71         fields['dl_type'] = eth.ethertype
72
73         if eth.ethertype == ether.ETH_TYPE_ARP:
74             arp_hdr = pkt.get_protocols(arp.arp)[0]
75             fields['nw_src'] = arp_hdr.src_ip
76             fields['nw_dst'] = arp_hdr.dst_ip
77
78         elif eth.ethertype == ether.ETH_TYPE_IP:
79             ip_hdr = pkt.get_protocols(ipv4.ipv4)[0]
80             fields['nw_src'] = ip_hdr.src
81             fields['nw_dst'] = ip_hdr.dst
82             fields['nw_proto'] = ip_hdr.proto
83
84             if ip_hdr.proto == inet.IPPROTO_TCP:
85                 tcp_hdr = pkt.get_protocols(tcp.tcp)[0]
86                 fields['tp_src'] = tcp_hdr.src_port
87                 fields['tp_dst'] = tcp_hdr.dst_port
88             elif ip_hdr.proto == inet.IPPROTO_UDP:
89                 tcp_hdr = pkt.get_protocols(tcp.tcp)[0]
90                 fields['tp_src'] = tcp_hdr.src_port
91                 fields['tp_dst'] = tcp_hdr.dst_port
92         
93         for match in self.exemption:
94             # the match specified for exemption should be a
95             # superset of the flows to exclude processing. 
96             superset = True
97
98             for key,val in match.iteritems():
99                 if key not in fields:
100                     superset = False
101                     break
102                 elif val != fields[key]:
103                     superset = False
104                     break
105
106             # This exemption rule matched
107             if superset:
108                 return True
109         
110
111     def add_flow(self, datapath, priority=ofproto_v1_3.OFP_DEFAULT_PRIORITY, match=None,
112                   actions=None,idle_timeout=0, hard_timeout=0, buffer_id=ofproto_v1_3.OFP_NO_BUFFER):
113         ofp = datapath.ofproto
114         ofp_parser = datapath.ofproto_parser
115
116         if actions != None:
117             inst = [ofp_parser.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)]
118         else:
119             inst = []
120
121         cookie = random.randint(0, 0xffffffffffffffff)
122
123         mod = ofp_parser.OFPFlowMod(datapath=datapath, priority=priority,
124                 buffer_id=buffer_id,cookie=cookie,
125                 match=match, idle_timeout=idle_timeout,
126                 hard_timeout=hard_timeout, instructions=inst,
127                 flags=ofp.OFPFF_SEND_FLOW_REM)
128
129         datapath.send_msg(mod)
130
131         match_str = ofctl_v1_3.match_to_str(match),
132         self.switch_flows[datapath.id].append({'cookie':cookie,
133                                                'match':match_str,
134                                                'actions':actions,
135                                                'priority':priority})
136
137         LOG.debug("Flow inserted to switch %x: cookie=%s, match=%s, actions=%s, priority=%d",
138                                   datapath.id, str(cookie), match_str, str(actions), priority)
139
140
141     @set_ev_cls(ofp_event.EventOFPStateChange,
142                 [MAIN_DISPATCHER, DEAD_DISPATCHER])
143     def state_change_handler(self, ev):
144         datapath = ev.datapath
145         assert datapath is not None
146
147         if ev.state == MAIN_DISPATCHER:
148             ofp = datapath.ofproto
149             ofp_parser = datapath.ofproto_parser
150
151             self.mac_to_port.setdefault(datapath.id, {})
152             self.switch_flows.setdefault(datapath.id, [])
153
154             # install table-miss flow entry
155             match = ofp_parser.OFPMatch()
156             actions = [ofp_parser.OFPActionOutput(ofp.OFPP_CONTROLLER, ofp.OFPCML_NO_BUFFER)]
157             self.add_flow(datapath=datapath, priority=0, match=match, actions=actions)
158
159         elif ev.state == DEAD_DISPATCHER:
160             if datapath.id != None:
161                 del self.mac_to_port[datapath.id]
162                 del self.switch_flows[datapath.id]
163
164
165     @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
166     def packet_in_handler(self, ev):
167         msg = ev.msg
168         datapath = msg.datapath
169         ofp = datapath.ofproto
170         ofp_parser = datapath.ofproto_parser
171         in_port = msg.match['in_port']
172
173         pkt = packet.Packet(msg.data)
174         if self.is_packet_exempted(pkt):
175             return
176
177         eth = pkt.get_protocols(ethernet.ethernet)[0]
178         dst = eth.dst
179         src = eth.src
180
181         # Skip processing LLDP packets. Leave it to the topology module
182         if eth.ethertype == ether.ETH_TYPE_LLDP:
183             return
184
185         dpid = datapath.id
186
187         # Learn a mac address to avoid FLOOD next time.
188         self.mac_to_port[dpid][src] = in_port
189
190         # Following is an optimization to stop troubling the controller
191         # too often. But, it has an effect of preventing the controller
192         # from seeing a few hosts because the ARP reply matches this
193         # rule and goes hidden from controller.
194
195         # On packet_in for any packet, program a low priority rule for
196         # the destination MAC so that we don't keep troubling controller
197         # actions = [ofp_parser.OFPActionOutput(in_port)]
198         # match = ofp_parser.OFPMatch(eth_dst=src)
199         # self.add_flow(datapath=datapath, priority=1,
200         #              match=match, actions=actions) 
201
202         # Learning switch logic below
203         if dst in self.mac_to_port[dpid]:
204             out_port = self.mac_to_port[dpid][dst]
205         else:
206             out_port = ofp.OFPP_FLOOD
207
208         actions = [ofp_parser.OFPActionOutput(out_port)]
209
210         # install a flow to avoid packet_in next time
211         if out_port != ofp.OFPP_FLOOD:
212             match = ofp_parser.OFPMatch(in_port=in_port, eth_dst=dst)
213             self.add_flow(datapath=datapath, priority=2,
214                     match=match, actions=actions,
215                     idle_timeout=DEFAULT_IDLE_TIMEOUT,
216                     hard_timeout=DEFAULT_HARD_TIMEOUT,
217                     buffer_id=msg.buffer_id)
218
219             # Are we done, or do we need to forward this packet?
220             if msg.buffer_id != ofp.OFP_NO_BUFFER:
221                 return
222
223         # For the case of ARP packets and unknown destination, just do
224         # single packet-out with the same action
225         data = None
226         if msg.buffer_id == ofp.OFP_NO_BUFFER:
227             data = msg.data
228
229         out = ofp_parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
230                                   in_port=in_port, actions=actions, data=data)
231         datapath.send_msg(out)
232
233     @set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER)
234     def flow_removed_handler(self, ev):
235         msg = ev.msg
236         dpid = msg.datapath.id
237         cookie = msg.cookie
238         match_str = ofctl_v1_3.match_to_str(msg.match)
239         index_to_delete = None
240
241         # Ensure that the flow removed is for a known switch
242         if dpid not in self.switch_flows:
243             return
244
245         for index, flow in enumerate(self.switch_flows[dpid]):
246             if flow['cookie'] == cookie:
247                 index_to_delete = index
248                 break
249
250         if index_to_delete is not None:
251             del self.switch_flows[dpid][index_to_delete]
252             LOG.debug("Flow removed on switch %d: match=%s, cookie=%s",
253                     dpid, match_str, cookie)
254