blob: 005708d135b1b9f04e9539a58952e6a4c3318711 [file] [log] [blame]
Umesh Krishnaswamy345ee992012-12-13 20:29:48 -08001/**
2* Copyright 2011, Big Switch Networks, Inc.
3* Originally created by David Erickson, Stanford University
4*
5* Licensed under the Apache License, Version 2.0 (the "License"); you may
6* not use this file except in compliance with the License. You may obtain
7* a copy of the License at
8*
9* http://www.apache.org/licenses/LICENSE-2.0
10*
11* Unless required by applicable law or agreed to in writing, software
12* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14* License for the specific language governing permissions and limitations
15* under the License.
16**/
17
18/**
19 * Floodlight
20 * A BSD licensed, Java based OpenFlow controller
21 *
22 * Floodlight is a Java based OpenFlow controller originally written by David Erickson at Stanford
23 * University. It is available under the BSD license.
24 *
25 * For documentation, forums, issue tracking and more visit:
26 *
27 * http://www.openflowhub.org/display/Floodlight/Floodlight+Home
28 **/
29
30package net.floodlightcontroller.learningswitch;
31
32import java.io.IOException;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.Collection;
36import java.util.Collections;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
40import java.util.concurrent.ConcurrentHashMap;
41
42import net.floodlightcontroller.core.FloodlightContext;
43import net.floodlightcontroller.core.IFloodlightProviderService;
44import net.floodlightcontroller.core.IOFMessageListener;
45import net.floodlightcontroller.core.IOFSwitch;
46import net.floodlightcontroller.core.module.FloodlightModuleContext;
47import net.floodlightcontroller.core.module.FloodlightModuleException;
48import net.floodlightcontroller.core.module.IFloodlightModule;
49import net.floodlightcontroller.core.module.IFloodlightService;
50import net.floodlightcontroller.core.types.MacVlanPair;
51import net.floodlightcontroller.counter.ICounterStoreService;
52import net.floodlightcontroller.packet.Ethernet;
53import net.floodlightcontroller.restserver.IRestApiService;
54
55import org.openflow.protocol.OFError;
56import org.openflow.protocol.OFFlowMod;
57import org.openflow.protocol.OFFlowRemoved;
58import org.openflow.protocol.OFMatch;
59import org.openflow.protocol.OFMessage;
60import org.openflow.protocol.OFPacketIn;
61import org.openflow.protocol.OFPacketOut;
62import org.openflow.protocol.OFPort;
63import org.openflow.protocol.OFType;
64import org.openflow.protocol.action.OFAction;
65import org.openflow.protocol.action.OFActionOutput;
66import org.openflow.util.HexString;
67import org.openflow.util.LRULinkedHashMap;
68import org.slf4j.Logger;
69import org.slf4j.LoggerFactory;
70
71public class LearningSwitch
72 implements IFloodlightModule, ILearningSwitchService, IOFMessageListener {
73 protected static Logger log = LoggerFactory.getLogger(LearningSwitch.class);
74
75 // Module dependencies
76 protected IFloodlightProviderService floodlightProvider;
77 protected ICounterStoreService counterStore;
78 protected IRestApiService restApi;
79
80 // Stores the learned state for each switch
81 protected Map<IOFSwitch, Map<MacVlanPair,Short>> macVlanToSwitchPortMap;
82
83 // flow-mod - for use in the cookie
84 public static final int LEARNING_SWITCH_APP_ID = 1;
85 // LOOK! This should probably go in some class that encapsulates
86 // the app cookie management
87 public static final int APP_ID_BITS = 12;
88 public static final int APP_ID_SHIFT = (64 - APP_ID_BITS);
89 public static final long LEARNING_SWITCH_COOKIE = (long) (LEARNING_SWITCH_APP_ID & ((1 << APP_ID_BITS) - 1)) << APP_ID_SHIFT;
90
91 // more flow-mod defaults
92 protected static final short IDLE_TIMEOUT_DEFAULT = 5;
93 protected static final short HARD_TIMEOUT_DEFAULT = 0;
94 protected static final short PRIORITY_DEFAULT = 100;
95
96 // for managing our map sizes
97 protected static final int MAX_MACS_PER_SWITCH = 1000;
98
99 // normally, setup reverse flow as well. Disable only for using cbench for comparison with NOX etc.
100 protected static final boolean LEARNING_SWITCH_REVERSE_FLOW = true;
101
102 /**
103 * @param floodlightProvider the floodlightProvider to set
104 */
105 public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) {
106 this.floodlightProvider = floodlightProvider;
107 }
108
109 @Override
110 public String getName() {
111 return "learningswitch";
112 }
113
114 /**
115 * Adds a host to the MAC/VLAN->SwitchPort mapping
116 * @param sw The switch to add the mapping to
117 * @param mac The MAC address of the host to add
118 * @param vlan The VLAN that the host is on
119 * @param portVal The switchport that the host is on
120 */
121 protected void addToPortMap(IOFSwitch sw, long mac, short vlan, short portVal) {
122 Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw);
123
124 if (vlan == (short) 0xffff) {
125 // OFMatch.loadFromPacket sets VLAN ID to 0xffff if the packet contains no VLAN tag;
126 // for our purposes that is equivalent to the default VLAN ID 0
127 vlan = 0;
128 }
129
130 if (swMap == null) {
131 // May be accessed by REST API so we need to make it thread safe
132 swMap = Collections.synchronizedMap(new LRULinkedHashMap<MacVlanPair,Short>(MAX_MACS_PER_SWITCH));
133 macVlanToSwitchPortMap.put(sw, swMap);
134 }
135 swMap.put(new MacVlanPair(mac, vlan), portVal);
136 }
137
138 /**
139 * Removes a host from the MAC/VLAN->SwitchPort mapping
140 * @param sw The switch to remove the mapping from
141 * @param mac The MAC address of the host to remove
142 * @param vlan The VLAN that the host is on
143 */
144 protected void removeFromPortMap(IOFSwitch sw, long mac, short vlan) {
145 if (vlan == (short) 0xffff) {
146 vlan = 0;
147 }
148 Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw);
149 if (swMap != null)
150 swMap.remove(new MacVlanPair(mac, vlan));
151 }
152
153 /**
154 * Get the port that a MAC/VLAN pair is associated with
155 * @param sw The switch to get the mapping from
156 * @param mac The MAC address to get
157 * @param vlan The VLAN number to get
158 * @return The port the host is on
159 */
160 public Short getFromPortMap(IOFSwitch sw, long mac, short vlan) {
161 if (vlan == (short) 0xffff) {
162 vlan = 0;
163 }
164 Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw);
165 if (swMap != null)
166 return swMap.get(new MacVlanPair(mac, vlan));
167
168 // if none found
169 return null;
170 }
171
172 /**
173 * Clears the MAC/VLAN -> SwitchPort map for all switches
174 */
175 public void clearLearnedTable() {
176 macVlanToSwitchPortMap.clear();
177 }
178
179 /**
180 * Clears the MAC/VLAN -> SwitchPort map for a single switch
181 * @param sw The switch to clear the mapping for
182 */
183 public void clearLearnedTable(IOFSwitch sw) {
184 Map<MacVlanPair, Short> swMap = macVlanToSwitchPortMap.get(sw);
185 if (swMap != null)
186 swMap.clear();
187 }
188
189 @Override
190 public synchronized Map<IOFSwitch, Map<MacVlanPair,Short>> getTable() {
191 return macVlanToSwitchPortMap;
192 }
193
194 /**
195 * Writes a OFFlowMod to a switch.
196 * @param sw The switch tow rite the flowmod to.
197 * @param command The FlowMod actions (add, delete, etc).
198 * @param bufferId The buffer ID if the switch has buffered the packet.
199 * @param match The OFMatch structure to write.
200 * @param outPort The switch port to output it to.
201 */
202 private void writeFlowMod(IOFSwitch sw, short command, int bufferId,
203 OFMatch match, short outPort) {
204 // from openflow 1.0 spec - need to set these on a struct ofp_flow_mod:
205 // struct ofp_flow_mod {
206 // struct ofp_header header;
207 // struct ofp_match match; /* Fields to match */
208 // uint64_t cookie; /* Opaque controller-issued identifier. */
209 //
210 // /* Flow actions. */
211 // uint16_t command; /* One of OFPFC_*. */
212 // uint16_t idle_timeout; /* Idle time before discarding (seconds). */
213 // uint16_t hard_timeout; /* Max time before discarding (seconds). */
214 // uint16_t priority; /* Priority level of flow entry. */
215 // uint32_t buffer_id; /* Buffered packet to apply to (or -1).
216 // Not meaningful for OFPFC_DELETE*. */
217 // uint16_t out_port; /* For OFPFC_DELETE* commands, require
218 // matching entries to include this as an
219 // output port. A value of OFPP_NONE
220 // indicates no restriction. */
221 // uint16_t flags; /* One of OFPFF_*. */
222 // struct ofp_action_header actions[0]; /* The action length is inferred
223 // from the length field in the
224 // header. */
225 // };
226
227 OFFlowMod flowMod = (OFFlowMod) floodlightProvider.getOFMessageFactory().getMessage(OFType.FLOW_MOD);
228 flowMod.setMatch(match);
229 flowMod.setCookie(LearningSwitch.LEARNING_SWITCH_COOKIE);
230 flowMod.setCommand(command);
231 flowMod.setIdleTimeout(LearningSwitch.IDLE_TIMEOUT_DEFAULT);
232 flowMod.setHardTimeout(LearningSwitch.HARD_TIMEOUT_DEFAULT);
233 flowMod.setPriority(LearningSwitch.PRIORITY_DEFAULT);
234 flowMod.setBufferId(bufferId);
235 flowMod.setOutPort((command == OFFlowMod.OFPFC_DELETE) ? outPort : OFPort.OFPP_NONE.getValue());
236 flowMod.setFlags((command == OFFlowMod.OFPFC_DELETE) ? 0 : (short) (1 << 0)); // OFPFF_SEND_FLOW_REM
237
238 // set the ofp_action_header/out actions:
239 // from the openflow 1.0 spec: need to set these on a struct ofp_action_output:
240 // uint16_t type; /* OFPAT_OUTPUT. */
241 // uint16_t len; /* Length is 8. */
242 // uint16_t port; /* Output port. */
243 // uint16_t max_len; /* Max length to send to controller. */
244 // type/len are set because it is OFActionOutput,
245 // and port, max_len are arguments to this constructor
246 flowMod.setActions(Arrays.asList((OFAction) new OFActionOutput(outPort, (short) 0xffff)));
247 flowMod.setLength((short) (OFFlowMod.MINIMUM_LENGTH + OFActionOutput.MINIMUM_LENGTH));
248
249 if (log.isTraceEnabled()) {
250 log.trace("{} {} flow mod {}",
251 new Object[]{ sw, (command == OFFlowMod.OFPFC_DELETE) ? "deleting" : "adding", flowMod });
252 }
253
254 counterStore.updatePktOutFMCounterStore(sw, flowMod);
255
256 // and write it out
257 try {
258 sw.write(flowMod, null);
259 } catch (IOException e) {
260 log.error("Failed to write {} to switch {}", new Object[]{ flowMod, sw }, e);
261 }
262 }
263
264 /**
265 * Writes an OFPacketOut message to a switch.
266 * @param sw The switch to write the PacketOut to.
267 * @param packetInMessage The corresponding PacketIn.
268 * @param egressPort The switchport to output the PacketOut.
269 */
270 private void writePacketOutForPacketIn(IOFSwitch sw,
271 OFPacketIn packetInMessage,
272 short egressPort) {
273 // from openflow 1.0 spec - need to set these on a struct ofp_packet_out:
274 // uint32_t buffer_id; /* ID assigned by datapath (-1 if none). */
275 // uint16_t in_port; /* Packet's input port (OFPP_NONE if none). */
276 // uint16_t actions_len; /* Size of action array in bytes. */
277 // struct ofp_action_header actions[0]; /* Actions. */
278 /* uint8_t data[0]; */ /* Packet data. The length is inferred
279 from the length field in the header.
280 (Only meaningful if buffer_id == -1.) */
281
282 OFPacketOut packetOutMessage = (OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT);
283 short packetOutLength = (short)OFPacketOut.MINIMUM_LENGTH; // starting length
284
285 // Set buffer_id, in_port, actions_len
286 packetOutMessage.setBufferId(packetInMessage.getBufferId());
287 packetOutMessage.setInPort(packetInMessage.getInPort());
288 packetOutMessage.setActionsLength((short)OFActionOutput.MINIMUM_LENGTH);
289 packetOutLength += OFActionOutput.MINIMUM_LENGTH;
290
291 // set actions
292 List<OFAction> actions = new ArrayList<OFAction>(1);
293 actions.add(new OFActionOutput(egressPort, (short) 0));
294 packetOutMessage.setActions(actions);
295
296 // set data - only if buffer_id == -1
297 if (packetInMessage.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
298 byte[] packetData = packetInMessage.getPacketData();
299 packetOutMessage.setPacketData(packetData);
300 packetOutLength += (short)packetData.length;
301 }
302
303 // finally, set the total length
304 packetOutMessage.setLength(packetOutLength);
305
306 // and write it out
307 try {
308 counterStore.updatePktOutFMCounterStore(sw, packetOutMessage);
309 sw.write(packetOutMessage, null);
310 } catch (IOException e) {
311 log.error("Failed to write {} to switch {}: {}", new Object[]{ packetOutMessage, sw, e });
312 }
313 }
314
315 /**
316 * Processes a OFPacketIn message. If the switch has learned the MAC/VLAN to port mapping
317 * for the pair it will write a FlowMod for. If the mapping has not been learned the
318 * we will flood the packet.
319 * @param sw
320 * @param pi
321 * @param cntx
322 * @return
323 */
324 private Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) {
325 // Read in packet data headers by using OFMatch
326 OFMatch match = new OFMatch();
327 match.loadFromPacket(pi.getPacketData(), pi.getInPort());
328 Long sourceMac = Ethernet.toLong(match.getDataLayerSource());
329 Long destMac = Ethernet.toLong(match.getDataLayerDestination());
330 Short vlan = match.getDataLayerVirtualLan();
331 if ((destMac & 0xfffffffffff0L) == 0x0180c2000000L) {
332 if (log.isTraceEnabled()) {
333 log.trace("ignoring packet addressed to 802.1D/Q reserved addr: switch {} vlan {} dest MAC {}",
334 new Object[]{ sw, vlan, HexString.toHexString(destMac) });
335 }
336 return Command.STOP;
337 }
338 if ((sourceMac & 0x010000000000L) == 0) {
339 // If source MAC is a unicast address, learn the port for this MAC/VLAN
340 this.addToPortMap(sw, sourceMac, vlan, pi.getInPort());
341 }
342
343 // Now output flow-mod and/or packet
344 Short outPort = getFromPortMap(sw, destMac, vlan);
345 if (outPort == null) {
346 // If we haven't learned the port for the dest MAC/VLAN, flood it
347 // Don't flood broadcast packets if the broadcast is disabled.
348 // XXX For LearningSwitch this doesn't do much. The sourceMac is removed
349 // from port map whenever a flow expires, so you would still see
350 // a lot of floods.
351 this.writePacketOutForPacketIn(sw, pi, OFPort.OFPP_FLOOD.getValue());
352 } else if (outPort == match.getInputPort()) {
353 log.trace("ignoring packet that arrived on same port as learned destination:"
354 + " switch {} vlan {} dest MAC {} port {}",
355 new Object[]{ sw, vlan, HexString.toHexString(destMac), outPort });
356 } else {
357 // Add flow table entry matching source MAC, dest MAC, VLAN and input port
358 // that sends to the port we previously learned for the dest MAC/VLAN. Also
359 // add a flow table entry with source and destination MACs reversed, and
360 // input and output ports reversed. When either entry expires due to idle
361 // timeout, remove the other one. This ensures that if a device moves to
362 // a different port, a constant stream of packets headed to the device at
363 // its former location does not keep the stale entry alive forever.
364 // FIXME: current HP switches ignore DL_SRC and DL_DST fields, so we have to match on
365 // NW_SRC and NW_DST as well
366 match.setWildcards(((Integer)sw.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).intValue()
367 & ~OFMatch.OFPFW_IN_PORT
368 & ~OFMatch.OFPFW_DL_VLAN & ~OFMatch.OFPFW_DL_SRC & ~OFMatch.OFPFW_DL_DST
369 & ~OFMatch.OFPFW_NW_SRC_MASK & ~OFMatch.OFPFW_NW_DST_MASK);
370 this.writeFlowMod(sw, OFFlowMod.OFPFC_ADD, pi.getBufferId(), match, outPort);
371 if (LEARNING_SWITCH_REVERSE_FLOW) {
372 this.writeFlowMod(sw, OFFlowMod.OFPFC_ADD, -1, match.clone()
373 .setDataLayerSource(match.getDataLayerDestination())
374 .setDataLayerDestination(match.getDataLayerSource())
375 .setNetworkSource(match.getNetworkDestination())
376 .setNetworkDestination(match.getNetworkSource())
377 .setTransportSource(match.getTransportDestination())
378 .setTransportDestination(match.getTransportSource())
379 .setInputPort(outPort),
380 match.getInputPort());
381 }
382 }
383 return Command.CONTINUE;
384 }
385
386 /**
387 * Processes a flow removed message. We will delete the learned MAC/VLAN mapping from
388 * the switch's table.
389 * @param sw The switch that sent the flow removed message.
390 * @param flowRemovedMessage The flow removed message.
391 * @return Whether to continue processing this message or stop.
392 */
393 private Command processFlowRemovedMessage(IOFSwitch sw, OFFlowRemoved flowRemovedMessage) {
394 if (flowRemovedMessage.getCookie() != LearningSwitch.LEARNING_SWITCH_COOKIE) {
395 return Command.CONTINUE;
396 }
397 if (log.isTraceEnabled()) {
398 log.trace("{} flow entry removed {}", sw, flowRemovedMessage);
399 }
400 OFMatch match = flowRemovedMessage.getMatch();
401 // When a flow entry expires, it means the device with the matching source
402 // MAC address and VLAN either stopped sending packets or moved to a different
403 // port. If the device moved, we can't know where it went until it sends
404 // another packet, allowing us to re-learn its port. Meanwhile we remove
405 // it from the macVlanToPortMap to revert to flooding packets to this device.
406 this.removeFromPortMap(sw, Ethernet.toLong(match.getDataLayerSource()),
407 match.getDataLayerVirtualLan());
408
409 // Also, if packets keep coming from another device (e.g. from ping), the
410 // corresponding reverse flow entry will never expire on its own and will
411 // send the packets to the wrong port (the matching input port of the
412 // expired flow entry), so we must delete the reverse entry explicitly.
413 this.writeFlowMod(sw, OFFlowMod.OFPFC_DELETE, -1, match.clone()
414 .setWildcards(((Integer)sw.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).intValue()
415 & ~OFMatch.OFPFW_DL_VLAN & ~OFMatch.OFPFW_DL_SRC & ~OFMatch.OFPFW_DL_DST
416 & ~OFMatch.OFPFW_NW_SRC_MASK & ~OFMatch.OFPFW_NW_DST_MASK)
417 .setDataLayerSource(match.getDataLayerDestination())
418 .setDataLayerDestination(match.getDataLayerSource())
419 .setNetworkSource(match.getNetworkDestination())
420 .setNetworkDestination(match.getNetworkSource())
421 .setTransportSource(match.getTransportDestination())
422 .setTransportDestination(match.getTransportSource()),
423 match.getInputPort());
424 return Command.CONTINUE;
425 }
426
427 // IOFMessageListener
428
429 @Override
430 public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
431 switch (msg.getType()) {
432 case PACKET_IN:
433 return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx);
434 case FLOW_REMOVED:
435 return this.processFlowRemovedMessage(sw, (OFFlowRemoved) msg);
436 case ERROR:
437 log.info("received an error {} from switch {}", (OFError) msg, sw);
438 return Command.CONTINUE;
439 default:
440 break;
441 }
442 log.error("received an unexpected message {} from switch {}", msg, sw);
443 return Command.CONTINUE;
444 }
445
446 @Override
447 public boolean isCallbackOrderingPrereq(OFType type, String name) {
448 return false;
449 }
450
451 @Override
452 public boolean isCallbackOrderingPostreq(OFType type, String name) {
453 return false;
454 }
455
456 // IFloodlightModule
457
458 @Override
459 public Collection<Class<? extends IFloodlightService>> getModuleServices() {
460 Collection<Class<? extends IFloodlightService>> l =
461 new ArrayList<Class<? extends IFloodlightService>>();
462 l.add(ILearningSwitchService.class);
463 return l;
464 }
465
466 @Override
467 public Map<Class<? extends IFloodlightService>, IFloodlightService>
468 getServiceImpls() {
469 Map<Class<? extends IFloodlightService>,
470 IFloodlightService> m =
471 new HashMap<Class<? extends IFloodlightService>,
472 IFloodlightService>();
473 m.put(ILearningSwitchService.class, this);
474 return m;
475 }
476
477 @Override
478 public Collection<Class<? extends IFloodlightService>>
479 getModuleDependencies() {
480 Collection<Class<? extends IFloodlightService>> l =
481 new ArrayList<Class<? extends IFloodlightService>>();
482 l.add(IFloodlightProviderService.class);
483 l.add(ICounterStoreService.class);
484 l.add(IRestApiService.class);
485 return l;
486 }
487
488 @Override
489 public void init(FloodlightModuleContext context)
490 throws FloodlightModuleException {
491 macVlanToSwitchPortMap =
492 new ConcurrentHashMap<IOFSwitch, Map<MacVlanPair,Short>>();
493 floodlightProvider =
494 context.getServiceImpl(IFloodlightProviderService.class);
495 counterStore =
496 context.getServiceImpl(ICounterStoreService.class);
497 restApi =
498 context.getServiceImpl(IRestApiService.class);
499 }
500
501 @Override
502 public void startUp(FloodlightModuleContext context) {
503 floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
504 floodlightProvider.addOFMessageListener(OFType.FLOW_REMOVED, this);
505 floodlightProvider.addOFMessageListener(OFType.ERROR, this);
506 restApi.addRestletRoutable(new LearningSwitchWebRoutable());
507 }
508}