blob: 364d1b48a90e1f950d3cdf2959ba8f90ac2fb84b [file] [log] [blame]
Jonathan Hartdf207092015-12-10 11:19:25 -08001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
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 */
16
17package org.onosproject.bgprouter;
18
19import com.google.common.collect.ConcurrentHashMultiset;
20import com.google.common.collect.HashMultimap;
21import com.google.common.collect.Maps;
22import com.google.common.collect.Multimap;
23import com.google.common.collect.Multiset;
24import org.apache.felix.scr.annotations.Activate;
25import org.apache.felix.scr.annotations.Component;
26import org.apache.felix.scr.annotations.Deactivate;
27import org.apache.felix.scr.annotations.Reference;
28import org.apache.felix.scr.annotations.ReferenceCardinality;
29import org.onlab.packet.Ethernet;
30import org.onlab.packet.IpAddress;
31import org.onlab.packet.IpPrefix;
32import org.onosproject.core.ApplicationId;
33import org.onosproject.core.CoreService;
34import org.onosproject.incubator.net.intf.Interface;
35import org.onosproject.incubator.net.intf.InterfaceService;
36import org.onosproject.net.DeviceId;
37import org.onosproject.net.config.NetworkConfigService;
38import org.onosproject.net.device.DeviceEvent;
39import org.onosproject.net.device.DeviceListener;
40import org.onosproject.net.device.DeviceService;
41import org.onosproject.net.flow.DefaultTrafficSelector;
42import org.onosproject.net.flow.DefaultTrafficTreatment;
43import org.onosproject.net.flow.TrafficSelector;
44import org.onosproject.net.flow.TrafficTreatment;
45import org.onosproject.net.flow.criteria.Criteria;
46import org.onosproject.net.flowobjective.DefaultFilteringObjective;
47import org.onosproject.net.flowobjective.DefaultForwardingObjective;
48import org.onosproject.net.flowobjective.DefaultNextObjective;
49import org.onosproject.net.flowobjective.FilteringObjective;
50import org.onosproject.net.flowobjective.FlowObjectiveService;
51import org.onosproject.net.flowobjective.ForwardingObjective;
52import org.onosproject.net.flowobjective.NextObjective;
53import org.onosproject.net.flowobjective.Objective;
54import org.onosproject.net.flowobjective.ObjectiveContext;
55import org.onosproject.net.flowobjective.ObjectiveError;
56import org.onosproject.routing.FibEntry;
57import org.onosproject.routing.FibListener;
58import org.onosproject.routing.FibUpdate;
59import org.onosproject.routing.RoutingService;
60import org.onosproject.routing.config.BgpConfig;
61import org.slf4j.Logger;
62import org.slf4j.LoggerFactory;
63
64import java.util.Collection;
65import java.util.HashMap;
66import java.util.Map;
67import java.util.Optional;
68import java.util.Set;
69
70/**
71 * Programs routes to a single OpenFlow switch.
72 */
73@Component(immediate = true)
74public class SingleSwitchRouter {
75
76 private final Logger log = LoggerFactory.getLogger(getClass());
77
78 private static final int PRIORITY_OFFSET = 100;
79 private static final int PRIORITY_MULTIPLIER = 5;
80
81 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
82 protected CoreService coreService;
83
84 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
85 protected RoutingService routingService;
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
88 protected InterfaceService interfaceService;
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
91 protected NetworkConfigService networkConfigService;
92
93 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected FlowObjectiveService flowObjectiveService;
95
96 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
97 protected DeviceService deviceService;
98
99 private InnerDeviceListener deviceListener;
100
101 // Device id of data-plane switch - should be learned from config
102 private DeviceId deviceId;
103
104 private ApplicationId appId;
105
106 // Reference count for how many times a next hop is used by a route
107 private final Multiset<IpAddress> nextHopsCount = ConcurrentHashMultiset.create();
108
109 // Mapping from prefix to its current next hop
110 private final Map<IpPrefix, IpAddress> prefixToNextHop = Maps.newHashMap();
111
112 // Mapping from next hop IP to next hop object containing group info
113 private final Map<IpAddress, Integer> nextHops = Maps.newHashMap();
114
115 // Stores FIB updates that are waiting for groups to be set up
116 private final Multimap<NextHopGroupKey, FibEntry> pendingUpdates = HashMultimap.create();
117
118
119 @Activate
120 protected void activate() {
121 ApplicationId routerAppId = coreService.getAppId(RoutingService.ROUTER_APP_ID);
122 BgpConfig bgpConfig =
123 networkConfigService.getConfig(routerAppId, RoutingService.CONFIG_CLASS);
124
125 if (bgpConfig == null) {
126 log.error("No BgpConfig found");
127 return;
128 }
129
130 getDeviceConfiguration(bgpConfig);
131
132 appId = coreService.getAppId(BgpRouter.BGP_ROUTER_APP);
133
134 deviceListener = new InnerDeviceListener();
135 deviceService.addListener(deviceListener);
136
137 routingService.addFibListener(new InternalFibListener());
138 routingService.start();
139
140 // Initialize devices now if they are already connected
141 if (deviceService.isAvailable(deviceId)) {
142 processIntfFilters(true, interfaceService.getInterfaces());
143 }
144
145 log.info("Started");
146 }
147
148 @Deactivate
149 protected void deactivate() {
150 routingService.stop();
151
152 deviceService.removeListener(deviceListener);
153
154 //processIntfFilters(false, configService.getInterfaces()); //TODO necessary?
155
156 log.info("Stopped");
157 }
158
159 private void getDeviceConfiguration(BgpConfig bgpConfig) {
160 Optional<BgpConfig.BgpSpeakerConfig> bgpSpeaker =
161 bgpConfig.bgpSpeakers().stream().findAny();
162
163 if (!bgpSpeaker.isPresent()) {
164 log.error("BGP speaker configuration not found");
165 return;
166 }
167
168 Optional<IpAddress> peerAddress =
169 bgpSpeaker.get().peers().stream().findAny();
170
171 if (!peerAddress.isPresent()) {
172 log.error("BGP speaker must have peers configured");
173 return;
174 }
175
176 Interface intf = interfaceService.getMatchingInterface(peerAddress.get());
177
178 if (intf == null) {
179 log.error("No interface found for peer");
180 return;
181 }
182
183 // Assume all peers are configured on the same device - this is required
184 // by the BGP router
185 deviceId = intf.connectPoint().deviceId();
186
187 log.info("Router dpid: {}", deviceId);
188 }
189
190 private void updateFibEntry(Collection<FibUpdate> updates) {
191 Map<FibEntry, Integer> toInstall = new HashMap<>(updates.size());
192
193 for (FibUpdate update : updates) {
194 FibEntry entry = update.entry();
195
196 addNextHop(entry);
197
198 Integer nextId;
199 synchronized (pendingUpdates) {
200 nextId = nextHops.get(entry.nextHopIp());
201 }
202
203 toInstall.put(update.entry(), nextId);
204 }
205
206 installFlows(toInstall);
207 }
208
209 private void installFlows(Map<FibEntry, Integer> entriesToInstall) {
210
211 for (Map.Entry<FibEntry, Integer> entry : entriesToInstall.entrySet()) {
212 FibEntry fibEntry = entry.getKey();
213 Integer nextId = entry.getValue();
214
215 flowObjectiveService.forward(deviceId,
216 generateRibForwardingObj(fibEntry.prefix(), nextId).add());
217 log.trace("Sending forwarding objective {} -> nextId:{}", fibEntry, nextId);
218 }
219
220 }
221
222 private synchronized void deleteFibEntry(Collection<FibUpdate> withdraws) {
223
224 for (FibUpdate update : withdraws) {
225 FibEntry entry = update.entry();
226 //Integer nextId = nextHops.get(entry.nextHopIp());
227
228 /* Group group = deleteNextHop(entry.prefix());
229 if (group == null) {
230 log.warn("Group not found when deleting {}", entry);
231 return;
232 }*/
233
234 flowObjectiveService.forward(deviceId,
235 generateRibForwardingObj(entry.prefix(), null).remove());
236
237 }
238
239 }
240
241 private ForwardingObjective.Builder generateRibForwardingObj(IpPrefix prefix,
242 Integer nextId) {
243 TrafficSelector selector = DefaultTrafficSelector.builder()
244 .matchEthType(Ethernet.TYPE_IPV4)
245 .matchIPDst(prefix)
246 .build();
247
248 int priority = prefix.prefixLength() * PRIORITY_MULTIPLIER + PRIORITY_OFFSET;
249
250 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder()
251 .fromApp(appId)
252 .makePermanent()
253 .withSelector(selector)
254 .withPriority(priority)
255 .withFlag(ForwardingObjective.Flag.SPECIFIC);
256
257 if (nextId == null) {
258 // Route withdraws are not specified with next hops. Generating
259 // dummy treatment as there is no equivalent nextId info.
260 fwdBuilder.withTreatment(DefaultTrafficTreatment.builder().build());
261 } else {
262 fwdBuilder.nextStep(nextId);
263 }
264 return fwdBuilder;
265 }
266
267 private synchronized void addNextHop(FibEntry entry) {
268 prefixToNextHop.put(entry.prefix(), entry.nextHopIp());
269 if (nextHopsCount.count(entry.nextHopIp()) == 0) {
270 // There was no next hop in the multiset
271
272 Interface egressIntf = interfaceService.getMatchingInterface(entry.nextHopIp());
273 if (egressIntf == null) {
274 log.warn("no egress interface found for {}", entry);
275 return;
276 }
277
278 NextHopGroupKey groupKey = new NextHopGroupKey(entry.nextHopIp());
279
280 NextHop nextHop = new NextHop(entry.nextHopIp(), entry.nextHopMac(), groupKey);
281
282 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
283 .setEthSrc(egressIntf.mac())
284 .setEthDst(nextHop.mac())
285 .pushVlan()
286 .setVlanId(egressIntf.vlan())
287 .setVlanPcp((byte) 0)
288 .setOutput(egressIntf.connectPoint().port())
289 .build();
290
291 int nextId = flowObjectiveService.allocateNextId();
292
293 NextObjective nextObjective = DefaultNextObjective.builder()
294 .withId(nextId)
295 .addTreatment(treatment)
296 .withType(NextObjective.Type.SIMPLE)
297 .fromApp(appId)
298 .add(); // TODO add callbacks
299
300 flowObjectiveService.next(deviceId, nextObjective);
301
302 nextHops.put(nextHop.ip(), nextId);
303
304 }
305
306 nextHopsCount.add(entry.nextHopIp());
307 }
308
309 /*private synchronized Group deleteNextHop(IpPrefix prefix) {
310 IpAddress nextHopIp = prefixToNextHop.remove(prefix);
311 NextHop nextHop = nextHops.get(nextHopIp);
312 if (nextHop == null) {
313 log.warn("No next hop found when removing prefix {}", prefix);
314 return null;
315 }
316
317 Group group = groupService.getGroup(deviceId,
318 new DefaultGroupKey(appKryo.
319 serialize(nextHop.group())));
320
321 // FIXME disabling group deletes for now until we verify the logic is OK
322 if (nextHopsCount.remove(nextHopIp, 1) <= 1) {
323 // There was one or less next hops, so there are now none
324
325 log.debug("removing group for next hop {}", nextHop);
326
327 nextHops.remove(nextHopIp);
328
329 groupService.removeGroup(deviceId,
330 new DefaultGroupKey(appKryo.build().serialize(nextHop.group())),
331 appId);
332 }
333
334 return group;
335 }*/
336
337 private void processIntfFilters(boolean install, Set<Interface> intfs) {
338 log.info("Processing {} router interfaces", intfs.size());
339 for (Interface intf : intfs) {
340 if (!intf.connectPoint().deviceId().equals(deviceId)) {
341 // Ignore interfaces if they are not on the router switch
342 continue;
343 }
344
345 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
346 fob.withKey(Criteria.matchInPort(intf.connectPoint().port()))
347 .addCondition(Criteria.matchEthDst(intf.mac()))
348 .addCondition(Criteria.matchVlanId(intf.vlan()));
349 intf.ipAddresses().stream()
350 .forEach(ipaddr -> fob.addCondition(
351 Criteria.matchIPDst(
352 IpPrefix.valueOf(ipaddr.ipAddress(), 32))));
353 fob.permit().fromApp(appId);
354 flowObjectiveService.filter(
355 deviceId,
356 fob.add(new ObjectiveContext() {
357 @Override
358 public void onSuccess(Objective objective) {
359 log.info("Successfully installed interface based "
360 + "filtering objectives for intf {}", intf);
361 }
362
363 @Override
364 public void onError(Objective objective,
365 ObjectiveError error) {
366 log.error("Failed to install interface filters for intf {}: {}",
367 intf, error);
368 // TODO something more than just logging
369 }
370 }));
371 }
372 }
373
374 private class InternalFibListener implements FibListener {
375
376 @Override
377 public void update(Collection<FibUpdate> updates,
378 Collection<FibUpdate> withdraws) {
379 SingleSwitchRouter.this.deleteFibEntry(withdraws);
380 SingleSwitchRouter.this.updateFibEntry(updates);
381 }
382 }
383
384
385 // Triggers driver setup when a device is (re)detected.
386 private class InnerDeviceListener implements DeviceListener {
387 @Override
388 public void event(DeviceEvent event) {
389 switch (event.type()) {
390 case DEVICE_ADDED:
391 case DEVICE_AVAILABILITY_CHANGED:
392 if (deviceService.isAvailable(event.subject().id())) {
393 log.info("Device connected {}", event.subject().id());
394 if (event.subject().id().equals(deviceId)) {
395 processIntfFilters(true, interfaceService.getInterfaces());
396 }
397 }
398 break;
399 // TODO other cases
400 case DEVICE_UPDATED:
401 case DEVICE_REMOVED:
402 case DEVICE_SUSPENDED:
403 case PORT_ADDED:
404 case PORT_UPDATED:
405 case PORT_REMOVED:
406 default:
407 break;
408 }
409 }
410 }
411}