[CORD-1616] Supports PD by DHCP relay App
Change-Id: I9a23534023ca2847bd3f77a3f9ee2b468c5bb422
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmConnectionInfo.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmConnectionInfo.java
new file mode 100644
index 0000000..cf8c393
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmConnectionInfo.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017-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.routing.fpm;
+
+import org.onosproject.cluster.NodeId;
+
+/**
+ * Information about an FPM connection.
+ */
+public class FpmConnectionInfo {
+
+ private final NodeId connectedTo;
+ private final long connectTime;
+ private final FpmPeer peer;
+
+ /**
+ * Creates a new connection info.
+ *
+ * @param connectedTo ONOS node the FPM peer is connected to
+ * @param peer FPM peer
+ * @param connectTime time the connection was made
+ */
+ public FpmConnectionInfo(NodeId connectedTo, FpmPeer peer, long connectTime) {
+ this.connectedTo = connectedTo;
+ this.peer = peer;
+ this.connectTime = connectTime;
+ }
+
+ /**
+ * Returns the node the FPM peers is connected to.
+ *
+ * @return ONOS node
+ */
+ public NodeId connectedTo() {
+ return connectedTo;
+ }
+
+ /**
+ * Returns the FPM peer.
+ *
+ * @return FPM peer
+ */
+ public FpmPeer peer() {
+ return peer;
+ }
+
+ /**
+ * Returns the time the connection was made.
+ *
+ * @return connect time
+ */
+ public long connectTime() {
+ return connectTime;
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmFrameDecoder.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmFrameDecoder.java
new file mode 100644
index 0000000..5c8f29b
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmFrameDecoder.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017-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.routing.fpm;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.handler.codec.frame.FrameDecoder;
+import org.onosproject.routing.fpm.protocol.FpmHeader;
+
+/**
+ * Frame decoder for FPM connections.
+ */
+public class FpmFrameDecoder extends FrameDecoder {
+
+ @Override
+ protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer)
+ throws Exception {
+
+ if (!channel.isConnected()) {
+ return null;
+ }
+
+ if (buffer.readableBytes() < FpmHeader.FPM_HEADER_LENGTH) {
+ return null;
+ }
+
+ buffer.markReaderIndex();
+
+ short version = buffer.readUnsignedByte();
+ short type = buffer.readUnsignedByte();
+ int length = buffer.readUnsignedShort();
+
+ buffer.resetReaderIndex();
+
+ if (buffer.readableBytes() < length) {
+ // Not enough bytes to read a whole message
+ return null;
+ }
+
+ byte[] fpmMessage = new byte[length];
+ buffer.readBytes(fpmMessage);
+
+ return FpmHeader.decode(fpmMessage, 0, fpmMessage.length);
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmInfoService.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmInfoService.java
new file mode 100644
index 0000000..a160fc0
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmInfoService.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017-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.routing.fpm;
+
+import java.util.Map;
+
+/**
+ * Provides information about the FPM route receiver module.
+ */
+public interface FpmInfoService {
+
+ /**
+ * Returns the FPM peers that are currently connected.
+ *
+ * @return a map of FPM peer with related information
+ */
+ Map<FpmPeer, FpmPeerInfo> peers();
+
+ /**
+ * Returns true if pushing routes to Quagga is emabled.
+ *
+ * @return true or false
+ */
+ boolean isPdPushEnabled();
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmListener.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmListener.java
new file mode 100644
index 0000000..07efa1d
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2017-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.routing.fpm;
+
+import org.onosproject.routing.fpm.protocol.FpmHeader;
+
+/**
+ * Listener for events from the route source.
+ */
+public interface FpmListener {
+
+ /**
+ * Handles an FPM message.
+ *
+ * @param peer FPM peer
+ * @param fpmMessage FPM message
+ */
+ void fpmMessage(FpmPeer peer, FpmHeader fpmMessage);
+
+ /**
+ * Signifies that a new peer has attempted to initiate an FPM connection.
+ *
+ * @param peer FPM peer
+ * @return true if the connection should be admitted, otherwise false
+ */
+ boolean peerConnected(FpmPeer peer);
+
+ /**
+ * Signifies that an FPM connection has been disconnected.
+ *
+ * @param peer FPM peer
+ */
+ void peerDisconnected(FpmPeer peer);
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmManager.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmManager.java
new file mode 100644
index 0000000..dbe58fe
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmManager.java
@@ -0,0 +1,707 @@
+/*
+ * Copyright 2017-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.routing.fpm;
+
+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.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.Service;
+import org.jboss.netty.bootstrap.ServerBootstrap;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelException;
+import org.jboss.netty.channel.ChannelFactory;
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.channel.group.ChannelGroup;
+import org.jboss.netty.channel.group.DefaultChannelGroup;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.jboss.netty.handler.timeout.IdleStateHandler;
+import org.jboss.netty.util.HashedWheelTimer;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpPrefix;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.host.InterfaceIpAddress;
+import org.onosproject.net.intf.InterfaceService;
+import org.onlab.util.KryoNamespace;
+import org.onlab.util.Tools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.core.CoreService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.routeservice.Route;
+import org.onosproject.routeservice.RouteAdminService;
+import org.onosproject.routing.fpm.protocol.FpmHeader;
+import org.onosproject.routing.fpm.protocol.Netlink;
+import org.onosproject.routing.fpm.protocol.NetlinkMessageType;
+import org.onosproject.routing.fpm.protocol.RouteAttribute;
+import org.onosproject.routing.fpm.protocol.RouteAttributeDst;
+import org.onosproject.routing.fpm.protocol.RouteAttributeGateway;
+import org.onosproject.routing.fpm.protocol.RtNetlink;
+import org.onosproject.routing.fpm.protocol.RtProtocol;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.onosproject.store.StoreDelegate;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.LinkedList;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import static java.util.concurrent.Executors.newCachedThreadPool;
+import static org.onlab.util.Tools.groupedThreads;
+import org.onosproject.routing.fpm.api.FpmPrefixStoreEvent;
+import org.onosproject.routing.fpm.api.FpmPrefixStore;
+import org.onosproject.routing.fpm.api.FpmRecord;
+
+/**
+ * Forwarding Plane Manager (FPM) route source.
+ */
+@Service
+@Component(immediate = true)
+public class FpmManager implements FpmInfoService {
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private static final int FPM_PORT = 2620;
+ private static final String APP_NAME = "org.onosproject.fpm";
+ private static final int IDLE_TIMEOUT_SECS = 5;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService componentConfigService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected RouteAdminService routeService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ClusterService clusterService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected StorageService storageService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected InterfaceService interfaceService;
+
+ @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY,
+ bind = "bindRipStore",
+ unbind = "unbindRipStore",
+ policy = ReferencePolicy.DYNAMIC,
+ target = "(fpm_type=RIP)")
+ protected volatile FpmPrefixStore ripStore;
+
+ @Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY,
+ bind = "bindDhcpStore",
+ unbind = "unbindDhcpStore",
+ policy = ReferencePolicy.DYNAMIC,
+ target = "(fpm_type=DHCP)")
+ protected volatile FpmPrefixStore dhcpStore;
+
+ private final StoreDelegate<FpmPrefixStoreEvent> fpmPrefixStoreDelegate
+ = new FpmPrefixStoreDelegate();
+
+ private ApplicationId appId;
+ private ServerBootstrap serverBootstrap;
+ private Channel serverChannel;
+ private ChannelGroup allChannels = new DefaultChannelGroup();
+
+ private ConsistentMap<FpmPeer, Set<FpmConnectionInfo>> peers;
+
+ private Map<FpmPeer, Map<IpPrefix, Route>> fpmRoutes = new ConcurrentHashMap<>();
+
+ @Property(name = "clearRoutes", boolValue = true,
+ label = "Whether to clear routes when the FPM connection goes down")
+ private boolean clearRoutes = true;
+
+ @Property(name = "pdPushEnabled", boolValue = false,
+ label = "Whether to push prefixes to Quagga over fpm connection")
+ private boolean pdPushEnabled = false;
+
+ @Property(name = "pdPushNextHopIPv4", value = "",
+ label = "IPv4 next-hop address for PD Pushing.")
+ private Ip4Address pdPushNextHopIPv4 = null;
+
+ @Property(name = "pdPushNextHopIPv6", value = "",
+ label = "IPv6 next-hop address for PD Pushing.")
+ private Ip6Address pdPushNextHopIPv6 = null;
+
+ protected void bindRipStore(FpmPrefixStore store) {
+ if ((ripStore == null) && (store != null)) {
+ ripStore = store;
+ ripStore.setDelegate(fpmPrefixStoreDelegate);
+ for (Channel ch : allChannels) {
+ processRipStaticRoutes(ch);
+ }
+ }
+ }
+
+ protected void unbindRipStore(FpmPrefixStore store) {
+ if (ripStore == store) {
+ ripStore.unsetDelegate(fpmPrefixStoreDelegate);
+ ripStore = null;
+ }
+ }
+
+ protected void bindDhcpStore(FpmPrefixStore store) {
+ if ((dhcpStore == null) && (store != null)) {
+ dhcpStore = store;
+ dhcpStore.setDelegate(fpmPrefixStoreDelegate);
+ for (Channel ch : allChannels) {
+ processDhcpStaticRoutes(ch);
+ }
+ }
+ }
+
+ protected void unbindDhcpStore(FpmPrefixStore store) {
+ if (dhcpStore == store) {
+ dhcpStore.unsetDelegate(fpmPrefixStoreDelegate);
+ dhcpStore = null;
+ }
+ }
+
+ @Activate
+ protected void activate(ComponentContext context) {
+ componentConfigService.preSetProperty(
+ "org.onosproject.incubator.store.routing.impl.RouteStoreImpl",
+ "distributed", "true");
+
+ componentConfigService.registerProperties(getClass());
+
+ KryoNamespace serializer = KryoNamespace.newBuilder()
+ .register(KryoNamespaces.API)
+ .register(FpmPeer.class)
+ .register(FpmConnectionInfo.class)
+ .build();
+ peers = storageService.<FpmPeer, Set<FpmConnectionInfo>>consistentMapBuilder()
+ .withName("fpm-connections")
+ .withSerializer(Serializer.using(serializer))
+ .build();
+
+ modified(context);
+ startServer();
+
+ appId = coreService.registerApplication(APP_NAME, peers::destroy);
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ componentConfigService.preSetProperty(
+ "org.onosproject.incubator.store.routing.impl.RouteStoreImpl",
+ "distributed", "false");
+
+ stopServer();
+ fpmRoutes.clear();
+ componentConfigService.unregisterProperties(getClass(), false);
+ log.info("Stopped");
+ }
+
+ @Modified
+ protected void modified(ComponentContext context) {
+ Dictionary<?, ?> properties = context.getProperties();
+ if (properties == null) {
+ return;
+ }
+ String strClearRoutes = Tools.get(properties, "clearRoutes");
+ if (strClearRoutes != null) {
+ clearRoutes = Boolean.parseBoolean(strClearRoutes);
+ log.info("clearRoutes is {}", clearRoutes);
+ }
+
+ String strPdPushEnabled = Tools.get(properties, "pdPushEnabled");
+ if (strPdPushEnabled != null) {
+ boolean oldValue = pdPushEnabled;
+ pdPushEnabled = Boolean.parseBoolean(strPdPushEnabled);
+ if (pdPushEnabled) {
+
+ pdPushNextHopIPv4 = null;
+ pdPushNextHopIPv6 = null;
+
+ String strPdPushNextHopIPv4 = Tools.get(properties, "pdPushNextHopIPv4");
+ if (strPdPushNextHopIPv4 != null) {
+ pdPushNextHopIPv4 = Ip4Address.valueOf(strPdPushNextHopIPv4);
+ }
+ String strPdPushNextHopIPv6 = Tools.get(properties, "pdPushNextHopIPv6");
+ if (strPdPushNextHopIPv6 != null) {
+ pdPushNextHopIPv6 = Ip6Address.valueOf(strPdPushNextHopIPv6);
+ }
+
+ if (pdPushNextHopIPv4 == null) {
+ pdPushNextHopIPv4 = interfaceService.getInterfaces()
+ .stream()
+ .filter(iface -> iface.name().contains("RUR"))
+ .map(Interface::ipAddressesList)
+ .flatMap(Collection::stream)
+ .map(InterfaceIpAddress::ipAddress)
+ .filter(IpAddress::isIp4)
+ .map(IpAddress::getIp4Address)
+ .findFirst()
+ .orElse(null);
+ }
+
+ if (pdPushNextHopIPv6 == null) {
+ pdPushNextHopIPv6 = interfaceService.getInterfaces()
+ .stream()
+ .filter(iface -> iface.name().contains("RUR"))
+ .map(Interface::ipAddressesList)
+ .flatMap(Collection::stream)
+ .map(InterfaceIpAddress::ipAddress)
+ .filter(IpAddress::isIp6)
+ .map(IpAddress::getIp6Address)
+ .findFirst()
+ .orElse(null);
+ }
+
+ log.info("PD pushing is enabled.");
+ if (pdPushNextHopIPv4 != null) {
+ log.info("ipv4 next-hop {}", pdPushNextHopIPv4.toString());
+ } else {
+ log.info("ipv4 next-hop is null");
+ }
+ if (pdPushNextHopIPv6 != null) {
+ log.info("ipv6 next-hop={}", pdPushNextHopIPv6.toString());
+ } else {
+ log.info("ipv6 next-hop is null");
+ }
+ if (!oldValue) {
+ processStaticRoutes();
+ }
+ } else {
+ log.info("PD pushing is disabled.");
+ }
+ }
+ }
+
+ private void startServer() {
+ HashedWheelTimer timer = new HashedWheelTimer(
+ groupedThreads("onos/fpm", "fpm-timer-%d", log));
+
+ ChannelFactory channelFactory = new NioServerSocketChannelFactory(
+ newCachedThreadPool(groupedThreads("onos/fpm", "sm-boss-%d", log)),
+ newCachedThreadPool(groupedThreads("onos/fpm", "sm-worker-%d", log)));
+ ChannelPipelineFactory pipelineFactory = () -> {
+ // Allocate a new session per connection
+ IdleStateHandler idleHandler =
+ new IdleStateHandler(timer, IDLE_TIMEOUT_SECS, 0, 0);
+ FpmSessionHandler fpmSessionHandler =
+ new FpmSessionHandler(this, new InternalFpmListener());
+ FpmFrameDecoder fpmFrameDecoder = new FpmFrameDecoder();
+
+ // Setup the processing pipeline
+ ChannelPipeline pipeline = Channels.pipeline();
+ pipeline.addLast("FpmFrameDecoder", fpmFrameDecoder);
+ pipeline.addLast("idle", idleHandler);
+ pipeline.addLast("FpmSession", fpmSessionHandler);
+ return pipeline;
+ };
+
+ InetSocketAddress listenAddress = new InetSocketAddress(FPM_PORT);
+
+ serverBootstrap = new ServerBootstrap(channelFactory);
+ serverBootstrap.setOption("child.reuseAddr", true);
+ serverBootstrap.setOption("child.keepAlive", true);
+ serverBootstrap.setOption("child.tcpNoDelay", true);
+ serverBootstrap.setPipelineFactory(pipelineFactory);
+ try {
+ serverChannel = serverBootstrap.bind(listenAddress);
+ allChannels.add(serverChannel);
+ } catch (ChannelException e) {
+ log.debug("Exception binding to FPM port {}: ",
+ listenAddress.getPort(), e);
+ stopServer();
+ }
+ }
+
+ private void stopServer() {
+ allChannels.close().awaitUninterruptibly();
+ allChannels.clear();
+ if (serverBootstrap != null) {
+ serverBootstrap.releaseExternalResources();
+ }
+
+ if (clearRoutes) {
+ peers.keySet().forEach(this::clearRoutes);
+ }
+ }
+
+ private void fpmMessage(FpmPeer peer, FpmHeader fpmMessage) {
+ if (fpmMessage.type() == FpmHeader.FPM_TYPE_KEEPALIVE) {
+ return;
+ }
+
+ Netlink netlink = fpmMessage.netlink();
+ RtNetlink rtNetlink = netlink.rtNetlink();
+
+ if (log.isTraceEnabled()) {
+ log.trace("Received FPM message: {}", fpmMessage);
+ }
+
+ if (!(rtNetlink.protocol() == RtProtocol.ZEBRA ||
+ rtNetlink.protocol() == RtProtocol.UNSPEC)) {
+ log.trace("Ignoring non-zebra route");
+ return;
+ }
+
+ IpAddress dstAddress = null;
+ IpAddress gateway = null;
+
+ for (RouteAttribute attribute : rtNetlink.attributes()) {
+ if (attribute.type() == RouteAttribute.RTA_DST) {
+ RouteAttributeDst raDst = (RouteAttributeDst) attribute;
+ dstAddress = raDst.dstAddress();
+ } else if (attribute.type() == RouteAttribute.RTA_GATEWAY) {
+ RouteAttributeGateway raGateway = (RouteAttributeGateway) attribute;
+ gateway = raGateway.gateway();
+ }
+ }
+
+ if (dstAddress == null) {
+ log.error("Dst address missing!");
+ return;
+ }
+
+ IpPrefix prefix = IpPrefix.valueOf(dstAddress, rtNetlink.dstLength());
+
+ List<Route> updates = new LinkedList<>();
+ List<Route> withdraws = new LinkedList<>();
+
+ Route route;
+ switch (netlink.type()) {
+ case RTM_NEWROUTE:
+ if (gateway == null) {
+ // We ignore interface routes with no gateway for now.
+ return;
+ }
+ route = new Route(Route.Source.FPM, prefix, gateway, clusterService.getLocalNode().id());
+
+
+ Route oldRoute = fpmRoutes.get(peer).put(prefix, route);
+
+ if (oldRoute != null) {
+ log.trace("Swapping {} with {}", oldRoute, route);
+ withdraws.add(oldRoute);
+ }
+ updates.add(route);
+ break;
+ case RTM_DELROUTE:
+ Route existing = fpmRoutes.get(peer).remove(prefix);
+ if (existing == null) {
+ log.warn("Got delete for non-existent prefix");
+ return;
+ }
+
+ route = new Route(Route.Source.FPM, prefix, existing.nextHop(), clusterService.getLocalNode().id());
+
+ withdraws.add(route);
+ break;
+ case RTM_GETROUTE:
+ default:
+ break;
+ }
+
+ routeService.withdraw(withdraws);
+ routeService.update(updates);
+ }
+
+ private void clearRoutes(FpmPeer peer) {
+ log.info("Clearing all routes for peer {}", peer);
+ Map<IpPrefix, Route> routes = fpmRoutes.remove(peer);
+ if (routes != null) {
+ routeService.withdraw(routes.values());
+ }
+ }
+
+ public void processStaticRoutes() {
+ for (Channel ch : allChannels) {
+ processStaticRoutes(ch);
+ }
+ }
+
+ public void processStaticRoutes(Channel ch) {
+ processRipStaticRoutes(ch);
+ processDhcpStaticRoutes(ch);
+ }
+
+ private void processRipStaticRoutes(Channel ch) {
+
+ /* Get RIP static routes. */
+ if (ripStore != null) {
+ Collection<FpmRecord> ripRecords = ripStore.getFpmRecords();
+ log.info("RIP store size is {}", ripRecords.size());
+
+ ripRecords.forEach(record -> sendRouteUpdateToChannel(true,
+ record.ipPrefix(), ch));
+ }
+ }
+
+ private void processDhcpStaticRoutes(Channel ch) {
+
+ /* Get Dhcp static routes. */
+ if (dhcpStore != null) {
+ Collection<FpmRecord> dhcpRecords = dhcpStore.getFpmRecords();
+ log.info("Dhcp store size is {}", dhcpRecords.size());
+
+ dhcpRecords.forEach(record -> sendRouteUpdateToChannel(true,
+ record.ipPrefix(), ch));
+ }
+ }
+
+ private void sendRouteUpdateToChannel(boolean isAdd, IpPrefix prefix, Channel ch) {
+
+ int netLinkLength;
+ short addrFamily;
+ IpAddress pdPushNextHop;
+
+ if (!pdPushEnabled) {
+ return;
+ }
+
+ try {
+ // Construct list of route attributes.
+ List<RouteAttribute> attributes = new ArrayList<>();
+ if (prefix.isIp4()) {
+ if (pdPushNextHopIPv4 == null) {
+ log.info("Prefix not pushed because ipv4 next-hop is null.");
+ return;
+ }
+ pdPushNextHop = pdPushNextHopIPv4;
+ netLinkLength = Ip4Address.BYTE_LENGTH + RouteAttribute.ROUTE_ATTRIBUTE_HEADER_LENGTH;
+ addrFamily = RtNetlink.RT_ADDRESS_FAMILY_INET;
+ } else {
+ if (pdPushNextHopIPv6 == null) {
+ log.info("Prefix not pushed because ipv6 next-hop is null.");
+ return;
+ }
+ pdPushNextHop = pdPushNextHopIPv6;
+ netLinkLength = Ip6Address.BYTE_LENGTH + RouteAttribute.ROUTE_ATTRIBUTE_HEADER_LENGTH;
+ addrFamily = RtNetlink.RT_ADDRESS_FAMILY_INET6;
+ }
+
+ RouteAttributeDst raDst = new RouteAttributeDst(
+ netLinkLength,
+ RouteAttribute.RTA_DST,
+ prefix.address());
+ attributes.add(raDst);
+
+ RouteAttributeGateway raGateway = new RouteAttributeGateway(
+ netLinkLength,
+ RouteAttribute.RTA_GATEWAY,
+ pdPushNextHop);
+ attributes.add(raGateway);
+
+ // Add RtNetlink header.
+ int srcLength = 0;
+ short tos = 0;
+ short table = 0;
+ short scope = 0;
+ long rtFlags = 0;
+ int messageLength = raDst.length() + raGateway.length() +
+ RtNetlink.RT_NETLINK_LENGTH;
+
+ RtNetlink rtNetlink = new RtNetlink(
+ addrFamily,
+ prefix.prefixLength(),
+ srcLength,
+ tos,
+ table,
+ RtProtocol.ZEBRA,
+ scope,
+ FpmHeader.FPM_TYPE_NETLINK,
+ rtFlags,
+ attributes);
+
+ // Add Netlink header.
+ NetlinkMessageType nlMsgType;
+ if (isAdd) {
+ nlMsgType = NetlinkMessageType.RTM_NEWROUTE;
+ } else {
+ nlMsgType = NetlinkMessageType.RTM_DELROUTE;
+ }
+ int flags = Netlink.NETLINK_REQUEST | Netlink.NETLINK_CREATE;
+ long sequence = 0;
+ long processPortId = 0;
+ messageLength += Netlink.NETLINK_HEADER_LENGTH;
+
+ Netlink netLink = new Netlink(messageLength,
+ nlMsgType,
+ flags,
+ sequence,
+ processPortId,
+ rtNetlink);
+
+ messageLength += FpmHeader.FPM_HEADER_LENGTH;
+
+ // Add FpmHeader.
+ FpmHeader fpmMessage = new FpmHeader(
+ FpmHeader.FPM_VERSION_1,
+ FpmHeader.FPM_TYPE_NETLINK,
+ messageLength,
+ netLink);
+
+ // Encode message in a channel buffer and transmit.
+ ch.write(fpmMessage.encode());
+
+ } catch (RuntimeException e) {
+ log.info("Route not sent over fpm connection.");
+ }
+ }
+
+ private void sendRouteUpdate(boolean isAdd, IpPrefix prefix) {
+
+ for (Channel ch : allChannels) {
+ sendRouteUpdateToChannel(isAdd, prefix, ch);
+ }
+ }
+
+ public boolean isPdPushEnabled() {
+ return pdPushEnabled;
+ }
+
+ private FpmPeerInfo toFpmInfo(FpmPeer peer, Collection<FpmConnectionInfo> connections) {
+ return new FpmPeerInfo(connections,
+ fpmRoutes.getOrDefault(peer, Collections.emptyMap()).size());
+ }
+
+ @Override
+ public Map<FpmPeer, FpmPeerInfo> peers() {
+ return peers.asJavaMap().entrySet().stream()
+ .collect(Collectors.toMap(
+ e -> e.getKey(),
+ e -> toFpmInfo(e.getKey(), e.getValue())));
+ }
+
+ private class InternalFpmListener implements FpmListener {
+ @Override
+ public void fpmMessage(FpmPeer peer, FpmHeader fpmMessage) {
+ FpmManager.this.fpmMessage(peer, fpmMessage);
+ }
+
+ @Override
+ public boolean peerConnected(FpmPeer peer) {
+ if (peers.keySet().contains(peer)) {
+ return false;
+ }
+
+ NodeId localNode = clusterService.getLocalNode().id();
+ peers.compute(peer, (p, infos) -> {
+ if (infos == null) {
+ infos = new HashSet<>();
+ }
+
+ infos.add(new FpmConnectionInfo(localNode, peer, System.currentTimeMillis()));
+ return infos;
+ });
+
+ fpmRoutes.computeIfAbsent(peer, p -> new ConcurrentHashMap<>());
+ return true;
+ }
+
+ @Override
+ public void peerDisconnected(FpmPeer peer) {
+ log.info("FPM connection to {} went down", peer);
+
+ if (clearRoutes) {
+ clearRoutes(peer);
+ }
+
+ peers.compute(peer, (p, infos) -> {
+ if (infos == null) {
+ return null;
+ }
+
+ infos.stream()
+ .filter(i -> i.connectedTo().equals(clusterService.getLocalNode().id()))
+ .findAny()
+ .ifPresent(i -> infos.remove(i));
+
+ if (infos.isEmpty()) {
+ return null;
+ }
+
+ return infos;
+ });
+ }
+ }
+
+ /**
+ * Adds a channel to the channel group.
+ *
+ * @param channel the channel to add
+ */
+ public void addSessionChannel(Channel channel) {
+ allChannels.add(channel);
+ }
+
+ /**
+ * Removes a channel from the channel group.
+ *
+ * @param channel the channel to remove
+ */
+ public void removeSessionChannel(Channel channel) {
+ allChannels.remove(channel);
+ }
+
+ /**
+ * Store delegate for Fpm Prefix store.
+ * Handles Fpm prefix store event.
+ */
+ class FpmPrefixStoreDelegate implements StoreDelegate<FpmPrefixStoreEvent> {
+
+ @Override
+ public void notify(FpmPrefixStoreEvent e) {
+
+ log.trace("FpmPrefixStoreEvent notify");
+
+ FpmRecord record = e.subject();
+ switch (e.type()) {
+ case ADD:
+ sendRouteUpdate(true, record.ipPrefix());
+ break;
+ case REMOVE:
+ sendRouteUpdate(false, record.ipPrefix());
+ break;
+ default:
+ log.warn("unsupported store event type", e.type());
+ return;
+ }
+ }
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmPeer.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmPeer.java
new file mode 100644
index 0000000..e0f98bf
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmPeer.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2017-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.routing.fpm;
+
+import org.onlab.packet.IpAddress;
+
+import java.net.InetSocketAddress;
+import java.util.Objects;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Represents an FPM peer.
+ */
+public class FpmPeer {
+
+ private final IpAddress address;
+ private final int port;
+
+ /**
+ * Creates a new FPM peer.
+ *
+ * @param address peer IP address
+ * @param port peer TCP port number
+ */
+ public FpmPeer(IpAddress address, int port) {
+ this.address = address;
+ this.port = port;
+ }
+
+ /**
+ * Returns the peers IP address.
+ *
+ * @return IP address
+ */
+ public IpAddress address() {
+ return address;
+ }
+
+ /**
+ * Returns the peer port number.
+ *
+ * @return port number
+ */
+ public int port() {
+ return port;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(address, port);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof FpmPeer)) {
+ return false;
+ }
+
+ FpmPeer that = (FpmPeer) other;
+
+ return Objects.equals(this.address, that.address) &&
+ Objects.equals(this.port, that.port);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("address", address)
+ .add("port", port)
+ .toString();
+ }
+
+ public static FpmPeer fromSocketAddress(InetSocketAddress address) {
+ return new FpmPeer(IpAddress.valueOf(address.getAddress()), address.getPort());
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmPeerInfo.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmPeerInfo.java
new file mode 100644
index 0000000..ca3eeb4
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmPeerInfo.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2017-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.routing.fpm;
+
+import java.util.Collection;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Information about an FPM peer.
+ */
+public class FpmPeerInfo {
+
+ private final Collection<FpmConnectionInfo> connections;
+ private final int routes;
+
+ /**
+ * Class constructor.
+ *
+ * @param connections connection information for the peer
+ * @param routes number of routes the peer has sent to this node
+ */
+ public FpmPeerInfo(Collection<FpmConnectionInfo> connections, int routes) {
+ this.connections = checkNotNull(connections);
+ this.routes = routes;
+ }
+
+ /**
+ * Returns connection information for the peer.
+ *
+ * @return collection of connection information
+ */
+ public Collection<FpmConnectionInfo> connections() {
+ return connections;
+ }
+
+ /**
+ * Returns number of routes sent to this node.
+ *
+ * @return number of routes
+ */
+ public int routes() {
+ return routes;
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmSessionHandler.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmSessionHandler.java
new file mode 100644
index 0000000..658c058
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/FpmSessionHandler.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2017-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.routing.fpm;
+
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+import org.jboss.netty.channel.ExceptionEvent;
+import org.jboss.netty.channel.MessageEvent;
+import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler;
+import org.jboss.netty.handler.timeout.IdleStateEvent;
+import org.jboss.netty.handler.timeout.ReadTimeoutException;
+import org.onosproject.routing.fpm.protocol.FpmHeader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Session handler for FPM protocol.
+ */
+public class FpmSessionHandler extends IdleStateAwareChannelHandler {
+
+ private static Logger log = LoggerFactory.getLogger(FpmSessionHandler.class);
+
+ private final FpmListener fpmListener;
+
+ private final FpmManager fpmManager;
+ private Channel channel;
+ private FpmPeer us;
+
+ private boolean useKeepalives;
+ private boolean initialized;
+
+ /**
+ * Class constructor.
+ *
+ * @param fpmMgr manager
+ * @param fpmListener listener for FPM messages
+ */
+ public FpmSessionHandler(FpmManager fpmMgr, FpmListener fpmListener) {
+ this.fpmManager = fpmMgr;
+ this.fpmListener = checkNotNull(fpmListener);
+ }
+
+ @Override
+ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
+ throws Exception {
+ FpmHeader fpmMessage = (FpmHeader) e.getMessage();
+
+ initConnection(ctx, fpmMessage);
+
+ fpmListener.fpmMessage(us, fpmMessage);
+ }
+
+ private void initConnection(ChannelHandlerContext ctx, FpmHeader message) {
+ if (!initialized) {
+ useKeepalives = message.version() >= FpmHeader.FPM_VERSION_ONOS_EXT;
+ if (useKeepalives) {
+ log.info("Using keepalives");
+ } else {
+ log.info("Not using keepalives");
+ // Remove the idle channel handler if using a protocol version
+ // with no keepalive messages
+ ctx.getPipeline().remove("idle");
+ }
+ initialized = true;
+ }
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
+ throws Exception {
+ if (e.getCause() instanceof ReadTimeoutException) {
+ log.warn("Haven't heard from FPM client for a while");
+ } else {
+ log.error("Exception thrown while handling FPM message", e.getCause());
+ }
+ if (channel != null) {
+ channel.close();
+ }
+ handleDisconnect();
+ }
+
+ @Override
+ public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
+ throws Exception {
+ SocketAddress socketAddress = ctx.getChannel().getRemoteAddress();
+
+ if (!(socketAddress instanceof InetSocketAddress)) {
+ throw new IllegalStateException("Address type is not InetSocketAddress");
+ }
+
+ us = FpmPeer.fromSocketAddress((InetSocketAddress) socketAddress);
+
+ if (!fpmListener.peerConnected(us)) {
+ log.error("Received new FPM connection while already connected");
+ ctx.getChannel().close();
+ return;
+ }
+
+ channel = ctx.getChannel();
+ fpmManager.addSessionChannel(e.getChannel());
+ fpmManager.processStaticRoutes(e.getChannel());
+ }
+
+ @Override
+ public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)
+ throws Exception {
+ }
+
+ @Override
+ public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e)
+ throws Exception {
+ handleDisconnect();
+ }
+
+ @Override
+ public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
+ throws Exception {
+ fpmManager.removeSessionChannel(e.getChannel());
+ }
+
+ private void handleDisconnect() {
+ if (us != null) {
+ fpmListener.peerDisconnected(us);
+ }
+ channel = null;
+ }
+
+ @Override
+ public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e)
+ throws Exception {
+ log.warn("FPM channel idle");
+ if (useKeepalives) {
+ ctx.getChannel().close();
+ }
+ super.channelIdle(ctx, e);
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/cli/FpmConnectionsList.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/cli/FpmConnectionsList.java
new file mode 100644
index 0000000..33ff5d6
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/cli/FpmConnectionsList.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2017-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.routing.fpm.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.packet.IpAddress;
+import org.onlab.util.Tools;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.routing.fpm.FpmPeerInfo;
+import org.onosproject.routing.fpm.FpmInfoService;
+import org.onosproject.routing.fpm.FpmPeer;
+
+import java.util.Comparator;
+import java.util.Map;
+
+/**
+ * Displays the current FPM connections.
+ */
+@Command(scope = "onos", name = "fpm-connections",
+ description = "Displays the current FPM connections")
+public class FpmConnectionsList extends AbstractShellCommand {
+
+ private static final String FORMAT = "peer %s:%s connected to %s since %s %s (%d routes locally)";
+
+ @Override
+ protected void execute() {
+ FpmInfoService fpmInfo = get(FpmInfoService.class);
+
+ if (fpmInfo.isPdPushEnabled()) {
+ print("PD Pushing is enabled/disbled.");
+ }
+ fpmInfo.peers().entrySet().stream()
+ .sorted(Comparator.<Map.Entry<FpmPeer, FpmPeerInfo>, IpAddress>comparing(e -> e.getKey().address())
+ .thenComparing(e -> e.getKey().port()))
+ .map(Map.Entry::getValue)
+ .forEach(this::print);
+ }
+
+ private void print(FpmPeerInfo info) {
+ ClusterService clusterService = get(ClusterService.class);
+
+ info.connections().forEach(cinfo ->
+ print(FORMAT, cinfo.peer().address(), cinfo.peer().port(),
+ cinfo.connectedTo(), Tools.timeAgo(cinfo.connectTime()),
+ cinfo.connectedTo().equals(clusterService.getLocalNode().id()) ? "*" : "",
+ info.routes())
+ );
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/cli/package-info.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/cli/package-info.java
new file mode 100644
index 0000000..ee64e04
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-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.
+ */
+
+/**
+ * FPM-related CLI commands.
+ */
+package org.onosproject.routing.fpm.cli;
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/package-info.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/package-info.java
new file mode 100644
index 0000000..c879073
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-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.
+ */
+
+/**
+ * Forwarding Plane Manager (FPM) implementation.
+ */
+package org.onosproject.routing.fpm;
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/FpmHeader.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/FpmHeader.java
new file mode 100644
index 0000000..607c9e1
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/FpmHeader.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2017-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.routing.fpm.protocol;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.DeserializationException;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import java.nio.ByteBuffer;
+
+import static org.onlab.packet.PacketUtils.checkInput;
+
+/**
+ * FPM header.
+ */
+public final class FpmHeader {
+ public static final int FPM_HEADER_LENGTH = 4;
+ public static final int FPM_MESSAGE_MAX_LENGTH = 4096;
+
+ public static final short FPM_VERSION_1 = 1;
+ public static final short FPM_VERSION_ONOS_EXT = 32;
+
+ private static final ImmutableSet<Short> SUPPORTED_VERSIONS =
+ ImmutableSet.<Short>builder()
+ .add(FPM_VERSION_1)
+ .add(FPM_VERSION_ONOS_EXT)
+ .build();
+
+ public static final short FPM_TYPE_NETLINK = 1;
+ public static final short FPM_TYPE_PROTOBUF = 2;
+ public static final short FPM_TYPE_KEEPALIVE = 32;
+
+ private static final String VERSION_NOT_SUPPORTED = "FPM version not supported: ";
+ private static final String TYPE_NOT_SUPPORTED = "FPM type not supported: ";
+
+ private final short version;
+ private final short type;
+ private final int length;
+
+ private final Netlink netlink;
+
+ /**
+ * Class constructor.
+ *
+ * @param version version
+ * @param type type
+ * @param length length
+ * @param netlink netlink header
+ */
+ public FpmHeader(short version, short type, int length, Netlink netlink) {
+ this.version = version;
+ this.type = type;
+ this.length = length;
+ this.netlink = netlink;
+ }
+
+ /**
+ * Returns the protocol version.
+ *
+ * @return protocol version
+ */
+ public short version() {
+ return version;
+ }
+
+ /**
+ * Returns the type.
+ *
+ * @return type
+ */
+ public short type() {
+ return type;
+ }
+
+ /**
+ * Returns the message length.
+ *
+ * @return message length
+ */
+ public int length() {
+ return length;
+ }
+
+ /**
+ * Returns the netlink header.
+ *
+ * @return netlink header
+ */
+ public Netlink netlink() {
+ return netlink;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("version", version)
+ .add("type", type)
+ .add("length", length)
+ .add("netlink", netlink)
+ .toString();
+ }
+
+ /**
+ * Decodes an FPM header from an input buffer.
+ *
+ * @param buffer input buffer
+ * @param start starting position the FPM header
+ * @param length length of the message
+ * @return FPM header
+ * @throws DeserializationException if an FPM header could not be decoded
+ * from the input buffer
+ */
+ public static FpmHeader decode(byte[] buffer, int start, int length) throws
+ DeserializationException {
+ checkInput(buffer, start, length, FPM_HEADER_LENGTH);
+
+ ByteBuffer bb = ByteBuffer.wrap(buffer, start, length);
+
+ short version = bb.get();
+ if (!SUPPORTED_VERSIONS.contains(version)) {
+ throw new DeserializationException(VERSION_NOT_SUPPORTED + version);
+ }
+
+ short type = bb.get();
+ int messageLength = bb.getShort();
+
+ if (type == FPM_TYPE_KEEPALIVE) {
+ return new FpmHeader(version, type, messageLength, null);
+ }
+
+ if (type != FPM_TYPE_NETLINK) {
+ throw new DeserializationException(TYPE_NOT_SUPPORTED + type);
+ }
+
+ Netlink netlink = Netlink.decode(buffer, bb.position(), bb.limit() - bb.position());
+
+ return new FpmHeader(version, type, messageLength, netlink);
+ }
+
+ /**
+ * Encode the FpmHeader contents into a ChannelBuffer.
+ *
+ * @return filled in ChannelBuffer
+ */
+ public ChannelBuffer encode() {
+
+ ChannelBuffer cb = ChannelBuffers.buffer(FPM_MESSAGE_MAX_LENGTH);
+
+ cb.writeByte(version);
+ cb.writeByte(type);
+ cb.writeShort(length);
+
+ netlink.encode(cb);
+ return cb;
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/Netlink.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/Netlink.java
new file mode 100644
index 0000000..df84ed6
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/Netlink.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2017-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.routing.fpm.protocol;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.DeserializationException;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import java.nio.ByteBuffer;
+
+import static org.onlab.packet.PacketUtils.checkInput;
+
+/**
+ * Netlink header.
+ * <p>
+ * Taken from struct nlmsghdr in linux/netlink.h
+ * </p>
+ */
+public final class Netlink {
+
+ public static final int NETLINK_HEADER_LENGTH = 16;
+
+ public static final int NETLINK_REQUEST = 0x01;
+ public static final int NETLINK_CREATE = 0x400;
+
+ private final long length;
+ private final NetlinkMessageType type;
+ private final int flags;
+ private final long sequence;
+ private final long processPortId;
+
+ private final RtNetlink rtNetlink;
+
+ /**
+ * Class constructor.
+ *
+ * @param length message length
+ * @param type type
+ * @param flags flags
+ * @param sequence sequence number
+ * @param processPortId port ID
+ * @param rtNetlink netlink routing message
+ */
+ public Netlink(long length, NetlinkMessageType type, int flags, long sequence,
+ long processPortId, RtNetlink rtNetlink) {
+ this.length = length;
+ this.type = type;
+ this.flags = flags;
+ this.sequence = sequence;
+ this.processPortId = processPortId;
+ this.rtNetlink = rtNetlink;
+ }
+
+ /**
+ * Returns the message length.
+ *
+ * @return length
+ */
+ public long length() {
+ return length;
+ }
+
+ /**
+ * Returns the message type.
+ *
+ * @return message type
+ */
+ public NetlinkMessageType type() {
+ return type;
+ }
+
+ /**
+ * Returns the flags.
+ *
+ * @return flags
+ */
+ public int flags() {
+ return flags;
+ }
+
+ /**
+ * Returns the sequence number.
+ *
+ * @return sequence number
+ */
+ public long sequence() {
+ return sequence;
+ }
+
+ /**
+ * Returns the port ID.
+ *
+ * @return port ID
+ */
+ public long processPortId() {
+ return processPortId;
+ }
+
+ /**
+ * Returns the netlink routing message.
+ *
+ * @return netlink routing message
+ */
+ public RtNetlink rtNetlink() {
+ return rtNetlink;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("length", length)
+ .add("type", type)
+ .add("flags", flags)
+ .add("sequence", sequence)
+ .add("processPortId", processPortId)
+ .add("rtNetlink", rtNetlink)
+ .toString();
+ }
+
+ /**
+ * Decodes a netlink header from an input buffer.
+ *
+ * @param buffer input buffer
+ * @param start starting position the netlink header
+ * @param length length of the message
+ * @return netlink header
+ * @throws DeserializationException if a netlink header could not be
+ * decoded from the input buffer
+ */
+ public static Netlink decode(byte[] buffer, int start, int length) throws
+ DeserializationException {
+ checkInput(buffer, start, length, NETLINK_HEADER_LENGTH);
+
+ ByteBuffer bb = ByteBuffer.wrap(buffer, start, length);
+
+ long messageLength = Integer.reverseBytes(bb.getInt());
+ int type = Short.reverseBytes(bb.getShort());
+ int flags = Short.reverseBytes(bb.getShort());
+ long sequence = Integer.reverseBytes(bb.getInt());
+ long processPortId = Integer.reverseBytes(bb.getInt());
+
+ NetlinkMessageType messageType = NetlinkMessageType.get(type);
+ if (messageType == null) {
+ throw new DeserializationException(
+ "Unsupported Netlink message type: " + type);
+ }
+
+ // Netlink messages from Quagga's FPM protocol are always in the
+ // netlink route family (family 0).
+ RtNetlink rtNetlink = RtNetlink.decode(buffer, bb.position(),
+ bb.limit() - bb.position());
+
+ return new Netlink(messageLength,
+ messageType,
+ flags,
+ sequence,
+ processPortId,
+ rtNetlink);
+ }
+
+ /**
+ * Encode the Netlink contents into the ChannelBuffer.
+ *
+ * @param cb channelbuffer to be filled in
+ */
+ public void encode(ChannelBuffer cb) {
+
+ cb.writeInt(Integer.reverseBytes((int) length));
+ cb.writeShort(Short.reverseBytes((short) type.type()));
+ cb.writeShort(Short.reverseBytes((short) flags));
+ cb.writeInt(Integer.reverseBytes((int) sequence));
+ cb.writeInt(Integer.reverseBytes((int) processPortId));
+
+ rtNetlink.encode(cb);
+ }
+
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/NetlinkMessageType.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/NetlinkMessageType.java
new file mode 100644
index 0000000..22e1fbf
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/NetlinkMessageType.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2017-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.routing.fpm.protocol;
+
+/**
+ * Netlink message types.
+ * <p>
+ * This is a subset of the types used for routing messages (rtnelink).
+ * Taken from linux/rtnetlink.h
+ * </p>
+ */
+public enum NetlinkMessageType {
+ RTM_NEWROUTE(24),
+ RTM_DELROUTE(25),
+ RTM_GETROUTE(26);
+
+ private final int type;
+
+ /**
+ * Enum constructor.
+ *
+ * @param type integer type value
+ */
+ NetlinkMessageType(int type) {
+ this.type = type;
+ }
+
+ /**
+ * Returns the integer type value for this message type.
+ *
+ * @return type value
+ */
+ public int type() {
+ return type;
+ }
+
+ /**
+ * Gets the NetlinkMessageType for the given integer type value.
+ *
+ * @param type type value
+ * @return Netlink message type, or null if unsupported type value
+ */
+ public static NetlinkMessageType get(int type) {
+ for (NetlinkMessageType m : NetlinkMessageType.values()) {
+ if (m.type() == type) {
+ return m;
+ }
+ }
+ return null;
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttribute.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttribute.java
new file mode 100644
index 0000000..43a21c0
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttribute.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017-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.routing.fpm.protocol;
+
+import com.google.common.collect.ImmutableMap;
+import org.onlab.packet.DeserializationException;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import java.nio.ByteBuffer;
+import java.util.Map;
+
+import static org.onlab.packet.PacketUtils.checkInput;
+
+/**
+ * Route attribute header.
+ */
+public abstract class RouteAttribute {
+
+ public static final int ROUTE_ATTRIBUTE_HEADER_LENGTH = 4;
+
+ public static final int RTA_DST = 1;
+ public static final int RTA_OIF = 4;
+ public static final int RTA_GATEWAY = 5;
+ public static final int RTA_PRIORITY = 6;
+
+ private final int length;
+ private final int type;
+
+ private static final Map<Integer, RouteAttributeDecoder<?>> TYPE_DECODER_MAP
+ = ImmutableMap.<Integer, RouteAttributeDecoder<?>>builder()
+ .put(RTA_DST, RouteAttributeDst.decoder())
+ .put(RTA_OIF, RouteAttributeOif.decoder())
+ .put(RTA_GATEWAY, RouteAttributeGateway.decoder())
+ .put(RTA_PRIORITY, RouteAttributePriority.decoder())
+ .build();
+
+ /**
+ * Class constructor.
+ *
+ * @param length attribute length
+ * @param type attribute type
+ */
+ protected RouteAttribute(int length, int type) {
+ this.length = length;
+ this.type = type;
+ }
+
+ /**
+ * Returns the attribute length.
+ *
+ * @return length
+ */
+ public int length() {
+ return length;
+ }
+
+ /**
+ * Returns the attribute type.
+ *
+ * @return type
+ */
+ public int type() {
+ return type;
+ }
+
+ @Override
+ public abstract String toString();
+
+ /**
+ * Decodes a route attribute from an input buffer.
+ *
+ * @param buffer input buffer
+ * @param start starting position the route attribute message
+ * @param length length of the message
+ * @return route attribute message
+ * @throws DeserializationException if a route attribute could not be
+ * decoded from the input buffer
+ */
+ public static RouteAttribute decode(byte[] buffer, int start, int length)
+ throws DeserializationException {
+ checkInput(buffer, start, length, ROUTE_ATTRIBUTE_HEADER_LENGTH);
+
+ ByteBuffer bb = ByteBuffer.wrap(buffer, start, length);
+
+ int tlvLength = Short.reverseBytes(bb.getShort());
+ int type = Short.reverseBytes(bb.getShort());
+
+ if (bb.remaining() < tlvLength - ROUTE_ATTRIBUTE_HEADER_LENGTH) {
+ throw new DeserializationException(
+ "Incorrect buffer size when decoding route attribute");
+ }
+
+ byte[] value = new byte[tlvLength - ROUTE_ATTRIBUTE_HEADER_LENGTH];
+ bb.get(value);
+
+ RouteAttributeDecoder<?> decoder = TYPE_DECODER_MAP.get(type);
+ if (decoder == null) {
+ throw new DeserializationException(
+ "No decoder found for route attribute type " + type);
+ }
+
+ return decoder.decodeAttribute(tlvLength, type, value);
+ }
+
+ /**
+ * Encode the RouteAttribute into the ChannelBuffer.
+ *
+ * @param cb channelbuffer to be filled in
+ */
+ public abstract void encode(ChannelBuffer cb);
+
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributeDecoder.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributeDecoder.java
new file mode 100644
index 0000000..961c9a6
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributeDecoder.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2017-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.routing.fpm.protocol;
+
+import org.onlab.packet.DeserializationException;
+
+/**
+ * Decoder for a route attribute.
+ */
+@FunctionalInterface
+public interface RouteAttributeDecoder<A extends RouteAttribute> {
+
+ /**
+ * Decodes the a route attribute from the input buffer.
+ *
+ * @param length length of the attribute
+ * @param type type of the attribute
+ * @param value input buffer
+ * @return route attribute
+ * @throws DeserializationException if a route attribute could not be
+ * decoded from the input buffer
+ */
+ A decodeAttribute(int length, int type, byte[] value)
+ throws DeserializationException;
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributeDst.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributeDst.java
new file mode 100644
index 0000000..1535100
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributeDst.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2017-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.routing.fpm.protocol;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+
+/**
+ * Destination address route attribute.
+ */
+public final class RouteAttributeDst extends RouteAttribute {
+
+ private final IpAddress dstAddress;
+
+ /**
+ * Class constructor.
+ *
+ * @param length length
+ * @param type type
+ * @param dstAddress destination address
+ */
+ public RouteAttributeDst(int length, int type, IpAddress dstAddress) {
+ super(length, type);
+
+ this.dstAddress = dstAddress;
+ }
+
+ /**
+ * Returns the destination IP address.
+ *
+ * @return destination IP address
+ */
+ public IpAddress dstAddress() {
+ return dstAddress;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("type", type())
+ .add("length", length())
+ .add("dstAddress", dstAddress)
+ .toString();
+ }
+
+ /**
+ * Returns a decoder for a destination address route attribute.
+ *
+ * @return destination address route attribute decoder
+ */
+ public static RouteAttributeDecoder<RouteAttributeDst> decoder() {
+ return (int length, int type, byte[] value) -> {
+
+ IpAddress dstAddress;
+ if (value.length == Ip4Address.BYTE_LENGTH) {
+ dstAddress = IpAddress.valueOf(IpAddress.Version.INET, value);
+ } else if (value.length == Ip6Address.BYTE_LENGTH) {
+ dstAddress = IpAddress.valueOf(IpAddress.Version.INET6, value);
+ } else {
+ throw new DeserializationException("Invalid address length");
+ }
+
+ return new RouteAttributeDst(length, type, dstAddress);
+ };
+ }
+
+ /**
+ * Encode the RouteAttributeDst contents into the ChannelBuffer.
+ *
+ * @param cb channelbuffer to be filled in
+ */
+ @Override
+ public void encode(ChannelBuffer cb) {
+
+ cb.writeShort(Short.reverseBytes((short) length()));
+ cb.writeShort(Short.reverseBytes((short) type()));
+
+ ChannelBuffer buffer = ChannelBuffers.copiedBuffer(dstAddress.toOctets());
+ if (length() == Ip6Address.BYTE_LENGTH +
+ RouteAttribute.ROUTE_ATTRIBUTE_HEADER_LENGTH) {
+ cb.writeBytes(buffer, Ip6Address.BYTE_LENGTH);
+ } else if (length() == Ip4Address.BYTE_LENGTH +
+ RouteAttribute.ROUTE_ATTRIBUTE_HEADER_LENGTH) {
+ cb.writeBytes(buffer, Ip4Address.BYTE_LENGTH);
+ } else {
+ throw new RuntimeException("Dst address length incorrect!");
+ }
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributeGateway.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributeGateway.java
new file mode 100644
index 0000000..886f52e
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributeGateway.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2017-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.routing.fpm.protocol;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.Ip6Address;
+import org.onlab.packet.IpAddress;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+
+/**
+ * Gateway route attribute.
+ */
+public final class RouteAttributeGateway extends RouteAttribute {
+
+ public static final int VALUE_LENGTH = 4;
+
+ private final IpAddress gateway;
+
+ /**
+ * Class constructor.
+ *
+ * @param length length
+ * @param type type
+ * @param gateway gateway address
+ */
+ public RouteAttributeGateway(int length, int type, IpAddress gateway) {
+ super(length, type);
+
+ this.gateway = gateway;
+ }
+
+ /**
+ * Returns the gateway address.
+ *
+ * @return gateway address
+ */
+ public IpAddress gateway() {
+ return gateway;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("type", type())
+ .add("length", length())
+ .add("gateway", gateway)
+ .toString();
+ }
+
+ /**
+ * Returns a decoder for a gateway route attribute.
+ *
+ * @return gateway route attribute decoder
+ */
+ public static RouteAttributeDecoder<RouteAttributeGateway> decoder() {
+ return (int length, int type, byte[] value) -> {
+
+ IpAddress gateway;
+ if (value.length == Ip4Address.BYTE_LENGTH) {
+ gateway = IpAddress.valueOf(IpAddress.Version.INET, value);
+ } else if (value.length == Ip6Address.BYTE_LENGTH) {
+ gateway = IpAddress.valueOf(IpAddress.Version.INET6, value);
+ } else {
+ throw new DeserializationException("Invalid address length");
+ }
+
+ return new RouteAttributeGateway(length, type, gateway);
+ };
+ }
+
+ /**
+ * Encode the RouteAttributeGateway contents into the ChannelBuffer.
+ *
+ * @param cb channelbuffer to be filled in
+ */
+ @Override
+ public void encode(ChannelBuffer cb) {
+
+ cb.writeShort(Short.reverseBytes((short) length()));
+ cb.writeShort(Short.reverseBytes((short) type()));
+
+ ChannelBuffer buffer = ChannelBuffers.copiedBuffer(gateway.toOctets());
+ if (length() == Ip6Address.BYTE_LENGTH +
+ RouteAttribute.ROUTE_ATTRIBUTE_HEADER_LENGTH) {
+ cb.writeBytes(buffer, Ip6Address.BYTE_LENGTH);
+ } else if (length() == Ip4Address.BYTE_LENGTH +
+ RouteAttribute.ROUTE_ATTRIBUTE_HEADER_LENGTH) {
+ cb.writeBytes(buffer, Ip4Address.BYTE_LENGTH);
+ } else {
+ throw new RuntimeException("Gateway address length incorrect!");
+ }
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributeOif.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributeOif.java
new file mode 100644
index 0000000..49a12e8
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributeOif.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2017-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.routing.fpm.protocol;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.DeserializationException;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import java.nio.ByteBuffer;
+
+/**
+ * Output interface route attribute.
+ */
+public final class RouteAttributeOif extends RouteAttribute {
+
+ private static final int VALUE_LENGTH = 4;
+
+ private final long outputInterface;
+
+ /**
+ * Class constructor.
+ *
+ * @param length length
+ * @param type type
+ * @param outputInterface output interface
+ */
+ public RouteAttributeOif(int length, int type, long outputInterface) {
+ super(length, type);
+
+ this.outputInterface = outputInterface;
+ }
+
+ /**
+ * Returns the output interface.
+ *
+ * @return output interface
+ */
+ public long outputInterface() {
+ return outputInterface;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("type", type())
+ .add("length", length())
+ .add("outputInterface", outputInterface)
+ .toString();
+ }
+
+ /**
+ * Returns a decoder for a output interface route attribute.
+ *
+ * @return output interface route attribute decoder
+ */
+ public static RouteAttributeDecoder<RouteAttributeOif> decoder() {
+ return (int length, int type, byte[] value) -> {
+ if (value.length != VALUE_LENGTH) {
+ throw new DeserializationException("Wrong value length");
+ }
+
+ long outputInterface = Integer.reverseBytes(ByteBuffer.wrap(value).getInt());
+
+ return new RouteAttributeOif(length, type, outputInterface);
+ };
+ }
+
+ /**
+ * Encode the RouteAttributeOif contents into the ChannelBuffer.
+ *
+ * @param cb channelbuffer to be filled in
+ */
+ @Override
+ public void encode(ChannelBuffer cb) {
+
+ cb.writeShort(Short.reverseBytes((short) length()));
+ cb.writeShort(Short.reverseBytes((short) type()));
+ cb.writeInt(Integer.reverseBytes((int) outputInterface));
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributePriority.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributePriority.java
new file mode 100644
index 0000000..2c45db0
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RouteAttributePriority.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2017-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.routing.fpm.protocol;
+
+import com.google.common.base.MoreObjects;
+import org.onlab.packet.DeserializationException;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import java.nio.ByteBuffer;
+
+/**
+ * Priority route attribute.
+ */
+public final class RouteAttributePriority extends RouteAttribute {
+
+ private static final int VALUE_LENGTH = 4;
+
+ private final long priority;
+
+ /**
+ * Class constructor.
+ *
+ * @param length length
+ * @param type type
+ * @param priority priority
+ */
+ public RouteAttributePriority(int length, int type, long priority) {
+ super(length, type);
+
+ this.priority = priority;
+ }
+
+ /**
+ * Returns the priority.
+ *
+ * @return priority
+ */
+ public long priority() {
+ return priority;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("type", type())
+ .add("length", length())
+ .add("priority", priority)
+ .toString();
+ }
+
+ /**
+ * Returns a decoder for a priority route attribute.
+ *
+ * @return priority route attribute decoder
+ */
+ public static RouteAttributeDecoder<RouteAttributePriority> decoder() {
+ return (int length, int type, byte[] value) -> {
+ if (value.length != VALUE_LENGTH) {
+ throw new DeserializationException("Wrong value length");
+ }
+
+ long priority = Integer.reverseBytes(ByteBuffer.wrap(value).getInt());
+
+ return new RouteAttributePriority(length, type, priority);
+ };
+ }
+
+ /**
+ * Encode the RouteAttributePriority contents into the ChannelBuffer.
+ *
+ * @param cb channelbuffer to be filled in
+ */
+ @Override
+ public void encode(ChannelBuffer cb) {
+
+ cb.writeShort(Short.reverseBytes((short) length()));
+ cb.writeShort(Short.reverseBytes((short) type()));
+ cb.writeInt(Integer.reverseBytes((int) priority));
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RtNetlink.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RtNetlink.java
new file mode 100644
index 0000000..adaaf62
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RtNetlink.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright 2017-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.routing.fpm.protocol;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import org.onlab.packet.DeserializationException;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import org.jboss.netty.buffer.ChannelBuffer;
+
+import static org.onlab.packet.PacketUtils.checkInput;
+
+/**
+ * Netlink routing message (rtnetlink).
+ * <p>
+ * Taken from struct rtmsg in linux/rtnetlink.h
+ * </p>
+ */
+public final class RtNetlink {
+
+ public static final int RT_ADDRESS_FAMILY_INET = 2;
+ public static final int RT_ADDRESS_FAMILY_INET6 = 10;
+ public static final int RT_NETLINK_LENGTH = 12;
+
+ private static final int MASK = 0xff;
+
+ private final short addressFamily;
+ private final int dstLength;
+ private final int srcLength;
+ private final short tos;
+ private final short table;
+ private final RtProtocol protocol;
+ private final short scope;
+ private final short type;
+ private final long flags;
+
+ private final List<RouteAttribute> attributes;
+
+ /**
+ * Class constructor.
+ *
+ * @param addressFamily address family
+ * @param dstLength destination address length
+ * @param srcLength source address length
+ * @param tos type of service
+ * @param table routing table
+ * @param protocol protocol
+ * @param scope scope
+ * @param type type
+ * @param flags flags
+ * @param attributes list of attributes
+ */
+ public RtNetlink(short addressFamily,
+ int dstLength,
+ int srcLength,
+ short tos,
+ short table,
+ RtProtocol protocol,
+ short scope,
+ short type,
+ long flags,
+ List<RouteAttribute> attributes) {
+
+ this.addressFamily = addressFamily;
+ this.dstLength = dstLength;
+ this.srcLength = srcLength;
+ this.tos = tos;
+ this.table = table;
+ this.protocol = protocol;
+ this.scope = scope;
+ this.type = type;
+ this.flags = flags;
+
+ this.attributes = ImmutableList.copyOf(attributes);
+
+ }
+
+ /**
+ * Returns the address family of the route.
+ *
+ * @return address family
+ */
+ public short addressFamily() {
+ return addressFamily;
+ }
+
+ /**
+ * Returns the destination address length.
+ *
+ * @return destination address length
+ */
+ public int dstLength() {
+ return dstLength;
+ }
+
+ /**
+ * Returns the source address length.
+ *
+ * @return source address length
+ */
+ public int srcLength() {
+ return srcLength;
+ }
+
+ /**
+ * Returns the type of service.
+ *
+ * @return type of service
+ */
+ public short tos() {
+ return tos;
+ }
+
+ /**
+ * Returns the routing table.
+ *
+ * @return routing table
+ */
+ public short table() {
+ return table;
+ }
+
+ /**
+ * Returns the protocol.
+ *
+ * @return protocol
+ */
+ public RtProtocol protocol() {
+ return protocol;
+ }
+
+ /**
+ * Returns the route scope.
+ *
+ * @return scope
+ */
+ public short scope() {
+ return scope;
+ }
+
+ /**
+ * Returns the route type.
+ *
+ * @return route type
+ */
+ public short type() {
+ return type;
+ }
+
+ /**
+ * Returns the route flags.
+ *
+ * @return route flags
+ */
+ public long flags() {
+ return flags;
+ }
+
+ /**
+ * Returns the list of route attributes.
+ *
+ * @return route attributes
+ */
+ public List<RouteAttribute> attributes() {
+ return attributes;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(getClass())
+ .add("addressFamily", addressFamily)
+ .add("dstLength", dstLength)
+ .add("srcLength", srcLength)
+ .add("tos", tos)
+ .add("table", table)
+ .add("protocol", protocol)
+ .add("scope", scope)
+ .add("type", type)
+ .add("flags", flags)
+ .add("attributes", attributes)
+ .toString();
+ }
+
+ /**
+ * Decodes an rtnetlink message from an input buffer.
+ *
+ * @param buffer input buffer
+ * @param start starting position the rtnetlink message
+ * @param length length of the message
+ * @return rtnetlink message
+ * @throws DeserializationException if an rtnetlink message could not be
+ * decoded from the input buffer
+ */
+ public static RtNetlink decode(byte[] buffer, int start, int length)
+ throws DeserializationException {
+ checkInput(buffer, start, length, RT_NETLINK_LENGTH);
+
+ ByteBuffer bb = ByteBuffer.wrap(buffer, start, length);
+
+ short addressFamily = (short) (bb.get() & MASK);
+ int dstLength = bb.get() & MASK;
+ int srcLength = bb.get() & MASK;
+ short tos = (short) (bb.get() & MASK);
+ short table = (short) (bb.get() & MASK);
+ short protocol = (short) (bb.get() & MASK);
+ short scope = (short) (bb.get() & MASK);
+ short type = (short) (bb.get() & MASK);
+ long flags = Integer.reverseBytes(bb.getInt());
+ List<RouteAttribute> attributes = new ArrayList<>();
+
+ RtProtocol rtProtocol = RtProtocol.get(protocol);
+
+ while (bb.hasRemaining()) {
+ RouteAttribute attribute = RouteAttribute.decode(buffer, bb.position(),
+ bb.limit() - bb.position());
+ attributes.add(attribute);
+ bb.position(bb.position() + attribute.length());
+ }
+
+ return new RtNetlink(
+ addressFamily,
+ dstLength,
+ srcLength,
+ tos,
+ table,
+ rtProtocol,
+ scope,
+ type,
+ flags,
+ attributes);
+ }
+
+
+ /**
+ * Encode the RtNetlink contents into the ChannelBuffer.
+ *
+ * @param cb channelbuffer to be filled in
+ */
+ public void encode(ChannelBuffer cb) {
+
+ cb.writeByte(addressFamily);
+ cb.writeByte(dstLength);
+ cb.writeByte(srcLength);
+ cb.writeByte(tos);
+ cb.writeByte(table);
+ cb.writeByte(protocol.value());
+ cb.writeByte(scope);
+ cb.writeByte(type);
+ cb.writeInt(Integer.reverseBytes((int) flags));
+
+ for (RouteAttribute attribute : attributes()) {
+ attribute.encode(cb);
+ }
+ }
+
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RtProtocol.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RtProtocol.java
new file mode 100644
index 0000000..4314c14
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/RtProtocol.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2017-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.routing.fpm.protocol;
+
+/**
+ * RtNetlink protocol value.
+ * <p>
+ * This is a subset of the protocol values used in rtnetlink.
+ * Taken from linux/rtnetlink.h
+ * </p>
+ */
+public enum RtProtocol {
+ /**
+ * Unspecified.
+ */
+ UNSPEC((short) 0),
+
+ /**
+ * Route installed by ICMP redirects.
+ */
+ REDIRECT((short) 1),
+
+ /**
+ * Route installed by kernel.
+ */
+ KERNEL((short) 2),
+
+ /**
+ * Route installed during boot.
+ */
+ BOOT((short) 3),
+
+ /**
+ * Route installed by administrator.
+ */
+ STATIC((short) 4),
+
+ /**
+ * GateD.
+ */
+ GATED((short) 8),
+
+ /**
+ * RDISC/ND router advertisements.
+ */
+ RA((short) 9),
+
+ /**
+ * Merit MRT.
+ */
+ MRT((short) 10),
+
+ /**
+ * Zebra.
+ */
+ ZEBRA((short) 11),
+
+ /**
+ * BIRD.
+ */
+ BIRD((short) 12),
+
+ /**
+ * DECnet routing daemon.
+ */
+ DNROUTED((short) 13),
+
+ /**
+ * XORP.
+ */
+ XORP((short) 14),
+
+ /**
+ * Netsukuku.
+ */
+ NTK((short) 15),
+
+ /**
+ * DHCP client.
+ */
+ DHCP((short) 16),
+
+ /**
+ * Multicast daemon.
+ */
+ MROUTED((short) 17),
+
+ /**
+ * Unknown.
+ */
+ UNKNOWN((short) 0);
+
+ private final short value;
+
+ /**
+ * Constructor.
+ *
+ * @param value value
+ */
+ RtProtocol(short value) {
+ this.value = value;
+ }
+
+ /**
+ * Returns the value.
+ *
+ * @return value
+ */
+ public short value() {
+ return value;
+ }
+
+ /**
+ * Gets the RtProtocol for the given integer value.
+ *
+ * @param value value
+ * @return RtProtocol, or null if unsupported type value
+ */
+ public static RtProtocol get(short value) {
+ for (RtProtocol p : RtProtocol.values()) {
+ if (p.value() == value) {
+ return p;
+ }
+ }
+ return UNKNOWN;
+ }
+}
diff --git a/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/package-info.java b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/package-info.java
new file mode 100644
index 0000000..251e193
--- /dev/null
+++ b/apps/routing/fpm/app/src/main/java/org/onosproject/routing/fpm/protocol/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2017-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.
+ */
+
+/**
+ * FPM protocol implementation.
+ */
+package org.onosproject.routing.fpm.protocol;