diff options
Diffstat (limited to 'framework/src/onos/apps/acl/src/main/java/org/onos/acl/impl/AclManager.java')
-rw-r--r-- | framework/src/onos/apps/acl/src/main/java/org/onos/acl/impl/AclManager.java | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/framework/src/onos/apps/acl/src/main/java/org/onos/acl/impl/AclManager.java b/framework/src/onos/apps/acl/src/main/java/org/onos/acl/impl/AclManager.java new file mode 100644 index 00000000..0ffd4bcd --- /dev/null +++ b/framework/src/onos/apps/acl/src/main/java/org/onos/acl/impl/AclManager.java @@ -0,0 +1,337 @@ +/* + * Copyright 2015 Open Networking Laboratory + * 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 org.onos.acl.impl; + +import org.onlab.packet.Ethernet; +import org.onlab.packet.IPv4; +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip4Prefix; +import org.onlab.packet.IpAddress; +import org.onlab.packet.TpPort; +import org.onos.acl.AclRule; +import org.onos.acl.AclService; +import org.onos.acl.AclStore; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onos.acl.RuleId; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.core.IdGenerator; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultFlowEntry; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.host.HostEvent; +import org.onosproject.net.host.HostListener; +import org.onosproject.net.host.HostService; +import org.slf4j.Logger; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Implementation of the ACL service. + */ +@Component(immediate = true) +@Service +public class AclManager implements AclService { + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowRuleService flowRuleService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostService hostService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipService mastershipService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected AclStore aclStore; + + private final Logger log = getLogger(getClass()); + private ApplicationId appId; + private final HostListener hostListener = new InternalHostListener(); + private IdGenerator idGenerator; + + /** + * Checks if the given IP address is in the given CIDR address. + */ + private boolean checkIpInCIDR(Ip4Address ip, Ip4Prefix cidr) { + int offset = 32 - cidr.prefixLength(); + int cidrPrefix = cidr.address().toInt(); + int ipIntValue = ip.toInt(); + cidrPrefix = cidrPrefix >> offset; + ipIntValue = ipIntValue >> offset; + cidrPrefix = cidrPrefix << offset; + ipIntValue = ipIntValue << offset; + + return (cidrPrefix == ipIntValue); + } + + private class InternalHostListener implements HostListener { + + /** + * Generate new ACL flow rules for new host following the given ACL rule. + */ + private void processHostAddedEvent(HostEvent event, AclRule rule) { + DeviceId deviceId = event.subject().location().deviceId(); + for (IpAddress address : event.subject().ipAddresses()) { + if ((rule.srcIp() != null) ? + (checkIpInCIDR(address.getIp4Address(), rule.srcIp())) : + (checkIpInCIDR(address.getIp4Address(), rule.dstIp()))) { + if (!aclStore.checkIfRuleWorksInDevice(rule.id(), deviceId)) { + List<RuleId> allowingRuleList = aclStore + .getAllowingRuleByDenyingRule(rule.id()); + if (allowingRuleList != null) { + for (RuleId allowingRuleId : allowingRuleList) { + generateACLFlow(aclStore.getAclRule(allowingRuleId), deviceId); + } + } + generateACLFlow(rule, deviceId); + } + } + } + } + + @Override + public void event(HostEvent event) { + // if a new host appears and an existing rule denies + // its traffic, a new ACL flow rule is generated. + if (event.type() == HostEvent.Type.HOST_ADDED) { + DeviceId deviceId = event.subject().location().deviceId(); + if (mastershipService.getLocalRole(deviceId) == MastershipRole.MASTER) { + for (AclRule rule : aclStore.getAclRules()) { + if (rule.action() != AclRule.Action.ALLOW) { + processHostAddedEvent(event, rule); + } + } + } + } + } + } + + @Activate + public void activate() { + appId = coreService.registerApplication("org.onos.acl"); + hostService.addListener(hostListener); + idGenerator = coreService.getIdGenerator("acl-ids"); + AclRule.bindIdGenerator(idGenerator); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + hostService.removeListener(hostListener); + flowRuleService.removeFlowRulesById(appId); + aclStore.clearAcl(); + log.info("Stopped"); + } + + @Override + public List<AclRule> getAclRules() { + return aclStore.getAclRules(); + } + + /** + * 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 matchCheck(AclRule newRule) { + for (AclRule existingRule : aclStore.getAclRules()) { + if (newRule.checkMatch(existingRule)) { + return true; + } + + if (existingRule.action() == AclRule.Action.ALLOW + && newRule.action() == AclRule.Action.DENY) { + if (existingRule.checkMatch(newRule)) { + aclStore.addDenyToAllowMapping(newRule.id(), existingRule.id()); + } + } + } + return false; + } + + @Override + public boolean addAclRule(AclRule rule) { + if (matchCheck(rule)) { + return false; + } + aclStore.addAclRule(rule); + log.info("ACL rule(id:{}) is added.", rule.id()); + if (rule.action() != AclRule.Action.ALLOW) { + enforceRuleAdding(rule); + } + return true; + } + + /** + * Gets a set containing all devices connecting with the hosts + * whose IP address is in the given CIDR IP address. + */ + private Set<DeviceId> getDeviceIdSet(Ip4Prefix cidrAddr) { + Set<DeviceId> deviceIdSet = new HashSet<>(); + final Iterable<Host> hosts = hostService.getHosts(); + + if (cidrAddr.prefixLength() != 32) { + for (Host h : hosts) { + for (IpAddress a : h.ipAddresses()) { + if (checkIpInCIDR(a.getIp4Address(), cidrAddr)) { + deviceIdSet.add(h.location().deviceId()); + } + } + } + } else { + for (Host h : hosts) { + for (IpAddress a : h.ipAddresses()) { + if (checkIpInCIDR(a.getIp4Address(), cidrAddr)) { + deviceIdSet.add(h.location().deviceId()); + return deviceIdSet; + } + } + } + } + return deviceIdSet; + } + + /** + * Enforces denying ACL rule by ACL flow rules. + */ + private void enforceRuleAdding(AclRule rule) { + Set<DeviceId> dpidSet; + if (rule.srcIp() != null) { + dpidSet = getDeviceIdSet(rule.srcIp()); + } else { + dpidSet = getDeviceIdSet(rule.dstIp()); + } + + for (DeviceId deviceId : dpidSet) { + List<RuleId> allowingRuleList = aclStore.getAllowingRuleByDenyingRule(rule.id()); + if (allowingRuleList != null) { + for (RuleId allowingRuleId : allowingRuleList) { + generateACLFlow(aclStore.getAclRule(allowingRuleId), deviceId); + } + } + generateACLFlow(rule, deviceId); + } + } + + /** + * Generates ACL flow rule according to ACL rule + * and install it into related device. + */ + private void generateACLFlow(AclRule rule, DeviceId deviceId) { + if (rule == null || aclStore.checkIfRuleWorksInDevice(rule.id(), deviceId)) { + return; + } + + TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); + TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder(); + FlowEntry.Builder flowEntry = DefaultFlowEntry.builder(); + + selectorBuilder.matchEthType(Ethernet.TYPE_IPV4); + if (rule.srcIp() != null) { + selectorBuilder.matchIPSrc(rule.srcIp()); + if (rule.dstIp() != null) { + selectorBuilder.matchIPDst(rule.dstIp()); + } + } else { + selectorBuilder.matchIPDst(rule.dstIp()); + } + if (rule.ipProto() != 0) { + selectorBuilder.matchIPProtocol(Integer.valueOf(rule.ipProto()).byteValue()); + } + if (rule.dstTpPort() != 0) { + switch (rule.ipProto()) { + case IPv4.PROTOCOL_TCP: + selectorBuilder.matchTcpDst(TpPort.tpPort(rule.dstTpPort())); + break; + case IPv4.PROTOCOL_UDP: + selectorBuilder.matchUdpDst(TpPort.tpPort(rule.dstTpPort())); + break; + default: + break; + } + } + if (rule.action() == AclRule.Action.ALLOW) { + treatment.add(Instructions.createOutput(PortNumber.CONTROLLER)); + } + flowEntry.forDevice(deviceId); + flowEntry.withPriority(aclStore.getPriorityByDevice(deviceId)); + flowEntry.withSelector(selectorBuilder.build()); + flowEntry.withTreatment(treatment.build()); + flowEntry.fromApp(appId); + flowEntry.makePermanent(); + // install flow rule + flowRuleService.applyFlowRules(flowEntry.build()); + log.debug("ACL flow rule {} is installed in {}.", flowEntry.build(), deviceId); + aclStore.addRuleToFlowMapping(rule.id(), flowEntry.build()); + aclStore.addRuleToDeviceMapping(rule.id(), deviceId); + } + + @Override + public void removeAclRule(RuleId ruleId) { + aclStore.removeAclRule(ruleId); + log.info("ACL rule(id:{}) is removed.", ruleId); + enforceRuleRemoving(ruleId); + } + + /** + * Enforces removing an existing ACL rule. + */ + private void enforceRuleRemoving(RuleId ruleId) { + Set<FlowRule> flowSet = aclStore.getFlowByRule(ruleId); + if (flowSet != null) { + for (FlowRule flowRule : flowSet) { + flowRuleService.removeFlowRules(flowRule); + log.debug("ACL flow rule {} is removed from {}.", flowRule.toString(), flowRule.deviceId().toString()); + } + } + aclStore.removeRuleToFlowMapping(ruleId); + aclStore.removeRuleToDeviceMapping(ruleId); + aclStore.removeDenyToAllowMapping(ruleId); + } + + @Override + public void clearAcl() { + aclStore.clearAcl(); + flowRuleService.removeFlowRulesById(appId); + log.info("ACL is cleared."); + } + +} |