Jonathan Hart

Initial skeleton of BgpRouter app.

Added BGP tunnelling and received routes.
Added pushing groups and can now ping through the router.

Change-Id: I21a265bd72e40fc430bd392201fadccbdd67be94
<?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.1.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-cli</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
</dependency>
</dependencies>
</project>
/*
* 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.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.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.DeviceId;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
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.group.DefaultGroupBucket;
import org.onosproject.net.group.DefaultGroupDescription;
import org.onosproject.net.group.Group;
import org.onosproject.net.group.GroupBucket;
import org.onosproject.net.group.GroupBuckets;
import org.onosproject.net.group.GroupDescription;
import org.onosproject.net.group.GroupKey;
import org.onosproject.net.group.GroupService;
import org.onosproject.net.packet.PacketService;
import org.onosproject.routingapi.FibListener;
import org.onosproject.routingapi.FibUpdate;
import org.onosproject.routingapi.RoutingService;
import org.onosproject.routingapi.config.Interface;
import org.onosproject.routingapi.config.RoutingConfigurationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* 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 = 1;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowRuleService flowService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected GroupService groupService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected RoutingService routingService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected RoutingConfigurationService configService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
private ApplicationId appId;
private final Multiset<NextHop> nextHops = ConcurrentHashMultiset.create();
private final Map<NextHop, NextHopGroupKey> groups = new HashMap<>();
private DeviceId deviceId = DeviceId.deviceId("of:00000000000000a1"); // TODO config
private TunnellingConnectivityManager connectivityManager;
@Activate
protected void activate() {
log.info("Bgp1Router started");
appId = coreService.registerApplication(BGP_ROUTER_APP);
connectivityManager = new TunnellingConnectivityManager(appId,
configService,
packetService);
routingService.start(new InternalFibListener());
connectivityManager.start();
log.info("BgpRouter started");
}
@Deactivate
protected void deactivate() {
routingService.stop();
connectivityManager.stop();
log.info("BgpRouter stopped");
}
private void updateFibEntry(Collection<FibUpdate> updates) {
for (FibUpdate update : updates) {
NextHop nextHop = new NextHop(update.entry().nextHopIp(),
update.entry().nextHopMac());
addNextHop(nextHop);
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchEthType(Ethernet.TYPE_IPV4)
.matchIPDst(update.entry().prefix())
.build();
// TODO ensure group exists
NextHopGroupKey groupKey = groups.get(nextHop);
Group group = groupService.getGroup(deviceId, groupKey);
if (group == null) {
// TODO handle this
log.warn("oops, group {} wasn't there");
continue;
}
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.group(group.id())
.build();
FlowRule flowRule = new DefaultFlowRule(deviceId, selector, treatment,
PRIORITY, appId, 0, true,
FlowRule.Type.IP);
flowService.applyFlowRules(flowRule);
}
}
private void deleteFibEntry(Collection<FibUpdate> withdraws) {
for (FibUpdate update : withdraws) {
NextHop nextHop = new NextHop(update.entry().nextHopIp(),
update.entry().nextHopMac());
deleteNextHop(nextHop);
TrafficSelector selector = DefaultTrafficSelector.builder()
.matchIPDst(update.entry().prefix())
.build();
FlowRule flowRule = new DefaultFlowRule(deviceId, selector, null,
PRIORITY, appId, 0, true,
FlowRule.Type.IP);
flowService.removeFlowRules(flowRule);
}
}
private void addNextHop(NextHop nextHop) {
if (nextHops.add(nextHop, 1) == 0) {
// There was no next hop in the multiset
Interface egressIntf = configService.getMatchingInterface(nextHop.ip());
if (egressIntf == null) {
log.warn("no egress interface found for {}", nextHop);
return;
}
NextHopGroupKey groupKey = new NextHopGroupKey(nextHop.ip());
groups.put(nextHop, groupKey);
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.setEthSrc(egressIntf.mac())
.setEthDst(nextHop.mac())
.setVlanId(egressIntf.vlan())
.setOutput(egressIntf.connectPoint().port())
.build();
GroupBucket bucket = DefaultGroupBucket.createIndirectGroupBucket(treatment);
GroupDescription groupDescription
= new DefaultGroupDescription(deviceId,
GroupDescription.Type.INDIRECT,
new GroupBuckets(Collections
.singletonList(bucket)),
groupKey,
appId);
groupService.addGroup(groupDescription);
}
}
private void deleteNextHop(NextHop nextHop) {
if (nextHops.remove(nextHop, 1) <= 1) {
// There was one or less next hops, so there are now none
log.debug("removing group");
GroupKey groupKey = groups.remove(nextHop);
groupService.removeGroup(deviceId, groupKey, appId);
}
}
private class InternalFibListener implements FibListener {
@Override
public void update(Collection<FibUpdate> updates,
Collection<FibUpdate> withdraws) {
BgpRouter.this.deleteFibEntry(withdraws);
BgpRouter.this.updateFibEntry(updates);
}
}
}
/*
* 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.base.MoreObjects;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import java.util.Objects;
/**
* Created by jono on 2/12/15.
*/
public class NextHop {
private final IpAddress ip;
private final MacAddress mac;
public NextHop(IpAddress ip, MacAddress mac) {
this.ip = ip;
this.mac = mac;
}
public IpAddress ip() {
return ip;
}
public MacAddress mac() {
return mac;
}
@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);
}
@Override
public int hashCode() {
return Objects.hash(ip, mac);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(getClass())
.add("ip", ip)
.add("mac", mac)
.toString();
}
}
/*
* 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.base.MoreObjects;
import org.onlab.packet.IpAddress;
import org.onosproject.net.group.GroupKey;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Created by jono on 2/16/15.
*/
public class NextHopGroupKey implements GroupKey {
private final IpAddress address;
public NextHopGroupKey(IpAddress address) {
this.address = checkNotNull(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();
}
}
/*
* 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.onosproject.core.ApplicationId;
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.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.OutboundPacket;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketPriority;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketService;
import org.onosproject.routingapi.config.BgpPeer;
import org.onosproject.routingapi.config.BgpSpeaker;
import org.onosproject.routingapi.config.InterfaceAddress;
import org.onosproject.routingapi.config.RoutingConfigurationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 ApplicationId appId;
private final PacketService packetService;
private final RoutingConfigurationService configService;
private final BgpProcessor processor = new BgpProcessor();
public TunnellingConnectivityManager(ApplicationId appId,
RoutingConfigurationService configService,
PacketService packetService) {
this.appId = appId;
this.configService = configService;
this.packetService = packetService;
}
public void start() {
packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 3);
TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
// Request packets with BGP port as their TCP source port
selector.matchEthType(Ethernet.TYPE_IPV4);
selector.matchIPProtocol(IPv4.PROTOCOL_TCP);
selector.matchTcpSrc(BGP_PORT);
packetService.requestPackets(selector.build(), PacketPriority.CONTROL,
appId);
selector = DefaultTrafficSelector.builder();
// Request packets with BGP port as their TCP destination port
selector.matchEthType(Ethernet.TYPE_IPV4);
selector.matchIPProtocol(IPv4.PROTOCOL_TCP);
selector.matchTcpDst(BGP_PORT);
packetService.requestPackets(selector.build(), PacketPriority.CONTROL,
appId);
}
public void stop() {
packetService.removeProcessor(processor);
// Should revoke packet requests in the future
}
/**
* 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;
Logger log = LoggerFactory.getLogger(getClass());
IPv4 ipv4 = (IPv4) context.inPacket().parsed().getPayload();
IpAddress dstAddress = IpAddress.valueOf(ipv4.getDestinationAddress());
for (BgpSpeaker speaker : configService.getBgpSpeakers().values()) {
if (context.inPacket().receivedFrom().equals(speaker.connectPoint())) {
BgpPeer peer = configService.getBgpPeers().get(dstAddress);
if (peer != null) {
outputPort = peer.connectPoint();
}
break;
}
for (InterfaceAddress addr : speaker.interfaceAddresses()) {
if (addr.ipAddress().equals(dstAddress) && !context.inPacket()
.receivedFrom().equals(speaker.connectPoint())) {
outputPort = speaker.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);
}
}
}
}
}
}
......@@ -47,6 +47,7 @@
<module>election</module>
<module>routing</module>
<module>routing-api</module>
<module>bgprouter</module>
</modules>
<properties>
......
......@@ -43,7 +43,7 @@
<version>${project.version}</version>
</dependency>
<dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-thirdparty</artifactId>
<version>${project.version}</version>
......
......@@ -215,6 +215,16 @@
<bundle>mvn:org.onosproject/onos-app-routing/@ONOS-VERSION</bundle>
</feature>
<feature name="onos-app-bgprouter" version="@FEATURE-VERSION"
description="BGP router application">
<feature>onos-api</feature>
<feature>onos-app-proxyarp</feature>
<feature>onos-app-config</feature>
<bundle>mvn:org.onosproject/onos-app-bgprouter/@ONOS-VERSION</bundle>
<bundle>mvn:org.onosproject/onos-app-routing-api/@ONOS-VERSION</bundle>
<bundle>mvn:org.onosproject/onos-app-routing/@ONOS-VERSION</bundle>
</feature>
<feature name="onos-app-calendar" version="@FEATURE-VERSION"
description="REST interface for scheduling intents from an external calendar">
<feature>onos-api</feature>
......