blob: d9141f437336fa07056fd881c181d6a873bc99cf [file] [log] [blame]
Dimitrios Mavrommatisf0c06322017-10-31 23:49:04 -07001/*
2 * Copyright 2017-present Open Networking Foundation
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 */
16package org.onosproject.artemis.impl;
17
18import com.fasterxml.jackson.core.JsonProcessingException;
19import com.fasterxml.jackson.databind.ObjectMapper;
20import com.google.common.collect.Maps;
21import com.google.common.collect.Sets;
22import io.netty.buffer.ByteBuf;
23import io.netty.buffer.Unpooled;
24import io.netty.channel.ChannelHandlerContext;
25import io.netty.util.CharsetUtil;
Dimitrios Mavrommatisf0c06322017-10-31 23:49:04 -070026import org.apache.felix.scr.annotations.Activate;
27import org.apache.felix.scr.annotations.Component;
28import org.apache.felix.scr.annotations.Deactivate;
29import org.apache.felix.scr.annotations.Reference;
30import org.apache.felix.scr.annotations.ReferenceCardinality;
31import org.apache.felix.scr.annotations.Service;
32import org.json.JSONObject;
33import org.onlab.packet.IpAddress;
34import org.onlab.packet.IpPrefix;
35import org.onlab.packet.TpPort;
36import org.onosproject.artemis.ArtemisDeaggregator;
37import org.onosproject.artemis.ArtemisEventListener;
38import org.onosproject.artemis.ArtemisMoasAgent;
39import org.onosproject.artemis.ArtemisPacketProcessor;
40import org.onosproject.artemis.ArtemisService;
41import org.onosproject.artemis.BgpSpeakers;
42import org.onosproject.artemis.impl.bgpspeakers.QuaggaBgpSpeakers;
43import org.onosproject.artemis.impl.moas.MoasClientController;
44import org.onosproject.artemis.impl.moas.MoasServerController;
45import org.onosproject.artemis.impl.objects.ArtemisMessage;
46import org.onosproject.core.ApplicationId;
47import org.onosproject.core.CoreService;
48import org.onosproject.net.DeviceId;
49import org.onosproject.net.Port;
50import org.onosproject.net.PortNumber;
51import org.onosproject.net.device.DeviceEvent;
52import org.onosproject.net.device.DeviceListener;
53import org.onosproject.net.device.DeviceService;
54import org.onosproject.net.flow.DefaultTrafficSelector;
55import org.onosproject.net.flow.DefaultTrafficTreatment;
56import org.onosproject.net.flow.FlowRuleService;
57import org.onosproject.net.flow.TrafficSelector;
58import org.onosproject.net.flow.TrafficTreatment;
59import org.onosproject.net.flowobjective.DefaultForwardingObjective;
60import org.onosproject.net.flowobjective.FlowObjectiveService;
61import org.onosproject.net.flowobjective.ForwardingObjective;
62import org.onosproject.net.intf.InterfaceService;
63import org.onosproject.ovsdb.controller.OvsdbBridge;
64import org.onosproject.ovsdb.controller.OvsdbClientService;
65import org.onosproject.ovsdb.controller.OvsdbController;
66import org.onosproject.ovsdb.controller.OvsdbInterface;
67import org.onosproject.routing.bgp.BgpInfoService;
68import org.slf4j.Logger;
69import org.slf4j.LoggerFactory;
70
71import java.util.Map;
72import java.util.Optional;
73import java.util.Set;
74
75import static org.onlab.packet.Ethernet.TYPE_IPV4;
76
77@Component(immediate = true)
78@Service
79public class ArtemisDeaggregatorImpl implements ArtemisDeaggregator {
80
81 private final Logger log = LoggerFactory.getLogger(getClass());
82 private static final int PRIORITY = 1000;
83
84 /* Services */
85 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
86 private BgpInfoService bgpInfoService;
87
88 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89 private ArtemisService artemisService;
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 private OvsdbController ovsdbController;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 private DeviceService deviceService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
98 private InterfaceService interfaceService;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
101 private FlowObjectiveService flowObjectiveService;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
104 private FlowRuleService flowRuleService;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
107 private CoreService coreService;
108
109 /* Variables */
110 private Set<BgpSpeakers> bgpSpeakers = Sets.newHashSet();
111 private MoasServerController moasServer;
112
113 private Port tunnelPort = null;
114 private ApplicationId appId;
115
116 private IpAddress remoteTunnelIp = null;
117 private IpPrefix remotePrefix = null;
118 private boolean rulesInstalled;
119
120 /* Agent */
121 private InternalMoasAgent moasAgent = new InternalMoasAgent();
122 private InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
123 private InternalDeviceListener deviceListener = new InternalDeviceListener();
124
125 private Set<MoasClientController> moasClientControllers = Sets.newConcurrentHashSet();
126
127 private final ArtemisEventListener artemisEventListener = this::handleArtemisEvent;
128
129 @Activate
130 protected void activate() {
131 rulesInstalled = false;
132
133 // FIXME: add other type of BGP Speakers when Dynamic Configuration is available
134 bgpSpeakers.add(new QuaggaBgpSpeakers(bgpInfoService));
135
136 moasServer = new MoasServerController();
137 moasServer.start(moasAgent, packetProcessor);
138
139 deviceService.addListener(deviceListener);
140
141 appId = coreService.getAppId("org.onosproject.artemis");
142
143 // enable OVSDB for the switches that we will install the GRE tunnel
144 artemisService.getConfig().ifPresent(config -> config.moasInfo().getTunnelPoints()
145 .forEach(tunnelPoint -> ovsdbController.connect(tunnelPoint.getOvsdbIp(), TpPort.tpPort(6640)))
146 );
147
148 artemisService.addListener(artemisEventListener);
149
150 log.info("Artemis Deaggregator Service Started");
151
152 /*
153 log.info("interfaces {}", interfaceService.getInterfaces());
154
155 [{
156 "name": "",
157 "connectPoint": "of:000000000000000a/2",
158 "ipAddresses": "[1.1.1.1/30]",
159 "macAddress": "00:00:00:00:00:01"
160 },
161 {
162 "name": "",
163 "connectPoint": "of:000000000000000a/3",
164 "ipAddresses": "[10.0.0.1/8]",
165 "macAddress": "00:00:00:00:00:01"
166 }]
167 */
168 }
169
170 @Deactivate
171 protected void deactivate() {
172 moasServer.stop();
173
174 moasClientControllers.forEach(MoasClientController::stop);
175 moasClientControllers.clear();
176
177 flowRuleService.removeFlowRulesById(appId);
178 deviceService.removeListener(deviceListener);
179
180 remoteTunnelIp = null;
181 remotePrefix = null;
182 tunnelPort = null;
183
184 artemisService.removeListener(artemisEventListener);
185
186 log.info("Artemis Deaggregator Service Stopped");
187 }
188
189 /**
190 * Create a GRE tunnel interface pointing to remote MOAS.
191 *
192 * @param remoteIp remote ip on GRE tunnel
193 */
194 private void createTunnelInterface(IpAddress remoteIp) {
195 ovsdbController.getNodeIds().forEach(nodeId -> artemisService.getConfig().flatMap(config ->
196 config.moasInfo().getTunnelPoints()
197 .stream()
198 .filter(tunnelPoint -> tunnelPoint.getOvsdbIp().toString().equals(nodeId.getIpAddress()))
199 .findFirst()
200 ).ifPresent(tunnelPoint -> {
201 OvsdbClientService ovsdbClient = ovsdbController.getOvsdbClient(nodeId);
202 ovsdbClient.dropInterface("gre-int");
203 Map<String, String> options = Maps.newHashMap();
204 options.put("remote_ip", remoteIp.toString());
205 OvsdbInterface ovsdbInterface = OvsdbInterface.builder()
206 .name("gre-int")
207 .options(options)
208 .type(OvsdbInterface.Type.GRE)
209 .build();
210 OvsdbBridge mainBridge = ovsdbClient.getBridges().iterator().next();
211 ovsdbClient.createInterface(mainBridge.name(), ovsdbInterface);
212 log.info("Tunnel setup at {} - {}", nodeId, tunnelPoint);
213 }));
214 }
215
216 /**
217 * Install rules.
218 */
219 private void installRules() {
220 log.info("Remote Data {} - {} - {}", tunnelPort, remoteTunnelIp, remotePrefix);
221 // FIXME: currently works only for a simple pair of client-server
222 if (!rulesInstalled && tunnelPort != null && remoteTunnelIp != null) {
223 if (remotePrefix != null) {
224 installServerRules();
225 } else {
226 installClientRules();
227 }
228 rulesInstalled = true;
229 }
230 }
231
232 /**
233 * Rules to be installed on MOAS Client.
234 */
235 private void installClientRules() {
236 log.info("installClientRules");
237 artemisService.getConfig().ifPresent(config -> {
238 // selector
239 TrafficSelector selector = DefaultTrafficSelector.builder()
240 .matchEthType(TYPE_IPV4)
241 .matchIPSrc(remoteTunnelIp.toIpPrefix())
242 .matchIPDst(config.moasInfo().getTunnelPoint().getLocalIp().toIpPrefix())
243 .build();
244 // treatment
245 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
246 .setOutput(PortNumber.LOCAL)
247 .build();
248 // forwarding objective builder
249 ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
250 .withSelector(selector)
251 .withTreatment(treatment)
252 .withPriority(PRIORITY)
253 .withFlag(ForwardingObjective.Flag.VERSATILE)
254 .fromApp(appId)
255 .add();
256 // send flow objective to specified switch
257 flowObjectiveService.forward(DeviceId.deviceId(tunnelPort.element().id().toString()),
258 forwardingObjective);
259
260 log.info("Installing flow rule = {}", forwardingObjective);
261 });
262 }
263
264 /**
265 * Rules to be isntalled on MOAS Server.
266 */
267 private void installServerRules() {
268 log.info("installServerRules");
269 artemisService.getConfig().ifPresent(config -> {
270 // selector
271 TrafficSelector selector = DefaultTrafficSelector.builder()
272 .matchEthType(TYPE_IPV4)
273 .matchIPDst(remotePrefix)
274 .build();
275 // treatment
276 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
277 .setOutput(tunnelPort.number())
278 .build();
279 // forwarding objective builder
280 ForwardingObjective forwardingObjective = DefaultForwardingObjective.builder()
281 .withSelector(selector)
282 .withTreatment(treatment)
283 .withPriority(PRIORITY)
284 .withFlag(ForwardingObjective.Flag.VERSATILE)
285 .fromApp(appId)
286 .add();
287 // send flow objective to specified switch
288 flowObjectiveService.forward(DeviceId.deviceId(tunnelPort.element().id().toString()),
289 forwardingObjective);
290
291 log.info("Installing flow rule = {}", forwardingObjective);
292
293 // selector
294 selector = DefaultTrafficSelector.builder()
295 .matchEthType(TYPE_IPV4)
296 .matchIPSrc(config.moasInfo().getTunnelPoint().getLocalIp().toIpPrefix())
297 .matchIPDst(remoteTunnelIp.toIpPrefix())
298 .build();
299 // treatment
300 treatment = DefaultTrafficTreatment.builder()
301 // FIXME: find a better way
302 .setOutput(PortNumber.portNumber(2))
303 .build();
304 // forwarding objective builder
305 forwardingObjective = DefaultForwardingObjective.builder()
306 .withSelector(selector)
307 .withTreatment(treatment)
308 .withPriority(PRIORITY)
309 .withFlag(ForwardingObjective.Flag.VERSATILE)
310 .fromApp(appId)
311 .add();
312 // send flow objective to specified switch
313 flowObjectiveService.forward(DeviceId.deviceId(tunnelPort.element().id().toString()),
314 forwardingObjective);
315
316 log.info("Installing flow rule = {}", forwardingObjective);
317 });
318 }
319
320 /**
321 * Handles a artemis event.
322 *
323 * @param event the artemis event
324 */
325 protected void handleArtemisEvent(ArtemisEvent event) {
326 if (event.type().equals(ArtemisEvent.Type.HIJACK_ADDED)) {
327 IpPrefix receivedPrefix = (IpPrefix) event.subject();
328
329 log.info("Deaggregator received a prefix " + receivedPrefix.toString());
330
331 // can only de-aggregate /23 subnets and higher
332 int cidr = receivedPrefix.prefixLength();
333 if (receivedPrefix.prefixLength() < 24) {
334 byte[] octets = receivedPrefix.address().toOctets();
335 int byteGroup = (cidr + 1) / 8,
336 bitPos = 8 - (cidr + 1) % 8;
337
338 octets[byteGroup] = (byte) (octets[byteGroup] & ~(1 << bitPos));
339 String low = IpPrefix.valueOf(IpAddress.Version.INET, octets, cidr + 1).toString();
340 octets[byteGroup] = (byte) (octets[byteGroup] | (1 << bitPos));
341 String high = IpPrefix.valueOf(IpAddress.Version.INET, octets, cidr + 1).toString();
342
343 String[] prefixes = {low, high};
344 bgpSpeakers.forEach(bgpSpeakers -> bgpSpeakers.announceSubPrefixes(prefixes));
345 } else {
346 log.warn("Initiating MOAS");
347
348 artemisService.getConfig().ifPresent(config -> config.monitoredPrefixes().forEach(artemisPrefixes -> {
349 log.info("checking if {} > {}", artemisPrefixes.prefix(), receivedPrefix);
350 if (artemisPrefixes.prefix().contains(receivedPrefix)) {
351 artemisPrefixes.moas().forEach(moasAddress -> {
352 log.info("Creating a client for {}", moasAddress);
353 MoasClientController client = new MoasClientController(
354 packetProcessor,
355 moasAddress,
356 config.moasInfo().getTunnelPoints().iterator().next()
357 .getLocalIp(),
358 receivedPrefix);
359 log.info("Running client");
360 client.run();
361 moasClientControllers.add(client);
362 }
363 );
364 }
365 }
366 ));
367 }
368
369 }
370 }
371
372 private class InternalPacketProcessor implements ArtemisPacketProcessor {
373 @Override
374 public void processMoasPacket(ArtemisMessage msg, ChannelHandlerContext ctx) {
375 log.info("Received {}", msg);
376 switch (msg.getType()) {
377 case INITIATE_FROM_CLIENT: {
378 artemisService.getConfig().ifPresent(config -> {
379 // SERVER SIDE CODE
380 createTunnelInterface(IpAddress.valueOf(msg.getLocalIp()));
381
382 ArtemisMessage message = new ArtemisMessage();
383 message.setType(ArtemisMessage.Type.INITIATE_FROM_SERVER);
384 message.setLocalIp(
385 config.moasInfo().getTunnelPoints()
386 .iterator()
387 .next()
388 .getLocalIp()
389 .toString());
390
391 ObjectMapper mapper = new ObjectMapper();
392 try {
393 String jsonInString = mapper.writeValueAsString(message);
394 ByteBuf buffer = Unpooled.copiedBuffer(jsonInString, CharsetUtil.UTF_8);
395 ctx.writeAndFlush(buffer);
396 } catch (JsonProcessingException e) {
Ray Milkeyba547f92018-02-01 15:22:31 -0800397 log.warn("processMoasPacket()", e);
Dimitrios Mavrommatisf0c06322017-10-31 23:49:04 -0700398 }
399
400 remoteTunnelIp = IpAddress.valueOf(msg.getLocalIp());
401 remotePrefix = IpPrefix.valueOf(msg.getLocalPrefix());
402 });
403 break;
404 }
405 case INITIATE_FROM_SERVER: {
406 // CLIENT SIDE CODE
407 createTunnelInterface(IpAddress.valueOf(msg.getLocalIp()));
408
409 remoteTunnelIp = IpAddress.valueOf(msg.getLocalIp());
410
411 break;
412 }
413 default:
414 }
415
416 installRules();
417 }
418
419 @Override
420 public void processMonitorPacket(JSONObject msg) {
421
422 }
423 }
424
425 private class InternalMoasAgent implements ArtemisMoasAgent {
426
427 @Override
428 public void addMoas(IpAddress ipAddress, ChannelHandlerContext ctx) {
429 Optional<ArtemisConfig> config = artemisService.getConfig();
430 if (config.isPresent() && config.get().moasInfo().getMoasAddresses().contains(ipAddress)) {
431 log.info("Received Moas request from legit IP address");
432 } else {
433 log.info("Received Moas request from unknown IP address; ignoring..");
434 ctx.close();
435 }
436 }
437
438 @Override
439 public void removeMoas(IpAddress ipAddress) {
440
441 }
442 }
443
444 private class InternalDeviceListener implements DeviceListener {
445
446 /*
447 EVENT
448 DefaultDevice{id=of:000000000000000a, type=SWITCH, manufacturer=Nicira, Inc., hwVersion=Open vSwitch,
449 swVersion=2.8.0, serialNumber=None, driver=ovs}
450 DefaultPort{element=of:000000000000000a, number=5, isEnabled=true, type=COPPER, portSpeed=0, annotations=
451 {portMac=96:13:4c:12:ca:8a, portName=gre-int}}
452 */
453 @Override
454 public void event(DeviceEvent event) {
455 switch (event.type()) {
456 case PORT_UPDATED:
457 case PORT_ADDED: {
458 log.info("event {}", event);
459 // FIXME: currently only one tunnel is supported
460 if (event.port().annotations().keys().contains("portName") &&
461 event.port().annotations().value("portName").equals("gre-int")) {
462 tunnelPort = event.port();
463
464 installRules();
465 }
Ray Milkeyd6a67c32018-02-02 10:30:35 -0800466 break;
Dimitrios Mavrommatisf0c06322017-10-31 23:49:04 -0700467 }
468 default:
469 }
470 }
471 }
472}