· 7 years ago · Dec 06, 2018, 05:08 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, inPort); // If the entry already exists, it is replaced
266 log.trace("Adding mac-port key-value to map:"
267 + " switch {} src MAC {} port {}",
268 new Object[]{ sw, sourceMac.toString(), inPort});
269 }
270
271 // Check if port for destination MAC is known
272 // If so output flow-mod and/or packet
273 // If link destination number is exceeded, drop
274 if ( (outPort = macToSwitchPortMap.get(sw).get(destMac)) == null)
275 {
276 // No port entry for the given MAC - Flood the packet
277 SwitchCommands.sendPacketOutPacketIn(sw, OFPort.FLOOD, pi);
278 log.trace("flooding packet:"
279 + " switch {} src MAC {} dest MAC {}",
280 new Object[]{ sw, sourceMac.toString(), destMac.toString()});
281 }
282 else if (outPort.equals(inPort))
283 {
284 // Switch shouldn't have sent packet to controller, since a Flow should already exist
285 log.trace("ignoring packet that arrived on same port as learned destination:"
286 + " switch {} dest MAC {} port {}",
287 new Object[]{ sw, destMac.toString(), outPort.getPortNumber() });
288 return Command.STOP;
289 }
290 else if ( (lnk_count = macLinkCounterMap.get(sourceMac) ) >= MAX_DESTINATION_NUMBER)
291 {
292 // Link Destinations exceeded, drop packet.
293 log.trace("ignoring packet due to excessive destination connections:"
294 + " switch {} dest MAC {} port {} links {}",
295 new Object[]{ sw, destMac.toString(), outPort.getPortNumber(), lnk_count });
296 return Command.STOP;
297 }
298 else
299 {
300 // Add flow table entry matching source MAC, dest MAC and input port
301 // that sends to the port we previously learned for the dest MAC.
302 // We write FlowMods with Buffer ID none then explicitly PacketOut the buffered packet
303 createFlowMod(sw, outPort, match, pi.getVersion());
304
305 // Also add a flow table entry with source and destination MACs reversed, and
306 // input and output ports reversed.
307 if (LEARNING_SWITCH_REVERSE_FLOW) {
308 // Create Reserve Match
309 Match rev_match = createReverseMatchfromMatch(sw, inPort, match); // CHECK PORT TO USE /IN/OUT
310 createFlowMod(sw, inPort, rev_match, pi.getVersion());
311 }
312
313 // Create or Increment MAC Counter entry
314 if ((lnk_count = macLinkCounterMap.putIfAbsent(sourceMac, 1)) != null) {
315 macLinkCounterMap.replace(sourceMac, ++lnk_count);
316 }
317
318 log.trace("creating new flow:"
319 + " switch {} src Mac {} dest MAC {} inPort {} outPort {} new link cnt {}",
320 new Object[]{ sw, sourceMac.toString(), destMac.toString(), inPort.getPortNumber(), outPort.getPortNumber(), lnk_count });
321
322 // Push packet to switch
323 SwitchCommands.sendPacketOutPacketIn(sw, outPort, pi);
324 }
325 return Command.STOP;
326 }
327
328 /**
329 * Processes a flow removed message.
330 * @param sw The switch that sent the flow removed message.
331 * @param flowRemovedMessage The flow removed message.
332 * @return Whether to continue processing this message or stop.
333 */
334 private Command processFlowRemovedMessage(IOFSwitch sw, OFFlowRemoved flowRemovedMessage) {
335 if (log.isTraceEnabled()) {
336 log.trace("{} flow entry removed {}", sw, flowRemovedMessage);
337 }
338 Integer lnk_count;
339 Match match = flowRemovedMessage.getMatch();
340 MacAddress sourceMac = match.get(MatchField.ETH_SRC);
341 // When a flow entry expires, it means the device with the matching source
342 // MAC address either stopped sending packets or moved to a different
343 // port. If the device moved, we can't know where it went until it sends
344 // another packet, allowing us to re-learn its port. Meanwhile we remove
345 // it from the macToPortMap to revert to flooding packets to this device.
346 removeFromPortMap(sw, sourceMac);
347 SwitchCommands.removeRules(sw, TableId.of(0), match);
348
349 // Also, if packets keep coming from another device (e.g. from ping), the
350 // corresponding reverse flow entry will never expire on its own and will
351 // send the packets to the wrong port (the matching input port of the
352 // expired flow entry), so we must delete the reverse entry explicitly.
353 if(LEARNING_SWITCH_REVERSE_FLOW) {
354 Match rev_match = createReverseMatchfromMatch(sw, match);
355 SwitchCommands.removeRules(sw, TableId.of(0), rev_match);
356 }
357
358 // Decrement Mac counter links
359 if ( (lnk_count = macLinkCounterMap.get(sourceMac) ) != null)
360 {
361 macLinkCounterMap.replace(sourceMac, --lnk_count);
362 log.trace("decrementing link count:"
363 + " switch {} dest MAC {} links {}",
364 new Object[]{ sw, sourceMac.toString(), lnk_count });
365 }
366
367 return Command.CONTINUE;
368 }
369
370 // IOFMessageListener
371
372 @Override
373 public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
374 switch (msg.getType()) {
375 case PACKET_IN:
376 return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx);
377 case FLOW_REMOVED:
378 return this.processFlowRemovedMessage(sw, (OFFlowRemoved) msg);
379 case ERROR:
380 log.info("received an error {} from switch {}", msg, sw);
381 return Command.CONTINUE;
382 default:
383 log.error("received an unexpected message {} from switch {}", msg, sw);
384 return Command.CONTINUE;
385 }
386 }
387
388 @Override
389 public boolean isCallbackOrderingPrereq(OFType type, String name) {
390 return false;
391 }
392
393 @Override
394 public boolean isCallbackOrderingPostreq(OFType type, String name) {
395 return (type.equals(OFType.PACKET_IN) && name.equals("forwarding")) ;
396 }
397
398 // IFloodlightModule
399
400 /**
401 * Tell the module system which services we provide.
402 */
403 @Override
404 public Collection<Class<? extends IFloodlightService>> getModuleServices()
405 { return null; }
406
407 /**
408 * Tell the module system which services we implement.
409 */
410 @Override
411 public Map<Class<? extends IFloodlightService>, IFloodlightService>
412 getServiceImpls()
413 { return null; }
414
415 @Override
416 public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
417 Collection<Class<? extends IFloodlightService>> l =
418 new ArrayList<Class<? extends IFloodlightService>>();
419 l.add(IFloodlightProviderService.class);
420 return l;
421 }
422
423 @Override
424 public void init(FloodlightModuleContext context) throws FloodlightModuleException {
425 macToSwitchPortMap = new ConcurrentHashMap<IOFSwitch, Map<MacAddress, OFPort>>();
426 macLinkCounterMap = new ConcurrentHashMap<MacAddress, Integer>();
427 floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class);
428 log.info("CGR module started {}");
429 }
430
431 @Override
432 public void startUp(FloodlightModuleContext context) {
433 // paag: register the IControllerCompletionListener
434 floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this);
435 floodlightProviderService.addOFMessageListener(OFType.FLOW_REMOVED, this);
436 floodlightProviderService.addOFMessageListener(OFType.ERROR, this);
437
438 // read our config options
439 Map<String, String> configOptions = context.getConfigParams(this);
440 try {
441 String idleTimeout = configOptions.get("idletimeout");
442 if (idleTimeout != null) {
443 FLOWMOD_DEFAULT_IDLE_TIMEOUT = Short.parseShort(idleTimeout);
444 }
445 } catch (NumberFormatException e) {
446 log.warn("Error parsing flow idle timeout, " +
447 "using default of {} seconds", FLOWMOD_DEFAULT_IDLE_TIMEOUT);
448 }
449 try {
450 String hardTimeout = configOptions.get("hardtimeout");
451 if (hardTimeout != null) {
452 FLOWMOD_DEFAULT_HARD_TIMEOUT = Short.parseShort(hardTimeout);
453 }
454 } catch (NumberFormatException e) {
455 log.warn("Error parsing flow hard timeout, " +
456 "using default of {} seconds", FLOWMOD_DEFAULT_HARD_TIMEOUT);
457 }
458 try {
459 String priority = configOptions.get("priority");
460 if (priority != null) {
461 FLOWMOD_PRIORITY = Short.parseShort(priority);
462 }
463 } catch (NumberFormatException e) {
464 log.warn("Error parsing flow priority, " +
465 "using default of {}",
466 FLOWMOD_PRIORITY);
467 }
468 log.debug("FlowMod idle timeout set to {} seconds", FLOWMOD_DEFAULT_IDLE_TIMEOUT);
469 log.debug("FlowMod hard timeout set to {} seconds", FLOWMOD_DEFAULT_HARD_TIMEOUT);
470 log.debug("FlowMod priority set to {}", FLOWMOD_PRIORITY);
471 }
472
473}