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