blob: 97e3cb447620ea733f71a1d744c2d1a1093d496e [file] [log] [blame]
/*
* Copyright 2014-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.
*/
package org.onosproject.net.host.impl;
import org.onlab.packet.Ip6Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.VlanId;
import org.onlab.util.Tools;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.HostLocation;
import org.onosproject.net.config.Config;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.config.basics.BasicHostConfig;
import org.onosproject.net.config.basics.HostAnnotationConfig;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.edge.EdgePortService;
import org.onosproject.net.host.HostAdminService;
import org.onosproject.net.host.HostDescription;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
import org.onosproject.net.host.HostProvider;
import org.onosproject.net.host.HostProviderRegistry;
import org.onosproject.net.host.HostProviderService;
import org.onosproject.net.host.HostService;
import org.onosproject.net.host.HostStore;
import org.onosproject.net.host.HostStoreDelegate;
import org.onosproject.net.intf.Interface;
import org.onosproject.net.intf.InterfaceService;
import org.onosproject.net.packet.PacketService;
import org.onosproject.net.provider.AbstractListenerProviderRegistry;
import org.onosproject.net.provider.AbstractProviderService;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import java.util.Dictionary;
import java.util.Optional;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.onlab.packet.IPv6.getLinkLocalAddress;
import static org.onosproject.net.OsgiPropertyConstants.HM_ALLOW_DUPLICATE_IPS;
import static org.onosproject.net.OsgiPropertyConstants.HM_ALLOW_DUPLICATE_IPS_DEFAULT;
import static org.onosproject.net.OsgiPropertyConstants.HM_GREEDY_LEARNING_IPV6;
import static org.onosproject.net.OsgiPropertyConstants.HM_GREEDY_LEARNING_IPV6_DEFAULT;
import static org.onosproject.net.OsgiPropertyConstants.HM_MONITOR_HOSTS;
import static org.onosproject.net.OsgiPropertyConstants.HM_MONITOR_HOSTS_DEFAULT;
import static org.onosproject.net.OsgiPropertyConstants.HM_PROBE_RATE;
import static org.onosproject.net.OsgiPropertyConstants.HM_PROBE_RATE_DEFAULT;
import static org.onosproject.security.AppGuard.checkPermission;
import static org.onosproject.security.AppPermission.Type.HOST_EVENT;
import static org.onosproject.security.AppPermission.Type.HOST_READ;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Provides basic implementation of the host SB & NB APIs.
*/
@Component(
immediate = true,
service = {
HostService.class,
HostAdminService.class,
HostProviderRegistry.class
},
property = {
HM_ALLOW_DUPLICATE_IPS + ":Boolean=" + HM_ALLOW_DUPLICATE_IPS_DEFAULT,
HM_MONITOR_HOSTS + ":Boolean=" + HM_MONITOR_HOSTS_DEFAULT,
HM_PROBE_RATE + ":Integer=" + HM_PROBE_RATE_DEFAULT,
HM_GREEDY_LEARNING_IPV6 + ":Boolean=" + HM_GREEDY_LEARNING_IPV6_DEFAULT
}
)
public class HostManager
extends AbstractListenerProviderRegistry<HostEvent, HostListener, HostProvider, HostProviderService>
implements HostService, HostAdminService, HostProviderRegistry {
private final Logger log = getLogger(getClass());
public static final String HOST_ID_NULL = "Host ID cannot be null";
private final NetworkConfigListener networkConfigListener = new InternalNetworkConfigListener();
private HostStoreDelegate delegate = new InternalStoreDelegate();
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected HostStore store;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected NetworkConfigService networkConfigService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected InterfaceService interfaceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected EdgePortService edgePortService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected ComponentConfigService cfgService;
/** Enable removal of duplicate ip address. */
private boolean allowDuplicateIps = HM_ALLOW_DUPLICATE_IPS_DEFAULT;
/** Enable/Disable monitoring of hosts. */
private boolean monitorHosts = HM_MONITOR_HOSTS_DEFAULT;
/** Set the probe Rate in milli seconds. */
private long probeRate = HM_PROBE_RATE_DEFAULT;
/** Enable/Disable greedy learning of IPv6 link local address. */
private boolean greedyLearningIpv6 = HM_GREEDY_LEARNING_IPV6_DEFAULT;
private HostMonitor monitor;
private HostAnnotationOperator hostAnnotationOperator;
@Activate
public void activate(ComponentContext context) {
hostAnnotationOperator = new HostAnnotationOperator(networkConfigService);
store.setDelegate(delegate);
eventDispatcher.addSink(HostEvent.class, listenerRegistry);
cfgService.registerProperties(getClass());
networkConfigService.addListener(networkConfigListener);
monitor = new HostMonitor(packetService, this, interfaceService, edgePortService);
monitor.setProbeRate(probeRate);
monitor.start();
modified(context);
cfgService.registerProperties(getClass());
log.info("Started");
}
@Deactivate
public void deactivate() {
store.unsetDelegate(delegate);
eventDispatcher.removeSink(HostEvent.class);
networkConfigService.removeListener(networkConfigListener);
cfgService.unregisterProperties(getClass(), false);
monitor.shutdown();
log.info("Stopped");
}
@Modified
public void modified(ComponentContext context) {
boolean oldValue = monitorHosts;
readComponentConfiguration(context);
if (probeRate > 0) {
monitor.setProbeRate(probeRate);
} else {
log.warn("ProbeRate cannot be less than 0");
}
if (oldValue != monitorHosts) {
if (monitorHosts) {
startMonitoring();
} else {
stopMonitoring();
}
}
}
/**
* Extracts properties from the component configuration context.
*
* @param context the component context
*/
private void readComponentConfiguration(ComponentContext context) {
Dictionary<?, ?> properties = context.getProperties();
Boolean flag;
flag = Tools.isPropertyEnabled(properties, HM_MONITOR_HOSTS);
if (flag == null) {
log.info("monitorHosts is not enabled " +
"using current value of {}", monitorHosts);
} else {
monitorHosts = flag;
log.info("Configured. monitorHosts {}",
monitorHosts ? "enabled" : "disabled");
}
Long longValue = Tools.getLongProperty(properties, HM_PROBE_RATE);
if (longValue == null || longValue == 0) {
log.info("probeRate is not set sing default value of {}", probeRate);
} else {
probeRate = longValue;
log.info("Configured. probeRate {}", probeRate);
}
flag = Tools.isPropertyEnabled(properties, HM_ALLOW_DUPLICATE_IPS);
if (flag == null) {
log.info("Removal of duplicate ip address is not configured");
} else {
allowDuplicateIps = flag;
log.info("Removal of duplicate ip address is {}",
allowDuplicateIps ? "disabled" : "enabled");
}
flag = Tools.isPropertyEnabled(properties, HM_GREEDY_LEARNING_IPV6);
if (flag == null) {
log.info("greedy learning is not enabled " +
"using current value of {}", greedyLearningIpv6);
} else {
greedyLearningIpv6 = flag;
log.info("Configured. greedyLearningIpv6 {}",
greedyLearningIpv6 ? "enabled" : "disabled");
}
}
/**
* Starts monitoring the hosts by IP Address.
*/
private void startMonitoring() {
store.getHosts().forEach(host -> {
host.ipAddresses().forEach(ip -> {
monitor.addMonitoringFor(ip);
});
});
}
/**
* Stops monitoring the hosts by IP Address.
*/
private void stopMonitoring() {
store.getHosts().forEach(host -> {
host.ipAddresses().forEach(ip -> {
monitor.stopMonitoring(ip);
});
});
}
@Override
protected HostProviderService createProviderService(HostProvider provider) {
monitor.registerHostProvider(provider);
return new InternalHostProviderService(provider);
}
@Override
public int getHostCount() {
checkPermission(HOST_READ);
return store.getHostCount();
}
@Override
public Iterable<Host> getHosts() {
checkPermission(HOST_READ);
return store.getHosts();
}
@Override
public Host getHost(HostId hostId) {
checkPermission(HOST_READ);
checkNotNull(hostId, HOST_ID_NULL);
return store.getHost(hostId);
}
@Override
public Set<Host> getHostsByVlan(VlanId vlanId) {
checkPermission(HOST_READ);
return store.getHosts(vlanId);
}
@Override
public Set<Host> getHostsByMac(MacAddress mac) {
checkPermission(HOST_READ);
checkNotNull(mac, "MAC address cannot be null");
return store.getHosts(mac);
}
@Override
public Set<Host> getHostsByIp(IpAddress ip) {
checkPermission(HOST_READ);
checkNotNull(ip, "IP address cannot be null");
return store.getHosts(ip);
}
@Override
public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
checkPermission(HOST_READ);
checkNotNull(connectPoint, "Connection point cannot be null");
return store.getConnectedHosts(connectPoint);
}
@Override
public Set<Host> getConnectedHosts(DeviceId deviceId) {
checkPermission(HOST_READ);
checkNotNull(deviceId, "Device ID cannot be null");
return store.getConnectedHosts(deviceId);
}
@Override
public void startMonitoringIp(IpAddress ip) {
checkPermission(HOST_EVENT);
monitor.addMonitoringFor(ip);
}
@Override
public void stopMonitoringIp(IpAddress ip) {
checkPermission(HOST_EVENT);
monitor.stopMonitoring(ip);
}
@Override
public void requestMac(IpAddress ip) {
// FIXME!!!! Auto-generated method stub
}
@Override
public void removeHost(HostId hostId) {
checkNotNull(hostId, HOST_ID_NULL);
store.removeHost(hostId);
}
// Personalized host provider service issued to the supplied provider.
private class InternalHostProviderService
extends AbstractProviderService<HostProvider>
implements HostProviderService {
InternalHostProviderService(HostProvider provider) {
super(provider);
}
@Override
public void hostDetected(HostId hostId, HostDescription hostDescription, boolean replaceIps) {
checkNotNull(hostId, HOST_ID_NULL);
checkValidity();
hostDescription = validateHost(hostDescription, hostId);
if (!allowDuplicateIps) {
removeDuplicates(hostId, hostDescription);
}
BasicHostConfig cfg = networkConfigService.getConfig(hostId, BasicHostConfig.class);
if (!isAllowed(cfg)) {
log.warn("Host {} is not allowed to be added into the contol domain", hostId);
return;
}
hostDescription = BasicHostOperator.combine(cfg, hostDescription);
HostAnnotationConfig annoConfig = networkConfigService.getConfig(hostId, HostAnnotationConfig.class);
if (annoConfig != null) {
hostDescription = hostAnnotationOperator.combine(hostId, hostDescription, Optional.of(annoConfig));
}
store.createOrUpdateHost(provider().id(), hostId,
hostDescription, replaceIps);
if (monitorHosts) {
hostDescription.ipAddress().forEach(ip -> {
monitor.addMonitoringFor(ip);
});
}
// Greedy learning of IPv6 host. We have to disable the greedy
// learning of configured hosts. Validate hosts each time will
// overwrite the learnt information with the configured information.
if (greedyLearningIpv6) {
// Auto-generation of the IPv6 link local address
// using the mac address
Ip6Address targetIp6Address = Ip6Address.valueOf(
getLinkLocalAddress(hostId.mac().toBytes())
);
// If we already know this guy we don't need to do other
if (!hostDescription.ipAddress().contains(targetIp6Address)) {
Host host = store.getHost(hostId);
// Configured host, skip it.
if (host != null && host.configured()) {
return;
}
// Host does not exist in the store or the target is not known
if ((host == null || !host.ipAddresses().contains(targetIp6Address))) {
// Use DAD to probe if interface MAC is not specified
MacAddress probeMac = interfaceService.getInterfacesByPort(hostDescription.location())
.stream().map(Interface::mac).findFirst().orElse(MacAddress.ONOS);
Ip6Address probeIp = !probeMac.equals(MacAddress.ONOS) ?
Ip6Address.valueOf(getLinkLocalAddress(probeMac.toBytes())) :
Ip6Address.ZERO;
// We send a probe using the monitoring service
monitor.sendProbe(
hostDescription.location(),
targetIp6Address,
probeIp,
probeMac,
hostId.vlanId()
);
}
}
}
}
// When a new IP is detected, remove that IP on other hosts if it exists
public void removeDuplicates(HostId hostId, HostDescription desc) {
desc.ipAddress().forEach(ip -> {
Set<Host> allHosts = store.getHosts(ip);
allHosts.forEach(eachHost -> {
if (!(eachHost.id().equals(hostId))) {
log.info("Duplicate ip {} found on host {} and {}", ip,
hostId.toString(), eachHost.id().toString());
store.removeIp(eachHost.id(), ip);
}
});
});
}
// returns a HostDescription made from the union of the BasicHostConfig
// annotations if it exists
private HostDescription validateHost(HostDescription hostDescription, HostId hostId) {
BasicHostConfig cfg = networkConfigService.getConfig(hostId, BasicHostConfig.class);
checkState(cfg == null || cfg.isAllowed(), "Host {} is not allowed", hostId);
return BasicHostOperator.combine(cfg, hostDescription);
}
@Override
public void hostVanished(HostId hostId) {
checkNotNull(hostId, HOST_ID_NULL);
checkValidity();
Host host = store.getHost(hostId);
if (!allowedToChange(hostId)) {
log.info("Request to remove {} is ignored due to provider mismatch", hostId);
return;
}
if (monitorHosts) {
host.ipAddresses().forEach(ip -> {
monitor.stopMonitoring(ip);
});
}
store.removeHost(hostId);
}
@Override
public void removeIpFromHost(HostId hostId, IpAddress ipAddress) {
checkNotNull(hostId, HOST_ID_NULL);
checkValidity();
if (!allowedToChange(hostId)) {
log.info("Request to remove {} from {} is ignored due to provider mismatch",
ipAddress, hostId);
return;
}
store.removeIp(hostId, ipAddress);
}
@Override
public void addLocationToHost(HostId hostId, HostLocation location) {
checkNotNull(hostId, HOST_ID_NULL);
checkValidity();
if (!allowedToChange(hostId)) {
log.info("Request to add {} to {} is ignored due to provider mismatch",
location, hostId);
return;
}
store.appendLocation(hostId, location);
}
@Override
public void removeLocationFromHost(HostId hostId, HostLocation location) {
checkNotNull(hostId, HOST_ID_NULL);
checkValidity();
if (!allowedToChange(hostId)) {
log.info("Request to remove {} from {} is ignored due to provider mismatch",
location, hostId);
return;
}
store.removeLocation(hostId, location);
}
/**
* Providers should only be able to remove a host that is provided by itself,
* or a host that is not configured.
*/
private boolean allowedToChange(HostId hostId) {
Host host = store.getHost(hostId);
return host == null || !host.configured() || host.providerId().equals(provider().id());
}
}
// Store delegate to re-post events emitted from the store.
private class InternalStoreDelegate implements HostStoreDelegate {
@Override
public void notify(HostEvent event) {
post(event);
}
}
// listens for NetworkConfigEvents of type BasicHostConfig and removes
// links that the config does not allow
private class InternalNetworkConfigListener implements NetworkConfigListener {
@Override
public boolean isRelevant(NetworkConfigEvent event) {
return (event.type() == NetworkConfigEvent.Type.CONFIG_ADDED
|| event.type() == NetworkConfigEvent.Type.CONFIG_UPDATED)
&& (event.configClass().equals(BasicHostConfig.class)
|| event.configClass().equals(HostAnnotationConfig.class));
}
@Override
public void event(NetworkConfigEvent event) {
log.debug("Detected host network config event {}", event.type());
HostEvent he = null;
HostId hostId = (HostId) event.subject();
BasicHostConfig cfg =
networkConfigService.getConfig(hostId, BasicHostConfig.class);
if (!isAllowed(cfg)) {
kickOutBadHost(hostId);
} else if (event.configClass().equals(BasicHostConfig.class)) {
Host host = getHost(hostId);
HostDescription desc =
(host == null) ? null : BasicHostOperator.descriptionOf(host);
desc = BasicHostOperator.combine(cfg, desc);
if (desc != null) {
he = store.createOrUpdateHost(host.providerId(), hostId, desc, false);
}
} else if (event.configClass().equals(HostAnnotationConfig.class)) {
Host host = getHost(hostId);
HostProvider hp = getProvider(host.providerId());
HostDescription desc = (host == null) ? null : BasicHostOperator.descriptionOf(host);
Optional<Config> prevConfig = event.prevConfig();
log.debug("Host annotations: {} prevconfig {} desc {}", hostId, prevConfig, desc);
desc = hostAnnotationOperator.combine(hostId, desc, prevConfig);
if (desc != null && hp != null) {
log.debug("Host annotations update - updated host description :{}", desc.toString());
he = store.createOrUpdateHost(hp.id(), hostId, desc, false);
if (he != null && he.subject() != null) {
log.debug("Host annotations update - Host Event : {}", he.subject().annotations());
}
}
}
if (he != null) {
post(he);
}
}
}
// by default allowed, otherwise check flag
private boolean isAllowed(BasicHostConfig cfg) {
return (cfg == null || cfg.isAllowed());
}
// removes the specified host, if it exists
private void kickOutBadHost(HostId hostId) {
Host badHost = getHost(hostId);
if (badHost != null) {
removeHost(hostId);
}
}
}