blob: 638f07997f32aa52cee561c4c18592fad74e9c28 [file] [log] [blame]
/*
* 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.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.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.");
}
}