diff options
Diffstat (limited to 'framework/src/onos/apps/bgprouter')
9 files changed, 1153 insertions, 0 deletions
diff --git a/framework/src/onos/apps/bgprouter/app.xml b/framework/src/onos/apps/bgprouter/app.xml new file mode 100644 index 00000000..c85e469a --- /dev/null +++ b/framework/src/onos/apps/bgprouter/app.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<app name="org.onosproject.bgprouter" origin="ON.Lab" version="${project.version}" + featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features" + features="${project.artifactId}"> + <description>${project.description}</description> + <artifact>mvn:${project.groupId}/${project.artifactId}/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-app-routing-api/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-app-routing/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-app-proxyarp/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-app-config/${project.version}</artifact> +</app> diff --git a/framework/src/onos/apps/bgprouter/features.xml b/framework/src/onos/apps/bgprouter/features.xml new file mode 100644 index 00000000..3457321c --- /dev/null +++ b/framework/src/onos/apps/bgprouter/features.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + ~ 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. + --> +<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}"> + <repository>mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features</repository> + <feature name="${project.artifactId}" version="${project.version}" + description="${project.description}"> + <feature>onos-api</feature> + <bundle>mvn:${project.groupId}/onos-app-routing-api/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-app-config/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-app-proxyarp/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-app-bgprouter/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-app-routing/${project.version}</bundle> + </feature> +</features> diff --git a/framework/src/onos/apps/bgprouter/pom.xml b/framework/src/onos/apps/bgprouter/pom.xml new file mode 100644 index 00000000..d3f6f522 --- /dev/null +++ b/framework/src/onos/apps/bgprouter/pom.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>onos-apps</artifactId> + <groupId>org.onosproject</groupId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>onos-app-bgprouter</artifactId> + + <packaging>bundle</packaging> + <description>BGP router application</description> + + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-app-routing-api</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-app-routing</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onlab-misc</artifactId> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-incubator-api</artifactId> + </dependency> + + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-app-config</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-app-proxyarp</artifactId> + <version>${project.version}</version> + </dependency> + + </dependencies> +</project> diff --git a/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/BgpRouter.java b/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/BgpRouter.java new file mode 100644 index 00000000..e5388d28 --- /dev/null +++ b/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/BgpRouter.java @@ -0,0 +1,465 @@ +/* + * 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.bgprouter; + +import com.google.common.collect.ConcurrentHashMultiset; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multiset; +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.Ethernet; +import org.onlab.packet.IpAddress; +import org.onlab.packet.IpPrefix; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.config.NetworkConfigService; +import org.onosproject.incubator.net.intf.Interface; +import org.onosproject.incubator.net.intf.InterfaceService; +import org.onosproject.net.DeviceId; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +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.flow.criteria.Criteria; +import org.onosproject.net.flowobjective.DefaultFilteringObjective; +import org.onosproject.net.flowobjective.DefaultForwardingObjective; +import org.onosproject.net.flowobjective.DefaultNextObjective; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.Objective; +import org.onosproject.net.flowobjective.ObjectiveContext; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.packet.PacketService; +import org.onosproject.routing.FibEntry; +import org.onosproject.routing.FibListener; +import org.onosproject.routing.FibUpdate; +import org.onosproject.routing.RoutingService; +import org.onosproject.routing.config.BgpConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/* For test only - will be removed before Cardinal release +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip4Prefix; +import org.onlab.packet.MacAddress; +import java.util.Collections; +import static org.onlab.util.Tools.delay; +*/ + +/** + * BgpRouter component. + */ +@Component(immediate = true) +public class BgpRouter { + + private static final Logger log = LoggerFactory.getLogger(BgpRouter.class); + + private static final String BGP_ROUTER_APP = "org.onosproject.bgprouter"; + + private static final int PRIORITY_OFFSET = 100; + private static final int PRIORITY_MULTIPLIER = 5; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected RoutingService routingService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected InterfaceService interfaceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected NetworkConfigService networkConfigService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PacketService packetService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowObjectiveService flowObjectiveService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + private ApplicationId appId; + + // Reference count for how many times a next hop is used by a route + private final Multiset<IpAddress> nextHopsCount = ConcurrentHashMultiset.create(); + + // Mapping from prefix to its current next hop + private final Map<IpPrefix, IpAddress> prefixToNextHop = Maps.newHashMap(); + + // Mapping from next hop IP to next hop object containing group info + private final Map<IpAddress, Integer> nextHops = Maps.newHashMap(); + + // Stores FIB updates that are waiting for groups to be set up + private final Multimap<NextHopGroupKey, FibEntry> pendingUpdates = HashMultimap.create(); + + // Device id of data-plane switch - should be learned from config + private DeviceId deviceId; + + // Device id of control-plane switch (OVS) connected to BGP Speaker - should be + // learned from config + private DeviceId ctrlDeviceId; + + // Responsible for handling BGP traffic (encapsulated within OF messages) + // between the data-plane switch and the Quagga VM using a control plane OVS. + private TunnellingConnectivityManager connectivityManager; + + private DeviceListener deviceListener; + private IcmpHandler icmpHandler; + + @Activate + protected void activate() { + appId = coreService.registerApplication(BGP_ROUTER_APP); + + ApplicationId routerAppId = coreService.getAppId(RoutingService.ROUTER_APP_ID); + BgpConfig bgpConfig = + networkConfigService.getConfig(routerAppId, RoutingService.CONFIG_CLASS); + + if (bgpConfig == null) { + log.error("No BgpConfig found"); + return; + } + + getDeviceConfiguration(bgpConfig); + + connectivityManager = new TunnellingConnectivityManager(appId, + bgpConfig, + interfaceService, + packetService, + flowObjectiveService); + + icmpHandler = new IcmpHandler(interfaceService, packetService); + deviceListener = new InnerDeviceListener(); + routingService.addFibListener(new InternalFibListener()); + routingService.start(); + deviceService.addListener(deviceListener); + connectivityManager.start(); + icmpHandler.start(); + + // Initialize devices now if they are already connected + if (deviceService.isAvailable(deviceId)) { + processIntfFilters(true, interfaceService.getInterfaces()); + } + + if (deviceService.isAvailable(ctrlDeviceId)) { + connectivityManager.notifySwitchAvailable(); + } + + log.info("BgpRouter started"); + } + + @Deactivate + protected void deactivate() { + routingService.stop(); + connectivityManager.stop(); + icmpHandler.stop(); + deviceService.removeListener(deviceListener); + //processIntfFilters(false, configService.getInterfaces()); //TODO necessary? + log.info("BgpRouter stopped"); + } + + private void getDeviceConfiguration(BgpConfig bgpConfig) { + Optional<BgpConfig.BgpSpeakerConfig> bgpSpeaker = + bgpConfig.bgpSpeakers().stream().findAny(); + + if (!bgpSpeaker.isPresent()) { + log.error("BGP speaker configuration not found"); + return; + } + + ctrlDeviceId = bgpSpeaker.get().connectPoint().deviceId(); + + Optional<IpAddress> peerAddress = + bgpSpeaker.get().peers().stream().findAny(); + + if (!peerAddress.isPresent()) { + log.error("BGP speaker must have peers configured"); + return; + } + + Interface intf = interfaceService.getMatchingInterface(peerAddress.get()); + + if (intf == null) { + log.error("No interface found for peer"); + return; + } + + // Assume all peers are configured on the same device - this is required + // by the BGP router + deviceId = intf.connectPoint().deviceId(); + + log.info("Router dpid: {}", deviceId); + log.info("Control Plane OVS dpid: {}", ctrlDeviceId); + } + + private void updateFibEntry(Collection<FibUpdate> updates) { + Map<FibEntry, Integer> toInstall = new HashMap<>(updates.size()); + + for (FibUpdate update : updates) { + FibEntry entry = update.entry(); + + addNextHop(entry); + + Integer nextId; + synchronized (pendingUpdates) { + nextId = nextHops.get(entry.nextHopIp()); + } + + toInstall.put(update.entry(), nextId); + } + + installFlows(toInstall); + } + + private void installFlows(Map<FibEntry, Integer> entriesToInstall) { + + for (Map.Entry<FibEntry, Integer> entry : entriesToInstall.entrySet()) { + FibEntry fibEntry = entry.getKey(); + Integer nextId = entry.getValue(); + + flowObjectiveService.forward(deviceId, + generateRibForwardingObj(fibEntry.prefix(), nextId).add()); + log.trace("Sending forwarding objective {} -> nextId:{}", fibEntry, nextId); + } + + } + + private synchronized void deleteFibEntry(Collection<FibUpdate> withdraws) { + + for (FibUpdate update : withdraws) { + FibEntry entry = update.entry(); + //Integer nextId = nextHops.get(entry.nextHopIp()); + + /* Group group = deleteNextHop(entry.prefix()); + if (group == null) { + log.warn("Group not found when deleting {}", entry); + return; + }*/ + + flowObjectiveService.forward(deviceId, + generateRibForwardingObj(entry.prefix(), null).remove()); + + } + + } + + private ForwardingObjective.Builder generateRibForwardingObj(IpPrefix prefix, + Integer nextId) { + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchIPDst(prefix) + .build(); + + int priority = prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET; + + ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder() + .fromApp(appId) + .makePermanent() + .withSelector(selector) + .withPriority(priority) + .withFlag(ForwardingObjective.Flag.SPECIFIC); + + if (nextId == null) { + // Route withdraws are not specified with next hops. Generating + // dummy treatment as there is no equivalent nextId info. + fwdBuilder.withTreatment(DefaultTrafficTreatment.builder().build()); + } else { + fwdBuilder.nextStep(nextId); + } + return fwdBuilder; + } + + private synchronized void addNextHop(FibEntry entry) { + prefixToNextHop.put(entry.prefix(), entry.nextHopIp()); + if (nextHopsCount.count(entry.nextHopIp()) == 0) { + // There was no next hop in the multiset + + Interface egressIntf = interfaceService.getMatchingInterface(entry.nextHopIp()); + if (egressIntf == null) { + log.warn("no egress interface found for {}", entry); + return; + } + + NextHopGroupKey groupKey = new NextHopGroupKey(entry.nextHopIp()); + + NextHop nextHop = new NextHop(entry.nextHopIp(), entry.nextHopMac(), groupKey); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setEthSrc(egressIntf.mac()) + .setEthDst(nextHop.mac()) + .pushVlan() + .setVlanId(egressIntf.vlan()) + .setVlanPcp((byte) 0) + .setOutput(egressIntf.connectPoint().port()) + .build(); + + int nextId = flowObjectiveService.allocateNextId(); + + NextObjective nextObjective = DefaultNextObjective.builder() + .withId(nextId) + .addTreatment(treatment) + .withType(NextObjective.Type.SIMPLE) + .fromApp(appId) + .add(); // TODO add callbacks + + flowObjectiveService.next(deviceId, nextObjective); + + nextHops.put(nextHop.ip(), nextId); + + } + + nextHopsCount.add(entry.nextHopIp()); + } + + /*private synchronized Group deleteNextHop(IpPrefix prefix) { + IpAddress nextHopIp = prefixToNextHop.remove(prefix); + NextHop nextHop = nextHops.get(nextHopIp); + if (nextHop == null) { + log.warn("No next hop found when removing prefix {}", prefix); + return null; + } + + Group group = groupService.getGroup(deviceId, + new DefaultGroupKey(appKryo. + serialize(nextHop.group()))); + + // FIXME disabling group deletes for now until we verify the logic is OK + if (nextHopsCount.remove(nextHopIp, 1) <= 1) { + // There was one or less next hops, so there are now none + + log.debug("removing group for next hop {}", nextHop); + + nextHops.remove(nextHopIp); + + groupService.removeGroup(deviceId, + new DefaultGroupKey(appKryo.build().serialize(nextHop.group())), + appId); + } + + return group; + }*/ + + private class InternalFibListener implements FibListener { + + @Override + public void update(Collection<FibUpdate> updates, + Collection<FibUpdate> withdraws) { + BgpRouter.this.deleteFibEntry(withdraws); + BgpRouter.this.updateFibEntry(updates); + } + } + + private void processIntfFilters(boolean install, Set<Interface> intfs) { + log.info("Processing {} router interfaces", intfs.size()); + for (Interface intf : intfs) { + if (!intf.connectPoint().deviceId().equals(deviceId)) { + // Ignore interfaces if they are not on the router switch + continue; + } + + FilteringObjective.Builder fob = DefaultFilteringObjective.builder(); + fob.withKey(Criteria.matchInPort(intf.connectPoint().port())) + .addCondition(Criteria.matchEthDst(intf.mac())) + .addCondition(Criteria.matchVlanId(intf.vlan())); + intf.ipAddresses().stream() + .forEach(ipaddr -> fob.addCondition( + Criteria.matchIPDst( + IpPrefix.valueOf(ipaddr.ipAddress(), 32)))); + fob.permit().fromApp(appId); + flowObjectiveService.filter( + deviceId, + fob.add(new ObjectiveContext() { + @Override + public void onSuccess(Objective objective) { + log.info("Successfully installed interface based " + + "filtering objectives for intf {}", intf); + } + + @Override + public void onError(Objective objective, + ObjectiveError error) { + log.error("Failed to install interface filters for intf {}: {}", + intf, error); + // TODO something more than just logging + } + })); + } + } + + // Triggers driver setup when a device is (re)detected. + private class InnerDeviceListener implements DeviceListener { + @Override + public void event(DeviceEvent event) { + switch (event.type()) { + case DEVICE_ADDED: + case DEVICE_AVAILABILITY_CHANGED: + if (deviceService.isAvailable(event.subject().id())) { + log.info("Device connected {}", event.subject().id()); + if (event.subject().id().equals(deviceId)) { + processIntfFilters(true, interfaceService.getInterfaces()); + + /* For test only - will be removed before Cardinal release + delay(1000); + FibEntry fibEntry = new FibEntry(Ip4Prefix.valueOf("10.1.0.0/16"), + Ip4Address.valueOf("192.168.10.1"), + MacAddress.valueOf("DE:AD:BE:EF:FE:ED")); + FibUpdate fibUpdate = new FibUpdate(FibUpdate.Type.UPDATE, fibEntry); + updateFibEntry(Collections.singletonList(fibUpdate)); + */ + } + + if (event.subject().id().equals(ctrlDeviceId)) { + connectivityManager.notifySwitchAvailable(); + } + } + break; + + // TODO other cases + case DEVICE_UPDATED: + break; + case DEVICE_REMOVED: + break; + case DEVICE_SUSPENDED: + break; + case PORT_ADDED: + break; + case PORT_UPDATED: + break; + case PORT_REMOVED: + break; + default: + break; + } + } + } +} diff --git a/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/IcmpHandler.java b/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/IcmpHandler.java new file mode 100644 index 00000000..88265350 --- /dev/null +++ b/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/IcmpHandler.java @@ -0,0 +1,157 @@ +/* + * 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.bgprouter; + +import org.onlab.packet.Ethernet; +import org.onlab.packet.ICMP; +import org.onlab.packet.IPv4; +import org.onlab.packet.IpAddress; +import org.onosproject.incubator.net.intf.Interface; +import org.onosproject.incubator.net.intf.InterfaceService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.host.InterfaceIpAddress; +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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; + +public class IcmpHandler { + + private static final Logger log = LoggerFactory.getLogger(IcmpHandler.class); + + private final PacketService packetService; + private final InterfaceService interfaceService; + + private final IcmpProcessor processor = new IcmpProcessor(); + + + public IcmpHandler(InterfaceService interfaceService, + PacketService packetService) { + this.interfaceService = interfaceService; + this.packetService = packetService; + } + + public void start() { + packetService.addProcessor(processor, PacketProcessor.director(4)); + } + + public void stop() { + packetService.removeProcessor(processor); + } + + private void processPacketIn(InboundPacket pkt) { + + boolean ipMatches = false; + Ethernet ethernet = pkt.parsed(); + IPv4 ipv4 = (IPv4) ethernet.getPayload(); + ConnectPoint connectPoint = pkt.receivedFrom(); + IpAddress destIpAddress = IpAddress.valueOf(ipv4.getDestinationAddress()); + Interface targetInterface = interfaceService.getMatchingInterface(destIpAddress); + + if (targetInterface == null) { + log.trace("No matching interface for {}", destIpAddress); + return; + } + + for (InterfaceIpAddress interfaceIpAddress: targetInterface.ipAddresses()) { + if (interfaceIpAddress.ipAddress().equals(destIpAddress)) { + ipMatches = true; + break; + } + } + + if (((ICMP) ipv4.getPayload()).getIcmpType() == ICMP.TYPE_ECHO_REQUEST && + ipMatches) { + sendICMPResponse(ethernet, connectPoint); + } + } + + private void sendICMPResponse(Ethernet icmpRequest, ConnectPoint outport) { + + Ethernet icmpReplyEth = new Ethernet(); + + IPv4 icmpRequestIpv4 = (IPv4) icmpRequest.getPayload(); + IPv4 icmpReplyIpv4 = new IPv4(); + + int destAddress = icmpRequestIpv4.getDestinationAddress(); + icmpReplyIpv4.setDestinationAddress(icmpRequestIpv4.getSourceAddress()); + icmpReplyIpv4.setSourceAddress(destAddress); + icmpReplyIpv4.setTtl((byte) 64); + icmpReplyIpv4.setChecksum((short) 0); + + ICMP icmpReply = new ICMP(); + icmpReply.setIcmpType(ICMP.TYPE_ECHO_REPLY); + icmpReply.setIcmpCode(ICMP.SUBTYPE_ECHO_REPLY); + icmpReply.setChecksum((short) 0); + + icmpReplyIpv4.setPayload(icmpReply); + + icmpReplyEth.setPayload(icmpReplyIpv4); + icmpReplyEth.setEtherType(Ethernet.TYPE_IPV4); + icmpReplyEth.setDestinationMACAddress(icmpRequest.getSourceMACAddress()); + icmpReplyEth.setSourceMACAddress(icmpRequest.getDestinationMACAddress()); + icmpReplyEth.setVlanID(icmpRequest.getVlanID()); + + sendPacketOut(outport, icmpReplyEth); + + } + + private void sendPacketOut(ConnectPoint outport, Ethernet payload) { + TrafficTreatment treatment = DefaultTrafficTreatment.builder(). + setOutput(outport.port()).build(); + OutboundPacket packet = new DefaultOutboundPacket(outport.deviceId(), + treatment, ByteBuffer.wrap(payload.serialize())); + packetService.emit(packet); + } + + /** + * Packet processor responsible receiving and filtering ICMP packets. + */ + private class IcmpProcessor implements PacketProcessor { + + @Override + public void process(PacketContext context) { + // Stop processing if the packet has been handled, since we + // can't do any more to it. + + if (context.isHandled()) { + return; + } + + Ethernet packet = context.inPacket().parsed(); + + if (packet == null) { + return; + } + + if (packet.getEtherType() == Ethernet.TYPE_IPV4) { + IPv4 ipv4Packet = (IPv4) packet.getPayload(); + if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_ICMP) { + processPacketIn(context.inPacket()); + } + } + } + } + +} diff --git a/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/NextHop.java b/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/NextHop.java new file mode 100644 index 00000000..88ce1a3a --- /dev/null +++ b/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/NextHop.java @@ -0,0 +1,100 @@ +/* + * 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.bgprouter; + +import java.util.Objects; + +import org.onlab.packet.IpAddress; +import org.onlab.packet.MacAddress; + +import com.google.common.base.MoreObjects; + +/** + * Represents a next hop for routing, whose MAC address has already been resolved. + */ +public class NextHop { + + private final IpAddress ip; + private final MacAddress mac; + private final NextHopGroupKey group; + + /** + * Creates a new next hop. + * + * @param ip next hop's IP address + * @param mac next hop's MAC address + * @param group next hop's group + */ + public NextHop(IpAddress ip, MacAddress mac, NextHopGroupKey group) { + this.ip = ip; + this.mac = mac; + this.group = group; + } + + /** + * Returns the next hop's IP address. + * + * @return next hop's IP address + */ + public IpAddress ip() { + return ip; + } + + /** + * Returns the next hop's MAC address. + * + * @return next hop's MAC address + */ + public MacAddress mac() { + return mac; + } + + /** + * Returns the next hop group. + * + * @return group + */ + public NextHopGroupKey group() { + return group; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof NextHop)) { + return false; + } + + NextHop that = (NextHop) o; + + return Objects.equals(this.ip, that.ip) && + Objects.equals(this.mac, that.mac) && + Objects.equals(this.group, that.group); + } + + @Override + public int hashCode() { + return Objects.hash(ip, mac, group); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("ip", ip) + .add("mac", mac) + .add("group", group) + .toString(); + } +} diff --git a/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/NextHopGroupKey.java b/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/NextHopGroupKey.java new file mode 100644 index 00000000..82a45a08 --- /dev/null +++ b/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/NextHopGroupKey.java @@ -0,0 +1,73 @@ +/* + * 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.bgprouter; + +import static com.google.common.base.Preconditions.checkNotNull; + +import java.util.Objects; + +import org.onlab.packet.IpAddress; + +import com.google.common.base.MoreObjects; + +/** + * Identifier for a next hop group. + */ +public class NextHopGroupKey { + + private final IpAddress address; + + /** + * Creates a new next hop group key. + * + * @param address next hop's IP address + */ + public NextHopGroupKey(IpAddress address) { + this.address = checkNotNull(address); + } + + /** + * Returns the next hop's IP address. + * + * @return next hop's IP address + */ + public IpAddress address() { + return address; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof NextHopGroupKey)) { + return false; + } + + NextHopGroupKey that = (NextHopGroupKey) o; + + return Objects.equals(this.address, that.address); + } + + @Override + public int hashCode() { + return Objects.hash(address); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("address", address) + .toString(); + } +} diff --git a/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/TunnellingConnectivityManager.java b/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/TunnellingConnectivityManager.java new file mode 100644 index 00000000..35af05ec --- /dev/null +++ b/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/TunnellingConnectivityManager.java @@ -0,0 +1,210 @@ +/* + * 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.bgprouter; + +import org.onlab.packet.Ethernet; +import org.onlab.packet.IPv4; +import org.onlab.packet.IpAddress; +import org.onlab.packet.TCP; +import org.onlab.packet.TpPort; +import org.onosproject.core.ApplicationId; +import org.onosproject.incubator.net.intf.Interface; +import org.onosproject.incubator.net.intf.InterfaceService; +import org.onosproject.net.ConnectPoint; +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.flowobjective.DefaultForwardingObjective; +import org.onosproject.net.flowobjective.FlowObjectiveService; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.packet.DefaultOutboundPacket; +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.config.BgpConfig; +import org.slf4j.Logger; + +import java.util.Optional; +import java.util.Set; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Manages connectivity between peers by tunnelling BGP traffic through + * OpenFlow packet-ins and packet-outs. + */ +public class TunnellingConnectivityManager { + + private static final short BGP_PORT = 179; + private final Logger log = getLogger(getClass()); + private final ApplicationId appId; + + private final BgpConfig.BgpSpeakerConfig bgpSpeaker; + + private final PacketService packetService; + private final InterfaceService interfaceService; + private final FlowObjectiveService flowObjectiveService; + + private final BgpProcessor processor = new BgpProcessor(); + + public TunnellingConnectivityManager(ApplicationId appId, + BgpConfig bgpConfig, + InterfaceService interfaceService, + PacketService packetService, + FlowObjectiveService flowObjectiveService) { + this.appId = appId; + this.interfaceService = interfaceService; + this.packetService = packetService; + this.flowObjectiveService = flowObjectiveService; + + Optional<BgpConfig.BgpSpeakerConfig> bgpSpeaker = + bgpConfig.bgpSpeakers().stream().findAny(); + + if (!bgpSpeaker.isPresent()) { + throw new IllegalArgumentException("Must have at least one BGP speaker configured"); + } + + this.bgpSpeaker = bgpSpeaker.get(); + + } + + public void start() { + packetService.addProcessor(processor, PacketProcessor.director(3)); + } + + public void stop() { + packetService.removeProcessor(processor); + // Should revoke packet requests in the future + } + + /** + * Pushes the flow rules for forwarding BGP TCP packets to controller. + * It is called when switches are connected and available. + */ + public void notifySwitchAvailable() { + // control plane OVS is available, push default flows + TrafficSelector selectorDst = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchIPProtocol(IPv4.PROTOCOL_TCP) + .matchTcpDst(TpPort.tpPort(BGP_PORT)) + .build(); + + TrafficSelector selectorSrc = DefaultTrafficSelector.builder() + .matchEthType(Ethernet.TYPE_IPV4) + .matchIPProtocol(IPv4.PROTOCOL_TCP) + .matchTcpSrc(TpPort.tpPort(BGP_PORT)) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .punt() + .build(); + + ForwardingObjective puntSrc = DefaultForwardingObjective.builder() + .fromApp(appId) + .makePermanent() + .withSelector(selectorSrc) + .withTreatment(treatment) + .withFlag(ForwardingObjective.Flag.VERSATILE) + .add(); + flowObjectiveService.forward(bgpSpeaker.connectPoint().deviceId(), + puntSrc); + + ForwardingObjective puntDst = DefaultForwardingObjective.builder() + .fromApp(appId) + .makePermanent() + .withSelector(selectorDst) + .withTreatment(treatment) + .withFlag(ForwardingObjective.Flag.VERSATILE) + .add(); + flowObjectiveService.forward(bgpSpeaker.connectPoint().deviceId(), + puntDst); + log.info("Sent punt forwarding objective to {}", bgpSpeaker.connectPoint().deviceId()); + } + + /** + * Forwards a BGP packet to another connect point. + * + * @param context the packet context of the incoming packet + */ + private void forward(PacketContext context) { + ConnectPoint outputPort = null; + + IPv4 ipv4 = (IPv4) context.inPacket().parsed().getPayload(); + IpAddress dstAddress = IpAddress.valueOf(ipv4.getDestinationAddress()); + + if (context.inPacket().receivedFrom().equals(bgpSpeaker.connectPoint())) { + if (bgpSpeaker.peers().contains(dstAddress)) { + Interface intf = interfaceService.getMatchingInterface(dstAddress); + if (intf != null) { + outputPort = intf.connectPoint(); + } + } + } else { + Set<Interface> interfaces = + interfaceService.getInterfacesByPort(context.inPacket().receivedFrom()); + + if (interfaces.stream() + .flatMap(intf -> intf.ipAddresses().stream()) + .anyMatch(ia -> ia.ipAddress().equals(dstAddress))) { + outputPort = bgpSpeaker.connectPoint(); + } + } + + if (outputPort != null) { + TrafficTreatment t = DefaultTrafficTreatment.builder() + .setOutput(outputPort.port()).build(); + OutboundPacket o = new DefaultOutboundPacket( + outputPort.deviceId(), t, context.inPacket().unparsed()); + packetService.emit(o); + } + } + + /** + * Packet processor responsible receiving and filtering BGP packets. + */ + private class BgpProcessor implements PacketProcessor { + + @Override + public void process(PacketContext context) { + // Stop processing if the packet has been handled, since we + // can't do any more to it. + if (context.isHandled()) { + return; + } + + Ethernet packet = context.inPacket().parsed(); + + if (packet == null) { + return; + } + + if (packet.getEtherType() == Ethernet.TYPE_IPV4) { + IPv4 ipv4Packet = (IPv4) packet.getPayload(); + if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_TCP) { + TCP tcpPacket = (TCP) ipv4Packet.getPayload(); + + if (tcpPacket.getDestinationPort() == BGP_PORT || + tcpPacket.getSourcePort() == BGP_PORT) { + forward(context); + } + } + } + } + } +} diff --git a/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/package-info.java b/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/package-info.java new file mode 100644 index 00000000..8db09e09 --- /dev/null +++ b/framework/src/onos/apps/bgprouter/src/main/java/org/onosproject/bgprouter/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * BGP routing application components. + */ +package org.onosproject.bgprouter;
\ No newline at end of file |