blob: 012dfb6bc1790ae61f91b7268d03913dcfaa2666 [file] [log] [blame]
Umesh Krishnaswamy345ee992012-12-13 20:29:48 -08001package net.floodlightcontroller.virtualnetwork;
2
3import java.io.IOException;
4import java.util.ArrayList;
5import java.util.Collection;
6import java.util.Collections;
7import java.util.HashMap;
8import java.util.HashSet;
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.openflow.protocol.OFFlowMod;
16import org.openflow.protocol.OFMatch;
17import org.openflow.protocol.OFMessage;
18import org.openflow.protocol.OFPacketIn;
19import org.openflow.protocol.OFPacketOut;
20import org.openflow.protocol.OFType;
21import org.openflow.protocol.action.OFAction;
22import org.openflow.util.HexString;
23import org.slf4j.Logger;
24import org.slf4j.LoggerFactory;
25
26import net.floodlightcontroller.core.FloodlightContext;
27import net.floodlightcontroller.core.IFloodlightProviderService;
28import net.floodlightcontroller.core.IOFMessageListener;
29import net.floodlightcontroller.core.IOFSwitch;
30import net.floodlightcontroller.core.module.FloodlightModuleContext;
31import net.floodlightcontroller.core.module.FloodlightModuleException;
32import net.floodlightcontroller.core.module.IFloodlightModule;
33import net.floodlightcontroller.core.module.IFloodlightService;
34import net.floodlightcontroller.core.util.AppCookie;
35import net.floodlightcontroller.devicemanager.IDevice;
36import net.floodlightcontroller.devicemanager.IDeviceListener;
37import net.floodlightcontroller.devicemanager.IDeviceService;
38import net.floodlightcontroller.packet.DHCP;
39import net.floodlightcontroller.packet.Ethernet;
40import net.floodlightcontroller.packet.IPacket;
41import net.floodlightcontroller.packet.IPv4;
42import net.floodlightcontroller.restserver.IRestApiService;
43import net.floodlightcontroller.routing.ForwardingBase;
44import net.floodlightcontroller.util.MACAddress;
45
46/**
47 * A simple Layer 2 (MAC based) network virtualization module. This module allows
48 * you to create simple L2 networks (host + gateway) and will drop traffic if
49 * they are not on the same virtual network.
50 *
51 * LIMITATIONS
52 * - This module does not allow overlapping of IPs or MACs
53 * - You can only have 1 gateway per virtual network (can be shared)
54 * - There is filtering of multicast/broadcast traffic
55 * - All DHCP traffic will be allowed, regardless of unicast/broadcast
56 *
57 * @author alexreimers
58 */
59public class VirtualNetworkFilter
60 implements IFloodlightModule, IVirtualNetworkService, IOFMessageListener, IDeviceListener {
61 protected static Logger log = LoggerFactory.getLogger(VirtualNetworkFilter.class);
62
63 private final short APP_ID = 20;
64
65 // Our dependencies
66 IFloodlightProviderService floodlightProvider;
67 IRestApiService restApi;
68 IDeviceService deviceService;
69
70 // Our internal state
71 protected Map<String, VirtualNetwork> vNetsByGuid; // List of all created virtual networks
72 protected Map<String, String> nameToGuid; // Logical name -> Network ID
73 protected Map<String, Integer> guidToGateway; // Network ID -> Gateway IP
74 protected Map<Integer, Set<String>> gatewayToGuid; // Gateway IP -> Network ID
75 protected Map<MACAddress, Integer> macToGateway; // Gateway MAC -> Gateway IP
76 protected Map<MACAddress, String> macToGuid; // Host MAC -> Network ID
77 protected Map<String, MACAddress> portToMac; // Host MAC -> logical port name
78
79 /**
80 * Adds a gateway to a virtual network.
81 * @param guid The ID (not name) of the network.
82 * @param ip The IP addresses of the gateway.
83 */
84 protected void addGateway(String guid, Integer ip) {
85 if (ip.intValue() != 0) {
86 if (log.isDebugEnabled())
87 log.debug("Adding {} as gateway for GUID {}",
88 IPv4.fromIPv4Address(ip), guid);
89
90 guidToGateway.put(guid, ip);
91 if (vNetsByGuid.get(guid) != null)
92 vNetsByGuid.get(guid).setGateway(IPv4.fromIPv4Address(ip));
93 if (gatewayToGuid.containsKey(ip)) {
94 Set<String> gSet = gatewayToGuid.get(ip);
95 gSet.add(guid);
96 } else {
97 Set<String> gSet = Collections.synchronizedSet(new HashSet<String>());
98 gSet.add(guid);
99 gatewayToGuid.put(ip, gSet);
100 }
101 }
102 }
103
104 /**
105 * Deletes a gateway for a virtual network.
106 * @param guid The ID (not name) of the network to delete
107 * the gateway for.
108 */
109 protected void deleteGateway(String guid) {
110 Integer gwIp = guidToGateway.remove(guid);
111 if (gwIp == null) return;
112 Set<String> gSet = gatewayToGuid.get(gwIp);
113 gSet.remove(guid);
114 if(vNetsByGuid.get(guid)!=null)
115 vNetsByGuid.get(guid).setGateway(null);
116 }
117
118 // IVirtualNetworkService
119
120 @Override
121 public void createNetwork(String guid, String network, Integer gateway) {
122 if (log.isDebugEnabled()) {
123 String gw = null;
124 try {
125 gw = IPv4.fromIPv4Address(gateway);
126 } catch (Exception e) {
127 // fail silently
128 }
129 log.debug("Creating network {} with ID {} and gateway {}",
130 new Object[] {network, guid, gw});
131 }
132
133 if (!nameToGuid.isEmpty()) {
134 // We have to iterate all the networks to handle name/gateway changes
135 for (Entry<String, String> entry : nameToGuid.entrySet()) {
136 if (entry.getValue().equals(guid)) {
137 nameToGuid.remove(entry.getKey());
138 break;
139 }
140 }
141 }
142 nameToGuid.put(network, guid);
143 if (vNetsByGuid.containsKey(guid))
144 vNetsByGuid.get(guid).setName(network); //network already exists, just updating name
145 else
146 vNetsByGuid.put(guid, new VirtualNetwork(network, guid)); //new network
147
148 // If they don't specify a new gateway the old one will be preserved
149 if ((gateway != null) && (gateway != 0)) {
150 addGateway(guid, gateway);
151 if(vNetsByGuid.get(guid)!=null)
152 vNetsByGuid.get(guid).setGateway(IPv4.fromIPv4Address(gateway));
153 }
154 }
155
156 @Override
157 public void deleteNetwork(String guid) {
158 String name = null;
159 if (nameToGuid.isEmpty()) {
160 log.warn("Could not delete network with ID {}, network doesn't exist",
161 guid);
162 return;
163 }
164 for (Entry<String, String> entry : nameToGuid.entrySet()) {
165 if (entry.getValue().equals(guid)) {
166 name = entry.getKey();
167 break;
168 }
169 log.warn("Could not delete network with ID {}, network doesn't exist",
170 guid);
171 }
172
173 if (log.isDebugEnabled())
174 log.debug("Deleting network with name {} ID {}", name, guid);
175
176 nameToGuid.remove(name);
177 deleteGateway(guid);
178 if(vNetsByGuid.get(guid)!=null){
179 vNetsByGuid.get(guid).clearHosts();
180 vNetsByGuid.remove(guid);
181 }
182 Collection<MACAddress> deleteList = new ArrayList<MACAddress>();
183 for (MACAddress host : macToGuid.keySet()) {
184 if (macToGuid.get(host).equals(guid)) {
185 deleteList.add(host);
186 }
187 }
188 for (MACAddress mac : deleteList) {
189 if (log.isDebugEnabled()) {
190 log.debug("Removing host {} from network {}",
191 HexString.toHexString(mac.toBytes()), guid);
192 }
193 macToGuid.remove(mac);
194 for (Entry<String, MACAddress> entry : portToMac.entrySet()) {
195 if (entry.getValue().equals(mac)) {
196 portToMac.remove(entry.getKey());
197 break;
198 }
199 }
200 }
201 }
202
203 @Override
204 public void addHost(MACAddress mac, String guid, String port) {
205 if (guid != null) {
206 if (log.isDebugEnabled()) {
207 log.debug("Adding {} to network ID {} on port {}",
208 new Object[] {mac, guid, port});
209 }
210 // We ignore old mappings
211 macToGuid.put(mac, guid);
212 portToMac.put(port, mac);
213 if(vNetsByGuid.get(guid)!=null)
214 vNetsByGuid.get(guid).addHost(new MACAddress(mac.toBytes()));
215 } else {
216 log.warn("Could not add MAC {} to network ID {} on port {}, the network does not exist",
217 new Object[] {mac, guid, port});
218 }
219 }
220
221 @Override
222 public void deleteHost(MACAddress mac, String port) {
223 if (log.isDebugEnabled()) {
224 log.debug("Removing host {} from port {}", mac, port);
225 }
226 if (mac == null && port == null) return;
227 if (port != null) {
228 MACAddress host = portToMac.remove(port);
229 if(vNetsByGuid.get(macToGuid.get(host)) != null)
230 vNetsByGuid.get(macToGuid.get(host)).removeHost(host);
231 macToGuid.remove(host);
232 } else if (mac != null) {
233 if (!portToMac.isEmpty()) {
234 for (Entry<String, MACAddress> entry : portToMac.entrySet()) {
235 if (entry.getValue().equals(mac)) {
236 if(vNetsByGuid.get(macToGuid.get(entry.getValue())) != null)
237 vNetsByGuid.get(macToGuid.get(entry.getValue())).removeHost(entry.getValue());
238 portToMac.remove(entry.getKey());
239 macToGuid.remove(entry.getValue());
240 return;
241 }
242 }
243 }
244 }
245 }
246
247 // IFloodlightModule
248
249 @Override
250 public Collection<Class<? extends IFloodlightService>> getModuleServices() {
251 Collection<Class<? extends IFloodlightService>> l =
252 new ArrayList<Class<? extends IFloodlightService>>();
253 l.add(IVirtualNetworkService.class);
254 return l;
255 }
256
257 @Override
258 public Map<Class<? extends IFloodlightService>, IFloodlightService>
259 getServiceImpls() {
260 Map<Class<? extends IFloodlightService>,
261 IFloodlightService> m =
262 new HashMap<Class<? extends IFloodlightService>,
263 IFloodlightService>();
264 m.put(IVirtualNetworkService.class, this);
265 return m;
266 }
267
268 @Override
269 public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
270 Collection<Class<? extends IFloodlightService>> l =
271 new ArrayList<Class<? extends IFloodlightService>>();
272 l.add(IFloodlightProviderService.class);
273 l.add(IRestApiService.class);
274 l.add(IDeviceService.class);
275 return l;
276 }
277
278 @Override
279 public void init(FloodlightModuleContext context)
280 throws FloodlightModuleException {
281 floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
282 restApi = context.getServiceImpl(IRestApiService.class);
283 deviceService = context.getServiceImpl(IDeviceService.class);
284
285 vNetsByGuid = new ConcurrentHashMap<String, VirtualNetwork>();
286 nameToGuid = new ConcurrentHashMap<String, String>();
287 guidToGateway = new ConcurrentHashMap<String, Integer>();
288 gatewayToGuid = new ConcurrentHashMap<Integer, Set<String>>();
289 macToGuid = new ConcurrentHashMap<MACAddress, String>();
290 portToMac = new ConcurrentHashMap<String, MACAddress>();
291 macToGateway = new ConcurrentHashMap<MACAddress, Integer>();
292 }
293
294 @Override
295 public void startUp(FloodlightModuleContext context) {
296 floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
297 restApi.addRestletRoutable(new VirtualNetworkWebRoutable());
298 deviceService.addListener(this);
299 }
300
301 // IOFMessageListener
302
303 @Override
304 public String getName() {
305 return "virtualizer";
306 }
307
308 @Override
309 public boolean isCallbackOrderingPrereq(OFType type, String name) {
310 // Link discovery should go before us so we don't block LLDPs
311 return (type.equals(OFType.PACKET_IN) &&
312 (name.equals("linkdiscovery") || (name.equals("devicemanager"))));
313 }
314
315 @Override
316 public boolean isCallbackOrderingPostreq(OFType type, String name) {
317 // We need to go before forwarding
318 return (type.equals(OFType.PACKET_IN) && name.equals("forwarding"));
319 }
320
321 @Override
322 public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
323 switch (msg.getType()) {
324 case PACKET_IN:
325 return processPacketIn(sw, (OFPacketIn)msg, cntx);
326 default:
327 break;
328 }
329 log.warn("Received unexpected message {}", msg);
330 return Command.CONTINUE;
331 }
332
333 /**
334 * Checks whether the frame is destined to or from a gateway.
335 * @param frame The ethernet frame to check.
336 * @return True if it is to/from a gateway, false otherwise.
337 */
338 protected boolean isDefaultGateway(Ethernet frame) {
339 if (macToGateway.containsKey(frame.getSourceMAC()))
340 return true;
341
342 Integer gwIp = macToGateway.get(frame.getDestinationMAC());
343 if (gwIp != null) {
344 MACAddress host = frame.getSourceMAC();
345 String srcNet = macToGuid.get(host);
346 if (srcNet != null) {
347 Integer gwIpSrcNet = guidToGateway.get(srcNet);
348 if ((gwIpSrcNet != null) && (gwIp.equals(gwIpSrcNet)))
349 return true;
350 }
351 }
352
353 return false;
354 }
355
356 /**
357 * Checks to see if two MAC Addresses are on the same network.
358 * @param m1 The first MAC.
359 * @param m2 The second MAC.
360 * @return True if they are on the same virtual network,
361 * false otherwise.
362 */
363 protected boolean oneSameNetwork(MACAddress m1, MACAddress m2) {
364 String net1 = macToGuid.get(m1);
365 String net2 = macToGuid.get(m2);
366 if (net1 == null) return false;
367 if (net2 == null) return false;
368 return net1.equals(net2);
369 }
370
371 /**
372 * Checks to see if an Ethernet frame is a DHCP packet.
373 * @param frame The Ethernet frame.
374 * @return True if it is a DHCP frame, false otherwise.
375 */
376 protected boolean isDhcpPacket(Ethernet frame) {
377 IPacket payload = frame.getPayload(); // IP
378 if (payload == null) return false;
379 IPacket p2 = payload.getPayload(); // TCP or UDP
380 if (p2 == null) return false;
381 IPacket p3 = p2.getPayload(); // Application
382 if ((p3 != null) && (p3 instanceof DHCP)) return true;
383 return false;
384 }
385
386 /**
387 * Processes an OFPacketIn message and decides if the OFPacketIn should be dropped
388 * or the processing should continue.
389 * @param sw The switch the PacketIn came from.
390 * @param msg The OFPacketIn message from the switch.
391 * @param cntx The FloodlightContext for this message.
392 * @return Command.CONTINUE if processing should be continued, Command.STOP otherwise.
393 */
394 protected Command processPacketIn(IOFSwitch sw, OFPacketIn msg, FloodlightContext cntx) {
395 Ethernet eth = IFloodlightProviderService.bcStore.get(cntx,
396 IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
397 Command ret = Command.STOP;
398 String srcNetwork = macToGuid.get(eth.getSourceMAC());
399 // If the host is on an unknown network we deny it.
400 // We make exceptions for ARP and DHCP.
401 if (eth.isBroadcast() || eth.isMulticast() || isDefaultGateway(eth) || isDhcpPacket(eth)) {
402 ret = Command.CONTINUE;
403 } else if (srcNetwork == null) {
404 log.trace("Blocking traffic from host {} because it is not attached to any network.",
405 HexString.toHexString(eth.getSourceMACAddress()));
406 ret = Command.STOP;
407 } else if (oneSameNetwork(eth.getSourceMAC(), eth.getDestinationMAC())) {
408 // if they are on the same network continue
409 ret = Command.CONTINUE;
410 }
411
412 if (log.isTraceEnabled())
413 log.trace("Results for flow between {} and {} is {}",
414 new Object[] {eth.getSourceMAC(), eth.getDestinationMAC(), ret});
415 /*
416 * TODO - figure out how to still detect gateways while using
417 * drop mods
418 if (ret == Command.STOP) {
419 if (!(eth.getPayload() instanceof ARP))
420 doDropFlow(sw, msg, cntx);
421 }
422 */
423 return ret;
424 }
425
426 /**
427 * Writes a FlowMod to a switch that inserts a drop flow.
428 * @param sw The switch to write the FlowMod to.
429 * @param pi The corresponding OFPacketIn. Used to create the OFMatch structure.
430 * @param cntx The FloodlightContext that gets passed to the switch.
431 */
432 protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) {
433 if (log.isTraceEnabled()) {
434 log.trace("doDropFlow pi={} srcSwitch={}",
435 new Object[] { pi, sw });
436 }
437
438 if (sw == null) {
439 log.warn("Switch is null, not installing drop flowmod for PacketIn {}", pi);
440 return;
441 }
442
443 // Create flow-mod based on packet-in and src-switch
444 OFFlowMod fm =
445 (OFFlowMod) floodlightProvider.getOFMessageFactory().getMessage(OFType.FLOW_MOD);
446 OFMatch match = new OFMatch();
447 match.loadFromPacket(pi.getPacketData(), pi.getInPort());
448 List<OFAction> actions = new ArrayList<OFAction>(); // no actions = drop
449 long cookie = AppCookie.makeCookie(APP_ID, 0);
450 fm.setCookie(cookie)
451 .setIdleTimeout(ForwardingBase.FLOWMOD_DEFAULT_IDLE_TIMEOUT)
452 .setHardTimeout(ForwardingBase.FLOWMOD_DEFAULT_HARD_TIMEOUT)
453 .setBufferId(OFPacketOut.BUFFER_ID_NONE)
454 .setMatch(match)
455 .setActions(actions)
456 .setLengthU(OFFlowMod.MINIMUM_LENGTH);
457 fm.setFlags(OFFlowMod.OFPFF_SEND_FLOW_REM);
458 try {
459 if (log.isTraceEnabled()) {
460 log.trace("write drop flow-mod srcSwitch={} match={} " +
461 "pi={} flow-mod={}",
462 new Object[] {sw, match, pi, fm});
463 }
464 sw.write(fm, cntx);
465 } catch (IOException e) {
466 log.error("Failure writing drop flow mod", e);
467 }
468 return;
469 }
470
471 // IDeviceListener
472
473 @Override
474 public void deviceAdded(IDevice device) {
475 if (device.getIPv4Addresses() == null) return;
476 for (Integer i : device.getIPv4Addresses()) {
477 if (gatewayToGuid.containsKey(i)) {
478 MACAddress mac = MACAddress.valueOf(device.getMACAddress());
479 if (log.isDebugEnabled())
480 log.debug("Adding MAC {} with IP {} a a gateway",
481 HexString.toHexString(mac.toBytes()),
482 IPv4.fromIPv4Address(i));
483 macToGateway.put(mac, i);
484 }
485 }
486 }
487
488 @Override
489 public void deviceRemoved(IDevice device) {
490 // if device is a gateway remove
491 MACAddress mac = MACAddress.valueOf(device.getMACAddress());
492 if (macToGateway.containsKey(mac)) {
493 if (log.isDebugEnabled())
494 log.debug("Removing MAC {} as a gateway",
495 HexString.toHexString(mac.toBytes()));
496 macToGateway.remove(mac);
497 }
498 }
499
500 @Override
501 public void deviceIPV4AddrChanged(IDevice device) {
502 // add or remove entry as gateway
503 deviceAdded(device);
504 }
505
506 @Override
507 public void deviceMoved(IDevice device) {
508 // ignore
509 }
510
511 @Override
512 public void deviceVlanChanged(IDevice device) {
513 // ignore
514 }
515
516 @Override
517 public Collection <VirtualNetwork> listNetworks() {
518 return vNetsByGuid.values();
519
520 }
521}