blob: a18e263c47a8ba90e1fc373c55978cd71b8c82a5 [file] [log] [blame]
/*
* Copyright 2015 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.reactive.routing;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.packet.Ethernet.TYPE_ARP;
import static org.onlab.packet.Ethernet.TYPE_IPV4;
import static org.onosproject.net.packet.PacketPriority.REACTIVE;
import static org.slf4j.LoggerFactory.getLogger;
import java.nio.ByteBuffer;
import java.util.Optional;
import java.util.Set;
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.onlab.packet.ARP;
import org.onlab.packet.EthType;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip6Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.incubator.net.intf.InterfaceService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Host;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.host.HostService;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.InboundPacket;
import org.onosproject.net.packet.OutboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.routing.IntentRequestListener;
import org.onosproject.routing.IntentSynchronizationService;
import org.onosproject.routing.RouteEntry;
import org.onosproject.routing.RoutingService;
import org.onosproject.routing.config.RoutingConfigurationService;
import org.slf4j.Logger;
/**
* This is reactive routing to handle 3 cases:
* (1) one host wants to talk to another host, both two hosts are in
* SDN network.
* (2) one host in SDN network wants to talk to another host in Internet.
* (3) one host from Internet wants to talk to another host in SDN network.
*/
@Component(immediate = true)
public class SdnIpReactiveRouting {
private static final String APP_NAME = "org.onosproject.reactive.routing";
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected RoutingService routingService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected IntentSynchronizationService intentSynchronizer;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected RoutingConfigurationService config;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected InterfaceService interfaceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
private ApplicationId appId;
private IntentRequestListener intentRequestListener;
private ReactiveRoutingProcessor processor =
new ReactiveRoutingProcessor();
@Activate
public void activate() {
appId = coreService.registerApplication(APP_NAME);
intentRequestListener = new ReactiveRoutingFib(appId, hostService,
interfaceService, intentSynchronizer);
packetService.addProcessor(processor, PacketProcessor.director(2));
requestIntercepts();
log.info("SDN-IP Reactive Routing Started");
}
@Deactivate
public void deactivate() {
withdrawIntercepts();
packetService.removeProcessor(processor);
processor = null;
log.info("SDN-IP Reactive Routing Stopped");
}
/**
* Request packet in via the PacketService.
*/
private void requestIntercepts() {
//TODO: to support IPv6 later
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
selector.matchEthType(TYPE_IPV4);
packetService.requestPackets(selector.build(), REACTIVE, appId);
selector.matchEthType(TYPE_ARP);
packetService.requestPackets(selector.build(), REACTIVE, appId);
}
/**
* Cancel request for packet in via PacketService.
*/
private void withdrawIntercepts() {
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
selector.matchEthType(TYPE_IPV4);
packetService.cancelPackets(selector.build(), REACTIVE, appId);
selector = DefaultTrafficSelector.builder();
selector.matchEthType(TYPE_ARP);
packetService.cancelPackets(selector.build(), REACTIVE, appId);
}
private class ReactiveRoutingProcessor implements PacketProcessor {
@Override
public void process(PacketContext context) {
InboundPacket pkt = context.inPacket();
Ethernet ethPkt = pkt.parsed();
if (ethPkt == null) {
return;
}
ConnectPoint srcConnectPoint = pkt.receivedFrom();
switch (EthType.EtherType.lookup(ethPkt.getEtherType())) {
case ARP:
ARP arpPacket = (ARP) ethPkt.getPayload();
Ip4Address targetIpAddress = Ip4Address
.valueOf(arpPacket.getTargetProtocolAddress());
// Only when it is an ARP request packet and the target IP
// address is a virtual gateway IP address, then it will be
// processed.
if (arpPacket.getOpCode() == ARP.OP_REQUEST
&& config.isVirtualGatewayIpAddress(targetIpAddress)) {
MacAddress gatewayMacAddress =
config.getVirtualGatewayMacAddress();
if (gatewayMacAddress == null) {
break;
}
Ethernet eth = ARP.buildArpReply(targetIpAddress,
gatewayMacAddress,
ethPkt);
TrafficTreatment.Builder builder =
DefaultTrafficTreatment.builder();
builder.setOutput(srcConnectPoint.port());
packetService.emit(new DefaultOutboundPacket(
srcConnectPoint.deviceId(),
builder.build(),
ByteBuffer.wrap(eth.serialize())));
}
break;
case IPV4:
// Parse packet
IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
IpAddress dstIp =
IpAddress.valueOf(ipv4Packet.getDestinationAddress());
IpAddress srcIp =
IpAddress.valueOf(ipv4Packet.getSourceAddress());
MacAddress srcMac = ethPkt.getSourceMAC();
packetReactiveProcessor(dstIp, srcIp, srcConnectPoint, srcMac);
// TODO emit packet first or packetReactiveProcessor first
ConnectPoint egressConnectPoint = null;
egressConnectPoint = getEgressConnectPoint(dstIp);
if (egressConnectPoint != null) {
forwardPacketToDst(context, egressConnectPoint);
}
break;
default:
break;
}
}
}
/**
* Routes packet reactively.
*
* @param dstIpAddress the destination IP address of a packet
* @param srcIpAddress the source IP address of a packet
* @param srcConnectPoint the connect point where a packet comes from
* @param srcMacAddress the source MAC address of a packet
*/
private void packetReactiveProcessor(IpAddress dstIpAddress,
IpAddress srcIpAddress,
ConnectPoint srcConnectPoint,
MacAddress srcMacAddress) {
checkNotNull(dstIpAddress);
checkNotNull(srcIpAddress);
checkNotNull(srcConnectPoint);
checkNotNull(srcMacAddress);
//
// Step1: Try to update the existing intent first if it exists.
//
IpPrefix ipPrefix = null;
RouteEntry routeEntry = null;
if (config.isIpAddressLocal(dstIpAddress)) {
if (dstIpAddress.isIp4()) {
ipPrefix = IpPrefix.valueOf(dstIpAddress,
Ip4Address.BIT_LENGTH);
} else {
ipPrefix = IpPrefix.valueOf(dstIpAddress,
Ip6Address.BIT_LENGTH);
}
} else {
// Get IP prefix from BGP route table
routeEntry = routingService.getLongestMatchableRouteEntry(dstIpAddress);
if (routeEntry != null) {
ipPrefix = routeEntry.prefix();
}
}
if (ipPrefix != null
&& intentRequestListener.mp2pIntentExists(ipPrefix)) {
intentRequestListener.updateExistingMp2pIntent(ipPrefix,
srcConnectPoint);
return;
}
//
// Step2: There is no existing intent for the destination IP address.
// Check whether it is necessary to create a new one. If necessary then
// create a new one.
//
TrafficType trafficType =
trafficTypeClassifier(srcConnectPoint, dstIpAddress);
switch (trafficType) {
case HOST_TO_INTERNET:
// If the destination IP address is outside the local SDN network.
// The Step 1 has already handled it. We do not need to do anything here.
intentRequestListener.setUpConnectivityHostToInternet(srcIpAddress,
ipPrefix, routeEntry.nextHop());
break;
case INTERNET_TO_HOST:
intentRequestListener.setUpConnectivityInternetToHost(dstIpAddress);
break;
case HOST_TO_HOST:
intentRequestListener.setUpConnectivityHostToHost(dstIpAddress,
srcIpAddress, srcMacAddress, srcConnectPoint);
break;
case INTERNET_TO_INTERNET:
log.trace("This is transit traffic, "
+ "the intent should be preinstalled already");
break;
case DROP:
// TODO here should setUpDropPacketIntent(...);
// We need a new type of intent here.
break;
case UNKNOWN:
log.trace("This is unknown traffic, so we do nothing");
break;
default:
break;
}
}
/**
* Classifies the traffic and return the traffic type.
*
* @param srcConnectPoint the connect point where the packet comes from
* @param dstIp the destination IP address in packet
* @return the traffic type which this packet belongs to
*/
private TrafficType trafficTypeClassifier(ConnectPoint srcConnectPoint,
IpAddress dstIp) {
LocationType dstIpLocationType = getLocationType(dstIp);
Optional<Interface> srcInterface =
interfaceService.getInterfacesByPort(srcConnectPoint).stream().findFirst();
Set<ConnectPoint> bgpPeerConnectPoints = config.getBgpPeerConnectPoints();
switch (dstIpLocationType) {
case INTERNET:
if (srcInterface.isPresent() &&
(!bgpPeerConnectPoints.contains(srcConnectPoint))) {
return TrafficType.HOST_TO_INTERNET;
} else {
return TrafficType.INTERNET_TO_INTERNET;
}
case LOCAL:
if (srcInterface.isPresent() &&
(!bgpPeerConnectPoints.contains(srcConnectPoint))) {
return TrafficType.HOST_TO_HOST;
} else {
// TODO Currently we only consider local public prefixes.
// In the future, we will consider the local private prefixes.
// If dstIpLocationType is a local private, we should return
// TrafficType.DROP.
return TrafficType.INTERNET_TO_HOST;
}
case NO_ROUTE:
return TrafficType.DROP;
default:
return TrafficType.UNKNOWN;
}
}
/**
* Evaluates the location of an IP address and returns the location type.
*
* @param ipAddress the IP address to evaluate
* @return the IP address location type
*/
private LocationType getLocationType(IpAddress ipAddress) {
if (config.isIpAddressLocal(ipAddress)) {
return LocationType.LOCAL;
} else if (routingService.getLongestMatchableRouteEntry(ipAddress) != null) {
return LocationType.INTERNET;
} else {
return LocationType.NO_ROUTE;
}
}
public ConnectPoint getEgressConnectPoint(IpAddress dstIpAddress) {
LocationType type = getLocationType(dstIpAddress);
if (type == LocationType.LOCAL) {
Set<Host> hosts = hostService.getHostsByIp(dstIpAddress);
if (!hosts.isEmpty()) {
return hosts.iterator().next().location();
} else {
hostService.startMonitoringIp(dstIpAddress);
return null;
}
} else if (type == LocationType.INTERNET) {
IpAddress nextHopIpAddress = null;
RouteEntry routeEntry = routingService.getLongestMatchableRouteEntry(dstIpAddress);
if (routeEntry != null) {
nextHopIpAddress = routeEntry.nextHop();
Interface it = interfaceService.getMatchingInterface(nextHopIpAddress);
if (it != null) {
return it.connectPoint();
} else {
return null;
}
} else {
return null;
}
} else {
return null;
}
}
/**
* Emits the specified packet onto the network.
*
* @param context the packet context
* @param connectPoint the connect point where the packet should be
* sent out
*/
private void forwardPacketToDst(PacketContext context,
ConnectPoint connectPoint) {
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setOutput(connectPoint.port()).build();
OutboundPacket packet =
new DefaultOutboundPacket(connectPoint.deviceId(), treatment,
context.inPacket().unparsed());
packetService.emit(packet);
log.trace("sending packet: {}", packet);
}
}