blob: 237eccee39acbcb7a51ecb890ca9d3874c8ead18 [file] [log] [blame]
Daniele Moro94660a02019-12-02 12:02:07 -08001/*
2 * Copyright 2019-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 */
16
Daniele Moroad7057d2020-01-15 10:54:37 -080017package org.opencord.bng.impl;
Daniele Moro94660a02019-12-02 12:02:07 -080018
Daniele Moro59903172020-02-20 19:23:11 -080019import com.google.common.cache.CacheBuilder;
20import com.google.common.cache.CacheLoader;
21import com.google.common.cache.LoadingCache;
Daniele Moro94660a02019-12-02 12:02:07 -080022import com.google.common.collect.Maps;
Daniele Moro59903172020-02-20 19:23:11 -080023import org.apache.commons.lang3.tuple.ImmutableTriple;
Daniele Moro94660a02019-12-02 12:02:07 -080024import org.onlab.packet.Data;
25import org.onlab.packet.DeserializationException;
26import org.onlab.packet.Ethernet;
27import org.onlab.packet.IpAddress;
28import org.onlab.packet.MacAddress;
29import org.onlab.packet.VlanId;
30import org.onlab.util.ItemNotFoundException;
Daniele Moro59903172020-02-20 19:23:11 -080031import org.onlab.util.SharedExecutors;
Daniele Moro94660a02019-12-02 12:02:07 -080032import org.onosproject.core.ApplicationId;
33import org.onosproject.core.CoreService;
34import org.onosproject.event.AbstractListenerManager;
35import org.onosproject.net.ConnectPoint;
Daniele Moro59903172020-02-20 19:23:11 -080036import org.onosproject.net.Port;
Daniele Moro94660a02019-12-02 12:02:07 -080037import org.onosproject.net.config.ConfigFactory;
38import org.onosproject.net.config.NetworkConfigEvent;
39import org.onosproject.net.config.NetworkConfigListener;
40import org.onosproject.net.config.NetworkConfigRegistry;
41import org.onosproject.net.device.DeviceService;
42import org.onosproject.net.driver.DriverService;
43import org.onosproject.net.flow.DefaultTrafficTreatment;
44import org.onosproject.net.flow.TrafficTreatment;
45import org.onosproject.net.intf.Interface;
46import org.onosproject.net.intf.InterfaceService;
47import org.onosproject.net.link.LinkService;
48import org.onosproject.net.packet.DefaultOutboundPacket;
49import org.onosproject.net.packet.OutboundPacket;
50import org.onosproject.net.packet.PacketContext;
51import org.onosproject.net.packet.PacketProcessor;
52import org.onosproject.net.packet.PacketService;
Daniele Moroad7057d2020-01-15 10:54:37 -080053import org.opencord.bng.BngAttachment;
54import org.opencord.bng.PppoeBngAttachment;
55import org.opencord.bng.PppoeBngControlHandler;
56import org.opencord.bng.PppoeEvent;
57import org.opencord.bng.PppoeEventListener;
58import org.opencord.bng.PppoeEventSubject;
Daniele Moro94660a02019-12-02 12:02:07 -080059import org.opencord.bng.config.PppoeRelayConfig;
60import org.opencord.bng.packets.GenericPpp;
61import org.opencord.bng.packets.Ipcp;
62import org.opencord.bng.packets.PppProtocolType;
63import org.opencord.bng.packets.Pppoe;
64import org.opencord.sadis.SadisService;
65import org.osgi.service.component.annotations.Activate;
66import org.osgi.service.component.annotations.Component;
67import org.osgi.service.component.annotations.Deactivate;
68import org.osgi.service.component.annotations.Reference;
69import org.osgi.service.component.annotations.ReferenceCardinality;
70import org.slf4j.Logger;
71import org.slf4j.LoggerFactory;
72
73import java.nio.ByteBuffer;
74import java.util.Map;
75import java.util.Objects;
76import java.util.Optional;
77import java.util.Set;
Daniele Moro59903172020-02-20 19:23:11 -080078import java.util.concurrent.ExecutionException;
79import java.util.concurrent.TimeUnit;
Daniele Moro94660a02019-12-02 12:02:07 -080080import java.util.stream.Collectors;
81
82import static org.onosproject.net.config.basics.SubjectFactories.APP_SUBJECT_FACTORY;
83
Daniele Moroad7057d2020-01-15 10:54:37 -080084@Component(immediate = true)
Daniele Moro94660a02019-12-02 12:02:07 -080085public class PppoeHandlerRelay
86 extends AbstractListenerManager<PppoeEvent, PppoeEventListener>
87 implements PppoeBngControlHandler {
88
89 private static final IpAddress IP_ADDRESS_ZERO = IpAddress.valueOf(0);
90
91 private final Logger log = LoggerFactory.getLogger(getClass());
92 private final InternalConfigListener cfgListener = new InternalConfigListener();
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY)
95 protected NetworkConfigRegistry cfgService;
96
97 @Reference(cardinality = ReferenceCardinality.MANDATORY)
98 protected InterfaceService interfaceService;
99
100 @Reference(cardinality = ReferenceCardinality.MANDATORY)
101 protected CoreService coreService;
102
103 @Reference(cardinality = ReferenceCardinality.MANDATORY)
104 protected PacketService packetService;
105
106 @Reference(cardinality = ReferenceCardinality.MANDATORY)
107 protected SadisService sadisService;
108
109 @Reference(cardinality = ReferenceCardinality.MANDATORY)
110 protected DeviceService deviceService;
111
112 @Reference(cardinality = ReferenceCardinality.MANDATORY)
113 protected LinkService linkService;
114
115 @Reference(cardinality = ReferenceCardinality.MANDATORY)
116 protected DriverService driverService;
117
118 private ConfigFactory<ApplicationId, PppoeRelayConfig> cfgFactory = new ConfigFactory<>(
119 APP_SUBJECT_FACTORY,
120 PppoeRelayConfig.class,
121 PppoeRelayConfig.KEY) {
122 @Override
123 public PppoeRelayConfig createConfig() {
124 return new PppoeRelayConfig();
125 }
126 };
127 private ApplicationId appId;
128 private InternalPacketProcessor internalPacketProcessor;
129 private PppoeRelayConfig pppoeRelayConfig;
Daniele Morof8a28ce2019-12-12 09:26:25 -0800130 private MacAddress macPppoeServer;
Daniele Moro94660a02019-12-02 12:02:07 -0800131
132 /**
133 * Ephemeral internal map to trace the attachment information. This map is
134 * mainly used to modify the packet towards the PPPoE server or towards the
Daniele Morod2d56202020-03-02 18:06:59 -0800135 * attachment.
136 * FIXME: this map should be cleaned after some time.
137 * FIXME: consider the case of user that moves around
Daniele Moro94660a02019-12-02 12:02:07 -0800138 */
139 private Map<MacAddress, BngAttachment> mapSrcMacToAttInfo;
140
Daniele Moro59903172020-02-20 19:23:11 -0800141 /**
142 * Cache to cache Sadis results during PPPoE connection establishment.
143 */
144 private final LoadingCache<ImmutableTriple<VlanId, VlanId, ConnectPoint>, ConnectPoint>
145 oltCpCache = CacheBuilder.newBuilder()
146 .expireAfterWrite(30, TimeUnit.SECONDS)
147 .build(new CacheLoader<>() {
148 @Override
149 public ConnectPoint load(ImmutableTriple<VlanId, VlanId, ConnectPoint> key) throws Exception {
150 return getOltConnectPoint(key.left, key.middle, key.right).orElseThrow();
151 }
152 });
153
Daniele Moro94660a02019-12-02 12:02:07 -0800154 @Activate
155 protected void activate() {
Daniele Moro59903172020-02-20 19:23:11 -0800156 mapSrcMacToAttInfo = Maps.newConcurrentMap();
Daniele Moro94660a02019-12-02 12:02:07 -0800157 appId = coreService.getAppId(BngManager.BNG_APP);
158 cfgService.addListener(cfgListener);
159 cfgService.registerConfigFactory(cfgFactory);
160
161 eventDispatcher.addSink(PppoeEvent.class, listenerRegistry);
162
163 updateConfig();
164
165 internalPacketProcessor = new InternalPacketProcessor();
166 packetService.addProcessor(internalPacketProcessor, PacketProcessor.director(0));
167
168 log.info("PPPoE Handler Relay activated");
169 }
170
171 @Deactivate
172 protected void deactivate() {
173 eventDispatcher.removeSink(PppoeEvent.class);
174 packetService.removeProcessor(internalPacketProcessor);
175 cfgService.unregisterConfigFactory(cfgFactory);
Daniele Moro59903172020-02-20 19:23:11 -0800176 oltCpCache.invalidateAll();
Daniele Moro94660a02019-12-02 12:02:07 -0800177 pppoeRelayConfig = null;
178 mapSrcMacToAttInfo = null;
179 internalPacketProcessor = null;
Daniele Morof8a28ce2019-12-12 09:26:25 -0800180 macPppoeServer = null;
Daniele Moro94660a02019-12-02 12:02:07 -0800181
182 log.info("PPPoE Handler Relay deactivated");
183 }
184
185
186 private void updateConfig() {
187 PppoeRelayConfig newPppoeRelayConfig = cfgService.getConfig(appId, PppoeRelayConfig.class);
188 log.info("{}", newPppoeRelayConfig);
189 if (this.pppoeRelayConfig == null &&
190 newPppoeRelayConfig != null &&
191 newPppoeRelayConfig.isValid()) {
192 // TODO: what happens if this is triggered in the middle of a session auth/packet relay?
193 this.pppoeRelayConfig = newPppoeRelayConfig;
194 }
195 }
196
197 private void processPppoePacket(PacketContext context) {
198 if (!isConfigured()) {
199 log.warn("Missing BNG PPPoE handler relay config. Abort packet processing");
200 return;
201 }
202 Ethernet eth = context.inPacket().parsed();
203 log.debug("Parsing the PPPoE header");
204 //FIXME: PPPoE and above headers are extracted from the ethernet
205 // payload. In case we want to modify the PPPoE/upper-layer header, remember to
206 // serialize it back on the Ethernet payload.
207 Pppoe pppoe = parsePppoeHeader(eth);
208 if (pppoe == null) {
209 return;
210 }
211
212 log.debug("Processing PPPoE header");
213
214 // Check from where the packet is received and if the interface is configured
215 ConnectPoint heardOn = context.inPacket().receivedFrom();
216 if (!heardOn.equals(pppoeRelayConfig.getPppoeServerConnectPoint()) &&
217 !heardOn.equals(pppoeRelayConfig.getAsgToOltConnectPoint()) &&
218 !interfaceService.getInterfacesByPort(heardOn).isEmpty()) {
219 log.info("PPPoE packet from unregistered port {}", heardOn);
220 return;
221 }
222
223 // Retrieve the MAC address of the device that intercepted the packet.
224 // This MAC address is the actual PPPoE server MAC address seen by the attachment
225 MacAddress bnguMac = interfaceService.getInterfacesByPort(heardOn).iterator().next().mac();
226
227 VlanId cTag = VlanId.vlanId(eth.getVlanID());
228 VlanId sTag = VlanId.vlanId(eth.getQinQVID());
229
230 // --------------------------------------- DEBUG ----------------------------------------------
231 if (Pppoe.isPPPoED(eth)) {
232 log.info("Received {} packet from {}",
233 pppoe.getPacketType(),
234 heardOn);
235 }
236
237 StringBuilder logPacketPppoes = new StringBuilder();
238 if (Pppoe.isPPPoES(eth)) {
239 logPacketPppoes.append("Received PPPoES ")
240 .append(PppProtocolType.lookup(pppoe.getPppProtocol()).type())
241 .append(" packet from ").append(heardOn).append(".");
242 }
243 if (logPacketPppoes.length() > 0) {
244 log.debug(logPacketPppoes.toString());
245 }
246 log.debug(eth.toString());
247 // --------------------------------------------------------------------------------------------
248
249 if (heardOn.equals(pppoeRelayConfig.getPppoeServerConnectPoint())) {
250 // DOWNSTREAM PACKET: from the PPPoE server to the attachment.
Daniele Morof8a28ce2019-12-12 09:26:25 -0800251
252 // Learn the MAC address of the PPPoE server
253 if (macPppoeServer == null) {
254 macPppoeServer = eth.getSourceMAC();
255 }
256
Daniele Moro94660a02019-12-02 12:02:07 -0800257 MacAddress dstMac = eth.getDestinationMAC();
258 log.debug("Packet to the attachment: {}", eth);
259 if (!mapSrcMacToAttInfo.containsKey(dstMac)) {
260 BngAttachment newAttInfo = PppoeBngAttachment.builder()
261 .withMacAddress(dstMac)
262 .withSTag(sTag)
263 .withCTag(cTag)
264 .withQinqTpid(eth.getQinQTPID())
265 .build();
266 mapSrcMacToAttInfo.put(dstMac, newAttInfo);
267 }
268 // Retrieve the information about the attachment from the internal MAP
269 BngAttachment attInfo = mapSrcMacToAttInfo.get(dstMac);
270
271 // Generate the events for this attachment
272 manageAttachmentStateDownstream(eth, pppoe, attInfo);
Daniele Moro94660a02019-12-02 12:02:07 -0800273 modPacketForAttachment(eth, attInfo, bnguMac);
274
275 log.debug("Packet modified as: {}", eth);
276 // Send out the packet towards the OLT
277 forwardPacket(pppoeRelayConfig.getAsgToOltConnectPoint(), eth);
278 } else {
279 // UPSTREAM DIRECTION: from the attachment to the PPPoE server
280 MacAddress srcMac = eth.getSourceMAC();
281 if (!mapSrcMacToAttInfo.containsKey(srcMac)) {
282 BngAttachment newAttInfo = PppoeBngAttachment.builder()
283 .withMacAddress(srcMac)
284 .withSTag(sTag)
285 .withCTag(cTag)
286 .withQinqTpid(eth.getQinQTPID())
287 .build();
288 mapSrcMacToAttInfo.put(srcMac, newAttInfo);
289 }
290
291 manageAttachmentStateUpstream(eth, pppoe);
292
293 modPacketForPPPoEServer(eth);
294 log.debug("Packet modified as: {}", eth);
295 // Forward packet to the PPPoE server connect point
296 forwardPacket(pppoeRelayConfig.getPppoeServerConnectPoint(), eth);
297 }
298 }
299
300 /**
301 * Generate an event related to PPPoE or IPCP state change.
302 *
303 * @param bngAppEventType Event type
304 * @param ip IP Address if it has been assigned, otherwise
305 * 0.0.0.0
306 * @param attInfo Local attachment information
307 */
308 private void generateEventPppoe(PppoeEvent.EventType bngAppEventType,
309 BngAttachment attInfo, short pppoeSessionId,
310 IpAddress ip) {
311 // Retrive the NNI connect point
Daniele Moro59903172020-02-20 19:23:11 -0800312 ConnectPoint oltConnectPoint;
313 try {
314 oltConnectPoint = oltCpCache.get(ImmutableTriple.of(attInfo.sTag(), attInfo.cTag(),
315 pppoeRelayConfig.getAsgToOltConnectPoint()));
316 } catch (ExecutionException e) {
317 // If unable to retrieve the OLT Connect Point log error and return.
318 // In this way we do NOT propagate the event and eventually create an
319 // inconsistent BNG Attachment.
320 log.error("Unable to retrieve the OLT Connect Point (\"NNI\" Connect Point)", e);
321 return;
322 }
Daniele Moro94660a02019-12-02 12:02:07 -0800323 log.info("Generating event of type {}", bngAppEventType);
324 post(new PppoeEvent(
325 bngAppEventType,
326 new PppoeEventSubject(
Daniele Moro59903172020-02-20 19:23:11 -0800327 oltConnectPoint,
Daniele Moro94660a02019-12-02 12:02:07 -0800328 ip,
329 attInfo.macAddress(),
Daniele Moro59903172020-02-20 19:23:11 -0800330 getPortNameAnnotation(oltConnectPoint),
Daniele Moro94660a02019-12-02 12:02:07 -0800331 pppoeSessionId,
332 attInfo.sTag(),
333 attInfo.cTag())
334 )
335 );
336 }
337
338 /**
339 * Generate attachment related state for the upstream direction.
340 *
341 * @param eth The ethernet packet
342 * @param pppoe PPPoE header
343 */
344 private void manageAttachmentStateUpstream(Ethernet eth, Pppoe pppoe) {
345 PppoeEvent.EventType eventType = null;
346 MacAddress srcMac = eth.getSourceMAC();
347 VlanId cTag = VlanId.vlanId(eth.getVlanID());
348 VlanId sTag = VlanId.vlanId(eth.getQinQVID());
349 BngAttachment attInfo = mapSrcMacToAttInfo.get(srcMac);
350 switch (PppProtocolType.lookup(pppoe.getPppProtocol())) {
351 case IPCP:
352 // Attachment information should be already present
353 Ipcp ipcp = (Ipcp) pppoe.getPayload();
354 if (ipcp.getCode() == Ipcp.CONF_REQ) {
355 log.debug("IPCP configuration request from attachment");
356 eventType = PppoeEvent.EventType.IPCP_CONF_REQUEST;
357 }
358 break;
359 case NO_PROTOCOL:
360 if (Pppoe.isPPPoED(eth) &&
361 pppoe.getPacketType() == Pppoe.PppoeType.PADI) {
362 log.info("PADI received from attachment {}/{}. Saved in internal store",
363 srcMac, sTag);
364 eventType = PppoeEvent.EventType.SESSION_INIT;
365 }
366 break;
367 default:
368 }
369 if (eventType != null) {
370 generateEventPppoe(eventType, attInfo, pppoe.getSessionId(), IP_ADDRESS_ZERO);
371 }
372 }
373
374 private String getPortNameAnnotation(ConnectPoint oltConnectPoint) {
375 return deviceService.getPort(oltConnectPoint.deviceId(),
376 oltConnectPoint.port()).annotations().value("portName");
377 }
378
379 /**
380 * Generate attachment related state for the downstream direction.
381 *
382 * @param eth The ethernet packet
383 * @param pppoe PPPoE header
384 * @param attInfo Attachment info stored in the internal store
385 */
386 private void manageAttachmentStateDownstream(Ethernet eth, Pppoe pppoe,
387 BngAttachment attInfo) {
388 PppoeEvent.EventType eventType = null;
389 IpAddress assignedIpAddress = IP_ADDRESS_ZERO;
390 switch (PppProtocolType.lookup(pppoe.getPppProtocol())) {
391 case IPCP:
392 Ipcp ipcp = (Ipcp) pppoe.getPayload();
393 if (ipcp.getCode() == Ipcp.ACK) {
394 log.info("Received a IPCP ACK from Server. Assigned IP Address {}",
395 ipcp.getIpAddress());
396 assignedIpAddress = ipcp.getIpAddress();
397 eventType = PppoeEvent.EventType.IPCP_CONF_ACK;
398 }
399 break;
400
401 case CHAP:
402 // Check if server has correctly authenticated the attachment
403 GenericPpp chap = (GenericPpp) pppoe.getPayload();
404 if (chap.getCode() == GenericPpp.CHAP_CODE_SUCCESS) {
405 log.info("CHAP authentication success: {}", attInfo.macAddress());
406 eventType = PppoeEvent.EventType.AUTH_SUCCESS;
407 }
408 if (chap.getCode() == GenericPpp.CHAP_CODE_FAILURE) {
409 log.info("CHAP authentication failed: {}", attInfo.macAddress());
410 eventType = PppoeEvent.EventType.AUTH_FAILURE;
411 }
412 break;
413
414 case PAP:
415 // Check if server has correctly authenticated the attachment
416 GenericPpp pap = (GenericPpp) pppoe.getPayload();
417 if (pap.getCode() == GenericPpp.PAP_AUTH_ACK) {
418 log.info("PAP authentication success: {}", attInfo.macAddress());
419 eventType = PppoeEvent.EventType.AUTH_SUCCESS;
420 }
421 if (pap.getCode() == GenericPpp.PAP_AUTH_NACK) {
422 log.info("PAP authentication failed: {}", attInfo.macAddress());
423 eventType = PppoeEvent.EventType.AUTH_FAILURE;
424 }
425 break;
426
427 case LCP:
428 GenericPpp lcp = (GenericPpp) pppoe.getPayload();
429 if (lcp.getCode() == GenericPpp.CODE_TERM_REQ) {
430 log.info("LCP Termination request from PPPoE server");
431 eventType = PppoeEvent.EventType.SESSION_TERMINATION;
432 }
433 break;
434
435 case NO_PROTOCOL:
436 if (Pppoe.isPPPoED(eth)) {
437 switch (pppoe.getPacketType()) {
438 case PADS:
439 // Set the current PPPoE session ID
440 eventType = PppoeEvent.EventType.SESSION_CONFIRMATION;
441 break;
442 case PADT:
443 log.info("PADT received from PPPoE server");
444 eventType = PppoeEvent.EventType.SESSION_TERMINATION;
445 break;
446 default:
447 }
448 }
449 break;
450 default:
451 }
452 // Generate and event if needed
453 if (eventType != null) {
454 generateEventPppoe(eventType, attInfo, pppoe.getSessionId(), assignedIpAddress);
455 }
456 }
457
458 private Pppoe parsePppoeHeader(Ethernet eth) {
459 try {
460 return Pppoe.deserializer().deserialize(((Data) eth.getPayload()).getData(),
461 0,
462 ((Data) eth.getPayload()).getData().length);
463 } catch (DeserializationException e) {
464 log.error("Error parsing the PPPoE Headers, packet skipped. \n" + e.getMessage());
465 return null;
466 }
467 }
468
469
470 /**
471 * Apply the modification to the packet to send it to the attachment.
472 *
473 * @param eth Packet to be modified
474 * @param attInfo Attachment information store in the internal map
475 */
476 private void modPacketForAttachment(Ethernet eth,
477 BngAttachment attInfo,
478 MacAddress newSourceMac) {
479 eth.setVlanID(attInfo.cTag().toShort());
480 eth.setQinQVID(attInfo.sTag().toShort());
481 eth.setQinQTPID(attInfo.qinqTpid());
482 eth.setSourceMACAddress(newSourceMac);
483 }
484
485 /**
486 * Apply the modification to the packet to send it to the PPPoE Server.
487 *
488 * @param eth Packet to be modified
489 */
490 private void modPacketForPPPoEServer(Ethernet eth) {
Daniele Moro94660a02019-12-02 12:02:07 -0800491 Set<Interface> interfaces = interfaceService.getInterfacesByPort(pppoeRelayConfig.getPppoeServerConnectPoint());
492 if (interfaces != null &&
493 interfaces.iterator().hasNext() &&
494 interfaces.iterator().next().vlanTagged() != null &&
495 interfaces.iterator().next().vlanTagged().iterator().hasNext()) {
496 VlanId vlanId = interfaces.iterator().next().vlanTagged().iterator().next();
497 if (vlanId != null && vlanId != VlanId.NONE) {
498 eth.setVlanID(vlanId.toShort());
499 eth.setQinQVID(Ethernet.VLAN_UNTAGGED);
500 } else {
501 eth.setVlanID(Ethernet.VLAN_UNTAGGED);
502 eth.setQinQVID(Ethernet.VLAN_UNTAGGED);
503 }
504 } else {
505 eth.setVlanID(Ethernet.VLAN_UNTAGGED);
506 eth.setQinQVID(Ethernet.VLAN_UNTAGGED);
507 }
508 // Modify DST Mac Address with the one of the PPPoE Server
509 if (!eth.getDestinationMAC().isBroadcast()) {
Daniele Morof8a28ce2019-12-12 09:26:25 -0800510 if (macPppoeServer == null) {
511 log.warn("NO Mac address for PPPoE server available! Dropping packet");
512 return;
513 }
514 eth.setDestinationMACAddress(macPppoeServer);
Daniele Moro94660a02019-12-02 12:02:07 -0800515 }
516 }
517
518 /**
519 * Retrieve the NNI Connect Point given the S-Tag, C-Tag and the OLT facing
520 * ASG connect point.
521 *
522 * @param sTag The S-Tag VLAN tag
523 * @param cTag The C-Tag VLAN tag
524 * @param asgToOltConnectPoint Connect point from ASG to OLT.
525 * @return
526 */
527 private Optional<ConnectPoint> getOltConnectPoint(
528 VlanId sTag, VlanId cTag, ConnectPoint asgToOltConnectPoint) {
529 // Retrieve the UNI port where this attachment is attached to. We assume
530 // an attachment is uniquely identified by its c-tag and s-tag in the
531 // scope of an OLT. In lack of a better API in SADIS, we retrieve info
532 // for all OLT ports and match those that have same c-tag and s-tag as
533 // the given attachemnt info.
Daniele Moro94660a02019-12-02 12:02:07 -0800534 var oltDeviceIds = linkService.getIngressLinks(asgToOltConnectPoint)
535 .stream()
536 .map(link -> link.src().deviceId())
537 .filter(deviceId -> {
538 try {
539 return driverService.getDriver(deviceId)
540 .name().contains("voltha");
541 } catch (ItemNotFoundException e) {
542 log.warn("Unable to find driver for {}", deviceId);
543 return false;
544 }
545 })
546 .collect(Collectors.toSet());
547
548 var oltConnectPoints = oltDeviceIds.stream()
549 .flatMap(deviceId -> deviceService.getPorts(deviceId).stream())
Daniele Moro59903172020-02-20 19:23:11 -0800550 .filter(Port::isEnabled)
Daniele Moro94660a02019-12-02 12:02:07 -0800551 .filter(port -> {
552 var portName = port.annotations().value("portName");
Daniele Moro59903172020-02-20 19:23:11 -0800553 // FIXME: here we support a single UNI per ONU port
554 if (portName == null ||
555 (portName.contains("-") && !portName.endsWith("-1"))) {
Daniele Moro94660a02019-12-02 12:02:07 -0800556 return false;
557 }
558 var info = sadisService.getSubscriberInfoService()
559 .get(portName);
560 return info != null &&
561 Objects.equals(cTag, info.cTag()) &&
562 Objects.equals(sTag, info.sTag());
563 })
564 .map(port -> new ConnectPoint(port.element().id(), port.number()))
565 .collect(Collectors.toSet());
566
567 if (oltConnectPoints.isEmpty()) {
568 log.error("Unable to find a connect point for attachment with S-Tag {} C-Tag {} on OLTs {}",
569 sTag, cTag, oltDeviceIds);
570 return Optional.empty();
571 } else if (oltConnectPoints.size() > 1) {
572 log.error("Multiple OLT connect points found for attachment S-Tag {} C-Tag {}," +
573 "aborting discovery as this is NOT supported (yet)..." +
574 "oltConnectPoints={}",
575 sTag, cTag, oltConnectPoints);
576 return Optional.empty();
577 }
578
579 return Optional.of(oltConnectPoints.iterator().next());
580 }
581
582 /**
583 * Send the specified packet, out to the specified connect point.
584 *
585 * @param toPort Output port to send the packet
586 * @param packet Packet to be sent
587 */
588 private void forwardPacket(ConnectPoint toPort, Ethernet packet) {
589 TrafficTreatment toPortTreatment = DefaultTrafficTreatment.builder()
590 .setOutput(toPort.port()).build();
591 OutboundPacket outboundPacket = new DefaultOutboundPacket(
592 toPort.deviceId(), toPortTreatment, ByteBuffer.wrap(packet.serialize()));
593 packetService.emit(outboundPacket);
594 }
595
596 /**
597 * Check if the handler is correctly configured.
598 *
599 * @return True if it is correctly configure, False otherwise
600 */
601 private boolean isConfigured() {
602 return pppoeRelayConfig != null;
603 }
604
605 /**
606 * The internal packet processor for PPPoE packets.
607 */
608 private class InternalPacketProcessor implements PacketProcessor {
609
610 @Override
611 public void process(PacketContext context) {
612 processPacketInternal(context);
613 }
614
615 private void processPacketInternal(PacketContext context) {
616 if (context == null || context.isHandled()) {
617 return;
618 }
619 Ethernet eth = context.inPacket().parsed();
620 if (eth == null) {
621 return;
622 }
623 if (!Pppoe.isPPPoES(eth) && !Pppoe.isPPPoED(eth)) {
624 return;
625 }
Daniele Moro59903172020-02-20 19:23:11 -0800626 SharedExecutors.getPoolThreadExecutor().submit(() -> {
627 try {
628 processPppoePacket(context);
629 } catch (Throwable e) {
630 log.error("Exception while processing packet", e);
631 }
632 });
Daniele Moro94660a02019-12-02 12:02:07 -0800633 }
634 }
635
636 /**
637 * Listener for network config events.
638 */
639 private class InternalConfigListener implements NetworkConfigListener {
640 @Override
641 public void event(NetworkConfigEvent event) {
642 switch (event.type()) {
643 case CONFIG_ADDED:
644 log.info("CONFIG_ADDED");
645 event.config().ifPresent(config -> {
646 pppoeRelayConfig = ((PppoeRelayConfig) config);
647 log.info("{} added", config.getClass().getSimpleName());
648 });
649 break;
650 // TODO: support at least updated and removed events
651 case CONFIG_UPDATED:
652 case CONFIG_REGISTERED:
653 case CONFIG_UNREGISTERED:
654 case CONFIG_REMOVED:
655 default:
656 log.warn("Unsupported event type {}", event.type());
657 break;
658 }
659 }
660
661 @Override
662 public boolean isRelevant(NetworkConfigEvent event) {
663 if (event.configClass().equals(PppoeRelayConfig.class)) {
664 return true;
665 }
666 log.debug("Ignore irrelevant event class {}", event.configClass().getName());
667 return false;
668 }
669 }
670}