blob: fee01e8933da7d82ac3452124e635174391ea22c [file] [log] [blame]
Jonathan Hart3930f632015-10-19 12:12:51 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Jonathan Hart3930f632015-10-19 12:12:51 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Jonathan Hartf4bd0482017-01-27 15:11:18 -080016
Jonathan Hart3930f632015-10-19 12:12:51 -070017package org.onosproject.routing.fpm;
18
19import org.apache.felix.scr.annotations.Activate;
20import org.apache.felix.scr.annotations.Component;
21import org.apache.felix.scr.annotations.Deactivate;
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080022import org.apache.felix.scr.annotations.Modified;
23import org.apache.felix.scr.annotations.Property;
24import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
Jonathan Hart3930f632015-10-19 12:12:51 -070026import org.apache.felix.scr.annotations.Service;
27import org.jboss.netty.bootstrap.ServerBootstrap;
28import org.jboss.netty.channel.Channel;
29import org.jboss.netty.channel.ChannelException;
30import org.jboss.netty.channel.ChannelFactory;
31import org.jboss.netty.channel.ChannelPipeline;
32import org.jboss.netty.channel.ChannelPipelineFactory;
33import org.jboss.netty.channel.Channels;
34import org.jboss.netty.channel.group.ChannelGroup;
35import org.jboss.netty.channel.group.DefaultChannelGroup;
36import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
Jonathan Hart72bbf882017-04-14 08:42:51 -070037import org.jboss.netty.handler.timeout.IdleStateHandler;
38import org.jboss.netty.util.HashedWheelTimer;
Jonathan Hart3930f632015-10-19 12:12:51 -070039import org.onlab.packet.IpAddress;
40import org.onlab.packet.IpPrefix;
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070041import org.onlab.util.KryoNamespace;
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080042import org.onlab.util.Tools;
43import org.onosproject.cfg.ComponentConfigService;
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070044import org.onosproject.cluster.ClusterService;
45import org.onosproject.cluster.NodeId;
Jonathan Hartd8b68492017-05-25 15:46:23 -070046import org.onosproject.core.CoreService;
Jonathan Hart1ad75f22016-04-13 21:24:13 -070047import org.onosproject.incubator.net.routing.Route;
48import org.onosproject.incubator.net.routing.RouteAdminService;
Jonathan Hart3930f632015-10-19 12:12:51 -070049import org.onosproject.routing.fpm.protocol.FpmHeader;
50import org.onosproject.routing.fpm.protocol.Netlink;
51import org.onosproject.routing.fpm.protocol.RouteAttribute;
52import org.onosproject.routing.fpm.protocol.RouteAttributeDst;
53import org.onosproject.routing.fpm.protocol.RouteAttributeGateway;
54import org.onosproject.routing.fpm.protocol.RtNetlink;
Jonathan Hart916bf892016-01-27 16:42:55 -080055import org.onosproject.routing.fpm.protocol.RtProtocol;
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070056import org.onosproject.store.serializers.KryoNamespaces;
57import org.onosproject.store.service.ConsistentMap;
58import org.onosproject.store.service.Serializer;
59import org.onosproject.store.service.StorageService;
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080060import org.osgi.service.component.ComponentContext;
Jonathan Hart3930f632015-10-19 12:12:51 -070061import org.slf4j.Logger;
62import org.slf4j.LoggerFactory;
63
64import java.net.InetSocketAddress;
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070065import java.util.Collection;
Jonathan Hartf4b2ca12017-05-17 16:10:16 -070066import java.util.Collections;
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080067import java.util.Dictionary;
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070068import java.util.HashSet;
Jonathan Hart1ad75f22016-04-13 21:24:13 -070069import java.util.LinkedList;
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080070import java.util.List;
Jonathan Hart3930f632015-10-19 12:12:51 -070071import java.util.Map;
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070072import java.util.Set;
Jonathan Hart3930f632015-10-19 12:12:51 -070073import java.util.concurrent.ConcurrentHashMap;
Jonathan Hartf4b2ca12017-05-17 16:10:16 -070074import java.util.stream.Collectors;
Jonathan Hart3930f632015-10-19 12:12:51 -070075
76import static java.util.concurrent.Executors.newCachedThreadPool;
77import static org.onlab.util.Tools.groupedThreads;
78
79/**
80 * Forwarding Plane Manager (FPM) route source.
81 */
82@Service
Jonathan Hartf4bd0482017-01-27 15:11:18 -080083@Component(immediate = true)
Jonathan Hart1ad75f22016-04-13 21:24:13 -070084public class FpmManager implements FpmInfoService {
Jonathan Hart3930f632015-10-19 12:12:51 -070085 private final Logger log = LoggerFactory.getLogger(getClass());
86
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080087 private static final int FPM_PORT = 2620;
Jonathan Hartd8b68492017-05-25 15:46:23 -070088 private static final String APP_NAME = "org.onosproject.fpm";
Jonathan Hart72bbf882017-04-14 08:42:51 -070089 private static final int IDLE_TIMEOUT_SECS = 5;
Jonathan Hartd8b68492017-05-25 15:46:23 -070090
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 protected CoreService coreService;
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080093
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 protected ComponentConfigService componentConfigService;
96
Jonathan Hart1ad75f22016-04-13 21:24:13 -070097 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 protected RouteAdminService routeService;
99
Jonathan Hartdc7e76c2017-03-27 11:35:34 -0700100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 protected ClusterService clusterService;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 protected StorageService storageService;
105
Jonathan Hart3930f632015-10-19 12:12:51 -0700106 private ServerBootstrap serverBootstrap;
107 private Channel serverChannel;
108 private ChannelGroup allChannels = new DefaultChannelGroup();
109
Jonathan Hartdc7e76c2017-03-27 11:35:34 -0700110 private ConsistentMap<FpmPeer, Set<FpmConnectionInfo>> peers;
Jonathan Hart6b045582016-02-03 10:00:08 -0800111
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700112 private Map<FpmPeer, Map<IpPrefix, Route>> fpmRoutes = new ConcurrentHashMap<>();
Jonathan Hart3930f632015-10-19 12:12:51 -0700113
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800114 @Property(name = "clearRoutes", boolValue = true,
115 label = "Whether to clear routes when the FPM connection goes down")
116 private boolean clearRoutes = true;
Jonathan Hart3930f632015-10-19 12:12:51 -0700117
118 @Activate
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800119 protected void activate(ComponentContext context) {
Charles Chane7926852017-02-02 11:41:19 -0800120 componentConfigService.preSetProperty(
121 "org.onosproject.incubator.store.routing.impl.RouteStoreImpl",
122 "distributed", "true");
123
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800124 componentConfigService.registerProperties(getClass());
Jonathan Hartdc7e76c2017-03-27 11:35:34 -0700125
126 KryoNamespace serializer = KryoNamespace.newBuilder()
127 .register(KryoNamespaces.API)
128 .register(FpmPeer.class)
129 .register(FpmConnectionInfo.class)
130 .build();
131 peers = storageService.<FpmPeer, Set<FpmConnectionInfo>>consistentMapBuilder()
132 .withName("fpm-connections")
133 .withSerializer(Serializer.using(serializer))
134 .build();
135
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800136 modified(context);
Jonathan Hart1ad75f22016-04-13 21:24:13 -0700137 startServer();
Jonathan Hartd8b68492017-05-25 15:46:23 -0700138
139 coreService.registerApplication(APP_NAME, peers::destroy);
140
Jonathan Hart3930f632015-10-19 12:12:51 -0700141 log.info("Started");
142 }
143
144 @Deactivate
145 protected void deactivate() {
Charles Chane7926852017-02-02 11:41:19 -0800146 componentConfigService.preSetProperty(
147 "org.onosproject.incubator.store.routing.impl.RouteStoreImpl",
148 "distributed", "false");
149
Jonathan Hart3930f632015-10-19 12:12:51 -0700150 stopServer();
Jonathan Hart1ad75f22016-04-13 21:24:13 -0700151 fpmRoutes.clear();
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800152 componentConfigService.unregisterProperties(getClass(), false);
Jonathan Hart3930f632015-10-19 12:12:51 -0700153 log.info("Stopped");
154 }
155
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800156 @Modified
157 protected void modified(ComponentContext context) {
158 Dictionary<?, ?> properties = context.getProperties();
159 if (properties == null) {
160 return;
161 }
162 String strClearRoutes = Tools.get(properties, "clearRoutes");
163 clearRoutes = Boolean.parseBoolean(strClearRoutes);
164
165 log.info("clearRoutes set to {}", clearRoutes);
166 }
167
Jonathan Hart3930f632015-10-19 12:12:51 -0700168 private void startServer() {
Jonathan Hart72bbf882017-04-14 08:42:51 -0700169 HashedWheelTimer timer = new HashedWheelTimer(
170 groupedThreads("onos/fpm", "fpm-timer-%d", log));
171
Jonathan Hart3930f632015-10-19 12:12:51 -0700172 ChannelFactory channelFactory = new NioServerSocketChannelFactory(
Yuta HIGUCHI1624df12016-07-21 16:54:33 -0700173 newCachedThreadPool(groupedThreads("onos/fpm", "sm-boss-%d", log)),
174 newCachedThreadPool(groupedThreads("onos/fpm", "sm-worker-%d", log)));
Jonathan Hart3930f632015-10-19 12:12:51 -0700175 ChannelPipelineFactory pipelineFactory = () -> {
176 // Allocate a new session per connection
Jonathan Hart72bbf882017-04-14 08:42:51 -0700177 IdleStateHandler idleHandler =
178 new IdleStateHandler(timer, IDLE_TIMEOUT_SECS, 0, 0);
Jonathan Hart3930f632015-10-19 12:12:51 -0700179 FpmSessionHandler fpmSessionHandler =
180 new FpmSessionHandler(new InternalFpmListener());
Jonathan Hart72bbf882017-04-14 08:42:51 -0700181 FpmFrameDecoder fpmFrameDecoder = new FpmFrameDecoder();
Jonathan Hart3930f632015-10-19 12:12:51 -0700182
183 // Setup the processing pipeline
184 ChannelPipeline pipeline = Channels.pipeline();
185 pipeline.addLast("FpmFrameDecoder", fpmFrameDecoder);
Jonathan Hart72bbf882017-04-14 08:42:51 -0700186 pipeline.addLast("idle", idleHandler);
Jonathan Hart3930f632015-10-19 12:12:51 -0700187 pipeline.addLast("FpmSession", fpmSessionHandler);
188 return pipeline;
189 };
190
191 InetSocketAddress listenAddress = new InetSocketAddress(FPM_PORT);
192
193 serverBootstrap = new ServerBootstrap(channelFactory);
194 serverBootstrap.setOption("child.reuseAddr", true);
195 serverBootstrap.setOption("child.keepAlive", true);
196 serverBootstrap.setOption("child.tcpNoDelay", true);
197 serverBootstrap.setPipelineFactory(pipelineFactory);
198 try {
199 serverChannel = serverBootstrap.bind(listenAddress);
200 allChannels.add(serverChannel);
201 } catch (ChannelException e) {
202 log.debug("Exception binding to FPM port {}: ",
203 listenAddress.getPort(), e);
204 stopServer();
205 }
206 }
207
208 private void stopServer() {
209 allChannels.close().awaitUninterruptibly();
210 allChannels.clear();
211 if (serverBootstrap != null) {
212 serverBootstrap.releaseExternalResources();
213 }
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800214
215 if (clearRoutes) {
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700216 peers.keySet().forEach(this::clearRoutes);
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800217 }
Jonathan Hart3930f632015-10-19 12:12:51 -0700218 }
219
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700220 private void fpmMessage(FpmPeer peer, FpmHeader fpmMessage) {
Jonathan Hart72bbf882017-04-14 08:42:51 -0700221 if (fpmMessage.type() == FpmHeader.FPM_TYPE_KEEPALIVE) {
222 return;
223 }
224
Jonathan Hart3930f632015-10-19 12:12:51 -0700225 Netlink netlink = fpmMessage.netlink();
226 RtNetlink rtNetlink = netlink.rtNetlink();
227
228 if (log.isTraceEnabled()) {
229 log.trace("Received FPM message: {}", fpmMessage);
230 }
231
Jonathan Hartb3dad102016-03-01 17:42:26 -0800232 if (!(rtNetlink.protocol() == RtProtocol.ZEBRA ||
233 rtNetlink.protocol() == RtProtocol.UNSPEC)) {
Jonathan Hart916bf892016-01-27 16:42:55 -0800234 log.trace("Ignoring non-zebra route");
235 return;
236 }
237
Jonathan Hart3930f632015-10-19 12:12:51 -0700238 IpAddress dstAddress = null;
239 IpAddress gateway = null;
240
241 for (RouteAttribute attribute : rtNetlink.attributes()) {
242 if (attribute.type() == RouteAttribute.RTA_DST) {
243 RouteAttributeDst raDst = (RouteAttributeDst) attribute;
244 dstAddress = raDst.dstAddress();
245 } else if (attribute.type() == RouteAttribute.RTA_GATEWAY) {
246 RouteAttributeGateway raGateway = (RouteAttributeGateway) attribute;
247 gateway = raGateway.gateway();
248 }
249 }
250
251 if (dstAddress == null) {
252 log.error("Dst address missing!");
253 return;
254 }
255
256 IpPrefix prefix = IpPrefix.valueOf(dstAddress, rtNetlink.dstLength());
257
Jonathan Hart1ad75f22016-04-13 21:24:13 -0700258 List<Route> updates = new LinkedList<>();
259 List<Route> withdraws = new LinkedList<>();
260
261 Route route;
Jonathan Hart3930f632015-10-19 12:12:51 -0700262 switch (netlink.type()) {
263 case RTM_NEWROUTE:
264 if (gateway == null) {
265 // We ignore interface routes with no gateway for now.
266 return;
267 }
Jonathan Hart10dbafd2017-05-18 15:53:03 -0700268 route = new Route(Route.Source.FPM, prefix, gateway, clusterService.getLocalNode().id());
Jonathan Hart3930f632015-10-19 12:12:51 -0700269
Jonathan Hart3930f632015-10-19 12:12:51 -0700270
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700271 Route oldRoute = fpmRoutes.get(peer).put(prefix, route);
272
273 if (oldRoute != null) {
274 log.trace("Swapping {} with {}", oldRoute, route);
275 withdraws.add(oldRoute);
276 }
Jonathan Hart1ad75f22016-04-13 21:24:13 -0700277 updates.add(route);
Jonathan Hart3930f632015-10-19 12:12:51 -0700278 break;
279 case RTM_DELROUTE:
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700280 Route existing = fpmRoutes.get(peer).remove(prefix);
Jonathan Hart3930f632015-10-19 12:12:51 -0700281 if (existing == null) {
282 log.warn("Got delete for non-existent prefix");
283 return;
284 }
285
Jonathan Hart10dbafd2017-05-18 15:53:03 -0700286 route = new Route(Route.Source.FPM, prefix, existing.nextHop(), clusterService.getLocalNode().id());
Jonathan Hart3930f632015-10-19 12:12:51 -0700287
Jonathan Hart1ad75f22016-04-13 21:24:13 -0700288 withdraws.add(route);
Jonathan Hart3930f632015-10-19 12:12:51 -0700289 break;
290 case RTM_GETROUTE:
291 default:
292 break;
293 }
294
Jonathan Hart1ad75f22016-04-13 21:24:13 -0700295 routeService.withdraw(withdraws);
296 routeService.update(updates);
Jonathan Hart3930f632015-10-19 12:12:51 -0700297 }
298
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800299
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700300 private void clearRoutes(FpmPeer peer) {
301 log.info("Clearing all routes for peer {}", peer);
302 Map<IpPrefix, Route> routes = fpmRoutes.remove(peer);
303 if (routes != null) {
304 routeService.withdraw(routes.values());
305 }
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800306 }
307
Jonathan Hartf4b2ca12017-05-17 16:10:16 -0700308 private FpmPeerInfo toFpmInfo(FpmPeer peer, Collection<FpmConnectionInfo> connections) {
309 return new FpmPeerInfo(connections,
310 fpmRoutes.getOrDefault(peer, Collections.emptyMap()).size());
311 }
312
Jonathan Hart6b045582016-02-03 10:00:08 -0800313 @Override
Jonathan Hartf4b2ca12017-05-17 16:10:16 -0700314 public Map<FpmPeer, FpmPeerInfo> peers() {
315 return peers.asJavaMap().entrySet().stream()
316 .collect(Collectors.toMap(
317 e -> e.getKey(),
318 e -> toFpmInfo(e.getKey(), e.getValue())));
Jonathan Hart6b045582016-02-03 10:00:08 -0800319 }
320
321 private class InternalFpmListener implements FpmListener {
Jonathan Hart3930f632015-10-19 12:12:51 -0700322 @Override
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700323 public void fpmMessage(FpmPeer peer, FpmHeader fpmMessage) {
324 FpmManager.this.fpmMessage(peer, fpmMessage);
Jonathan Hart3930f632015-10-19 12:12:51 -0700325 }
Jonathan Hart6b045582016-02-03 10:00:08 -0800326
327 @Override
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700328 public boolean peerConnected(FpmPeer peer) {
329 if (peers.keySet().contains(peer)) {
Jonathan Hart6b045582016-02-03 10:00:08 -0800330 return false;
331 }
332
Jonathan Hartdc7e76c2017-03-27 11:35:34 -0700333 NodeId localNode = clusterService.getLocalNode().id();
334 peers.compute(peer, (p, infos) -> {
335 if (infos == null) {
336 infos = new HashSet<>();
337 }
338
339 infos.add(new FpmConnectionInfo(localNode, peer, System.currentTimeMillis()));
340 return infos;
341 });
342
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700343 fpmRoutes.computeIfAbsent(peer, p -> new ConcurrentHashMap<>());
Jonathan Hart6b045582016-02-03 10:00:08 -0800344 return true;
345 }
346
347 @Override
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700348 public void peerDisconnected(FpmPeer peer) {
349 log.info("FPM connection to {} went down", peer);
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800350
351 if (clearRoutes) {
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700352 clearRoutes(peer);
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800353 }
354
Jonathan Hartdc7e76c2017-03-27 11:35:34 -0700355 peers.compute(peer, (p, infos) -> {
Jonathan Hartd8b68492017-05-25 15:46:23 -0700356 if (infos == null) {
357 return null;
358 }
359
Jonathan Hartdc7e76c2017-03-27 11:35:34 -0700360 infos.stream()
361 .filter(i -> i.connectedTo().equals(clusterService.getLocalNode().id()))
362 .findAny()
363 .ifPresent(i -> infos.remove(i));
364
365 if (infos.isEmpty()) {
366 return null;
367 }
368
369 return infos;
370 });
Jonathan Hart6b045582016-02-03 10:00:08 -0800371 }
Jonathan Hart3930f632015-10-19 12:12:51 -0700372 }
373
374}