blob: d70524c4a5cfa91ea115cd74ec5a788c65535e44 [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Laboratory
*
* 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.openstacknetworking.switching;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
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.Modified;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.packet.ARP;
import org.onlab.packet.Ethernet;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.util.Tools;
import org.onosproject.net.Host;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.openstackinterface.OpenstackInterfaceService;
import org.onosproject.openstackinterface.OpenstackNetwork;
import org.onosproject.openstackinterface.OpenstackPort;
import org.onosproject.openstacknetworking.AbstractVmHandler;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.Dictionary;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.openstacknetworking.Constants.*;
/**
* Handles ARP packet from VMs.
*/
@Component(immediate = true)
public final class OpenstackSwitchingArpHandler extends AbstractVmHandler {
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String GATEWAY_MAC = "gatewayMac";
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected OpenstackInterfaceService openstackService;
@Property(name = GATEWAY_MAC, value = DEFAULT_GATEWAY_MAC_STR,
label = "Fake MAC address for virtual network subnet gateway")
private String gatewayMac = DEFAULT_GATEWAY_MAC_STR;
private final InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
private final Set<Ip4Address> gateways = Sets.newConcurrentHashSet();
@Activate
protected void activate() {
packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
super.activate();
}
@Deactivate
protected void deactivate() {
packetService.removeProcessor(packetProcessor);
super.deactivate();
}
@Modified
protected void modified(ComponentContext context) {
Dictionary<?, ?> properties = context.getProperties();
String updatedMac;
updatedMac = Tools.get(properties, GATEWAY_MAC);
if (!Strings.isNullOrEmpty(updatedMac) && !updatedMac.equals(gatewayMac)) {
gatewayMac = updatedMac;
}
log.info("Modified");
}
/**
* Processes ARP request packets.
* It checks if the target IP is owned by a known host first and then ask to
* OpenStack if it's not. This ARP proxy does not support overlapping IP.
*
* @param context packet context
* @param ethPacket ethernet packet
*/
private void processPacketIn(PacketContext context, Ethernet ethPacket) {
ARP arpPacket = (ARP) ethPacket.getPayload();
if (arpPacket.getOpCode() != ARP.OP_REQUEST) {
return;
}
Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
MacAddress replyMac = gateways.contains(targetIp) ? MacAddress.valueOf(gatewayMac) :
getMacFromHostService(targetIp);
if (replyMac.equals(MacAddress.NONE)) {
replyMac = getMacFromOpenstack(targetIp);
}
if (replyMac == MacAddress.NONE) {
log.debug("Failed to find MAC address for {}", targetIp.toString());
return;
}
Ethernet ethReply = ARP.buildArpReply(
targetIp.getIp4Address(),
replyMac,
ethPacket);
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(context.inPacket().receivedFrom().port())
.build();
packetService.emit(new DefaultOutboundPacket(
context.inPacket().receivedFrom().deviceId(),
treatment,
ByteBuffer.wrap(ethReply.serialize())));
}
/**
* Returns MAC address of a host with a given target IP address by asking to
* OpenStack. It does not support overlapping IP.
*
* @param targetIp target ip address
* @return mac address, or null if it fails to fetch the mac
*/
private MacAddress getMacFromOpenstack(IpAddress targetIp) {
checkNotNull(targetIp);
OpenstackPort openstackPort = openstackService.ports()
.stream()
.filter(port -> port.fixedIps().containsValue(targetIp.getIp4Address()))
.findFirst()
.orElse(null);
if (openstackPort != null) {
log.debug("Found MAC from OpenStack for {}", targetIp.toString());
return openstackPort.macAddress();
} else {
return MacAddress.NONE;
}
}
/**
* Returns MAC address of a host with a given target IP address by asking to
* host service. It does not support overlapping IP.
*
* @param targetIp target ip
* @return mac address, or null if it fails to find the mac
*/
private MacAddress getMacFromHostService(IpAddress targetIp) {
checkNotNull(targetIp);
Host host = hostService.getHostsByIp(targetIp)
.stream()
.findFirst()
.orElse(null);
if (host != null) {
log.debug("Found MAC from host service for {}", targetIp.toString());
return host.mac();
} else {
return MacAddress.NONE;
}
}
@Override
protected void hostDetected(Host host) {
OpenstackNetwork osNet = openstackService.network(host.annotations().value(NETWORK_ID));
if (osNet == null) {
log.warn("Failed to get OpenStack network for {}", host);
return;
}
osNet.subnets().forEach(subnet -> gateways.add(Ip4Address.valueOf(subnet.gatewayIp())));
}
@Override
protected void hostRemoved(Host host) {
// TODO remove subnet gateway from gateways if no hosts exists on that subnet
}
private class InternalPacketProcessor implements PacketProcessor {
@Override
public void process(PacketContext context) {
if (context.isHandled()) {
return;
}
Ethernet ethPacket = context.inPacket().parsed();
if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
return;
}
processPacketIn(context, ethPacket);
}
}
}