[SEBA-593] Splitting aaa app in implementation and api bundles
Change-Id: Ib81650be0695258bdd1f4cd6131f2206760e0da6
diff --git a/app/src/main/java/org/opencord/aaa/impl/PortBasedRadiusCommunicator.java b/app/src/main/java/org/opencord/aaa/impl/PortBasedRadiusCommunicator.java
new file mode 100755
index 0000000..e1b8bb9
--- /dev/null
+++ b/app/src/main/java/org/opencord/aaa/impl/PortBasedRadiusCommunicator.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.opencord.aaa.impl;
+
+import org.onlab.packet.ARP;
+import org.onlab.packet.DeserializationException;
+import org.onlab.packet.EthType;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.Ip4Address;
+import org.onlab.packet.IPv4;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.RADIUS;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.UDP;
+
+import org.onosproject.core.ApplicationId;
+import org.onosproject.mastership.MastershipEvent;
+import org.onosproject.mastership.MastershipListener;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketService;
+
+import org.opencord.aaa.AaaConfig;
+import org.opencord.aaa.RadiusCommunicator;
+import org.opencord.sadis.BaseInformationService;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+
+import org.slf4j.Logger;
+
+import com.google.common.collect.Maps;
+
+import static org.onosproject.net.packet.PacketPriority.CONTROL;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Handles communication with the RADIUS server through ports
+ * of the SDN switches.
+ */
+public class PortBasedRadiusCommunicator implements RadiusCommunicator {
+
+ // for verbose output
+ private final Logger log = getLogger(getClass());
+
+ // our unique identifier
+ private ApplicationId appId;
+
+ // to receive Packet-in events that we'll respond to
+ PacketService packetService;
+
+ DeviceService deviceService;
+
+ MastershipService mastershipService;
+
+ BaseInformationService<SubscriberAndDeviceInformation> subsService;
+
+ // to store local mapping of IP Address and Serial No of Device
+ private Map<Ip4Address, String> ipToSnMap;
+
+ // connect points to the RADIUS server
+ Set<ConnectPoint> radiusConnectPoints;
+
+ // Parsed RADIUS server addresses
+ protected InetAddress radiusIpAddress;
+
+ // RADIUS server TCP port number
+ protected short radiusServerPort;
+
+ protected String radiusMacAddress;
+
+ // NAS IP address
+ protected InetAddress nasIpAddress;
+
+ protected String nasMacAddress;
+
+ // RADIUS server Vlan ID
+ private short radiusVlanID;
+
+ // RADIUS p-bit
+ private byte radiusPBit;
+
+ PacketCustomizer pktCustomizer;
+ AaaManager aaaManager;
+
+ ConnectPoint radiusServerConnectPoint = null;
+
+ InnerMastershipListener changeListener = new InnerMastershipListener();
+ InnerDeviceListener deviceListener = new InnerDeviceListener();
+
+ PortBasedRadiusCommunicator(ApplicationId appId, PacketService pktService,
+ MastershipService masService, DeviceService devService,
+ BaseInformationService<SubscriberAndDeviceInformation> subsService,
+ PacketCustomizer pktCustomizer, AaaManager aaaManager) {
+ this.appId = appId;
+ this.packetService = pktService;
+ this.mastershipService = masService;
+ this.deviceService = devService;
+ this.subsService = subsService;
+ this.pktCustomizer = pktCustomizer;
+ this.aaaManager = aaaManager;
+
+ ipToSnMap = Maps.newConcurrentMap();
+ mastershipService.addListener(changeListener);
+ deviceService.addListener(deviceListener);
+
+ log.info("Created PortBased");
+ }
+
+ private void initializeLocalState() {
+ synchronized (this) {
+ radiusServerConnectPoint = null;
+ if (radiusConnectPoints != null) {
+ // find a connect point through a device for which we are master
+ for (ConnectPoint cp: radiusConnectPoints) {
+ if (mastershipService.isLocalMaster(cp.deviceId())) {
+ if (deviceService.isAvailable(cp.deviceId())) {
+ radiusServerConnectPoint = cp;
+ }
+ break;
+ }
+ }
+ }
+
+ log.info("RADIUS connectPoint in initializeLocalState is {}", radiusServerConnectPoint);
+
+ if (radiusServerConnectPoint == null) {
+ log.error("Master of none, can't send radius Message to server");
+ }
+ }
+ }
+
+ @Override
+ public void initializeLocalState(AaaConfig newCfg) {
+ if (newCfg.nasIp() != null) {
+ nasIpAddress = newCfg.nasIp();
+ }
+ if (newCfg.radiusIp() != null) {
+ radiusIpAddress = newCfg.radiusIp();
+ }
+ if (newCfg.radiusMac() != null) {
+ radiusMacAddress = newCfg.radiusMac();
+ }
+ if (newCfg.nasMac() != null) {
+ nasMacAddress = newCfg.nasMac();
+ }
+
+ radiusServerPort = newCfg.radiusServerUdpPort();
+ radiusVlanID = newCfg.radiusServerVlanId();
+ radiusPBit = newCfg.radiusServerPBit();
+
+ radiusConnectPoints = newCfg.radiusServerConnectPoints();
+
+ initializeLocalState();
+ }
+
+ @Override
+ public void clearLocalState() {
+ mastershipService.removeListener(changeListener);
+ deviceService.removeListener(deviceListener);
+ }
+
+ @Override
+ public void deactivate() {
+ mastershipService.removeListener(changeListener);
+ deviceService.removeListener(deviceListener);
+ }
+
+ @Override
+ public void requestIntercepts() {
+ TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_ARP);
+ packetService.requestPackets(selectorArpServer.build(), CONTROL, appId);
+
+ TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_UDP)
+ .matchUdpSrc(TpPort.tpPort(radiusServerPort));
+ packetService.requestPackets(selectorServer.build(), CONTROL, appId);
+
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ selector.matchEthType(EthType.EtherType.EAPOL.ethType().toShort());
+ packetService.requestPackets(selector.build(), CONTROL, appId);
+ }
+
+ @Override
+ public void withdrawIntercepts() {
+ TrafficSelector.Builder selectorArpServer = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_ARP);
+ packetService.cancelPackets(selectorArpServer.build(), CONTROL, appId);
+
+ TrafficSelector.Builder selectorServer = DefaultTrafficSelector.builder()
+ .matchEthType(Ethernet.TYPE_IPV4)
+ .matchIPProtocol(IPv4.PROTOCOL_UDP)
+ .matchUdpSrc(TpPort.tpPort(radiusServerPort));
+ packetService.cancelPackets(selectorServer.build(), CONTROL, appId);
+
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
+ selector.matchEthType(EthType.EtherType.EAPOL.ethType().toShort());
+ packetService.cancelPackets(selector.build(), CONTROL, appId);
+ }
+
+ @Override
+ public void sendRadiusPacket(RADIUS radiusPacket, InboundPacket inPkt) {
+ // create the packet
+ Ethernet ethReply = new Ethernet();
+ ethReply.setSourceMACAddress(nasMacAddress);
+ ethReply.setDestinationMACAddress(radiusMacAddress);
+ ethReply.setEtherType(Ethernet.TYPE_IPV4);
+ ethReply.setVlanID(radiusVlanID);
+ ethReply.setPriorityCode(radiusPBit);
+
+ IPv4 ipv4Packet = new IPv4();
+ ipv4Packet.setTtl((byte) 64);
+ ipv4Packet.setSourceAddress(Ip4Address.
+ valueOf(nasIpAddress).toInt());
+ ipv4Packet.setDestinationAddress(Ip4Address.
+ valueOf(radiusIpAddress).toInt());
+
+ UDP udpPacket = new UDP();
+ udpPacket.setSourcePort(radiusServerPort);
+ udpPacket.setDestinationPort(radiusServerPort);
+
+ udpPacket.setPayload(radiusPacket);
+ ipv4Packet.setPayload(udpPacket);
+ ethReply.setPayload(ipv4Packet);
+
+ // store the IP address and SN of the device, later to be used
+ // for ARP responses
+ String serialNo = deviceService.getDevice(inPkt.
+ receivedFrom().deviceId()).serialNumber();
+
+ SubscriberAndDeviceInformation deviceInfo = subsService.get(serialNo);
+
+ if (deviceInfo == null) {
+ log.warn("No Device found with SN {}", serialNo);
+ return;
+ }
+ ipToSnMap.put(deviceInfo.ipAddress(), serialNo);
+
+ // send the message out
+ sendFromRadiusServerPort(pktCustomizer.
+ customizeEthernetIPHeaders(ethReply, inPkt));
+ }
+
+ /**
+ * Sends packet to the RADIUS server using one of the switch ports.
+ *
+ * @param packet Ethernet packet to be sent
+ */
+ private void sendFromRadiusServerPort(Ethernet packet) {
+ if (radiusServerConnectPoint != null) {
+ log.trace("AAA Manager sending Ethernet packet = {}", packet);
+ TrafficTreatment t = DefaultTrafficTreatment.builder()
+ .setOutput(radiusServerConnectPoint.port()).build();
+ OutboundPacket o = new DefaultOutboundPacket(
+ radiusServerConnectPoint.deviceId(), t, ByteBuffer.wrap(packet.serialize()));
+ packetService.emit(o);
+ } else {
+ log.error("Unable to send RADIUS packet, connectPoint is null");
+ }
+ }
+
+ @Override
+ public void handlePacketFromServer(PacketContext context) {
+ // Extract the original Ethernet frame from the packet information
+ InboundPacket pkt = context.inPacket();
+ Ethernet ethPkt = pkt.parsed();
+ if (ethPkt == null) {
+ return;
+ }
+
+ // identify if incoming packet
+ switch (EthType.EtherType.lookup(ethPkt.getEtherType())) {
+ case ARP:
+ handleArpPacketFromServer(context);
+ break;
+ case IPV4:
+ handleIPv4PacketFromServer(context);
+ break;
+ default:
+ log.debug("Skipping Ethernet packet type {}",
+ EthType.EtherType.lookup(ethPkt.getEtherType()));
+ }
+ }
+
+ /**
+ * Handles ARP packets from RADIUS server.
+ *
+ * @param context Context for the packet
+ */
+ private void handleArpPacketFromServer(PacketContext context) {
+ // Extract the original Ethernet frame from the packet information
+ InboundPacket pkt = context.inPacket();
+ Ethernet ethPkt = pkt.parsed();
+ if (ethPkt == null) {
+ return;
+ }
+
+ ARP arpPacket = (ARP) ethPkt.getPayload();
+
+ Ip4Address targetAddress = Ip4Address.valueOf(arpPacket.
+ getTargetProtocolAddress());
+
+ String serialNo = ipToSnMap.get(targetAddress);
+ if (serialNo == null) {
+ log.info("No mapping found for ARP reply, target address {}",
+ targetAddress);
+ return;
+ }
+ MacAddress senderMac = subsService.get(serialNo).hardwareIdentifier();
+ if (senderMac == null) {
+ log.warn("ARP resolution, MAC address not found for SN {}", serialNo);
+ return;
+ }
+
+ ARP arpReply = (ARP) arpPacket.clone();
+ arpReply.setOpCode(ARP.OP_REPLY);
+ arpReply.setTargetProtocolAddress(arpPacket.getSenderProtocolAddress());
+ arpReply.setTargetHardwareAddress(arpPacket.getSenderHardwareAddress());
+ arpReply.setSenderProtocolAddress(arpPacket.getTargetProtocolAddress());
+ arpReply.setSenderHardwareAddress(senderMac.toBytes());
+
+ log.debug("AAA Manager: Query for ARP of IP : {}", arpPacket.getTargetProtocolAddress());
+
+ // Ethernet Frame.
+ Ethernet ethReply = new Ethernet();
+ ethReply.setSourceMACAddress(senderMac);
+ ethReply.setDestinationMACAddress(ethPkt.getSourceMAC());
+ ethReply.setEtherType(Ethernet.TYPE_ARP);
+ ethReply.setVlanID(radiusVlanID);
+ ethReply.setPriorityCode(ethPkt.getPriorityCode());
+
+ ethReply.setPayload(arpReply);
+ sendFromRadiusServerPort(ethReply);
+ }
+
+ /**
+ * Handles IP packets from RADIUS server.
+ *
+ * @param context Context for the packet
+ */
+ private void handleIPv4PacketFromServer(PacketContext context) {
+ // Extract the original Ethernet frame from the packet information
+ InboundPacket pkt = context.inPacket();
+ Ethernet ethPkt = pkt.parsed();
+ if (ethPkt == null) {
+ return;
+ }
+
+ IPv4 ipv4Packet = (IPv4) ethPkt.getPayload();
+
+ if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
+ UDP udpPacket = (UDP) ipv4Packet.getPayload();
+
+ if (udpPacket.getSourcePort() == radiusServerPort) {
+ //This packet is RADIUS packet from the server.
+ RADIUS radiusMsg;
+ try {
+ radiusMsg =
+ RADIUS.deserializer()
+ .deserialize(udpPacket.serialize(),
+ 8,
+ udpPacket.getLength() - 8);
+ try {
+ aaaManager.handleRadiusPacket(radiusMsg);
+ } catch (StateMachineException sme) {
+ log.error("Illegal state machine operation", sme);
+ }
+ } catch (DeserializationException dex) {
+ log.error("Cannot deserialize packet", dex);
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles Mastership changes for the devices which connect
+ * to the RADIUS server.
+ */
+ private class InnerMastershipListener implements MastershipListener {
+ @Override
+ public void event(MastershipEvent event) {
+ if (radiusServerConnectPoint != null &&
+ radiusServerConnectPoint.deviceId().
+ equals(event.subject())) {
+ log.trace("Mastership Event recevived for {}", event.subject());
+ // mastership of the device for our connect point has changed
+ // reselect
+ initializeLocalState();
+ }
+ }
+ }
+
+ /**
+ * Handles Device status change for the devices which connect
+ * to the RADIUS server.
+ */
+ private class InnerDeviceListener implements DeviceListener {
+ @Override
+ public void event(DeviceEvent event) {
+ log.trace("Device Event recevived for {} event {}", event.subject(), event.type());
+ if (radiusServerConnectPoint == null) {
+ switch (event.type()) {
+ case DEVICE_ADDED:
+ case DEVICE_AVAILABILITY_CHANGED:
+ // some device is available check if we can get one
+ initializeLocalState();
+ break;
+ default:
+ break;
+ }
+ return;
+ }
+ if (radiusServerConnectPoint.deviceId().
+ equals(event.subject().id())) {
+ switch (event.type()) {
+ case DEVICE_AVAILABILITY_CHANGED:
+ case DEVICE_REMOVED:
+ case DEVICE_SUSPENDED:
+ // state of our device has changed, check if we need
+ // to re-select
+ initializeLocalState();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+}