| /* |
| * Copyright 2015-present Open Networking Foundation |
| * |
| * 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. |
| * |
| * 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. |
| */ |
| package org.onosproject.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.onosproject.acl.AclRule; |
| import org.onosproject.acl.AclService; |
| import org.onosproject.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.onosproject.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.onosproject.acl"); |
| hostService.addListener(hostListener); |
| idGenerator = coreService.getIdGenerator("acl-ids"); |
| AclRule.bindIdGenerator(idGenerator); |
| log.info("Started"); |
| } |
| |
| @Deactivate |
| public void deactivate() { |
| hostService.removeListener(hostListener); |
| this.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."); |
| } |
| |
| } |