blob: cd1ed7809a26913c76480b8595446884fc92fd6f [file] [log] [blame]
Jonathan Hart3930f632015-10-19 12:12:51 -07001/*
Jonathan Hartf4bd0482017-01-27 15:11:18 -08002 * Copyright 2017-present Open Networking Laboratory
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
Jonathan Hart6b045582016-02-03 10:00:08 -080019import com.google.common.collect.ImmutableMap;
Jonathan Hart3930f632015-10-19 12:12:51 -070020import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080023import org.apache.felix.scr.annotations.Modified;
24import org.apache.felix.scr.annotations.Property;
25import org.apache.felix.scr.annotations.Reference;
26import org.apache.felix.scr.annotations.ReferenceCardinality;
Jonathan Hart3930f632015-10-19 12:12:51 -070027import org.apache.felix.scr.annotations.Service;
28import org.jboss.netty.bootstrap.ServerBootstrap;
29import org.jboss.netty.channel.Channel;
30import org.jboss.netty.channel.ChannelException;
31import org.jboss.netty.channel.ChannelFactory;
32import org.jboss.netty.channel.ChannelPipeline;
33import org.jboss.netty.channel.ChannelPipelineFactory;
34import org.jboss.netty.channel.Channels;
35import org.jboss.netty.channel.group.ChannelGroup;
36import org.jboss.netty.channel.group.DefaultChannelGroup;
37import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
38import org.onlab.packet.IpAddress;
39import org.onlab.packet.IpPrefix;
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070040import org.onlab.util.KryoNamespace;
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080041import org.onlab.util.Tools;
42import org.onosproject.cfg.ComponentConfigService;
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070043import org.onosproject.cluster.ClusterService;
44import org.onosproject.cluster.NodeId;
Jonathan Hartd8b68492017-05-25 15:46:23 -070045import org.onosproject.core.CoreService;
Jonathan Hart1ad75f22016-04-13 21:24:13 -070046import org.onosproject.incubator.net.routing.Route;
47import org.onosproject.incubator.net.routing.RouteAdminService;
Jonathan Hart3930f632015-10-19 12:12:51 -070048import org.onosproject.routing.fpm.protocol.FpmHeader;
49import org.onosproject.routing.fpm.protocol.Netlink;
50import org.onosproject.routing.fpm.protocol.RouteAttribute;
51import org.onosproject.routing.fpm.protocol.RouteAttributeDst;
52import org.onosproject.routing.fpm.protocol.RouteAttributeGateway;
53import org.onosproject.routing.fpm.protocol.RtNetlink;
Jonathan Hart916bf892016-01-27 16:42:55 -080054import org.onosproject.routing.fpm.protocol.RtProtocol;
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070055import org.onosproject.store.serializers.KryoNamespaces;
56import org.onosproject.store.service.ConsistentMap;
57import org.onosproject.store.service.Serializer;
58import org.onosproject.store.service.StorageService;
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080059import org.osgi.service.component.ComponentContext;
Jonathan Hart3930f632015-10-19 12:12:51 -070060import org.slf4j.Logger;
61import org.slf4j.LoggerFactory;
62
63import java.net.InetSocketAddress;
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070064import java.util.Collection;
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080065import java.util.Dictionary;
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070066import java.util.HashSet;
Jonathan Hart1ad75f22016-04-13 21:24:13 -070067import java.util.LinkedList;
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080068import java.util.List;
Jonathan Hart3930f632015-10-19 12:12:51 -070069import java.util.Map;
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070070import java.util.Set;
Jonathan Hart3930f632015-10-19 12:12:51 -070071import java.util.concurrent.ConcurrentHashMap;
72
73import static java.util.concurrent.Executors.newCachedThreadPool;
74import static org.onlab.util.Tools.groupedThreads;
75
76/**
77 * Forwarding Plane Manager (FPM) route source.
78 */
79@Service
Jonathan Hartf4bd0482017-01-27 15:11:18 -080080@Component(immediate = true)
Jonathan Hart1ad75f22016-04-13 21:24:13 -070081public class FpmManager implements FpmInfoService {
Jonathan Hart3930f632015-10-19 12:12:51 -070082 private final Logger log = LoggerFactory.getLogger(getClass());
83
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080084 private static final int FPM_PORT = 2620;
Jonathan Hartd8b68492017-05-25 15:46:23 -070085 private static final String APP_NAME = "org.onosproject.fpm";
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
88 protected CoreService coreService;
Jonathan Hart1af5a7a2016-02-15 08:51:27 -080089
90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
91 protected ComponentConfigService componentConfigService;
92
Jonathan Hart1ad75f22016-04-13 21:24:13 -070093 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected RouteAdminService routeService;
95
Jonathan Hartdc7e76c2017-03-27 11:35:34 -070096 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected ClusterService clusterService;
98
99 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
100 protected StorageService storageService;
101
Jonathan Hart3930f632015-10-19 12:12:51 -0700102 private ServerBootstrap serverBootstrap;
103 private Channel serverChannel;
104 private ChannelGroup allChannels = new DefaultChannelGroup();
105
Jonathan Hartdc7e76c2017-03-27 11:35:34 -0700106 private ConsistentMap<FpmPeer, Set<FpmConnectionInfo>> peers;
Jonathan Hart6b045582016-02-03 10:00:08 -0800107
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700108 private Map<FpmPeer, Map<IpPrefix, Route>> fpmRoutes = new ConcurrentHashMap<>();
Jonathan Hart3930f632015-10-19 12:12:51 -0700109
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800110 @Property(name = "clearRoutes", boolValue = true,
111 label = "Whether to clear routes when the FPM connection goes down")
112 private boolean clearRoutes = true;
Jonathan Hart3930f632015-10-19 12:12:51 -0700113
114 @Activate
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800115 protected void activate(ComponentContext context) {
Charles Chane7926852017-02-02 11:41:19 -0800116 componentConfigService.preSetProperty(
117 "org.onosproject.incubator.store.routing.impl.RouteStoreImpl",
118 "distributed", "true");
119
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800120 componentConfigService.registerProperties(getClass());
Jonathan Hartdc7e76c2017-03-27 11:35:34 -0700121
122 KryoNamespace serializer = KryoNamespace.newBuilder()
123 .register(KryoNamespaces.API)
124 .register(FpmPeer.class)
125 .register(FpmConnectionInfo.class)
126 .build();
127 peers = storageService.<FpmPeer, Set<FpmConnectionInfo>>consistentMapBuilder()
128 .withName("fpm-connections")
129 .withSerializer(Serializer.using(serializer))
130 .build();
131
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800132 modified(context);
Jonathan Hart1ad75f22016-04-13 21:24:13 -0700133 startServer();
Jonathan Hartd8b68492017-05-25 15:46:23 -0700134
135 coreService.registerApplication(APP_NAME, peers::destroy);
136
Jonathan Hart3930f632015-10-19 12:12:51 -0700137 log.info("Started");
138 }
139
140 @Deactivate
141 protected void deactivate() {
Charles Chane7926852017-02-02 11:41:19 -0800142 componentConfigService.preSetProperty(
143 "org.onosproject.incubator.store.routing.impl.RouteStoreImpl",
144 "distributed", "false");
145
Jonathan Hart3930f632015-10-19 12:12:51 -0700146 stopServer();
Jonathan Hart1ad75f22016-04-13 21:24:13 -0700147 fpmRoutes.clear();
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800148 componentConfigService.unregisterProperties(getClass(), false);
Jonathan Hart3930f632015-10-19 12:12:51 -0700149 log.info("Stopped");
150 }
151
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800152 @Modified
153 protected void modified(ComponentContext context) {
154 Dictionary<?, ?> properties = context.getProperties();
155 if (properties == null) {
156 return;
157 }
158 String strClearRoutes = Tools.get(properties, "clearRoutes");
159 clearRoutes = Boolean.parseBoolean(strClearRoutes);
160
161 log.info("clearRoutes set to {}", clearRoutes);
162 }
163
Jonathan Hart3930f632015-10-19 12:12:51 -0700164 private void startServer() {
165 ChannelFactory channelFactory = new NioServerSocketChannelFactory(
Yuta HIGUCHI1624df12016-07-21 16:54:33 -0700166 newCachedThreadPool(groupedThreads("onos/fpm", "sm-boss-%d", log)),
167 newCachedThreadPool(groupedThreads("onos/fpm", "sm-worker-%d", log)));
Jonathan Hart3930f632015-10-19 12:12:51 -0700168 ChannelPipelineFactory pipelineFactory = () -> {
169 // Allocate a new session per connection
170 FpmSessionHandler fpmSessionHandler =
171 new FpmSessionHandler(new InternalFpmListener());
172 FpmFrameDecoder fpmFrameDecoder =
173 new FpmFrameDecoder();
174
175 // Setup the processing pipeline
176 ChannelPipeline pipeline = Channels.pipeline();
177 pipeline.addLast("FpmFrameDecoder", fpmFrameDecoder);
178 pipeline.addLast("FpmSession", fpmSessionHandler);
179 return pipeline;
180 };
181
182 InetSocketAddress listenAddress = new InetSocketAddress(FPM_PORT);
183
184 serverBootstrap = new ServerBootstrap(channelFactory);
185 serverBootstrap.setOption("child.reuseAddr", true);
186 serverBootstrap.setOption("child.keepAlive", true);
187 serverBootstrap.setOption("child.tcpNoDelay", true);
188 serverBootstrap.setPipelineFactory(pipelineFactory);
189 try {
190 serverChannel = serverBootstrap.bind(listenAddress);
191 allChannels.add(serverChannel);
192 } catch (ChannelException e) {
193 log.debug("Exception binding to FPM port {}: ",
194 listenAddress.getPort(), e);
195 stopServer();
196 }
197 }
198
199 private void stopServer() {
200 allChannels.close().awaitUninterruptibly();
201 allChannels.clear();
202 if (serverBootstrap != null) {
203 serverBootstrap.releaseExternalResources();
204 }
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800205
206 if (clearRoutes) {
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700207 peers.keySet().forEach(this::clearRoutes);
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800208 }
Jonathan Hart3930f632015-10-19 12:12:51 -0700209 }
210
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700211 private void fpmMessage(FpmPeer peer, FpmHeader fpmMessage) {
Jonathan Hart3930f632015-10-19 12:12:51 -0700212 Netlink netlink = fpmMessage.netlink();
213 RtNetlink rtNetlink = netlink.rtNetlink();
214
215 if (log.isTraceEnabled()) {
216 log.trace("Received FPM message: {}", fpmMessage);
217 }
218
Jonathan Hartb3dad102016-03-01 17:42:26 -0800219 if (!(rtNetlink.protocol() == RtProtocol.ZEBRA ||
220 rtNetlink.protocol() == RtProtocol.UNSPEC)) {
Jonathan Hart916bf892016-01-27 16:42:55 -0800221 log.trace("Ignoring non-zebra route");
222 return;
223 }
224
Jonathan Hart3930f632015-10-19 12:12:51 -0700225 IpAddress dstAddress = null;
226 IpAddress gateway = null;
227
228 for (RouteAttribute attribute : rtNetlink.attributes()) {
229 if (attribute.type() == RouteAttribute.RTA_DST) {
230 RouteAttributeDst raDst = (RouteAttributeDst) attribute;
231 dstAddress = raDst.dstAddress();
232 } else if (attribute.type() == RouteAttribute.RTA_GATEWAY) {
233 RouteAttributeGateway raGateway = (RouteAttributeGateway) attribute;
234 gateway = raGateway.gateway();
235 }
236 }
237
238 if (dstAddress == null) {
239 log.error("Dst address missing!");
240 return;
241 }
242
243 IpPrefix prefix = IpPrefix.valueOf(dstAddress, rtNetlink.dstLength());
244
Jonathan Hart1ad75f22016-04-13 21:24:13 -0700245 List<Route> updates = new LinkedList<>();
246 List<Route> withdraws = new LinkedList<>();
247
248 Route route;
Jonathan Hart3930f632015-10-19 12:12:51 -0700249 switch (netlink.type()) {
250 case RTM_NEWROUTE:
251 if (gateway == null) {
252 // We ignore interface routes with no gateway for now.
253 return;
254 }
Jonathan Hart10dbafd2017-05-18 15:53:03 -0700255 route = new Route(Route.Source.FPM, prefix, gateway, clusterService.getLocalNode().id());
Jonathan Hart3930f632015-10-19 12:12:51 -0700256
Jonathan Hart3930f632015-10-19 12:12:51 -0700257
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700258 Route oldRoute = fpmRoutes.get(peer).put(prefix, route);
259
260 if (oldRoute != null) {
261 log.trace("Swapping {} with {}", oldRoute, route);
262 withdraws.add(oldRoute);
263 }
Jonathan Hart1ad75f22016-04-13 21:24:13 -0700264 updates.add(route);
Jonathan Hart3930f632015-10-19 12:12:51 -0700265 break;
266 case RTM_DELROUTE:
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700267 Route existing = fpmRoutes.get(peer).remove(prefix);
Jonathan Hart3930f632015-10-19 12:12:51 -0700268 if (existing == null) {
269 log.warn("Got delete for non-existent prefix");
270 return;
271 }
272
Jonathan Hart10dbafd2017-05-18 15:53:03 -0700273 route = new Route(Route.Source.FPM, prefix, existing.nextHop(), clusterService.getLocalNode().id());
Jonathan Hart3930f632015-10-19 12:12:51 -0700274
Jonathan Hart1ad75f22016-04-13 21:24:13 -0700275 withdraws.add(route);
Jonathan Hart3930f632015-10-19 12:12:51 -0700276 break;
277 case RTM_GETROUTE:
278 default:
279 break;
280 }
281
Jonathan Hart1ad75f22016-04-13 21:24:13 -0700282 routeService.withdraw(withdraws);
283 routeService.update(updates);
Jonathan Hart3930f632015-10-19 12:12:51 -0700284 }
285
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800286
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700287 private void clearRoutes(FpmPeer peer) {
288 log.info("Clearing all routes for peer {}", peer);
289 Map<IpPrefix, Route> routes = fpmRoutes.remove(peer);
290 if (routes != null) {
291 routeService.withdraw(routes.values());
292 }
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800293 }
294
Jonathan Hart6b045582016-02-03 10:00:08 -0800295 @Override
Jonathan Hartdc7e76c2017-03-27 11:35:34 -0700296 public Map<FpmPeer, Collection<FpmConnectionInfo>> peers() {
297 return ImmutableMap.<FpmPeer, Collection<FpmConnectionInfo>>builder()
298 .putAll(peers.asJavaMap())
299 .build();
Jonathan Hart6b045582016-02-03 10:00:08 -0800300 }
301
302 private class InternalFpmListener implements FpmListener {
Jonathan Hart3930f632015-10-19 12:12:51 -0700303 @Override
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700304 public void fpmMessage(FpmPeer peer, FpmHeader fpmMessage) {
305 FpmManager.this.fpmMessage(peer, fpmMessage);
Jonathan Hart3930f632015-10-19 12:12:51 -0700306 }
Jonathan Hart6b045582016-02-03 10:00:08 -0800307
308 @Override
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700309 public boolean peerConnected(FpmPeer peer) {
310 if (peers.keySet().contains(peer)) {
Jonathan Hart6b045582016-02-03 10:00:08 -0800311 return false;
312 }
313
Jonathan Hartdc7e76c2017-03-27 11:35:34 -0700314 NodeId localNode = clusterService.getLocalNode().id();
315 peers.compute(peer, (p, infos) -> {
316 if (infos == null) {
317 infos = new HashSet<>();
318 }
319
320 infos.add(new FpmConnectionInfo(localNode, peer, System.currentTimeMillis()));
321 return infos;
322 });
323
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700324 fpmRoutes.computeIfAbsent(peer, p -> new ConcurrentHashMap<>());
Jonathan Hart6b045582016-02-03 10:00:08 -0800325 return true;
326 }
327
328 @Override
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700329 public void peerDisconnected(FpmPeer peer) {
330 log.info("FPM connection to {} went down", peer);
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800331
332 if (clearRoutes) {
Jonathan Hartb10f1e72017-05-02 16:36:26 -0700333 clearRoutes(peer);
Jonathan Hart1af5a7a2016-02-15 08:51:27 -0800334 }
335
Jonathan Hartdc7e76c2017-03-27 11:35:34 -0700336 peers.compute(peer, (p, infos) -> {
Jonathan Hartd8b68492017-05-25 15:46:23 -0700337 if (infos == null) {
338 return null;
339 }
340
Jonathan Hartdc7e76c2017-03-27 11:35:34 -0700341 infos.stream()
342 .filter(i -> i.connectedTo().equals(clusterService.getLocalNode().id()))
343 .findAny()
344 .ifPresent(i -> infos.remove(i));
345
346 if (infos.isEmpty()) {
347 return null;
348 }
349
350 return infos;
351 });
Jonathan Hart6b045582016-02-03 10:00:08 -0800352 }
Jonathan Hart3930f632015-10-19 12:12:51 -0700353 }
354
355}