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