/** * Copyright 2015, Big Switch Networks, Inc. * Originally created by Pengfei Lu, Network and Cloud Computing Laboratory, Dalian University of Technology, China * Advisers: Keqiu Li, Heng Qi and Haisheng Yu * This work is supported by the State Key Program of National Natural Science of China(Grant No. 61432002) * and Prospective Research Project on Future Networks in Jiangsu Future Networks Innovation Institute. * * 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.accesscontrollist; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import net.floodlightcontroller.accesscontrollist.ACLRule.Action; import net.floodlightcontroller.accesscontrollist.ap.AP; import net.floodlightcontroller.accesscontrollist.ap.APManager; import net.floodlightcontroller.accesscontrollist.util.IPAddressUtil; import net.floodlightcontroller.accesscontrollist.web.ACLWebRoutable; 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.devicemanager.IDevice; import net.floodlightcontroller.devicemanager.IDeviceListener; import net.floodlightcontroller.devicemanager.IDeviceService; import net.floodlightcontroller.devicemanager.SwitchPort; import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.staticentry.StaticEntryPusher; import net.floodlightcontroller.storage.IStorageSourceService; import org.projectfloodlight.openflow.protocol.match.MatchFields; import org.projectfloodlight.openflow.types.IPv4Address; import org.projectfloodlight.openflow.util.HexString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ACL implements IACLService, IFloodlightModule, IDeviceListener { protected IRestApiService restApi; protected IDeviceService deviceManager; protected IStorageSourceService storageSource; protected static Logger logger; private APManager apManager; private int lastRuleId = 1; // rule id counter private Map<Integer, ACLRule> aclRules; private Map<String, Integer> dpid2FlowPriority; private Map<Integer, Set<String>> ruleId2Dpid; private Map<Integer, Set<String>> ruleId2FlowName; private Map<Integer, List<Integer>> deny2Allow; private final int DEFAULT_PRIORITY = 30000; /** * Checks if an existing ACL rule already works in a given switch. */ private boolean checkIfRuleWorksInSwitch(int ruleId, String dpid) { return ruleId2Dpid.containsKey(ruleId) && ruleId2Dpid.get(ruleId).contains(dpid); } /** * Adds a new mapping from ACL rule to ACL flow. */ private void addRuleToFlowMapping(int ruleId, String flowName) { if (!ruleId2FlowName.containsKey(ruleId)) { ruleId2FlowName.put(ruleId, new HashSet<String>()); } ruleId2FlowName.get(ruleId).add(flowName); } /** * Adds a new mapping from ACL rule to switch. */ private void addRuleToSwitchMapping(int ruleId, String dpid) { if (!ruleId2Dpid.containsKey(ruleId)) { ruleId2Dpid.put(ruleId, new HashSet<String>()); } ruleId2Dpid.get(ruleId).add(dpid); } /** * Gets the current priority for new ACL flow by device id. */ private int getPriorityBySwitch(String dpid) { if (!dpid2FlowPriority.containsKey(dpid)) { dpid2FlowPriority.put(dpid, DEFAULT_PRIORITY - 1); return DEFAULT_PRIORITY; } else { int priority = dpid2FlowPriority.get(dpid); dpid2FlowPriority.put(dpid, priority - 1); return priority; } } @Override public List<ACLRule> getRules() { return new ArrayList<ACLRule>(aclRules.values()); } /** * Checks if the new ACL rule matches an existing rule. If existing allowing * rules matches the new denying rule, store the mappings. * * @return true if the new ACL rule matches an existing rule, false * otherwise */ private boolean checkRuleMatch(ACLRule newRule) { List<Integer> allowRuleList = new ArrayList<>(); for (ACLRule existingRule : getRules()) { if (newRule.match(existingRule)) { return true; } if (existingRule.getAction() == Action.ALLOW && newRule.getAction() == Action.DENY) { if (existingRule.match(newRule)) { allowRuleList.add(existingRule.getId()); } } } deny2Allow.put(newRule.getId(), allowRuleList); return false; } @Override public boolean addRule(ACLRule rule) { rule.setId(lastRuleId++); if (checkRuleMatch(rule)) { lastRuleId--; return false; } aclRules.put(rule.getId(), rule); logger.info("ACL rule(id:{}) is added.", rule.getId()); if (rule.getAction() != Action.ALLOW) { enforceAddedRule(rule); } return true; } @Override public void removeRule(int ruleId) { aclRules.remove(ruleId); logger.info("ACL rule(id:{}) is removed.", ruleId); enforceRemovedRule(ruleId); } @Override public void removeAllRules() { this.lastRuleId = 1; this.aclRules = new TreeMap<>(); this.dpid2FlowPriority = new HashMap<>(); this.ruleId2Dpid = new HashMap<>(); this.deny2Allow = new HashMap<>(); for (Set<String> flowNameSet : ruleId2FlowName.values()) { for (String flowName : flowNameSet) { storageSource.deleteRowAsync(StaticEntryPusher.TABLE_NAME, flowName); logger.debug("ACL flow(id:{}) is removed.", flowName); } } this.ruleId2FlowName = new HashMap<>(); } /** * Enforces denying ACL rule by ACL flow. */ private void enforceAddedRule(ACLRule denyRule) { Set<String> dpidSet; if (denyRule.getNw_src() != null) { dpidSet = apManager.getDpidSet(denyRule.getNw_src_prefix(), denyRule.getNw_src_maskbits()); } else { dpidSet = apManager.getDpidSet(denyRule.getNw_dst_prefix(), denyRule.getNw_dst_maskbits()); } for (String dpid : dpidSet) { String flowName; List<Integer> allowRuleList = deny2Allow.get(denyRule.getId()); for (int allowRuleId : allowRuleList) { flowName = "ACLRule_" + allowRuleId + "_" + dpid; generateFlow(aclRules.get(allowRuleId), dpid, flowName); } flowName = "ACLRule_" + denyRule.getId() + "_" + dpid; generateFlow(denyRule, dpid, flowName); } } /** * Enforces removing an existing ACL rule. */ private void enforceRemovedRule(int ruleId) { if (ruleId2FlowName.containsKey(ruleId)) { for (String flowName : ruleId2FlowName.get(ruleId)) { storageSource.deleteRowAsync(StaticEntryPusher.TABLE_NAME, flowName); logger.debug("ACL flow(id:{}) is removed.", flowName); } ruleId2FlowName.remove(ruleId); } ruleId2Dpid.remove(ruleId); deny2Allow.remove(ruleId); } /** * Generates ACL flow rule according to ACL rule * and installs it into switch. */ private void generateFlow(ACLRule rule, String dpid, String flowName) { if (rule == null || checkIfRuleWorksInSwitch(rule.getId(), dpid)) { return; } int priority = getPriorityBySwitch(dpid); if (rule.getNw_src() != null) { HashMap<String, Object> flow = new HashMap<String, Object>(); flow.put(StaticEntryPusher.Columns.COLUMN_SWITCH, dpid); flow.put(StaticEntryPusher.Columns.COLUMN_NAME, flowName); flow.put(StaticEntryPusher.Columns.COLUMN_ACTIVE, Boolean.toString(true)); flow.put(StaticEntryPusher.Columns.COLUMN_COOKIE, "0"); flow.put(StaticEntryPusher.Columns.COLUMN_PRIORITY, Integer.toString(priority)); flow.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.ETH_TYPE), "2048"); flow.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.IPV4_SRC), rule.getNw_src()); if (rule.getNw_dst() != null) { flow.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.IPV4_DST), rule.getNw_dst()); } if (rule.getNw_proto() != 0) { flow.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.IP_PROTO), Integer.toString(rule.getNw_proto())); } if (rule.getAction() == Action.ALLOW) { flow.put(StaticEntryPusher.Columns.COLUMN_ACTIONS, "output=controller"); } if (rule.getTp_dst() != 0) { flow.put(StaticEntryPusher.Columns.COLUMN_TP_DST, Integer.toString(rule.getTp_dst())); } storageSource .insertRowAsync(StaticEntryPusher.TABLE_NAME, flow); } else { HashMap<String, Object> flow = new HashMap<String, Object>(); flow.put(StaticEntryPusher.Columns.COLUMN_SWITCH, dpid); flow.put(StaticEntryPusher.Columns.COLUMN_NAME, flowName); flow.put(StaticEntryPusher.Columns.COLUMN_ACTIVE, Boolean.toString(true)); flow.put(StaticEntryPusher.Columns.COLUMN_COOKIE, "0"); flow.put(StaticEntryPusher.Columns.COLUMN_PRIORITY, Integer.toString(priority)); flow.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.ETH_TYPE), "2048"); flow.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.IPV4_DST), rule.getNw_dst()); if (rule.getNw_proto() != 0) { flow.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.IP_PROTO), Integer.toString(rule.getNw_proto())); } if (rule.getAction() == Action.ALLOW) { flow.put(StaticEntryPusher.Columns.COLUMN_ACTIONS, "output=controller"); } if (rule.getTp_dst() != 0) { flow.put(StaticEntryPusher.Columns.COLUMN_TP_DST, Integer.toString(rule.getTp_dst())); } storageSource .insertRowAsync(StaticEntryPusher.TABLE_NAME, flow); } addRuleToSwitchMapping(rule.getId(), dpid); addRuleToFlowMapping(rule.getId(), flowName); logger.debug("ACL flow(id:{}) is added in {}.", flowName, dpid); } @Override public Collection<Class<? extends IFloodlightService>> getModuleServices() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IACLService.class); return l; } @Override public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); // We are the class that implements the service m.put(IACLService.class, this); return m; } @Override public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IRestApiService.class); l.add(IDeviceService.class); return l; } @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { restApi = context.getServiceImpl(IRestApiService.class); deviceManager = context.getServiceImpl(IDeviceService.class); logger = LoggerFactory.getLogger(ACL.class); storageSource = context.getServiceImpl(IStorageSourceService.class); aclRules = new TreeMap<>(); apManager = new APManager(); ruleId2FlowName = new HashMap<>(); ruleId2Dpid = new HashMap<>(); dpid2FlowPriority = new HashMap<>(); deny2Allow = new HashMap<>(); } @Override public void startUp(FloodlightModuleContext context) { // register REST interface restApi.addRestletRoutable(new ACLWebRoutable()); deviceManager.addListener(this); } @Override public void deviceAdded(IDevice device) { SwitchPort[] switchPort = device.getAttachmentPoints(); if (switchPort.length == 0) { //Device manager does not yet know an attachment point for a device (Bug Fix) return; } IPv4Address[] ips = device.getIPv4Addresses(); if (ips.length == 0) { // A new no-ip device added return; } String dpid = HexString.toHexString(switchPort[0].getNodeId() .getLong()); String ip = IPv4.fromIPv4Address(ips[0].getInt()); logger.debug("AP(dpid:{},ip:{}) is added", dpid, ip); AP ap = new AP(ip, dpid); apManager.addAP(ap); processAPAdded(ap); } /** * Generates new ACL flow when a new device appears * and existing ACL rules denies its traffic. */ private void processAPAdded(AP ap) { String dpid = ap.getDpid(); int ip = IPv4.toIPv4Address(ap.getIp()); for (ACLRule rule : getRules()) { if (rule.getAction() != Action.ALLOW) { if (rule.getNw_src() != null) { if (IPAddressUtil.containIP(rule.getNw_src_prefix(), rule.getNw_src_maskbits(), ip)) { if (checkIfRuleWorksInSwitch(rule.getId(), dpid)) { continue; } String flowName = "ACLRule_" + rule.getId() + "_" + dpid; generateFlow(rule, dpid, flowName); } } else { if (IPAddressUtil.containIP(rule.getNw_dst_prefix(), rule.getNw_dst_maskbits(), ip)) { if (checkIfRuleWorksInSwitch(rule.getId(), dpid)) { continue; } String flowName = "ACLRule_" + rule.getId() + "_" + dpid; generateFlow(rule, dpid, flowName); } } } } } @Override public void deviceRemoved(IDevice device) { } @Override public void deviceMoved(IDevice device) { } @Override public void deviceIPV6AddrChanged(IDevice device) { logger.debug("IPv6 not implemented in ACL. Device changed: {}", device.toString()); } @Override public void deviceIPV4AddrChanged(IDevice device) { SwitchPort[] switchPort = device.getAttachmentPoints(); IPv4Address[] ips = device.getIPv4Addresses(); String dpid = HexString.toHexString(switchPort[0].getNodeId() .getLong()); String ip = null; // some device may first appear with no IP address(default set to // 0.0.0.0), ignore it for (IPv4Address i : ips) { if (i.getInt() != 0) { ip = IPv4.fromIPv4Address(i.getInt()); break; } } logger.debug("AP(dpid:{},ip:{}) is added", dpid, ip); AP ap = new AP(ip, dpid); apManager.addAP(ap); processAPAdded(ap); } @Override public void deviceVlanChanged(IDevice device) { } @Override public String getName() { return "ACL manager"; } @Override public boolean isCallbackOrderingPrereq(String type, String name) { return false; } @Override public boolean isCallbackOrderingPostreq(String type, String name) { return false; } }