· 7 years ago · Dec 08, 2018, 08:26 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( "Sw: " + sw
124 + " --- New PortMAC: "
125 + " MAC {} PORT {}",
126 new Object[]{ mac.toString(),
127 portVal});
128 }
129 }
130
131 /**
132 * Removes a host from the MAC->SwitchPort mapping
133 * @param sw The switch to remove the mapping from
134 * @param mac The MAC address of the host to remove
135 */
136 protected void removeFromPortMap(IOFSwitch sw, MacAddress mac) {
137
138 Map<MacAddress, OFPort> swMap = ControllerMap.get(sw);
139 if (swMap != null) {
140 swMap.remove(mac);
141 log.info( "Sw: " + sw
142 + " --- Removed PortMAC: "
143 + " MAC {}",
144 new Object[]{ mac.toString()});
145 }
146 }
147
148 /**
149 * Get the port that a MAC is associated with
150 * @param sw The switch to get the mapping from
151 * @param mac The MAC address to get
152 * @return The port the host is on
153 */
154 public OFPort getFromPortMap(IOFSwitch sw, MacAddress mac) {
155 Map<MacAddress, OFPort> swMap = ControllerMap.get(sw);
156 if (swMap != null) {
157 return swMap.get(mac);
158 }
159
160 // if none found
161 return null;
162 }
163
164 /**
165 * Clears the MAC -> SwitchPort map for all switches
166 */
167 public void clearLearnedTable() {
168 ControllerMap.clear();
169 }
170
171 /**
172 * Clears the MAC/VLAN -> SwitchPort map for a single switch
173 * @param sw The switch to clear the mapping for
174 */
175 public void clearLearnedTable(IOFSwitch sw) {
176 Map<MacAddress, OFPort> swMap = ControllerMap.get(sw);
177 if (swMap != null) {
178 swMap.clear();
179 }
180 }
181
182 protected Match createMatchFromPacket(IOFSwitch sw, OFPort inPort, FloodlightContext cntx)
183 {
184 // The packet in match will only contain the port number.
185 // We need to add in specifics for the hosts we're routing between.
186 Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
187 MacAddress srcMac = eth.getSourceMACAddress();
188 MacAddress dstMac = eth.getDestinationMACAddress();
189
190 Match.Builder mb = sw.getOFFactory().buildMatch();
191 mb.setExact(MatchField.IN_PORT, inPort)
192 .setExact(MatchField.ETH_SRC, srcMac)
193 .setExact(MatchField.ETH_DST, dstMac);
194 return mb.build();
195 }
196
197 protected Match createReverseMatchfromMatch(IOFSwitch sw, OFPort outPort, Match match)
198 {
199 Match.Builder rev_match = match.createBuilder();
200 rev_match.setExact(MatchField.IN_PORT, outPort)
201 .setExact(MatchField.ETH_SRC, match.get(MatchField.ETH_DST))
202 .setExact(MatchField.ETH_DST, match.get(MatchField.ETH_SRC));
203 return rev_match.build();
204 }
205
206 protected Match createReverseMatchfromMatch(IOFSwitch sw, Match match)
207 {
208 Match.Builder rev_match = match.createBuilder();
209 rev_match.setExact(MatchField.ETH_SRC, match.get(MatchField.ETH_DST))
210 .setExact(MatchField.ETH_DST, match.get(MatchField.ETH_SRC));
211
212 return rev_match.build();
213 }
214
215 protected void createFlowMod(IOFSwitch sw, OFPort Port, Match match, OFVersion version)
216 {
217 // Action List
218 List<OFAction> al = new ArrayList<OFAction>();
219 OFActionOutput output = sw.getOFFactory().actions().buildOutput()
220 .setPort(Port) // outPort is the port trough which the sw should send the Matching Packets
221 .setMaxLen(0xffFFffFF)
222 .build();
223 al.add(output);
224
225
226 // Write the flow mod using SwitchCommands install rule method since it receives either instructions or actions
227 // depending on the OpenFlow Version you should do the following:
228 if (version.compareTo(OFVersion.OF_13) == 0 )
229 {
230 OFInstructionApplyActions applyActions = sw.getOFFactory().instructions().buildApplyActions().setActions(al).build(); //use the instructions builder to build an applyActions instruction with the given action list.
231 ArrayList<OFInstruction> instructionList = new ArrayList<OFInstruction>();
232 instructionList.add(applyActions); //add the applyActions Instruction to the Instruction list
233 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);
234
235 } else {
236 SwitchCommands.installRule(sw, null, CGRmodule.FLOWMOD_PRIORITY, match, null, al, CGRmodule.FLOWMOD_DEFAULT_HARD_TIMEOUT, CGRmodule.FLOWMOD_DEFAULT_IDLE_TIMEOUT, OFBufferId.NO_BUFFER, true);
237 }
238
239 log.info( "Sw: " + sw
240 + " *** New Flow: "
241 + "SRC {} DEST {} PORT {}",
242 new Object[]{ match.get(MatchField.ETH_SRC).toString(),
243 match.get(MatchField.ETH_DST).toString(),
244 Port.getPortNumber() });
245 }
246
247 /**
248 * Processes a OFPacketIn message. If the switch has learned the MAC to port mapping
249 * for the pair it will write a FlowMod for. If the mapping has not been learned the
250 * we will flood the packet.
251 * @param sw
252 * @param pi
253 * @param cntx
254 * @return
255 */
256 private Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx)
257 {
258 Integer lnk_count;
259
260 // Read inPort and declare outPort
261 OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT));
262 OFPort outPort;
263
264 /* Read packet header attributes into a Match object */
265 Match match = createMatchFromPacket(sw, inPort, cntx);
266 MacAddress sourceMac = match.get(MatchField.ETH_SRC);
267 MacAddress destMac = match.get(MatchField.ETH_DST);
268
269
270 if (sourceMac == null)
271 {
272 sourceMac = MacAddress.NONE;
273 }
274 if (destMac == null)
275 {
276 destMac = MacAddress.NONE;
277 }
278
279 if ((destMac.getLong() & 0xfffffffffff0L) == 0x0180c2000000L)
280 {
281 if (log.isTraceEnabled())
282 {
283 log.info("ignoring packet addressed to 802.1D/Q reserved addr: switch {} dest MAC {}",
284 new Object[]{ sw,
285 destMac.toString() });
286 }
287 return Command.STOP;
288 }
289
290 if ( (sourceMac.getLong() & 0x010000000000L) == 0)
291 {
292 // If source MAC is a unicast address, learn the port for this MAC/VLAN
293 addToPortMap(sw, sourceMac, inPort); // If the entry already exists, it is replaced
294 }
295
296 // Check if port for destination MAC is known
297 // If so output flow-mod and/or packet
298 // If link destination number is exceeded, drop
299 if ( (outPort = getFromPortMap(sw, destMac)) == null)
300 {
301 // No port entry for the given MAC - Flood the packet
302 SwitchCommands.sendPacketOutPacketIn(sw, OFPort.FLOOD, pi);
303 log.info( "Sw: " + sw
304 + " ... Flooding: "
305 + "SRC {} DEST {}",
306 new Object[]{ sourceMac.toString(),
307 destMac.toString()});
308 }
309 else if( ( lnk_count = macLinkCounterMap.get(sourceMac) ) != null && lnk_count >= MAX_DESTINATION_NUMBER)
310 {
311 // Link Destinations exceeded, drop packet.
312 log.info( "Sw: " + sw
313 + " ---> ignoring packet due to excessive destination connections: "
314 + "DEST {} PORT {} Link Count {}",
315 new Object[]{ destMac.toString(),
316 outPort.getPortNumber(),
317 lnk_count });
318 }
319 else
320 {
321 // If there is no MacLinkCounter entry for the SourceMAC
322 // Push packet to switch
323 SwitchCommands.sendPacketOutPacketIn(sw, outPort, pi);
324 log.info( "Sw: " + sw
325 + " ... Sending packet to known destination port: "
326 + "SRC {} DEST {} inP {} outP {} {}",
327 new Object[]{ sourceMac.toString(),
328 destMac.toString(),
329 inPort.getPortNumber(),
330 outPort.getPortNumber() });
331
332 if (sourceMac != null && destMac != null)
333 {
334 // Add flow table entry matching source MAC, dest MAC and input port
335 // that sends to the port we previously learned for the dest MAC.
336 // We write FlowMods with Buffer ID none then explicitly PacketOut the buffered packet
337 createFlowMod(sw, outPort, match, pi.getVersion());
338
339 // Also add a flow table entry with source and destination MACs reversed, and
340 // input and output ports reversed.
341 if (LEARNING_SWITCH_REVERSE_FLOW)
342 {
343 // Create Reserve Match
344 Match rev_match = createReverseMatchfromMatch(sw, inPort, match); // CHECK PORT TO USE /IN/OUT
345 createFlowMod(sw, inPort, rev_match, pi.getVersion());
346 }
347
348 // Create or Increment MAC Counter entry
349 if ( (lnk_count = macLinkCounterMap.putIfAbsent(sourceMac, 1)) != null)
350 {
351 macLinkCounterMap.replace(sourceMac, ++lnk_count);
352 }
353
354 log.info( "Sw: " + sw
355 + " *** Link Count Update: "
356 + "MAC {} LinkCount {}",
357 new Object[]{ sourceMac.toString(),
358 macLinkCounterMap.get(sourceMac) });
359 }
360 }
361 return Command.STOP;
362 }
363
364 /**
365 * Processes a flow removed message.
366 * @param sw The switch that sent the flow removed message.
367 * @param flowRemovedMessage The flow removed message.
368 * @return Whether to continue processing this message or stop.
369 */
370 private Command processFlowRemovedMessage(IOFSwitch sw, OFFlowRemoved flowRemovedMessage) {
371 Integer lnk_count;
372 Match match = flowRemovedMessage.getMatch();
373 MacAddress sourceMac = match.get(MatchField.ETH_SRC);
374 // When a flow entry expires, it means the device with the matching source
375 // MAC address either stopped sending packets or moved to a different
376 // port. If the device moved, we can't know where it went until it sends
377 // another packet, allowing us to re-learn its port. Meanwhile we remove
378 // it from the macToPortMap to revert to flooding packets to this device.
379 removeFromPortMap(sw, sourceMac);
380 //SwitchCommands.removeRules(sw, TableId.of(0), match);
381
382 // Also, if packets keep coming from another device (e.g. from ping), the
383 // corresponding reverse flow entry will never expire on its own and will
384 // send the packets to the wrong port (the matching input port of the
385 // expired flow entry), so we must delete the reverse entry explicitly.
386 if(LEARNING_SWITCH_REVERSE_FLOW) {
387 Match rev_match = createReverseMatchfromMatch(sw, match);
388 SwitchCommands.removeRules(sw, rev_match, flowRemovedMessage.getVersion() );
389 }
390
391 // Decrement MAC counter links
392 if ( (lnk_count = macLinkCounterMap.get(sourceMac) ) != null /*&& lnk_count > 0*/)
393 {
394 macLinkCounterMap.replace(sourceMac, --lnk_count);
395 }
396
397 log.info( "Sw: " + sw
398 + " *** Flow Removed: "
399 + "SRC {} LinkCount {}",
400 new Object[]{ sourceMac.toString(),
401 macLinkCounterMap.get(sourceMac) });
402
403 return Command.CONTINUE;
404 }
405
406 // IOFMessageListener
407
408 @Override
409 public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
410 switch (msg.getType()) {
411 case PACKET_IN:
412 return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx);
413 case FLOW_REMOVED:
414 return this.processFlowRemovedMessage(sw, (OFFlowRemoved) msg);
415 case ERROR:
416 log.info("received an error {} from switch {}", msg, sw);
417 return Command.CONTINUE;
418 default:
419 log.error("received an unexpected message {} from switch {}", msg, sw);
420 return Command.CONTINUE;
421 }
422 }
423
424 @Override
425 public boolean isCallbackOrderingPrereq(OFType type, String name) {
426 return false;
427 }
428
429 @Override
430 public boolean isCallbackOrderingPostreq(OFType type, String name) {
431 return (type.equals(OFType.PACKET_IN) && name.equals("forwarding")) ;
432 }
433
434 // IFloodlightModule
435
436 /**
437 * Tell the module system which services we provide.
438 */
439 @Override
440 public Collection<Class<? extends IFloodlightService>> getModuleServices()
441 { return null; }
442
443 /**
444 * Tell the module system which services we implement.
445 */
446 @Override
447 public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls()
448 { return null; }
449
450 @Override
451 public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
452 Collection<Class<? extends IFloodlightService>> l =
453 new ArrayList<Class<? extends IFloodlightService>>();
454 l.add(IFloodlightProviderService.class);
455 return l;
456 }
457
458 @Override
459 public void init(FloodlightModuleContext context) throws FloodlightModuleException {
460 ControllerMap = new ConcurrentHashMap<IOFSwitch, Map<MacAddress, OFPort>>();
461 macLinkCounterMap = new ConcurrentHashMap<MacAddress, Integer>();
462 floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class);
463 log.info("CGR module started {}");
464 }
465
466 @Override
467 public void startUp(FloodlightModuleContext context)
468 {
469 // paag: register the IControllerCompletionListener
470 floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this);
471 floodlightProviderService.addOFMessageListener(OFType.FLOW_REMOVED, this);
472 floodlightProviderService.addOFMessageListener(OFType.ERROR, this);
473
474 // read our config options
475 Map<String, String> configOptions = context.getConfigParams(this);
476 try {
477 String idleTimeout = configOptions.get("idletimeout");
478 if (idleTimeout != null) {
479 FLOWMOD_DEFAULT_IDLE_TIMEOUT = Short.parseShort(idleTimeout);
480 }
481 } catch (NumberFormatException e) {
482 log.warn("Error parsing flow idle timeout, " +
483 "using default of {} seconds", FLOWMOD_DEFAULT_IDLE_TIMEOUT);
484 }
485 try {
486 String hardTimeout = configOptions.get("hardtimeout");
487 if (hardTimeout != null) {
488 FLOWMOD_DEFAULT_HARD_TIMEOUT = Short.parseShort(hardTimeout);
489 }
490 } catch (NumberFormatException e) {
491 log.warn("Error parsing flow hard timeout, " +
492 "using default of {} seconds", FLOWMOD_DEFAULT_HARD_TIMEOUT);
493 }
494 try {
495 String priority = configOptions.get("priority");
496 if (priority != null) {
497 FLOWMOD_PRIORITY = Short.parseShort(priority);
498 }
499 } catch (NumberFormatException e) {
500 log.warn("Error parsing flow priority, " +
501 "using default of {}",
502 FLOWMOD_PRIORITY);
503 }
504 log.debug("FlowMod idle timeout set to {} seconds", FLOWMOD_DEFAULT_IDLE_TIMEOUT);
505 log.debug("FlowMod hard timeout set to {} seconds", FLOWMOD_DEFAULT_HARD_TIMEOUT);
506 log.debug("FlowMod priority set to {}", FLOWMOD_PRIORITY);
507 }
508
509}