/** * Copyright 2013, Big Switch Networks, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. **/ package net.floodlightcontroller.loadbalancer; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.openflow.protocol.OFFlowMod; import org.openflow.protocol.OFMatch; import org.openflow.protocol.OFMessage; import org.openflow.protocol.OFPacketIn; import org.openflow.protocol.OFPacketOut; import org.openflow.protocol.OFPort; import org.openflow.protocol.OFType; import org.openflow.protocol.action.OFAction; import org.openflow.protocol.action.OFActionDataLayerDestination; import org.openflow.protocol.action.OFActionDataLayerSource; import org.openflow.protocol.action.OFActionEnqueue; import org.openflow.protocol.action.OFActionNetworkLayerDestination; import org.openflow.protocol.action.OFActionNetworkLayerSource; import org.openflow.protocol.action.OFActionNetworkTypeOfService; import org.openflow.protocol.action.OFActionOutput; import org.openflow.protocol.action.OFActionStripVirtualLan; import org.openflow.protocol.action.OFActionTransportLayerDestination; import org.openflow.protocol.action.OFActionTransportLayerSource; import org.openflow.protocol.action.OFActionVirtualLanIdentifier; import org.openflow.protocol.action.OFActionVirtualLanPriorityCodePoint; import org.openflow.util.HexString; import org.openflow.util.U16; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.counter.ICounterStoreService; import net.floodlightcontroller.devicemanager.IDevice; import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.devicemanager.SwitchPort; import net.floodlightcontroller.packet.ARP; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.ICMP; import net.floodlightcontroller.packet.IPacket; import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.packet.TCP; import net.floodlightcontroller.packet.UDP; import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.routing.Route; import net.floodlightcontroller.staticflowentry.IStaticFlowEntryPusherService; import net.floodlightcontroller.topology.ITopologyService; import net.floodlightcontroller.topology.NodePortTuple; import net.floodlightcontroller.util.MACAddress; import net.floodlightcontroller.util.OFMessageDamper; /** * A simple load balancer module for ping, tcp, and udp flows. This module is accessed * via a REST API defined close to the OpenStack Quantum LBaaS (Load-balancer-as-a-Service) * v1.0 API proposal. Since the proposal has not been final, no efforts have yet been * made to confirm compatibility at this time. * * Limitations: * - client records and static flows not purged after use, will exhaust switch flow tables over time * - round robin policy among servers based on connections, not traffic volume * - health monitoring feature not implemented yet * * @author kcwang */ public class LoadBalancer implements IFloodlightModule, ILoadBalancerService, IOFMessageListener { protected static Logger log = LoggerFactory.getLogger(LoadBalancer.class); // Our dependencies protected IFloodlightProviderService floodlightProvider; protected IRestApiService restApi; protected ICounterStoreService counterStore; protected OFMessageDamper messageDamper; protected IDeviceService deviceManager; protected IRoutingService routingEngine; protected ITopologyService topology; protected IStaticFlowEntryPusherService sfp; protected HashMap<String, LBVip> vips; protected HashMap<String, LBPool> pools; protected HashMap<String, LBMember> members; protected HashMap<Integer, String> vipIpToId; protected HashMap<Integer, MACAddress> vipIpToMac; protected HashMap<Integer, String> memberIpToId; protected HashMap<IPClient, LBMember> clientToMember; //Copied from Forwarding with message damper routine for pushing proxy Arp protected static int OFMESSAGE_DAMPER_CAPACITY = 10000; // ms. protected static int OFMESSAGE_DAMPER_TIMEOUT = 250; // ms protected static String LB_ETHER_TYPE = "0x800"; protected static int LB_PRIORITY = 32768; // Comparator for sorting by SwitchCluster public Comparator<SwitchPort> clusterIdComparator = new Comparator<SwitchPort>() { @Override public int compare(SwitchPort d1, SwitchPort d2) { Long d1ClusterId = topology.getL2DomainId(d1.getSwitchDPID()); Long d2ClusterId = topology.getL2DomainId(d2.getSwitchDPID()); return d1ClusterId.compareTo(d2ClusterId); } }; // data structure for storing connected public class IPClient { int ipAddress; byte nw_proto; short srcPort; // tcp/udp src port. icmp type (OFMatch convention) short targetPort; // tcp/udp dst port, icmp code (OFMatch convention) public IPClient() { ipAddress = 0; nw_proto = 0; srcPort = -1; targetPort = -1; } } @Override public String getName() { return "loadbalancer"; } @Override public boolean isCallbackOrderingPrereq(OFType type, String name) { return (type.equals(OFType.PACKET_IN) && (name.equals("topology") || name.equals("devicemanager") || name.equals("virtualizer"))); } @Override public boolean isCallbackOrderingPostreq(OFType type, String name) { return (type.equals(OFType.PACKET_IN) && name.equals("forwarding")); } @Override public net.floodlightcontroller.core.IListener.Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { switch (msg.getType()) { case PACKET_IN: return processPacketIn(sw, (OFPacketIn)msg, cntx); default: break; } log.warn("Received unexpected message {}", msg); return Command.CONTINUE; } private net.floodlightcontroller.core.IListener.Command processPacketIn(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); IPacket pkt = eth.getPayload(); if (eth.isBroadcast() || eth.isMulticast()) { // handle ARP for VIP if (pkt instanceof ARP) { // retrieve arp to determine target IP address ARP arpRequest = (ARP) eth.getPayload(); int targetProtocolAddress = IPv4.toIPv4Address(arpRequest .getTargetProtocolAddress()); if (vipIpToId.containsKey(targetProtocolAddress)) { String vipId = vipIpToId.get(targetProtocolAddress); vipProxyArpReply(sw, pi, cntx, vipId); return Command.STOP; } } } else { // currently only load balance IPv4 packets - no-op for other traffic if (pkt instanceof IPv4) { IPv4 ip_pkt = (IPv4) pkt; // If match Vip and port, check pool and choose member int destIpAddress = ip_pkt.getDestinationAddress(); if (vipIpToId.containsKey(destIpAddress)){ IPClient client = new IPClient(); client.ipAddress = ip_pkt.getSourceAddress(); client.nw_proto = ip_pkt.getProtocol(); if (ip_pkt.getPayload() instanceof TCP) { TCP tcp_pkt = (TCP) ip_pkt.getPayload(); client.srcPort = tcp_pkt.getSourcePort(); client.targetPort = tcp_pkt.getDestinationPort(); } if (ip_pkt.getPayload() instanceof UDP) { UDP udp_pkt = (UDP) ip_pkt.getPayload(); client.srcPort = udp_pkt.getSourcePort(); client.targetPort = udp_pkt.getDestinationPort(); } if (ip_pkt.getPayload() instanceof ICMP) { client.srcPort = 8; client.targetPort = 0; } LBVip vip = vips.get(vipIpToId.get(destIpAddress)); LBPool pool = pools.get(vip.pickPool(client)); LBMember member = members.get(pool.pickMember(client)); // for chosen member, check device manager and find and push routes, in both directions pushBidirectionalVipRoutes(sw, pi, cntx, client, member); // packet out based on table rule pushPacket(pkt, sw, pi.getBufferId(), pi.getInPort(), OFPort.OFPP_TABLE.getValue(), cntx, true); return Command.STOP; } } } // bypass non-load-balanced traffic for normal processing (forwarding) return Command.CONTINUE; } /** * used to send proxy Arp for load balanced service requests * @param IOFSwitch sw * @param OFPacketIn pi * @param FloodlightContext cntx * @param String vipId */ protected void vipProxyArpReply(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx, String vipId) { log.debug("vipProxyArpReply"); Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); // retrieve original arp to determine host configured gw IP address if (! (eth.getPayload() instanceof ARP)) return; ARP arpRequest = (ARP) eth.getPayload(); // have to do proxy arp reply since at this point we cannot determine the requesting application type byte[] vipProxyMacBytes = vips.get(vipId).proxyMac.toBytes(); // generate proxy ARP reply IPacket arpReply = new Ethernet() .setSourceMACAddress(vipProxyMacBytes) .setDestinationMACAddress(eth.getSourceMACAddress()) .setEtherType(Ethernet.TYPE_ARP) .setVlanID(eth.getVlanID()) .setPriorityCode(eth.getPriorityCode()) .setPayload( new ARP() .setHardwareType(ARP.HW_TYPE_ETHERNET) .setProtocolType(ARP.PROTO_TYPE_IP) .setHardwareAddressLength((byte) 6) .setProtocolAddressLength((byte) 4) .setOpCode(ARP.OP_REPLY) .setSenderHardwareAddress(vipProxyMacBytes) .setSenderProtocolAddress( arpRequest.getTargetProtocolAddress()) .setTargetHardwareAddress( eth.getSourceMACAddress()) .setTargetProtocolAddress( arpRequest.getSenderProtocolAddress())); // push ARP reply out pushPacket(arpReply, sw, OFPacketOut.BUFFER_ID_NONE, OFPort.OFPP_NONE.getValue(), pi.getInPort(), cntx, true); log.debug("proxy ARP reply pushed as {}", IPv4.fromIPv4Address(vips.get(vipId).address)); return; } /** * used to push any packet - borrowed routine from Forwarding * * @param OFPacketIn pi * @param IOFSwitch sw * @param int bufferId * @param short inPort * @param short outPort * @param FloodlightContext cntx * @param boolean flush */ public void pushPacket(IPacket packet, IOFSwitch sw, int bufferId, short inPort, short outPort, FloodlightContext cntx, boolean flush) { if (log.isTraceEnabled()) { log.trace("PacketOut srcSwitch={} inPort={} outPort={}", new Object[] {sw, inPort, outPort}); } OFPacketOut po = (OFPacketOut) floodlightProvider.getOFMessageFactory() .getMessage(OFType.PACKET_OUT); // set actions List<OFAction> actions = new ArrayList<OFAction>(); actions.add(new OFActionOutput(outPort, (short) 0xffff)); po.setActions(actions) .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH); short poLength = (short) (po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH); // set buffer_id, in_port po.setBufferId(bufferId); po.setInPort(inPort); // set data - only if buffer_id == -1 if (po.getBufferId() == OFPacketOut.BUFFER_ID_NONE) { if (packet == null) { log.error("BufferId is not set and packet data is null. " + "Cannot send packetOut. " + "srcSwitch={} inPort={} outPort={}", new Object[] {sw, inPort, outPort}); return; } byte[] packetData = packet.serialize(); poLength += packetData.length; po.setPacketData(packetData); } po.setLength(poLength); try { counterStore.updatePktOutFMCounterStoreLocal(sw, po); messageDamper.write(sw, po, cntx, flush); } catch (IOException e) { log.error("Failure writing packet out", e); } } /** * used to find and push in-bound and out-bound routes using StaticFlowEntryPusher * @param IOFSwitch sw * @param OFPacketIn pi * @param FloodlightContext cntx * @param IPClient client * @param LBMember member */ protected void pushBidirectionalVipRoutes(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx, IPClient client, LBMember member) { // borrowed code from Forwarding to retrieve src and dst device entities // Check if we have the location of the destination IDevice srcDevice = null; IDevice dstDevice = null; // retrieve all known devices Collection<? extends IDevice> allDevices = deviceManager .getAllDevices(); for (IDevice d : allDevices) { for (int j = 0; j < d.getIPv4Addresses().length; j++) { if (srcDevice == null && client.ipAddress == d.getIPv4Addresses()[j]) srcDevice = d; if (dstDevice == null && member.address == d.getIPv4Addresses()[j]) { dstDevice = d; member.macString = dstDevice.getMACAddressString(); } if (srcDevice != null && dstDevice != null) break; } } // srcDevice and/or dstDevice is null, no route can be pushed if (srcDevice == null || dstDevice == null) return; Long srcIsland = topology.getL2DomainId(sw.getId()); if (srcIsland == null) { log.debug("No openflow island found for source {}/{}", sw.getStringId(), pi.getInPort()); return; } // Validate that we have a destination known on the same island // Validate that the source and destination are not on the same switchport boolean on_same_island = false; boolean on_same_if = false; for (SwitchPort dstDap : dstDevice.getAttachmentPoints()) { long dstSwDpid = dstDap.getSwitchDPID(); Long dstIsland = topology.getL2DomainId(dstSwDpid); if ((dstIsland != null) && dstIsland.equals(srcIsland)) { on_same_island = true; if ((sw.getId() == dstSwDpid) && (pi.getInPort() == dstDap.getPort())) { on_same_if = true; } break; } } if (!on_same_island) { // Flood since we don't know the dst device if (log.isTraceEnabled()) { log.trace("No first hop island found for destination " + "device {}, Action = flooding", dstDevice); } return; } if (on_same_if) { if (log.isTraceEnabled()) { log.trace("Both source and destination are on the same " + "switch/port {}/{}, Action = NOP", sw.toString(), pi.getInPort()); } return; } // Install all the routes where both src and dst have attachment // points. Since the lists are stored in sorted order we can // traverse the attachment points in O(m+n) time SwitchPort[] srcDaps = srcDevice.getAttachmentPoints(); Arrays.sort(srcDaps, clusterIdComparator); SwitchPort[] dstDaps = dstDevice.getAttachmentPoints(); Arrays.sort(dstDaps, clusterIdComparator); int iSrcDaps = 0, iDstDaps = 0; // following Forwarding's same routing routine, retrieve both in-bound and out-bound routes for // all clusters. while ((iSrcDaps < srcDaps.length) && (iDstDaps < dstDaps.length)) { SwitchPort srcDap = srcDaps[iSrcDaps]; SwitchPort dstDap = dstDaps[iDstDaps]; Long srcCluster = topology.getL2DomainId(srcDap.getSwitchDPID()); Long dstCluster = topology.getL2DomainId(dstDap.getSwitchDPID()); int srcVsDest = srcCluster.compareTo(dstCluster); if (srcVsDest == 0) { if (!srcDap.equals(dstDap) && (srcCluster != null) && (dstCluster != null)) { Route routeIn = routingEngine.getRoute(srcDap.getSwitchDPID(), (short)srcDap.getPort(), dstDap.getSwitchDPID(), (short)dstDap.getPort(), 0); Route routeOut = routingEngine.getRoute(dstDap.getSwitchDPID(), (short)dstDap.getPort(), srcDap.getSwitchDPID(), (short)srcDap.getPort(), 0); // use static flow entry pusher to push flow mod along in and out path // in: match src client (ip, port), rewrite dest from vip ip/port to member ip/port, forward // out: match dest client (ip, port), rewrite src from member ip/port to vip ip/port, forward if (routeIn != null) { pushStaticVipRoute(true, routeIn, client, member, sw.getId()); } if (routeOut != null) { pushStaticVipRoute(false, routeOut, client, member, sw.getId()); } } iSrcDaps++; iDstDaps++; } else if (srcVsDest < 0) { iSrcDaps++; } else { iDstDaps++; } } return; } /** * used to push given route using static flow entry pusher * @param boolean inBound * @param Route route * @param IPClient client * @param LBMember member * @param long pinSwitch */ public void pushStaticVipRoute(boolean inBound, Route route, IPClient client, LBMember member, long pinSwitch) { List<NodePortTuple> path = route.getPath(); if (path.size()>0) { for (int i = 0; i < path.size(); i+=2) { long sw = path.get(i).getNodeId(); String swString = HexString.toHexString(path.get(i).getNodeId()); String entryName; String matchString = null; String actionString = null; OFFlowMod fm = (OFFlowMod) floodlightProvider.getOFMessageFactory() .getMessage(OFType.FLOW_MOD); fm.setIdleTimeout((short) 0); // infinite fm.setHardTimeout((short) 0); // infinite fm.setBufferId(OFPacketOut.BUFFER_ID_NONE); fm.setCommand((short) 0); fm.setFlags((short) 0); fm.setOutPort(OFPort.OFPP_NONE.getValue()); fm.setCookie((long) 0); fm.setPriority(Short.MAX_VALUE); if (inBound) { entryName = "inbound-vip-"+ member.vipId+"-client-"+client.ipAddress+"-port-"+client.targetPort +"-srcswitch-"+path.get(0).getNodeId()+"-sw-"+sw; matchString = "nw_src="+IPv4.fromIPv4Address(client.ipAddress)+"," + "nw_proto="+String.valueOf(client.nw_proto)+"," + "tp_src="+String.valueOf(client.srcPort & 0xffff)+"," + "dl_type="+LB_ETHER_TYPE+"," + "in_port="+String.valueOf(path.get(i).getPortId()); if (sw == pinSwitch) { actionString = "set-dst-ip="+IPv4.fromIPv4Address(member.address)+"," + "set-dst-mac="+member.macString+"," + "output="+path.get(i+1).getPortId(); } else { actionString = "output="+path.get(i+1).getPortId(); } } else { entryName = "outbound-vip-"+ member.vipId+"-client-"+client.ipAddress+"-port-"+client.targetPort +"-srcswitch-"+path.get(0).getNodeId()+"-sw-"+sw; matchString = "nw_dst="+IPv4.fromIPv4Address(client.ipAddress)+"," + "nw_proto="+String.valueOf(client.nw_proto)+"," + "tp_dst="+String.valueOf(client.srcPort & 0xffff)+"," + "dl_type="+LB_ETHER_TYPE+"," + "in_port="+String.valueOf(path.get(i).getPortId()); if (sw == pinSwitch) { actionString = "set-src-ip="+IPv4.fromIPv4Address(vips.get(member.vipId).address)+"," + "set-src-mac="+vips.get(member.vipId).proxyMac.toString()+"," + "output="+path.get(i+1).getPortId(); } else { actionString = "output="+path.get(i+1).getPortId(); } } parseActionString(fm, actionString, log); fm.setPriority(U16.t(LB_PRIORITY)); OFMatch ofMatch = new OFMatch(); try { ofMatch.fromString(matchString); } catch (IllegalArgumentException e) { log.debug("ignoring flow entry {} on switch {} with illegal OFMatch() key: " + matchString, entryName, swString); } fm.setMatch(ofMatch); sfp.addFlow(entryName, fm, swString); } } return; } @Override public Collection<LBVip> listVips() { return vips.values(); } @Override public Collection<LBVip> listVip(String vipId) { Collection<LBVip> result = new HashSet<LBVip>(); result.add(vips.get(vipId)); return result; } @Override public LBVip createVip(LBVip vip) { if (vip == null) vip = new LBVip(); vips.put(vip.id, vip); vipIpToId.put(vip.address, vip.id); vipIpToMac.put(vip.address, vip.proxyMac); return vip; } @Override public LBVip updateVip(LBVip vip) { vips.put(vip.id, vip); return vip; } @Override public int removeVip(String vipId) { if(vips.containsKey(vipId)){ vips.remove(vipId); return 0; } else { return -1; } } @Override public Collection<LBPool> listPools() { return pools.values(); } @Override public Collection<LBPool> listPool(String poolId) { Collection<LBPool> result = new HashSet<LBPool>(); result.add(pools.get(poolId)); return result; } @Override public LBPool createPool(LBPool pool) { if (pool==null) pool = new LBPool(); pools.put(pool.id, pool); if (pool.vipId != null && vips.containsKey(pool.vipId)) vips.get(pool.vipId).pools.add(pool.id); else { log.error("specified vip-id must exist"); pool.vipId = null; pools.put(pool.id, pool); } return pool; } @Override public LBPool updatePool(LBPool pool) { pools.put(pool.id, pool); return null; } @Override public int removePool(String poolId) { LBPool pool; if(pools!=null){ pool = pools.get(poolId); if (pool.vipId != null) vips.get(pool.vipId).pools.remove(poolId); pools.remove(poolId); return 0; } else { return -1; } } @Override public Collection<LBMember> listMembers() { return members.values(); } @Override public Collection<LBMember> listMember(String memberId) { Collection<LBMember> result = new HashSet<LBMember>(); result.add(members.get(memberId)); return result; } @Override public Collection<LBMember> listMembersByPool(String poolId) { Collection<LBMember> result = new HashSet<LBMember>(); if(pools.containsKey(poolId)) { ArrayList<String> memberIds = pools.get(poolId).members; for (int i=0; i<memberIds.size(); i++) result.add(members.get(memberIds.get(i))); } return result; } @Override public LBMember createMember(LBMember member) { if (member == null) member = new LBMember(); members.put(member.id, member); memberIpToId.put(member.address, member.id); if (member.poolId != null && pools.get(member.poolId) != null) { member.vipId = pools.get(member.poolId).vipId; if (!pools.get(member.poolId).members.contains(member.id)) pools.get(member.poolId).members.add(member.id); } else log.error("member must be specified with non-null pool_id"); return member; } @Override public LBMember updateMember(LBMember member) { members.put(member.id, member); return member; } @Override public int removeMember(String memberId) { LBMember member; member = members.get(memberId); if(member != null){ if (member.poolId != null) pools.get(member.poolId).members.remove(memberId); members.remove(memberId); return 0; } else { return -1; } } @Override public Collection<LBMonitor> listMonitors() { // TODO Auto-generated method stub return null; } @Override public Collection<LBMonitor> listMonitor(String monitorId) { // TODO Auto-generated method stub return null; } @Override public LBMonitor createMonitor(LBMonitor monitor) { // TODO Auto-generated method stub return null; } @Override public LBMonitor updateMonitor(LBMonitor monitor) { // TODO Auto-generated method stub return null; } @Override public int removeMonitor(String monitorId) { // TODO Auto-generated method stub return 0; } @Override public Collection<Class<? extends IFloodlightService>> getModuleServices() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(ILoadBalancerService.class); return l; } @Override public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); m.put(ILoadBalancerService.class, this); return m; } @Override public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IFloodlightProviderService.class); l.add(IRestApiService.class); l.add(ICounterStoreService.class); l.add(IDeviceService.class); l.add(ITopologyService.class); l.add(IRoutingService.class); l.add(IStaticFlowEntryPusherService.class); return l; } @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); restApi = context.getServiceImpl(IRestApiService.class); counterStore = context.getServiceImpl(ICounterStoreService.class); deviceManager = context.getServiceImpl(IDeviceService.class); routingEngine = context.getServiceImpl(IRoutingService.class); topology = context.getServiceImpl(ITopologyService.class); sfp = context.getServiceImpl(IStaticFlowEntryPusherService.class); messageDamper = new OFMessageDamper(OFMESSAGE_DAMPER_CAPACITY, EnumSet.of(OFType.FLOW_MOD), OFMESSAGE_DAMPER_TIMEOUT); vips = new HashMap<String, LBVip>(); pools = new HashMap<String, LBPool>(); members = new HashMap<String, LBMember>(); vipIpToId = new HashMap<Integer, String>(); vipIpToMac = new HashMap<Integer, MACAddress>(); memberIpToId = new HashMap<Integer, String>(); } @Override public void startUp(FloodlightModuleContext context) { floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); restApi.addRestletRoutable(new LoadBalancerWebRoutable()); } // Utilities borrowed from StaticFlowEntries private static class SubActionStruct { OFAction action; int len; } /** * Parses OFFlowMod actions from strings. * @param flowMod The OFFlowMod to set the actions for * @param actionstr The string containing all the actions * @param log A logger to log for errors. */ public static void parseActionString(OFFlowMod flowMod, String actionstr, Logger log) { List<OFAction> actions = new LinkedList<OFAction>(); int actionsLength = 0; if (actionstr != null) { actionstr = actionstr.toLowerCase(); for (String subaction : actionstr.split(",")) { String action = subaction.split("[=:]")[0]; SubActionStruct subaction_struct = null; if (action.equals("output")) { subaction_struct = decode_output(subaction, log); } else if (action.equals("enqueue")) { subaction_struct = decode_enqueue(subaction, log); } else if (action.equals("strip-vlan")) { subaction_struct = decode_strip_vlan(subaction, log); } else if (action.equals("set-vlan-id")) { subaction_struct = decode_set_vlan_id(subaction, log); } else if (action.equals("set-vlan-priority")) { subaction_struct = decode_set_vlan_priority(subaction, log); } else if (action.equals("set-src-mac")) { subaction_struct = decode_set_src_mac(subaction, log); } else if (action.equals("set-dst-mac")) { subaction_struct = decode_set_dst_mac(subaction, log); } else if (action.equals("set-tos-bits")) { subaction_struct = decode_set_tos_bits(subaction, log); } else if (action.equals("set-src-ip")) { subaction_struct = decode_set_src_ip(subaction, log); } else if (action.equals("set-dst-ip")) { subaction_struct = decode_set_dst_ip(subaction, log); } else if (action.equals("set-src-port")) { subaction_struct = decode_set_src_port(subaction, log); } else if (action.equals("set-dst-port")) { subaction_struct = decode_set_dst_port(subaction, log); } else { log.error("Unexpected action '{}', '{}'", action, subaction); } if (subaction_struct != null) { actions.add(subaction_struct.action); actionsLength += subaction_struct.len; } } } log.debug("action {}", actions); flowMod.setActions(actions); flowMod.setLengthU(OFFlowMod.MINIMUM_LENGTH + actionsLength); } private static SubActionStruct decode_output(String subaction, Logger log) { SubActionStruct sa = null; Matcher n; n = Pattern.compile("output=(?:((?:0x)?\\d+)|(all)|(controller)|(local)|(ingress-port)|(normal)|(flood))").matcher(subaction); if (n.matches()) { OFActionOutput action = new OFActionOutput(); action.setMaxLength(Short.MAX_VALUE); short port = OFPort.OFPP_NONE.getValue(); if (n.group(1) != null) { try { port = get_short(n.group(1)); } catch (NumberFormatException e) { log.debug("Invalid port in: '{}' (error ignored)", subaction); return null; } } else if (n.group(2) != null) port = OFPort.OFPP_ALL.getValue(); else if (n.group(3) != null) port = OFPort.OFPP_CONTROLLER.getValue(); else if (n.group(4) != null) port = OFPort.OFPP_LOCAL.getValue(); else if (n.group(5) != null) port = OFPort.OFPP_IN_PORT.getValue(); else if (n.group(6) != null) port = OFPort.OFPP_NORMAL.getValue(); else if (n.group(7) != null) port = OFPort.OFPP_FLOOD.getValue(); action.setPort(port); log.debug("action {}", action); sa = new SubActionStruct(); sa.action = action; sa.len = OFActionOutput.MINIMUM_LENGTH; } else { log.error("Invalid subaction: '{}'", subaction); return null; } return sa; } private static SubActionStruct decode_enqueue(String subaction, Logger log) { SubActionStruct sa = null; Matcher n; n = Pattern.compile("enqueue=(?:((?:0x)?\\d+)\\:((?:0x)?\\d+))").matcher(subaction); if (n.matches()) { short portnum = 0; if (n.group(1) != null) { try { portnum = get_short(n.group(1)); } catch (NumberFormatException e) { log.debug("Invalid port-num in: '{}' (error ignored)", subaction); return null; } } int queueid = 0; if (n.group(2) != null) { try { queueid = get_int(n.group(2)); } catch (NumberFormatException e) { log.debug("Invalid queue-id in: '{}' (error ignored)", subaction); return null; } } OFActionEnqueue action = new OFActionEnqueue(); action.setPort(portnum); action.setQueueId(queueid); log.debug("action {}", action); sa = new SubActionStruct(); sa.action = action; sa.len = OFActionEnqueue.MINIMUM_LENGTH; } else { log.debug("Invalid action: '{}'", subaction); return null; } return sa; } private static SubActionStruct decode_strip_vlan(String subaction, Logger log) { SubActionStruct sa = null; Matcher n = Pattern.compile("strip-vlan").matcher(subaction); if (n.matches()) { OFActionStripVirtualLan action = new OFActionStripVirtualLan(); log.debug("action {}", action); sa = new SubActionStruct(); sa.action = action; sa.len = OFActionStripVirtualLan.MINIMUM_LENGTH; } else { log.debug("Invalid action: '{}'", subaction); return null; } return sa; } private static SubActionStruct decode_set_vlan_id(String subaction, Logger log) { SubActionStruct sa = null; Matcher n = Pattern.compile("set-vlan-id=((?:0x)?\\d+)").matcher(subaction); if (n.matches()) { if (n.group(1) != null) { try { short vlanid = get_short(n.group(1)); OFActionVirtualLanIdentifier action = new OFActionVirtualLanIdentifier(); action.setVirtualLanIdentifier(vlanid); log.debug(" action {}", action); sa = new SubActionStruct(); sa.action = action; sa.len = OFActionVirtualLanIdentifier.MINIMUM_LENGTH; } catch (NumberFormatException e) { log.debug("Invalid VLAN in: {} (error ignored)", subaction); return null; } } } else { log.debug("Invalid action: '{}'", subaction); return null; } return sa; } private static SubActionStruct decode_set_vlan_priority(String subaction, Logger log) { SubActionStruct sa = null; Matcher n = Pattern.compile("set-vlan-priority=((?:0x)?\\d+)").matcher(subaction); if (n.matches()) { if (n.group(1) != null) { try { byte prior = get_byte(n.group(1)); OFActionVirtualLanPriorityCodePoint action = new OFActionVirtualLanPriorityCodePoint(); action.setVirtualLanPriorityCodePoint(prior); log.debug(" action {}", action); sa = new SubActionStruct(); sa.action = action; sa.len = OFActionVirtualLanPriorityCodePoint.MINIMUM_LENGTH; } catch (NumberFormatException e) { log.debug("Invalid VLAN priority in: {} (error ignored)", subaction); return null; } } } else { log.debug("Invalid action: '{}'", subaction); return null; } return sa; } private static SubActionStruct decode_set_src_mac(String subaction, Logger log) { SubActionStruct sa = null; Matcher n = Pattern.compile("set-src-mac=(?:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+))").matcher(subaction); if (n.matches()) { byte[] macaddr = get_mac_addr(n, subaction, log); if (macaddr != null) { OFActionDataLayerSource action = new OFActionDataLayerSource(); action.setDataLayerAddress(macaddr); log.debug("action {}", action); sa = new SubActionStruct(); sa.action = action; sa.len = OFActionDataLayerSource.MINIMUM_LENGTH; } } else { log.debug("Invalid action: '{}'", subaction); return null; } return sa; } private static SubActionStruct decode_set_dst_mac(String subaction, Logger log) { SubActionStruct sa = null; Matcher n = Pattern.compile("set-dst-mac=(?:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+)\\:(\\p{XDigit}+))").matcher(subaction); if (n.matches()) { byte[] macaddr = get_mac_addr(n, subaction, log); if (macaddr != null) { OFActionDataLayerDestination action = new OFActionDataLayerDestination(); action.setDataLayerAddress(macaddr); log.debug(" action {}", action); sa = new SubActionStruct(); sa.action = action; sa.len = OFActionDataLayerDestination.MINIMUM_LENGTH; } } else { log.debug("Invalid action: '{}'", subaction); return null; } return sa; } private static SubActionStruct decode_set_tos_bits(String subaction, Logger log) { SubActionStruct sa = null; Matcher n = Pattern.compile("set-tos-bits=((?:0x)?\\d+)").matcher(subaction); if (n.matches()) { if (n.group(1) != null) { try { byte tosbits = get_byte(n.group(1)); OFActionNetworkTypeOfService action = new OFActionNetworkTypeOfService(); action.setNetworkTypeOfService(tosbits); log.debug(" action {}", action); sa = new SubActionStruct(); sa.action = action; sa.len = OFActionNetworkTypeOfService.MINIMUM_LENGTH; } catch (NumberFormatException e) { log.debug("Invalid dst-port in: {} (error ignored)", subaction); return null; } } } else { log.debug("Invalid action: '{}'", subaction); return null; } return sa; } private static SubActionStruct decode_set_src_ip(String subaction, Logger log) { SubActionStruct sa = null; Matcher n = Pattern.compile("set-src-ip=(?:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))").matcher(subaction); if (n.matches()) { int ipaddr = get_ip_addr(n, subaction, log); OFActionNetworkLayerSource action = new OFActionNetworkLayerSource(); action.setNetworkAddress(ipaddr); log.debug(" action {}", action); sa = new SubActionStruct(); sa.action = action; sa.len = OFActionNetworkLayerSource.MINIMUM_LENGTH; } else { log.debug("Invalid action: '{}'", subaction); return null; } return sa; } private static SubActionStruct decode_set_dst_ip(String subaction, Logger log) { SubActionStruct sa = null; Matcher n = Pattern.compile("set-dst-ip=(?:(\\d+)\\.(\\d+)\\.(\\d+)\\.(\\d+))").matcher(subaction); if (n.matches()) { int ipaddr = get_ip_addr(n, subaction, log); OFActionNetworkLayerDestination action = new OFActionNetworkLayerDestination(); action.setNetworkAddress(ipaddr); log.debug("action {}", action); sa = new SubActionStruct(); sa.action = action; sa.len = OFActionNetworkLayerDestination.MINIMUM_LENGTH; } else { log.debug("Invalid action: '{}'", subaction); return null; } return sa; } private static SubActionStruct decode_set_src_port(String subaction, Logger log) { SubActionStruct sa = null; Matcher n = Pattern.compile("set-src-port=((?:0x)?\\d+)").matcher(subaction); if (n.matches()) { if (n.group(1) != null) { try { short portnum = get_short(n.group(1)); OFActionTransportLayerSource action = new OFActionTransportLayerSource(); action.setTransportPort(portnum); log.debug("action {}", action); sa = new SubActionStruct(); sa.action = action; sa.len = OFActionTransportLayerSource.MINIMUM_LENGTH;; } catch (NumberFormatException e) { log.debug("Invalid src-port in: {} (error ignored)", subaction); return null; } } } else { log.debug("Invalid action: '{}'", subaction); return null; } return sa; } private static SubActionStruct decode_set_dst_port(String subaction, Logger log) { SubActionStruct sa = null; Matcher n = Pattern.compile("set-dst-port=((?:0x)?\\d+)").matcher(subaction); if (n.matches()) { if (n.group(1) != null) { try { short portnum = get_short(n.group(1)); OFActionTransportLayerDestination action = new OFActionTransportLayerDestination(); action.setTransportPort(portnum); log.debug("action {}", action); sa = new SubActionStruct(); sa.action = action; sa.len = OFActionTransportLayerDestination.MINIMUM_LENGTH;; } catch (NumberFormatException e) { log.debug("Invalid dst-port in: {} (error ignored)", subaction); return null; } } } else { log.debug("Invalid action: '{}'", subaction); return null; } return sa; } private static byte[] get_mac_addr(Matcher n, String subaction, Logger log) { byte[] macaddr = new byte[6]; for (int i=0; i<6; i++) { if (n.group(i+1) != null) { try { macaddr[i] = get_byte("0x" + n.group(i+1)); } catch (NumberFormatException e) { log.debug("Invalid src-mac in: '{}' (error ignored)", subaction); return null; } } else { log.debug("Invalid src-mac in: '{}' (null, error ignored)", subaction); return null; } } return macaddr; } private static int get_ip_addr(Matcher n, String subaction, Logger log) { int ipaddr = 0; for (int i=0; i<4; i++) { if (n.group(i+1) != null) { try { ipaddr = ipaddr<<8; ipaddr = ipaddr | get_int(n.group(i+1)); } catch (NumberFormatException e) { log.debug("Invalid src-ip in: '{}' (error ignored)", subaction); return 0; } } else { log.debug("Invalid src-ip in: '{}' (null, error ignored)", subaction); return 0; } } return ipaddr; } // Parse int as decimal, hex (start with 0x or #) or octal (starts with 0) private static int get_int(String str) { return Integer.decode(str); } // Parse short as decimal, hex (start with 0x or #) or octal (starts with 0) private static short get_short(String str) { return (short)(int)Integer.decode(str); } // Parse byte as decimal, hex (start with 0x or #) or octal (starts with 0) private static byte get_byte(String str) { return Integer.decode(str).byteValue(); } }