· 7 years ago · Dec 07, 2018, 10:52 PM
1package net.floodlightcontroller.cgrmodule;
2
3import java.util.ArrayList;
4import java.util.Collection;
5import java.util.Collections;
6import java.util.HashMap;
7import java.util.HashSet;
8import java.util.Iterator;
9import java.util.List;
10import java.util.Map;
11import java.util.Map.Entry;
12import java.util.Set;
13import java.util.concurrent.ConcurrentHashMap;
14
15import org.projectfloodlight.openflow.protocol.OFFlowMod;
16import org.projectfloodlight.openflow.protocol.OFFlowModCommand;
17import org.projectfloodlight.openflow.protocol.OFFlowModFlags;
18import org.projectfloodlight.openflow.protocol.OFFlowRemoved;
19import org.projectfloodlight.openflow.protocol.OFMessage;
20import org.projectfloodlight.openflow.protocol.OFPacketIn;
21import org.projectfloodlight.openflow.protocol.OFPacketOut;
22import org.projectfloodlight.openflow.protocol.OFType;
23import org.projectfloodlight.openflow.protocol.OFVersion;
24import org.projectfloodlight.openflow.protocol.action.OFAction;
25import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
26import org.projectfloodlight.openflow.protocol.action.OFActions;
27import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
28import org.projectfloodlight.openflow.protocol.instruction.OFInstructionApplyActions;
29import org.projectfloodlight.openflow.protocol.instruction.OFInstructions;
30import org.projectfloodlight.openflow.protocol.match.Match;
31import org.projectfloodlight.openflow.protocol.match.MatchField;
32import org.projectfloodlight.openflow.types.MacAddress;
33import org.projectfloodlight.openflow.types.OFBufferId;
34import org.projectfloodlight.openflow.types.OFPort;
35import org.projectfloodlight.openflow.types.OFVlanVidMatch;
36import org.projectfloodlight.openflow.types.TableId;
37import org.projectfloodlight.openflow.types.U64;
38import org.projectfloodlight.openflow.types.VlanVid;
39import org.projectfloodlight.openflow.util.LRULinkedHashMap;
40import org.slf4j.Logger;
41import org.slf4j.LoggerFactory;
42
43import net.floodlightcontroller.cgrmodule.util.*;
44import net.floodlightcontroller.core.FloodlightContext;
45import net.floodlightcontroller.core.IControllerCompletionListener;
46import net.floodlightcontroller.core.IFloodlightProviderService;
47import net.floodlightcontroller.core.IOFMessageListener;
48import net.floodlightcontroller.core.IOFSwitch;
49import net.floodlightcontroller.core.module.FloodlightModuleContext;
50import net.floodlightcontroller.core.module.FloodlightModuleException;
51import net.floodlightcontroller.core.module.IFloodlightModule;
52import net.floodlightcontroller.core.module.IFloodlightService;
53import net.floodlightcontroller.learningswitch.LearningSwitch;
54import net.floodlightcontroller.packet.Ethernet;
55import net.floodlightcontroller.util.OFMessageUtils;
56
57public class CGRmodule implements IFloodlightModule, IOFMessageListener {
58 protected static Logger log = LoggerFactory.getLogger(CGRmodule.class);
59
60 // Module dependencies
61 protected IFloodlightProviderService floodlightProviderService;
62
63 // Stores the learned state for each switch
64 protected Map<IOFSwitch, Map<MacAddress, OFPort>> ControllerMap;
65
66 // Stores the number of connections for a given MAC address.
67 protected Map<MacAddress, Integer> macLinkCounterMap;
68
69 // flow-mod - for use in the cookie
70 public static final int LEARNING_SWITCH_APP_ID = 1;
71 // LOOK! This should probably go in some class that encapsulates
72 // the app cookie management
73 public static final int APP_ID_BITS = 12;
74 public static final int APP_ID_SHIFT = (64 - APP_ID_BITS);
75 public static final long LEARNING_SWITCH_COOKIE = (long) (LEARNING_SWITCH_APP_ID & ((1 << APP_ID_BITS) - 1)) << APP_ID_SHIFT;
76
77 // more flow-mod defaults
78 protected static short FLOWMOD_DEFAULT_IDLE_TIMEOUT = 5; // in seconds
79 protected static short FLOWMOD_DEFAULT_HARD_TIMEOUT = 0; // infinite
80 protected static short FLOWMOD_PRIORITY = 100;
81
82 // for managing our map sizes
83 protected static final int MAX_MACS_PER_SWITCH = 1000;
84
85 // CGR Firewall module
86 protected static final int MAX_DESTINATION_NUMBER = 3;
87 protected static final int MAX_ELEPHANT_FLOWS = 3;
88 protected static final int ELEPHANT_FLOW_BW = 10;
89
90 // normally, setup reverse flow as well. Disable only for using cbench for comparison with NOX etc.
91 protected static final boolean LEARNING_SWITCH_REVERSE_FLOW = true;
92
93 /**
94 * @param floodlightProvider the floodlightProvider to set
95 */
96 public void setFloodlightProvider(IFloodlightProviderService floodlightProviderService) {
97 this.floodlightProviderService = floodlightProviderService;
98 }
99
100 @Override
101 public String getName() {
102 return "CGRModule";
103 }
104
105 /**
106 * Adds a host to the MAC->SwitchPort mapping
107 * @param sw The switch to add the mapping to
108 * @param mac The MAC address of the host to add
109 * @param portVal The switchport that the host is on
110 */
111 protected void addToPortMap(IOFSwitch sw, MacAddress mac, OFPort portVal)
112 {
113 Map<MacAddress, OFPort> swMap = ControllerMap.get(sw);
114
115 // Check if the switch already exists
116 if (swMap == null) {
117 swMap = new LRULinkedHashMap<MacAddress, OFPort>(MAX_MACS_PER_SWITCH);
118 ControllerMap.put(sw, swMap);
119 }
120
121 if ( swMap.putIfAbsent(mac, portVal) != portVal) {
122 swMap.replace(mac, portVal);
123 log.info("Adding mac-port key-value to map:"
124 + " switch {} src MAC {} port {}",
125 new Object[]{ sw, mac.toString(), portVal});
126 }
127 }
128
129 /**
130 * Removes a host from the MAC->SwitchPort mapping
131 * @param sw The switch to remove the mapping from
132 * @param mac The MAC address of the host to remove
133 */
134 protected void removeFromPortMap(IOFSwitch sw, MacAddress mac) {
135
136 Map<MacAddress, OFPort> swMap = ControllerMap.get(sw);
137 if (swMap != null) {
138 swMap.remove(mac);
139 }
140 }
141
142 /**
143 * Get the port that a MAC is associated with
144 * @param sw The switch to get the mapping from
145 * @param mac The MAC address to get
146 * @return The port the host is on
147 */
148 public OFPort getFromPortMap(IOFSwitch sw, MacAddress mac) {
149 Map<MacAddress, OFPort> swMap = ControllerMap.get(sw);
150 if (swMap != null) {
151 return swMap.get(mac);
152 }
153
154 // if none found
155 return null;
156 }
157
158 /**
159 * Clears the MAC -> SwitchPort map for all switches
160 */
161 public void clearLearnedTable() {
162 ControllerMap.clear();
163 }
164
165 /**
166 * Clears the MAC/VLAN -> SwitchPort map for a single switch
167 * @param sw The switch to clear the mapping for
168 */
169 public void clearLearnedTable(IOFSwitch sw) {
170 Map<MacAddress, OFPort> swMap = ControllerMap.get(sw);
171 if (swMap != null) {
172 swMap.clear();
173 }
174 }
175
176 protected Match createMatchFromPacket(IOFSwitch sw, OFPort inPort, FloodlightContext cntx)
177 {
178 // The packet in match will only contain the port number.
179 // We need to add in specifics for the hosts we're routing between.
180 Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
181 MacAddress srcMac = eth.getSourceMACAddress();
182 MacAddress dstMac = eth.getDestinationMACAddress();
183
184 Match.Builder mb = sw.getOFFactory().buildMatch();
185 mb.setExact(MatchField.IN_PORT, inPort)
186 .setExact(MatchField.ETH_SRC, srcMac)
187 .setExact(MatchField.ETH_DST, dstMac);
188 return mb.build();
189 }
190
191 protected Match createReverseMatchfromMatch(IOFSwitch sw, OFPort outPort, Match match)
192 {
193 Match.Builder rev_match = match.createBuilder();
194 rev_match.setExact(MatchField.IN_PORT, outPort)
195 .setExact(MatchField.ETH_SRC, match.get(MatchField.ETH_DST))
196 .setExact(MatchField.ETH_DST, match.get(MatchField.ETH_SRC));
197 return rev_match.build();
198 }
199
200 protected Match createReverseMatchfromMatch(IOFSwitch sw, Match match)
201 {
202 Match.Builder rev_match = match.createBuilder();
203 rev_match.setExact(MatchField.ETH_SRC, match.get(MatchField.ETH_DST))
204 .setExact(MatchField.ETH_DST, match.get(MatchField.ETH_SRC));
205
206 return rev_match.build();
207 }
208
209 protected void createFlowMod(IOFSwitch sw, OFPort Port, Match match, OFVersion version)
210 {
211 // Action List
212 List<OFAction> al = new ArrayList<OFAction>();
213 OFActionOutput output = sw.getOFFactory().actions().buildOutput()
214 .setPort(Port) // outPort is the port trough which the sw should send the Matching Packets
215 .setMaxLen(0xffFFffFF)
216 .build();
217 al.add(output);
218
219 // Write the flow mod using SwitchCommands install rule method since it receives either instructions or actions
220 // depending on the OpenFlow Version you should do the following:
221 if (version.compareTo(OFVersion.OF_13) == 0 )
222 {
223 OFInstructionApplyActions applyActions = sw.getOFFactory().instructions().buildApplyActions().setActions(al).build(); //use the instructions builder to build an applyActions instruction with the given action list.
224 ArrayList<OFInstruction> instructionList = new ArrayList<OFInstruction>();
225 instructionList.add(applyActions); //add the applyActions Instruction to the Instruction list
226 SwitchCommands.installRule(sw, TableId.of(0), CGRmodule.FLOWMOD_PRIORITY, match, instructionList, null, CGRmodule.FLOWMOD_DEFAULT_HARD_TIMEOUT, CGRmodule.FLOWMOD_DEFAULT_IDLE_TIMEOUT, OFBufferId.NO_BUFFER, true);
227
228 } else {
229 SwitchCommands.installRule(sw, TableId.of(0), CGRmodule.FLOWMOD_PRIORITY, match, null, al, CGRmodule.FLOWMOD_DEFAULT_HARD_TIMEOUT, CGRmodule.FLOWMOD_DEFAULT_IDLE_TIMEOUT, OFBufferId.NO_BUFFER, true);
230 }
231 }
232
233 /**
234 * Processes a OFPacketIn message. If the switch has learned the MAC to port mapping
235 * for the pair it will write a FlowMod for. If the mapping has not been learned the
236 * we will flood the packet.
237 * @param sw
238 * @param pi
239 * @param cntx
240 * @return
241 */
242 private Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx)
243 {
244 Integer lnk_count;
245
246 // Read inPort and declare outPort
247 OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT));
248 OFPort outPort;
249
250 /* Read packet header attributes into a Match object */
251 Match match = createMatchFromPacket(sw, inPort, cntx);
252 MacAddress sourceMac = match.get(MatchField.ETH_SRC);
253 MacAddress destMac = match.get(MatchField.ETH_DST);
254
255
256 if (sourceMac == null)
257 {
258 sourceMac = MacAddress.NONE;
259 return Command.STOP;
260 }
261 if (destMac == null)
262 {
263 destMac = MacAddress.NONE;
264 return Command.STOP;
265 }
266
267 if ((destMac.getLong() & 0xfffffffffff0L) == 0x0180c2000000L)
268 {
269 if (log.isTraceEnabled())
270 {
271 log.info("ignoring packet addressed to 802.1D/Q reserved addr: switch {} dest MAC {}",
272 new Object[]{ sw, destMac.toString() });
273 }
274 return Command.STOP;
275 }
276
277 // If source MAC is a unicast address, learn the port for this MAC/VLAN
278 if ( (sourceMac.getLong() & 0x010000000000L) == 0)
279 {
280 addToPortMap(sw, sourceMac, inPort); // If the entry already exists, it is replaced
281 }
282
283 // Check if port for destination MAC is known
284 // If so output flow-mod and/or packet
285 // If link destination number is exceeded, drop
286 if ( (outPort = ControllerMap.get(sw).get(destMac)) == null)
287 {
288 // No port entry for the given MAC - Flood the packet
289 SwitchCommands.sendPacketOutPacketIn(sw, OFPort.FLOOD, pi);
290 log.info("flooding packet:"
291 + " switch {} src MAC {} dest MAC {}",
292 new Object[]{ sw, sourceMac.toString(), destMac.toString()});
293 }
294 else if (outPort.equals(inPort))
295 {
296 // Switch shouldn't have sent packet to controller, since a Flow should already exist
297 log.info("ignoring packet that arrived on same port as learned destination:"
298 + " switch {} dest MAC {} port {}",
299 new Object[]{ sw, destMac.toString(), outPort.getPortNumber() });
300 return Command.STOP;
301 }
302 else if( ( lnk_count = macLinkCounterMap.get(sourceMac) ) != null )
303 {
304 if ( lnk_count >= MAX_DESTINATION_NUMBER )
305 {
306 // Link Destinations exceeded, drop packet.
307 log.info("ignoring packet due to excessive destination connections:"
308 + " switch {} dest MAC {} port {} links {}",
309 new Object[]{ sw, destMac.toString(), outPort.getPortNumber(), lnk_count });
310 return Command.STOP;
311 }
312 }
313
314 else
315 {
316 // Add flow table entry matching source MAC, dest MAC and input port
317 // that sends to the port we previously learned for the dest MAC.
318 // We write FlowMods with Buffer ID none then explicitly PacketOut the buffered packet
319 createFlowMod(sw, outPort, match, pi.getVersion());
320
321 // Also add a flow table entry with source and destination MACs reversed, and
322 // input and output ports reversed.
323 if (LEARNING_SWITCH_REVERSE_FLOW)
324 {
325 // Create Reserve Match
326 Match rev_match = createReverseMatchfromMatch(sw, inPort, match); // CHECK PORT TO USE /IN/OUT
327 createFlowMod(sw, inPort, rev_match, pi.getVersion());
328 }
329
330 // Create or Increment MAC Counter entry
331 if (sourceMac != null)
332 {
333 if (macLinkCounterMap.putIfAbsent(sourceMac, 1) != null)
334 {
335 lnk_count=macLinkCounterMap.get(sourceMac);
336
337 macLinkCounterMap.replace(sourceMac, ++lnk_count);
338
339 log.info("Link Count Update:"
340 + " sw {} MAC {} link_count {}",
341 new Object[]{ sw, sourceMac.toString(), lnk_count });
342 } else
343 {
344 log.info("New Link Count:"
345 + " sw {} MAC {} link_count {}",
346 new Object[]{ sw, sourceMac.toString(), macLinkCounterMap.get(sourceMac) });
347 }
348
349 log.info("creating new flow:"
350 + " switch {} src Mac {} dest MAC {} inPort {} outPort {} new link cnt {}",
351 new Object[]{ sw, sourceMac.toString(), destMac.toString(), inPort.getPortNumber(), outPort.getPortNumber(), macLinkCounterMap.get(sourceMac) });
352
353 // Push packet to switch
354 SwitchCommands.sendPacketOutPacketIn(sw, outPort, pi);
355 }
356 }
357 return Command.STOP;
358 }
359
360 /**
361 * Processes a flow removed message.
362 * @param sw The switch that sent the flow removed message.
363 * @param flowRemovedMessage The flow removed message.
364 * @return Whether to continue processing this message or stop.
365 */
366 private Command processFlowRemovedMessage(IOFSwitch sw, OFFlowRemoved flowRemovedMessage) {
367 if (log.isTraceEnabled()) {
368 log.info("{} flow entry removed {}", sw, flowRemovedMessage);
369 }
370 Integer lnk_count;
371 Match match = flowRemovedMessage.getMatch();
372 MacAddress sourceMac = match.get(MatchField.ETH_SRC);
373 // When a flow entry expires, it means the device with the matching source
374 // MAC address either stopped sending packets or moved to a different
375 // port. If the device moved, we can't know where it went until it sends
376 // another packet, allowing us to re-learn its port. Meanwhile we remove
377 // it from the macToPortMap to revert to flooding packets to this device.
378 removeFromPortMap(sw, sourceMac);
379 //SwitchCommands.removeRules(sw, TableId.of(0), match);
380
381 // Also, if packets keep coming from another device (e.g. from ping), the
382 // corresponding reverse flow entry will never expire on its own and will
383 // send the packets to the wrong port (the matching input port of the
384 // expired flow entry), so we must delete the reverse entry explicitly.
385 if(LEARNING_SWITCH_REVERSE_FLOW) {
386 Match rev_match = createReverseMatchfromMatch(sw, match);
387 SwitchCommands.removeRules(sw, TableId.of(0), rev_match);
388 }
389
390 // Decrement Mac counter links
391 if ( (lnk_count = macLinkCounterMap.get(sourceMac) ) != null && lnk_count > 0)
392 {
393 macLinkCounterMap.replace(sourceMac, --lnk_count);
394
395 log.info("decrementing link count:"
396 + " switch {} src MAC {} links {}",
397 new Object[]{ sw, sourceMac.toString(), lnk_count });
398 }
399
400 return Command.CONTINUE;
401 }
402
403 // IOFMessageListener
404
405 @Override
406 public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
407 switch (msg.getType()) {
408 case PACKET_IN:
409 return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx);
410 case FLOW_REMOVED:
411 return this.processFlowRemovedMessage(sw, (OFFlowRemoved) msg);
412 case ERROR:
413 log.info("received an error {} from switch {}", msg, sw);
414 return Command.CONTINUE;
415 default:
416 log.error("received an unexpected message {} from switch {}", msg, sw);
417 return Command.CONTINUE;
418 }
419 }
420
421 @Override
422 public boolean isCallbackOrderingPrereq(OFType type, String name) {
423 return (type.equals(OFType.PACKET_IN) && name.equals ("forwarding"));
424 }
425
426 @Override
427 public boolean isCallbackOrderingPostreq(OFType type, String name) {
428 return (type.equals(OFType.PACKET_IN) && name.equals("forwarding")) ;
429 }
430
431 // IFloodlightModule
432
433 /**
434 * Tell the module system which services we provide.
435 */
436 @Override
437 public Collection<Class<? extends IFloodlightService>> getModuleServices()
438 { return null; }
439
440 /**
441 * Tell the module system which services we implement.
442 */
443 @Override
444 public Map<Class<? extends IFloodlightService>, IFloodlightService>
445 getServiceImpls()
446 { return null; }
447
448 @Override
449 public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
450 Collection<Class<? extends IFloodlightService>> l =
451 new ArrayList<Class<? extends IFloodlightService>>();
452 l.add(IFloodlightProviderService.class);
453 return l;
454 }
455
456 @Override
457 public void init(FloodlightModuleContext context) throws FloodlightModuleException {
458 ControllerMap = new ConcurrentHashMap<IOFSwitch, Map<MacAddress, OFPort>>();
459 macLinkCounterMap = new ConcurrentHashMap<MacAddress, Integer>();
460 floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class);
461 log.info("CGR module started {}");
462 }
463
464 @Override
465 public void startUp(FloodlightModuleContext context)
466 {
467 // paag: register the IControllerCompletionListener
468 floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this);
469 floodlightProviderService.addOFMessageListener(OFType.FLOW_REMOVED, this);
470 floodlightProviderService.addOFMessageListener(OFType.ERROR, this);
471
472 // read our config options
473 Map<String, String> configOptions = context.getConfigParams(this);
474 try {
475 String idleTimeout = configOptions.get("idletimeout");
476 if (idleTimeout != null) {
477 FLOWMOD_DEFAULT_IDLE_TIMEOUT = Short.parseShort(idleTimeout);
478 }
479 } catch (NumberFormatException e) {
480 log.warn("Error parsing flow idle timeout, " +
481 "using default of {} seconds", FLOWMOD_DEFAULT_IDLE_TIMEOUT);
482 }
483 try {
484 String hardTimeout = configOptions.get("hardtimeout");
485 if (hardTimeout != null) {
486 FLOWMOD_DEFAULT_HARD_TIMEOUT = Short.parseShort(hardTimeout);
487 }
488 } catch (NumberFormatException e) {
489 log.warn("Error parsing flow hard timeout, " +
490 "using default of {} seconds", FLOWMOD_DEFAULT_HARD_TIMEOUT);
491 }
492 try {
493 String priority = configOptions.get("priority");
494 if (priority != null) {
495 FLOWMOD_PRIORITY = Short.parseShort(priority);
496 }
497 } catch (NumberFormatException e) {
498 log.warn("Error parsing flow priority, " +
499 "using default of {}",
500 FLOWMOD_PRIORITY);
501 }
502 log.debug("FlowMod idle timeout set to {} seconds", FLOWMOD_DEFAULT_IDLE_TIMEOUT);
503 log.debug("FlowMod hard timeout set to {} seconds", FLOWMOD_DEFAULT_HARD_TIMEOUT);
504 log.debug("FlowMod priority set to {}", FLOWMOD_PRIORITY);
505 }
506
507}